Using SocketCAN in .NET Core

By | February 4, 2021

SocketCAN is a powerful tool for creating CAN Bus oriented applications on Linux. Popular choices for interfacing with SocketCAN are of course C/C++ or Python using the python-can package. As a huge fan of both SocketCAN and C#, I challenged myself to write the current Wikipedia example entirely in C#. In order to achieve this, .NET has Platform Invocation Services (P/Invoke) that allows managed code to call unmanaged code. For example, operating system libraries or 3rd party libraries written in an unmanaged language like C/C++ can be accessed by a managed .NET language such as C# using P/Invoke. This has come up in my professional career of developing vehicle network communication tools where many vehicle communication libraries are typically written in C or C++. P/Invoke can be leveraged in order to bridge the gap between an application written entirely in C# (and optionally XAML) and an unmanaged device API.

Here is the SocketCAN sample code from Wikipedia that I will be porting to C#:

int main(void)
{
	int s;
	int nbytes;
	struct sockaddr_can addr;
	struct can_frame frame;
	struct ifreq ifr;

	const char *ifname = "vcan0";

	if((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) == -1) {
		perror("Error while opening socket");
		return -1;
	}

	strcpy(ifr.ifr_name, ifname);
	ioctl(s, SIOCGIFINDEX, &ifr);
	
	addr.can_family  = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;

	printf("%s at index %d\n", ifname, ifr.ifr_ifindex);

	if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
		perror("Error in socket bind");
		return -2;
	}

	frame.can_id  = 0x123;
	frame.can_dlc = 2;
	frame.data[0] = 0x11;
	frame.data[1] = 0x22;

	nbytes = write(s, &frame, sizeof(struct can_frame));

	printf("Wrote %d bytes\n", nbytes);
	
	return 0;
}

The SocketCAN code above makes use of several constants. There are constants for the Address Family, Protocol Family, the Socket Type, and later an ioctl SIOCGIFINDEX used for looking up an interface index by name. I have defined these constants in a simple static class.

public static class Constants
{
    // Address Family CAN
    public const int AF_CAN = 29; // Per socket.h
    // Protocol Family CAN
    public const int PF_CAN = 29; // Per socket.h
    // Raw Protocol Interface
    public const int SOCK_RAW = 3; // Per socket_type.h
    // Socket Configuration Control: name -> if_index mapping
    public const int SIOCGIFINDEX = 0x8933; // Per ioctls.h
}

SocketCAN also defines several different Protocol Types that can be used within the CAN Protocol Family. The Protocol Types combined with the Protocol Family and Socket Type constants above are what we need to pass into the socket libc function. In this case it makes the most sense to utilize an enum in order to define the Protocol Types in C#.

public enum ProtocolType 
{
    /* RAW sockets */
    CAN_RAW	= 1,
    /* Broadcast Manager */
    CAN_BCM	= 2, 
    /* VW Transport Protocol v1.6 */
    CAN_TP16	= 3,
    /* VW Transport Protocol v2.0 */
    CAN_TP20	= 4, 
    /* Bosch MCNet */
    CAN_MCNET	= 5, 
    /* ISO 15765-2 Transport Protocol */
    CAN_ISOTP	= 6,
    /* SAE J1939 */
    CAN_J1939   = 7,
    CAN_NPROTO	= 8,
}

The ioctl function in libc makes use of an interface request structure called ifreq and is defined in if.h.

struct ifreq
  {
# define IFHWADDRLEN	6
# define IFNAMSIZ	IF_NAMESIZE
    union
      {
	char ifrn_name[IFNAMSIZ];	/* Interface name, e.g. "en0".  */
      } ifr_ifrn;

    union
      {
	struct sockaddr ifru_addr;
	struct sockaddr ifru_dstaddr;
	struct sockaddr ifru_broadaddr;
	struct sockaddr ifru_netmask;
	struct sockaddr ifru_hwaddr;
	short int ifru_flags;
	int ifru_ivalue;
	int ifru_mtu;
	struct ifmap ifru_map;
	char ifru_slave[IFNAMSIZ];	/* Just fits the size */
	char ifru_newname[IFNAMSIZ];
	__caddr_t ifru_data;
      } ifr_ifru;
  };

The members we care about in each union are interface name and index:

# define ifr_name	ifr_ifrn.ifrn_name   /* interface name */
# define ifr_ifindex	ifr_ifru.ifru_ivalue /* interface index */

The C# Ifreq class below represents the above ifreq struct in C. The StructLayout attribute is used to control the memory layout of the unmanaged type. The Sequential option takes the members in the managed type and places them in the same order as they appear in unmanaged memory. Additionally, we specify that the Name string needs to be marshaled as a fixed-length character array of size 16.

// Interface Request Structure used for Socket IOCTLs
[StructLayout(LayoutKind.Sequential)]
public class Ifreq
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=16)]
    public string Name;

    public int IfIndex { get; set; }

    public Ifreq(string name)
    {
        Name = name;
        IfIndex = 0;
    }
}

The bind function in libc is passed a sockaddr_can struct that specifies the address family and index for the CAN interface. There is also the ISO-TP CAN Transmit and Response ID information that we will not make use of in this demo, but it is necessary to be aware of these members for marshalling purposes.

struct sockaddr_can {
        sa_family_t can_family;
        int         can_ifindex;
        union {
                /* transport protocol class address info (e.g. ISOTP) */
                struct { canid_t rx_id, tx_id; } tp;

                /* reserved for future CAN protocols address information */
        } can_addr;
};

Creating the managed class counterpart for this struct is about as easy as it gets as all members of the C struct are integer-based. We specify the Sequential StructLayout to ensure the unmanaged type is properly laid out in memory.

[StructLayout(LayoutKind.Sequential)]
public class SockAddrCan
{
    public ushort CanFamily { get; set; }
    public int CanIfIndex { get; set; }
    public uint RxId { get; set; }
    public uint TxId { get; set; }
}

The last struct we need to model is can_frame which is what we will be sending to the write function of libc.

struct can_frame {
        canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
        __u8    can_dlc; /* frame payload length in byte (0 .. 8) */
        __u8    __pad;   /* padding */
        __u8    __res0;  /* reserved / padding */
        __u8    __res1;  /* reserved / padding */
        __u8    data[8] __attribute__((aligned(8)));
};

The can_frame is simple to model as well in C# with the exception that we have to specify that the Data byte array is marshaled as an array of elements of size 8. Again, we use Sequential StructLayout to dictate how the unmanaged type should be laid out in memory by using the order defined in the C# class counterpart.

[StructLayout(LayoutKind.Sequential)]
public class CanFrame
{
    public uint CanId { get; set; }      
    public byte Dlc { get; set; }  
    public byte Pad { get; set; }
    public byte Res0 { get; set; } 
    public byte Res1 { get; set; }
    
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=8)]
    public byte[] Data;

    public CanFrame()
    {
        Data = new byte[8];
    }
}

Lastly, there is the libc function calls socket, ioctl, bind, and write that we need to invoke. To do this we use the DllImport attribute and specify libc as the library we are using. We also set SetLastError to true in order to potentially utilize errno via Marshal.GetLastWin32Error.

[DllImport("libc", EntryPoint="socket", SetLastError=true)]
public static extern IntPtr Socket(int addressFamily, int socketType, ProtocolType protocolType);

[DllImport("libc", EntryPoint="ioctl", SetLastError=true)]
public static extern int Ioctl(IntPtr socketHandle, int request, [In][Out] Ifreq ifreq);

[DllImport("libc", EntryPoint="bind", SetLastError=true)]
public static extern int Bind(IntPtr socketHandle, SockAddrCan addr, int addrSize);

[DllImport("libc", EntryPoint="write", SetLastError=true)]
public static extern int Write(IntPtr socketHandle, CanFrame frame, int frameSize);

Putting this altogether we get the following C# port of the original C code.

static void Main(string[] args)
{
    IntPtr socketHandle = NativeMethods.Socket(Constants.PF_CAN, Constants.SOCK_RAW, ProtocolType.CAN_RAW);
    Console.WriteLine($"Socket Handle: {socketHandle}");

    if (socketHandle != new IntPtr(-1))
    {
        var ifr = new Ifreq("vcan0");
        int ioctlResult = NativeMethods.Ioctl(socketHandle, Constants.SIOCGIFINDEX, ifr);
        Console.WriteLine($"Ioctl Return Code: {ioctlResult}");
        
        if (ioctlResult != -1)
        {
            var addr = new SockAddrCan()
            {
                CanFamily = Constants.AF_CAN,
                CanIfIndex = ifr.IfIndex,
            };

            Console.WriteLine($"{ifr.Name} at index {ifr.IfIndex}");

            int bindResult = NativeMethods.Bind(socketHandle, addr, Marshal.SizeOf(typeof(SockAddrCan)));
            Console.WriteLine($"Bind Return Code: {bindResult}");
            if (bindResult != -1)
            {
                var canFrame = new CanFrame()
                {
                    CanId = 0x123,
                    Dlc = 2,
                };
                canFrame.Data[0] = 0x11;
                canFrame.Data[1] = 0x22;

                int nBytes = NativeMethods.Write(socketHandle, canFrame, Marshal.SizeOf(typeof(CanFrame)));
                Console.WriteLine($"Wrote {nBytes} bytes");
            }
        }
    }
}

Output from this application will look something like this:

Socket Handle: 27
Ioctl Return Code: 0
vcan0 at index 4
Bind Return Code: 0
Wrote 16 bytes

If we listen with candump on vcan0 and then run this program we will see the following:

candump vcan0
vcan0  123   [2]  11 22

Now that we have confirmed that the application outputs the correct CAN message out onto the bus, the porting process is complete. This code is a great start to incorporating SocketCAN into applications written in C# using .NET and what is laid out here can certainly be expanded upon. Thanks as always for reading. The C# source code from this article can be downloaded here.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.