Commit a30f6d45 authored by Pier-Yves Lessard's avatar Pier-Yves Lessard Committed by Christian Heimes

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
parent ed94a8b2
...@@ -103,6 +103,10 @@ created. Socket addresses are represented as follows: ...@@ -103,6 +103,10 @@ created. Socket addresses are represented as follows:
``'can0'``. The network interface name ``''`` can be used to receive packets ``'can0'``. The network interface name ``''`` can be used to receive packets
from all network interfaces of this family. 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` - 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 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 kernel control using a dynamically-assigned ID. The tuple can be used if ID
...@@ -341,6 +345,16 @@ Constants ...@@ -341,6 +345,16 @@ Constants
.. versionadded:: 3.5 .. 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 .. data:: AF_RDS
PF_RDS PF_RDS
SOL_RDS SOL_RDS
...@@ -427,7 +441,7 @@ The following functions all create :ref:`socket objects <socket-objects>`. ...@@ -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_`` :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 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 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 arguments are ignored, causing the socket with the specified file descriptor
to return. Unlike :func:`socket.fromfd`, *fileno* will return the same to return. Unlike :func:`socket.fromfd`, *fileno* will return the same
socket and not a duplicate. This may help close a detached socket using 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>`. ...@@ -445,6 +459,8 @@ The following functions all create :ref:`socket objects <socket-objects>`.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
The returned socket is now non-inheritable. The returned socket is now non-inheritable.
.. versionchanged:: 3.7
The CAN_ISOTP protocol was added.
.. function:: socketpair([family[, type[, proto]]]) .. function:: socketpair([family[, type[, proto]]])
...@@ -1661,7 +1677,7 @@ the interface:: ...@@ -1661,7 +1677,7 @@ the interface::
# disabled promiscuous mode # disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) 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 network using the raw socket protocol. To use CAN with the broadcast
manager protocol instead, open a socket with:: manager protocol instead, open a socket with::
...@@ -1671,7 +1687,7 @@ After binding (:const:`CAN_RAW`) or connecting (:const:`CAN_BCM`) the socket, yo ...@@ -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 can use the :meth:`socket.send`, and the :meth:`socket.recv` operations (and
their counterparts) on the socket object as usual. their counterparts) on the socket object as usual.
This example might require special privileges:: This last example might require special privileges::
import socket import socket
import struct import struct
......
...@@ -55,6 +55,16 @@ def _have_socket_can(): ...@@ -55,6 +55,16 @@ def _have_socket_can():
s.close() s.close()
return True 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(): def _have_socket_rds():
"""Check whether RDS sockets are supported on this host.""" """Check whether RDS sockets are supported on this host."""
try: try:
...@@ -77,6 +87,8 @@ def _have_socket_alg(): ...@@ -77,6 +87,8 @@ def _have_socket_alg():
HAVE_SOCKET_CAN = _have_socket_can() HAVE_SOCKET_CAN = _have_socket_can()
HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp()
HAVE_SOCKET_RDS = _have_socket_rds() HAVE_SOCKET_RDS = _have_socket_rds()
HAVE_SOCKET_ALG = _have_socket_alg() HAVE_SOCKET_ALG = _have_socket_alg()
...@@ -1709,6 +1721,49 @@ class CANTest(ThreadedCANSocketTest): ...@@ -1709,6 +1721,49 @@ class CANTest(ThreadedCANSocketTest):
self.assertEqual(bytes_sent, len(header_plus_frame)) 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.') @unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.')
class BasicRDSTest(unittest.TestCase): class BasicRDSTest(unittest.TestCase):
......
...@@ -908,6 +908,7 @@ John Lenton ...@@ -908,6 +908,7 @@ John Lenton
Kostyantyn Leschenko Kostyantyn Leschenko
Benno Leslie Benno Leslie
Christopher Tur Lesniewski-Laas Christopher Tur Lesniewski-Laas
Pier-Yves Lessard
Alain Leufroy Alain Leufroy
Mark Levinson Mark Levinson
Mark Levitt Mark Levitt
......
Added support for CAN ISO-TP protocol in the socket module.
...@@ -1373,9 +1373,22 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto) ...@@ -1373,9 +1373,22 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
ifname = ifr.ifr_name; ifname = ifr.ifr_name;
} }
return Py_BuildValue("O&h", PyUnicode_DecodeFSDefault, switch (proto) {
ifname, #ifdef CAN_ISOTP
a->can_family); 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 #endif
...@@ -1869,7 +1882,9 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, ...@@ -1869,7 +1882,9 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
} }
#endif #endif
#if defined(AF_CAN) && defined(CAN_RAW) && defined(CAN_BCM) #ifdef AF_CAN
#if defined(CAN_RAW) && defined(CAN_BCM)
case AF_CAN: case AF_CAN:
switch (s->sock_proto) { switch (s->sock_proto) {
case CAN_RAW: case CAN_RAW:
...@@ -1880,7 +1895,6 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, ...@@ -1880,7 +1895,6 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
PyObject *interfaceName; PyObject *interfaceName;
struct ifreq ifr; struct ifreq ifr;
Py_ssize_t len; Py_ssize_t len;
addr = (struct sockaddr_can *)addr_ret; addr = (struct sockaddr_can *)addr_ret;
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter, if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter,
...@@ -1913,6 +1927,54 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, ...@@ -1913,6 +1927,54 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
Py_DECREF(interfaceName); Py_DECREF(interfaceName);
return 1; 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: default:
PyErr_SetString(PyExc_OSError, PyErr_SetString(PyExc_OSError,
"getsockaddrarg: unsupported CAN protocol"); "getsockaddrarg: unsupported CAN protocol");
...@@ -6995,6 +7057,9 @@ PyInit__socket(void) ...@@ -6995,6 +7057,9 @@ PyInit__socket(void)
PyModule_AddIntMacro(m, CAN_SFF_MASK); PyModule_AddIntMacro(m, CAN_SFF_MASK);
PyModule_AddIntMacro(m, CAN_EFF_MASK); PyModule_AddIntMacro(m, CAN_EFF_MASK);
PyModule_AddIntMacro(m, CAN_ERR_MASK); PyModule_AddIntMacro(m, CAN_ERR_MASK);
#ifdef CAN_ISOTP
PyModule_AddIntMacro(m, CAN_ISOTP);
#endif
#endif #endif
#ifdef HAVE_LINUX_CAN_RAW_H #ifdef HAVE_LINUX_CAN_RAW_H
PyModule_AddIntMacro(m, CAN_RAW_FILTER); PyModule_AddIntMacro(m, CAN_RAW_FILTER);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment