Skip to content

create_datagram_endpoint() no longer compatible with CAN bus raw sockets  #114887

Closed
@tjhowse

Description

@tjhowse

Bug report

Bug description:

Problem description

We are using create_datagram_endpoint() to set up an asyncio-managed connection to a native hardware CAN bus interface at can0 under debian linux. We are using the sock named argument to pass in a pre-built raw socket to the FD.

This works in Python 3.5.3 but broke following a migration to 3.11.4. We found that the datagram endpoint would fail to be brought up, and the connection_made(transport) method on the protocol_factory would never be called. When I rejigged our code to await the coroutine I got:

    ValueError: A UDP Socket was expected, got <socket.socket fd=7, family=29, type=3, proto=1, laddr=('can0',)

I believe this has gone unnoticed due to hardware CAN bus interfaces being rare.

Relevant history

#4231
#4898
#4922

Discussion of underlying issues: https://bugs.python.org/issue32331

Core issue

A socket connected to a CAN bus has sock.type socket.SOCK_RAW, I.E. 0b11. This fails the comparison to socket.SOCK_DGRAM (0b01) so create_datagram_endpoint() raises an exception because the socket's type fails the "is a datagram" check, even though it is. In 3.5.3 the check was:

    (sock.type & socket.SOCK_DGRAM) == socket.SOCK_DGRAM

This worked because it was treating SOCK_DGRAM as a bitwise mask, which it kinda is, but kinda isn't.

x86_64-linux-gnu/bits/socket_type.h:

    ...
    /* Types of sockets.  */
    enum __socket_type
    {
    SOCK_STREAM = 1,		/* Sequenced, reliable, connection-based
                    byte streams.  */
    #define SOCK_STREAM SOCK_STREAM
    SOCK_DGRAM = 2,		/* Connectionless, unreliable datagrams
                    of fixed maximum length.  */
    #define SOCK_DGRAM SOCK_DGRAM
    SOCK_RAW = 3,			/* Raw protocol interface.  */
    #define SOCK_RAW SOCK_RAW
    SOCK_RDM = 4,			/* Reliably-delivered messages.  */
    ...

I am torn on the best way to approach a fix here. I have written a PR that replaces

    if sock.type != socket.SOCK_DGRAM:

comparisons with

    if not sock.type & socket.SOCK_DGRAM:

however this perpetuates the enum-vs-bitmask confusion. A more constrained change might be to change

    if sock.type != socket.SOCK_DGRAM:

to

    if not (sock.type == socket.SOCK_DGRAM || sock.type == socket.SOCK_RAW):

but that may not catch every instance of the problem. I'd appreciate some guidance on this.

Cheers,
Travis.

CPython versions tested on:

3.11

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.11only security fixes3.12only security fixes3.13bugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions