xref: /freebsd/tests/sys/netpfil/common/pft_ping.py (revision 344d411c676dc92e648be86de369c4d1dd070209)
1#!/usr/bin/env python
2#
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2017 Kristof Provost <kp@FreeBSD.org>
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27#
28
29import argparse
30import scapy.all as sp
31import sys
32from sniffer import Sniffer
33
34PAYLOAD_MAGIC = bytes.fromhex('42c0ffee')
35
36def check_ping_request(args, packet):
37	if args.ip6:
38		return check_ping6_request(args, packet)
39	else:
40		return check_ping4_request(args, packet)
41
42def check_ping4_request(args, packet):
43	"""
44	Verify that the packet matches what we'd have sent
45	"""
46	dst_ip = args.to[0]
47
48	ip = packet.getlayer(sp.IP)
49	if not ip:
50		return False
51	if ip.dst != dst_ip:
52		return False
53
54	icmp = packet.getlayer(sp.ICMP)
55	if not icmp:
56		return False
57	if sp.icmptypes[icmp.type] != 'echo-request':
58		return False
59
60	raw = packet.getlayer(sp.Raw)
61	if not raw:
62		return False
63	if raw.load != PAYLOAD_MAGIC:
64		return False
65
66	# Wait to check expectations until we've established this is the packet we
67	# sent.
68	if args.expect_tos:
69		if ip.tos != int(args.expect_tos[0]):
70			print("Unexpected ToS value %d, expected %d" \
71				% (ip.tos, int(args.expect_tos[0])))
72			return False
73
74	return True
75
76def check_ping6_request(args, packet):
77	"""
78	Verify that the packet matches what we'd have sent
79	"""
80	dst_ip = args.to[0]
81
82	ip = packet.getlayer(sp.IPv6)
83	if not ip:
84		return False
85	if ip.dst != dst_ip:
86		return False
87
88	icmp = packet.getlayer(sp.ICMPv6EchoRequest)
89	if not icmp:
90		return False
91	if icmp.data != PAYLOAD_MAGIC:
92		return False
93
94	return True
95
96def ping(send_if, dst_ip, args):
97	ether = sp.Ether()
98	ip = sp.IP(dst=dst_ip)
99	icmp = sp.ICMP(type='echo-request')
100	raw = sp.raw(PAYLOAD_MAGIC)
101
102	if args.send_tos:
103		ip.tos = int(args.send_tos[0])
104
105	req = ether / ip / icmp / raw
106	sp.sendp(req, iface=send_if, verbose=False)
107
108def ping6(send_if, dst_ip, args):
109	ether = sp.Ether()
110	ip6 = sp.IPv6(dst=dst_ip)
111	icmp = sp.ICMPv6EchoRequest(data=sp.raw(PAYLOAD_MAGIC))
112
113	req = ether / ip6 / icmp
114	sp.sendp(req, iface=send_if, verbose=False)
115
116def main():
117	parser = argparse.ArgumentParser("pft_ping.py",
118		description="Ping test tool")
119	parser.add_argument('--sendif', nargs=1,
120		required=True,
121		help='The interface through which the packet(s) will be sent')
122	parser.add_argument('--recvif', nargs=1,
123		help='The interface on which to expect the ICMP echo response')
124	parser.add_argument('--ip6', action='store_true',
125		help='Use IPv6')
126	parser.add_argument('--to', nargs=1,
127		required=True,
128		help='The destination IP address for the ICMP echo request')
129
130	# Packet settings
131	parser.add_argument('--send-tos', nargs=1,
132		help='Set the ToS value for the transmitted packet')
133
134	# Expectations
135	parser.add_argument('--expect-tos', nargs=1,
136		help='The expected ToS value in the received packet')
137
138	args = parser.parse_args()
139
140	# We may not have a default route. Tell scapy where to start looking for routes
141	sp.conf.iface6 = args.sendif[0]
142
143	sniffer = None
144	if not args.recvif is None:
145		sniffer = Sniffer(args, check_ping_request)
146
147	if args.ip6:
148		ping6(args.sendif[0], args.to[0], args)
149	else:
150		ping(args.sendif[0], args.to[0], args)
151
152	if sniffer:
153		sniffer.join()
154
155		if sniffer.foundCorrectPacket:
156			sys.exit(0)
157		else:
158			sys.exit(1)
159
160if __name__ == '__main__':
161	main()
162