September 15, 2023 | 21:27

Distribute IP Routes with Unifi Controller

Recently, I couldn’t access a machine within another VLAN anymore, because Docker on that machine used a subnet within the 192.168.x.x range for an internal network, that led to packets not finding the way back to me. Docker apparently uses the following ranges by default:

  • 172.[17-31].0.0/16
  • 192.168.[0-240].0/20

The routes on the machine were as follows. Unifi provides my machines with a default gateway, e.g. 192.168.20.1 and the route for the local subnet 192.168.20.0/24 was automatically added by the kernel. The third one is the one created by Docker which conflicted with the other VLAN.

$ ip route | grep 192
default via 192.168.20.1 dev eth0 
192.168.20.0/24 dev eth0 proto kernel scope link src 192.168.20.8
192.168.32.0/20 dev br-3110aab1f21c proto kernel scope link src 192.168.32.1

Restrain Docker’s IP Pool

There is a setting within /etc/docker/daemon.json that can be used to specify the address pool from which Docker will allocate new networks. The base option defines the overall range, whereas the size option defines the netmask of each subnet. So, in the example below, Docker will create networks within the range 172.81.[0-255].0/24.

# /etc/docker/daemon.json
{
	"default-address-pools":
	[
		{
            "base":"172.81.0.0/16",
            "size":24
        }
	]
}

Besides the solution above, I also read that Docker wouldn’t have created the 192.168.x.x subnet, if it had conflicted with an existing route. So I wondered how I could instruct my Unifi Security Gateway to distribute a custom route via its DHCP server.

Routes with RFC3442

If a host in another VLAN is to be reached by that machine it can route the traffic through the default gateway, obviously. However, I want the DHCP server to create a route for the whole 192.168.0.0/16 range, where all the VLAN subnets are located. The relevant DHCP option 121 is described in RFC3442, whose abstract reads:

This document defines a new Dynamic Host Configuration Protocol (DHCP) option which is passed from the DHCP Server to the DHCP Client to configure a list of static routes in the client. The network destinations in these routes are classless - each routing table entry includes a subnet mask.

And later, it describes how the routes are encoded.

The code for this option is 121, and its minimum length is 5 bytes. This option can contain one or more static routes, each of which consists of a destination descriptor and the IP address of the router that should be used to reach that destination.

[…]

Destination descriptors describe the IP subnet number and subnet mask of a particular destination using a compact encoding. This encoding consists of one octet describing the width of the subnet mask, followed by all the significant octets of the subnet number.

The RFC goes on explaining how the encoding works. However, at this point, I would like to give an example. Let’s say we want to encode the route 192.168.0.0/16 via 192.168.20.1 according to the RFC. The first octet is the subnet mask in CIDR notion, so we start with a 16.. Now we append the network part of the subnet, which lead to 16.192.168 (that’s already the socalled destination descriptor). Finally, we append the router’s IP and we have completed our route 16.192.168.192.168.20.1.

Another route, this time for the default gateway 0.0.0.0/0 via 192.168.20.1, would simply result in 0.192.168.20.1. Concatenating both routes gives us 0.192.168.20.1.16.192.168.192.168.20.1. More routes can be added at will.

Configure UniFi Controller

To configure that DHCP option within the UniFi Controller, navigate to Settings -> Networks and select the relevant network. Scroll down to DHCP Service Management and click Show Options. Scroll further down to Custom DHCP Options and click Select Options. In the dialog pop-up click on Add Option and enter the following:

  • DHCP Name: Select a desired name, e.g. 192-168_16_Route
  • Type: Hex Array
  • Hex Array: The route string in hexadecimal notation (see below), e.g. 00:c0:a8:14:01:10:c0:a8:c0:a8:14:01
  • Code: 121

The route string we created above, needs to be transferred to hexadecimal notation. There is an online calculator, or you can use the following Python snippet, which is based on this Gist.

# credits: https://gist.github.com/casebeer/8308838

def hex_encode(route):
	return b":".join([b"%02x" % octet for octet in route])

routes = [
  (0,                 192,168,20,1 ),
	(16,    192,168,    192,168,20,1 ), 
	
]

print(b":".join(hex_encode(route) for route in routes).decode("utf-8"))

If you added that option to one of the UniFi controller’s networks, the option is also added to all other networks, however without a Value and without being enabled. You can edit the option to insert a hex route string and enable the option.

After getting a new DHCP lease, the target machine now happily holds the route we defined above:

$ ip route | grep 192
default via 192.168.20.1 dev eth0 
192.168.0.0/16 via 192.168.20.1 dev eth0 
192.168.20.0/24 dev eth0 proto kernel scope link src 192.168.20.8

© Pavel Pi 2021

Powered by Hugo & Kiss'Em.