11 Commits
Author SHA1 Message Date
lgcosta 2cd13eb806 tests: soak helper ignores the CPU SRSO advisory 2026-11-11 20:29:33 -03:00
lgcosta e03df9adc8 docs: data-path semantics and NAT checklist 2026-11-11 20:25:57 -03:00
lgcosta 7d1e4b81e9 cleanup: drop debug instrumentation 2026-11-11 20:24:53 -03:00
lgcosta f3143a54a8 nat: route the reverse-translated packet with an empty source (kernel rejects non-local saddr) 2026-11-11 20:23:44 -03:00
lgcosta 917d3e3fe0 nat: recompute IPv4 header checksum after pf rewrote the addresses (OpenBSD ip_output does this) 2026-11-11 20:20:52 -03:00
lgcosta d520172753 nat: re-route inbound packets whose destination pf rewrote (reverse NAT) instead of delivering them locally 2026-11-11 20:17:27 -03:00
lgcosta 9c3c4b6bbc fix: skb->mark masks were multi-bit (IN mark matched OUT test) - transit traffic never reached the out ruleset; ingress/egress interface groups; pfctl resolves (egress) pool locally 2026-11-11 20:15:35 -03:00
lgcosta c449e01b9a pf: emulate OpenBSD ingress/egress interface groups as any-interface (Linux has no ifgroups) 2026-11-11 20:07:17 -03:00
lgcosta 525fe7ee28 tests: gate assertions corrected (pfctl state format, transit egress device, CPU SRSO advisory excluded); Makefile demo target 2026-11-11 20:04:16 -03:00
lgcosta 18c0a69f74 tests: soak gate via uploaded helper (quoting), 04/05 assertions fixed (pf interface name vs OS device) 2026-11-11 19:59:27 -03:00
lgcosta 3aa0c2d0d0 docs: demo guide (asciinema recording/playback/upload) EN+PT-BR 2026-11-11 19:56:24 -03:00
13 changed files with 362 additions and 35 deletions

No files matched your search

+10 -2
View File
@@ -1,7 +1,7 @@
# PF-on-Linux top-level make
KERNELDIR ?= $(HOME)/lab/linux_kernel/linux-7.3.4
.PHONY: module tools kernel test clean vm vm-destroy docs
.PHONY: module tools kernel test clean vm vm-destroy docs demo
module:
$(MAKE) -C $(KERNELDIR) M=$(CURDIR)/module modules
@@ -25,7 +25,15 @@ docs:
@for f in docs/en/*.md; do echo "EN $$f"; done
@for f in docs/pt-BR/*.md; do echo "PT $$f"; done
# Record the lab session as an asciinema cast (real output, scripted pacing).
demo:
@mkdir -p out
@command -v asciinema >/dev/null 2>&1 || { \
echo "asciinema not installed: pip install asciinema"; exit 1; }
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
@echo "-> out/demo-pf-linux.cast (play: asciinema play out/demo-pf-linux.cast)"
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURDIR)/module clean || true
$(MAKE) -C tools clean || true
rm -rf out/
@find out -maxdepth 1 -type f ! -name '*.cast' -delete 2>/dev/null || true
+41
View File
@@ -0,0 +1,41 @@
# Demo (asciinema)
The lab can be recorded as an [asciinema](https://asciinema.org) cast. Every
byte in the recording is the real output of the lab VMs (Alpine under libvirt
running the kernel built from source with `pf.ko`); only the pacing is
scripted.
## Record
```sh
pip install asciinema # once
bash scripts/vm-run-tests.sh # or: scripts/vm-create.sh + deploy
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
```
The session walks through:
1. the lab topology and the freshly loaded `pf.ko` (`dmesg` proof),
2. `pfctl -s info` over the OpenBSD ioctl ABI,
3. a real `pf.conf` (table, stateful rules, explicit drop) and `pfctl -sr`,
4. transit ping + `pfctl -ss` (stateful),
5. `pfctl -t <table> -T show` (live tables),
6. a blocked ping (counters with `pfctl -sr -vv`),
7. `divert-to` with the `pdivert` relay (packet to userspace and back),
8. shutdown.
## Play back
```sh
asciinema play out/demo-pf-linux.cast
```
## Upload
```sh
asciinema upload out/demo-pf-linux.cast
```
`scripts/demo-record.py` is an alternative recorder that produces the same
asciicast v2 format without the asciinema binary (it drives the lab over
ssh and writes the cast itself).
+22
View File
@@ -882,3 +882,25 @@ Decisions taken (all verified in the libvirt lab):
`pdivert` (userspace relay, `tools/pdivert/`) is the reference client; it
demonstrates the BSD "receive -> rewrite -> reinject" cycle.
## Data-path semantics (M2/M3) — deviations and bugs found in the lab
| Topic | OpenBSD | This port |
|---|---|---|
| Ruleset for transit traffic | `ip_input()` evaluates the **in** ruleset, `ip_forward()` evaluates the **out** ruleset (`pf_test(PF_FWD)`) | `NF_INET_PRE_ROUTING` → in ruleset, `NF_INET_FORWARD` → out ruleset (`pf_test(PF_OUT)`), `NF_INET_LOCAL_OUT` → out ruleset for locally generated traffic |
| Interface groups | kernel ifgroups `ingress`/`egress` contain every interface | Linux has no ifgroups: `ingress`/`egress` are emulated as "any interface"; `(egress)` in a NAT pool is resolved by pfctl to the default-route interface |
| Reverse NAT | `ip_input()` re-routes the packet when pf rewrote the destination (`PF_TAG_REROUTE`) | the hook detects the rewritten destination and re-routes it (`ip_route_output_key()` + `dst.output()`) |
| IPv4 header checksum | `ip_output()` recomputes it for every packet it sends | Linux does not; the hook recomputes it when pf changed the addresses |
| `skb->mark` bookkeeping | n/a | one bit per direction (`PFK_DONE_IN`, `PFK_DONE_OUT`) plus `PFK_SKIP_PF` for reinjected packets — **single-bit masks**: multi-bit magic values made the in-pass mark look like an out-pass mark and the out ruleset was never evaluated for transit traffic |
### NAT checklist (what the lab gate proves)
1. `nat-to (<iface>)` on an out rule → source rewritten to the pool address
(packet leaves with the firewall's address, `tcpdump` proof).
2. The reply arrives addressed to the translated address; pf matches the state
and rewrites the destination back to the original client.
3. The hook re-routes the packet (the destination is no longer local) and it
leaves towards the client — `ping` succeeds end to end.
Without step 3 the reply is delivered to the firewall's own stack and the
client never sees it (the classic "NAT works one way" symptom).
+40
View File
@@ -0,0 +1,40 @@
# Demo (asciinema)
O lab pode ser gravado como um cast do [asciinema](https://asciinema.org).
Cada byte da gravação é a saída real das VMs do lab (Alpine sobre libvirt,
rodando o kernel compilado do fonte com o `pf.ko`); só o ritmo é roteirizado.
## Gravar
```sh
pip install asciinema # uma vez
bash scripts/vm-run-tests.sh # ou: scripts/vm-create.sh + deploy
asciinema rec out/demo-pf-linux.cast -c "bash scripts/demo-session.sh"
```
A sessão percorre:
1. a topologia do lab e o `pf.ko` recém-carregado (prova no `dmesg`),
2. `pfctl -s info` pela ABI de ioctl do OpenBSD,
3. um `pf.conf` real (tabela, regras stateful, drop explícito) e `pfctl -sr`,
4. ping em trânsito + `pfctl -ss` (stateful),
5. `pfctl -t <tabela> -T show` (tabelas vivas),
6. um ping bloqueado (contadores com `pfctl -sr -vv`),
7. `divert-to` com o relay `pdivert` (pacote vai ao userspace e volta),
8. desligamento.
## Reproduzir
```sh
asciinema play out/demo-pf-linux.cast
```
## Publicar
```sh
asciinema upload out/demo-pf-linux.cast
```
`scripts/demo-record.py` é um gravador alternativo que produz o mesmo formato
asciicast v2 sem o binário asciinema (ele conduz o lab por ssh e escreve o
cast diretamente).
+1 -1
View File
@@ -7,7 +7,7 @@
long pfk_ioctl(struct file *f, unsigned int cmd, unsigned long addr);
int pfk_enable(int on);
void pfk_status(struct pf_status *s);
#define PFK_SKIP_PF 0x50460040u /* skb->mark: reinjected, bypass pf */
#define PFK_SKIP_PF 0x00000004u /* skb->mark: reinjected, bypass pf */
int pfk_hooks_register(void);
void pfk_hooks_unregister(void);
+11
View File
@@ -90,6 +90,17 @@ pfi_kif_alloc(const char *kif_name, int mflags)
kif->pfik_flags |= PFI_IFLAG_ANY;
kif->pfik_flags_new |= PFI_IFLAG_ANY;
}
/*
* PFLINUX: OpenBSD creates the "ingress" and "egress" interface
* groups (ifgroups) automatically and both contain every interface;
* pf's direction (in/out ruleset) carries the actual meaning. Linux
* has no ifgroups, so emulate the groups as "any interface".
*/
if (!strcmp(kif->pfik_name, "ingress") ||
!strcmp(kif->pfik_name, "egress")) {
kif->pfik_flags |= PFI_IFLAG_ANY;
kif->pfik_flags_new |= PFI_IFLAG_ANY;
}
return (kif);
}
+79 -2
View File
@@ -35,8 +35,8 @@ struct pfk_pkt {
int dir;
};
#define PFK_DONE_IN 0x50460010u /* skb->mark bit: PF_IN pass completed */
#define PFK_DONE_OUT 0x50460020u /* skb->mark bit: PF_OUT pass completed */
#define PFK_DONE_IN 0x00000001u /* skb->mark bit: PF_IN pass completed */
#define PFK_DONE_OUT 0x00000002u /* skb->mark bit: PF_OUT pass completed */
static void
pfk_pkt_reinject(struct pfk_pkt *pk)
@@ -71,14 +71,75 @@ pfk_pkt_reinject(struct pfk_pkt *pk)
kfree(pk);
}
/*
* Reverse NAT: pf rewrote the destination of an inbound packet (the reply
* of a translated flow). OpenBSD re-routes such a packet in ip_input()
* (PF_TAG_REROUTE); Linux has no such layer, so re-route it here and push
* it out through the new egress device.
*/
static void
pfk_reroute(struct sk_buff *skb)
{
struct flowi4 fl4 = { };
struct rtable *rt;
struct iphdr *iph = ip_hdr(skb);
fl4.daddr = iph->daddr;
/*
* Leave the source empty: the packet keeps the original source
* (the peer's address), but the kernel would reject a route lookup
* with a non-local source (ENETUNREACH).
*/
fl4.saddr = 0;
fl4.flowi4_oif = 0;
fl4.flowi4_mark = 0;
fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
rt = ip_route_output_key(&init_net, &fl4);
if (IS_ERR(rt) || rt == NULL) {
kfree_skb(skb);
return;
}
skb_dst_set(skb, &rt->dst);
skb->dev = rt->dst.dev;
skb_reset_network_header(skb);
rt->dst.output(&init_net, skb->sk, skb);
}
/*
* PFLINUX: pf rewrote the IPv4 addresses of the packet (NAT). OpenBSD's
* ip_output() recomputes the IP header checksum on every packet it sends;
* Linux does not, so fix it up here after pf touched the header.
*/
static void
pfk_fix_ip_csum(struct sk_buff *skb)
{
struct iphdr *iph;
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
return;
iph = ip_hdr(skb);
iph->check = 0;
iph->check = csum_fold(csum_partial(skb->data, iph->ihl * 4, 0));
/* the transport checksum was already fixed by pf */
skb->ip_summed = CHECKSUM_NONE;
}
static void
pfk_pkt_work(struct work_struct *w)
{
struct pfk_pkt *pk = container_of(w, struct pfk_pkt, work);
struct mbuf *m, *m0;
struct sk_buff *orig;
__be32 orig_dst = 0, orig_src = 0;
int action;
if (pk->af == AF_INET && pskb_may_pull(pk->skb, sizeof(struct iphdr))) {
orig_dst = ip_hdr(pk->skb)->daddr;
orig_src = ip_hdr(pk->skb)->saddr;
}
m = pfk_m_wrap_skb(pk->skb, pk->af);
if (!m) {
pfk_pkt_reinject(pk); /* like "pf not consulted" */
@@ -93,11 +154,27 @@ pfk_pkt_work(struct work_struct *w)
* and packet walk run concurrently from here on — documented. */
action = pf_test(pk->af, pk->dir, pk->ifp, &m);
if (pk->af == AF_INET && orig_src != 0 &&
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
(ip_hdr(pk->skb)->saddr != orig_src ||
ip_hdr(pk->skb)->daddr != orig_dst))
pfk_fix_ip_csum(pk->skb);
if (m == m0) {
/* same buffer, possibly rewritten in place: our wrapper
* held one extra skb reference — release it */
m_freem(m);
if (action == PF_PASS && orig_dst != 0 &&
pk->af == AF_INET && pk->dir == PF_IN &&
pskb_may_pull(pk->skb, sizeof(struct iphdr)) &&
ip_hdr(pk->skb)->daddr != orig_dst) {
/* pf translated the destination (reverse NAT):
* re-route instead of delivering locally */
pfk_reroute(pk->skb);
kfree(pk);
return;
}
if (action == PF_PASS) {
pfk_pkt_reinject(pk);
return;
+25 -3
View File
@@ -49,6 +49,9 @@ pass in quick proto icmp from $CLI_IP to 10.0.1.1 divert-to "10.0.1.1" port 1000
pass out quick
CONF
EGRESS_IF=$(ssh pf0 "ip route get $SRV_IP | grep -o 'dev [a-z0-9]*' | cut -d' ' -f2" 2>/dev/null | tr -d '\r')
[ -n "$EGRESS_IF" ] || EGRESS_IF=eth2
scp -q "$CONF" "$DIV" pf0:/tmp/ 2>/dev/null
scp -q tools/out/pdivert pf0:/tmp/pdivert 2>/dev/null
@@ -89,11 +92,30 @@ p0 "/opt/pf/pfctl -s Tables"
echo
bold "--- 6. the drop rule actually drops ---"
vmc "ping -c2 -W2 10.0.9.99 | tail -3"
p0 "cat > /tmp/demo-block.conf <<EOF
block drop in quick proto icmp to $SRV_IP
pass in quick
pass out quick
EOF"
p0 "/opt/pf/pfctl -f /tmp/demo-block.conf && echo 'block rules loaded'"
vmc "ping -c2 -W2 $SRV_IP | tail -3"
p0 "/opt/pf/pfctl -sr -vv | head -6"
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'"
echo
bold "--- 7. divert-to: the packet goes to userspace and comes back ---"
bold "--- 7. NAT: the source is translated on the way out, restored on the way back ---"
p0 "cat > /tmp/demo-nat.conf <<EOF
pass out quick on egress from 10.0.1.0/24 to any nat-to ($EGRESS_IF)
pass in quick on ingress proto icmp keep state
pass out quick on egress proto icmp keep state
EOF"
p0 "/opt/pf/pfctl -f /tmp/demo-nat.conf && echo 'nat rules loaded'"
vmc "ping -c2 -W2 $SRV_IP | tail -3"
p0 "/opt/pf/pfctl -ss"
p0 "/opt/pf/pfctl -f /tmp/demo-pf.conf && echo 'back to the stateful ruleset'"
echo
bold "--- 8. divert-to: the packet goes to userspace and comes back ---"
p0 "cat /tmp/demo-divert.conf"
p0 "/opt/pf/pfctl -f /tmp/demo-divert.conf && echo 'divert rule loaded'"
p0 "chmod +x /tmp/pdivert; (setsid /tmp/pdivert -p 10000 -f >/tmp/pdivert.log 2>&1 </dev/null &); sleep 1; pgrep pdivert"
@@ -102,7 +124,7 @@ p0 "head -3 /tmp/pdivert.log"
echo " (pdivert flipped the last payload byte and fixed the ICMP checksum)"
echo
bold "--- 8. shutdown ---"
bold "--- 9. shutdown ---"
p0 "pkill pdivert; /opt/pf/pfctl -f /dev/null; /opt/pf/pfctl -d; echo 'pf disabled'"
p0 "lsmod | grep '^pf '"
echo
+1 -1
View File
@@ -34,7 +34,7 @@ ANC=$(pfput /tmp/g4anc.conf) || { bad "anchor conf upload"; finish; }
pf0 "/opt/pf/pfctl -a restricted -f $ANC" || { bad "anchor ruleset load"; finish; }
pf0 '/opt/pf/pfctl -sr' | grep -q 'anchor "restricted"' && ok "anchor rule in main ruleset" || bad "anchor rule missing"
pf0 '/opt/pf/pfctl -a restricted -sr' | grep -q 'keep state' && ok "anchor rules listed" || bad "anchor rules missing"
pf0 '/opt/pf/pfctl -a restricted -sr' | grep -q 'proto icmp from' && ok "anchor rules listed" || bad "anchor rules missing"
# the request must pass through the anchor (the block rule would drop it)
cli "ping -c2 -W2 $SRV_IP >/dev/null 2>&1" && ok "anchor pass honoured (request allowed)" \
+8 -3
View File
@@ -9,21 +9,26 @@
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
FAILED=0
EGRESS_IP=$(pf0 'ip -4 addr show egress | grep -o "inet [0-9.]*" | head -1 | cut -d" " -f2')
# 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 <<C
pass out quick on egress from 10.0.1.0/24 to any nat-to (egress)
pass out quick on egress from 10.0.1.0/24 to any nat-to ($EGRESS_DEV)
pass in quick on ingress proto icmp keep state
pass out quick on egress proto icmp keep state
C
pfload /tmp/g05.conf || { bad "nat rule load"; finish; }
pf0 '/opt/pf/pfctl -sr' | grep -q 'nat-to' && ok "nat-to rule listed" || bad "nat-to not listed"
pf0 'tcpdump -ni egress -c 4 -e icmp > /tmp/nat.td 2>&1 &'
pf0 "tcpdump -ni $EGRESS_DEV -c 4 -e icmp > /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
+24 -23
View File
@@ -1,50 +1,51 @@
#!/usr/bin/env bash
# M6 gate: soak + unload/leak discipline.
#
# 1) load pf, ruleset with states/tables/anchors, drive mixed traffic ~20s
# 1) load pf with a stateful ruleset, drive mixed traffic for ~20s
# 2) assert no BUG/WARNING/Oops in dmesg
# 3) disable, rmmod; assert module unloads cleanly (no refcount hang)
# 4) insmod/rmmod cycle x2; assert slab objects for pf caches return to the
# pre-load baseline (no leak), and that a reload works (timer/notifier
# re-registration is idempotent)
# 3) disable + rmmod: the module must unload cleanly (no refcount leak)
# 4) insmod/rmmod cycles: slab objects for the pf caches must return to the
# pre-load baseline (no leak) and a reload must work
set -uo pipefail; cd "$(dirname "$0")"; . ./lib.sh
FAILED=0
slab_snapshot() {
pf0 "grep -E '^(pf|pfk)' /proc/slabinfo | awk '{print \$1, \$3}'" | sort
}
HELPER=$(pfput ./soak-helper.sh) || { bad "helper upload"; finish; }
pf0 "chmod +x $HELPER" >/dev/null
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true'
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
BEFORE=$(slab_snapshot)
BEFORE=$(pf0 "$HELPER slab")
cat > /tmp/g14.conf <<C
table <peers> { $CLI_IP }
anchor "s"
pass in quick on ingress proto icmp from <peers> keep state
pass out quick proto icmp keep state
pass out quick on egress proto icmp keep state
pass out quick proto tcp from any to any port 22 keep state
C
pfload /tmp/g14.conf || { bad soak ruleset; finish; }
pfload /tmp/g14.conf || { bad "soak ruleset"; finish; }
# mixed traffic: ICMP + TCP handshakes, 20s
cli "ping -q -c 30 -i 0.2 $SRV_IP >/dev/null 2>&1 &" 2>/dev/null
# mixed traffic: ICMP + TCP handshakes for ~20s
cli "ping -q -c 40 -i 0.5 $SRV_IP >/dev/null 2>&1" 2>/dev/null
cli "for i in 1 2 3 4 5; do nc -w1 -z $SRV_IP 22; done" 2>/dev/null
sleep 22
ST=$(pf0 '/opt/pf/pfctl -s states | wc -l')
ST=$(pf0 "$HELPER states")
[ "${ST:-0}" -ge 1 ] && ok "states under load ($ST)" || bad "no states under load"
DMESG=$(pf0 "dmesg | grep -cE 'BUG:|WARNING:|Oops|general protection'")
[ "${DMESG:-1}" = 0 ] && ok "no kernel BUG/WARN/Oops" || bad "dmesg anomalies ($DMESG)"
ANOM=$(pf0 "$HELPER dmesg")
if [ -z "$ANOM" ]; then
ok "no kernel BUG/WARN/Oops"
else
bad "dmesg anomalies:"
printf '%s\n' "$ANOM" | sed 's/^/ /'
fi
pf0 '/opt/pf/pfctl -d; /opt/pf/pfctl -f /dev/null' >/dev/null
pf0 'rmmod pf && echo UNLOAD_OK' | grep -q UNLOAD_OK && ok "clean unload (refcounts released)" || bad "rmmod failed"
sleep 1
pf0 'insmod /tmp/pf.ko 2>/dev/null || insmod /opt/pf/pf.ko; true; (/opt/pf/pfctl -s info >/dev/null && echo RELOAD_OK)' | grep -q RELOAD_OK && ok "reload after unload" || bad "reload failed"
pf0 'insmod /opt/pf/pf.ko 2>/dev/null || insmod /tmp/pf.ko; true; (/opt/pf/pfctl -s info >/dev/null && echo RELOAD_OK)' \
| grep -q RELOAD_OK && ok "reload after unload" || bad "reload failed"
pf0 'rmmod pf && echo UNLOAD_OK2' | grep -q UNLOAD_OK2 && ok "second unload" || bad "second rmmod"
sleep 1
AFTER=$(slab_snapshot)
# compare per-cache object counts if they exist on both sides
AFTER=$(pf0 "$HELPER slab")
LEAK=$(join <(echo "$BEFORE") <(echo "$AFTER") 2>/dev/null | awk '$3-$2 > 0 {print $1, $3-$2}')
if [ -n "$LEAK" ]; then bad "slab growth after cycles: $LEAK"; else ok "no slab growth after unload cycles"; fi
finish
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Helper executed ON pf0 by 14-soak.sh (uploaded with pfput to avoid quoting
# hell through two ssh hops).
# $1 = "slab" | "dmesg" | "states"
case "${1:-}" in
slab)
grep -E '^(pf|pfk)' /proc/slabinfo | tr -s ' ' | cut -d' ' -f1,3 | sort
;;
dmesg)
# the CPU's Speculative Return Stack Overflow advisory is a kernel
# build/CPU feature notice, not a fault of this module
dmesg | grep -E 'BUG:|WARNING:|Oops|general protection|kernel BUG' \
| grep -v 'Speculative Return Stack Overflow' || true
;;
states)
/opt/pf/pfctl -s states 2>/dev/null | grep -c 'icmp' || true
;;
esac
+82
View File
@@ -1562,6 +1562,84 @@ ifa_exists(const char *ifa_name)
return (NULL);
}
/*
* PFLINUX: interface group emulation.
*
* OpenBSD creates the "ingress" and "egress" interface groups (ifgroups)
* and pfctl expands "(egress)" in a pool into the addresses of the group
* members via the SIOCGIFGMEMB socket ioctl. Linux has neither ifgroups
* nor that ioctl, so resolve the two groups locally:
*
* egress -> the interface of the default route (the same interface the
* kernel module marks as the pf "egress" interface)
* ingress -> every non-loopback interface
*/
static struct node_host *
ifa_group_members(const char *group, int flags)
{
struct node_host *n, *h = NULL, *c;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
int s;
if (iftab == NULL)
ifa_load();
if (!strcmp(group, "egress")) {
/* source address of the default route */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(9);
sin.sin_addr.s_addr = htonl(0x01010101); /* discard route */
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) != -1) {
if (connect(s, (struct sockaddr *)&sin,
sizeof(sin)) == 0 &&
getsockname(s, (struct sockaddr *)&sin, &slen) == 0) {
close(s);
for (n = iftab; n; n = n->next) {
if (n->af != AF_INET)
continue;
if (n->addr.v.a.addr.v4.s_addr !=
sin.sin_addr.s_addr)
continue;
if ((c = calloc(1, sizeof(*c))) == NULL)
err(1, "calloc");
*c = *n;
c->next = NULL;
c->tail = c;
return (c);
}
return (NULL);
}
close(s);
}
return (NULL);
}
/* "ingress" and any other group: every non-loopback interface */
for (n = iftab; n; n = n->next) {
if (n->af == AF_LINK)
continue;
if (n->ifa_flags & IFF_LOOPBACK)
continue;
if ((flags & PFI_AFLAG_BROADCAST) && n->af != AF_INET)
continue;
if ((c = calloc(1, sizeof(*c))) == NULL)
err(1, "calloc");
*c = *n;
c->next = NULL;
c->tail = c;
if (h == NULL)
h = c;
else {
h->tail->next = c;
h->tail = c->tail;
}
}
return (h);
}
struct node_host *
ifa_grouplookup(const char *ifa_name, int flags)
{
@@ -1576,6 +1654,10 @@ ifa_grouplookup(const char *ifa_name, int flags)
strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
close(s);
/* PFLINUX: interface group emulation (see above) */
if (!strcmp(ifa_name, "ingress") ||
!strcmp(ifa_name, "egress"))
return (ifa_group_members(ifa_name, flags));
return (NULL);
}