Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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:
Expand Down Expand Up @@ -142,6 +152,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()
Expand Down Expand Up @@ -2116,6 +2128,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):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for CAN_J1939 sockets (available on Linux 5.4+)
93 changes: 93 additions & 0 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Modules/socketmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -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" "
Expand Down
4 changes: 2 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down