gus

gus

Nerves Core Team

Erlang socket module for SocketCAN on Nerves device

Hi all,

I have a Raspberry Pi Nerves device which I have attached a MCP2515 SPI to CAN transceiver board. I can get this board to work fine with regular raspbian, and I just send commands using candump and cansend utilities from the canutils tool suite. I’ve got to the point where I have a slightly customized buildroot image of the standard rpi3 firmware to enable the MCP2515 board and include the SocketCAN kernel modules (heavily based off of the work done by @brien and his 2018 talk titled “Customize Your Car: An Adventure in Using Elixir and Nerves to Hack Your Vehicle’s Electronics Network”. Huge thank you :folded_hands:)

So far, so good. I was testing using the ng_can library, and indeed able to read the data. However, I have found the library to be a little bit less flexible than I need for my application. Particularly, it sets a fixed can bus bitrate at initialization. Additionally, the library hasn’t been updated in several years, and I’d like to avoid pulling in the C NIF dependencies. Plus, I’ve been looking for an excuse to finally create and publish my own library, and it is one that help me better cement my understanding of several OTP concepts.

So I’m thinking about trying to create a pure-elixir library for SocketCAN. I was browsing this thread on community automotive projects, which hints that this should be possible with just the :socket module from Erlang.

Here’s where my trouble starts: I’ll be the first to admit that the Erlang documentation is daunting to me, having spent all my time in Elixir thus far. I can’t understand just quite why I can’t get the socket connection to the system SocketCAN to work, or if I am even approaching it in the right way. Maybe the problem is that it isn’t even supported on my hardware for some reason - though theoretically it should, since I have the kernel modules properly installed (ng_can does work).

Some reference: the SocketCAN docs say that the socket connection can be opened in C with s = socket(PF_CAN, SOCK_RAW, CAN_RAW);. This is pretty much the same thing for the python-can library implementation, which is sock = socket.socket(constants.PF_CAN, socket.SOCK_RAW, constants.CAN_RAW)

I think my main confusion is how to emulate these parameters in :socket.open/3 (though maybe I need :socket.open/4?).

Below is a couple of things I’ve tried/some extra info.

Also, I tried to join the Elixir slack to be part of the Nerves community there, but I can’t find any valid join link. If someone could shoot one my way I would be grateful :slight_smile:

Thank you!
Gus

iex(nerves@nerves.local)1> :socket.open(:local, :raw, :pf_can)
{:error, {:invalid, {:protocol, :pf_can}}}
iex(nerves@nerves.local)2> cat "/etc/protocols"               
# Internet (IP) protocols
#
# Updated from http://www.iana.org/assignments/protocol-numbers and other
# sources.

ip      0       IP              # internet protocol, pseudo protocol number
hopopt  0       HOPOPT          # IPv6 Hop-by-Hop Option [RFC1883]
icmp    1       ICMP            # internet control message protocol
igmp    2       IGMP            # Internet Group Management
ggp     3       GGP             # gateway-gateway protocol
ipencap 4       IP-ENCAP        # IP encapsulated in IP (officially ``IP'')
st      5       ST              # ST datagram mode
tcp     6       TCP             # transmission control protocol
egp     8       EGP             # exterior gateway protocol
igp     9       IGP             # any private interior gateway (Cisco)
pup     12      PUP             # PARC universal packet protocol
udp     17      UDP             # user datagram protocol
hmp     20      HMP             # host monitoring protocol
xns-idp 22      XNS-IDP         # Xerox NS IDP
rdp     27      RDP             # "reliable datagram" protocol
iso-tp4 29      ISO-TP4         # ISO Transport Protocol class 4 [RFC905]
dccp    33      DCCP            # Datagram Congestion Control Prot. [RFC4340]
xtp     36      XTP             # Xpress Transfer Protocol
ddp     37      DDP             # Datagram Delivery Protocol
idpr-cmtp 38    IDPR-CMTP       # IDPR Control Message Transport
ipv6    41      IPv6            # Internet Protocol, version 6
ipv6-route 43   IPv6-Route      # Routing Header for IPv6
ipv6-frag 44    IPv6-Frag       # Fragment Header for IPv6
idrp    45      IDRP            # Inter-Domain Routing Protocol
rsvp    46      RSVP            # Reservation Protocol
gre     47      GRE             # General Routing Encapsulation
esp     50      IPSEC-ESP       # Encap Security Payload [RFC2406]
ah      51      IPSEC-AH        # Authentication Header [RFC2402]
skip    57      SKIP            # SKIP
ipv6-icmp 58    IPv6-ICMP       # ICMP for IPv6
ipv6-nonxt 59   IPv6-NoNxt      # No Next Header for IPv6
ipv6-opts 60    IPv6-Opts       # Destination Options for IPv6
rspf    73      RSPF CPHB       # Radio Shortest Path First (officially CPHB)
vmtp    81      VMTP            # Versatile Message Transport
eigrp   88      EIGRP           # Enhanced Interior Routing Protocol (Cisco)
ospf    89      OSPFIGP         # Open Shortest Path First IGP
ax.25   93      AX.25           # AX.25 frames
ipip    94      IPIP            # IP-within-IP Encapsulation Protocol
etherip 97      ETHERIP         # Ethernet-within-IP Encapsulation [RFC3378]
encap   98      ENCAP           # Yet Another IP encapsulation [RFC1241]
#       99                      # any private encryption scheme
pim     103     PIM             # Protocol Independent Multicast
ipcomp  108     IPCOMP          # IP Payload Compression Protocol
vrrp    112     VRRP            # Virtual Router Redundancy Protocol [RFC5798]
l2tp    115     L2TP            # Layer Two Tunneling Protocol [RFC2661]
isis    124     ISIS            # IS-IS over IPv4
sctp    132     SCTP            # Stream Control Transmission Protocol
fc      133     FC              # Fibre Channel
mobility-header 135 Mobility-Header # Mobility Support for IPv6 [RFC3775]
udplite 136     UDPLite         # UDP-Lite [RFC3828]
mpls-in-ip 137  MPLS-in-IP      # MPLS-in-IP [RFC4023]
manet   138                     # MANET Protocols [RFC5498]
hip     139     HIP             # Host Identity Protocol
shim6   140     Shim6           # Shim6 Protocol [RFC5533]
wesp    141     WESP            # Wrapped Encapsulating Security Payload
rohc    142     ROHC            # Robust Header Compression
iex(nerves@nerves.local)3> :socket.supports()
[
  ioctl_requests: [
    siftxqlen: true,
    sifmtu: true,
    sifdstaddr: true,
    sifbrdaddr: true,
    sifaddr: true,
    giftxqlen: true,
    gifnetmask: true,
    gifname: true,
    gifmtu: true,
    gifmap: true,
    gifindex: true,
    gifhwaddr: true,
    gifflags: true,
    gifdstaddr: true,
    gifconf: true,
    gifbrdaddr: true,
    gifaddr: true,
    sifflags: true
  ],
  ioctl_flags: [
    staticarp: false,
    slave: true,
    simplex: false,
    renaming: false,
    promisc: true,
    ppromisc: false,
    portsel: true,
    pointopoint: true,
    oactive: false,
    notrailers: true,
    noarp: true,
    master: true,
    lower_up: false,
    link2: false,
    link1: false,
    link0: false,
    knowsepoch: false,
    echo: false,
    dynamic: true,
    dying: false,
    dormant: false,
    cantconfig: false,
    automedia: true,
    allmulti: true,
    nogroup: false,
    multicast: true,
    up: true,
    loopback: true,
    broadcast: true,
    debug: true,
    running: true,
    monitor: false
  ],
  options: [
    {{:ipv6, :authhdr}, true},
    {{:ipv6, :use_min_mtu}, false},
    {{:socket, :busy_poll}, false},
    {{:IPv6, :pktoptions}, false},
    {{:IP, :options}, false},
    {{:IP, :transparent}, true},
    {{:ipv6, :recvpktinfo}, true},
    {{:socket, :acceptconn}, true},
    {{:IP, :dontfrag}, false},
    {{:IP, :recvif}, false},
    {{:IPv6, :router_alert}, true},
    {{:ip, :multicast_if}, true},
    {{:IPv6, :recvtclass}, true},
    {{:ip, :freebind}, true},
    {{:socket, :domain}, true},
    {{:socket, :reuseaddr}, true},
    {{:ipv6, :portrange}, false},
    {{:tcp, :maxseg}, true},
    {{:IPv6, :drop_membership}, true},
    {{:ipv6, :unicast_hops}, true},
    {{:IP, :recvdstaddr}, false},
    {{:tcp, :info}, false},
    {{:IP, :multicast_if}, true},
    {{:IP, :hdrincl}, true},
    {{:ipv6, :faith}, false},
    {{:IPv6, :tclass}, true},
    {{:ipv6, :rthdr}, true},
    {{:TCP, :maxseg}, true},
    {{:ip, :sendsrcaddr}, false},
    {{:ipv6, :addrform}, true},
    {{:socket, :rxq_ovfl}, false},
    {{:ipv6, :flowinfo}, false},
    {{:ip, :dontfrag}, false},
    {{:socket, :dontroute}, true},
    {{:IPv6, :recvpktinfo}, true},
    {{:IP, :tos}, true},
    {{:ipv6, :recvhoplimit}, true},
    {{:ipv6, :add_membership}, true},
    {{:IPv6, :checksum}, false},
    {{:IPv6, :rthdr}, true},
    {{:IPv6, :dstopts}, true},
    {{:ipv6, :multicast_if}, true},
    {{:socket, :priority}, true},
    {{:IPv6, :v6only}, true},
    {{:ipv6, ...}, true},
    {{...}, ...},
    {...},
    ...
  ],
  msg_flags: [
    peek: true,
    oob: true,
    nosignal: true,
    errqueue: true,
    eor: true,
    ctrunc: true,
    confirm: true,
    cmsg_cloexec: true,
    dontroute: true,
    trunc: true,
    more: true
  ],
  protocols: [
    DCCP: true,
    hopopt: true,
    egp: true,
    icmp: true,
    EGP: true,
    wesp: true,
    udp: true,
    RSPF: true,
    HOPOPT: true,
    skip: true,
    CPHB: true,
    vmtp: true,
    "IPv6-Opts": true,
    ROHC: true,
    "mpls-in-ip": true,
    RDP: true,
    fc: true,
    IPv6: true,
    PUP: true,
    PIM: true,
    ipcomp: true,
    IDRP: true,
    igp: true,
    GGP: true,
    L2TP: true,
    "IPSEC-ESP": true,
    ah: true,
    EIGRP: true,
    sctp: true,
    WESP: true,
    "ipv6-icmp": true,
    ISIS: true,
    UDPLite: true,
    "IPv6-NoNxt": true,
    idrp: true,
    "IPv6-ICMP": true,
    ICMP: true,
    ST: true,
    TCP: true,
    vrrp: true,
    rsvp: true,
    RSVP: true,
    ggp: true,
    "iso-tp4": true,
    DDP: true,
    ...
  ],
  sctp: false,
  ipv6: true,
  local: true,
  netns: true,
  sendfile: true
]
iex(nerves@nerves.local)4> :socket.open(29, :raw)             
{:error, :eprotonosupport}
iex(nerves@nerves.local)5> "29 is the value defined for PF_CAN in socket.h"
"29 is the value defined for PF_CAN in socket.h"
iex(nerves@nerves.local)6> :socket.open(29, :raw, :local)
{:error, {:invalid, {:protocol, :local}}}
iex(nerves@nerves.local)7> :socket.open(29, :raw, :ip)   
{:error, :eprotonosupport}
iex(nerves@nerves.local)8> :socket.open(29, :raw, :ipv6)
{:error, :einval}
iex(nerves@nerves.local)9> :socket.open(29, :raw, :tcp) 
{:error, :eprotonosupport}
iex(nerves@nerves.local)10> :socket.open(29, :raw, :udp)
{:error, :einval}
iex(nerves@nerves.local)11>

Marked As Solved

hauleth

hauleth

So few things:

  • Erlang do not exposè PF_CAN for you. You need to check your OS and extract respective integer value on your own. I know this can be daunting, but with Python you should be able to do it. I also have looked up that in the SocketCAN project and found that it is equal to 29.
  • It also do not exposè CAN_RAW protocol value as well, so you need to use integer value there. Some digging in Linux headers shows that it is value 1.

Now we need to apply it to our code:

defmodule GenCAN do
  @pf_can 29

  @can_raw

  # You probably can come up with more flexible implementation than that
  def open, do: :socket.open(@pf_can, :raw, @can_raw)
end

Also Liked

jhogberg

jhogberg

Erlang Core Team

#{ ... snip ... } is a map, not a record. Use %{ :family => 29, :addr => address } instead.

Note that address has to be a valid struct sockaddr_can in binary form, just as it would appear in memory for the bind(2) call.

__kernel_sa_family_t seems to be a short, so 16 bits. You then have an int which is 32 bits, but alignment restrictions most likely mean that it’s most likely preceded by 16 bits of padding. There’s also tail padding to consider but I’m not sure how much that will be in this case.

<<family::size(16)-little,
  0::size(16)-little, # Padding
  ifIndex::size(32)-little,
  rxId::size(32)-little,
  txId::size(32)-little,
  0::size(40)>> # Experiment until it works?
pgorczak

pgorczak

So I ran into this today and figured out the issue with the address data.

The :socket.bind/2 function takes a map with :family and :addr keys. Their values get copied into the sa_family and sa_data fields of a sockaddr struct in the erlang source.

struct sockaddr addr;
((struct sockaddr_can*) &addr)->can_ifindex = ifindex;
return addr.sa_data; // <- this is what we want to pass to :socket.bind

This means, your approach works, when cutting off the first field of the binary:

addr = <<0::size(16)-little, ifindex::size(32)-little, 0::size(32), 0::size(32), 0::size(64)>>

The initial 2 Bytes of padding remain–I think because sockaddr is aligned to 2 Bytes while sockaddr_can is aligned to 4 Bytes (can_ifindex being an int).

Where Next?

Popular in Questions Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44265 214
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement