mirror of
https://github.com/python/cpython.git
synced 2025-07-14 23:05:17 +00:00
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
This commit is contained in:
parent
ed94a8b285
commit
a30f6d45ac
5 changed files with 146 additions and 8 deletions
|
@ -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
|
||||||
|
|
||||||
.. 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>`.
|
||||||
: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>`.
|
||||||
.. 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::
|
||||||
# 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
|
||||||
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():
|
||||||
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():
|
||||||
|
|
||||||
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):
|
||||||
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
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
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)
|
||||||
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,
|
||||||
}
|
}
|
||||||
#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,
|
||||||
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,
|
||||||
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)
|
||||||
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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue