xref: /freebsd/sbin/ping/tests/test_ping.py (revision 4b9d6057)
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        ip.options = ""
67        pkt = ip / icmp / oip / oicmp / load
68    return pkt
69
70
71def generate_ip_options(opts):
72    if not opts:
73        return ""
74
75    routers = [
76        "192.0.2.10",
77        "192.0.2.20",
78        "192.0.2.30",
79        "192.0.2.40",
80        "192.0.2.50",
81        "192.0.2.60",
82        "192.0.2.70",
83        "192.0.2.80",
84        "192.0.2.90",
85    ]
86    routers_zero = [0, 0, 0, 0, 0, 0, 0, 0, 0]
87    if opts == "EOL":
88        options = sc.IPOption(b"\x00")
89    elif opts == "NOP":
90        options = sc.IPOption(b"\x01")
91    elif opts == "NOP-40":
92        options = sc.IPOption(b"\x01" * 40)
93    elif opts == "RR":
94        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
95        options = sc.IPOption_RR(pointer=40, routers=routers)
96    elif opts == "RR-same":
97        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
98        options = sc.IPOption_RR(pointer=3, routers=routers_zero)
99    elif opts == "RR-trunc":
100        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
101        options = sc.IPOption_RR(length=7, routers=routers_zero)
102    elif opts == "LSRR":
103        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
104        options = sc.IPOption_LSRR(routers=routers)
105    elif opts == "LSRR-trunc":
106        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
107        options = sc.IPOption_LSRR(length=3, routers=routers_zero)
108    elif opts == "SSRR":
109        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
110        options = sc.IPOption_SSRR(routers=routers)
111    elif opts == "SSRR-trunc":
112        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
113        options = sc.IPOption_SSRR(length=3, routers=routers_zero)
114    elif opts == "unk":
115        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
116        options = sc.IPOption(b"\x9f")
117    elif opts == "unk-40":
118        ToolsHelper.set_sysctl("net.inet.ip.process_options", 0)
119        options = sc.IPOption(b"\x9f" * 40)
120    else:
121        options = ""
122    return options
123
124
125def pinger(
126    # Required arguments
127    # Avoid setting defaults on these arguments,
128    # as we want to set them explicitly in the tests
129    iface: str,
130    /,
131    src: sc.scapy.fields.SourceIPField,
132    dst: sc.scapy.layers.inet.DestIPField,
133    icmp_type: sc.scapy.fields.ByteEnumField,
134    icmp_code: sc.scapy.fields.MultiEnumField,
135    # IP arguments
136    ihl: Optional[sc.scapy.fields.BitField] = None,
137    flags: Optional[sc.scapy.fields.FlagsField] = None,
138    opts: Optional[str] = None,
139    oip_ihl: Optional[sc.scapy.fields.BitField] = None,
140    special: Optional[str] = None,
141    # ICMP arguments
142    # Match names with <netinet/ip_icmp.h>
143    icmp_pptr: sc.scapy.fields.ByteField = 0,
144    icmp_gwaddr: sc.scapy.fields.IPField = "0.0.0.0",
145    icmp_nextmtu: sc.scapy.fields.ShortField = 0,
146    icmp_otime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
147    icmp_rtime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
148    icmp_ttime: sc.scapy.layers.inet.ICMPTimeStampField = 0,
149    icmp_mask: sc.scapy.fields.IPField = "0.0.0.0",
150    request: Optional[str] = None,
151    # Miscellaneous arguments
152    count: int = 1,
153    dup: bool = False,
154    verbose: bool = True,
155) -> subprocess.CompletedProcess:
156    """P I N G E R
157
158    Echo reply faker
159
160    :param str iface: Interface to send packet to
161    :keyword src: Source packet IP
162    :type src: class:`scapy.fields.SourceIPField`
163    :keyword dst: Destination packet IP
164    :type dst: class:`scapy.layers.inet.DestIPField`
165    :keyword icmp_type: ICMP type
166    :type icmp_type: class:`scapy.fields.ByteEnumField`
167    :keyword icmp_code: ICMP code
168    :type icmp_code: class:`scapy.fields.MultiEnumField`
169
170    :keyword ihl: Internet Header Length, defaults to None
171    :type ihl: class:`scapy.fields.BitField`, optional
172    :keyword flags: IP flags - one of `DF`, `MF` or `evil`, defaults to None
173    :type flags: class:`scapy.fields.FlagsField`, optional
174    :keyword opts: Include IP options - one of `EOL`, `NOP`, `NOP-40`, `unk`,
175        `unk-40`, `RR`, `RR-same`, `RR-trunc`, `LSRR`, `LSRR-trunc`, `SSRR` or
176        `SSRR-trunc`, defaults to None
177    :type opts: str, optional
178    :keyword oip_ihl: Inner packet's Internet Header Length, defaults to None
179    :type oip_ihl: class:`scapy.fields.BitField`, optional
180    :keyword special: Send a special packet - one of `no-payload`, `not-mine`,
181        `tcp`, `udp`, `wrong` or `warp`, defaults to None
182    :type special: str, optional
183    :keyword icmp_pptr: ICMP pointer, defaults to 0
184    :type icmp_pptr: class:`scapy.fields.ByteField`
185    :keyword icmp_gwaddr: ICMP gateway IP address, defaults to "0.0.0.0"
186    :type icmp_gwaddr: class:`scapy.fields.IPField`
187    :keyword icmp_nextmtu: ICMP next MTU, defaults to 0
188    :type icmp_nextmtu: class:`scapy.fields.ShortField`
189    :keyword icmp_otime: ICMP originate timestamp, defaults to 0
190    :type icmp_otime: class:`scapy.layers.inet.ICMPTimeStampField`
191    :keyword icmp_rtime: ICMP receive timestamp, defaults to 0
192    :type icmp_rtime: class:`scapy.layers.inet.ICMPTimeStampField`
193    :keyword icmp_ttime: ICMP transmit timestamp, defaults to 0
194    :type icmp_ttime: class:`scapy.layers.inet.ICMPTimeStampField`
195    :keyword icmp_mask: ICMP address mask, defaults to "0.0.0.0"
196    :type icmp_mask: class:`scapy.fields.IPField`
197    :keyword request: Request type - one of `mask` or `timestamp`,
198        defaults to None
199    :type request: str, optional
200    :keyword count: Number of packets to send, defaults to 1
201    :type count: int
202    :keyword dup: Duplicate packets, defaults to `False`
203    :type dup: bool
204    :keyword verbose: Turn on/off verbosity, defaults to `True`
205    :type verbose: bool
206
207    :return: A class:`subprocess.CompletedProcess` with the output from the
208        ping utility
209    :rtype: class:`subprocess.CompletedProcess`
210    """
211    tun = sc.TunTapInterface(iface)
212    subprocess.run(["ifconfig", tun.iface, "up"], check=True)
213    subprocess.run(["ifconfig", tun.iface, src, dst], check=True)
214    ip_opts = generate_ip_options(opts)
215    ip = sc.IP(ihl=ihl, flags=flags, src=dst, dst=src, options=ip_opts)
216    command = [
217        "/sbin/ping",
218        "-c",
219        str(count),
220        "-t",
221        str(count),
222    ]
223    if verbose:
224        command += ["-v"]
225    if request == "mask":
226        command += ["-Mm"]
227    if request == "timestamp":
228        command += ["-Mt"]
229    if special:
230        command += ["-p1"]
231    if opts in [
232        "RR",
233        "RR-same",
234        "RR-trunc",
235        "LSRR",
236        "LSRR-trunc",
237        "SSRR",
238        "SSRR-trunc",
239    ]:
240        command += ["-R"]
241    command += [dst]
242    with subprocess.Popen(
243        args=command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
244    ) as ping:
245        for dummy in range(count):
246            echo = tun.recv()
247            icmp = sc.ICMP(
248                type=icmp_type,
249                code=icmp_code,
250                id=echo[sc.ICMP].id,
251                seq=echo[sc.ICMP].seq,
252                ts_ori=icmp_otime,
253                ts_rx=icmp_rtime,
254                ts_tx=icmp_ttime,
255                gw=icmp_gwaddr,
256                ptr=icmp_pptr,
257                addr_mask=icmp_mask,
258                nexthopmtu=icmp_nextmtu,
259            )
260            pkt = build_response_packet(echo, ip, icmp, oip_ihl, special)
261            tun.send(pkt)
262            if dup is True:
263                tun.send(pkt)
264        stdout, stderr = ping.communicate()
265    return subprocess.CompletedProcess(
266        ping.args, ping.returncode, stdout, stderr
267    )
268
269
270def redact(output):
271    """Redact some elements of ping's output"""
272    pattern_replacements = [
273        ("localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"),
274        ("from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"),
275        ("hlim=[0-9]*", "hlim="),
276        ("ttl=[0-9]*", "ttl="),
277        ("time=[0-9.-]*", "time="),
278        ("cp: .*", "cp: xx xx xx xx xx xx xx xx"),
279        ("dp: .*", "dp: xx xx xx xx xx xx xx xx"),
280        ("\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"),
281        ("[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    def test_ping(self, expected):
728        """Test ping"""
729        ping = subprocess.run(
730            expected["args"].split(),
731            capture_output=True,
732            timeout=15,
733            text=True,
734        )
735        assert ping.returncode == expected["returncode"]
736        assert redact(ping.stdout) == expected["stdout"]
737        assert ping.stderr == expected["stderr"]
738
739    # Each param in ping46_testdata contains a dictionary with the arguments
740    # and the expected outcome (returncode, redacted stdout, and stderr)
741    # common to `ping -4` and `ping -6`
742    ping46_testdata = [
743        pytest.param(
744            {
745                "args": "-Wx localhost",
746                "returncode": os.EX_USAGE,
747                "stdout": "",
748                "stderr": "ping: invalid timing interval: `x'\n",
749            },
750            id="_Wx_localhost",
751        ),
752    ]
753
754    @pytest.mark.parametrize("expected", ping46_testdata)
755    @pytest.mark.require_user("root")
756    def test_ping_46(self, expected):
757        """Test ping -4/ping -6"""
758        for version in [4, 6]:
759            ping = subprocess.run(
760                ["ping", f"-{version}"] + expected["args"].split(),
761                capture_output=True,
762                timeout=15,
763                text=True,
764            )
765            assert ping.returncode == expected["returncode"]
766            assert redact(ping.stdout) == expected["stdout"]
767            assert ping.stderr == expected["stderr"]
768
769    # Each param in pinger_testdata contains a dictionary with the keywords to
770    # `pinger()` and a dictionary with the expected outcome (returncode,
771    # stdout, stderr, and if ping's output is redacted)
772    pinger_testdata = [
773        pytest.param(
774            {
775                "src": "192.0.2.1",
776                "dst": "192.0.2.2",
777                "icmp_type": 0,
778                "icmp_code": 0,
779            },
780            {
781                "returncode": 0,
782                "stdout": """\
783PING 192.0.2.2 (192.0.2.2): 56 data bytes
78464 bytes from: icmp_seq=0 ttl= time= ms
785
786--- 192.0.2.2 ping statistics ---
7871 packets transmitted, 1 packets received, 0.0% packet loss
788round-trip min/avg/max/stddev = /// ms
789""",
790                "stderr": "",
791                "redacted": True,
792            },
793            id="_0_0",
794        ),
795        pytest.param(
796            {
797                "src": "192.0.2.1",
798                "dst": "192.0.2.2",
799                "icmp_type": 0,
800                "icmp_code": 0,
801                "opts": "EOL",
802            },
803            {
804                "returncode": 0,
805                "stdout": """\
806PING 192.0.2.2 (192.0.2.2): 56 data bytes
80764 bytes from: icmp_seq=0 ttl= time= ms
808wrong total length 88 instead of 84
809
810--- 192.0.2.2 ping statistics ---
8111 packets transmitted, 1 packets received, 0.0% packet loss
812round-trip min/avg/max/stddev = /// ms
813""",
814                "stderr": "",
815                "redacted": True,
816            },
817            id="_0_0_opts_EOL",
818        ),
819        pytest.param(
820            {
821                "src": "192.0.2.1",
822                "dst": "192.0.2.2",
823                "icmp_type": 0,
824                "icmp_code": 0,
825                "opts": "LSRR",
826            },
827            {
828                "returncode": 0,
829                "stdout": """\
830PING 192.0.2.2 (192.0.2.2): 56 data bytes
83164 bytes from: icmp_seq=0 ttl= time= ms
832LSRR: 	192.0.2.10
833	192.0.2.20
834	192.0.2.30
835	192.0.2.40
836	192.0.2.50
837	192.0.2.60
838	192.0.2.70
839	192.0.2.80
840	192.0.2.90
841
842--- 192.0.2.2 ping statistics ---
8431 packets transmitted, 1 packets received, 0.0% packet loss
844round-trip min/avg/max/stddev = /// ms
845""",
846                "stderr": "",
847                "redacted": True,
848            },
849            id="_0_0_opts_LSRR",
850        ),
851        pytest.param(
852            {
853                "src": "192.0.2.1",
854                "dst": "192.0.2.2",
855                "icmp_type": 0,
856                "icmp_code": 0,
857                "opts": "LSRR-trunc",
858            },
859            {
860                "returncode": 0,
861                "stdout": """\
862PING 192.0.2.2 (192.0.2.2): 56 data bytes
86364 bytes from: icmp_seq=0 ttl= time= ms
864LSRR: 	(truncated route)
865
866--- 192.0.2.2 ping statistics ---
8671 packets transmitted, 1 packets received, 0.0% packet loss
868round-trip min/avg/max/stddev = /// ms
869""",
870                "stderr": "",
871                "redacted": True,
872            },
873            id="_0_0_opts_LSRR_trunc",
874        ),
875        pytest.param(
876            {
877                "src": "192.0.2.1",
878                "dst": "192.0.2.2",
879                "icmp_type": 0,
880                "icmp_code": 0,
881                "opts": "SSRR",
882            },
883            {
884                "returncode": 0,
885                "stdout": """\
886PING 192.0.2.2 (192.0.2.2): 56 data bytes
88764 bytes from: icmp_seq=0 ttl= time= ms
888SSRR: 	192.0.2.10
889	192.0.2.20
890	192.0.2.30
891	192.0.2.40
892	192.0.2.50
893	192.0.2.60
894	192.0.2.70
895	192.0.2.80
896	192.0.2.90
897
898--- 192.0.2.2 ping statistics ---
8991 packets transmitted, 1 packets received, 0.0% packet loss
900round-trip min/avg/max/stddev = /// ms
901""",
902                "stderr": "",
903                "redacted": True,
904            },
905            id="_0_0_opts_SSRR",
906        ),
907        pytest.param(
908            {
909                "src": "192.0.2.1",
910                "dst": "192.0.2.2",
911                "icmp_type": 0,
912                "icmp_code": 0,
913                "opts": "SSRR-trunc",
914            },
915            {
916                "returncode": 0,
917                "stdout": """\
918PING 192.0.2.2 (192.0.2.2): 56 data bytes
91964 bytes from: icmp_seq=0 ttl= time= ms
920SSRR: 	(truncated route)
921
922--- 192.0.2.2 ping statistics ---
9231 packets transmitted, 1 packets received, 0.0% packet loss
924round-trip min/avg/max/stddev = /// ms
925""",
926                "stderr": "",
927                "redacted": True,
928            },
929            id="_0_0_opts_SSRR_trunc",
930        ),
931        pytest.param(
932            {
933                "src": "192.0.2.1",
934                "dst": "192.0.2.2",
935                "icmp_type": 0,
936                "icmp_code": 0,
937                "opts": "RR",
938            },
939            {
940                "returncode": 0,
941                "stdout": """\
942PING 192.0.2.2 (192.0.2.2): 56 data bytes
94364 bytes from: icmp_seq=0 ttl= time= ms
944RR: 	192.0.2.10
945	192.0.2.20
946	192.0.2.30
947	192.0.2.40
948	192.0.2.50
949	192.0.2.60
950	192.0.2.70
951	192.0.2.80
952	192.0.2.90
953
954--- 192.0.2.2 ping statistics ---
9551 packets transmitted, 1 packets received, 0.0% packet loss
956round-trip min/avg/max/stddev = /// ms
957""",
958                "stderr": "",
959                "redacted": True,
960            },
961            id="_0_0_opts_RR",
962        ),
963        pytest.param(
964            {
965                "src": "192.0.2.1",
966                "dst": "192.0.2.2",
967                "icmp_type": 0,
968                "icmp_code": 0,
969                "opts": "RR-same",
970            },
971            {
972                "returncode": 0,
973                "stdout": """\
974PING 192.0.2.2 (192.0.2.2): 56 data bytes
97564 bytes from: icmp_seq=0 ttl= time= ms	(same route)
976
977--- 192.0.2.2 ping statistics ---
9781 packets transmitted, 1 packets received, 0.0% packet loss
979round-trip min/avg/max/stddev = /// ms
980""",
981                "stderr": "",
982                "redacted": True,
983            },
984            id="_0_0_opts_RR_same",
985        ),
986        pytest.param(
987            {
988                "src": "192.0.2.1",
989                "dst": "192.0.2.2",
990                "icmp_type": 0,
991                "icmp_code": 0,
992                "opts": "RR-trunc",
993            },
994            {
995                "returncode": 0,
996                "stdout": """\
997PING 192.0.2.2 (192.0.2.2): 56 data bytes
99864 bytes from: icmp_seq=0 ttl= time= ms
999RR: 	(truncated route)
1000
1001--- 192.0.2.2 ping statistics ---
10021 packets transmitted, 1 packets received, 0.0% packet loss
1003round-trip min/avg/max/stddev = /// ms
1004""",
1005                "stderr": "",
1006                "redacted": True,
1007            },
1008            id="_0_0_opts_RR_trunc",
1009        ),
1010        pytest.param(
1011            {
1012                "src": "192.0.2.1",
1013                "dst": "192.0.2.2",
1014                "icmp_type": 0,
1015                "icmp_code": 0,
1016                "opts": "NOP",
1017            },
1018            {
1019                "returncode": 0,
1020                "stdout": """\
1021PING 192.0.2.2 (192.0.2.2): 56 data bytes
102264 bytes from: icmp_seq=0 ttl= time= ms
1023wrong total length 88 instead of 84
1024NOP
1025
1026--- 192.0.2.2 ping statistics ---
10271 packets transmitted, 1 packets received, 0.0% packet loss
1028round-trip min/avg/max/stddev = /// ms
1029""",
1030                "stderr": "",
1031                "redacted": True,
1032            },
1033            id="_0_0_opts_NOP",
1034        ),
1035        pytest.param(
1036            {
1037                "src": "192.0.2.1",
1038                "dst": "192.0.2.2",
1039                "icmp_type": 3,
1040                "icmp_code": 1,
1041                "ihl": 0x4,
1042            },
1043            {
1044                "returncode": 2,
1045                "stdout": """\
1046PING 192.0.2.2 (192.0.2.2): 56 data bytes
1047
1048--- 192.0.2.2 ping statistics ---
10491 packets transmitted, 0 packets received, 100.0% packet loss
1050""",
1051                "stderr": "",  # "IHL too short" message not shown
1052                "redacted": False,
1053            },
1054            id="_IHL_too_short",
1055        ),
1056        pytest.param(
1057            {
1058                "src": "192.0.2.1",
1059                "dst": "192.0.2.2",
1060                "icmp_type": 3,
1061                "icmp_code": 1,
1062                "special": "no-payload",
1063            },
1064            {
1065                "returncode": 2,
1066                "stdout": """\
1067PATTERN: 0x01
1068PING 192.0.2.2 (192.0.2.2): 56 data bytes
1069
1070--- 192.0.2.2 ping statistics ---
10711 packets transmitted, 0 packets received, 100.0% packet loss
1072""",
1073                "stderr": """\
1074ping: quoted data too short (28 bytes) from 192.0.2.2
1075""",
1076                "redacted": False,
1077            },
1078            id="_quoted_data_too_short",
1079        ),
1080        pytest.param(
1081            {
1082                "src": "192.0.2.1",
1083                "dst": "192.0.2.2",
1084                "icmp_type": 3,
1085                "icmp_code": 1,
1086                "oip_ihl": 0x4,
1087            },
1088            {
1089                "returncode": 2,
1090                "stdout": """\
1091PING 192.0.2.2 (192.0.2.2): 56 data bytes
1092
1093--- 192.0.2.2 ping statistics ---
10941 packets transmitted, 0 packets received, 100.0% packet loss
1095""",
1096                "stderr": "",  # "inner IHL too short" message not shown
1097                "redacted": False,
1098            },
1099            id="_inner_IHL_too_short",
1100        ),
1101        pytest.param(
1102            {
1103                "src": "192.0.2.1",
1104                "dst": "192.0.2.2",
1105                "icmp_type": 3,
1106                "icmp_code": 1,
1107                "oip_ihl": 0xF,
1108            },
1109            {
1110                "returncode": 2,
1111                "stdout": """\
1112PING 192.0.2.2 (192.0.2.2): 56 data bytes
1113
1114--- 192.0.2.2 ping statistics ---
11151 packets transmitted, 0 packets received, 100.0% packet loss
1116""",
1117                "stderr": """\
1118ping: inner packet too short (84 bytes) from 192.0.2.2
1119""",
1120                "redacted": False,
1121            },
1122            id="_inner_packet_too_short",
1123        ),
1124        pytest.param(
1125            {
1126                "src": "192.0.2.1",
1127                "dst": "192.0.2.2",
1128                "icmp_type": 3,
1129                "icmp_code": 1,
1130                "oip_ihl": 0xF,
1131                "special": "no-payload",
1132            },
1133            {
1134                "returncode": 2,
1135                "stdout": """\
1136PATTERN: 0x01
1137PING 192.0.2.2 (192.0.2.2): 56 data bytes
1138
1139--- 192.0.2.2 ping statistics ---
11401 packets transmitted, 0 packets received, 100.0% packet loss
1141""",
1142                "stderr": "",
1143                "redacted": False,
1144            },
1145            id="_max_inner_packet_ihl_without_payload",
1146        ),
1147        pytest.param(
1148            {
1149                "src": "192.0.2.1",
1150                "dst": "192.0.2.2",
1151                "icmp_type": 0,
1152                "icmp_code": 0,
1153                "opts": "NOP-40",
1154            },
1155            {
1156                "returncode": 0,
1157                "stdout": """\
1158PING 192.0.2.2 (192.0.2.2): 56 data bytes
115964 bytes from: icmp_seq=0 ttl= time= ms
1160wrong total length 124 instead of 84
1161NOP
1162NOP
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
1201
1202--- 192.0.2.2 ping statistics ---
12031 packets transmitted, 1 packets received, 0.0% packet loss
1204round-trip min/avg/max/stddev = /// ms
1205""",
1206                "stderr": "",
1207                "redacted": True,
1208            },
1209            id="_0_0_opts_NOP_40",
1210        ),
1211        pytest.param(
1212            {
1213                "src": "192.0.2.1",
1214                "dst": "192.0.2.2",
1215                "icmp_type": 0,
1216                "icmp_code": 0,
1217                "opts": "unk",
1218            },
1219            {
1220                "returncode": 0,
1221                "stdout": """\
1222PING 192.0.2.2 (192.0.2.2): 56 data bytes
122364 bytes from: icmp_seq=0 ttl= time= ms
1224wrong total length 88 instead of 84
1225unknown option 9f
1226
1227--- 192.0.2.2 ping statistics ---
12281 packets transmitted, 1 packets received, 0.0% packet loss
1229round-trip min/avg/max/stddev = /// ms
1230""",
1231                "stderr": "",
1232                "redacted": True,
1233            },
1234            id="_0_0_opts_unk",
1235        ),
1236        pytest.param(
1237            {
1238                "src": "192.0.2.1",
1239                "dst": "192.0.2.2",
1240                "icmp_type": 3,
1241                "icmp_code": 1,
1242                "opts": "NOP-40",
1243            },
1244            {
1245                "returncode": 2,
1246                "stdout": """\
1247PING 192.0.2.2 (192.0.2.2): 56 data bytes
1248132 bytes from 192.0.2.2: Destination Host Unreachable
1249Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst Opts
1250 4  f  00 007c 0001   0 0000  40  01 d868 192.0.2.1 192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101
1251
1252
1253--- 192.0.2.2 ping statistics ---
12541 packets transmitted, 0 packets received, 100.0% packet loss
1255""",
1256                "stderr": "",
1257                "redacted": False,
1258            },
1259            id="_3_1_opts_NOP_40",
1260        ),
1261        pytest.param(
1262            {
1263                "src": "192.0.2.1",
1264                "dst": "192.0.2.2",
1265                "icmp_type": 3,
1266                "icmp_code": 1,
1267                "flags": "DF",
1268            },
1269            {
1270                "returncode": 2,
1271                "stdout": """\
1272PING 192.0.2.2 (192.0.2.2): 56 data bytes
127392 bytes from 192.0.2.2: Destination Host Unreachable
1274Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1275 4  5  00 0054 0001   2 0000  40  01 b6a4 192.0.2.1 192.0.2.2
1276
1277
1278--- 192.0.2.2 ping statistics ---
12791 packets transmitted, 0 packets received, 100.0% packet loss
1280""",
1281                "stderr": "",
1282                "redacted": False,
1283            },
1284            id="_3_1_flags_DF",
1285        ),
1286        pytest.param(
1287            {
1288                "src": "192.0.2.1",
1289                "dst": "192.0.2.2",
1290                "icmp_type": 3,
1291                "icmp_code": 1,
1292                "special": "tcp",
1293            },
1294            {
1295                "returncode": 2,
1296                "stdout": """\
1297PATTERN: 0x01
1298PING 192.0.2.2 (192.0.2.2): 56 data bytes
1299
1300--- 192.0.2.2 ping statistics ---
13011 packets transmitted, 0 packets received, 100.0% packet loss
1302""",
1303                "stderr": """\
1304ping: quoted data too short (40 bytes) from 192.0.2.2
1305""",
1306                "redacted": False,
1307            },
1308            id="_3_1_special_tcp",
1309        ),
1310        pytest.param(
1311            {
1312                "src": "192.0.2.1",
1313                "dst": "192.0.2.2",
1314                "icmp_type": 3,
1315                "icmp_code": 1,
1316                "special": "udp",
1317            },
1318            {
1319                "returncode": 2,
1320                "stdout": """\
1321PATTERN: 0x01
1322PING 192.0.2.2 (192.0.2.2): 56 data bytes
1323
1324--- 192.0.2.2 ping statistics ---
13251 packets transmitted, 0 packets received, 100.0% packet loss
1326""",
1327                "stderr": """\
1328ping: quoted data too short (28 bytes) from 192.0.2.2
1329""",
1330                "redacted": False,
1331            },
1332            id="_3_1_special_udp",
1333        ),
1334        pytest.param(
1335            {
1336                "src": "192.0.2.1",
1337                "dst": "192.0.2.2",
1338                "icmp_type": 3,
1339                "icmp_code": 1,
1340                "verbose": False,
1341            },
1342            {
1343                "returncode": 2,
1344                "stdout": """\
1345PING 192.0.2.2 (192.0.2.2): 56 data bytes
134692 bytes from 192.0.2.2: Destination Host Unreachable
1347Vr HL TOS  Len   ID Flg  off TTL Pro  cks       Src       Dst
1348 4  5  00 0054 0001   0 0000  40  01 f6a4 192.0.2.1 192.0.2.2
1349
1350
1351--- 192.0.2.2 ping statistics ---
13521 packets transmitted, 0 packets received, 100.0% packet loss
1353""",
1354                "stderr": "",
1355                "redacted": False,
1356            },
1357            id="_3_1_verbose_false",
1358        ),
1359        pytest.param(
1360            {
1361                "src": "192.0.2.1",
1362                "dst": "192.0.2.2",
1363                "icmp_type": 3,
1364                "icmp_code": 1,
1365                "special": "not-mine",
1366                "verbose": False,
1367            },
1368            {
1369                "returncode": 2,
1370                "stdout": """\
1371PATTERN: 0x01
1372PING 192.0.2.2 (192.0.2.2): 56 data bytes
1373
1374--- 192.0.2.2 ping statistics ---
13751 packets transmitted, 0 packets received, 100.0% packet loss
1376""",
1377                "stderr": "",
1378                "redacted": False,
1379            },
1380            id="_3_1_special_not_mine_verbose_false",
1381        ),
1382        pytest.param(
1383            {
1384                "src": "192.0.2.1",
1385                "dst": "192.0.2.2",
1386                "icmp_type": 0,
1387                "icmp_code": 0,
1388                "special": "warp",
1389            },
1390            {
1391                "returncode": 0,
1392                "stdout": """\
1393PATTERN: 0x01
1394PING 192.0.2.2 (192.0.2.2): 56 data bytes
139564 bytes from: icmp_seq=0 ttl= time= ms
1396
1397--- 192.0.2.2 ping statistics ---
13981 packets transmitted, 1 packets received, 0.0% packet loss
1399round-trip min/avg/max/stddev = /// ms
1400""",
1401                "stderr": """\
1402ping: time of day goes back (- ms), clamping time to 0
1403""",
1404                "redacted": True,
1405            },
1406            id="_0_0_special_warp",
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": "wrong",
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
1422wrong data byte #55 should be 0x1 but was 0x0
1423cp: xx xx xx xx xx xx xx xx
1424	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1425	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1426	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  0
1427dp: xx xx xx xx xx xx xx xx
1428	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1429	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1430	  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
1431
1432--- 192.0.2.2 ping statistics ---
14331 packets transmitted, 1 packets received, 0.0% packet loss
1434round-trip min/avg/max/stddev = /// ms
1435""",
1436                "stderr": "",
1437                "redacted": True,
1438            },
1439            id="_0_0_special_wrong",
1440        ),
1441    ]
1442
1443    @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata)
1444    @pytest.mark.require_progs(["scapy"])
1445    @pytest.mark.require_user("root")
1446    def test_pinger(self, pinger_kargs, expected):
1447        """Test ping using pinger(), a reply faker"""
1448        iface = IfaceFactory().create_iface("", "tun")[0].name
1449        ping = pinger(iface, **pinger_kargs)
1450        assert ping.returncode == expected["returncode"]
1451        if expected["redacted"]:
1452            assert redact(ping.stdout) == expected["stdout"]
1453            assert redact(ping.stderr) == expected["stderr"]
1454        else:
1455            assert ping.stdout == expected["stdout"]
1456            assert ping.stderr == expected["stderr"]
1457