Multiple Default Routes in Ubuntu 18.04 (and other distros that use Netplan)

In my previous article on the subject, I showed you how to configure multiple default routes in Ubuntu by using the /etc/network/interfaces file. Well… that’s gone now. In newer versions of Ubuntu (I’m using 18.04 LTS) the interfaces file and the ifup/ifdown scripts have been replaced with the new and shiny Netplan! The bad news is my previous method won’t work any more. So here’s how you do it with Netplan.

Warning: If you have a public IP address on your workstation, be sure to apply the proper firewall and security measures! Your workstation will be directly accessible from the Internet! If your workstation is compromised, an attacker will have a vector to your internal LAN though it.

The Situation:

Your workstation/server has both a network connection to the local network with internet access, and a public IP address (or a second, different LAN/WAN/DMZ) on a separate interface. You’d like to run some services, perhaps the Apache web server, and serve them via the public IP address you have directly on your NIC, but still use the LAN connection for everything else. Or maybe you just want to be able to ssh directly to your workstation from home, without forwarding a port on the main firewall. Whatever you want to serve on the public IP address, this is how you can do it.

The Solution:

We use the iproute2 extensions built into Ubuntu (And Debian. Other distros use it as well. YMMV) in order to set up a separate routing table for the second interface. This makes sure that requests hitting the IP address of the interface are replied to via that address, rather then through the default route (aka: the LAN). If you don’t do this, incoming connections are never answered.

We do this via the /etc/netplan/*.yaml files. It’s possible to set up multiple interfaces exactly the same, but that is left as an exercise for the reader.

WTF is YAML?

On my Ubuntu 18.04 system, there is a file in /etc/netplan named 01-network-manager-all.yaml. This is the file I’ll be editing, but it doesn’t really matter what your file is called as long as it ends in .yaml. Be sure to follow the formatting exactly. Yaml is a markup language “human friendly data serialization standard for all programming languages.” Make sure you backup your existing *.yaml file before you edit it. You can leave the backup in the /etc/netplan directory if you add a different extension to it like this:

cp 01-network-manager-all.yaml 01-network-manager-all.yaml.backup

Here is an example netplan yaml file:

network:
  version: 2
  renderer: networkd # change this from 'NetworkManager' if it's set.
  ethernets:
    # LAN NETWORK
    eno1:
      addresses: [10.0.0.100/16]
      gateway4: 10.0.0.1
      nameservers:
        addresses: [10.0.0.10, 10.0.0.11]
    # PUBLIC IP
    eno2:
      addresses: [123.123.123.123/27]
      #gateway4:    # unset, since we configure the route below
      routes:
        - to: 0.0.0.0/0
          via: 123.123.123.1
          metric: 500
          table: 1
      routing-policy:
        - from: 123.123.123.123
          table: 1

Note that changing the ‘renderer’ variable to ‘networkd’ will mean that Network Manager no longer has control of your configuration. All networking changes will be made in this file.

The ‘table’ variable (It’s in two places above) must be incremented for each publicly routed interface you set up. If you want to make the ‘table’ variable more readable, you can define them in the /etc/iproute2/rt_tables file like this:

# 
# reserved values 
# 
255     local 
254     main 
253     default 
0       unspec 
# 
# local 
# Define your new/custom routing tables here:
1      ispA
2      ispB
3      ispC

Then you can set the ‘table’ variable to ‘ispA’ instead of ‘1’. However, this is not necessary. It works just fine with numbers, and you can always put a comment in the yaml file to document which interface is which, if you want to.

After you save your changes, you can run ‘sudo netplan generate’ to test the config. If it returns to a prompt without any errors, your file is readable. If you do get errors, check out the documentation at yaml.org and check your formatting. I found the errors to be pretty self-explanatory, but try feeding them to Google if you can’t figure it out.

Once your config is error free, you can run ‘sudo netplan apply’ to apply the changes to your network configuration without rebooting.

After you’ve set this up, you can view the new routing table and rule for the interface by running:

$ ip route show table 1 
default via 123.123.123.1 dev eno1 proto static metric 500
$ ip rule show
0:      from all lookup local  
0:  from 123.123.123.123 lookup 1  
32766:  from all lookup main  
32767:  from all lookup default

Here we see the routing table for eno1, as well as the rule that tells the system to use it for incoming connections to eno1’s IP address.