LPIC-2 System Security: Packet Filtering, Routing, and VPNs
- Turn a Linux box into a router by enabling and persisting IP forwarding
- Write a safe nftables ruleset that never locks you out, and read its iptables-legacy equivalent
- Apply NAT correctly: MASQUERADE/SNAT for outbound, DNAT for inbound port forwarding
- Make firewall rules survive a reboot, and frame how OpenVPN and IPsec ride on top of these same controls
Why a Linux box becomes a router
Understand the difference between a host and a router, and why forwarding is off by default.
By default a Linux machine is a host: it accepts packets addressed to itself and drops anything addressed to someone else. A router does the opposite — it accepts a packet on one interface and passes it out another toward its destination.
Think of a host as a private mailbox that only takes mail with your name on it. A router is the post office sorting room: a letter arrives, the clerk reads the destination, and forwards it on. A stock Linux kernel refuses to be that sorting room until you explicitly turn the behaviour on.
That switch is the kernel parameter
net.ipv4.ip_forward. It is0(off) on every fresh install — a security default, because an accidental router can bridge networks you never meant to connect.Enable and persist IP forwarding
Turn forwarding on now AND make it survive a reboot — the distinction the exam tests.
There are two states to set, and they are independent — exactly like
activevsenabledfor a service:- the runtime value in
/proc/sys(lost at reboot) - the persistent value in a config file under
/etc/sysctl.d/(re-applied at every boot)
Start by reading the current value so you have a baseline.
sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 0
0confirms forwarding is off: this box is a host, not a router.sysctlreads the live kernel value — the same number you'd get fromcat /proc/sys/net/ipv4/ip_forward.sudo sysctl -w net.ipv4.ip_forward=1net.ipv4.ip_forward = 1
- -w
- Write a value to a kernel parameter for the running system, immediately.
This flips forwarding on right now, but only in memory. Reboot and it reverts to 0. This is the trap: people test routing successfully, reboot, and routing silently dies.To make it permanent, drop a
.conffile into/etc/sysctl.d/. The kernel applies every file in that directory at boot, so your setting is reapplied automatically.echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-router.confnet.ipv4.ip_forward = 1
teewrites the line to the file as root (a plain>redirect would run as your user and be denied). The99-prefix means it loads last, so it wins over any default. Apply all sysctl files without rebooting usingsudo sysctl --system.Check yourself
You ran
sysctl -w net.ipv4.ip_forward=1and routing worked. After a reboot it stopped. What single thing did you forget?- the runtime value in
nftables vs iptables: the same job, two tools
Know which firewall framework you are using and read a basic ruleset in both.
A Linux firewall is a set of rules the kernel checks each packet against, deciding accept or drop. Two front-ends exist for the same kernel machinery:
- nftables — the modern tool. One command,
nft, one config file,/etc/nftables.conf. Rules live in named tables and chains with hooks (input,output,forward). - iptables — the legacy tool. Built-in chains named in capitals: INPUT (packets for this host), OUTPUT (packets from this host), FORWARD (packets passing through, i.e. routed). Saved with
iptables-save.
Think of a chain as a bouncer's checklist: rules are read top to bottom, the first match decides, and if nothing matches the chain's default policy applies.
sudo nft list rulesettable inet filter { chain input { type filter hook input priority 0; policy accept; iif "lo" accept ct state established,related accept tcp dport 22 accept } chain forward { type filter hook forward priority 0; policy accept; } }inetmeans the table covers both IPv4 and IPv6. Theinputchain hooks packets destined for this host. Read the order: loopback is allowed, thenestablished,related, then SSH on 22.policy acceptat the top is the safety net while you build — you tighten it todroponly after the allow rules are in place.Example — not runnable on your lab box
sudo iptables -L -n -vChain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 42 3120 ACCEPT all -- lo * 0.0.0.0/0 0.0.0.0/0 118 9400 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 6 360 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)- -L
- List the rules in all chains (or one named chain).
- -n
- Numeric: show IPs and ports as numbers, no DNS or /etc/services lookups (faster, unambiguous).
- -v
- Verbose: add the packet/byte counters and the in/out interface columns.
This is the exact same firewall as the nft output above, in legacy syntax. Same three allows in the same order, same chains. The pkts/bytescounters tell you which rules are actually matching traffic — invaluable when debugging "why is this blocked?"Check yourself
A packet is being routed through your Linux router, not delivered to the router itself. Which iptables chain evaluates it?
- nftables — the modern tool. One command,
The lockout trap: allow established + loopback before you drop
Build a default-drop firewall in the one order that doesn't sever your own session.
A secure firewall uses a default-drop policy: deny everything, then allow only what you intend. But order matters, because rules apply the instant you commit them.
Two allows must come before you set the policy to drop:
1. loopback (
iif lo) — countless local services talk to themselves over127.0.0.1; drop it and they break in confusing ways.2. established,related — your SSH session is an already-established connection. The kernel's connection tracking (
ct state/conntrack) remembers it. Allowingestablished,relatedlets reply packets for connections you already opened flow back.Example — not runnable on your lab box
sudo nft add rule inet filter input ct state established,related acceptSilent success. ct state established,relatedmatches packets belonging to a connection conntrack already knows — including the replies of your SSH session. This rule (plus the loopback and SSH allows) is what makes the next command survivable.Example — not runnable on your lab box
sudo nft chain inet filter input '{ policy drop ; }'Only NOW is it safe to flip the default to drop. Everything not explicitly accepted above is denied, but your established session and loopback keep working. Do this in the wrong order and the screen goes dead. The same trap in legacy syntax is iptables -P INPUT DROPrun too early.Check yourself
On a remote server over SSH, you run
iptables -P INPUT DROPfirst, then start adding ACCEPT rules. What happens to your SSH session?NAT: MASQUERADE/SNAT outbound, DNAT inbound
Rewrite addresses so many hosts share one IP, and expose an internal service to the outside.
NAT (Network Address Translation) rewrites the addresses in packets as they pass through your router. Two directions, two jobs:
- Outbound — MASQUERADE / SNAT. Many internal machines on private IPs share one public address. The router rewrites their source address to its own on the way out, and untangles the replies on the way back. Think of an office switchboard: dozens of internal extensions, one public phone number to the world. Use MASQUERADE when the public IP is dynamic (DHCP/dial-up); use SNAT when it is static (slightly faster, you name the fixed IP).
- Inbound — DNAT. The reverse: a packet hits the router's public IP on a port, and DNAT rewrites the destination to an internal host — port forwarding. Like the receptionist routing an incoming call to extension 204.
Example — not runnable on your lab box
sudo nft add rule inet nat postrouting oifname "eth0" masquerade- postrouting
- The hook for rewriting packets on their way OUT, after the routing decision — where source NAT belongs.
- oifname "eth0"
- Match only traffic leaving via eth0 (the public/upstream interface).
- masquerade
- Rewrite the source address to eth0's current IP, whatever it is.
Every internal client now reaches the internet behind the router's single public address. The legacy equivalent is iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE. Remember: NAT still needs `ip_forward=1` and a FORWARD policy that permits the traffic, or packets are translated but never routed.Example — not runnable on your lab box
sudo nft add rule inet nat prerouting iif "eth0" tcp dport 443 dnat ip to 192.0.2.10:443- prerouting
- The hook for rewriting packets as they arrive, BEFORE the routing decision — where destination NAT belongs.
- tcp dport 443
- Match inbound HTTPS arriving on the public interface.
- dnat ip to 192.0.2.10:443
- Rewrite the destination to the internal web server's address and port.
Outsiders connect to the router's public IP on 443 and are transparently forwarded to internal host 192.0.2.10(a reserved documentation address). That's inbound port forwarding. Legacy:iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 192.0.2.10:443.Check yourself
You want outsiders reaching your router's public IP on port 8080 to land on an internal host. Source NAT or destination NAT?
VPNs ride on routing and the firewall
Place OpenVPN and IPsec on top of forwarding, NAT, and packet filtering.
A VPN builds an encrypted tunnel across the untrusted internet so two networks (or a roaming user and a network) behave as if directly wired together. It is not separate from what you just learned — it depends on it: the tunnel carries traffic the box must forward, so VPNs need
ip_forward=1and FORWARD rules just like any router. Two families dominate LPIC-2:- OpenVPN — a userspace daemon built on TLS/PKI. Each peer holds a certificate signed by your CA; the server runs on a single UDP port (1194 by convention). Because it's one well-defined port, the firewall rule is simple. Think of it as HTTPS for your whole network: the same certificate trust model, applied to a tunnel.
- IPsec (strongSwan) — a kernel-level standard, the usual choice for site-to-site. It works in two phases: IKE (the UDP 500 / 4500 negotiation that authenticates the peers and agrees on keys), then ESP (the protocol that actually encrypts and carries the data). Think IKE as the handshake at the border, ESP as the armoured truck that then runs back and forth.
Example — not runnable on your lab box
sudo nft add rule inet filter input udp dport 1194 accept- udp dport 1194
- OpenVPN's default listening port — UDP unless you've configured TCP.
- accept
- Let inbound tunnel-setup packets reach the OpenVPN daemon.
Without this, your default-drop policy silently blocks every client before the TLS handshake even starts — the connection just times out. For IPsec you'd instead open UDP 500 and 4500 and allow the ESP protocol ( meta l4proto esp accept, oriptables -A INPUT -p esp -j ACCEPT).sudo nft add rule inet filter forward iif "tun0" accept- iif "tun0"
- tun0 is the virtual interface OpenVPN creates for tunnelled traffic.
- accept
- Permit traffic arriving from the tunnel to be forwarded toward the LAN.
Opening the port lets a client connect; this FORWARD rule (plus ip_forward=1) is what lets its traffic actually reach the network behind the VPN. Beginners open 1194, connect successfully, then can't reach anything — because they allowedinputbut neverforward. The tunnel is up; the routing isn't.Check yourself
OpenVPN authenticates peers primarily with what, in contrast to a shared password?
Try it in your live terminal
Inspect the real router state on the sandbox to your right — read-only, nothing destructive.
You have a real root shell. These commands only read state — none of them set a drop policy or apply a rule, so you can run them freely without risking a lockout. They're the exact first moves when you inherit an unfamiliar Linux router and need to understand it.
sysctl net.ipv4.ip_forwardIs this box routing? 1means forwarding is on (it's acting as a router);0means it's a plain host. Your first question on any gateway.sudo nft list ruleset 2>/dev/null | head || sudo iptables -L -n | headShow the firewall. Try the modern nftfirst; if nftables isn't in use it falls back to listing the legacyiptablesrules. Either way you see the chains, the allows, and the default policy.ip routeThe routing table — where packets for each destination go. Look for the default via ...line (the upstream gateway) and which interface each network is reached through. This is how you confirm the box knows the paths its firewall is filtering.
- Routing is off by default. Enable it with
net.ipv4.ip_forward=1AND persist it in/etc/sysctl.d/— runtime-only changes die at reboot. - nftables (
nft,/etc/nftables.conf) is modern; iptables (INPUT/OUTPUT/FORWARD,iptables-save) is legacy. Allow loopback andestablished,relatedBEFORE setting a drop policy, or you cut your own SSH session. - NAT: MASQUERADE/SNAT shares one IP outbound; DNAT forwards a port inbound. Rules are runtime-only until saved to
/etc/nftables.conforiptables-save. - VPNs sit on top of all of it: OpenVPN is PKI/certificate-based on one UDP port; IPsec/strongSwan does IKE then ESP for site-to-site. Open the VPN port AND allow forwarding AND persist
ip_forward.
| Goal | What to do | Watch out for |
|---|---|---|
| Route between interfaces | ip_forward=1 + /etc/sysctl.d/*.conf | sysctl -w alone is lost at reboot |
| Default-deny firewall | Allow lo + established,related, THEN policy drop | Drop set first locks out your SSH session |
| Many hosts, one public IP (outbound) | masquerade / SNAT in postrouting | Needs ip_forward + a FORWARD allow too |
| Expose internal service (inbound) | dnat in prerouting | It's destination NAT, not source NAT |
| Keep firewall after reboot | /etc/nftables.conf or iptables-save | nft add / -A are memory-only |
| VPN gateway (OpenVPN) | Open UDP 1194 (input) + allow tun0 (forward) | Port open but no forward = tunnel up, no traffic |
| VPN gateway (IPsec) | Open UDP 500/4500 + allow ESP | IKE negotiates, ESP carries — both must pass |
