Skip to content

Commit

Permalink
Issue python#10141: socket: add SocketCAN (PF_CAN) support. Initial p…
Browse files Browse the repository at this point in the history
…atch by Matthias

Fuchs, updated by Tiago Gonçalves.
  • Loading branch information
Charles-François Natali committed Oct 6, 2011
1 parent 90c30e8 commit 47413c1
Show file tree
Hide file tree
Showing 10 changed files with 683 additions and 321 deletions.
71 changes: 66 additions & 5 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ Socket addresses are represented as follows:
If *addr_type* is TIPC_ADDR_ID, then *v1* is the node, *v2* is the
reference, and *v3* should be set to 0.

- A tuple ``(interface, )`` is used for the :const:`AF_CAN` address family,
where *interface* is a string representing a network interface name like
``'can0'``. The network interface name ``''`` can be used to receive packets
from all network interfaces of this family.

- Certain other address families (:const:`AF_BLUETOOTH`, :const:`AF_PACKET`)
support specific representations.

Expand Down Expand Up @@ -216,6 +221,19 @@ The module :mod:`socket` exports the following constants and functions:
in the Unix header files are defined; for a few symbols, default values are
provided.

.. data:: AF_CAN
PF_CAN
SOL_CAN_*
CAN_*

Many constants of these forms, documented in the Linux documentation, are
also defined in the socket module.

Availability: Linux >= 2.6.25.

.. versionadded:: 3.3


.. data:: SIO_*
RCVALL_*

Expand Down Expand Up @@ -387,10 +405,14 @@ The module :mod:`socket` exports the following constants and functions:

Create a new socket using the given address family, socket type and protocol
number. The address family should be :const:`AF_INET` (the default),
:const:`AF_INET6` or :const:`AF_UNIX`. The socket type should be
:const:`SOCK_STREAM` (the default), :const:`SOCK_DGRAM` or perhaps one of the
other ``SOCK_`` constants. The protocol number is usually zero and may be
omitted in that case.
:const:`AF_INET6`, :const:`AF_UNIX` or :const:`AF_CAN`. The socket type
should be :const:`SOCK_STREAM` (the 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 in that case or
:const:`CAN_RAW` in case the address family is :const:`AF_CAN`.

.. versionchanged:: 3.3
The AF_CAN family was added.


.. function:: socketpair([family[, type[, proto]]])
Expand Down Expand Up @@ -1213,7 +1235,7 @@ sends traffic to the first one connected successfully. ::
print('Received', repr(data))


The last example shows how to write a very simple network sniffer with raw
The next example shows how to write a very simple network sniffer with raw
sockets on Windows. The example requires administrator privileges to modify
the interface::

Expand All @@ -1238,6 +1260,45 @@ 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
network. This example might require special priviledge::

import socket
import struct


# CAN frame packing/unpacking (see `struct can_frame` in <linux/can.h>)

can_frame_fmt = "=IB3x8s"

def build_can_frame(can_id, data):
can_dlc = len(data)
data = data.ljust(8, b'\x00')
return struct.pack(can_frame_fmt, can_id, can_dlc, data)

def dissect_can_frame(frame):
can_id, can_dlc, data = struct.unpack(can_frame_fmt, frame)
return (can_id, can_dlc, data[:can_dlc])


# create a raw socket and bind it to the `vcan0` interface
s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
s.bind(('vcan0',))

while True:
cf, addr = s.recvfrom(16)

print('Received: can_id=%x, can_dlc=%x, data=%s' % dissect_can_frame(cf))

try:
s.send(cf)
except socket.error:
print('Error sending CAN frame')

try:
s.send(build_can_frame(0x01, b'\x01\x02\x03'))
except socket.error:
print('Error sending CAN frame')

Running an example several times with too small delay between executions, could
lead to this error::
Expand Down
21 changes: 14 additions & 7 deletions Doc/whatsnew/3.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,22 @@ signal
socket
------

The :class:`~socket.socket` class now exposes addititonal methods to
process ancillary data when supported by the underlying platform:
* The :class:`~socket.socket` class now exposes additional methods to process
ancillary data when supported by the underlying platform:

* :func:`~socket.socket.sendmsg`
* :func:`~socket.socket.recvmsg`
* :func:`~socket.socket.recvmsg_into`
* :func:`~socket.socket.sendmsg`
* :func:`~socket.socket.recvmsg`
* :func:`~socket.socket.recvmsg_into`

(Contributed by David Watson in :issue:`6560`, based on an earlier patch by
Heiko Wundram)

* The :class:`~socket.socket` class now supports the PF_CAN protocol family
(http://en.wikipedia.org/wiki/Socketcan), on Linux
(http://lwn.net/Articles/253425).

(Contributed by Matthias Fuchs, updated by Tiago Gonçalves in :issue:`10141`)

(Contributed by David Watson in :issue:`6560`, based on an earlier patch
by Heiko Wundram)

ssl
---
Expand Down
164 changes: 164 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import signal
import math
import pickle
import struct
try:
import fcntl
except ImportError:
Expand All @@ -36,6 +37,18 @@
thread = None
threading = None

def _have_socket_can():
"""Check whether CAN sockets are supported on this host."""
try:
s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
except (AttributeError, socket.error, OSError):
return False
else:
s.close()
return True

HAVE_SOCKET_CAN = _have_socket_can()

# Size in bytes of the int type
SIZEOF_INT = array.array("i").itemsize

Expand Down Expand Up @@ -80,6 +93,30 @@ def doCleanups(self, *args, **kwargs):
with self._cleanup_lock:
return super().doCleanups(*args, **kwargs)

class SocketCANTest(unittest.TestCase):

"""To be able to run this test, a `vcan0` CAN interface can be created with
the following commands:
# modprobe vcan
# ip link add dev vcan0 type vcan
# ifconfig vcan0 up
"""
interface = 'vcan0'
bufsize = 128

def setUp(self):
self.s = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
try:
self.s.bind((self.interface,))
except socket.error:
self.skipTest('network interface `%s` does not exist' %
self.interface)
self.s.close()

def tearDown(self):
self.s.close()
self.s = None

class ThreadableTest:
"""Threadable Test class
Expand Down Expand Up @@ -210,6 +247,26 @@ def clientTearDown(self):
self.cli = None
ThreadableTest.clientTearDown(self)

class ThreadedCANSocketTest(SocketCANTest, ThreadableTest):

def __init__(self, methodName='runTest'):
SocketCANTest.__init__(self, methodName=methodName)
ThreadableTest.__init__(self)

def clientSetUp(self):
self.cli = socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
try:
self.cli.bind((self.interface,))
except socket.error:
self.skipTest('network interface `%s` does not exist' %
self.interface)
self.cli.close()

def clientTearDown(self):
self.cli.close()
self.cli = None
ThreadableTest.clientTearDown(self)

class SocketConnectedTest(ThreadedTCPSocketTest):
"""Socket tests for client-server connection.
Expand Down Expand Up @@ -1072,6 +1129,112 @@ def test_listen_backlog0(self):
srv.close()


@unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
class BasicCANTest(unittest.TestCase):

def testCrucialConstants(self):
socket.AF_CAN
socket.PF_CAN
socket.CAN_RAW

def testCreateSocket(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
pass

def testBindAny(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
s.bind(('', ))

def testTooLongInterfaceName(self):
# most systems limit IFNAMSIZ to 16, take 1024 to be sure
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
self.assertRaisesRegexp(socket.error, 'interface name too long',
s.bind, ('x' * 1024,))

@unittest.skipUnless(hasattr(socket, "CAN_RAW_LOOPBACK"),
'socket.CAN_RAW_LOOPBACK required for this test.')
def testLoopback(self):
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
for loopback in (0, 1):
s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK,
loopback)
self.assertEqual(loopback,
s.getsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_LOOPBACK))

@unittest.skipUnless(hasattr(socket, "CAN_RAW_FILTER"),
'socket.CAN_RAW_FILTER required for this test.')
def testFilter(self):
can_id, can_mask = 0x200, 0x700
can_filter = struct.pack("=II", can_id, can_mask)
with socket.socket(socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW) as s:
s.setsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, can_filter)
self.assertEqual(can_filter,
s.getsockopt(socket.SOL_CAN_RAW, socket.CAN_RAW_FILTER, 8))


@unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.')
@unittest.skipUnless(thread, 'Threading required for this test.')
class CANTest(ThreadedCANSocketTest):

"""The CAN frame structure is defined in <linux/can.h>:
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* data length code: 0 .. 8 */
__u8 data[8] __attribute__((aligned(8)));
};
"""
can_frame_fmt = "=IB3x8s"

def __init__(self, methodName='runTest'):
ThreadedCANSocketTest.__init__(self, methodName=methodName)

@classmethod
def build_can_frame(cls, can_id, data):
"""Build a CAN frame."""
can_dlc = len(data)
data = data.ljust(8, b'\x00')
return struct.pack(cls.can_frame_fmt, can_id, can_dlc, data)

@classmethod
def dissect_can_frame(cls, frame):
"""Dissect a CAN frame."""
can_id, can_dlc, data = struct.unpack(cls.can_frame_fmt, frame)
return (can_id, can_dlc, data[:can_dlc])

def testSendFrame(self):
cf, addr = self.s.recvfrom(self.bufsize)
self.assertEqual(self.cf, cf)
self.assertEqual(addr[0], self.interface)
self.assertEqual(addr[1], socket.AF_CAN)

def _testSendFrame(self):
self.cf = self.build_can_frame(0x00, b'\x01\x02\x03\x04\x05')
self.cli.send(self.cf)

def testSendMaxFrame(self):
cf, addr = self.s.recvfrom(self.bufsize)
self.assertEqual(self.cf, cf)

def _testSendMaxFrame(self):
self.cf = self.build_can_frame(0x00, b'\x07' * 8)
self.cli.send(self.cf)

def testSendMultiFrames(self):
cf, addr = self.s.recvfrom(self.bufsize)
self.assertEqual(self.cf1, cf)

cf, addr = self.s.recvfrom(self.bufsize)
self.assertEqual(self.cf2, cf)

def _testSendMultiFrames(self):
self.cf1 = self.build_can_frame(0x07, b'\x44\x33\x22\x11')
self.cli.send(self.cf1)

self.cf2 = self.build_can_frame(0x12, b'\x99\x22\x33')
self.cli.send(self.cf2)


@unittest.skipUnless(thread, 'Threading required for this test.')
class BasicTCPTest(SocketConnectedTest):

Expand Down Expand Up @@ -4194,6 +4357,7 @@ def test_main():
if isTipcAvailable():
tests.append(TIPCTest)
tests.append(TIPCThreadableTest)
tests.extend([BasicCANTest, CANTest])
tests.extend([
CmsgMacroTests,
SendmsgUDPTest,
Expand Down
2 changes: 2 additions & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ John Fouhy
Martin Franklin
Robin Friedrich
Ivan Frohne
Matthias Fuchs
Jim Fulton
Tadayoshi Funaba
Gyro Funch
Expand Down Expand Up @@ -354,6 +355,7 @@ Michael Gilfix
Yannick Gingras
Christoph Gohlke
Tim Golden
Tiago Gonçalves
Chris Gonnerman
David Goodger
Hans de Graaff
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,9 @@ Tools/Demos
Extension Modules
-----------------

- Issue #10141: socket: Add SocketCAN (PF_CAN) support. Initial patch by
Matthias Fuchs, updated by Tiago Gonçalves.

- Issue #13070: Fix a crash when a TextIOWrapper caught in a reference cycle
would be finalized after the reference to its underlying BufferedRWPair's
writer got cleared by the GC.
Expand Down
Loading

0 comments on commit 47413c1

Please sign in to comment.