[issue10141] SocketCan support

Charles-François Natali report at bugs.python.org
Fri Sep 23 23:33:36 CEST 2011


Charles-François Natali <neologix at free.fr> added the comment:

>  - dummy question: why an address is a tuple with 1 string instead of just
> the string? Does AF_UNIX also uses a tuple of 1 string?

I think the reason behind the tuple is future proofing.
Here's the definition of `struct sockaddr_can` in my Linux box's headers:
"""
/**
 * struct sockaddr_can - the sockaddr structure for CAN sockets
 * @can_family:  address family number AF_CAN.
 * @can_ifindex: CAN network interface index.
 * @can_addr:    protocol specific address information
 */
struct sockaddr_can {
    sa_family_t can_family;
    int         can_ifindex;
    union {
        /* transport protocol class address information (e.g. ISOTP) */
        struct { canid_t rx_id, tx_id; } tp;

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

By making it a tuple, it will be easier to extend the address that
must be passed to bind(2), should it ever evolve, in a backward
compatible way. Well, that's just a guess (I'm by no means a SocketCAN
expert :-).

>  - the example should also use struct.pack() to create the frame, I don't
> like hardcoded BLOB

Done.

>  - in test_socket: _have_socket_can() interprets permission denied as "CAN
> is not supported", it would be nice to provide a better skip message. Create
> maybe a decorator based?

AFAICT, it shouldn't fail with EPERM or so.
Also, I'm not sure what the message would look like, and it's probably
a bit overkill.

>  - _have_socket_can(): you may move s.close() outside the try block (add
> maybe a "else:" block?) because you may hide a real bug in .close()

Changed that.

>  - data += b'\0' * (8 - can_dlc): I prefer data = data.ljust(8, '\x00')

Hum... Done.

>  - you might add frame encoder/decoder in your example

Done.

>  - if (!strcmp(PyBytes_AS_STRING(interfaceName), "")) hum.....
> PyBytes_GET_SIZE(intername)==0 should be enough

Done.

>  - you truncate the interface name, it can be surprising, I would prefer an
> error (e.g. "interface name too long: 20 characters, the maximum is 10
> characters" ?)

I changed that, and added a test. Also, note that AF_PACKET suffers
from the same problem.
I'll submit a separate patch.

>  - (oh no! don't include horrible configure diff in patches for the bug
> tracker :-p)

Yeah, I usually take care of that, but forgot this time.

> In which Linux version was CAN introduced?
>

Apparently, 2.6.25. Note that we don't need
@support.requires_linux_version() though, it should be catched by
HAVE_SOCKET_CAN (also, you can't use it as a class decorator...).

Here's the updated patch. It passes on all the buildbots (of course,
it's only relevant on Linux).

----------
Added file: http://bugs.python.org/file23234/socketcan_v5.patch

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue10141>
_______________________________________
-------------- next part --------------
diff -r a06ef7ab7321 Doc/library/socket.rst
--- a/Doc/library/socket.rst	Wed Sep 21 22:05:01 2011 +0200
+++ b/Doc/library/socket.rst	Fri Sep 23 23:27:19 2011 +0200
@@ -80,6 +80,11 @@
     If *addr_type* is TIPC_ADDR_ID, then *v1* is the node, *v2* is the
     reference, and *v3* should be set to 0.
 
+- A tuple ``(interface, )`` is used for the :const:`AF_CAN` address family,
+  where *interface* is a string representing a network interface name like
+  ``'can0'``. The network interface name ``''`` can be used to receive packets
+  from all network interfaces of this family.
+
 - Certain other address families (:const:`AF_BLUETOOTH`, :const:`AF_PACKET`)
   support specific representations.
 
@@ -216,6 +221,19 @@
    in the Unix header files are defined; for a few symbols, default values are
    provided.
 
+.. data:: AF_CAN
+          PF_CAN
+          SOL_CAN_*
+          CAN_*
+
+   Many constants of these forms, documented in the Linux documentation, are
+   also defined in the socket module.
+
+   Availability: Linux >= 2.6.25.
+
+   .. versionadded:: 3.3
+
+
 .. data:: SIO_*
           RCVALL_*
 
@@ -387,10 +405,14 @@
 
    Create a new socket using the given address family, socket type and protocol
    number.  The address family should be :const:`AF_INET` (the default),
-   :const:`AF_INET6` or :const:`AF_UNIX`.  The socket type should be
-   :const:`SOCK_STREAM` (the default), :const:`SOCK_DGRAM` or perhaps one of the
-   other ``SOCK_`` constants.  The protocol number is usually zero and may be
-   omitted in that case.
+   :const:`AF_INET6`, :const:`AF_UNIX` or :const:`AF_CAN`. The socket type
+   should be :const:`SOCK_STREAM` (the default), :const:`SOCK_DGRAM`,
+   :const:`SOCK_RAW` or perhaps one of the other ``SOCK_`` constants. The
+   protocol number is usually zero and may be omitted in that case or
+   :const:`CAN_RAW` in case the address family is :const:`AF_CAN`.
+
+   .. versionchanged:: 3.3
+      The AF_CAN family was added.
 
 
 .. function:: socketpair([family[, type[, proto]]])
@@ -1213,7 +1235,7 @@
    print('Received', repr(data))
 
 
-The last example shows how to write a very simple network sniffer with raw
+The next example shows how to write a very simple network sniffer with raw
 sockets on Windows. The example requires administrator privileges to modify
 the interface::
 
@@ -1238,6 +1260,45 @@
    # disabled promiscuous mode
    s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
 
+The last example shows how to use the socket interface to communicate to a CAN
+network. This example might require special priviledge::
+
+   import socket
+   import struct
+
+
+   # CAN frame packing/unpacking (see `struct can_frame` in <linux/can.h>)
+
+   can_frame_fmt = "=IB3x8s"
+
+   def build_can_frame(can_id, data):
+       can_dlc = len(data)
+       data = data.ljust(8, b'\x00')
+       return struct.pack(can_frame_fmt, can_id, can_dlc, data)
+
+   def dissect_can_frame(frame):
+       can_id, can_dlc, data = struct.unpack(can_frame_fmt, frame)
+       return (can_id, can_dlc, data[:can_dlc])
+
+
+   # create a raw socket and bind it to the `vcan0` interface
+   s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
+   s.bind(('vcan0',))
+
+   while True:
+       cf, addr = s.recvfrom(16)
+
+       print('Received: can_id=%x, can_dlc=%x, data=%s' % dissect_can_frame(cf))
+
+       try:
+           s.send(cf)
+       except socket.error:
+           print('Error sending CAN frame')
+
+       try:
+           s.send(build_can_frame(0x01, b'\x01\x02\x03'))
+       except socket.error:
+           print('Error sending CAN frame')
 
 Running an example several times with too small delay between executions, could
 lead to this error::
diff -r a06ef7ab7321 Doc/whatsnew/3.3.rst
--- a/Doc/whatsnew/3.3.rst	Wed Sep 21 22:05:01 2011 +0200
+++ b/Doc/whatsnew/3.3.rst	Fri Sep 23 23:27:19 2011 +0200
@@ -246,15 +246,22 @@
 socket
 ------
 
-The :class:`~socket.socket` class now exposes addititonal methods to
-process ancillary data when supported by the underlying platform:
+* The :class:`~socket.socket` class now exposes addititonal methods to process
+  ancillary data when supported by the underlying platform:
 
-* :func:`~socket.socket.sendmsg`
-* :func:`~socket.socket.recvmsg`
-* :func:`~socket.socket.recvmsg_into`
+  * :func:`~socket.socket.sendmsg`
+  * :func:`~socket.socket.recvmsg`
+  * :func:`~socket.socket.recvmsg_into`
 
-(Contributed by David Watson in :issue:`6560`, based on an earlier patch
-by Heiko Wundram)
+  (Contributed by David Watson in :issue:`6560`, based on an earlier patch by
+  Heiko Wundram)
+
+* The :class:`~socket.socket` class now supports the PF_CAN protocol family
+  (http://en.wikipedia.org/wiki/Socketcan), on Linux
+  (http://lwn.net/Articles/253425).
+
+  (Contributed by Matthias Fuchs, updated by Tiago Gon��alves in :issue:`10141`)
+
 
 ssl
 ---
diff -r a06ef7ab7321 Lib/test/test_socket.py
--- a/Lib/test/test_socket.py	Wed Sep 21 22:05:01 2011 +0200
+++ b/Lib/test/test_socket.py	Fri Sep 23 23:27:19 2011 +0200
@@ -21,6 +21,7 @@
 import signal
 import math
 import pickle
+import struct
 try:
     import fcntl
 except ImportError:
@@ -36,6 +37,18 @@
     thread = None
     threading = None
 
+def _have_socket_can():
+    """Check whether CAN sockets are supported on this host."""
+    try:
+        s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
+    except (AttributeError, socket.error, OSError):
+        return False
+    else:
+        s.close()
+    return True
+
+HAVE_SOCKET_CAN = _have_socket_can()
+
 # Size in bytes of the int type
 SIZEOF_INT = array.array("i").itemsize
 
@@ -80,6 +93,24 @@
             with self._cleanup_lock:
                 return super().doCleanups(*args, **kwargs)
 
+class SocketCANTest(unittest.TestCase):
+
+    interface = 'vcan0'
+    bufsize = 128
+
+    def setUp(self):
+        self.s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
+        try:
+            self.s.bind((self.interface,))
+        except socket.error:
+            self.skipTest('network interface `%s` does not exist' %
+                           self.interface)
+            self.s.close()
+
+    def tearDown(self):
+        self.s.close()
+        self.s = None
+
 class ThreadableTest:
     """Threadable Test class
 
@@ -210,6 +241,26 @@
         self.cli = None
         ThreadableTest.clientTearDown(self)
 
+class ThreadedCANSocketTest(SocketCANTest, ThreadableTest):
+
+    def __init__(self, methodName='runTest'):
+        SocketCANTest.__init__(self, methodName=methodName)
+        ThreadableTest.__init__(self)
+
+    def clientSetUp(self):
+        self.cli = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
+        try:
+            self.cli.bind((self.interface,))
+        except socket.error:
+            self.skipTest('network interface `%s` does not exist' %
+                           self.interface)
+            self.cli.close()
+
+    def clientTearDown(self):
+        self.cli.close()
+        self.cli = None
+        ThreadableTest.clientTearDown(self)
+
 class SocketConnectedTest(ThreadedTCPSocketTest):
     """Socket tests for client-server connection.
 
@@ -1072,6 +1123,112 @@
         srv.close()
 
 
+ at unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
+class BasicCANTest(unittest.TestCase):
+
+    def testCrucialConstants(self):
+        socket.AF_CAN
+        socket.PF_CAN
+        socket.CAN_RAW
+
+    def testCreateSocket(self):
+        with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
+            pass
+
+    def testBindAny(self):
+        with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
+            s.bind(('', ))
+
+    def testTooLongInterfaceName(self):
+        # most systems limit IFNAMSIZ to 16, take 1024 to be sure
+        with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
+            self.assertRaisesRegexp(socket.error, 'interface name too long',
+                                    s.bind, ('x' * 1024,))
+
+    @unittest.skipUnless(hasattr(socket, "CAN_RAW_LOOPBACK"),
+                         'socket.CAN_RAW_LOOPBACK required for this test.')
+    def testLoopback(self):
+        with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
+            for loopback in (0, 1):
+                s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK,
+                             loopback)
+                self.assertEqual(loopback,
+                    s.getsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK))
+
+    @unittest.skipUnless(hasattr(socket, "CAN_RAW_FILTER"),
+                         'socket.CAN_RAW_FILTER required for this test.')
+    def testFilter(self):
+        can_id, can_mask = 0x200, 0x700
+        can_filter = struct.pack("=II", can_id, can_mask)
+        with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
+            s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, can_filter)
+            self.assertEqual(can_filter,
+                    s.getsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, 8))
+
+
+ at unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
+ at unittest.skipUnless(thread, 'Threading required for this test.')
+class CANTest(ThreadedCANSocketTest):
+
+    """The CAN frame structure is defined in <linux/can.h>:
+
+    struct can_frame {
+        canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
+        __u8    can_dlc; /* data length code: 0 .. 8 */
+        __u8    data[8] __attribute__((aligned(8)));
+    };
+    """
+    can_frame_fmt = "=IB3x8s"
+
+    def __init__(self, methodName='runTest'):
+        ThreadedCANSocketTest.__init__(self, methodName=methodName)
+
+    @classmethod
+    def build_can_frame(cls, can_id, data):
+        """Build a CAN frame."""
+        can_dlc = len(data)
+        data = data.ljust(8, b'\x00')
+        return struct.pack(cls.can_frame_fmt, can_id, can_dlc, data)
+
+    @classmethod
+    def dissect_can_frame(cls, frame):
+        """Dissect a CAN frame."""
+        can_id, can_dlc, data = struct.unpack(cls.can_frame_fmt, frame)
+        return (can_id, can_dlc, data[:can_dlc])
+
+    def testSendFrame(self):
+        cf, addr = self.s.recvfrom(self.bufsize)
+        self.assertEqual(self.cf, cf)
+        self.assertEqual(addr[0], self.interface)
+        self.assertEqual(addr[1], socket.AF_CAN)
+
+    def _testSendFrame(self):
+        self.cf = self.build_can_frame(0x00, b'\x01\x02\x03\x04\x05')
+        self.cli.send(self.cf)
+
+    def testSendMaxFrame(self):
+        cf, addr = self.s.recvfrom(self.bufsize)
+        self.assertEqual(self.cf, cf)
+
+    def _testSendMaxFrame(self):
+        self.cf = self.build_can_frame(0x00, b'\x07' * 8)
+        self.cli.send(self.cf)
+
+    def testSendMultiFrames(self):
+        cf, addr = self.s.recvfrom(self.bufsize)
+        self.assertEqual(self.cf1, cf)
+
+        cf, addr = self.s.recvfrom(self.bufsize)
+        self.assertEqual(self.cf2, cf)
+
+    def _testSendMultiFrames(self):
+        self.cf1 = self.build_can_frame(0x07, b'\x44\x33\x22\x11')
+        self.cli.send(self.cf1)
+
+        self.cf2 = self.build_can_frame(0x12, b'\x99\x22\x33')
+        self.cli.send(self.cf2)
+
+
 @unittest.skipUnless(thread, 'Threading required for this test.')
 class BasicTCPTest(SocketConnectedTest):
 
@@ -4185,6 +4342,7 @@
     if isTipcAvailable():
         tests.append(TIPCTest)
         tests.append(TIPCThreadableTest)
+    tests.extend([BasicCANTest, CANTest])
     tests.extend([
         CmsgMacroTests,
         SendmsgUDPTest,
diff -r a06ef7ab7321 Misc/ACKS
--- a/Misc/ACKS	Wed Sep 21 22:05:01 2011 +0200
+++ b/Misc/ACKS	Fri Sep 23 23:27:19 2011 +0200
@@ -318,6 +318,7 @@
 Martin Franklin
 Robin Friedrich
 Ivan Frohne
+Matthias Fuchs
 Jim Fulton
 Tadayoshi Funaba
 Gyro Funch
@@ -353,6 +354,7 @@
 Yannick Gingras
 Christoph Gohlke
 Tim Golden
+Tiago Gon��alves
 Chris Gonnerman
 David Goodger
 Hans de Graaff
diff -r a06ef7ab7321 Modules/socketmodule.c
--- a/Modules/socketmodule.c	Wed Sep 21 22:05:01 2011 +0200
+++ b/Modules/socketmodule.c	Fri Sep 23 23:27:19 2011 +0200
@@ -1220,6 +1220,25 @@
     }
 #endif
 
+#ifdef HAVE_LINUX_CAN_H
+    case AF_CAN:
+    {
+        struct sockaddr_can *a = (struct sockaddr_can *)addr;
+        char *ifname = "";
+        struct ifreq ifr;
+        /* need to look up interface name given index */
+        if (a->can_ifindex) {
+            ifr.ifr_ifindex = a->can_ifindex;
+            if (ioctl(sockfd, SIOCGIFNAME, &ifr) == 0)
+                ifname = ifr.ifr_name;
+        }
+
+        return Py_BuildValue("O&h", PyUnicode_DecodeFSDefault,
+                                    ifname,
+                                    a->can_family);
+    }
+#endif
+
     /* More cases here... */
 
     default:
@@ -1587,6 +1606,51 @@
     }
 #endif
 
+#ifdef HAVE_LINUX_CAN_H
+    case AF_CAN:
+        switch (s->sock_proto) {
+        case CAN_RAW:
+        {
+            struct sockaddr_can* addr;
+            PyObject *interfaceName;
+            struct ifreq ifr;
+            addr = (struct sockaddr_can *)addr_ret;
+            Py_ssize_t len;
+
+            if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, &interfaceName))
+                return 0;
+
+            len = PyBytes_GET_SIZE(interfaceName);
+
+            if (len == 0) {
+                ifr.ifr_ifindex = 0;
+            } else if (len < sizeof(ifr.ifr_name)) {
+                strcpy(ifr.ifr_name, PyBytes_AS_STRING(interfaceName));
+                if (ioctl(s->sock_fd, SIOCGIFINDEX, &ifr) < 0) {
+                    s->errorhandler();
+                    Py_DECREF(interfaceName);
+                    return 0;
+                }
+            } else {
+                PyErr_SetString(socket_error,
+                                "AF_CAN interface name too long");
+                Py_DECREF(interfaceName);
+                return 0;
+            }
+
+            addr->can_family = AF_CAN;
+            addr->can_ifindex = ifr.ifr_ifindex;
+
+            *len_ret = sizeof(*addr);
+            Py_DECREF(interfaceName);
+            return 1;
+        }
+        default:
+            PyErr_SetString(socket_error, "getsockaddrarg: unsupported CAN protocol");
+            return 0;
+        }
+#endif
+
     /* More cases here... */
 
     default:
@@ -1680,6 +1744,14 @@
     }
 #endif
 
+#ifdef HAVE_LINUX_CAN_H
+    case AF_CAN:
+    {
+        *len_ret = sizeof (struct sockaddr_can);
+        return 1;
+    }
+#endif
+
     /* More cases here... */
 
     default:
@@ -5533,6 +5605,15 @@
     PyModule_AddStringConstant(m, "BDADDR_LOCAL", "00:00:00:FF:FF:FF");
 #endif
 
+#ifdef AF_CAN
+    /* Controller Area Network */
+    PyModule_AddIntConstant(m, "AF_CAN", AF_CAN);
+#endif
+#ifdef PF_CAN
+    /* Controller Area Network */
+    PyModule_AddIntConstant(m, "PF_CAN", PF_CAN);
+#endif
+
 #ifdef AF_PACKET
     PyModule_AddIntMacro(m, AF_PACKET);
 #endif
@@ -5803,6 +5884,28 @@
 #else
     PyModule_AddIntConstant(m, "SOL_UDP", 17);
 #endif
+#ifdef SOL_CAN_BASE
+    PyModule_AddIntConstant(m, "SOL_CAN_BASE", SOL_CAN_BASE);
+#endif
+#ifdef SOL_CAN_RAW
+    PyModule_AddIntConstant(m, "SOL_CAN_RAW", SOL_CAN_RAW);
+    PyModule_AddIntConstant(m, "CAN_RAW", CAN_RAW);
+#endif
+#ifdef HAVE_LINUX_CAN_H
+    PyModule_AddIntConstant(m, "CAN_EFF_FLAG", CAN_EFF_FLAG);
+    PyModule_AddIntConstant(m, "CAN_RTR_FLAG", CAN_RTR_FLAG);
+    PyModule_AddIntConstant(m, "CAN_ERR_FLAG", CAN_ERR_FLAG);
+
+    PyModule_AddIntConstant(m, "CAN_SFF_MASK", CAN_SFF_MASK);
+    PyModule_AddIntConstant(m, "CAN_EFF_MASK", CAN_EFF_MASK);
+    PyModule_AddIntConstant(m, "CAN_ERR_MASK", CAN_ERR_MASK);
+#endif
+#ifdef HAVE_LINUX_CAN_RAW_H
+    PyModule_AddIntConstant(m, "CAN_RAW_FILTER", CAN_RAW_FILTER);
+    PyModule_AddIntConstant(m, "CAN_RAW_ERR_FILTER", CAN_RAW_ERR_FILTER);
+    PyModule_AddIntConstant(m, "CAN_RAW_LOOPBACK", CAN_RAW_LOOPBACK);
+    PyModule_AddIntConstant(m, "CAN_RAW_RECV_OWN_MSGS", CAN_RAW_RECV_OWN_MSGS);
+#endif
 #ifdef  IPPROTO_IP
     PyModule_AddIntConstant(m, "IPPROTO_IP", IPPROTO_IP);
 #else
diff -r a06ef7ab7321 Modules/socketmodule.h
--- a/Modules/socketmodule.h	Wed Sep 21 22:05:01 2011 +0200
+++ b/Modules/socketmodule.h	Fri Sep 23 23:27:19 2011 +0200
@@ -72,6 +72,14 @@
 # include <linux/tipc.h>
 #endif
 
+#ifdef HAVE_LINUX_CAN_H
+#include <linux/can.h>
+#endif
+
+#ifdef HAVE_LINUX_CAN_RAW_H
+#include <linux/can/raw.h>
+#endif
+
 #ifndef Py__SOCKET_H
 #define Py__SOCKET_H
 #ifdef __cplusplus
@@ -126,6 +134,9 @@
 #ifdef HAVE_NETPACKET_PACKET_H
     struct sockaddr_ll ll;
 #endif
+#ifdef HAVE_LINUX_CAN_H
+    struct sockaddr_can can;
+#endif
 } sock_addr_t;
 
 /* The object holding a socket.  It holds some extra information,
diff -r a06ef7ab7321 configure.in
--- a/configure.in	Wed Sep 21 22:05:01 2011 +0200
+++ b/configure.in	Fri Sep 23 23:27:19 2011 +0200
@@ -1354,6 +1354,13 @@
 #endif
 ])
 
+# On Linux, can.h and can/raw.h require sys/socket.h
+AC_CHECK_HEADERS(linux/can.h linux/can/raw.h,,,[
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+])
+
 # checks for typedefs
 was_it_defined=no
 AC_MSG_CHECKING(for clock_t in time.h)
diff -r a06ef7ab7321 pyconfig.h.in
--- a/pyconfig.h.in	Wed Sep 21 22:05:01 2011 +0200
+++ b/pyconfig.h.in	Fri Sep 23 23:27:19 2011 +0200
@@ -467,6 +467,12 @@
 /* Define to 1 if you have the `linkat' function. */
 #undef HAVE_LINKAT
 
+/* Define to 1 if you have the <linux/can.h> header file. */
+#undef HAVE_LINUX_CAN_H
+
+/* Define to 1 if you have the <linux/can/raw.h> header file. */
+#undef HAVE_LINUX_CAN_RAW_H
+
 /* Define to 1 if you have the <linux/netlink.h> header file. */
 #undef HAVE_LINUX_NETLINK_H
 


More information about the Python-bugs-list mailing list