Skip to content

Commit

Permalink
bpo-30987 - Support for ISO-TP protocol in SocketCAN (python#2956)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pylessard authored and GadgetSteve committed Sep 10, 2017
1 parent 777ceca commit 04f20ba
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 8 deletions.
22 changes: 19 additions & 3 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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]]])

Expand Down Expand Up @@ -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::

Expand All @@ -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
Expand Down
55 changes: 55 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()
Expand Down Expand Up @@ -1709,6 +1721,49 @@ def testBCM(self):
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):

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ John Lenton
Kostyantyn Leschenko
Benno Leslie
Christopher Tur Lesniewski-Laas
Pier-Yves Lessard
Alain Leufroy
Mark Levinson
Mark Levitt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for CAN ISO-TP protocol in the socket module.
75 changes: 70 additions & 5 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 04f20ba

Please sign in to comment.