Network Fundamentals — Hands-on in a Linux VM
Lock Down One Server with nftables
This lab runs on a VM
The namespace srv is the server and cli is the client. You write the firewall rules inside srv.
If you lock down the VM's own input chain, you block the grading agent too, so
do not put any rules in the root namespace.
Goal
With nftables, build "block by default and open only what is needed". Allow reply packets with connection tracking, punch holes by port and by source, count the packets that get dropped, and save the rules to a file.
Why it matters
A firewall is not a list of rules but order and defaults. If the default is drop, whatever you forgot is blocked, and if it is accept, whatever you forgot is open. And if you do not understand connection tracking, you end up writing rules for "outgoing works but replies don't come back" — in fact the most common self-lockout is turning on a firewall while connected over SSH and blocking the replies to your own session. nftables is the successor to iptables, and Kubernetes, Docker, and systemd all create their rules on top of it, so you have to be able to read it to find out "who put in this rule".
Steps
- Create the namespaces
cli·srvand the vethcli0↔srv0, attach10.70.0.2/24·10.70.0.1/24, and make ping work. - In
srv, start apython3 -m http.serverlistening on10.70.0.1:80and annc -lklistening on10.70.0.1:2222. - In
srv, create a tableinet fwand aninputhook chain (policydrop) so that nothing works fromcli. - Add three rules to the
inputchain — allowlo, allowct state established,related, and allowicmp— so that ping works again. - Add a
tcp dport 80allow rule so that the web works fromcliwhile 2222 still does not. - Add a rule that allows
tcp dport 2222only fromip saddr 10.70.0.2. - Put a
counter droprule at the end of the chain, knock on port 3333 fromcli, and then save thenft list chainoutput showing that counter to/root/fw/dropped.txt. - Save the complete ruleset of
srvto/root/fw/ruleset.nft. It must pass the syntax check withnft -c -f.
Notes
- Write all rules with
ip netns exec srv nft …. Check withip netns exec srv nft list ruleset. - Creating the chain:
nft 'add chain inet fw input { type filter hook input priority 0; policy drop; }'. - Rule example:
nft add rule inet fw input iifname "lo" accept. - Starting the servers:
ip netns exec srv setsid python3 -m http.server 80 --bind 10.70.0.1 --directory /root/fw > /root/fw/http.log 2>&1 < /dev/null &. - Common mistake 1: leaving out
established,relatedand then "I opened 80 but the reply doesn't come back". The replies the server sends go through output, so they are not affected, but the replies to traffic the server sends out as a client get caught by it. - Common mistake 2:
iif "lo"versusiifname "lo"— both work, but in a saved ruleset it may show up asiif.
Server and client
Create the namespaces cli·srv and the veth cli0↔srv0, attach 10.70.0.2/24·10.70.0.1/24, and make ping work.
cli0 gets 10.70.0.2 in cli, and srv0 gets 10.70.0.1 in srv. Bring lo UP too. ip netns exec cli ping -c 1 10.70.0.1.
Two services
In srv, start a python3 -m http.server listening on 10.70.0.1:80 and an nc -lk listening on 10.70.0.1:2222.
Start both with ip netns exec srv setsid … > 로그 2>&1 < /dev/null & (replace the log placeholder with a log file). ip netns exec srv ss -ltn must show 80 and 2222 as LISTEN, and ip netns exec cli curl -s http://10.70.0.1/ must return something.
Block by default
In srv, create a table inet fw and an input hook chain (policy drop) so that nothing works from cli.
After nft add table inet fw, run nft 'add chain inet fw input { type filter hook input priority 0; policy drop; }'. Now both ping and curl from cli must time out with no response (not refused).
The basic three lines
Add three rules to the input chain — allow lo, allow ct state established,related, and allow icmp — so that ping works again.
The three are nft add rule inet fw input iifname "lo" accept, … ct state established,related accept, and … ip protocol icmp accept. These three are the first lines of almost every firewall.
Open only 80
Add a tcp dport 80 allow rule so that the web works from cli while 2222 still does not.
nft add rule inet fw input tcp dport 80 accept. To check: curl -s -m 2 http://10.70.0.1/ must work, and nc -z -w 2 10.70.0.1 2222 must fail.
Narrow it by source
Add a rule that allows tcp dport 2222 only from ip saddr 10.70.0.2.
nft add rule inet fw input ip saddr 10.70.0.2 tcp dport 2222 accept. Open the port, but only to a specific source — this is how you narrow management ports. ip netns exec cli nc -z -w 2 10.70.0.1 2222 must work.
Count what gets dropped
Put a counter drop rule at the end of the chain, knock on port 3333 from cli, and then save the nft list chain output showing that counter to /root/fw/dropped.txt.
After nft add rule inet fw input counter drop, run ip netns exec cli nc -z -w 1 10.70.0.1 3333 once or twice. In ip netns exec srv nft list chain inet fw input > /root/fw/dropped.txt, counter packets N must be greater than 0. Also keep in mind that to use log inside a netns you need net.netfilter.nf_log_all_netns=1.
Rules as a file
Save the complete ruleset of srv to /root/fw/ruleset.nft. It must pass the syntax check with nft -c -f.
ip netns exec srv nft list ruleset > /root/fw/ruleset.nft. On a real server, this file would become /etc/nftables.conf and be read with nft -f at boot. If nft -c -f /root/fw/ruleset.nft finishes without saying anything, it passes.