This short post shows how you can use Python to convert TCP/UDP port number to port name and vice versa.

Most of us know names of common TCP and UDP ports like 22/ssh, 23/telnet, 80/http or 443/https. We learn these early in our networking careers and many of them are so common that even when woken up middle of the night you'd know 53 is domain aka dns!

But there are also many not-so commonly used ports that have been given names. These ones sometimes show up in firewall logs or are mentioned in literature. Some vendors also try to replace numeric value with a human readable name in the configs and outputs of different commands.

One way or the other, I'd be good to have an easy method of getting port number given its name, and on occasion we might want to get name of particular port number.

There are many ways one could achieve that. We might search web, drop into documentation, or even check /etc/services if we have access to Linux box.

I decided to check if we can do some programmatic translation with Python, seeing as sometimes we could have hundreds of entries to process and I don't fancy doing that by hand.

As it turns out one of the built-in libraries has just what we need. This library is called socket, which has two functions of interest getservbyname and getservbyport. First one translates port name to number, second one does the reverse. Both of them also accept optional protocol name, either tcp or udp. If no protocol is provided we get result as long as there is match for any of these.

Ok, so let's get to it and see some examples.

>>> from socket import getservbyname, getservbyport
>>> getservbyname("ssh")
22
>>> getservbyname("domain", "udp")
53
>>> getservbyname("https", "tcp")
443

Hey, it's working. We can simply use port name but if we want to we can provide protocol name as well.

Naturally we now need to try translating port number to name:

>>> getservbyport(80)
'http'
>>> getservbyport(161, "udp")
'snmp'
>>> getservbyport(21, "tcp")
'ftp'

Again, it's all looking great. Port number on its own translated, same with number and protocol name.

But, there are cases when using just name/number works fine but might fail when protocol is specified as well.

>>> getservbyport(25)
'smtp'

>>> getservbyport(25, "udp")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: port/proto not found

>>> getservbyname("ntp")
123

>>> getservbyname("ntp", "tcp")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: service/proto not found

Ah yes, ntp does not use TCP and smtp is not using UDP either. So this makes sense. Just to be on the safe side however, we can use try..except block and prevent accidents from happening.

>>> try:
...     getservbyname("ntp", "tcp")
... except OSError:
...     print("No matching port number with this protocol")
... 
No matching port number with this protocol

But that's not all. There are also rare cases where port can have different name depending on the protocol using it.

>>> getservbyport(21, "udp")
'fsp'
>>> getservbyport(21, "tcp")
'ftp'
>>> getservbyport(512, "udp")
'biff'
>>> getservbyport(512, "tcp")
'exec'
>>> getservbyname("who", "udp")
513
>>> getservbyname("login", "tcp")
513
>>> getservbyname("syslog", "udp")
514
>>> getservbyname("shell", "tcp")
514

Funky, same port, but different names! On the bright side these are all the cases that I could find :)

For completeness, same ports without protocol specified:

>>> getservbyport(21)
'ftp'
>>> getservbyport(512)
'exec'
>>> getservbyport(513)
'login'
>>> getservbyport(514)
'shell'

As you can see TCP name is used by default.

Generally you probably won't care that much so it's fine to use these functions without specifying protocol name.

If you want accuracy however you will need to specify protocol when translating port name/number and enclose your code in try..except block.

Important note

Results returned by getservbyport and getservbyname are OS dependent.

You should generally get the same names/port numbers regardless of the OS but in some cases one OS might have more services defined than the other.

  • On Linux based systems you can find port definitions in /etc/services file.
  • If you use Windows the equivalent should be found in %SystemRoot%\system32\drivers\etc\services.

If you want something that is OS independent that can be used for quick searching from CLI I recommend looking at whatportis utility, link in References.

Real-world applications

It's definitely useful for humans to be able to check what port name corresponds to what port number. There are however real-world applications where we might want to deploy our newly acquired knowledge.

For instance, look at the below output taken from Arista device:

veos1#sh ip access-lists so-many-ports 
IP Access List so-many-ports
        10 deny tcp 10.0.0.0/24 10.0.1.0/24 eq tcpmux
        20 deny tcp 10.0.0.0/24 10.0.1.0/24 eq systat
        30 deny udp 10.0.0.0/24 10.0.1.0/24 eq daytime
        40 deny tcp 10.0.0.0/24 10.0.1.0/24 eq ssh
        50 deny tcp 10.0.0.0/24 10.0.1.0/24 eq telnet
        60 deny udp 10.0.0.0/24 10.0.1.0/24 eq time
        70 deny tcp 10.0.0.0/24 10.0.1.0/24 eq gopher
        80 deny tcp 10.0.0.0/24 10.0.1.0/24 eq acr-nema
        90 deny udp 10.0.0.0/24 10.0.1.0/24 eq qmtp
        100 permit tcp 10.0.0.0/24 10.0.1.0/24 eq ipx
        110 permit tcp 10.0.0.0/24 10.0.1.0/24 eq rpc2portmap
        120 deny tcp 10.0.0.0/24 10.0.1.0/24 eq svrloc
        130 deny udp 10.0.0.0/24 10.0.1.0/24 eq talk
        140 permit tcp 10.0.0.0/24 10.0.1.0/24 eq uucp
        150 deny tcp 10.0.0.0/24 10.0.1.0/24 eq dhcpv6-server
        160 deny tcp 10.0.0.0/24 10.0.1.0/24 eq submission
        170 permit tcp 10.0.0.0/24 10.0.1.0/24 eq ms-sql-m

It's a made up ACL but one thing stands out, there are no port numbers! Yes, by default Arista will convert port number to a port name both in config and in the output of show commands. In newer versions of EOS you can disable this behaviour but that might not always be allowed.

Real question is, is this a problem? Well, personally most of these port names mean nothing to me so I can't tell if given entry should be there or not. Displaying port names might be a great idea in principle but in my opinion it gets in the way.

Another reason why we might want to stick to port numbers is config abstraction. If I wanted to store this ACL in my Infrastructure as a Code repository I definitely would want to store ports in their numeric format. I don't know what devices I will use in the future and you might want to have multiple system consume this data, so why make it difficult?

Having said that, I wrote a small example program to quickly convert these port names.

from pathlib import Path
from socket import getservbyname

def main():
    with Path("arista-sh-acl.txt").open() as fin:
        aces = [line for line in fin.read().split("\n") if line]

    aces_clean = []
    for ace in aces:
        units = ace.strip().split(" ")
        prot = units[2]
        port = units[-1]
        if not port.isdigit():
            try:
                port_no = getservbyname(port, prot)
                aces_clean.append(" ".join(units[:-1] + [str(port_no)]))
            except OSError:
                print(f"Couldn't translate port name '{port}' for protocol {prot}")

    print("\n".join(aces_clean))

if __name__ == "__main__":
    main()

I saved lines of my ACL in file named arista-sh-acl.txt and ran the program:

(venv) przemek@quark:~/netdev/prot_names$ python acl_no_prot_names.py 
Couldn't translate port name 'qmtp' for protocol udp
Couldn't translate port name 'ipx' for protocol tcp
Couldn't translate port name 'dhcpv6-server' for protocol tcp
10 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 1
20 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 11
30 deny udp 10.0.0.0/24 10.0.1.0/24 eq 13
40 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 22
50 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 23
60 deny udp 10.0.0.0/24 10.0.1.0/24 eq 37
70 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 70
80 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 104
110 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 369
120 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 427
130 deny udp 10.0.0.0/24 10.0.1.0/24 eq 517
140 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 540
160 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 587
170 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 1434

Promising, but why did it fail on 3 names? Well it turns out that Ubuntu I'm running this on has only one protocol listed next to some of the definitions while Arista translates ports regardless of protocol.

Solution in this case is to just ignore protocol and ask for any definition, updated program below:

from pathlib import Path
from socket import getservbyname

def main():
    with Path("arista-sh-acl.txt").open() as fin:
        aces = [line for line in fin.read().split("\n") if line]

    aces_clean = []
    for ace in aces:
        units = ace.strip().split(" ")
        prot = units[2]
        port = units[-1]
        if not port.isdigit():
            try:
                port_no = getservbyname(port)
                aces_clean.append(" ".join(units[:-1] + [str(port_no)]))
            except OSError:
                print(f"Couldn't translate port name '{port}' for protocol {prot}")

    print("\n".join(aces_clean))

if __name__ == "__main__":
    main()

Let's run it again:

(venv) przemek@quark:~/netdev/prot_names$ python acl_no_prot_names.py 
10 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 1
20 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 11
30 deny udp 10.0.0.0/24 10.0.1.0/24 eq 13
40 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 22
50 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 23
60 deny udp 10.0.0.0/24 10.0.1.0/24 eq 37
70 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 70
80 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 104
90 deny udp 10.0.0.0/24 10.0.1.0/24 eq 209
100 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 213
110 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 369
120 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 427
130 deny udp 10.0.0.0/24 10.0.1.0/24 eq 517
140 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 540
150 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 547
160 deny tcp 10.0.0.0/24 10.0.1.0/24 eq 587
170 permit tcp 10.0.0.0/24 10.0.1.0/24 eq 1434

Much better now. All names have been translated to numbers!

So there you have it. You should now know how to use Python to translate between port numbers and their names. If you ever need to normalize output from network devices or other systems you'll know how to do it programmatically.

References