Target Duration: 2–4 minutes (~300–450 spoken words)
Focus: Pointwise verbal delivery covering kernel IP forwarding, the 5 Netfilter hooks, egress SNAT, ingress DNAT, inter-container peering, and resolving the Martian loopback drop.
Opening & Scope:
"For packet routing and firewall rules, I leveraged Linux Netfilter hooks and iptables to manage outbound internet access, inbound port mapping, and inter-container communication across subnets."
Step 1: Enabling Kernel IP Forwarding:
"First, by default, the Linux kernel operates as an end-host and drops packets not addressed to its own IP. So, I started by enabling IP forwarding in /proc/sys/net/ipv4/ip_forward, which turns the host into a software router capable of forwarding packets between virtual and physical interfaces."
Step 2: Egress Internet Access via Source NAT (MASQUERADE):
"Next, because container IP addresses like 192.168.1.2 are private and non-routable on the public internet, I configured a Source NAT rule in Netfilter's POSTROUTING chain using MASQUERADE. When a packet leaves the host's physical network adapter, the kernel rewrites its source IP to the host's public IP and tracks the connection in the conntrack table so reply packets find their way back to the container. I also added FORWARD chain rules permitting traffic between the container veth interface and the physical card."
Step 3: Ingress Port Forwarding via Destination NAT across Two Chains:
"Then, to expose an internal container port—say, container port 8080—as host port 3000, I configured Destination NAT (DNAT) across two distinct Netfilter chains:
1. I added a rule in the PREROUTING chain to intercept packets arriving from external machines before routing decisions and translate the destination IP and port to the container.
2. I added a separate rule in the OUTPUT chain to intercept packets generated locally by processes running on the host machine.
Having rules in both chains is crucial because external packets hit PREROUTING but never OUTPUT, while host-local packets hit OUTPUT but never PREROUTING. Defining both ensures the service is reachable both from external clients and from the host itself."
Step 4: Inter-Container Peering (Cross-Subnet Routing):
"For inter-container communication, Container A on 192.168.1.0/24 and Container B on 192.168.2.0/24 cannot communicate by default because the firewall blocks forwarding between their interfaces. To connect them, I added bidirectional rules in the FORWARD filter chain matching traffic between Container A's host veth interface and Container B's host veth interface. Since the host routing table already has direct routes to both subnets, the kernel routes packets seamlessly between the containers without needing any NAT."
Step 5: The Loopback Martian Packet Problem vs. Targeting Host Physical IP:
"Finally, I analyzed a critical edge case regarding host-local access:
curl 127.0.0.1:3000, the OUTPUT DNAT rule rewrites the destination to the container IP, but the packet's source IP remains 127.0.0.1. When the kernel attempts to route this packet out through the non-loopback veth interface, it drops the packet as a 'Martian address' because loopback source IPs are strictly forbidden on physical or virtual network links. curl http://10.129.135.190:3000). Because the request targets the physical IP, the host socket binds a valid physical LAN source IP (10.129.135.190). Our OUTPUT DNAT rule intercepts the packet, translates the destination to the container (192.168.x.2:8080), and routes it onto the veth link without triggering Martian drops. The container receives the packet from 10.129.135.190 and routes replies back via its default gateway cleanly."| Step | What Was Done | How It Works | Why This Mechanism / Order |
|---|---|---|---|
| 1. Kernel Routing | Enabled IP forwarding | Wrote 1 to /proc/sys/net/ipv4/ip_forward. |
Enables host kernel to act as a router between interfaces. |
| 2. Outbound SNAT | Masqueraded egress traffic | Added MASQUERADE in POSTROUTING + FORWARD rules. |
Replaces private container IP with host public IP on internet egress. |
| 3. Inbound DNAT | Mapped host port to container | DNAT rules in both PREROUTING (external) & OUTPUT (local). |
Dual rules required because external & local packets hit different chains. |
| 4. Container Peering | Enabled cross-subnet traffic | Added bidirectional FORWARD rules between veth pairs. |
Permits host kernel to route packets between subnets without NAT. |
| 5. Martian Drops vs Physical IP | Targeted host physical IP | Curled <HOST_PHYSICAL_IP>:3000 (10.129.x.x) instead of 127.0.0.1. |
Avoids 127.0.0.1 Martian drops on veth; assigns valid source IP so replies route back. |
PREROUTING and OUTPUT for port forwarding?Answer: Packets arriving from external network interfaces enter the Netfilter stack at
PREROUTINGand never hitOUTPUT. Conversely, packets generated by a process running on the host itself enter the stack atOUTPUTand never hitPREROUTING. If you only define aPREROUTINGrule, accessing the port works from external machines but fails when run directly on the host machine.
SNAT and MASQUERADE?Answer:
SNATrequires specifying a fixed, static egress IP address (--to-source <ip>), which is computationally lighter for static IPs.MASQUERADEis a specialized form of SNAT designed for dynamic interfaces: it automatically queries the egress interface's current IP address for each new connection.
FORWARD rules without NAT?Answer: Both container subnets are already directly connected to the host routing table via their respective
vethinterfaces. Because the host kernel already knows how to route packets between192.168.1.0/24and192.168.2.0/24, no address translation is needed—we only need iptablesFORWARDrules to permit the firewall to let packets cross between the interfaces.
curl 127.0.0.1:<port> fail with DNAT, and how does curling the host physical IP work in code?Answer:
- Why127.0.0.1fails: When a process on the host curls127.0.0.1:3000, the socket assigns source IP127.0.0.1. TheOUTPUTDNAT rule rewrites the destination to192.168.x.2:8080. When the kernel tries to emit the packet onto the container'svethinterface, the kernel's routing layer drops it as a Martian packet (127.0.0.0/8cannot be transmitted over non-loopback devices). Even if transmitted, the container receiving a packet from127.0.0.1would reply to its own local loopback instead of the host.
- How curling the host physical IP solves it: When the host curls its own physical LAN IP (e.g.10.129.135.190:3000), the socket binds source IP10.129.135.190. OurOUTPUTDNAT rule (-m addrtype --src-type LOCAL --dst-type LOCAL) matches and rewrites the destination to192.168.x.2:8080. Because the source IP is a valid routable LAN address, the kernel transmits it across thevethinterface without Martian drops. The container receives the request from10.129.135.190and routes its reply back via its default gateway (192.168.x.1).