M1: compat layer + pf.ko skeleton builds against Linux 7.3.4

- pfk.h: BSD types/ioctl encoding (OpenBSD bit layout), locks (NET_LOCK=rwsem),
  pools, timeouts, mbuf shim over sk_buff, ifnet registry, toeplitz/siphash
- pfk_mod/pfk_hooks: misc /dev/pf (0600), IPv4 netfilter hook skeleton,
  DIOCGETSTATUS answered (M1 skeleton; engine ioctls land with pf_ioctl.c)
- vendorize/stage-port scripts: deterministic upstream staging with seds
  (struct renames, IFF_*->PFIFF_*, sa_len drops) logged to docs/porting-notes
- fixes: modules_prepare+Module.symvers for out-of-tree builds
This commit is contained in:
lgcosta committed 2026-11-11 12:09:34 -03:00
1 parent ba81284bd3
commit da2e9b679a
87 files changed
+47256 -3

No files matched your search

+5 -1
View File
@@ -6,7 +6,11 @@ K="${KERNELDIR:-$(cd "$REPO/../linux-7.3.4" && pwd)}"
cd "$K"
"$REPO/scripts/kernel-config.sh" "${1:-}"
JOBS="${JOBS:-$(($(nproc)-2))}"
make -j"$JOBS" -s bzImage modules
make -j"$JOBS" -s bzImage modules_prepare
# External module builds need Module.symvers (kbuild only writes
# vmlinux.symvers) and scripts/module.lds (from modules_prepare above).
[ -f Module.symvers ] || cp vmlinux.symvers Module.symvers
[ -f scripts/module.lds ] || { echo "FATAL: scripts/module.lds missing" >&2; exit 1; }
mkdir -p "$REPO/out"
KVER="$(make -s kernelversion)"
cp -f arch/x86/boot/bzImage "$REPO/out/linux-${KVER}-bzImage"
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Build pf.ko out-of-tree against the patched kernel tree.
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
K="${KERNELDIR:-$(cd "$REPO/../linux-7.3.4" && pwd)}"
[ -e "$K/Module.symvers" ] || {
if [ -f "$K/vmlinux.symvers" ]; then cp "$K/vmlinux.symvers" "$K/Module.symvers";
else echo "kernel tree not prepared — run scripts/build-kernel.sh first" >&2; exit 1; fi; }
[ -f "$K/scripts/module.lds" ] || make -C "$K" -j4 modules_prepare >/dev/null
make -C "$K" M="$REPO/module" "$@"
echo MODULE_BUILD_OK
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/env bash
# lab-lib.sh — helpers shared by vm-create.sh / vm-destroy.sh / vm-deploy.sh.
# Source it; it never executes anything on its own. Topology single-source:
# lab/ips.env. Disks: per-VM qcow2 with backing_file to the shared read-only
# alpine-base.qcow2 (fast creates, ~MBs of delta storage; the base MUST NOT be
# modified once children exist — vm-create only re-syncs it when the laptop
# source hash differs).
# shellcheck disable=SC2034
LAB_LIB="$(readlink -f "${BASH_SOURCE[0]}")"
: "${REPO:=$(cd "$(dirname "$LAB_LIB")/.." && pwd)}"
export REPO
log() { printf 'pflab: %s\n' "$*"; }
warn() { printf 'pflab WARN: %s\n' "$*" >&2; }
die() { printf 'pflab FAIL: %s\n' "$*" >&2; exit 1; }
lab_env() {
[ -f "$REPO/lab/ips.env" ] || die "missing $REPO/lab/ips.env"
# shellcheck disable=SC1091
. "$REPO/lab/ips.env"
# bare-IP derivatives (route/via fields)
PF0_LAN_IP=${PF0_LAN%%/*}; PF0_WAN_IP=${PF0_WAN%%/*}; PF0_WAN2_IP=${PF0_WAN2%%/*}
CLI_IP=${CLI%%/*}; SRV_IP=${SRV%%/*}; GW2_WAN_IP=${GW2_WAN%%/*}; GW2_WAN2_IP=${GW2_WAN2%%/*}
LAB_SEEDS="$REPO/lab/seeds"
VMS="pf0 cli srv gw2"
}
SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10
-o LogLevel=ERROR -o ControlMaster=auto -o ControlPersist=120
-o "ControlPath=$HOME/.ssh/cm-pflab-%r@%h:%p")
vm_name() { echo "pftest-$1"; }
vm_ip() { local v=${1^^}_SSH; [ -n "${!v-}" ] || die "unknown vm alias: $1"; echo "${!v}"; }
# Run command(s) on deb.
deb() { ssh "${SSH_OPTS[@]}" "$DEB_USER@$DEB" "$@"; }
deb_cmd() { deb "bash -s" <<<"$1"; }
# Run a shell script inside a lab VM (nested ssh from deb with the lab key).
vm_cmd() { local ip=$1; deb "ssh -i $LAB_KEY_DEB ${SSH_OPTS[*]} root@$ip 'bash -s'" <<<"$2"; }
# --- template rendering: replace @KEY@ tokens from environment + extra K=V ---
RENDER_KEYS=(
DEB DEB_USER
PF0_MGMT PF0_LAN PF0_WAN PF0_WAN2 CLI CLI_GW SRV SRV_GW GW2_WAN2 GW2_WAN
PF0_SSH CLI_SSH SRV_SSH GW2_SSH
PF0_LAN_IP PF0_WAN_IP PF0_WAN2_IP CLI_IP SRV_IP GW2_WAN_IP GW2_WAN2_IP
MGMT_NET NET_PF_LAN NET_PF_WAN NET_PF_WAN2
PF_LAN_HOSTIP PF_WAN_HOSTIP PF_WAN2_HOSTIP PF_NET_PREFIX
VM_VCPUS VM_MEM_MB
PF0_MAC_MGMT PF0_MAC_LAN PF0_MAC_WAN PF0_MAC_WAN2
CLI_MAC_MGMT CLI_MAC_LAN SRV_MAC_MGMT SRV_MAC_WAN GW2_MAC_WAN GW2_MAC_WAN2
ROOT_PASS LAB_PACKAGES
IMG_DIR BASE_IMG SEED_DIR DEPLOY_DIR BOOT_DIR
LAB_KEY_DEB LAB_KEY_LAPTOP LAPTOP_SSH_FRAG BASE_SRC_REL
)
_esc() { local s=$1; s=${s//\\/\\\\}; s=${s//&/\\&}; s=${s//\~/\\~}; s=${s//$'\n'/\\n}; printf '%s' "$s"; }
render() { # render <template> <out> [KEY=VALUE ...]
local tmpl=$1 out=$2 kv; shift 2
local -a prog=()
for kv in "$@"; do
prog+=(-e "s~@${kv%%=*}@~$(_esc "${kv#*=}")~g")
done
for k in "${RENDER_KEYS[@]}"; do
prog+=(-e "s~@$k@~$(_esc "${!k-}")~g")
done
sed "${prog[@]}" "$tmpl" > "$out"
}
# --- deb host prerequisites ---
host_prereqs() {
deb true || die "cannot ssh $DEB_USER@$DEB (key auth? host up?)"
deb_cmd "set -e
systemctl is-active --quiet libvirtd || systemctl enable --now libvirtd 2>/dev/null || systemctl enable --now virtqemud
virsh -c qemu:///system list --all >/dev/null
command -v rsync >/dev/null || { apt-get update -qq; DEBIAN_FRONTEND=noninteractive apt-get install -y -qq rsync; }
command -v genisoimage >/dev/null || { DEBIAN_FRONTEND=noninteractive apt-get install -y -qq genisoimage; }
mkdir -p $SEED_DIR $BOOT_DIR"
}
# --- libvirt networks: default (start+autostart) + isolated pf-* L2 nets ---
# pf-* carry a host bridge IP (.254) so deb can ssh/icmp into VM data NICs;
# no <dhcp> and forward=none -> pure isolated segments, guest gw IPs are
# pf0 itself (no host/guest address conflict).
ensure_networks() {
deb "virsh net-start $MGMT_NET" 2>/dev/null || true
deb "virsh net-autostart $MGMT_NET" || true
local entry n b ip
for entry in "pf-lan $NET_PF_LAN $PF_LAN_HOSTIP" "pf-wan $NET_PF_WAN $PF_WAN_HOSTIP" "pf-wan2 $NET_PF_WAN2 $PF_WAN2_HOSTIP"; do
set -- $entry; b=$1; n=$2; ip=$3
if ! deb "virsh net-info $n" >/dev/null 2>&1; then
printf '<network>\n <name>%s</name>\n <forward mode="none"/>\n <bridge name="%s" stp="on" delay="0"/>\n <ip address="%s" netmask="%s"/>\n</network>\n' \
"$n" "virbr-$b" "$ip" "$PF_NET_PREFIX" | deb "virsh net-define /dev/stdin" >/dev/null
fi
deb "virsh net-start $n" 2>/dev/null || true
deb "virsh net-autostart $n" || true
done
}
# --- lab SSH keypair: private stays on deb; laptop keeps a copy for ProxyJump ---
lab_keypair() {
deb "test -f $LAB_KEY_DEB" || deb "ssh-keygen -q -t ed25519 -N '' -f $LAB_KEY_DEB -C pflab@deb"
LAB_PUBKEY=$(deb "cat $LAB_KEY_DEB.pub")
[ -n "$LAB_PUBKEY" ] || die "could not read lab pubkey on deb"
if [ ! -f "$LAB_KEY_LAPTOP" ] || ! cmp -s <(deb "cat $LAB_KEY_DEB") "$LAB_KEY_LAPTOP"; then
umask 077
deb "cat $LAB_KEY_DEB" > "$LAB_KEY_LAPTOP"
deb "cat $LAB_KEY_DEB.pub" > "$LAB_KEY_LAPTOP.pub"
fi
}
# --- shared base image (laptop _vendor -> deb), synced once by sha256 ---
base_image_sync() {
local lsha rsha=""
[ -f "$REPO/$BASE_SRC_REL" ] || die "base image missing: $REPO/$BASE_SRC_REL"
lsha=$(sha256sum "$REPO/$BASE_SRC_REL" | cut -d' ' -f1)
if [ "${RECREATE_BASE:-0}" != 1 ]; then
rsha=$(deb "sha256sum $BASE_IMG 2>/dev/null" | cut -d' ' -f1 || true)
fi
if [ "$lsha" = "$rsha" ]; then log "base image current on deb"; return 0; fi
log "syncing base image -> deb:$BASE_IMG"
rsync -a --info=progress2 -e "ssh ${SSH_OPTS[*]}" "$REPO/$BASE_SRC_REL" "$DEB_USER@$DEB:$BASE_IMG"
}
# --- guest boot kernel: the alpine cloud qcow2 ships an EMPTY ESP + no MBR
# code, so it is not BIOS- nor UEFI-bootable in QEMU as delivered. We extract
# the image's own kernel+initramfs (once, cached on deb) and direct-boot all
# VMs with root=UUID of the base's ext4 partition (the same backing image).
ensure_guest_kernel() {
deb "test -f $BOOT_DIR/guest-vmlinuz -a -f $BOOT_DIR/guest-initramfs -a -f $BOOT_DIR/guest-root.uuid" \
&& return 0
log "extracting guest kernel/initramfs from base image"
deb_cmd "set -e
modprobe nbd max_part=8 2>/dev/null || true
qemu-nbd -r -c /dev/nbd1 $BASE_IMG
trap 'umount /mnt/pflab-extract 2>/dev/null || true; qemu-nbd -d /dev/nbd1' EXIT
for i in 1 2 3 4 5; do [ -b /dev/nbd1p2 ] && break; sleep 1; done
[ -b /dev/nbd1p2 ]
mkdir -p /mnt/pflab-extract $BOOT_DIR
mount -o ro /dev/nbd1p2 /mnt/pflab-extract
cp /mnt/pflab-extract/boot/vmlinuz-lts $BOOT_DIR/guest-vmlinuz
cp /mnt/pflab-extract/boot/initramfs-lts $BOOT_DIR/guest-initramfs
blkid -s UUID -o value /dev/nbd1p2 > $BOOT_DIR/guest-root.uuid
chmod a+r $BOOT_DIR/guest-vmlinuz $BOOT_DIR/guest-initramfs $BOOT_DIR/guest-root.uuid"
}
default_kernelblock() { # prints kernelblock xml for extracted direct boot
local uuid
uuid=$(deb "cat $BOOT_DIR/guest-root.uuid")
printf ' <kernel>%s/guest-vmlinuz</kernel>\n <initrd>%s/guest-initramfs</initrd>\n <cmdline>root=UUID=%s ro console=ttyS0,115200n8</cmdline>' \
"$BOOT_DIR" "$BOOT_DIR" "$uuid"
}
# --- per-role runcmd additions ---
runcmd_extra() {
case $1 in
pf0)
cat <<'EOF'
- |
mkdir -p /etc/local.d
cat > /etc/local.d/pflab.start <<'EOS'
#!/bin/sh
sysctl -q -p /etc/sysctl.d/00-pflab.conf 2>/dev/null
IF=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
[ -n "$IF" ] || exit 0
iptables -t nat -A POSTROUTING -o "$IF" -j MASQUERADE
EOS
chmod +x /etc/local.d/pflab.start
rc-update add local boot >/dev/null 2>&1 || true
/etc/local.d/pflab.start
EOF
;;
cli) echo " - ip route replace default via $CLI_GW" ;;
srv) echo " - ip route replace default via $SRV_GW" ;;
gw2) echo " - ip route replace default via $PF0_WAN_IP" ;;
esac
}
# --- one VM: seed iso + backing disk + domain define/start ---
create_domain() { # create_domain <alias> [kernelblock]
local a=$1 kblock=${2:-} name disk extra seeds
name=$(vm_name "$a"); disk="$IMG_DIR/$name.qcow2"
seeds="$LAB_SEEDS/$name"
deb_cmd "virsh destroy $name 2>/dev/null; virsh undefine $name --nvram 2>/dev/null; virsh undefine $name 2>/dev/null; rm -f /var/lib/libvirt/qemu/nvram/$name*.raw; true"
rm -rf "$seeds"; mkdir -p "$seeds"
extra=$(runcmd_extra "$a")
render "$REPO/lab/cloud-init/user-data.tmpl" "$seeds/user-data" \
"VM=$a" "RUNCMD_EXTRA=$extra" "SSH_PUBKEY=$LAB_PUBKEY"
render "$REPO/lab/cloud-init/meta-data.tmpl" "$seeds/meta-data" "VM=$a"
render "$REPO/lab/cloud-init/network-config.$a.tmpl" "$seeds/network-config"
render "$REPO/lab/domains/$name.xml.tmpl" "$seeds/$name.xml" \
"NAME=$name" "VM=$a" "DISK=$disk" "SEED=$SEED_DIR/$name/seed.iso" "KERNELBLOCK=$kblock"
rsync -rt --delete --chmod=D0755,F0644 -e "ssh ${SSH_OPTS[*]}" "$seeds/" "$DEB_USER@$DEB:$SEED_DIR/$name/"
deb_cmd "cd '$SEED_DIR/$name' && rm -f seed.iso && \
genisoimage -quiet -o seed.iso -volid CIDATA -joliet -rock user-data meta-data network-config"
deb_cmd "chmod a+rX '$SEED_DIR' '$SEED_DIR/$name'"
deb_cmd "rm -f '$disk' && qemu-img create -f qcow2 -F qcow2 -b '$BASE_IMG' '$disk' >/dev/null"
deb "virsh define $SEED_DIR/$name/$name.xml" >/dev/null
deb "virsh start $name" >/dev/null
log "started $name"
}
wait_ready() { # wait_ready <alias> [timeout-s] — polls cloud-init done marker
local a=$1 t=${2:-300} ip name el=0
ip=$(vm_ip "$a"); name=$(vm_name "$a")
while :; do
if vm_cmd "$ip" 'test -f /run/pf-lab-ready' 2>/dev/null; then
log "$a ready"; return 0
fi
deb "LC_ALL=C virsh dominfo $name 2>/dev/null | grep -q running" \
|| die "$name is not running (virsh console $name)"
sleep 5; el=$((el+5))
[ "$el" -ge "$t" ] && die "timeout waiting for $a cloud-init"
done
}
packages_fixup() { # safety net if cloud-init ran before pf0 had transit up
local a ip
for a in $VMS; do
ip=$(vm_ip "$a")
vm_cmd "$ip" 'command -v iperf3 >/dev/null 2>&1 || {
. /etc/os-release
grep -qE "^[^#]*/community" /etc/apk/repositories || echo "${HOME}/v${VERSION_ID}/community" >> /etc/apk/repositories
apk update -q >/dev/null 2>&1
apk add -q iperf3 tcpdump python3 py3-scapy bash >/dev/null 2>&1 || warn "apk add failed on '"$a"'"
}' || warn "no ssh to $a for package fixup"
done
}
# --- ssh access plumbing: aliases pf0|cli|srv|gw2 from deb and from laptop ---
laptop_ssh_config() {
mkdir -p "$(dirname "$LAPTOP_SSH_FRAG")"; chmod 700 "$(dirname "$LAPTOP_SSH_FRAG")" 2>/dev/null || true
cat > "$LAPTOP_SSH_FRAG" <<EOF
# >>> generated by pf_port/scripts/vm-create.sh (topology: lab/ips.env) >>>
Host pf0 cli srv gw2
User root
IdentityFile $LAB_KEY_LAPTOP
ProxyJump $DEB_USER@$DEB
StrictHostKeyChecking accept-new
UserKnownHostsFile $HOME/.ssh/known_hosts_pflab
ConnectTimeout 15
ServerAliveInterval 15
Host pf0
HostName $PF0_SSH
Host cli
HostName $CLI_SSH
Host srv
HostName $SRV_SSH
Host gw2
HostName $GW2_SSH
# <<< generated <<<
EOF
touch ~/.ssh/config
grep -qE '^Include[[:space:]]+~/.ssh/config.d' ~/.ssh/config || {
cp -p ~/.ssh/config ~/.ssh/config.pflab-bak 2>/dev/null || true
printf 'Include ~/.ssh/config.d/*\n' | cat - ~/.ssh/config > ~/.ssh/config.tmp && mv ~/.ssh/config.tmp ~/.ssh/config
log "added Include ~/.ssh/config.d/* to ~/.ssh/config (backup: ~/.ssh/config.pflab-bak)"
}
}
deb_ssh_config() {
deb_cmd 'f=$HOME/.ssh/config; touch "$f"
sed -i "/# >>> pflab/,/# <<< pflab/d" "$f"
cat >> "$f" <<EOF
# >>> pflab (generated by vm-create.sh) >>>
Host pf0 cli srv gw2
User root
IdentityFile '"$LAB_KEY_DEB"'
StrictHostKeyChecking accept-new
LogLevel ERROR
ConnectTimeout 8
Host pf0
HostName '"$PF0_SSH"'
Host cli
HostName '"$CLI_SSH"'
Host srv
HostName '"$SRV_SSH"'
Host gw2
HostName '"$GW2_SSH"'
# <<< pflab <<<
EOF'
}
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Deterministic staging of OpenBSD 8.0 sources into module/ ports.
# Every transformation is logged in docs/porting-notes.md ("porting log").
# Re-running refreshes pristine copies while PRESERVING our port edits is
# NOT done here — ported files are committed and edited in-tree thereafter;
# this script is used (a) first-time staging, (b) `--diff <file>` to compare
# our ported copy against pristine upstream for rebase forensics.
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
U="$REPO/_vendor/upstream"
M="$REPO/module"
LOG="$REPO/docs/porting-notes.md"
stage_hdrs() {
mkdir -p "$M/include/net" "$M/include/compat/net" "$M/include/compat/sys" "$M/include/compat/crypto"
cp -f "$U/sys/net/pfvar.h" "$M/include/net/pfvar.h"
cp -f "$U/sys/net/pfvar_priv.h" "$M/include/net/pfvar_priv.h"
cp -f "$U/sys/net/if_pflog.h" "$M/include/compat/net/if_pflog.h"
cp -f "$U/sys/net/radix.h" "$M/include/compat/net/radix.h"
cp -f "$U/sys/net/rtable.h" "$M/include/compat/net/rtable.h"
cp -f "$U/sys/net/ifq.h" "$M/include/compat/net/ifq.h"
cp -f "$U/sys/net/toeplitz.h" "$M/include/compat/net/toeplitz.h"
cp -f "$U/sys/sys/queue.h" "$M/include/compat/sys/queue.h"
cp -f "$U/sys/sys/tree.h" "$M/include/compat/sys/tree.h"
# NOTE: radix.h / siphash.h / toeplitz.h are NOT copied verbatim — our
# cleaned PFLINUX versions live in module/include/compat/{net,crypto}/
# (BSD syntax like __bounded / _BYTE_ORDER does not compile in-kernel).
}
stage_c() { # relpath upstream -> module/compat|pf
local up="$1" dst="$2"
cp -f "$up" "$dst"
}
# port_c <upstream-sys-rel> <module-rel> : first-time staging of an OpenBSD
# .c file with deterministic transformations (all logged to PORT notes):
# 1. all original #include lines -> single '#include <pfk.h>' (+ keep
# none: pfvar is pulled by pfk.h chain when needed via -I)
# 2. BSD wire struct renames (avoid Linux-uapi collisions):
# ip6_hdr→pf_ip6_hdr, struct ip→pf_ip, tcphdr→pf_tcphdr,
# udphdr→pf_udphdr, struct icmp→pf_icmp, icmp6_hdr→icmp6hdr
# 3. BSD IFF_* flag names -> PFIFF_* (Linux owns IFF_*)
# 4. sa_len/sin_len/six_len assignment statements commented out
# 5. '#if NPFLOG > 0'-style option headers were never included (dropped)
port_c() {
local up="$U/sys/$1" dst="$M/$2"
[ -f "$up" ] || { echo "no upstream $1" >&2; return 1; }
mkdir -p "$(dirname "$dst")"
if [ -f "$dst" ]; then echo "EXISTS: $2 (not re-staged; use --diff)"; return 0; fi
{
printf '/* PFLINUX port of OpenBSD 8.0 %s — see docs/porting-notes.md */\n' "$1"
printf '#include <pfk.h>\n'
# keep everything after the last original #include (body)
awk 'BEGIN{last=0} /^#include/{last=NR} END{print last}' "$up" |
{ read n; tail -n +$((n+1)) "$up"; }
} > "$dst.part"
sed -i -E \
-e 's/\bstruct ip6_hdr\b/struct pf_ip6_hdr/g' \
-e 's/\bstruct ip\b/struct pf_ip/g' \
-e 's/\bstruct tcphdr\b/struct pf_tcphdr/g' \
-e 's/\bstruct udphdr\b/struct pf_udphdr/g' \
-e 's/\bstruct icmp\b/struct pf_icmp/g' \
-e 's/\bstruct icmp6_hdr\b/struct icmp6hdr/g' \
-e 's/\bIFF_([A-Z0-9_]+)/PFIFF_\1/g' \
-e 's/\bIP_OPTIONS\b/PF_IP_OPTIONS/g' \
-e 's/\bIP_RECORD_ROUTE\b/PF_IP_RECORD_ROUTE/g' \
-e 's/\bIP_TIMESTAMP\b/PF_IP_TIMESTAMP/g' \
-e 's/->sa_len[[:space:]]*=[^;]*;/\/* PFLINUX sa_len: *\/;/g' \
-e 's/([a-z0-9_]*(sa|sin|six|sun)_len)[[:space:]]*=[^;]*;/\/* PFLINUX \1 *\/;/g' \
"$dst.part"
mv "$dst.part" "$dst"
log "port_c $1 -> $2 (includes→pfk.h; struct renames; sa_len nulled)"
}
PORTED_LIST='net/pf.c net/pf_ioctl.c net/pf_norm.c net/pf_if.c net/pf_lb.c
net/pf_ruleset.c net/pf_table.c net/pf_osfp.c net/pf_syncookies.c
net/ifq.c net/hfsc.c net/fq_codel.c net/if_pflog.c netinet/ip_divert.c'
# NOTE: radix.c/rtable.c (ported by hand in T10 against our cleaned
# compat/net/radix.h) and siphash/toeplitz/sha2 (own PFLINUX impls) are
# deliberately NOT in the auto-port list.
log() { mkdir -p "$(dirname "$LOG")"; printf '\n%s\n```sh\n%s\n```\n' "## $(date -Is) staging" "$*" >> "$LOG"; }
cmd="${1:-stage}"
case "$cmd" in
stage)
log "stage: headers + skeletons"
stage_hdrs
echo "headers staged: $(find "$M/include/net" "$M/include/compat" -type f | wc -l)"
;;
port)
shift
for f in ${*:-$PORTED_LIST}; do
case "$f" in
net/pf.c|net/pf_ioctl.c|net/pf_norm.c|net/pf_if.c|net/pf_lb.c|\
net/pf_ruleset.c|net/pf_table.c|net/pf_osfp.c|net/pf_syncookies.c|\
net/ifq.c|net/hfsc.c|net/fq_codel.c|net/if_pflog.c)
port_c "$f" "pfsrc/${f##*/}";;
netinet/ip_divert.c)
port_c "$f" "pfsrc/ip_divert.c";;
esac
done
;;
--diff)
[ -f "$U/$2" ] || { echo "no pristine $2"; exit 1; }
diff -u "$U/$2" "$M/$2" | head -80
;;
*) echo "usage: stage-port.sh [stage|--diff relpath]"; exit 1;;
esac
+3 -2
View File
@@ -9,8 +9,9 @@ SYS_K=(net/pf.c net/pf_ioctl.c net/pf_norm.c net/pf_if.c net/pf_lb.c net/pf_rule
net/pf_table.c net/pf_osfp.c net/pf_syncookies.c net/pfvar.h net/pfvar_priv.h
net/if_pflog.c net/if_pflog.h net/ifq.c net/ifq.h net/hfsc.c net/hfsc.h
net/fq_codel.c net/fq_codel.h netinet/ip_divert.c netinet/ip_divert.h
net/radix.c net/radix.h net/radix_node.h net/rtable.c net/rtable.h
sys/tree.h sys/queue.h sys/param.h sys/hash.h)
net/radix.c net/radix.h net/rtable.c net/rtable.h net/toeplitz.c net/toeplitz.h
crypto/sha2.c crypto/sha2.h crypto/siphash.c crypto/siphash.h
sys/tree.h sys/queue.h sys/param.h sys/hash.h sys/_endian.h sys/_lock.h)
SRC_K=(sbin/pfctl/parse.y sbin/pfctl/pfctl.c sbin/pfctl/pfctl.h sbin/pfctl/pfctl_parser.c
sbin/pfctl/pfctl_parser.h sbin/pfctl/pfctl_optimize.c sbin/pfctl/pfctl_print.c
sbin/pfctl/pfctl_osfp.c sbin/pfctl/pfctl_queue.c sbin/pfctl/pfctl_radix.c
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# vm-create.sh — build (or rebuild) the pftest-* libvirt lab on deb.
# Topology + paths: lab/ips.env. Idempotent: pre-existing pftest-* domains,
# disks and seeds are torn down and recreated; shared base image and data
# networks are reused when already correct. Leave all VMs running at exit.
#
# --pf0-kernel=PATH direct kernel boot for pf0 (laptop path is rsynced to
# deb:BOOT_DIR/bzImage; a deb-local path is used as-is).
# cmdline: root=/dev/vda2 ro console=ttyS0
# --pf0-initrd=PATH optional initrd alongside --pf0-kernel
# --recreate-base force re-sync of alpine-base.qcow2 from _vendor
# -h help
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck disable=SC1091
. "$REPO/scripts/lab-lib.sh"
PF0_KERNEL=""; PF0_INITRD=""; RECREATE_BASE=0
for arg in "$@"; do
case $arg in
--pf0-kernel=*) PF0_KERNEL=${arg#*=}; [ -n "$PF0_KERNEL" ] || die "--pf0-kernel needs a value" ;;
--pf0-initrd=*) PF0_INITRD=${arg#*=} ;;
--recreate-base) RECREATE_BASE=1 ;;
-h|--help) sed -n '2,12p' "$0"; exit 0 ;;
*) die "unknown arg: $arg (-h for usage)" ;;
esac
done
lab_env
command -v rsync >/dev/null || die "rsync missing on laptop"
command -v genisoimage >/dev/null 2>&1 || true # iso is built on deb, not here
host_prereqs
ensure_networks
lab_keypair
base_image_sync
# direct kernel boot: every VM boots via the image's own extracted kernel
# (base image is not BIOS/UEFI bootable, see lab-lib.sh); --pf0-kernel
# replaces pf0's kernel with a built bzImage (T6), keeping its root on vda2.
ensure_guest_kernel
DEFAULT_KB=$(default_kernelblock)
PF0_KB="$DEFAULT_KB"
if [ -n "$PF0_KERNEL" ]; then
deb "mkdir -p $BOOT_DIR"
KPATH="$PF0_KERNEL"
if [ -f "$PF0_KERNEL" ]; then
rsync -a -e "ssh ${SSH_OPTS[*]}" "$PF0_KERNEL" "$DEB_USER@$DEB:$BOOT_DIR/bzImage"
KPATH="$BOOT_DIR/bzImage"
fi
PF0_KB=" <kernel>$KPATH</kernel>"$'\n'" <cmdline>root=/dev/vda2 ro console=ttyS0,115200n8</cmdline>"
if [ -n "$PF0_INITRD" ]; then
IPATH="$PF0_INITRD"
if [ -f "$PF0_INITRD" ]; then
rsync -a -e "ssh ${SSH_OPTS[*]}" "$PF0_INITRD" "$DEB_USER@$DEB:$BOOT_DIR/initrd"
IPATH="$BOOT_DIR/initrd"
fi
PF0_KB=" <kernel>$KPATH</kernel>"$'\n'" <initrd>$IPATH</initrd>"$'\n'" <cmdline>root=/dev/vda2 ro console=ttyS0,115200n8</cmdline>"
fi
log "pf0 direct kernel boot override: $KPATH"
fi
# pf0 first: cli/srv/gw2 cloud-init needs pf0's transit+masq for pkgs
create_domain pf0 "$PF0_KB"
wait_ready pf0 420
for a in cli srv gw2; do create_domain "$a" "$DEFAULT_KB"; done
for a in cli srv gw2; do wait_ready "$a"; done
packages_fixup
deb_ssh_config
laptop_ssh_config
log "validate: ssh pf0 'ip -br a'"
ssh "${SSH_OPTS[@]}" pf0 'ip -br a' || warn "ssh pf0 failed — check $LAPTOP_SSH_FRAG"
log "LAB UP: aliases on laptop: ssh pf0|cli|srv|gw2 (ProxyJump $DEB_USER@$DEB)"
deb "virsh list" || true
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# vm-deploy.sh — ship build artifacts laptop -> deb -> pf0.
# Default sources (tolerated when absent): out/linux-*-bzImage / out/bzImage,
# module/*.ko, tools/out/* (files or dirs). Explicit paths as args override.
# deb landing zone: $DEPLOY_DIR (/var/lib/pflab/deploy); pf0 gets /opt/pf/.
# Transfer into the guest uses rsync when the guest has it, else scp.
# Exit 0 with a warning when there is nothing to deploy (T6/T7 call freely).
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck disable=SC1091
. "$REPO/scripts/lab-lib.sh"
lab_env
cd "$REPO"
FILES=()
add() { # add <path...> — files only; dirs expanded
local p
for p in "$@"; do
if [ -d "$p" ]; then add "$p"/*
elif [ -f "$p" ]; then FILES+=("$p")
fi
done
}
if [ $# -gt 0 ]; then
add "$@"
else
shopt -s nullglob
add out/linux-*-bzImage out/bzImage out/*.ko module/*.ko tools/out/*
fi
if [ ${#FILES[@]} -eq 0 ]; then
log "nothing to deploy (out/ bzImage, module/*.ko, tools/out/* all absent) — OK"
exit 0
fi
log "deploying ${#FILES[@]} artifact(s): ${FILES[*]#$REPO/}"
vm_cmd "$(vm_ip pf0)" 'true' 2>/dev/null || die "pf0 unreachable — run ./scripts/vm-create.sh first"
deb "mkdir -p $DEPLOY_DIR"
rsync -a -e "ssh ${SSH_OPTS[*]}" "${FILES[@]}" "$DEB_USER@$DEB:$DEPLOY_DIR/"
log "staged on deb: $DEPLOY_DIR"
vm_cmd "$(vm_ip pf0)" 'mkdir -p /opt/pf'
if deb "command -v rsync" >/dev/null 2>&1 && \
vm_cmd "$(vm_ip pf0)" 'command -v rsync' >/dev/null 2>&1; then
deb_cmd "rsync -a -e 'ssh -i $LAB_KEY_DEB ${SSH_OPTS[*]}' '$DEPLOY_DIR/' root@$(vm_ip pf0):/opt/pf/"
else
# no rsync in guest: per-file scp
deb_cmd "for f in '$DEPLOY_DIR'/*; do scp -i $LAB_KEY_DEB ${SSH_OPTS[*]} -q \"\$f\" root@$(vm_ip pf0):/opt/pf/; done"
fi
log "pf0:/opt/pf ->"
vm_cmd "$(vm_ip pf0)" 'ls -l /opt/pf'
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# vm-destroy.sh — tear the lab down: pftest-* domains, their qcow2 disks,
# seed ISOs/dirs, the pf-lan/pf-wan/pf-wan2 networks, and the generated
# laptop ssh fragment. The shared alpine-base.qcow2 and 'default' network are
# KEPT by default (re-create is then fast); --purge-base removes the base,
# --purge-keys also drops the lab keypair on deb + laptop copies.
# Never touches non-pftest domains or the 'default' network state.
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck disable=SC1091
. "$REPO/scripts/lab-lib.sh"
PURGE_BASE=0; PURGE_KEYS=0; QUIET=0
for arg in "$@"; do
case $arg in
--purge-base) PURGE_BASE=1 ;;
--purge-keys) PURGE_KEYS=1 ;;
--quiet) QUIET=1 ;;
-h|--help) sed -n '2,8p' "$0"; exit 0 ;;
*) die "unknown arg: $arg (-h for usage)" ;;
esac
done
[ $QUIET = 1 ] && exec >/dev/null 2>&1
lab_env
deb true || { warn "deb unreachable; removing laptop artifacts only"; rm -f "$LAPTOP_SSH_FRAG"; exit 0; }
for a in $VMS; do
name=$(vm_name "$a")
deb_cmd "virsh destroy $name 2>/dev/null; virsh undefine $name --nvram 2>/dev/null; virsh undefine $name 2>/dev/null; rm -f /var/lib/libvirt/qemu/nvram/$name*.raw; true"
deb_cmd "rm -rf '$SEED_DIR/$name' '$IMG_DIR/$name.qcow2' /tmp/$name.xml"
log "removed domain+disk+seed: $name"
done
rm -rf "$LAB_SEEDS"
for n in "$NET_PF_LAN" "$NET_PF_WAN" "$NET_PF_WAN2"; do
deb_cmd "virsh net-destroy $n 2>/dev/null; virsh net-undefine $n 2>/dev/null; true"
done
log "removed networks: $NET_PF_LAN $NET_PF_WAN $NET_PF_WAN2 (default left as-is)"
deb_cmd 'f=$HOME/.ssh/config; [ -f "$f" ] && sed -i "/# >>> pflab/,/# <<< pflab/d" "$f" || true'
rm -f "$LAPTOP_SSH_FRAG" "$HOME/.ssh/known_hosts_pflab"
log "removed generated ssh fragments (laptop + deb config blocks)"
if [ $PURGE_BASE = 1 ]; then
deb "rm -f $BASE_IMG"
log "removed base image on deb"
fi
if [ $PURGE_KEYS = 1 ]; then
deb "rm -f $LAB_KEY_DEB $LAB_KEY_DEB.pub"
rm -f "$LAB_KEY_LAPTOP" "$LAB_KEY_LAPTOP.pub"
log "removed lab keypair (deb + laptop)"
fi
log "VERIFY state on deb:"
deb 'virsh list --all; echo ---; virsh net-list --all; echo ---; ls -l /var/lib/libvirt/images/ /var/lib/libvirt/pflab/ 2>/dev/null || true'