mirror of https://github.com/python/cpython.git
bpo-40291: Add support for CAN_J1939 sockets (GH-19538)
Add support for CAN_J1939 sockets that wrap SAE J1939 protocol functionality provided by Linux 5.4+ kernels.
This commit is contained in:
parent
fd33cdbd05
commit
360371f79c
|
@ -118,6 +118,10 @@ created. Socket addresses are represented as follows:
|
|||
- :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).
|
||||
- :const:`CAN_J1939` protocol require a tuple ``(interface, name, pgn, addr)``
|
||||
where additional parameters are 64-bit unsigned integer representing the
|
||||
ECU name, a 32-bit unsigned integer representing the Parameter Group Number
|
||||
(PGN), and an 8-bit integer representing the address.
|
||||
|
||||
- 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
|
||||
|
@ -428,6 +432,15 @@ Constants
|
|||
|
||||
.. versionadded:: 3.7
|
||||
|
||||
.. data:: CAN_J1939
|
||||
|
||||
CAN_J1939, in the CAN protocol family, is the SAE J1939 protocol.
|
||||
J1939 constants, documented in the Linux documentation.
|
||||
|
||||
.. availability:: Linux >= 5.4.
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
.. data:: AF_PACKET
|
||||
PF_PACKET
|
||||
|
@ -544,7 +557,8 @@ The following functions all create :ref:`socket objects <socket-objects>`.
|
|||
default), :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`, :const:`CAN_BCM` or :const:`CAN_ISOTP`.
|
||||
should be one of :const:`CAN_RAW`, :const:`CAN_BCM`, :const:`CAN_ISOTP` or
|
||||
:const:`CAN_J1939`.
|
||||
|
||||
If *fileno* is specified, the values for *family*, *type*, and *proto* are
|
||||
auto-detected from the specified file descriptor. Auto-detection can be
|
||||
|
@ -588,6 +602,9 @@ The following functions all create :ref:`socket objects <socket-objects>`.
|
|||
``SOCK_NONBLOCK``, but ``sock.type`` will be set to
|
||||
``socket.SOCK_STREAM``.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
The CAN_J1939 protocol was added.
|
||||
|
||||
.. function:: socketpair([family[, type[, proto]]])
|
||||
|
||||
Build a pair of connected socket objects using the given address family, socket
|
||||
|
|
|
@ -80,6 +80,16 @@ def _have_socket_can_isotp():
|
|||
s.close()
|
||||
return True
|
||||
|
||||
def _have_socket_can_j1939():
|
||||
"""Check whether CAN J1939 sockets are supported on this host."""
|
||||
try:
|
||||
s = socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939)
|
||||
except (AttributeError, OSError):
|
||||
return False
|
||||
else:
|
||||
s.close()
|
||||
return True
|
||||
|
||||
def _have_socket_rds():
|
||||
"""Check whether RDS sockets are supported on this host."""
|
||||
try:
|
||||
|
@ -143,6 +153,8 @@ def socket_setdefaulttimeout(timeout):
|
|||
|
||||
HAVE_SOCKET_CAN_ISOTP = _have_socket_can_isotp()
|
||||
|
||||
HAVE_SOCKET_CAN_J1939 = _have_socket_can_j1939()
|
||||
|
||||
HAVE_SOCKET_RDS = _have_socket_rds()
|
||||
|
||||
HAVE_SOCKET_ALG = _have_socket_alg()
|
||||
|
@ -2117,6 +2129,68 @@ def testBind(self):
|
|||
raise
|
||||
|
||||
|
||||
@unittest.skipUnless(HAVE_SOCKET_CAN_J1939, 'CAN J1939 required for this test.')
|
||||
class J1939Test(unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.interface = "vcan0"
|
||||
|
||||
@unittest.skipUnless(hasattr(socket, "CAN_J1939"),
|
||||
'socket.CAN_J1939 required for this test.')
|
||||
def testJ1939Constants(self):
|
||||
socket.CAN_J1939
|
||||
|
||||
socket.J1939_MAX_UNICAST_ADDR
|
||||
socket.J1939_IDLE_ADDR
|
||||
socket.J1939_NO_ADDR
|
||||
socket.J1939_NO_NAME
|
||||
socket.J1939_PGN_REQUEST
|
||||
socket.J1939_PGN_ADDRESS_CLAIMED
|
||||
socket.J1939_PGN_ADDRESS_COMMANDED
|
||||
socket.J1939_PGN_PDU1_MAX
|
||||
socket.J1939_PGN_MAX
|
||||
socket.J1939_NO_PGN
|
||||
|
||||
# J1939 socket options
|
||||
socket.SO_J1939_FILTER
|
||||
socket.SO_J1939_PROMISC
|
||||
socket.SO_J1939_SEND_PRIO
|
||||
socket.SO_J1939_ERRQUEUE
|
||||
|
||||
socket.SCM_J1939_DEST_ADDR
|
||||
socket.SCM_J1939_DEST_NAME
|
||||
socket.SCM_J1939_PRIO
|
||||
socket.SCM_J1939_ERRQUEUE
|
||||
|
||||
socket.J1939_NLA_PAD
|
||||
socket.J1939_NLA_BYTES_ACKED
|
||||
|
||||
socket.J1939_EE_INFO_NONE
|
||||
socket.J1939_EE_INFO_TX_ABORT
|
||||
|
||||
socket.J1939_FILTER_MAX
|
||||
|
||||
@unittest.skipUnless(hasattr(socket, "CAN_J1939"),
|
||||
'socket.CAN_J1939 required for this test.')
|
||||
def testCreateJ1939Socket(self):
|
||||
with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s:
|
||||
pass
|
||||
|
||||
def testBind(self):
|
||||
try:
|
||||
with socket.socket(socket.PF_CAN, socket.SOCK_DGRAM, socket.CAN_J1939) as s:
|
||||
addr = self.interface, socket.J1939_NO_NAME, socket.J1939_NO_PGN, socket.J1939_NO_ADDR
|
||||
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):
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Add support for CAN_J1939 sockets (available on Linux 5.4+)
|
|
@ -1556,6 +1556,16 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
|
|||
a->can_addr.tp.tx_id);
|
||||
}
|
||||
#endif /* CAN_ISOTP */
|
||||
#ifdef CAN_J1939
|
||||
case CAN_J1939:
|
||||
{
|
||||
return Py_BuildValue("O&KkB", PyUnicode_DecodeFSDefault,
|
||||
ifname,
|
||||
a->can_addr.j1939.name,
|
||||
a->can_addr.j1939.pgn,
|
||||
a->can_addr.j1939.addr);
|
||||
}
|
||||
#endif /* CAN_J1939 */
|
||||
default:
|
||||
{
|
||||
return Py_BuildValue("(O&)", PyUnicode_DecodeFSDefault,
|
||||
|
@ -2237,6 +2247,55 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
|
|||
return 1;
|
||||
}
|
||||
#endif /* CAN_ISOTP */
|
||||
#ifdef CAN_J1939
|
||||
case CAN_J1939:
|
||||
{
|
||||
PyObject *interfaceName;
|
||||
struct ifreq ifr;
|
||||
Py_ssize_t len;
|
||||
uint64_t j1939_name;
|
||||
uint32_t j1939_pgn;
|
||||
uint8_t j1939_addr;
|
||||
|
||||
struct sockaddr_can *addr = &addrbuf->can;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O&KkB", PyUnicode_FSConverter,
|
||||
&interfaceName,
|
||||
&j1939_name,
|
||||
&j1939_pgn,
|
||||
&j1939_addr))
|
||||
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.j1939.name = j1939_name;
|
||||
addr->can_addr.j1939.pgn = j1939_pgn;
|
||||
addr->can_addr.j1939.addr = j1939_addr;
|
||||
|
||||
*len_ret = sizeof(*addr);
|
||||
Py_DECREF(interfaceName);
|
||||
return 1;
|
||||
}
|
||||
#endif /* CAN_J1939 */
|
||||
default:
|
||||
PyErr_Format(PyExc_OSError,
|
||||
"%s(): unsupported CAN protocol", caller);
|
||||
|
@ -7687,6 +7746,9 @@ PyInit__socket(void)
|
|||
#ifdef CAN_ISOTP
|
||||
PyModule_AddIntMacro(m, CAN_ISOTP);
|
||||
#endif
|
||||
#ifdef CAN_J1939
|
||||
PyModule_AddIntMacro(m, CAN_J1939);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_CAN_RAW_H
|
||||
PyModule_AddIntMacro(m, CAN_RAW_FILTER);
|
||||
|
@ -7734,6 +7796,37 @@ PyInit__socket(void)
|
|||
PyModule_AddIntConstant(m, "CAN_BCM_CAN_FD_FRAME", CAN_FD_FRAME);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_CAN_J1939_H
|
||||
PyModule_AddIntMacro(m, J1939_MAX_UNICAST_ADDR);
|
||||
PyModule_AddIntMacro(m, J1939_IDLE_ADDR);
|
||||
PyModule_AddIntMacro(m, J1939_NO_ADDR);
|
||||
PyModule_AddIntMacro(m, J1939_NO_NAME);
|
||||
PyModule_AddIntMacro(m, J1939_PGN_REQUEST);
|
||||
PyModule_AddIntMacro(m, J1939_PGN_ADDRESS_CLAIMED);
|
||||
PyModule_AddIntMacro(m, J1939_PGN_ADDRESS_COMMANDED);
|
||||
PyModule_AddIntMacro(m, J1939_PGN_PDU1_MAX);
|
||||
PyModule_AddIntMacro(m, J1939_PGN_MAX);
|
||||
PyModule_AddIntMacro(m, J1939_NO_PGN);
|
||||
|
||||
/* J1939 socket options */
|
||||
PyModule_AddIntMacro(m, SO_J1939_FILTER);
|
||||
PyModule_AddIntMacro(m, SO_J1939_PROMISC);
|
||||
PyModule_AddIntMacro(m, SO_J1939_SEND_PRIO);
|
||||
PyModule_AddIntMacro(m, SO_J1939_ERRQUEUE);
|
||||
|
||||
PyModule_AddIntMacro(m, SCM_J1939_DEST_ADDR);
|
||||
PyModule_AddIntMacro(m, SCM_J1939_DEST_NAME);
|
||||
PyModule_AddIntMacro(m, SCM_J1939_PRIO);
|
||||
PyModule_AddIntMacro(m, SCM_J1939_ERRQUEUE);
|
||||
|
||||
PyModule_AddIntMacro(m, J1939_NLA_PAD);
|
||||
PyModule_AddIntMacro(m, J1939_NLA_BYTES_ACKED);
|
||||
|
||||
PyModule_AddIntMacro(m, J1939_EE_INFO_NONE);
|
||||
PyModule_AddIntMacro(m, J1939_EE_INFO_TX_ABORT);
|
||||
|
||||
PyModule_AddIntMacro(m, J1939_FILTER_MAX);
|
||||
#endif
|
||||
#ifdef SOL_RDS
|
||||
PyModule_AddIntMacro(m, SOL_RDS);
|
||||
#endif
|
||||
|
|
|
@ -144,6 +144,10 @@ typedef int socklen_t;
|
|||
#include <linux/can/bcm.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LINUX_CAN_J1939_H
|
||||
#include <linux/can/j1939.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_SYS_DOMAIN_H
|
||||
#include <sys/sys_domain.h>
|
||||
#endif
|
||||
|
|
|
@ -8282,8 +8282,8 @@ fi
|
|||
done
|
||||
|
||||
|
||||
# On Linux, can.h and can/raw.h require sys/socket.h
|
||||
for ac_header in linux/can.h linux/can/raw.h linux/can/bcm.h
|
||||
# On Linux, can.h, can/bcm.h, can/j1939.h, can/raw.h require sys/socket.h
|
||||
for ac_header in linux/can.h linux/can/bcm.h linux/can/j1939.h linux/can/raw.h
|
||||
do :
|
||||
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
|
||||
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "
|
||||
|
|
|
@ -2236,8 +2236,8 @@ AC_CHECK_HEADERS(linux/vm_sockets.h,,,[
|
|||
#endif
|
||||
])
|
||||
|
||||
# On Linux, can.h and can/raw.h require sys/socket.h
|
||||
AC_CHECK_HEADERS(linux/can.h linux/can/raw.h linux/can/bcm.h,,,[
|
||||
# On Linux, can.h, can/bcm.h, can/j1939.h, can/raw.h require sys/socket.h
|
||||
AC_CHECK_HEADERS(linux/can.h linux/can/bcm.h linux/can/j1939.h linux/can/raw.h,,,[
|
||||
#ifdef HAVE_SYS_SOCKET_H
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
|
|
@ -622,6 +622,9 @@
|
|||
/* Define to 1 if you have the <linux/can.h> header file. */
|
||||
#undef HAVE_LINUX_CAN_H
|
||||
|
||||
/* Define to 1 if you have the <linux/can/j1939.h> header file. */
|
||||
#undef HAVE_LINUX_CAN_J1939_H
|
||||
|
||||
/* Define if compiling using Linux 3.6 or later. */
|
||||
#undef HAVE_LINUX_CAN_RAW_FD_FRAMES
|
||||
|
||||
|
|
Loading…
Reference in New Issue