xref: /freebsd/tests/sys/netpfil/common/pft_icmp_check.py (revision a39dedeb31052ec74b0cd394d56f8d7cc8534645)
130276ef1SKristof Provost#!/usr/bin/env python3
230276ef1SKristof Provost#
330276ef1SKristof Provost# SPDX-License-Identifier: BSD-2-Clause
430276ef1SKristof Provost#
530276ef1SKristof Provost# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
630276ef1SKristof Provost#
730276ef1SKristof Provost# Redistribution and use in source and binary forms, with or without
830276ef1SKristof Provost# modification, are permitted provided that the following conditions
930276ef1SKristof Provost# are met:
1030276ef1SKristof Provost# 1. Redistributions of source code must retain the above copyright
1130276ef1SKristof Provost#    notice, this list of conditions and the following disclaimer.
1230276ef1SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright
1330276ef1SKristof Provost#    notice, this list of conditions and the following disclaimer in the
1430276ef1SKristof Provost#    documentation and/or other materials provided with the distribution.
1530276ef1SKristof Provost#
1630276ef1SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1730276ef1SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1830276ef1SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1930276ef1SKristof Provost# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2030276ef1SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2130276ef1SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2230276ef1SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2330276ef1SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2430276ef1SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2530276ef1SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2630276ef1SKristof Provost# SUCH DAMAGE.
2730276ef1SKristof Provost#
2830276ef1SKristof Provost
2930276ef1SKristof Provostimport argparse
3030276ef1SKristof Provostimport logging
3130276ef1SKristof Provostlogging.getLogger("scapy").setLevel(logging.CRITICAL)
3230276ef1SKristof Provostimport random
3330276ef1SKristof Provostimport scapy.all as sp
3430276ef1SKristof Provostimport socket
3530276ef1SKristof Provostimport sys
3630276ef1SKristof Provostfrom sniffer import Sniffer
3730276ef1SKristof Provost
3830276ef1SKristof ProvostPAYLOAD_MAGIC = bytes.fromhex('42c0ffee')
3930276ef1SKristof Provost
4030276ef1SKristof Provostdef ping(send_if, dst_ip, args):
4130276ef1SKristof Provost	ether = sp.Ether()
4230276ef1SKristof Provost	ip = sp.IP(dst=dst_ip, src=args.fromaddr[0])
4330276ef1SKristof Provost	icmp = sp.ICMP(type='echo-request')
4430276ef1SKristof Provost	raw = sp.raw(PAYLOAD_MAGIC * 250) # We want 1000 bytes payload, -ish
4530276ef1SKristof Provost
4630276ef1SKristof Provost	ip.flags = 2 # Don't fragment
4730276ef1SKristof Provost	icmp.seq = random.randint(0, 65535)
4830276ef1SKristof Provost	args.icmp_seq = icmp.seq
4930276ef1SKristof Provost
5030276ef1SKristof Provost	req = ether / ip / icmp / raw
5130276ef1SKristof Provost	sp.sendp(req, iface=send_if, verbose=False)
5230276ef1SKristof Provost
5330276ef1SKristof Provostdef check_icmp_too_big(args, packet):
5430276ef1SKristof Provost	"""
5530276ef1SKristof Provost	Verify that this is an ICMP packet too big error, and that the IP addresses
5630276ef1SKristof Provost	in the payload packet match expectations.
5730276ef1SKristof Provost	"""
5830276ef1SKristof Provost	icmp = packet.getlayer(sp.ICMP)
5930276ef1SKristof Provost	if not icmp:
6030276ef1SKristof Provost		return False
6130276ef1SKristof Provost
6230276ef1SKristof Provost	if not icmp.type == 3:
6330276ef1SKristof Provost		return False
6430276ef1SKristof Provost	ip = packet.getlayer(sp.IPerror)
6530276ef1SKristof Provost	if not ip:
6630276ef1SKristof Provost		return False
6730276ef1SKristof Provost
6830276ef1SKristof Provost	if ip.src != args.fromaddr[0]:
6930276ef1SKristof Provost		print("Incorrect src addr %s" % ip.src)
7030276ef1SKristof Provost		return False
7130276ef1SKristof Provost	if ip.dst != args.to[0]:
7230276ef1SKristof Provost		print("Incorrect dst addr %s" % ip.dst)
7330276ef1SKristof Provost		return False
7430276ef1SKristof Provost
7530276ef1SKristof Provost	icmp2 = packet.getlayer(sp.ICMPerror)
7630276ef1SKristof Provost	if not icmp2:
7730276ef1SKristof Provost		print("IPerror doesn't contain ICMP")
7830276ef1SKristof Provost		return False
7930276ef1SKristof Provost	if icmp2.seq != args.icmp_seq:
8030276ef1SKristof Provost		print("Incorrect icmp seq %d != %d" % (icmp2.seq, args.icmp_seq))
8130276ef1SKristof Provost		return False
8230276ef1SKristof Provost	return True
8330276ef1SKristof Provost
8430276ef1SKristof Provostdef main():
8530276ef1SKristof Provost	parser = argparse.ArgumentParser("pft_icmp_check.py",
8630276ef1SKristof Provost	    description="ICMP error validation tool")
8730276ef1SKristof Provost	parser.add_argument('--to', nargs=1, required=True,
8830276ef1SKristof Provost	    help='The destination IP address')
8930276ef1SKristof Provost	parser.add_argument('--fromaddr', nargs=1, required=True,
9030276ef1SKristof Provost	    help='The source IP address')
9130276ef1SKristof Provost	parser.add_argument('--sendif', nargs=1, required=True,
9230276ef1SKristof Provost	    help='The interface through which the packet(s) will be sent')
9330276ef1SKristof Provost	parser.add_argument('--recvif', nargs=1,
9430276ef1SKristof Provost	    help='The interface on which to expect the ICMP error')
9530276ef1SKristof Provost
9630276ef1SKristof Provost	args = parser.parse_args()
9730276ef1SKristof Provost	sniffer = None
9830276ef1SKristof Provost	if not args.recvif is None:
99*a39dedebSKajetan Staszkiewicz		sniffer = Sniffer(args, check_icmp_too_big, args.recvif[0])
10030276ef1SKristof Provost
10130276ef1SKristof Provost	ping(args.sendif[0], args.to[0], args)
10230276ef1SKristof Provost
10330276ef1SKristof Provost	if sniffer:
10430276ef1SKristof Provost		sniffer.join()
10530276ef1SKristof Provost
106*a39dedebSKajetan Staszkiewicz		if sniffer.correctPackets:
10730276ef1SKristof Provost			sys.exit(0)
10830276ef1SKristof Provost		else:
10930276ef1SKristof Provost			sys.exit(1)
11030276ef1SKristof Provost
11130276ef1SKristof Provostif __name__ == '__main__':
11230276ef1SKristof Provost	main()
113