The Definitive Guide to Linux Network Programming

The Definitive Guide to Linux Network Programming · Keir Davis, John W. Turner & Nathan Yocom ·361 pages

Linux socket programming from first principles: Berkeley socket API lifecycle, three server architectures (iterative/select/fork), TCP vs UDP and stateful vs stateless protocol design, OpenSSL TLS integration (SSL_CTX/SSL layer setup), and secure C coding (buffer overflow prevention, unsafe function replacements, error-handling wrappers).

Capabilities (7)
  • Build TCP servers and clients using Berkeley socket API (socket/bind/listen/accept/connect/read/write)
  • Implement select()-based multiplexing to handle N clients in a single process without threads
  • Build fork-per-client servers with proper SIGCHLD handling to avoid zombie processes
  • Integrate OpenSSL TLS into existing socket code using SSL_CTX/SSL/SSL_set_fd pattern
  • Design text-based application protocols (IRC-style command/reply format with stateful sessions)
  • Prevent buffer overflows by replacing unsafe C functions (gets/strcpy/sprintf) with bounded alternatives
  • Debug network programs with tcpdump, netstat, nc, and telnet for manual protocol testing
How to use

Install this skill and Claude can implement complete TCP servers in C using iterative, select-multiplexed, or fork-per-client architectures, add OpenSSL TLS to existing plaintext socket code, design application-layer protocols with correct framing, and audit C networking code for buffer overflow and error-handling bugs

Why it matters

The Berkeley socket API is the universal interface for networked software, and many production vulnerabilities in networked C programs trace directly to unsafe string functions or missing error checks that appear in introductory examples — this skill enables building correct, secure daemons from scratch

Example use cases
  • Write a single-process TCP echo server using select() that handles up to 64 simultaneous clients without threads, with proper fd_set copy discipline and zombie-safe SIGCHLD handling
  • Add OpenSSL TLS to an existing plaintext TCP server using SSL_CTX and SSL_set_fd, including certificate loading and per-connection handshake
  • Review a custom IRC-like server implementation for buffer overflow vectors, zombie child processes from missing SIGCHLD handling, and use of unsafe gets()/strcpy() functions

Linux Network Programming Skill

Berkeley Socket API

Server lifecycle

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

int sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// Allow port reuse (avoids "address in use" errors during dev)
int val = 1;
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

struct sockaddr_in sAddr = {0};
sAddr.sin_family      = AF_INET;
sAddr.sin_port        = htons(8000);
sAddr.sin_addr.s_addr = INADDR_ANY;     // listen on all interfaces

bind(sd, (struct sockaddr *)&sAddr, sizeof(sAddr));
listen(sd, 5);                           // backlog = 5

while (1) {
    struct sockaddr_in clientAddr = {0};
    socklen_t clientLen = sizeof(clientAddr);
    int clientSd = accept(sd, (struct sockaddr *)&clientAddr, &clientLen);
    // clientSd is the new per-connection socket
    write(clientSd, "Hello\n", 6);
    close(clientSd);
}
close(sd);

Client lifecycle

int sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

struct sockaddr_in server = {0};
server.sin_family      = AF_INET;
server.sin_port        = htons(8000);
inet_aton("127.0.0.1", &server.sin_addr);

connect(sd, (struct sockaddr *)&server, sizeof(server));

char buf[256];
int n = read(sd, buf, sizeof(buf));
close(sd);

Socket address byte order

htons(port)     // host-to-network short (port numbers)
htonl(addr)     // host-to-network long  (IPv4 addresses)
ntohs(port)     // network-to-host short
ntohl(addr)     // network-to-host long
inet_aton(str, &addr)  // "192.168.1.1" → sin_addr
gethostbyname("host")  // DNS lookup → struct hostent*

Server Architecture Patterns

1. Iterative (single-client)

socket → bind → listen → loop: accept → read/write → close

Simplest. Only one client at a time. Use for testing/tools.

2. Multiplexing with select() (single-process, N clients)

fd_set readset, testset;
FD_ZERO(&readset);
FD_SET(listensock, &readset);

while (1) {
    testset = readset;  // select() MODIFIES the set — copy each time
    select(FD_SETSIZE, &testset, NULL, NULL, NULL);  // NULL = block forever

    for (int x = 0; x < FD_SETSIZE; x++) {
        if (!FD_ISSET(x, &testset)) continue;

        if (x == listensock) {
            // new connection
            int newsock = accept(listensock, NULL, NULL);
            FD_SET(newsock, &readset);       // add to watch set
        } else {
            // data from existing client
            int n = recv(x, buf, sizeof(buf), 0);
            if (n <= 0) {
                close(x);
                FD_CLR(x, &readset);         // remove from watch set
            } else {
                send(x, buf, n, 0);          // echo back
            }
        }
    }
}

Key rules:

  • Copy readset to testset before each select() call (select modifies the set)
  • FD_SETSIZE is typically 1024 — for more connections use epoll
  • No threads needed; all I/O in one loop

3. Fork-per-client (multi-process)

#include <signal.h>
#include <sys/wait.h>

// Prevent zombies: reap child when it exits
signal(SIGCHLD, SIG_IGN);   // Linux-specific: auto-reap
// Or:
void sigchld_handler(int s) { while (waitpid(-1, NULL, WNOHANG) > 0); }
signal(SIGCHLD, sigchld_handler);

while (1) {
    int clientSd = accept(listensock, NULL, NULL);
    pid_t pid = fork();
    if (pid == 0) {
        // Child: handle client
        close(listensock);   // child doesn't need the listener
        // ... read/write clientSd ...
        close(clientSd);
        exit(0);
    } else {
        // Parent: accept next connection
        close(clientSd);     // parent doesn't use this socket
    }
}

Key rules:

  • Parent must close(clientSd) and child must close(listensock) — otherwise fd reference counts leak
  • Call waitpid() or set SIGCHLD to avoid zombie processes
  • Child crash doesn’t affect parent or other children (process isolation)

Architecture Selection Guide

PatternConcurrencyComplexityShared StateBest For
IterativeNoneTrivialTrivialTesting, CLI tools
select() multiplexingSingle-processModerateYes (same proc)Many idle connections
Fork per clientMulti-processModerateHard (shmget)Isolation needed
Thread per clientMulti-threadModerateEasy (shared heap)Short-lived requests
Process poolMulti-processHighHardPre-forked, high load

Custom Protocol Design

Design decisions checklist

1. TCP vs UDP?
   TCP  → connection-oriented, ordered, reliable
   UDP  → connectionless, no ordering, lower overhead (DNS, NTP)

2. Text vs Binary?
   Text   → human-readable, easy to debug (telnet test), IRC-like
   Binary → compact, endian-sensitive (use htonl/ntohl for all fields)

3. Stateful vs Stateless?
   Stateful  → server tracks session per connection (FTP, SMTP, POP3)
   Stateless → each request self-contained; client re-sends context (HTTP)

4. Active vs Passive server?
   Passive → server only responds to client commands (HTTP, SMTP)
   Active  → server can push to client without request (chat, pub/sub)

5. Session identification?
   If stateless but need sessions: assign session ID, send as cookie/URL param

Text protocol message format (IRC-inspired)

CLIENT → SERVER: COMMAND param1 param2 ...\r\n
SERVER → CLIENT: 100 Success\r\n           ← numeric reply (success/fail)
SERVER → CLIENT: JOIN nickname\r\n         ← independent event message
  • Commands: alpha characters
  • Replies: numeric code + description
  • Client sends one command at a time; waits for reply before next

State storage options

LocationMechanismRisk
Server memorySession struct per fdLost on crash; session hijack if ID exposed
Client cookieName=value; domain-scopedReadable to any local user
URL parameter?session=abc123Logged in server access logs

OpenSSL TLS Integration

Initialization (call once at program start)

#include <openssl/ssl.h>
#include <openssl/err.h>

OpenSSL_add_all_algorithms();
SSL_load_error_strings();

Server with TLS

// 1. Method + context
SSL_METHOD *method = TLSv1_server_method();
SSL_CTX *ctx = SSL_CTX_new(method);

// 2. Load certificate and private key (can be in same PEM file)
SSL_CTX_use_certificate_file(ctx, "server.pem", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx,  "server.pem", SSL_FILETYPE_PEM);
SSL_CTX_check_private_key(ctx);   // verify key matches cert

// 3. Create plain socket as usual
int server_fd = socket(PF_INET, SOCK_STREAM, 0);
bind(server_fd, ...); listen(server_fd, 5);

// 4. For each connection: wrap in SSL
int client_fd = accept(server_fd, ...);
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_fd);        // associate SSL with socket fd
if (SSL_accept(ssl) <= 0) {        // TLS handshake (server side)
    ERR_print_errors_fp(stderr);
}

// 5. Secure read/write
SSL_write(ssl, buf, len);
SSL_read(ssl,  buf, len);

// 6. Clean shutdown
if (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN)
    SSL_shutdown(ssl);             // reciprocate shutdown handshake
SSL_free(ssl);
close(client_fd);

// Cleanup
SSL_CTX_free(ctx);

Client with TLS

SSL_METHOD *method = TLSv1_client_method();
SSL_CTX *ctx = SSL_CTX_new(method);

int sd = socket(PF_INET, SOCK_STREAM, 0);
connect(sd, ...);

SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sd);
SSL_connect(ssl);                  // TLS handshake (client side)

// Optional: inspect connection
printf("[%s, %s]\n", SSL_get_version(ssl), SSL_get_cipher(ssl));

BIO layer (stacking I/O)

BIO = stackable I/O abstraction
Examples: socket BIO → 3DES BIO → file BIO

Allows: single SSL_read() that decrypts and writes to file
Used when building custom transport pipelines

Secure Coding Practices

Unsafe functions → safe replacements

UnsafeSafe AlternativeProblem
gets()fgets(buf, n, stdin)No bounds check
strcpy()strncpy(dst, src, n)No bounds check
sprintf()snprintf(buf, n, fmt, ...)No bounds check
strcat()strncat(dst, src, n)No bounds check
scanf()fgets() + sscanf()No bounds check
strcmp()explicit equal checkReturns –1/0/1, not bool

strcmp() pitfall

// WRONG: strcmp returns -1/0/1, so !strcmp(-1) == true even on mismatch
if (!strcmp(password_buffer, "secret")) { /* WRONG */ }

// CORRECT: check for exact equality
if (strcmp(password_buffer, "secret") == 0) { /* correct */ }

Error handling wrapper pattern

// Wrap every syscall — keeps main code clean
int w_socket(int domain, int type, int protocol) {
    int sd = socket(domain, type, protocol);
    if (sd == -1) {
        fprintf(stderr, "[%s:%d] socket error\n", __FILE__, __LINE__);
        perror("socket");
        exit(-1);
    }
    return sd;
}
// Same pattern for bind, listen, accept, connect, read, write

Buffer overflow prevention

// memcpy: always use explicit destination size, never strlen(src)
char dest[1024];
char src[2048] = "...";
memcpy(dest, src, sizeof(dest));          // SAFE: bounded by dest
// NOT: memcpy(dest, src, strlen(src));   // UNSAFE if src > dest

// Always check malloc return
void *w_malloc(size_t n) {
    void *p = calloc(1, n);              // calloc also zeroes memory
    if (!p) { perror("malloc"); exit(1); }
    return p;
}

Debugging Tools

# Traffic capture
tcpdump -i eth0 port 8000               # raw packets
wireshark                                # GUI analysis, session follow

# Port inspection
netstat -tlnp                           # listening TCP sockets
ss -tlnp                                # faster alternative

# Socket testing
telnet localhost 8000                   # manual text protocol test
nc -zv localhost 8000                   # connection test
nc -l 8000                             # listen for test client

# DNS
host www.example.com                    # A record lookup
host -t mx example.com                  # MX record