Forwarding CAN Bus traffic to a Docker container using vxcan on Raspberry Pi

vxcan is a Linux kernel driver/module that can be used to set up a virtual CAN tunnel across network namespaces. For example it allows you to generate virtual CAN frames on your host and send them to a container; or forward real CAN traffic between a USB-CAN adapter and a container, without exposing the entire host network to the container.

The following instructions are for a Raspberry Pi 4 model B running Raspberry Pi OS, on kernel {% c-line %}5.4.72-v7l+{%c-line-end%}  (use {% c-line %}uname -r{%c-line-end%} to verify). It should work with fairly minor modifications for other OSes; some paths and package names may be different.

First, install some dependencies and download the vxcan module source code:

{% c-block language="console" %}
sudo apt-get update
sudo apt-get install raspberrypi-kernel-headers can-utils
mkdir vxcan
cd vxcan
wget ""
{% c-block-end %}

We'll also need a Makefile (make sure it's using tabs and not spaces!):

{% c-block language="makefile" %}
obj-m += vxcan.o

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
{% c-block-end %}

At this point you should have a directory with two files, {% c-line %}vxcan.c{% c-line-end %} and {% c-line %}Makefile{% c-line-end %}, so let's build the kernel module and load it:

{% c-block language="console" %}
sudo chown root:root vxcan.ko
sudo chmod 0644 vxcan.ko
sudo mv vxcan.ko /lib/modules/5.4.72-v7l+/kernel/net/can/
sudo depmod -A
sudo modprobe vxcan
sudo modprobe can-gw
{% c-block-end %}

Let's also add a file to {% c-line %}/etc/modules-load.d{% c-line-end %} so that the modules will load on startup. Create {% c-line %}/etc/modules-load.d/can.conf{% c-line-end %} and add the following:

{% c-block language="console" %}
{% c-block-end %}

Next, in a separate terminal let's start a container and install canutils within the container:

{% c-block language="console" %}
docker run --rm -it --name cantest ubuntu:20.04
apt-get update && apt-get install -y can-utils
{% c-block-end %}

Then in our original terminal let's set up the vxcan network and move one end of it into the container's network namespace:

{% c-block language="console" %}
DOCKERPID=$(docker inspect -f '{{ .State.Pid }}' cantest)
sudo ip link add vxcan0 type vxcan peer name vxcan1
sudo ip link set vxcan1 netns $DOCKERPID
sudo ip link set vxcan0 up
sudo nsenter -t $DOCKERPID -n ip link set vxcan1 up
{% c-block-end %}

We moved {% c-line %}vxcan1{% c-line-end %} into the container's namespace, so now back in the container we can run: {% c-line %}candump vxcan1{% c-line-end %}

Finally, in our host we can send data to {% c-line %}vxcan0{% c-line-end %} and have it show up in the container: {% c-line %}cansend vxcan0 123#1122{% c-line-end %}

Bonus point: if you have a real CAN adapter, you can also forward traffic from that adapter into the container using cangw:

{% c-block language="console" %}
sudo cangw -A -s can0 -d vxcan0 -e
sudo cangw -A -s vxcan0 -d can0 -e
{% c-block-end %}

Questions? Feedback? Want to learn more about how Lager can help debug your CAN device? Contact us at

Want to stay up to date on the future of firmware? Join our mailing list.