Networking, Low-Level Sockets, and IP Address Calculations

Updated

September 7, 2026

Networking, Low-Level Sockets, and IP Address Calculations

After reading this chapter, you will master low-level networking primitives using the standard library: construct TCP client and server sockets with socket, dissect and sanitize URLs with urllib.parse, and calculate subnets, masks, and IP ranges with ipaddress.

Mental model

Python provides direct wrappers around operating system Berkeley sockets and IP networking primitives:

Socket Lifecycle (TCP Server):
  socket(AF_INET, SOCK_STREAM) ──▶ Creates OS socket file descriptor
        │
        ▼ bind(('127.0.0.1', 8080))
  Binds to interface and port
        │
        ▼ listen(backlog=5)
  Enters listening state for incoming SYN packets
        │
        ▼ accept() ──▶ Blocks until 3-way handshake completes
        │              Returns (client_socket, client_address)
        │
        ▼ sendall() / recv() (Stream bytes over TCP connection)
        │
        ▼ close() (Sends FIN packet, releases file descriptor)

The ipaddress module models IPv4 and IPv6 addresses, subnets, and host ranges as first-class objects with mathematical comparison and containment operations.


Minimal example

Save as sockets_and_ipaddress.py:

# sockets_and_ipaddress.py
import ipaddress
import urllib.parse

def main() -> None:
    # 1. IP Network & CIDR Calculations
    subnet = ipaddress.ip_network("10.0.1.0/24")
    print(f"Subnet CIDR          : {subnet}")
    print(f"Network Address      : {subnet.network_address}")
    print(f"Broadcast Address    : {subnet.broadcast_address}")
    print(f"Netmask              : {subnet.netmask}")
    print(f"Usable Host Count    : {subnet.num_addresses - 2}")

    # Membership test (O(1) bitmask check)
    target_ip = ipaddress.ip_address("10.0.1.55")
    print(f"Is {target_ip} in {subnet}? {target_ip in subnet}")

    # 2. URL Parsing with urllib.parse
    raw_url = "https://api.internal.net:8443/v1/telemetry?filter=active&limit=50#summary"
    parsed = urllib.parse.urlparse(raw_url)
    
    print(f"\nParsed URL Components:")
    print(f"  Scheme  : {parsed.scheme}")
    print(f"  Host    : {parsed.hostname}")
    print(f"  Port    : {parsed.port}")
    print(f"  Path    : {parsed.path}")
    
    # Parse query string parameters into a dictionary
    params = urllib.parse.parse_qs(parsed.query)
    print(f"  Query Parameters: {params}")

if __name__ == "__main__":
    main()

Run via uv run python sockets_and_ipaddress.py:

Subnet CIDR          : 10.0.1.0/24
Network Address      : 10.0.1.0
Broadcast Address    : 10.0.1.255
Netmask              : 255.255.255.0
Usable Host Count    : 254
Is 10.0.1.55 in 10.0.1.0/24? True

Parsed URL Components:
  Scheme  : https
  Host    : api.internal.net
  Port    : 8443
  Path    : /v1/telemetry
  Query Parameters: {'filter': ['active'], 'limit': ['50']}

Worked examples

Case 1: Low-Level TCP Loopback Echo Service

Writing a raw TCP client and server directly demonstrates socket buffers, timeouts, and byte transfers:

# tcp_loopback_echo.py
import socket
import threading
import time

def start_echo_server(port: int, stop_event: threading.Event) -> None:
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # SO_REUSEADDR allows instant restart without waiting for TIME_WAIT
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(("127.0.0.1", port))
    server.listen(1)
    server.settimeout(1.0)  # Unblock periodically to check stop_event

    print(f"[Server] Listening on 127.0.0.1:{port}...")
    while not stop_event.is_set():
        try:
            client_conn, client_addr = server.accept()
        except socket.timeout:
            continue

        with client_conn:
            data = client_conn.recv(1024)
            if data:
                print(f"[Server] Received: {data.decode()} from {client_addr}")
                client_conn.sendall(b"ECHO:" + data)
    server.close()

def main() -> None:
    test_port = 18888
    stop_event = threading.Event()
    server_thread = threading.Thread(target=start_echo_server, args=(test_port, stop_event))
    server_thread.start()

    time.sleep(0.05)  # Wait for server to bind

    # Client connection
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.settimeout(2.0)
    try:
        client.connect(("127.0.0.1", test_port))
        message = b"PING_CLUSTER"
        client.sendall(message)
        response = client.recv(1024)
        print(f"[Client] Received back: {response.decode()}")
    finally:
        client.close()
        stop_event.set()
        server_thread.join()

if __name__ == "__main__":
    main()

Run:

uv run python tcp_loopback_echo.py

Output:

[Server] Listening on 127.0.0.1:18888...
[Server] Received: PING_CLUSTER from ('127.0.0.1', ...)
[Client] Received back: ECHO:PING_CLUSTER

Case 2: Subnet Splitting and Overlap Detection

Automating IP Address Management (IPAM) requires calculating available subnets and ensuring IP blocks never overlap:

# ipam_calculator.py
import ipaddress

def plan_datacenter_subnets() -> None:
    vpc_cidr = ipaddress.ip_network("172.16.0.0/16")
    print(f"Allocating subnets from VPC CIDR: {vpc_cidr}")

    # Subdivide /16 into /24 subnets
    subnets = list(vpc_cidr.subnets(new_prefix=24))
    print(f"Generated {len(subnets)} /24 subnets. First 3:")
    for sn in subnets[:3]:
        print(f"  - {sn} (Hosts: {sn.num_addresses - 2})")

    # Check for overlaps
    public_subnet = ipaddress.ip_network("172.16.1.0/24")
    private_subnet = ipaddress.ip_network("172.16.2.0/24")
    print(f"\nDo public and private subnets overlap? {public_subnet.overlaps(private_subnet)}")

if __name__ == "__main__":
    plan_datacenter_subnets()

Run:

uv run python ipam_calculator.py

Output:

Allocating subnets from VPC CIDR: 172.16.0.0/16
Generated 256 /24 subnets. First 3:
  - 172.16.0.0/24 (Hosts: 254)
  - 172.16.1.0/24 (Hosts: 254)
  - 172.16.2.0/24 (Hosts: 254)

Do public and private subnets overlap? False

Pitfalls

Pitfall 1: Host Bits Set in IPv4Network

Creating an IPv4Network("192.168.1.15/24") raises ValueError: has host bits set because the last octet is non-zero in a /24 network:

# THE BUG:
net = ipaddress.ip_network("192.168.1.15/24")  # ValueError!

# THE FIX: If you want the network containing this host IP, pass strict=False
net = ipaddress.ip_network("192.168.1.15/24", strict=False)  # Resolves to 192.168.1.0/24

# OR use IPv4Interface:
iface = ipaddress.ip_interface("192.168.1.15/24")  # Preserves both host IP and network

Pitfall 2: Assuming recv() Returns a Full Packet

TCP is a stream-oriented protocol, not message-oriented. Calling sock.recv(1024) may return 10 bytes, 100 bytes, or 1024 bytes depending on OS buffer chunks and network fragmentation. Always loop until you reach an expected length or delimiter (like \n).


Exercises

  1. Write a script that checks whether an IP address is a private RFC 1918 address using ipaddress.IPv4Address.is_private.
  2. Construct a URL query string from a dictionary of parameters using urllib.parse.urlencode().
  3. Create a non-blocking TCP socket and demonstrate catching BlockingIOError when reading without incoming data.
  4. Calculate the supernet of two adjacent /24 subnets using ipaddress.collapse_addresses().

Further reading

  • Python Standard Library: socket, ipaddress, and urllib.parse modules.
  • RFC 793: Transmission Control Protocol.
  • RFC 4632: Classless Inter-domain Routing (CIDR).