xref: /freebsd/tests/sys/netpfil/common/pft_ping.py (revision 609fa228bae6d864558f5167d4a964aab2a5fc88)
14a7d8405SKristof Provost#!/usr/bin/env python3
265d553b0SKristof Provost#
365d553b0SKristof Provost# SPDX-License-Identifier: BSD-2-Clause
465d553b0SKristof Provost#
565d553b0SKristof Provost# Copyright (c) 2017 Kristof Provost <kp@FreeBSD.org>
6f57218e4SKajetan Staszkiewicz# Copyright (c) 2023 Kajetan Staszkiewicz <vegeta@tuxpowered.net>
765d553b0SKristof Provost#
865d553b0SKristof Provost# Redistribution and use in source and binary forms, with or without
965d553b0SKristof Provost# modification, are permitted provided that the following conditions
1065d553b0SKristof Provost# are met:
1165d553b0SKristof Provost# 1. Redistributions of source code must retain the above copyright
1265d553b0SKristof Provost#    notice, this list of conditions and the following disclaimer.
1365d553b0SKristof Provost# 2. Redistributions in binary form must reproduce the above copyright
1465d553b0SKristof Provost#    notice, this list of conditions and the following disclaimer in the
1565d553b0SKristof Provost#    documentation and/or other materials provided with the distribution.
1665d553b0SKristof Provost#
1765d553b0SKristof Provost# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1865d553b0SKristof Provost# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1965d553b0SKristof Provost# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2065d553b0SKristof Provost# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2165d553b0SKristof Provost# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2265d553b0SKristof Provost# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2365d553b0SKristof Provost# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2465d553b0SKristof Provost# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2565d553b0SKristof Provost# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2665d553b0SKristof Provost# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2765d553b0SKristof Provost# SUCH DAMAGE.
2865d553b0SKristof Provost#
2995312530SKristof Provost
3095312530SKristof Provostimport argparse
31a26e895fSKristof Provostimport logging
32a26e895fSKristof Provostlogging.getLogger("scapy").setLevel(logging.CRITICAL)
33f57218e4SKajetan Staszkiewiczimport math
3495312530SKristof Provostimport scapy.all as sp
3595312530SKristof Provostimport sys
36*609fa228SKristof Provostimport socket
37f57218e4SKajetan Staszkiewicz
38f57218e4SKajetan Staszkiewiczfrom copy import copy
3995312530SKristof Provostfrom sniffer import Sniffer
4095312530SKristof Provost
41f57218e4SKajetan Staszkiewiczlogging.basicConfig(format='%(message)s')
42f57218e4SKajetan StaszkiewiczLOGGER = logging.getLogger(__name__)
43f57218e4SKajetan Staszkiewicz
44cfa8b648SLi-Wen HsuPAYLOAD_MAGIC = bytes.fromhex('42c0ffee')
4595312530SKristof Provost
46f57218e4SKajetan Staszkiewiczdef build_payload(l):
47f57218e4SKajetan Staszkiewicz    pl = len(PAYLOAD_MAGIC)
48f57218e4SKajetan Staszkiewicz    ret = PAYLOAD_MAGIC * math.floor(l/pl)
49f57218e4SKajetan Staszkiewicz    ret += PAYLOAD_MAGIC[0:(l % pl)]
50f57218e4SKajetan Staszkiewicz    return ret
51cd579b6fSKristof Provost
52cd579b6fSKristof Provost
536ffd4aebSKajetan Staszkiewiczdef clean_params(params):
546ffd4aebSKajetan Staszkiewicz    # Prepare a copy of safe copy of params
556ffd4aebSKajetan Staszkiewicz    ret = copy(params)
566ffd4aebSKajetan Staszkiewicz    ret.pop('src_address')
576ffd4aebSKajetan Staszkiewicz    ret.pop('dst_address')
586ffd4aebSKajetan Staszkiewicz    ret.pop('flags')
596ffd4aebSKajetan Staszkiewicz    return ret
606ffd4aebSKajetan Staszkiewicz
616ffd4aebSKajetan Staszkiewiczdef prepare_ipv6(send_params):
62f57218e4SKajetan Staszkiewicz    src_address = send_params.get('src_address')
636ffd4aebSKajetan Staszkiewicz    dst_address = send_params.get('dst_address')
64f57218e4SKajetan Staszkiewicz    hlim = send_params.get('hlim')
65f57218e4SKajetan Staszkiewicz    tc = send_params.get('tc')
66f57218e4SKajetan Staszkiewicz    ip6 = sp.IPv6(dst=dst_address)
67f57218e4SKajetan Staszkiewicz    if src_address:
68f57218e4SKajetan Staszkiewicz        ip6.src = src_address
69f57218e4SKajetan Staszkiewicz    if hlim:
70f57218e4SKajetan Staszkiewicz        ip6.hlim = hlim
71f57218e4SKajetan Staszkiewicz    if tc:
72f57218e4SKajetan Staszkiewicz        ip6.tc = tc
73f57218e4SKajetan Staszkiewicz    return ip6
74cd579b6fSKristof Provost
75cd579b6fSKristof Provost
766ffd4aebSKajetan Staszkiewiczdef prepare_ipv4(send_params):
77f57218e4SKajetan Staszkiewicz    src_address = send_params.get('src_address')
786ffd4aebSKajetan Staszkiewicz    dst_address = send_params.get('dst_address')
79f57218e4SKajetan Staszkiewicz    flags = send_params.get('flags')
80f57218e4SKajetan Staszkiewicz    tos = send_params.get('tc')
81f57218e4SKajetan Staszkiewicz    ttl = send_params.get('hlim')
8232df0124SKristof Provost    opt = send_params.get('nop')
8332df0124SKristof Provost    options = ''
8432df0124SKristof Provost    if opt:
8532df0124SKristof Provost        options='\x00'
8632df0124SKristof Provost    ip = sp.IP(dst=dst_address, options=options)
87f57218e4SKajetan Staszkiewicz    if src_address:
88f57218e4SKajetan Staszkiewicz        ip.src = src_address
89f57218e4SKajetan Staszkiewicz    if flags:
90f57218e4SKajetan Staszkiewicz        ip.flags = flags
91f57218e4SKajetan Staszkiewicz    if tos:
92f57218e4SKajetan Staszkiewicz        ip.tos = tos
93f57218e4SKajetan Staszkiewicz    if ttl:
94f57218e4SKajetan Staszkiewicz        ip.ttl = ttl
95f57218e4SKajetan Staszkiewicz    return ip
96cd579b6fSKristof Provost
9795312530SKristof Provost
986ffd4aebSKajetan Staszkiewiczdef send_icmp_ping(send_params):
99f57218e4SKajetan Staszkiewicz    send_length = send_params['length']
100d7c9de2dSKajetan Staszkiewicz    send_frag_length = send_params['frag_length']
101d7c9de2dSKajetan Staszkiewicz    packets = []
10295312530SKristof Provost    ether = sp.Ether()
1036ffd4aebSKajetan Staszkiewicz    if ':' in send_params['dst_address']:
1046ffd4aebSKajetan Staszkiewicz        ip6 = prepare_ipv6(send_params)
105f57218e4SKajetan Staszkiewicz        icmp = sp.ICMPv6EchoRequest(data=sp.raw(build_payload(send_length)))
106d7c9de2dSKajetan Staszkiewicz        if send_frag_length:
10765074f6fSKajetan Staszkiewicz            for packet in sp.fragment6(ip6 / icmp, fragSize=send_frag_length):
108d7c9de2dSKajetan Staszkiewicz                packets.append(ether / packet)
109d7c9de2dSKajetan Staszkiewicz        else:
110d7c9de2dSKajetan Staszkiewicz            packets.append(ether / ip6 / icmp)
111d7c9de2dSKajetan Staszkiewicz
112f57218e4SKajetan Staszkiewicz    else:
1136ffd4aebSKajetan Staszkiewicz        ip = prepare_ipv4(send_params)
114f57218e4SKajetan Staszkiewicz        icmp = sp.ICMP(type='echo-request')
115f57218e4SKajetan Staszkiewicz        raw = sp.raw(build_payload(send_length))
116d7c9de2dSKajetan Staszkiewicz        if send_frag_length:
117d7c9de2dSKajetan Staszkiewicz            for packet in sp.fragment(ip / icmp / raw, fragsize=send_frag_length):
118d7c9de2dSKajetan Staszkiewicz                packets.append(ether / packet)
119d7c9de2dSKajetan Staszkiewicz        else:
120d7c9de2dSKajetan Staszkiewicz            packets.append(ether / ip / icmp / raw)
121d7c9de2dSKajetan Staszkiewicz    for packet in packets:
1226ffd4aebSKajetan Staszkiewicz        sp.sendp(packet, iface=send_params['sendif'], verbose=False)
12395312530SKristof Provost
1242d3fda5fSKristof Provost
1256ffd4aebSKajetan Staszkiewiczdef send_tcp_syn(send_params):
126f57218e4SKajetan Staszkiewicz    tcpopt_unaligned = send_params.get('tcpopt_unaligned')
127f57218e4SKajetan Staszkiewicz    seq = send_params.get('seq')
128f57218e4SKajetan Staszkiewicz    mss = send_params.get('mss')
129f57218e4SKajetan Staszkiewicz    ether = sp.Ether()
130f57218e4SKajetan Staszkiewicz    opts=[('Timestamp', (1, 1)), ('MSS', mss if mss else 1280)]
131f57218e4SKajetan Staszkiewicz    if tcpopt_unaligned:
132f57218e4SKajetan Staszkiewicz        opts = [('NOP', 0 )] + opts
1336ffd4aebSKajetan Staszkiewicz    if ':' in send_params['dst_address']:
1346ffd4aebSKajetan Staszkiewicz        ip = prepare_ipv6(send_params)
135f57218e4SKajetan Staszkiewicz    else:
1366ffd4aebSKajetan Staszkiewicz        ip = prepare_ipv4(send_params)
13785ea6992SKajetan Staszkiewicz    tcp = sp.TCP(
13885ea6992SKajetan Staszkiewicz        sport=send_params.get('sport'), dport=send_params.get('dport'),
13985ea6992SKajetan Staszkiewicz        flags='S', options=opts, seq=seq,
14085ea6992SKajetan Staszkiewicz    )
141f57218e4SKajetan Staszkiewicz    req = ether / ip / tcp
1426ffd4aebSKajetan Staszkiewicz    sp.sendp(req, iface=send_params['sendif'], verbose=False)
143f57218e4SKajetan Staszkiewicz
144f57218e4SKajetan Staszkiewicz
14565074f6fSKajetan Staszkiewiczdef send_udp(send_params):
14665074f6fSKajetan Staszkiewicz    LOGGER.debug(f'Sending UDP ping')
14765074f6fSKajetan Staszkiewicz    packets = []
14865074f6fSKajetan Staszkiewicz    send_length = send_params['length']
14965074f6fSKajetan Staszkiewicz    send_frag_length = send_params['frag_length']
15065074f6fSKajetan Staszkiewicz    ether = sp.Ether()
15165074f6fSKajetan Staszkiewicz    if ':' in send_params['dst_address']:
15265074f6fSKajetan Staszkiewicz        ip6 = prepare_ipv6(send_params)
15365074f6fSKajetan Staszkiewicz        udp = sp.UDP(
15465074f6fSKajetan Staszkiewicz            sport=send_params.get('sport'), dport=send_params.get('dport'),
15565074f6fSKajetan Staszkiewicz        )
15665074f6fSKajetan Staszkiewicz        raw = sp.Raw(load=build_payload(send_length))
15765074f6fSKajetan Staszkiewicz        if send_frag_length:
15865074f6fSKajetan Staszkiewicz            for packet in sp.fragment6(ip6 / udp / raw, fragSize=send_frag_length):
15965074f6fSKajetan Staszkiewicz                packets.append(ether / packet)
16065074f6fSKajetan Staszkiewicz        else:
16165074f6fSKajetan Staszkiewicz            packets.append(ether / ip6 / udp / raw)
16265074f6fSKajetan Staszkiewicz    else:
16365074f6fSKajetan Staszkiewicz        ip = prepare_ipv4(send_params)
16465074f6fSKajetan Staszkiewicz        udp = sp.UDP(
16565074f6fSKajetan Staszkiewicz            sport=send_params.get('sport'), dport=send_params.get('dport'),
16665074f6fSKajetan Staszkiewicz        )
16765074f6fSKajetan Staszkiewicz        raw = sp.Raw(load=build_payload(send_length))
16865074f6fSKajetan Staszkiewicz        if send_frag_length:
16965074f6fSKajetan Staszkiewicz            for packet in sp.fragment(ip / udp / raw, fragsize=send_frag_length):
17065074f6fSKajetan Staszkiewicz                packets.append(ether / packet)
17165074f6fSKajetan Staszkiewicz        else:
17265074f6fSKajetan Staszkiewicz            packets.append(ether / ip / udp / raw)
17365074f6fSKajetan Staszkiewicz
17465074f6fSKajetan Staszkiewicz    for packet in packets:
17565074f6fSKajetan Staszkiewicz        sp.sendp(packet, iface=send_params['sendif'], verbose=False)
17665074f6fSKajetan Staszkiewicz
17765074f6fSKajetan Staszkiewicz
1786ffd4aebSKajetan Staszkiewiczdef send_ping(ping_type, send_params):
179f57218e4SKajetan Staszkiewicz    if ping_type == 'icmp':
1806ffd4aebSKajetan Staszkiewicz        send_icmp_ping(send_params)
1816ffd4aebSKajetan Staszkiewicz    elif (
1826ffd4aebSKajetan Staszkiewicz        ping_type == 'tcpsyn' or
1836ffd4aebSKajetan Staszkiewicz        ping_type == 'tcp3way'
1846ffd4aebSKajetan Staszkiewicz    ):
1856ffd4aebSKajetan Staszkiewicz        send_tcp_syn(send_params)
18665074f6fSKajetan Staszkiewicz    elif ping_type == 'udp':
18765074f6fSKajetan Staszkiewicz        send_udp(send_params)
188f57218e4SKajetan Staszkiewicz    else:
18965074f6fSKajetan Staszkiewicz        raise Exception('Unsupported ping type')
190f57218e4SKajetan Staszkiewicz
191f57218e4SKajetan Staszkiewicz
192f57218e4SKajetan Staszkiewiczdef check_ipv4(expect_params, packet):
193f57218e4SKajetan Staszkiewicz    src_address = expect_params.get('src_address')
194f57218e4SKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
195f57218e4SKajetan Staszkiewicz    flags = expect_params.get('flags')
196f57218e4SKajetan Staszkiewicz    tos = expect_params.get('tc')
197f57218e4SKajetan Staszkiewicz    ttl = expect_params.get('hlim')
1982d3fda5fSKristof Provost    ip = packet.getlayer(sp.IP)
1996ffd4aebSKajetan Staszkiewicz    LOGGER.debug(f'Packet: {ip}')
2002d3fda5fSKristof Provost    if not ip:
201f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv4!')
2022d3fda5fSKristof Provost        return False
203f57218e4SKajetan Staszkiewicz    if src_address and ip.src != src_address:
2046ffd4aebSKajetan Staszkiewicz        LOGGER.debug(f'Wrong IPv4 source {ip.src}, expected {src_address}')
2052d3fda5fSKristof Provost        return False
206f57218e4SKajetan Staszkiewicz    if dst_address and ip.dst != dst_address:
2076ffd4aebSKajetan Staszkiewicz        LOGGER.debug(f'Wrong IPv4 destination {ip.dst}, expected {dst_address}')
2082d3fda5fSKristof Provost        return False
209f57218e4SKajetan Staszkiewicz    if flags and ip.flags != flags:
210f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong IP flags value {ip.flags}, expected {flags}')
211f57218e4SKajetan Staszkiewicz        return False
212f57218e4SKajetan Staszkiewicz    if tos and ip.tos != tos:
213f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong ToS value {ip.tos}, expected {tos}')
214f57218e4SKajetan Staszkiewicz        return False
215f57218e4SKajetan Staszkiewicz    if ttl and ip.ttl != ttl:
216f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong TTL value {ip.ttl}, expected {ttl}')
217f57218e4SKajetan Staszkiewicz        return False
218f57218e4SKajetan Staszkiewicz    return True
2192d3fda5fSKristof Provost
220f57218e4SKajetan Staszkiewicz
221f57218e4SKajetan Staszkiewiczdef check_ipv6(expect_params, packet):
222f57218e4SKajetan Staszkiewicz    src_address = expect_params.get('src_address')
223f57218e4SKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
224f57218e4SKajetan Staszkiewicz    flags = expect_params.get('flags')
225f57218e4SKajetan Staszkiewicz    hlim = expect_params.get('hlim')
226f57218e4SKajetan Staszkiewicz    tc = expect_params.get('tc')
227f57218e4SKajetan Staszkiewicz    ip6 = packet.getlayer(sp.IPv6)
228f57218e4SKajetan Staszkiewicz    if not ip6:
229f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv6!')
230f57218e4SKajetan Staszkiewicz        return False
231*609fa228SKristof Provost    if src_address and socket.inet_pton(socket.AF_INET6, ip6.src) != \
232*609fa228SKristof Provost      socket.inet_pton(socket.AF_INET6, src_address):
2336ffd4aebSKajetan Staszkiewicz        LOGGER.debug(f'Wrong IPv6 source {ip6.src}, expected {src_address}')
234f57218e4SKajetan Staszkiewicz        return False
235*609fa228SKristof Provost    if dst_address and socket.inet_pton(socket.AF_INET6, ip6.dst) != \
236*609fa228SKristof Provost      socket.inet_pton(socket.AF_INET6, dst_address):
2376ffd4aebSKajetan Staszkiewicz        LOGGER.debug(f'Wrong IPv6 destination {ip6.dst}, expected {dst_address}')
238f57218e4SKajetan Staszkiewicz        return False
239f57218e4SKajetan Staszkiewicz    # IPv6 has no IP-level checksum.
240f57218e4SKajetan Staszkiewicz    if flags:
241f57218e4SKajetan Staszkiewicz        raise Exception("There's no fragmentation flags in IPv6")
242f57218e4SKajetan Staszkiewicz    if hlim and ip6.hlim != hlim:
243f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong Hop Limit value {ip6.hlim}, expected {hlim}')
244f57218e4SKajetan Staszkiewicz        return False
245f57218e4SKajetan Staszkiewicz    if tc and ip6.tc != tc:
246f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong TC value {ip6.tc}, expected {tc}')
247f57218e4SKajetan Staszkiewicz        return False
248f57218e4SKajetan Staszkiewicz    return True
249f57218e4SKajetan Staszkiewicz
250f57218e4SKajetan Staszkiewicz
251f57218e4SKajetan Staszkiewiczdef check_ping_4(expect_params, packet):
252f57218e4SKajetan Staszkiewicz    expect_length = expect_params['length']
253f57218e4SKajetan Staszkiewicz    if not check_ipv4(expect_params, packet):
254f57218e4SKajetan Staszkiewicz        return False
255f57218e4SKajetan Staszkiewicz    icmp = packet.getlayer(sp.ICMP)
256f57218e4SKajetan Staszkiewicz    if not icmp:
257f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv4 ICMP!')
258f57218e4SKajetan Staszkiewicz        return False
259f57218e4SKajetan Staszkiewicz    raw = packet.getlayer(sp.Raw)
260f57218e4SKajetan Staszkiewicz    if not raw:
261f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet contains no payload!')
262f57218e4SKajetan Staszkiewicz        return False
263f57218e4SKajetan Staszkiewicz    if raw.load != build_payload(expect_length):
264f57218e4SKajetan Staszkiewicz        LOGGER.debug('Payload magic does not match!')
265f57218e4SKajetan Staszkiewicz        return False
266f57218e4SKajetan Staszkiewicz    return True
267f57218e4SKajetan Staszkiewicz
268f57218e4SKajetan Staszkiewicz
269f57218e4SKajetan Staszkiewiczdef check_ping_request_4(expect_params, packet):
270f57218e4SKajetan Staszkiewicz    if not check_ping_4(expect_params, packet):
271f57218e4SKajetan Staszkiewicz        return False
272f57218e4SKajetan Staszkiewicz    icmp = packet.getlayer(sp.ICMP)
273f57218e4SKajetan Staszkiewicz    if sp.icmptypes[icmp.type] != 'echo-request':
274f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv4 ICMP Echo Request!')
275f57218e4SKajetan Staszkiewicz        return False
276f57218e4SKajetan Staszkiewicz    return True
277f57218e4SKajetan Staszkiewicz
278f57218e4SKajetan Staszkiewicz
279f57218e4SKajetan Staszkiewiczdef check_ping_reply_4(expect_params, packet):
280f57218e4SKajetan Staszkiewicz    if not check_ping_4(expect_params, packet):
281f57218e4SKajetan Staszkiewicz        return False
282f57218e4SKajetan Staszkiewicz    icmp = packet.getlayer(sp.ICMP)
283f57218e4SKajetan Staszkiewicz    if sp.icmptypes[icmp.type] != 'echo-reply':
284f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv4 ICMP Echo Reply!')
285f57218e4SKajetan Staszkiewicz        return False
286f57218e4SKajetan Staszkiewicz    return True
287f57218e4SKajetan Staszkiewicz
288f57218e4SKajetan Staszkiewicz
289f57218e4SKajetan Staszkiewiczdef check_ping_request_6(expect_params, packet):
290f57218e4SKajetan Staszkiewicz    expect_length = expect_params['length']
291f57218e4SKajetan Staszkiewicz    if not check_ipv6(expect_params, packet):
292f57218e4SKajetan Staszkiewicz        return False
293f57218e4SKajetan Staszkiewicz    icmp = packet.getlayer(sp.ICMPv6EchoRequest)
294f57218e4SKajetan Staszkiewicz    if not icmp:
295f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv6 ICMP Echo Request!')
296f57218e4SKajetan Staszkiewicz        return False
297f57218e4SKajetan Staszkiewicz    if icmp.data != build_payload(expect_length):
298f57218e4SKajetan Staszkiewicz        LOGGER.debug('Payload magic does not match!')
299f57218e4SKajetan Staszkiewicz        return False
300f57218e4SKajetan Staszkiewicz    return True
301f57218e4SKajetan Staszkiewicz
302f57218e4SKajetan Staszkiewicz
303f57218e4SKajetan Staszkiewiczdef check_ping_reply_6(expect_params, packet):
304f57218e4SKajetan Staszkiewicz    expect_length = expect_params['length']
305f57218e4SKajetan Staszkiewicz    if not check_ipv6(expect_params, packet):
306f57218e4SKajetan Staszkiewicz        return False
307f57218e4SKajetan Staszkiewicz    icmp = packet.getlayer(sp.ICMPv6EchoReply)
308f57218e4SKajetan Staszkiewicz    if not icmp:
309f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not IPv6 ICMP Echo Reply!')
310f57218e4SKajetan Staszkiewicz        return False
311f57218e4SKajetan Staszkiewicz    if icmp.data != build_payload(expect_length):
312f57218e4SKajetan Staszkiewicz        LOGGER.debug('Payload magic does not match!')
313f57218e4SKajetan Staszkiewicz        return False
314f57218e4SKajetan Staszkiewicz    return True
315f57218e4SKajetan Staszkiewicz
316f57218e4SKajetan Staszkiewicz
3176ffd4aebSKajetan Staszkiewiczdef check_ping_request(args, packet):
3186ffd4aebSKajetan Staszkiewicz    src_address = args['expect_params'].get('src_address')
3196ffd4aebSKajetan Staszkiewicz    dst_address = args['expect_params'].get('dst_address')
320f57218e4SKajetan Staszkiewicz    if not (src_address or dst_address):
321f57218e4SKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the ping request!')
322f57218e4SKajetan Staszkiewicz    if (
323f57218e4SKajetan Staszkiewicz        (src_address and ':' in src_address) or
324f57218e4SKajetan Staszkiewicz        (dst_address and ':' in dst_address)
325f57218e4SKajetan Staszkiewicz    ):
3266ffd4aebSKajetan Staszkiewicz        return check_ping_request_6(args['expect_params'], packet)
327f57218e4SKajetan Staszkiewicz    else:
3286ffd4aebSKajetan Staszkiewicz        return check_ping_request_4(args['expect_params'], packet)
329f57218e4SKajetan Staszkiewicz
330f57218e4SKajetan Staszkiewicz
3316ffd4aebSKajetan Staszkiewiczdef check_ping_reply(args, packet):
3326ffd4aebSKajetan Staszkiewicz    src_address = args['expect_params'].get('src_address')
3336ffd4aebSKajetan Staszkiewicz    dst_address = args['expect_params'].get('dst_address')
334f57218e4SKajetan Staszkiewicz    if not (src_address or dst_address):
335f57218e4SKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the ping reply!')
336f57218e4SKajetan Staszkiewicz    if (
337f57218e4SKajetan Staszkiewicz        (src_address and ':' in src_address) or
338f57218e4SKajetan Staszkiewicz        (dst_address and ':' in dst_address)
339f57218e4SKajetan Staszkiewicz    ):
3406ffd4aebSKajetan Staszkiewicz        return check_ping_reply_6(args['expect_params'], packet)
341f57218e4SKajetan Staszkiewicz    else:
3426ffd4aebSKajetan Staszkiewicz        return check_ping_reply_4(args['expect_params'], packet)
343f57218e4SKajetan Staszkiewicz
344f57218e4SKajetan Staszkiewicz
345f57218e4SKajetan Staszkiewiczdef check_tcp(expect_params, packet):
346f57218e4SKajetan Staszkiewicz    tcp_flags = expect_params.get('tcp_flags')
347f57218e4SKajetan Staszkiewicz    mss = expect_params.get('mss')
348f57218e4SKajetan Staszkiewicz    seq = expect_params.get('seq')
349f57218e4SKajetan Staszkiewicz    tcp = packet.getlayer(sp.TCP)
350f57218e4SKajetan Staszkiewicz    if not tcp:
351f57218e4SKajetan Staszkiewicz        LOGGER.debug('Packet is not TCP!')
352f57218e4SKajetan Staszkiewicz        return False
3532d3fda5fSKristof Provost    chksum = tcp.chksum
3542d3fda5fSKristof Provost    tcp.chksum = None
3552d3fda5fSKristof Provost    newpacket = sp.Ether(sp.raw(packet[sp.Ether]))
3562d3fda5fSKristof Provost    new_chksum = newpacket[sp.TCP].chksum
3576ffd4aebSKajetan Staszkiewicz    if new_chksum and chksum != new_chksum:
358f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong TCP checksum {chksum}, expected {new_chksum}!')
3592d3fda5fSKristof Provost        return False
360f57218e4SKajetan Staszkiewicz    if tcp_flags and tcp.flags != tcp_flags:
361f57218e4SKajetan Staszkiewicz        LOGGER.debug(f'Wrong TCP flags {tcp.flags}, expected {tcp_flags}!')
362f57218e4SKajetan Staszkiewicz        return False
363f57218e4SKajetan Staszkiewicz    if seq:
364f57218e4SKajetan Staszkiewicz        if tcp_flags == 'S':
365f57218e4SKajetan Staszkiewicz            tcp_seq = tcp.seq
366f57218e4SKajetan Staszkiewicz        elif tcp_flags == 'SA':
367f57218e4SKajetan Staszkiewicz            tcp_seq = tcp.ack - 1
368f57218e4SKajetan Staszkiewicz        if seq != tcp_seq:
369f57218e4SKajetan Staszkiewicz            LOGGER.debug(f'Wrong TCP Sequence Number {tcp_seq}, expected {seq}')
370f57218e4SKajetan Staszkiewicz            return False
371f57218e4SKajetan Staszkiewicz    if mss:
372f57218e4SKajetan Staszkiewicz        for option in tcp.options:
373f57218e4SKajetan Staszkiewicz            if option[0] == 'MSS':
374f57218e4SKajetan Staszkiewicz                if option[1] != mss:
375f57218e4SKajetan Staszkiewicz                    LOGGER.debug(f'Wrong TCP MSS {option[1]}, expected {mss}')
376f57218e4SKajetan Staszkiewicz                    return False
3772d3fda5fSKristof Provost    return True
3782d3fda5fSKristof Provost
3792d3fda5fSKristof Provost
38065074f6fSKajetan Staszkiewiczdef check_udp(expect_params, packet):
38165074f6fSKajetan Staszkiewicz    expect_length = expect_params['length']
38265074f6fSKajetan Staszkiewicz    udp = packet.getlayer(sp.UDP)
38365074f6fSKajetan Staszkiewicz    if not udp:
38465074f6fSKajetan Staszkiewicz        LOGGER.debug('Packet is not UDP!')
38565074f6fSKajetan Staszkiewicz        return False
38665074f6fSKajetan Staszkiewicz    raw = packet.getlayer(sp.Raw)
38765074f6fSKajetan Staszkiewicz    if not raw:
38865074f6fSKajetan Staszkiewicz        LOGGER.debug('Packet contains no payload!')
38965074f6fSKajetan Staszkiewicz        return False
39065074f6fSKajetan Staszkiewicz    if raw.load != build_payload(expect_length):
39165074f6fSKajetan Staszkiewicz        LOGGER.debug(f'Payload magic does not match len {len(raw.load)} vs {expect_length}!')
39265074f6fSKajetan Staszkiewicz        return False
39365074f6fSKajetan Staszkiewicz    orig_chksum = udp.chksum
39465074f6fSKajetan Staszkiewicz    udp.chksum = None
39565074f6fSKajetan Staszkiewicz    newpacket = sp.Ether(sp.raw(packet[sp.Ether]))
39665074f6fSKajetan Staszkiewicz    new_chksum = newpacket[sp.UDP].chksum
39765074f6fSKajetan Staszkiewicz    if new_chksum and orig_chksum != new_chksum:
39865074f6fSKajetan Staszkiewicz        LOGGER.debug(f'Wrong UDP checksum {orig_chksum}, expected {new_chksum}!')
39965074f6fSKajetan Staszkiewicz        return False
40065074f6fSKajetan Staszkiewicz
40165074f6fSKajetan Staszkiewicz    return True
40265074f6fSKajetan Staszkiewicz
40365074f6fSKajetan Staszkiewicz
404f57218e4SKajetan Staszkiewiczdef check_tcp_syn_request_4(expect_params, packet):
405f57218e4SKajetan Staszkiewicz    if not check_ipv4(expect_params, packet):
406f57218e4SKajetan Staszkiewicz        return False
407f57218e4SKajetan Staszkiewicz    if not check_tcp(expect_params | {'tcp_flags': 'S'}, packet):
408f57218e4SKajetan Staszkiewicz        return False
409f57218e4SKajetan Staszkiewicz    return True
4102d3fda5fSKristof Provost
4112d3fda5fSKristof Provost
4126ffd4aebSKajetan Staszkiewiczdef check_tcp_syn_reply_4(send_params, expect_params, packet):
413f57218e4SKajetan Staszkiewicz    if not check_ipv4(expect_params, packet):
414f57218e4SKajetan Staszkiewicz        return False
415f57218e4SKajetan Staszkiewicz    if not check_tcp(expect_params | {'tcp_flags': 'SA'}, packet):
416f57218e4SKajetan Staszkiewicz        return False
417f57218e4SKajetan Staszkiewicz    return True
418f57218e4SKajetan Staszkiewicz
419f57218e4SKajetan Staszkiewicz
4206ffd4aebSKajetan Staszkiewiczdef check_tcp_3way_4(args, packet):
4216ffd4aebSKajetan Staszkiewicz    send_params = args['send_params']
4226ffd4aebSKajetan Staszkiewicz
4236ffd4aebSKajetan Staszkiewicz    expect_params_sa = clean_params(args['expect_params'])
4246ffd4aebSKajetan Staszkiewicz    expect_params_sa['src_address'] = send_params['dst_address']
4256ffd4aebSKajetan Staszkiewicz    expect_params_sa['dst_address'] = send_params['src_address']
4266ffd4aebSKajetan Staszkiewicz
4276ffd4aebSKajetan Staszkiewicz    # Sniff incoming SYN+ACK packet
4286ffd4aebSKajetan Staszkiewicz    if (
4296ffd4aebSKajetan Staszkiewicz        check_ipv4(expect_params_sa, packet) and
4306ffd4aebSKajetan Staszkiewicz        check_tcp(expect_params_sa | {'tcp_flags': 'SA'}, packet)
4316ffd4aebSKajetan Staszkiewicz    ):
4326ffd4aebSKajetan Staszkiewicz        ether = sp.Ether()
4336ffd4aebSKajetan Staszkiewicz        ip_sa = packet.getlayer(sp.IP)
4346ffd4aebSKajetan Staszkiewicz        tcp_sa = packet.getlayer(sp.TCP)
4356ffd4aebSKajetan Staszkiewicz        reply_params = clean_params(send_params)
4366ffd4aebSKajetan Staszkiewicz        reply_params['src_address'] = ip_sa.dst
4376ffd4aebSKajetan Staszkiewicz        reply_params['dst_address'] = ip_sa.src
4386ffd4aebSKajetan Staszkiewicz        ip_a = prepare_ipv4(reply_params)
4396ffd4aebSKajetan Staszkiewicz        tcp_a = sp.TCP(
4406ffd4aebSKajetan Staszkiewicz            sport=tcp_sa.dport, dport=tcp_sa.sport, flags='A',
4416ffd4aebSKajetan Staszkiewicz            seq=tcp_sa.ack, ack=tcp_sa.seq + 1,
4426ffd4aebSKajetan Staszkiewicz        )
4436ffd4aebSKajetan Staszkiewicz        req = ether / ip_a / tcp_a
4446ffd4aebSKajetan Staszkiewicz        sp.sendp(req, iface=send_params['sendif'], verbose=False)
4456ffd4aebSKajetan Staszkiewicz        return True
4466ffd4aebSKajetan Staszkiewicz
4476ffd4aebSKajetan Staszkiewicz    return False
4486ffd4aebSKajetan Staszkiewicz
4496ffd4aebSKajetan Staszkiewicz
45065074f6fSKajetan Staszkiewiczdef check_udp_request_4(expect_params, packet):
45165074f6fSKajetan Staszkiewicz    if not check_ipv4(expect_params, packet):
45265074f6fSKajetan Staszkiewicz        return False
45365074f6fSKajetan Staszkiewicz    if not check_udp(expect_params, packet):
45465074f6fSKajetan Staszkiewicz        return False
45565074f6fSKajetan Staszkiewicz    return True
45665074f6fSKajetan Staszkiewicz
45765074f6fSKajetan Staszkiewicz
458f57218e4SKajetan Staszkiewiczdef check_tcp_syn_request_6(expect_params, packet):
459f57218e4SKajetan Staszkiewicz    if not check_ipv6(expect_params, packet):
460f57218e4SKajetan Staszkiewicz        return False
461f57218e4SKajetan Staszkiewicz    if not check_tcp(expect_params | {'tcp_flags': 'S'}, packet):
462f57218e4SKajetan Staszkiewicz        return False
463f57218e4SKajetan Staszkiewicz    return True
464f57218e4SKajetan Staszkiewicz
465f57218e4SKajetan Staszkiewicz
466f57218e4SKajetan Staszkiewiczdef check_tcp_syn_reply_6(expect_params, packet):
467f57218e4SKajetan Staszkiewicz    if not check_ipv6(expect_params, packet):
468f57218e4SKajetan Staszkiewicz        return False
469f57218e4SKajetan Staszkiewicz    if not check_tcp(expect_params | {'tcp_flags': 'SA'}, packet):
470f57218e4SKajetan Staszkiewicz        return False
471f57218e4SKajetan Staszkiewicz    return True
472f57218e4SKajetan Staszkiewicz
473f57218e4SKajetan Staszkiewicz
4746ffd4aebSKajetan Staszkiewiczdef check_tcp_3way_6(args, packet):
4756ffd4aebSKajetan Staszkiewicz    send_params = args['send_params']
4766ffd4aebSKajetan Staszkiewicz
4776ffd4aebSKajetan Staszkiewicz    expect_params_sa = clean_params(args['expect_params'])
4786ffd4aebSKajetan Staszkiewicz    expect_params_sa['src_address'] = send_params['dst_address']
4796ffd4aebSKajetan Staszkiewicz    expect_params_sa['dst_address'] = send_params['src_address']
4806ffd4aebSKajetan Staszkiewicz
4816ffd4aebSKajetan Staszkiewicz    # Sniff incoming SYN+ACK packet
4826ffd4aebSKajetan Staszkiewicz    if (
4836ffd4aebSKajetan Staszkiewicz        check_ipv6(expect_params_sa, packet) and
4846ffd4aebSKajetan Staszkiewicz        check_tcp(expect_params_sa | {'tcp_flags': 'SA'}, packet)
4856ffd4aebSKajetan Staszkiewicz    ):
4866ffd4aebSKajetan Staszkiewicz        ether = sp.Ether()
4876ffd4aebSKajetan Staszkiewicz        ip6_sa = packet.getlayer(sp.IPv6)
4886ffd4aebSKajetan Staszkiewicz        tcp_sa = packet.getlayer(sp.TCP)
4896ffd4aebSKajetan Staszkiewicz        reply_params = clean_params(send_params)
4906ffd4aebSKajetan Staszkiewicz        reply_params['src_address'] = ip6_sa.dst
4916ffd4aebSKajetan Staszkiewicz        reply_params['dst_address'] = ip6_sa.src
4926ffd4aebSKajetan Staszkiewicz        ip_a = prepare_ipv6(reply_params)
4936ffd4aebSKajetan Staszkiewicz        tcp_a = sp.TCP(
4946ffd4aebSKajetan Staszkiewicz            sport=tcp_sa.dport, dport=tcp_sa.sport, flags='A',
4956ffd4aebSKajetan Staszkiewicz            seq=tcp_sa.ack, ack=tcp_sa.seq + 1,
4966ffd4aebSKajetan Staszkiewicz        )
4976ffd4aebSKajetan Staszkiewicz        req = ether / ip_a / tcp_a
4986ffd4aebSKajetan Staszkiewicz        sp.sendp(req, iface=send_params['sendif'], verbose=False)
4996ffd4aebSKajetan Staszkiewicz        return True
5006ffd4aebSKajetan Staszkiewicz
5016ffd4aebSKajetan Staszkiewicz    return False
5026ffd4aebSKajetan Staszkiewicz
5036ffd4aebSKajetan Staszkiewicz
50465074f6fSKajetan Staszkiewiczdef check_udp_request_6(expect_params, packet):
50565074f6fSKajetan Staszkiewicz    if not check_ipv6(expect_params, packet):
50665074f6fSKajetan Staszkiewicz        return False
50765074f6fSKajetan Staszkiewicz    if not check_udp(expect_params, packet):
50865074f6fSKajetan Staszkiewicz        return False
50965074f6fSKajetan Staszkiewicz    return True
51065074f6fSKajetan Staszkiewicz
5116ffd4aebSKajetan Staszkiewiczdef check_tcp_syn_request(args, packet):
5126ffd4aebSKajetan Staszkiewicz    expect_params = args['expect_params']
513f57218e4SKajetan Staszkiewicz    src_address = expect_params.get('src_address')
514f57218e4SKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
515f57218e4SKajetan Staszkiewicz    if not (src_address or dst_address):
516f57218e4SKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the tcp syn request!')
517f57218e4SKajetan Staszkiewicz    if (
518f57218e4SKajetan Staszkiewicz        (src_address and ':' in src_address) or
519f57218e4SKajetan Staszkiewicz        (dst_address and ':' in dst_address)
520f57218e4SKajetan Staszkiewicz    ):
521f57218e4SKajetan Staszkiewicz        return check_tcp_syn_request_6(expect_params, packet)
522f57218e4SKajetan Staszkiewicz    else:
523f57218e4SKajetan Staszkiewicz        return check_tcp_syn_request_4(expect_params, packet)
524f57218e4SKajetan Staszkiewicz
525f57218e4SKajetan Staszkiewicz
5266ffd4aebSKajetan Staszkiewiczdef check_tcp_syn_reply(args, packet):
5276ffd4aebSKajetan Staszkiewicz    expect_params = args['expect_params']
528f57218e4SKajetan Staszkiewicz    src_address = expect_params.get('src_address')
529f57218e4SKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
530f57218e4SKajetan Staszkiewicz    if not (src_address or dst_address):
531f57218e4SKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the tcp syn reply!')
532f57218e4SKajetan Staszkiewicz    if (
533f57218e4SKajetan Staszkiewicz        (src_address and ':' in src_address) or
534f57218e4SKajetan Staszkiewicz        (dst_address and ':' in dst_address)
535f57218e4SKajetan Staszkiewicz    ):
536f57218e4SKajetan Staszkiewicz        return check_tcp_syn_reply_6(expect_params, packet)
537f57218e4SKajetan Staszkiewicz    else:
538f57218e4SKajetan Staszkiewicz        return check_tcp_syn_reply_4(expect_params, packet)
539f57218e4SKajetan Staszkiewicz
5406ffd4aebSKajetan Staszkiewiczdef check_tcp_3way(args, packet):
5416ffd4aebSKajetan Staszkiewicz    expect_params = args['expect_params']
5426ffd4aebSKajetan Staszkiewicz    src_address = expect_params.get('src_address')
5436ffd4aebSKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
5446ffd4aebSKajetan Staszkiewicz    if not (src_address or dst_address):
5456ffd4aebSKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the tcp syn reply!')
5466ffd4aebSKajetan Staszkiewicz    if (
5476ffd4aebSKajetan Staszkiewicz            (src_address and ':' in src_address) or
5486ffd4aebSKajetan Staszkiewicz            (dst_address and ':' in dst_address)
5496ffd4aebSKajetan Staszkiewicz    ):
5506ffd4aebSKajetan Staszkiewicz        return check_tcp_3way_6(args, packet)
5516ffd4aebSKajetan Staszkiewicz    else:
5526ffd4aebSKajetan Staszkiewicz        return check_tcp_3way_4(args, packet)
553f57218e4SKajetan Staszkiewicz
5546ffd4aebSKajetan Staszkiewicz
55565074f6fSKajetan Staszkiewiczdef check_udp_request(args, packet):
55665074f6fSKajetan Staszkiewicz    expect_params = args['expect_params']
55765074f6fSKajetan Staszkiewicz    src_address = expect_params.get('src_address')
55865074f6fSKajetan Staszkiewicz    dst_address = expect_params.get('dst_address')
55965074f6fSKajetan Staszkiewicz    if not (src_address or dst_address):
56065074f6fSKajetan Staszkiewicz        raise Exception('Source or destination address must be given to match the tcp syn request!')
56165074f6fSKajetan Staszkiewicz    if (
56265074f6fSKajetan Staszkiewicz            (src_address and ':' in src_address) or
56365074f6fSKajetan Staszkiewicz            (dst_address and ':' in dst_address)
56465074f6fSKajetan Staszkiewicz    ):
56565074f6fSKajetan Staszkiewicz        return check_udp_request_6(expect_params, packet)
56665074f6fSKajetan Staszkiewicz    else:
56765074f6fSKajetan Staszkiewicz        return check_udp_request_4(expect_params, packet)
56865074f6fSKajetan Staszkiewicz
56965074f6fSKajetan Staszkiewicz
5706ffd4aebSKajetan Staszkiewiczdef setup_sniffer(
5716ffd4aebSKajetan Staszkiewicz        recvif, ping_type, sniff_type, expect_params, defrag, send_params,
5726ffd4aebSKajetan Staszkiewicz):
573f57218e4SKajetan Staszkiewicz    if ping_type == 'icmp' and sniff_type == 'request':
574f57218e4SKajetan Staszkiewicz        checkfn = check_ping_request
575f57218e4SKajetan Staszkiewicz    elif ping_type == 'icmp' and sniff_type == 'reply':
576f57218e4SKajetan Staszkiewicz        checkfn = check_ping_reply
577f57218e4SKajetan Staszkiewicz    elif ping_type == 'tcpsyn' and sniff_type == 'request':
578f57218e4SKajetan Staszkiewicz        checkfn = check_tcp_syn_request
579f57218e4SKajetan Staszkiewicz    elif ping_type == 'tcpsyn' and sniff_type == 'reply':
580f57218e4SKajetan Staszkiewicz        checkfn = check_tcp_syn_reply
5816ffd4aebSKajetan Staszkiewicz    elif ping_type == 'tcp3way' and sniff_type == 'reply':
5826ffd4aebSKajetan Staszkiewicz        checkfn = check_tcp_3way
58365074f6fSKajetan Staszkiewicz    elif ping_type == 'udp' and sniff_type == 'request':
58465074f6fSKajetan Staszkiewicz        checkfn = check_udp_request
585f57218e4SKajetan Staszkiewicz    else:
58665074f6fSKajetan Staszkiewicz        raise Exception('Unspported ping and sniff type combination')
587f57218e4SKajetan Staszkiewicz
5886ffd4aebSKajetan Staszkiewicz    return Sniffer(
5896ffd4aebSKajetan Staszkiewicz        {'send_params': send_params, 'expect_params': expect_params},
5906ffd4aebSKajetan Staszkiewicz        checkfn, recvif, defrag=defrag,
5916ffd4aebSKajetan Staszkiewicz    )
592f57218e4SKajetan Staszkiewicz
593f57218e4SKajetan Staszkiewicz
594f57218e4SKajetan Staszkiewiczdef parse_args():
59595312530SKristof Provost    parser = argparse.ArgumentParser("pft_ping.py",
59695312530SKristof Provost        description="Ping test tool")
597f57218e4SKajetan Staszkiewicz
598f57218e4SKajetan Staszkiewicz    # Parameters of sent ping request
5996aeaadf6SKajetan Staszkiewicz    parser.add_argument('--sendif', required=True,
60095312530SKristof Provost        help='The interface through which the packet(s) will be sent')
6016aeaadf6SKajetan Staszkiewicz    parser.add_argument('--to', required=True,
602f57218e4SKajetan Staszkiewicz        help='The destination IP address for the ping request')
603f57218e4SKajetan Staszkiewicz    parser.add_argument('--ping-type',
60465074f6fSKajetan Staszkiewicz        choices=('icmp', 'tcpsyn', 'tcp3way', 'udp'),
6056ffd4aebSKajetan Staszkiewicz        help='Type of ping: ICMP (default) or TCP SYN or 3-way TCP handshake',
606f57218e4SKajetan Staszkiewicz        default='icmp')
6076aeaadf6SKajetan Staszkiewicz    parser.add_argument('--fromaddr',
608f57218e4SKajetan Staszkiewicz        help='The source IP address for the ping request')
60995312530SKristof Provost
610f57218e4SKajetan Staszkiewicz    # Where to look for packets to analyze.
611f57218e4SKajetan Staszkiewicz    # The '+' format is ugly as it mixes positional with optional syntax.
612f57218e4SKajetan Staszkiewicz    # But we have no positional parameters so I guess it's fine to use it.
613f57218e4SKajetan Staszkiewicz    parser.add_argument('--recvif', nargs='+',
614f57218e4SKajetan Staszkiewicz        help='The interfaces on which to expect the ping request')
615f57218e4SKajetan Staszkiewicz    parser.add_argument('--replyif', nargs='+',
616f57218e4SKajetan Staszkiewicz        help='The interfaces which to expect the ping response')
6172d3fda5fSKristof Provost
61895312530SKristof Provost    # Packet settings
619f57218e4SKajetan Staszkiewicz    parser_send = parser.add_argument_group('Values set in transmitted packets')
6206aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-flags', type=str,
621f57218e4SKajetan Staszkiewicz        help='IPv4 fragmentation flags')
6226aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-frag-length', type=int,
623d7c9de2dSKajetan Staszkiewicz        help='Force IP fragmentation with given fragment length')
6246aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-hlim', type=int,
625f57218e4SKajetan Staszkiewicz        help='IPv6 Hop Limit or IPv4 Time To Live')
6266aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-mss', type=int,
627f57218e4SKajetan Staszkiewicz        help='TCP Maximum Segment Size')
6286aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-seq', type=int,
629f57218e4SKajetan Staszkiewicz        help='TCP sequence number')
63085ea6992SKajetan Staszkiewicz    parser_send.add_argument('--send-sport', type=int,
63185ea6992SKajetan Staszkiewicz        help='TCP source port')
6326ffd4aebSKajetan Staszkiewicz    parser_send.add_argument('--send-dport', type=int, default=9,
63385ea6992SKajetan Staszkiewicz        help='TCP destination port')
6346aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-length', type=int, default=len(PAYLOAD_MAGIC),
6356aeaadf6SKajetan Staszkiewicz        help='ICMP Echo Request payload size')
6366aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--send-tc', type=int,
637f57218e4SKajetan Staszkiewicz        help='IPv6 Traffic Class or IPv4 DiffServ / ToS')
638f57218e4SKajetan Staszkiewicz    parser_send.add_argument('--send-tcpopt-unaligned', action='store_true',
639f57218e4SKajetan Staszkiewicz        help='Include unaligned TCP options')
64032df0124SKristof Provost    parser_send.add_argument('--send-nop', action='store_true',
64132df0124SKristof Provost        help='Include a NOP IPv4 option')
64295312530SKristof Provost
64395312530SKristof Provost    # Expectations
644f57218e4SKajetan Staszkiewicz    parser_expect = parser.add_argument_group('Values expected in sniffed packets')
6456aeaadf6SKajetan Staszkiewicz    parser_expect.add_argument('--expect-flags', type=str,
646f57218e4SKajetan Staszkiewicz        help='IPv4 fragmentation flags')
6476aeaadf6SKajetan Staszkiewicz    parser_expect.add_argument('--expect-hlim', type=int,
648f57218e4SKajetan Staszkiewicz        help='IPv6 Hop Limit or IPv4 Time To Live')
6496aeaadf6SKajetan Staszkiewicz    parser_expect.add_argument('--expect-mss', type=int,
650f57218e4SKajetan Staszkiewicz        help='TCP Maximum Segment Size')
6516aeaadf6SKajetan Staszkiewicz    parser_send.add_argument('--expect-seq', type=int,
652f57218e4SKajetan Staszkiewicz        help='TCP sequence number')
6536aeaadf6SKajetan Staszkiewicz    parser_expect.add_argument('--expect-tc', type=int,
654f57218e4SKajetan Staszkiewicz        help='IPv6 Traffic Class or IPv4 DiffServ / ToS')
65595312530SKristof Provost
656f57218e4SKajetan Staszkiewicz    parser.add_argument('-v', '--verbose', action='store_true',
657f57218e4SKajetan Staszkiewicz        help=('Enable verbose logging. Apart of potentially useful information '
658f57218e4SKajetan Staszkiewicz            'you might see warnings from parsing packets like NDP or other '
659f57218e4SKajetan Staszkiewicz            'packets not related to the test being run. Use only when '
660f57218e4SKajetan Staszkiewicz            'developing because real tests expect empty stderr and stdout.'))
661f57218e4SKajetan Staszkiewicz
662f57218e4SKajetan Staszkiewicz    return parser.parse_args()
663f57218e4SKajetan Staszkiewicz
664f57218e4SKajetan Staszkiewicz
665f57218e4SKajetan Staszkiewiczdef main():
666f57218e4SKajetan Staszkiewicz    args = parse_args()
667f57218e4SKajetan Staszkiewicz
668f57218e4SKajetan Staszkiewicz    if args.verbose:
669f57218e4SKajetan Staszkiewicz        LOGGER.setLevel(logging.DEBUG)
670f57218e4SKajetan Staszkiewicz
6716aeaadf6SKajetan Staszkiewicz    # Split parameters into send and expect parameters. Parameters might be
6726aeaadf6SKajetan Staszkiewicz    # missing from the command line, always fill the dictionaries with None.
673f57218e4SKajetan Staszkiewicz    send_params = {}
674f57218e4SKajetan Staszkiewicz    expect_params = {}
67585ea6992SKajetan Staszkiewicz    for param_name in (
67685ea6992SKajetan Staszkiewicz        'flags', 'hlim', 'length', 'mss', 'seq', 'tc', 'frag_length',
67785ea6992SKajetan Staszkiewicz        'sport', 'dport',
67885ea6992SKajetan Staszkiewicz    ):
679f57218e4SKajetan Staszkiewicz        param_arg = vars(args).get(f'send_{param_name}')
6806aeaadf6SKajetan Staszkiewicz        send_params[param_name] = param_arg if param_arg else None
681f57218e4SKajetan Staszkiewicz        param_arg = vars(args).get(f'expect_{param_name}')
6826aeaadf6SKajetan Staszkiewicz        expect_params[param_name] = param_arg if param_arg else None
683f57218e4SKajetan Staszkiewicz
684f57218e4SKajetan Staszkiewicz    expect_params['length'] = send_params['length']
685f57218e4SKajetan Staszkiewicz    send_params['tcpopt_unaligned'] = args.send_tcpopt_unaligned
68632df0124SKristof Provost    send_params['nop'] = args.send_nop
6876aeaadf6SKajetan Staszkiewicz    send_params['src_address'] = args.fromaddr if args.fromaddr else None
6886ffd4aebSKajetan Staszkiewicz    send_params['dst_address'] = args.to
6896ffd4aebSKajetan Staszkiewicz    send_params['sendif'] = args.sendif
69095312530SKristof Provost
69195312530SKristof Provost    # We may not have a default route. Tell scapy where to start looking for routes
6926aeaadf6SKajetan Staszkiewicz    sp.conf.iface6 = args.sendif
69395312530SKristof Provost
694f57218e4SKajetan Staszkiewicz    # Configuration sanity checking.
6956aeaadf6SKajetan Staszkiewicz    if not (args.replyif or args.recvif):
696f57218e4SKajetan Staszkiewicz        raise Exception('With no reply or recv interface specified no traffic '
697f57218e4SKajetan Staszkiewicz            'can be sniffed and verified!'
698f57218e4SKajetan Staszkiewicz        )
69995312530SKristof Provost
700f57218e4SKajetan Staszkiewicz    sniffers = []
7012d3fda5fSKristof Provost
702d7c9de2dSKajetan Staszkiewicz    if send_params['frag_length']:
70365074f6fSKajetan Staszkiewicz        if (
70465074f6fSKajetan Staszkiewicz            (send_params['src_address'] and ':' in send_params['src_address']) or
70565074f6fSKajetan Staszkiewicz            (send_params['dst_address'] and ':' in send_params['dst_address'])
70665074f6fSKajetan Staszkiewicz        ):
70765074f6fSKajetan Staszkiewicz            defrag = 'IPv6'
70865074f6fSKajetan Staszkiewicz        else:
70965074f6fSKajetan Staszkiewicz            defrag = 'IPv4'
710d7c9de2dSKajetan Staszkiewicz    else:
711d7c9de2dSKajetan Staszkiewicz        defrag = False
712d7c9de2dSKajetan Staszkiewicz
7136aeaadf6SKajetan Staszkiewicz    if args.recvif:
714f57218e4SKajetan Staszkiewicz        sniffer_params = copy(expect_params)
715f57218e4SKajetan Staszkiewicz        sniffer_params['src_address'] = None
7166aeaadf6SKajetan Staszkiewicz        sniffer_params['dst_address'] = args.to
7176aeaadf6SKajetan Staszkiewicz        for iface in args.recvif:
718f57218e4SKajetan Staszkiewicz            LOGGER.debug(f'Installing receive sniffer on {iface}')
719f57218e4SKajetan Staszkiewicz            sniffers.append(
720d7c9de2dSKajetan Staszkiewicz                setup_sniffer(iface, args.ping_type, 'request',
7216ffd4aebSKajetan Staszkiewicz                              sniffer_params, defrag, send_params,
722f57218e4SKajetan Staszkiewicz            ))
7236b52139eSKristof Provost
7246aeaadf6SKajetan Staszkiewicz    if args.replyif:
725f57218e4SKajetan Staszkiewicz        sniffer_params = copy(expect_params)
7266aeaadf6SKajetan Staszkiewicz        sniffer_params['src_address'] = args.to
727f57218e4SKajetan Staszkiewicz        sniffer_params['dst_address'] = None
7286aeaadf6SKajetan Staszkiewicz        for iface in args.replyif:
729f57218e4SKajetan Staszkiewicz            LOGGER.debug(f'Installing reply sniffer on {iface}')
730f57218e4SKajetan Staszkiewicz            sniffers.append(
731d7c9de2dSKajetan Staszkiewicz                setup_sniffer(iface, args.ping_type, 'reply',
7326ffd4aebSKajetan Staszkiewicz                              sniffer_params, defrag, send_params,
733f57218e4SKajetan Staszkiewicz            ))
734cd579b6fSKristof Provost
735f57218e4SKajetan Staszkiewicz    LOGGER.debug(f'Installed {len(sniffers)} sniffers')
73695312530SKristof Provost
7376ffd4aebSKajetan Staszkiewicz    send_ping(args.ping_type, send_params)
738cd579b6fSKristof Provost
739f57218e4SKajetan Staszkiewicz    err = 0
740f57218e4SKajetan Staszkiewicz    sniffer_num = 0
741f57218e4SKajetan Staszkiewicz    for sniffer in sniffers:
74295312530SKristof Provost        sniffer.join()
743f57218e4SKajetan Staszkiewicz        if sniffer.correctPackets == 1:
744f57218e4SKajetan Staszkiewicz            LOGGER.debug(f'Expected ping has been sniffed on {sniffer._recvif}.')
74595312530SKristof Provost        else:
746f57218e4SKajetan Staszkiewicz            # Set a bit in err for each failed sniffer.
747f57218e4SKajetan Staszkiewicz            err |= 1<<sniffer_num
748f57218e4SKajetan Staszkiewicz            if sniffer.correctPackets > 1:
749f57218e4SKajetan Staszkiewicz                LOGGER.debug(f'Duplicated ping has been sniffed on {sniffer._recvif}!')
7506b52139eSKristof Provost            else:
751f57218e4SKajetan Staszkiewicz                LOGGER.debug(f'Expected ping has not been sniffed on {sniffer._recvif}!')
752f57218e4SKajetan Staszkiewicz        sniffer_num += 1
753f57218e4SKajetan Staszkiewicz
754f57218e4SKajetan Staszkiewicz    return err
755f57218e4SKajetan Staszkiewicz
7566b52139eSKristof Provost
75795312530SKristof Provostif __name__ == '__main__':
758f57218e4SKajetan Staszkiewicz    sys.exit(main())
759