1import pytest 2 3import logging 4import os 5import re 6import subprocess 7 8from atf_python.sys.net.vnet import IfaceFactory 9from atf_python.sys.net.vnet import SingleVnetTestTemplate 10from atf_python.sys.net.tools import ToolsHelper 11from typing import List 12from typing import Optional 13 14logging.getLogger("scapy").setLevel(logging.CRITICAL) 15import scapy.all as sc 16 17 18def build_response_packet(echo, ip, icmp, oip_ihl, special): 19 icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38] 20 oip = echo[sc.IP] 21 oicmp = echo[sc.ICMP] 22 load = echo[sc.ICMP].payload 23 oip[sc.IP].remove_payload() 24 oicmp[sc.ICMP].remove_payload() 25 oicmp.type = 8 26 27 # As if the original IP packet had these set 28 oip.ihl = None 29 oip.len = None 30 oip.id = 1 31 oip.flags = ip.flags 32 oip.chksum = None 33 oip.options = ip.options 34 35 # Inner packet (oip) options 36 if oip_ihl: 37 oip.ihl = oip_ihl 38 39 # Special options 40 if special == "no-payload": 41 load = "" 42 if special == "tcp": 43 oip.proto = "tcp" 44 tcp = sc.TCP(sport=1234, dport=5678) 45 return ip / icmp / oip / tcp 46 if special == "udp": 47 oip.proto = "udp" 48 udp = sc.UDP(sport=1234, dport=5678) 49 return ip / icmp / oip / udp 50 if special == "warp": 51 # Build a package with a timestamp of INT_MAX 52 # (time-warped package) 53 payload_no_timestamp = sc.bytes_hex(load)[16:] 54 load = (b"\xff" * 8) + sc.hex_bytes(payload_no_timestamp) 55 if special == "wrong": 56 # Build a package with a wrong last byte 57 payload_no_last_byte = sc.bytes_hex(load)[:-2] 58 load = (sc.hex_bytes(payload_no_last_byte)) + b"\x00" 59 if special == "not-mine": 60 # Modify the ICMP Identifier field 61 oicmp.id += 1 62 63 if icmp.type in icmp_id_seq_types: 64 pkt = ip / icmp / load 65 else: 66 ip.options = "" 67 pkt = ip / icmp / oip / oicmp / load 68 return pkt 69 70 71def generate_ip_options(opts): 72 if not opts: 73 return "" 74 75 routers = [ 76 "192.0.2.10", 77 "192.0.2.20", 78 "192.0.2.30", 79 "192.0.2.40", 80 "192.0.2.50", 81 "192.0.2.60", 82 "192.0.2.70", 83 "192.0.2.80", 84 "192.0.2.90", 85 ] 86 routers_zero = [0, 0, 0, 0, 0, 0, 0, 0, 0] 87 if opts == "EOL": 88 options = sc.IPOption(b"\x00") 89 elif opts == "NOP": 90 options = sc.IPOption(b"\x01") 91 elif opts == "NOP-40": 92 options = sc.IPOption(b"\x01" * 40) 93 elif opts == "RR": 94 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 95 options = sc.IPOption_RR(pointer=40, routers=routers) 96 elif opts == "RR-same": 97 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 98 options = sc.IPOption_RR(pointer=3, routers=routers_zero) 99 elif opts == "RR-trunc": 100 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 101 options = sc.IPOption_RR(length=7, routers=routers_zero) 102 elif opts == "LSRR": 103 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 104 options = sc.IPOption_LSRR(routers=routers) 105 elif opts == "LSRR-trunc": 106 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 107 options = sc.IPOption_LSRR(length=3, routers=routers_zero) 108 elif opts == "SSRR": 109 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 110 options = sc.IPOption_SSRR(routers=routers) 111 elif opts == "SSRR-trunc": 112 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 113 options = sc.IPOption_SSRR(length=3, routers=routers_zero) 114 elif opts == "unk": 115 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 116 options = sc.IPOption(b"\x9f") 117 elif opts == "unk-40": 118 ToolsHelper.set_sysctl("net.inet.ip.process_options", 0) 119 options = sc.IPOption(b"\x9f" * 40) 120 else: 121 options = "" 122 return options 123 124 125def pinger( 126 # Required arguments 127 # Avoid setting defaults on these arguments, 128 # as we want to set them explicitly in the tests 129 iface: str, 130 /, 131 src: sc.scapy.fields.SourceIPField, 132 dst: sc.scapy.layers.inet.DestIPField, 133 icmp_type: sc.scapy.fields.ByteEnumField, 134 icmp_code: sc.scapy.fields.MultiEnumField, 135 # IP arguments 136 ihl: Optional[sc.scapy.fields.BitField] = None, 137 flags: Optional[sc.scapy.fields.FlagsField] = None, 138 opts: Optional[str] = None, 139 oip_ihl: Optional[sc.scapy.fields.BitField] = None, 140 special: Optional[str] = None, 141 # ICMP arguments 142 # Match names with <netinet/ip_icmp.h> 143 icmp_pptr: sc.scapy.fields.ByteField = 0, 144 icmp_gwaddr: sc.scapy.fields.IPField = "0.0.0.0", 145 icmp_nextmtu: sc.scapy.fields.ShortField = 0, 146 icmp_otime: sc.scapy.layers.inet.ICMPTimeStampField = 0, 147 icmp_rtime: sc.scapy.layers.inet.ICMPTimeStampField = 0, 148 icmp_ttime: sc.scapy.layers.inet.ICMPTimeStampField = 0, 149 icmp_mask: sc.scapy.fields.IPField = "0.0.0.0", 150 request: Optional[str] = None, 151 # Miscellaneous arguments 152 count: int = 1, 153 dup: bool = False, 154 verbose: bool = True, 155) -> subprocess.CompletedProcess: 156 """P I N G E R 157 158 Echo reply faker 159 160 :param str iface: Interface to send packet to 161 :keyword src: Source packet IP 162 :type src: class:`scapy.fields.SourceIPField` 163 :keyword dst: Destination packet IP 164 :type dst: class:`scapy.layers.inet.DestIPField` 165 :keyword icmp_type: ICMP type 166 :type icmp_type: class:`scapy.fields.ByteEnumField` 167 :keyword icmp_code: ICMP code 168 :type icmp_code: class:`scapy.fields.MultiEnumField` 169 170 :keyword ihl: Internet Header Length, defaults to None 171 :type ihl: class:`scapy.fields.BitField`, optional 172 :keyword flags: IP flags - one of `DF`, `MF` or `evil`, defaults to None 173 :type flags: class:`scapy.fields.FlagsField`, optional 174 :keyword opts: Include IP options - one of `EOL`, `NOP`, `NOP-40`, `unk`, 175 `unk-40`, `RR`, `RR-same`, `RR-trunc`, `LSRR`, `LSRR-trunc`, `SSRR` or 176 `SSRR-trunc`, defaults to None 177 :type opts: str, optional 178 :keyword oip_ihl: Inner packet's Internet Header Length, defaults to None 179 :type oip_ihl: class:`scapy.fields.BitField`, optional 180 :keyword special: Send a special packet - one of `no-payload`, `not-mine`, 181 `tcp`, `udp`, `wrong` or `warp`, defaults to None 182 :type special: str, optional 183 :keyword icmp_pptr: ICMP pointer, defaults to 0 184 :type icmp_pptr: class:`scapy.fields.ByteField` 185 :keyword icmp_gwaddr: ICMP gateway IP address, defaults to "0.0.0.0" 186 :type icmp_gwaddr: class:`scapy.fields.IPField` 187 :keyword icmp_nextmtu: ICMP next MTU, defaults to 0 188 :type icmp_nextmtu: class:`scapy.fields.ShortField` 189 :keyword icmp_otime: ICMP originate timestamp, defaults to 0 190 :type icmp_otime: class:`scapy.layers.inet.ICMPTimeStampField` 191 :keyword icmp_rtime: ICMP receive timestamp, defaults to 0 192 :type icmp_rtime: class:`scapy.layers.inet.ICMPTimeStampField` 193 :keyword icmp_ttime: ICMP transmit timestamp, defaults to 0 194 :type icmp_ttime: class:`scapy.layers.inet.ICMPTimeStampField` 195 :keyword icmp_mask: ICMP address mask, defaults to "0.0.0.0" 196 :type icmp_mask: class:`scapy.fields.IPField` 197 :keyword request: Request type - one of `mask` or `timestamp`, 198 defaults to None 199 :type request: str, optional 200 :keyword count: Number of packets to send, defaults to 1 201 :type count: int 202 :keyword dup: Duplicate packets, defaults to `False` 203 :type dup: bool 204 :keyword verbose: Turn on/off verbosity, defaults to `True` 205 :type verbose: bool 206 207 :return: A class:`subprocess.CompletedProcess` with the output from the 208 ping utility 209 :rtype: class:`subprocess.CompletedProcess` 210 """ 211 tun = sc.TunTapInterface(iface) 212 subprocess.run(["ifconfig", tun.iface, "up"], check=True) 213 subprocess.run(["ifconfig", tun.iface, src, dst], check=True) 214 ip_opts = generate_ip_options(opts) 215 ip = sc.IP(ihl=ihl, flags=flags, src=dst, dst=src, options=ip_opts) 216 command = [ 217 "/sbin/ping", 218 "-c", 219 str(count), 220 "-t", 221 str(count), 222 ] 223 if verbose: 224 command += ["-v"] 225 if request == "mask": 226 command += ["-Mm"] 227 if request == "timestamp": 228 command += ["-Mt"] 229 if special: 230 command += ["-p1"] 231 if opts in [ 232 "RR", 233 "RR-same", 234 "RR-trunc", 235 "LSRR", 236 "LSRR-trunc", 237 "SSRR", 238 "SSRR-trunc", 239 ]: 240 command += ["-R"] 241 command += [dst] 242 with subprocess.Popen( 243 args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True 244 ) as ping: 245 for dummy in range(count): 246 echo = tun.recv() 247 icmp = sc.ICMP( 248 type=icmp_type, 249 code=icmp_code, 250 id=echo[sc.ICMP].id, 251 seq=echo[sc.ICMP].seq, 252 ts_ori=icmp_otime, 253 ts_rx=icmp_rtime, 254 ts_tx=icmp_ttime, 255 gw=icmp_gwaddr, 256 ptr=icmp_pptr, 257 addr_mask=icmp_mask, 258 nexthopmtu=icmp_nextmtu, 259 ) 260 pkt = build_response_packet(echo, ip, icmp, oip_ihl, special) 261 tun.send(pkt) 262 if dup is True: 263 tun.send(pkt) 264 stdout, stderr = ping.communicate() 265 return subprocess.CompletedProcess( 266 ping.args, ping.returncode, stdout, stderr 267 ) 268 269 270def redact(output): 271 """Redact some elements of ping's output""" 272 pattern_replacements = [ 273 ("localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"), 274 ("from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"), 275 ("hlim=[0-9]*", "hlim="), 276 ("ttl=[0-9]*", "ttl="), 277 ("time=[0-9.-]*", "time="), 278 ("\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"), 279 ("[0-9\.]+/[0-9.]+", "/"), 280 ] 281 for pattern, repl in pattern_replacements: 282 output = re.sub(pattern, repl, output) 283 return output 284 285 286class TestPing(SingleVnetTestTemplate): 287 IPV6_PREFIXES: List[str] = ["2001:db8::1/64"] 288 IPV4_PREFIXES: List[str] = ["192.0.2.1/24"] 289 290 # Each param in testdata contains a dictionary with the command, 291 # and the expected outcome (returncode, redacted stdout, and stderr) 292 testdata = [ 293 pytest.param( 294 { 295 "args": "ping -4 -c1 -s56 -t1 localhost", 296 "returncode": 0, 297 "stdout": """\ 298PING localhost: 56 data bytes 29964 bytes from: icmp_seq=0 ttl= time= ms 300 301--- localhost ping statistics --- 3021 packets transmitted, 1 packets received, 0.0% packet loss 303round-trip min/avg/max/stddev = /// ms 304""", 305 "stderr": "", 306 }, 307 id="_4_c1_s56_t1_localhost", 308 ), 309 pytest.param( 310 { 311 "args": "ping -6 -c1 -s8 -t1 localhost", 312 "returncode": 0, 313 "stdout": """\ 314PING6(56=40+8+8 bytes) ::1 --> ::1 31516 bytes from ::1, icmp_seq=0 hlim= time= ms 316 317--- localhost ping6 statistics --- 3181 packets transmitted, 1 packets received, 0.0% packet loss 319round-trip min/avg/max/std-dev = /// ms 320""", 321 "stderr": "", 322 }, 323 id="_6_c1_s8_t1_localhost", 324 ), 325 pytest.param( 326 { 327 "args": "ping -A -c1 192.0.2.1", 328 "returncode": 0, 329 "stdout": """\ 330PING 192.0.2.1 (192.0.2.1): 56 data bytes 33164 bytes from: icmp_seq=0 ttl= time= ms 332 333--- 192.0.2.1 ping statistics --- 3341 packets transmitted, 1 packets received, 0.0% packet loss 335round-trip min/avg/max/stddev = /// ms 336""", 337 "stderr": "", 338 }, 339 id="_A_c1_192_0_2_1", 340 ), 341 pytest.param( 342 { 343 "args": "ping -A -c1 192.0.2.2", 344 "returncode": 2, 345 "stdout": """\ 346PING 192.0.2.2 (192.0.2.2): 56 data bytes 347 348--- 192.0.2.2 ping statistics --- 3491 packets transmitted, 0 packets received, 100.0% packet loss 350""", 351 "stderr": "", 352 }, 353 id="_A_c1_192_0_2_2", 354 ), 355 pytest.param( 356 { 357 "args": "ping -A -c1 2001:db8::1", 358 "returncode": 0, 359 "stdout": """\ 360PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 36116 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms 362 363--- 2001:db8::1 ping6 statistics --- 3641 packets transmitted, 1 packets received, 0.0% packet loss 365round-trip min/avg/max/std-dev = /// ms 366""", 367 "stderr": "", 368 }, 369 id="_A_c1_2001_db8__1", 370 ), 371 pytest.param( 372 { 373 "args": "ping -A -c1 2001:db8::2", 374 "returncode": 2, 375 "stdout": """\ 376PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 377 378--- 2001:db8::2 ping6 statistics --- 3791 packets transmitted, 0 packets received, 100.0% packet loss 380""", 381 "stderr": "", 382 }, 383 id="_A_c1_2001_db8__2", 384 ), 385 pytest.param( 386 { 387 "args": "ping -A -c3 192.0.2.1", 388 "returncode": 0, 389 "stdout": """\ 390PING 192.0.2.1 (192.0.2.1): 56 data bytes 39164 bytes from: icmp_seq=0 ttl= time= ms 39264 bytes from: icmp_seq=1 ttl= time= ms 39364 bytes from: icmp_seq=2 ttl= time= ms 394 395--- 192.0.2.1 ping statistics --- 3963 packets transmitted, 3 packets received, 0.0% packet loss 397round-trip min/avg/max/stddev = /// ms 398""", 399 "stderr": "", 400 }, 401 id="_A_3_192_0.2.1", 402 ), 403 pytest.param( 404 { 405 "args": "ping -A -c3 192.0.2.2", 406 "returncode": 2, 407 "stdout": """\ 408\x07\x07PING 192.0.2.2 (192.0.2.2): 56 data bytes 409 410--- 192.0.2.2 ping statistics --- 4113 packets transmitted, 0 packets received, 100.0% packet loss 412""", 413 "stderr": "", 414 }, 415 id="_A_c3_192_0_2_2", 416 ), 417 pytest.param( 418 { 419 "args": "ping -A -c3 2001:db8::1", 420 "returncode": 0, 421 "stdout": """\ 422PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 42316 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms 42416 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms 42516 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms 426 427--- 2001:db8::1 ping6 statistics --- 4283 packets transmitted, 3 packets received, 0.0% packet loss 429round-trip min/avg/max/std-dev = /// ms 430""", 431 "stderr": "", 432 }, 433 id="_A_c3_2001_db8__1", 434 ), 435 pytest.param( 436 { 437 "args": "ping -A -c3 2001:db8::2", 438 "returncode": 2, 439 "stdout": """\ 440\x07\x07PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 441 442--- 2001:db8::2 ping6 statistics --- 4433 packets transmitted, 0 packets received, 100.0% packet loss 444""", 445 "stderr": "", 446 }, 447 id="_A_c3_2001_db8__2", 448 ), 449 pytest.param( 450 { 451 "args": "ping -c1 192.0.2.1", 452 "returncode": 0, 453 "stdout": """\ 454PING 192.0.2.1 (192.0.2.1): 56 data bytes 45564 bytes from: icmp_seq=0 ttl= time= ms 456 457--- 192.0.2.1 ping statistics --- 4581 packets transmitted, 1 packets received, 0.0% packet loss 459round-trip min/avg/max/stddev = /// ms 460""", 461 "stderr": "", 462 }, 463 id="_c1_192_0_2_1", 464 ), 465 pytest.param( 466 { 467 "args": "ping -c1 192.0.2.2", 468 "returncode": 2, 469 "stdout": """\ 470PING 192.0.2.2 (192.0.2.2): 56 data bytes 471 472--- 192.0.2.2 ping statistics --- 4731 packets transmitted, 0 packets received, 100.0% packet loss 474""", 475 "stderr": "", 476 }, 477 id="_c1_192_0_2_2", 478 ), 479 pytest.param( 480 { 481 "args": "ping -c1 2001:db8::1", 482 "returncode": 0, 483 "stdout": """\ 484PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 48516 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms 486 487--- 2001:db8::1 ping6 statistics --- 4881 packets transmitted, 1 packets received, 0.0% packet loss 489round-trip min/avg/max/std-dev = /// ms 490""", 491 "stderr": "", 492 }, 493 id="_c1_2001_db8__1", 494 ), 495 pytest.param( 496 { 497 "args": "ping -c1 2001:db8::2", 498 "returncode": 2, 499 "stdout": """\ 500PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 501 502--- 2001:db8::2 ping6 statistics --- 5031 packets transmitted, 0 packets received, 100.0% packet loss 504""", 505 "stderr": "", 506 }, 507 id="_c1_2001_db8__2", 508 ), 509 pytest.param( 510 { 511 "args": "ping -c1 -S127.0.0.1 -s56 -t1 localhost", 512 "returncode": 0, 513 "stdout": """\ 514PING localhost from: 56 data bytes 51564 bytes from: icmp_seq=0 ttl= time= ms 516 517--- localhost ping statistics --- 5181 packets transmitted, 1 packets received, 0.0% packet loss 519round-trip min/avg/max/stddev = /// ms 520""", 521 "stderr": "", 522 }, 523 id="_c1_S127_0_0_1_s56_t1_localhost", 524 ), 525 pytest.param( 526 { 527 "args": "ping -c1 -S::1 -s8 -t1 localhost", 528 "returncode": 0, 529 "stdout": """\ 530PING6(56=40+8+8 bytes) ::1 --> ::1 53116 bytes from ::1, icmp_seq=0 hlim= time= ms 532 533--- localhost ping6 statistics --- 5341 packets transmitted, 1 packets received, 0.0% packet loss 535round-trip min/avg/max/std-dev = /// ms 536""", 537 "stderr": "", 538 }, 539 id="_c1_S__1_s8_t1_localhost", 540 ), 541 pytest.param( 542 { 543 "args": "ping -c3 192.0.2.1", 544 "returncode": 0, 545 "stdout": """\ 546PING 192.0.2.1 (192.0.2.1): 56 data bytes 54764 bytes from: icmp_seq=0 ttl= time= ms 54864 bytes from: icmp_seq=1 ttl= time= ms 54964 bytes from: icmp_seq=2 ttl= time= ms 550 551--- 192.0.2.1 ping statistics --- 5523 packets transmitted, 3 packets received, 0.0% packet loss 553round-trip min/avg/max/stddev = /// ms 554""", 555 "stderr": "", 556 }, 557 id="_c3_192_0_2_1", 558 ), 559 pytest.param( 560 { 561 "args": "ping -c3 192.0.2.2", 562 "returncode": 2, 563 "stdout": """\ 564PING 192.0.2.2 (192.0.2.2): 56 data bytes 565 566--- 192.0.2.2 ping statistics --- 5673 packets transmitted, 0 packets received, 100.0% packet loss 568""", 569 "stderr": "", 570 }, 571 id="_c3_192_0_2_2", 572 ), 573 pytest.param( 574 { 575 "args": "ping -c3 2001:db8::1", 576 "returncode": 0, 577 "stdout": """\ 578PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 57916 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms 58016 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms 58116 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms 582 583--- 2001:db8::1 ping6 statistics --- 5843 packets transmitted, 3 packets received, 0.0% packet loss 585round-trip min/avg/max/std-dev = /// ms 586""", 587 "stderr": "", 588 }, 589 id="_c3_2001_db8__1", 590 ), 591 pytest.param( 592 { 593 "args": "ping -c3 2001:db8::2", 594 "returncode": 2, 595 "stdout": """\ 596PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 597 598--- 2001:db8::2 ping6 statistics --- 5993 packets transmitted, 0 packets received, 100.0% packet loss 600""", 601 "stderr": "", 602 }, 603 id="_c3_2001_db8__2", 604 ), 605 pytest.param( 606 { 607 "args": "ping -q -c1 192.0.2.1", 608 "returncode": 0, 609 "stdout": """\ 610PING 192.0.2.1 (192.0.2.1): 56 data bytes 611 612--- 192.0.2.1 ping statistics --- 6131 packets transmitted, 1 packets received, 0.0% packet loss 614round-trip min/avg/max/stddev = /// ms 615""", 616 "stderr": "", 617 }, 618 id="_q_c1_192_0_2_1", 619 ), 620 pytest.param( 621 { 622 "args": "ping -q -c1 192.0.2.2", 623 "returncode": 2, 624 "stdout": """\ 625PING 192.0.2.2 (192.0.2.2): 56 data bytes 626 627--- 192.0.2.2 ping statistics --- 6281 packets transmitted, 0 packets received, 100.0% packet loss 629""", 630 "stderr": "", 631 }, 632 id="_q_c1_192_0_2_2", 633 ), 634 pytest.param( 635 { 636 "args": "ping -q -c1 2001:db8::1", 637 "returncode": 0, 638 "stdout": """\ 639PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 640 641--- 2001:db8::1 ping6 statistics --- 6421 packets transmitted, 1 packets received, 0.0% packet loss 643round-trip min/avg/max/std-dev = /// ms 644""", 645 "stderr": "", 646 }, 647 id="_q_c1_2001_db8__1", 648 ), 649 pytest.param( 650 { 651 "args": "ping -q -c1 2001:db8::2", 652 "returncode": 2, 653 "stdout": """\ 654PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 655 656--- 2001:db8::2 ping6 statistics --- 6571 packets transmitted, 0 packets received, 100.0% packet loss 658""", 659 "stderr": "", 660 }, 661 id="_q_c1_2001_db8__2", 662 ), 663 pytest.param( 664 { 665 "args": "ping -q -c3 192.0.2.1", 666 "returncode": 0, 667 "stdout": """\ 668PING 192.0.2.1 (192.0.2.1): 56 data bytes 669 670--- 192.0.2.1 ping statistics --- 6713 packets transmitted, 3 packets received, 0.0% packet loss 672round-trip min/avg/max/stddev = /// ms 673""", 674 "stderr": "", 675 }, 676 id="_q_c3_192_0_2_1", 677 ), 678 pytest.param( 679 { 680 "args": "ping -q -c3 192.0.2.2", 681 "returncode": 2, 682 "stdout": """\ 683PING 192.0.2.2 (192.0.2.2): 56 data bytes 684 685--- 192.0.2.2 ping statistics --- 6863 packets transmitted, 0 packets received, 100.0% packet loss 687""", 688 "stderr": "", 689 }, 690 id="_q_c3_192_0_2_2", 691 ), 692 pytest.param( 693 { 694 "args": "ping -q -c3 2001:db8::1", 695 "returncode": 0, 696 "stdout": """\ 697PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1 698 699--- 2001:db8::1 ping6 statistics --- 7003 packets transmitted, 3 packets received, 0.0% packet loss 701round-trip min/avg/max/std-dev = /// ms 702""", 703 "stderr": "", 704 }, 705 id="_q_c3_2001_db8__1", 706 ), 707 pytest.param( 708 { 709 "args": "ping -q -c3 2001:db8::2", 710 "returncode": 2, 711 "stdout": """\ 712PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2 713 714--- 2001:db8::2 ping6 statistics --- 7153 packets transmitted, 0 packets received, 100.0% packet loss 716""", 717 "stderr": "", 718 }, 719 id="_q_c3_2001_db8__2", 720 ), 721 ] 722 723 @pytest.mark.parametrize("expected", testdata) 724 def test_ping(self, expected): 725 """Test ping""" 726 ping = subprocess.run( 727 expected["args"].split(), 728 capture_output=True, 729 timeout=15, 730 text=True, 731 ) 732 assert ping.returncode == expected["returncode"] 733 assert redact(ping.stdout) == expected["stdout"] 734 assert ping.stderr == expected["stderr"] 735 736 # Each param in ping46_testdata contains a dictionary with the arguments 737 # and the expected outcome (returncode, redacted stdout, and stderr) 738 # common to `ping -4` and `ping -6` 739 ping46_testdata = [ 740 pytest.param( 741 { 742 "args": "-Wx localhost", 743 "returncode": os.EX_USAGE, 744 "stdout": "", 745 "stderr": "ping: invalid timing interval: `x'\n", 746 }, 747 id="_Wx_localhost", 748 ), 749 ] 750 751 @pytest.mark.parametrize("expected", ping46_testdata) 752 def test_ping_46(self, expected): 753 """Test ping -4/ping -6""" 754 for version in [4, 6]: 755 ping = subprocess.run( 756 ["ping", f"-{version}"] + expected["args"].split(), 757 capture_output=True, 758 timeout=15, 759 text=True, 760 ) 761 assert ping.returncode == expected["returncode"] 762 assert redact(ping.stdout) == expected["stdout"] 763 assert ping.stderr == expected["stderr"] 764 765 # Each param in pinger_testdata contains a dictionary with the keywords to 766 # `pinger()` and a dictionary with the expected outcome (returncode, 767 # stdout, stderr, and if ping's output is redacted) 768 pinger_testdata = [ 769 pytest.param( 770 { 771 "src": "192.0.2.1", 772 "dst": "192.0.2.2", 773 "icmp_type": 0, 774 "icmp_code": 0, 775 }, 776 { 777 "returncode": 0, 778 "stdout": """\ 779PING 192.0.2.2 (192.0.2.2): 56 data bytes 78064 bytes from: icmp_seq=0 ttl= time= ms 781 782--- 192.0.2.2 ping statistics --- 7831 packets transmitted, 1 packets received, 0.0% packet loss 784round-trip min/avg/max/stddev = /// ms 785""", 786 "stderr": "", 787 "redacted": True, 788 }, 789 id="_0_0", 790 ), 791 pytest.param( 792 { 793 "src": "192.0.2.1", 794 "dst": "192.0.2.2", 795 "icmp_type": 0, 796 "icmp_code": 0, 797 "opts": "EOL", 798 }, 799 { 800 "returncode": 0, 801 "stdout": """\ 802PING 192.0.2.2 (192.0.2.2): 56 data bytes 80364 bytes from: icmp_seq=0 ttl= time= ms 804wrong total length 88 instead of 84 805 806--- 192.0.2.2 ping statistics --- 8071 packets transmitted, 1 packets received, 0.0% packet loss 808round-trip min/avg/max/stddev = /// ms 809""", 810 "stderr": "", 811 "redacted": True, 812 }, 813 id="_0_0_opts_EOL", 814 ), 815 pytest.param( 816 { 817 "src": "192.0.2.1", 818 "dst": "192.0.2.2", 819 "icmp_type": 0, 820 "icmp_code": 0, 821 "opts": "LSRR", 822 }, 823 { 824 "returncode": 0, 825 "stdout": """\ 826PING 192.0.2.2 (192.0.2.2): 56 data bytes 82764 bytes from: icmp_seq=0 ttl= time= ms 828LSRR: 192.0.2.10 829 192.0.2.20 830 192.0.2.30 831 192.0.2.40 832 192.0.2.50 833 192.0.2.60 834 192.0.2.70 835 192.0.2.80 836 192.0.2.90 837 838--- 192.0.2.2 ping statistics --- 8391 packets transmitted, 1 packets received, 0.0% packet loss 840round-trip min/avg/max/stddev = /// ms 841""", 842 "stderr": "", 843 "redacted": True, 844 }, 845 id="_0_0_opts_LSRR", 846 ), 847 pytest.param( 848 { 849 "src": "192.0.2.1", 850 "dst": "192.0.2.2", 851 "icmp_type": 0, 852 "icmp_code": 0, 853 "opts": "LSRR-trunc", 854 }, 855 { 856 "returncode": 0, 857 "stdout": """\ 858PING 192.0.2.2 (192.0.2.2): 56 data bytes 85964 bytes from: icmp_seq=0 ttl= time= ms 860LSRR: (truncated route) 861 862 863--- 192.0.2.2 ping statistics --- 8641 packets transmitted, 1 packets received, 0.0% packet loss 865round-trip min/avg/max/stddev = /// ms 866""", 867 "stderr": "", 868 "redacted": True, 869 }, 870 id="_0_0_opts_LSRR_trunc", 871 ), 872 pytest.param( 873 { 874 "src": "192.0.2.1", 875 "dst": "192.0.2.2", 876 "icmp_type": 0, 877 "icmp_code": 0, 878 "opts": "SSRR", 879 }, 880 { 881 "returncode": 0, 882 "stdout": """\ 883PING 192.0.2.2 (192.0.2.2): 56 data bytes 88464 bytes from: icmp_seq=0 ttl= time= ms 885SSRR: 192.0.2.10 886 192.0.2.20 887 192.0.2.30 888 192.0.2.40 889 192.0.2.50 890 192.0.2.60 891 192.0.2.70 892 192.0.2.80 893 192.0.2.90 894 895--- 192.0.2.2 ping statistics --- 8961 packets transmitted, 1 packets received, 0.0% packet loss 897round-trip min/avg/max/stddev = /// ms 898""", 899 "stderr": "", 900 "redacted": True, 901 }, 902 id="_0_0_opts_SSRR", 903 ), 904 pytest.param( 905 { 906 "src": "192.0.2.1", 907 "dst": "192.0.2.2", 908 "icmp_type": 0, 909 "icmp_code": 0, 910 "opts": "SSRR-trunc", 911 }, 912 { 913 "returncode": 0, 914 "stdout": """\ 915PING 192.0.2.2 (192.0.2.2): 56 data bytes 91664 bytes from: icmp_seq=0 ttl= time= ms 917SSRR: (truncated route) 918 919 920--- 192.0.2.2 ping statistics --- 9211 packets transmitted, 1 packets received, 0.0% packet loss 922round-trip min/avg/max/stddev = /// ms 923""", 924 "stderr": "", 925 "redacted": True, 926 }, 927 id="_0_0_opts_SSRR_trunc", 928 ), 929 pytest.param( 930 { 931 "src": "192.0.2.1", 932 "dst": "192.0.2.2", 933 "icmp_type": 0, 934 "icmp_code": 0, 935 "opts": "RR", 936 }, 937 { 938 "returncode": 0, 939 "stdout": """\ 940PING 192.0.2.2 (192.0.2.2): 56 data bytes 94164 bytes from: icmp_seq=0 ttl= time= ms 942RR: 192.0.2.10 943 192.0.2.20 944 192.0.2.30 945 192.0.2.40 946 192.0.2.50 947 192.0.2.60 948 192.0.2.70 949 192.0.2.80 950 192.0.2.90 951 952--- 192.0.2.2 ping statistics --- 9531 packets transmitted, 1 packets received, 0.0% packet loss 954round-trip min/avg/max/stddev = /// ms 955""", 956 "stderr": "", 957 "redacted": True, 958 }, 959 id="_0_0_opts_RR", 960 ), 961 pytest.param( 962 { 963 "src": "192.0.2.1", 964 "dst": "192.0.2.2", 965 "icmp_type": 0, 966 "icmp_code": 0, 967 "opts": "RR-same", 968 }, 969 { 970 "returncode": 0, 971 "stdout": """\ 972PING 192.0.2.2 (192.0.2.2): 56 data bytes 97364 bytes from: icmp_seq=0 ttl= time= ms (same route) 974 975--- 192.0.2.2 ping statistics --- 9761 packets transmitted, 1 packets received, 0.0% packet loss 977round-trip min/avg/max/stddev = /// ms 978""", 979 "stderr": "", 980 "redacted": True, 981 }, 982 id="_0_0_opts_RR_same", 983 ), 984 pytest.param( 985 { 986 "src": "192.0.2.1", 987 "dst": "192.0.2.2", 988 "icmp_type": 0, 989 "icmp_code": 0, 990 "opts": "RR-trunc", 991 }, 992 { 993 "returncode": 0, 994 "stdout": """\ 995PING 192.0.2.2 (192.0.2.2): 56 data bytes 99664 bytes from: icmp_seq=0 ttl= time= ms 997RR: (truncated route) 998 999--- 192.0.2.2 ping statistics --- 10001 packets transmitted, 1 packets received, 0.0% packet loss 1001round-trip min/avg/max/stddev = /// ms 1002""", 1003 "stderr": "", 1004 "redacted": True, 1005 }, 1006 id="_0_0_opts_RR_trunc", 1007 ), 1008 pytest.param( 1009 { 1010 "src": "192.0.2.1", 1011 "dst": "192.0.2.2", 1012 "icmp_type": 0, 1013 "icmp_code": 0, 1014 "opts": "NOP", 1015 }, 1016 { 1017 "returncode": 0, 1018 "stdout": """\ 1019PING 192.0.2.2 (192.0.2.2): 56 data bytes 102064 bytes from: icmp_seq=0 ttl= time= ms 1021wrong total length 88 instead of 84 1022NOP 1023 1024--- 192.0.2.2 ping statistics --- 10251 packets transmitted, 1 packets received, 0.0% packet loss 1026round-trip min/avg/max/stddev = /// ms 1027""", 1028 "stderr": "", 1029 "redacted": True, 1030 }, 1031 id="_0_0_opts_NOP", 1032 ), 1033 pytest.param( 1034 { 1035 "src": "192.0.2.1", 1036 "dst": "192.0.2.2", 1037 "icmp_type": 0, 1038 "icmp_code": 0, 1039 "opts": "NOP-40", 1040 }, 1041 { 1042 "returncode": 0, 1043 "stdout": """\ 1044PING 192.0.2.2 (192.0.2.2): 56 data bytes 104564 bytes from: icmp_seq=0 ttl= time= ms 1046wrong total length 124 instead of 84 1047NOP 1048NOP 1049NOP 1050NOP 1051NOP 1052NOP 1053NOP 1054NOP 1055NOP 1056NOP 1057NOP 1058NOP 1059NOP 1060NOP 1061NOP 1062NOP 1063NOP 1064NOP 1065NOP 1066NOP 1067NOP 1068NOP 1069NOP 1070NOP 1071NOP 1072NOP 1073NOP 1074NOP 1075NOP 1076NOP 1077NOP 1078NOP 1079NOP 1080NOP 1081NOP 1082NOP 1083NOP 1084NOP 1085NOP 1086NOP 1087 1088--- 192.0.2.2 ping statistics --- 10891 packets transmitted, 1 packets received, 0.0% packet loss 1090round-trip min/avg/max/stddev = /// ms 1091""", 1092 "stderr": "", 1093 "redacted": True, 1094 }, 1095 id="_0_0_opts_NOP_40", 1096 ), 1097 pytest.param( 1098 { 1099 "src": "192.0.2.1", 1100 "dst": "192.0.2.2", 1101 "icmp_type": 0, 1102 "icmp_code": 0, 1103 "opts": "unk", 1104 }, 1105 { 1106 "returncode": 0, 1107 "stdout": """\ 1108PING 192.0.2.2 (192.0.2.2): 56 data bytes 110964 bytes from: icmp_seq=0 ttl= time= ms 1110wrong total length 88 instead of 84 1111unknown option 9f 1112 1113--- 192.0.2.2 ping statistics --- 11141 packets transmitted, 1 packets received, 0.0% packet loss 1115round-trip min/avg/max/stddev = /// ms 1116""", 1117 "stderr": "", 1118 "redacted": True, 1119 }, 1120 id="_0_0_opts_unk", 1121 ), 1122 pytest.param( 1123 { 1124 "src": "192.0.2.1", 1125 "dst": "192.0.2.2", 1126 "icmp_type": 3, 1127 "icmp_code": 1, 1128 "opts": "NOP-40", 1129 }, 1130 { 1131 "returncode": 2, 1132 "stdout": """\ 1133PING 192.0.2.2 (192.0.2.2): 56 data bytes 1134132 bytes from 192.0.2.2: Destination Host Unreachable 1135Vr HL TOS Len ID Flg off TTL Pro cks Src Dst 1136 4 f 00 007c 0001 0 0000 40 01 d868 192.0.2.1 192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101 1137 1138 1139--- 192.0.2.2 ping statistics --- 11401 packets transmitted, 0 packets received, 100.0% packet loss 1141""", 1142 "stderr": "", 1143 "redacted": False, 1144 }, 1145 id="_3_1_opts_NOP_40", 1146 ), 1147 pytest.param( 1148 { 1149 "src": "192.0.2.1", 1150 "dst": "192.0.2.2", 1151 "icmp_type": 3, 1152 "icmp_code": 1, 1153 "flags": "DF", 1154 }, 1155 { 1156 "returncode": 2, 1157 "stdout": """\ 1158PING 192.0.2.2 (192.0.2.2): 56 data bytes 115992 bytes from 192.0.2.2: Destination Host Unreachable 1160Vr HL TOS Len ID Flg off TTL Pro cks Src Dst 1161 4 5 00 0054 0001 2 0000 40 01 b6a4 192.0.2.1 192.0.2.2 1162 1163 1164--- 192.0.2.2 ping statistics --- 11651 packets transmitted, 0 packets received, 100.0% packet loss 1166""", 1167 "stderr": "", 1168 "redacted": False, 1169 }, 1170 id="_3_1_flags_DF", 1171 ), 1172 pytest.param( 1173 { 1174 "src": "192.0.2.1", 1175 "dst": "192.0.2.2", 1176 "icmp_type": 3, 1177 "icmp_code": 1, 1178 "special": "tcp", 1179 }, 1180 { 1181 "returncode": 2, 1182 "stdout": """\ 1183PATTERN: 0x01 1184PING 192.0.2.2 (192.0.2.2): 56 data bytes 1185 1186--- 192.0.2.2 ping statistics --- 11871 packets transmitted, 0 packets received, 100.0% packet loss 1188""", 1189 "stderr": """\ 1190ping: quoted data too short (40 bytes) from 192.0.2.2 1191""", 1192 "redacted": False, 1193 }, 1194 id="_3_1_special_tcp", 1195 ), 1196 pytest.param( 1197 { 1198 "src": "192.0.2.1", 1199 "dst": "192.0.2.2", 1200 "icmp_type": 3, 1201 "icmp_code": 1, 1202 "special": "udp", 1203 }, 1204 { 1205 "returncode": 2, 1206 "stdout": """\ 1207PATTERN: 0x01 1208PING 192.0.2.2 (192.0.2.2): 56 data bytes 1209 1210--- 192.0.2.2 ping statistics --- 12111 packets transmitted, 0 packets received, 100.0% packet loss 1212""", 1213 "stderr": """\ 1214ping: quoted data too short (28 bytes) from 192.0.2.2 1215""", 1216 "redacted": False, 1217 }, 1218 id="_3_1_special_udp", 1219 ), 1220 pytest.param( 1221 { 1222 "src": "192.0.2.1", 1223 "dst": "192.0.2.2", 1224 "icmp_type": 3, 1225 "icmp_code": 1, 1226 "verbose": False, 1227 }, 1228 { 1229 "returncode": 2, 1230 "stdout": """\ 1231PING 192.0.2.2 (192.0.2.2): 56 data bytes 123292 bytes from 192.0.2.2: Destination Host Unreachable 1233Vr HL TOS Len ID Flg off TTL Pro cks Src Dst 1234 4 5 00 0054 0001 0 0000 40 01 f6a4 192.0.2.1 192.0.2.2 1235 1236 1237--- 192.0.2.2 ping statistics --- 12381 packets transmitted, 0 packets received, 100.0% packet loss 1239""", 1240 "stderr": "", 1241 "redacted": False, 1242 }, 1243 id="_3_1_verbose_false", 1244 ), 1245 pytest.param( 1246 { 1247 "src": "192.0.2.1", 1248 "dst": "192.0.2.2", 1249 "icmp_type": 3, 1250 "icmp_code": 1, 1251 "special": "not-mine", 1252 "verbose": False, 1253 }, 1254 { 1255 "returncode": 2, 1256 "stdout": """\ 1257PATTERN: 0x01 1258PING 192.0.2.2 (192.0.2.2): 56 data bytes 1259 1260--- 192.0.2.2 ping statistics --- 12611 packets transmitted, 0 packets received, 100.0% packet loss 1262""", 1263 "stderr": "", 1264 "redacted": False, 1265 }, 1266 id="_3_1_special_not_mine_verbose_false", 1267 ), 1268 pytest.param( 1269 { 1270 "src": "192.0.2.1", 1271 "dst": "192.0.2.2", 1272 "icmp_type": 0, 1273 "icmp_code": 0, 1274 "special": "warp", 1275 }, 1276 { 1277 "returncode": 0, 1278 "stdout": """\ 1279PATTERN: 0x01 1280PING 192.0.2.2 (192.0.2.2): 56 data bytes 128164 bytes from: icmp_seq=0 ttl= time= ms 1282 1283--- 192.0.2.2 ping statistics --- 12841 packets transmitted, 1 packets received, 0.0% packet loss 1285round-trip min/avg/max/stddev = /// ms 1286""", 1287 "stderr": """\ 1288ping: time of day goes back (- ms), clamping time to 0 1289""", 1290 "redacted": True, 1291 }, 1292 id="_0_0_special_warp", 1293 ), 1294 ] 1295 1296 @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata) 1297 @pytest.mark.require_progs(["scapy"]) 1298 @pytest.mark.require_user("root") 1299 def test_pinger(self, pinger_kargs, expected): 1300 """Test ping using pinger(), a reply faker""" 1301 iface = IfaceFactory().create_iface("", "tun")[0].name 1302 ping = pinger(iface, **pinger_kargs) 1303 assert ping.returncode == expected["returncode"] 1304 if expected["redacted"]: 1305 assert redact(ping.stdout) == expected["stdout"] 1306 assert redact(ping.stderr) == expected["stderr"] 1307 else: 1308 assert ping.stdout == expected["stdout"] 1309 assert ping.stderr == expected["stderr"] 1310