summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPier-Yves Lessard <py.lessard@gmail.com>2017-08-28 08:32:44 (GMT)
committerChristian Heimes <christian@python.org>2017-08-28 08:32:44 (GMT)
commita30f6d45ac3e72761b96a8df0527182029eaee24 (patch)
tree78979f1ea774a03b5f895f4a661a829a247f3a83
parented94a8b2851914bcda3a77b28b25517b8baa91e6 (diff)
downloadcpython-a30f6d45ac3e72761b96a8df0527182029eaee24.zip
cpython-a30f6d45ac3e72761b96a8df0527182029eaee24.tar.gz
cpython-a30f6d45ac3e72761b96a8df0527182029eaee24.tar.bz2
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
-rw-r--r--Doc/library/socket.rst22
-rw-r--r--Lib/test/test_socket.py55
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2017-07-30-22-00-12.bpo-30987.228rW0.rst1
-rw-r--r--Modules/socketmodule.c75
5 files changed, 146 insertions, 8 deletions
diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst
index 8af6bc5..c5064e9 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 <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 <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 f28f7d6..8423488 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 @@ class CANTest(ThreadedCANSocketTest):
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 b2e0592..91e0424 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 0000000..de30ea8
--- /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 bf8d19f..beadecf 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);