Ports, Sockets & Address Structures How Processes Are Identified on a Network

 

Chapter 58 – Ports, Sockets & Address Structures
Part 5 of 8 Β |Β  How Processes Are Identified on a Network
πŸ”’ Port Numbers
🏠 sockaddr Structs
πŸ”Œ Socket Pairs
πŸ“‹ Well-Known Ports

Identifying a Process on the Network

IP addresses identify a machine on the network. But a machine runs many processes β€” web server, SSH server, database, email β€” all at the same time. How does the OS know which process to deliver a packet to?

The answer is port numbers. Together, an (IP address + port number) uniquely identifies a communication endpoint. This is called a socket address. Two socket addresses (source + destination) uniquely identify a connection β€” called a socket pair.

Key Terms:

Port Number Well-Known Ports Registered Ports Ephemeral Ports sockaddr_in sockaddr_in6 sockaddr_storage Socket Pair 5-Tuple

πŸ”’ Port Numbers β€” 16 Bits of Process Identity

A port number is a 16-bit unsigned integer (0–65535). It identifies a specific service or process within a host. Port numbers are divided into three ranges:

Range Name Who Uses It Examples
0 – 1023 Well-Known / Privileged Ports System services (root only on Linux) HTTP:80, HTTPS:443, SSH:22, DNS:53
1024 – 49151 Registered Ports Applications (IANA registered) MySQL:3306, PostgreSQL:5432, Redis:6379
49152 – 65535 Ephemeral / Dynamic Ports OS assigns automatically to client sockets Your browser’s outgoing port
Linux Ephemeral Port Range: You can see and change it:
cat /proc/sys/net/ipv4/ip_local_port_range
Default: typically 32768 – 60999 on Linux (not exactly 49152+ β€” Linux uses its own range).

πŸ“‹ Important Well-Known Port Numbers
Port Protocol Service Transport
20/21 FTP File Transfer Protocol (data/control) TCP
22 SSH Secure Shell TCP
23 Telnet Remote terminal (insecure) TCP
25 SMTP Email sending TCP
53 DNS Domain Name System UDP (+ TCP for large)
67/68 DHCP Dynamic Host Configuration UDP
80 HTTP Web (unencrypted) TCP
110 POP3 Email retrieval TCP
123 NTP Network Time Protocol UDP
443 HTTPS Secure Web (TLS) TCP
3306 MySQL MySQL database TCP
On Linux, see all services and ports:
cat /etc/services β€” maps service names to port numbers
getservbyname("http", "tcp") β€” C function to look up port by name

🏠 Socket Address Structures β€” The C Structs

In C, socket addresses are represented as structures. The key ones are:

#include <sys/socket.h>
#include <netinet/in.h>

/* ---- Generic socket address (base type used in syscalls) ---- */
struct sockaddr {
    sa_family_t  sa_family;   /* Address family: AF_INET, AF_INET6, AF_UNIX */
    char         sa_data[14]; /* Address data (varies by family) */
};

/* ---- IPv4 socket address ---- */
struct sockaddr_in {
    sa_family_t  sin_family;  /* = AF_INET */
    in_port_t    sin_port;    /* Port number in NETWORK BYTE ORDER */
    struct in_addr sin_addr;  /* IPv4 address in NETWORK BYTE ORDER */
    char         sin_zero[8]; /* Padding to match sockaddr size */
};

struct in_addr {
    in_addr_t    s_addr;      /* 32-bit IPv4 address (network byte order) */
};

/* ---- IPv6 socket address ---- */
struct sockaddr_in6 {
    sa_family_t  sin6_family;   /* = AF_INET6 */
    in_port_t    sin6_port;     /* Port (network byte order) */
    uint32_t     sin6_flowinfo; /* IPv6 flow info (usually 0) */
    struct in6_addr sin6_addr;  /* 128-bit IPv6 address */
    uint32_t     sin6_scope_id; /* Scope ID for link-local addresses */
};

/* ---- Generic storage (holds any address type) ---- */
struct sockaddr_storage {
    sa_family_t  ss_family;       /* Address family */
    /* ... enough space for any address type (128 bytes total) ... */
};
/* Use this when you don't know if you'll get IPv4 or IPv6 */
Why cast to (struct sockaddr *)?
Socket system calls like bind(), connect(), accept() all take a struct sockaddr *. But you fill in a struct sockaddr_in (IPv4) or struct sockaddr_in6 (IPv6). You cast the specific type to the generic type when calling. This is C’s way of doing polymorphism.

πŸ’» Code: Filling in Socket Address Structures
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(void) {
    /* -------- IPv4 address setup -------- */
    struct sockaddr_in addr4;

    memset(&addr4, 0, sizeof(addr4));    /* Zero out all fields first! */
    addr4.sin_family = AF_INET;
    addr4.sin_port   = htons(8080);      /* host→network byte order */

    /* Method 1: specific IP string */
    inet_pton(AF_INET, "192.168.1.100", &addr4.sin_addr);

    /* Method 2: bind to all interfaces (server typically does this) */
    addr4.sin_addr.s_addr = INADDR_ANY;  /* = 0.0.0.0 */

    /* Method 3: loopback */
    addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); /* = 127.0.0.1 */


    /* -------- IPv6 address setup -------- */
    struct sockaddr_in6 addr6;

    memset(&addr6, 0, sizeof(addr6));
    addr6.sin6_family   = AF_INET6;
    addr6.sin6_port     = htons(8080);
    addr6.sin6_flowinfo = 0;
    addr6.sin6_scope_id = 0;

    /* Listen on all IPv6 interfaces */
    addr6.sin6_addr = in6addr_any;       /* = :: */

    /* Or loopback */
    addr6.sin6_addr = in6addr_loopback;  /* = ::1 */

    /* Or specific IPv6 address */
    inet_pton(AF_INET6, "2001:db8::1", &addr6.sin6_addr);

    printf("Setup complete.\n");
    return 0;
}
/* Use in bind() β€” cast to (struct sockaddr *) */

/* IPv4: */
bind(sockfd, (struct sockaddr *)&addr4, sizeof(addr4));

/* IPv6: */
bind(sockfd, (struct sockaddr *)&addr6, sizeof(addr6));

/* Generic (when you have sockaddr_storage): */
struct sockaddr_storage ss;
/* ... fill ss ... */
bind(sockfd, (struct sockaddr *)&ss, sizeof(ss));

πŸ”Œ Socket Pair β€” Uniquely Identifying a Connection

A single TCP connection is uniquely identified by a 5-tuple:

Protocol
TCP
+
Source IP
192.168.1.5
+
Source Port
54321
+
Dest IP
93.184.216.34
+
Dest Port
80

This 5-tuple is unique per connection. The OS uses it to demultiplex incoming packets to the right socket. This is why a web server on port 80 can handle thousands of simultaneous connections β€” each has a different source IP and/or source port.

/* See active connections and their 5-tuples on Linux: */
$ ss -tnp

/* Example output:
State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port
ESTAB    0       0       192.168.1.5:54321   93.184.216.34:80   users:(("curl",pid=1234))
ESTAB    0       0       192.168.1.5:54322   93.184.216.34:80   users:(("curl",pid=1235))
LISTEN   0       128     0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=999))
*/

🎯 Interview Questions β€” Ports & Addressing
Q1: What is the range of port numbers and how are they divided?

A: 0–65535 (16-bit). Three ranges: 0–1023 = well-known/privileged (require root to bind on Linux), 1024–49151 = registered (IANA-assigned for applications), 49152–65535 = ephemeral/dynamic (OS assigns to clients automatically).

Q2: What is the difference between sockaddr, sockaddr_in, and sockaddr_storage?

A: struct sockaddr is the generic base type used in socket system call parameters. struct sockaddr_in is the IPv4-specific structure you actually fill in. struct sockaddr_in6 is for IPv6. struct sockaddr_storage is large enough to hold any address family β€” use it when writing address-family-agnostic code (e.g., a server that handles both IPv4 and IPv6). You cast the specific struct to (struct sockaddr *) when calling socket APIs.

Q3: What is INADDR_ANY and when do you use it?

A: INADDR_ANY (value = 0, i.e., 0.0.0.0) means “bind to all available network interfaces.” Servers typically use it so they accept connections on any network interface (eth0, wlan0, lo, etc.) on the specified port. If you bind to a specific IP (e.g., 192.168.1.1), only connections arriving on that interface are accepted.

Q4: How does the OS demultiplex incoming TCP packets to the correct socket?

A: Using the 5-tuple: (protocol, source IP, source port, destination IP, destination port). When a packet arrives, the kernel looks up this 5-tuple in its socket table. For a LISTEN socket, it matches on (protocol, destination IP, destination port) only. For ESTABLISHED sockets, all 5 fields must match. This is why thousands of connections to the same server port (e.g., :80) can coexist β€” they differ in source IP or source port.

Q5: Why can’t a normal user bind to port 80 on Linux?

A: Ports below 1024 are “privileged ports.” On Linux, only processes with the CAP_NET_BIND_SERVICE capability (traditionally root) can bind to them. This prevents unprivileged users from hijacking system services. Workarounds: run as root, use setcap CAP_NET_BIND_SERVICE on the binary, use a reverse proxy (nginx on port 80 forwards to your app on port 8080), or use iptables port redirect.

πŸ“– Chapter 58 Navigation

← Part 4: TCP Next: Byte Order β†’

Leave a Reply

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