Skip to content

Commit 04f20ba

Browse files
pylessardGadgetSteve
authored andcommitted
bpo-30987 - Support for ISO-TP protocol in SocketCAN (python#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
1 parent 777ceca commit 04f20ba

File tree

5 files changed

+146
-8
lines changed

5 files changed

+146
-8
lines changed

Doc/library/socket.rst

+19-3
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ created. Socket addresses are represented as follows:
103103
``'can0'``. The network interface name ``''`` can be used to receive packets
104104
from all network interfaces of this family.
105105

106+
- :const:`CAN_ISOTP` protocol require a tuple ``(interface, rx_addr, tx_addr)``
107+
where both additional parameters are unsigned long integer that represent a
108+
CAN identifier (standard or extended).
109+
106110
- A string or a tuple ``(id, unit)`` is used for the :const:`SYSPROTO_CONTROL`
107111
protocol of the :const:`PF_SYSTEM` family. The string is the name of a
108112
kernel control using a dynamically-assigned ID. The tuple can be used if ID
@@ -341,6 +345,16 @@ Constants
341345

342346
.. versionadded:: 3.5
343347

348+
.. data:: CAN_ISOTP
349+
350+
CAN_ISOTP, in the CAN protocol family, is the ISO-TP (ISO 15765-2) protocol.
351+
ISO-TP constants, documented in the Linux documentation.
352+
353+
Availability: Linux >= 2.6.25
354+
355+
.. versionadded:: 3.7
356+
357+
344358
.. data:: AF_RDS
345359
PF_RDS
346360
SOL_RDS
@@ -427,7 +441,7 @@ The following functions all create :ref:`socket objects <socket-objects>`.
427441
:const:`SOCK_DGRAM`, :const:`SOCK_RAW` or perhaps one of the other ``SOCK_``
428442
constants. The protocol number is usually zero and may be omitted or in the
429443
case where the address family is :const:`AF_CAN` the protocol should be one
430-
of :const:`CAN_RAW` or :const:`CAN_BCM`. If *fileno* is specified, the other
444+
of :const:`CAN_RAW`, :const:`CAN_BCM` or :const:`CAN_ISOTP`. If *fileno* is specified, the other
431445
arguments are ignored, causing the socket with the specified file descriptor
432446
to return. Unlike :func:`socket.fromfd`, *fileno* will return the same
433447
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>`.
445459
.. versionchanged:: 3.4
446460
The returned socket is now non-inheritable.
447461

462+
.. versionchanged:: 3.7
463+
The CAN_ISOTP protocol was added.
448464

449465
.. function:: socketpair([family[, type[, proto]]])
450466

@@ -1661,7 +1677,7 @@ the interface::
16611677
# disabled promiscuous mode
16621678
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
16631679

1664-
The last example shows how to use the socket interface to communicate to a CAN
1680+
The next example shows how to use the socket interface to communicate to a CAN
16651681
network using the raw socket protocol. To use CAN with the broadcast
16661682
manager protocol instead, open a socket with::
16671683

@@ -1671,7 +1687,7 @@ After binding (:const:`CAN_RAW`) or connecting (:const:`CAN_BCM`) the socket, yo
16711687
can use the :meth:`socket.send`, and the :meth:`socket.recv` operations (and
16721688
their counterparts) on the socket object as usual.
16731689

1674-
This example might require special privileges::
1690+
This last example might require special privileges::
16751691

16761692
import socket
16771693
import struct

Lib/test/test_socket.py

+55
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ def _have_socket_can():
5555
s.close()
5656
return True
5757

58+
def _have_socket_can_isotp():
59+
"""Check whether CAN ISOTP sockets are supported on this host."""
60+
try:
61+
s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP)
62+
except (AttributeError, OSError):
63+
return False
64+
else:
65+
s.close()
66+
return True
67+
5868
def _have_socket_rds():
5969
"""Check whether RDS sockets are supported on this host."""
6070
try:
@@ -77,6 +87,8 @@ def _have_socket_alg():
7787

7888
HAVE_SOCKET_CAN = _have_socket_can()
7989

90+
HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp()
91+
8092
HAVE_SOCKET_RDS = _have_socket_rds()
8193

8294
HAVE_SOCKET_ALG = _have_socket_alg()
@@ -1709,6 +1721,49 @@ def testBCM(self):
17091721
self.assertEqual(bytes_sent, len(header_plus_frame))
17101722

17111723

1724+
@unittest.skipUnless(HAVE_SOCKET_CAN_ISOTP, 'CAN ISOTP required for this test.')
1725+
class ISOTPTest(unittest.TestCase):
1726+
1727+
def __init__(self, *args, **kwargs):
1728+
super().__init__(*args, **kwargs)
1729+
self.interface = "vcan0"
1730+
1731+
def testCrucialConstants(self):
1732+
socket.AF_CAN
1733+
socket.PF_CAN
1734+
socket.CAN_ISOTP
1735+
socket.SOCK_DGRAM
1736+
1737+
def testCreateSocket(self):
1738+
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
1739+
pass
1740+
1741+
@unittest.skipUnless(hasattr(socket, "CAN_ISOTP"),
1742+
'socket.CAN_ISOTP required for this test.')
1743+
def testCreateISOTPSocket(self):
1744+
with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s:
1745+
pass
1746+
1747+
def testTooLongInterfaceName(self):
1748+
# most systems limit IFNAMSIZ to 16, take 1024 to be sure
1749+
with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s:
1750+
with self.assertRaisesRegex(OSError, 'interface name too long'):
1751+
s.bind(('x' * 1024, 1, 2))
1752+
1753+
def testBind(self):
1754+
try:
1755+
with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP) as s:
1756+
addr = self.interface, 0x123, 0x456
1757+
s.bind(addr)
1758+
self.assertEqual(s.getsockname(), addr)
1759+
except OSError as e:
1760+
if e.errno == errno.ENODEV:
1761+
self.skipTest('network interface `%s` does not exist' %
1762+
self.interface)
1763+
else:
1764+
raise
1765+
1766+
17121767
@unittest.skipUnless(HAVE_SOCKET_RDS, 'RDS sockets required for this test.')
17131768
class BasicRDSTest(unittest.TestCase):
17141769

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@ John Lenton
908908
Kostyantyn Leschenko
909909
Benno Leslie
910910
Christopher Tur Lesniewski-Laas
911+
Pier-Yves Lessard
911912
Alain Leufroy
912913
Mark Levinson
913914
Mark Levitt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for CAN ISO-TP protocol in the socket module.

Modules/socketmodule.c

+70-5
Original file line numberDiff line numberDiff line change
@@ -1373,9 +1373,22 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
13731373
ifname = ifr.ifr_name;
13741374
}
13751375

1376-
return Py_BuildValue("O&h", PyUnicode_DecodeFSDefault,
1377-
ifname,
1378-
a->can_family);
1376+
switch (proto) {
1377+
#ifdef CAN_ISOTP
1378+
case CAN_ISOTP:
1379+
{
1380+
return Py_BuildValue("O&kk", PyUnicode_DecodeFSDefault,
1381+
ifname,
1382+
a->can_addr.tp.rx_id,
1383+
a->can_addr.tp.tx_id);
1384+
}
1385+
#endif
1386+
default:
1387+
{
1388+
return Py_BuildValue("O&", PyUnicode_DecodeFSDefault,
1389+
ifname);
1390+
}
1391+
}
13791392
}
13801393
#endif
13811394

@@ -1869,7 +1882,9 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
18691882
}
18701883
#endif
18711884

1872-
#if defined(AF_CAN) && defined(CAN_RAW) && defined(CAN_BCM)
1885+
#ifdef AF_CAN
1886+
1887+
#if defined(CAN_RAW) && defined(CAN_BCM)
18731888
case AF_CAN:
18741889
switch (s->sock_proto) {
18751890
case CAN_RAW:
@@ -1880,7 +1895,6 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
18801895
PyObject *interfaceName;
18811896
struct ifreq ifr;
18821897
Py_ssize_t len;
1883-
18841898
addr = (struct sockaddr_can *)addr_ret;
18851899

18861900
if (!PyArg_ParseTuple(args, "O&", PyUnicode_FSConverter,
@@ -1913,6 +1927,54 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
19131927
Py_DECREF(interfaceName);
19141928
return 1;
19151929
}
1930+
#endif
1931+
1932+
#ifdef CAN_ISOTP
1933+
case CAN_ISOTP:
1934+
{
1935+
struct sockaddr_can *addr;
1936+
PyObject *interfaceName;
1937+
struct ifreq ifr;
1938+
Py_ssize_t len;
1939+
unsigned long int rx_id, tx_id;
1940+
1941+
addr = (struct sockaddr_can *)addr_ret;
1942+
1943+
if (!PyArg_ParseTuple(args, "O&kk", PyUnicode_FSConverter,
1944+
&interfaceName,
1945+
&rx_id,
1946+
&tx_id))
1947+
return 0;
1948+
1949+
len = PyBytes_GET_SIZE(interfaceName);
1950+
1951+
if (len == 0) {
1952+
ifr.ifr_ifindex = 0;
1953+
} else if ((size_t)len < sizeof(ifr.ifr_name)) {
1954+
strncpy(ifr.ifr_name, PyBytes_AS_STRING(interfaceName), sizeof(ifr.ifr_name));
1955+
ifr.ifr_name[(sizeof(ifr.ifr_name))-1] = '\0';
1956+
if (ioctl(s->sock_fd, SIOCGIFINDEX, &ifr) < 0) {
1957+
s->errorhandler();
1958+
Py_DECREF(interfaceName);
1959+
return 0;
1960+
}
1961+
} else {
1962+
PyErr_SetString(PyExc_OSError,
1963+
"AF_CAN interface name too long");
1964+
Py_DECREF(interfaceName);
1965+
return 0;
1966+
}
1967+
1968+
addr->can_family = AF_CAN;
1969+
addr->can_ifindex = ifr.ifr_ifindex;
1970+
addr->can_addr.tp.rx_id = rx_id;
1971+
addr->can_addr.tp.tx_id = tx_id;
1972+
1973+
*len_ret = sizeof(*addr);
1974+
Py_DECREF(interfaceName);
1975+
return 1;
1976+
}
1977+
#endif
19161978
default:
19171979
PyErr_SetString(PyExc_OSError,
19181980
"getsockaddrarg: unsupported CAN protocol");
@@ -6995,6 +7057,9 @@ PyInit__socket(void)
69957057
PyModule_AddIntMacro(m, CAN_SFF_MASK);
69967058
PyModule_AddIntMacro(m, CAN_EFF_MASK);
69977059
PyModule_AddIntMacro(m, CAN_ERR_MASK);
7060+
#ifdef CAN_ISOTP
7061+
PyModule_AddIntMacro(m, CAN_ISOTP);
7062+
#endif
69987063
#endif
69997064
#ifdef HAVE_LINUX_CAN_RAW_H
70007065
PyModule_AddIntMacro(m, CAN_RAW_FILTER);

0 commit comments

Comments
 (0)