skip to Main Content

I have a project where I read system information from the host inside a container. Right now I got CPU, RAM and Storage to work, but Network turns out to be a little harder. I am using the Node.js library https://systeminformation.io/network.html, which reads the network stats from /sys/class/net/.

The only solution that I found right now, is to use --network host, but that does not seem like the best way, because it breaks a lot of other networking related stuff and I cannot make the assumption that everybody who uses my project is fine with that.

I have tried --add-host=host.docker.internal:host-gateway as well, but while it does show up in /etc/hosts, it does not add a network interface to /sys/class/net/.

My knowledge on Docker and Linux is very limited, so does someone know if there is any other way?

My workaround for now is, to use readlink -f /sys/class/net/$(ip addr show | awk '/inet.*brd/{print $NF; exit}') to get the final path to the network statistics of the default interface and mount it to a imaginary path in the container. Therefore I don’t use the mentioned systeminformation library for that right now. I would still like to have something that is a bit more reliable and in the best case officially supported by docker. I am fine with something that is not compatible with systeminformation, though.

3

Answers


  1. You could mount the host’s /sys/class/net/ directory as a volume in your container and patch the systeminformation package to read the contents of your custom path instead of the default path. The changes would need to be made in lib/network.js. You can see in that file how the directory is hardcoded throughout, just do a find/replace in your local copy to change all instances of the default path.

    Login or Signup to reply.
  2. An easy way is to mount the whole "/sys" filesystem of the host into the container. Either mount them to a new location (e.g. /sys_host) or over-mount the original "/sys" in the container:

    # docker run -it --rm -v /sys:/sys:ro debian:bullseye-slim bash
    root@b84df3184dce:/# ls -l /sys/class/net/
    lrwxrwxrwx 1 root root 0 Oct 25  2021 enp2s0 -> ../../devices/pci0000:00/0000:00:1c.1/0000:02:00.0/net/enp2s0
    lrwxrwxrwx 1 root root 0 Oct 25  2021 enp3s0 -> ../../devices/pci0000:00/0000:00:1c.2/0000:03:00.0/net/enp3s0
    [...]
    
    root@b84df3184dce:/# ls -l /sys/class/net/enp2s0/
    -r--r--r--  1 root root 4096 Oct 25  2021 addr_assign_type
    -r--r--r--  1 root root 4096 Oct 25  2021 addr_len
    -r--r--r--  1 root root 4096 Oct 25  2021 address
    -r--r--r--  1 root root 4096 Oct 25  2021 broadcast
    [...]
    

    Please be aware that this way the container has access to the whole "/sys" filesystem of the host. The relative links from the network interface to the pci device still work.

    If you don’t need to write you should mount it read-only by appending ":ro" to the mounted path.

    Login or Signup to reply.
  3. There is a way to enter the host network namespace after starting the container. This can be used to run one process in the container in the container network namespace and another process in the host network namespace. Communication between the processes can be done using a unix domain socket.

    Alternatively you can just mount a new instance of the sysfs which points to the host network namespace. If I understood correctly this is what you really need.

    For this to work you need access to the host net namespace (I mount /proc/1/ns/net to the container for this purpose). Additionally the capabilities CAP_SYS_PTRACE and CAP_SYS_ADMIN are needed.

    # /proc/1 is the 'init' process of the host which is always running in host network namespace
    $ docker run -it --rm --cap-add CAP_SYS_PTRACE --cap-add CAP_SYS_ADMIN -v /proc/1/ns/net:/host_ns_net:ro debian:bullseye-slim bash
    root@8b40f2f48808:/ ls -l /sys/class/net
    lrwxrwxrwx 1 root root 0 Jun  2 21:09 eth0 -> ../../devices/virtual/net/eth0
    lrwxrwxrwx 1 root root 0 Jun  2 21:09 lo -> ../../devices/virtual/net/lo
    
    # enter the host network namespace
    root@8b40f2f48808:/ nsenter --net=/host_ns_net bash
    
    # now we are in the host network namespace and can see the host network interfaces
    root@8b40f2f48808:/ mkdir /sys2
    root@8b40f2f48808:/ mount -t sysfs nodevice /sys2
    root@8b40f2f48808:/ ls -l /sys2/class/net/
    lrwxrwxrwx 1 root root 0 Oct 25  2021 enp2s0 -> ../../devices/pci0000:00/0000:00:1c.1/0000:02:00.0/net/enp2s0
    lrwxrwxrwx 1 root root 0 Oct 25  2021 enp3s0 -> ../../devices/pci0000:00/0000:00:1c.2/0000:03:00.0/net/enp3s0
    [...]
    
    root@8b40f2f48808:/ ls -l /sys2/class/net/enp2s0/
    -r--r--r--  1 root root 4096 Oct 25  2021 addr_assign_type
    -r--r--r--  1 root root 4096 Oct 25  2021 addr_len
    -r--r--r--  1 root root 4096 Oct 25  2021 address
    -r--r--r--  1 root root 4096 Oct 25  2021 broadcast
    [...]
    
    # Now you can switch back to the original network namespace
    # of the container; the dir "/sys2" is still accessible
    root@8b40f2f48808:/ exit
    

    Putting this together for non-interactive usage:

    Use the docker run with the following parameters:

    docker run -it --rm --cap-add CAP_SYS_PTRACE --cap-add CAP_SYS_ADMIN -v /proc/1/ns/net:/host_ns_net:ro debian:bullseye-slim bash

    Execute these commands in the container before starting your node app:

    mkdir /sys2
    nsenter --net=/host_ns_net mount -t sysfs nodevice /sys2
    

    After nsenter (and mount) exits, you are back in the network namespace of the container. In theory you could drop the extended capabilities now.

    Now you can access the network devices under /sys2/class/net.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search