git: 9f44a47fd079 - main - ipfw(8): add ioctl/instruction generation tests
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 13 Jun 2023 11:55:42 UTC
The branch main has been updated by melifaro:
URL: https://cgit.FreeBSD.org/src/commit/?id=9f44a47fd07924afc035991af15d84e6585dea4f
commit 9f44a47fd07924afc035991af15d84e6585dea4f
Author: Alexander V. Chernikov <melifaro@FreeBSD.org>
AuthorDate: 2023-06-11 08:12:04 +0000
Commit: Alexander V. Chernikov <melifaro@FreeBSD.org>
CommitDate: 2023-06-13 11:55:37 +0000
ipfw(8): add ioctl/instruction generation tests
Differential Revision: https://reviews.freebsd.org/D40488
MFC after: 2 weeks
---
etc/mtree/BSD.tests.dist | 2 +
sbin/ipfw/ipfw2.c | 47 +-
sbin/ipfw/ipfw2.h | 1 +
sbin/ipfw/main.c | 6 +-
sbin/ipfw/tests/Makefile | 5 +
sbin/ipfw/tests/test_add_rule.py | 400 +++++++++++++++
tests/atf_python/sys/Makefile | 2 +-
tests/atf_python/sys/netpfil/Makefile | 11 +
tests/atf_python/sys/netpfil/__init__.py | 0
tests/atf_python/sys/netpfil/ipfw/Makefile | 12 +
tests/atf_python/sys/netpfil/ipfw/__init__.py | 0
tests/atf_python/sys/netpfil/ipfw/insn_headers.py | 198 ++++++++
tests/atf_python/sys/netpfil/ipfw/insns.py | 555 +++++++++++++++++++++
tests/atf_python/sys/netpfil/ipfw/ioctl.py | 505 +++++++++++++++++++
tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py | 90 ++++
tests/atf_python/sys/netpfil/ipfw/ipfw.py | 118 +++++
tests/atf_python/sys/netpfil/ipfw/utils.py | 61 +++
17 files changed, 2007 insertions(+), 6 deletions(-)
diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 8dc52086fe33..8b9d0ac6bccd 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -450,6 +450,8 @@
..
ifconfig
..
+ ipfw
+ ..
md5
..
mdconfig
diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c
index 683465a024bc..36f39beba5bb 100644
--- a/sbin/ipfw/ipfw2.c
+++ b/sbin/ipfw/ipfw2.c
@@ -587,6 +587,13 @@ stringnum_cmp(const char *a, const char *b)
return (strcmp(a, b));
}
+struct debug_header {
+ uint16_t cmd_type;
+ uint16_t spare1;
+ uint32_t opt_name;
+ uint32_t total_len;
+ uint32_t spare2;
+};
/*
* conditionally runs the command.
@@ -597,8 +604,18 @@ do_cmd(int optname, void *optval, uintptr_t optlen)
{
int i;
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 1,
+ .opt_name = optname,
+ .total_len = optlen + sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, optval, optlen);
+ }
+
if (g_co.test_only)
- return 0;
+ return (0);
if (ipfw_socket == -1)
ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
@@ -617,7 +634,7 @@ do_cmd(int optname, void *optval, uintptr_t optlen)
} else {
i = setsockopt(ipfw_socket, IPPROTO_IP, optname, optval, optlen);
}
- return i;
+ return (i);
}
/*
@@ -634,6 +651,18 @@ int
do_set3(int optname, ip_fw3_opheader *op3, size_t optlen)
{
+ op3->opcode = optname;
+
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 2,
+ .opt_name = optname,
+ .total_len = optlen, sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, op3, optlen);
+ }
+
if (g_co.test_only)
return (0);
@@ -642,7 +671,6 @@ do_set3(int optname, ip_fw3_opheader *op3, size_t optlen)
if (ipfw_socket < 0)
err(EX_UNAVAILABLE, "socket");
- op3->opcode = optname;
return (setsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen));
}
@@ -663,6 +691,18 @@ do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen)
int error;
socklen_t len;
+ op3->opcode = optname;
+
+ if (g_co.debug_only) {
+ struct debug_header dbg = {
+ .cmd_type = 3,
+ .opt_name = optname,
+ .total_len = *optlen + sizeof(struct debug_header),
+ };
+ write(1, &dbg, sizeof(dbg));
+ write(1, op3, *optlen);
+ }
+
if (g_co.test_only)
return (0);
@@ -671,7 +711,6 @@ do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen)
if (ipfw_socket < 0)
err(EX_UNAVAILABLE, "socket");
- op3->opcode = optname;
len = *optlen;
error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, &len);
diff --git a/sbin/ipfw/ipfw2.h b/sbin/ipfw/ipfw2.h
index a554f9b9f6fc..92fa05ae14b2 100644
--- a/sbin/ipfw/ipfw2.h
+++ b/sbin/ipfw/ipfw2.h
@@ -48,6 +48,7 @@ struct cmdline_opts {
int test_only; /* only check syntax */
int comment_only; /* only print action and comment */
int verbose; /* be verbose on some commands */
+ int debug_only; /* output ioctl i/o on stdout */
/* The options below can have multiple values. */
diff --git a/sbin/ipfw/main.c b/sbin/ipfw/main.c
index 577224047cd0..b1bed5ad008c 100644
--- a/sbin/ipfw/main.c
+++ b/sbin/ipfw/main.c
@@ -277,7 +277,7 @@ ipfw_main(int oldac, char **oldav)
optind = optreset = 1; /* restart getopt() */
if (is_ipfw()) {
- while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtv")) != -1)
+ while ((ch = getopt(ac, av, "abcdDefhinNp:qs:STtvx")) != -1)
switch (ch) {
case 'a':
do_acct = 1;
@@ -354,6 +354,10 @@ ipfw_main(int oldac, char **oldav)
g_co.verbose = 1;
break;
+ case 'x': /* debug output */
+ g_co.debug_only = 1;
+ break;
+
default:
free(save_av);
return 1;
diff --git a/sbin/ipfw/tests/Makefile b/sbin/ipfw/tests/Makefile
new file mode 100644
index 000000000000..987410f5d710
--- /dev/null
+++ b/sbin/ipfw/tests/Makefile
@@ -0,0 +1,5 @@
+PACKAGE= tests
+
+ATF_TESTS_PYTEST+= test_add_rule.py
+
+.include <bsd.test.mk>
diff --git a/sbin/ipfw/tests/test_add_rule.py b/sbin/ipfw/tests/test_add_rule.py
new file mode 100755
index 000000000000..65b4e7d33646
--- /dev/null
+++ b/sbin/ipfw/tests/test_add_rule.py
@@ -0,0 +1,400 @@
+import errno
+import json
+import os
+import socket
+import struct
+import subprocess
+import sys
+from ctypes import c_byte
+from ctypes import c_char
+from ctypes import c_int
+from ctypes import c_long
+from ctypes import c_uint32
+from ctypes import c_uint8
+from ctypes import c_ulong
+from ctypes import c_ushort
+from ctypes import sizeof
+from ctypes import Structure
+from enum import Enum
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import NamedTuple
+from typing import Optional
+from typing import Union
+
+import pytest
+from atf_python.sys.netpfil.ipfw.insns import Icmp6RejectCode
+from atf_python.sys.netpfil.ipfw.insns import IcmpRejectCode
+from atf_python.sys.netpfil.ipfw.insns import Insn
+from atf_python.sys.netpfil.ipfw.insns import InsnComment
+from atf_python.sys.netpfil.ipfw.insns import InsnEmpty
+from atf_python.sys.netpfil.ipfw.insns import InsnIp
+from atf_python.sys.netpfil.ipfw.insns import InsnIp6
+from atf_python.sys.netpfil.ipfw.insns import InsnPorts
+from atf_python.sys.netpfil.ipfw.insns import InsnProb
+from atf_python.sys.netpfil.ipfw.insns import InsnProto
+from atf_python.sys.netpfil.ipfw.insns import InsnReject
+from atf_python.sys.netpfil.ipfw.insns import InsnTable
+from atf_python.sys.netpfil.ipfw.insns import IpFwOpcode
+from atf_python.sys.netpfil.ipfw.ioctl import CTlv
+from atf_python.sys.netpfil.ipfw.ioctl import CTlvRule
+from atf_python.sys.netpfil.ipfw.ioctl import IpFwTlvType
+from atf_python.sys.netpfil.ipfw.ioctl import IpFwXRule
+from atf_python.sys.netpfil.ipfw.ioctl import NTlv
+from atf_python.sys.netpfil.ipfw.ioctl import Op3CmdType
+from atf_python.sys.netpfil.ipfw.ioctl import RawRule
+from atf_python.sys.netpfil.ipfw.ipfw import DebugIoReader
+from atf_python.sys.netpfil.ipfw.utils import enum_from_int
+from atf_python.utils import BaseTest
+
+
+IPFW_PATH = "/sbin/ipfw"
+
+
+def differ(w_obj, g_obj, w_stack=[], g_stack=[]):
+ if bytes(w_obj) == bytes(g_obj):
+ return True
+ num_objects = 0
+ for i, w_child in enumerate(w_obj.obj_list):
+ if i > len(g_obj.obj_list):
+ print("MISSING object from chain {}".format(" / ".join(w_stack)))
+ w_child.print_obj()
+ print("==========================")
+ return False
+ g_child = g_obj.obj_list[i]
+ if bytes(w_child) == bytes(g_child):
+ num_objects += 1
+ continue
+ w_stack.append(w_obj.obj_name)
+ g_stack.append(g_obj.obj_name)
+ if not differ(w_child, g_child, w_stack, g_stack):
+ return False
+ break
+ if num_objects == len(w_obj.obj_list) and num_objects < len(g_obj.obj_list):
+ g_child = g_obj.obj_list[num_objects]
+ print("EXTRA object from chain {}".format(" / ".join(g_stack)))
+ g_child.print_obj()
+ print("==========================")
+ return False
+ print("OBJECTS DIFFER")
+ print("WANTED CHAIN: {}".format(" / ".join(w_stack)))
+ w_obj.print_obj()
+ w_obj.print_obj_hex()
+ print("==========================")
+ print("GOT CHAIN: {}".format(" / ".join(g_stack)))
+ g_obj.print_obj()
+ g_obj.print_obj_hex()
+ print("==========================")
+ return False
+
+
+class TestAddRule(BaseTest):
+ def compile_rule(self, out):
+ tlvs = []
+ if "objs" in out:
+ tlvs.append(CTlv(IpFwTlvType.IPFW_TLV_TBLNAME_LIST, out["objs"]))
+ rule = RawRule(rulenum=out.get("rulenum", 0), obj_list=out["insns"])
+ tlvs.append(CTlvRule(obj_list=[rule]))
+ return IpFwXRule(Op3CmdType.IP_FW_XADD, tlvs)
+
+ def verify_rule(self, in_data: str, out_data):
+ # Prepare the desired output
+ expected = self.compile_rule(out_data)
+
+ reader = DebugIoReader(IPFW_PATH)
+ ioctls = reader.get_records(in_data)
+ assert len(ioctls) == 1 # Only 1 ioctl request expected
+ got = ioctls[0]
+
+ if not differ(expected, got):
+ print("=> CMD: {}".format(in_data))
+ print("=> WANTED:")
+ expected.print_obj()
+ print("==========================")
+ print("=> GOT:")
+ got.print_obj()
+ print("==========================")
+ assert bytes(got) == bytes(expected)
+
+ @pytest.mark.parametrize(
+ "rule",
+ [
+ pytest.param(
+ {
+ "in": "add 200 allow ip from any to any",
+ "out": {
+ "insns": [InsnEmpty(IpFwOpcode.O_ACCEPT)],
+ "rulenum": 200,
+ },
+ },
+ id="test_rulenum",
+ ),
+ pytest.param(
+ {
+ "in": "add allow ip from { 1.2.3.4 or 2.3.4.5 } to any",
+ "out": {
+ "insns": [
+ InsnIp(IpFwOpcode.O_IP_SRC, ip="1.2.3.4", is_or=True),
+ InsnIp(IpFwOpcode.O_IP_SRC, ip="2.3.4.5"),
+ InsnEmpty(IpFwOpcode.O_ACCEPT),
+ ],
+ },
+ },
+ id="test_or",
+ ),
+ pytest.param(
+ {
+ "in": "add allow ip from table(AAA) to table(BBB)",
+ "out": {
+ "objs": [
+ NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=1, name="AAA"),
+ NTlv(IpFwTlvType.IPFW_TLV_TBL_NAME, idx=2, name="BBB"),
+ ],
+ "insns": [
+ InsnTable(IpFwOpcode.O_IP_SRC_LOOKUP, arg1=1),
+ InsnTable(IpFwOpcode.O_IP_DST_LOOKUP, arg1=2),
+ InsnEmpty(IpFwOpcode.O_ACCEPT),
+ ],
+ },
+ },
+ id="test_tables",
+ ),
+ pytest.param(
+ {
+ "in": "add allow ip from any to 1.2.3.4 // test comment",
+ "out": {
+ "insns": [
+ InsnIp(IpFwOpcode.O_IP_DST, ip="1.2.3.4"),
+ InsnComment(comment="test comment"),
+ InsnEmpty(IpFwOpcode.O_ACCEPT),
+ ],
+ },
+ },
+ id="test_comment",
+ ),
+ ],
+ )
+ def test_add_rule(self, rule):
+ """Tests if the compiled rule is sane and matches the spec"""
+ self.verify_rule(rule["in"], rule["out"])
+
+ @pytest.mark.parametrize(
+ "action",
+ [
+ pytest.param(("allow", InsnEmpty(IpFwOpcode.O_ACCEPT)), id="test_allow"),
+ pytest.param(
+ (
+ "abort",
+ Insn(IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_REJECT_ABORT),
+ ),
+ id="abort",
+ ),
+ pytest.param(
+ (
+ "abort6",
+ Insn(
+ IpFwOpcode.O_UNREACH6, arg1=Icmp6RejectCode.ICMP6_UNREACH_ABORT
+ ),
+ ),
+ id="abort6",
+ ),
+ pytest.param(("accept", InsnEmpty(IpFwOpcode.O_ACCEPT)), id="accept"),
+ pytest.param(("deny", InsnEmpty(IpFwOpcode.O_DENY)), id="deny"),
+ pytest.param(
+ (
+ "reject",
+ Insn(IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_UNREACH_HOST),
+ ),
+ id="reject",
+ ),
+ pytest.param(
+ (
+ "reset",
+ Insn(IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_REJECT_RST),
+ ),
+ id="reset",
+ ),
+ pytest.param(
+ (
+ "reset6",
+ Insn(IpFwOpcode.O_UNREACH6, arg1=Icmp6RejectCode.ICMP6_UNREACH_RST),
+ ),
+ id="reset6",
+ ),
+ pytest.param(
+ (
+ "unreach port",
+ InsnReject(
+ IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_UNREACH_PORT
+ ),
+ ),
+ id="unreach_port",
+ ),
+ pytest.param(
+ (
+ "unreach port",
+ InsnReject(
+ IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_UNREACH_PORT
+ ),
+ ),
+ id="unreach_port",
+ ),
+ pytest.param(
+ (
+ "unreach needfrag",
+ InsnReject(
+ IpFwOpcode.O_REJECT, arg1=IcmpRejectCode.ICMP_UNREACH_NEEDFRAG
+ ),
+ ),
+ id="unreach_needfrag",
+ ),
+ pytest.param(
+ (
+ "unreach needfrag 1420",
+ InsnReject(
+ IpFwOpcode.O_REJECT,
+ arg1=IcmpRejectCode.ICMP_UNREACH_NEEDFRAG,
+ mtu=1420,
+ ),
+ ),
+ id="unreach_needfrag_mtu",
+ ),
+ pytest.param(
+ (
+ "unreach6 port",
+ Insn(
+ IpFwOpcode.O_UNREACH6,
+ arg1=Icmp6RejectCode.ICMP6_DST_UNREACH_NOPORT,
+ ),
+ ),
+ id="unreach6_port",
+ ),
+ pytest.param(("count", InsnEmpty(IpFwOpcode.O_COUNT)), id="count"),
+ # TOK_NAT
+ pytest.param(
+ ("queue 42", Insn(IpFwOpcode.O_QUEUE, arg1=42)), id="queue_42"
+ ),
+ pytest.param(("pipe 42", Insn(IpFwOpcode.O_PIPE, arg1=42)), id="pipe_42"),
+ pytest.param(
+ ("skipto 42", Insn(IpFwOpcode.O_SKIPTO, arg1=42)), id="skipto_42"
+ ),
+ pytest.param(
+ ("netgraph 42", Insn(IpFwOpcode.O_NETGRAPH, arg1=42)), id="netgraph_42"
+ ),
+ pytest.param(
+ ("ngtee 42", Insn(IpFwOpcode.O_NGTEE, arg1=42)), id="ngtee_42"
+ ),
+ pytest.param(
+ ("divert 42", Insn(IpFwOpcode.O_DIVERT, arg1=42)), id="divert_42"
+ ),
+ pytest.param(
+ ("divert natd", Insn(IpFwOpcode.O_DIVERT, arg1=8668)), id="divert_natd"
+ ),
+ pytest.param(("tee 42", Insn(IpFwOpcode.O_TEE, arg1=42)), id="tee_42"),
+ pytest.param(
+ ("call 420", Insn(IpFwOpcode.O_CALLRETURN, arg1=420)), id="call_420"
+ ),
+ # TOK_FORWARD
+ # TOK_COMMENT
+ pytest.param(
+ ("setfib 1", Insn(IpFwOpcode.O_SETFIB, arg1=1 | 0x8000)),
+ id="setfib_1",
+ marks=pytest.mark.skip("needs net.fibs>1"),
+ ),
+ pytest.param(
+ ("setdscp 42", Insn(IpFwOpcode.O_SETDSCP, arg1=42 | 0x8000)),
+ id="setdscp_42",
+ ),
+ pytest.param(("reass", InsnEmpty(IpFwOpcode.O_REASS)), id="reass"),
+ pytest.param(
+ ("return", InsnEmpty(IpFwOpcode.O_CALLRETURN, is_not=True)), id="return"
+ ),
+ ],
+ )
+ def test_add_action(self, action):
+ """Tests if the rule action is compiled properly"""
+ rule_in = "add {} ip from any to any".format(action[0])
+ rule_out = {"insns": [action[1]]}
+ self.verify_rule(rule_in, rule_out)
+
+ @pytest.mark.parametrize(
+ "insn",
+ [
+ pytest.param(
+ {
+ "in": "add prob 0.7 allow ip from any to any",
+ "out": InsnProb(prob=0.7),
+ },
+ id="test_prob",
+ ),
+ pytest.param(
+ {
+ "in": "add allow tcp from any to any",
+ "out": InsnProto(arg1=6),
+ },
+ id="test_proto",
+ ),
+ pytest.param(
+ {
+ "in": "add allow ip from any to any 57",
+ "out": InsnPorts(IpFwOpcode.O_IP_DSTPORT, port_pairs=[57, 57]),
+ },
+ id="test_ports",
+ ),
+ ],
+ )
+ def test_add_single_instruction(self, insn):
+ """Tests if the compiled rule is sane and matches the spec"""
+
+ # Prepare the desired output
+ out = {
+ "insns": [insn["out"], InsnEmpty(IpFwOpcode.O_ACCEPT)],
+ }
+ self.verify_rule(insn["in"], out)
+
+ @pytest.mark.parametrize(
+ "opcode",
+ [
+ pytest.param(IpFwOpcode.O_IP_SRCPORT, id="src"),
+ pytest.param(IpFwOpcode.O_IP_DSTPORT, id="dst"),
+ ],
+ )
+ @pytest.mark.parametrize(
+ "params",
+ [
+ pytest.param(
+ {
+ "in": "57",
+ "out": [(57, 57)],
+ },
+ id="test_single",
+ ),
+ pytest.param(
+ {
+ "in": "57-59",
+ "out": [(57, 59)],
+ },
+ id="test_range",
+ ),
+ pytest.param(
+ {
+ "in": "57-59,41",
+ "out": [(57, 59), (41, 41)],
+ },
+ id="test_ranges",
+ ),
+ ],
+ )
+ def test_add_ports(self, params, opcode):
+ if opcode == IpFwOpcode.O_IP_DSTPORT:
+ txt = "add allow ip from any to any " + params["in"]
+ else:
+ txt = "add allow ip from any " + params["in"] + " to any"
+ out = {
+ "insns": [
+ InsnPorts(opcode, port_pairs=params["out"]),
+ InsnEmpty(IpFwOpcode.O_ACCEPT),
+ ]
+ }
+ self.verify_rule(txt, out)
diff --git a/tests/atf_python/sys/Makefile b/tests/atf_python/sys/Makefile
index 540c3803ada5..85f66a85088e 100644
--- a/tests/atf_python/sys/Makefile
+++ b/tests/atf_python/sys/Makefile
@@ -3,7 +3,7 @@
.PATH: ${.CURDIR}
FILES= __init__.py
-SUBDIR= net netlink
+SUBDIR= net netlink netpfil
.include <bsd.own.mk>
FILESDIR= ${TESTSBASE}/atf_python/sys
diff --git a/tests/atf_python/sys/netpfil/Makefile b/tests/atf_python/sys/netpfil/Makefile
new file mode 100644
index 000000000000..417a16d85359
--- /dev/null
+++ b/tests/atf_python/sys/netpfil/Makefile
@@ -0,0 +1,11 @@
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}
+
+FILES= __init__.py
+SUBDIR= ipfw
+
+.include <bsd.own.mk>
+FILESDIR= ${TESTSBASE}/atf_python/sys/netpfil
+
+.include <bsd.prog.mk>
diff --git a/tests/atf_python/sys/netpfil/__init__.py b/tests/atf_python/sys/netpfil/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/tests/atf_python/sys/netpfil/ipfw/Makefile b/tests/atf_python/sys/netpfil/ipfw/Makefile
new file mode 100644
index 000000000000..a85dc7de9417
--- /dev/null
+++ b/tests/atf_python/sys/netpfil/ipfw/Makefile
@@ -0,0 +1,12 @@
+.include <src.opts.mk>
+
+.PATH: ${.CURDIR}
+
+FILES= __init__.py insns.py insn_headers.py ioctl.py ioctl_headers.py \
+ ipfw.py utils.py
+
+.include <bsd.own.mk>
+FILESDIR= ${TESTSBASE}/atf_python/sys/netpfil/ipfw
+
+.include <bsd.prog.mk>
+
diff --git a/tests/atf_python/sys/netpfil/ipfw/__init__.py b/tests/atf_python/sys/netpfil/ipfw/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/tests/atf_python/sys/netpfil/ipfw/insn_headers.py b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py
new file mode 100644
index 000000000000..5c160d0758d6
--- /dev/null
+++ b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py
@@ -0,0 +1,198 @@
+from enum import Enum
+
+
+class IpFwOpcode(Enum):
+ O_NOP = 0
+ O_IP_SRC = 1
+ O_IP_SRC_MASK = 2
+ O_IP_SRC_ME = 3
+ O_IP_SRC_SET = 4
+ O_IP_DST = 5
+ O_IP_DST_MASK = 6
+ O_IP_DST_ME = 7
+ O_IP_DST_SET = 8
+ O_IP_SRCPORT = 9
+ O_IP_DSTPORT = 10
+ O_PROTO = 11
+ O_MACADDR2 = 12
+ O_MAC_TYPE = 13
+ O_LAYER2 = 14
+ O_IN = 15
+ O_FRAG = 16
+ O_RECV = 17
+ O_XMIT = 18
+ O_VIA = 19
+ O_IPOPT = 20
+ O_IPLEN = 21
+ O_IPID = 22
+ O_IPTOS = 23
+ O_IPPRECEDENCE = 24
+ O_IPTTL = 25
+ O_IPVER = 26
+ O_UID = 27
+ O_GID = 28
+ O_ESTAB = 29
+ O_TCPFLAGS = 30
+ O_TCPWIN = 31
+ O_TCPSEQ = 32
+ O_TCPACK = 33
+ O_ICMPTYPE = 34
+ O_TCPOPTS = 35
+ O_VERREVPATH = 36
+ O_VERSRCREACH = 37
+ O_PROBE_STATE = 38
+ O_KEEP_STATE = 39
+ O_LIMIT = 40
+ O_LIMIT_PARENT = 41
+ O_LOG = 42
+ O_PROB = 43
+ O_CHECK_STATE = 44
+ O_ACCEPT = 45
+ O_DENY = 46
+ O_REJECT = 47
+ O_COUNT = 48
+ O_SKIPTO = 49
+ O_PIPE = 50
+ O_QUEUE = 51
+ O_DIVERT = 52
+ O_TEE = 53
+ O_FORWARD_IP = 54
+ O_FORWARD_MAC = 55
+ O_NAT = 56
+ O_REASS = 57
+ O_IPSEC = 58
+ O_IP_SRC_LOOKUP = 59
+ O_IP_DST_LOOKUP = 60
+ O_ANTISPOOF = 61
+ O_JAIL = 62
+ O_ALTQ = 63
+ O_DIVERTED = 64
+ O_TCPDATALEN = 65
+ O_IP6_SRC = 66
+ O_IP6_SRC_ME = 67
+ O_IP6_SRC_MASK = 68
+ O_IP6_DST = 69
+ O_IP6_DST_ME = 70
+ O_IP6_DST_MASK = 71
+ O_FLOW6ID = 72
+ O_ICMP6TYPE = 73
+ O_EXT_HDR = 74
+ O_IP6 = 75
+ O_NETGRAPH = 76
+ O_NGTEE = 77
+ O_IP4 = 78
+ O_UNREACH6 = 79
+ O_TAG = 80
+ O_TAGGED = 81
+ O_SETFIB = 82
+ O_FIB = 83
+ O_SOCKARG = 84
+ O_CALLRETURN = 85
+ O_FORWARD_IP6 = 86
+ O_DSCP = 87
+ O_SETDSCP = 88
+ O_IP_FLOW_LOOKUP = 89
+ O_EXTERNAL_ACTION = 90
+ O_EXTERNAL_INSTANCE = 91
+ O_EXTERNAL_DATA = 92
+ O_SKIP_ACTION = 93
+ O_TCPMSS = 94
+ O_MAC_SRC_LOOKUP = 95
+ O_MAC_DST_LOOKUP = 96
+ O_SETMARK = 97
+ O_MARK = 98
+ O_LAST_OPCODE = 99
+
+
+class Op3CmdType(Enum):
+ IP_FW_TABLE_XADD = 86
+ IP_FW_TABLE_XDEL = 87
+ IP_FW_TABLE_XGETSIZE = 88
+ IP_FW_TABLE_XLIST = 89
+ IP_FW_TABLE_XDESTROY = 90
+ IP_FW_TABLES_XLIST = 92
+ IP_FW_TABLE_XINFO = 93
+ IP_FW_TABLE_XFLUSH = 94
+ IP_FW_TABLE_XCREATE = 95
+ IP_FW_TABLE_XMODIFY = 96
+ IP_FW_XGET = 97
+ IP_FW_XADD = 98
+ IP_FW_XDEL = 99
+ IP_FW_XMOVE = 100
+ IP_FW_XZERO = 101
+ IP_FW_XRESETLOG = 102
+ IP_FW_SET_SWAP = 103
+ IP_FW_SET_MOVE = 104
+ IP_FW_SET_ENABLE = 105
+ IP_FW_TABLE_XFIND = 106
+ IP_FW_XIFLIST = 107
+ IP_FW_TABLES_ALIST = 108
+ IP_FW_TABLE_XSWAP = 109
+ IP_FW_TABLE_VLIST = 110
+ IP_FW_NAT44_XCONFIG = 111
+ IP_FW_NAT44_DESTROY = 112
+ IP_FW_NAT44_XGETCONFIG = 113
+ IP_FW_NAT44_LIST_NAT = 114
+ IP_FW_NAT44_XGETLOG = 115
+ IP_FW_DUMP_SOPTCODES = 116
+ IP_FW_DUMP_SRVOBJECTS = 117
+ IP_FW_NAT64STL_CREATE = 130
+ IP_FW_NAT64STL_DESTROY = 131
+ IP_FW_NAT64STL_CONFIG = 132
+ IP_FW_NAT64STL_LIST = 133
+ IP_FW_NAT64STL_STATS = 134
+ IP_FW_NAT64STL_RESET_STATS = 135
+ IP_FW_NAT64LSN_CREATE = 140
+ IP_FW_NAT64LSN_DESTROY = 141
+ IP_FW_NAT64LSN_CONFIG = 142
+ IP_FW_NAT64LSN_LIST = 143
+ IP_FW_NAT64LSN_STATS = 144
+ IP_FW_NAT64LSN_LIST_STATES = 145
+ IP_FW_NAT64LSN_RESET_STATS = 146
+ IP_FW_NPTV6_CREATE = 150
+ IP_FW_NPTV6_DESTROY = 151
+ IP_FW_NPTV6_CONFIG = 152
+ IP_FW_NPTV6_LIST = 153
+ IP_FW_NPTV6_STATS = 154
+ IP_FW_NPTV6_RESET_STATS = 155
+ IP_FW_NAT64CLAT_CREATE = 160
+ IP_FW_NAT64CLAT_DESTROY = 161
+ IP_FW_NAT64CLAT_CONFIG = 162
+ IP_FW_NAT64CLAT_LIST = 163
+ IP_FW_NAT64CLAT_STATS = 164
+ IP_FW_NAT64CLAT_RESET_STATS = 165
+
+
+class IcmpRejectCode(Enum):
+ ICMP_UNREACH_NET = 0
+ ICMP_UNREACH_HOST = 1
+ ICMP_UNREACH_PROTOCOL = 2
+ ICMP_UNREACH_PORT = 3
+ ICMP_UNREACH_NEEDFRAG = 4
+ ICMP_UNREACH_SRCFAIL = 5
+ ICMP_UNREACH_NET_UNKNOWN = 6
+ ICMP_UNREACH_HOST_UNKNOWN = 7
+ ICMP_UNREACH_ISOLATED = 8
+ ICMP_UNREACH_NET_PROHIB = 9
+ ICMP_UNREACH_HOST_PROHIB = 10
+ ICMP_UNREACH_TOSNET = 11
+ ICMP_UNREACH_TOSHOST = 12
+ ICMP_UNREACH_FILTER_PROHIB = 13
+ ICMP_UNREACH_HOST_PRECEDENCE = 14
+ ICMP_UNREACH_PRECEDENCE_CUTOFF = 15
+ ICMP_REJECT_RST = 256
+ ICMP_REJECT_ABORT = 257
+
+
+class Icmp6RejectCode(Enum):
+ ICMP6_DST_UNREACH_NOROUTE = 0
+ ICMP6_DST_UNREACH_ADMIN = 1
+ ICMP6_DST_UNREACH_BEYONDSCOPE = 2
+ ICMP6_DST_UNREACH_NOTNEIGHBOR = 2
+ ICMP6_DST_UNREACH_ADDR = 3
+ ICMP6_DST_UNREACH_NOPORT = 4
+ ICMP6_DST_UNREACH_POLICY = 5
+ ICMP6_DST_UNREACH_REJECT = 6
+ ICMP6_DST_UNREACH_SRCROUTE = 7
+ ICMP6_UNREACH_RST = 256
+ ICMP6_UNREACH_ABORT = 257
diff --git a/tests/atf_python/sys/netpfil/ipfw/insns.py b/tests/atf_python/sys/netpfil/ipfw/insns.py
new file mode 100644
index 000000000000..12f145f49393
--- /dev/null
+++ b/tests/atf_python/sys/netpfil/ipfw/insns.py
@@ -0,0 +1,555 @@
+#!/usr/bin/env python3
+import os
+import socket
+import struct
+import subprocess
+import sys
+from ctypes import c_byte
+from ctypes import c_char
+from ctypes import c_int
+from ctypes import c_long
+from ctypes import c_uint32
+from ctypes import c_uint8
+from ctypes import c_ulong
+from ctypes import c_ushort
+from ctypes import sizeof
+from ctypes import Structure
+from enum import Enum
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import NamedTuple
+from typing import Optional
+from typing import Union
+
+from atf_python.sys.netpfil.ipfw.insn_headers import IpFwOpcode
+from atf_python.sys.netpfil.ipfw.insn_headers import IcmpRejectCode
+from atf_python.sys.netpfil.ipfw.insn_headers import Icmp6RejectCode
+from atf_python.sys.netpfil.ipfw.utils import AttrDescr
+from atf_python.sys.netpfil.ipfw.utils import enum_or_int
+from atf_python.sys.netpfil.ipfw.utils import enum_from_int
+from atf_python.sys.netpfil.ipfw.utils import prepare_attrs_map
+
+
+insn_actions = (
+ IpFwOpcode.O_CHECK_STATE.value,
+ IpFwOpcode.O_REJECT.value,
+ IpFwOpcode.O_UNREACH6.value,
+ IpFwOpcode.O_ACCEPT.value,
+ IpFwOpcode.O_DENY.value,
+ IpFwOpcode.O_COUNT.value,
+ IpFwOpcode.O_NAT.value,
+ IpFwOpcode.O_QUEUE.value,
+ IpFwOpcode.O_PIPE.value,
+ IpFwOpcode.O_SKIPTO.value,
+ IpFwOpcode.O_NETGRAPH.value,
+ IpFwOpcode.O_NGTEE.value,
+ IpFwOpcode.O_DIVERT.value,
+ IpFwOpcode.O_TEE.value,
+ IpFwOpcode.O_CALLRETURN.value,
+ IpFwOpcode.O_FORWARD_IP.value,
+ IpFwOpcode.O_FORWARD_IP6.value,
+ IpFwOpcode.O_SETFIB.value,
+ IpFwOpcode.O_SETDSCP.value,
+ IpFwOpcode.O_REASS.value,
+ IpFwOpcode.O_SETMARK.value,
+ IpFwOpcode.O_EXTERNAL_ACTION.value,
+)
+
+
+class IpFwInsn(Structure):
+ _fields_ = [
+ ("opcode", c_uint8),
+ ("length", c_uint8),
+ ("arg1", c_ushort),
+ ]
+
+
+class BaseInsn(object):
+ obj_enum_class = IpFwOpcode
+
+ def __init__(self, opcode, is_or, is_not, arg1):
+ if isinstance(opcode, Enum):
+ self.obj_type = opcode.value
+ self._enum = opcode
+ else:
+ self.obj_type = opcode
+ self._enum = enum_from_int(self.obj_enum_class, self.obj_type)
+ self.is_or = is_or
+ self.is_not = is_not
+ self.arg1 = arg1
+ self.is_action = self.obj_type in insn_actions
+ self.ilen = 1
+ self.obj_list = []
+
+ @property
+ def obj_name(self):
+ if self._enum is not None:
+ return self._enum.name
+ else:
+ return "opcode#{}".format(self.obj_type)
+
+ @staticmethod
+ def get_insn_len(data: bytes) -> int:
+ (opcode_len,) = struct.unpack("@B", data[1:2])
+ return opcode_len & 0x3F
+
+ @classmethod
+ def _validate_len(cls, data, valid_options=None):
+ if len(data) < 4:
+ raise ValueError("opcode too short")
+ opcode_type, opcode_len = struct.unpack("@BB", data[:2])
+ if len(data) != ((opcode_len & 0x3F) * 4):
+ raise ValueError("wrong length")
+ if valid_options and len(data) not in valid_options:
+ raise ValueError(
+ "len {} not in {} for {}".format(
+ len(data), valid_options,
+ enum_from_int(cls.obj_enum_class, data[0])
+ )
+ )
+
+ @classmethod
+ def _validate(cls, data):
+ cls._validate_len(data)
+
+ @classmethod
+ def _parse(cls, data):
+ insn = IpFwInsn.from_buffer_copy(data[:4])
+ is_or = (insn.length & 0x40) != 0
+ is_not = (insn.length & 0x80) != 0
+ return cls(opcode=insn.opcode, is_or=is_or, is_not=is_not, arg1=insn.arg1)
+
*** 1231 LINES SKIPPED ***