xref: /freebsd/sbin/ping/tests/test_ping.py (revision 3e845b1090565912375c5578cf0399d27b7fa70c)
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 == "not-mine":
84        # Modify the ICMP Identifier field
85        oicmp.id += 1
86
87    if icmp.type in icmp_id_seq_types:
88        pkt = ip / icmp / load
89    else:
90        del ip.options
91        pkt = ip / icmp / oip / oicmp / load
92    return pkt
93
94
95def generate_ip_options(opts):
96    if not opts:
97        return []
98
99    routers = [
100        "192.0.2.10",
101        "192.0.2.20",
102        "192.0.2.30",
103        "192.0.2.40",
104        "192.0.2.50",
105        "192.0.2.60",
106        "192.0.2.70",
107        "192.0.2.80",
108        "192.0.2.90",
109    ]
110    routers_zero = [0, 0, 0, 0, 0, 0, 0, 0, 0]
111    if opts == "EOL":
112        options = sc.IPOption_EOL()
113    elif opts == "NOP":
114        options = sc.IPOption_NOP()
115    elif opts == "NOP-40":
116        options = sc.IPOption_NOP() * 40
117    elif opts == "RR":
118        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
119        options = sc.IPOption_RR(pointer=40, routers=routers)
120    elif opts == "RR-same":
121        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
122        options = sc.IPOption_RR(pointer=3, routers=routers_zero)
123    elif opts == "RR-trunc":
124        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
125        options = sc.IPOption_RR(length=7, routers=routers_zero)
126    elif opts == "LSRR":
127        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
128        options = sc.IPOption_LSRR(routers=routers)
129    elif opts == "LSRR-trunc":
130        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
131        options = sc.IPOption_LSRR(length=3, routers=routers_zero)
132    elif opts == "SSRR":
133        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
134        options = sc.IPOption_SSRR(routers=routers)
135    elif opts == "SSRR-trunc":
136        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
137        options = sc.IPOption_SSRR(length=3, routers=routers_zero)
138    elif opts == "unk":
139        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
140        options = b"\x9f"
141    elif opts == "unk-40":
142        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
143        options = b"\x9f" * 40
144    else:
145        options = []
146    return options
147
148
149def pinger(
150    # Required arguments
151    # Avoid setting defaults on these arguments,
152    # as we want to set them explicitly in the tests
153    iface: str,
154    /,
155    src: sc.scapy.fields.SourceIPField,
156    dst: sc.scapy.layers.inet.DestIPField,
157    icmp_type: sc.scapy.fields.ByteEnumField,
158    icmp_code: sc.scapy.fields.MultiEnumField,
159    # IP arguments
160    ihl: Optional[sc.scapy.fields.BitField] = None,
161    flags: Optional[sc.scapy.fields.FlagsField] = 0,
162    opts: Optional[str] = None,
163    oip_ihl: Optional[sc.scapy.fields.BitField] = None,
164    special: Optional[str] = None,
165    # ICMP arguments
166    # Match names with <netinet/ip_icmp.h>
167    icmp_pptr: sc.scapy.fields.ByteField = 0,
168    icmp_gwaddr: sc.scapy.fields.IPField = "0.0.0.0",
169    icmp_nextmtu: sc.scapy.fields.ShortField = 0,
170    icmp_otime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
171    icmp_rtime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
172    icmp_ttime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
173    icmp_mask: sc.scapy.fields.IPField = "0.0.0.0",
174    request: Optional[str] = None,
175    # Miscellaneous arguments
176    count: int = 1,
177    dup: bool = False,
178    verbose: bool = True,
179) -> subprocess.CompletedProcess:
180    """P I N G E R
181
182    Echo reply faker
183
184    :param str iface: Interface to send packet to
185    :keyword src: Source packet IP
186    :type src: class:`scapy.fields.SourceIPField`
187    :keyword dst: Destination packet IP
188    :type dst: class:`scapy.layers.inet.DestIPField`
189    :keyword icmp_type: ICMP type
190    :type icmp_type: class:`scapy.fields.ByteEnumField`
191    :keyword icmp_code: ICMP code
192    :type icmp_code: class:`scapy.fields.MultiEnumField`
193
194    :keyword ihl: Internet Header Length, defaults to None
195    :type ihl: class:`scapy.fields.BitField`, optional
196    :keyword flags: IP flags - one of `DF`, `MF` or `evil`, defaults to 0
197    :type flags: class:`scapy.fields.FlagsField`, optional
198    :keyword opts: Include IP options - one of `EOL`, `NOP`, `NOP-40`, `unk`,
199        `unk-40`, `RR`, `RR-same`, `RR-trunc`, `LSRR`, `LSRR-trunc`, `SSRR` or
200        `SSRR-trunc`, defaults to None
201    :type opts: str, optional
202    :keyword oip_ihl: Inner packet's Internet Header Length, defaults to None
203    :type oip_ihl: class:`scapy.fields.BitField`, optional
204    :keyword special: Send a special packet - one of `no-payload`, `not-mine`,
205        `tcp`, `udp`, `wrong` or `warp`, defaults to None
206    :type special: str, optional
207    :keyword icmp_pptr: ICMP pointer, defaults to 0
208    :type icmp_pptr: class:`scapy.fields.ByteField`
209    :keyword icmp_gwaddr: ICMP gateway IP address, defaults to "0.0.0.0"
210    :type icmp_gwaddr: class:`scapy.fields.IPField`
211    :keyword icmp_nextmtu: ICMP next MTU, defaults to 0
212    :type icmp_nextmtu: class:`scapy.fields.ShortField`
213    :keyword icmp_otime: ICMP originate timestamp, defaults to 0
214    :type icmp_otime: class:`scapy.layers.inet.ICMPTimeStampField`
215    :keyword icmp_rtime: ICMP receive timestamp, defaults to 0
216    :type icmp_rtime: class:`scapy.layers.inet.ICMPTimeStampField`
217    :keyword icmp_ttime: ICMP transmit timestamp, defaults to 0
218    :type icmp_ttime: class:`scapy.layers.inet.ICMPTimeStampField`
219    :keyword icmp_mask: ICMP address mask, defaults to "0.0.0.0"
220    :type icmp_mask: class:`scapy.fields.IPField`
221    :keyword request: Request type - one of `mask` or `timestamp`,
222        defaults to None
223    :type request: str, optional
224    :keyword count: Number of packets to send, defaults to 1
225    :type count: int
226    :keyword dup: Duplicate packets, defaults to `False`
227    :type dup: bool
228    :keyword verbose: Turn on/off verbosity, defaults to `True`
229    :type verbose: bool
230
231    :return: A class:`subprocess.CompletedProcess` with the output from the
232        ping utility
233    :rtype: class:`subprocess.CompletedProcess`
234    """
235    tun = sc.TunTapInterface(iface)
236    subprocess.run(["ifconfig", tun.iface, "up"], check=True)
237    subprocess.run(["ifconfig", tun.iface, src, dst], check=True)
238    ip_opts = generate_ip_options(opts)
239    ip = sc.IP(ihl=ihl, flags=flags, src=dst, dst=src, options=ip_opts)
240    command = [
241        "/sbin/ping",
242        "-c",
243        str(count),
244        "-t",
245        str(count),
246    ]
247    if verbose:
248        command += ["-v"]
249    if request == "mask":
250        command += ["-Mm"]
251    if request == "timestamp":
252        command += ["-Mt"]
253    if special:
254        command += ["-p1"]
255    if opts in [
256        "RR",
257        "RR-same",
258        "RR-trunc",
259        "LSRR",
260        "LSRR-trunc",
261        "SSRR",
262        "SSRR-trunc",
263    ]:
264        command += ["-R"]
265    command += [dst]
266    with subprocess.Popen(
267        args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
268    ) as ping:
269        for dummy in range(count):
270            echo = tun.recv()
271            icmp = sc.ICMP(
272                type=icmp_type,
273                code=icmp_code,
274                id=echo[sc.ICMP].id,
275                seq=echo[sc.ICMP].seq,
276                ts_ori=icmp_otime,
277                ts_rx=icmp_rtime,
278                ts_tx=icmp_ttime,
279                gw=icmp_gwaddr,
280                ptr=icmp_pptr,
281                addr_mask=icmp_mask,
282                nexthopmtu=icmp_nextmtu,
283            )
284            pkt = build_response_packet(echo, ip, icmp, oip_ihl, special)
285            tun.send(pkt)
286            if dup is True:
287                tun.send(pkt)
288        stdout, stderr = ping.communicate()
289    return subprocess.CompletedProcess(
290        ping.args, ping.returncode, stdout, stderr
291    )
292
293
294def redact(output):
295    """Redact some elements of ping's output"""
296    pattern_replacements = [
297        (r"localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"),
298        (r"from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"),
299        ("hlim=[0-9]*", "hlim="),
300        ("ttl=[0-9]*", "ttl="),
301        ("time=[0-9.-]*", "time="),
302        ("cp: .*", "cp: xx xx xx xx xx xx xx xx"),
303        ("dp: .*", "dp: xx xx xx xx xx xx xx xx"),
304        (r"\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"),
305        (r"[0-9\.]+/[0-9.]+", "/"),
306    ]
307    for pattern, repl in pattern_replacements:
308        output = re.sub(pattern, repl, output)
309    return output
310
311
312class TestPing(SingleVnetTestTemplate):
313    IPV6_PREFIXES: List[str] = ["2001:db8::1/64"]
314    IPV4_PREFIXES: List[str] = ["192.0.2.1/24"]
315
316    # Each param in testdata contains a dictionary with the command,
317    # and the expected outcome (returncode, redacted stdout, and stderr)
318    testdata = [
319        pytest.param(
320            {
321                "args": "ping -4 -c1 -s56 -t1 localhost",
322                "returncode": 0,
323                "stdout": """\
324PING localhost: 56 data bytes
32564 bytes from: icmp_seq=0 ttl= time= ms
326
327--- localhost ping statistics ---
3281 packets transmitted, 1 packets received, 0.0% packet loss
329round-trip min/avg/max/stddev = /// ms
330""",
331                "stderr": "",
332            },
333            id="_4_c1_s56_t1_localhost",
334        ),
335        pytest.param(
336            {
337                "args": "ping -6 -c1 -s8 -t1 localhost",
338                "returncode": 0,
339                "stdout": """\
340PING(56=40+8+8 bytes) ::1 --> ::1
34116 bytes from ::1, icmp_seq=0 hlim= time= ms
342
343--- localhost ping statistics ---
3441 packets transmitted, 1 packets received, 0.0% packet loss
345round-trip min/avg/max/stddev = /// ms
346""",
347                "stderr": "",
348            },
349            id="_6_c1_s8_t1_localhost",
350        ),
351        pytest.param(
352            {
353                "args": "ping -A -c1 192.0.2.1",
354                "returncode": 0,
355                "stdout": """\
356PING 192.0.2.1 (192.0.2.1): 56 data bytes
35764 bytes from: icmp_seq=0 ttl= time= ms
358
359--- 192.0.2.1 ping statistics ---
3601 packets transmitted, 1 packets received, 0.0% packet loss
361round-trip min/avg/max/stddev = /// ms
362""",
363                "stderr": "",
364            },
365            id="_A_c1_192_0_2_1",
366        ),
367        pytest.param(
368            {
369                "args": "ping -A -c1 192.0.2.2",
370                "returncode": 2,
371                "stdout": """\
372PING 192.0.2.2 (192.0.2.2): 56 data bytes
373
374--- 192.0.2.2 ping statistics ---
3751 packets transmitted, 0 packets received, 100.0% packet loss
376""",
377                "stderr": "",
378            },
379            id="_A_c1_192_0_2_2",
380        ),
381        pytest.param(
382            {
383                "args": "ping -A -c1 2001:db8::1",
384                "returncode": 0,
385                "stdout": """\
386PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
38716 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
388
389--- 2001:db8::1 ping statistics ---
3901 packets transmitted, 1 packets received, 0.0% packet loss
391round-trip min/avg/max/stddev = /// ms
392""",
393                "stderr": "",
394            },
395            id="_A_c1_2001_db8__1",
396        ),
397        pytest.param(
398            {
399                "args": "ping -A -c1 2001:db8::2",
400                "returncode": 2,
401                "stdout": """\
402PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
403
404--- 2001:db8::2 ping statistics ---
4051 packets transmitted, 0 packets received, 100.0% packet loss
406""",
407                "stderr": "",
408            },
409            id="_A_c1_2001_db8__2",
410        ),
411        pytest.param(
412            {
413                "args": "ping -A -c3 192.0.2.1",
414                "returncode": 0,
415                "stdout": """\
416PING 192.0.2.1 (192.0.2.1): 56 data bytes
41764 bytes from: icmp_seq=0 ttl= time= ms
41864 bytes from: icmp_seq=1 ttl= time= ms
41964 bytes from: icmp_seq=2 ttl= time= ms
420
421--- 192.0.2.1 ping statistics ---
4223 packets transmitted, 3 packets received, 0.0% packet loss
423round-trip min/avg/max/stddev = /// ms
424""",
425                "stderr": "",
426            },
427            id="_A_3_192_0.2.1",
428        ),
429        pytest.param(
430            {
431                "args": "ping -A -c3 192.0.2.2",
432                "returncode": 2,
433                "stdout": """\
434\x07\x07PING 192.0.2.2 (192.0.2.2): 56 data bytes
435
436--- 192.0.2.2 ping statistics ---
4373 packets transmitted, 0 packets received, 100.0% packet loss
438""",
439                "stderr": "",
440            },
441            id="_A_c3_192_0_2_2",
442        ),
443        pytest.param(
444            {
445                "args": "ping -A -c3 2001:db8::1",
446                "returncode": 0,
447                "stdout": """\
448PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
44916 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
45016 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
45116 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
452
453--- 2001:db8::1 ping statistics ---
4543 packets transmitted, 3 packets received, 0.0% packet loss
455round-trip min/avg/max/stddev = /// ms
456""",
457                "stderr": "",
458            },
459            id="_A_c3_2001_db8__1",
460        ),
461        pytest.param(
462            {
463                "args": "ping -A -c3 2001:db8::2",
464                "returncode": 2,
465                "stdout": """\
466\x07\x07PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
467
468--- 2001:db8::2 ping statistics ---
4693 packets transmitted, 0 packets received, 100.0% packet loss
470""",
471                "stderr": "",
472            },
473            id="_A_c3_2001_db8__2",
474        ),
475        pytest.param(
476            {
477                "args": "ping -c1 192.0.2.1",
478                "returncode": 0,
479                "stdout": """\
480PING 192.0.2.1 (192.0.2.1): 56 data bytes
48164 bytes from: icmp_seq=0 ttl= time= ms
482
483--- 192.0.2.1 ping statistics ---
4841 packets transmitted, 1 packets received, 0.0% packet loss
485round-trip min/avg/max/stddev = /// ms
486""",
487                "stderr": "",
488            },
489            id="_c1_192_0_2_1",
490        ),
491        pytest.param(
492            {
493                "args": "ping -c1 192.0.2.2",
494                "returncode": 2,
495                "stdout": """\
496PING 192.0.2.2 (192.0.2.2): 56 data bytes
497
498--- 192.0.2.2 ping statistics ---
4991 packets transmitted, 0 packets received, 100.0% packet loss
500""",
501                "stderr": "",
502            },
503            id="_c1_192_0_2_2",
504        ),
505        pytest.param(
506            {
507                "args": "ping -c1 2001:db8::1",
508                "returncode": 0,
509                "stdout": """\
510PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
51116 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
512
513--- 2001:db8::1 ping statistics ---
5141 packets transmitted, 1 packets received, 0.0% packet loss
515round-trip min/avg/max/stddev = /// ms
516""",
517                "stderr": "",
518            },
519            id="_c1_2001_db8__1",
520        ),
521        pytest.param(
522            {
523                "args": "ping -c1 2001:db8::2",
524                "returncode": 2,
525                "stdout": """\
526PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
527
528--- 2001:db8::2 ping statistics ---
5291 packets transmitted, 0 packets received, 100.0% packet loss
530""",
531                "stderr": "",
532            },
533            id="_c1_2001_db8__2",
534        ),
535        pytest.param(
536            {
537                "args": "ping -c1 -S127.0.0.1 -s56 -t1 localhost",
538                "returncode": 0,
539                "stdout": """\
540PING localhost from: 56 data bytes
54164 bytes from: icmp_seq=0 ttl= time= ms
542
543--- localhost ping statistics ---
5441 packets transmitted, 1 packets received, 0.0% packet loss
545round-trip min/avg/max/stddev = /// ms
546""",
547                "stderr": "",
548            },
549            id="_c1_S127_0_0_1_s56_t1_localhost",
550        ),
551        pytest.param(
552            {
553                "args": "ping -c1 -S::1 -s8 -t1 localhost",
554                "returncode": 0,
555                "stdout": """\
556PING(56=40+8+8 bytes) ::1 --> ::1
55716 bytes from ::1, icmp_seq=0 hlim= time= ms
558
559--- localhost ping statistics ---
5601 packets transmitted, 1 packets received, 0.0% packet loss
561round-trip min/avg/max/stddev = /// ms
562""",
563                "stderr": "",
564            },
565            id="_c1_S__1_s8_t1_localhost",
566        ),
567        pytest.param(
568            {
569                "args": "ping -c3 192.0.2.1",
570                "returncode": 0,
571                "stdout": """\
572PING 192.0.2.1 (192.0.2.1): 56 data bytes
57364 bytes from: icmp_seq=0 ttl= time= ms
57464 bytes from: icmp_seq=1 ttl= time= ms
57564 bytes from: icmp_seq=2 ttl= time= ms
576
577--- 192.0.2.1 ping statistics ---
5783 packets transmitted, 3 packets received, 0.0% packet loss
579round-trip min/avg/max/stddev = /// ms
580""",
581                "stderr": "",
582            },
583            id="_c3_192_0_2_1",
584        ),
585        pytest.param(
586            {
587                "args": "ping -c3 192.0.2.2",
588                "returncode": 2,
589                "stdout": """\
590PING 192.0.2.2 (192.0.2.2): 56 data bytes
591
592--- 192.0.2.2 ping statistics ---
5933 packets transmitted, 0 packets received, 100.0% packet loss
594""",
595                "stderr": "",
596            },
597            id="_c3_192_0_2_2",
598        ),
599        pytest.param(
600            {
601                "args": "ping -c3 2001:db8::1",
602                "returncode": 0,
603                "stdout": """\
604PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
60516 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
60616 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
60716 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
608
609--- 2001:db8::1 ping statistics ---
6103 packets transmitted, 3 packets received, 0.0% packet loss
611round-trip min/avg/max/stddev = /// ms
612""",
613                "stderr": "",
614            },
615            id="_c3_2001_db8__1",
616        ),
617        pytest.param(
618            {
619                "args": "ping -c3 2001:db8::2",
620                "returncode": 2,
621                "stdout": """\
622PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
623
624--- 2001:db8::2 ping statistics ---
6253 packets transmitted, 0 packets received, 100.0% packet loss
626""",
627                "stderr": "",
628            },
629            id="_c3_2001_db8__2",
630        ),
631        pytest.param(
632            {
633                "args": "ping -q -c1 192.0.2.1",
634                "returncode": 0,
635                "stdout": """\
636PING 192.0.2.1 (192.0.2.1): 56 data bytes
637
638--- 192.0.2.1 ping statistics ---
6391 packets transmitted, 1 packets received, 0.0% packet loss
640round-trip min/avg/max/stddev = /// ms
641""",
642                "stderr": "",
643            },
644            id="_q_c1_192_0_2_1",
645        ),
646        pytest.param(
647            {
648                "args": "ping -q -c1 192.0.2.2",
649                "returncode": 2,
650                "stdout": """\
651PING 192.0.2.2 (192.0.2.2): 56 data bytes
652
653--- 192.0.2.2 ping statistics ---
6541 packets transmitted, 0 packets received, 100.0% packet loss
655""",
656                "stderr": "",
657            },
658            id="_q_c1_192_0_2_2",
659        ),
660        pytest.param(
661            {
662                "args": "ping -q -c1 2001:db8::1",
663                "returncode": 0,
664                "stdout": """\
665PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
666
667--- 2001:db8::1 ping statistics ---
6681 packets transmitted, 1 packets received, 0.0% packet loss
669round-trip min/avg/max/stddev = /// ms
670""",
671                "stderr": "",
672            },
673            id="_q_c1_2001_db8__1",
674        ),
675        pytest.param(
676            {
677                "args": "ping -q -c1 2001:db8::2",
678                "returncode": 2,
679                "stdout": """\
680PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
681
682--- 2001:db8::2 ping statistics ---
6831 packets transmitted, 0 packets received, 100.0% packet loss
684""",
685                "stderr": "",
686            },
687            id="_q_c1_2001_db8__2",
688        ),
689        pytest.param(
690            {
691                "args": "ping -q -c3 192.0.2.1",
692                "returncode": 0,
693                "stdout": """\
694PING 192.0.2.1 (192.0.2.1): 56 data bytes
695
696--- 192.0.2.1 ping statistics ---
6973 packets transmitted, 3 packets received, 0.0% packet loss
698round-trip min/avg/max/stddev = /// ms
699""",
700                "stderr": "",
701            },
702            id="_q_c3_192_0_2_1",
703        ),
704        pytest.param(
705            {
706                "args": "ping -q -c3 192.0.2.2",
707                "returncode": 2,
708                "stdout": """\
709PING 192.0.2.2 (192.0.2.2): 56 data bytes
710
711--- 192.0.2.2 ping statistics ---
7123 packets transmitted, 0 packets received, 100.0% packet loss
713""",
714                "stderr": "",
715            },
716            id="_q_c3_192_0_2_2",
717        ),
718        pytest.param(
719            {
720                "args": "ping -q -c3 2001:db8::1",
721                "returncode": 0,
722                "stdout": """\
723PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
724
725--- 2001:db8::1 ping statistics ---
7263 packets transmitted, 3 packets received, 0.0% packet loss
727round-trip min/avg/max/stddev = /// ms
728""",
729                "stderr": "",
730            },
731            id="_q_c3_2001_db8__1",
732        ),
733        pytest.param(
734            {
735                "args": "ping -q -c3 2001:db8::2",
736                "returncode": 2,
737                "stdout": """\
738PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
739
740--- 2001:db8::2 ping statistics ---
7413 packets transmitted, 0 packets received, 100.0% packet loss
742""",
743                "stderr": "",
744            },
745            id="_q_c3_2001_db8__2",
746        ),
747    ]
748
749    @pytest.mark.parametrize("expected", testdata)
750    @pytest.mark.require_user("root")
751    @pytest.mark.require_user("unprivileged")
752    def test_ping(self, expected):
753        """Test ping"""
754        ping = subprocess.run(
755            expected["args"].split(),
756            capture_output=True,
757            timeout=15,
758            text=True,
759        )
760        assert ping.returncode == expected["returncode"]
761        assert redact(ping.stdout) == expected["stdout"]
762        assert ping.stderr == expected["stderr"]
763
764    # Each param in ping46_testdata contains a dictionary with the arguments
765    # and the expected outcome (returncode, redacted stdout, and stderr)
766    # common to `ping -4` and `ping -6`
767    ping46_testdata = [
768        pytest.param(
769            {
770                "args": "-Wx localhost",
771                "returncode": os.EX_USAGE,
772                "stdout": "",
773                "stderr": "ping: invalid timing interval: `x'\n",
774            },
775            id="_Wx_localhost",
776        ),
777    ]
778
779    @pytest.mark.parametrize("expected", ping46_testdata)
780    @pytest.mark.require_user("root")
781    @pytest.mark.require_user("unprivileged")
782    def test_ping_46(self, expected):
783        """Test ping -4/ping -6"""
784        for version in [4, 6]:
785            ping = subprocess.run(
786                ["ping", f"-{version}"] + expected["args"].split(),
787                capture_output=True,
788                timeout=15,
789                text=True,
790            )
791            assert ping.returncode == expected["returncode"]
792            assert redact(ping.stdout) == expected["stdout"]
793            assert ping.stderr == expected["stderr"]
794
795    # Each param in pinger_testdata contains a dictionary with the keywords to
796    # `pinger()` and a dictionary with the expected outcome (returncode,
797    # stdout, stderr, and if ping's output is redacted)
798    pinger_testdata = [
799        pytest.param(
800            {
801                "src": "192.0.2.1",
802                "dst": "192.0.2.2",
803                "icmp_type": 0,
804                "icmp_code": 0,
805            },
806            {
807                "returncode": 0,
808                "stdout": """\
809PING 192.0.2.2 (192.0.2.2): 56 data bytes
81064 bytes from: icmp_seq=0 ttl= time= ms
811
812--- 192.0.2.2 ping statistics ---
8131 packets transmitted, 1 packets received, 0.0% packet loss
814round-trip min/avg/max/stddev = /// ms
815""",
816                "stderr": "",
817                "redacted": True,
818            },
819            id="_0_0",
820        ),
821        pytest.param(
822            {
823                "src": "192.0.2.1",
824                "dst": "192.0.2.2",
825                "icmp_type": 0,
826                "icmp_code": 0,
827                "opts": "EOL",
828            },
829            {
830                "returncode": 0,
831                "stdout": """\
832PING 192.0.2.2 (192.0.2.2): 56 data bytes
83364 bytes from: icmp_seq=0 ttl= time= ms
834wrong total length 88 instead of 84
835
836--- 192.0.2.2 ping statistics ---
8371 packets transmitted, 1 packets received, 0.0% packet loss
838round-trip min/avg/max/stddev = /// ms
839""",
840                "stderr": "",
841                "redacted": True,
842            },
843            id="_0_0_opts_EOL",
844        ),
845        pytest.param(
846            {
847                "src": "192.0.2.1",
848                "dst": "192.0.2.2",
849                "icmp_type": 0,
850                "icmp_code": 0,
851                "opts": "LSRR",
852            },
853            {
854                "returncode": 0,
855                "stdout": """\
856PING 192.0.2.2 (192.0.2.2): 56 data bytes
85764 bytes from: icmp_seq=0 ttl= time= ms
858LSRR: 	192.0.2.10
859	192.0.2.20
860	192.0.2.30
861	192.0.2.40
862	192.0.2.50
863	192.0.2.60
864	192.0.2.70
865	192.0.2.80
866	192.0.2.90
867
868--- 192.0.2.2 ping statistics ---
8691 packets transmitted, 1 packets received, 0.0% packet loss
870round-trip min/avg/max/stddev = /// ms
871""",
872                "stderr": "",
873                "redacted": True,
874            },
875            id="_0_0_opts_LSRR",
876        ),
877        pytest.param(
878            {
879                "src": "192.0.2.1",
880                "dst": "192.0.2.2",
881                "icmp_type": 0,
882                "icmp_code": 0,
883                "opts": "LSRR-trunc",
884            },
885            {
886                "returncode": 0,
887                "stdout": """\
888PING 192.0.2.2 (192.0.2.2): 56 data bytes
88964 bytes from: icmp_seq=0 ttl= time= ms
890LSRR: 	(truncated route)
891
892--- 192.0.2.2 ping statistics ---
8931 packets transmitted, 1 packets received, 0.0% packet loss
894round-trip min/avg/max/stddev = /// ms
895""",
896                "stderr": "",
897                "redacted": True,
898            },
899            id="_0_0_opts_LSRR_trunc",
900        ),
901        pytest.param(
902            {
903                "src": "192.0.2.1",
904                "dst": "192.0.2.2",
905                "icmp_type": 0,
906                "icmp_code": 0,
907                "opts": "SSRR",
908            },
909            {
910                "returncode": 0,
911                "stdout": """\
912PING 192.0.2.2 (192.0.2.2): 56 data bytes
91364 bytes from: icmp_seq=0 ttl= time= ms
914SSRR: 	192.0.2.10
915	192.0.2.20
916	192.0.2.30
917	192.0.2.40
918	192.0.2.50
919	192.0.2.60
920	192.0.2.70
921	192.0.2.80
922	192.0.2.90
923
924--- 192.0.2.2 ping statistics ---
9251 packets transmitted, 1 packets received, 0.0% packet loss
926round-trip min/avg/max/stddev = /// ms
927""",
928                "stderr": "",
929                "redacted": True,
930            },
931            id="_0_0_opts_SSRR",
932        ),
933        pytest.param(
934            {
935                "src": "192.0.2.1",
936                "dst": "192.0.2.2",
937                "icmp_type": 0,
938                "icmp_code": 0,
939                "opts": "SSRR-trunc",
940            },
941            {
942                "returncode": 0,
943                "stdout": """\
944PING 192.0.2.2 (192.0.2.2): 56 data bytes
94564 bytes from: icmp_seq=0 ttl= time= ms
946SSRR: 	(truncated route)
947
948--- 192.0.2.2 ping statistics ---
9491 packets transmitted, 1 packets received, 0.0% packet loss
950round-trip min/avg/max/stddev = /// ms
951""",
952                "stderr": "",
953                "redacted": True,
954            },
955            id="_0_0_opts_SSRR_trunc",
956        ),
957        pytest.param(
958            {
959                "src": "192.0.2.1",
960                "dst": "192.0.2.2",
961                "icmp_type": 0,
962                "icmp_code": 0,
963                "opts": "RR",
964            },
965            {
966                "returncode": 0,
967                "stdout": """\
968PING 192.0.2.2 (192.0.2.2): 56 data bytes
96964 bytes from: icmp_seq=0 ttl= time= ms
970RR: 	192.0.2.10
971	192.0.2.20
972	192.0.2.30
973	192.0.2.40
974	192.0.2.50
975	192.0.2.60
976	192.0.2.70
977	192.0.2.80
978	192.0.2.90
979
980--- 192.0.2.2 ping statistics ---
9811 packets transmitted, 1 packets received, 0.0% packet loss
982round-trip min/avg/max/stddev = /// ms
983""",
984                "stderr": "",
985                "redacted": True,
986            },
987            id="_0_0_opts_RR",
988        ),
989        pytest.param(
990            {
991                "src": "192.0.2.1",
992                "dst": "192.0.2.2",
993                "icmp_type": 0,
994                "icmp_code": 0,
995                "opts": "RR-same",
996            },
997            {
998                "returncode": 0,
999                "stdout": """\
1000PING 192.0.2.2 (192.0.2.2): 56 data bytes
100164 bytes from: icmp_seq=0 ttl= time= ms	(same route)
1002
1003--- 192.0.2.2 ping statistics ---
10041 packets transmitted, 1 packets received, 0.0% packet loss
1005round-trip min/avg/max/stddev = /// ms
1006""",
1007                "stderr": "",
1008                "redacted": True,
1009            },
1010            id="_0_0_opts_RR_same",
1011        ),
1012        pytest.param(
1013            {
1014                "src": "192.0.2.1",
1015                "dst": "192.0.2.2",
1016                "icmp_type": 0,
1017                "icmp_code": 0,
1018                "opts": "RR-trunc",
1019            },
1020            {
1021                "returncode": 0,
1022                "stdout": """\
1023PING 192.0.2.2 (192.0.2.2): 56 data bytes
102464 bytes from: icmp_seq=0 ttl= time= ms
1025RR: 	(truncated route)
1026
1027--- 192.0.2.2 ping statistics ---
10281 packets transmitted, 1 packets received, 0.0% packet loss
1029round-trip min/avg/max/stddev = /// ms
1030""",
1031                "stderr": "",
1032                "redacted": True,
1033            },
1034            id="_0_0_opts_RR_trunc",
1035        ),
1036        pytest.param(
1037            {
1038                "src": "192.0.2.1",
1039                "dst": "192.0.2.2",
1040                "icmp_type": 0,
1041                "icmp_code": 0,
1042                "opts": "NOP",
1043            },
1044            {
1045                "returncode": 0,
1046                "stdout": """\
1047PING 192.0.2.2 (192.0.2.2): 56 data bytes
104864 bytes from: icmp_seq=0 ttl= time= ms
1049wrong total length 88 instead of 84
1050NOP
1051
1052--- 192.0.2.2 ping statistics ---
10531 packets transmitted, 1 packets received, 0.0% packet loss
1054round-trip min/avg/max/stddev = /// ms
1055""",
1056                "stderr": "",
1057                "redacted": True,
1058            },
1059            id="_0_0_opts_NOP",
1060        ),
1061        pytest.param(
1062            {
1063                "src": "192.0.2.1",
1064                "dst": "192.0.2.2",
1065                "icmp_type": 3,
1066                "icmp_code": 1,
1067                "ihl": 0x4,
1068            },
1069            {
1070                "returncode": 2,
1071                "stdout": """\
1072PING 192.0.2.2 (192.0.2.2): 56 data bytes
1073
1074--- 192.0.2.2 ping statistics ---
10751 packets transmitted, 0 packets received, 100.0% packet loss
1076""",
1077                "stderr": "",  # "IHL too short" message not shown
1078                "redacted": False,
1079            },
1080            id="_IHL_too_short",
1081        ),
1082        pytest.param(
1083            {
1084                "src": "192.0.2.1",
1085                "dst": "192.0.2.2",
1086                "icmp_type": 3,
1087                "icmp_code": 1,
1088                "special": "no-payload",
1089            },
1090            {
1091                "returncode": 2,
1092                "stdout": """\
1093PATTERN: 0x01
1094PING 192.0.2.2 (192.0.2.2): 56 data bytes
1095
1096--- 192.0.2.2 ping statistics ---
10971 packets transmitted, 0 packets received, 100.0% packet loss
1098""",
1099                "stderr": """\
1100ping: quoted data too short (28 bytes) from 192.0.2.2
1101""",
1102                "redacted": False,
1103            },
1104            id="_quoted_data_too_short",
1105        ),
1106        pytest.param(
1107            {
1108                "src": "192.0.2.1",
1109                "dst": "192.0.2.2",
1110                "icmp_type": 3,
1111                "icmp_code": 1,
1112                "oip_ihl": 0x4,
1113            },
1114            {
1115                "returncode": 2,
1116                "stdout": """\
1117PING 192.0.2.2 (192.0.2.2): 56 data bytes
1118
1119--- 192.0.2.2 ping statistics ---
11201 packets transmitted, 0 packets received, 100.0% packet loss
1121""",
1122                "stderr": "",  # "inner IHL too short" message not shown
1123                "redacted": False,
1124            },
1125            id="_inner_IHL_too_short",
1126        ),
1127        pytest.param(
1128            {
1129                "src": "192.0.2.1",
1130                "dst": "192.0.2.2",
1131                "icmp_type": 3,
1132                "icmp_code": 1,
1133                "oip_ihl": 0xF,
1134            },
1135            {
1136                "returncode": 2,
1137                "stdout": """\
1138PING 192.0.2.2 (192.0.2.2): 56 data bytes
1139
1140--- 192.0.2.2 ping statistics ---
11411 packets transmitted, 0 packets received, 100.0% packet loss
1142""",
1143                "stderr": """\
1144ping: inner packet too short (84 bytes) from 192.0.2.2
1145""",
1146                "redacted": False,
1147            },
1148            id="_inner_packet_too_short",
1149        ),
1150        pytest.param(
1151            {
1152                "src": "192.0.2.1",
1153                "dst": "192.0.2.2",
1154                "icmp_type": 3,
1155                "icmp_code": 1,
1156                "oip_ihl": 0xF,
1157                "special": "no-payload",
1158            },
1159            {
1160                "returncode": 2,
1161                "stdout": """\
1162PATTERN: 0x01
1163PING 192.0.2.2 (192.0.2.2): 56 data bytes
1164
1165--- 192.0.2.2 ping statistics ---
11661 packets transmitted, 0 packets received, 100.0% packet loss
1167""",
1168                "stderr": "",
1169                "redacted": False,
1170            },
1171            id="_max_inner_packet_ihl_without_payload",
1172        ),
1173        pytest.param(
1174            {
1175                "src": "192.0.2.1",
1176                "dst": "192.0.2.2",
1177                "icmp_type": 0,
1178                "icmp_code": 0,
1179                "opts": "NOP-40",
1180            },
1181            {
1182                "returncode": 0,
1183                "stdout": """\
1184PING 192.0.2.2 (192.0.2.2): 56 data bytes
118564 bytes from: icmp_seq=0 ttl= time= ms
1186wrong total length 124 instead of 84
1187NOP
1188NOP
1189NOP
1190NOP
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
1227
1228--- 192.0.2.2 ping statistics ---
12291 packets transmitted, 1 packets received, 0.0% packet loss
1230round-trip min/avg/max/stddev = /// ms
1231""",
1232                "stderr": "",
1233                "redacted": True,
1234            },
1235            id="_0_0_opts_NOP_40",
1236        ),
1237        pytest.param(
1238            {
1239                "src": "192.0.2.1",
1240                "dst": "192.0.2.2",
1241                "icmp_type": 0,
1242                "icmp_code": 0,
1243                "opts": "unk",
1244            },
1245            {
1246                "returncode": 0,
1247                "stdout": """\
1248PING 192.0.2.2 (192.0.2.2): 56 data bytes
124964 bytes from: icmp_seq=0 ttl= time= ms
1250wrong total length 88 instead of 84
1251unknown option 9f
1252
1253--- 192.0.2.2 ping statistics ---
12541 packets transmitted, 1 packets received, 0.0% packet loss
1255round-trip min/avg/max/stddev = /// ms
1256""",
1257                "stderr": "",
1258                "redacted": True,
1259            },
1260            id="_0_0_opts_unk",
1261        ),
1262        pytest.param(
1263            {
1264                "src": "192.0.2.1",
1265                "dst": "192.0.2.2",
1266                "icmp_type": 3,
1267                "icmp_code": 1,
1268                "opts": "NOP-40",
1269            },
1270            {
1271                "returncode": 2,
1272                "stdout": """\
1273PING 192.0.2.2 (192.0.2.2): 56 data bytes
1274132 bytes from 192.0.2.2: Destination Host Unreachable
1275Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst Opts
1276 4  f  00 007c 0001   0 0000  40  01 d868 192.0.2.1 192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101
1277
1278
1279--- 192.0.2.2 ping statistics ---
12801 packets transmitted, 0 packets received, 100.0% packet loss
1281""",
1282                "stderr": "",
1283                "redacted": False,
1284            },
1285            id="_3_1_opts_NOP_40",
1286        ),
1287        pytest.param(
1288            {
1289                "src": "192.0.2.1",
1290                "dst": "192.0.2.2",
1291                "icmp_type": 3,
1292                "icmp_code": 1,
1293                "flags": "DF",
1294            },
1295            {
1296                "returncode": 2,
1297                "stdout": """\
1298PING 192.0.2.2 (192.0.2.2): 56 data bytes
129992 bytes from 192.0.2.2: Destination Host Unreachable
1300Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1301 4  5  00 0054 0001   2 0000  40  01 b6a4 192.0.2.1 192.0.2.2
1302
1303
1304--- 192.0.2.2 ping statistics ---
13051 packets transmitted, 0 packets received, 100.0% packet loss
1306""",
1307                "stderr": "",
1308                "redacted": False,
1309            },
1310            id="_3_1_flags_DF",
1311        ),
1312        pytest.param(
1313            {
1314                "src": "192.0.2.1",
1315                "dst": "192.0.2.2",
1316                "icmp_type": 3,
1317                "icmp_code": 1,
1318                "special": "tcp",
1319            },
1320            {
1321                "returncode": 2,
1322                "stdout": """\
1323PATTERN: 0x01
1324PING 192.0.2.2 (192.0.2.2): 56 data bytes
1325
1326--- 192.0.2.2 ping statistics ---
13271 packets transmitted, 0 packets received, 100.0% packet loss
1328""",
1329                "stderr": """\
1330ping: quoted data too short (40 bytes) from 192.0.2.2
1331""",
1332                "redacted": False,
1333            },
1334            id="_3_1_special_tcp",
1335        ),
1336        pytest.param(
1337            {
1338                "src": "192.0.2.1",
1339                "dst": "192.0.2.2",
1340                "icmp_type": 3,
1341                "icmp_code": 1,
1342                "special": "udp",
1343            },
1344            {
1345                "returncode": 2,
1346                "stdout": """\
1347PATTERN: 0x01
1348PING 192.0.2.2 (192.0.2.2): 56 data bytes
1349
1350--- 192.0.2.2 ping statistics ---
13511 packets transmitted, 0 packets received, 100.0% packet loss
1352""",
1353                "stderr": """\
1354ping: quoted data too short (28 bytes) from 192.0.2.2
1355""",
1356                "redacted": False,
1357            },
1358            id="_3_1_special_udp",
1359        ),
1360        pytest.param(
1361            {
1362                "src": "192.0.2.1",
1363                "dst": "192.0.2.2",
1364                "icmp_type": 3,
1365                "icmp_code": 1,
1366                "verbose": False,
1367            },
1368            {
1369                "returncode": 2,
1370                "stdout": """\
1371PING 192.0.2.2 (192.0.2.2): 56 data bytes
137292 bytes from 192.0.2.2: Destination Host Unreachable
1373Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1374 4  5  00 0054 0001   0 0000  40  01 f6a4 192.0.2.1 192.0.2.2
1375
1376
1377--- 192.0.2.2 ping statistics ---
13781 packets transmitted, 0 packets received, 100.0% packet loss
1379""",
1380                "stderr": "",
1381                "redacted": False,
1382            },
1383            id="_3_1_verbose_false",
1384        ),
1385        pytest.param(
1386            {
1387                "src": "192.0.2.1",
1388                "dst": "192.0.2.2",
1389                "icmp_type": 3,
1390                "icmp_code": 1,
1391                "special": "not-mine",
1392                "verbose": False,
1393            },
1394            {
1395                "returncode": 2,
1396                "stdout": """\
1397PATTERN: 0x01
1398PING 192.0.2.2 (192.0.2.2): 56 data bytes
1399
1400--- 192.0.2.2 ping statistics ---
14011 packets transmitted, 0 packets received, 100.0% packet loss
1402""",
1403                "stderr": "",
1404                "redacted": False,
1405            },
1406            id="_3_1_special_not_mine_verbose_false",
1407        ),
1408        pytest.param(
1409            {
1410                "src": "192.0.2.1",
1411                "dst": "192.0.2.2",
1412                "icmp_type": 0,
1413                "icmp_code": 0,
1414                "special": "warp",
1415            },
1416            {
1417                "returncode": 0,
1418                "stdout": """\
1419PATTERN: 0x01
1420PING 192.0.2.2 (192.0.2.2): 56 data bytes
142164 bytes from: icmp_seq=0 ttl= time= ms
1422
1423--- 192.0.2.2 ping statistics ---
14241 packets transmitted, 1 packets received, 0.0% packet loss
1425round-trip min/avg/max/stddev = /// ms
1426""",
1427                "stderr": """\
1428ping: time of day goes back (- ms), clamping time to 0
1429""",
1430                "redacted": True,
1431            },
1432            id="_0_0_special_warp",
1433        ),
1434        pytest.param(
1435            {
1436                "src": "192.0.2.1",
1437                "dst": "192.0.2.2",
1438                "icmp_type": 0,
1439                "icmp_code": 0,
1440                "special": "wrong",
1441            },
1442            {
1443                "returncode": 0,
1444                "stdout": """\
1445PATTERN: 0x01
1446PING 192.0.2.2 (192.0.2.2): 56 data bytes
144764 bytes from: icmp_seq=0 ttl= time= ms
1448wrong data byte #55 should be 0x1 but was 0x0
1449cp: xx xx xx xx xx xx xx xx
1450	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1451	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1452	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0
1453dp: 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  1
1457
1458--- 192.0.2.2 ping statistics ---
14591 packets transmitted, 1 packets received, 0.0% packet loss
1460round-trip min/avg/max/stddev = /// ms
1461""",
1462                "stderr": "",
1463                "redacted": True,
1464            },
1465            id="_0_0_special_wrong",
1466        ),
1467    ]
1468
1469    @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata)
1470    @pytest.mark.require_progs(["scapy"])
1471    @pytest.mark.require_user("root")
1472    def test_pinger(self, pinger_kargs, expected):
1473        """Test ping using pinger(), a reply faker"""
1474        iface = IfaceFactory().create_iface("", "tun")[0].name
1475        ping = pinger(iface, **pinger_kargs)
1476        assert ping.returncode == expected["returncode"]
1477        if expected["redacted"]:
1478            assert redact(ping.stdout) == expected["stdout"]
1479            assert redact(ping.stderr) == expected["stderr"]
1480        else:
1481            assert ping.stdout == expected["stdout"]
1482            assert ping.stderr == expected["stderr"]
1483