/etc/services
Port Numbers
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
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) |
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.
| 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 |
| 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.
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;
}
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.
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;
}
Your Codegetaddrinfo( |
โ | 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.
# 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
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.
| 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.
