From 4d609c08e748168f03ab5ed8b9976996f3fcdf02 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Tue, 25 Jul 2017 17:54:23 -0700 Subject: [PATCH 01/13] Added support for CAN_ISOTP protocol --- Modules/socketmodule.c | 72 ++++++++++++++++++++++++++++++++++++++++-- Modules/socketmodule.h | 4 +++ configure | 4 +-- configure.ac | 4 +-- pyconfig.h.in | 3 ++ 5 files changed, 81 insertions(+), 6 deletions(-) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index e18dd32d90045d..8def56a959e81a 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1869,7 +1869,9 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, } #endif -#if defined(AF_CAN) && defined(CAN_RAW) && defined(CAN_BCM) +#if defined(AF_CAN) + +#if defined(CAN_RAW) && defined(CAN_BCM) case AF_CAN: switch (s->sock_proto) { case CAN_RAW: @@ -1880,7 +1882,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 +1914,54 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, Py_DECREF(interfaceName); return 1; } +#endif + +#if defined(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"); @@ -7018,6 +7067,25 @@ PyInit__socket(void) PyModule_AddIntConstant(m, "CAN_BCM_RX_TIMEOUT", RX_TIMEOUT); PyModule_AddIntConstant(m, "CAN_BCM_RX_CHANGED", RX_CHANGED); #endif +#ifdef HAVE_LINUX_CAN_ISOTP_H + PyModule_AddIntMacro(m, SOL_CAN_ISOTP); + PyModule_AddIntMacro(m, CAN_ISOTP); + PyModule_AddIntMacro(m, CAN_ISOTP_OPTS); + PyModule_AddIntMacro(m, CAN_ISOTP_RECV_FC); + PyModule_AddIntMacro(m, CAN_ISOTP_TX_STMIN); + PyModule_AddIntMacro(m, CAN_ISOTP_RX_STMIN); + PyModule_AddIntMacro(m, CAN_ISOTP_LL_OPTS); + PyModule_AddIntMacro(m, CAN_ISOTP_LISTEN_MODE); + PyModule_AddIntMacro(m, CAN_ISOTP_EXTEND_ADDR); + PyModule_AddIntMacro(m, CAN_ISOTP_TX_PADDING); + PyModule_AddIntMacro(m, CAN_ISOTP_RX_PADDING); + PyModule_AddIntMacro(m, CAN_ISOTP_CHK_PAD_LEN); + PyModule_AddIntMacro(m, CAN_ISOTP_CHK_PAD_DATA); + PyModule_AddIntMacro(m, CAN_ISOTP_HALF_DUPLEX); + PyModule_AddIntMacro(m, CAN_ISOTP_FORCE_TXSTMIN); + PyModule_AddIntMacro(m, CAN_ISOTP_FORCE_RXSTMIN); + PyModule_AddIntMacro(m, CAN_ISOTP_RX_EXT_ADDR); +#endif #ifdef SOL_RDS PyModule_AddIntMacro(m, SOL_RDS); #endif diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 03f982b91083f3..e1141e683b4e87 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -91,6 +91,10 @@ typedef int socklen_t; #include #endif +#ifdef HAVE_LINUX_CAN_ISOTP_H +#include +#endif + #ifdef HAVE_SYS_SYS_DOMAIN_H #include #endif diff --git a/configure b/configure index a6f7d2bd7f0d9e..7a4f61696b571a 100755 --- a/configure +++ b/configure @@ -8127,8 +8127,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/raw.h, can/isotp.h require sys/socket.h +for ac_header in linux/can.h linux/can/raw.h linux/can/bcm.h linux/can/isotp.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 a5da830e89623c..3058fb20d7b889 100644 --- a/configure.ac +++ b/configure.ac @@ -2120,8 +2120,8 @@ AC_CHECK_HEADERS(linux/netlink.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/raw.h, can/isotp.h require sys/socket.h +AC_CHECK_HEADERS(linux/can.h linux/can/raw.h linux/can/bcm.h linux/can/isotp.h,,,[ #ifdef HAVE_SYS_SOCKET_H #include #endif diff --git a/pyconfig.h.in b/pyconfig.h.in index 0dd05aa65b8a4f..77b6e90a6c0936 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -559,6 +559,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_CAN_H +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_CAN_ISOTP_H + /* Define if compiling using Linux 3.6 or later. */ #undef HAVE_LINUX_CAN_RAW_FD_FRAMES From ccd65b8cccd9096c006e10aa6e0dfcac7d9c2fbd Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 30 Jul 2017 19:53:24 -0700 Subject: [PATCH 02/13] Added unit tests for CAN ISOTP --- Lib/test/test_socket.py | 232 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index f28f7d6816e925..7b743e9a4e9913 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() @@ -353,6 +365,48 @@ def clientTearDown(self): self.cli = None ThreadableTest.clientTearDown(self) +class ThreadableISOTPTest(unittest.TestCase, ThreadableTest): + """To be able to run this test, a `vcan0` CAN interface can be created with + the following commands: + # modprobe vcan + # ip link add dev vcan0 type vcan + # ifconfig vcan0 up + """ + interface = 'vcan0' + addr_offset=1 + address_lock = threading.Lock() + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self,*args, **kwargs) + ThreadableTest.__init__(self) + (self.cli_addr, self.srv_addr) = ThreadableISOTPTest.getAddressPair() + + @classmethod + def getAddressPair(cls): + cls.address_lock.acquire_lock() + latched_offset = cls.addr_offset + cls.addr_offset += 2 + if cls.addr_offset > 0x7FE: # 11 bit standard CAN identifier (ISO-11898-2) + cls.addr_offset = 1 + cls.address_lock.release_lock() + return (latched_offset, latched_offset+1) + + def clientSetUp(self): + try: + self.cli = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + if 'cli' in self.opts: + for opt_const in self.opts['cli']: + self.serv.setsockopt(socket.SOL_CAN_ISOTP, opt_const, self.opts['cli'][opt_const]) + self.cli.bind((self.interface, self.srv_addr, self.cli_addr)) + except OSError: + # skipTest should not be called here, and will be called in the + # server instead + pass + + def clientTearDown(self): + self.cli.close() + self.cli = None + ThreadableTest.clientTearDown(self) + class ThreadedRDSSocketTest(SocketRDSTest, ThreadableTest): def __init__(self, methodName='runTest'): @@ -1709,6 +1763,184 @@ def testBCM(self): self.assertEqual(bytes_sent, len(header_plus_frame)) +@unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') +class BasicISOTPTest(unittest.TestCase): + + def testCrucialConstants(self): + socket.AF_CAN + socket.PF_CAN + socket.CAN_ISOTP + socket.SOCK_DGRAM + + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP"), + 'socket.CAN_ISOTP required for this test.') + def testISOTPConstants(self): + socket.CAN_ISOTP + + socket.SOL_CAN_ISOTP # Socket Option Level for ISOTP protocol (AF_CAN + 6) + socket.CAN_ISOTP_OPTS # sets struct can_isotp_options within the driver. + socket.CAN_ISOTP_RECV_FC # set flow control options for receiver + socket.CAN_ISOTP_TX_STMIN # override sepration time received in flow control frame + socket.CAN_ISOTP_RX_STMIN # force to ignore messages received in an interval smaller than this value + socket.CAN_ISOTP_LL_OPTS # sets struct can_isotp_ll_options within the driver + + # Options flags + socket.CAN_ISOTP_LISTEN_MODE # listen only (do not send FC) + socket.CAN_ISOTP_EXTEND_ADDR # enable extended addressing + socket.CAN_ISOTP_TX_PADDING # enable CAN frame padding tx path + socket.CAN_ISOTP_RX_PADDING # enable CAN frame padding rx path + socket.CAN_ISOTP_CHK_PAD_LEN # check received CAN frame padding + socket.CAN_ISOTP_CHK_PAD_DATA # check received CAN frame padding + socket.CAN_ISOTP_HALF_DUPLEX # half duplex error state handling + socket.CAN_ISOTP_FORCE_TXSTMIN # ignore stmin from received FC + socket.CAN_ISOTP_FORCE_RXSTMIN # ignore CFs depending on rx stmin + socket.CAN_ISOTP_RX_EXT_ADDR # different rx extended addressing + + + 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: + self.assertRaisesRegex(OSError, 'interface name too long', + s.bind, ('x' * 1024,1,2)) + + @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), + 'socket.SOL_CAN_ISOTP is required for this test') + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_OPTS"), + 'socket.CAN_ISOTP_OPTS is required for this test') + def testSetOpts(self): + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + flags = 0x01234567 + frame_txtime = 0x89ABCDEF + ext_address = 0x11 + txpad_content = 0x21 + rxpad_content = 0x31 + rx_ext_address = ext_address + opts = struct.pack("=LLBBBB",flags, frame_txtime, ext_address, txpad_content, rxpad_content, rx_ext_address) + s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_OPTS, opts) + readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_OPTS, len(opts)) + self.assertEqual(readopts,opts) + + @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), + 'socket.SOL_CAN_ISOTP is required for this test') + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), + 'socket.CAN_ISOTP_RECV_FC is required for this test') + def testSetFCOpts(self): + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + bs = 0x12 + stmin = 0x22 + wftmax = 0x32 + opts = struct.pack("=BBB",bs, stmin, wftmax) + s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, opts) + readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, len(opts)) + self.assertEqual(readopts,opts) + + @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), + 'socket.SOL_CAN_ISOTP is required for this test') + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_LL_OPTS"), + 'socket.CAN_ISOTP_LL_OPTS is required for this test') + def testSetLLOpts(self): + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + mtu = 16 + tx_dl = 8 + tx_flags = 0x13 + opts = struct.pack("=BBB",mtu, tx_dl, tx_flags) + s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_LL_OPTS, opts) + readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_LL_OPTS, len(opts)) + self.assertEqual(readopts,opts) + + +@unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') +class ISOTPTest(ThreadableISOTPTest): + + test_opts = { + 'testTiming' : { + 'cli' : { + socket.CAN_ISOTP_RECV_FC : struct.pack("=BBB",10, 0x7F, 10) # bs, stmin, wftmax + }, + 'serv' : { + + } + } + } + + def __init__(self, methodName='runTest', *args, **kwargs): + ThreadableISOTPTest.__init__(self,methodName, *args, **kwargs) + if methodName in ISOTPTest.test_opts: + self.opts = ISOTPTest.test_opts[methodName] + else: + self.opts = {} + + def setUp(self, *args, **kwargs): + self.serv = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + try: + if 'serv' in self.opts: + for opt_const in self.opts['serv']: + self.serv.setsockopt(socket.SOL_CAN_ISOTP, opt_const, self.opts['serv'][opt_const]) + self.serv.bind((self.interface, self.cli_addr, self.srv_addr)) + except OSError: + self.skipTest('network interface `%s` does not exist' % + self.interface) + + def tearDown(self): + self.serv.close() + self.serv=None + + @classmethod + def makeRandomData(cls, n): + data = [random.randint(0,0xFF) for i in range(0,n)] + return struct.pack("B"*len(data), *data) + + def _testFullFrame(self): + self.bindata = self.makeRandomData(4095) + self.cli.send(self.bindata) + + def testFullFrame(self): + time.sleep(0.1) + bindata = self.serv.recv(len(self.bindata)) + self.assertEqual(bindata, self.bindata) + + def _testTiming(self): + self.bindata2 = self.makeRandomData(150) + self.tic = time.time() + self.cli.send(self.bindata2) + + @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), + 'socket.SOL_CAN_ISOTP is required for this test') + @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), + 'socket.CAN_ISOTP_RECV_FC is required for this test') + def testTiming(self): + """ + The main goal of this test is to validate that the driver correctly receive options via setsockopt. + We are not trying to validate the proper functioning of the driver itself. + """ + time.sleep(0.1) + bindata2 = self.serv.recv(len(self.bindata2)) + self.toc = time.time() + self.assertEqual(bindata2, self.bindata2) + measured_time = math.ceil(self.toc-self.tic) + min_time = math.floor(self.calcFrameCnt(len(self.bindata2))*0.127) + self.assertGreater(measured_time, min_time) + + def calcFrameCnt(self, size): + if size < 1 or size > 4095: + raise ValueError('ISOTP frame size must be between 1 and 4095') + + if (size < 8): + return 1 + else: + return int(1 + math.ceil((float(size) - 6.0) / 7.0)) + + @unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.') class BasicRDSTest(unittest.TestCase): From 00171565e2566ef7e4ef21c1081c50bc5ddcba71 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 30 Jul 2017 21:19:30 -0700 Subject: [PATCH 03/13] Updated documentation for ISO-TP protocol --- Doc/library/socket.rst | 59 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 8af6bc5439beea..cab4b397529881 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,17 @@ 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, are also + defined in the socket module. + + Availability: Linux >= 2.6.25 + + .. versionadded:: 3.7 + + .. data:: AF_RDS PF_RDS SOL_RDS @@ -427,7 +442,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 +460,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 +1678,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:: @@ -1729,6 +1746,44 @@ There is a :mod:`socket` flag to set, in order to prevent this, the :data:`SO_REUSEADDR` flag tells the kernel to reuse a local socket in ``TIME_WAIT`` state, without waiting for its natural timeout to expire. +The last example shows how to use the socket interface to communicate to a CAN +network using the ISO-TP protocol. Altough Linux defines the required interface +in , it does not support a native implementation of the protocol. +The appropriate module must be loaded in the kernel. + + modprobe can-isotp + +If the system does not support ISO-TP protocol, creating the socket might return:: + + OSError: [Errno 93] Protocol not supported + +This example might require special privileges:: + + import socket + import struct + + # create a iso-tp socket and bind it to the 'vcan0' interface, with rx_addr = 0x123 and tx_addr=0x456 + s1 = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + s2 = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + + # set socket some options before binding to the interface. + stmin = 0x32 # Minimum Separation Time : 50 milliseconds between each CAN frame. See ISO 15765-2 for acceptables values. + bs = 3 # Block Size : sends a FlowControl frame every 3 Consecutive Frames + wftmax = 5 # Maximum Wait Frame Transmission - Maximum number of consecutive Wait Frame. + s2.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, struct.pack("=BBB", bs, stmin, wftmax)) + + s1.bind(('vcan0',0x123, 0x456)) + s2.bind(('vcan0',0x456, 0x123)) + + data = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10" + s1.send(data) + s2.recv(len(data)) + + # The following transmission will occur + # vcan0 456 [8] 10 0A 01 02 03 04 05 06 + # vcan0 123 [3] 30 03 32 + # vcan0 456 [5] 21 07 08 09 10 + .. seealso:: From 85a154523464d5103c35856294ed719ed37e50d2 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 30 Jul 2017 21:55:45 -0700 Subject: [PATCH 04/13] Removed trailing whitespace in documentation --- Doc/library/socket.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index cab4b397529881..612caf1d697331 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -103,7 +103,7 @@ 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)`` + - :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). From 32399d4f7960d687becec551a80d04bf74130878 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 30 Jul 2017 22:00:40 -0700 Subject: [PATCH 05/13] Added blurb NEWS.d file --- .../NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst 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. From fc058f8810a91fc6ea8b659b7ec650503ecbc5a3 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 30 Jul 2017 22:14:10 -0700 Subject: [PATCH 06/13] updated Misc/ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index e0640c114ff34f..7b709ba41f4839 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1766,3 +1766,4 @@ Jelle Zijlstra Gennadiy Zlobin Doug Zongker Peter Åstrand +Pier-Yves Lessard From 2aa760dfc9aa1c7f0ff242b2619cdae714d29184 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Mon, 31 Jul 2017 06:38:12 -0700 Subject: [PATCH 07/13] Fixed broken unit test that was using isotp const outside of skippable section --- Lib/test/test_socket.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 7b743e9a4e9913..ae07ec386a6dbb 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -378,6 +378,7 @@ class ThreadableISOTPTest(unittest.TestCase, ThreadableTest): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self,*args, **kwargs) ThreadableTest.__init__(self) + self.test_opts = {} (self.cli_addr, self.srv_addr) = ThreadableISOTPTest.getAddressPair() @classmethod @@ -392,6 +393,7 @@ def getAddressPair(cls): def clientSetUp(self): try: + self.opts = self.test_opts[self._testMethodName] if self._testMethodName in self.test_opts else {} self.cli = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) if 'cli' in self.opts: for opt_const in self.opts['cli']: @@ -1860,28 +1862,25 @@ def testSetLLOpts(self): @unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') +@unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), + 'socket.SOL_CAN_ISOTP is required for this test') +@unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), + 'socket.CAN_ISOTP_RECV_FC is required for this test') class ISOTPTest(ThreadableISOTPTest): - test_opts = { - 'testTiming' : { + def __init__(self, methodName='runTest', *args, **kwargs): + ThreadableISOTPTest.__init__(self,methodName, *args, **kwargs) + + def setUp(self, *args, **kwargs): + self.test_opts['testTiming'] = { 'cli' : { socket.CAN_ISOTP_RECV_FC : struct.pack("=BBB",10, 0x7F, 10) # bs, stmin, wftmax }, - 'serv' : { - - } + 'serv' : {} } - } - def __init__(self, methodName='runTest', *args, **kwargs): - ThreadableISOTPTest.__init__(self,methodName, *args, **kwargs) - if methodName in ISOTPTest.test_opts: - self.opts = ISOTPTest.test_opts[methodName] - else: - self.opts = {} - - def setUp(self, *args, **kwargs): self.serv = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) + self.opts = self.test_opts[self._testMethodName] if self._testMethodName in self.test_opts else {} try: if 'serv' in self.opts: for opt_const in self.opts['serv']: @@ -1910,14 +1909,10 @@ def testFullFrame(self): self.assertEqual(bindata, self.bindata) def _testTiming(self): - self.bindata2 = self.makeRandomData(150) + self.bindata2 = self.makeRandomData(512) self.tic = time.time() self.cli.send(self.bindata2) - @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), - 'socket.SOL_CAN_ISOTP is required for this test') - @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), - 'socket.CAN_ISOTP_RECV_FC is required for this test') def testTiming(self): """ The main goal of this test is to validate that the driver correctly receive options via setsockopt. @@ -1929,7 +1924,7 @@ def testTiming(self): self.assertEqual(bindata2, self.bindata2) measured_time = math.ceil(self.toc-self.tic) min_time = math.floor(self.calcFrameCnt(len(self.bindata2))*0.127) - self.assertGreater(measured_time, min_time) + self.assertGreaterEqual(measured_time, min_time) def calcFrameCnt(self, size): if size < 1 or size > 4095: From fba09e6dc3eb1b8967dcfe57eb4c7868a3586b58 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Wed, 2 Aug 2017 19:22:45 -0700 Subject: [PATCH 08/13] Removed dependecy over third party project --- Doc/library/socket.rst | 43 +-------- Lib/test/test_socket.py | 193 ++-------------------------------------- Misc/ACKS | 6 +- Modules/socketmodule.c | 21 +---- Modules/socketmodule.h | 4 - configure | 4 +- configure.ac | 4 +- pyconfig.h.in | 3 - 8 files changed, 18 insertions(+), 260 deletions(-) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 612caf1d697331..c5064e92c2118b 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -348,8 +348,7 @@ Constants .. 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, are also - defined in the socket module. + ISO-TP constants, documented in the Linux documentation. Availability: Linux >= 2.6.25 @@ -1688,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 @@ -1746,44 +1745,6 @@ There is a :mod:`socket` flag to set, in order to prevent this, the :data:`SO_REUSEADDR` flag tells the kernel to reuse a local socket in ``TIME_WAIT`` state, without waiting for its natural timeout to expire. -The last example shows how to use the socket interface to communicate to a CAN -network using the ISO-TP protocol. Altough Linux defines the required interface -in , it does not support a native implementation of the protocol. -The appropriate module must be loaded in the kernel. - - modprobe can-isotp - -If the system does not support ISO-TP protocol, creating the socket might return:: - - OSError: [Errno 93] Protocol not supported - -This example might require special privileges:: - - import socket - import struct - - # create a iso-tp socket and bind it to the 'vcan0' interface, with rx_addr = 0x123 and tx_addr=0x456 - s1 = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) - s2 = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) - - # set socket some options before binding to the interface. - stmin = 0x32 # Minimum Separation Time : 50 milliseconds between each CAN frame. See ISO 15765-2 for acceptables values. - bs = 3 # Block Size : sends a FlowControl frame every 3 Consecutive Frames - wftmax = 5 # Maximum Wait Frame Transmission - Maximum number of consecutive Wait Frame. - s2.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, struct.pack("=BBB", bs, stmin, wftmax)) - - s1.bind(('vcan0',0x123, 0x456)) - s2.bind(('vcan0',0x456, 0x123)) - - data = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10" - s1.send(data) - s2.recv(len(data)) - - # The following transmission will occur - # vcan0 456 [8] 10 0A 01 02 03 04 05 06 - # vcan0 123 [3] 30 03 32 - # vcan0 456 [5] 21 07 08 09 10 - .. seealso:: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index ae07ec386a6dbb..ca39fd0f73db5a 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -365,50 +365,6 @@ def clientTearDown(self): self.cli = None ThreadableTest.clientTearDown(self) -class ThreadableISOTPTest(unittest.TestCase, ThreadableTest): - """To be able to run this test, a `vcan0` CAN interface can be created with - the following commands: - # modprobe vcan - # ip link add dev vcan0 type vcan - # ifconfig vcan0 up - """ - interface = 'vcan0' - addr_offset=1 - address_lock = threading.Lock() - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self,*args, **kwargs) - ThreadableTest.__init__(self) - self.test_opts = {} - (self.cli_addr, self.srv_addr) = ThreadableISOTPTest.getAddressPair() - - @classmethod - def getAddressPair(cls): - cls.address_lock.acquire_lock() - latched_offset = cls.addr_offset - cls.addr_offset += 2 - if cls.addr_offset > 0x7FE: # 11 bit standard CAN identifier (ISO-11898-2) - cls.addr_offset = 1 - cls.address_lock.release_lock() - return (latched_offset, latched_offset+1) - - def clientSetUp(self): - try: - self.opts = self.test_opts[self._testMethodName] if self._testMethodName in self.test_opts else {} - self.cli = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) - if 'cli' in self.opts: - for opt_const in self.opts['cli']: - self.serv.setsockopt(socket.SOL_CAN_ISOTP, opt_const, self.opts['cli'][opt_const]) - self.cli.bind((self.interface, self.srv_addr, self.cli_addr)) - except OSError: - # skipTest should not be called here, and will be called in the - # server instead - pass - - def clientTearDown(self): - self.cli.close() - self.cli = None - ThreadableTest.clientTearDown(self) - class ThreadedRDSSocketTest(SocketRDSTest, ThreadableTest): def __init__(self, methodName='runTest'): @@ -1766,7 +1722,7 @@ def testBCM(self): @unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') -class BasicISOTPTest(unittest.TestCase): +class ISOTPTest(unittest.TestCase): def testCrucialConstants(self): socket.AF_CAN @@ -1774,31 +1730,6 @@ def testCrucialConstants(self): socket.CAN_ISOTP socket.SOCK_DGRAM - @unittest.skipUnless(hasattr(socket, "CAN_ISOTP"), - 'socket.CAN_ISOTP required for this test.') - def testISOTPConstants(self): - socket.CAN_ISOTP - - socket.SOL_CAN_ISOTP # Socket Option Level for ISOTP protocol (AF_CAN + 6) - socket.CAN_ISOTP_OPTS # sets struct can_isotp_options within the driver. - socket.CAN_ISOTP_RECV_FC # set flow control options for receiver - socket.CAN_ISOTP_TX_STMIN # override sepration time received in flow control frame - socket.CAN_ISOTP_RX_STMIN # force to ignore messages received in an interval smaller than this value - socket.CAN_ISOTP_LL_OPTS # sets struct can_isotp_ll_options within the driver - - # Options flags - socket.CAN_ISOTP_LISTEN_MODE # listen only (do not send FC) - socket.CAN_ISOTP_EXTEND_ADDR # enable extended addressing - socket.CAN_ISOTP_TX_PADDING # enable CAN frame padding tx path - socket.CAN_ISOTP_RX_PADDING # enable CAN frame padding rx path - socket.CAN_ISOTP_CHK_PAD_LEN # check received CAN frame padding - socket.CAN_ISOTP_CHK_PAD_DATA # check received CAN frame padding - socket.CAN_ISOTP_HALF_DUPLEX # half duplex error state handling - socket.CAN_ISOTP_FORCE_TXSTMIN # ignore stmin from received FC - socket.CAN_ISOTP_FORCE_RXSTMIN # ignore CFs depending on rx stmin - socket.CAN_ISOTP_RX_EXT_ADDR # different rx extended addressing - - def testCreateSocket(self): with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s: pass @@ -1815,125 +1746,15 @@ def testTooLongInterfaceName(self): self.assertRaisesRegex(OSError, 'interface name too long', s.bind, ('x' * 1024,1,2)) - @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), - 'socket.SOL_CAN_ISOTP is required for this test') - @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_OPTS"), - 'socket.CAN_ISOTP_OPTS is required for this test') - def testSetOpts(self): - with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: - flags = 0x01234567 - frame_txtime = 0x89ABCDEF - ext_address = 0x11 - txpad_content = 0x21 - rxpad_content = 0x31 - rx_ext_address = ext_address - opts = struct.pack("=LLBBBB",flags, frame_txtime, ext_address, txpad_content, rxpad_content, rx_ext_address) - s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_OPTS, opts) - readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_OPTS, len(opts)) - self.assertEqual(readopts,opts) - - @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), - 'socket.SOL_CAN_ISOTP is required for this test') - @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), - 'socket.CAN_ISOTP_RECV_FC is required for this test') - def testSetFCOpts(self): - with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: - bs = 0x12 - stmin = 0x22 - wftmax = 0x32 - opts = struct.pack("=BBB",bs, stmin, wftmax) - s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, opts) - readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_RECV_FC, len(opts)) - self.assertEqual(readopts,opts) - - @unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), - 'socket.SOL_CAN_ISOTP is required for this test') - @unittest.skipUnless(hasattr(socket, "CAN_ISOTP_LL_OPTS"), - 'socket.CAN_ISOTP_LL_OPTS is required for this test') - def testSetLLOpts(self): - with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: - mtu = 16 - tx_dl = 8 - tx_flags = 0x13 - opts = struct.pack("=BBB",mtu, tx_dl, tx_flags) - s.setsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_LL_OPTS, opts) - readopts = s.getsockopt(socket.SOL_CAN_ISOTP, socket.CAN_ISOTP_LL_OPTS, len(opts)) - self.assertEqual(readopts,opts) - - -@unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.') -@unittest.skipUnless(hasattr(socket, "SOL_CAN_ISOTP"), - 'socket.SOL_CAN_ISOTP is required for this test') -@unittest.skipUnless(hasattr(socket, "CAN_ISOTP_RECV_FC"), - 'socket.CAN_ISOTP_RECV_FC is required for this test') -class ISOTPTest(ThreadableISOTPTest): - - def __init__(self, methodName='runTest', *args, **kwargs): - ThreadableISOTPTest.__init__(self,methodName, *args, **kwargs) - - def setUp(self, *args, **kwargs): - self.test_opts['testTiming'] = { - 'cli' : { - socket.CAN_ISOTP_RECV_FC : struct.pack("=BBB",10, 0x7F, 10) # bs, stmin, wftmax - }, - 'serv' : {} - } - - self.serv = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) - self.opts = self.test_opts[self._testMethodName] if self._testMethodName in self.test_opts else {} + def testBind(self): try: - if 'serv' in self.opts: - for opt_const in self.opts['serv']: - self.serv.setsockopt(socket.SOL_CAN_ISOTP, opt_const, self.opts['serv'][opt_const]) - self.serv.bind((self.interface, self.cli_addr, self.srv_addr)) - except OSError: - self.skipTest('network interface `%s` does not exist' % - self.interface) - - def tearDown(self): - self.serv.close() - self.serv=None - - @classmethod - def makeRandomData(cls, n): - data = [random.randint(0,0xFF) for i in range(0,n)] - return struct.pack("B"*len(data), *data) + with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: + s.bind(("vcan0",1,2)) - def _testFullFrame(self): - self.bindata = self.makeRandomData(4095) - self.cli.send(self.bindata) + except OSError as e: + if e.errno not in [errno.ENODEV, errno.EPROTONOSUPPORT]: + raise - def testFullFrame(self): - time.sleep(0.1) - bindata = self.serv.recv(len(self.bindata)) - self.assertEqual(bindata, self.bindata) - - def _testTiming(self): - self.bindata2 = self.makeRandomData(512) - self.tic = time.time() - self.cli.send(self.bindata2) - - def testTiming(self): - """ - The main goal of this test is to validate that the driver correctly receive options via setsockopt. - We are not trying to validate the proper functioning of the driver itself. - """ - time.sleep(0.1) - bindata2 = self.serv.recv(len(self.bindata2)) - self.toc = time.time() - self.assertEqual(bindata2, self.bindata2) - measured_time = math.ceil(self.toc-self.tic) - min_time = math.floor(self.calcFrameCnt(len(self.bindata2))*0.127) - self.assertGreaterEqual(measured_time, min_time) - - def calcFrameCnt(self, size): - if size < 1 or size > 4095: - raise ValueError('ISOTP frame size must be between 1 and 4095') - - if (size < 8): - return 1 - else: - return int(1 + math.ceil((float(size) - 6.0) / 7.0)) @unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.') diff --git a/Misc/ACKS b/Misc/ACKS index 7b709ba41f4839..777d10ce3da65e 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -904,6 +904,7 @@ John Lenton Kostyantyn Leschenko Benno Leslie Christopher Tur Lesniewski-Laas +Pier-Yves Lessard Alain Leufroy Mark Levinson Mark Levitt @@ -1356,7 +1357,7 @@ Cheryl Sabella Patrick Sabin Sébastien Sablé Amit Saha -Suman Saha +Suman Shahaf Hajime Saitou George Sakkis Victor Salgado @@ -1765,5 +1766,4 @@ Tarek Ziadé Jelle Zijlstra Gennadiy Zlobin Doug Zongker -Peter Åstrand -Pier-Yves Lessard +Peter Åstrand \ No newline at end of file diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 8def56a959e81a..99580e76558f5e 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7042,6 +7042,8 @@ PyInit__socket(void) PyModule_AddIntMacro(m, CAN_SFF_MASK); PyModule_AddIntMacro(m, CAN_EFF_MASK); PyModule_AddIntMacro(m, CAN_ERR_MASK); + + PyModule_AddIntMacro(m, CAN_ISOTP); #endif #ifdef HAVE_LINUX_CAN_RAW_H PyModule_AddIntMacro(m, CAN_RAW_FILTER); @@ -7067,25 +7069,6 @@ PyInit__socket(void) PyModule_AddIntConstant(m, "CAN_BCM_RX_TIMEOUT", RX_TIMEOUT); PyModule_AddIntConstant(m, "CAN_BCM_RX_CHANGED", RX_CHANGED); #endif -#ifdef HAVE_LINUX_CAN_ISOTP_H - PyModule_AddIntMacro(m, SOL_CAN_ISOTP); - PyModule_AddIntMacro(m, CAN_ISOTP); - PyModule_AddIntMacro(m, CAN_ISOTP_OPTS); - PyModule_AddIntMacro(m, CAN_ISOTP_RECV_FC); - PyModule_AddIntMacro(m, CAN_ISOTP_TX_STMIN); - PyModule_AddIntMacro(m, CAN_ISOTP_RX_STMIN); - PyModule_AddIntMacro(m, CAN_ISOTP_LL_OPTS); - PyModule_AddIntMacro(m, CAN_ISOTP_LISTEN_MODE); - PyModule_AddIntMacro(m, CAN_ISOTP_EXTEND_ADDR); - PyModule_AddIntMacro(m, CAN_ISOTP_TX_PADDING); - PyModule_AddIntMacro(m, CAN_ISOTP_RX_PADDING); - PyModule_AddIntMacro(m, CAN_ISOTP_CHK_PAD_LEN); - PyModule_AddIntMacro(m, CAN_ISOTP_CHK_PAD_DATA); - PyModule_AddIntMacro(m, CAN_ISOTP_HALF_DUPLEX); - PyModule_AddIntMacro(m, CAN_ISOTP_FORCE_TXSTMIN); - PyModule_AddIntMacro(m, CAN_ISOTP_FORCE_RXSTMIN); - PyModule_AddIntMacro(m, CAN_ISOTP_RX_EXT_ADDR); -#endif #ifdef SOL_RDS PyModule_AddIntMacro(m, SOL_RDS); #endif diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index e1141e683b4e87..03f982b91083f3 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -91,10 +91,6 @@ typedef int socklen_t; #include #endif -#ifdef HAVE_LINUX_CAN_ISOTP_H -#include -#endif - #ifdef HAVE_SYS_SYS_DOMAIN_H #include #endif diff --git a/configure b/configure index 7a4f61696b571a..a6f7d2bd7f0d9e 100755 --- a/configure +++ b/configure @@ -8127,8 +8127,8 @@ fi done -# On Linux, can.h, can/raw.h, can/isotp.h require sys/socket.h -for ac_header in linux/can.h linux/can/raw.h linux/can/bcm.h linux/can/isotp.h +# 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 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 3058fb20d7b889..a5da830e89623c 100644 --- a/configure.ac +++ b/configure.ac @@ -2120,8 +2120,8 @@ AC_CHECK_HEADERS(linux/netlink.h,,,[ #endif ]) -# On Linux, can.h, can/raw.h, can/isotp.h require sys/socket.h -AC_CHECK_HEADERS(linux/can.h linux/can/raw.h linux/can/bcm.h linux/can/isotp.h,,,[ +# 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,,,[ #ifdef HAVE_SYS_SOCKET_H #include #endif diff --git a/pyconfig.h.in b/pyconfig.h.in index 77b6e90a6c0936..0dd05aa65b8a4f 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -559,9 +559,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_CAN_H -/* Define to 1 if you have the header file. */ -#undef HAVE_LINUX_CAN_ISOTP_H - /* Define if compiling using Linux 3.6 or later. */ #undef HAVE_LINUX_CAN_RAW_FD_FRAMES From 17c58d70c307ce8383a821cd6c954d5b561d49b0 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Wed, 2 Aug 2017 21:05:34 -0700 Subject: [PATCH 09/13] Added implementation for getsockname + unit tests --- Lib/test/test_socket.py | 15 +++++++++++---- Modules/socketmodule.c | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index ca39fd0f73db5a..611a20e7ad313a 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1724,6 +1724,10 @@ def testBCM(self): @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 @@ -1749,14 +1753,17 @@ def testTooLongInterfaceName(self): def testBind(self): try: with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s: - s.bind(("vcan0",1,2)) - + addr = (self.interface,0x123,0x456) + s.bind(addr) + self.assertEqual(s.getsockname(), addr) except OSError as e: - if e.errno not in [errno.ENODEV, errno.EPROTONOSUPPORT]: + 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/Modules/socketmodule.c b/Modules/socketmodule.c index 99580e76558f5e..17e20d2d4f3903 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 From 7da8fedcbe2a6a95c05c993a5018211d751380af Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Wed, 2 Aug 2017 21:13:45 -0700 Subject: [PATCH 10/13] Missing newline at end of ACKS file --- Misc/ACKS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/ACKS b/Misc/ACKS index 777d10ce3da65e..e91554c4f3210f 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1766,4 +1766,4 @@ Tarek Ziadé Jelle Zijlstra Gennadiy Zlobin Doug Zongker -Peter Åstrand \ No newline at end of file +Peter Åstrand From 1365fca342f8cd2ff0a3c919f78729b52f89cf3e Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Thu, 3 Aug 2017 21:12:21 -0700 Subject: [PATCH 11/13] Accidentally inserted a type in ACKS file --- Misc/ACKS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/ACKS b/Misc/ACKS index e91554c4f3210f..6e2ae850ab2aa3 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1357,7 +1357,7 @@ Cheryl Sabella Patrick Sabin Sébastien Sablé Amit Saha -Suman Shahaf +Suman Saha Hajime Saitou George Sakkis Victor Salgado From 280141c99b81411cd000bb61391a9c39935901c6 Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sat, 26 Aug 2017 13:00:12 -0700 Subject: [PATCH 12/13] Followed tiran changes review #1 recommendations --- Lib/test/test_socket.py | 6 +++--- Modules/socketmodule.c | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 611a20e7ad313a..4060a7dbf24c1d 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1747,13 +1747,13 @@ def testCreateISOTPSocket(self): 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: - self.assertRaisesRegex(OSError, 'interface name too long', - s.bind, ('x' * 1024,1,2)) + 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) + addr = self.interface,0x123,0x456 s.bind(addr) self.assertEqual(s.getsockname(), addr) except OSError as e: diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 17e20d2d4f3903..225551c4c02ac1 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -1882,7 +1882,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, } #endif -#if defined(AF_CAN) +#ifdef AF_CAN #if defined(CAN_RAW) && defined(CAN_BCM) case AF_CAN: @@ -1929,7 +1929,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, } #endif -#if defined(CAN_ISOTP) +#ifdef CAN_ISOTP case CAN_ISOTP: { struct sockaddr_can *addr; @@ -7055,9 +7055,10 @@ 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); PyModule_AddIntMacro(m, CAN_RAW_ERR_FILTER); From e2cc6f3146083bed0ddf2fb1f20e9d64d129f9ed Mon Sep 17 00:00:00 2001 From: Pier-Yves Lessard Date: Sun, 27 Aug 2017 13:56:26 -0700 Subject: [PATCH 13/13] Added spaces after comma --- Lib/test/test_socket.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 4060a7dbf24c1d..84234887747983 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1748,12 +1748,12 @@ 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)) + 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 + addr = self.interface, 0x123, 0x456 s.bind(addr) self.assertEqual(s.getsockname(), addr) except OSError as e: