[Python-checkins] bpo-40291: Add support for CAN_J1939 sockets (GH-19538)

karl ding webhook-mailer at python.org
Wed Apr 29 18:31:28 EDT 2020


https://github.com/python/cpython/commit/360371f79c48f15bbcee7aeecacf97a899913b25
commit: 360371f79c48f15bbcee7aeecacf97a899913b25
branch: master
author: karl ding <karlding at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2020-04-29T15:31:19-07:00
summary:

bpo-40291: Add support for CAN_J1939 sockets (GH-19538)



Add support for CAN_J1939 sockets that wrap SAE J1939 protocol
functionality provided by Linux 5.4+ kernels.

files:
A Misc/NEWS.d/next/Library/2020-04-14-22-31-27.bpo-40291._O8hXn.rst
M Doc/library/socket.rst
M Lib/test/test_socket.py
M Modules/socketmodule.c
M Modules/socketmodule.h
M configure
M configure.ac
M pyconfig.h.in

diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 87dee1a801dea..d798c1a9d10a0 100755
--- a/Doc/library/socket.rst
+++ b/Doc/library/socket.rst
@@ -118,6 +118,10 @@ created.  Socket addresses are represented as follows:
   - :const:`CAN_ISOTP` protocol require a tuple ``(interface, rx_addr, tx_addr)``
     where both additional parameters are unsigned long integer that represent a
     CAN identifier (standard or extended).
+  - :const:`CAN_J1939` protocol require a tuple ``(interface, name, pgn, addr)``
+    where additional parameters are 64-bit unsigned integer representing the
+    ECU name, a 32-bit unsigned integer representing the Parameter Group Number
+    (PGN), and an 8-bit integer representing the address.
 
 - A string or a tuple ``(id, unit)`` is used for the :const:`SYSPROTO_CONTROL`
   protocol of the :const:`PF_SYSTEM` family. The string is the name of a
@@ -428,6 +432,15 @@ Constants
 
    .. versionadded:: 3.7
 
+.. data:: CAN_J1939
+
+   CAN_J1939, in the CAN protocol family, is the SAE J1939 protocol.
+   J1939 constants, documented in the Linux documentation.
+
+   .. availability:: Linux >= 5.4.
+
+   .. versionadded:: 3.9
+
 
 .. data:: AF_PACKET
           PF_PACKET
@@ -544,7 +557,8 @@ The following functions all create :ref:`socket objects <socket-objects>`.
    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
    or in the case where the address family is :const:`AF_CAN` the protocol
-   should be one of :const:`CAN_RAW`, :const:`CAN_BCM` or :const:`CAN_ISOTP`.
+   should be one of :const:`CAN_RAW`, :const:`CAN_BCM`, :const:`CAN_ISOTP` or
+   :const:`CAN_J1939`.
 
    If *fileno* is specified, the values for *family*, *type*, and *proto* are
    auto-detected from the specified file descriptor.  Auto-detection can be
@@ -588,6 +602,9 @@ The following functions all create :ref:`socket objects <socket-objects>`.
       ``SOCK_NONBLOCK``, but ``sock.type`` will be set to
       ``socket.SOCK_STREAM``.
 
+   .. versionchanged:: 3.9
+       The CAN_J1939 protocol was added.
+
 .. function:: socketpair([family[, type[, proto]]])
 
    Build a pair of connected socket objects using the given address family, socket
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 87ae2e127a236..4a436cf3c143a 100755
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -80,6 +80,16 @@ def _have_socket_can_isotp():
         s.close()
     return True
 
+def _have_socket_can_j1939():
+    """Check whether CAN J1939 sockets are supported on this host."""
+    try:
+        s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939)
+    except (AttributeError, OSError):
+        return False
+    else:
+        s.close()
+    return True
+
 def _have_socket_rds():
     """Check whether RDS sockets are supported on this host."""
     try:
@@ -143,6 +153,8 @@ def socket_setdefaulttimeout(timeout):
 
 HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp()
 
+HAVE_SOCKET_CAN_J1939 = _have_socket_can_j1939()
+
 HAVE_SOCKET_RDS = _have_socket_rds()
 
 HAVE_SOCKET_ALG = _have_socket_alg()
@@ -2117,6 +2129,68 @@ def testBind(self):
                 raise
 
 
+ at unittest.skipUnless(HAVE_SOCKET_CAN_J1939, 'CAN J1939 required for this test.')
+class J1939Test(unittest.TestCase):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.interface = "vcan0"
+
+    @unittest.skipUnless(hasattr(socket, "CAN_J1939"),
+                         'socket.CAN_J1939 required for this test.')
+    def testJ1939Constants(self):
+        socket.CAN_J1939
+
+        socket.J1939_MAX_UNICAST_ADDR
+        socket.J1939_IDLE_ADDR
+        socket.J1939_NO_ADDR
+        socket.J1939_NO_NAME
+        socket.J1939_PGN_REQUEST
+        socket.J1939_PGN_ADDRESS_CLAIMED
+        socket.J1939_PGN_ADDRESS_COMMANDED
+        socket.J1939_PGN_PDU1_MAX
+        socket.J1939_PGN_MAX
+        socket.J1939_NO_PGN
+
+        # J1939 socket options
+        socket.SO_J1939_FILTER
+        socket.SO_J1939_PROMISC
+        socket.SO_J1939_SEND_PRIO
+        socket.SO_J1939_ERRQUEUE
+
+        socket.SCM_J1939_DEST_ADDR
+        socket.SCM_J1939_DEST_NAME
+        socket.SCM_J1939_PRIO
+        socket.SCM_J1939_ERRQUEUE
+
+        socket.J1939_NLA_PAD
+        socket.J1939_NLA_BYTES_ACKED
+
+        socket.J1939_EE_INFO_NONE
+        socket.J1939_EE_INFO_TX_ABORT
+
+        socket.J1939_FILTER_MAX
+
+    @unittest.skipUnless(hasattr(socket, "CAN_J1939"),
+                         'socket.CAN_J1939 required for this test.')
+    def testCreateJ1939Socket(self):
+        with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s:
+            pass
+
+    def testBind(self):
+        try:
+            with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s:
+                addr = self.interface, socket.J1939_NO_NAME, socket.J1939_NO_PGN, socket.J1939_NO_ADDR
+                s.bind(addr)
+                self.assertEqual(s.getsockname(), addr)
+        except OSError as e:
+            if e.errno == errno.ENODEV:
+                self.skipTest('network interface `%s` does not exist' %
+                           self.interface)
+            else:
+                raise
+
+
 @unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.')
 class BasicRDSTest(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Library/2020-04-14-22-31-27.bpo-40291._O8hXn.rst b/Misc/NEWS.d/next/Library/2020-04-14-22-31-27.bpo-40291._O8hXn.rst
new file mode 100644
index 0000000000000..a560ef12302bb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-04-14-22-31-27.bpo-40291._O8hXn.rst
@@ -0,0 +1 @@
+Add support for CAN_J1939 sockets (available on Linux 5.4+)
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index 9db8535eb3434..580ac0af5aff5 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -1556,6 +1556,16 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
                                           a->can_addr.tp.tx_id);
           }
 #endif /* CAN_ISOTP */
+#ifdef CAN_J1939
+          case CAN_J1939:
+          {
+              return Py_BuildValue("O&KkB", PyUnicode_DecodeFSDefault,
+                                          ifname,
+                                          a->can_addr.j1939.name,
+                                          a->can_addr.j1939.pgn,
+                                          a->can_addr.j1939.addr);
+          }
+#endif /* CAN_J1939 */
           default:
           {
               return Py_BuildValue("(O&)", PyUnicode_DecodeFSDefault,
@@ -2237,6 +2247,55 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
             return 1;
         }
 #endif /* CAN_ISOTP */
+#ifdef CAN_J1939
+        case CAN_J1939:
+        {
+            PyObject *interfaceName;
+            struct ifreq ifr;
+            Py_ssize_t len;
+            uint64_t j1939_name;
+            uint32_t j1939_pgn;
+            uint8_t j1939_addr;
+
+            struct sockaddr_can *addr = &addrbuf->can;
+
+            if (!PyArg_ParseTuple(args, "O&KkB", PyUnicode_FSConverter,
+                                              &interfaceName,
+                                              &j1939_name,
+                                              &j1939_pgn,
+                                              &j1939_addr))
+                return 0;
+
+            len = PyBytes_GET_SIZE(interfaceName);
+
+            if (len == 0) {
+                ifr.ifr_ifindex = 0;
+            } else if ((size_t)len < sizeof(ifr.ifr_name)) {
+                strncpy(ifr.ifr_name, PyBytes_AS_STRING(interfaceName), sizeof(ifr.ifr_name));
+                ifr.ifr_name[(sizeof(ifr.ifr_name))-1] = '\0';
+                if (ioctl(s->sock_fd, SIOCGIFINDEX, &ifr) < 0) {
+                    s->errorhandler();
+                    Py_DECREF(interfaceName);
+                    return 0;
+                }
+            } else {
+                PyErr_SetString(PyExc_OSError,
+                                "AF_CAN interface name too long");
+                Py_DECREF(interfaceName);
+                return 0;
+            }
+
+            addr->can_family = AF_CAN;
+            addr->can_ifindex = ifr.ifr_ifindex;
+            addr->can_addr.j1939.name = j1939_name;
+            addr->can_addr.j1939.pgn = j1939_pgn;
+            addr->can_addr.j1939.addr = j1939_addr;
+
+            *len_ret = sizeof(*addr);
+            Py_DECREF(interfaceName);
+            return 1;
+        }
+#endif /* CAN_J1939 */
         default:
             PyErr_Format(PyExc_OSError,
                          "%s(): unsupported CAN protocol", caller);
@@ -7687,6 +7746,9 @@ PyInit__socket(void)
 #ifdef CAN_ISOTP
     PyModule_AddIntMacro(m, CAN_ISOTP);
 #endif
+#ifdef CAN_J1939
+    PyModule_AddIntMacro(m, CAN_J1939);
+#endif
 #endif
 #ifdef HAVE_LINUX_CAN_RAW_H
     PyModule_AddIntMacro(m, CAN_RAW_FILTER);
@@ -7734,6 +7796,37 @@ PyInit__socket(void)
     PyModule_AddIntConstant(m, "CAN_BCM_CAN_FD_FRAME", CAN_FD_FRAME);
 #endif
 #endif
+#ifdef HAVE_LINUX_CAN_J1939_H
+    PyModule_AddIntMacro(m, J1939_MAX_UNICAST_ADDR);
+    PyModule_AddIntMacro(m, J1939_IDLE_ADDR);
+    PyModule_AddIntMacro(m, J1939_NO_ADDR);
+    PyModule_AddIntMacro(m, J1939_NO_NAME);
+    PyModule_AddIntMacro(m, J1939_PGN_REQUEST);
+    PyModule_AddIntMacro(m, J1939_PGN_ADDRESS_CLAIMED);
+    PyModule_AddIntMacro(m, J1939_PGN_ADDRESS_COMMANDED);
+    PyModule_AddIntMacro(m, J1939_PGN_PDU1_MAX);
+    PyModule_AddIntMacro(m, J1939_PGN_MAX);
+    PyModule_AddIntMacro(m, J1939_NO_PGN);
+
+    /* J1939 socket options */
+    PyModule_AddIntMacro(m, SO_J1939_FILTER);
+    PyModule_AddIntMacro(m, SO_J1939_PROMISC);
+    PyModule_AddIntMacro(m, SO_J1939_SEND_PRIO);
+    PyModule_AddIntMacro(m, SO_J1939_ERRQUEUE);
+
+    PyModule_AddIntMacro(m, SCM_J1939_DEST_ADDR);
+    PyModule_AddIntMacro(m, SCM_J1939_DEST_NAME);
+    PyModule_AddIntMacro(m, SCM_J1939_PRIO);
+    PyModule_AddIntMacro(m, SCM_J1939_ERRQUEUE);
+
+    PyModule_AddIntMacro(m, J1939_NLA_PAD);
+    PyModule_AddIntMacro(m, J1939_NLA_BYTES_ACKED);
+
+    PyModule_AddIntMacro(m, J1939_EE_INFO_NONE);
+    PyModule_AddIntMacro(m, J1939_EE_INFO_TX_ABORT);
+
+    PyModule_AddIntMacro(m, J1939_FILTER_MAX);
+#endif
 #ifdef SOL_RDS
     PyModule_AddIntMacro(m, SOL_RDS);
 #endif
diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h
index 7684e59cd4546..ba2c9f5c31c3b 100644
--- a/Modules/socketmodule.h
+++ b/Modules/socketmodule.h
@@ -144,6 +144,10 @@ typedef int socklen_t;
 #include <linux/can/bcm.h>
 #endif
 
+#ifdef HAVE_LINUX_CAN_J1939_H
+#include <linux/can/j1939.h>
+#endif
+
 #ifdef HAVE_SYS_SYS_DOMAIN_H
 #include <sys/sys_domain.h>
 #endif
diff --git a/configure b/configure
index 29d5f4ce66735..a8a35d0defc6b 100755
--- a/configure
+++ b/configure
@@ -8282,8 +8282,8 @@ fi
 done
 
 
-# On Linux, can.h and can/raw.h require sys/socket.h
-for ac_header in linux/can.h linux/can/raw.h linux/can/bcm.h
+# On Linux, can.h, can/bcm.h, can/j1939.h, can/raw.h require sys/socket.h
+for ac_header in linux/can.h linux/can/bcm.h linux/can/j1939.h linux/can/raw.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "
diff --git a/configure.ac b/configure.ac
index 240ddeb9b3d22..f996051efc719 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2236,8 +2236,8 @@ AC_CHECK_HEADERS(linux/vm_sockets.h,,,[
 #endif
 ])
 
-# On Linux, can.h and can/raw.h require sys/socket.h
-AC_CHECK_HEADERS(linux/can.h linux/can/raw.h linux/can/bcm.h,,,[
+# On Linux, can.h, can/bcm.h, can/j1939.h, can/raw.h require sys/socket.h
+AC_CHECK_HEADERS(linux/can.h linux/can/bcm.h linux/can/j1939.h linux/can/raw.h,,,[
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
 #endif
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 76a10474208cf..75ac368aadafe 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -622,6 +622,9 @@
 /* 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/j1939.h> header file. */
+#undef HAVE_LINUX_CAN_J1939_H
+
 /* Define if compiling using Linux 3.6 or later. */
 #undef HAVE_LINUX_CAN_RAW_FD_FRAMES
 



More information about the Python-checkins mailing list