#!/usr/bin/env bash # M3 gate: nat-to — source NAT of transit traffic to the egress address. # # OpenBSD 8.0 unified the old "nat on ..." rule into the rule syntax: # pass out on egress from to any nat-to (egress) # # Expected: the reply path is unaffected, srv sees the packet with # src = pf0's egress address (not the client's), and pfctl shows the rule. set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh FAILED=0 # pf calls the interface "egress"; the OS names it ethX - resolve both EGRESS_DEV=$(pf0 "ip route get $SRV_IP | grep -o 'dev [a-z0-9]*' | cut -d' ' -f2") EGRESS_IP=$(pf0 "ip -4 addr show $EGRESS_DEV | grep -o 'inet [0-9.]*' | head -1 | cut -d' ' -f2") [ -n "$EGRESS_IP" ] || EGRESS_IP=192.168.122.8 pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true' pf0 '/opt/pf/pfctl -e; /opt/pf/pfctl -f /dev/null' >/dev/null # OpenBSD expands "(egress)" (an interface *group*) into the addresses of all # its members - on a multi-NIC host that is ambiguous, so the gate uses the # concrete transit interface (the OpenBSD-recommended form for NAT pools). cat > /tmp/g05.conf < /tmp/nat.td 2>&1 &" sleep 1 cli "ping -c2 -W2 $SRV_IP >/dev/null 2>&1" && ok "transit ping through NAT" || bad "transit ping" sleep 2 TD=$(pf0 'cat /tmp/nat.td') if echo "$TD" | grep -q "$CLI_IP"; then bad "client address $CLI_IP still visible on egress" else ok "source rewritten on egress (no $CLI_IP)" fi echo "$TD" | grep -q "$EGRESS_IP" && ok "egress source is $EGRESS_IP" || bad "egress source not seen" pf0 '/opt/pf/pfctl -ss' | grep -q "$SRV_IP" && ok "state present" || bad "no state" pf0 '/opt/pf/pfctl -d' >/dev/null finish