Sockets: Internet Domains The /etc/services File

 

Chapter 59 โ€“ Sockets: Internet Domains
Part 3 of 3 ย |ย  The /etc/services File (Section 59.9)
๐Ÿ“„ Topic
/etc/services
๐Ÿ”ข Focus
Port Numbers
๐Ÿ“˜ Source
TLPI Ch.59

What is /etc/services?

Every network service โ€” HTTP, SSH, FTP, DNS โ€” uses a specific port number. Instead of hard-coding numbers like 22 or 80 in your programs, Linux provides the file /etc/services which maps service names to port numbers (and protocols).

The functions getaddrinfo() and getnameinfo() use this file internally, so you can write "http" instead of 80 in your code โ€” making programs more readable and portable.

Key Terms

/etc/services Port number IANA Well-known ports getservbyname() getservbyport() getaddrinfo() getnameinfo() ntohs() htons()

Why Store Port Numbers in a File?

Port numbers for well-known services (SSH, HTTP, FTP, etc.) are assigned by IANA (Internet Assigned Numbers Authority). These numbers are standardised globally โ€” HTTP is always port 80, SSH is always port 22.

Unlike IP addresses, port numbers almost never change. So a simple lookup file is sufficient โ€” there is no need for a complex distributed system like DNS.

Property IP Addresses (โ†’ DNS) Port Numbers (โ†’ /etc/services)
How often they change Frequently (servers move) Rarely / never (standardised)
Number of entries Billions Hundreds (well-known range: 0โ€“1023)
Solution used DNS (distributed, hierarchical) /etc/services (simple local file)
Authority Zone administrators IANA (globally centralised)

Format of /etc/services

Each line has three fields (plus optional comment):

# service-name    port/protocol    [aliases]    # comment
echo             7/tcp            Echo          # echo service
echo             7/udp            Echo
ssh              22/tcp                         # Secure Shell
ssh              22/udp
telnet           23/tcp                         # Telnet
telnet           23/udp
smtp             25/tcp                         # Simple Mail Transfer Protocol
smtp             25/udp
domain           53/tcp                         # Domain Name Server
domain           53/udp
http             80/tcp                         # Hypertext Transfer Protocol
http             80/udp
ntp              123/tcp                        # Network Time Protocol
ntp              123/udp
login            513/tcp                        # rlogin(1)
who              513/udp                        # rwho(1)
shell            514/tcp                        # rsh(1)
syslog           514/udp                        # syslog

Column Meaning Example
service-name The official name you use in your program http, ssh, smtp
port/protocol Port number and transport protocol (tcp or udp) 80/tcp, 53/udp
aliases (optional) Alternative names for the same service Echo for the echo service

Notice that many services appear twice โ€” once for tcp and once for udp. The port number is the same but the protocol is different.

Port Number Ranges
Range Name Who Uses Them Examples
0 โ€“ 1023 Well-known ports IANA-assigned, require root to bind HTTP(80), SSH(22), DNS(53)
1024 โ€“ 49151 Registered ports IANA-registered for specific apps MySQL(3306), PostgreSQL(5432)
49152 โ€“ 65535 Dynamic/ephemeral ports Kernel assigns to client sockets automatically Source port of your browser connection

Well-known
0โ€“1023
Registered
1024โ€“49151
Dynamic / Ephemeral
49152โ€“65535

Common Well-Known Ports (from /etc/services)
Service Port Protocol Purpose
echo 7 TCP & UDP Echoes back whatever is sent (testing)
ssh 22 TCP & UDP Secure Shell remote login
telnet 23 TCP & UDP Insecure remote login (legacy)
smtp 25 TCP & UDP Simple Mail Transfer Protocol (email sending)
domain 53 TCP & UDP DNS (Domain Name Server)
http 80 TCP & UDP Hypertext Transfer Protocol (web)
ntp 123 TCP & UDP Network Time Protocol
login 513 TCP rlogin remote login
who 513 UDP rwho โ€” show logged-in users
shell 514 TCP rsh remote shell
syslog 514 UDP Remote syslog messages

Notice something interesting: both who and shell use port 514 โ€” but one is UDP and the other is TCP. Port numbers are scoped to the protocol, so the same number can be used for TCP and UDP independently.

Using Service Names in Your C Code

The best approach is to pass the service name as a string to getaddrinfo(). It automatically looks up /etc/services for you:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

int main(void)
{
    struct addrinfo hints, *result;
    int sfd, s;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_INET;      /* IPv4 */
    hints.ai_socktype = SOCK_STREAM;  /* TCP */

    /*
     * Pass "http" as the service name โ€” getaddrinfo()
     * looks this up in /etc/services and finds port 80.
     * You can also pass "80" directly as a string.
     */
    s = getaddrinfo("www.example.com", "http", &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(1);
    }

    /* Create socket using the returned info */
    sfd = socket(result->ai_family,
                 result->ai_socktype,
                 result->ai_protocol);
    if (sfd == -1) { perror("socket"); exit(1); }

    /* Connect to the server */
    if (connect(sfd, result->ai_addr, result->ai_addrlen) == -1) {
        perror("connect"); exit(1);
    }

    printf("Connected to www.example.com on port 80 (http)\n");

    freeaddrinfo(result);
    return 0;
}

Direct Lookup: getservbyname() and getservbyport()

You can also directly query /etc/services using these older (but still useful) functions:

#include <netdb.h>

/* Look up a service by NAME โ€” returns port number */
struct servent *getservbyname(const char *name, const char *proto);

/* Look up a service by PORT โ€” returns service name */
struct servent *getservbyport(int port, const char *proto);

/* The returned struct looks like this: */
struct servent {
    char  *s_name;     /* official service name, e.g. "http" */
    char **s_aliases;  /* NULL-terminated list of aliases */
    int    s_port;     /* port number in NETWORK byte order */
    char  *s_proto;    /* protocol name: "tcp" or "udp" */
};

Example โ€” look up SSH port by name:

#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>  /* for ntohs() */

int main(void)
{
    struct servent *se;

    /* Find port for SSH over TCP */
    se = getservbyname("ssh", "tcp");
    if (se == NULL) {
        fprintf(stderr, "Service not found\n");
        return 1;
    }
    /* s_port is in NETWORK byte order, use ntohs() to convert */
    printf("ssh/tcp port = %d\n", ntohs(se->s_port));

    /* Find port for DNS over UDP */
    se = getservbyname("domain", "udp");
    if (se != NULL)
        printf("domain/udp port = %d\n", ntohs(se->s_port));

    /* Reverse: find service name for port 80/tcp */
    se = getservbyport(htons(80), "tcp");
    if (se != NULL)
        printf("Port 80/tcp = %s\n", se->s_name);

    return 0;
}

Expected output:

ssh/tcp port = 22
domain/udp port = 53
Port 80/tcp = http

Note: getservbyname() and getservbyport() are not thread-safe (they use internal static storage). For multi-threaded programs, use getaddrinfo() and getnameinfo() instead.

Reverse Lookup: getnameinfo() for Port โ†’ Service Name

getnameinfo() is the complement of getaddrinfo(). Given a socket address structure (with an IP and port), it returns the hostname and service name as strings. It uses /etc/services for port-to-name translation.

#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>

int main(void)
{
    struct sockaddr_in addr;
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];
    int s;

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(22);           /* SSH port */
    inet_pton(AF_INET, "93.184.216.34", &addr.sin_addr); /* example.com */

    /*
     * getnameinfo() translates:
     *   IP โ†’ hostname  (via reverse DNS lookup)
     *   port โ†’ service name (via /etc/services)
     *
     * NI_NUMERICHOST: return IP string, don't do reverse DNS
     * NI_NUMERICSERV: return port number as string, skip /etc/services
     */
    s = getnameinfo((struct sockaddr *)&addr, sizeof(addr),
                    host, NI_MAXHOST,
                    service, NI_MAXSERV,
                    NI_NUMERICHOST);  /* don't reverse-lookup hostname */
    if (s != 0) {
        fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));
        return 1;
    }

    printf("Host: %s\n", host);       /* 93.184.216.34 */
    printf("Service: %s\n", service); /* ssh */
    return 0;
}

How getaddrinfo() and /etc/services Work Together

Your Code
getaddrinfo(
"example.com",
"http", ...)
โ†’ getaddrinfo()
Resolves hostname via DNS
Resolves “http” via
/etc/services โ†’ port 80
โ†’ Returns addrinfo struct
ai_addr with IP + port 80
Ready to use in connect()
or bind()

Internally, when you pass "http" as the service string, getaddrinfo() calls something equivalent to getservbyname("http", "tcp") and gets port 80, then fills the sin_port field of the returned address structures for you automatically.

Useful Shell Commands for /etc/services
# View the actual file
cat /etc/services | head -30

# Find the port for a specific service
grep "^http " /etc/services

# Find the service name for a port
grep "^[^ ]* *80/" /etc/services

# Query using getent (uses NSS, like getservbyname does)
getent services ssh
getent services 80/tcp

# Check what port a running service is listening on
ss -tlnp          # show TCP listening sockets
ss -ulnp          # show UDP listening sockets
netstat -tlnp     # older equivalent

Interview Questions & Answers

Q1. What is /etc/services and why does it exist?

/etc/services is a local text file that maps service names (like http, ssh) to their port numbers and protocols. It allows programs to refer to services by name rather than hard-coded numbers, improving readability and portability. Because port numbers are standardised by IANA and rarely change, a simple file is sufficient โ€” no distributed system like DNS is needed.

Q2. What is IANA and what does it do for port numbers?

IANA (Internet Assigned Numbers Authority) centrally registers and maintains assignments for well-known port numbers (0โ€“1023) and registered ports (1024โ€“49151). This ensures that http is always port 80 and ssh is always port 22 everywhere in the world. The /etc/services file reflects these assignments.

Q3. What is the difference between well-known ports and ephemeral ports?

Well-known ports (0โ€“1023) are reserved for standard services (HTTP=80, SSH=22) and require root privilege to bind on Linux. Ephemeral (dynamic) ports (49152โ€“65535) are automatically assigned by the kernel to client sockets. When your browser opens a connection to a web server, the kernel gives the client socket a temporary ephemeral port number for that connection.

Q4. How does getaddrinfo() use /etc/services?

When you pass a service name string (like "http") as the second argument to getaddrinfo(), it internally looks up /etc/services to convert the name to a port number (80). It then fills the sin_port (or sin6_port) field of the returned address structure automatically โ€” you do not need to call htons(80) yourself.

Q5. Why do many services appear twice in /etc/services (once for tcp, once for udp)?

Port numbers are scoped per protocol. Port 53/tcp and port 53/udp are independent โ€” the DNS service uses both. TCP is used for large DNS responses (zone transfers), while UDP is used for regular short queries. Having both listed allows programs to look up the service for either protocol independently.

Q6. What is the difference between getservbyname() and getaddrinfo() for service lookup?

getservbyname() is an older, simpler function that only looks up port numbers โ€” it does not resolve hostnames. It also uses internal static storage, so it is not thread-safe. getaddrinfo() is the modern, thread-safe API that resolves both hostnames (via DNS) and service names (via /etc/services) in one call and returns a linked list of results.

Q7. Why must you use ntohs() when reading s_port from struct servent?

The s_port field in struct servent is stored in network byte order (big-endian). On little-endian CPUs (like x86), you must call ntohs() (network-to-host short) to convert it to host byte order before printing or comparing it as a human-readable number. Forgetting this gives you a wrong byte-swapped value.

Q8. Can two different services have the same port number? How?

Yes โ€” if they use different protocols. Looking at /etc/services, login uses port 513/tcp and who uses port 513/udp. Similarly, shell uses 514/tcp and syslog uses 514/udp. Since TCP and UDP have independent port namespaces, the same number can be assigned to completely different services as long as they use different protocols.

Chapter 59 Summary: What You Learned
Part Topic Key Takeaway
Part 1 IPv6 UDP Client Use AF_INET6, sockaddr_in6, inet_pton(), sendto(), recvfrom()
Part 2 DNS Hierarchical, distributed name system. App makes recursive query; local DNS does iterative resolution. Caching speeds up repeated lookups.
Part 3 /etc/services Maps service names โ†” port numbers. Used by getaddrinfo()/getnameinfo(). Port numbers are IANA-standardised and static.

You have completed Chapter 59 โ€“ Part 3 of 3!

Go back and review the other parts or explore more TLPI tutorials.

โ† Part 1: IPv6 UDP Client โ† Part 2: DNS

Leave a Reply

Your email address will not be published. Required fields are marked *