--- /dev/null
+# $OpenBSD: Makefile,v 1.1.1.1 2022/04/27 18:28:40 bluhm Exp $
+
+# Copyright (c) 2022 Alexander Bluhm <bluhm@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Set up two loopback interfaces in different routing domains.
+# One loopback interface has a allow-opts pf rule, the other has
+# default pass policy. Send packets with IP options and IPv6
+# option header and check wheter tcpdump finds them on lo or pflog.
+
+# The following ports must be installed:
+#
+# scapy powerful interactive packet manipulation in python
+
+.if ! exists(/usr/local/bin/scapy)
+regress:
+ @echo Install scapy package to run this regress.
+ @echo SKIPPED
+.endif
+
+# This test uses routing domain and interface number 11 and 12.
+# Adjust it here, if you want to use something else.
+N1 = 11
+N2 = 12
+NUMS = ${N1} ${N2}
+
+.include <bsd.own.mk>
+
+.if ! (make(clean) || make(cleandir) || make(obj))
+
+PF_STATUS != ${SUDO} pfctl -si | sed -n 's/^Status: \([^ ]*\) .*/\1/p'
+.if empty(PF_STATUS:MEnabled)
+regress:
+ @echo pf status: "${PF_STATUS}"
+ @echo Enable pf to run this regress.
+ @echo SKIPPED
+.endif
+
+PF_SKIP != ${SUDO} pfctl -sI -v | sed -n 's/ (skip)//p'
+.if ! empty(PF_SKIP:Mlo*:Nlo0)
+regress:
+ @echo pf skip: "${PF_SKIP}"
+ @echo Do not set skip on interface lo, lo${N1}, or lo${N2}.
+ @echo SKIPPED
+.endif
+
+PF_ANCHOR != ${SUDO} pfctl -sr | sed -n 's/^anchor "\([^"]*\)" all$$/\1/p'
+.if empty(PF_ANCHOR:Mregress)
+regress:
+ @echo pf anchor: "${PF_ANCHOR}"
+ @echo Need anchor '"regress"' in pf.conf to load additional rules.
+ @echo SKIPPED
+.endif
+
+.endif
+
+.PHONY: busy-rdomains ifconfig unconfig pfctl
+
+REGRESS_SETUP_ONCE += busy-rdomains
+busy-rdomains:
+ # Check if rdomains are busy.
+.for n in ${NUMS}
+ @if /sbin/ifconfig | grep -v '^lo$n:' | grep ' rdomain $n '; then\
+ echo routing domain $n is already used >&2; exit 1; fi
+.endfor
+
+REGRESS_SETUP_ONCE += ifconfig
+ifconfig: unconfig
+ # Create and configure loopback interfaces.
+.for n in ${NUMS}
+ ${SUDO} ifconfig lo$n rdomain $n
+ ${SUDO} ifconfig lo$n inet 127.0.0.1/8
+ ${SUDO} ifconfig lo$n inet 127.0.0.$n alias
+ ${SUDO} ifconfig lo$n inet6 ::1/128
+ ${SUDO} ifconfig lo$n inet6 fe80::$n/64
+.endfor
+ ${SUDO} route -n -T ${N1} add -inet -host 127.0.0.${N2} 127.0.0.1
+ ${SUDO} route -n -T ${N2} add -inet -host 127.0.0.${N1} 127.0.0.1
+
+REGRESS_CLEANUP += unconfig
+unconfig: stamp-stop
+ # Destroy interfaces.
+.for n in ${NUMS}
+ -${SUDO} ifconfig lo$n inet 127.0.0.1 delete
+ -${SUDO} ifconfig lo$n inet 127.0.0.$n delete
+ -${SUDO} ifconfig lo$n inet6 ::1 delete
+ -${SUDO} ifconfig lo$n inet6 fe80::$n/64 delete
+.endfor
+ rm -f stamp-ifconfig
+
+addr.py: Makefile
+ # Create python include file containing the addresses.
+ rm -f $@ $@.tmp
+.for var in N1 N2
+ echo '${var}="${${var}}"' >>$@.tmp
+ echo 'IF_${var}="lo${${var}}"' >>$@.tmp
+ echo 'ADDR_${var}="127.0.0.${${var}}"' >>$@.tmp
+ echo 'ADDR6_${var}="fe80::${${var}}"' >>$@.tmp
+.endfor
+ mv $@.tmp $@
+
+REGRESS_SETUP_ONCE += pfctl
+pfctl: addr.py pf.conf
+ # Load the pf rules into the kernel.
+ cat addr.py ${.CURDIR}/pf.conf | /sbin/pfctl -n -f -
+ cat addr.py ${.CURDIR}/pf.conf | ${SUDO} pfctl -a regress -f -
+
+# run tcpdump on lo and pflog device
+DUMPCMD = /usr/sbin/tcpdump -l -e -vvv -s 2048 -ni
+
+stamp-bpf: stamp-bpf-lo${N1} stamp-bpf-lo${N2} stamp-bpf-pflog0
+ sleep 2 # XXX
+ @date >$@
+
+.for i in lo${N1} lo${N2} pflog0
+
+stamp-bpf-$i: stamp-ifconfig
+ rm -f $i.tcpdump
+ ${SUDO} pkill -f '^${DUMPCMD} $i' || true
+ ${SUDO} ${DUMPCMD} $i >$i.tcpdump &
+ rm -f stamp-stop
+ @date >$@
+
+.endfor
+
+stamp-stop:
+ sleep 2 # XXX
+ -${SUDO} pkill -f '^${DUMPCMD}'
+ rm -f stamp-bpf*
+ @date >$@
+
+# Set variables so that make runs with and without obj directory.
+# Only do that if necessary to keep visible output short.
+.if ${.CURDIR} == ${.OBJDIR}
+PYTHON = python3 -u ./
+PYPATH =
+.else
+PYTHON = python3 -u ${.CURDIR}/
+PYPATH = PYTHONPATH=${.OBJDIR}
+.endif
+
+# ping
+
+REGRESS_TARGETS += run-ping
+run-ping: stamp-bpf
+ # Ping localhost on loopback
+ /sbin/ping -n -w 1 -c 1 -V ${N1} 127.0.0.${N1}
+ /sbin/ping -n -w 1 -c 1 -V ${N2} 127.0.0.${N2}
+
+REGRESS_TARGETS += run-ping6
+run-ping6: stamp-bpf
+ # Ping localhost on loopback
+ /sbin/ping6 -n -w 1 -c 1 -V ${N1} fe80::${N1}%lo${N1}
+ /sbin/ping6 -n -w 1 -c 1 -V ${N2} fe80::${N2}%lo${N2}
+
+REGRESS_TARGETS += run-bpf-ping
+run-bpf-ping: stamp-stop
+ # Check that ping packet went through loopback.
+ grep ' 127.0.0.${N1}: icmp: echo request' lo${N1}.tcpdump
+ grep ' 127.0.0.${N2}: icmp: echo request' lo${N2}.tcpdump
+ grep ' fe80:.*::${N1}: icmp6: echo request' lo${N1}.tcpdump
+ grep ' fe80:.*::${N2}: icmp6: echo request' lo${N2}.tcpdump
+ ! grep ': icmp: echo request' pflog0.tcpdump
+ ! grep ': icmp6: echo request' pflog0.tcpdump
+
+# ping with RR option
+
+REGRESS_TARGETS += run-ping-record
+run-ping-record: stamp-bpf
+ # Ping localhost with record route option
+ /sbin/ping -n -w 1 -c 1 -V ${N1} -R 127.0.0.${N1}
+ ! /sbin/ping -n -w 1 -c 1 -V ${N2} -R 127.0.0.${N2}
+
+REGRESS_TARGETS += run-bpf-ping-record
+run-bpf-ping-record: stamp-stop
+ # Check that ping packet with options is in pflog0.
+ grep ' 127.0.0.${N1}: icmp: echo request .*\
+ optlen=40 RR' lo${N1}.tcpdump
+ grep ' 127.0.0.${N2}: icmp: echo request .*\
+ optlen=40 RR' pflog0.tcpdump
+
+# icmp
+
+REGRESS_TARGETS += run-icmp
+run-icmp: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp.py N2
+
+REGRESS_TARGETS += run-icmp6
+run-icmp6: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6.py N2
+
+REGRESS_TARGETS += run-bpf-icmp
+run-bpf-icmp: stamp-stop
+ # Check that icmp packet went through loopback.
+ grep ' 127.0.0.${N1}: icmp: type-#6' lo${N1}.tcpdump
+ grep ' 127.0.0.${N2}: icmp: type-#6' lo${N2}.tcpdump
+ grep ' fe80::${N1}: icmp6: type-#6' lo${N1}.tcpdump
+ grep ' fe80::${N2}: icmp6: type-#6' lo${N2}.tcpdump
+ ! grep ': icmp: type-#6' pflog0.tcpdump
+ ! grep ': icmp6: type-#6' pflog0.tcpdump
+
+# option extension header
+
+REGRESS_TARGETS += run-icmp6-hop
+run-icmp6-hop: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6_hop.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6_hop.py N2
+
+REGRESS_TARGETS += run-icmp6-dst
+run-icmp6-dst: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6_dst.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6_dst.py N2
+
+REGRESS_TARGETS += run-bpf-ext
+run-bpf-ext: stamp-stop
+ # Check that icmp6 packet with extension headers were blocked
+ fgrep ' fe80::${N2}: HBH icmp6' pflog0.tcpdump
+ fgrep ' fe80::${N2}: DSTOPT icmp6' pflog0.tcpdump
+ ! grep fe80::${N1} pflog0.tcpdump
+
+# icmp with options
+
+REGRESS_TARGETS += run-icmp-pad
+run-icmp-pad: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp_pad.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp_pad.py N2
+
+REGRESS_TARGETS += run-icmp-eol
+run-icmp-eol: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp_eol.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp_eol.py N2
+
+REGRESS_TARGETS += run-icmp6-pad
+run-icmp6-pad: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6_hop_pad.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6_hop_pad.py N2
+
+REGRESS_TARGETS += run-icmp-ra
+run-icmp-ra: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp_ra.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp_ra.py N2
+
+REGRESS_TARGETS += run-icmp6-ra
+run-icmp6-ra: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6_hop_ra.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6_hop_ra.py N2
+
+REGRESS_TARGETS += run-icmp-bad
+run-icmp-bad: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp_bad.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp_bad.py N2
+
+REGRESS_TARGETS += run-icmp6-bad
+run-icmp6-bad: stamp-bpf
+ ${SUDO} ${PYPATH} /sbin/route -T ${N1} exec ${PYTHON}icmp6_hop_bad.py N1
+ ${SUDO} ${PYPATH} /sbin/route -T ${N2} exec ${PYTHON}icmp6_hop_bad.py N2
+
+REGRESS_TARGETS += run-bpf-opts
+run-bpf-opts: stamp-stop
+ # Check that icmp packet with options were blocked
+ grep ' 127.0.0.${N2}:.* optlen=4 NOP NOP NOP NOP)' pflog0.tcpdump
+ grep ' 127.0.0.${N2}:.* optlen=4 NOP EOL-2)' pflog0.tcpdump
+ grep ' 127.0.0.${N2}:.* optlen=8 NOP IPOPT-148{4} NOP ' pflog0.tcpdump
+ grep ' 127.0.0.${N2}:.* optlen=4 IPOPT-3{4})' pflog0.tcpdump
+ grep ' fe80::${N2}: HBH icmp6' pflog0.tcpdump
+ grep ' fe80::${N2}: HBH (rtalert: 0x0000) icmp6' pflog0.tcpdump
+ grep ' fe80::${N2}: HBH (type 0x03: len=0) icmp6' pflog0.tcpdump
+ ! grep '127.0.0.${N1}' pflog0.tcpdump
+ ! grep 'fe80::${N1}' pflog0.tcpdump
+
+CLEANFILES += addr.py *.pyc *.tcpdump *.log stamp-*
+
+.include <bsd.regress.mk>