aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBoris Lytochkin <lytboris@gmail.com>2026-03-01 18:54:24 +0000
committerAndrey V. Elsukov <ae@FreeBSD.org>2026-03-24 09:31:11 +0000
commita9b93531788cc125c928cc3488975f0330f75137 (patch)
treeb6ec303d4d35d0d671dcfa3c15c1215981454d48 /tests
parent37aa41dde130ad1cf4d39e08a6d3a06108889c27 (diff)
ipfw: add support for masked ip-address lookups
Current radix-based implementation of lookup tables in ipfw does not support non-contiguous prefixes while this type of lookup is needed to write CPU-effective firewall configurations. For some of the cases we can reach the goal using a masked table lookup by adding masked (e.g. zero non-significant bits) records into a table and then zero non-significant bits in lookup key prior to making a table lookup. Obtained from: Yandex LLC Relnotes: yes Sponsored by: Yandex LLC Differential Revision: https://reviews.freebsd.org/D53694 (cherry picked from commit 32cd3ee5901ea33d41ff550e5f40ce743c8d4165)
Diffstat (limited to 'tests')
-rw-r--r--tests/atf_python/sys/netpfil/ipfw/insn_headers.py37
-rw-r--r--tests/atf_python/sys/netpfil/ipfw/insns.py179
-rw-r--r--tests/atf_python/sys/netpfil/ipfw/ioctl.py1
-rw-r--r--tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py41
-rw-r--r--tests/sys/netpfil/common/utils.subr3
-rw-r--r--tests/sys/netpfil/ipfw/Makefile6
-rwxr-xr-xtests/sys/netpfil/ipfw/lookup.sh428
-rw-r--r--tests/sys/netpfil/ipfw/lookup_inetd.conf1
8 files changed, 666 insertions, 30 deletions
diff --git a/tests/atf_python/sys/netpfil/ipfw/insn_headers.py b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py
index 5c160d0758d6..e0eb6711323e 100644
--- a/tests/atf_python/sys/netpfil/ipfw/insn_headers.py
+++ b/tests/atf_python/sys/netpfil/ipfw/insn_headers.py
@@ -101,7 +101,8 @@ class IpFwOpcode(Enum):
O_MAC_DST_LOOKUP = 96
O_SETMARK = 97
O_MARK = 98
- O_LAST_OPCODE = 99
+ O_TABLE_LOOKUP = 99
+ O_LAST_OPCODE = 100
class Op3CmdType(Enum):
@@ -196,3 +197,37 @@ class Icmp6RejectCode(Enum):
ICMP6_DST_UNREACH_SRCROUTE = 7
ICMP6_UNREACH_RST = 256
ICMP6_UNREACH_ABORT = 257
+
+
+class IpFwTableLookupType(Enum):
+ LOOKUP_NONE = 0
+ LOOKUP_DST_IP = 1
+ LOOKUP_SRC_IP = 2
+ LOOKUP_DST_PORT = 3
+ LOOKUP_SRC_PORT = 4
+ LOOKUP_UID = 5
+ LOOKUP_JAIL = 6
+ LOOKUP_DSCP = 7
+ LOOKUP_DST_MAC = 8
+ LOOKUP_SRC_MAC = 9
+ LOOKUP_MARK = 10
+ LOOKUP_RULENUM = 11
+ LOOKUP_DST_IP4 = 12
+ LOOKUP_SRC_IP4 = 13
+ LOOKUP_DST_IP6 = 14
+ LOOKUP_SRC_IP6 = 15
+
+
+class IpFwTableValueType(Enum):
+ TVALUE_TAG = 0
+ TVALUE_PIPE = 1
+ TVALUE_DIVERT = 2
+ TVALUE_SKIPTO = 3
+ TVALUE_NETGRAPH = 4
+ TVALUE_FIB = 5
+ TVALUE_NAT = 6
+ TVALUE_NH4 = 7
+ TVALUE_DSCP = 8
+ TVALUE_LIMIT = 9
+ TVALUE_MARK = 10
+ TVALUE_NH6 = 11
diff --git a/tests/atf_python/sys/netpfil/ipfw/insns.py b/tests/atf_python/sys/netpfil/ipfw/insns.py
index f8a56de901ae..dbb9af659794 100644
--- a/tests/atf_python/sys/netpfil/ipfw/insns.py
+++ b/tests/atf_python/sys/netpfil/ipfw/insns.py
@@ -25,6 +25,8 @@ 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.insn_headers import IpFwTableLookupType
+from atf_python.sys.netpfil.ipfw.insn_headers import IpFwTableValueType
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
@@ -165,7 +167,7 @@ class BaseInsn(object):
hdr = IpFwInsn.from_buffer_copy(data[off : off + sizeof(IpFwInsn)])
insn_len = (hdr.length & 0x3F) * 4
if off + insn_len > len(data):
- raise ValueError("wrng length")
+ raise ValueError("wrong length")
# print("GET insn type {} len {}".format(hdr.opcode, insn_len))
attr = attr_map.get(hdr.opcode, None)
if attr is None:
@@ -309,6 +311,22 @@ class InsnU32(Insn):
return " arg1={} u32={}".format(self.arg1, self.u32)
+class InsnKidx(InsnU32):
+ def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0):
+ super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, u32=kidx)
+
+ @property
+ def kidx(self):
+ return self.u32
+
+ @kidx.setter
+ def kidx(self, kidx):
+ self.u32 = kidx
+
+ def _print_obj_value(self):
+ return " arg1={}, kidx={}".format(self.arg1, self.kidx)
+
+
class InsnProb(InsnU32):
def __init__(
self,
@@ -353,18 +371,23 @@ class InsnIp(InsnU32):
return " ip={}".format(self.ip)
-class InsnTable(Insn):
+class InsnTable(InsnKidx):
+ def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0, value=None):
+ super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, kidx=kidx)
+ self.val = value
+
@classmethod
def _validate(cls, data):
- cls._validate_len(data, [4, 8])
+ cls._validate_len(data, [8, 12])
@classmethod
def _parse(cls, data):
self = super()._parse(data)
- if len(data) == 8:
- (self.val,) = struct.unpack("@I", data[4:8])
- self.ilen = 2
+ (self.kidx,) = struct.unpack("@I", data[4:8])
+ if len(data) == 12:
+ (self.val,) = struct.unpack("@I", data[8:12])
+ self.ilen = 3
else:
self.val = None
return self
@@ -377,9 +400,131 @@ class InsnTable(Insn):
def _print_obj_value(self):
if getattr(self, "val", None) is not None:
- return " table={} value={}".format(self.arg1, self.val)
+ return " table={} value={}".format(self.kidx, self.val)
else:
- return " table={}".format(self.arg1)
+ return " table={}".format(self.kidx)
+
+
+class InsnLookup(InsnKidx):
+ def __init__(self, opcode, is_or=False, is_not=False, arg1=0, kidx=0, value=None, bitmask=None):
+ super().__init__(opcode, is_or=is_or, is_not=is_not, arg1=arg1, kidx=kidx)
+ self.val = value
+ self.bm = bitmask
+ self.decompile_arg1()
+
+ @classmethod
+ def _validate(cls, data):
+ cls._validate_len(data, [8, 24])
+
+ @classmethod
+ def _parse(cls, data):
+ self = super()._parse(data)
+
+ if len(data) == 24:
+ self.decompile_arg1()
+ self.ilen = 8
+ if self.do_masking != 0:
+ if self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP4, IpFwTableLookupType.LOOKUP_SRC_IP4]:
+ self.bm = socket.inet_ntop(socket.AF_INET, data[8:12])
+ elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP6, IpFwTableLookupType.LOOKUP_SRC_IP6]:
+ self.bm = socket.inet_ntop(socket.AF_INET6, data[8:24])
+ elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_MAC, IpFwTableLookupType.LOOKUP_SRC_MAC]:
+ self.bm = ':'.join(f'{b:02x}' for b in struct.unpack('!6B', data[8:14]))
+ elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP, IpFwTableLookupType.LOOKUP_SRC_IP]:
+ raise ValueError(f"maksing LOOKUP_SRC/DST_IP is invalid")
+ else:
+ (self.bm,) = struct.unpack("@I", data[8:12])
+ elif self.do_matching != 0:
+ match self.tvalue_type:
+ case IpFwTableValueType.TVALUE_NH4:
+ self.val = socket.inet_ntop(socket.AF_INET, data[8:12])
+ case IpFwTableValueType.TVALUE_NH6:
+ self.val = socket.inet_ntop(socket.AF_INET6, data[8:24])
+ case _:
+ (self.val,) = struct.unpack("@I", data[8:12])
+ else:
+ raise ValueError(f"insn_lookup is used but neither masking or matching are set")
+
+ return self
+
+ @classmethod
+ def compile_arg1(cls, value_type=None, value=None, lookup_type=None, bitmask=None):
+ arg1 = 0
+ if value_type is not None:
+ arg1 |= value_type.value << 8
+ arg1 |= 0x8000
+ if lookup_type is not None:
+ arg1 |= lookup_type.value
+ if bitmask is not None:
+ arg1 |= 0x0080
+ return arg1
+
+ def decompile_arg1(self):
+ self.do_masking = ((self.arg1 & 0x0080) != 0)
+ self.do_matching = ((self.arg1 & 0x8000) != 0)
+ self.lookup_int_type = self.arg1 & 0x007F
+ self.tvalue_int_type = (self.arg1 & 0x7F00) >> 8
+ self.lookup_type = enum_from_int(IpFwTableLookupType, self.lookup_int_type)
+ self.tvalue_type = enum_from_int(IpFwTableValueType, self.tvalue_int_type)
+
+ if self.do_matching != 0 and self.do_masking != 0:
+ raise ValueError(f"masking and matching can not be combined")
+ elif self.do_matching == 0 and self.do_masking == 0:
+ return
+ self.ilen = 8
+
+ def _validate_arg1(self):
+ if self.do_matching == 0 and self.val is not None:
+ raise ValueError(f"matching bit is NOT set yet value is set to {self.val}")
+ elif self.do_matching != 0 and self.val is None:
+ raise ValueError("matching bit is set yet value is None")
+ elif self.do_matching != 0 and self.tvalue_type is None:
+ raise ValueError(f"matching bit is set but unknown table value ({self.tvalue_type}) is set")
+
+ if self.do_masking == 0 and self.bm is not None:
+ raise ValueError(f"masking bit is NOT set yet bitmask is set to {self.bm}")
+ elif self.do_masking != 0 and self.bm is None:
+ raise ValueError("masking bit is set yet bitmask is None")
+ elif self.do_masking != 0 and self.lookup_type is None:
+ raise ValueError(f"masking bit is set but unknown lookup type ({self.lookup_int_type}) is set")
+ elif self.do_masking != 0 and self.lookup_type == IpFwTableLookupType.LOOKUP_NONE:
+ raise ValueError(f"masking bit is set but LOOKUP_NONE lookup type is set")
+
+ def __bytes__(self):
+ # perform sanity check
+ self._validate_arg1()
+ ret = super().__bytes__()
+ if self.do_masking != 0:
+ if (isinstance(self.bm, str) and
+ self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP4, IpFwTableLookupType.LOOKUP_SRC_IP4]):
+ ret += socket.inet_pton(socket.AF_INET, self.bm)
+ elif self.lookup_type in [IpFwTableLookupType.LOOKUP_DST_IP6, IpFwTableLookupType.LOOKUP_SRC_IP6]:
+ ret += socket.inet_pton(socket.AF_INET6, self.bm)
+ elif self.lookup_type in [IpFwTableLookupType.LOOKUP_SRC_MAC, IpFwTableLookupType.LOOKUP_SRC_MAC]:
+ ret += struct.pack('!6B10x', *(int(b, 16) for b in self.bm.split(':')))
+ else:
+ ret += struct.pack("@I12x", self.bm)
+ elif self.do_matching != 0:
+ match self.tvalue_type:
+ case IpFwTableValueType.TVALUE_NH4:
+ ret += socket.inet_pton(socket.AF_INET, self.val)
+ case IpFwTableValueType.TVALUE_NH6:
+ ret += socket.inet_pton(socket.AF_INET6, self.val)
+ case _:
+ ret += struct.pack("@I12x", self.val)
+ return ret
+
+ def _print_obj_value(self):
+ # perform sanity check
+ self._validate_arg1()
+ if self.do_matching != 0:
+ return " arg1={}, lookup_type={}, tvalue_type={}, kidx={}, value={}".format(
+ self.arg1, self.lookup_type, self.tvalue_type, self.kidx, self.val)
+ elif self.do_masking != 0:
+ return " arg1={}, lookup_type{}, tvalue_type={}, kidx={}, bitmask={}".format(
+ self.arg1, self.lookup_type, self.tvalue_type, self.kidx, self.bm)
+ else:
+ return " arg1={}, kidx={}".format(self.arg1, self.kidx)
class InsnReject(Insn):
@@ -510,7 +655,7 @@ class InsnIp6(Insn):
insn_attrs = prepare_attrs_map(
[
- AttrDescr(IpFwOpcode.O_CHECK_STATE, InsnU32),
+ AttrDescr(IpFwOpcode.O_CHECK_STATE, InsnKidx),
AttrDescr(IpFwOpcode.O_ACCEPT, InsnEmpty),
AttrDescr(IpFwOpcode.O_COUNT, InsnEmpty),
@@ -530,10 +675,9 @@ insn_attrs = prepare_attrs_map(
AttrDescr(IpFwOpcode.O_SETFIB, Insn),
AttrDescr(IpFwOpcode.O_SETDSCP, Insn),
AttrDescr(IpFwOpcode.O_REASS, InsnEmpty),
- AttrDescr(IpFwOpcode.O_SETMARK, InsnU32),
-
- AttrDescr(IpFwOpcode.O_EXTERNAL_ACTION, InsnU32),
- AttrDescr(IpFwOpcode.O_EXTERNAL_INSTANCE, InsnU32),
+ AttrDescr(IpFwOpcode.O_SETMARK, Insn),
+ AttrDescr(IpFwOpcode.O_EXTERNAL_ACTION, InsnKidx),
+ AttrDescr(IpFwOpcode.O_EXTERNAL_INSTANCE, InsnKidx),
@@ -548,11 +692,12 @@ insn_attrs = prepare_attrs_map(
AttrDescr(IpFwOpcode.O_IP_DST, InsnIp),
AttrDescr(IpFwOpcode.O_IP6_DST, InsnIp6),
AttrDescr(IpFwOpcode.O_IP6_SRC, InsnIp6),
- AttrDescr(IpFwOpcode.O_IP_SRC_LOOKUP, InsnU32),
- AttrDescr(IpFwOpcode.O_IP_DST_LOOKUP, InsnU32),
+ AttrDescr(IpFwOpcode.O_IP_SRC_LOOKUP, InsnLookup),
+ AttrDescr(IpFwOpcode.O_IP_DST_LOOKUP, InsnLookup),
AttrDescr(IpFwOpcode.O_IP_SRCPORT, InsnPorts),
AttrDescr(IpFwOpcode.O_IP_DSTPORT, InsnPorts),
- AttrDescr(IpFwOpcode.O_PROBE_STATE, InsnU32),
- AttrDescr(IpFwOpcode.O_KEEP_STATE, InsnU32),
+ AttrDescr(IpFwOpcode.O_PROBE_STATE, InsnKidx),
+ AttrDescr(IpFwOpcode.O_KEEP_STATE, InsnKidx),
+ AttrDescr(IpFwOpcode.O_TABLE_LOOKUP, InsnLookup),
]
)
diff --git a/tests/atf_python/sys/netpfil/ipfw/ioctl.py b/tests/atf_python/sys/netpfil/ipfw/ioctl.py
index 4c6d3f234c6c..9d289569eb41 100644
--- a/tests/atf_python/sys/netpfil/ipfw/ioctl.py
+++ b/tests/atf_python/sys/netpfil/ipfw/ioctl.py
@@ -26,6 +26,7 @@ import pytest
from atf_python.sys.netpfil.ipfw.insns import BaseInsn
from atf_python.sys.netpfil.ipfw.insns import insn_attrs
from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTableLookupType
+from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTableValueType
from atf_python.sys.netpfil.ipfw.ioctl_headers import IpFwTlvType
from atf_python.sys.netpfil.ipfw.ioctl_headers import Op3CmdType
from atf_python.sys.netpfil.ipfw.utils import align8
diff --git a/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py b/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py
index dc5c74bd1ad1..440514f0a7d8 100644
--- a/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py
+++ b/tests/atf_python/sys/netpfil/ipfw/ioctl_headers.py
@@ -61,16 +61,37 @@ class Op3CmdType(Enum):
class IpFwTableLookupType(Enum):
- LOOKUP_DST_IP = 0
- LOOKUP_SRC_IP = 1
- LOOKUP_DST_PORT = 2
- LOOKUP_SRC_PORT = 3
- LOOKUP_UID = 4
- LOOKUP_JAIL = 5
- LOOKUP_DSCP = 6
- LOOKUP_DST_MAC = 7
- LOOKUP_SRC_MAC = 8
- LOOKUP_MARK = 9
+ LOOKUP_NONE = 0
+ LOOKUP_DST_IP = 1
+ LOOKUP_SRC_IP = 2
+ LOOKUP_DST_PORT = 3
+ LOOKUP_SRC_PORT = 4
+ LOOKUP_UID = 5
+ LOOKUP_JAIL = 6
+ LOOKUP_DSCP = 7
+ LOOKUP_DST_MAC = 8
+ LOOKUP_SRC_MAC = 9
+ LOOKUP_MARK = 10
+ LOOKUP_RULENUM = 11
+ LOOKUP_DST_IP4 = 12
+ LOOKUP_SRC_IP4 = 13
+ LOOKUP_DST_IP6 = 14
+ LOOKUP_SRC_IP6 = 15
+
+
+class IpFwTableValueType(Enum):
+ TVALUE_TAG = 0
+ TVALUE_PIPE = 1
+ TVALUE_DIVERT = 2
+ TVALUE_SKIPTO = 3
+ TVALUE_NETGRAPH = 4
+ TVALUE_FIB = 5
+ TVALUE_NAT = 6
+ TVALUE_NH4 = 8
+ TVALUE_DSCP = 9
+ TVALUE_LIMIT = 10
+ TVALUE_MARK = 11
+ TVALUE_NH6 = 12
class IpFwTlvType(Enum):
diff --git a/tests/sys/netpfil/common/utils.subr b/tests/sys/netpfil/common/utils.subr
index 528cf6642508..8f298960bef5 100644
--- a/tests/sys/netpfil/common/utils.subr
+++ b/tests/sys/netpfil/common/utils.subr
@@ -52,6 +52,7 @@ firewall_config()
if [ ${fw} == "ipfw" ]; then
jexec ${jname} ipfw -q -f flush
+ jexec ${jname} ipfw -q -f table all destroy
jexec ${jname} /bin/sh $cwd/ipfw.rule
elif [ ${fw} == "pf" ]; then
jexec ${jname} sysctl net.pf.filter_local=1
@@ -86,6 +87,8 @@ firewall_init()
if [ ${firewall} == "ipfw" ]; then
if ! kldstat -q -m ipfw; then
atf_skip "This test requires ipfw"
+ elif [ $(sysctl -n net.inet.ip.fw.default_to_accept) -ne 1 ]; then
+ atf_fail "ipfw tests require net.inet.ip.fw.default_to_accept=1 tunable"
fi
elif [ ${firewall} == "pf" ]; then
if [ ! -c /dev/pf ]; then
diff --git a/tests/sys/netpfil/ipfw/Makefile b/tests/sys/netpfil/ipfw/Makefile
index d4dbdb00f251..873ef43ba917 100644
--- a/tests/sys/netpfil/ipfw/Makefile
+++ b/tests/sys/netpfil/ipfw/Makefile
@@ -3,9 +3,11 @@ PACKAGE= tests
TESTSDIR= ${TESTSBASE}/sys/netpfil/ipfw
ATF_TESTS_SH+= fwd \
- divert
+ divert \
+ lookup
-${PACKAGE}FILES+= fwd_inetd.conf
+${PACKAGE}FILES+= fwd_inetd.conf \
+ lookup_inetd.conf
# Allow tests to run in parallel in their own jails
TEST_METADATA+= execenv="jail"
diff --git a/tests/sys/netpfil/ipfw/lookup.sh b/tests/sys/netpfil/ipfw/lookup.sh
new file mode 100755
index 000000000000..c1f48dd7c99f
--- /dev/null
+++ b/tests/sys/netpfil/ipfw/lookup.sh
@@ -0,0 +1,428 @@
+#! /usr/libexec/atf-sh
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Boris Lytochkin
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#
+
+common_dir="$(atf_get_srcdir)/../common"
+. ${common_dir}/utils.subr
+
+NC="nc -w 1 -dnN"
+
+setup_network_v4()
+{
+ epair="$1"
+
+ ifconfig ${epair}a 192.0.2.0/31 up
+ ifconfig ${epair_recv}a up
+
+ vnet_mkjail alcatraz ${epair}b
+
+ jexec alcatraz ifconfig ${epair}b 192.0.2.1/31 up
+
+ jexec alcatraz /usr/sbin/inetd -p /dev/null $(atf_get_srcdir)/lookup_inetd.conf
+
+ # Sanity checks
+ atf_check -s exit:0 -o ignore ping -i .1 -c 3 -s 1200 192.0.2.1
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+}
+
+atf_test_case "ip4" "cleanup"
+ip4_head()
+{
+ atf_set descr 'IPv4 lookup test'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+ip4_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v4 ${epair}
+
+ # Source address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.2.0/32" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup src-ip 1 in"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+ # Destination address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.2.1/32" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dst-ip 1 in"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+ # Masked part
+
+ # Masked source address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.0.0/32" \
+ "ipfw -q add 100 allow tcp from any to any lookup src-ip4:255.255.253.255 1 in" \
+ "ipfw -q add 200 unreach port ip from any to any in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ # Masked destination address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.0.1/32" \
+ "ipfw -q add 100 allow tcp from any to any lookup dst-ip4:255.255.253.255 1 in" \
+ "ipfw -q add 200 unreach port ip from any to any in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ # Masked source address !match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.0.0/32" \
+ "ipfw -q add 100 allow tcp from any to any lookup src-ip4:128.255.253.255 1 in" \
+ "ipfw -q add 200 unreach port ip from any to any in"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+ # Masked destination address !match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 192.0.2.1/32" \
+ "ipfw -q add 100 allow tcp from any to any lookup dst-ip4:128.255.255.255 1 in" \
+ "ipfw -q add 200 unreach port ip from any to any in"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+}
+
+
+setup_network_v6()
+{
+ epair="$1"
+
+ ifconfig ${epair}a inet6 2001:db8:42::1/64 up no_dad -ifdisabled
+
+ vnet_mkjail alcatraz ${epair}b
+
+ jexec alcatraz ifconfig ${epair}b inet6 2001:db8:42::2/64 up no_dad
+
+ jexec alcatraz /usr/sbin/inetd -p /dev/null $(atf_get_srcdir)/lookup_inetd.conf
+
+ # Sanity checks
+ atf_check -s exit:0 -o ignore ping6 -i .1 -c 3 -s 1200 2001:db8:42::2
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+}
+
+atf_test_case "ip6" "cleanup"
+ip6_head()
+{
+ atf_set descr 'IPv6 lookup test'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+ip6_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v6 ${epair}
+
+ # Source address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2001:db8:42::1/128" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup src-ip 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ # Destination address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2001:db8:42::2/128" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dst-ip 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ # Source address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2000::/64" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup src-ip 1 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+ # Masked part
+
+ # Masked source address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2000:db8:42::1/128" \
+ "ipfw -q add 100 allow tcp from any to any lookup src-ip6:fff0:ffff:ffff:ffff:ffff:ffff:ffff:ffff 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+ # Destination address match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2001:0:42::2/128" \
+ "ipfw -q add 100 allow tcp from any to any lookup dst-ip6:ffff:0:ffff:ffff:ffff:ffff:ffff:ffff 1 in" \
+ "ipfw -q add 200 count tcp from any to any in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+ # Masked source address !match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2001:db8:42::1/128" \
+ "ipfw -q add 100 allow tcp from any to any lookup src-ip6:fff0:ffff:ffff:ffff:ffff:ffff:ffff:ffff 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ # Masked destination address !match
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type addr" \
+ "ipfw -q table 1 add 2000:0:42::2/128" \
+ "ipfw -q add 100 allow tcp from any to any lookup dst-ip6:ffff:0:ffff:ffff:ffff:ffff:ffff:ffff 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+}
+
+atf_test_case "rulenum" "cleanup"
+rulenum_head()
+{
+ atf_set descr 'Rule number lookup test'
+ atf_set require.user root
+}
+
+rulenum_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v6 ${epair}
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 100" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup rulenum 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 101" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup rulenum 1 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+ # Masked part
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 96" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup rulenum:0x60 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 101" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup rulenum:32 1 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+}
+
+atf_test_case "port" "cleanup"
+port_head()
+{
+ atf_set descr 'Lookup src-/dst-port works'
+ atf_set require.user root
+}
+
+port_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v4 ${epair}
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 82" \
+ "ipfw add 10 allow tcp from any to any lookup dst-port 1 in" \
+ "ipfw add 20 unreach port tcp from any to any dst-port 82"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 80" \
+ "ipfw add 10 allow tcp from any to any lookup dst-port 1 in" \
+ "ipfw add 20 unreach port tcp from any to any dst-port 82"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 82" \
+ "ipfw add 10 allow tcp from any to any lookup src-port 1 out" \
+ "ipfw add 20 unreach port tcp from any to any src-port 82 out"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 80" \
+ "ipfw add 10 allow tcp from any to any lookup src-port 1 in" \
+ "ipfw add 20 unreach port tcp from any to any src-port 22222 in"
+ atf_check -s exit:1 ${NC} -p 22222 192.0.2.1 82
+
+ # Masked part
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 18" \
+ "ipfw add 10 allow tcp from any to any lookup dst-port:0x1F 1 in" \
+ "ipfw add 20 unreach port tcp from any to any dst-port 82"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 18" \
+ "ipfw add 10 allow tcp from any to any lookup dst-port:255 1 in" \
+ "ipfw add 20 unreach port tcp from any to any dst-port 82"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+}
+
+atf_test_case "dscp_v6" "cleanup"
+dscp_v6_head()
+{
+ atf_set descr 'DSCP for IPv6 lookup test'
+ atf_set require.user root
+}
+
+dscp_v6_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v6 ${epair}
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 12" \
+ "ipfw -q add 50 setdscp 12 tcp from any to any" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dscp 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 101" \
+ "ipfw -q add 50 setdscp 12 tcp from any to any" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dscp 1 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+ # Masked part
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 12" \
+ "ipfw -q add 50 setdscp 13 tcp from any to any" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dscp:0xFE 1 in"
+ atf_check -s exit:1 ${NC} 2001:db8:42::2 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 13" \
+ "ipfw -q add 50 setdscp 13 tcp from any to any" \
+ "ipfw -q add 100 unreach port tcp from any to any lookup dscp:1 1 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 2001:db8:42::2 82
+
+}
+
+atf_test_case "dscp_v4" "cleanup"
+dscp_v4_head()
+{
+ atf_set descr 'Lookup DSCP for IPv4 test'
+ atf_set require.user root
+}
+
+dscp_v4_body()
+{
+ firewall_init "ipfw"
+
+ epair=$(vnet_mkepair)
+
+ setup_network_v4 ${epair}
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 12" \
+ "ipfw -q add 50 setdscp 12 tcp from any to any dst-port 82 in" \
+ "ipfw -q add 100 allow tcp from any to any lookup dscp 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any port 82 in"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 13" \
+ "ipfw -q add 50 setdscp 12 tcp from any to any dst-port 82 in" \
+ "ipfw -q add 100 allow tcp from any to any lookup dscp 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any dst-port 82 in"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+
+ # Masked part
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 48" \
+ "ipfw -q add 50 setdscp 50 tcp from any to any dst-port 82 in" \
+ "ipfw -q add 100 allow tcp from any to any lookup dscp:0xF0 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any dst-port 82"
+ atf_check -o "inline:GOOD 82\n" ${NC} 192.0.2.1 82
+
+ firewall_config "alcatraz" ipfw ipfw \
+ "ipfw -q table 1 create type number" \
+ "ipfw -q table 1 add 48" \
+ "ipfw -q add 50 setdscp 50 tcp from any to any dst-port 82 in" \
+ "ipfw -q add 100 allow tcp from any to any lookup dscp:255 1 in" \
+ "ipfw -q add 200 unreach port tcp from any to any dst-port 82"
+ atf_check -s exit:1 ${NC} 192.0.2.1 82
+}
+
+
+lookup_cleanup()
+{
+ firewall_cleanup $1
+}
+
+atf_init_test_cases()
+{
+ for test in "ip4" "ip6" \
+ "rulenum" "port" \
+ "dscp_v4" "dscp_v6" \
+ ; do
+ atf_add_test_case "${test}"
+ alias "${test}_cleanup"="lookup_cleanup"
+ done
+}
+
diff --git a/tests/sys/netpfil/ipfw/lookup_inetd.conf b/tests/sys/netpfil/ipfw/lookup_inetd.conf
new file mode 100644
index 000000000000..47978ef983e5
--- /dev/null
+++ b/tests/sys/netpfil/ipfw/lookup_inetd.conf
@@ -0,0 +1 @@
+xfer stream tcp46 nowait nobody /bin/echo echo GOOD 82