From ec9f390f0aa5db2e7661b86e2d209a02e6bbd426 Mon Sep 17 00:00:00 2001 From: pandavon <735591750@qq.com> Date: Tue, 6 Oct 2026 21:59:25 +0800 Subject: [PATCH] fix(socketcan): identify channel in binding errors --- can/interfaces/socketcan/socketcan.py | 11 +++++++++- doc/changelog.d/2104.fixed.rst | 1 + test/test_socketcan.py | 30 ++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 doc/changelog.d/2104.fixed.rst diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 6dc856cbf..6f026f5ca 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -598,7 +598,16 @@ def bind_socket(sock: socket.socket, channel: str = "can0") -> None: If the specified interface isn't found. """ log.debug("Binding socket to channel=%s", channel) - sock.bind((channel,)) + try: + sock.bind((channel,)) + except OSError as error: + context = f"SocketCAN channel {channel!r}" + if error.strerror is None: + error.args = (f"{error} ({context})",) + else: + error.strerror = f"{error.strerror} ({context})" + error.args = (error.errno, error.strerror) + raise log.debug("Bound socket.") diff --git a/doc/changelog.d/2104.fixed.rst b/doc/changelog.d/2104.fixed.rst new file mode 100644 index 000000000..b650d7a93 --- /dev/null +++ b/doc/changelog.d/2104.fixed.rst @@ -0,0 +1 @@ +Include the SocketCAN channel in socket binding errors. diff --git a/test/test_socketcan.py b/test/test_socketcan.py index 9d042f425..bbdb7220a 100644 --- a/test/test_socketcan.py +++ b/test/test_socketcan.py @@ -5,11 +5,12 @@ """ import ctypes +import errno import struct import sys import unittest import warnings -from unittest.mock import patch +from unittest.mock import Mock, patch import can from can.interfaces.socketcan.constants import ( @@ -22,6 +23,7 @@ from can.interfaces.socketcan.socketcan import ( BcmMsgHead, bcm_header_factory, + bind_socket, build_bcm_header, build_bcm_transmit_header, build_bcm_tx_delete_header, @@ -32,6 +34,32 @@ class SocketCANTest(unittest.TestCase): + def test_bind_socket_failure_identifies_channel(self): + for channel, error in ( + ("can0", OSError(errno.ENODEV, "No such device")), + ("vcan42", PermissionError(errno.EPERM, "Operation not permitted")), + ("can-special", OSError("bind failed")), + ): + with self.subTest(channel=channel): + sock = Mock() + sock.bind.side_effect = error + + with self.assertRaises(OSError) as caught: + bind_socket(sock, channel) + + self.assertIn(channel, str(caught.exception)) + self.assertIn(channel, repr(caught.exception)) + self.assertIs(caught.exception, error) + self.assertEqual(caught.exception.errno, error.errno) + sock.bind.assert_called_once_with((channel,)) + + def test_bind_socket_success(self): + sock = Mock() + + bind_socket(sock, "can0") + + sock.bind.assert_called_once_with(("can0",)) + def setUp(self): self._ctypes_sizeof = ctypes.sizeof self._ctypes_alignment = ctypes.alignment