diff --git a/tests/sys/netpfil/common/pft_ping.py b/tests/sys/netpfil/common/pft_ping.py --- a/tests/sys/netpfil/common/pft_ping.py +++ b/tests/sys/netpfil/common/pft_ping.py @@ -115,6 +115,35 @@ return True +def check_ping_reply(args, packet): + return check_ping4_reply(args, packet) + +def check_ping4_reply(args, packet): + """ + Check that this is a reply to the ping request we sent + """ + dst_ip = args.to[0] + + ip = packet.getlayer(sp.IP) + if not ip: + return False + if ip.src != dst_ip: + return False + + icmp = packet.getlayer(sp.ICMP) + if not icmp: + return False + if sp.icmptypes[icmp.type] != 'echo-reply': + return False + + raw = packet.getlayer(sp.Raw) + if not raw: + return False + if raw.load != PAYLOAD_MAGIC: + return False + + return True + def ping(send_if, dst_ip, args): ether = sp.Ether() ip = sp.IP(dst=dst_ip) @@ -124,6 +153,9 @@ if args.send_tos: ip.tos = int(args.send_tos[0]) + if args.fromaddr: + ip.src = args.fromaddr[0] + req = ether / ip / icmp / raw sp.sendp(req, iface=send_if, verbose=False) @@ -132,6 +164,9 @@ ip6 = sp.IPv6(dst=dst_ip) icmp = sp.ICMPv6EchoRequest(data=sp.raw(PAYLOAD_MAGIC)) + if args.fromaddr: + ip.src = args.fromaddr[0] + req = ether / ip6 / icmp sp.sendp(req, iface=send_if, verbose=False) @@ -189,6 +224,8 @@ required=True, help='The interface through which the packet(s) will be sent') parser.add_argument('--recvif', nargs=1, + help='The interface on which to expect the ICMP echo request') + parser.add_argument('--replyif', nargs=1, help='The interface on which to expect the ICMP echo response') parser.add_argument('--checkdup', nargs=1, help='The interface on which to expect the duplicated ICMP packets') @@ -197,6 +234,8 @@ parser.add_argument('--to', nargs=1, required=True, help='The destination IP address for the ICMP echo request') + parser.add_argument('--fromaddr', nargs=1, + help='The source IP address for the ICMP echo request') # TCP options parser.add_argument('--tcpsyn', action='store_true', @@ -225,6 +264,11 @@ sniffer = Sniffer(args, checkfn) + replysniffer = None + if not args.replyif is None: + checkfn=check_ping_reply + replysniffer = Sniffer(args, checkfn, recvif=args.replyif[0]) + dupsniffer = None if args.checkdup is not None: dupsniffer = Sniffer(args, check_dup, recvif=args.checkdup[0]) @@ -250,5 +294,13 @@ else: sys.exit(1) + if replysniffer: + replysniffer.join() + + if replysniffer.foundCorrectPacket: + sys.exit(0) + else: + sys.exit(1) + if __name__ == '__main__': main() diff --git a/tests/sys/netpfil/pf/pass_block.sh b/tests/sys/netpfil/pf/pass_block.sh --- a/tests/sys/netpfil/pf/pass_block.sh +++ b/tests/sys/netpfil/pf/pass_block.sh @@ -27,6 +27,8 @@ . $(atf_get_srcdir)/utils.subr +common_dir=$(atf_get_srcdir)/../common + atf_test_case "v4" "cleanup" v4_head() { @@ -189,10 +191,75 @@ pft_cleanup } +atf_test_case "urpf" "cleanup" +urpf_head() +{ + atf_set descr "Test unicast reverse path forwarding check" + atf_set require.user root + atf_set require.progs scapy +} + +urpf_body() +{ + pft_init + + epair_one=$(vnet_mkepair) + epair_two=$(vnet_mkepair) + + vnet_mkjail alcatraz ${epair_one}b ${epair_two}b + + ifconfig ${epair_one}a 192.0.2.2/24 up + ifconfig ${epair_two}a 198.51.100.2/24 up + + jexec alcatraz ifconfig ${epair_one}b 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair_two}b 198.51.100.1/24 up + jexec alcatraz sysctl net.inet.ip.forwarding=1 + + # Sanity checks + atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.1 + atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.1 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_one}a \ + --to 192.0.2.1 \ + --fromaddr 198.51.100.2 \ + --replyif ${epair_two}a + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_two}a \ + --to 198.51.100.1 \ + --fromaddr 192.0.2.2 \ + --replyif ${epair_one}a + + pft_set_rules alcatraz \ + "block quick from urpf-failed" + jexec alcatraz pfctl -e + + # Correct source still works + atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.1 + atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.1 + + # Unexpected source interface is blocked + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_one}a \ + --to 192.0.2.1 \ + --fromaddr 198.51.100.2 \ + --replyif ${epair_two}a + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_two}a \ + --to 198.51.100.1 \ + --fromaddr 192.0.2.2 \ + --replyif ${epair_one}a +} + +urpf_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4" atf_add_test_case "v6" atf_add_test_case "noalias" atf_add_test_case "nested_inline" + atf_add_test_case "urpf" }