computer-science5 min read

Computer Networks Tutorial: Learn Networking from Scratch (2026)

Computer Networks Tutorial: Learn Networking from Scratch (2026)

Published:  |  Category: Computer Science  |  Reading time: ~15 min
Computer Networks Tutorial: Learn Networking from Scratch (2026)

Computer networks are the nervous system of the digital world, connecting billions of devices across continents. My experience building distributed systems and debugging packet loss in production has taught me that network fundamentals are indispensable — the difference between a 50 ms response and a timeout often lies in understanding TCP flow control or DNS resolution order. This tutorial follows the OSI and TCP/IP models from the physical layer up to the application layer, with hands-on packet analysis throughout.

We will examine how data is framed, routed, and reliably delivered across unreliable media. Each layer adds headers, encapsulates payloads, and provides services to the layer above. By the end, you should be able to read a packet capture, diagnose common failures, and design network-aware applications.

OSI Model and Encapsulation

The OSI model defines seven abstraction layers: Physical, Data Link, Network, Transport, Session, Presentation, and Application. Each layer communicates with its peer on the remote host using protocol data units (PDUs). Encapsulation wraps each layer's PDU inside the next layer's payload — a TCP segment becomes the payload of an IP packet, which becomes the payload of an Ethernet frame. The TCP/IP model collapses this into four layers: Link, Internet, Transport, and Application.

# Pseudo-code for encapsulation

def encapsulate(application_data):
    tcp_seg = TCP(port=443) + application_data
    ip_pkt = IP(src="10.0.0.1", dst="10.0.0.2") + tcp_seg
    eth_frame = Ethernet(src_mac, dst_mac) + ip_pkt
    return eth_frame.bytes()

def decapsulate(frame_bytes):
    eth = Ethernet(frame_bytes)
    ip = IP(eth.payload)
    tcp = TCP(ip.payload)
    return tcp.payload

Ethernet and ARP

Ethernet is the dominant link-layer technology for local area networks. Each frame carries source and destination MAC addresses, a type field, payload (up to 1500 bytes MTU), and a CRC trailer for error detection. The Address Resolution Protocol (ARP) maps IP addresses to MAC addresses on a local subnet. When a host needs to send to an IP, it broadcasts an ARP request; the target replies with its MAC. ARP spoofing is a classic attack where a malicious host sends forged ARP replies to intercept traffic.

# ARP request/response simulation

class ARPTable:
    def __init__(self):
        self.table = {}

    def request(self, ip, sender_mac):
        print(f"Who has {ip}? Tell {sender_mac}")
        return self.table.get(ip)

    def reply(self, ip, mac):
        self.table[ip] = mac
        return f"{ip} is at {mac}"

IP Addressing and Subnetting

Internet Protocol (IP) addresses identify hosts across networks. IPv4 uses 32-bit addresses written in dotted decimal (e.g., 192.168.1.1), with a network prefix and host suffix indicated by the subnet mask. Subnetting divides a network into smaller segments — for example, 192.168.1.0/24 has 254 usable addresses. CIDR notation (e.g., /24) replaced classful addressing to allow flexible prefix lengths. IPv6 uses 128-bit addresses to solve IPv4 exhaustion and eliminates NAT by providing globally unique addresses for every device.

def subnet_info(ip_cidr):
    ip_str, prefix = ip_cidr.split('/')
    prefix = int(prefix)
    ip_int = sum(int(b) << (24 - 8*i) for i, b in enumerate(ip_str.split('.')))
    mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
    network = ip_int & mask
    broadcast = network | ~mask & 0xFFFFFFFF
    return {
        "network": f"{(network>>24)&0xFF}.{(network>>16)&0xFF}.{(network>>8)&0xFF}.{network&0xFF}/{prefix}",
        "broadcast": f"{(broadcast>>24)&0xFF}.{(broadcast>>16)&0xFF}.{(broadcast>>8)&0xFF}.{broadcast&0xFF}",
        "usable_hosts": 2**(32-prefix) - 2
    }

TCP: Reliable Data Delivery

TCP provides reliable, ordered, error-checked byte streams over an unreliable IP layer. It achieves reliability through sequence numbers, acknowledgment (ACK) packets, and retransmission timeouts (RTO). The three-way handshake (SYN, SYN-ACK, ACK) establishes a connection before data flows. Flow control uses a sliding window advertised by the receiver to prevent the sender from overwhelming it. Congestion control algorithms — Tahoe, Reno, Cubic — adjust the sending rate based on packet loss and RTT to share bandwidth fairly.

def tcp_cubic(cwnd, ssthresh, loss=False):
    if loss:
        ssthresh = cwnd * 0.5
        cwnd = 1
    elif cwnd < ssthresh:
        cwnd *= 2
    else:
        cwnd += 1 / cwnd
    return cwnd, ssthresh

UDP and DNS

UDP is a connectionless transport protocol with minimal overhead — no handshake, no retransmission, no ordering guarantees. It is ideal for real-time applications like voice/video calls, gaming, and DNS queries where timeliness matters more than perfect reliability. DNS (Domain Name System) translates human-readable domain names to IP addresses. A recursive resolver queries root servers, TLD servers, and authoritative nameservers to resolve a name, caching results at each level with time-to-live (TTL) values.

import socket

def resolve_dns(domain):
    try:
        ip = socket.gethostbyname(domain)
        return ip
    except socket.gaierror:
        return "Resolution failed"

HTTP/2 and HTTP/3

HTTP/1.1 suffered from head-of-line blocking — one slow request blocks all others on the same connection. HTTP/2 introduced multiplexing (multiple streams over a single TCP connection), header compression (HPACK), and server push. However, TCP-level head-of-line blocking persisted because a lost packet stalls all streams. HTTP/3 switches the transport to QUIC, built on UDP, which eliminates head-of-line blocking at the transport layer, reduces connection establishment to 0-RTT in many cases, and includes built-in encryption via TLS 1.3.

class HTTP2Frame:
    def __init__(self, stream_id, frame_type, flags, payload):
        self.length = len(payload)
        self.stream_id = stream_id
        self.type = frame_type
        self.flags = flags
        self.payload = payload

    def pack(self):
        header = bytearray(9)
        header[0:3] = self.length.to_bytes(3, 'big')
        header[3] = self.type
        header[4] = self.flags
        header[5:9] = self.stream_id.to_bytes(4, 'big')
        return bytes(header) + self.payload

Frequently Asked Questions

What is the difference between TCP and UDP?

TCP is connection-oriented, guarantees ordered delivery, handles retransmission, and includes congestion control. UDP is connectionless, provides no reliability guarantees, and has lower overhead. Choose TCP for data integrity, UDP for low latency.

How does NAT work?

Network Address Translation (NAT) maps private IP addresses (e.g., 192.168.x.x) to a single public IP. The NAT device rewrites packet headers and tracks connections in a translation table. This allows multiple devices to share one public IP and provides a basic security boundary.

What causes packet loss in a network?

Packet loss occurs due to buffer overflow in routers (congestion), bit errors on noisy links (especially wireless), faulty hardware, or firewall rules dropping traffic. TCP interprets loss as congestion and reduces its sending rate.

How does a VPN work at the network level?

A VPN creates an encrypted tunnel between the client and the VPN server. The client's IP packets are encapsulated inside an encrypted payload, which is then sent over the public Internet. The VPN server decrypts and forwards packets to the destination, masking the client's real IP.

Originally published on Ayodhyyya. Last updated June 1, 2026.