Translate a network config from one vendor to another — and get a list of everything that didn’t.
Netcanon parses one vendor’s running-config, renders another’s, and declares every field it translates as supported, lossy, or unsupported — with the reason.
Cisco IOS-XE · Cisco NX-OS · Cisco IOS-XR · Juniper Junos · Arista EOS · Aruba AOS-S · Aruba AOS-CX · FortiGate · MikroTik RouterOS · OPNsense · VyOS
Handling something sensitive? Don’t paste it. Run the container yourself — it is one command, and it is the same image the demo runs.
INPUT (cisco_iosxe_cli)
hostname access-sw-01
!
vlan 10
name DATA
!
vlan 20
name VOICE
!
interface GigabitEthernet1/0/1
description Server-A
switchport mode access
switchport access vlan 10
no shutdown
!
interface GigabitEthernet1/0/2
description Desk-Phone
switchport mode access
switchport access vlan 20
no shutdown
!
interface GigabitEthernet1/0/24
description Uplink-to-core
switchport mode trunk
switchport trunk allowed vlan 10,20
!
snmp-server community public RO
ip name-server 192.168.1.10
ip name-server 192.168.1.11
ntp server 192.168.1.20
!
ip access-list extended MGMT-PROTECT
permit tcp 192.168.99.0 0.0.0.255 any eq 22
deny ip any any log
!
ip nat inside source list MGMT-PROTECT interface GigabitEthernet1/0/24 overload
!
ip route 0.0.0.0 0.0.0.0 192.168.1.1
OUTPUT (juniper_junos)
set system host-name access-sw-01
set system name-server 192.168.1.10
set system name-server 192.168.1.11
set system ntp server 192.168.1.20
set interfaces ge-1/0/1 description "Server-A"
set interfaces ge-1/0/1 unit 0 family ethernet-switching interface-mode access
set interfaces ge-1/0/1 unit 0 family ethernet-switching vlan members DATA
set interfaces ge-1/0/2 description "Desk-Phone"
set interfaces ge-1/0/2 unit 0 family ethernet-switching interface-mode access
set interfaces ge-1/0/2 unit 0 family ethernet-switching vlan members VOICE
set interfaces ge-1/0/24 description "Uplink-to-core"
set interfaces ge-1/0/24 unit 0 family ethernet-switching interface-mode trunk
set interfaces ge-1/0/24 unit 0 family ethernet-switching vlan members DATA
set interfaces ge-1/0/24 unit 0 family ethernet-switching vlan members VOICE
set vlans DATA vlan-id 10
set vlans VOICE vlan-id 20
set routing-options static route 0.0.0.0/0 next-hop 192.168.1.1
set snmp community public authorization read-only
This pane is the unedited output of:
docker run --rm --entrypoint netcanon ghcr.io/netcanon/netcanon:latest demo --pair cisco__junos
Installed via pip instead? netcanon demo --pair cisco__junos.
Image is amd64/x86-64 only today.
INPUT (fortigate_cli)
config system global
set hostname "branch-fw-01"
end
config system dns
set primary 192.168.1.10
set secondary 192.168.1.11
end
config system interface
edit "internal1"
set ip 192.168.10.1 255.255.255.0
set allowaccess ping
next
edit "internal2"
set ip 192.168.20.1 255.255.255.0
set allowaccess ping
next
end
config system dhcp server
edit 1
set interface "internal1"
set default-gateway 192.168.10.1
set netmask 255.255.255.0
config ip-range
edit 1
set start-ip 192.168.10.100
set end-ip 192.168.10.200
next
end
next
end
config firewall policy
edit 1
set name "lan-to-wan"
set srcintf "internal1"
set dstintf "wan1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set nat enable
next
end
OUTPUT (mikrotik_routeros)
/system identity
set name=branch-fw-01
/interface ethernet
set [ find default-name=ether1 ] disabled=no
set [ find default-name=ether2 ] disabled=no
/ip address
add address=192.168.10.1/24 interface=ether1
add address=192.168.20.1/24 interface=ether2
/ip pool
add name=dhcp_pool1 ranges=192.168.10.100-192.168.10.200
/ip dhcp-server network
add address=192.168.10.0/24 gateway=192.168.10.1
/system dns
set servers=192.168.1.10,192.168.1.11
Advisories: port ‘internal1’ / ‘internal2’ are hardware-switch (L2 fabric) members; the mapping to ‘ether1’ / ‘ether2’ is positional (name-shape only) and does NOT carry the L2-switch membership — verify the target port’s physical cabling and L2 role.
This pane is the unedited output of:
docker run --rm --entrypoint netcanon ghcr.io/netcanon/netcanon:latest demo --pair fortigate__mikrotik
Image is amd64/x86-64 only today.
More pairs — Aruba AOS-S → Arista, OPNsense → Junos: walkthroughs →
Every visitor gets a real, isolated instance — and when they run out, the demo refuses new sessions rather than quietly sharing one. The command above is the no-wait path.
What translates. What refuses. On purpose.
Translates: hostnames, interfaces (names, addresses, VRF bindings, MTU),
VLANs and L2 membership, static routes, DNS/NTP, SNMP, LAGs, local users, DHCP pools,
VRFs — the boring 80% of a migration. Cross-vendor interface naming included:
GigabitEthernet1/0/1 becomes ge-1/0/1.
Detects and lists, deliberately does not translate: firewall rules, NAT, VPN, QoS, route-maps, dynamic routing stanzas. These are vendor-specific stateful policy that does not translate cleanly across vendors — so netcanon surfaces them by name in a banner instead of pretending.
Netcanon is the right tool for the router portion of a migration — and explicitly the wrong tool to claim it does the firewall portion.
Netcanon is built and maintained by one person. The audit machinery below exists because you shouldn’t have to take one person’s word for fidelity.
What works for your vendor: Cisco IOS-XE · NX-OS · IOS-XR · Junos · Arista EOS · Aruba AOS-S · Aruba AOS-CX · FortiGate · MikroTik · OPNsense · VyOS
Every field has a declared fate
An excerpt from the capability matrix — the fortigate_cli panel, verbatim:
| Canonical path | Class | Reason |
|---|---|---|
…/vrrp-groups/group | supported | Nested-edit form: config system interface / edit X / config vrrp / edit N / set vrip Y … |
…/interface/config/description | lossy | FortiOS limits the interface alias to 25 characters; longer descriptions from other vendors are truncated. |
…/vrrp-groups/group/virtual-ips | lossy | FortiOS config vrrp / edit N accepts a single set vrip per group. Multi-IP canonical
groups (IOS-XE repeated vrrp N ip X, Junos virtual-address [ X Y Z ]) emit the first VIP
and drop the tail with a # review: line — operator must split into multiple groups manually. |
…/filter/rule | unsupported | config firewall policy is Tier 3 — session-based, zone-aware, UTM-enabled semantics
don’t translate cleanly. |
…/nat/rule | unsupported | FortiGate NAT lives inside firewall policy and address / VIP objects — not auto-translatable. |
Round-trip validated against real configs. The output is a draft for your review, not a deploy artifact.
The full matrix is in the docs
— and queryable from the running app: GET /api/v1/migration/adapters/{name}/capabilities
Because you are being asked to paste a network config into someone else’s website
- Your session is one throwaway container — destroyed when you close the tab or after 15 minutes, whichever comes first.
- No network egress at all — the container holding your config cannot reach the internet.
- Nothing written to disk — read-only root, tmpfs-only writes, swap off, core dumps discarded.
None of that is a promise you have to take on trust: the demo whitepaper states each claim, and VERIFY gives you the commands to check them yourself.
Demo bundle demo-v0.1.3 · deployed 2026-07-28.
Your session is a throwaway container. Your config is never stored. Sensitive? Don’t paste it — run it locally instead.
Run it on your machine
docker run --rm -p 8000:8000 -e NETCANON_ALLOW_INSECURE_BIND=1 \
ghcr.io/netcanon/netcanon:latest
then open http://localhost:8000. The image refuses an unauthenticated non-loopback
bind by default; the flag acknowledges you’re running it locally. For anything reachable by
other hosts, set NETCANON_API_KEY instead.
Image is amd64/x86-64 only today.
Prefer pip? pip install netcanon — same app, same CLI. Windows? There’s an MSI on
Releases.
No telemetry, no phone-home, no update checks — on any install path (pip, Docker, MSI). The only network connections netcanon makes are the SSH sessions you configure for backups.
The audit underneath
Every covered vendor pair is audited cell-by-cell for silent translation loss — the kind that produces output that looks valid but quietly drops a field. The methodology is published. So is the list of cells currently open: every residual is enumerated and triaged, nothing swept under the rug.
The honest follow-up: the audit only covers cells we have fixtures for. Real-world configs reach paths synthetic fixtures haven’t. That’s where you come in — see Contribute.
Also in the box
Config backup over SSH. Netcanon pulls running-configs on a schedule and stores them verbatim — its backup half exists to feed translation. If backup and diff is your only need, Oxidized and RANCID are purpose-built and more mature on device-driver breadth; we say so ourselves.
Config sanitizer. Sharing a config in a ticket or a forum post? Sanitize it first:
netcanon sanitize -i my-config.txt --source-vendor cisco_iosxe_cli --dry-run
Every substitution is shown for your review.
Where it fits
Most adjacent tools occupy a different slot — netcanon’s niche is bidirectional translation between vendors’ native running-config formats. Batfish analyses what you translated; NAPALM deploys it. Complement, not alternative.
| Tool | What it does | Translates native config? |
|---|---|---|
| Netcanon | Multi-vendor config translation (parse + render) | Yes — bidirectional |
| Batfish | Config analysis + routing simulation | No — parse-only; complements netcanon |
| Capirca / Aerleon | Firewall ACL DSL → vendor syntax | No — render-only from a DSL; competes (firewall scope) |
| NAPALM / Netmiko | Device get/set + SSH transport | No — no translation; complements (deploy side) |
| Oxidized / RANCID | Multi-vendor config backup + diff | No — backup-only; overlaps netcanon’s backup half |
Cisco IOS-XE → Junos · FortiGate → MikroTik · Aruba AOS-S → Arista EOS · OPNsense → Junos — each walkthrough ends in a manual-review checklist.
Drive it from your stack
Run netcanon as a container beside your stack; drive it over the versioned HTTP API or the CLI. It is an app with an API, not an embeddable library — process isolation keeps its dependency tree out of yours.
curl -s -H "Authorization: Bearer $NETCANON_API_KEY" \
-H "Content-Type: application/json" \
-X POST http://localhost:8000/api/v1/migration/plan \
-d '{"source":"cisco_iosxe_cli","target":"juniper_junos","raw_text":"..."}'
The response carries the rendered config and the declared losses in the same payload:
{
"status": "completed",
"validation": { "severity": "ok", "lossy_paths": [], "unsupported_paths": [] },
"rendered": "set system host-name access-sw-01\n...",
"port_renames": { "GigabitEthernet1/0/1": "ge-1/0/1" },
"dropped_tier3_sections": [ "ip access-list extended MGMT-PROTECT" ]
}
Response excerpt — bookkeeping fields elided; the full shape is in the
running app’s own /docs (OpenAPI).
v0.x promise: /api/v1 request/response shapes don’t break; changes ship as new
endpoints; the CHANGELOG
names every break.
Found a config it gets wrong?
That’s the contribution this project values most. Sanitize it first — the sanitizer shows every substitution for your review — then submit it as a fixture.