@Override
    protected void doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (!(remoteAddress instanceof SocketCANAddress)) {
            logger.error("Expecting remote address of type SocketCANAddress");
            pipeline().fireExceptionCaught(new IllegalArgumentException("Expecting remote address of type SocketCANAddress"));
            return;
        }
        this.localAddress = localAddress;
        remoteRawSocketAddress = (SocketCANAddress) remoteAddress;

        // Try to get the device name of the network interface that we want to open.
        String interfaceName = getInterfaceName(remoteRawSocketAddress);
        if(interfaceName == null) {
            logger.error("Interface name is not specified and couldn't detect it automatically");
            pipeline().fireExceptionCaught(new IllegalArgumentException("Interface name is not specified and couldn't detect it automatically"));
            return;
        }

        NetworkDevice device = LinuxNetworkDevice.lookup(interfaceName);
        // Get a handle to the network-device and open it.
        handle = CanChannels.newRawChannel(device);

        if(logger.isDebugEnabled()) {
            logger.debug(String.format("Listening on device %s", interfaceName));
        }

        // TODO If the address allows fine tuning which packets to process, set a filter to reduce the load.
//        String filter =
//        if(filter.length() > 0) {
//            handle.setFilter(filter, BpfProgram.BpfCompileMode.OPTIMIZE);
//        }

        // Create a buffer where the raw socket worker can send data to.
        ByteBuf buffer = Unpooled.buffer();

        // Start a thread that processes the callbacks from the raw socket and simply
        // forwards the bytes read to the buffer.
        loopThread = new Thread(() -> {
            try {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(16);
                while (!isInputShutdown()) {
                    handle.readUnsafe(byteBuffer);
                    buffer.writeBytes(byteBuffer);
                    byteBuffer.rewind();
                }
            } catch (Exception e) {
                logger.warn("Could not read data", e);
                pipeline().fireExceptionCaught(e);
            } catch (Throwable e) {
                logger.warn("Fatal error while handling CAN communication", e);
                pipeline().fireExceptionCaught(e);
            }
        }, "javacan-reader");
        loopThread.start();

        activate(new CANInputStream(buffer), new CANOutputStream(handle));
    }
A naive copy of pcap channel implementation which allows to pass over data to PLC4X as well as drop it back to JavaCAN.
Sadly all this involves double parsing.
plc4j/transports/socketcan/src/main/java/org/apache/plc4x/java/transport/socketcan/netty/SocketCANChannel.java