10 — Networking Fundamentals

Networking is how your Linux box talks to other machines — via TCP/IP, DNS, HTTP, SSH. This chapter covers the network stack, interface configuration (ip), routing, DNS, sockets (ss), and the everyday tools (curl, ssh, rsync).

The TCP/IP Model

bash
┌─────────────────────────────────────┐
│  Application  (HTTP, SSH, DNS, ...)  │  ← your programs
├─────────────────────────────────────┤
│  Transport    (TCP / UDP)            │  ← ports, reliability
├─────────────────────────────────────┤
│  Internet     (IP)                   │  ← routing, IP addresses
├─────────────────────────────────────┤
│  Link         (Ethernet, Wi-Fi)      │  ← MAC addresses, switches
└─────────────────────────────────────┘

Network Interfaces

bash
# Complex Implementation: comprehensive network state in one command
ip -br addr show              # brief (one line per interface: name, state, IP)
ip route                      # routing table
ss -tlnp                      # TCP listening + process

# Legacy (deprecated but common):
ifconfig                      # interfaces + IPs
netstat -rn                   # routing table (use ip route instead)

Interface names:

  • eth0, enp3s0, ens33 — Ethernet (en = Ethernet, p = PCI bus, s = slot).
  • wlan0, wlp2s0 — Wi-Fi.
  • lo — loopback (127.0.0.1).
  • docker0, br-xxx — Docker bridges.

Configuring (Temporary)

bash
sudo ip link set eth0 up               # bring interface up
sudo ip addr add 192.168.1.10/24 dev eth0   # add an IP
sudo ip route add default via 192.168.1.1    # default gateway
# These are TEMPORARY (lost on reboot). Persistent config is in:
# /etc/netplan/ (Ubuntu), /etc/NetworkManager/ or nmcli (RHEL)

Ubuntu — Netplan

yaml
# /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
    eth1:
      addresses: [192.168.1.10/24]
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [8.8.8.8, 1.1.1.1]
bash
sudo netplan apply          # apply config
sudo netplan try            # test (rolls back if you don't confirm in 120s)

Routing

bash
# Complex Implementation: diagnose "can't reach X"
ip route                      # show routing table
ip route get 8.8.8.8          # which route + interface for this destination?
tracepath 8.8.8.8             # trace the path (no root needed)
mtr 8.8.8.8                   # continuous traceroute (live)

# default route — where packets go if no other route matches (your gateway)
# 192.168.1.0/24 dev eth0 — this subnet is directly on eth0 (no router)

DNS

bash
# Complex Implementation: full DNS diagnostic chain
cat /etc/resolv.conf                # nameservers (often auto-generated)
cat /etc/hosts                      # static overrides (checked BEFORE DNS)
cat /etc/nsswitch.conf | grep hosts # order: files dns?
dig example.com                     # full DNS query (A record)
dig @8.8.8.8 example.com            # query a specific server
getent hosts example.com            # via NSS (checks /etc/hosts, then DNS)

# Edge Case: /etc/hosts overrides DNS
# If /etc/hosts has "127.0.0.1 example.com", dig shows the real IP
# but getent/curl/ping use 127.0.0.1 (hosts is checked first)

Edge Case: resolv.conf Is Often Auto-Generated

bash
# On modern systems, resolv.conf is generated by systemd-resolved or NetworkManager
# Don't edit it directly — changes are overwritten
resolvectl status                   # systemd-resolved status
resolvectl dns eth0 8.8.8.8         # set DNS for an interface
# Edit Netplan/NetworkManager config for persistence

Sockets — ss (Replaces netstat)

bash
# Complex Implementation: find what's listening on port 80 + established connections
ss -tlnp | grep :80              # TCP listening + numeric + process
ss -t state established          # all established TCP connections
ss -t state time-wait            # connections in TIME_WAIT
ss -s                            # socket summary
ss -i                            # internal TCP info (RTT, congestion, cwnd)

Common ss flags: -t TCP, -u UDP, -l listening, -n numeric (don't resolve), -p show process.

HTTP — curl

bash
# Complex Implementation: production HTTP health check
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" https://example.com
# 200 0.342s

# POST JSON with headers:
curl -H "Content-Type: application/json" \
     -d '{"key":"value"}' \
     https://api.example.com

# Override DNS (test a specific server without changing DNS):
curl --resolve example.com:443:1.2.3.4 https://example.com

# Debug TLS:
curl -v https://example.com 2>&1 | head

SSH — Secure Shell

bash
# Complex Implementation: jump host (bastion) to reach an internal server
ssh -J jumpuser@jumphost alice@internal.server

# Local port forward (tunnel to an internal service):
ssh -L 8080:internal-app:80 alice@bastion
# Now localhost:8080 → internal-app:80 (via bastion)

# Dynamic SOCKS proxy:
ssh -D 1080 alice@server

# SSH config file simplifies frequent connections:
# ~/.ssh/config
# Host prod
#     HostName prod.example.com
#     User alice
#     Port 2222
#     IdentityFile ~/.ssh/id_ed25519

rsync (Preferred over scp)

bash
# Complex Implementation: efficient sync with delete + exclude + dry-run
rsync -avz --delete --exclude='*.log' --exclude='node_modules' dir/ alice@server:/tmp/dir/
# -a: archive (recursive, preserve perms/owner/timestamps)
# -v: verbose
# -z: compress
# --delete: delete files on dest not in source
# --exclude: skip patterns

# Trailing slash matters:
# rsync dir/ dest/  → copies CONTENTS of dir into dest (dest/file1)
# rsync dir dest/   → copies dir itself into dest (dest/dir/file1)

# Always dry-run first:
rsync -avzn dir/ alice@server:/tmp/dir/

Firewall

bash
# UFW (Ubuntu)
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw limit 22/tcp           # rate-limit SSH (blocks brute force)
sudo ufw --force enable

# firewalld (RHEL)
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --reload

Caveat: Always Allow SSH Before Enabling UFW

bash
# NAIVE: enable firewall without allowing SSH → locked out
sudo ufw enable
# PRODUCTION: allow SSH first
sudo ufw allow 22/tcp
sudo ufw enable

Diagnostics

bash
# Complex Implementation: network diagnostic flow
ping -c 4 8.8.8.8               # is the host reachable? (ICMP)
mtr 8.8.8.8                     # where does it fail? (continuous traceroute)
nc -zv example.com 80           # is port 80 open?
sudo tcpdump -i eth0 port 80 -n # capture port 80 traffic
dig example.com                 # DNS resolving?
curl -v https://example.com    # HTTP working?

# Edge Case: ping fails but host is up
# Some firewalls block ICMP. A failed ping doesn't always mean the host is down.
# Try curl or nc instead.

💡 Tips & Tricks

  • Idiom: use ss (not netstat) for socket statistics — ss is faster, more informative, and netstat is deprecated. ss -tlnp is the go-to for "what's listening?"
  • Idiom: use ip (not ifconfig) for interface config — ifconfig is deprecated and missing features. ip -br addr gives a clean one-line-per-interface view.
  • Idiom: use rsync (not scp) for file transfer — rsync only sends diffs, resumes interrupted transfers, and preserves attributes. scp copies the whole file every time.
  • Idiom: use curl -s -o /dev/null -w "%{http_code}" for script-friendly HTTP checks — extracts just the status code, no body.
  • Idiom: use dig +short for just the IP — dig +short example.com gives 93.184.216.34 (no headers, no metadata).
  • Debug: use mtr (not traceroute) for live route diagnosis — mtr updates continuously, showing packet loss per hop. traceroute is one-shot.

⚠️ Edge Cases & Gotchas

  • "Connection refused" vs "Connection timed out": refused = something is listening but refused you (port closed, or firewall REJECT). Timed out = nothing responded (firewall DROP, or network issue). The distinction matters for diagnosis.
  • /etc/hosts overrides DNS: /etc/hosts is checked before DNS (controlled by nsswitch.conf: hosts: files dns). A stale entry in /etc/hosts can silently override the correct DNS record. dig bypasses /etc/hosts; getent hosts respects it.
  • resolv.conf is often auto-generated: on systemd-resolved or NetworkManager systems, editing /etc/resolv.conf directly is overwritten on reboot. Use resolvectl or edit Netplan/NetworkManager config.
  • scp can't do remote-to-remote: scp alice@server1:file alice@server2:file doesn't work directly — it tries to copy from server1 to your local machine then to server2. Use rsync or ssh alice@server1 "scp file alice@server2:file".
  • rsync trailing slash matters: rsync dir/ dest/ copies the contents of dir. rsync dir dest/ copies dir itself. Always dry-run first (-n).
  • Firewall can block ICMP (ping): a failed ping doesn't always mean the host is down. Try curl or nc -zv host port.
  • Port conflicts on -p: -p 8080:80 fails if host port 8080 is already in use. Check with ss -tlnp | grep 8080.

🧠 Spot the Bug

An admin wants to test if a web server is reachable, but this command hangs for 60 seconds:

bash
curl http://internal-server:80/

ping internal-server works. dig internal-server returns the correct IP. What's likely wrong, and what should they check?

Answer

"Connection timed out" (curl hanging) + "ping works" means the host is reachable at the network layer (ICMP) but the TCP port (80) is not responding. Likely causes:

  1. Firewall is DROPping port 80 (DROP = no response, hangs until timeout; REJECT = immediate "connection refused"). Check: sudo ufw status or sudo firewall-cmd --list-all or sudo iptables -L -n.
  2. The web server isn't running and the firewall is set to DROP (not REJECT) — so instead of "connection refused," it hangs. Check: ss -tlnp | grep :80.
  3. A firewall on the path (not the server itself) is blocking port 80. Check: mtr --tcp --port 80 internal-server.
bash
# Diagnostic flow:
ss -tlnp | grep :80               # is the server listening?
nc -zv internal-server 80         # is the port open? (gives clearer error than curl)
sudo iptables -L -n              # local firewall rules
mtr --tcp --port 80 internal-server  # path diagnostic

The key insight: "connection refused" (fast) vs "connection timed out" (slow) tells you whether the firewall is REJECTing or DROPping, and whether anything is listening at all.