2 # -*- coding: utf-8 -*-
4 # By Gerald Combs <gerald@wireshark.org>
6 # Copyright (c) 2018 Peter Wu <peter@lekensteyn.nl>
8 # SPDX-License-Identifier: GPL-2.0-or-later
17 from matchers import *
20 @fixtures.fixture(scope='session')
21 def cmd_sharkd(program):
22 return program('sharkd')
26 def run_sharkd_session(cmd_sharkd, request):
27 self = request.instance
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)
35 self.assertIn('Hello in child.', sharkd_proc.stderr_str)
38 for line in sharkd_proc.stdout_str.splitlines():
43 jdata = json.loads(line)
44 except json.JSONDecodeError:
45 self.fail('Invalid JSON: %r' % line)
48 return run_sharkd_session_real
52 def check_sharkd_session(run_sharkd_session, request):
53 self = request.instance
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
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')},
72 def test_sharkd_req_status_no_pcap(self, check_sharkd_session):
73 check_sharkd_session((
76 {"frames": 0, "duration": 0.0},
79 def test_sharkd_req_status(self, check_sharkd_session, capture_file):
80 check_sharkd_session((
81 {"req": "load", "file": capture_file('dhcp.pcap')},
85 {"frames": 4, "duration": 0.070345000,
86 "filename": "dhcp.pcap", "filesize": 1400},
89 def test_sharkd_req_analyse(self, check_sharkd_session, capture_file):
90 check_sharkd_session((
91 {"req": "load", "file": capture_file('dhcp.pcap')},
95 {"frames": 4, "protocols": ["frame", "eth", "ethertype", "ip", "udp",
96 "dhcp"], "first": 1102274184.317452908, "last": 1102274184.387798071},
99 def test_sharkd_req_info(self, check_sharkd_session):
100 matchTapNameList = MatchList(
101 {"tap": MatchAny(str), "name": MatchAny(str)})
102 check_sharkd_session((
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,
121 def test_sharkd_req_check(self, check_sharkd_session, capture_file):
122 check_sharkd_session((
123 {"req": "load", "file": capture_file('dhcp.pcap')},
125 {"req": "check", "filter": "garbage filter"},
126 {"req": "check", "field": "garbage field"},
127 {"req": "check", "filter": "ip", "field": "ip"},
131 {"err": 0, "filter": '"filter" was unexpected in this context.'},
132 {"err": 0, "field": "notfound"},
133 {"err": 0, "filter": "ok", "field": "ok"},
136 def test_sharkd_req_complete_field(self, check_sharkd_session):
137 check_sharkd_session((
139 {"req": "complete", "field": "frame.le"},
140 {"req": "complete", "field": "garbage.nothing.matches"},
143 {"err": 0, "field": MatchList(
144 {"f": "frame.len", "t": 7, "n": "Frame length on the wire"}, match_element=any)},
145 {"err": 0, "field": []},
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"},
153 {"err": 0, "pref": MatchList(
154 {"f": "tcp.check_checksum", "d": "Validate the TCP checksum if possible"}, match_element=any)},
155 {"err": 0, "pref": []},
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')},
166 "c": MatchList(MatchAny(str)),
167 "num": MatchAny(int),
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')},
179 {"req": "tap", "tap0": "garbage tap"},
184 def test_sharkd_req_tap(self, check_sharkd_session, capture_file):
185 check_sharkd_session((
186 {"req": "load", "file": capture_file('dhcp.pcap')},
188 {"req": "tap", "tap0": "conv:Ethernet", "tap1": "endpt:TCP"},
198 "geoip": MatchAny(bool),
202 "tap": "conv:Ethernet",
205 "geoip": MatchAny(bool),
208 "saddr": MatchAny(str),
209 "daddr": "Broadcast",
216 "filter": "eth.addr==00:0b:82:01:fc:42 && eth.addr==ff:ff:ff:ff:ff:ff",
219 "saddr": MatchAny(str),
220 "daddr": MatchAny(str),
227 "filter": "eth.addr==00:08:74:ad:f1:9b && eth.addr==00:0b:82:01:fc:42",
231 # XXX remove the last null element, it is not part of the interface.
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')},
242 {"req": "follow", "follow": "garbage follow", "filter": "ip"},
243 {"req": "follow", "follow": "HTTP", "filter": "garbage filter"},
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"},
254 {"err": 0, "shost": "NONE", "sport": "0", "sbytes": 0,
255 "chost": "NONE", "cport": "0", "cbytes": 0},
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"},
265 "shost": "255.255.255.255", "sport": "67", "sbytes": 272,
266 "chost": "0.0.0.0", "cport": "68", "cbytes": 0,
268 {"n": 1, "d": MatchRegExp(r'AQEGAAAAPR0A[a-zA-Z0-9]{330}AANwQBAwYq/wAAAAAAAAA=')}]},
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')},
275 {"req": "iograph", "graph0": "garbage graph name"},
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"},
290 {"iograph": [{"items": [308.000000]}]},
291 {"iograph": [{"items": [4.000000]}, {"items": [1312.000000]}]},
293 {"errmsg": 'Filter "garbage filter" is invalid - "filter" was unexpected in this context.'}]},
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"},
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"},
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},
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},
326 # XXX remove the first 0 element, it is not part of the interface.
327 {"err": 0, "fol": [0, ["UDP", "udp.stream eq 1"]]},
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},
339 "l": "Dynamic Host Configuration Protocol (Offer)",
344 "l": "Padding: 000000000000000000000000000000000000000000000000…",
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'
349 }, match_element=any), # match one element from 'tree'
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},
364 {"err": 0, "comment": "foo\nbar", "fol": MatchAny(list)},
367 def test_sharkd_req_setconf_bad(self, check_sharkd_session):
368 check_sharkd_session((
369 {"req": "setconf", "name": "uat:garbage-pref", "value": "\"\""},
371 {"err": 1, "errmsg": "Unknown preference"},
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"},
380 def test_sharkd_req_dumpconf_all(self, check_sharkd_session):
381 check_sharkd_session((
384 {"prefs": MatchObject({"tcp.check_checksum": {"b": 0}})},
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"},
394 # TODO remove "RSA Session-ID:" support and support "CLIENT_RANDOM "... only
395 {"file": "keylog.txt", "mime": "text/plain",
396 "data": MatchRegExp(r'UlNBIFNlc3Npb24tSUQ6.+')},
399 def test_sharkd_req_bye(self, check_sharkd_session):
400 check_sharkd_session((
405 def test_sharkd_bad_request(self, check_sharkd_session):
406 check_sharkd_session((
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"},
420 # Check that the UAT preference is set. There is no way to query it
421 # (other than testing for side-effects in dissection).
424 {"prefs": {"tcp.check_checksum": {"b": 1}}},
426 {"prefs": {"tcp.check_checksum": {"b": 0}}},
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"},
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"}
444 {"prefs": {"wlan.ignore_wep": {"e": [
446 {"v": 1, "d": "Yes - without IV"},
447 {"v": 2, "s": 1, "d": "Yes - with IV"}
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"}