Static routes work until the network changes. Add a router and every neighbor needs a new route. Lose a link and traffic keeps heading for a dead next hop until someone notices. A routing protocol replaces that bookkeeping: routers tell each other what they can reach, work out the best paths on their own, and recompute them when something fails.
IS-IS is one of the interior gateway protocols (IGPs) that do this, alongside OSPF. It is a link-state protocol, and it is widely used inside service provider and large data center networks. Every router describes its own links and the prefixes it owns, floods that description to the others, and ends up with the same map of the network as everyone else. Each router then computes shortest paths from that map.
This post shows IS-IS working on a four-router lab you can deploy yourself: adjacencies forming, the link-state database, shortest-path selection, and what happens when a link fails.
How IS-IS works
Identity. Each router has a NET (network entity title). This is the one from r1 in the lab:
49.0001.0000.0000.0001.00
│ │ │ │
│ │ │ └─ N-selector, always 00 on a router
│ │ └──────────────── System ID: unique per router
│ └───────────────────── Rest of the area ID (the area is 49.0001)
└──────────────────────── AFI (49 is the private range)
Levels. Routers in the same area form Level-1 adjacencies. Level-2 adjacencies connect areas. The lab uses a single area, so everything is Level-1.
Adjacencies. Routers send hello packets on every IS-IS interface. When two routers hear each other, they form an adjacency. IS-IS packets are carried directly in layer 2 frames, not inside IP packets, so an adjacency doesn’t depend on IP configuration. On a link between exactly two routers you set the network type to point-to-point. Without it, Ethernet links default to broadcast mode, which elects a designated intermediate system (DIS) that a two-router link doesn’t need.
The database. Once adjacent, routers exchange link-state packets (LSPs). Each router originates one LSP describing its neighbors and the prefixes it advertises. All the LSPs together form the link-state database, and every router in the area holds the same one.
Shortest paths. Each router runs the SPF algorithm (Dijkstra) over the database, using the metric of every link as its cost, and installs the winning routes.
One IS-IS instance carries both IPv4 and IPv6 routes, and the lab uses both.
The lab
Four routers in a square, running IPv4 and IPv6 side by side. The link between r1 and r4 has a higher metric (50) than the others (10), which will matter later.
r1 ───────────────────── r2
│ │
│ (metric 50) │
│ │
r4 ───────────────────── r3
| Node | Loopback (IPv4) | Loopback (IPv6) | NET |
|---|---|---|---|
| r1 | 10.255.0.1/32 | 2001:db8:100::1/128 | 49.0001.0000.0000.0001.00 |
| r2 | 10.255.0.2/32 | 2001:db8:100::2/128 | 49.0001.0000.0000.0002.00 |
| r3 | 10.255.0.3/32 | 2001:db8:100::3/128 | 49.0001.0000.0000.0003.00 |
| r4 | 10.255.0.4/32 | 2001:db8:100::4/128 | 49.0001.0000.0000.0004.00 |
| Node | Interface | IPv4 address | IPv6 address | Connected to |
|---|---|---|---|---|
| r1 | eth1 | 10.0.12.1/24 | 2001:db8:12::1/64 | r2 |
| r1 | eth2 | 10.0.41.2/24 | 2001:db8:41::2/64 | r4 (metric 50) |
| r2 | eth1 | 10.0.12.2/24 | 2001:db8:12::2/64 | r1 |
| r2 | eth2 | 10.0.23.1/24 | 2001:db8:23::1/64 | r3 |
| r3 | eth1 | 10.0.23.2/24 | 2001:db8:23::2/64 | r2 |
| r3 | eth2 | 10.0.34.1/24 | 2001:db8:34::1/64 | r4 |
| r4 | eth1 | 10.0.34.2/24 | 2001:db8:34::2/64 | r3 |
| r4 | eth2 | 10.0.41.1/24 | 2001:db8:41::1/64 | r1 (metric 50) |
The routers run FRR as containers, deployed with containerlab.
Get the files from the lab repository and cd into isis-basics.
The configuration
This is r1’s frr.conf:
frr defaults traditional
hostname r1
!
interface lo
ip address 10.255.0.1/32
ipv6 address 2001:db8:100::1/128
ip router isis LAB
ipv6 router isis LAB
isis passive
exit
!
interface eth1
ip address 10.0.12.1/24
ipv6 address 2001:db8:12::1/64
ip router isis LAB
ipv6 router isis LAB
isis network point-to-point
exit
!
interface eth2
ip address 10.0.41.2/24
ipv6 address 2001:db8:41::2/64
ip router isis LAB
ipv6 router isis LAB
isis network point-to-point
isis metric 50
exit
!
router isis LAB
is-type level-1
net 49.0001.0000.0000.0001.00
log-adjacency-changes
lsp-gen-interval 1
exit
router isis LABcreates the IS-IS instance.is-type level-1andnetset the level and the router’s identity.ip router isis LABandipv6 router isis LABon an interface enable IS-IS there, one line per address family: the router sends hellos and advertises the interface’s IPv4 and IPv6 prefixes.isis passiveon the loopback advertises its address without sending hellos, since no neighbor lives behind a loopback.isis network point-to-pointsets the network type discussed above.isis metric 50raises the cost of the r1–r4 link for both address families. The metric applies to traffic leaving the interface, so r4’s end of the link has the same line.lsp-gen-interval 1lets a router regenerate its own LSP once per second. FRR throttles this by default: without the line, a freshly deployed lab took about 31 seconds to converge in my runs, and with it, about 4. The line is only there to make the lab quick to start.
FRR also needs the IS-IS daemon enabled. The lab’s daemons file sets isisd=yes.
Deploy
sudo containerlab --runtime podman deploy -t topology.clab.yml
Give it a few seconds to converge, then look at r1’s neighbors:
sudo podman exec clab-isis-r1 vtysh -c "show isis neighbor"
Area LAB:
System Id Interface L State Holdtime SNPA
r2 eth1 1 Up 27 2020.2020.2020
r4 eth2 1 Up 28 2020.2020.2020
r1 has two adjacencies, both Level-1 and Up. The System Id column shows hostnames because IS-IS routers advertise theirs. The holdtime counts down from 30 seconds and resets with each hello. The interface detail shows the timers:
sudo podman exec clab-isis-r1 vtysh -c "show isis interface detail"
Interface: eth2, State: Up, Active, Circuit Id: 0x0
Type: p2p, Level: L1
Level-1 Information:
Metric: 50, Active neighbors: 1
Hello interval: 3, Holddown count: 10, Padding: yes
CNSP interval: 10, PSNP interval: 2
IP Prefix(es):
10.0.41.2/24
IPv6 Link-Locals:
fe80::a8c1:abff:fe2f:ae82/64
IPv6 Prefixes:
2001:db8:41::2/64
Hellos go out every 3 seconds, and a neighbor is declared dead after 10 missed hellos, so 30 seconds. The type is p2p and the metric is the 50 we configured. The output continues with the other interfaces, and the loopback shows Passive.
The link-state database
sudo podman exec clab-isis-r1 vtysh -c "show isis database"
Area LAB:
IS-IS Level-1 link-state database:
LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL
r1.00-00 * 163 0x00000005 0x778a 1165 0/0/0
r2.00-00 163 0x00000004 0x0b9b 1146 0/0/0
r3.00-00 163 0x00000006 0x045f 1165 0/0/0
r4.00-00 163 0x00000004 0x15a2 1162 0/0/0
4 LSPs
One LSP per router. The asterisk marks r1’s own. The sequence number increases every time a router regenerates its LSP, and the checksum identifies the content. I ran the same command on all four routers and got the same LSP IDs and checksums on each, which is what “the same map of the network” means in practice.
To see what an LSP contains:
sudo podman exec clab-isis-r1 vtysh -c "show isis database detail r2.00-00"
r2.00-00 163 0x00000004 0x0b9b 1145 0/0/0
Protocols Supported: IPv4, IPv6
Area Address: 49.0001
Hostname: r2
Extended Reachability: 0000.0000.0001.00 (Metric: 10)
Extended Reachability: 0000.0000.0003.00 (Metric: 10)
Extended IP Reachability: 10.0.12.0/24 (Metric: 10)
Extended IP Reachability: 10.0.23.0/24 (Metric: 10)
Extended IP Reachability: 10.255.0.2/32 (Metric: 10)
IPv6 Reachability: 2001:db8:12::/64 (Metric: 10)
IPv6 Reachability: 2001:db8:23::/64 (Metric: 10)
IPv6 Reachability: 2001:db8:100::2/128 (Metric: 10)
(I trimmed the router ID and capability lines.) This is r2 describing itself: it supports IPv4 and IPv6, it is adjacent to the routers with system IDs …0001 (r1) and …0003 (r3) at metric 10 each, and it owns three IPv4 and three IPv6 prefixes. That is all any other router needs from r2 to include it in its map, for both address families.
Routes
SPF turns the database into routes, separately for each address family:
sudo podman exec clab-isis-r1 vtysh -c "show ip route isis"
sudo podman exec clab-isis-r1 vtysh -c "show ipv6 route isis"
IPv4 unicast VRF default:
I 10.0.12.0/24 [115/20] via 10.0.12.2, eth1 inactive, weight 1, 00:00:07
I>* 10.0.23.0/24 [115/20] via 10.0.12.2, eth1, weight 1, 00:00:07
I>* 10.0.34.0/24 [115/30] via 10.0.12.2, eth1, weight 1, 00:00:07
I 10.0.41.0/24 [115/80] via 10.0.12.2, eth1, weight 1, 00:00:06
I>* 10.255.0.2/32 [115/20] via 10.0.12.2, eth1, weight 1, 00:00:07
I>* 10.255.0.3/32 [115/30] via 10.0.12.2, eth1, weight 1, 00:00:07
I>* 10.255.0.4/32 [115/40] via 10.0.12.2, eth1, weight 1, 00:00:06
IPv6 unicast VRF default:
I>* 2001:db8:23::/64 [115/20] via fe80::a8c1:abff:fec0:3847, eth1, weight 1, 00:00:05
I>* 2001:db8:34::/64 [115/30] via fe80::a8c1:abff:fec0:3847, eth1, weight 1, 00:00:05
I>* 2001:db8:100::2/128 [115/20] via fe80::a8c1:abff:fec0:3847, eth1, weight 1, 00:00:05
I>* 2001:db8:100::3/128 [115/30] via fe80::a8c1:abff:fec0:3847, eth1, weight 1, 00:00:05
I>* 2001:db8:100::4/128 [115/40] via fe80::a8c1:abff:fec0:3847, eth1, weight 1, 00:00:05
(I left out FRR’s route-code legend.) I marks IS-IS routes, 115 is IS-IS’s administrative distance, and the second number is the total metric. >* means the route was selected and installed. The two IPv4 lines without it are subnets r1 is directly attached to, 10.0.12.0/24 and 10.0.41.0/24. A neighbor advertises them too, but the connected route has a lower distance and wins, so the IS-IS copy isn’t used.
The IPv4 next hops are the neighbor’s address on the link. The IPv6 next hops are link-local addresses, because the adjacencies are formed over link-local addresses.
Test the data path from r1 to r4’s loopback, over both families:
sudo podman exec clab-isis-r1 ping -c 3 10.255.0.4
sudo podman exec clab-isis-r1 ping -6 -c 3 2001:db8:100::4
64 bytes from 10.255.0.4: seq=2 ttl=62 time=0.073 ms
--- 10.255.0.4 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.073/0.099/0.133 ms
64 bytes from 2001:db8:100::4: seq=2 ttl=62 time=0.117 ms
--- 2001:db8:100::4 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.096/0.140/0.207 ms
Metrics decide the path
r4 is directly connected to r1, but look at the routes above: r4’s loopback is reached via eth1 at metric 40, which is the path through r2 and r3, for both IPv4 and IPv6. A traceroute shows it:
sudo podman exec clab-isis-r1 traceroute -n 10.255.0.4
traceroute to 10.255.0.4 (10.255.0.4), 30 hops max, 46 byte packets
1 10.0.12.2 0.009 ms 0.004 ms 0.003 ms
2 10.0.23.2 0.004 ms 0.004 ms 0.004 ms
3 10.255.0.4 0.003 ms 0.003 ms 0.004 ms
The cost of the route is the sum of the link metrics plus the metric of the loopback prefix, which is 10:
- Via r2 and r3: 10 + 10 + 10 + 10 = 40
- Directly over the r1–r4 link: 50 + 10 = 60
IS-IS picks the lower total, so the three-hop path wins over the direct link. IS-IS cares only about metrics, not hop count, so you control which paths traffic takes by setting them.
When a link fails
Take down r1’s link to r2 and see what r1 does to reach r3’s loopback. Before, in both families:
sudo podman exec clab-isis-r1 vtysh -c "show ip route 10.255.0.3/32"
sudo podman exec clab-isis-r1 vtysh -c "show ipv6 route 2001:db8:100::3/128"
Routing entry for 10.255.0.3/32
Known via "isis", distance 115, metric 30, best
Flags: Selected
Status: Installed
* 10.0.12.2, via eth1, weight 1
Routing entry for 2001:db8:100::3/128
Known via "isis", distance 115, metric 30, best
Flags: Selected
Status: Installed
* fe80::a8c1:abff:fec0:3847, via eth1, weight 1
Now fail the link and look again:
sudo podman exec clab-isis-r1 ip link set eth1 down
sudo podman exec clab-isis-r1 vtysh -c "show ip route 10.255.0.3/32"
sudo podman exec clab-isis-r1 vtysh -c "show ipv6 route 2001:db8:100::3/128"
sudo podman exec clab-isis-r1 vtysh -c "show isis neighbor"
Routing entry for 10.255.0.3/32
Known via "isis", distance 115, metric 70, best
Flags: Selected
Status: Installed
* 10.0.41.1, via eth2, weight 1
Routing entry for 2001:db8:100::3/128
Known via "isis", distance 115, metric 70, best
Flags: Selected
Status: Installed
* fe80::a8c1:abff:fe24:427c, via eth2, weight 1
Area LAB:
System Id Interface L State Holdtime SNPA
r4 eth2 1 Up 28 2020.2020.2020
The adjacency with r2 is gone, and both routes to r3 now go out eth2 with metric 70: 50 to reach r4, then 10 to r3, then 10 for the loopback prefix. A traceroute and a ping confirm the traffic follows:
sudo podman exec clab-isis-r1 traceroute -n 10.255.0.3
traceroute to 10.255.0.3 (10.255.0.3), 30 hops max, 46 byte packets
1 10.0.41.1 0.007 ms 0.003 ms 0.003 ms
2 10.255.0.3 0.004 ms 0.003 ms 0.003 ms
Nobody touched a route. r1 and r2 updated their LSPs, flooded them, and every router recomputed its paths, for IPv4 and IPv6 at once.
How fast this happens depends on how the failure is detected. Here the interface went down, which IS-IS notices immediately. In five runs, both routes were replaced in 240 to 300 milliseconds, and that figure includes the overhead of polling for the change. A failure that doesn’t take the interface down, such as a neighbor that silently stops sending, is only detected when the 30-second holdtime expires.
Bring the link back:
sudo podman exec clab-isis-r1 ip link set eth1 up
The adjacency re-forms and both routes return to eth1 at metric 30. In my runs this took between 1.7 and 2.3 seconds.
Tear it down
sudo containerlab --runtime podman destroy -t topology.clab.yml --cleanup