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 "https://raw.githubusercontent.com/torvalds/linux/master/drivers/net/can/vxcan.c"
{% 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
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
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" %}
make
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" %}
vxcan
can-gw
{% 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 blog@lagerdata.com