Skip to main content

2.5 Adding and Removing Routing Table Entries: Dancing in the FIB

In the previous section, we broke down the Netlink message header and those dazzling flag bits. Now, it's time to throw that theory into a real-world scenario.

We're going to look at a very specific and frequent operation: adding a route to the kernel's routing table (FIB), or removing it.

This is an excellent observation sample because it completely demonstrates how a user-space command pulls the kernel's FIB subsystem along through the Netlink thread. Moreover, it hides a killer feature of Netlink—event broadcasting.


Behind the Scenes: From ip Commands to Kernel Callbacks

Let's look at adding a route first.

You type this line in the command line:

ip route add 192.168.2.11 via 192.168.2.20

This command doesn't directly modify memory. What it does is very pure: it builds a Netlink message of type RTM_NEWROUTE in user space, then throws it into the kernel through a AF_NETLINK socket.

Once the message crosses the boundary, the kernel's rtnetlink mechanism starts turning.

  1. Receiving: The kernel's rtnetlink socket receives this message and hands it to the rtnetlink_rcv() method. This is the main entry point for the entire routing subsystem.
  2. Dispatching: rtnetlink_rcv() sees that the message type is RTM_NEWROUTE and looks for who should handle this.
  3. Execution: Ultimately, execution is handed to the inet_rtm_newroute() function in net/ipv4/fib_frontend.c. This is where the real heavy lifting happens.

inet_rtm_newroute() doesn't just simply insert a row into an array—it has to manipulate the FIB (Forwarding Information Base). This is the "bible" of kernel routing decisions; any slight error will cause network packets to either fail to send or go straight to hell. It calls fib_table_insert() to actually write this route into the hardware-independent routing table database.

Broadcasting: Don't Keep It a Secret

If things ended here, Netlink would only be a "fancy IOCTL."

But Netlink is different. After fib_table_insert() writes the route into the database, it does another thing—shouts it out.

It calls the rtmsg_fib() method, passing in the RTM_NEWROUTE parameter. rtmsg_fib()'s job isn't to manipulate routes, but to construct a new Netlink notification message and then call rtnl_notify().

This "shout" is sent to the RTNLGRP_IPV4_ROUTE multicast group.

It's like someone shouting in a noisy room: "Hey everyone, a route to 192.168.2.11 was just added!" Everyone who "subscribed" to this channel can hear it.

  • Tools in the iproute2 suite are listening.
  • Advanced user-space routing daemons (like xorp or bird) are also listening.
  • Even other kernel modules, if they registered for this group, can hear it too.

This "work and broadcast simultaneously" design is the foundation of modern Linux network management. It means user-space daemons don't need to poll the kernel to check for table changes; the kernel proactively pushes them.


Deleting a Route: Same Script, Different Ending

The delete operation follows almost exactly the same flow, just with a few swapped nouns.

You execute:

ip route del 192.168.2.11

This time, the ip command generates a RTM_DELROUTE message.

  1. The message flies into the kernel and hits rtnetlink_rcv() again.
  2. This time it's dispatched to the inet_rtm_delroute() callback function (also in fib_frontend.c).
  3. inet_rtm_delroute() calls fib_table_delete() to erase this record from the FIB.
  4. Similarly, it calls rtmsg_fib() here, except this time it passes in RTM_DELROUTE.
  5. rtnl_notify() sets off again, broadcasting to RTNLGRP_IPV4_ROUTE: "Hey everyone, that route was just deleted."

You'll notice that when the kernel handles "add" and "delete," the logic is symmetric, with the focus on both: completing the actual data structure operations and ensuring subscribers receive notifications.


Hands-on Verification: Listening to the Kernel's Heartbeat

Talk is cheap. Let's see this broadcasting mechanism with our own eyes.

We need two terminals. It's like if you want to verify whether someone is shouting in a room, the best way is to bring your own radio in.

Terminal 1: Start Listening

ip monitor route

This command starts a background daemon. What does it do? It opens a Netlink socket and executes setsockopt() to join the RTNLGRP_IPV4_ROUTE multicast group. Now, it's all ears waiting for messages.

Terminal 2: Generate an Event

Now in the second terminal, let's add a random route:

ip route add 192.168.1.10 via 192.168.2.200

In that instant, the ip command sends a request, the kernel processes the request, and then the kernel broadcasts a notification.

Back in Terminal 1, you should immediately see a line of output:

192.168.1.10 via 192.168.2.200 dev em1

See that? You didn't type that command; this is the notification broadcast back from the kernel. Your listening process received the kernel's rtnl_notify() and printed it out.

Next, delete it in Terminal 2:

ip route del 192.168.1.10

Terminal 1 will immediately show:

Deleted 192.168.1.10 via 192.168.2.200 dev em1

Notice that Deleted prefix—that's a friendly hint from ip monitor, identifying that the broadcast message type is RTM_DELROUTE.

Routes aren't the only things that can be broadcast. rtnetlink defines various multicast groups.

You can try listening to "link" layer events. Execute this in Terminal 1:

ip monitor link

This makes the Netlink socket join the RTNLGRP_LINK group. As long as any network interface state changes—UP/DOWN, new VLAN, added Bridge—you can catch it.

Now go to Terminal 2 and try adding a VLAN interface:

vconfig add eth1 200

Terminal 1 will instantly spit out a bunch of information:

4: eth1.200@eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
link/ether 00:e0:4c:53:44:58 brd ff:ff:ff:ff:ff:ff

Let's try adding a bridge too:

brctl addbr mybr

Terminal 1 continues to follow up:

5: mybr: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
link/ether a2:7c:be:62:b5:b6 brd ff:ff:ff:ff:ff:ff

This is why tools like NetworkManager can react so fast—they don't need to poll files under /sys at all. They just sit quietly next to a Netlink socket, waiting for the kernel to tell them what happened.


Chapter Summary

By this point, you've seen through the full bag of tricks of Netlink Sockets:

  1. It is a standard socket-based IPC channel, replacing the legacy IOCTL.
  2. It has a strict message format (header + TLV attributes) capable of carrying complex network configurations.
  3. Most importantly, it is multicast. The kernel doesn't just passively receive commands; it proactively broadcasts network topology changes to subscribers.

This mechanism forms the cornerstone of modern Linux network management. Whether it's the ip command or the underlying rtnetlink, they're essentially all playing by these rules.

But there's a problem.

If you're a kernel developer and you want to add a Netlink interface to some non-standard kernel module you wrote, what would you do? rtnetlink is for the networking subsystem; you can't just force your random hardware control messages into a type like RTM_NEWROUTE.

The 32 standard Netlink protocol family slots are actually in short supply. To solve this "address space crisis," the kernel later introduced an extension mechanism.

In the next section, we'll talk about Generic Netlink—the ultimate multiplexing scheme designed to solve the "not enough sockets" problem.