Add `getprotobynumber` to the `socket` module

  1. getprotobyname already exists there.
  2. This should be trivial to implement.
  3. getservbyport also exists.
    If we do it for /etc/services, then why not /etc/protocols?
    It doesn’t make sense to make a multiplication table: getservbyport, getservbyname, getprotobyname, but not getprotobynumber. It creates the implicit expectation by users something exists only for them to find out it doesn’t.
    Warning: I am aware this is a duplicate or reopening on here. Maybe it was rejected last time but it has been half a decade since.
get service protocol
by name getservbyname getprotobyname
by id # getservbyport getprotobynumber

I’ll be honest, I have never used getservbyport that I can ever recall. How useful is getprotobynumber? Do you have a situation where this will be of value?

There is an underlying C function though so this shouldn’tbe too hard, if someone wanted to do it.

1 Like

I don’t strictly need it, but if it existed I would use it. For example, when analyzing network traffic metadata, I’d prefer to translate protocol numbers to names using the standard library rather than maintaining my own mapping or parsing /etc/protocols in Python. That makes reports more readable and relies on the system’s existing protocol database.

My main point is API consistency. We already have getservbyname/getservbyport and getprotobyname; getprotobynumber seems like the natural missing counterpart. It’s not an essential feature, but it would complete the interface and match user expectations.

Here is the code I want to eliminate:

_PROTO_NAMES_CACHE = None


def _load_proto_names():
    """Parse /etc/protocols into a {number: NAME} mapping.

    socket.getprotobynumber() does not exist in Python's standard
    library (only the reverse, getprotobyname(), was ever added), so
    we build the reverse lookup ourselves. Returns an empty dict if
    /etc/protocols isn't available (e.g. on Windows).
    """
    mapping = {}
    try:
        with open("/etc/protocols", encoding="utf-8") as f:
            for line in f:
                line = line.split("#", 1)[0].strip()
                if not line:
                    continue
                parts = line.split()
                if len(parts) < 2:
                    continue
                try:
                    mapping[int(parts[1])] = parts[0].upper()
                except ValueError:
                    continue
    except OSError:
        pass
    return mapping


def proto_name_for(number):
    """Look up the well-known protocol name for a number from /etc/protocols.

    Falls back to the bare assigned number (as a string) if there is no
    registered name for that protocol.
    """
    global _PROTO_NAMES_CACHE
    if _PROTO_NAMES_CACHE is None:
        _PROTO_NAMES_CACHE = _load_proto_names()
    return _PROTO_NAMES_CACHE.get(number, str(number))

While here is the output I want to be trivial for even beginners:

Num packets: 999914, Num bytes: 366325065
IP Protocols:
 1 ICMP    :       6794
 6 TCP     :     950654
17 UDP     :      38332
47 GRE     :       2626
50 IPV6-CRYPT:       1484
89 OSPF    :         24

PHP has it.

What was changed since the last rejection?

1 Like

Rare use-cases are a fact. But please also consider that the socket module is different. Actually, it is full of objects that only few people need, because it makes low-level OS functions accesible to Python.

2 Likes

Python is increasingly used in networking.

The manual page of getprotobynumber() is scary: the API is unsafe when used in more than one thread. We try to avoid such function in Python since it can cause too many issues (crashes) in multi-threaded applications.

ATTRIBUTES
       For an explanation of the terms used in this section, see attrib‐
       utes(7).
       ┌────────────────────┬───────────────┬──────────────────────────┐
       │ Interface          │ Attribute     │ Value                    │
       ├────────────────────┼───────────────┼──────────────────────────┤
       │ getprotoent()      │ Thread safety │ MT-Unsafe race:protoent  │
       │                    │               │ race:protoentbuf locale  │
       ├────────────────────┼───────────────┼──────────────────────────┤
       │ getprotobyname()   │ Thread safety │ MT-Unsafe                │
       │                    │               │ race:protobyname locale  │
       ├────────────────────┼───────────────┼──────────────────────────┤
       │ getprotobynumber() │ Thread safety │ MT-Unsafe                │
       │                    │               │ race:protobynumber       │
       │                    │               │ locale                   │
       ├────────────────────┼───────────────┼──────────────────────────┤
       │ setprotoent(),     │ Thread safety │ MT-Unsafe race:protoent  │
       │ endprotoent()      │               │ locale                   │
       └────────────────────┴───────────────┴──────────────────────────┘

       In the above table, protoent in race:protoent signifies  that  if
       any  of  the  functions  setprotoent(), getprotoent(), or endpro‐
       toent() are used in parallel in different threads of  a  program,
       then data races could occur.

IMO your short Python _load_proto_names() function parsing /etc/protocols is safer and better than the C API. If you consider that other people are looking for a similar function, you can publish it as a PyPI package.

Note: see also the (official) IANA list: Protocol Numbers.

My main point is API consistency. We already have getservbyname/getservbyport and getprotobyname; getprotobynumber seems like the natural missing counterpart. It’s not an essential feature, but it would complete the interface and match user expectations.

PHP has it.

Python is increasingly used in networking.

Sorry, I’m still not convinced that it’s worth it to add the socket.getprotobynumber() function :slight_smile:

1 Like

Yes, but the same is true of getservbyport and getservbyname.