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