Part 4 showed ACF-CAN tunneling using Open1722’s user-space example applications. This part goes one level deeper: the Open1722 Linux kernel module (examples/acf-can/linux-kernel-mod/), which implements ACF-CAN tunneling directly in the kernel — not using the two veth interfaces on a single machine from the upstream example, but between two physical BeagleBone Black boards connected via an Ethernet cable.

This post is meant as a self-contained hands-on project, grounded in the real Readme.md from examples/acf-can/linux-kernel-mod/ in the Open1722 repository — not in the AUTOSAR module IEEE1722Tp itself. Open1722 implements the general IEEE 1722 standard directly; the kernel module is an experimental community project (a "fun experiment" per its own README), not an AUTOSAR BSW module. The learning value (getting hands-on with ACF-CAN) still carries over 1:1 to the concept from Part 2.

Architecture: ACF-CAN as an ordinary SocketCAN interface

The trick behind the kernel module: from user space, there’s no difference between a real CAN driver (hardware CAN controller) and the acfcan network device — both serve the normal Linux SocketCAN interface. cansend/candump work identically, regardless of whether there’s a real CAN bus or ACF-CAN over Ethernet behind them.

ACF-CAN kernel module between two real BeagleBone Black boards
FeatureStatus per the Open1722 README

CAN and CAN FD

Supported

Configurable stream and bus ID

Supported

NTSCF

Supported

Sending and receiving

Supported

TSCF

Not yet supported

CAN-BRIEF

Not yet supported

Batching (multiple ACF-CAN messages per frame)

Not yet supported — currently exactly one ACF-CAN message per IEEE 1722 frame

Per its own README, the kernel module is explicitly "in active development" — it deliberately only supports NTSCF, not TSCF, and doesn’t yet bundle multiple ACF messages per frame (unlike the IEEE1722TpStreamAcfMixedBusTypeCollection described in Part 2). That’s entirely sufficient for a first hands-on encounter with ACF-CAN.

Cross-compiling against the BeagleBone Black’s Yocto kernel

Instead of the Ubuntu host described in the Open1722 README, this cross-compiles against the Yocto kernel already shown in the kernel module series:

source /opt/poky/*/environment-setup-cortexa8hf-neon-poky-linux-gnueabi

cd Open1722/examples/acf-can/linux-kernel-mod
export CONFIG_ACF_CAN=m
make KDIR=$KERNEL_SRC

$KERNEL_SRC points to the configured kernel source tree of the Yocto build (tmp/work-shared/beaglebone-yocto/kernel-source) — exactly the same kernel version and configuration running on the target, otherwise loading fails with a version mismatch (see the kernel module post).

This build step is identical on both boards — the module has to be built (or at least copied) and loaded individually on each BeagleBone Black.

Loading the module

On both boards, first load the CAN subsystem dependency, then the module itself:

sudo modprobe can
sudo insmod acfcan.ko

modprobe can is needed so that all symbols required by the CAN subsystem are already loaded into the kernel — without this step, insmod acfcan.ko fails with missing symbols the first time it’s loaded from a local directory.

Setup: two physical interfaces instead of two veth pairs

The upstream example in the Open1722 README shows two acfcan interfaces connected via a veth pair on one machine (ip link add dev mon1 type veth peer name mon2). Here, the same principle happens instead over a real Ethernet cable between two boards.

On board A

sudo ip link add dev ecu0 type acfcan
sudo ip link set ecu0 mtu 72

# ethif points at the board's real eth0, not a veth interface
echo -n "eth0" | sudo tee /sys/class/net/ecu0/acfcan/ethif

# dstmac is board B's real MAC address
echo -n "<Board B's MAC address>" | sudo tee /sys/class/net/ecu0/acfcan/dstmac

echo -n "cafe11" | sudo tee /sys/class/net/ecu0/acfcan/tx_streamid
echo -n "dead22" | sudo tee /sys/class/net/ecu0/acfcan/rx_streamid

sudo ip link set up ecu0

On board B

sudo ip link add dev ecu1 type acfcan
sudo ip link set ecu1 mtu 72

echo -n "eth0" | sudo tee /sys/class/net/ecu1/acfcan/ethif
echo -n "<Board A's MAC address>" | sudo tee /sys/class/net/ecu1/acfcan/dstmac

echo -n "dead22" | sudo tee /sys/class/net/ecu1/acfcan/tx_streamid
echo -n "cafe11" | sudo tee /sys/class/net/ecu1/acfcan/rx_streamid

sudo ip link set up ecu1

Board A’s tx_streamid must exactly match board B’s rx_streamid, and vice versa — the stream IDs have to be mirrored in opposite directions. Get this wrong, and the other side silently drops the ACF-CAN packets without an obvious error. All sysfs options under /sys/class/net/<devname>/acfcan/ can only be set while the interface is down.

Test: real CAN traffic between two boards

# Listen on board B
candump ecu1

# Send on board A
cansend ecu0 123#DEADBEEF

The message shows up on board B, even though there’s no real CAN bus between the boards — it was tunneled entirely over the Ethernet cable as an ACF-CAN message. Unlike the upstream veth example, this isn’t a loopback on a single machine, but real network traffic between two physical devices.

Running tcpdump -i eth0 -w capture.pcap in parallel on one of the boards (or on a TSN switch from the EthSwt series sitting in between) lets you inspect the IEEE 1722 frame in Wireshark — the NTSCF header and ACF-CAN payload are directly visible there, including the stream IDs configured earlier.

Diagnostics

dmesg --follow

# Enable dynamic debug, if the kernel supports it
echo 'module acfcan +p' | sudo tee /sys/kernel/debug/dynamic_debug/control
ProblemTypical cause

insmod fails with missing symbols

Forgot modprobe can

No traffic visible in candump

tx_streamid/rx_streamid not mirrored correctly, or wrong dstmac

insmod fails with a version mismatch

Module not built against exactly the kernel running on the board — see the kernel module post

Secure Boot prevents loading

Rarely relevant in practice for a self-built Yocto kernel on the BeagleBone Black — more relevant for Ubuntu hosts with Secure Boot enabled

Conclusion: the first building block for a zonal gateway experiment

This setup — a BeagleBone Black transparently tunneling its own (simulated) CAN subnet over a TSN-capable Ethernet backbone to a "central" node — is, in miniature, exactly the scenario from Part 2: a partially migrated zonal architecture where a zonal controller transparently tunnels legacy CAN traffic without the actual CAN ECUs noticing anything. Two BeagleBone Black boards and an Ethernet cable are enough to make this principle tangible before moving on to a real, multi-layered vehicle network.

Summary

AspectKey takeaway

Architecture

acfcan looks like an ordinary SocketCAN interface from user space — cansend/candump work unchanged

Build

Cross-compile against the Yocto kernel source tree, exactly as in the kernel module fundamentals post — CONFIG_ACF_CAN=m, modprobe can before insmod

Setup

Two physical acfcan interfaces, ethif pointing at the real eth0, dstmac set to the real peer, tx_streamid/rx_streamid mirrored in opposite directions

Test

cansend/candump across two physical boards, tcpdump to make the IEEE 1722 frame visible

Takeaway

A small, concretely followable example of the zonal-gateway scenario from Part 2 — not an AUTOSAR BSW module, but the same underlying principle