Hands-on with Linux Firewall Rules

03 Jun, 2025 zhangwuji

From K8s troubleshooting to iptables in-depth analysis: hands-on teaching you to play with Linux firewall rules

contexts

While dealing with a Kubernetes cluster failure yesterday, the technical team encountered a typical challenge:
A worker node is experiencing connection failures when accessing cluster services via the NodePort method.
After several hours of in-depth investigation, the root cause of the failure was finally locked for the abnormal configuration of the node firewall rule set.
This troubleshooting process fully exposed the limitations of the traditional iptables command line tool in complex scenarios and spawned the birth of a visual diagnostic tool.

iptables basics

four watches and five chains

Four tables:

  • - filter: Filter packets for firewall rules.
  • - nat: Network Address Translation, used to modify the source or destination IP address of a packet.
  • - mangle: packet content modification, used to modify packet content or priority, etc.
  • - raw: Controls how packets are handled without connection tracking.

Five chains (chains):

  • - PREROUTING: Processing of packets whose destination address is local;
  • - INPUT: Processing of packets that enter the machine and are routed locally;
  • - FORWARD: For all forwarded packets;
  • - OUTPUT: Processing of locally generated outgoing packets;
  • - POSTROUTING: Handling of packets leaving the machine.

The default order of the four tables is: raw -> mangle -> nat -> filter
Abbreviation: rmnf->guidance (five-stroke code)

▌Pre-Routing Phase of Inbound Routing (PREROUTING)

External traffic coming in → ├─ raw table (priority 1): handle connection tracking exceptions ├─ mangle table (priority 2): modify packet headers for TOS/TTL, etc. └─ nat table (priority 3): perform DNAT destination address translation Sequence: 123

▌Routing Decision Phase

mangle table (priority 2): supports complex packet modification └─ filter table (priority 4): defines forwarding policy (default deny) │ └─ filter table (priority 4): defines forwarding policy (default deny) │ └─ order: 24

▌Local Outgoing Phase (OUTPUT)

Local processes generate traffic → ├─ raw table (priority 1): outbound connection tracking exceptions ├─ mangle table (priority 2): modify outbound packet headers ├─ nat table (priority 3): perform SNAT source address translation └─ filter table (priority 4): final outbound filtering Sequence 1234

▌Post Outbound Routing Phase (POSTROUTING)

Prepare to leave the machine → ├─ mangle table (priority 2): last chance to modify (e.g., TTL) └─ nat table (priority 3): complete SNAT/MASQUERADE Order 23

iptables command format

iptables -t table-name [-A|-D|-F|-L|-Z|-N|-X|-P|-E|-I] chain-name [match-criteria] [-j process-action]
  • - table name
    • -t: -table, specifies the table to be manipulated. If not added, it defaults to the filter table.
  • - commands
    • -A: -append, appends a rule to the specified chain.
    • -D: -delete, removes a rule from the specified chain.
    • -F: -flush, clear all rules in the specified chain.
    • -L: -list, list all rules in the specified chain.
    • -Z: -zero, clear the counter of the specified chain.
    • -N: -new-chain, create a new chain.
    • -X: -delete-chain, deletes a customized chain.
    • -P: -policy, sets the default policy for the chain.
    • -E: -rename-chain, rename a chain.
    • -I: -insert, inserts a rule in the specified chain.
  • - match condition
    • -p: -protocol, specifies the protocol type. For example, -p tcp indicates that only packets of the TCP protocol are matched.
    • -s: -source, specifies the source IP address. For example, -s 192.168.1.100 means that only packets from that IP are matched.
    • -d: -destination, specifies the destination IP address. For example, -d 192.168.1.100 means that only packets to that IP are matched.
    • -i: -in-interface, specifies the network interface into the machine. For example, -i eth0 means that only packets passing through that interface are matched.
    • -o: -out-interface, specifies the network interface leaving the machine. For example, -o eth0 indicates that only packets passing through that interface are matched.
    • -sport, -sport: Specifies the source port. For example, -sport 80 means that only TCP packets from that port are matched.
    • -dport, -dport: Specifies the destination port. For example, -dport 80 means that only TCP packets to that port are matched.
  • - Processing actions
    • -j: -jump, specifies the processing action. For example, -j ACCEPT means to accept packets, -j DROP means to drop packets. -j LOG means logging. -j RETURN means return without continuing to match subsequent rules.

iptables common commands

  • - Viewing iptables rules
    • - View rules for all chains (-L without chain is rules for all chains, without -t is default filter table)
      iptables -L
    • - View rules for a specified table
      iptables -t nat -L
    • - View the rules for a given chain
      iptables -L INPUT
    • - View the rule number of the specified chain
      iptables -L INPUT --line-numbers
    • - View rule numbers and counters for a given chain
      iptables -L INPUT --line-numbers --verbose
    • - View the rule numbers and counters for the specified chain, displayed in a tree
      iptables -L INPUT --line-numbers --verbose --list

scripts

In the face of the troubleshooting dilemma brought about by massive iptables rules, traditional command-line tools expose three core flaws:

  • - Information overload problem: thousands of rules are presented in linear text and the logic of jumping between chains is difficult to trace
  • - Semantic breaks: -j Lack of contextual interpretation of target chains such as KUBE-SERVICES
  • - Risk of change: direct editing of production rules can lead to service disruptions
  • To address the above issues, write the following script, the script through the tree visualization and color marking, intelligent detection of circular references, support for interactive selection, compatible with multiple environments, automatic cleanup of temporary files, significantly improve the efficiency of iptables rule scheduling.
    The following script only passed experimentally on centos 7.6 (3.10.0-957.el7.x86_64).

Script content

vim show_iptables.sh  #! /bin/bash  #Script to dynamically analyze the chaining of a specified iptables table and display it in a tree structure #set-x #Define color (compatible with more terminals) RED=$'\033[31m' GREEN=$'\033[32m' YELLOW=$'\033[33m' BLUE=$'\033[34m' PURPLE=$'\033[35m' CYAN=$'\ 033[36m' GRAY=$'\033[90m' NC=$'\033[0m'  #Temporary file TEMP_FILE="/tmp/iptables_rules.txt"  #Global associative array (explicitly declared) declare -A VISITED_CHAINS  #Get all available tables get_tables() { if [[ -f /proc/net/ip_tables_names ]]; then cat /proc/net/ip_tables_names 2>/dev/null else # Compatible with older systems iptables -L -n 2>/dev/ null | grep -Po 'Table: \K\w+' | sort -u fi }  #Extract chain names (add filter) extract_chains() { grep -E "^:[A-Za-z0-9_-]+ " "$TEMP_FILE" | cut -d ' ' -f 1 | tr -d ':' | grep -v '^$' }  #Get rules for chain (enhanced filtering) find_rules_for_chain() { local chain=$1 [[ -z "$chain" ] ] && return grep -E "^-A $chain " "$TEMP_FILE" | sed '/^#/d' }  #Extract target chain (strict checksum) extract_targets() { local rule=$1 echo "$rule" | grep -oP '\s-(j|g)\s+\K[^\s]+' | grep -E '^[A-Za-z0-9_-]+$' }  #Rule formatting (defensive processing) format_rule() { local rule=$1 # Remove chain declarations and comments rule=$(echo "$rule" | sed -E 's/^-A [^ ]* //; s/(--comment "[^"]*")//g') # Highlight key elements echo "$rule" | sed -E \ -e "s/(-j |-g )([^ ]+)/${RED}\1${YELLOW}\2${NC}/g" \ -e "s/(-[pm] |--(src|dport|sport|destination| match))/${CYAN}\1${NC}/g" }  #Tree printing (critical fix) print_tree() { local chain=$1 local prefix=$2 local visited=$3 local depth=$4 # Null chain name defense if [[ -z "$chain" ]]; then echo -e "${prefix}${RED} Invalid null link name ${NC}" return fi # Loop detection if [[ "$visited" == *"|$chain|"* ]]; then echo -e "${prefix}$ {RED}└── Circular reference: $chain${NC}" return fi # Depth limit if (( depth > 15 )); then echo -e "${prefix}${YELLOW}└── Maximum depth reached ${NC}" return fi # Record access chain (secure write) if [[ -n "$chain" ]]; then VISITED_CHAINS["$chain"]=1 fi # Get rules local rules=() while IFS= read -r rule; do rules+=(" $rule") done <<< "$(find_rules_for_chain "$chain")" # Extract subchain local targets=() for rule in "${rules[@]}"; do while IFS= read -r target; do if [[ -n "$target" && ! " ${targets[*]} " =~ " $target " ]]; then targets+=("$target") fi done <<< "$(extract_targets "$rule")" done # print current chain local color case $((depth % 6)) in 0) color=$BLUE;; 1) color=$GREEN;; 2) color=$PURPLE;; 3) color=$CYAN;; 4) color=$YELLOW. ; *) color=$RED;; esac echo -e "${prefix}${color}├── ${chain}${NC}" # Print rules local rule_prefix="│ " for rule in "${rules [@]}"; do echo -e "${prefix}${rule_prefix}${GRAY}├── ▪ ${NC}$(format_rule "$rule")" done # prints the subchain local total=${ #targets[@]} for i in "${!targets[@]}"; do local target=${targets[$i]} if (( i == total - 1 )); then print_tree "$target" "${ prefix} └── " "${visited}|$chain|" $((depth + 1)) else print_tree "$target" "${prefix} ├── " "${visited}|$chain|" $((depth + 1)) fi done }  #Main Program main() { echo -e "${GREEN} ▪ iptables chain relationship topology (rule inline display) ▪ ${NC}" echo -e "${YELLOW} description:" echo -e " ${GRAY} ▪ Gray entry for rule ${NC}" echo -e " ${RED} red entry for rule ${NC} indicates jump target" echo -e " ${CYAN} cyan entry for rule ${NC} indicates match condition \n" echo -e "${BLUE} ▏ chain [${selected_chain}] topology: ${NC}" print_ tree "$selected_chain" "" "" 0 echo "" }  #Implementation process #1. select_table tables=($(get_tables)) if [[ ${#tables[@]} -eq 0 ]]; then echo -e "${RED} error: no iptables table ${NC} found" exit 1 fi echo "Available iptables table:" select selected_table in "${tables[@]}"; do if [[ -n "$selected_table" ]]; then break else echo -e "${RED} Invalid selection, please retype ${ NC}" fi done  #2. select_chains iptables-save -t "$selected_table" > "$TEMP_FILE" chains=($(extract_chains)) if [[ ${#chains[@]} -eq 0 ]]; then echo -e "${RED} error: no chain ${NC} found in table ${selected_table}" rm -f "$TEMP_FILE" exit 1 fi echo "Chains available in table ${selected_table}:" selected_chain in "${#{#chains[@]} -eq 0 ]; then echo -e chain in "${chains[@]}"; do if [[ -n "$selected_chain" ]]; then break else echo -e "${RED} is not validly selected, please re-enter ${NC}" fi done  #3. Execution analysis main rm -f "$TEMP_FILE"

test (machinery etc)

  • - Generate two rules to view the output
#Create a test rule in the INPUT chain of the filter table sudo iptables -t filter -A INPUT -p tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST "  #Create test rule (logs but never matches actual traffic) sudo iptables -t filter -A cali-INPUT -p tcp --sport 65535 -j LOG --log-prefix "CALI_TEST_RULE "
  • - Execution Script
. /show_iptables.sh Available iptables tables: 1) raw 2) mangle 3) filter 4) nat #? 3 Available chains for table filter: 1) INPUT 19) cali-from-hep-forward 2) FORWARD 20) cali-from-host-endpoint 3) OUTPUT 21) cali-from-wl-dispatch 4) DOCKER 22) cali-fw- cali163c2dd037c 5) DOCKER-ISOLATION-STAGE-1 23) cali-fw-caliceb7f36db92 6) DOCKER-ISOLATION-STAGE-2 24) cali-pri-_56duOTW9GxmBnwvgZx 7) DOCKER-USER 25) cali-pri-_RRPF6JYgiXDfvzOhm- 8) KUBE-EXTERNAL-SERVICES 26) cali-pri-_pJvVwNmnIJS_Hgp2My 9) KUBE-FIREWALL 27) cali-pro-_ 56duOTW9GxmBnwvgZx 10) KUBE-FORWARD 28) cali-pro-_RRPF6JYgiXDfvzOhm- 11) KUBE-KUBELET-CANARY 29) cali-pro-_pJvVwNmnIJS_Hgp2My 12) KUBE- NODEPORTS 30) cali-to-hep-forward 13) KUBE-PROXY-CANARY 31) cali-to-host-endpoint 14) KUBE-SERVICES 32) cali-to-wl-dispatch 15) cali-FORWARD 33 ) cali-tw-cali163c2dd037c 16) cali-INPUT 34) cali-tw-caliceb7f36db92 17) cali-OUTPUT 35) cali-wl-to-host 18) cali-cidr-block #▏Chain [INPUT] topology: ▪ Gray entries are rules.  m conntrack --ctstate NEW -m comment -j KUBE-EXTERNAL-SERVICES │ ├─ ▪ -j KUBE-FIREWALL │ ├─ ▪ -p tcp -m tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST " ├── ├── cali-INPUT ├── │ ├── ▪ -p ipv4 -m comment -m comment -m set --match-set cali40all-hosts-net src -m addrtype --dst-type LOCAL -j ACCEPT ├── │ ├── ▪ -p ipv4 -m comment -m comment -j DROP ├── │ ├── ▪ -i cali+ -m comment -g cali-wl-to-host ├── │ ├── ▪ -m comment -m mark --mark 0x10000/0x10000 -j ACCEPT ├── │ ├── ▪ -m comment -j mark --set-xmark 0x0/0xf0000 ├── │ ├── ▪ -m comment -j cali-from-host-endpoint ├── │ ├── ▪ -m comment -m comment -m mark --mark 0x10000/0x10000 -j ACCEPT ├── │ ├── ▪ -p tcp -m tcp --sport 65535 -j LOG --log-prefix "CALI_TEST_RULE "
  • - Clearance test rules
sudo iptables -t filter -D INPUT -p tcp --sport 12345 -j LOG --log-prefix "FILTER_TEST " sudo iptables -t filter -D cali-INPUT -p tcp --sport 65535 -j LOG -- log-prefix "CALI_TEST_RULE "

The above script enables you to quickly view the topological relationships of iptables rules for easy understanding and debugging.

Leave a Reply

Your email address will not be published. Required fields are marked *