Skip to content

On Linux, set up socket object correctly when creating from an fd #251

Closed

Description

@njsmith

Python has APIs for wrapping a bare socket fd into a socket object: socket.socket(fileno=...) to wrap, and socket.fromfd(fileno, ...) to dup-and-wrap. We currently inherit these with no changes.

There's a fairly nasty bug in Python though: to work with a socket correctly, it's not enough to know the fd; Python also needs to know the address family (most important), the socket type, and the protocol (least important). On Windows, it correctly pulls this information out of the passed-in socket. [Edit: actually Windows is broken too.] On other platforms, it doesn't (see bpo-28134, bpo-27377).

AFAICT on MacOS this is mostly a lost cause. You can use getsockname to get the address family, but that only works on connected stream sockets, and there's no way to query for the other attributes. I guess you might be able to distinguish Unix/IPv4/IPv6 families by making getsockopt calls with the appropriate level and see if you get an error or not? Maybe LOCAL_PEERCRED is a reliable signature of AF_UNIX+SOCK_STREAM, and then there's a bunch of IPv6-specific sockopts... (see unix(4), ip(4), ip6(4)).

But on Linux – which is the point of this bug! – this is actually pretty easy. You can just ask for all the key pieces of information like:

sock_family = getsockopt(fd, SOL_SOCKET, SO_DOMAIN)
sock_type = getsockopt(fd, SOL_SOCKET, SO_TYPE)
sock_proto = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL)

(more detailed example: https://github.com/tiran/socketfromfd/blob/master/socketfromfd.py)

And this actually matters, because to support systemd socket activation, we need to be able to take bare fds and wrap them into trio socket objects, on Linux.

So: we should teach trio.socket.socket to do these checks on Linux when fileno= is given, and we should implement trio.socket.fromfd in terms of trio.socket.socket. (The standard library's implementation of socket.fileno in terms of socket.socket is ~2 lines of code, we can just copy them into trio.)

Activity

  1. njsmith commented on Aug 28, 2018

    @njsmith
    MemberAuthor

    The description above is a bit confusing... for example, it links to some complicated code using ctypes, but that's only needed for old python versions we don't support anyway. For us it can be fairly simple. Basically we need something like a function that takes the family/type/proto that the user gave us, and fixes them up as best we can (which might be more or less, depending on the OS). For example (untested):

    def _fix_attrs_for_fileno(family, type, proto, fileno):
        # Wrap the raw fileno into a Python socket object
        # This object might have the wrong metadata, but it lets us easily call getsockopt
        # and then we'll throw it away and construct a new one with the correct metadata.
        sockobj = _stdlib_socket.socket(fileno=fileno)
        try:
            if hasattr(socket, "SO_DOMAIN"):
                family = sockobj.getsockopt(SOL_SOCKET, SO_DOMAIN)
            if hasattr(socket, "SO_TYPE"):
                type = sockobj.getsockopt(SOL_SOCKET, SO_TYPE)
            if hasattr(socket, "SO_PROTOCOL"):
                proto = sockobj.getsockopt(SOL_SOCKET, SO_PROTOCOL)
        finally:
            # Unwrap it again, so that sockobj.__del__ doesn't try to close our socket
            sockobj.detach()
        return family, type, proto

    And then in fromfd and socket we can do something like:

    family, type, proto = _fix_attrs_for_fileno(family, type, proto, fileno)

    (copied from #577 (comment))

  2. njsmith commented on Aug 31, 2018

    @njsmith
    MemberAuthor

    It looks python 3.7 now handles this automatically: python/cpython#1349

    Their approach is slightly different than what I wrote above... it only fills in the family/type/proto when they're unspecified; if the user sets one explicitly, then it trusts them. Not sure what the point of this is (surely the kernel is never wrong about the attributes of a socket?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions