1#
2# Wireshark tests
3# By Gerald Combs <gerald@wireshark.org>
4#
5# Ported from a set of Bash scripts which were copyright 2005 Ulf Lamping
6#
7# SPDX-License-Identifier: GPL-2.0-or-later
8#
9'''Command line option tests'''
10
11import json
12import sys
13import os.path
14import subprocess
15import subprocesstest
16import fixtures
17import shutil
18
19#glossaries = ('fields', 'protocols', 'values', 'decodes', 'defaultprefs', 'currentprefs')
20
21glossaries = ('decodes', 'values')
22testout_pcap = 'testout.pcap'
23
24
25@fixtures.uses_fixtures
26class case_dumpcap_options(subprocesstest.SubprocessTestCase):
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 'CEFGHJKNOQRTUVWXYejloxz':
31            self.assertRun((cmd_dumpcap, '-' + char_arg), env=base_env,
32                           expected_return=self.exit_command_line)
33
34    # XXX Should we generate individual test functions instead of looping?
35    def test_dumpcap_valid_chars(self, cmd_dumpcap, base_env):
36        for char_arg in 'hv':
37            self.assertRun((cmd_dumpcap, '-' + char_arg), env=base_env)
38
39    # XXX Should we generate individual test functions instead of looping?
40    def test_dumpcap_interface_chars(self, cmd_dumpcap, base_env):
41        '''Valid dumpcap parameters requiring capture permissions'''
42        valid_returns = [self.exit_ok, self.exit_error]
43        for char_arg in 'DL':
44            process = self.runProcess((cmd_dumpcap, '-' + char_arg), env=base_env)
45            self.assertIn(process.returncode, valid_returns)
46
47
48@fixtures.mark_usefixtures('base_env')
49@fixtures.uses_fixtures
50class case_dumpcap_capture_clopts(subprocesstest.SubprocessTestCase):
51    def test_dumpcap_invalid_capfilter(self, cmd_dumpcap, capture_interface):
52        '''Invalid capture filter'''
53        invalid_filter = '__invalid_protocol'
54        # $DUMPCAP -f 'jkghg' -w './testout.pcap' > ./testout.txt 2>&1
55        testout_file = self.filename_from_id(testout_pcap)
56        self.runProcess((cmd_dumpcap, '-f', invalid_filter, '-w', testout_file))
57        self.assertTrue(self.grepOutput('Invalid capture filter "' + invalid_filter + '" for interface'))
58
59    def test_dumpcap_invalid_interface_name(self, cmd_dumpcap, capture_interface):
60        '''Invalid capture interface name'''
61        invalid_interface = '__invalid_interface'
62        # $DUMPCAP -i invalid_interface -w './testout.pcap' > ./testout.txt 2>&1
63        testout_file = self.filename_from_id(testout_pcap)
64        self.runProcess((cmd_dumpcap, '-i', invalid_interface, '-w', testout_file))
65        self.assertTrue(self.grepOutput('The capture session could not be initiated'))
66
67    def test_dumpcap_invalid_interface_index(self, cmd_dumpcap, capture_interface):
68        '''Invalid capture interface index'''
69        invalid_index = '0'
70        # $DUMPCAP -i 0 -w './testout.pcap' > ./testout.txt 2>&1
71        testout_file = self.filename_from_id(testout_pcap)
72        self.runProcess((cmd_dumpcap, '-i', invalid_index, '-w', testout_file))
73        self.assertTrue(self.grepOutput('There is no interface with that adapter index'))
74
75
76@fixtures.mark_usefixtures('test_env')
77@fixtures.uses_fixtures
78class case_basic_clopts(subprocesstest.SubprocessTestCase):
79    def test_existing_file(self, cmd_tshark, capture_file):
80        # $TSHARK -r "${CAPTURE_DIR}dhcp.pcap" > ./testout.txt 2>&1
81        self.assertRun((cmd_tshark, '-r', capture_file('dhcp.pcap')))
82
83    def test_nonexistent_file(self, cmd_tshark, capture_file):
84        # $TSHARK - r ThisFileDontExist.pcap > ./testout.txt 2 > &1
85        self.assertRun((cmd_tshark, '-r', capture_file('__ceci_nest_pas_une.pcap')),
86                       expected_return=self.exit_error)
87
88
89@fixtures.mark_usefixtures('test_env')
90@fixtures.uses_fixtures
91class case_tshark_options(subprocesstest.SubprocessTestCase):
92    # XXX Should we generate individual test functions instead of looping?
93    def test_tshark_invalid_chars(self, cmd_tshark):
94        '''Invalid tshark parameters'''
95        for char_arg in 'ABCEFHJKMNORTUWXYZabcdefijkmorstuwyz':
96            self.assertRun((cmd_tshark, '-' + char_arg),
97                           expected_return=self.exit_command_line)
98
99    # XXX Should we generate individual test functions instead of looping?
100    def test_tshark_valid_chars(self, cmd_tshark):
101        for char_arg in 'Ghv':
102            self.assertRun((cmd_tshark, '-' + char_arg))
103
104    # XXX Should we generate individual test functions instead of looping?
105    def test_tshark_interface_chars(self, cmd_tshark, cmd_dumpcap):
106        '''Valid tshark parameters requiring capture permissions'''
107        # These options require dumpcap
108        valid_returns = [self.exit_ok, self.exit_error]
109        for char_arg in 'DL':
110            process = self.runProcess((cmd_tshark, '-' + char_arg))
111            self.assertIn(process.returncode, valid_returns)
112
113
114@fixtures.mark_usefixtures('test_env')
115@fixtures.uses_fixtures
116class case_tshark_capture_clopts(subprocesstest.SubprocessTestCase):
117    def test_tshark_invalid_capfilter(self, cmd_tshark, capture_interface):
118        '''Invalid capture filter'''
119        invalid_filter = '__invalid_protocol'
120        # $TSHARK -f 'jkghg' -w './testout.pcap' > ./testout.txt 2>&1
121        testout_file = self.filename_from_id(testout_pcap)
122        self.runProcess((cmd_tshark, '-f', invalid_filter, '-w', testout_file ))
123        self.assertTrue(self.grepOutput('Invalid capture filter "' + invalid_filter + '" for interface'))
124
125    def test_tshark_invalid_interface_name(self, cmd_tshark, capture_interface):
126        '''Invalid capture interface name'''
127        invalid_interface = '__invalid_interface'
128        # $TSHARK -i invalid_interface -w './testout.pcap' > ./testout.txt 2>&1
129        testout_file = self.filename_from_id(testout_pcap)
130        self.runProcess((cmd_tshark, '-i', invalid_interface, '-w', testout_file))
131        self.assertTrue(self.grepOutput('The capture session could not be initiated'))
132
133    def test_tshark_invalid_interface_index(self, cmd_tshark, capture_interface):
134        '''Invalid capture interface index'''
135        invalid_index = '0'
136        # $TSHARK -i 0 -w './testout.pcap' > ./testout.txt 2>&1
137        testout_file = self.filename_from_id(testout_pcap)
138        self.runProcess((cmd_tshark, '-i', invalid_index, '-w', testout_file))
139        self.assertTrue(self.grepOutput('There is no interface with that adapter index'))
140
141
142@fixtures.mark_usefixtures('test_env')
143@fixtures.uses_fixtures
144class case_tshark_name_resolution_clopts(subprocesstest.SubprocessTestCase):
145    def test_tshark_valid_name_resolution(self, cmd_tshark, capture_interface):
146        # $TSHARK -N mnNtdv -a duration:1 > ./testout.txt 2>&1
147        self.assertRun((cmd_tshark, '-N', 'mnNtdv', '-a', 'duration: 1'))
148
149    # XXX Add invalid name resolution.
150
151@fixtures.mark_usefixtures('test_env')
152@fixtures.uses_fixtures
153class case_tshark_unicode_clopts(subprocesstest.SubprocessTestCase):
154    def test_tshark_unicode_display_filter(self, cmd_tshark, capture_file):
155        '''Unicode (UTF-8) display filter'''
156        self.assertRun((cmd_tshark, '-r', capture_file('http.pcap'), '-Y', 'tcp.flags.str == "·······AP···"'))
157        self.assertTrue(self.grepOutput('HEAD.*/v4/iuident.cab'))
158
159
160@fixtures.uses_fixtures
161class case_tshark_dump_glossaries(subprocesstest.SubprocessTestCase):
162    def test_tshark_dump_glossary(self, cmd_tshark, base_env):
163        for glossary in glossaries:
164            try:
165                self.log_fd.truncate()
166            except Exception:
167                pass
168            self.assertRun((cmd_tshark, '-G', glossary), env=base_env, max_lines=20)
169            self.assertEqual(self.countOutput(count_stdout=False, count_stderr=True), 0, 'Found error output while printing glossary ' + glossary)
170
171    def test_tshark_glossary_valid_utf8(self, cmd_tshark, base_env):
172        for glossary in glossaries:
173            env = base_env
174            env['LANG'] = 'en_US.UTF-8'
175            g_contents = subprocess.check_output((cmd_tshark, '-G', glossary), env=env, stderr=subprocess.PIPE)
176            decoded = True
177            try:
178                g_contents.decode('UTF-8')
179            except UnicodeDecodeError:
180                decoded = False
181            self.assertTrue(decoded, '{} is not valid UTF-8'.format(glossary))
182
183    def test_tshark_glossary_plugin_count(self, cmd_tshark, base_env, features):
184        if not features.have_plugins:
185            self.skipTest('Test requires binary plugin support.')
186        self.assertRun((cmd_tshark, '-G', 'plugins'), env=base_env)
187        self.assertGreaterEqual(self.countOutput('dissector'), 10, 'Fewer than 10 dissector plugins found')
188
189    def test_tshark_elastic_mapping(self, cmd_tshark, dirs, base_env):
190        def get_ip_props(obj):
191            return obj['mappings']['doc']['properties']['layers']['properties']['ip']['properties']
192        self.maxDiff = None
193        baseline_file = os.path.join(dirs.baseline_dir, 'elastic-mapping-ip-subset.json')
194        with open(baseline_file) as f:
195            expected_obj = json.load(f)
196        keys_to_check = get_ip_props(expected_obj).keys()
197        proc = self.assertRun((cmd_tshark, '-G', 'elastic-mapping', '--elastic-mapping-filter', 'ip'))
198        actual_obj = json.loads(proc.stdout_str)
199        ip_props = get_ip_props(actual_obj)
200        for key in list(ip_props.keys()):
201            if key not in keys_to_check:
202                del ip_props[key]
203        self.assertEqual(actual_obj, expected_obj)
204
205    def test_tshark_unicode_folders(self, cmd_tshark, unicode_env, features):
206        '''Folders output with unicode'''
207        if not features.have_lua:
208            self.skipTest('Test requires Lua scripting support.')
209        proc = self.assertRun((cmd_tshark, '-G', 'folders'), env=unicode_env.env)
210        out = proc.stdout_str
211        pluginsdir = [x.split('\t', 1)[1] for x in out.splitlines() if x.startswith('Personal Lua Plugins:')]
212        self.assertEqual([unicode_env.pluginsdir], pluginsdir)
213
214
215@fixtures.mark_usefixtures('test_env')
216@fixtures.uses_fixtures
217class case_tshark_z_expert(subprocesstest.SubprocessTestCase):
218    def test_tshark_z_expert_all(self, cmd_tshark, capture_file):
219        self.assertRun((cmd_tshark, '-q', '-z', 'expert',
220            '-r', capture_file('http-ooo.pcap')))
221        self.assertTrue(self.grepOutput('Errors'))
222        self.assertTrue(self.grepOutput('Warns'))
223        self.assertTrue(self.grepOutput('Chats'))
224
225    def test_tshark_z_expert_error(self, cmd_tshark, capture_file):
226        self.assertRun((cmd_tshark, '-q', '-z', 'expert,error',
227            '-r', capture_file('http-ooo.pcap')))
228        self.assertTrue(self.grepOutput('Errors'))
229        self.assertFalse(self.grepOutput('Warns'))
230        self.assertFalse(self.grepOutput('Chats'))
231
232    def test_tshark_z_expert_warn(self, cmd_tshark, capture_file):
233        self.assertRun((cmd_tshark, '-q', '-z', 'expert,warn',
234            '-r', capture_file('http-ooo.pcap')))
235        self.assertTrue(self.grepOutput('Errors'))
236        self.assertTrue(self.grepOutput('Warns'))
237        self.assertFalse(self.grepOutput('Chats'))
238
239    def test_tshark_z_expert_note(self, cmd_tshark, capture_file):
240        self.assertRun((cmd_tshark, '-q', '-z', 'expert,note',
241            '-r', capture_file('http2-data-reassembly.pcap')))
242        self.assertTrue(self.grepOutput('Warns'))
243        self.assertTrue(self.grepOutput('Notes'))
244        self.assertFalse(self.grepOutput('Chats'))
245
246    def test_tshark_z_expert_chat(self, cmd_tshark, capture_file):
247        self.assertRun((cmd_tshark, '-q', '-z', 'expert,chat',
248            '-r', capture_file('http-ooo.pcap')))
249        self.assertTrue(self.grepOutput('Errors'))
250        self.assertTrue(self.grepOutput('Warns'))
251        self.assertTrue(self.grepOutput('Chats'))
252
253    def test_tshark_z_expert_comment(self, cmd_tshark, capture_file):
254        self.assertRun((cmd_tshark, '-q', '-z', 'expert,comment',
255            '-r', capture_file('sip.pcapng')))
256        self.assertTrue(self.grepOutput('Notes'))
257        self.assertTrue(self.grepOutput('Comments'))
258
259    def test_tshark_z_expert_invalid_filter(self, cmd_tshark, capture_file):
260        invalid_filter = '__invalid_protocol'
261        self.assertRun((cmd_tshark, '-q', '-z', 'expert,' + invalid_filter,
262            '-r', capture_file('http-ooo.pcap')),
263            expected_return=self.exit_command_line)
264        self.assertTrue(self.grepOutput('Filter "' + invalid_filter + '" is invalid'))
265
266    def test_tshark_z_expert_error_invalid_filter(self, cmd_tshark, capture_file):
267        invalid_filter = '__invalid_protocol'
268        self.assertRun((cmd_tshark, '-q', '-z', 'expert,error,' + invalid_filter,
269            '-r', capture_file('http-ooo.pcap')),
270            expected_return=self.exit_command_line)
271        self.assertTrue(self.grepOutput('Filter "' + invalid_filter + '" is invalid'))
272
273    def test_tshark_z_expert_filter(self, cmd_tshark, capture_file):
274        self.assertRun((cmd_tshark, '-q', '-z', 'expert,udp',  # udp is a filter
275            '-r', capture_file('http-ooo.pcap')))
276        self.assertFalse(self.grepOutput('Errors'))
277        self.assertFalse(self.grepOutput('Warns'))
278        self.assertFalse(self.grepOutput('Chats'))
279
280    def test_tshark_z_expert_error_filter(self, cmd_tshark, capture_file):
281        self.assertRun((cmd_tshark, '-q', '-z', 'expert,error,udp',  # udp is a filter
282            '-r', capture_file('http-ooo.pcap')))
283        self.assertFalse(self.grepOutput('Errors'))
284        self.assertFalse(self.grepOutput('Warns'))
285        self.assertFalse(self.grepOutput('Chats'))
286
287
288@fixtures.mark_usefixtures('test_env')
289@fixtures.uses_fixtures
290class case_tshark_extcap(subprocesstest.SubprocessTestCase):
291    # dumpcap dependency has been added to run this test only with capture support
292    def test_tshark_extcap_interfaces(self, cmd_tshark, cmd_dumpcap, test_env, home_path):
293        # Script extcaps don't work with the current code on windows.
294        # https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html
295        # TODO: skip this test until it will get fixed.
296        if sys.platform == 'win32':
297            self.skipTest('FIXME extcap .py scripts needs special treatment on Windows')
298        extcap_dir_path = os.path.join(home_path, 'extcap')
299        os.makedirs(extcap_dir_path)
300        test_env['WIRESHARK_EXTCAP_DIR'] = extcap_dir_path
301        source_file = os.path.join(os.path.dirname(__file__), 'sampleif.py')
302        shutil.copy2(source_file, extcap_dir_path)
303        # Ensure the test extcap_tool is properly loaded
304        self.assertRun((cmd_tshark, '-D'), env=test_env)
305        self.assertEqual(1, self.countOutput('sampleif'))
306        # Ensure tshark lists 2 interfaces in the preferences
307        self.assertRun((cmd_tshark, '-G', 'currentprefs'), env=test_env)
308        self.assertEqual(2, self.countOutput('extcap.sampleif.test'))
309