How to Use Ubuntu iproute2 to Configure Advanced Policy-Based Routing

The Limitation of Destination Routing

In standard TCP/IP networking, the Linux kernel makes routing decisions based on a single variable: the destination IP address. When an application on an Ubuntu server attempts to send a packet to 8.8.8.8, the kernel consults its central routing table (viewable via ip route), finds the default gateway (e.g., 192.168.1.1), and blasts the packet out of that interface.

This works perfectly for 99% of servers. But consider a complex edge-router scenario. You have an Ubuntu server acting as a gateway for an office. It has two internet connections: a high-speed, expensive fiber line (ISP 1 on eth0) and a slow, cheap DSL line (ISP 2 on eth1).

You want all standard web browsing to go out the slow DSL line, but you want all Voice-over-IP (VoIP) traffic to go out the fast fiber line. Standard destination routing cannot solve this, because both the web browser and the VoIP phone might be connecting to the same remote cloud provider.

To overcome this, Linux network engineers use Policy-Based Routing (PBR) via the iproute2 suite. PBR allows you to build multiple, independent routing tables and use complex conditional logic (Policies) to determine which table a packet should use based on the source IP, the TCP/UDP port, or even the specific application.

Step 1: Understanding the iproute2 Architecture

The ip command in Ubuntu is not just a replacement for ifconfig. It is the frontend for the Linux kernel’s massive policy routing database (RPDB).

By default, Linux has three routing tables:

  1. Local (ID 255): Used for internal loopback traffic. (Do not touch this).
  2. Main (ID 254): The standard routing table you see when you type ip route.
  3. Default (ID 253): Usually empty.

You can view the rules that govern these tables by running:

ip rule list

The output will show: 0: from all lookup local, then 32766: from all lookup main. This means the kernel checks the local table first. If no match is found, it drops down to the main table.

Step 2: Creating a Custom Routing Table

To route the VoIP traffic out of the expensive fiber line (ISP 1), you must first build a dedicated routing table specifically for that ISP.

Routing tables are defined in a simple text file: /etc/iproute2/rt_tables.

Open this file in a text editor (e.g., sudo nano /etc/iproute2/rt_tables) and append a new line at the bottom. You must assign it a unique numerical ID (e.g., 100) and a human-readable name (e.g., fiber_route).

100 fiber_route

Save and close the file.

Step 3: Populating the Custom Table

Now that the fiber_route table exists, you must populate it. It is completely empty.

Suppose the IP address of the expensive Fiber modem (ISP 1) is 203.0.113.1 on interface eth0.

You use the ip route command, but you explicitly append table fiber_route to force the route into your new silo, rather than the main routing table.

sudo ip route add default via 203.0.113.1 dev eth0 table fiber_route

You can verify the table is populated by running:

ip route show table fiber_route

The kernel now has a hidden, parallel routing dimension that explicitly shunts all traffic out of the eth0 fiber interface.

Step 4: Building the Policy Rules (The IP Rule Engine)

The final step is the hardest: you must command the kernel to intercept specific packets and force them into the fiber_route table instead of the main table.

Scenario A: Routing by Source IP Address

Suppose your internal VoIP phones all live on a dedicated subnet: 10.0.50.0/24. You want all traffic from this subnet to use the fiber line.

You use the ip rule command to build the intercept policy:

sudo ip rule add from 10.0.50.0/24 table fiber_route

Now, when a packet arrives from 10.0.50.15, the kernel evaluates the RPDB. It hits this new rule, stops evaluating, jumps into the fiber_route table, sees the default gateway pointing to the fiber modem, and blasts the packet out of eth0.

Scenario B: Routing by iptables Firewall Marks (Advanced)

What if you can’t route by Source IP? What if the VoIP software is running on the same server as the web browser, but you only want the VoIP traffic (UDP port 5060) to use the fiber line?

You cannot use ip rule to check ports directly. You must combine it with iptables (or nftables).

First, use iptables to mathematically “mark” the specific VoIP packets with a hidden hexadecimal tag (e.g., 0x1) in the PREROUTING chain:

sudo iptables -t mangle -A PREROUTING -p udp --dport 5060 -j MARK --set-mark 1

Second, instruct the ip rule engine to hunt for that specific tag and shove those packets into the fiber_route table:

sudo ip rule add fwmark 1 table fiber_route

The web browser (TCP 443) remains unmarked and falls through to the main routing table (going out the slow DSL line), while the VoIP packet is tagged, intercepted, and routed out the fast fiber line.

Step 5: Making the Rules Persistent

The fatal flaw of the ip route and ip rule commands is that they are entirely volatile. If you reboot the server, your custom routing tables will vanish, and the network will collapse.

To make them persistent in modern Ubuntu, you must translate these commands into your Netplan YAML configuration.

Open your Netplan file (e.g., /etc/netplan/00-installer-config.yaml).

network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 203.0.113.10/24
      routes:
        - to: default
          via: 203.0.113.1
          table: 100
      routing-policy:
        - from: 10.0.50.0/24
          table: 100

Run sudo netplan apply to permanently fuse the Policy-Based Routing architecture into the OS.

Conclusion

Relying exclusively on the main routing table forces administrators into a rigid, destination-only routing paradigm. By mastering the iproute2 suite and Policy-Based Routing, Linux engineers decouple traffic control from destination constraints. The ability to intercept packets via firewall marks or source IPs and shunt them into parallel routing tables allows for complex multi-WAN load balancing, stringent quality of service (QoS) enforcement, and absolute control over edge-network traffic flows.

RELATED POSTS

  • How to Configure Linux chrony for High-Precision Network Time Protocol (NTP)
  • How to Use the fuser Command to Identify Processes Locking a File in Linux
  • How to Manage Log Files Using Logrotate on Ubuntu Linux
  • How to Manage Linux Services using the systemctl Command
  • How to Install and Configure the Postfix Mail Server on Ubuntu
  • Get the best tech tips delivered straight to your inbox.

    Join thousands of readers mastering Apple, Google, Microsoft, and Linux.