Docker – Running Dnsmasq as DHCP Proxy from a Container

containersdhcpdnsmasqdockerpxe-boot

I'm trying to configure dnsmasq to operate in DHCP proxy mode, supplying PXE network boot information to clients, while my router continues to act as the DHCP server.

Ideally, I'd like to run dnsmasq in a Docker container.

I've first tried setting it up in a VMware VM running Linux, like this:

# Verbose DHCP logging
log-dhcp

# Disable DNS
port=0

# Enable TFTP
enable-tftp
tftp-root=/tftproot

# Answer DHCP discovery requests coming in over the virtual machine
dhcp-range=172.0.0.0,proxy,255.0.0.0

dhcp-vendorclass=set:bios,PXEClient:Arch:00000
pxe-service=tag:bios,x86PC,"Netboot",netboot.xyz.kpxe,172.16.80.2

I fired up another VMware VM and set it to boot from the network. After a long wait it does start configuring over DHCP and it successfully boots. This is the output from dnsmasq:

dnsmasq-dhcp: 705858459 available DHCP subnet: 172.0.0.0/255.0.0.0
dnsmasq-dhcp: 705858459 vendor class: PXEClient:Arch:00000:UNDI:002001
dnsmasq-dhcp: 705858459 PXE(ens33) 00:0c:29:12:8b:9b proxy
dnsmasq-dhcp: 705858459 tags: bios, ens33
dnsmasq-dhcp: 705858459 broadcast response
dnsmasq-dhcp: 705858459 sent size:  1 option: 53 message-type  2
dnsmasq-dhcp: 705858459 sent size:  4 option: 54 server-identifier  172.16.80.2
dnsmasq-dhcp: 705858459 sent size:  9 option: 60 vendor-class  50:58:45:43:6c:69:65:6e:74
dnsmasq-dhcp: 705858459 sent size: 17 option: 97 client-machine-id  00:56:4d:e4:12:5a:70:00:5b:07:fb:d3:7b:cc...
dnsmasq-dhcp: 705858459 sent size: 31 option: 43 vendor-encap  06:01:03:0a:04:00:50:58:45:08:07:80:00:01...
dnsmasq-dhcp: 705858459 available DHCP subnet: 172.0.0.0/255.0.0.0
dnsmasq-dhcp: 705858459 vendor class: PXEClient:Arch:00000:UNDI:002001
dnsmasq-dhcp: 705858459 available DHCP subnet: 172.0.0.0/255.0.0.0
dnsmasq-dhcp: 705858459 vendor class: PXEClient:Arch:00000:UNDI:002001
dnsmasq-dhcp: 705858459 PXE(ens33) 172.16.80.3 00:0c:29:12:8b:9b netboot.xyz.kpxe
dnsmasq-dhcp: 705858459 tags: bios, ens33
dnsmasq-dhcp: 705858459 bootfile name: netboot.xyz.kpxe
dnsmasq-dhcp: 705858459 next server: 172.16.80.2
dnsmasq-dhcp: 705858459 sent size:  1 option: 53 message-type  5
dnsmasq-dhcp: 705858459 sent size:  4 option: 54 server-identifier  172.16.80.2
dnsmasq-dhcp: 705858459 sent size:  9 option: 60 vendor-class  50:58:45:43:6c:69:65:6e:74
dnsmasq-dhcp: 705858459 sent size: 17 option: 97 client-machine-id  00:56:4d:e4:12:5a:70:00:5b:07:fb:d3:7b:cc...
dnsmasq-dhcp: 705858459 sent size:  7 option: 43 vendor-encap  47:04:80:00:00:00:ff
dnsmasq-tftp: error 0 TFTP Aborted received from 172.16.80.3
dnsmasq-tftp: failed sending /tftproot/netboot.xyz.kpxe to 172.16.80.3
dnsmasq-tftp: sent /tftproot/netboot.xyz.kpxe to 172.16.80.3

Now I run the same thing inside a Docker container:

docker run --rm -it \
    --cap-add NET_ADMIN \
    -v /tftproot:/tftproot:ro \
    -v $(pwd)/dnsmasq.conf:/etc/dnsmasq.conf:ro \
    -p 67:67/udp \
    -p 69:69/udp \
    -p 4011:4011/udp \
    andyshinn/dnsmasq:2.81 --no-daemon

The container detects the client performing a DHCPDISCOVER and tries to respond:

dnsmasq-dhcp: 705858459 available DHCP subnet: 172.0.0.0/255.0.0.0
dnsmasq-dhcp: 705858459 vendor class: PXEClient:Arch:00000:UNDI:002001
dnsmasq-dhcp: 705858459 PXE(eth0) 00:0c:29:12:8b:9b proxy
dnsmasq-dhcp: 705858459 tags: bios, eth0
dnsmasq-dhcp: 705858459 broadcast response
dnsmasq-dhcp: 705858459 sent size:  1 option: 53 message-type  2
dnsmasq-dhcp: 705858459 sent size:  4 option: 54 server-identifier  172.17.0.2
dnsmasq-dhcp: 705858459 sent size:  9 option: 60 vendor-class  50:58:45:43:6c:69:65:6e:74
dnsmasq-dhcp: 705858459 sent size: 17 option: 97 client-machine-id  00:56:4d:e4:12:5a:70:00:5b:07:fb:d3:7b:cc...
dnsmasq-dhcp: 705858459 sent size: 31 option: 43 vendor-encap  06:01:03:0a:04:00:50:58:45:08:07:80:00:01...
[above repeats a few times]

The client does not acknowledge the network boot information when dnsmasq is run with the identical configuration inside a Docker container.

I have two theories:

  1. The packets sent to the client never make it there, due to some peculiarity of Docker networking.

  2. The server-identifier being sent back from dnsmasq has the IP address of the Docker container's interface, which is not the network-facing IP address of the host running the container (which should be 172.16.80.2).

Best Answer

dnsmasq responds to the client using a UDP broadcast packet on port 68, which does not make it out of the bridged Docker network unless using --net=host.

One workaround is to add a socat listener on the Docker network that forwards to the host:

socat:
  image: alpine/socat
  command: >
    UDP4-LISTEN:68,reuseaddr,fork
    TCP4:172.18.0.1:24000

and then listen on the host and re-broadcast to the network:

socat \
  TCP4-LISTEN:24000,bind=172.18.0.1,reuseaddr,fork \
  UDP-DATAGRAM:255.255.255.255:68,broadcast

This will get the response to the client, but there may be more work to be done.