3 # By Gerald Combs <gerald@wireshark.org>
5 # Ported from a set of Bash scripts which were copyright 2005 Ulf Lamping
7 # SPDX-License-Identifier: GPL-2.0-or-later
9 '''Command line option tests'''
16 from subprocesstest
import ExitCodes
, grep_output
, count_output
20 #glossaries = ('fields', 'protocols', 'values', 'decodes', 'defaultprefs', 'currentprefs')
22 glossaries
= ('decodes', 'values')
23 testout_pcap
= 'testout.pcap'
26 class TestDumpcapOptions
:
27 # XXX Should we generate individual test functions instead of looping?
28 def test_dumpcap_invalid_chars(self
, cmd_dumpcap
, base_env
):
29 '''Invalid dumpcap parameters'''
30 for char_arg
in 'CEFGHJKNORTUVWXYejloxz':
31 process
= subprocesstest
.run((cmd_dumpcap
, '-' + char_arg
), env
=base_env
)
32 assert process
.returncode
== ExitCodes
.COMMAND_LINE
34 # XXX Should we generate individual test functions instead of looping?
35 def test_dumpcap_valid_chars(self
, cmd_dumpcap
, base_env
):
37 process
= subprocesstest
.run((cmd_dumpcap
, '-' + char_arg
), env
=base_env
)
38 assert process
.returncode
== ExitCodes
.OK
40 # XXX Should we generate individual test functions instead of looping?
41 def test_dumpcap_interface_chars(self
, cmd_dumpcap
, base_env
):
42 '''Valid dumpcap parameters requiring capture permissions'''
43 valid_returns
= [ExitCodes
.OK
, ExitCodes
.INVALID_INTERFACE
]
45 process
= subprocesstest
.run((cmd_dumpcap
, '-' + char_arg
), env
=base_env
)
46 assert process
.returncode
in valid_returns
49 class TestDumpcapClopts
:
50 def test_dumpcap_invalid_capfilter(self
, cmd_dumpcap
, capture_interface
, result_file
, base_env
):
51 '''Invalid capture filter'''
52 invalid_filter
= '__invalid_protocol'
53 # $DUMPCAP -f 'jkghg' -w './testout.pcap' > ./testout.txt 2>&1
54 testout_file
= result_file(testout_pcap
)
55 process
= subprocesstest
.run((cmd_dumpcap
, '-f', invalid_filter
, '-w', testout_file
), capture_output
=True, env
=base_env
)
56 assert grep_output(process
.stderr
, 'Invalid capture filter "' + invalid_filter
+ '" for interface')
58 def test_dumpcap_invalid_interface_name(self
, cmd_dumpcap
, capture_interface
, result_file
, base_env
):
59 '''Invalid capture interface name'''
60 invalid_interface
= '__invalid_interface'
61 # $DUMPCAP -i invalid_interface -w './testout.pcap' > ./testout.txt 2>&1
62 testout_file
= result_file(testout_pcap
)
63 process
= subprocesstest
.run((cmd_dumpcap
, '-i', invalid_interface
, '-w', testout_file
), capture_output
=True, env
=base_env
)
64 assert grep_output(process
.stderr
, 'There is no device named "__invalid_interface"') or \
65 grep_output(process
.stderr
, 'The capture session could not be initiated on capture device "__invalid_interface"')
67 def test_dumpcap_invalid_interface_index(self
, cmd_dumpcap
, capture_interface
, result_file
, base_env
):
68 '''Invalid capture interface index'''
70 # $DUMPCAP -i 0 -w './testout.pcap' > ./testout.txt 2>&1
71 testout_file
= result_file(testout_pcap
)
72 process
= subprocesstest
.run((cmd_dumpcap
, '-i', invalid_index
, '-w', testout_file
), capture_output
=True, env
=base_env
)
73 assert grep_output(process
.stderr
, 'There is no interface with that adapter index')
76 class TestBasicClopts
:
77 def test_existing_file(self
, cmd_tshark
, capture_file
, test_env
):
78 # $TSHARK -r "${CAPTURE_DIR}dhcp.pcap" > ./testout.txt 2>&1
79 process
= subprocesstest
.run((cmd_tshark
, '-r', capture_file('dhcp.pcap')), env
=test_env
)
80 assert process
.returncode
== ExitCodes
.OK
82 def test_existing_file_longopt(self
, cmd_tshark
, capture_file
, test_env
):
83 # $TSHARK -r "${CAPTURE_DIR}dhcp.pcap" > ./testout.txt 2>&1
84 process
= subprocesstest
.run((cmd_tshark
, '--read-file', capture_file('dhcp.pcap'),
85 '--display-filter', 'dhcp'), env
=test_env
)
86 assert process
.returncode
== ExitCodes
.OK
88 def test_nonexistent_file(self
, cmd_tshark
, capture_file
, test_env
):
89 # $TSHARK - r ThisFileDontExist.pcap > ./testout.txt 2 > &1
90 process
= subprocesstest
.run((cmd_tshark
, '-r', capture_file('__ceci_nest_pas_une.pcap')), env
=test_env
)
91 assert process
.returncode
== ExitCodes
.INVALID_FILE_ERROR
94 class TestTsharkOptions
:
95 # XXX Should we generate individual test functions instead of looping?
96 def test_tshark_invalid_chars(self
, cmd_tshark
, test_env
):
97 '''Invalid tshark parameters'''
98 # Most of these are valid but require a mandatory parameter
99 for char_arg
in 'ABCEFGHJKMNORTUWXYZabcdefijkmorstuwyz':
100 process
= subprocesstest
.run((cmd_tshark
, '-' + char_arg
), env
=test_env
)
101 assert process
.returncode
== ExitCodes
.COMMAND_LINE
103 # XXX Should we generate individual test functions instead of looping?
104 def test_tshark_valid_chars(self
, cmd_tshark
, test_env
):
105 for char_arg
in 'hv':
106 process
= subprocesstest
.run((cmd_tshark
, '-' + char_arg
), env
=test_env
)
107 assert process
.returncode
== ExitCodes
.OK
109 # XXX Should we generate individual test functions instead of looping?
110 def test_tshark_interface_chars(self
, cmd_tshark
, cmd_dumpcap
, test_env
):
111 '''Valid tshark parameters requiring capture permissions'''
112 # These options require dumpcap, but may fail with a pacp error
113 # if WinPcap or Npcap are not present
114 valid_returns
= [ExitCodes
.OK
, ExitCodes
.PCAP_ERROR
, ExitCodes
.INVALID_CAPABILITY
, ExitCodes
.INVALID_INTERFACE
]
115 for char_arg
in 'DL':
116 process
= subprocesstest
.run((cmd_tshark
, '-' + char_arg
), env
=test_env
)
117 assert process
.returncode
in valid_returns
119 def test_tshark_disable_protos(self
, cmd_tshark
, capture_file
, test_env
):
120 '''--disable-protocol/--enable-protocol from !16923'''
121 process
= subprocesstest
.run((cmd_tshark
, "-r", capture_file("http.pcap"),
122 "--disable-protocol", "ALL",
123 "--enable-protocol", "eth,ip",
124 "-Tjson", "-eeth.type", "-eip.proto", "-ehttp.host",
125 ), capture_output
=True, env
=test_env
)
126 assert process
.returncode
== ExitCodes
.OK
127 obj
= json
.loads(process
.stdout
)[0]['_source']['layers']
128 assert obj
.get('eth.type', 'NOT FOUND') == ['0x0800']
129 assert obj
.get('ip.proto', 'NOT FOUND') == ['6']
130 assert obj
.get('http.host', 'NOT FOUND') == 'NOT FOUND'
133 class TestTsharkCaptureClopts
:
134 def test_tshark_invalid_capfilter(self
, cmd_tshark
, capture_interface
, result_file
, test_env
):
135 '''Invalid capture filter'''
136 invalid_filter
= '__invalid_protocol'
137 # $TSHARK -f 'jkghg' -w './testout.pcap' > ./testout.txt 2>&1
138 testout_file
= result_file(testout_pcap
)
139 process
= subprocesstest
.run((cmd_tshark
, '-f', invalid_filter
, '-w', testout_file
), capture_output
=True, env
=test_env
)
140 assert grep_output(process
.stderr
, 'Invalid capture filter "' + invalid_filter
+ '" for interface')
142 def test_tshark_invalid_interface_name(self
, cmd_tshark
, capture_interface
, result_file
, test_env
):
143 '''Invalid capture interface name'''
144 invalid_interface
= '__invalid_interface'
145 # $TSHARK -i invalid_interface -w './testout.pcap' > ./testout.txt 2>&1
146 testout_file
= result_file(testout_pcap
)
147 process
= subprocesstest
.run((cmd_tshark
, '-i', invalid_interface
, '-w', testout_file
), capture_output
=True, env
=test_env
)
148 assert grep_output(process
.stderr
, 'There is no device named "__invalid_interface"') or \
149 grep_output(process
.stderr
, 'The capture session could not be initiated on capture device "__invalid_interface"')
151 def test_tshark_invalid_interface_index(self
, cmd_tshark
, capture_interface
, result_file
, test_env
):
152 '''Invalid capture interface index'''
154 # $TSHARK -i 0 -w './testout.pcap' > ./testout.txt 2>&1
155 testout_file
= result_file(testout_pcap
)
156 process
= subprocesstest
.run((cmd_tshark
, '-i', invalid_index
, '-w', testout_file
), capture_output
=True, env
=test_env
)
157 assert grep_output(process
.stderr
, 'There is no interface with that adapter index')
160 class TestTsharkNameResolutionClopts
:
161 def test_tshark_valid_name_resolution(self
, cmd_tshark
, capture_file
, test_env
):
162 # $TSHARK -N mnNtdv -a duration:1 > ./testout.txt 2>&1
163 process
= subprocesstest
.run((cmd_tshark
,
164 '-r', capture_file('empty.pcap'),
167 assert process
.returncode
== 0
169 # XXX Add invalid name resolution.
171 class TestTsharkUnicodeClopts
:
172 def test_tshark_unicode_display_filter(self
, cmd_tshark
, capture_file
, test_env
):
173 '''Unicode (UTF-8) display filter'''
174 process
= subprocesstest
.run((cmd_tshark
, '-r', capture_file('http.pcap'), '-Y', 'tcp.flags.str == "·······AP···"'), capture_output
=True, env
=test_env
)
175 assert grep_output(process
.stdout
, 'HEAD.*/v4/iuident.cab')
178 class TestTsharkDumpGlossaries
:
179 def test_tshark_dump_glossary(self
, cmd_tshark
, base_env
):
180 for glossary
in glossaries
:
181 process
= subprocesstest
.run((cmd_tshark
, '-G', glossary
), capture_output
=True, env
=base_env
)
182 assert not process
.stderr
, 'Found error output while printing glossary ' + glossary
184 def test_tshark_glossary_valid_utf8(self
, cmd_tshark
, base_env
):
185 for glossary
in glossaries
:
187 env
['LANG'] = 'en_US.UTF-8'
188 # subprocess.run() returns bytes here.
189 proc
= subprocess
.run((cmd_tshark
, '-G', glossary
), capture_output
=True, env
=env
)
190 assert proc
.returncode
== 0
191 proc
.stdout
.decode('UTF-8')
193 def test_tshark_glossary_plugin_count(self
, cmd_tshark
, base_env
, features
):
194 if not features
.have_plugins
:
195 pytest
.skip('Test requires binary plugin support.')
196 process
= subprocesstest
.run((cmd_tshark
, '-G', 'plugins'), capture_output
=True, env
=base_env
)
197 assert count_output(process
.stdout
, 'dissector') >= 10, 'Fewer than 10 dissector plugins found'
199 def test_tshark_elastic_mapping(self
, cmd_tshark
, dirs
, base_env
):
200 def get_ip_props(obj
):
201 return obj
['mappings']['properties']['layers']['properties']['ip']['properties']
202 baseline_file
= os
.path
.join(dirs
.baseline_dir
, 'elastic-mapping-ip-subset.json')
203 with
open(baseline_file
) as f
:
204 expected_obj
= json
.load(f
)
205 keys_to_check
= get_ip_props(expected_obj
).keys()
206 proc
= subprocesstest
.run((cmd_tshark
, '-G', 'elastic-mapping', '--elastic-mapping-filter', 'ip'), capture_output
=True, env
=base_env
)
207 actual_obj
= json
.loads(proc
.stdout
)
208 ip_props
= get_ip_props(actual_obj
)
209 for key
in list(ip_props
.keys()):
210 if key
not in keys_to_check
:
212 assert actual_obj
== expected_obj
214 def test_tshark_unicode_folders(self
, cmd_tshark
, unicode_env
, features
):
215 '''Folders output with unicode'''
216 if not features
.have_lua
:
217 pytest
.skip('Test requires Lua scripting support.')
218 if sys
.platform
== 'win32' and not features
.have_lua_unicode
:
219 pytest
.skip('Test requires a patched Lua build with UTF-8 support.')
220 proc
= subprocesstest
.run((cmd_tshark
, '-G', 'folders'), capture_output
=True, env
=unicode_env
.env
)
222 pluginsdir
= [x
.split('\t', 1)[1] for x
in out
.splitlines() if x
.startswith('Personal Lua Plugins:')]
223 assert [unicode_env
.pluginsdir
] == pluginsdir
226 class TestTsharkZExpert
:
227 def test_tshark_z_expert_all(self
, cmd_tshark
, capture_file
, test_env
):
228 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert',
229 '-o', 'tcp.check_checksum:TRUE',
230 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
231 # http2-data-reassembly.pcap has Errors, Warnings, Notes, and Chats
232 # when TCP checksum are verified.
233 assert grep_output(proc
.stdout
, 'Errors')
234 assert grep_output(proc
.stdout
, 'Warns')
235 assert grep_output(proc
.stdout
, 'Notes')
236 assert grep_output(proc
.stdout
, 'Chats')
238 def test_tshark_z_expert_error(self
, cmd_tshark
, capture_file
, test_env
):
239 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,error',
240 '-o', 'tcp.check_checksum:TRUE',
241 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
242 assert grep_output(proc
.stdout
, 'Errors')
243 assert not grep_output(proc
.stdout
, 'Warns')
244 assert not grep_output(proc
.stdout
, 'Notes')
245 assert not grep_output(proc
.stdout
, 'Chats')
247 def test_tshark_z_expert_warn(self
, cmd_tshark
, capture_file
, test_env
):
248 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,warn',
249 '-o', 'tcp.check_checksum:TRUE',
250 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
251 assert grep_output(proc
.stdout
, 'Errors')
252 assert grep_output(proc
.stdout
, 'Warns')
253 assert not grep_output(proc
.stdout
, 'Notes')
254 assert not grep_output(proc
.stdout
, 'Chats')
256 def test_tshark_z_expert_note(self
, cmd_tshark
, capture_file
, test_env
):
257 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,note',
258 '-o', 'tcp.check_checksum:TRUE',
259 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
260 assert grep_output(proc
.stdout
, 'Errors')
261 assert grep_output(proc
.stdout
, 'Warns')
262 assert grep_output(proc
.stdout
, 'Notes')
263 assert not grep_output(proc
.stdout
, 'Chats')
265 def test_tshark_z_expert_chat(self
, cmd_tshark
, capture_file
, test_env
):
266 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,chat',
267 '-o', 'tcp.check_checksum:TRUE',
268 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
269 assert grep_output(proc
.stdout
, 'Errors')
270 assert grep_output(proc
.stdout
, 'Warns')
271 assert grep_output(proc
.stdout
, 'Notes')
272 assert grep_output(proc
.stdout
, 'Chats')
274 def test_tshark_z_expert_comment(self
, cmd_tshark
, capture_file
, test_env
):
275 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,comment',
276 '-r', capture_file('sip.pcapng')), capture_output
=True, env
=test_env
)
277 assert grep_output(proc
.stdout
, 'Notes')
278 assert grep_output(proc
.stdout
, 'Comments')
280 def test_tshark_z_expert_invalid_filter(self
, cmd_tshark
, capture_file
, test_env
):
281 invalid_filter
= '__invalid_protocol'
282 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,' + invalid_filter
,
283 '-r', capture_file('http-ooo.pcap')), capture_output
=True, env
=test_env
)
284 assert proc
.returncode
== ExitCodes
.COMMAND_LINE
285 assert grep_output(proc
.stdout
, 'Filter "' + invalid_filter
+ '" is invalid')
287 def test_tshark_z_expert_error_invalid_filter(self
, cmd_tshark
, capture_file
, test_env
):
288 invalid_filter
= '__invalid_protocol'
289 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,error,' + invalid_filter
,
290 '-r', capture_file('http-ooo.pcap')), capture_output
=True, env
=test_env
)
291 assert proc
.returncode
== ExitCodes
.COMMAND_LINE
292 assert grep_output(proc
.stdout
, 'Filter "' + invalid_filter
+ '" is invalid')
294 def test_tshark_z_expert_filter(self
, cmd_tshark
, capture_file
, test_env
):
295 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,udp',
296 '-o', 'tcp.check_checksum:TRUE',
297 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
298 # Filtering for UDP should produce no expert infos.
299 assert not grep_output(proc
.stdout
, 'Errors')
300 assert not grep_output(proc
.stdout
, 'Warns')
301 assert not grep_output(proc
.stdout
, 'Notes')
302 assert not grep_output(proc
.stdout
, 'Chats')
304 def test_tshark_z_expert_error_filter(self
, cmd_tshark
, capture_file
, test_env
):
305 proc
= subprocesstest
.run((cmd_tshark
, '-q', '-z', 'expert,note,http', # tls is a filter
306 '-o', 'tcp.check_checksum:TRUE',
307 '-r', capture_file('http-ooo-fuzzed.pcapng')), capture_output
=True, env
=test_env
)
308 # Filtering for HTTP and Note level expert info should produce only
309 # Error and Warning level expert infos with checksumming turned on.
310 # The Note warnings on are packets with TCP but not HTTP, and we're
311 # filtering out the Chat level.
312 assert grep_output(proc
.stdout
, 'Errors')
313 assert grep_output(proc
.stdout
, 'Warns')
314 assert not grep_output(proc
.stdout
, 'Notes')
315 assert not grep_output(proc
.stdout
, 'Chats')
318 class TestTsharkExtcap
:
319 # dumpcap dependency has been added to run this test only with capture support
320 def test_tshark_extcap_interfaces(self
, cmd_tshark
, cmd_dumpcap
, test_env
, home_path
):
321 # Script extcaps don't work with the current code on windows.
322 # https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html
323 # TODO: skip this test until it will get fixed.
324 if sys
.platform
== 'win32':
325 pytest
.skip('FIXME extcap .py scripts needs special treatment on Windows')
326 # Various guides and vulnerability scanners recommend setting /tmp noexec.
327 # If our temp path is such, the extcap script won't work.
329 if os
.statvfs(home_path
).f_flag
& os
.ST_NOEXEC
:
330 pytest
.skip('Test requires temp directory to allow execution')
331 except AttributeError:
332 # Most Linux and NetBSD have ST_NOEXEC; Darwin and other *BSDs don't.
334 extcap_dir_path
= os
.path
.join(home_path
, 'extcap')
335 os
.makedirs(extcap_dir_path
)
336 test_env
['WIRESHARK_EXTCAP_DIR'] = extcap_dir_path
337 source_file
= os
.path
.join(os
.path
.dirname(__file__
), 'sampleif.py')
338 # We run our tests in a bare, reproducible home environment. This can result in an
339 # invalid or missing Python interpreter if our main environment has a wonky Python
340 # path, as is the case in the GitLab SaaS macOS runners which use `asdf`. Force
341 # sampleif.py to use our current Python executable.
342 with
open(source_file
, 'r') as sf
:
343 sampleif_py
= sf
.read()
344 sampleif_py
= sampleif_py
.replace('/usr/bin/env python3', sys
.executable
)
345 sys
.stderr
.write(sampleif_py
)
346 extcap_file
= os
.path
.join(extcap_dir_path
, 'sampleif.py')
347 with
open(extcap_file
, 'w') as ef
:
348 ef
.write(sampleif_py
)
349 os
.fchmod(ef
.fileno(), os
.fstat(sf
.fileno()).st_mode
)
351 # Ensure the test extcap_tool is properly loaded
352 proc
= subprocesstest
.run((cmd_tshark
, '-D'), capture_output
=True, env
=test_env
)
353 assert count_output(proc
.stdout
, 'sampleif') == 1
354 # Ensure tshark lists 2 interfaces in the preferences
355 proc
= subprocesstest
.run((cmd_tshark
, '-G', 'currentprefs'), capture_output
=True, env
=test_env
)
356 assert count_output(proc
.stdout
, 'extcap.sampleif.test') == 2