Linux networking
Core14
Why did the ip command replace ifconfig and route, and what are the ip equivalents of the old commands?
Say this first
ifconfig,route,arpandnetstatcome from net-tools, which talks to the kernel through old ioctl calls and/procfiles and has barely changed in years. iproute2 (ip,ss,bridge,tc) uses netlink, the kernel's native networking interface, so it sees everything the kernel can do: several addresses per interface, multiple routing tables and policy rules, namespaces, VRFs and tunnels. Many minimal distros no longer install net-tools at all.Go deeper
The mapping I use
Old (net-tools) New (iproute2) ifconfigip addrandip linkifconfig eth0 upip link set eth0 uproute -nip routearp -nip neighnetstat -tulpnss -tulpnnetstat -iip -s linkbrctl showbridge linkorip link show master br0vconfig add eth0 10ip link add link eth0 name eth0.10 type vlan id 10What ifconfig gets wrong
- It shows only the primary IPv4 address of an interface. A second address added with
ip addr addis invisible toifconfigunless it was given a label likeeth0:1. People have chased "missing" addresses for hours because of this. route -nshows only the main table. If policy routing is in use, it hides the tables that matter.- It knows nothing about namespaces, VRFs or most modern tunnel types.
Habits worth showing
Shell ip -br addr # one line per interface: name, state, addresses ip -br link # one line per interface: name, state, MAC, flags ip -c route # colour output ip -s link show ens192 # packet, error and drop countersThey will ask next
Do changes made with ip survive a reboot?
No.
ipchanges the running kernel state only. To persist, put the config where the distro's network manager reads it: netplan YAML on Ubuntu, NetworkManager connections (nmcli) on RHEL family,.networkfiles for systemd-networkd, or/etc/network/interfaceson classic Debian.Is ifconfig removed from Linux?
No, it is a userspace package (net-tools) that is deprecated and often not installed by default. You can install it, but scripts and troubleshooting should use iproute2.
Traps
- Saying ifconfig shows all addresses. It hides secondary addresses that have no label.
- Forgetting that ip changes are runtime only and vanish on reboot or on a network service restart.
How did you do?Own page- It shows only the primary IPv4 address of an interface. A second address added with
Walk me through the output of ip addr show. What do UP, LOWER_UP and state mean?
Say this first
The flags in angle brackets are what the interface is configured and able to do:
UPmeans administratively up,LOWER_UPmeans the driver sees carrier (layer 1 is up).stateis the operational state the kernel computed:UP,DOWN, orUNKNOWNfor things like loopback that do not report carrier. Then come the MTU, the queueing discipline, the MAC, and each address with its prefix, scope and lifetime.Go deeper
A real example
Output $ ip addr show dev ens192 2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:50:56:a1:2b:3c brd ff:ff:ff:ff:ff:ff inet 10.0.10.21/24 brd 10.0.10.255 scope global dynamic ens192 valid_lft 85912sec preferred_lft 85912sec inet6 fe80::250:56ff:fea1:2b3c/64 scope link valid_lft forever preferred_lft forever2:is the interface index. Other commands refer to it (for exampleveth0@if7).UPadmin up,LOWER_UPcarrier present. A cable pull showsNO-CARRIERandstate DOWNwhileUPstays, because the admin state did not change.mtu 1500,qdisc mq(multiqueue NIC),qlen 1000is the transmit queue length.inet 10.0.10.21/24 brd 10.0.10.255 scope global dynamic: address with prefix, broadcast, scopeglobal(routable), anddynamicmeans it came from DHCP;valid_lftcounts down the lease.inet6 fe80::.../64 scope linkis the IPv6 link-local address, built here from the MAC with EUI-64.
Scopes
globalis usable anywhere,linkonly on this link (fe80::, 169.254/16),hostonly inside this machine (127.0.0.1 onlo).The short form
Output $ ip -br addr lo UNKNOWN 127.0.0.1/8 ::1/128 ens192 UP 10.0.10.21/24 fe80::250:56ff:fea1:2b3c/64They will ask next
The interface shows UP in the flags but state DOWN. What is wrong?
It is admin up but has no carrier. Check the cable, the switch port (shut, err-disabled, wrong speed), the SFP, or the VM's virtual NIC connection.
ethtool ens192showsLink detected: no.Why does lo show state UNKNOWN?
Loopback has no carrier concept, so the kernel cannot compute an operational state and reports UNKNOWN. That is normal, as it is for some tun and dummy devices.
How do you add a second address, and how does it show up?
ip addr add 10.0.10.22/24 dev ens192. Because it is in the same subnet as the first one, it is listed withscope global secondary. If the primary is deleted, secondaries in that subnet are deleted too unlessnet.ipv4.conf.all.promote_secondaries=1is set.
Traps
- Reading LOWER_UP as "the interface is enabled". It only means carrier is present.
- Ignoring
dynamicandvalid_lft. A DHCP address with a short lifetime left can explain intermittent loss.
How did you do?Own pageHow do you read the Linux routing table, and how does Linux choose between routes?
Say this first
ip routeshows the main table. Linux picks the longest prefix match first. Between routes with the same prefix it picks the lowest metric. There is no administrative distance as on Cisco: whoever installs the route sets the metric. Each line shows the prefix, the next hop (via), the interface (dev), who installed it (proto), the scope and the preferred source address (src).Go deeper
A real table
Output $ ip route default via 10.0.10.1 dev ens192 proto static 10.0.10.0/24 dev ens192 proto kernel scope link src 10.0.10.21 172.16.40.0/22 via 10.0.10.254 dev ens192 proto static 192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1 linkdowndefault via 10.0.10.1: the default route, written asdefaultinstead of 0.0.0.0/0.proto kernel scope link: the connected route the kernel added when the address was configured.scope linkmeans the destination is directly on the wire, no gateway.proto static: installed by the network manager from config.proto dhcpcomes from a lease. Routes added by hand withip route addgetproto boot, whichip routedoes not print.src 10.0.10.21: the source address the kernel prefers for traffic using this route.linkdown: the interface has no carrier, here libvirt's bridge with no VMs running.
Changing it
Shell ip route add 172.16.40.0/22 via 10.0.10.254 dev ens192 ip route replace 172.16.40.0/22 via 10.0.10.253 # add or overwrite in one step ip route del 172.16.40.0/22 ip route add default via 10.0.10.1 metric 100Metrics in practice
NetworkManager gives Ethernet default routes metric 100 and Wi-Fi 600, which is why a laptop prefers the cable when both are connected. The lower metric wins.
Other tables
ip routeonly shows tablemain. Thelocaltable holds the host's own addresses and broadcast addresses.ip route show table allshows everything.They will ask next
Where are the routes for the host's own addresses?
In the
localtable (ID 255), consulted first by rule priority 0.ip route show table locallists entries likelocal 10.0.10.21 dev ens192 proto kernel scope host src 10.0.10.21.What happens if you add a second route to the same prefix with the same metric?
ip route addfails withRTNETLINK answers: File exists. You either give it a different metric (a floating backup), useip route append, or build one ECMP route with severalnexthop viaentries.How do you add an ECMP default route?
ip route add default nexthop via 10.0.10.1 dev ens192 weight 1 nexthop via 10.0.20.1 dev ens224 weight 1. IPv4 multipath hashes on source and destination address by default;net.ipv4.fib_multipath_hash_policy=1adds ports (layer 4) so different flows between the same two hosts can spread.
Traps
- Talking about administrative distance in the Linux kernel. Longest match, then metric. Routing daemons such as FRR apply their own distances before they install a route.
- Reading only
ip routewhen policy routing rules send traffic to another table.
How did you do?Book 3.3 Internet (IP)Own pageHow do you see which ports are listening on a Linux server and which process owns them?
Say this first
ss -tulpn: TCP and UDP, listening sockets, with the process, numeric (no name lookups). Run it as root, or-pcannot show processes owned by other users.ssreplacednetstat -tulpnbecause it reads sockets through netlink instead of parsing/proc/net/tcp, so it is much faster on busy hosts. The local address column tells you whether the service listens on all addresses or only on loopback.Go deeper
A real example
Output $ sudo ss -tlnp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=14)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=901,fd=3)) LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("java",pid=1532,fd=41)) LISTEN 0 511 *:443 *:* users:(("apache2",pid=1204,fd=6)) LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=901,fd=4))127.0.0.53%lo:53is the systemd-resolved stub, bound to loopback only.0.0.0.0:22accepts on every IPv4 address.[::]:22is the IPv6 socket.127.0.0.1:8080is reachable only from the host itself. If clients cannot connect, this is why.*:443is one socket that accepts IPv4 and IPv6.- For a
LISTENsocket,Recv-Qis the number of connections waiting for the application to accept them andSend-Qis the backlog size.
The letters
-tTCP,-uUDP,-llistening only,-aall states,-pprocess,-nnumeric,-4or-6to pick a family,-xfor Unix sockets,-sfor a summary.They will ask next
A service is listening but remote clients get connection refused. What do you check?
The bind address first:
127.0.0.1or one specific IP means other addresses refuse. Then the host firewall (a REJECT rule also gives "refused") and whether the client really reaches this host.The Recv-Q of a listening socket keeps growing. What does that mean?
The accept queue is filling: the application is not calling accept fast enough. When it overflows, new SYNs are dropped and
nstat -az TcpExtListenOverflowsclimbs. That is an application or capacity problem, not a network one.
Traps
- Running
ss -pwithout root and concluding no process owns the port. - Treating
0.0.0.0as a destination. In a listening socket it means "every local IPv4 address".
How did you do?Book 1.4 SoftwareOwn pageWalk me through dig output. Where do you look to know whether a lookup really worked?
Say this first
I read the header first:
status(NOERROR, NXDOMAIN, SERVFAIL or REFUSED), the flags, and the answer count. NOERROR with ANSWER 0 means the name exists but has no record of that type. Then the answer section for the records and their TTLs, and the footer for which server answered and how long it took.aain the flags means an authoritative server answered; without it the answer came from a resolver's cache.Go deeper
Example through the systemd-resolved stub
Output $ dig www.example.com A ; <<>> DiG 9.18.28 <<>> www.example.com A ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41872 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 65494 ;; QUESTION SECTION: ;www.example.com. IN A ;; ANSWER SECTION: www.example.com. 300 IN CNAME web.example.net. web.example.net. 60 IN A 203.0.113.80 ;; Query time: 12 msec ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) ;; WHEN: Tue Nov 10 09:14:02 UTC 2026 ;; MSG SIZE rcvd: 89status: NOERRORandANSWER: 2: success. The name is a CNAME toweb.example.net, which has the A record.- Flags:
qrthis is a response,rdwe asked for recursion,rathe server offers it. Noaa, so cached. - TTL 300 and 60 seconds: how long a resolver may cache each record. Repeat the query and they count down.
SERVER: 127.0.0.53: the local systemd-resolved stub answered, not the real DNS server.
Status codes
Status Meaning NOERROR, ANSWER > 0 Records found NOERROR, ANSWER 0 Name exists, no record of that type (NODATA) NXDOMAIN The name does not exist SERVFAIL The resolver failed, often DNSSEC validation or unreachable authoritative servers REFUSED The server will not answer you, typically recursion not allowed for your source Options I use
dig @10.0.20.53 name(ask a specific server),+short,+trace(walk from the root),-x 203.0.113.80(PTR),dig example.com MX,+norecurse(see what a server has itself).They will ask next
How do you get the authoritative answer instead of a cached one?
Find the zone's servers with
dig example.com NS +shortand query one directly with@, or usedig +trace, which follows the delegation from the root itself. The answer should carry theaaflag.When would you use nslookup instead?
For a quick check, and on Windows where dig is not installed. Its output is less detailed: no flags, no TTLs by default. Like dig, it queries DNS directly and skips
/etc/hosts.
Traps
- Treating NOERROR as success without checking the answer count.
- Forgetting that dig talks to the server in resolv.conf, which on many hosts is the local stub.
How did you do?Book 9.3 Infrastructure ApplicationsOwn pageHow do you use curl to debug a web or API problem from a Linux box?
Say this first
curl -vshows each stage: DNS result, TCP connect, TLS handshake, the request headers sent and the response headers received, so I can see where it breaks.-wwith timing variables splits the delay into DNS, connect, TLS and server time.--resolvesends the request to a specific backend IP while keeping the real hostname for SNI and the Host header. The exit code names the failing stage: 6 DNS, 7 connect, 28 timeout, 35 TLS handshake, 60 certificate.Go deeper
Verbose run (trimmed)
Output $ curl -sv -o /dev/null https://app.example.com/health * Trying 203.0.113.80:443... * Connected to app.example.com (203.0.113.80) port 443 * ALPN: server accepted h2 * Server certificate: * subject: CN=app.example.com > GET /health HTTP/2 > Host: app.example.com > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/2 200 < content-type: application/jsonLines with
*are curl's own notes,>is what it sent,<is what came back.Where the time goes
Output $ curl -s -o /dev/null -w 'dns %{time_namelookup} connect %{time_connect} tls %{time_appconnect} ttfb %{time_starttransfer} total %{time_total}\n' https://app.example.com/health dns 0.004213 connect 0.031870 tls 0.089402 ttfb 0.912337 total 0.915006The values are cumulative from the start. Here DNS, TCP and TLS are fast and the server takes about 0.8 s to produce the first byte: an application problem, not a network one.
Other options I reach for
--resolve app.example.com:443:10.0.20.15: test one backend behind a load balancer with correct SNI.-Iheaders only,-Lfollow redirects,-kskip certificate checks (testing only).--connect-timeout 3and-m 10so a script does not hang.-4or-6to force a family,-x http://proxy:3128to test through a proxy.curl -v telnet://10.0.20.15:5432as a quick TCP port test when telnet and nc are not installed.
They will ask next
Why use --resolve instead of putting the IP in the URL with a Host header?
With HTTPS the client sends the hostname in SNI and checks the certificate against the URL host. An IP in the URL breaks both, so you test a different thing than real users.
--resolvechanges only where the TCP connection goes.curl to an HTTPS site hangs right after the TCP connect. What do you suspect?
The handshake starts but large packets (the server certificate) do not arrive: a path MTU black hole, or a middlebox dropping TLS. Compare with a lower MTU or
ss -tnito see retransmissions.
Traps
- Leaving
-kin production scripts and hiding real certificate problems. - Reading
time_totalalone. The breakdown is what tells you who to call.
How did you do?Book 9.1 Traditional ApplicationsOwn pageHow do you turn a Linux box into a router, and why is enabling ip_forward often not enough?
Say this first
By default Linux behaves as a host and drops packets that are not addressed to it.
net.ipv4.ip_forward=1lets it route IPv4 between interfaces; IPv6 has its own switch,net.ipv6.conf.all.forwarding=1.sysctl -wchanges the running value; a file in/etc/sysctl.d/makes it survive a reboot. After that the packets still have to pass the firewall FORWARD chain and rp_filter, and the far side needs a route back.Go deeper
Check and set
Output $ sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 0 $ sudo sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1Shell echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/90-router.conf sudo sysctl --system # reload every sysctl file cat /proc/sys/net/ipv4/ip_forwardThe checklist after forwarding is on
- Firewall FORWARD chain. Docker sets the filter FORWARD policy to DROP when it starts; hardened
images often do the same.
iptables -S FORWARDornft list ruleset. - rp_filter. With strict mode, packets that arrive on an interface the router would not use to reach their source are dropped silently.
- Return route. The next router or the server must know the way back to the source subnet, or you need NAT.
- NAT if the inside uses private addresses and the outside does not route them.
Who turns it on for you
Docker, libvirt and Kubernetes components set
ip_forward=1when they start, which is why a host can suddenly start routing after someone installs a container runtime.They will ask next
Why can enabling IPv6 forwarding break the router's own IPv6 default route?
With forwarding on, the kernel acts as a router and ignores Router Advertisements unless the interface has
accept_ra=2. A box that learned its default route from RAs loses it. Setnet.ipv6.conf.ens192.accept_ra=2or configure the route statically.A PC behind the box can ping both of the router's interfaces but nothing beyond. Does that prove forwarding works?
No. Both interface addresses belong to the router itself, so those pings are delivered locally and never forwarded. Linux answers for any of its addresses on any interface. Test with a host beyond the router.
Traps
- Using
sysctl -wand forgetting to persist it. The router stops routing after the next reboot. - Assuming forwarding is the whole job. The FORWARD chain policy is the next most common reason.
How did you do?Book 3.3 Internet (IP)Own page- Firewall FORWARD chain. Docker sets the filter FORWARD policy to DROP when it starts; hardened
images often do the same.
What are the iptables tables and chains, and what is each one for?
Say this first
A table groups rules by job, a chain is the point in the packet's path where they run.
filter(the default) accepts or drops in INPUT, FORWARD and OUTPUT.natrewrites addresses in PREROUTING (DNAT), POSTROUTING (SNAT, MASQUERADE), OUTPUT and INPUT, and only sees the first packet of each connection.manglechanges packets or sets marks in all five chains.rawruns before connection tracking, mostly to exempt traffic from it.securityis for SELinux labels.Go deeper
The map
Table Job Built-in chains filter Allow or block INPUT, FORWARD, OUTPUT nat Address and port translation PREROUTING, INPUT, OUTPUT, POSTROUTING mangle Marks, TTL, DSCP, TCPMSS PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING raw Before conntrack, CT --notrackPREROUTING, OUTPUT security SELinux security marks INPUT, FORWARD, OUTPUT The five hooks in one line each
- PREROUTING: every packet arriving, before the routing decision.
- INPUT: packets for this host.
- FORWARD: packets routed through this host.
- OUTPUT: packets created by this host.
- POSTROUTING: every packet leaving, after routing.
Working with it
Shell iptables -S # filter table as commands iptables -t nat -S # nat table iptables -N SSH-IN # user-defined chain iptables -A INPUT -p tcp --dport 22 -j SSH-INWithout
-t, iptables works onfilter. Terminating targets are ACCEPT, DROP and REJECT; LOG and MARK continue to the next rule; RETURN goes back to the calling chain.They will ask next
Where does DNAT go, and where does SNAT go?
DNAT in nat PREROUTING (and nat OUTPUT for traffic the host itself creates), so the routing decision uses the new destination. SNAT and MASQUERADE in nat POSTROUTING, after routing has chosen the output interface.
Can you filter in the nat table?
You should not. nat chains see only the first packet of a connection, and DROP is not allowed there on modern versions. Filter in the filter table.
Traps
- Saying "the iptables INPUT chain" as if there were only one. Several tables have an INPUT chain.
- Forgetting that
iptableswith no-tis the filter table, soiptables -Lhides all NAT rules.
How did you do?Book 8.5 Example SystemsOwn pageHow does iptables evaluate rules, and how do you add, remove and save them safely?
Say this first
Rules in a chain are checked top to bottom and the first rule with a terminating target (ACCEPT, DROP, REJECT) wins. If nothing matches, the chain's policy applies; only built-in chains have a policy, ACCEPT or DROP.
-Aappends to the bottom,-Iinserts at the top or at a position,-Ddeletes by spec or line number. Rules live in kernel memory, so they neediptables-saveand a restore at boot to persist.Go deeper
Reading a chain
Output $ sudo iptables -S INPUT -P INPUT DROP -A INPUT -i lo -j ACCEPT -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -A INPUT -p tcp -m tcp --dport 443 -j ACCEPTiptables -L INPUT -n -v --line-numbersshows the same rules with packet counters and positions.Changing it
Shell iptables -I INPUT 3 -p tcp -s 10.0.50.0/24 --dport 9100 -j ACCEPT # insert as rule 3 iptables -D INPUT 3 # delete rule 3 iptables -Z INPUT # zero the countersCounters are the best debugging tool: zero them, reproduce, and see which rule's packet count moved.
Persisting
- Debian and Ubuntu:
iptables-persistentrestores/etc/iptables/rules.v4at boot;netfilter-persistent savewrites it. - RHEL family with
iptables-services:/etc/sysconfig/iptables. - By hand:
iptables-save > fileandiptables-restore < file. Restore replaces every table in the file.
They will ask next
DROP or REJECT?
REJECT answers: an ICMP port unreachable by default, or a TCP reset with
--reject-with tcp-reset. The client fails fast. DROP is silent and the client waits for its timeout. I use REJECT inside the network so applications fail quickly, and DROP at the internet edge.How do you change rules on a remote box without locking yourself out?
Keep the ESTABLISHED rule at the top so the current SSH session survives, and use a rollback:
iptables-applyreverts unless you confirm, or schedule a restore of the saved rules a few minutes ahead and cancel it once you have checked access.
Traps
- Setting the INPUT policy to DROP before adding the SSH allow rule over SSH.
- Appending an allow rule with
-Abelow an existing catch-all drop. It never matches.
How did you do?Own page- Debian and Ubuntu:
What is a Linux bridge, and how do you build one?
Say this first
A Linux bridge is a layer 2 switch inside the kernel. It learns source MACs per port, forwards known destinations, and floods broadcasts and unknown unicast. Its ports can be physical NICs, VLAN interfaces, veth ends or VM tap devices. The bridge device itself (
br0) is also the host's own port on that switch, so the host's IP address goes onbr0, never on the enslaved NIC. STP is off by default. KVM, Docker and LXC all build on bridges.Go deeper
Build one
Shell ip link add br0 type bridge ip link set ens224 master br0 ip link set ens224 up ip addr add 10.30.0.5/24 dev br0 ip link set br0 upLook at it
Output $ ip -br link show master br0 ens224 UP 00:50:56:a1:6c:10 <BROADCAST,MULTICAST,UP,LOWER_UP> vnet0 UNKNOWN fe:54:00:6b:1e:01 <BROADCAST,MULTICAST,UP,LOWER_UP> $ bridge fdb show br br0 | grep -v permanent 52:54:00:6b:1e:01 dev vnet0 master br0 00:1c:7f:3a:11:02 dev ens224 master br0The fdb is the MAC address table: the VM's MAC learned on its tap port, the upstream router learned on the NIC.
Defaults worth knowing
- MAC ageing 300 seconds.
- STP disabled (
stp_state 0), so ports forward immediately and a loop is a storm. bridge-utilsandbrctlare the old tools;ip linkandbridgereplace them.
They will ask next
You enslaved the NIC that held the host's IP and the host went offline. Why?
Once a NIC is a bridge port, the bridge takes every frame it receives and delivers it up through
br0. The address left on the NIC no longer receives anything. Move the address (and default route) tobr0, ideally in the same netplan or nmcli change so you do not cut your own session.Can a Linux bridge run spanning tree?
The kernel bridge implements classic 802.1D STP:
ip link set br0 type bridge stp_state 1. For RSTP or MSTP you run the userspacemstpddaemon. Most hypervisor bridges leave STP off and avoid loops by design.Do bridged packets pass through iptables?
Only when the
br_netfiltermodule is loaded andnet.bridge.bridge-nf-call-iptables=1. Kubernetes requires it, and it is a common surprise when a FORWARD policy of DROP starts blocking bridged traffic.
Traps
- Leaving the IP on the physical NIC after enslaving it.
- Bridging two uplinks to the same switch with STP off and creating a loop.
How did you do?Book 3.2 Switched EthernetBook 3.5 ImplementationOwn pageHow do you put a Linux server on a VLAN trunk?
Say this first
Create a VLAN subinterface on top of the physical NIC, one per VLAN:
ip link add link ens192 name ens192.10 type vlan id 10, then put the address onens192.10. The 8021q module tags outgoing frames and sorts incoming tagged frames to the right subinterface. The switch port must be a trunk carrying those VLANs. Untagged frames still arrive on the parentens192.Go deeper
Runtime
Shell ip link add link ens192 name ens192.10 type vlan id 10 ip addr add 10.10.0.21/24 dev ens192.10 ip link set ens192 up ip link set ens192.10 upOutput $ ip -d link show ens192.10 5: ens192.10@ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 00:50:56:a1:2b:3c brd ff:ff:ff:ff:ff:ff promiscuity 0 ... vlan protocol 802.1Q id 10 <REORDER_HDR> ...@ens192names the parent. The VLAN interface uses the parent's MAC.Persistent, netplan
YAML network: version: 2 ethernets: ens192: {} vlans: ens192.10: id: 10 link: ens192 addresses: [10.10.0.21/24]Details
- The VLAN interface MTU defaults to the parent's and cannot exceed it. The NIC carries the 4-byte tag.
- QinQ:
ip link add link ens192 name ens192.100 type vlan proto 802.1ad id 100, then a second 802.1Q VLAN on top of it. - The interface name is free text;
ens192.10is a convention.
They will ask next
Pings in VLAN 10 fail but the parent interface works untagged. Where do you look?
The switch port: is it a trunk, is VLAN 10 allowed, is the native VLAN what you expect. On the host,
tcpdump -i ens192 -nn -e vlan 10shows whether tagged frames leave and come back.How does this differ from a VLAN-aware bridge?
A VLAN interface makes the host itself a member of that VLAN. A VLAN-aware bridge switches frames for other devices (VMs, containers) between ports. Hypervisors use the bridge; plain servers use VLAN interfaces.
Traps
- Configuring VLAN interfaces against an access port. The switch drops or untags the frames.
How did you do?Book 3.2 Switched EthernetOwn pageWhat is a veth pair, and how do you find which container a host-side veth belongs to?
Say this first
A veth pair is two virtual Ethernet interfaces joined like a patch cable: a frame sent into one comes out of the other. You create both ends together, put one in a namespace and leave the other in the root namespace, often attached to a bridge. That is how a container's
eth0connects todocker0or a CNI bridge. To match ends, use the peer interface index shown after@ifinip link, orethtool -S.Go deeper
Create and place
Shell ip netns add red ip link add veth-red type veth peer name veth-red-br ip link set veth-red netns red ip link set veth-red-br master br0 ip link set veth-red-br up ip -n red link set veth-red upMatching the ends
Output $ ip -br link show type veth veth-red-br@if5 UP d2:5f:0a:91:3c:11 <BROADCAST,MULTICAST,UP,LOWER_UP> $ ethtool -S veth-red-br NIC statistics: peer_ifindex: 5Index 5 is the peer's interface index inside the other namespace. For a container, compare it with
nsenter -t <pid> -n ip -br linkorcat /sys/class/net/eth0/iflinkinside the container.Rules
- Interface names are limited to 15 characters.
- Both ends must be up, and if one end is deleted the other disappears with it.
- veth reports a nominal 10 Gbps but real throughput depends on CPU.
They will ask next
veth or tap, what is the difference?
A veth joins two kernel network stacks. A tap device joins the kernel to a userspace program, such as QEMU, which reads and writes frames through a file descriptor. VMs use tap, containers use veth.
Why does a new veth show state LOWERLAYERDOWN?
Its peer is down. A veth only has carrier when both ends are up.
Traps
- Bringing up the host end and forgetting the namespace end (or the reverse).
- Using a name longer than 15 characters; the command fails.
How did you do?Own pageHow do you write tcpdump filters, and how do you read a tcpdump line?
Say this first
tcpdump takes a BPF filter in pcap-filter syntax: primitives like
host,net,port,portrange, withsrcordstqualifiers and protocol names (tcp,udp,icmp,arp), joined withand,or,notand parentheses. I always use-nnso it does not resolve names or ports,-ifor the interface,-cto stop after a count, and-wto save a pcap for Wireshark. Quote the filter.Go deeper
Filters I actually use
Shell tcpdump -i ens192 -nn host 10.0.10.30 tcpdump -i ens192 -nn 'src net 10.20.0.0/24 and dst port 443' tcpdump -i any -nn 'port 53' tcpdump -i ens192 -nn -e arp tcpdump -i ens192 -nn 'icmp or tcp[tcpflags] & (tcp-syn|tcp-rst) != 0' tcpdump -i ens192 -nn 'tcp port 22 and not host 10.0.50.10' tcpdump -i ens192 -nn -c 2000 -w /tmp/web.pcap 'host 198.51.100.80 and port 443' tcpdump -nn -r /tmp/web.pcap 'tcp[tcpflags] & tcp-rst != 0'Reading a handshake
Output 09:21:14.502113 IP 10.0.10.21.51544 > 198.51.100.80.443: Flags [S], seq 3125110123, win 64240, options [mss 1460,sackOK,TS val 1840233 ecr 0,nop,wscale 7], length 0 09:21:14.505391 IP 198.51.100.80.443 > 10.0.10.21.51544: Flags [S.], seq 2287712361, ack 3125110124, win 65160, options [mss 1460,sackOK,TS val 99120 ecr 1840233,nop,wscale 7], length 0 09:21:14.505430 IP 10.0.10.21.51544 > 198.51.100.80.443: Flags [.], ack 1, win 502, length 0- Address and port are joined with a dot:
10.0.10.21.51544. - Flags:
SSYN,S.SYN-ACK,.ACK,P.data with push,F.FIN,Rreset. - After the handshake tcpdump prints relative sequence numbers (
ack 1);-Sshows absolute ones. win 502is scaled: with wscale 7 that is 502 x 128 bytes.- The 3.3 ms between SYN and SYN-ACK is the network round trip to the server.
Options
-elink-layer header (MACs, VLAN tag),-vmore IP detail,-Apayload as ASCII,-Xhex,-Q inor-Q outfor direction,-ssnap length (the modern default captures whole packets).They will ask next
Why do you always use -nn?
Without it tcpdump does reverse DNS for every address, which is slow, can hide output while it waits, and adds its own DNS packets to the capture.
How do you capture only one VLAN on a trunk?
tcpdump -i ens192 -nn -e 'vlan 10 and host 10.10.0.21'. Putvlanfirst: it shifts the header offsets for every primitive after it, sohost x and vlan 10does not mean the same thing.What does -i any change?
It captures on every interface using Linux cooked capture, so you lose the real Ethernet header and the interface is not put in promiscuous mode. Good for "which interface does this leave on", bad for MAC-level work.
Traps
- Forgetting quotes, so the shell eats the parentheses or
&. - Writing
port 53and thinking it only matches UDP. It matches TCP too; addudpif you mean it.
How did you do?Book 5.2 Reliable Byte Stream (TCP)Own page- Address and port are joined with a dot:
What are netplan, systemd-networkd and NetworkManager, and how do you tell which one manages a host?
Say this first
systemd-networkd and NetworkManager are daemons that configure interfaces, addresses, routes and DNS. Netplan, on Ubuntu, is only a YAML front end: it renders its files into config for networkd (the default on Ubuntu Server) or NetworkManager (Ubuntu Desktop) and does nothing at runtime. RHEL and Fedora use NetworkManager with
nmcli. Anything done withipis temporary; the daemon's config is what survives a reboot.networkctl listandnmcli device statusshow who manages each interface.Go deeper
Netplan (Ubuntu)
YAML # /etc/netplan/01-netcfg.yaml network: version: 2 renderer: networkd ethernets: ens192: addresses: [10.0.10.21/24] routes: - to: default via: 10.0.10.1 nameservers: addresses: [10.0.20.53, 10.0.20.54] search: [corp.example.com]netplan tryapplies it and rolls back after 120 seconds unless you confirm, which is the safe way over SSH.netplan applyapplies without a safety net.gateway4is deprecated; use a routeto: default.systemd-networkd
Output $ networkctl list IDX LINK TYPE OPERATIONAL SETUP 1 lo loopback carrier unmanaged 2 ens192 ether routable configured 2 links listed.Native files are
/etc/systemd/network/*.networkwith[Match],[Network]and[Link]sections;networkctl status ens192shows addresses, gateway, DNS and recent log lines.NetworkManager
Shell nmcli device status nmcli con mod ens192 ipv4.method manual ipv4.addresses 10.0.10.21/24 ipv4.gateway 10.0.10.1 ipv4.dns "10.0.20.53 10.0.20.54" nmcli con up ens192Connections are stored as keyfiles in
/etc/NetworkManager/system-connections/. RHEL 9 deprecated the oldifcfgfiles. The connection name and the device name can differ:nmcli con showlists both.They will ask next
What happens if two of these manage the same interface?
They fight: addresses disappear, routes flip, DHCP restarts. Make sure exactly one owns each interface, for example with NetworkManager's
unmanaged-devicesor by choosing one netplan renderer.And classic Debian?
ifupdownwith/etc/network/interfacesandifuporifdown. Still common on Debian servers and Proxmox hosts.
Traps
- Editing the files netplan generates under
/run. They are rewritten on the nextnetplan applyor boot. - Running
netplan applyon a remote box with a typo instead ofnetplan try.
How did you do?Own page
Working16
How do you find out exactly which route, interface and source address Linux will use for a destination?
Say this first
ip route get <destination>asks the kernel to do the real lookup, through the policy rules and every table, and prints the chosen next hop, output interface and source address. I addfrom,iif,oiformarkto simulate other traffic, for example a packet arriving on a given interface. It beats reading the table by eye whenever policy routing, ECMP or a cached path MTU is involved.Go deeper
Examples
Output $ ip route get 172.16.41.9 172.16.41.9 via 10.0.10.254 dev ens192 src 10.0.10.21 uid 1000 cacheThe kernel matched
172.16.40.0/22 via 10.0.10.254and will source from10.0.10.21.Shell ip route get 198.51.100.80 from 203.0.113.10 # what does traffic sourced from this IP do ip route get 198.51.100.80 mark 0x2 # what does marked traffic do ip route get 10.0.99.21 from 10.0.50.10 iif ens224 # simulate a packet arriving on ens224Source address selection
If the route has a
srchint, Linux uses it. Otherwise it picks the primary address of the output interface whose scope fits the destination. Applications can override it by binding to an address.Two things it reveals that
ip routedoes not- Reverse path filter drops. Simulating an inbound packet with
iifreturnsRTNETLINK answers: Invalid cross-device linkwhen rp_filter would drop it. - Path MTU. If the kernel learned a smaller MTU from an ICMP "fragmentation needed", the output shows
cache expires 412sec mtu 1400.
They will ask next
What does uid in the output mean?
The user ID of the process asking. It matters only if
ip rule ... uidrangerules exist, which can route different users through different tables.How would you check which route the host uses to reach its own address?
ip route get 10.0.10.21returns alocalroute viadev lofrom thelocaltable. Traffic to your own address never leaves the box, whichever interface owns it.
Traps
- Trusting a hand reading of
ip routeon a host withip ruleentries. Let the kernel do the lookup.
How did you do?Own page- Reverse path filter drops. Simulating an inbound packet with
How do you filter ss output, and how do you check the health of a single TCP connection?
Say this first
sshas its own filter language: states (state established,state time-wait), addresses (dst,src) and ports (dport = :443), combined withand,orand parentheses. For one connection,ss -tniprints the kernel's TCP internals: RTT, RTO, congestion window, MSS, retransmissions and the congestion algorithm, which tells you whether a slow transfer is loss, latency or the application.Go deeper
Filters
Shell ss -tn state established '( dport = :443 or sport = :443 )' ss -tn dst 10.0.20.0/24 ss -tan state time-wait | wc -l ss -to state established dport = :5432 # -o adds timers (keepalive, retransmit) ss -s # totals per state and protocolQuote the expression so the shell does not eat the parentheses. Ports are written with a leading colon,
:443, as in the man page examples.TCP internals for one flow (trimmed)
Output $ ss -tni dst 198.51.100.80 State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 10.0.10.21:51544 198.51.100.80:443 cubic wscale:7,7 rto:204 rtt:3.1/1.2 mss:1448 pmtu:1500 cwnd:10 retrans:0/2 send 37.4Mbpsrtt:3.1/1.2: smoothed RTT and its variation in ms.rto:204: retransmission timeout. Linux keeps a 200 ms minimum, so short RTT paths sit just above it.cwnd:10segments,mss:1448(1500 minus IP, TCP and the 12-byte timestamp option).retrans:0/2: retransmissions outstanding now / in total. A growing total means loss on the path.send 37.4Mbpsis cwnd times MSS divided by RTT, the rate the window currently allows.
They will ask next
There are thousands of TIME-WAIT sockets. Is that a problem?
Usually not. TIME-WAIT sits on the side that closed first and lasts 60 seconds on Linux. It hurts only when one client opens so many short connections to the same destination and port that it runs out of ephemeral ports (
net.ipv4.ip_local_port_range, 32768 to 60999 by default). Then use connection pooling, ornet.ipv4.tcp_tw_reusefor outgoing connections.And thousands of CLOSE-WAIT?
That is an application bug. The peer closed, the kernel acknowledged, and the local program never called close on the socket. The network is fine.
Traps
- Forgetting the quotes around a filter with parentheses, so the shell eats them and ss reports a syntax error.
- Blaming the network for CLOSE-WAIT build-up.
How did you do?Book 5.2 Reliable Byte Stream (TCP)Own pageWhat do the states in ip neigh mean, such as REACHABLE, STALE, DELAY and FAILED?
Say this first
ip neighis the ARP table for IPv4 and the NDP cache for IPv6, with a state machine.REACHABLE: the mapping was confirmed recently.STALE: the confirmation is old, but the entry is still used.DELAY: the entry was used while stale and the kernel waits a few seconds for an upper layer, such as a TCP ACK, to confirm it.PROBE: it is sending unicast ARP requests.FAILED: nobody answered.INCOMPLETE: first resolution in progress.PERMANENTis a static entry.Go deeper
Example
Output $ ip neigh show dev ens192 10.0.10.1 lladdr 00:1c:7f:3a:11:02 REACHABLE 10.0.10.30 lladdr 00:50:56:a1:9c:44 STALE 10.0.10.31 lladdr 00:50:56:a1:47:e0 DELAY 10.0.10.44 FAILED 10.0.10.5 lladdr 00:50:56:a1:00:05 PERMANENTThe lifecycle and default timers
- No entry: the kernel broadcasts a request, entry is
INCOMPLETE(3 tries,mcast_solicit, 1 s apart). - Reply received:
REACHABLEfor a random time between 15 and 45 seconds (base_reachable_time_ms30000, randomised by half either way). - Timer expires:
STALE. Still used to send traffic, nothing is sent on the wire yet. - A packet is sent using a stale entry:
DELAYfor 5 seconds (delay_first_probe_time). If TCP sees an ACK from that neighbour in that window, it confirms the entry and it returns toREACHABLE. - No confirmation:
PROBE, 3 unicast requests (ucast_solicit). No answer:FAILED.
So
STALEis normal and healthy.FAILEDandINCOMPLETEare the states that mean trouble.Fixing entries
Shell ip neigh flush dev ens192 # clear dynamic entries on one interface ip neigh del 10.0.10.30 dev ens192 ip neigh replace 10.0.10.5 lladdr 00:50:56:a1:00:05 dev ens192 nud permanent ip -s neigh show 10.0.10.30 # adds used/confirmed/updated timersThey will ask next
You moved an IP to a new server and some clients still reach the old one. What do you do?
Their ARP caches still point at the old MAC. Send gratuitous ARP from the new server:
arping -U -c 3 -I ens192 10.0.10.30(iputils arping). Clustering tools such as keepalived do this on failover. If a firewall or router ignores gratuitous ARP, clear its entry by hand.The kernel logs "neighbour table overflow". What happened?
The cache hit
gc_thresh3(1024 entries by default, withgc_thresh1128 andgc_thresh2512). It happens on hosts that sit on very large subnets or on Kubernetes nodes. Raise the three thresholds with sysctl undernet.ipv4.neigh.default..What does the router flag mean in an IPv6 entry?
The neighbour advertised itself as a router (the R flag in its NA). It appears for the default gateway's link-local address in
ip -6 neigh.
Traps
- Calling STALE an error. It is the normal resting state of an entry nobody has confirmed lately.
- Using
arp -nand missing IPv6 entries.ip neighshows both families.
How did you do?Book 3.3 Internet (IP)Own page- No entry: the kernel broadcasts a request, entry is
What is policy routing on Linux, and how do ip rule and multiple routing tables work together?
Say this first
Linux can hold up to 2^32 routing tables, and
ip ruledecides which table a packet is looked up in. Rules are checked in priority order and match on source, destination, incoming or outgoing interface, firewall mark, user ID or TOS. The default rules are priority 0lookup local, 32766lookup mainand 32767lookup default. If the chosen table has no matching route, the kernel moves on to the next rule.Go deeper
Default state
Output $ ip rule 0: from all lookup local 32766: from all lookup main 32767: from all lookup defaultTable IDs:
local255,main254,default253. Names live in/etc/iproute2/rt_tables(or a file inrt_tables.d/), but you can always use the number.Adding a table and a rule
Shell ip route add default via 203.0.113.9 dev ens224 table 200 ip rule add from 203.0.113.10 lookup 200 priority 1000 ip rule add fwmark 0x2 lookup 200 priority 1100 ip rule add iif ens256 lookup 200 priority 1200Output $ ip rule 0: from all lookup local 1000: from 203.0.113.10 lookup 200 1100: from all fwmark 0x2 lookup 200 1200: from all iif ens256 lookup 200 32766: from all lookup main 32767: from all lookup defaultSelectors and actions
- Selectors:
from,to,iif,oif,fwmark,tos,uidrange, andnotto invert. - Actions:
lookup <table>, orblackhole,unreachable,prohibitto drop. suppress_prefixlength 0: use the table but ignore a default route found in it.wg-quickuses this withnot fwmark 51820 lookup 51820so WireGuard can take the default route without looping its own encrypted packets into the tunnel.
Where it is used
Two uplinks with symmetric return traffic, sending one subnet or one application out a different ISP, VPN split tunnelling, and Kubernetes and cloud CNIs that give each secondary interface its own table.
They will ask next
Do you need to flush a route cache after changing rules?
Not on modern kernels. The IPv4 routing cache was removed in kernel 3.6, so rules and routes apply to the next packet. Existing NAT and conntrack entries still keep their old translations, though.
What happens if the table a rule points at has no route for the destination?
The lookup in that table fails and the kernel continues with the next rule. That is why a table with only a default route still lets local-subnet traffic fall back to
mainif you order things carefully, and why a missing route sometimes "works" through the wrong table.
Traps
- Thinking
ip routeshows everything. It only shows table main. - Putting a rule at a priority above 32766 and wondering why main always wins.
How did you do?Book 3.3 Internet (IP)Own page- Selectors:
How does name resolution work on a modern Linux host, and what is 127.0.0.53?
Say this first
An application calls getaddrinfo in glibc, which follows the
hosts:line in/etc/nsswitch.conf: usuallyfiles(/etc/hosts) first, then DNS. DNS goes to the servers in/etc/resolv.conf. On hosts with systemd-resolved, resolv.conf points at127.0.0.53, a local caching stub, and the real upstream servers are kept per interface;resolvectl statusshows them. dig and nslookup read resolv.conf directly and skip/etc/hosts;getent hostsfollows the same path as applications.Go deeper
The pieces
Output $ ls -l /etc/resolv.conf lrwxrwxrwx 1 root root 39 Mar 3 10:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf $ grep -v '^#' /etc/resolv.conf nameserver 127.0.0.53 options edns0 trust-ad search corp.example.comOutput $ resolvectl status ens192 Link 2 (ens192) Current Scopes: DNS Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 10.0.20.53 DNS Servers: 10.0.20.53 10.0.20.54 DNS Domain: corp.example.comThe upstream servers come from netplan, NetworkManager or DHCP.
/run/systemd/resolve/resolv.conflists them in classic format for tools that need it.resolv.conf rules worth knowing (glibc)
- At most 3
nameserverlines are used, tried in order. options timeout:5 attempts:2are the defaults: a dead first server costs 5 seconds per try.searchdomains are appended to names with fewer dots thanndots(default 1).
Commands
Shell getent hosts app.corp.example.com # same path as applications, includes /etc/hosts resolvectl query app.corp.example.com resolvectl flush-caches resolvectl statistics # cache hits and missesThey will ask next
ping resolves a name but dig says NXDOMAIN. How?
The name is in
/etc/hosts(or resolved by mDNS or another NSS module). ping uses NSS, dig queries DNS directly.getent hosts nameshows what applications really get.How does a VPN get its own DNS for corporate names only?
systemd-resolved supports per-link routing domains. The VPN link gets DNS servers and a domain like
~corp.example.com; queries for that domain go to the VPN's servers and everything else goes to the default link.resolvectl domain tun0 '~corp.example.com'sets it by hand.
Traps
- Editing
/etc/resolv.confby hand on a host where systemd-resolved or NetworkManager manages it. It gets overwritten or bypasses the stub. - Calling
nameserver 127.0.0.53a misconfiguration. It is the stub, and the real servers are in resolvectl.
How did you do?Book 9.3 Infrastructure ApplicationsOwn page- At most 3
What is rp_filter, what do the values 0, 1 and 2 mean, and when does it break things?
Say this first
rp_filter is reverse path filtering, the anti-spoofing check from RFC 3704. For each incoming packet the kernel looks up the route back to the source.
1strict: the best route back must point out the same interface the packet arrived on, otherwise drop.2loose: the source only needs to be reachable through some interface.0off. The effective value per interface is the higher ofconf.allandconf.<iface>. Strict mode silently drops asymmetric traffic on multihomed hosts.Go deeper
Check it
Output $ sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.ens224.rp_filter net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.ens224.rp_filter = 1The kernel default is 0, but distributions ship their own value (1 or 2 are both common). Check, do not assume.
conf.defaultonly applies to interfaces created after it is set.Seeing the drops
Shell sysctl -w net.ipv4.conf.all.log_martians=1 dmesg | grep martian nstat -az TcpExtIPReversePathFilter ip route get 10.0.99.21 from 10.0.50.10 iif ens224Output IPv4: martian source 10.0.99.21 from 10.0.50.10, on dev ens224 RTNETLINK answers: Invalid cross-device linkIn the martian log the first address is the destination and the second the source. "Invalid cross-device link" from
ip route getis the same check failing.Typical breakage
- A server with a management NIC: monitoring traffic arrives on the management interface, but the route back to the monitoring subnet points out the data interface.
- Two uplinks with traffic coming in on the one that is not the default.
- Routers in an ECMP or asymmetric design.
Fixes, best first
- Make the routing symmetric (policy routing by source address).
- Loose mode (2) on that interface, which still drops sources with no route at all.
- 0 only when you understand why, for example inside some overlay or VPN designs.
They will ask next
You set rp_filter to 0 on ens224 and packets are still dropped. Why?
net.ipv4.conf.all.rp_filteris still 1, and the kernel uses the maximum ofalland the interface value. To relax one interface, set it to 2 (max of 1 and 2 is 2), or loweralland set the other interfaces explicitly.Is there an rp_filter for IPv6?
Not as a sysctl. IPv6 reverse path checks are done in the firewall: the
rpfiltermatch in ip6tables (-t raw -A PREROUTING -m rpfilter --invert -j DROP) orfib saddr . iif oif missing dropin nftables. firewalld adds one by default.
Traps
- Setting the interface value to 0 while
allis 1 and believing it is off. - Disabling rp_filter everywhere instead of fixing the asymmetric routing.
How did you do?Own pageTrace a packet through netfilter for traffic to the host, through the host and from the host.
Say this first
Every packet that arrives hits PREROUTING (raw, then conntrack, mangle, nat DNAT), then the routing decision. If it is for this host it goes to INPUT; if it is to be routed it goes to FORWARD and then POSTROUTING (where SNAT happens). Locally created packets are routed first, then go through OUTPUT and POSTROUTING. DNAT happens before routing so the new destination decides the path; SNAT happens last so filter rules still see the original source.
Go deeper
The three paths
Output to the host: in -> PREROUTING -> routing -> INPUT -> local process through the host: in -> PREROUTING -> routing -> FORWARD -> POSTROUTING -> out from the host: local process -> routing -> OUTPUT -> POSTROUTING -> outTable order inside each hook
Hook Order PREROUTING raw, conntrack, mangle, nat (DNAT) INPUT mangle, filter, security, nat (SNAT for local delivery, rare) FORWARD mangle, filter, security OUTPUT raw, conntrack, mangle, nat (DNAT), filter, security POSTROUTING mangle, nat (SNAT, MASQUERADE) These come from the hook priorities: raw -300, conntrack -200, mangle -150, DNAT -100, filter 0, security 50, SNAT 100. nftables uses the same numbers, which is why its chains declare
priority filterorpriority srcnat.Consequences interviewers ask about
- A port-forward needs a FORWARD rule that matches the inside address and port, because DNAT already happened in PREROUTING.
- A MASQUERADE rule does not change what FORWARD sees: the source is still the private address there.
- Traffic to the router's own address hits INPUT, never FORWARD, even if it arrived on the "outside".
- The nat table is consulted only for the first packet; later packets reuse the conntrack entry.
- After OUTPUT, if mangle changed the mark or nat changed the destination, the kernel re-routes the packet.
They will ask next
Do bridged frames go through iptables?
Only if the
br_netfiltermodule is loaded andnet.bridge.bridge-nf-call-iptables=1. Then bridged IPv4 frames pass the FORWARD chain too. Docker and Kubernetes load it, which is why bridged traffic can suddenly be dropped by a FORWARD policy of DROP.Which chains does the reply from the host's own web server traverse?
OUTPUT then POSTROUTING. It is a locally generated packet. The incoming request went PREROUTING then INPUT.
Traps
- Writing the FORWARD rule with the public IP for a port-forward. After DNAT, FORWARD sees the private one.
- Expecting INPUT rules to protect containers or VMs behind the host. Their traffic goes through FORWARD.
How did you do?Book 8.5 Example SystemsOwn pageWhat is the difference between MASQUERADE and SNAT, and when do you use each?
Say this first
Both rewrite the source address in nat POSTROUTING. SNAT uses the fixed address you give it with
--to-source. MASQUERADE looks up the outgoing interface's address for each new connection, so it suits DHCP or PPPoE links whose address changes, and it drops the interface's connections when the interface goes down. With a static public address, SNAT is the cleaner choice; it does the same work without the lookup.Go deeper
The two rules
Shell iptables -t nat -A POSTROUTING -s 10.20.0.0/24 -o ens192 -j MASQUERADE iptables -t nat -A POSTROUTING -s 10.20.0.0/24 -o ens192 -j SNAT --to-source 198.51.100.10The nftables equivalents in an
ipfamily nat table:Output oifname "ens192" ip saddr 10.20.0.0/24 masquerade oifname "ens192" ip saddr 10.20.0.0/24 snat to 198.51.100.10How the translation works
- Only the first packet of a connection goes through the nat table. The mapping is stored in conntrack and applied to every later packet in both directions.
- Linux keeps the original source port if it is free on the public address, and picks another if not.
--random-fullyrandomises port choice, which helps when many clients behind one IP hit the same server. -s 10.20.0.0/24 -o ens192keeps the rule from translating traffic that should stay internal, like traffic between two inside interfaces or to a VPN.
Seeing it
Output $ sudo conntrack -L -p tcp --dport 443 tcp 6 431998 ESTABLISHED src=10.20.0.15 dst=203.0.113.80 sport=40112 dport=443 src=203.0.113.80 dst=198.51.100.10 sport=443 dport=40112 [ASSURED] mark=0 use=1The reply tuple is addressed to 198.51.100.10: the NAT is visible there.
They will ask next
You change the NAT rule but some flows still use the old address. Why?
Existing conntrack entries keep their translation until they expire. Long-lived UDP flows (SIP, IPsec, DNS forwarders) are the classic case. Delete them with
conntrack -Dmatching the flow, or flush withconntrack -Fin a maintenance window.Do you need MASQUERADE for a container host?
Yes, unless the network routes the container subnet back to the host. Docker installs
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADEfor its default bridge.
Traps
- Writing MASQUERADE without
-oor-sand translating traffic that should stay internal. - Thinking the nat table is checked for every packet. Only the first one; conntrack does the rest.
How did you do?Own pageHow do you forward a port on a Linux router to an internal server, and what usually goes wrong?
Say this first
Three pieces: a DNAT rule in nat PREROUTING that rewrites the destination, a FORWARD rule that allows the new inside address and port, and a server whose return path goes back through the router. What goes wrong: the FORWARD rule matching the public address, the server's default gateway pointing elsewhere, and inside users who try the public address and hit the hairpin problem.
Go deeper
Setup
R1: outside
ens192198.51.100.10, insideens22410.20.0.1/24. Forward TCP 8443 to 10.20.0.50:443.Shell iptables -t nat -A PREROUTING -i ens192 -p tcp --dport 8443 -j DNAT --to-destination 10.20.0.50:443 iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -i ens192 -o ens224 -p tcp -d 10.20.0.50 --dport 443 -m conntrack --ctstate NEW -j ACCEPTnftables:
Output table ip nat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; iifname "ens192" tcp dport 8443 dnat to 10.20.0.50:443 } }Hairpin NAT
An inside client 10.20.0.15 opens
198.51.100.10:8443. The rule above has-i ens192, so nothing happens. Even with the DNAT applied on the inside interface, the server replies straight to 10.20.0.15 on the same subnet, from 10.20.0.50, and the client drops it because it expected 198.51.100.10. Fixes:- Match
-d 198.51.100.10instead of-i ens192, and addiptables -t nat -A POSTROUTING -s 10.20.0.0/24 -d 10.20.0.50 -p tcp --dport 443 -j MASQUERADEso the server replies to the router. - Or better, split DNS: inside clients resolve the name to 10.20.0.50 directly.
Traffic from the router itself
Locally generated packets skip PREROUTING. To redirect them, add the DNAT in nat OUTPUT as well.
They will ask next
The DNAT counter increases but the connection still times out. What next?
Capture on the inside interface. If SYNs leave toward 10.20.0.50 and no SYN-ACK returns, the server's firewall or default gateway is the problem. If nothing leaves, the FORWARD chain or rp_filter is dropping.
How do you see the translation for a live connection?
conntrack -L -p tcp --dport 8443. The original tuple shows the public destination and port, the reply tuple shows 10.20.0.50 port 443 as the source.
Traps
- Allowing
-d 198.51.100.10 --dport 8443in FORWARD. After DNAT, FORWARD sees 10.20.0.50 and port 443. - Forgetting that the server must route its replies through the box doing the DNAT.
How did you do?Own page- Match
What is nftables, and how is it different from iptables?
Say this first
nftables is the replacement for iptables, ip6tables, arptables and ebtables, in the kernel since 3.13 and the default on current Debian, Ubuntu and RHEL. It has one tool,
nft, one ruleset for all families, and no built-in tables or chains: you create them and attach chains to hooks with a priority. It adds sets and maps, so one rule can match hundreds of addresses, and it loads a whole ruleset atomically from a file. Today'siptablescommand is usuallyiptables-nft, which writes nftables rules under the hood.Go deeper
A small ruleset
Output table inet filter { set admin_nets { type ipv4_addr flags interval elements = { 10.0.50.0/24, 10.0.51.0/24 } } chain input { type filter hook input priority filter; policy drop; ct state established,related accept iif "lo" accept ip saddr @admin_nets tcp dport 22 accept tcp dport { 80, 443 } accept } }Differences that matter
iptables nftables Tools iptables, ip6tables, ebtables, arptables nft Tables and chains Predefined You define them, any name IPv4 and IPv6 Separate rulesets One inettable can do bothMany addresses One rule each, or ipset Native sets and maps Counters Always on Only where you write counterUpdates Rule by rule Atomic, whole file with nft -fWhich backend am I on
Output $ iptables -V iptables v1.8.10 (nf_tables)(nf_tables)means iptables-nft: rules you add with iptables appear innft list ruleset.(legacy)means the old x_tables backend.iptables-translateconverts a rule to nft syntax.They will ask next
What happens if iptables-legacy rules and nftables rules both exist?
Both run. Each base chain on a hook is evaluated in priority order and a packet must survive all of them. An accept in one chain only ends that chain; a drop anywhere is final. That is how a forgotten legacy rule keeps dropping traffic that the nft ruleset allows.
Why does nft let you choose the chain priority?
Several chains can attach to the same hook, and priority orders them, using the same values as the old tables:
raw-300,mangle-150,dstnat-100,filter0,srcnat100.
Traps
- Running
nft flush ruleseton a box where Docker or firewalld also own rules. It wipes theirs too. - Forgetting
counterand then reading empty counters as "no traffic".
How did you do?Book 8.5 Example SystemsOwn pageWrite a minimal stateful host firewall that allows SSH from a management subnet and blocks everything else inbound.
Say this first
Default drop on input, then in order: allow loopback, allow established and related traffic, drop invalid, allow ICMP echo, allow TCP 22 only from the management subnet. Outbound stays open. The established rule near the top lets replies to the host's own connections back in and makes the rule set cheap, because most packets match it first. ICMP errors for existing flows arrive as RELATED, so PMTUD keeps working.
Go deeper
iptables
Shell iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT iptables -A INPUT -p tcp -s 10.0.50.0/24 --dport 22 -j ACCEPT iptables -P INPUT DROPThe policy is set last so an SSH session building the rules is never cut off halfway.
nftables, same logic, IPv4 and IPv6 together
Output #!/usr/sbin/nft -f flush ruleset table inet host { chain input { type filter hook input priority filter; policy drop; iif "lo" accept ct state established,related accept ct state invalid drop meta l4proto { icmp, ipv6-icmp } accept ip saddr 10.0.50.0/24 tcp dport 22 accept } }Saved as
/etc/nftables.conf(Debian, Ubuntu) or/etc/sysconfig/nftables.conf(RHEL), loaded at boot bynftables.service.Why ICMPv6 must be open
IPv6 neighbour discovery and router advertisements are ICMPv6. Drop them and IPv6 stops working entirely, not just ping.
They will ask next
Why put the established rule before the SSH rule?
Order is performance and safety: almost every packet belongs to an existing flow, so it matches in one step. Only new connections go further down the list.
The host uses Docker. Does this ruleset protect the containers?
No. Published container ports are DNATed and go through FORWARD, not INPUT. Rules for them go in the
DOCKER-USERchain. Also,flush rulesetin the nft file would wipe Docker's rules on reload.
Traps
- Dropping all ICMP. IPv4 PMTUD and the whole of IPv6 depend on it.
- Forgetting loopback. Local services talking to each other on 127.0.0.1 break in odd ways.
How did you do?Book 8.5 Example SystemsOwn pageWhat is conntrack, and what do the states NEW, ESTABLISHED, RELATED and INVALID mean?
Say this first
conntrack is the kernel's connection tracking table. It records every flow as a pair of tuples, original and reply, and it is what makes stateful filtering and NAT possible. NEW is the first packet of a flow. ESTABLISHED means traffic has been seen in both directions. RELATED is a new flow tied to an existing one, such as an ICMP error or FTP data with a helper. INVALID is a packet that fits no known flow, like a TCP segment far outside the window. UNTRACKED is traffic exempted in the raw table.
Go deeper
Reading the table
Output $ sudo conntrack -L -p tcp --dport 443 tcp 6 431997 ESTABLISHED src=10.20.0.15 dst=198.51.100.80 sport=40112 dport=443 src=198.51.100.80 dst=203.0.113.10 sport=443 dport=40112 [ASSURED] mark=0 use=1 tcp 6 117 SYN_SENT src=10.20.0.16 dst=198.51.100.81 sport=52210 dport=443 [UNREPLIED] src=198.51.100.81 dst=203.0.113.10 sport=443 dport=52210 mark=0 use=1 conntrack v1.4.7 (conntrack-tools): 2 flow entries have been shown.6is the protocol number, then the seconds left before the entry expires.ESTABLISHED,SYN_SENTare conntrack's internal TCP states, more detailed than the firewall view.- The first tuple is the original direction, the second the expected reply. A different reply destination (203.0.113.10) means the flow is source NATed.
[UNREPLIED]: nothing came back yet.[ASSURED]: seen both ways, will not be evicted early.
Default timeouts worth knowing
TCP established 432000 seconds (5 days), TCP SYN_SENT 120 seconds, a UDP flow that has seen one packet 30 seconds; UDP flows with traffic both ways get a longer "stream" timeout. They are sysctls named
net.netfilter.nf_conntrack_<protocol>_timeout....Firewall view
Shell iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT nft add rule inet filter forward ct state established,related acceptUDP and ICMP have no connections, but conntrack still gives them states: a DNS reply is ESTABLISHED because a matching query went out.
They will ask next
Can a TCP packet without SYN be NEW?
Yes. By default (
nf_conntrack_tcp_loose=1) conntrack picks up flows mid-stream, for example after a firewall restart. If you want only SYNs to open flows, match--synortcp flags synwith ct state new.Why does active FTP or SIP not work through a Linux NAT by default?
Their control channel carries addresses and ports for a second flow. A conntrack helper reads that and creates the RELATED expectation. Since kernel 4.7 helpers are no longer assigned automatically; you attach them explicitly with the
CT --helpertarget or an nftct helperobject.
Traps
- Confusing conntrack's TCP state (SYN_SENT, TIME_WAIT) with the firewall state (NEW, ESTABLISHED).
- Forgetting that conntrack keeps old NAT mappings after rule changes.
How did you do?Own pageHow do you make a Linux bridge VLAN aware, with trunk and access ports?
Say this first
Create the bridge with
vlan_filtering 1, then set VLAN membership per port withbridge vlan. A port with several tagged VLANs is a trunk. A port with one VLAN markedpvid untaggedis an access port: untagged frames coming in join that VLAN, and frames going out lose their tag. Every port starts in VLAN 1 as PVID untagged, so remove it where you do not want it. One VLAN-aware bridge replaces the old pattern of one bridge per VLAN.Go deeper
Config
Shell ip link add br0 type bridge vlan_filtering 1 ip link set ens224 master br0 # trunk to the switch ip link set vnet0 master br0 # VM in VLAN 10 ip link set vnet1 master br0 # VM in VLAN 20 bridge vlan add dev ens224 vid 10 bridge vlan add dev ens224 vid 20 bridge vlan del dev vnet0 vid 1 bridge vlan add dev vnet0 vid 10 pvid untagged bridge vlan del dev vnet1 vid 1 bridge vlan add dev vnet1 vid 20 pvid untaggedResult
Output $ bridge vlan show port vlan-id ens224 1 PVID Egress Untagged 10 20 br0 1 PVID Egress Untagged vnet0 10 PVID Egress Untagged vnet1 20 PVID Egress Untaggedens224is a trunk: VLAN 1 untagged (the native VLAN, which must match the switch) plus 10 and 20 tagged.vnet0andvnet1are access ports in 10 and 20.br0is the host's own port. To give the host an address in VLAN 10:bridge vlan add dev br0 vid 10 selfandip link add link br0 name br0.10 type vlan id 10.
The Cisco mapping
PVID is the access VLAN or native VLAN. "Egress Untagged" is the VLAN that leaves without a tag. A VLAN listed without flags is tagged, as on an 802.1Q trunk.
They will ask next
How do you stop new ports from joining VLAN 1 automatically?
ip link set br0 type bridge default_pvid 0. New ports then join no VLAN until you add one.How would you do this without VLAN filtering?
One bridge per VLAN:
ens224.10inbr10with the VLAN 10 VMs,ens224.20inbr20. It works but means many bridges and VLAN interfaces, so the VLAN-aware bridge is the better design today.
Traps
- Forgetting VLAN 1 stays on every port as PVID untagged unless you remove it.
- Adding VLANs to the ports but not to
br0 self, then wondering why the host cannot reach a VLAN.
How did you do?Book 3.2 Switched EthernetOwn pageWhat are the Linux bonding modes, and which ones need configuration on the switch?
Say this first
Seven modes, 0 to 6. Mode 1, active-backup, needs nothing on the switch and works across two switches. Mode 4, 802.3ad, is LACP and needs an LACP port-channel on the switch (or MLAG across two). Modes 0 (balance-rr), 2 (balance-xor) and 3 (broadcast) need a static EtherChannel. Modes 5 (balance-tlb) and 6 (balance-alb) balance without switch support by playing with MACs. In a data centre I use mode 4 with
miimon 100, and mode 1 when the switches cannot bundle.Go deeper
The table
Mode Name Switch side How it sends 0 balance-rr Static channel Packet by packet round robin, reorders TCP 1 active-backup Nothing One active link, others standby 2 balance-xor Static channel Hash per flow ( xmit_hash_policy)3 broadcast Static channel Every packet on every link 4 802.3ad LACP Hash per flow, LACP negotiates members 5 balance-tlb Nothing Outbound spread by load, inbound on one link 6 balance-alb Nothing tlb plus inbound IPv4 spread by answering ARP with different slave MACs Parameters that matter
miimon 100: check link state every 100 ms. Without a monitor the bond never notices a failure.lacp_rate fast: ask the partner to send LACPDUs every second instead of every 30.xmit_hash_policy:layer2(default),layer2+3,layer3+4.primary ens1f0in active-backup to prefer one link.
Checking it
cat /proc/net/bonding/bond0shows the mode, the active slave or the LACP aggregator, and each slave's link state and failure count.They will ask next
Two top-of-rack switches without MLAG. Which mode?
Active-backup (mode 1). LACP needs both links to end on one logical switch; with two independent switches the second link ends up in a different aggregator and carries nothing. With vPC or MLAG, mode 4 works.
Why does active-backup send gratuitous ARP on failover?
The bond's MAC now appears on a different switch port. The gratuitous ARP (and the frame it causes) makes the switches update their MAC tables at once instead of waiting for the old entry to age out.
Traps
- Using mode 0 or 2 against switch ports with no channel configured. The switch sees the MAC flap between ports.
- Leaving
miimonunset and discovering the bond never failed over.
How did you do?Own pageWhat is a network namespace, and what does it isolate?
Say this first
A network namespace is a separate copy of the kernel network stack: its own interfaces, addresses, routing tables and rules, neighbour table, netfilter rules, conntrack table, sockets and
net.*sysctls. A new namespace contains only a loopback interface, and it is down. Every container runs in its own network namespace;ip netnscreates and enters named ones for labs and tooling.Go deeper
Working with them
Shell ip netns add red ip netns list ip netns exec red ip -br addr # run any command inside ip -n red link set lo up # short form of ip netns exec red ip ... ip link set ens256 netns red # move a physical NIC in ip netns exec red bash # a shell insideOutput $ ip netns exec red ip -br addr lo DOWNBring
loup or anything that talks to 127.0.0.1 inside the namespace fails.Getting traffic in and out
- A veth pair, one end inside and one in the root namespace or on a bridge.
- Moving a physical NIC or SR-IOV virtual function inside.
- macvlan or ipvlan devices on top of a host NIC.
Finding namespaces you did not create
ip netns listonly shows names bind-mounted under/run/netns. Docker and Kubernetes namespaces belong to processes:lsns -t netlists them, andnsenter -t <pid> -n ip addrruns a command inside one.They will ask next
How does a namespace differ from a VRF?
A VRF (an l3mdev device) separates routing tables only: interfaces stay in one stack and sockets can be bound to the VRF. A namespace separates everything, including firewall rules and sockets. VRFs suit a router with a management VRF; namespaces suit containers and full isolation.
Can two namespaces use the same IP addresses?
Yes. Each has its own interfaces and routing tables, so overlapping addresses never meet. That is why every container can have an
eth0with similar addresses, and why namespaces are handy for testing overlapping customer networks on one box.
Traps
- Forgetting
ip link set lo upin a new namespace. - Expecting
ip netns listto show Docker containers.
How did you do?Own pageHow do you check, test and change MTU on Linux, and how do you find a path MTU problem?
Say this first
ip link showshows each interface's MTU;ip link set dev ens192 mtu 9000changes it until reboot, and the network manager's config makes it permanent. To test a path, ping with the DF bit set and a payload of MTU minus 28:ping -M do -s 1472. Linux caches a smaller path MTU learned from ICMP "fragmentation needed" per destination, andip route getshows it. The classic symptom of a broken path MTU is small requests working while large transfers or TLS handshakes hang.Go deeper
Test
Output $ ping -M do -s 1472 -c 1 10.0.10.30 PING 10.0.10.30 (10.0.10.30) 1472(1500) bytes of data. 1480 bytes from 10.0.10.30: icmp_seq=1 ttl=64 time=0.412 ms $ ping -M do -s 8972 -c 1 10.0.10.30 PING 10.0.10.30 (10.0.10.30) 8972(9000) bytes of data. ping: local error: message too long, mtu=1500 $ ping -M do -s 1472 -c 1 198.51.100.80 PING 198.51.100.80 (198.51.100.80) 1472(1500) bytes of data. From 10.0.10.1 icmp_seq=1 Frag needed and DF set (mtu = 1400)- 1472 + 8 (ICMP) + 20 (IP) = 1500. For jumbo, 8972 + 28 = 9000.
- "local error" means our own interface MTU is smaller than the packet.
- "Frag needed" comes from a router on the path, here telling us the next link is 1400.
The cache
Output $ ip route get 198.51.100.80 198.51.100.80 via 10.0.10.1 dev ens192 src 10.0.10.21 uid 1000 cache expires 586sec mtu 1400Learned values expire after 10 minutes (
net.ipv4.route.mtu_expires600).tracepathwalks the path and reports where the MTU drops.Common overheads
VXLAN adds 50 bytes (1450 inside a 1500 underlay), GRE 24 (1476), and WireGuard interfaces default to 1420.
On a Linux router
Clamp TCP MSS for tunnels and PPPoE:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu, or in nftablestcp flags syn tcp option maxseg size set rt mtu.They will ask next
One host on a VLAN is set to 9000 and the rest to 1500. What breaks?
TCP mostly survives because each side advertises an MSS from its own MTU and the sender uses the smaller. Large UDP or ICMP from the jumbo host is dropped by the 1500-byte receivers and switch ports. Everything on one layer 2 segment must agree on the MTU.
How do you make the MTU change permanent?
Netplan
mtu: 9000under the interface, NetworkManagernmcli con mod ens192 802-3-ethernet.mtu 9000, or systemd-networkdMTUBytes=9000in the[Link]section.
Traps
- Testing with
ping -s 1500. The payload size excludes the 28 bytes of headers. - Raising the server MTU without the switch ports, which then drop the jumbo frames as giants.
How did you do?Book 3.3 Internet (IP)Own page
Expert6
A Linux server has two uplinks to two ISPs. Connections that come in on the second one never get a reply. Why, and how do you fix it?
Say this first
The reply is routed by destination only, so it follows the main table's default route out the first ISP, but with the second ISP's address as source. The first ISP drops it as spoofed (BCP 38 filtering), or the client's firewall sees a reply from the wrong path. The fix is source-based policy routing: a second table with the second ISP's default route, and a rule that sends anything sourced from the second address to that table. Replies then leave the way the request came in.
Go deeper
Setup
ens192198.51.100.10/29, gateway 198.51.100.9 (ISP1, main default route)ens224203.0.113.10/29, gateway 203.0.113.9 (ISP2)
The fix
Shell echo '200 isp2' > /etc/iproute2/rt_tables.d/isp2.conf ip route add 203.0.113.8/29 dev ens224 src 203.0.113.10 table isp2 ip route add default via 203.0.113.9 dev ens224 table isp2 ip rule add from 203.0.113.10 lookup isp2 priority 1000Output $ ip route show table isp2 default via 203.0.113.9 dev ens224 203.0.113.8/29 dev ens224 scope link src 203.0.113.10 $ ip route get 192.0.2.50 from 203.0.113.10 192.0.2.50 from 203.0.113.10 via 203.0.113.9 dev ens224 table isp2 uid 0 cacheThe connected route in table isp2 keeps traffic to ISP2's own subnet on the wire instead of via the gateway.
Persisting it
Netplan has
routing-policyand atablekey on routes; NetworkManager hasipv4.routing-rulesandipv4.route-table; systemd-networkd has[RoutingPolicyRule]sections. Plainipcommands vanish on reboot.Why strict rp_filter is now happy too
The reverse path check looks up the route back to the client using the local address as the source, so the
from 203.0.113.10rule matches and the route points outens224, the interface the packet came in on.They will ask next
The box is a router doing port forwarding to an internal server. Does the source rule still work?
No. The internal server's reply arrives on the LAN interface with the server's private source, so
from 203.0.113.10never matches. Mark the connection when it arrives and restore the mark on the reply:iptables -t mangle -A PREROUTING -i ens224 -m conntrack --ctstate NEW -j CONNMARK --set-mark 2,iptables -t mangle -A PREROUTING -i ens256 -j CONNMARK --restore-mark, thenip rule add fwmark 2 lookup isp2. Conntrack reverses the DNAT on the way out, so the reply leavesens224with 203.0.113.10 as its source.How would you send one application's outbound traffic out ISP2?
Run it as its own user and add
ip rule add uidrange 1001-1001 lookup isp2. The lookup happens at connect time, so the source address is chosen fromens224as well. Marking packets in mangle OUTPUT also works but the source was already chosen, so you then need SNAT onens224.
Traps
- Adding a second default route to main with a higher metric and expecting it to handle inbound replies.
- Fixing it by setting rp_filter to 0. That hides the symptom and the ISP still drops the spoofed-looking reply.
How did you do?Own pageA Linux firewall starts dropping new connections and dmesg says the conntrack table is full. How do you handle it?
Say this first
When
nf_conntrack_countreachesnf_conntrack_max, the kernel drops packets that would create new entries and logsnf_conntrack: table full, dropping packet. Existing flows keep working, new ones fail, which looks like random timeouts. Short term I raise the limit and check memory. Then I find out why: a SYN flood or scan, a leak of half-open entries, or long default timeouts on a box with huge flow counts. Stateless high-volume traffic, such as a busy DNS server, can be exempted in the raw table.Go deeper
Confirm
Output $ dmesg | grep conntrack nf_conntrack: nf_conntrack: table full, dropping packet $ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_count = 262144 net.netfilter.nf_conntrack_max = 262144conntrack -Sshows per-CPU counters; a risingdropandearly_dropconfirm it.Short-term relief
Shell sysctl -w net.netfilter.nf_conntrack_max=1048576 echo 262144 > /sys/module/nf_conntrack/parameters/hashsizeKeep the hash table at a sensible fraction of the max (a quarter is a common choice) so lookups stay fast. Each entry costs kernel memory, so check free RAM before multiplying the limit.
Find the cause
Shell conntrack -L -o extended | awk '{print $3}' | sort | uniq -c # entries per protocol conntrack -L -p tcp --state SYN_SENT | wc -l # SYNs never answered conntrack -L -p tcp --state SYN_RECV | wc -l # half-open, answered once conntrack -L -p tcp | awk '{print $5}' | sort | uniq -c | sort -rn | head # top sourcesStructural fixes
- Shorter timeouts where safe, for example
nf_conntrack_tcp_timeout_establishedwell below 5 days. - Exempt stateless traffic you do not need to track:
iptables -t raw -A PREROUTING -p udp --dport 53 -j CT --notrackand the matching OUTPUT rule. These packets are then UNTRACKED and need their own explicit ACCEPT rules in filter. - Rate limiting or SYN cookies upstream for floods.
They will ask next
Why not just disable conntrack on the box?
NAT and every
ct stateor--ctstaterule depend on it. You can only exempt specific traffic, and only traffic that needs neither NAT nor stateful matching.Why can a Kubernetes node hit this when the cluster is not under attack?
kube-proxy in iptables mode NATs every service connection, and nodes running many short-lived connections (DNS lookups to CoreDNS through the service IP are the usual one) can fill the table. Raising the limit, NodeLocal DNSCache, or an eBPF data plane are the usual answers.
Traps
- Raising nf_conntrack_max without checking memory or the hash size.
- Calling it a network outage. Established traffic still flows; only new flows fail.
How did you do?Own page- Shorter timeouts where safe, for example
How does Docker's default networking work on a Linux host, and what does it do to your firewall?
Say this first
Each container gets its own network namespace with an
eth0that is one end of a veth pair. The other end is attached to thedocker0bridge, which holds the gateway 172.17.0.1/16. Docker enablesip_forward, adds a MASQUERADE rule so containers reach the outside, and adds DNAT rules in aDOCKERchain for every published port. Because published ports are DNATed, their traffic goes through FORWARD, not INPUT, so host firewall rules on INPUT (ufw's default) do not protect them. Your own rules belong inDOCKER-USER.Go deeper
What Docker installs (excerpt)
Output $ sudo iptables -t nat -S -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A DOCKER -i docker0 -j RETURN -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80That last rule is
docker run -p 8080:80. In the filter table Docker sets the FORWARD policy to DROP and jumps toDOCKER-USERbefore its own chains. The chain layout has changed between releases, butDOCKER-USERis the documented, stable place for your rules.Restricting a published port
Shell iptables -I DOCKER-USER -i ens192 ! -s 10.0.50.0/24 -p tcp -m conntrack --ctorigdstport 8080 -j DROP--ctorigdstportmatches the port before DNAT, because by the time FORWARD runs the destination port is already 80. Or publish only on loopback with-p 127.0.0.1:8080:80.User-defined networks
docker network createmakes abr-<id>bridge with its own subnet and MASQUERADE rule, and containers on it get Docker's embedded DNS at 127.0.0.11 for name resolution between containers.They will ask next
Containers cannot reach a corporate subnet that starts with 172.17 or 172.18. Why?
The host has connected routes for its Docker bridges, so traffic to an overlapping corporate range never leaves the host. Move Docker's ranges with
bipanddefault-address-poolsin/etc/docker/daemon.json.How do you look at a container's interfaces from the host?
Enter its network namespace through its PID:
nsenter -t $(docker inspect -f '{{.State.Pid}}' web) -n ip addr.ip netns listdoes not show Docker's namespaces because Docker does not create the bind mounts that command reads.You reload your firewall and containers lose internet access. Why?
A tool that restores a full saved ruleset (iptables-restore without
--noflush,nft flush ruleset) removed Docker's chains, including the MASQUERADE rule. Restarting Docker rebuilds them; the long-term fix is to stop the reload from owning tables Docker uses.
Traps
- Trusting ufw or INPUT rules to protect published container ports.
- Running
nft flush rulesetor a fulliptables-restoreon a Docker host.
How did you do?Own pageA server has a 2 x 10G LACP bond but one big transfer never goes above 10 Gbps, and all traffic to the gateway uses one link. Why?
Say this first
LACP balances by flow, not by packet, so one TCP connection always rides one member link and is capped at 10G. On top of that the default
xmit_hash_policyislayer2, which hashes only MAC addresses. Every routed packet has the gateway's MAC as destination, so with layer2 all off-subnet traffic picks the same link.layer3+4hashes IPs and ports and spreads many flows across both links. Inbound balance is decided by the switch's own hash.Go deeper
See the policy and the per-link load
Output $ grep -E 'Mode|Hash' /proc/net/bonding/bond0 Bonding Mode: IEEE 802.3ad Dynamic link aggregation Transmit Hash Policy: layer2 (0) $ ip -s link show ens1f0 | grep -A1 TX $ ip -s link show ens1f1 | grep -A1 TXOne member's TX counters climbing and the other idle is the symptom.
Change it
Shell ip link set bond0 type bond xmit_hash_policy layer3+4Netplan:
transmit-hash-policy: layer3+4under the bond'sparameters. The kernel documentation notes layer3+4 is not strictly 802.3ad compliant because fragments of one packet may take different links, which is harmless in practice.The switch side
Return traffic is balanced by the switch. On a Catalyst,
show etherchannel load-balanceshows the method;port-channel load-balance src-dst-ip(or a mixed IP and port method where supported) avoids the same trap on the way in.What does give more than 10G to one transfer
Several parallel flows (multi-stream copies,
iperf3 -P 4), a faster single link, or mode 0 at the cost of TCP reordering. LACP never splits one flow.They will ask next
How would you prove the bond works with iperf3?
Run one stream and see about 9.4 Gbps, then
iperf3 -c 10.10.0.30 -P 8with layer3+4 hashing and watch both members' TX counters move. Several streams can still collide on one link; hashing is statistical.What does it mean if the two slaves show different Aggregator IDs in /proc/net/bonding/bond0?
They did not join the same LACP aggregator, usually because the switch ports are not in one port-channel or are on two switches without MLAG. Only the active aggregator carries traffic, so you have one link of capacity and no redundancy you can count on.
Traps
- Promising 20G for a single flow on a 2 x 10G bond.
- Leaving layer2 hashing on a server whose traffic all goes through one gateway.
How did you do?Own pageUsing one Linux host, build two isolated hosts that can talk to each other and reach the internet. Walk me through it.
Say this first
Two namespaces as the hosts, a bridge in the root namespace as their switch and gateway, one veth pair per namespace with one end on the bridge. Address the namespaces in one subnet, put the gateway address on the bridge, add a default route in each namespace. For the internet: turn on
ip_forwardand MASQUERADE the subnet out the uplink. That is exactly what Docker does withdocker0, and building it by hand shows you understand every piece.Go deeper
The build
Shell ip netns add red ip netns add blue ip link add br0 type bridge ip addr add 10.50.0.1/24 dev br0 ip link set br0 up for ns in red blue; do ip link add veth-$ns type veth peer name veth-$ns-br ip link set veth-$ns netns $ns ip link set veth-$ns-br master br0 ip link set veth-$ns-br up ip -n $ns link set lo up ip -n $ns link set veth-$ns up done ip -n red addr add 10.50.0.10/24 dev veth-red ip -n blue addr add 10.50.0.20/24 dev veth-blue ip -n red route add default via 10.50.0.1 ip -n blue route add default via 10.50.0.1 sysctl -w net.ipv4.ip_forward=1 iptables -t nat -A POSTROUTING -s 10.50.0.0/24 ! -o br0 -j MASQUERADETest in order
ip netns exec red ping -c 2 10.50.0.20: bridging works (TTL 64, nothing routed).ip netns exec red ping -c 2 10.50.0.1: the gateway answers.ip netns exec red ping -c 2 198.51.100.1: forwarding and NAT work.conntrack -L -p icmpshows the translation to the host's uplink address.
What usually breaks
- A FORWARD policy of DROP, typical on a host with Docker. Allow
-i br0out and established traffic back. br_netfilterloaded withbridge-nf-call-iptables=1: then even red-to-blue bridged traffic must pass FORWARD.- DNS inside the namespace:
ip netns execuses/etc/netns/red/resolv.confif it exists, else the host's, which may point at 127.0.0.53, a stub that does not exist inside the namespace.
They will ask next
How would you add a second subnet so red and blue are routed instead of bridged?
Put them on separate bridges (or straight veth to the root namespace) with different subnets, give the host an address in each, and point each namespace's default route at its own gateway. With
ip_forwardon, the host routes between them, and the TTL drops by one.Why can blue not resolve names when red can ping by IP?
Most likely the host's resolv.conf points at the systemd-resolved stub 127.0.0.53, which only listens in the root namespace. Create
/etc/netns/blue/resolv.confwith a reachable server.
Traps
- Forgetting
loand one veth end, then debugging routing that is fine. - Writing MASQUERADE without
! -o br0and translating the traffic between the namespaces too.
How did you do?Book 3.5 ImplementationOwn pageA capture on a Linux server shows TCP segments of 20 to 60 KB and checksum errors, yet the MTU is 1500. Is something broken?
Say this first
No. tcpdump sees packets where the kernel hands them to the driver, not on the wire. On transmit, TSO and GSO let the kernel pass one large segment that the NIC (or the last software step) cuts into MTU-sized frames, and checksum offload means the NIC fills in the checksum after tcpdump saw it, hence "incorrect". On receive, GRO merges consecutive segments before the capture point. The wire carries normal 1500-byte frames. To see the real frames, capture on a SPAN port or switch the offloads off for the test.
Go deeper
What it looks like
Output 10:02:31.114502 IP 10.0.10.21.443 > 10.0.10.30.51022: Flags [P.], seq 1:28961, ack 518, win 501, length 2896028960 bytes is 20 segments of 1448. With
-v, outgoing packets showcksum 0x2e4f (incorrect -> 0x8a1b).Check the offloads
Output $ ethtool -k ens192 | grep -E 'checksumming|segmentation-offload|receive-offload' rx-checksumming: on tx-checksumming: on tcp-segmentation-offload: on generic-segmentation-offload: on generic-receive-offload: on large-receive-offload: offSee wire-sized packets for a test
Shell ethtool -K ens192 tso off gso off gro off # capture, then put them back ethtool -K ens192 tso on gso on gro onThis costs CPU and changes behaviour slightly, so do it briefly and not on a loaded production box.
They will ask next
Can receive offloads break a Linux router or bridge?
LRO can: it merges packets in a way that cannot be undone exactly, so merged frames could not be forwarded as they arrived. The kernel disables LRO on devices that forward or are bridge ports. GRO is safe because the kernel re-segments with GSO on the way out.
Where do you capture if you need exactly what was on the wire?
Off the host: a SPAN or ERSPAN session or a tap on the switch side. The capture point on the host is above the NIC, so it always reflects offloads.
Traps
- Opening a ticket for "giant packets" or "bad checksums" seen in a host capture.
- Disabling offloads permanently to make captures look tidy.
How did you do?Own page