Skip to content

Commit

Permalink
pf tests: test pflow NAT state information
Browse files Browse the repository at this point in the history
pflow(4) now also exports NAT session creation/destruction information.
Test that this works as expected.

While here improve the parsing of ipfix (i.e. pflowproto 10) a bit, and
check more information for the existing state information exports.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D43117
  • Loading branch information
kprovost committed Jan 16, 2024
1 parent 2be6f75 commit c4d97a9
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 8 deletions.
58 changes: 55 additions & 3 deletions tests/sys/netpfil/pf/pflow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ state_defaults_body()
ping -c 1 192.0.2.1

# We default to version 5
atf_check -o match:"^v=5.*" \
atf_check -o match:".*v=5.*" \
$(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055

# Switch to version 10
jexec alcatraz pflowctl -s ${pflow} proto 10

ping -c 1 192.0.2.1

atf_check -o match:"^v=10.*" \
atf_check -o match:".*v=10.*" \
$(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055
}

Expand Down Expand Up @@ -174,7 +174,7 @@ v6_body()

ping -6 -c 1 2001:db8::1

atf_check -o match:"^v=10.*" \
atf_check -o match:".*v=10,IPv6,proto=58,src=2001:db8::2,dst=2001:db8::1.*" \
$(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055
}

Expand All @@ -183,9 +183,61 @@ v6_cleanup()
pft_cleanup
}

atf_test_case "nat" "cleanup"
nat_head()
{
atf_set descr 'Test pflow export for NAT44'
atf_set require.user root
atf_set require.progs scapy
}

nat_body()
{
pflow_init

epair=$(vnet_mkepair)
epair_srv=$(vnet_mkepair)

vnet_mkjail srv ${epair_srv}a
vnet_mkjail rtr ${epair_srv}b ${epair}a

ifconfig ${epair}b 192.0.2.2/24 up

jexec srv ifconfig ${epair_srv}a 198.51.100.2/24 up
jexec srv route add default 198.51.100.1

jexec rtr ifconfig ${epair_srv}b 198.51.100.1/24 up
jexec rtr ifconfig ${epair}a 192.0.2.1/24 up
jexec rtr sysctl net.inet.ip.forwarding=1

route add -net 198.51.100.0/24 192.0.2.1

jexec rtr pfctl -e
pft_set_rules rtr \
"set state-defaults pflow" \
"nat pass on ${epair_srv}b inet from 192.0.2.0/24 to any -> (${epair_srv}b)" \
"pass"

pflow=$(jexec rtr pflowctl -c)
jexec rtr pflowctl -s ${pflow} dst 192.0.2.2:2055 proto 10

# Sanity check
atf_check -s exit:0 -o ignore \
ping -c 1 198.51.100.2

atf_check -o match:".*v=10,NAT=4,proto=1,src=192.0.2.2-198.51.100.1.*" \
$(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}b --port 2055
}

nat_cleanup()
{
pft_cleanup
}

atf_init_test_cases()
{
atf_add_test_case "basic"
atf_add_test_case "state_defaults"
atf_add_test_case "v6"
atf_add_test_case "nat"
}
41 changes: 36 additions & 5 deletions tests/sys/netpfil/pf/pft_read_ipfix.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,43 @@
logging.getLogger("scapy").setLevel(logging.CRITICAL)
import scapy.all as sp

def parse_ipfix(p):
if not sp.NetflowDataflowsetV9 in p:
# A template?
return

c = p
while sp.NetflowDataflowsetV9 in c:
datafl = c[sp.NetflowDataflowsetV9]
if datafl.templateID == 258:
# NAT
for r in datafl.records:
print("v=10,NAT=%d,proto=%s,src=%s-%s,dst=%s-%s" %
(int.from_bytes(r.natEvent, "big"), r.PROTOCOL,
r.IPV4_SRC_ADDR, r.postNATSourceIPv4Address,
r.IPV4_DST_ADDR, r.postNATDestinationIPv4Address))
elif datafl.templateID == 257:
# IPv6
for r in datafl.records:
print("v=10,IPv6,proto=%s,src=%s,dst=%s" %
(r.PROTOCOL, r.IPV6_SRC_ADDR, r.IPV6_DST_ADDR))
elif datafl.templateID == 256:
# IPv4
for r in datafl.records:
print("v=10,proto=%s,src=%s,dst=%s" %
(r.PROTOCOL, r.IPV4_SRC_ADDR, r.IPV4_DST_ADDR))

c = datafl.payload

def receive(recvif, recvport):
pkts = sp.sniff(iface=recvif, timeout=65)

if len(pkts) == 0:
print("No data")
return

pkts = sp.ipfix_defragment(pkts)

for pkt in pkts:
udp = pkt.getlayer(sp.UDP)
if not udp:
Expand All @@ -46,18 +76,19 @@ def receive(recvif, recvport):
if udp.dport != recvport:
continue

hdr = pkt.getlayer(sp.NetflowHeader)
if not sp.NetflowHeader in pkt:
continue

hdr = pkt.getlayer(sp.NetflowHeader)
if hdr.version == 5:
v5hdr = pkt.getlayer(sp.NetflowHeaderV5)
out=""
for i in range(1, v5hdr.count + 1):
r = pkt.getlayer(sp.NetflowRecordV5, nb=i)
out = "%s,proto=%d,src=%s,dst=%s,srcport=%d,dstport=%d" % (out, r.prot, r.src, r.dst, r.srcport, r.dstport)
print("v=%d,count=%d%s" % (hdr.version, v5hdr.count, out))
print("v=%d,proto=%d,src=%s,dst=%s,srcport=%d,dstport=%d" %
(hdr.version, r.prot, r.src, r.dst, r.srcport, r.dstport))
elif hdr.version == 10:
print("v=10")
return
parse_ipfix(pkt)

def main():
parser = argparse.ArgumentParser("pft_read_ipfix.py",
Expand Down

0 comments on commit c4d97a9

Please sign in to comment.