xref: /freebsd/sbin/ping/tests/test_ping.py (revision 5797a03fe8a7c3355a727aaaea803ee1f42d106b)
1import pytest
2
3import logging
4import os
5import re
6import subprocess
7
8from atf_python.sys.net.vnet import IfaceFactory
9from atf_python.sys.net.vnet import SingleVnetTestTemplate
10from atf_python.sys.net.tools import ToolsHelper
11from typing import List
12from typing import Optional
13
14logging.getLogger("scapy").setLevel(logging.CRITICAL)
15import scapy.all as sc
16
17
18def build_response_packet(echo, ip, icmp, oip_ihl, special):
19    icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38]
20    oip = echo[sc.IP]
21    oicmp = echo[sc.ICMP]
22    load = echo[sc.ICMP].payload
23    oip[sc.IP].remove_payload()
24    oicmp[sc.ICMP].remove_payload()
25    oicmp.type = 8
26
27    # As if the original IP packet had these set
28    oip.ihl = None
29    oip.len = None
30    oip.id = 1
31    oip.flags = ip.flags
32    oip.chksum = None
33    oip.options = ip.options
34
35    # Inner packet (oip) options
36    if oip_ihl:
37        oip.ihl = oip_ihl
38
39    # Special options
40    if special == "no-payload":
41        load = ""
42    if special == "tcp":
43        oip.proto = "tcp"
44        tcp = sc.TCP(sport=1234, dport=5678)
45        return ip / icmp / oip / tcp
46    if special == "udp":
47        oip.proto = "udp"
48        udp = sc.UDP(sport=1234, dport=5678)
49        return ip / icmp / oip / udp
50    if special == "warp":
51        # Build a package with a timestamp of INT_MAX
52        # (time-warped package)
53        payload_no_timestamp = sc.bytes_hex(load)[16:]
54        load = b"\x7f" + (b"\xff" * 7) + sc.hex_bytes(payload_no_timestamp)
55    if special == "wrong":
56        # Build a package with a wrong last byte
57        payload_no_last_byte = sc.bytes_hex(load)[:-2]
58        load = (sc.hex_bytes(payload_no_last_byte)) + b"\x00"
59    if special == "not-mine":
60        # Modify the ICMP Identifier field
61        oicmp.id += 1
62
63    if icmp.type in icmp_id_seq_types:
64        pkt = ip / icmp / load
65    else:
66        del ip.options
67        pkt = ip / icmp / oip / oicmp / load
68    return pkt
69
70
71def generate_ip_options(opts):
72    if not opts:
73        return []
74
75    routers = [
76        "192.0.2.10",
77        "192.0.2.20",
78        "192.0.2.30",
79        "192.0.2.40",
80        "192.0.2.50",
81        "192.0.2.60",
82        "192.0.2.70",
83        "192.0.2.80",
84        "192.0.2.90",
85    ]
86    routers_zero = [0, 0, 0, 0, 0, 0, 0, 0, 0]
87    if opts == "EOL":
88        options = sc.IPOption_EOL()
89    elif opts == "NOP":
90        options = sc.IPOption_NOP()
91    elif opts == "NOP-40":
92        options = sc.IPOption_NOP() * 40
93    elif opts == "RR":
94        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
95        options = sc.IPOption_RR(pointer=40, routers=routers)
96    elif opts == "RR-same":
97        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
98        options = sc.IPOption_RR(pointer=3, routers=routers_zero)
99    elif opts == "RR-trunc":
100        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
101        options = sc.IPOption_RR(length=7, routers=routers_zero)
102    elif opts == "LSRR":
103        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
104        options = sc.IPOption_LSRR(routers=routers)
105    elif opts == "LSRR-trunc":
106        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
107        options = sc.IPOption_LSRR(length=3, routers=routers_zero)
108    elif opts == "SSRR":
109        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
110        options = sc.IPOption_SSRR(routers=routers)
111    elif opts == "SSRR-trunc":
112        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
113        options = sc.IPOption_SSRR(length=3, routers=routers_zero)
114    elif opts == "unk":
115        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
116        options = b"\x9f"
117    elif opts == "unk-40":
118        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
119        options = b"\x9f" * 40
120    else:
121        options = []
122    return options
123
124
125def pinger(
126    # Required arguments
127    # Avoid setting defaults on these arguments,
128    # as we want to set them explicitly in the tests
129    iface: str,
130    /,
131    src: sc.scapy.fields.SourceIPField,
132    dst: sc.scapy.layers.inet.DestIPField,
133    icmp_type: sc.scapy.fields.ByteEnumField,
134    icmp_code: sc.scapy.fields.MultiEnumField,
135    # IP arguments
136    ihl: Optional[sc.scapy.fields.BitField] = None,
137    flags: Optional[sc.scapy.fields.FlagsField] = 0,
138    opts: Optional[str] = None,
139    oip_ihl: Optional[sc.scapy.fields.BitField] = None,
140    special: Optional[str] = None,
141    # ICMP arguments
142    # Match names with <netinet/ip_icmp.h>
143    icmp_pptr: sc.scapy.fields.ByteField = 0,
144    icmp_gwaddr: sc.scapy.fields.IPField = "0.0.0.0",
145    icmp_nextmtu: sc.scapy.fields.ShortField = 0,
146    icmp_otime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
147    icmp_rtime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
148    icmp_ttime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
149    icmp_mask: sc.scapy.fields.IPField = "0.0.0.0",
150    request: Optional[str] = None,
151    # Miscellaneous arguments
152    count: int = 1,
153    dup: bool = False,
154    verbose: bool = True,
155) -> subprocess.CompletedProcess:
156    """P I N G E R
157
158    Echo reply faker
159
160    :param str iface: Interface to send packet to
161    :keyword src: Source packet IP
162    :type src: class:`scapy.fields.SourceIPField`
163    :keyword dst: Destination packet IP
164    :type dst: class:`scapy.layers.inet.DestIPField`
165    :keyword icmp_type: ICMP type
166    :type icmp_type: class:`scapy.fields.ByteEnumField`
167    :keyword icmp_code: ICMP code
168    :type icmp_code: class:`scapy.fields.MultiEnumField`
169
170    :keyword ihl: Internet Header Length, defaults to None
171    :type ihl: class:`scapy.fields.BitField`, optional
172    :keyword flags: IP flags - one of `DF`, `MF` or `evil`, defaults to 0
173    :type flags: class:`scapy.fields.FlagsField`, optional
174    :keyword opts: Include IP options - one of `EOL`, `NOP`, `NOP-40`, `unk`,
175        `unk-40`, `RR`, `RR-same`, `RR-trunc`, `LSRR`, `LSRR-trunc`, `SSRR` or
176        `SSRR-trunc`, defaults to None
177    :type opts: str, optional
178    :keyword oip_ihl: Inner packet's Internet Header Length, defaults to None
179    :type oip_ihl: class:`scapy.fields.BitField`, optional
180    :keyword special: Send a special packet - one of `no-payload`, `not-mine`,
181        `tcp`, `udp`, `wrong` or `warp`, defaults to None
182    :type special: str, optional
183    :keyword icmp_pptr: ICMP pointer, defaults to 0
184    :type icmp_pptr: class:`scapy.fields.ByteField`
185    :keyword icmp_gwaddr: ICMP gateway IP address, defaults to "0.0.0.0"
186    :type icmp_gwaddr: class:`scapy.fields.IPField`
187    :keyword icmp_nextmtu: ICMP next MTU, defaults to 0
188    :type icmp_nextmtu: class:`scapy.fields.ShortField`
189    :keyword icmp_otime: ICMP originate timestamp, defaults to 0
190    :type icmp_otime: class:`scapy.layers.inet.ICMPTimeStampField`
191    :keyword icmp_rtime: ICMP receive timestamp, defaults to 0
192    :type icmp_rtime: class:`scapy.layers.inet.ICMPTimeStampField`
193    :keyword icmp_ttime: ICMP transmit timestamp, defaults to 0
194    :type icmp_ttime: class:`scapy.layers.inet.ICMPTimeStampField`
195    :keyword icmp_mask: ICMP address mask, defaults to "0.0.0.0"
196    :type icmp_mask: class:`scapy.fields.IPField`
197    :keyword request: Request type - one of `mask` or `timestamp`,
198        defaults to None
199    :type request: str, optional
200    :keyword count: Number of packets to send, defaults to 1
201    :type count: int
202    :keyword dup: Duplicate packets, defaults to `False`
203    :type dup: bool
204    :keyword verbose: Turn on/off verbosity, defaults to `True`
205    :type verbose: bool
206
207    :return: A class:`subprocess.CompletedProcess` with the output from the
208        ping utility
209    :rtype: class:`subprocess.CompletedProcess`
210    """
211    tun = sc.TunTapInterface(iface)
212    subprocess.run(["ifconfig", tun.iface, "up"], check=True)
213    subprocess.run(["ifconfig", tun.iface, src, dst], check=True)
214    ip_opts = generate_ip_options(opts)
215    ip = sc.IP(ihl=ihl, flags=flags, src=dst, dst=src, options=ip_opts)
216    command = [
217        "/sbin/ping",
218        "-c",
219        str(count),
220        "-t",
221        str(count),
222    ]
223    if verbose:
224        command += ["-v"]
225    if request == "mask":
226        command += ["-Mm"]
227    if request == "timestamp":
228        command += ["-Mt"]
229    if special:
230        command += ["-p1"]
231    if opts in [
232        "RR",
233        "RR-same",
234        "RR-trunc",
235        "LSRR",
236        "LSRR-trunc",
237        "SSRR",
238        "SSRR-trunc",
239    ]:
240        command += ["-R"]
241    command += [dst]
242    with subprocess.Popen(
243        args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
244    ) as ping:
245        for dummy in range(count):
246            echo = tun.recv()
247            icmp = sc.ICMP(
248                type=icmp_type,
249                code=icmp_code,
250                id=echo[sc.ICMP].id,
251                seq=echo[sc.ICMP].seq,
252                ts_ori=icmp_otime,
253                ts_rx=icmp_rtime,
254                ts_tx=icmp_ttime,
255                gw=icmp_gwaddr,
256                ptr=icmp_pptr,
257                addr_mask=icmp_mask,
258                nexthopmtu=icmp_nextmtu,
259            )
260            pkt = build_response_packet(echo, ip, icmp, oip_ihl, special)
261            tun.send(pkt)
262            if dup is True:
263                tun.send(pkt)
264        stdout, stderr = ping.communicate()
265    return subprocess.CompletedProcess(
266        ping.args, ping.returncode, stdout, stderr
267    )
268
269
270def redact(output):
271    """Redact some elements of ping's output"""
272    pattern_replacements = [
273        (r"localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"),
274        (r"from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"),
275        ("hlim=[0-9]*", "hlim="),
276        ("ttl=[0-9]*", "ttl="),
277        ("time=[0-9.-]*", "time="),
278        ("cp: .*", "cp: xx xx xx xx xx xx xx xx"),
279        ("dp: .*", "dp: xx xx xx xx xx xx xx xx"),
280        (r"\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"),
281        (r"[0-9\.]+/[0-9.]+", "/"),
282    ]
283    for pattern, repl in pattern_replacements:
284        output = re.sub(pattern, repl, output)
285    return output
286
287
288class TestPing(SingleVnetTestTemplate):
289    IPV6_PREFIXES: List[str] = ["2001:db8::1/64"]
290    IPV4_PREFIXES: List[str] = ["192.0.2.1/24"]
291
292    # Each param in testdata contains a dictionary with the command,
293    # and the expected outcome (returncode, redacted stdout, and stderr)
294    testdata = [
295        pytest.param(
296            {
297                "args": "ping -4 -c1 -s56 -t1 localhost",
298                "returncode": 0,
299                "stdout": """\
300PING localhost: 56 data bytes
30164 bytes from: icmp_seq=0 ttl= time= ms
302
303--- localhost ping statistics ---
3041 packets transmitted, 1 packets received, 0.0% packet loss
305round-trip min/avg/max/stddev = /// ms
306""",
307                "stderr": "",
308            },
309            id="_4_c1_s56_t1_localhost",
310        ),
311        pytest.param(
312            {
313                "args": "ping -6 -c1 -s8 -t1 localhost",
314                "returncode": 0,
315                "stdout": """\
316PING(56=40+8+8 bytes) ::1 --> ::1
31716 bytes from ::1, icmp_seq=0 hlim= time= ms
318
319--- localhost ping statistics ---
3201 packets transmitted, 1 packets received, 0.0% packet loss
321round-trip min/avg/max/stddev = /// ms
322""",
323                "stderr": "",
324            },
325            id="_6_c1_s8_t1_localhost",
326        ),
327        pytest.param(
328            {
329                "args": "ping -A -c1 192.0.2.1",
330                "returncode": 0,
331                "stdout": """\
332PING 192.0.2.1 (192.0.2.1): 56 data bytes
33364 bytes from: icmp_seq=0 ttl= time= ms
334
335--- 192.0.2.1 ping statistics ---
3361 packets transmitted, 1 packets received, 0.0% packet loss
337round-trip min/avg/max/stddev = /// ms
338""",
339                "stderr": "",
340            },
341            id="_A_c1_192_0_2_1",
342        ),
343        pytest.param(
344            {
345                "args": "ping -A -c1 192.0.2.2",
346                "returncode": 2,
347                "stdout": """\
348PING 192.0.2.2 (192.0.2.2): 56 data bytes
349
350--- 192.0.2.2 ping statistics ---
3511 packets transmitted, 0 packets received, 100.0% packet loss
352""",
353                "stderr": "",
354            },
355            id="_A_c1_192_0_2_2",
356        ),
357        pytest.param(
358            {
359                "args": "ping -A -c1 2001:db8::1",
360                "returncode": 0,
361                "stdout": """\
362PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
36316 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
364
365--- 2001:db8::1 ping statistics ---
3661 packets transmitted, 1 packets received, 0.0% packet loss
367round-trip min/avg/max/stddev = /// ms
368""",
369                "stderr": "",
370            },
371            id="_A_c1_2001_db8__1",
372        ),
373        pytest.param(
374            {
375                "args": "ping -A -c1 2001:db8::2",
376                "returncode": 2,
377                "stdout": """\
378PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
379
380--- 2001:db8::2 ping statistics ---
3811 packets transmitted, 0 packets received, 100.0% packet loss
382""",
383                "stderr": "",
384            },
385            id="_A_c1_2001_db8__2",
386        ),
387        pytest.param(
388            {
389                "args": "ping -A -c3 192.0.2.1",
390                "returncode": 0,
391                "stdout": """\
392PING 192.0.2.1 (192.0.2.1): 56 data bytes
39364 bytes from: icmp_seq=0 ttl= time= ms
39464 bytes from: icmp_seq=1 ttl= time= ms
39564 bytes from: icmp_seq=2 ttl= time= ms
396
397--- 192.0.2.1 ping statistics ---
3983 packets transmitted, 3 packets received, 0.0% packet loss
399round-trip min/avg/max/stddev = /// ms
400""",
401                "stderr": "",
402            },
403            id="_A_3_192_0.2.1",
404        ),
405        pytest.param(
406            {
407                "args": "ping -A -c3 192.0.2.2",
408                "returncode": 2,
409                "stdout": """\
410\x07\x07PING 192.0.2.2 (192.0.2.2): 56 data bytes
411
412--- 192.0.2.2 ping statistics ---
4133 packets transmitted, 0 packets received, 100.0% packet loss
414""",
415                "stderr": "",
416            },
417            id="_A_c3_192_0_2_2",
418        ),
419        pytest.param(
420            {
421                "args": "ping -A -c3 2001:db8::1",
422                "returncode": 0,
423                "stdout": """\
424PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
42516 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
42616 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
42716 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
428
429--- 2001:db8::1 ping statistics ---
4303 packets transmitted, 3 packets received, 0.0% packet loss
431round-trip min/avg/max/stddev = /// ms
432""",
433                "stderr": "",
434            },
435            id="_A_c3_2001_db8__1",
436        ),
437        pytest.param(
438            {
439                "args": "ping -A -c3 2001:db8::2",
440                "returncode": 2,
441                "stdout": """\
442\x07\x07PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
443
444--- 2001:db8::2 ping statistics ---
4453 packets transmitted, 0 packets received, 100.0% packet loss
446""",
447                "stderr": "",
448            },
449            id="_A_c3_2001_db8__2",
450        ),
451        pytest.param(
452            {
453                "args": "ping -c1 192.0.2.1",
454                "returncode": 0,
455                "stdout": """\
456PING 192.0.2.1 (192.0.2.1): 56 data bytes
45764 bytes from: icmp_seq=0 ttl= time= ms
458
459--- 192.0.2.1 ping statistics ---
4601 packets transmitted, 1 packets received, 0.0% packet loss
461round-trip min/avg/max/stddev = /// ms
462""",
463                "stderr": "",
464            },
465            id="_c1_192_0_2_1",
466        ),
467        pytest.param(
468            {
469                "args": "ping -c1 192.0.2.2",
470                "returncode": 2,
471                "stdout": """\
472PING 192.0.2.2 (192.0.2.2): 56 data bytes
473
474--- 192.0.2.2 ping statistics ---
4751 packets transmitted, 0 packets received, 100.0% packet loss
476""",
477                "stderr": "",
478            },
479            id="_c1_192_0_2_2",
480        ),
481        pytest.param(
482            {
483                "args": "ping -c1 2001:db8::1",
484                "returncode": 0,
485                "stdout": """\
486PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
48716 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
488
489--- 2001:db8::1 ping statistics ---
4901 packets transmitted, 1 packets received, 0.0% packet loss
491round-trip min/avg/max/stddev = /// ms
492""",
493                "stderr": "",
494            },
495            id="_c1_2001_db8__1",
496        ),
497        pytest.param(
498            {
499                "args": "ping -c1 2001:db8::2",
500                "returncode": 2,
501                "stdout": """\
502PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
503
504--- 2001:db8::2 ping statistics ---
5051 packets transmitted, 0 packets received, 100.0% packet loss
506""",
507                "stderr": "",
508            },
509            id="_c1_2001_db8__2",
510        ),
511        pytest.param(
512            {
513                "args": "ping -c1 -S127.0.0.1 -s56 -t1 localhost",
514                "returncode": 0,
515                "stdout": """\
516PING localhost from: 56 data bytes
51764 bytes from: icmp_seq=0 ttl= time= ms
518
519--- localhost ping statistics ---
5201 packets transmitted, 1 packets received, 0.0% packet loss
521round-trip min/avg/max/stddev = /// ms
522""",
523                "stderr": "",
524            },
525            id="_c1_S127_0_0_1_s56_t1_localhost",
526        ),
527        pytest.param(
528            {
529                "args": "ping -c1 -S::1 -s8 -t1 localhost",
530                "returncode": 0,
531                "stdout": """\
532PING(56=40+8+8 bytes) ::1 --> ::1
53316 bytes from ::1, icmp_seq=0 hlim= time= ms
534
535--- localhost ping statistics ---
5361 packets transmitted, 1 packets received, 0.0% packet loss
537round-trip min/avg/max/stddev = /// ms
538""",
539                "stderr": "",
540            },
541            id="_c1_S__1_s8_t1_localhost",
542        ),
543        pytest.param(
544            {
545                "args": "ping -c3 192.0.2.1",
546                "returncode": 0,
547                "stdout": """\
548PING 192.0.2.1 (192.0.2.1): 56 data bytes
54964 bytes from: icmp_seq=0 ttl= time= ms
55064 bytes from: icmp_seq=1 ttl= time= ms
55164 bytes from: icmp_seq=2 ttl= time= ms
552
553--- 192.0.2.1 ping statistics ---
5543 packets transmitted, 3 packets received, 0.0% packet loss
555round-trip min/avg/max/stddev = /// ms
556""",
557                "stderr": "",
558            },
559            id="_c3_192_0_2_1",
560        ),
561        pytest.param(
562            {
563                "args": "ping -c3 192.0.2.2",
564                "returncode": 2,
565                "stdout": """\
566PING 192.0.2.2 (192.0.2.2): 56 data bytes
567
568--- 192.0.2.2 ping statistics ---
5693 packets transmitted, 0 packets received, 100.0% packet loss
570""",
571                "stderr": "",
572            },
573            id="_c3_192_0_2_2",
574        ),
575        pytest.param(
576            {
577                "args": "ping -c3 2001:db8::1",
578                "returncode": 0,
579                "stdout": """\
580PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
58116 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
58216 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
58316 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
584
585--- 2001:db8::1 ping statistics ---
5863 packets transmitted, 3 packets received, 0.0% packet loss
587round-trip min/avg/max/stddev = /// ms
588""",
589                "stderr": "",
590            },
591            id="_c3_2001_db8__1",
592        ),
593        pytest.param(
594            {
595                "args": "ping -c3 2001:db8::2",
596                "returncode": 2,
597                "stdout": """\
598PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
599
600--- 2001:db8::2 ping statistics ---
6013 packets transmitted, 0 packets received, 100.0% packet loss
602""",
603                "stderr": "",
604            },
605            id="_c3_2001_db8__2",
606        ),
607        pytest.param(
608            {
609                "args": "ping -q -c1 192.0.2.1",
610                "returncode": 0,
611                "stdout": """\
612PING 192.0.2.1 (192.0.2.1): 56 data bytes
613
614--- 192.0.2.1 ping statistics ---
6151 packets transmitted, 1 packets received, 0.0% packet loss
616round-trip min/avg/max/stddev = /// ms
617""",
618                "stderr": "",
619            },
620            id="_q_c1_192_0_2_1",
621        ),
622        pytest.param(
623            {
624                "args": "ping -q -c1 192.0.2.2",
625                "returncode": 2,
626                "stdout": """\
627PING 192.0.2.2 (192.0.2.2): 56 data bytes
628
629--- 192.0.2.2 ping statistics ---
6301 packets transmitted, 0 packets received, 100.0% packet loss
631""",
632                "stderr": "",
633            },
634            id="_q_c1_192_0_2_2",
635        ),
636        pytest.param(
637            {
638                "args": "ping -q -c1 2001:db8::1",
639                "returncode": 0,
640                "stdout": """\
641PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
642
643--- 2001:db8::1 ping statistics ---
6441 packets transmitted, 1 packets received, 0.0% packet loss
645round-trip min/avg/max/stddev = /// ms
646""",
647                "stderr": "",
648            },
649            id="_q_c1_2001_db8__1",
650        ),
651        pytest.param(
652            {
653                "args": "ping -q -c1 2001:db8::2",
654                "returncode": 2,
655                "stdout": """\
656PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
657
658--- 2001:db8::2 ping statistics ---
6591 packets transmitted, 0 packets received, 100.0% packet loss
660""",
661                "stderr": "",
662            },
663            id="_q_c1_2001_db8__2",
664        ),
665        pytest.param(
666            {
667                "args": "ping -q -c3 192.0.2.1",
668                "returncode": 0,
669                "stdout": """\
670PING 192.0.2.1 (192.0.2.1): 56 data bytes
671
672--- 192.0.2.1 ping statistics ---
6733 packets transmitted, 3 packets received, 0.0% packet loss
674round-trip min/avg/max/stddev = /// ms
675""",
676                "stderr": "",
677            },
678            id="_q_c3_192_0_2_1",
679        ),
680        pytest.param(
681            {
682                "args": "ping -q -c3 192.0.2.2",
683                "returncode": 2,
684                "stdout": """\
685PING 192.0.2.2 (192.0.2.2): 56 data bytes
686
687--- 192.0.2.2 ping statistics ---
6883 packets transmitted, 0 packets received, 100.0% packet loss
689""",
690                "stderr": "",
691            },
692            id="_q_c3_192_0_2_2",
693        ),
694        pytest.param(
695            {
696                "args": "ping -q -c3 2001:db8::1",
697                "returncode": 0,
698                "stdout": """\
699PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
700
701--- 2001:db8::1 ping statistics ---
7023 packets transmitted, 3 packets received, 0.0% packet loss
703round-trip min/avg/max/stddev = /// ms
704""",
705                "stderr": "",
706            },
707            id="_q_c3_2001_db8__1",
708        ),
709        pytest.param(
710            {
711                "args": "ping -q -c3 2001:db8::2",
712                "returncode": 2,
713                "stdout": """\
714PING(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
715
716--- 2001:db8::2 ping statistics ---
7173 packets transmitted, 0 packets received, 100.0% packet loss
718""",
719                "stderr": "",
720            },
721            id="_q_c3_2001_db8__2",
722        ),
723    ]
724
725    @pytest.mark.parametrize("expected", testdata)
726    @pytest.mark.require_user("root")
727    @pytest.mark.require_user("unprivileged")
728    def test_ping(self, expected):
729        """Test ping"""
730        ping = subprocess.run(
731            expected["args"].split(),
732            capture_output=True,
733            timeout=15,
734            text=True,
735        )
736        assert ping.returncode == expected["returncode"]
737        assert redact(ping.stdout) == expected["stdout"]
738        assert ping.stderr == expected["stderr"]
739
740    # Each param in ping46_testdata contains a dictionary with the arguments
741    # and the expected outcome (returncode, redacted stdout, and stderr)
742    # common to `ping -4` and `ping -6`
743    ping46_testdata = [
744        pytest.param(
745            {
746                "args": "-Wx localhost",
747                "returncode": os.EX_USAGE,
748                "stdout": "",
749                "stderr": "ping: invalid timing interval: `x'\n",
750            },
751            id="_Wx_localhost",
752        ),
753    ]
754
755    @pytest.mark.parametrize("expected", ping46_testdata)
756    @pytest.mark.require_user("root")
757    @pytest.mark.require_user("unprivileged")
758    def test_ping_46(self, expected):
759        """Test ping -4/ping -6"""
760        for version in [4, 6]:
761            ping = subprocess.run(
762                ["ping", f"-{version}"] + expected["args"].split(),
763                capture_output=True,
764                timeout=15,
765                text=True,
766            )
767            assert ping.returncode == expected["returncode"]
768            assert redact(ping.stdout) == expected["stdout"]
769            assert ping.stderr == expected["stderr"]
770
771    # Each param in pinger_testdata contains a dictionary with the keywords to
772    # `pinger()` and a dictionary with the expected outcome (returncode,
773    # stdout, stderr, and if ping's output is redacted)
774    pinger_testdata = [
775        pytest.param(
776            {
777                "src": "192.0.2.1",
778                "dst": "192.0.2.2",
779                "icmp_type": 0,
780                "icmp_code": 0,
781            },
782            {
783                "returncode": 0,
784                "stdout": """\
785PING 192.0.2.2 (192.0.2.2): 56 data bytes
78664 bytes from: icmp_seq=0 ttl= time= ms
787
788--- 192.0.2.2 ping statistics ---
7891 packets transmitted, 1 packets received, 0.0% packet loss
790round-trip min/avg/max/stddev = /// ms
791""",
792                "stderr": "",
793                "redacted": True,
794            },
795            id="_0_0",
796        ),
797        pytest.param(
798            {
799                "src": "192.0.2.1",
800                "dst": "192.0.2.2",
801                "icmp_type": 0,
802                "icmp_code": 0,
803                "opts": "EOL",
804            },
805            {
806                "returncode": 0,
807                "stdout": """\
808PING 192.0.2.2 (192.0.2.2): 56 data bytes
80964 bytes from: icmp_seq=0 ttl= time= ms
810wrong total length 88 instead of 84
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_opts_EOL",
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": "LSRR",
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
834LSRR: 	192.0.2.10
835	192.0.2.20
836	192.0.2.30
837	192.0.2.40
838	192.0.2.50
839	192.0.2.60
840	192.0.2.70
841	192.0.2.80
842	192.0.2.90
843
844--- 192.0.2.2 ping statistics ---
8451 packets transmitted, 1 packets received, 0.0% packet loss
846round-trip min/avg/max/stddev = /// ms
847""",
848                "stderr": "",
849                "redacted": True,
850            },
851            id="_0_0_opts_LSRR",
852        ),
853        pytest.param(
854            {
855                "src": "192.0.2.1",
856                "dst": "192.0.2.2",
857                "icmp_type": 0,
858                "icmp_code": 0,
859                "opts": "LSRR-trunc",
860            },
861            {
862                "returncode": 0,
863                "stdout": """\
864PING 192.0.2.2 (192.0.2.2): 56 data bytes
86564 bytes from: icmp_seq=0 ttl= time= ms
866LSRR: 	(truncated route)
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_trunc",
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": "SSRR",
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
890SSRR: 	192.0.2.10
891	192.0.2.20
892	192.0.2.30
893	192.0.2.40
894	192.0.2.50
895	192.0.2.60
896	192.0.2.70
897	192.0.2.80
898	192.0.2.90
899
900--- 192.0.2.2 ping statistics ---
9011 packets transmitted, 1 packets received, 0.0% packet loss
902round-trip min/avg/max/stddev = /// ms
903""",
904                "stderr": "",
905                "redacted": True,
906            },
907            id="_0_0_opts_SSRR",
908        ),
909        pytest.param(
910            {
911                "src": "192.0.2.1",
912                "dst": "192.0.2.2",
913                "icmp_type": 0,
914                "icmp_code": 0,
915                "opts": "SSRR-trunc",
916            },
917            {
918                "returncode": 0,
919                "stdout": """\
920PING 192.0.2.2 (192.0.2.2): 56 data bytes
92164 bytes from: icmp_seq=0 ttl= time= ms
922SSRR: 	(truncated route)
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_trunc",
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": "RR",
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
946RR: 	192.0.2.10
947	192.0.2.20
948	192.0.2.30
949	192.0.2.40
950	192.0.2.50
951	192.0.2.60
952	192.0.2.70
953	192.0.2.80
954	192.0.2.90
955
956--- 192.0.2.2 ping statistics ---
9571 packets transmitted, 1 packets received, 0.0% packet loss
958round-trip min/avg/max/stddev = /// ms
959""",
960                "stderr": "",
961                "redacted": True,
962            },
963            id="_0_0_opts_RR",
964        ),
965        pytest.param(
966            {
967                "src": "192.0.2.1",
968                "dst": "192.0.2.2",
969                "icmp_type": 0,
970                "icmp_code": 0,
971                "opts": "RR-same",
972            },
973            {
974                "returncode": 0,
975                "stdout": """\
976PING 192.0.2.2 (192.0.2.2): 56 data bytes
97764 bytes from: icmp_seq=0 ttl= time= ms	(same route)
978
979--- 192.0.2.2 ping statistics ---
9801 packets transmitted, 1 packets received, 0.0% packet loss
981round-trip min/avg/max/stddev = /// ms
982""",
983                "stderr": "",
984                "redacted": True,
985            },
986            id="_0_0_opts_RR_same",
987        ),
988        pytest.param(
989            {
990                "src": "192.0.2.1",
991                "dst": "192.0.2.2",
992                "icmp_type": 0,
993                "icmp_code": 0,
994                "opts": "RR-trunc",
995            },
996            {
997                "returncode": 0,
998                "stdout": """\
999PING 192.0.2.2 (192.0.2.2): 56 data bytes
100064 bytes from: icmp_seq=0 ttl= time= ms
1001RR: 	(truncated 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_trunc",
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": "NOP",
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
1025wrong total length 88 instead of 84
1026NOP
1027
1028--- 192.0.2.2 ping statistics ---
10291 packets transmitted, 1 packets received, 0.0% packet loss
1030round-trip min/avg/max/stddev = /// ms
1031""",
1032                "stderr": "",
1033                "redacted": True,
1034            },
1035            id="_0_0_opts_NOP",
1036        ),
1037        pytest.param(
1038            {
1039                "src": "192.0.2.1",
1040                "dst": "192.0.2.2",
1041                "icmp_type": 3,
1042                "icmp_code": 1,
1043                "ihl": 0x4,
1044            },
1045            {
1046                "returncode": 2,
1047                "stdout": """\
1048PING 192.0.2.2 (192.0.2.2): 56 data bytes
1049
1050--- 192.0.2.2 ping statistics ---
10511 packets transmitted, 0 packets received, 100.0% packet loss
1052""",
1053                "stderr": "",  # "IHL too short" message not shown
1054                "redacted": False,
1055            },
1056            id="_IHL_too_short",
1057        ),
1058        pytest.param(
1059            {
1060                "src": "192.0.2.1",
1061                "dst": "192.0.2.2",
1062                "icmp_type": 3,
1063                "icmp_code": 1,
1064                "special": "no-payload",
1065            },
1066            {
1067                "returncode": 2,
1068                "stdout": """\
1069PATTERN: 0x01
1070PING 192.0.2.2 (192.0.2.2): 56 data bytes
1071
1072--- 192.0.2.2 ping statistics ---
10731 packets transmitted, 0 packets received, 100.0% packet loss
1074""",
1075                "stderr": """\
1076ping: quoted data too short (28 bytes) from 192.0.2.2
1077""",
1078                "redacted": False,
1079            },
1080            id="_quoted_data_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                "oip_ihl": 0x4,
1089            },
1090            {
1091                "returncode": 2,
1092                "stdout": """\
1093PING 192.0.2.2 (192.0.2.2): 56 data bytes
1094
1095--- 192.0.2.2 ping statistics ---
10961 packets transmitted, 0 packets received, 100.0% packet loss
1097""",
1098                "stderr": "",  # "inner IHL too short" message not shown
1099                "redacted": False,
1100            },
1101            id="_inner_IHL_too_short",
1102        ),
1103        pytest.param(
1104            {
1105                "src": "192.0.2.1",
1106                "dst": "192.0.2.2",
1107                "icmp_type": 3,
1108                "icmp_code": 1,
1109                "oip_ihl": 0xF,
1110            },
1111            {
1112                "returncode": 2,
1113                "stdout": """\
1114PING 192.0.2.2 (192.0.2.2): 56 data bytes
1115
1116--- 192.0.2.2 ping statistics ---
11171 packets transmitted, 0 packets received, 100.0% packet loss
1118""",
1119                "stderr": """\
1120ping: inner packet too short (84 bytes) from 192.0.2.2
1121""",
1122                "redacted": False,
1123            },
1124            id="_inner_packet_too_short",
1125        ),
1126        pytest.param(
1127            {
1128                "src": "192.0.2.1",
1129                "dst": "192.0.2.2",
1130                "icmp_type": 3,
1131                "icmp_code": 1,
1132                "oip_ihl": 0xF,
1133                "special": "no-payload",
1134            },
1135            {
1136                "returncode": 2,
1137                "stdout": """\
1138PATTERN: 0x01
1139PING 192.0.2.2 (192.0.2.2): 56 data bytes
1140
1141--- 192.0.2.2 ping statistics ---
11421 packets transmitted, 0 packets received, 100.0% packet loss
1143""",
1144                "stderr": "",
1145                "redacted": False,
1146            },
1147            id="_max_inner_packet_ihl_without_payload",
1148        ),
1149        pytest.param(
1150            {
1151                "src": "192.0.2.1",
1152                "dst": "192.0.2.2",
1153                "icmp_type": 0,
1154                "icmp_code": 0,
1155                "opts": "NOP-40",
1156            },
1157            {
1158                "returncode": 0,
1159                "stdout": """\
1160PING 192.0.2.2 (192.0.2.2): 56 data bytes
116164 bytes from: icmp_seq=0 ttl= time= ms
1162wrong total length 124 instead of 84
1163NOP
1164NOP
1165NOP
1166NOP
1167NOP
1168NOP
1169NOP
1170NOP
1171NOP
1172NOP
1173NOP
1174NOP
1175NOP
1176NOP
1177NOP
1178NOP
1179NOP
1180NOP
1181NOP
1182NOP
1183NOP
1184NOP
1185NOP
1186NOP
1187NOP
1188NOP
1189NOP
1190NOP
1191NOP
1192NOP
1193NOP
1194NOP
1195NOP
1196NOP
1197NOP
1198NOP
1199NOP
1200NOP
1201NOP
1202NOP
1203
1204--- 192.0.2.2 ping statistics ---
12051 packets transmitted, 1 packets received, 0.0% packet loss
1206round-trip min/avg/max/stddev = /// ms
1207""",
1208                "stderr": "",
1209                "redacted": True,
1210            },
1211            id="_0_0_opts_NOP_40",
1212        ),
1213        pytest.param(
1214            {
1215                "src": "192.0.2.1",
1216                "dst": "192.0.2.2",
1217                "icmp_type": 0,
1218                "icmp_code": 0,
1219                "opts": "unk",
1220            },
1221            {
1222                "returncode": 0,
1223                "stdout": """\
1224PING 192.0.2.2 (192.0.2.2): 56 data bytes
122564 bytes from: icmp_seq=0 ttl= time= ms
1226wrong total length 88 instead of 84
1227unknown option 9f
1228
1229--- 192.0.2.2 ping statistics ---
12301 packets transmitted, 1 packets received, 0.0% packet loss
1231round-trip min/avg/max/stddev = /// ms
1232""",
1233                "stderr": "",
1234                "redacted": True,
1235            },
1236            id="_0_0_opts_unk",
1237        ),
1238        pytest.param(
1239            {
1240                "src": "192.0.2.1",
1241                "dst": "192.0.2.2",
1242                "icmp_type": 3,
1243                "icmp_code": 1,
1244                "opts": "NOP-40",
1245            },
1246            {
1247                "returncode": 2,
1248                "stdout": """\
1249PING 192.0.2.2 (192.0.2.2): 56 data bytes
1250132 bytes from 192.0.2.2: Destination Host Unreachable
1251Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst Opts
1252 4  f  00 007c 0001   0 0000  40  01 d868 192.0.2.1 192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101
1253
1254
1255--- 192.0.2.2 ping statistics ---
12561 packets transmitted, 0 packets received, 100.0% packet loss
1257""",
1258                "stderr": "",
1259                "redacted": False,
1260            },
1261            id="_3_1_opts_NOP_40",
1262        ),
1263        pytest.param(
1264            {
1265                "src": "192.0.2.1",
1266                "dst": "192.0.2.2",
1267                "icmp_type": 3,
1268                "icmp_code": 1,
1269                "flags": "DF",
1270            },
1271            {
1272                "returncode": 2,
1273                "stdout": """\
1274PING 192.0.2.2 (192.0.2.2): 56 data bytes
127592 bytes from 192.0.2.2: Destination Host Unreachable
1276Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1277 4  5  00 0054 0001   2 0000  40  01 b6a4 192.0.2.1 192.0.2.2
1278
1279
1280--- 192.0.2.2 ping statistics ---
12811 packets transmitted, 0 packets received, 100.0% packet loss
1282""",
1283                "stderr": "",
1284                "redacted": False,
1285            },
1286            id="_3_1_flags_DF",
1287        ),
1288        pytest.param(
1289            {
1290                "src": "192.0.2.1",
1291                "dst": "192.0.2.2",
1292                "icmp_type": 3,
1293                "icmp_code": 1,
1294                "special": "tcp",
1295            },
1296            {
1297                "returncode": 2,
1298                "stdout": """\
1299PATTERN: 0x01
1300PING 192.0.2.2 (192.0.2.2): 56 data bytes
1301
1302--- 192.0.2.2 ping statistics ---
13031 packets transmitted, 0 packets received, 100.0% packet loss
1304""",
1305                "stderr": """\
1306ping: quoted data too short (40 bytes) from 192.0.2.2
1307""",
1308                "redacted": False,
1309            },
1310            id="_3_1_special_tcp",
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": "udp",
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 (28 bytes) from 192.0.2.2
1331""",
1332                "redacted": False,
1333            },
1334            id="_3_1_special_udp",
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                "verbose": False,
1343            },
1344            {
1345                "returncode": 2,
1346                "stdout": """\
1347PING 192.0.2.2 (192.0.2.2): 56 data bytes
134892 bytes from 192.0.2.2: Destination Host Unreachable
1349Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1350 4  5  00 0054 0001   0 0000  40  01 f6a4 192.0.2.1 192.0.2.2
1351
1352
1353--- 192.0.2.2 ping statistics ---
13541 packets transmitted, 0 packets received, 100.0% packet loss
1355""",
1356                "stderr": "",
1357                "redacted": False,
1358            },
1359            id="_3_1_verbose_false",
1360        ),
1361        pytest.param(
1362            {
1363                "src": "192.0.2.1",
1364                "dst": "192.0.2.2",
1365                "icmp_type": 3,
1366                "icmp_code": 1,
1367                "special": "not-mine",
1368                "verbose": False,
1369            },
1370            {
1371                "returncode": 2,
1372                "stdout": """\
1373PATTERN: 0x01
1374PING 192.0.2.2 (192.0.2.2): 56 data bytes
1375
1376--- 192.0.2.2 ping statistics ---
13771 packets transmitted, 0 packets received, 100.0% packet loss
1378""",
1379                "stderr": "",
1380                "redacted": False,
1381            },
1382            id="_3_1_special_not_mine_verbose_false",
1383        ),
1384        pytest.param(
1385            {
1386                "src": "192.0.2.1",
1387                "dst": "192.0.2.2",
1388                "icmp_type": 0,
1389                "icmp_code": 0,
1390                "special": "warp",
1391            },
1392            {
1393                "returncode": 0,
1394                "stdout": """\
1395PATTERN: 0x01
1396PING 192.0.2.2 (192.0.2.2): 56 data bytes
139764 bytes from: icmp_seq=0 ttl= time= ms
1398
1399--- 192.0.2.2 ping statistics ---
14001 packets transmitted, 1 packets received, 0.0% packet loss
1401round-trip min/avg/max/stddev = /// ms
1402""",
1403                "stderr": """\
1404ping: time of day goes back (- ms), clamping time to 0
1405""",
1406                "redacted": True,
1407            },
1408            id="_0_0_special_warp",
1409        ),
1410        pytest.param(
1411            {
1412                "src": "192.0.2.1",
1413                "dst": "192.0.2.2",
1414                "icmp_type": 0,
1415                "icmp_code": 0,
1416                "special": "wrong",
1417            },
1418            {
1419                "returncode": 0,
1420                "stdout": """\
1421PATTERN: 0x01
1422PING 192.0.2.2 (192.0.2.2): 56 data bytes
142364 bytes from: icmp_seq=0 ttl= time= ms
1424wrong data byte #55 should be 0x1 but was 0x0
1425cp: xx xx xx xx xx xx xx xx
1426	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1427	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1428	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0
1429dp: xx xx xx xx xx xx xx xx
1430	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1431	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1432	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1433
1434--- 192.0.2.2 ping statistics ---
14351 packets transmitted, 1 packets received, 0.0% packet loss
1436round-trip min/avg/max/stddev = /// ms
1437""",
1438                "stderr": "",
1439                "redacted": True,
1440            },
1441            id="_0_0_special_wrong",
1442        ),
1443    ]
1444
1445    @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata)
1446    @pytest.mark.require_progs(["scapy"])
1447    @pytest.mark.require_user("root")
1448    def test_pinger(self, pinger_kargs, expected):
1449        """Test ping using pinger(), a reply faker"""
1450        iface = IfaceFactory().create_iface("", "tun")[0].name
1451        ping = pinger(iface, **pinger_kargs)
1452        assert ping.returncode == expected["returncode"]
1453        if expected["redacted"]:
1454            assert redact(ping.stdout) == expected["stdout"]
1455            assert redact(ping.stderr) == expected["stderr"]
1456        else:
1457            assert ping.stdout == expected["stdout"]
1458            assert ping.stderr == expected["stderr"]
1459