text2pcap: allow to set interface name
[metze/wireshark/wip.git] / test / suite_sharkd.py
1 #
2 # -*- coding: utf-8 -*-
3 # Wireshark tests
4 # By Gerald Combs <gerald@wireshark.org>
5 #
6 # Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
7 #
8 # SPDX-License-Identifier: GPL-2.0-or-later
9 #
10 '''sharkd tests'''
11
12 import json
13 import subprocess
14 import unittest
15 import subprocesstest
16 import fixtures
17 from matchers import *
18
19
20 @fixtures.fixture(scope='session')
21 def cmd_sharkd(program):
22     return program('sharkd')
23
24
25 @fixtures.fixture
26 def run_sharkd_session(cmd_sharkd, request):
27     self = request.instance
28
29     def run_sharkd_session_real(sharkd_commands):
30         sharkd_proc = self.startProcess(
31             (cmd_sharkd, '-'), stdin=subprocess.PIPE)
32         sharkd_proc.stdin.write('\n'.join(sharkd_commands).encode('utf8'))
33         self.waitProcess(sharkd_proc)
34
35         self.assertIn('Hello in child.', sharkd_proc.stderr_str)
36
37         outputs = []
38         for line in sharkd_proc.stdout_str.splitlines():
39             line = line.strip()
40             if not line:
41                 continue
42             try:
43                 jdata = json.loads(line)
44             except json.JSONDecodeError:
45                 self.fail('Invalid JSON: %r' % line)
46             outputs.append(jdata)
47         return tuple(outputs)
48     return run_sharkd_session_real
49
50
51 @fixtures.fixture
52 def check_sharkd_session(run_sharkd_session, request):
53     self = request.instance
54
55     def check_sharkd_session_real(sharkd_commands, expected_outputs):
56         sharkd_commands = [json.dumps(x) for x in sharkd_commands]
57         actual_outputs = run_sharkd_session(sharkd_commands)
58         self.assertEqual(expected_outputs, actual_outputs)
59     return check_sharkd_session_real
60
61
62 @fixtures.mark_usefixtures('base_env')
63 @fixtures.uses_fixtures
64 class case_sharkd(subprocesstest.SubprocessTestCase):
65     def test_sharkd_req_load_bad_pcap(self, check_sharkd_session, capture_file):
66         check_sharkd_session((
67             {"req": "load", "file": capture_file('non-existant.pcap')},
68         ), (
69             {"err": 2},
70         ))
71
72     def test_sharkd_req_status_no_pcap(self, check_sharkd_session):
73         check_sharkd_session((
74             {"req": "status"},
75         ), (
76             {"frames": 0, "duration": 0.0},
77         ))
78
79     def test_sharkd_req_status(self, check_sharkd_session, capture_file):
80         check_sharkd_session((
81             {"req": "load", "file": capture_file('dhcp.pcap')},
82             {"req": "status"},
83         ), (
84             {"err": 0},
85             {"frames": 4, "duration": 0.070345000,
86                 "filename": "dhcp.pcap", "filesize": 1400},
87         ))
88
89     def test_sharkd_req_analyse(self, check_sharkd_session, capture_file):
90         check_sharkd_session((
91             {"req": "load", "file": capture_file('dhcp.pcap')},
92             {"req": "analyse"},
93         ), (
94             {"err": 0},
95             {"frames": 4, "protocols": ["frame", "eth", "ethertype", "ip", "udp",
96                                         "dhcp"], "first": 1102274184.317452908, "last": 1102274184.387798071},
97         ))
98
99     def test_sharkd_req_info(self, check_sharkd_session):
100         matchTapNameList = MatchList(
101             {"tap": MatchAny(str), "name": MatchAny(str)})
102         check_sharkd_session((
103             {"req": "info"},
104         ), (
105             {
106                 "version": MatchAny(str),
107                 "columns": MatchList({"format": MatchAny(str), "name": MatchAny(str)}),
108                 "stats": matchTapNameList,
109                 "convs": matchTapNameList,
110                 "eo": matchTapNameList,
111                 "srt": matchTapNameList,
112                 "rtd": matchTapNameList,
113                 "seqa": matchTapNameList,
114                 "taps": matchTapNameList,
115                 "follow": matchTapNameList,
116                 "ftypes": MatchList(MatchAny(str)),
117                 "nstat": matchTapNameList,
118             },
119         ))
120
121     def test_sharkd_req_check(self, check_sharkd_session, capture_file):
122         check_sharkd_session((
123             {"req": "load", "file": capture_file('dhcp.pcap')},
124             {"req": "check"},
125             {"req": "check", "filter": "garbage filter"},
126             {"req": "check", "field": "garbage field"},
127             {"req": "check", "filter": "ip", "field": "ip"},
128         ), (
129             {"err": 0},
130             {"err": 0},
131             {"err": 0, "filter": '"filter" was unexpected in this context.'},
132             {"err": 0, "field": "notfound"},
133             {"err": 0, "filter": "ok", "field": "ok"},
134         ))
135
136     def test_sharkd_req_complete_field(self, check_sharkd_session):
137         check_sharkd_session((
138             {"req": "complete"},
139             {"req": "complete", "field": "frame.le"},
140             {"req": "complete", "field": "garbage.nothing.matches"},
141         ), (
142             {"err": 0},
143             {"err": 0, "field": MatchList(
144                 {"f": "frame.len", "t": 7, "n": "Frame length on the wire"}, match_element=any)},
145             {"err": 0, "field": []},
146         ))
147
148     def test_sharkd_req_complete_pref(self, check_sharkd_session):
149         check_sharkd_session((
150             {"req": "complete", "pref": "tcp."},
151             {"req": "complete", "pref": "garbage.nothing.matches"},
152         ), (
153             {"err": 0, "pref": MatchList(
154                 {"f": "tcp.check_checksum", "d": "Validate the TCP checksum if possible"}, match_element=any)},
155             {"err": 0, "pref": []},
156         ))
157
158     def test_sharkd_req_frames(self, check_sharkd_session, capture_file):
159         # XXX need test for optional input parameters, ignored/marked/commented
160         check_sharkd_session((
161             {"req": "load", "file": capture_file('dhcp.pcap')},
162             {"req": "frames"},
163         ), (
164             {"err": 0},
165             MatchList({
166                 "c": MatchList(MatchAny(str)),
167                 "num": MatchAny(int),
168                 "bg": MatchAny(str),
169                 "fg": MatchAny(str),
170             }),
171         ))
172
173     def test_sharkd_req_tap_invalid(self, check_sharkd_session, capture_file):
174         # XXX Unrecognized taps result in an empty line, modify
175         #     run_sharkd_session such that checking for it is possible.
176         check_sharkd_session((
177             {"req": "load", "file": capture_file('dhcp.pcap')},
178             {"req": "tap"},
179             {"req": "tap", "tap0": "garbage tap"},
180         ), (
181             {"err": 0},
182         ))
183
184     def test_sharkd_req_tap(self, check_sharkd_session, capture_file):
185         check_sharkd_session((
186             {"req": "load", "file": capture_file('dhcp.pcap')},
187             {"req": "tap"},
188             {"req": "tap", "tap0": "conv:Ethernet", "tap1": "endpt:TCP"},
189         ), (
190             {"err": 0},
191             {
192                 "err": 0,
193                 "taps": [
194                     {
195                         "tap": "endpt:TCP",
196                         "type": "host",
197                         "proto": "TCP",
198                         "geoip": MatchAny(bool),
199                         "hosts": [],
200                     },
201                     {
202                         "tap": "conv:Ethernet",
203                         "type": "conv",
204                         "proto": "Ethernet",
205                         "geoip": MatchAny(bool),
206                         "convs": [
207                             {
208                                 "saddr": MatchAny(str),
209                                 "daddr": "Broadcast",
210                                 "txf": 2,
211                                 "txb": 628,
212                                 "rxf": 0,
213                                 "rxb": 0,
214                                 "start": 0,
215                                 "stop": 0.070031,
216                                 "filter": "eth.addr==00:0b:82:01:fc:42 && eth.addr==ff:ff:ff:ff:ff:ff",
217                             },
218                             {
219                                 "saddr": MatchAny(str),
220                                 "daddr": MatchAny(str),
221                                 "rxf": 0,
222                                 "rxb": 0,
223                                 "txf": 2,
224                                 "txb": 684,
225                                 "start": 0.000295,
226                                 "stop": 0.070345,
227                                 "filter": "eth.addr==00:08:74:ad:f1:9b && eth.addr==00:0b:82:01:fc:42",
228                             }
229                         ],
230                     },
231                     # XXX remove the last null element, it is not part of the interface.
232                     None
233                 ]
234             },
235         ))
236
237     def test_sharkd_req_follow_bad(self, check_sharkd_session, capture_file):
238         # Unrecognized taps currently produce no output (not even err).
239         check_sharkd_session((
240             {"req": "load", "file": capture_file('dhcp.pcap')},
241             {"req": "follow"},
242             {"req": "follow", "follow": "garbage follow", "filter": "ip"},
243             {"req": "follow", "follow": "HTTP", "filter": "garbage filter"},
244         ), (
245             {"err": 0},
246         ))
247
248     def test_sharkd_req_follow_no_match(self, check_sharkd_session, capture_file):
249         check_sharkd_session((
250             {"req": "load", "file": capture_file('dhcp.pcap')},
251             {"req": "follow", "follow": "HTTP", "filter": "ip"},
252         ), (
253             {"err": 0},
254             {"err": 0, "shost": "NONE", "sport": "0", "sbytes": 0,
255              "chost": "NONE", "cport": "0", "cbytes": 0},
256         ))
257
258     def test_sharkd_req_follow_udp(self, check_sharkd_session, capture_file):
259         check_sharkd_session((
260             {"req": "load", "file": capture_file('dhcp.pcap')},
261             {"req": "follow", "follow": "UDP", "filter": "frame.number==1"},
262         ), (
263             {"err": 0},
264             {"err": 0,
265              "shost": "255.255.255.255", "sport": "67", "sbytes": 272,
266              "chost": "0.0.0.0", "cport": "68", "cbytes": 0,
267              "payloads": [
268                  {"n": 1, "d": MatchRegExp(r'AQEGAAAAPR0A[a-zA-Z0-9]{330}AANwQBAwYq/wAAAAAAAAA=')}]},
269         ))
270
271     def test_sharkd_req_iograph_bad(self, check_sharkd_session, capture_file):
272         check_sharkd_session((
273             {"req": "load", "file": capture_file('dhcp.pcap')},
274             {"req": "iograph"},
275             {"req": "iograph", "graph0": "garbage graph name"},
276         ), (
277             {"err": 0},
278             {"iograph": []},
279             {"iograph": []},
280         ))
281
282     def test_sharkd_req_iograph_basic(self, check_sharkd_session, capture_file):
283         check_sharkd_session((
284             {"req": "load", "file": capture_file('dhcp.pcap')},
285             {"req": "iograph", "graph0": "max:udp.length", "filter0": "udp.length"},
286             {"req": "iograph", "graph0": "packets", "graph1": "bytes"},
287             {"req": "iograph", "graph0": "packets", "filter0": "garbage filter"},
288         ), (
289             {"err": 0},
290             {"iograph": [{"items": [308.000000]}]},
291             {"iograph": [{"items": [4.000000]}, {"items": [1312.000000]}]},
292             {"iograph": [
293                 {"errmsg": 'Filter "garbage filter" is invalid - "filter" was unexpected in this context.'}]},
294         ))
295
296     def test_sharkd_req_intervals_bad(self, check_sharkd_session, capture_file):
297         check_sharkd_session((
298             {"req": "load", "file": capture_file('dhcp.pcap')},
299             {"req": "intervals", "filter": "garbage filter"},
300         ), (
301             {"err": 0},
302         ))
303
304     def test_sharkd_req_intervals_basic(self, check_sharkd_session, capture_file):
305         check_sharkd_session((
306             {"req": "load", "file": capture_file('dhcp.pcap')},
307             {"req": "intervals"},
308             {"req": "intervals", "interval": 1},
309             {"req": "intervals", "filter": "frame.number <= 2"},
310         ), (
311             {"err": 0},
312             {"intervals": [[0, 4, 1312]], "last": 0,
313                 "frames": 4, "bytes": 1312},
314             {"intervals": [[0, 2, 656], [70, 2, 656]],
315                 "last": 70, "frames": 4, "bytes": 1312},
316             {"intervals": [[0, 2, 656]], "last": 0, "frames": 2, "bytes": 656},
317         ))
318
319     def test_sharkd_req_frame_basic(self, check_sharkd_session, capture_file):
320         # XXX add more tests for other options (ref_frame, prev_frame, columns, color, bytes, hidden)
321         check_sharkd_session((
322             {"req": "load", "file": capture_file('dhcp.pcap')},
323             {"req": "frame", "frame": 2},
324         ), (
325             {"err": 0},
326             # XXX remove the first 0 element, it is not part of the interface.
327             {"err": 0, "fol": [0, ["UDP", "udp.stream eq 1"]]},
328         ))
329
330     def test_sharkd_req_frame_proto(self, check_sharkd_session, capture_file):
331         # Check proto tree output (including an UTF-8 value).
332         check_sharkd_session((
333             {"req": "load", "file": capture_file('dhcp.pcap')},
334             {"req": "frame", "frame": 2, "proto": True},
335         ), (
336             {"err": 0},
337             MatchObject({
338                 "tree": MatchList({
339                     "l": "Dynamic Host Configuration Protocol (Offer)",
340                     "t": "proto",
341                     "f": "dhcp",
342                     "e": MatchAny(int),
343                     "n": MatchList({
344                         "l": "Padding: 000000000000000000000000000000000000000000000000…",
345                         "h": [316, 26],
346                         "f": "dhcp.option.padding == 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
347                     }, match_element=any),  # match one element from 'n'
348                     "h": [42, 300],
349                 }, match_element=any),  # match one element from 'tree'
350             }),
351         ))
352
353     def test_sharkd_req_setcomment(self, check_sharkd_session, capture_file):
354         check_sharkd_session((
355             {"req": "load", "file": capture_file('dhcp.pcap')},
356             # invalid frame number returns early.
357             {"req": "setcomment", "frame": 99999, "comment": "meh\nbaz"},
358             {"req": "setcomment", "frame": 3, "comment": "foo\nbar"},
359             {"req": "frame", "frame": 3},
360
361         ), (
362             {"err": 0},
363             {"err": 0},
364             {"err": 0, "comment": "foo\nbar", "fol": MatchAny(list)},
365         ))
366
367     def test_sharkd_req_setconf_bad(self, check_sharkd_session):
368         check_sharkd_session((
369             {"req": "setconf", "name": "uat:garbage-pref", "value": "\"\""},
370         ), (
371             {"err": 1, "errmsg": "Unknown preference"},
372         ))
373
374     def test_sharkd_req_dumpconf_bad(self, check_sharkd_session):
375         check_sharkd_session((
376             {"req": "dumpconf", "pref": "invalid-garbage-preference"},
377             {"req": "dumpconf", "pref": "uat:custom_http_header_fields"},
378         ), ())
379
380     def test_sharkd_req_dumpconf_all(self, check_sharkd_session):
381         check_sharkd_session((
382             {"req": "dumpconf"},
383         ), (
384             {"prefs": MatchObject({"tcp.check_checksum": {"b": 0}})},
385         ))
386
387     def test_sharkd_req_download_tls_secrets(self, check_sharkd_session, capture_file):
388         # XXX test download for eo: and rtp: too
389         check_sharkd_session((
390             {"req": "load", "file": capture_file('tls12-dsb.pcapng')},
391             {"req": "download", "token": "ssl-secrets"},
392         ), (
393             {"err": 0},
394             # TODO remove "RSA Session-ID:" support and support "CLIENT_RANDOM "... only
395             {"file": "keylog.txt", "mime": "text/plain",
396                 "data": MatchRegExp(r'UlNBIFNlc3Npb24tSUQ6.+')},
397         ))
398
399     def test_sharkd_req_bye(self, check_sharkd_session):
400         check_sharkd_session((
401             {"req": "bye"},
402         ), (
403         ))
404
405     def test_sharkd_bad_request(self, check_sharkd_session):
406         check_sharkd_session((
407             {"req": 1337},
408         ), (
409         ))
410
411     def test_sharkd_config(self, check_sharkd_session):
412         check_sharkd_session((
413             {"req": "setconf", "name": "uat:custom_http_header_fields",
414                 "value": "\"X-Header-Name\", \"Description\""},
415             {"req": "setconf", "name": "tcp.check_checksum", "value": "TRUE"},
416             {"req": "dumpconf", "pref": "tcp.check_checksum"},
417             {"req": "setconf", "name": "tcp.check_checksum", "value": "FALSE"},
418             {"req": "dumpconf", "pref": "tcp.check_checksum"},
419         ), (
420             # Check that the UAT preference is set. There is no way to query it
421             # (other than testing for side-effects in dissection).
422             {"err": 0},
423             {"err": 0},
424             {"prefs": {"tcp.check_checksum": {"b": 1}}},
425             {"err": 0},
426             {"prefs": {"tcp.check_checksum": {"b": 0}}},
427         ))
428
429     def test_sharkd_config_enum(self, check_sharkd_session):
430         '''Dump default enum preference value, change it and restore it.'''
431         check_sharkd_session((
432             {"req": "dumpconf", "pref": "wlan.ignore_wep"},
433             {"req": "setconf", "name": "wlan.ignore_wep", "value": "Yes - with IV"},
434             {"req": "dumpconf", "pref": "wlan.ignore_wep"},
435             {"req": "setconf", "name": "wlan.ignore_wep", "value": "No"},
436             {"req": "dumpconf", "pref": "wlan.ignore_wep"},
437         ), (
438             {"prefs": {"wlan.ignore_wep": {"e": [
439                 {"v": 0, "s": 1, "d": "No"},
440                 {"v": 1, "d": "Yes - without IV"},
441                 {"v": 2, "d": "Yes - with IV"}
442             ]}}},
443             {"err": 0},
444             {"prefs": {"wlan.ignore_wep": {"e": [
445                 {"v": 0, "d": "No"},
446                 {"v": 1, "d": "Yes - without IV"},
447                 {"v": 2, "s": 1, "d": "Yes - with IV"}
448             ]}}},
449             {"err": 0},
450             {"prefs": {"wlan.ignore_wep": {"e": [
451                 {"v": 0, "s": 1, "d": "No"},
452                 {"v": 1, "d": "Yes - without IV"},
453                 {"v": 2, "d": "Yes - with IV"}
454             ]}}},
455         ))