Michael Zamot / Writing

IS-IS: how routers share a map of the network

How IS-IS works: adjacencies, link-state packets, the shared database, shortest-path selection, and what happens when a link fails.

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
NodeLoopback (IPv4)Loopback (IPv6)NET
r110.255.0.1/322001:db8:100::1/12849.0001.0000.0000.0001.00
r210.255.0.2/322001:db8:100::2/12849.0001.0000.0000.0002.00
r310.255.0.3/322001:db8:100::3/12849.0001.0000.0000.0003.00
r410.255.0.4/322001:db8:100::4/12849.0001.0000.0000.0004.00
NodeInterfaceIPv4 addressIPv6 addressConnected to
r1eth110.0.12.1/242001:db8:12::1/64r2
r1eth210.0.41.2/242001:db8:41::2/64r4 (metric 50)
r2eth110.0.12.2/242001:db8:12::2/64r1
r2eth210.0.23.1/242001:db8:23::1/64r3
r3eth110.0.23.2/242001:db8:23::2/64r2
r3eth210.0.34.1/242001:db8:34::1/64r4
r4eth110.0.34.2/242001:db8:34::2/64r3
r4eth210.0.41.1/242001:db8:41::1/64r1 (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 LAB creates the IS-IS instance. is-type level-1 and net set the level and the router’s identity.
  • ip router isis LAB and ipv6 router isis LAB on 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 passive on the loopback advertises its address without sending hellos, since no neighbor lives behind a loopback.
  • isis network point-to-point sets the network type discussed above.
  • isis metric 50 raises 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 1 lets 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.

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.

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
← All writing