Building Cisco-Like Loopback Interfaces on Linux

Cisco routers use loopback interfaces as stable Layer 3 identities. They are independent of physical ports, making them useful for router IDs, routing-protocol endpoints, management addresses, and service IPs. Linux can provide nearly the same functionality without a custom kernel module: use a dummy network interface.

This article explains how dummy interfaces behave, how to route to them, how to use them with policy routing and FRRouting, and how to create one from C using Netlink. It also covers anycast, VRRP, and the cases where a custom kernel driver or a TUN device is actually necessary.

1. Dummy versus loopback: what is actually routable?

Dummy Routing

Create a dummy device and assign a host address:

sudo ip link add dummy0 type dummy
sudo ip addr add 10.255.0.1/32 dev dummy0
sudo ip link set dummy0 up
ip addr show dummy0

Linux installs a local route for the address, normally visible in table local:

ip route show table local
# local 10.255.0.1 dev dummy0 proto kernel scope host src 10.255.0.1

ping 10.255.0.1 succeeds on the host. This does not mean that packets are physically transmitted through dummy0: packets destined to a local address are delivered by the IP stack. The dummy driver's transmit path discards packets sent out through it.

To reach the address from another machine, that machine or its upstream routers must know a path to the Linux host. For example, if the Linux host has eth0=192.168.1.1:

# On a second machine on 192.168.1.0/24:
sudo ip route add 10.255.0.1/32 via 192.168.1.1
ping 10.255.0.1

The packet arrives on eth0 and is delivered locally because 10.255.0.1 belongs to the host. IPv4 forwarding is not required for this traffic. Forwarding is required only for packets whose destinations are not local and which must traverse the router.

A dummy interface provides a routable local identity, not a virtual wire. Adding ip route add 10.200.0.0/24 dev dummy0 does not make 10.200.0.0/24 reachable: packets routed out of the dummy device are dropped.

2. Source-based policy routing

Policy Routing

Suppose a router has these addresses:

DeviceAddressRole
dummy010.255.0.1/32Stable identity
eth0192.168.1.1/24LAN
eth1172.16.0.2/24ISP 1
eth2172.17.0.2/24ISP 2

To route traffic sourced from the dummy address through ISP 2:

sudo ip route add default via 172.17.0.1 dev eth2 table 200
sudo ip rule add from 10.255.0.1/32 lookup 200 priority 1000
ip rule show
ip route get 8.8.8.8 from 10.255.0.1

The expected lookup selects eth2, with source 10.255.0.1. This only works end-to-end if the upstream network accepts the source address and a return route exists. On multihomed hosts, reverse-path filtering (rp_filter) can also interfere with asymmetric paths.

Linux checks the local-routing rule before ordinary user policy rules, so traffic destined to a locally owned dummy address is ordinarily delivered locally.

3. Stable BGP endpoints with FRRouting

Bgp Peering

Loopback addresses make good BGP peering endpoints because the session can survive a change in physical path provided another routed path remains available.

Example topology:

Router ARouter B
Physical IP192.168.10.1192.168.10.2
Dummy IP10.255.0.1/3210.255.0.2/32
ASN6500165002

On each router, create the appropriate dummy interface. Before starting the BGP session, provide underlay reachability:

# Router A
sudo ip route add 10.255.0.2/32 via 192.168.10.2

# Router B
sudo ip route add 10.255.0.1/32 via 192.168.10.1

Install FRRouting (sudo apt install frr on Debian/Ubuntu), enable bgpd in /etc/frr/daemons, and configure it with vtysh.

Router A:

configure terminal
router bgp 65001
 bgp router-id 10.255.0.1
 neighbor 10.255.0.2 remote-as 65002
 neighbor 10.255.0.2 update-source 10.255.0.1
 neighbor 10.255.0.2 ebgp-multihop 2
 address-family ipv4 unicast
  neighbor 10.255.0.2 activate
 exit-address-family

Router B:

configure terminal
router bgp 65002
 bgp router-id 10.255.0.2
 neighbor 10.255.0.1 remote-as 65001
 neighbor 10.255.0.1 update-source 10.255.0.2
 neighbor 10.255.0.1 ebgp-multihop 2
 address-family ipv4 unicast
  neighbor 10.255.0.1 activate
 exit-address-family

Check the session with:

sudo vtysh -c 'show bgp summary'

A BGP router ID is only a 32-bit identifier: it need not be assigned to an interface or reachable. Using the loopback address as the router ID is a convention, not a requirement.

4. Anycast service addresses

Anycast

Anycast uses the same service IP on multiple hosts, with the routing system selecting which host receives traffic. For example, three DNS servers might all own 10.100.0.53/32 on dummy0:

sudo ip link add dummy0 type dummy
sudo ip addr add 10.100.0.53/32 dev dummy0
sudo ip link set dummy0 up

Each server advertises the prefix via its routing daemon. In FRRouting, the advertisement can include:

router bgp 65001
 address-family ipv4 unicast
  network 10.100.0.53/32
 exit-address-family

This assumes BGP neighbors are configured and the prefix exists in the relevant routing table. Routing preference is determined by routing policies and metrics; it is not necessarily geographic proximity.

A key failure mode is that the dummy interface stays up even when the DNS application fails. A production deployment must link route advertisement or withdrawal to service health. Anycast is particularly useful for stateless services such as DNS; stateful TCP sessions can break if routing shifts an active flow to another node.

5. VRRP and high availability

Vrrp

For a virtual IP on the same Ethernet subnet as clients, Keepalived/VRRP is often a better fit than an always-present dummy address. Consider:

  • Server A: 192.168.1.10
  • Server B: 192.168.1.11
  • Floating VIP: 192.168.1.100

Example Keepalived configuration for Server A:

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1

    virtual_ipaddress {
        192.168.1.100/24 dev eth0
    }
}

Server B uses compatible settings with a lower priority, for example 100. For a VIP on a directly attached subnet, assigning it to eth0 supports normal neighbor discovery. For routed service IPs, a dummy interface plus dynamic routing may be preferable. Keepalived configurations also need appropriate peer communication, health checks, and security controls for production use.

6. Create a dummy interface from C with libnl

The preferred implementation is a userspace management program using rtnetlink, not a new kernel network driver. Netlink messages include:

TaskMessageKey attributes
Create interfaceRTM_NEWLINKIFLA_IFNAME, IFLA_LINKINFO, IFLA_INFO_KIND=dummy
Bring upRTM_NEWLINKIFF_UP
Add IP addressRTM_NEWADDRIFA_LOCAL, IFA_ADDRESS
Add routeRTM_NEWROUTERTA_DST, RTA_GATEWAY, RTA_OIF
Add policy ruleRTM_NEWRULEFRA_SRC, FRA_TABLE, FRA_PRIORITY
Delete interfaceRTM_DELLINKInterface identifier

Install development packages:

sudo apt install libnl-3-dev libnl-route-3-dev pkg-config

Minimal implementation (cloop.c):

#include <stdio.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/route/link.h>
#include <netlink/route/link/dummy.h>
#include <netlink/route/addr.h>
#include <netlink/addr.h>

int main(void)
{
    struct nl_sock *sock = nl_socket_alloc();
    struct rtnl_link *link = NULL, *change = NULL;
    struct rtnl_addr *addr = NULL;
    struct nl_addr *ip = NULL;
    int err = -1, index;

    if (!sock) return 1;
    err = nl_connect(sock, NETLINK_ROUTE);
    if (err < 0) goto out;

    link = rtnl_link_dummy_alloc();
    if (!link) { err = -NLE_NOMEM; goto out; }
    rtnl_link_set_name(link, "cloop0");
    err = rtnl_link_add(sock, link, NLM_F_CREATE | NLM_F_EXCL);
    if (err < 0) goto out;

    index = rtnl_link_name2i(sock, "cloop0");
    if (index <= 0) { err = -NLE_OBJ_NOTFOUND; goto out; }

    addr = rtnl_addr_alloc();
    if (!addr) { err = -NLE_NOMEM; goto out; }
    err = nl_addr_parse("10.255.0.1/32", AF_INET, &ip);
    if (err < 0) goto out;
    rtnl_addr_set_ifindex(addr, index);
    err = rtnl_addr_set_local(addr, ip);
    if (err < 0) goto out;
    err = rtnl_addr_add(sock, addr, 0);
    if (err < 0) goto out;

    change = rtnl_link_alloc();
    if (!change) { err = -NLE_NOMEM; goto out; }
    rtnl_link_set_flags(change, IFF_UP);
    err = rtnl_link_change(sock, link, change, 0);
    if (err < 0) goto out;

    puts("Created cloop0: 10.255.0.1/32");
out:
    if (err < 0) fprintf(stderr, "Netlink error: %s\n", nl_geterror(err));
    if (change) rtnl_link_put(change);
    if (ip) nl_addr_put(ip);
    if (addr) rtnl_addr_put(addr);
    if (link) rtnl_link_put(link);
    nl_socket_free(sock);
    return err < 0 ? 1 : 0;
}

Compile and run:

gcc cloop.c -o cloop $(pkg-config --cflags --libs libnl-3.0 libnl-route-3.0)
sudo ./cloop
ip -d link show cloop0
ip addr show cloop0
ping 10.255.0.1

The program requires CAP_NET_ADMIN in its network namespace. It intentionally uses NLM_F_EXCL, so rerunning it while cloop0 exists returns an error. This is a minimal demonstration: a production daemon should reconcile desired state, handle Netlink notifications, validate inputs, and roll back partially applied changes.

7. Do you need a kernel module?

Usually not. A kernel module using alloc_netdev() and register_netdev() can expose a new device, but it introduces kernel API compatibility and lifecycle concerns. A device built with ether_setup() is Ethernet-like, not an exact clone of Linux lo (ARPHRD_LOOPBACK with loopback-specific semantics).

Use existing device types unless you truly need new transmit/receive behavior:

RequirementRecommended mechanism
Stable router IP / Cisco-style Loopbackdummy
Custom source-based routingip rule / rtnetlink
BGP/OSPF control planeFRRouting
Service anycastdummy + BGP + health checks
Active/passive floating LAN VIPKeepalived / VRRP
Userspace processing of IP packetsTUN
Virtual Ethernet frames in userspaceTAP
Routed path between hostsGRE, IPIP, WireGuard, etc.
Novel device-specific kernel TX/RX semanticsCustom net_device driver

8. Suggested architecture for a router-like product

C Architecture

A clean Cisco-like management architecture has three layers:

  1. C management daemon: parses commands such as interface Loopback0, stores desired configuration, creates dummy interfaces, assigns addresses, and watches rtnetlink notifications.
  2. Linux kernel: owns the interfaces, local and forwarding routing tables, policy rules, and packet forwarding.
  3. FRRouting: implements BGP/OSPF, route advertisements, and convergence.

For example, a CLI command interface Loopback0 could map to dummy device lo0 or Loopback0; ip address 10.255.0.1/32 maps to RTM_NEWADDR; and routing protocol configuration is delegated to FRRouting.

This approach preserves the operational advantages of Cisco loopbacks without maintaining a custom kernel driver. The central rule is simple: dummy interfaces anchor addresses; physical interfaces and tunnels carry transit traffic.

References

Tags: linux, networking, routing, policy-routing, c, netlink, bgp, anycast, vrrp

← Back to all posts