From a30f6d45ac3e72761b96a8df0527182029eaee24 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Mon, 28 Aug 2017 04:32:44 -0400 Subject: [PATCH] bpo-30987 - Support for ISO-TP protocol in SocketCAN (#2956) * Added support for CAN_ISOTP protocol * Added unit tests for CAN ISOTP * Updated documentation for ISO-TP protocol * Removed trailing whitespace in documentation * Added blurb NEWS.d file * updated Misc/ACKS * Fixed broken unit test that was using isotp const outside of skippable section * Removed dependecy over third party project * Added implementation for getsockname + unit tests * Missing newline at end of ACKS file * Accidentally inserted a type in ACKS file * Followed tiran changes review #1 recommendations * Added spaces after comma --- Doc/library/socket.rst | 22 +++++- Lib/test/test_socket.py | 55 ++++++++++++++ Misc/ACKS | 1 + .../2017-07-30-22-00-12.bpo-30987.228rW0.rst | 1 + Modules/socketmodule.c | 75 +++++++++++++++++-- 5 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 8af6bc5439beea..c5064e92c2118b 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -103,6 +103,10 @@ created. Socket addresses are represented as follows: ``'can0'``. The network interface name ``''`` can be used to receive packets from all network interfaces of this family. + - :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). + - 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 kernel control using a dynamically-assigned ID. The tuple can be used if ID @@ -341,6 +345,16 @@ Constants .. versionadded:: 3.5 +.. data:: CAN_ISOTP + + CAN_ISOTP, in the CAN protocol family, is the ISO-TP (ISO 15765-2) protocol. + ISO-TP constants, documented in the Linux documentation. + + Availability: Linux >= 2.6.25 + + .. versionadded:: 3.7 + + .. data:: AF_RDS PF_RDS SOL_RDS @@ -427,7 +441,7 @@ The following functions all create :ref:`socket objects `. :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` or :const:`CAN_BCM`. If *fileno* is specified, the other + of :const:`CAN_RAW`, :const:`CAN_BCM` or :const:`CAN_ISOTP`. If *fileno* is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike :func:`socket.fromfd`, *fileno* will return the same socket and not a duplicate. This may help close a detached socket using @@ -445,6 +459,8 @@ The following functions all create :ref:`socket objects `. .. versionchanged:: 3.4 The returned socket is now non-inheritable. + .. versionchanged:: 3.7 + The CAN_ISOTP protocol was added. .. function:: socketpair([family[, type[, proto]]]) @@ -1661,7 +1677,7 @@ the interface:: # 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 +The next example shows how to use the socket interface to communicate to a CAN network using the raw socket protocol. To use CAN with the broadcast manager protocol instead, open a socket with:: @@ -1671,7 +1687,7 @@ After binding (:const:`CAN_RAW`) or connecting (:const:`CAN_BCM`) the socket, yo can use the :meth:`socket.send`, and the :meth:`socket.recv` operations (and their counterparts) on the socket object as usual. -This example might require special privileges:: +This last example might require special privileges:: import socket import struct diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index f28f7d6816e925..84234887747983 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -55,6 +55,16 @@ def _have_socket_can(): s.close() return True +def _have_socket_can_isotp(): + """Check whether CAN ISOTP sockets are supported on this host.""" + try: + s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + except (AttributeError, OSError): + return False + else: + s.close() + return True + def _have_socket_rds(): """Check whether RDS sockets are supported on this host.""" try: @@ -77,6 +87,8 @@ def _have_socket_alg(): HAVE_SOCKET_CAN = _have_socket_can() +HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp() + HAVE_SOCKET_RDS = _have_socket_rds() HAVE_SOCKET_ALG = _have_socket_alg() @@ -1709,6 +1721,49 @@ def testBCM(self): self.assertEqual(bytes_sent, len(header_plus_frame)) +@unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') +class ISOTPTest(unittest.TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.interface = "vcan0" + + def testCrucialConstants(self): + socket.AF_CAN + socket.PF_CAN + socket.CAN_ISOTP + socket.SOCK_DGRAM + + def testCreateSocket(self): + with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s: + pass + + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP"), + 'socket.CAN_ISOTP required for this test.') + def testCreateISOTPSocket(self): + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + pass + + def testTooLongInterfaceName(self): + # most systems limit IFNAMSIZ to 16, take 1024 to be sure + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + with self.assertRaisesRegex(OSError, 'interface name too long'): + s.bind(('x' * 1024, 1, 2)) + + def testBind(self): + try: + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + addr = self.interface, 0x123, 0x456 + 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/ACKS b/Misc/ACKS index b2e05920489c4f..91e04246f73b3b 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -908,6 +908,7 @@ John Lenton Kostyantyn Leschenko Benno Leslie Christopher Tur Lesniewski-Laas +Pier-Yves Lessard Alain Leufroy Mark Levinson Mark Levitt diff --git a/Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst b/Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst new file mode 100644 index 00000000000000..de30ea8850c33e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst @@ -0,0 +1 @@ +Added support for CAN ISO-TP protocol in the socket module. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index bf8d19fe2f40cb..beadecfad50ec9 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1373,9 +1373,22 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto) ifname = ifr.ifr_name; } - return Py_BuildValue("O&h", PyUnicode_DecodeFSDefault, - ifname, - a->can_family); + switch (proto) { +#ifdef CAN_ISOTP + case CAN_ISOTP: + { + return Py_BuildValue("O&kk", PyUnicode_DecodeFSDefault, + ifname, + a->can_addr.tp.rx_id, + a->can_addr.tp.tx_id); + } +#endif + default: + { + return Py_BuildValue("O&", PyUnicode_DecodeFSDefault, + ifname); + } + } } #endif @@ -1869,7 +1882,9 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, } #endif -#if defined(AF_CAN) && defined(CAN_RAW) && defined(CAN_BCM) +#ifdef AF_CAN + +#if defined(CAN_RAW) && defined(CAN_BCM) case AF_CAN: switch (s->sock_proto) { case CAN_RAW: @@ -1880,7 +1895,6 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, PyObject *interfaceName; struct ifreq ifr; Py_ssize_t len; - addr = (struct sockaddr_can *)addr_ret; if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, @@ -1913,6 +1927,54 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, Py_DECREF(interfaceName); return 1; } +#endif + +#ifdef CAN_ISOTP + case CAN_ISOTP: + { + struct sockaddr_can *addr; + PyObject *interfaceName; + struct ifreq ifr; + Py_ssize_t len; + unsigned long int rx_id, tx_id; + + addr = (struct sockaddr_can *)addr_ret; + + if (!PyArg_ParseTuple(args, "O&kk", PyUnicode_FSConverter, + &interfaceName, + &rx_id, + &tx_id)) + 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.tp.rx_id = rx_id; + addr->can_addr.tp.tx_id = tx_id; + + *len_ret = sizeof(*addr); + Py_DECREF(interfaceName); + return 1; + } +#endif default: PyErr_SetString(PyExc_OSError, "getsockaddrarg: unsupported CAN protocol"); @@ -6995,6 +7057,9 @@ PyInit__socket(void) PyModule_AddIntMacro(m, CAN_SFF_MASK); PyModule_AddIntMacro(m, CAN_EFF_MASK); PyModule_AddIntMacro(m, CAN_ERR_MASK); +#ifdef CAN_ISOTP + PyModule_AddIntMacro(m, CAN_ISOTP); +#endif #endif #ifdef HAVE_LINUX_CAN_RAW_H PyModule_AddIntMacro(m, CAN_RAW_FILTER);