Tech
How Linux Networking Works: From Packets to Python Sockets
Explore the journey of network data from the physical wire through the Linux kernel and into your application. Learn how packets, sockets, and the TCP/IP stack interact to move data.
June 2026 · 6 min read · 1 views · 0 hearts
Advertisement
Data doesn't just appear in your browser. It arrives in chunks, decoded by layers of software that most developers rarely think about. But if you've ever debugged a slow API call, tuned a web server, or wondered why curl sometimes hangs, you've touched the edges of Linux networking. Here’s how it actually works—from the wire to your application.
The Packet: The Atom of Network Data
Every byte you send over a network gets wrapped in a packet—a structured chunk of data with a header and a payload. On Linux, packets arrive at a network interface card (NIC) and get passed to the kernel. The kernel doesn’t just dump them into your Python script; it inspects headers, checks checksums, and routes them to the right process.
The key layers are:
- Ethernet frame – physical address (MAC) handling, stripped by the NIC driver.
- IP packet – contains source/destination IPs, handled by the kernel’s IP stack.
- TCP or UDP segment – port numbers, sequence numbers, flags. TCP adds reliability.
- Payload – the actual data (HTTP request body, a file chunk, etc.)
A packet is like a Russian nesting doll: the kernel peels off one layer, then the next, until the raw data reaches your application.
Sockets: The Doorway Between Kernel and User Space
A socket is the file-like endpoint your program talks to. On Linux, everything is a file, and sockets are no exception. When you call socket.socket() in Python, you ask the kernel for a file descriptor that can send/receive network data.
Under the hood, socket() triggers a system call that allocates kernel memory for buffer space, protocol state, and a queue. The kernel doesn’t invent new objects—it maps your socket to internal structures like struct sock and struct inet_sock.
The Two Main Socket Types
- Stream sockets (TCP) – reliable, ordered, connection-based. Like a phone call.
- Datagram sockets (UDP) – unreliable, unordered, no connection. Like sending postcards.
When you bind() a socket to a port, the kernel registers that port in a global table—no two sockets can bind the same port on the same IP without SO_REUSEADDR.
The Journey of a Packet: from connect() to recv()
Let’s trace what happens when you run a simple Python script that does requests.get("http://example.com").
- DNS lookup – Python calls
getaddrinfo(), which sends a UDP query to a DNS server. The kernel handles socket creation for you. - Socket creation –
socket(AF_INET, SOCK_STREAM, 0)asks for a TCP IPv4 socket. - Connect –
connect()triggers a TCP three-way handshake: - Your kernel sends SYN → remote sends SYN-ACK → your kernel sends ACK. - All this happens in kernel space. Your Python code is blocked onconnect()until the handshake finishes. - Send data –
send()copies your HTTP request bytes into a kernel buffer. The kernel builds a TCP segment, adds IP headers, wraps an Ethernet frame, and pushes it to the NIC driver. - Receive – Incoming packets arrive. The NIC triggers a hardware interrupt. The kernel reads the packet, matches it to your socket by the 5-tuple (src IP, src port, dst IP, dst port, protocol), and places the payload in your socket’s receive buffer.
- Read – Your
recv()call copies data from kernel buffer to user-space memory.
This is why socket programming is fast—the kernel does the heavy lifting of reassembly, retransmission, and flow control.
The Internet: Routing and Beyond
Packets don’t travel in isolation. Between your machine and example.com lies a maze of routers. Each router uses the IP destination to look up its forwarding table—a map of network prefixes to next-hop IPs.
Linux itself can act as a router. You can see its routing table with ip route show. Every outgoing packet gets a route lookup:
- Local destinations → delivered directly to the local socket.
- Remote destinations → forwarded to the next hop (default gateway usually).
This is also where network namespaces shine. Each namespace has its own routing table, interfaces, and even its own localhost. Containers use this isolation.
What Can Go Wrong? Real-World Hiccups
Understanding this stack helps you debug real problems:
- Socket buffer overflow – If your Python app doesn’t
recv()fast enough, the kernel drops packets.netstat -sshows dropped segments. - TCP window scaling – Large transfers stall if one side can’t advertise enough window space.
sysctlcan tunetcp_rmem. - Non-blocking sockets –
send()can returnEWOULDBLOCKif the kernel buffer is full. You need to handleEAGAINin Python.
The Takeaway
Linux networking is elegant but not magic. Every curl call, every socket.connect(), every HTTP response goes through the same pipeline: packet → kernel → socket → application. Knowing that path lets you predict performance bottlenecks, fix strange timeouts, and write faster network code.
Next time you type ping google.com, remember: you’re not just sending a byte. You’re starting a chain of kernel work, routing decisions, and buffer management that spans continents. And you can control every piece of it.
Advertisement
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.