The Definitive Guide to Linux Network Programming
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).
- › 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
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
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
- › 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
readsettotestsetbefore eachselect()call (select modifies the set) FD_SETSIZEis 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 mustclose(listensock)— otherwise fd reference counts leak - Call
waitpid()or setSIGCHLDto avoid zombie processes - Child crash doesn’t affect parent or other children (process isolation)
Architecture Selection Guide
| Pattern | Concurrency | Complexity | Shared State | Best For |
|---|---|---|---|---|
| Iterative | None | Trivial | Trivial | Testing, CLI tools |
select() multiplexing | Single-process | Moderate | Yes (same proc) | Many idle connections |
| Fork per client | Multi-process | Moderate | Hard (shmget) | Isolation needed |
| Thread per client | Multi-thread | Moderate | Easy (shared heap) | Short-lived requests |
| Process pool | Multi-process | High | Hard | Pre-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
| Location | Mechanism | Risk |
|---|---|---|
| Server memory | Session struct per fd | Lost on crash; session hijack if ID exposed |
| Client cookie | Name=value; domain-scoped | Readable to any local user |
| URL parameter | ?session=abc123 | Logged 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
| Unsafe | Safe Alternative | Problem |
|---|---|---|
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 check | Returns –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