Welcome back to Linux Networking Mastery! In Part 1 we explored the fundamentals of the Linux network stack and basic inspection commands. In Part 2 we took control of interfaces—bringing them up/down, assigning temporary and persistent IP addresses (via Netplan, NetworkManager/nmcli, systemd-networkd, etc.), and handling IPv4/IPv6 differences.
Now we move to one of the most powerful (and sometimes confusing) parts of Linux networking: routing. We'll examine how the kernel decides where to send packets, how to add and manipulate routes, introduce policy-based routing for complex scenarios, and finish with network namespaces—a foundational technology for isolation in containers and advanced setups.
By the end of this post, you'll understand the routing table deeply, be able to configure static routes and multiple gateways, and set up a basic multi-interface router.
Understanding the Routing Table
The Linux kernel uses a routing table to decide the next hop for every outgoing packet. View it with:
ip route show
# or more verbose
ip route list
Typical output on a single-interface system:
default via 192.168.1.1 dev enp0s3 proto dhcp src 192.168.1.100 metric 100
192.168.1.0/24 dev enp0s3 proto kernel scope link src 192.168.1.100 metric 100
default via ...→ default gateway (your router)192.168.1.0/24 dev enp0s3 ...→ directly connected local subnet (added automatically by kernel when IP is assigned)proto kernel→ route added by kernelproto dhcp→ added by DHCP clientmetric→ preference (lower = better; used when multiple routes match)
For IPv6:
ip -6 route show
You'll see link-local routes, possibly a default via router advertisement, etc.
Adding and Removing Static Routes (Temporary)
Add a route to a specific network:
sudo ip route add 10.10.0.0/24 via 192.168.1.50 dev enp0s3
Add/change default gateway:
sudo ip route add default via 192.168.1.254 dev enp0s3
# or replace existing default
sudo ip route replace default via 192.168.1.254 dev enp0s3
Remove a route:
sudo ip route del 10.10.0.0/24
sudo ip route del default
Note: ip route change works only for some fields (e.g., metric); often safer to del then add/replace.
Making Routes Persistent
Use the same configuration systems from Part 2:
Netplan (Ubuntu/Debian):
network: version: 2 ethernets: enp0s3: addresses: [192.168.1.100/24] routes: - to: 10.10.0.0/24 via: 192.168.1.50 - to: default via: 192.168.1.1nmcli (RHEL/Fedora):
sudo nmcli con mod "System enp0s3" +ipv4.routes "10.10.0.0/24 192.168.1.50" sudo nmcli con up "System enp0s3"systemd-networkd:
Add to
.networkfile:[Route] Destination=10.10.0.0/24 Gateway=192.168.1.50
Policy-Based Routing (Multiple Routing Tables)
Standard routing looks only at destination IP. Policy routing lets you route based on source IP, protocol, fwmark, etc., using multiple tables.
Define custom tables in
/etc/iproute2/rt_tables:100 isp1 200 isp2Add routes to those tables:
sudo ip route add default via 203.0.113.1 dev enp1s0 table isp1 sudo ip route add default via 198.51.100.1 dev enp2s0 table isp2Add rules to select table:
# Route traffic from source 192.168.10.0/24 via isp1 sudo ip rule add from 192.168.10.0/24 table isp1 # Route marked packets (e.g., via iptables/nftables) via isp2 sudo ip rule add fwmark 2 table isp2View rules:
ip rule showLower pref number = higher priority.
Flush cache after changes (older kernels):
sudo ip route flush cache
(Note: Routing cache was removed in kernel 3.6 for IPv4 and later for IPv6; modern kernels usually don't need this.)
Network Namespaces: Isolated Routing Environments
Network namespaces give each namespace its own interfaces, addresses, routes, etc.—perfect for testing, containers, or isolating routing domains.
Create and enter a namespace:
sudo ip netns add blue
sudo ip netns exec blue bash
Inside namespace:
ip link show # only lo exists by default
ip addr add 10.0.0.1/24 dev lo
ip link set lo up
Create virtual Ethernet pair to connect namespaces/host:
sudo ip link add veth-blue type veth peer name veth-host
sudo ip link set veth-blue netns blue
sudo ip netns exec blue ip addr add 172.16.0.2/24 dev veth-blue
sudo ip netns exec blue ip link set veth-blue up
sudo ip addr add 172.16.0.1/24 dev veth-host
sudo ip link set veth-host up
Now add routes:
# In host
sudo ip route add 10.0.0.0/24 via 172.16.0.2
# In blue namespace
ip route add default via 172.16.0.1
Ping between them to test.
Practical Example: Configuring a Simple Linux Router
Scenario: Two interfaces — enp1s0 (WAN, 203.0.113.10/24, gateway 203.0.113.1), enp2s0 (LAN, 192.168.100.1/24).
Steps:
Enable IP forwarding:
sudo sysctl -w net.ipv4.ip_forward=1 # Persistent: echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-ip-forward.confAssign IPs (persistent via your config method).
Add NAT for LAN clients (using nftables or iptables):
sudo nft add table nat sudo nft add chain nat postrouting { type nat hook postrouting priority 100 \; } sudo nft add rule nat postrouting oifname "enp1s0" masqueradeLAN clients use 192.168.100.1 as gateway.
Hands-On Exercises
- Add a static route to a private network (e.g., 172.20.0.0/16 via your current gateway). Verify with
traceroute. - Create a second routing table with a dummy default route; add a rule based on source IP from a secondary address on your interface.
- Create two namespaces, connect them with veth pair, assign IPs, add routes, and ping between them.
- (Advanced) Set up basic forwarding between two physical interfaces (disable if on production machine!).
Warning: Changing default routes or enabling forwarding can disrupt connectivity—use a VM or have console access.
What's Next?
In Part 4, we'll focus on name resolution: how Linux finds hostnames, configuring /etc/resolv.conf, using systemd-resolved, setting up local caching, and troubleshooting DNS problems.