]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/ping/tests/test_ping.py
sqlite3: Vendor import of sqlite3 3.42.0
[FreeBSD/FreeBSD.git] / sbin / ping / tests / test_ping.py
1 import pytest
2
3 import logging
4 import os
5 import re
6 import subprocess
7
8 from atf_python.sys.net.vnet import IfaceFactory
9 from atf_python.sys.net.vnet import SingleVnetTestTemplate
10 from atf_python.sys.net.tools import ToolsHelper
11 from typing import List
12 from typing import Optional
13
14 logging.getLogger("scapy").setLevel(logging.CRITICAL)
15 import scapy.all as sc
16
17
18 def build_response_packet(echo, ip, icmp, oip_ihl, special):
19     icmp_id_seq_types = [0, 8, 13, 14, 15, 16, 17, 18, 37, 38]
20     oip = echo[sc.IP]
21     oicmp = echo[sc.ICMP]
22     load = echo[sc.ICMP].payload
23     oip[sc.IP].remove_payload()
24     oicmp[sc.ICMP].remove_payload()
25     oicmp.type = 8
26
27     # As if the original IP packet had these set
28     oip.ihl = None
29     oip.len = None
30     oip.id = 1
31     oip.flags = ip.flags
32     oip.chksum = None
33     oip.options = ip.options
34
35     # Inner packet (oip) options
36     if oip_ihl:
37         oip.ihl = oip_ihl
38
39     # Special options
40     if special == "no-payload":
41         load = ""
42     if special == "tcp":
43         oip.proto = "tcp"
44         tcp = sc.TCP(sport=1234, dport=5678)
45         return ip / icmp / oip / tcp
46     if special == "udp":
47         oip.proto = "udp"
48         udp = sc.UDP(sport=1234, dport=5678)
49         return ip / icmp / oip / udp
50     if special == "warp":
51         # Build a package with a timestamp of INT_MAX
52         # (time-warped package)
53         payload_no_timestamp = sc.bytes_hex(load)[16:]
54         load = (b"\xff" * 8) + sc.hex_bytes(payload_no_timestamp)
55     if special == "wrong":
56         # Build a package with a wrong last byte
57         payload_no_last_byte = sc.bytes_hex(load)[:-2]
58         load = (sc.hex_bytes(payload_no_last_byte)) + b"\x00"
59     if special == "not-mine":
60         # Modify the ICMP Identifier field
61         oicmp.id += 1
62
63     if icmp.type in icmp_id_seq_types:
64         pkt = ip / icmp / load
65     else:
66         ip.options = ""
67         pkt = ip / icmp / oip / oicmp / load
68     return pkt
69
70
71 def 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
125 def 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
270 def redact(output):
271     """Redact some elements of ping's output"""
272     pattern_replacements = [
273         ("localhost \([0-9]{1,3}(\.[0-9]{1,3}){3}\)", "localhost"),
274         ("from [0-9]{1,3}(\.[0-9]{1,3}){3}", "from"),
275         ("hlim=[0-9]*", "hlim="),
276         ("ttl=[0-9]*", "ttl="),
277         ("time=[0-9.-]*", "time="),
278         ("\(-[0-9\.]+[0-9]+ ms\)", "(- ms)"),
279         ("[0-9\.]+/[0-9.]+", "/"),
280     ]
281     for pattern, repl in pattern_replacements:
282         output = re.sub(pattern, repl, output)
283     return output
284
285
286 class TestPing(SingleVnetTestTemplate):
287     IPV6_PREFIXES: List[str] = ["2001:db8::1/64"]
288     IPV4_PREFIXES: List[str] = ["192.0.2.1/24"]
289
290     # Each param in testdata contains a dictionary with the command,
291     # and the expected outcome (returncode, redacted stdout, and stderr)
292     testdata = [
293         pytest.param(
294             {
295                 "args": "ping -4 -c1 -s56 -t1 localhost",
296                 "returncode": 0,
297                 "stdout": """\
298 PING localhost: 56 data bytes
299 64 bytes from: icmp_seq=0 ttl= time= ms
300
301 --- localhost ping statistics ---
302 1 packets transmitted, 1 packets received, 0.0% packet loss
303 round-trip min/avg/max/stddev = /// ms
304 """,
305                 "stderr": "",
306             },
307             id="_4_c1_s56_t1_localhost",
308         ),
309         pytest.param(
310             {
311                 "args": "ping -6 -c1 -s8 -t1 localhost",
312                 "returncode": 0,
313                 "stdout": """\
314 PING6(56=40+8+8 bytes) ::1 --> ::1
315 16 bytes from ::1, icmp_seq=0 hlim= time= ms
316
317 --- localhost ping6 statistics ---
318 1 packets transmitted, 1 packets received, 0.0% packet loss
319 round-trip min/avg/max/std-dev = /// ms
320 """,
321                 "stderr": "",
322             },
323             id="_6_c1_s8_t1_localhost",
324         ),
325         pytest.param(
326             {
327                 "args": "ping -A -c1 192.0.2.1",
328                 "returncode": 0,
329                 "stdout": """\
330 PING 192.0.2.1 (192.0.2.1): 56 data bytes
331 64 bytes from: icmp_seq=0 ttl= time= ms
332
333 --- 192.0.2.1 ping statistics ---
334 1 packets transmitted, 1 packets received, 0.0% packet loss
335 round-trip min/avg/max/stddev = /// ms
336 """,
337                 "stderr": "",
338             },
339             id="_A_c1_192_0_2_1",
340         ),
341         pytest.param(
342             {
343                 "args": "ping -A -c1 192.0.2.2",
344                 "returncode": 2,
345                 "stdout": """\
346 PING 192.0.2.2 (192.0.2.2): 56 data bytes
347
348 --- 192.0.2.2 ping statistics ---
349 1 packets transmitted, 0 packets received, 100.0% packet loss
350 """,
351                 "stderr": "",
352             },
353             id="_A_c1_192_0_2_2",
354         ),
355         pytest.param(
356             {
357                 "args": "ping -A -c1 2001:db8::1",
358                 "returncode": 0,
359                 "stdout": """\
360 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
361 16 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
362
363 --- 2001:db8::1 ping6 statistics ---
364 1 packets transmitted, 1 packets received, 0.0% packet loss
365 round-trip min/avg/max/std-dev = /// ms
366 """,
367                 "stderr": "",
368             },
369             id="_A_c1_2001_db8__1",
370         ),
371         pytest.param(
372             {
373                 "args": "ping -A -c1 2001:db8::2",
374                 "returncode": 2,
375                 "stdout": """\
376 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
377
378 --- 2001:db8::2 ping6 statistics ---
379 1 packets transmitted, 0 packets received, 100.0% packet loss
380 """,
381                 "stderr": "",
382             },
383             id="_A_c1_2001_db8__2",
384         ),
385         pytest.param(
386             {
387                 "args": "ping -A -c3 192.0.2.1",
388                 "returncode": 0,
389                 "stdout": """\
390 PING 192.0.2.1 (192.0.2.1): 56 data bytes
391 64 bytes from: icmp_seq=0 ttl= time= ms
392 64 bytes from: icmp_seq=1 ttl= time= ms
393 64 bytes from: icmp_seq=2 ttl= time= ms
394
395 --- 192.0.2.1 ping statistics ---
396 3 packets transmitted, 3 packets received, 0.0% packet loss
397 round-trip min/avg/max/stddev = /// ms
398 """,
399                 "stderr": "",
400             },
401             id="_A_3_192_0.2.1",
402         ),
403         pytest.param(
404             {
405                 "args": "ping -A -c3 192.0.2.2",
406                 "returncode": 2,
407                 "stdout": """\
408 \x07\x07PING 192.0.2.2 (192.0.2.2): 56 data bytes
409
410 --- 192.0.2.2 ping statistics ---
411 3 packets transmitted, 0 packets received, 100.0% packet loss
412 """,
413                 "stderr": "",
414             },
415             id="_A_c3_192_0_2_2",
416         ),
417         pytest.param(
418             {
419                 "args": "ping -A -c3 2001:db8::1",
420                 "returncode": 0,
421                 "stdout": """\
422 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
423 16 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
424 16 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
425 16 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
426
427 --- 2001:db8::1 ping6 statistics ---
428 3 packets transmitted, 3 packets received, 0.0% packet loss
429 round-trip min/avg/max/std-dev = /// ms
430 """,
431                 "stderr": "",
432             },
433             id="_A_c3_2001_db8__1",
434         ),
435         pytest.param(
436             {
437                 "args": "ping -A -c3 2001:db8::2",
438                 "returncode": 2,
439                 "stdout": """\
440 \x07\x07PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
441
442 --- 2001:db8::2 ping6 statistics ---
443 3 packets transmitted, 0 packets received, 100.0% packet loss
444 """,
445                 "stderr": "",
446             },
447             id="_A_c3_2001_db8__2",
448         ),
449         pytest.param(
450             {
451                 "args": "ping -c1 192.0.2.1",
452                 "returncode": 0,
453                 "stdout": """\
454 PING 192.0.2.1 (192.0.2.1): 56 data bytes
455 64 bytes from: icmp_seq=0 ttl= time= ms
456
457 --- 192.0.2.1 ping statistics ---
458 1 packets transmitted, 1 packets received, 0.0% packet loss
459 round-trip min/avg/max/stddev = /// ms
460 """,
461                 "stderr": "",
462             },
463             id="_c1_192_0_2_1",
464         ),
465         pytest.param(
466             {
467                 "args": "ping -c1 192.0.2.2",
468                 "returncode": 2,
469                 "stdout": """\
470 PING 192.0.2.2 (192.0.2.2): 56 data bytes
471
472 --- 192.0.2.2 ping statistics ---
473 1 packets transmitted, 0 packets received, 100.0% packet loss
474 """,
475                 "stderr": "",
476             },
477             id="_c1_192_0_2_2",
478         ),
479         pytest.param(
480             {
481                 "args": "ping -c1 2001:db8::1",
482                 "returncode": 0,
483                 "stdout": """\
484 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
485 16 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
486
487 --- 2001:db8::1 ping6 statistics ---
488 1 packets transmitted, 1 packets received, 0.0% packet loss
489 round-trip min/avg/max/std-dev = /// ms
490 """,
491                 "stderr": "",
492             },
493             id="_c1_2001_db8__1",
494         ),
495         pytest.param(
496             {
497                 "args": "ping -c1 2001:db8::2",
498                 "returncode": 2,
499                 "stdout": """\
500 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
501
502 --- 2001:db8::2 ping6 statistics ---
503 1 packets transmitted, 0 packets received, 100.0% packet loss
504 """,
505                 "stderr": "",
506             },
507             id="_c1_2001_db8__2",
508         ),
509         pytest.param(
510             {
511                 "args": "ping -c1 -S127.0.0.1 -s56 -t1 localhost",
512                 "returncode": 0,
513                 "stdout": """\
514 PING localhost from: 56 data bytes
515 64 bytes from: icmp_seq=0 ttl= time= ms
516
517 --- localhost ping statistics ---
518 1 packets transmitted, 1 packets received, 0.0% packet loss
519 round-trip min/avg/max/stddev = /// ms
520 """,
521                 "stderr": "",
522             },
523             id="_c1_S127_0_0_1_s56_t1_localhost",
524         ),
525         pytest.param(
526             {
527                 "args": "ping -c1 -S::1 -s8 -t1 localhost",
528                 "returncode": 0,
529                 "stdout": """\
530 PING6(56=40+8+8 bytes) ::1 --> ::1
531 16 bytes from ::1, icmp_seq=0 hlim= time= ms
532
533 --- localhost ping6 statistics ---
534 1 packets transmitted, 1 packets received, 0.0% packet loss
535 round-trip min/avg/max/std-dev = /// ms
536 """,
537                 "stderr": "",
538             },
539             id="_c1_S__1_s8_t1_localhost",
540         ),
541         pytest.param(
542             {
543                 "args": "ping -c3 192.0.2.1",
544                 "returncode": 0,
545                 "stdout": """\
546 PING 192.0.2.1 (192.0.2.1): 56 data bytes
547 64 bytes from: icmp_seq=0 ttl= time= ms
548 64 bytes from: icmp_seq=1 ttl= time= ms
549 64 bytes from: icmp_seq=2 ttl= time= ms
550
551 --- 192.0.2.1 ping statistics ---
552 3 packets transmitted, 3 packets received, 0.0% packet loss
553 round-trip min/avg/max/stddev = /// ms
554 """,
555                 "stderr": "",
556             },
557             id="_c3_192_0_2_1",
558         ),
559         pytest.param(
560             {
561                 "args": "ping -c3 192.0.2.2",
562                 "returncode": 2,
563                 "stdout": """\
564 PING 192.0.2.2 (192.0.2.2): 56 data bytes
565
566 --- 192.0.2.2 ping statistics ---
567 3 packets transmitted, 0 packets received, 100.0% packet loss
568 """,
569                 "stderr": "",
570             },
571             id="_c3_192_0_2_2",
572         ),
573         pytest.param(
574             {
575                 "args": "ping -c3 2001:db8::1",
576                 "returncode": 0,
577                 "stdout": """\
578 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
579 16 bytes from 2001:db8::1, icmp_seq=0 hlim= time= ms
580 16 bytes from 2001:db8::1, icmp_seq=1 hlim= time= ms
581 16 bytes from 2001:db8::1, icmp_seq=2 hlim= time= ms
582
583 --- 2001:db8::1 ping6 statistics ---
584 3 packets transmitted, 3 packets received, 0.0% packet loss
585 round-trip min/avg/max/std-dev = /// ms
586 """,
587                 "stderr": "",
588             },
589             id="_c3_2001_db8__1",
590         ),
591         pytest.param(
592             {
593                 "args": "ping -c3 2001:db8::2",
594                 "returncode": 2,
595                 "stdout": """\
596 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
597
598 --- 2001:db8::2 ping6 statistics ---
599 3 packets transmitted, 0 packets received, 100.0% packet loss
600 """,
601                 "stderr": "",
602             },
603             id="_c3_2001_db8__2",
604         ),
605         pytest.param(
606             {
607                 "args": "ping -q -c1 192.0.2.1",
608                 "returncode": 0,
609                 "stdout": """\
610 PING 192.0.2.1 (192.0.2.1): 56 data bytes
611
612 --- 192.0.2.1 ping statistics ---
613 1 packets transmitted, 1 packets received, 0.0% packet loss
614 round-trip min/avg/max/stddev = /// ms
615 """,
616                 "stderr": "",
617             },
618             id="_q_c1_192_0_2_1",
619         ),
620         pytest.param(
621             {
622                 "args": "ping -q -c1 192.0.2.2",
623                 "returncode": 2,
624                 "stdout": """\
625 PING 192.0.2.2 (192.0.2.2): 56 data bytes
626
627 --- 192.0.2.2 ping statistics ---
628 1 packets transmitted, 0 packets received, 100.0% packet loss
629 """,
630                 "stderr": "",
631             },
632             id="_q_c1_192_0_2_2",
633         ),
634         pytest.param(
635             {
636                 "args": "ping -q -c1 2001:db8::1",
637                 "returncode": 0,
638                 "stdout": """\
639 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
640
641 --- 2001:db8::1 ping6 statistics ---
642 1 packets transmitted, 1 packets received, 0.0% packet loss
643 round-trip min/avg/max/std-dev = /// ms
644 """,
645                 "stderr": "",
646             },
647             id="_q_c1_2001_db8__1",
648         ),
649         pytest.param(
650             {
651                 "args": "ping -q -c1 2001:db8::2",
652                 "returncode": 2,
653                 "stdout": """\
654 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
655
656 --- 2001:db8::2 ping6 statistics ---
657 1 packets transmitted, 0 packets received, 100.0% packet loss
658 """,
659                 "stderr": "",
660             },
661             id="_q_c1_2001_db8__2",
662         ),
663         pytest.param(
664             {
665                 "args": "ping -q -c3 192.0.2.1",
666                 "returncode": 0,
667                 "stdout": """\
668 PING 192.0.2.1 (192.0.2.1): 56 data bytes
669
670 --- 192.0.2.1 ping statistics ---
671 3 packets transmitted, 3 packets received, 0.0% packet loss
672 round-trip min/avg/max/stddev = /// ms
673 """,
674                 "stderr": "",
675             },
676             id="_q_c3_192_0_2_1",
677         ),
678         pytest.param(
679             {
680                 "args": "ping -q -c3 192.0.2.2",
681                 "returncode": 2,
682                 "stdout": """\
683 PING 192.0.2.2 (192.0.2.2): 56 data bytes
684
685 --- 192.0.2.2 ping statistics ---
686 3 packets transmitted, 0 packets received, 100.0% packet loss
687 """,
688                 "stderr": "",
689             },
690             id="_q_c3_192_0_2_2",
691         ),
692         pytest.param(
693             {
694                 "args": "ping -q -c3 2001:db8::1",
695                 "returncode": 0,
696                 "stdout": """\
697 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::1
698
699 --- 2001:db8::1 ping6 statistics ---
700 3 packets transmitted, 3 packets received, 0.0% packet loss
701 round-trip min/avg/max/std-dev = /// ms
702 """,
703                 "stderr": "",
704             },
705             id="_q_c3_2001_db8__1",
706         ),
707         pytest.param(
708             {
709                 "args": "ping -q -c3 2001:db8::2",
710                 "returncode": 2,
711                 "stdout": """\
712 PING6(56=40+8+8 bytes) 2001:db8::1 --> 2001:db8::2
713
714 --- 2001:db8::2 ping6 statistics ---
715 3 packets transmitted, 0 packets received, 100.0% packet loss
716 """,
717                 "stderr": "",
718             },
719             id="_q_c3_2001_db8__2",
720         ),
721     ]
722
723     @pytest.mark.parametrize("expected", testdata)
724     def test_ping(self, expected):
725         """Test ping"""
726         ping = subprocess.run(
727             expected["args"].split(),
728             capture_output=True,
729             timeout=15,
730             text=True,
731         )
732         assert ping.returncode == expected["returncode"]
733         assert redact(ping.stdout) == expected["stdout"]
734         assert ping.stderr == expected["stderr"]
735
736     # Each param in ping46_testdata contains a dictionary with the arguments
737     # and the expected outcome (returncode, redacted stdout, and stderr)
738     # common to `ping -4` and `ping -6`
739     ping46_testdata = [
740         pytest.param(
741             {
742                 "args": "-Wx localhost",
743                 "returncode": os.EX_USAGE,
744                 "stdout": "",
745                 "stderr": "ping: invalid timing interval: `x'\n",
746             },
747             id="_Wx_localhost",
748         ),
749     ]
750
751     @pytest.mark.parametrize("expected", ping46_testdata)
752     def test_ping_46(self, expected):
753         """Test ping -4/ping -6"""
754         for version in [4, 6]:
755             ping = subprocess.run(
756                 ["ping", f"-{version}"] + expected["args"].split(),
757                 capture_output=True,
758                 timeout=15,
759                 text=True,
760             )
761             assert ping.returncode == expected["returncode"]
762             assert redact(ping.stdout) == expected["stdout"]
763             assert ping.stderr == expected["stderr"]
764
765     # Each param in pinger_testdata contains a dictionary with the keywords to
766     # `pinger()` and a dictionary with the expected outcome (returncode,
767     # stdout, stderr, and if ping's output is redacted)
768     pinger_testdata = [
769         pytest.param(
770             {
771                 "src": "192.0.2.1",
772                 "dst": "192.0.2.2",
773                 "icmp_type": 0,
774                 "icmp_code": 0,
775             },
776             {
777                 "returncode": 0,
778                 "stdout": """\
779 PING 192.0.2.2 (192.0.2.2): 56 data bytes
780 64 bytes from: icmp_seq=0 ttl= time= ms
781
782 --- 192.0.2.2 ping statistics ---
783 1 packets transmitted, 1 packets received, 0.0% packet loss
784 round-trip min/avg/max/stddev = /// ms
785 """,
786                 "stderr": "",
787                 "redacted": True,
788             },
789             id="_0_0",
790         ),
791         pytest.param(
792             {
793                 "src": "192.0.2.1",
794                 "dst": "192.0.2.2",
795                 "icmp_type": 0,
796                 "icmp_code": 0,
797                 "opts": "EOL",
798             },
799             {
800                 "returncode": 0,
801                 "stdout": """\
802 PING 192.0.2.2 (192.0.2.2): 56 data bytes
803 64 bytes from: icmp_seq=0 ttl= time= ms
804 wrong total length 88 instead of 84
805
806 --- 192.0.2.2 ping statistics ---
807 1 packets transmitted, 1 packets received, 0.0% packet loss
808 round-trip min/avg/max/stddev = /// ms
809 """,
810                 "stderr": "",
811                 "redacted": True,
812             },
813             id="_0_0_opts_EOL",
814         ),
815         pytest.param(
816             {
817                 "src": "192.0.2.1",
818                 "dst": "192.0.2.2",
819                 "icmp_type": 0,
820                 "icmp_code": 0,
821                 "opts": "LSRR",
822             },
823             {
824                 "returncode": 0,
825                 "stdout": """\
826 PING 192.0.2.2 (192.0.2.2): 56 data bytes
827 64 bytes from: icmp_seq=0 ttl= time= ms
828 LSRR:   192.0.2.10
829         192.0.2.20
830         192.0.2.30
831         192.0.2.40
832         192.0.2.50
833         192.0.2.60
834         192.0.2.70
835         192.0.2.80
836         192.0.2.90
837
838 --- 192.0.2.2 ping statistics ---
839 1 packets transmitted, 1 packets received, 0.0% packet loss
840 round-trip min/avg/max/stddev = /// ms
841 """,
842                 "stderr": "",
843                 "redacted": True,
844             },
845             id="_0_0_opts_LSRR",
846         ),
847         pytest.param(
848             {
849                 "src": "192.0.2.1",
850                 "dst": "192.0.2.2",
851                 "icmp_type": 0,
852                 "icmp_code": 0,
853                 "opts": "LSRR-trunc",
854             },
855             {
856                 "returncode": 0,
857                 "stdout": """\
858 PING 192.0.2.2 (192.0.2.2): 56 data bytes
859 64 bytes from: icmp_seq=0 ttl= time= ms
860 LSRR:   (truncated route)
861
862
863 --- 192.0.2.2 ping statistics ---
864 1 packets transmitted, 1 packets received, 0.0% packet loss
865 round-trip min/avg/max/stddev = /// ms
866 """,
867                 "stderr": "",
868                 "redacted": True,
869             },
870             id="_0_0_opts_LSRR_trunc",
871         ),
872         pytest.param(
873             {
874                 "src": "192.0.2.1",
875                 "dst": "192.0.2.2",
876                 "icmp_type": 0,
877                 "icmp_code": 0,
878                 "opts": "SSRR",
879             },
880             {
881                 "returncode": 0,
882                 "stdout": """\
883 PING 192.0.2.2 (192.0.2.2): 56 data bytes
884 64 bytes from: icmp_seq=0 ttl= time= ms
885 SSRR:   192.0.2.10
886         192.0.2.20
887         192.0.2.30
888         192.0.2.40
889         192.0.2.50
890         192.0.2.60
891         192.0.2.70
892         192.0.2.80
893         192.0.2.90
894
895 --- 192.0.2.2 ping statistics ---
896 1 packets transmitted, 1 packets received, 0.0% packet loss
897 round-trip min/avg/max/stddev = /// ms
898 """,
899                 "stderr": "",
900                 "redacted": True,
901             },
902             id="_0_0_opts_SSRR",
903         ),
904         pytest.param(
905             {
906                 "src": "192.0.2.1",
907                 "dst": "192.0.2.2",
908                 "icmp_type": 0,
909                 "icmp_code": 0,
910                 "opts": "SSRR-trunc",
911             },
912             {
913                 "returncode": 0,
914                 "stdout": """\
915 PING 192.0.2.2 (192.0.2.2): 56 data bytes
916 64 bytes from: icmp_seq=0 ttl= time= ms
917 SSRR:   (truncated route)
918
919
920 --- 192.0.2.2 ping statistics ---
921 1 packets transmitted, 1 packets received, 0.0% packet loss
922 round-trip min/avg/max/stddev = /// ms
923 """,
924                 "stderr": "",
925                 "redacted": True,
926             },
927             id="_0_0_opts_SSRR_trunc",
928         ),
929         pytest.param(
930             {
931                 "src": "192.0.2.1",
932                 "dst": "192.0.2.2",
933                 "icmp_type": 0,
934                 "icmp_code": 0,
935                 "opts": "RR",
936             },
937             {
938                 "returncode": 0,
939                 "stdout": """\
940 PING 192.0.2.2 (192.0.2.2): 56 data bytes
941 64 bytes from: icmp_seq=0 ttl= time= ms
942 RR:     192.0.2.10
943         192.0.2.20
944         192.0.2.30
945         192.0.2.40
946         192.0.2.50
947         192.0.2.60
948         192.0.2.70
949         192.0.2.80
950         192.0.2.90
951
952 --- 192.0.2.2 ping statistics ---
953 1 packets transmitted, 1 packets received, 0.0% packet loss
954 round-trip min/avg/max/stddev = /// ms
955 """,
956                 "stderr": "",
957                 "redacted": True,
958             },
959             id="_0_0_opts_RR",
960         ),
961         pytest.param(
962             {
963                 "src": "192.0.2.1",
964                 "dst": "192.0.2.2",
965                 "icmp_type": 0,
966                 "icmp_code": 0,
967                 "opts": "RR-same",
968             },
969             {
970                 "returncode": 0,
971                 "stdout": """\
972 PING 192.0.2.2 (192.0.2.2): 56 data bytes
973 64 bytes from: icmp_seq=0 ttl= time= ms (same route)
974
975 --- 192.0.2.2 ping statistics ---
976 1 packets transmitted, 1 packets received, 0.0% packet loss
977 round-trip min/avg/max/stddev = /// ms
978 """,
979                 "stderr": "",
980                 "redacted": True,
981             },
982             id="_0_0_opts_RR_same",
983         ),
984         pytest.param(
985             {
986                 "src": "192.0.2.1",
987                 "dst": "192.0.2.2",
988                 "icmp_type": 0,
989                 "icmp_code": 0,
990                 "opts": "RR-trunc",
991             },
992             {
993                 "returncode": 0,
994                 "stdout": """\
995 PING 192.0.2.2 (192.0.2.2): 56 data bytes
996 64 bytes from: icmp_seq=0 ttl= time= ms
997 RR:     (truncated route)
998
999 --- 192.0.2.2 ping statistics ---
1000 1 packets transmitted, 1 packets received, 0.0% packet loss
1001 round-trip min/avg/max/stddev = /// ms
1002 """,
1003                 "stderr": "",
1004                 "redacted": True,
1005             },
1006             id="_0_0_opts_RR_trunc",
1007         ),
1008         pytest.param(
1009             {
1010                 "src": "192.0.2.1",
1011                 "dst": "192.0.2.2",
1012                 "icmp_type": 0,
1013                 "icmp_code": 0,
1014                 "opts": "NOP",
1015             },
1016             {
1017                 "returncode": 0,
1018                 "stdout": """\
1019 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1020 64 bytes from: icmp_seq=0 ttl= time= ms
1021 wrong total length 88 instead of 84
1022 NOP
1023
1024 --- 192.0.2.2 ping statistics ---
1025 1 packets transmitted, 1 packets received, 0.0% packet loss
1026 round-trip min/avg/max/stddev = /// ms
1027 """,
1028                 "stderr": "",
1029                 "redacted": True,
1030             },
1031             id="_0_0_opts_NOP",
1032         ),
1033         pytest.param(
1034             {
1035                 "src": "192.0.2.1",
1036                 "dst": "192.0.2.2",
1037                 "icmp_type": 0,
1038                 "icmp_code": 0,
1039                 "opts": "NOP-40",
1040             },
1041             {
1042                 "returncode": 0,
1043                 "stdout": """\
1044 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1045 64 bytes from: icmp_seq=0 ttl= time= ms
1046 wrong total length 124 instead of 84
1047 NOP
1048 NOP
1049 NOP
1050 NOP
1051 NOP
1052 NOP
1053 NOP
1054 NOP
1055 NOP
1056 NOP
1057 NOP
1058 NOP
1059 NOP
1060 NOP
1061 NOP
1062 NOP
1063 NOP
1064 NOP
1065 NOP
1066 NOP
1067 NOP
1068 NOP
1069 NOP
1070 NOP
1071 NOP
1072 NOP
1073 NOP
1074 NOP
1075 NOP
1076 NOP
1077 NOP
1078 NOP
1079 NOP
1080 NOP
1081 NOP
1082 NOP
1083 NOP
1084 NOP
1085 NOP
1086 NOP
1087
1088 --- 192.0.2.2 ping statistics ---
1089 1 packets transmitted, 1 packets received, 0.0% packet loss
1090 round-trip min/avg/max/stddev = /// ms
1091 """,
1092                 "stderr": "",
1093                 "redacted": True,
1094             },
1095             id="_0_0_opts_NOP_40",
1096         ),
1097         pytest.param(
1098             {
1099                 "src": "192.0.2.1",
1100                 "dst": "192.0.2.2",
1101                 "icmp_type": 0,
1102                 "icmp_code": 0,
1103                 "opts": "unk",
1104             },
1105             {
1106                 "returncode": 0,
1107                 "stdout": """\
1108 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1109 64 bytes from: icmp_seq=0 ttl= time= ms
1110 wrong total length 88 instead of 84
1111 unknown option 9f
1112
1113 --- 192.0.2.2 ping statistics ---
1114 1 packets transmitted, 1 packets received, 0.0% packet loss
1115 round-trip min/avg/max/stddev = /// ms
1116 """,
1117                 "stderr": "",
1118                 "redacted": True,
1119             },
1120             id="_0_0_opts_unk",
1121         ),
1122         pytest.param(
1123             {
1124                 "src": "192.0.2.1",
1125                 "dst": "192.0.2.2",
1126                 "icmp_type": 3,
1127                 "icmp_code": 1,
1128                 "opts": "NOP-40",
1129             },
1130             {
1131                 "returncode": 2,
1132                 "stdout": """\
1133 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1134 132 bytes from 192.0.2.2: Destination Host Unreachable
1135 Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
1136  4  f  00 007c 0001   0 0000  40  01 d868 192.0.2.1  192.0.2.2 01010101010101010101010101010101010101010101010101010101010101010101010101010101
1137
1138
1139 --- 192.0.2.2 ping statistics ---
1140 1 packets transmitted, 0 packets received, 100.0% packet loss
1141 """,
1142                 "stderr": "",
1143                 "redacted": False,
1144             },
1145             id="_3_1_opts_NOP_40",
1146         ),
1147         pytest.param(
1148             {
1149                 "src": "192.0.2.1",
1150                 "dst": "192.0.2.2",
1151                 "icmp_type": 3,
1152                 "icmp_code": 1,
1153                 "flags": "DF",
1154             },
1155             {
1156                 "returncode": 2,
1157                 "stdout": """\
1158 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1159 92 bytes from 192.0.2.2: Destination Host Unreachable
1160 Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
1161  4  5  00 0054 0001   2 0000  40  01 b6a4 192.0.2.1  192.0.2.2 
1162
1163
1164 --- 192.0.2.2 ping statistics ---
1165 1 packets transmitted, 0 packets received, 100.0% packet loss
1166 """,
1167                 "stderr": "",
1168                 "redacted": False,
1169             },
1170             id="_3_1_flags_DF",
1171         ),
1172         pytest.param(
1173             {
1174                 "src": "192.0.2.1",
1175                 "dst": "192.0.2.2",
1176                 "icmp_type": 3,
1177                 "icmp_code": 1,
1178                 "special": "tcp",
1179             },
1180             {
1181                 "returncode": 2,
1182                 "stdout": """\
1183 PATTERN: 0x01
1184 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1185
1186 --- 192.0.2.2 ping statistics ---
1187 1 packets transmitted, 0 packets received, 100.0% packet loss
1188 """,
1189                 "stderr": """\
1190 ping: quoted data too short (40 bytes) from 192.0.2.2
1191 """,
1192                 "redacted": False,
1193             },
1194             id="_3_1_special_tcp",
1195         ),
1196         pytest.param(
1197             {
1198                 "src": "192.0.2.1",
1199                 "dst": "192.0.2.2",
1200                 "icmp_type": 3,
1201                 "icmp_code": 1,
1202                 "special": "udp",
1203             },
1204             {
1205                 "returncode": 2,
1206                 "stdout": """\
1207 PATTERN: 0x01
1208 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1209
1210 --- 192.0.2.2 ping statistics ---
1211 1 packets transmitted, 0 packets received, 100.0% packet loss
1212 """,
1213                 "stderr": """\
1214 ping: quoted data too short (28 bytes) from 192.0.2.2
1215 """,
1216                 "redacted": False,
1217             },
1218             id="_3_1_special_udp",
1219         ),
1220         pytest.param(
1221             {
1222                 "src": "192.0.2.1",
1223                 "dst": "192.0.2.2",
1224                 "icmp_type": 3,
1225                 "icmp_code": 1,
1226                 "verbose": False,
1227             },
1228             {
1229                 "returncode": 2,
1230                 "stdout": """\
1231 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1232 92 bytes from 192.0.2.2: Destination Host Unreachable
1233 Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
1234  4  5  00 0054 0001   0 0000  40  01 f6a4 192.0.2.1  192.0.2.2 
1235
1236
1237 --- 192.0.2.2 ping statistics ---
1238 1 packets transmitted, 0 packets received, 100.0% packet loss
1239 """,
1240                 "stderr": "",
1241                 "redacted": False,
1242             },
1243             id="_3_1_verbose_false",
1244         ),
1245         pytest.param(
1246             {
1247                 "src": "192.0.2.1",
1248                 "dst": "192.0.2.2",
1249                 "icmp_type": 3,
1250                 "icmp_code": 1,
1251                 "special": "not-mine",
1252                 "verbose": False,
1253             },
1254             {
1255                 "returncode": 2,
1256                 "stdout": """\
1257 PATTERN: 0x01
1258 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1259
1260 --- 192.0.2.2 ping statistics ---
1261 1 packets transmitted, 0 packets received, 100.0% packet loss
1262 """,
1263                 "stderr": "",
1264                 "redacted": False,
1265             },
1266             id="_3_1_special_not_mine_verbose_false",
1267         ),
1268         pytest.param(
1269             {
1270                 "src": "192.0.2.1",
1271                 "dst": "192.0.2.2",
1272                 "icmp_type": 0,
1273                 "icmp_code": 0,
1274                 "special": "warp",
1275             },
1276             {
1277                 "returncode": 0,
1278                 "stdout": """\
1279 PATTERN: 0x01
1280 PING 192.0.2.2 (192.0.2.2): 56 data bytes
1281 64 bytes from: icmp_seq=0 ttl= time= ms
1282
1283 --- 192.0.2.2 ping statistics ---
1284 1 packets transmitted, 1 packets received, 0.0% packet loss
1285 round-trip min/avg/max/stddev = /// ms
1286 """,
1287                 "stderr": """\
1288 ping: time of day goes back (- ms), clamping time to 0
1289 """,
1290                 "redacted": True,
1291             },
1292             id="_0_0_special_warp",
1293         ),
1294     ]
1295
1296     @pytest.mark.parametrize("pinger_kargs, expected", pinger_testdata)
1297     @pytest.mark.require_progs(["scapy"])
1298     @pytest.mark.require_user("root")
1299     def test_pinger(self, pinger_kargs, expected):
1300         """Test ping using pinger(), a reply faker"""
1301         iface = IfaceFactory().create_iface("", "tun")[0].name
1302         ping = pinger(iface, **pinger_kargs)
1303         assert ping.returncode == expected["returncode"]
1304         if expected["redacted"]:
1305             assert redact(ping.stdout) == expected["stdout"]
1306             assert redact(ping.stderr) == expected["stderr"]
1307         else:
1308             assert ping.stdout == expected["stdout"]
1309             assert ping.stderr == expected["stderr"]