gh-129288: Add optional l2_cid and l2_bdaddr_type in BTPROTO_L2CAP socket address tuple (#129293)

Add two optional, traling elements in the AF_BLUETOOTH socket address tuple:

- l2_cid, to allow e.g raw LE ATT connections
- l2_bdaddr_type. To be able to connect L2CAP sockets to Bluetooth LE devices,
  the l2_bdaddr_type must be set to BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM.
This commit is contained in:
Fredrik Ahlberg 2025-02-27 13:51:47 +01:00 committed by GitHub
parent a083633fa0
commit 45a24f54af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 7 deletions

View file

@ -137,8 +137,19 @@ created. Socket addresses are represented as follows:
- :const:`AF_BLUETOOTH` supports the following protocols and address
formats:
- :const:`BTPROTO_L2CAP` accepts ``(bdaddr, psm)`` where ``bdaddr`` is
the Bluetooth address as a string and ``psm`` is an integer.
- :const:`BTPROTO_L2CAP` accepts a tuple
``(bdaddr, psm[, cid[, bdaddr_type]])`` where:
- ``bdaddr`` is a string specifying the Bluetooth address.
- ``psm`` is an integer specifying the Protocol/Service Multiplexer.
- ``cid`` is an optional integer specifying the Channel Identifier.
If not given, defaults to zero.
- ``bdaddr_type`` is an optional integer specifying the address type;
one of :const:`BDADDR_BREDR` (default), :const:`BDADDR_LE_PUBLIC`,
:const:`BDADDR_LE_RANDOM`.
.. versionchanged:: next
Added ``cid`` and ``bdaddr_type`` fields.
- :const:`BTPROTO_RFCOMM` accepts ``(bdaddr, channel)`` where ``bdaddr``
is the Bluetooth address as a string and ``channel`` is an integer.
@ -626,6 +637,14 @@ Constants
This constant contains a boolean value which indicates if IPv6 is supported on
this platform.
.. data:: AF_BLUETOOTH
BTPROTO_L2CAP
BTPROTO_RFCOMM
BTPROTO_HCI
BTPROTO_SCO
Integer constants for use with Bluetooth addresses.
.. data:: BDADDR_ANY
BDADDR_LOCAL
@ -634,6 +653,15 @@ Constants
any address when specifying the binding socket with
:const:`BTPROTO_RFCOMM`.
.. data:: BDADDR_BREDR
BDADDR_LE_PUBLIC
BDADDR_LE_RANDOM
These constants describe the Bluetooth address type when binding or
connecting a :const:`BTPROTO_L2CAP` socket.
.. versionadded:: next
.. data:: HCI_FILTER
HCI_TIME_STAMP
HCI_DATA_DIR

View file

@ -177,6 +177,17 @@ def _have_socket_bluetooth():
return True
def _have_socket_bluetooth_l2cap():
"""Check whether BTPROTO_L2CAP sockets are supported on this host."""
try:
s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
except (AttributeError, OSError):
return False
else:
s.close()
return True
def _have_socket_hyperv():
"""Check whether AF_HYPERV sockets are supported on this host."""
try:
@ -219,6 +230,8 @@ HAVE_SOCKET_UDPLITE = (
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
HAVE_SOCKET_BLUETOOTH_L2CAP = _have_socket_bluetooth_l2cap()
HAVE_SOCKET_HYPERV = _have_socket_hyperv()
# Size in bytes of the int type
@ -2608,6 +2621,33 @@ class BasicBluetoothTest(unittest.TestCase):
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_SCO) as s:
pass
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
def testBindLeAttL2capSocket(self):
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
# ATT is the only CID allowed in userspace by the Linux kernel
CID_ATT = 4
f.bind((socket.BDADDR_ANY, 0, CID_ATT, socket.BDADDR_LE_PUBLIC))
addr = f.getsockname()
self.assertEqual(addr, (socket.BDADDR_ANY, 0, CID_ATT, socket.BDADDR_LE_PUBLIC))
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
def testBindLePsmL2capSocket(self):
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
# First user PSM in LE L2CAP
psm = 0x80
f.bind((socket.BDADDR_ANY, psm, 0, socket.BDADDR_LE_RANDOM))
addr = f.getsockname()
self.assertEqual(addr, (socket.BDADDR_ANY, psm, 0, socket.BDADDR_LE_RANDOM))
@unittest.skipUnless(HAVE_SOCKET_BLUETOOTH_L2CAP, 'Bluetooth L2CAP sockets required for this test')
def testBindBrEdrL2capSocket(self):
with socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) as f:
# First user PSM in BR/EDR L2CAP
psm = 0x1001
f.bind((socket.BDADDR_ANY, psm))
addr = f.getsockname()
self.assertEqual(addr, (socket.BDADDR_ANY, psm))
@unittest.skipUnless(HAVE_SOCKET_HYPERV,
'Hyper-V sockets required for this test.')

View file

@ -0,0 +1 @@
Add optional ``l2_cid`` and ``l2_bdaddr_type`` fields to :mod:`socket` ``BTPROTO_L2CAP`` sockaddr tuple.

View file

@ -1495,9 +1495,20 @@ makesockaddr(SOCKET_T sockfd, struct sockaddr *addr, size_t addrlen, int proto)
PyObject *addrobj = makebdaddr(&_BT_L2_MEMB(a, bdaddr));
PyObject *ret = NULL;
if (addrobj) {
/* Retain old format for non-LE address.
(cid may be set for BR/EDR, but we're discarding it for now)
*/
if (_BT_L2_MEMB(a, bdaddr_type) == BDADDR_BREDR) {
ret = Py_BuildValue("Oi",
addrobj,
_BT_L2_MEMB(a, psm));
} else {
ret = Py_BuildValue("OiiB",
addrobj,
_BT_L2_MEMB(a, psm),
_BT_L2_MEMB(a, cid),
_BT_L2_MEMB(a, bdaddr_type));
}
Py_DECREF(addrobj);
}
return ret;
@ -2047,8 +2058,11 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
struct sockaddr_l2 *addr = &addrbuf->bt_l2;
memset(addr, 0, sizeof(struct sockaddr_l2));
_BT_L2_MEMB(addr, family) = AF_BLUETOOTH;
if (!PyArg_ParseTuple(args, "si", &straddr,
&_BT_L2_MEMB(addr, psm))) {
_BT_L2_MEMB(addr, bdaddr_type) = BDADDR_BREDR;
if (!PyArg_ParseTuple(args, "si|iB", &straddr,
&_BT_L2_MEMB(addr, psm),
&_BT_L2_MEMB(addr, cid),
&_BT_L2_MEMB(addr, bdaddr_type))) {
PyErr_Format(PyExc_OSError,
"%s(): wrong format", caller);
return 0;
@ -7782,6 +7796,9 @@ socket_exec(PyObject *m)
ADD_INT_MACRO(m, AF_BLUETOOTH);
#ifdef BTPROTO_L2CAP
ADD_INT_MACRO(m, BTPROTO_L2CAP);
ADD_INT_MACRO(m, BDADDR_BREDR);
ADD_INT_MACRO(m, BDADDR_LE_PUBLIC);
ADD_INT_MACRO(m, BDADDR_LE_RANDOM);
#endif /* BTPROTO_L2CAP */
#ifdef BTPROTO_HCI
ADD_INT_MACRO(m, BTPROTO_HCI);