Kathara
Kathara copied to clipboard
Running a DHCP server inside Kathará (improving Netkit compatibility)
Kathará currently has two limitations with respect to Netkit that complicate the deployment of a DHCP server inside a lab.
- By default Docker bind-mounts both files
/etc/hosts
and/etc/resolv.conf
limiting the ability of the DHCP client to change the last one when a DNS server is specified ; - Netkit provides deterministic pseudo-random MAC address for the machines interfaces (thanks to this patch), facilitating static DHCP leases.
To circumvent these limitations, we currently invoke the following shell script from shared.startup
:
umount /etc/resolv.conf
umount /etc/hosts
echo '127.0.0.1 localhost' > /etc/hosts
for eth in $(ip -br a | grep '^eth' | cut -d@ -f1); do
ip link set $eth address $(/shared/mojo/macaddr $HOSTNAME $eth)
done
where macaddr
is a small Python script:
#!/usr/bin/env python3
from hashlib import sha1
from sys import argv
m = sha1()
m.update(argv[1].encode())
m.update(b"-")
m.update(argv[2].encode())
d = m.digest()
addr = [ (d[i]+d[i+6]) % 256 for i in range(6) ]
addr[0] &= 0xfe
addr[0] |= 0x02
print(':'.join(map(lambda x : ("00"+hex(x)[2:])[-2:],addr)))
It might be possible to directly provide these functionality inside Kathará.
Hi @nopid, unfortunately we cannot replicate the same function used in Netkit. I'll briefly explain what happens in the Network Plugin.
We create a veth pair with random names (automatically assigned by Linux), then one end of the veth is attached to the Linux bridge corresponding to the requested collision domain, and then we just say to Docker: "the other end will be attached to this endpoint", without specifying the interface name, but only the prefix eth
(here).
So, during the process we don't know the name of the container (but only the ID of the "network endpoint") and the final name of the network interface. However, we generate the MAC address from the network ID and the endpoint ID (here). I don't know if you can retrieve such information from docker inspect
at runtime, otherwise your solution seems a good workaround.
About /etc/resolv.conf
and /etc/hosts
, your solution seems good. I workarounded the overwrite of the /etc/resolv.conf
file with a nasty trick (here). But using umount
is better. I'll patch that in the next release.
Hi @Skazza94,
Thank you for the explanation and the pointer to the code where you generate the mac address from the network ID and endpoint ID. Of course, as these IDs vary on lab restart the provided mac addresses cannot be used for static DHCP configuration.
Reading through both the Docker SDK for Python documentation and the network package for Go documentation, it looks like the Network Plugin can receive drivers options during Join Requests (see Options
in https://pkg.go.dev/github.com/docker/go-plugins-helpers/network#JoinRequest) that could be send during the connect call on the Python side (see driver_opts
in https://docker-py.readthedocs.io/en/stable/networks.html#docker.models.networks.Network.connect). This might provide a channel to transmit a requested MAC address from Kathará to Docker during the network connection?
Hi @nopid,
the solution you suggest seems good. In this way we can pass the machine name and the interface number (without eth
).
I'm just concerned about one last thing.
We can pass the dict only with the connect
command of Network
. However, the first network of the device is attached on container creation (here), otherwise Docker will not create a network stack for the container (setting Network="none") and any other network can't be connected later. I cannot find a way to pass the dict in the Container.create
method: https://docker-py.readthedocs.io/en/stable/containers.html#docker.models.containers.ContainerCollection.run.
EDIT: I opened an issue on Dockerpy, let's see what happens!
Hi @Skazza94,
The good news is that the low-level api seems to support a network_config
parameter that can carry some driver_opt
(via create_networking_config
and create_endpoint_config
) so it might be just a matter of adding support for it inside _create_container_args
. Let's see what happen with your https://github.com/docker/docker-py/issues/2896 issue.
Hi @nopid, I know that this is a really old issue.
Some time ago I implemented the driver_opt
in container run
/create
and finally now it is merged in dockerpy
6.1.0!
I will start to implement deterministic MAC addresses with the logic that we discussed (i.e. using device name and interface index).
Best regards.