xref: /freebsd/sbin/ping/tests/test_ping.py (revision 0b9434d707c71de7b1b62e400e6e780224f0384d)
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)
15try:
16    import scapy.all as sc
17except ImportError as e:
18    # Fake scapy well enough to be able to list test cases
19    from types import SimpleNamespace
20    sc = SimpleNamespace(
21        scapy=SimpleNamespace(
22            fields=SimpleNamespace(
23                SourceIPField=0,
24                ByteEnumField=0,
25                MultiEnumField=0,
26                BitField=0,
27                FlagsField=0,
28                ByteField=0,
29                IPField=0,
30                ShortField=0,
31            ),
32            layers=SimpleNamespace(
33                inet=SimpleNamespace(
34                    DestIPField=0,
35                    ICMPTimeStampField=0,
36                )
37            )
38        )
39    )
40
41
42def build_response_packet(echo, ip, icmp, oip_ihl, special):
43    icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38]
44    oip = echo[sc.IP]
45    oicmp = echo[sc.ICMP]
46    load = echo[sc.ICMP].payload
47    oip[sc.IP].remove_payload()
48    oicmp[sc.ICMP].remove_payload()
49    oicmp.type = 8
50
51    # As if the original IP packet had these set
52    oip.ihl = None
53    oip.len = None
54    oip.id = 1
55    oip.flags = ip.flags
56    oip.chksum = None
57    oip.options = ip.options
58
59    # Inner packet (oip) options
60    if oip_ihl:
61        oip.ihl = oip_ihl
62
63    # Special options
64    if special == "no-payload":
65        load = ""
66    if special == "tcp":
67        oip.proto = "tcp"
68        tcp = sc.TCP(sport=1234, dport=5678)
69        return ip / icmp / oip / tcp
70    if special == "udp":
71        oip.proto = "udp"
72        udp = sc.UDP(sport=1234, dport=5678)
73        return ip / icmp / oip / udp
74    if special == "warp":
75        # Build a package with a timestamp of INT_MAX
76        # (time-warped package)
77        payload_no_timestamp = sc.bytes_hex(load)[16:]
78        load = b"\x7f" + (b"\xff" * 7) + sc.hex_bytes(payload_no_timestamp)
79    if special == "wrong":
80        # Build a package with a wrong last byte
81        payload_no_last_byte = sc.bytes_hex(load)[:-2]
82        load = (sc.hex_bytes(payload_no_last_byte)) + b"\x00"
83    if special == "short-wrong":
84        # Build a short package with a wrong last byte
85        payload_no_last_byte = sc.bytes_hex(load)[:-4]
86        load = (sc.hex_bytes(payload_no_last_byte)) + b"\x00"
87    if special == "not-mine":
88        # Modify the ICMP Identifier field
89        oicmp.id += 1
90
91    if icmp.type in icmp_id_seq_types:
92        pkt = ip / icmp / load
93    else:
94        del ip.options
95        pkt = ip / icmp / oip / oicmp / load
96    return pkt
97
98
99def generate_ip_options(opts):
100    if not opts:
101        return []
102
103    routers = [
104        "192.0.2.10",
105        "192.0.2.20",
106        "192.0.2.30",
107        "192.0.2.40",
108        "192.0.2.50",
109        "192.0.2.60",
110        "192.0.2.70",
111        "192.0.2.80",
112        "192.0.2.90",
113    ]
114    routers_zero = [0, 0, 0, 0, 0, 0, 0, 0, 0]
115    if opts == "EOL":
116        options = sc.IPOption_EOL()
117    elif opts == "NOP":
118        options = sc.IPOption_NOP()
119    elif opts == "NOP-40":
120        options = sc.IPOption_NOP() * 40
121    elif opts == "RR":
122        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
123        options = sc.IPOption_RR(pointer=40, routers=routers)
124    elif opts == "RR-same":
125        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
126        options = sc.IPOption_RR(pointer=3, routers=routers_zero)
127    elif opts == "RR-trunc":
128        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
129        options = sc.IPOption_RR(length=7, routers=routers_zero)
130    elif opts == "LSRR":
131        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
132        options = sc.IPOption_LSRR(routers=routers)
133    elif opts == "LSRR-trunc":
134        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
135        options = sc.IPOption_LSRR(length=3, routers=routers_zero)
136    elif opts == "SSRR":
137        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
138        options = sc.IPOption_SSRR(routers=routers)
139    elif opts == "SSRR-trunc":
140        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
141        options = sc.IPOption_SSRR(length=3, routers=routers_zero)
142    elif opts == "unk":
143        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
144        options = b"\x9f"
145    elif opts == "unk-40":
146        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
147        options = b"\x9f" * 40
148    else:
149        options = []
150    return options
151
152
153def pinger(
154    # Required arguments
155    # Avoid setting defaults on these arguments,
156    # as we want to set them explicitly in the tests
157    iface: str,
158    /,
159    src: sc.scapy.fields.SourceIPField,
160    dst: sc.scapy.layers.inet.DestIPField,
161    icmp_type: sc.scapy.fields.ByteEnumField,
162    icmp_code: sc.scapy.fields.MultiEnumField,
163    # IP arguments
164    ihl: Optional[sc.scapy.fields.BitField] = None,
165    flags: Optional[sc.scapy.fields.FlagsField] = 0,
166    opts: Optional[str] = None,
167    oip_ihl: Optional[sc.scapy.fields.BitField] = None,
168    special: Optional[str] = None,
169    # ICMP arguments
170    # Match names with <netinet/ip_icmp.h>
171    icmp_pptr: sc.scapy.fields.ByteField = 0,
172    icmp_gwaddr: sc.scapy.fields.IPField = "0.0.0.0",
173    icmp_nextmtu: sc.scapy.fields.ShortField = 0,
174    icmp_otime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
175    icmp_rtime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
176    icmp_ttime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
177    icmp_mask: sc.scapy.fields.IPField = "0.0.0.0",
178    request: Optional[str] = None,
179    # Miscellaneous arguments
180    count: int = 1,
181    dup: bool = False,
182    verbose: bool = True,
183) -> subprocess.CompletedProcess:
184    """P I N G E R
185
186    Echo reply faker
187
188    :param str iface: Interface to send packet to
189    :keyword src: Source packet IP
190    :type src: class:`scapy.fields.SourceIPField`
191    :keyword dst: Destination packet IP
192    :type dst: class:`scapy.layers.inet.DestIPField`
193    :keyword icmp_type: ICMP type
194    :type icmp_type: class:`scapy.fields.ByteEnumField`
195    :keyword icmp_code: ICMP code
196    :type icmp_code: class:`scapy.fields.MultiEnumField`
197
198    :keyword ihl: Internet Header Length, defaults to None
199    :type ihl: class:`scapy.fields.BitField`, optional
200    :keyword flags: IP flags - one of `DF`, `MF` or `evil`, defaults to 0
201    :type flags: class:`scapy.fields.FlagsField`, optional
202    :keyword opts: Include IP options - one of `EOL`, `NOP`, `NOP-40`, `unk`,
203        `unk-40`, `RR`, `RR-same`, `RR-trunc`, `LSRR`, `LSRR-trunc`, `SSRR` or
204        `SSRR-trunc`, defaults to None
205    :type opts: str, optional
206    :keyword oip_ihl: Inner packet's Internet Header Length, defaults to None
207    :type oip_ihl: class:`scapy.fields.BitField`, optional
208    :keyword special: Send a special packet - one of `no-payload`, `not-mine`,
209        `short-wrong`, `tcp`, `udp`, `wrong` or `warp`, defaults to None
210    :type special: str, optional
211    :keyword icmp_pptr: ICMP pointer, defaults to 0
212    :type icmp_pptr: class:`scapy.fields.ByteField`
213    :keyword icmp_gwaddr: ICMP gateway IP address, defaults to "0.0.0.0"
214    :type icmp_gwaddr: class:`scapy.fields.IPField`
215    :keyword icmp_nextmtu: ICMP next MTU, defaults to 0
216    :type icmp_nextmtu: class:`scapy.fields.ShortField`
217    :keyword icmp_otime: ICMP originate timestamp, defaults to 0
218    :type icmp_otime: class:`scapy.layers.inet.ICMPTimeStampField`
219    :keyword icmp_rtime: ICMP receive timestamp, defaults to 0
220    :type icmp_rtime: class:`scapy.layers.inet.ICMPTimeStampField`
221    :keyword icmp_ttime: ICMP transmit timestamp, defaults to 0
222    :type icmp_ttime: class:`scapy.layers.inet.ICMPTimeStampField`
223    :keyword icmp_mask: ICMP address mask, defaults to "0.0.0.0"
224    :type icmp_mask: class:`scapy.fields.IPField`
225    :keyword request: Request type - one of `mask` or `timestamp`,
226        defaults to None
227    :type request: str, optional
228    :keyword count: Number of packets to send, defaults to 1
229    :type count: int
230    :keyword dup: Duplicate packets, defaults to `False`
231    :type dup: bool
232    :keyword verbose: Turn on/off verbosity, defaults to `True`
233    :type verbose: bool
234
235    :return: A class:`subprocess.CompletedProcess` with the output from the
236        ping utility
237    :rtype: class:`subprocess.CompletedProcess`
238    """
239    tun = sc.TunTapInterface(iface)
240    subprocess.run(["ifconfig", tun.iface, "up"], check=True)
241    subprocess.run(["ifconfig", tun.iface, src, dst], check=True)
242    ip_opts = generate_ip_options(opts)
243    ip = sc.IP(ihl=ihl, flags=flags, src=dst, dst=src, options=ip_opts)
244    command = [
245        "/sbin/ping",
246        "-c",
247        str(count),
248        "-t",
249        str(count),
250    ]
251    if verbose:
252        command += ["-v"]
253    if request == "mask":
254        command += ["-Mm"]
255    if request == "timestamp":
256        command += ["-Mt"]
257    if special:
258        command += ["-p1"]
259    if opts in [
260        "RR",
261        "RR-same",
262        "RR-trunc",
263        "LSRR",
264        "LSRR-trunc",
265        "SSRR",
266        "SSRR-trunc",
267    ]:
268        command += ["-R"]
269    command += [dst]
270    with subprocess.Popen(
271        args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
272    ) as ping:
273        for dummy in range(count):
274            echo = tun.recv()
275            icmp = sc.ICMP(
276                type=icmp_type,
277                code=icmp_code,
278                id=echo[sc.ICMP].id,
279                seq=echo[sc.ICMP].seq,
280                ts_ori=icmp_otime,
281                ts_rx=icmp_rtime,
282                ts_tx=icmp_ttime,
283                gw=icmp_gwaddr,
284                ptr=icmp_pptr,
285                addr_mask=icmp_mask,
286                nexthopmtu=icmp_nextmtu,
287            )
288            pkt = build_response_packet(echo, ip, icmp, oip_ihl, special)
289            tun.send(pkt)
290            if dup is True:
291                tun.send(pkt)
292        stdout, stderr = ping.communicate()
293    return subprocess.CompletedProcess(
294        ping.args, ping.returncode, stdout, stderr
295    )
296
297
298def redact(output):
299    """Redact some elements of ping's output"""
300    pattern_replacements = [
301        (r"localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"),
302        (r"from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"),
303        ("hlim=[0-9]*", "hlim="),
304        ("ttl=[0-9]*", "ttl="),
305        ("time=[0-9.-]*", "time="),
306        ("cp: .*", "cp: xx xx xx xx xx xx xx xx"),
307        ("dp: .*", "dp: xx xx xx xx xx xx xx xx"),
308        (r"\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"),
309        (r"[0-9\.]+/[0-9.]+", "/"),
310    ]
311    for pattern, repl in pattern_replacements:
312        output = re.sub(pattern, repl, output)
313    return output
314
315
316class TestPing(SingleVnetTestTemplate):
317    IPV6_PREFIXES: List[str] = ["2001:db8::1/64"]
318    IPV4_PREFIXES: List[str] = ["192.0.2.1/24"]
319
320    # Each param in testdata contains a dictionary with the command,
321    # and the expected outcome (returncode, redacted stdout, and stderr)
322    testdata = [
323        pytest.param(
324            {
325                "args": "ping -4 -c1 -s56 -t1 localhost",
326                "returncode": 0,
327                "stdout": """\
328PING localhost: 56 data bytes
32964 bytes from: icmp_seq=0 ttl= time= ms
330
331--- localhost ping statistics ---
3321 packets transmitted, 1 packets received, 0.0% packet loss
333round-trip min/avg/max/stddev = /// ms
334""",
335                "stderr": "",
336            },
337            id="_4_c1_s56_t1_localhost",
338        ),
339        pytest.param(
340            {
341                "args": "ping -6 -c1 -s8 -t1 localhost",
342                "returncode": 0,
343                "stdout": """\
344PING(56=40+8+8 bytes) ::1 --> ::1
34516 bytes from ::1, icmp_seq=0 hlim= time= ms
346
347--- localhost ping statistics ---
3481 packets transmitted, 1 packets received, 0.0% packet loss
349round-trip min/avg/max/stddev = /// ms
350""",
351                "stderr": "",
352            },
353            id="_6_c1_s8_t1_localhost",
354        ),
355        pytest.param(
356            {
357                "args": "ping -A -c1 192.0.2.1",
358                "returncode": 0,
359                "stdout": """\
360PING 192.0.2.1 (192.0.2.1): 56 data bytes
36164 bytes from: icmp_seq=0 ttl= time= ms
362
363--- 192.0.2.1 ping statistics ---
3641 packets transmitted, 1 packets received, 0.0% packet loss
365round-trip min/avg/max/stddev = /// ms
366""",
367                "stderr": "",
368            },
369            id="_A_c1_192_0_2_1",
370        ),
371        pytest.param(
372            {
373                "args": "ping -A -c1 192.0.2.2",
374                "returncode": 2,
375                "stdout": """\
376PING 192.0.2.2 (192.0.2.2): 56 data bytes
377
378--- 192.0.2.2 ping statistics ---
3791 packets transmitted, 0 packets received, 100.0% packet loss
380""",
381                "stderr": "",
382            },
383            id="_A_c1_192_0_2_2",
384        ),
385        pytest.param(
386            {
387                "args": "ping -A -c1 2001:db8::1",
388                "returncode": 0,
389                "stdout": """\
390PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
39116 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
392
393--- 2001:db8::1 ping statistics ---
3941 packets transmitted, 1 packets received, 0.0% packet loss
395round-trip min/avg/max/stddev = /// ms
396""",
397                "stderr": "",
398            },
399            id="_A_c1_2001_db8__1",
400        ),
401        pytest.param(
402            {
403                "args": "ping -A -c1 2001:db8::2",
404                "returncode": 2,
405                "stdout": """\
406PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
407
408--- 2001:db8::2 ping statistics ---
4091 packets transmitted, 0 packets received, 100.0% packet loss
410""",
411                "stderr": "",
412            },
413            id="_A_c1_2001_db8__2",
414        ),
415        pytest.param(
416            {
417                "args": "ping -A -c3 192.0.2.1",
418                "returncode": 0,
419                "stdout": """\
420PING 192.0.2.1 (192.0.2.1): 56 data bytes
42164 bytes from: icmp_seq=0 ttl= time= ms
42264 bytes from: icmp_seq=1 ttl= time= ms
42364 bytes from: icmp_seq=2 ttl= time= ms
424
425--- 192.0.2.1 ping statistics ---
4263 packets transmitted, 3 packets received, 0.0% packet loss
427round-trip min/avg/max/stddev = /// ms
428""",
429                "stderr": "",
430            },
431            id="_A_3_192_0.2.1",
432        ),
433        pytest.param(
434            {
435                "args": "ping -A -c3 192.0.2.2",
436                "returncode": 2,
437                "stdout": """\
438PING 192.0.2.2 (192.0.2.2): 56 data bytes
439\x07\x07
440--- 192.0.2.2 ping statistics ---
4413 packets transmitted, 0 packets received, 100.0% packet loss
442""",
443                "stderr": "",
444            },
445            id="_A_c3_192_0_2_2",
446        ),
447        pytest.param(
448            {
449                "args": "ping -A -c3 2001:db8::1",
450                "returncode": 0,
451                "stdout": """\
452PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
45316 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
45416 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
45516 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
456
457--- 2001:db8::1 ping statistics ---
4583 packets transmitted, 3 packets received, 0.0% packet loss
459round-trip min/avg/max/stddev = /// ms
460""",
461                "stderr": "",
462            },
463            id="_A_c3_2001_db8__1",
464        ),
465        pytest.param(
466            {
467                "args": "ping -A -c3 2001:db8::2",
468                "returncode": 2,
469                "stdout": """\
470PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
471\x07\x07
472--- 2001:db8::2 ping statistics ---
4733 packets transmitted, 0 packets received, 100.0% packet loss
474""",
475                "stderr": "",
476            },
477            id="_A_c3_2001_db8__2",
478        ),
479        pytest.param(
480            {
481                "args": "ping -c1 192.0.2.1",
482                "returncode": 0,
483                "stdout": """\
484PING 192.0.2.1 (192.0.2.1): 56 data bytes
48564 bytes from: icmp_seq=0 ttl= time= ms
486
487--- 192.0.2.1 ping statistics ---
4881 packets transmitted, 1 packets received, 0.0% packet loss
489round-trip min/avg/max/stddev = /// ms
490""",
491                "stderr": "",
492            },
493            id="_c1_192_0_2_1",
494        ),
495        pytest.param(
496            {
497                "args": "ping -c1 192.0.2.2",
498                "returncode": 2,
499                "stdout": """\
500PING 192.0.2.2 (192.0.2.2): 56 data bytes
501
502--- 192.0.2.2 ping statistics ---
5031 packets transmitted, 0 packets received, 100.0% packet loss
504""",
505                "stderr": "",
506            },
507            id="_c1_192_0_2_2",
508        ),
509        pytest.param(
510            {
511                "args": "ping -c1 2001:db8::1",
512                "returncode": 0,
513                "stdout": """\
514PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
51516 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
516
517--- 2001:db8::1 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_2001_db8__1",
524        ),
525        pytest.param(
526            {
527                "args": "ping -c1 2001:db8::2",
528                "returncode": 2,
529                "stdout": """\
530PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
531
532--- 2001:db8::2 ping statistics ---
5331 packets transmitted, 0 packets received, 100.0% packet loss
534""",
535                "stderr": "",
536            },
537            id="_c1_2001_db8__2",
538        ),
539        pytest.param(
540            {
541                "args": "ping -c1 -S127.0.0.1 -s56 -t1 localhost",
542                "returncode": 0,
543                "stdout": """\
544PING localhost from: 56 data bytes
54564 bytes from: icmp_seq=0 ttl= time= ms
546
547--- localhost ping statistics ---
5481 packets transmitted, 1 packets received, 0.0% packet loss
549round-trip min/avg/max/stddev = /// ms
550""",
551                "stderr": "",
552            },
553            id="_c1_S127_0_0_1_s56_t1_localhost",
554        ),
555        pytest.param(
556            {
557                "args": "ping -c1 -S::1 -s8 -t1 localhost",
558                "returncode": 0,
559                "stdout": """\
560PING(56=40+8+8 bytes) ::1 --> ::1
56116 bytes from ::1, icmp_seq=0 hlim= time= ms
562
563--- localhost ping statistics ---
5641 packets transmitted, 1 packets received, 0.0% packet loss
565round-trip min/avg/max/stddev = /// ms
566""",
567                "stderr": "",
568            },
569            id="_c1_S__1_s8_t1_localhost",
570        ),
571        pytest.param(
572            {
573                "args": "ping -c3 192.0.2.1",
574                "returncode": 0,
575                "stdout": """\
576PING 192.0.2.1 (192.0.2.1): 56 data bytes
57764 bytes from: icmp_seq=0 ttl= time= ms
57864 bytes from: icmp_seq=1 ttl= time= ms
57964 bytes from: icmp_seq=2 ttl= time= ms
580
581--- 192.0.2.1 ping statistics ---
5823 packets transmitted, 3 packets received, 0.0% packet loss
583round-trip min/avg/max/stddev = /// ms
584""",
585                "stderr": "",
586            },
587            id="_c3_192_0_2_1",
588        ),
589        pytest.param(
590            {
591                "args": "ping -c3 192.0.2.2",
592                "returncode": 2,
593                "stdout": """\
594PING 192.0.2.2 (192.0.2.2): 56 data bytes
595
596--- 192.0.2.2 ping statistics ---
5973 packets transmitted, 0 packets received, 100.0% packet loss
598""",
599                "stderr": "",
600            },
601            id="_c3_192_0_2_2",
602        ),
603        pytest.param(
604            {
605                "args": "ping -c3 2001:db8::1",
606                "returncode": 0,
607                "stdout": """\
608PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
60916 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
61016 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
61116 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
612
613--- 2001:db8::1 ping statistics ---
6143 packets transmitted, 3 packets received, 0.0% packet loss
615round-trip min/avg/max/stddev = /// ms
616""",
617                "stderr": "",
618            },
619            id="_c3_2001_db8__1",
620        ),
621        pytest.param(
622            {
623                "args": "ping -c3 2001:db8::2",
624                "returncode": 2,
625                "stdout": """\
626PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
627
628--- 2001:db8::2 ping statistics ---
6293 packets transmitted, 0 packets received, 100.0% packet loss
630""",
631                "stderr": "",
632            },
633            id="_c3_2001_db8__2",
634        ),
635        pytest.param(
636            {
637                "args": "ping -q -c1 192.0.2.1",
638                "returncode": 0,
639                "stdout": """\
640PING 192.0.2.1 (192.0.2.1): 56 data bytes
641
642--- 192.0.2.1 ping statistics ---
6431 packets transmitted, 1 packets received, 0.0% packet loss
644round-trip min/avg/max/stddev = /// ms
645""",
646                "stderr": "",
647            },
648            id="_q_c1_192_0_2_1",
649        ),
650        pytest.param(
651            {
652                "args": "ping -q -c1 192.0.2.2",
653                "returncode": 2,
654                "stdout": """\
655PING 192.0.2.2 (192.0.2.2): 56 data bytes
656
657--- 192.0.2.2 ping statistics ---
6581 packets transmitted, 0 packets received, 100.0% packet loss
659""",
660                "stderr": "",
661            },
662            id="_q_c1_192_0_2_2",
663        ),
664        pytest.param(
665            {
666                "args": "ping -q -c1 2001:db8::1",
667                "returncode": 0,
668                "stdout": """\
669PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
670
671--- 2001:db8::1 ping statistics ---
6721 packets transmitted, 1 packets received, 0.0% packet loss
673round-trip min/avg/max/stddev = /// ms
674""",
675                "stderr": "",
676            },
677            id="_q_c1_2001_db8__1",
678        ),
679        pytest.param(
680            {
681                "args": "ping -q -c1 2001:db8::2",
682                "returncode": 2,
683                "stdout": """\
684PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
685
686--- 2001:db8::2 ping statistics ---
6871 packets transmitted, 0 packets received, 100.0% packet loss
688""",
689                "stderr": "",
690            },
691            id="_q_c1_2001_db8__2",
692        ),
693        pytest.param(
694            {
695                "args": "ping -q -c3 192.0.2.1",
696                "returncode": 0,
697                "stdout": """\
698PING 192.0.2.1 (192.0.2.1): 56 data bytes
699
700--- 192.0.2.1 ping statistics ---
7013 packets transmitted, 3 packets received, 0.0% packet loss
702round-trip min/avg/max/stddev = /// ms
703""",
704                "stderr": "",
705            },
706            id="_q_c3_192_0_2_1",
707        ),
708        pytest.param(
709            {
710                "args": "ping -q -c3 192.0.2.2",
711                "returncode": 2,
712                "stdout": """\
713PING 192.0.2.2 (192.0.2.2): 56 data bytes
714
715--- 192.0.2.2 ping statistics ---
7163 packets transmitted, 0 packets received, 100.0% packet loss
717""",
718                "stderr": "",
719            },
720            id="_q_c3_192_0_2_2",
721        ),
722        pytest.param(
723            {
724                "args": "ping -q -c3 2001:db8::1",
725                "returncode": 0,
726                "stdout": """\
727PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
728
729--- 2001:db8::1 ping statistics ---
7303 packets transmitted, 3 packets received, 0.0% packet loss
731round-trip min/avg/max/stddev = /// ms
732""",
733                "stderr": "",
734            },
735            id="_q_c3_2001_db8__1",
736        ),
737        pytest.param(
738            {
739                "args": "ping -q -c3 2001:db8::2",
740                "returncode": 2,
741                "stdout": """\
742PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
743
744--- 2001:db8::2 ping statistics ---
7453 packets transmitted, 0 packets received, 100.0% packet loss
746""",
747                "stderr": "",
748            },
749            id="_q_c3_2001_db8__2",
750        ),
751    ]
752
753    @pytest.mark.parametrize("expected", testdata)
754    @pytest.mark.require_user("root")
755    @pytest.mark.require_user("unprivileged")
756    def test_ping(self, expected):
757        """Test ping"""
758        ping = subprocess.run(
759            expected["args"].split(),
760            capture_output=True,
761            timeout=15,
762            text=True,
763        )
764        assert ping.returncode == expected["returncode"]
765        assert redact(ping.stdout) == expected["stdout"]
766        assert ping.stderr == expected["stderr"]
767
768    # Each param in ping46_testdata contains a dictionary with the arguments
769    # and the expected outcome (returncode, redacted stdout, and stderr)
770    # common to `ping -4` and `ping -6`
771    ping46_testdata = [
772        pytest.param(
773            {
774                "args": "-Wx localhost",
775                "returncode": os.EX_USAGE,
776                "stdout": "",
777                "stderr": "ping: invalid timing interval: `x'\n",
778            },
779            id="_Wx_localhost",
780        ),
781    ]
782
783    @pytest.mark.parametrize("expected", ping46_testdata)
784    @pytest.mark.require_user("root")
785    @pytest.mark.require_user("unprivileged")
786    def test_ping_46(self, expected):
787        """Test ping -4/ping -6"""
788        for version in [4, 6]:
789            ping = subprocess.run(
790                ["ping", f"-{version}"] + expected["args"].split(),
791                capture_output=True,
792                timeout=15,
793                text=True,
794            )
795            assert ping.returncode == expected["returncode"]
796            assert redact(ping.stdout) == expected["stdout"]
797            assert ping.stderr == expected["stderr"]
798
799    # Each param in pinger_testdata contains a dictionary with the keywords to
800    # `pinger()` and a dictionary with the expected outcome (returncode,
801    # stdout, stderr, and if ping's output is redacted)
802    pinger_testdata = [
803        pytest.param(
804            {
805                "src": "192.0.2.1",
806                "dst": "192.0.2.2",
807                "icmp_type": 0,
808                "icmp_code": 0,
809            },
810            {
811                "returncode": 0,
812                "stdout": """\
813PING 192.0.2.2 (192.0.2.2): 56 data bytes
81464 bytes from: icmp_seq=0 ttl= time= ms
815
816--- 192.0.2.2 ping statistics ---
8171 packets transmitted, 1 packets received, 0.0% packet loss
818round-trip min/avg/max/stddev = /// ms
819""",
820                "stderr": "",
821                "redacted": True,
822            },
823            id="_0_0",
824        ),
825        pytest.param(
826            {
827                "src": "192.0.2.1",
828                "dst": "192.0.2.2",
829                "icmp_type": 0,
830                "icmp_code": 0,
831                "opts": "EOL",
832            },
833            {
834                "returncode": 0,
835                "stdout": """\
836PING 192.0.2.2 (192.0.2.2): 56 data bytes
83764 bytes from: icmp_seq=0 ttl= time= ms
838wrong total length 88 instead of 84
839
840--- 192.0.2.2 ping statistics ---
8411 packets transmitted, 1 packets received, 0.0% packet loss
842round-trip min/avg/max/stddev = /// ms
843""",
844                "stderr": "",
845                "redacted": True,
846            },
847            id="_0_0_opts_EOL",
848        ),
849        pytest.param(
850            {
851                "src": "192.0.2.1",
852                "dst": "192.0.2.2",
853                "icmp_type": 0,
854                "icmp_code": 0,
855                "opts": "LSRR",
856            },
857            {
858                "returncode": 0,
859                "stdout": """\
860PING 192.0.2.2 (192.0.2.2): 56 data bytes
86164 bytes from: icmp_seq=0 ttl= time= ms
862LSRR: 	192.0.2.10
863	192.0.2.20
864	192.0.2.30
865	192.0.2.40
866	192.0.2.50
867	192.0.2.60
868	192.0.2.70
869	192.0.2.80
870	192.0.2.90
871
872--- 192.0.2.2 ping statistics ---
8731 packets transmitted, 1 packets received, 0.0% packet loss
874round-trip min/avg/max/stddev = /// ms
875""",
876                "stderr": "",
877                "redacted": True,
878            },
879            id="_0_0_opts_LSRR",
880        ),
881        pytest.param(
882            {
883                "src": "192.0.2.1",
884                "dst": "192.0.2.2",
885                "icmp_type": 0,
886                "icmp_code": 0,
887                "opts": "LSRR-trunc",
888            },
889            {
890                "returncode": 0,
891                "stdout": """\
892PING 192.0.2.2 (192.0.2.2): 56 data bytes
89364 bytes from: icmp_seq=0 ttl= time= ms
894LSRR: 	(truncated route)
895
896--- 192.0.2.2 ping statistics ---
8971 packets transmitted, 1 packets received, 0.0% packet loss
898round-trip min/avg/max/stddev = /// ms
899""",
900                "stderr": "",
901                "redacted": True,
902            },
903            id="_0_0_opts_LSRR_trunc",
904        ),
905        pytest.param(
906            {
907                "src": "192.0.2.1",
908                "dst": "192.0.2.2",
909                "icmp_type": 0,
910                "icmp_code": 0,
911                "opts": "SSRR",
912            },
913            {
914                "returncode": 0,
915                "stdout": """\
916PING 192.0.2.2 (192.0.2.2): 56 data bytes
91764 bytes from: icmp_seq=0 ttl= time= ms
918SSRR: 	192.0.2.10
919	192.0.2.20
920	192.0.2.30
921	192.0.2.40
922	192.0.2.50
923	192.0.2.60
924	192.0.2.70
925	192.0.2.80
926	192.0.2.90
927
928--- 192.0.2.2 ping statistics ---
9291 packets transmitted, 1 packets received, 0.0% packet loss
930round-trip min/avg/max/stddev = /// ms
931""",
932                "stderr": "",
933                "redacted": True,
934            },
935            id="_0_0_opts_SSRR",
936        ),
937        pytest.param(
938            {
939                "src": "192.0.2.1",
940                "dst": "192.0.2.2",
941                "icmp_type": 0,
942                "icmp_code": 0,
943                "opts": "SSRR-trunc",
944            },
945            {
946                "returncode": 0,
947                "stdout": """\
948PING 192.0.2.2 (192.0.2.2): 56 data bytes
94964 bytes from: icmp_seq=0 ttl= time= ms
950SSRR: 	(truncated route)
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_SSRR_trunc",
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",
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
974RR: 	192.0.2.10
975	192.0.2.20
976	192.0.2.30
977	192.0.2.40
978	192.0.2.50
979	192.0.2.60
980	192.0.2.70
981	192.0.2.80
982	192.0.2.90
983
984--- 192.0.2.2 ping statistics ---
9851 packets transmitted, 1 packets received, 0.0% packet loss
986round-trip min/avg/max/stddev = /// ms
987""",
988                "stderr": "",
989                "redacted": True,
990            },
991            id="_0_0_opts_RR",
992        ),
993        pytest.param(
994            {
995                "src": "192.0.2.1",
996                "dst": "192.0.2.2",
997                "icmp_type": 0,
998                "icmp_code": 0,
999                "opts": "RR-same",
1000            },
1001            {
1002                "returncode": 0,
1003                "stdout": """\
1004PING 192.0.2.2 (192.0.2.2): 56 data bytes
100564 bytes from: icmp_seq=0 ttl= time= ms	(same route)
1006
1007--- 192.0.2.2 ping statistics ---
10081 packets transmitted, 1 packets received, 0.0% packet loss
1009round-trip min/avg/max/stddev = /// ms
1010""",
1011                "stderr": "",
1012                "redacted": True,
1013            },
1014            id="_0_0_opts_RR_same",
1015        ),
1016        pytest.param(
1017            {
1018                "src": "192.0.2.1",
1019                "dst": "192.0.2.2",
1020                "icmp_type": 0,
1021                "icmp_code": 0,
1022                "opts": "RR-trunc",
1023            },
1024            {
1025                "returncode": 0,
1026                "stdout": """\
1027PING 192.0.2.2 (192.0.2.2): 56 data bytes
102864 bytes from: icmp_seq=0 ttl= time= ms
1029RR: 	(truncated route)
1030
1031--- 192.0.2.2 ping statistics ---
10321 packets transmitted, 1 packets received, 0.0% packet loss
1033round-trip min/avg/max/stddev = /// ms
1034""",
1035                "stderr": "",
1036                "redacted": True,
1037            },
1038            id="_0_0_opts_RR_trunc",
1039        ),
1040        pytest.param(
1041            {
1042                "src": "192.0.2.1",
1043                "dst": "192.0.2.2",
1044                "icmp_type": 0,
1045                "icmp_code": 0,
1046                "opts": "NOP",
1047            },
1048            {
1049                "returncode": 0,
1050                "stdout": """\
1051PING 192.0.2.2 (192.0.2.2): 56 data bytes
105264 bytes from: icmp_seq=0 ttl= time= ms
1053wrong total length 88 instead of 84
1054NOP
1055
1056--- 192.0.2.2 ping statistics ---
10571 packets transmitted, 1 packets received, 0.0% packet loss
1058round-trip min/avg/max/stddev = /// ms
1059""",
1060                "stderr": "",
1061                "redacted": True,
1062            },
1063            id="_0_0_opts_NOP",
1064        ),
1065        pytest.param(
1066            {
1067                "src": "192.0.2.1",
1068                "dst": "192.0.2.2",
1069                "icmp_type": 3,
1070                "icmp_code": 1,
1071                "ihl": 0x4,
1072            },
1073            {
1074                "returncode": 2,
1075                "stdout": """\
1076PING 192.0.2.2 (192.0.2.2): 56 data bytes
1077
1078--- 192.0.2.2 ping statistics ---
10791 packets transmitted, 0 packets received, 100.0% packet loss
1080""",
1081                "stderr": "",  # "IHL too short" message not shown
1082                "redacted": False,
1083            },
1084            id="_IHL_too_short",
1085        ),
1086        pytest.param(
1087            {
1088                "src": "192.0.2.1",
1089                "dst": "192.0.2.2",
1090                "icmp_type": 3,
1091                "icmp_code": 1,
1092                "special": "no-payload",
1093            },
1094            {
1095                "returncode": 2,
1096                "stdout": """\
1097PATTERN: 0x01
1098PING 192.0.2.2 (192.0.2.2): 56 data bytes
1099
1100--- 192.0.2.2 ping statistics ---
11011 packets transmitted, 0 packets received, 100.0% packet loss
1102""",
1103                "stderr": """\
1104ping: quoted data too short (28 bytes) from 192.0.2.2
1105""",
1106                "redacted": False,
1107            },
1108            id="_quoted_data_too_short",
1109        ),
1110        pytest.param(
1111            {
1112                "src": "192.0.2.1",
1113                "dst": "192.0.2.2",
1114                "icmp_type": 3,
1115                "icmp_code": 1,
1116                "oip_ihl": 0x4,
1117            },
1118            {
1119                "returncode": 2,
1120                "stdout": """\
1121PING 192.0.2.2 (192.0.2.2): 56 data bytes
1122
1123--- 192.0.2.2 ping statistics ---
11241 packets transmitted, 0 packets received, 100.0% packet loss
1125""",
1126                "stderr": "",  # "inner IHL too short" message not shown
1127                "redacted": False,
1128            },
1129            id="_inner_IHL_too_short",
1130        ),
1131        pytest.param(
1132            {
1133                "src": "192.0.2.1",
1134                "dst": "192.0.2.2",
1135                "icmp_type": 3,
1136                "icmp_code": 1,
1137                "oip_ihl": 0xF,
1138            },
1139            {
1140                "returncode": 2,
1141                "stdout": """\
1142PING 192.0.2.2 (192.0.2.2): 56 data bytes
1143
1144--- 192.0.2.2 ping statistics ---
11451 packets transmitted, 0 packets received, 100.0% packet loss
1146""",
1147                "stderr": """\
1148ping: inner packet too short (84 bytes) from 192.0.2.2
1149""",
1150                "redacted": False,
1151            },
1152            id="_inner_packet_too_short",
1153        ),
1154        pytest.param(
1155            {
1156                "src": "192.0.2.1",
1157                "dst": "192.0.2.2",
1158                "icmp_type": 3,
1159                "icmp_code": 1,
1160                "oip_ihl": 0xF,
1161                "special": "no-payload",
1162            },
1163            {
1164                "returncode": 2,
1165                "stdout": """\
1166PATTERN: 0x01
1167PING 192.0.2.2 (192.0.2.2): 56 data bytes
1168
1169--- 192.0.2.2 ping statistics ---
11701 packets transmitted, 0 packets received, 100.0% packet loss
1171""",
1172                "stderr": "",
1173                "redacted": False,
1174            },
1175            id="_max_inner_packet_ihl_without_payload",
1176        ),
1177        pytest.param(
1178            {
1179                "src": "192.0.2.1",
1180                "dst": "192.0.2.2",
1181                "icmp_type": 0,
1182                "icmp_code": 0,
1183                "opts": "NOP-40",
1184            },
1185            {
1186                "returncode": 0,
1187                "stdout": """\
1188PING 192.0.2.2 (192.0.2.2): 56 data bytes
118964 bytes from: icmp_seq=0 ttl= time= ms
1190wrong total length 124 instead of 84
1191NOP
1192NOP
1193NOP
1194NOP
1195NOP
1196NOP
1197NOP
1198NOP
1199NOP
1200NOP
1201NOP
1202NOP
1203NOP
1204NOP
1205NOP
1206NOP
1207NOP
1208NOP
1209NOP
1210NOP
1211NOP
1212NOP
1213NOP
1214NOP
1215NOP
1216NOP
1217NOP
1218NOP
1219NOP
1220NOP
1221NOP
1222NOP
1223NOP
1224NOP
1225NOP
1226NOP
1227NOP
1228NOP
1229NOP
1230NOP
1231
1232--- 192.0.2.2 ping statistics ---
12331 packets transmitted, 1 packets received, 0.0% packet loss
1234round-trip min/avg/max/stddev = /// ms
1235""",
1236                "stderr": "",
1237                "redacted": True,
1238            },
1239            id="_0_0_opts_NOP_40",
1240        ),
1241        pytest.param(
1242            {
1243                "src": "192.0.2.1",
1244                "dst": "192.0.2.2",
1245                "icmp_type": 0,
1246                "icmp_code": 0,
1247                "opts": "unk",
1248            },
1249            {
1250                "returncode": 0,
1251                "stdout": """\
1252PING 192.0.2.2 (192.0.2.2): 56 data bytes
125364 bytes from: icmp_seq=0 ttl= time= ms
1254wrong total length 88 instead of 84
1255unknown option 9f
1256
1257--- 192.0.2.2 ping statistics ---
12581 packets transmitted, 1 packets received, 0.0% packet loss
1259round-trip min/avg/max/stddev = /// ms
1260""",
1261                "stderr": "",
1262                "redacted": True,
1263            },
1264            id="_0_0_opts_unk",
1265        ),
1266        pytest.param(
1267            {
1268                "src": "192.0.2.1",
1269                "dst": "192.0.2.2",
1270                "icmp_type": 3,
1271                "icmp_code": 1,
1272                "opts": "NOP-40",
1273            },
1274            {
1275                "returncode": 2,
1276                "stdout": """\
1277PING 192.0.2.2 (192.0.2.2): 56 data bytes
1278132 bytes from 192.0.2.2: Destination Host Unreachable
1279Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst Opts
1280 4  f  00 007c 0001   0 0000  40  01 d868 192.0.2.1 192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101
1281
1282
1283--- 192.0.2.2 ping statistics ---
12841 packets transmitted, 0 packets received, 100.0% packet loss
1285""",
1286                "stderr": "",
1287                "redacted": False,
1288            },
1289            id="_3_1_opts_NOP_40",
1290        ),
1291        pytest.param(
1292            {
1293                "src": "192.0.2.1",
1294                "dst": "192.0.2.2",
1295                "icmp_type": 3,
1296                "icmp_code": 1,
1297                "flags": "DF",
1298            },
1299            {
1300                "returncode": 2,
1301                "stdout": """\
1302PING 192.0.2.2 (192.0.2.2): 56 data bytes
130392 bytes from 192.0.2.2: Destination Host Unreachable
1304Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1305 4  5  00 0054 0001   2 0000  40  01 b6a4 192.0.2.1 192.0.2.2
1306
1307
1308--- 192.0.2.2 ping statistics ---
13091 packets transmitted, 0 packets received, 100.0% packet loss
1310""",
1311                "stderr": "",
1312                "redacted": False,
1313            },
1314            id="_3_1_flags_DF",
1315        ),
1316        pytest.param(
1317            {
1318                "src": "192.0.2.1",
1319                "dst": "192.0.2.2",
1320                "icmp_type": 3,
1321                "icmp_code": 1,
1322                "special": "tcp",
1323            },
1324            {
1325                "returncode": 2,
1326                "stdout": """\
1327PATTERN: 0x01
1328PING 192.0.2.2 (192.0.2.2): 56 data bytes
1329
1330--- 192.0.2.2 ping statistics ---
13311 packets transmitted, 0 packets received, 100.0% packet loss
1332""",
1333                "stderr": """\
1334ping: quoted data too short (40 bytes) from 192.0.2.2
1335""",
1336                "redacted": False,
1337            },
1338            id="_3_1_special_tcp",
1339        ),
1340        pytest.param(
1341            {
1342                "src": "192.0.2.1",
1343                "dst": "192.0.2.2",
1344                "icmp_type": 3,
1345                "icmp_code": 1,
1346                "special": "udp",
1347            },
1348            {
1349                "returncode": 2,
1350                "stdout": """\
1351PATTERN: 0x01
1352PING 192.0.2.2 (192.0.2.2): 56 data bytes
1353
1354--- 192.0.2.2 ping statistics ---
13551 packets transmitted, 0 packets received, 100.0% packet loss
1356""",
1357                "stderr": """\
1358ping: quoted data too short (28 bytes) from 192.0.2.2
1359""",
1360                "redacted": False,
1361            },
1362            id="_3_1_special_udp",
1363        ),
1364        pytest.param(
1365            {
1366                "src": "192.0.2.1",
1367                "dst": "192.0.2.2",
1368                "icmp_type": 3,
1369                "icmp_code": 1,
1370                "verbose": False,
1371            },
1372            {
1373                "returncode": 2,
1374                "stdout": """\
1375PING 192.0.2.2 (192.0.2.2): 56 data bytes
137692 bytes from 192.0.2.2: Destination Host Unreachable
1377Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1378 4  5  00 0054 0001   0 0000  40  01 f6a4 192.0.2.1 192.0.2.2
1379
1380
1381--- 192.0.2.2 ping statistics ---
13821 packets transmitted, 0 packets received, 100.0% packet loss
1383""",
1384                "stderr": "",
1385                "redacted": False,
1386            },
1387            id="_3_1_verbose_false",
1388        ),
1389        pytest.param(
1390            {
1391                "src": "192.0.2.1",
1392                "dst": "192.0.2.2",
1393                "icmp_type": 3,
1394                "icmp_code": 1,
1395                "special": "not-mine",
1396                "verbose": False,
1397            },
1398            {
1399                "returncode": 2,
1400                "stdout": """\
1401PATTERN: 0x01
1402PING 192.0.2.2 (192.0.2.2): 56 data bytes
1403
1404--- 192.0.2.2 ping statistics ---
14051 packets transmitted, 0 packets received, 100.0% packet loss
1406""",
1407                "stderr": "",
1408                "redacted": False,
1409            },
1410            id="_3_1_special_not_mine_verbose_false",
1411        ),
1412        pytest.param(
1413            {
1414                "src": "192.0.2.1",
1415                "dst": "192.0.2.2",
1416                "icmp_type": 0,
1417                "icmp_code": 0,
1418                "special": "warp",
1419            },
1420            {
1421                "returncode": 0,
1422                "stdout": """\
1423PATTERN: 0x01
1424PING 192.0.2.2 (192.0.2.2): 56 data bytes
142564 bytes from: icmp_seq=0 ttl= time= ms
1426
1427--- 192.0.2.2 ping statistics ---
14281 packets transmitted, 1 packets received, 0.0% packet loss
1429round-trip min/avg/max/stddev = /// ms
1430""",
1431                "stderr": """\
1432ping: time of day goes back (- ms), clamping time to 0
1433""",
1434                "redacted": True,
1435            },
1436            id="_0_0_special_warp",
1437        ),
1438        pytest.param(
1439            {
1440                "src": "192.0.2.1",
1441                "dst": "192.0.2.2",
1442                "icmp_type": 0,
1443                "icmp_code": 0,
1444                "special": "wrong",
1445            },
1446            {
1447                "returncode": 0,
1448                "stdout": """\
1449PATTERN: 0x01
1450PING 192.0.2.2 (192.0.2.2): 56 data bytes
145164 bytes from: icmp_seq=0 ttl= time= ms
1452wrong data byte #55 should be 0x1 but was 0x0
1453cp: xx xx xx xx xx xx xx xx
1454	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1455	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1456	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0
1457dp: xx xx xx xx xx xx xx xx
1458	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1459	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1460	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1461
1462--- 192.0.2.2 ping statistics ---
14631 packets transmitted, 1 packets received, 0.0% packet loss
1464round-trip min/avg/max/stddev = /// ms
1465""",
1466                "stderr": "",
1467                "redacted": True,
1468            },
1469            id="_0_0_special_wrong",
1470        ),
1471        pytest.param(
1472            {
1473                "src": "192.0.2.1",
1474                "dst": "192.0.2.2",
1475                "icmp_type": 0,
1476                "icmp_code": 0,
1477                "special": "short-wrong",
1478            },
1479            {
1480                "returncode": 0,
1481                "stdout": """\
1482PATTERN: 0x01
1483PING 192.0.2.2 (192.0.2.2): 56 data bytes
148463 bytes from: icmp_seq=0 ttl= time= ms
1485wrong total length 83 instead of 84
1486wrong data byte #54 should be 0x1 but was 0x0
1487cp: xx xx xx xx xx xx xx xx
1488	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1489	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1490	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0
1491dp: xx xx xx xx xx xx xx xx
1492	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1493	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1494	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1495
1496--- 192.0.2.2 ping statistics ---
14971 packets transmitted, 1 packets received, 0.0% packet loss
1498round-trip min/avg/max/stddev = /// ms
1499""",
1500                "stderr": "",
1501                "redacted": True,
1502            },
1503            id="_0_0_special_short_wrong",
1504        ),
1505    ]
1506
1507    @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata)
1508    @pytest.mark.require_progs(["scapy"])
1509    @pytest.mark.require_user("root")
1510    def test_pinger(self, pinger_kargs, expected):
1511        """Test ping using pinger(), a reply faker"""
1512        iface = IfaceFactory().create_iface("", "tun")[0].name
1513        ping = pinger(iface, **pinger_kargs)
1514        assert ping.returncode == expected["returncode"]
1515        if expected["redacted"]:
1516            assert redact(ping.stdout) == expected["stdout"]
1517            assert redact(ping.stderr) == expected["stderr"]
1518        else:
1519            assert ping.stdout == expected["stdout"]
1520            assert ping.stderr == expected["stderr"]
1521