tap-follow: fix memory leak
[metze/wireshark/wip.git] / doc / extcap_example.py
1 #!/usr/bin/env python
2
3 # Copyright 2014 Roland Knall <rknall [AT] gmail.com>
4 #
5 # Wireshark - Network traffic analyzer
6 # By Gerald Combs <gerald@wireshark.org>
7 # Copyright 1998 Gerald Combs
8 #
9 # SPDX-License-Identifier: GPL-2.0-or-later
10 #
11
12 """
13 This is a generic example, which produces pcap packages every n seconds, and
14 is configurable via extcap options.
15
16 @note
17 {
18 To use this script on Windows, please generate an extcap_example.bat inside
19 the extcap folder, with the following content:
20
21 -------
22 @echo off
23 <Path to python interpreter> <Path to script file> %*
24 -------
25
26 Windows is not able to execute Python scripts directly, which also goes for all
27 other script-based formates beside VBScript
28 }
29
30 """
31
32 from __future__ import print_function
33
34 import os
35 import sys
36 import signal
37 import re
38 import argparse
39 import time
40 import struct
41 import binascii
42 from threading import Thread
43
44 ERROR_USAGE          = 0
45 ERROR_ARG            = 1
46 ERROR_INTERFACE      = 2
47 ERROR_FIFO           = 3
48 ERROR_DELAY          = 4
49
50 CTRL_CMD_INITIALIZED = 0
51 CTRL_CMD_SET         = 1
52 CTRL_CMD_ADD         = 2
53 CTRL_CMD_REMOVE      = 3
54 CTRL_CMD_ENABLE      = 4
55 CTRL_CMD_DISABLE     = 5
56 CTRL_CMD_STATUSBAR   = 6
57 CTRL_CMD_INFORMATION = 7
58 CTRL_CMD_WARNING     = 8
59 CTRL_CMD_ERROR       = 9
60
61 CTRL_ARG_MESSAGE     = 0
62 CTRL_ARG_DELAY       = 1
63 CTRL_ARG_VERIFY      = 2
64 CTRL_ARG_BUTTON      = 3
65 CTRL_ARG_HELP        = 4
66 CTRL_ARG_RESTORE     = 5
67 CTRL_ARG_LOGGER      = 6
68 CTRL_ARG_NONE        = 255
69
70 initialized = False
71 message = ''
72 delay = 0.0
73 verify = False
74 button = False
75 button_disabled = False
76
77 """
78 This code has been taken from http://stackoverflow.com/questions/5943249/python-argparse-and-controlling-overriding-the-exit-status-code - originally developed by Rob Cowie http://stackoverflow.com/users/46690/rob-cowie
79 """
80 class ArgumentParser(argparse.ArgumentParser):
81     def _get_action_from_name(self, name):
82         """Given a name, get the Action instance registered with this parser.
83         If only it were made available in the ArgumentError object. It is
84         passed as it's first arg...
85         """
86         container = self._actions
87         if name is None:
88             return None
89         for action in container:
90             if '/'.join(action.option_strings) == name:
91                 return action
92             elif action.metavar == name:
93                 return action
94             elif action.dest == name:
95                 return action
96
97     def error(self, message):
98         exc = sys.exc_info()[1]
99         if exc:
100             exc.argument = self._get_action_from_name(exc.argument_name)
101             raise exc
102         super(ArgumentParser, self).error(message)
103
104 #### EXTCAP FUNCTIONALITY
105
106 """@brief Extcap configuration
107 This method prints the extcap configuration, which will be picked up by the
108 interface in Wireshark to present a interface specific configuration for
109 this extcap plugin
110 """
111 def extcap_config(interface, option):
112     args = []
113     values = []
114
115     args.append ( (0, '--delay', 'Time delay', 'Time delay between packages', 'integer', '{range=1,15}{default=5}') )
116     args.append ( (1, '--message', 'Message', 'Package message content', 'string', '{required=true}{placeholder=Please enter a message here ...}') )
117     args.append ( (2, '--verify', 'Verify', 'Verify package content', 'boolflag', '{default=yes}') )
118     args.append ( (3, '--remote', 'Remote Channel', 'Remote Channel Selector', 'selector', '{reload=true}{placeholder=Load interfaces ...}'))
119     args.append ( (4, '--fake_ip', 'Fake IP Address', 'Use this ip address as sender', 'string', '{save=false}{validation=\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b}'))
120     args.append ( (5, '--ltest', 'Long Test', 'Long Test Value', 'long', '{default=123123123123123123}{group=Numeric Values}'))
121     args.append ( (6, '--d1test', 'Double 1 Test', 'Long Test Value', 'double', '{default=123.456}{group=Numeric Values}'))
122     args.append ( (7, '--d2test', 'Double 2 Test', 'Long Test Value', 'double', '{default= 123,456}{group=Numeric Values}'))
123     args.append ( (8, '--password', 'Password', 'Package message password', 'password', '') )
124     args.append ( (9, '--ts', 'Start Time', 'Capture start time', 'timestamp', '{group=Time / Log}') )
125     args.append ( (10, '--logfile', 'Log File Test', 'The Log File Test', 'fileselect', '{group=Time / Log}') )
126     args.append ( (11, '--radio', 'Radio Test', 'Radio Test Value', 'radio', '{group=Selection}') )
127     args.append ( (12, '--multi', 'MultiCheck Test', 'MultiCheck Test Value', 'multicheck', '{group=Selection}') )
128
129     if ( option == "remote" ):
130         values.append ( (3, "if1", "Remote Interface 1", "false" ) )
131         values.append ( (3, "if2", "Remote Interface 2", "true" ) )
132         values.append ( (3, "if3", "Remote Interface 3", "false" ) )
133         values.append ( (3, "if4", "Remote Interface 4", "false" ) )
134
135     if ( option == "radio" ):
136         values.append ( (11, "r1", "Radio Option 1", "false" ) )
137         values.append ( (11, "r2", "Radio Option 2", "false" ) )
138         values.append ( (11, "r3", "Radio Option 3", "true" ) )
139
140
141     if ( len(option) <= 0 ):
142         for arg in args:
143             print ("arg {number=%d}{call=%s}{display=%s}{tooltip=%s}{type=%s}%s" % arg)
144
145         values.append ( (3, "if1", "Remote1", "true" ) )
146         values.append ( (3, "if2", "Remote2", "false" ) )
147
148         values.append ( (11, "r1", "Radio1", "false" ) )
149         values.append ( (11, "r2", "Radio2", "true" ) )
150
151         values.append ( (12, "m1", "MultiCheck1", "false" ) )
152         values.append ( (12, "m2", "MultiCheck2", "false" ) )
153
154     for value in values:
155         print ("value {arg=%d}{value=%s}{display=%s}{default=%s}" % value)
156
157
158 def extcap_version():
159     print ("extcap {version=1.0}{help=http://www.wireshark.org}{display=Example extcap interface}")
160
161 def extcap_interfaces():
162     print ("extcap {version=1.0}{help=http://www.wireshark.org}{display=Example extcap interface}")
163     print ("interface {value=example1}{display=Example interface 1 for extcap}")
164     print ("interface {value=example2}{display=Example interface 2 for extcap}")
165     print ("control {number=%d}{type=string}{display=Message}{tooltip=Package message content. Must start with a capital letter.}{placeholder=Enter package message content here ...}{validation=^[A-Z]+}" % CTRL_ARG_MESSAGE)
166     print ("control {number=%d}{type=selector}{display=Time delay}{tooltip=Time delay between packages}" % CTRL_ARG_DELAY)
167     print ("control {number=%d}{type=boolean}{display=Verify}{default=true}{tooltip=Verify package content}" % CTRL_ARG_VERIFY)
168     print ("control {number=%d}{type=button}{display=Turn on}{tooltip=Turn on or off}" % CTRL_ARG_BUTTON)
169     print ("control {number=%d}{type=button}{role=help}{display=Help}{tooltip=Show help}" % CTRL_ARG_HELP)
170     print ("control {number=%d}{type=button}{role=restore}{display=Restore}{tooltip=Restore default values}" % CTRL_ARG_RESTORE)
171     print ("control {number=%d}{type=button}{role=logger}{display=Log}{tooltip=Show capture log}" % CTRL_ARG_LOGGER)
172     print ("value {control=%d}{value=1}{display=1}" % CTRL_ARG_DELAY)
173     print ("value {control=%d}{value=2}{display=2}" % CTRL_ARG_DELAY)
174     print ("value {control=%d}{value=3}{display=3}" % CTRL_ARG_DELAY)
175     print ("value {control=%d}{value=4}{display=4}" % CTRL_ARG_DELAY)
176     print ("value {control=%d}{value=5}{display=5}{default=true}" % CTRL_ARG_DELAY)
177     print ("value {control=%d}{value=60}{display=60}" % CTRL_ARG_DELAY)
178
179
180 def extcap_dlts(interface):
181     if ( interface == '1' ):
182         print ("dlt {number=147}{name=USER0}{display=Demo Implementation for Extcap}")
183     elif ( interface == '2' ):
184         print ("dlt {number=148}{name=USER1}{display=Demo Implementation for Extcap}")
185
186 def validate_capture_filter(capture_filter):
187     if capture_filter != "filter" and capture_filter != "valid":
188         print("Illegal capture filter")
189
190 """
191
192 ### FAKE DATA GENERATOR
193
194 Extcap capture routine
195  This routine simulates a capture by any kind of user defined device. The parameters
196  are user specified and must be handled by the extcap.
197
198  The data captured inside this routine is fake, so change this routine to present
199  your own input data, or call your own capture program via Popen for example. See
200
201  for more details.
202
203 """
204 def unsigned(n):
205     return int(n) & 0xFFFFFFFF
206
207 def pcap_fake_header():
208
209     header = bytearray()
210     header += struct.pack('<L', int ('a1b2c3d4', 16 ))
211     header += struct.pack('<H', unsigned(2) ) # Pcap Major Version
212     header += struct.pack('<H', unsigned(4) ) # Pcap Minor Version
213     header += struct.pack('<I', int(0)) # Timezone
214     header += struct.pack('<I', int(0)) # Accurancy of timestamps
215     header += struct.pack('<L', int ('0000ffff', 16 )) # Max Length of capture frame
216     header += struct.pack('<L', unsigned(1)) # Ethernet
217     return header
218
219 # Calculates and returns the IP checksum based on the given IP Header
220 def ip_checksum(iph):
221     #split into bytes
222     words = splitN(''.join(iph.split()),4)
223     csum = 0;
224     for word in words:
225         csum += int(word, base=16)
226     csum += (csum >> 16)
227     csum = csum & 0xFFFF ^ 0xFFFF
228     return csum
229
230 def pcap_fake_package ( message, fake_ip ):
231
232     pcap = bytearray()
233     #length = 14 bytes [ eth ] + 20 bytes [ ip ] + messagelength
234
235     caplength = len(message) + 14 + 20
236     timestamp = int(time.time())
237
238     pcap += struct.pack('<L', unsigned(timestamp ) ) # timestamp seconds
239     pcap += struct.pack('<L', 0x00  ) # timestamp nanoseconds
240     pcap += struct.pack('<L', unsigned(caplength ) ) # length captured
241     pcap += struct.pack('<L', unsigned(caplength ) ) # length in frame
242
243 # ETH
244     pcap += struct.pack('h', 0 ) # source mac
245     pcap += struct.pack('h', 0 ) # source mac
246     pcap += struct.pack('h', 0 ) # source mac
247     pcap += struct.pack('h', 0 ) # dest mac
248     pcap += struct.pack('h', 0 ) # dest mac
249     pcap += struct.pack('h', 0 ) # dest mac
250     pcap += struct.pack('<h', unsigned(8 )) # protocol (ip)
251
252 # IP
253     pcap += struct.pack('b', int ( '45', 16 )) # IP version
254     pcap += struct.pack('b', int ( '0', 16 )) #
255     pcap += struct.pack('>H', unsigned(len(message)+20) ) # length of data + payload
256     pcap += struct.pack('<H', int ( '0', 16 )) # Identification
257     pcap += struct.pack('b', int ( '40', 16 )) # Don't fragment
258     pcap += struct.pack('b', int ( '0', 16 )) # Fragment Offset
259     pcap += struct.pack('b', int ( '40', 16 ))
260     pcap += struct.pack('B', 0xFE ) # Protocol (2 = unspecified)
261     pcap += struct.pack('<H', int ( '0000', 16 )) # Checksum
262
263     parts = fake_ip.split('.')
264     ipadr = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
265     pcap += struct.pack('>L', ipadr ) # Source IP
266     pcap += struct.pack('>L', int ( '7F000001', 16 )) # Dest IP
267
268     pcap += message
269     return pcap
270
271 def control_read(fn):
272     try:
273         header = fn.read(6)
274         sp, _, length, arg, typ = struct.unpack('>sBHBB', header)
275         if length > 2:
276             payload = fn.read(length - 2)
277         else:
278             payload = ''
279         return arg, typ, payload
280     except:
281         return None, None, None
282
283 def control_read_thread(control_in, fn_out):
284     global initialized, message, delay, verify, button, button_disabled
285     with open(control_in, 'rb', 0 ) as fn:
286         arg = 0
287         while arg != None:
288             arg, typ, payload = control_read(fn)
289             log = ''
290             if typ == CTRL_CMD_INITIALIZED:
291                 initialized = True
292             elif arg == CTRL_ARG_MESSAGE:
293                 message = payload
294                 log = "Message = " + payload
295             elif arg == CTRL_ARG_DELAY:
296                 delay = float(payload)
297                 log = "Time delay = " + payload
298             elif arg == CTRL_ARG_VERIFY:
299                 # Only read this after initialized
300                 if initialized:
301                     verify = (payload[0] != '\0')
302                     log = "Verify = " + str(verify)
303                     control_write(fn_out, CTRL_ARG_NONE, CTRL_CMD_STATUSBAR, "Verify changed")
304             elif arg == CTRL_ARG_BUTTON:
305                 control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_DISABLE, "")
306                 button_disabled = True
307                 if button == True:
308                     control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_SET, "Turn on")
309                     button = False
310                     log = "Button turned off"
311                 else:
312                     control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_SET, "Turn off")
313                     button = True
314                     log = "Button turned on"
315
316             if len(log) > 0:
317                 control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_ADD, log + "\n")
318
319 def control_write(fn, arg, typ, payload):
320     packet = bytearray()
321     packet += struct.pack('>sBHBB', b'T', 0, len(payload) + 2, arg, typ)
322     if sys.version_info[0] >= 3 and isinstance(payload, str):
323         packet += payload.encode('utf-8')
324     else:
325         packet += payload
326     fn.write(packet)
327
328 def control_write_defaults(fn_out):
329     global initialized, message, delay, verify
330
331     while not initialized:
332         time.sleep(.1)  # Wait for initial control values
333
334     # Write startup configuration to Toolbar controls
335     control_write(fn_out, CTRL_ARG_MESSAGE, CTRL_CMD_SET, message)
336     control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_SET, str(int(delay)))
337     control_write(fn_out, CTRL_ARG_VERIFY, CTRL_CMD_SET, struct.pack('B', verify))
338
339     for i in range(1,16):
340         item = bytearray()
341         item += str(i) + struct.pack('B', 0) + str(i) + " sec"
342         control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_ADD, item)
343
344     control_write(fn_out, CTRL_ARG_DELAY, CTRL_CMD_REMOVE, str(60))
345
346 def extcap_capture(interface, fifo, control_in, control_out, in_delay, in_verify, in_message, remote, fake_ip):
347     global message, delay, verify, button_disabled
348     delay = in_delay if in_delay != 0 else 5
349     message = in_message
350     verify = in_verify
351     counter = 1
352     fn_out = None
353
354     with open(fifo, 'wb', 0 ) as fh:
355         fh.write (pcap_fake_header())
356
357         if control_out != None:
358             fn_out = open(control_out, 'wb', 0)
359             control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_SET, "Log started at " + time.strftime("%c") + "\n")
360
361         if control_in != None:
362             # Start reading thread
363             thread = Thread(target = control_read_thread, args = (control_in, fn_out))
364             thread.start()
365
366         if fn_out != None:
367             control_write_defaults(fn_out)
368
369         while True:
370             if fn_out != None:
371                 log = "Received packet #" + str(counter) + "\n"
372                 control_write(fn_out, CTRL_ARG_LOGGER, CTRL_CMD_ADD, log)
373                 counter = counter + 1
374
375                 if button_disabled == True:
376                     control_write(fn_out, CTRL_ARG_BUTTON, CTRL_CMD_ENABLE, "")
377                     control_write(fn_out, CTRL_ARG_NONE, CTRL_CMD_INFORMATION, "Turn action finished.")
378                     button_disabled = False
379
380             out = ("%s|%04X%s|%s" % ( remote.strip(), len(message), message, verify )).encode("utf8")
381             fh.write (pcap_fake_package(out, fake_ip))
382             time.sleep(delay)
383
384     thread.join()
385     if fn_out != None:
386         fn_out.close()
387
388 def extcap_close_fifo(fifo):
389     # This is apparently needed to workaround an issue on Windows/macOS
390     # where the message cannot be read. (really?)
391     fh = open(fifo, 'wb', 0 )
392     fh.close()
393
394 ####
395
396 def usage():
397     print ( "Usage: %s <--extcap-interfaces | --extcap-dlts | --extcap-interface | --extcap-config | --capture | --extcap-capture-filter | --fifo>" % sys.argv[0] )
398
399 if __name__ == '__main__':
400     interface = ""
401     option = ""
402
403     # Capture options
404     delay = 0
405     message = ""
406     fake_ip = ""
407     ts = 0
408
409     parser = ArgumentParser(
410             prog="Extcap Example",
411             description="Extcap example program for python"
412             )
413
414     # Extcap Arguments
415     parser.add_argument("--capture", help="Start the capture routine", action="store_true" )
416     parser.add_argument("--extcap-interfaces", help="Provide a list of interfaces to capture from", action="store_true")
417     parser.add_argument("--extcap-interface", help="Provide the interface to capture from")
418     parser.add_argument("--extcap-dlts", help="Provide a list of dlts for the given interface", action="store_true")
419     parser.add_argument("--extcap-config", help="Provide a list of configurations for the given interface", action="store_true")
420     parser.add_argument("--extcap-capture-filter", help="Used together with capture to provide a capture filter")
421     parser.add_argument("--fifo", help="Use together with capture to provide the fifo to dump data to")
422     parser.add_argument("--extcap-control-in", help="Used to get control messages from toolbar")
423     parser.add_argument("--extcap-control-out", help="Used to send control messages to toolbar")
424     parser.add_argument("--extcap-version", help="Shows the version of this utility", nargs='?', default="")
425     parser.add_argument("--extcap-reload-option", help="Reload elements for the given option")
426
427     # Interface Arguments
428     parser.add_argument("--verify", help="Demonstrates a verification bool flag", action="store_true" )
429     parser.add_argument("--delay", help="Demonstrates an integer variable", type=int, default=0, choices=[0, 1, 2, 3, 4, 5, 6] )
430     parser.add_argument("--remote", help="Demonstrates a selector choice", default="if1", choices=["if1", "if2", "if3", "if4"] )
431     parser.add_argument("--message", help="Demonstrates string variable", nargs='?', default="" )
432     parser.add_argument("--fake_ip", help="Add a fake sender IP adress", nargs='?', default="127.0.0.1" )
433     parser.add_argument("--ts", help="Capture start time", action="store_true" )
434
435     try:
436         args, unknown = parser.parse_known_args()
437     except argparse.ArgumentError as exc:
438         print( "%s: %s" % ( exc.argument.dest, exc.message ), file=sys.stderr)
439         fifo_found = 0
440         fifo = ""
441         for arg in sys.argv:
442             if (arg == "--fifo" or arg == "--extcap-fifo") :
443                 fifo_found = 1
444             elif ( fifo_found == 1 ):
445                 fifo = arg
446                 break
447         extcap_close_fifo(fifo)
448         sys.exit(ERROR_ARG)
449
450     if ( len(sys.argv) <= 1 ):
451         parser.exit("No arguments given!")
452
453     if ( args.extcap_version and not args.extcap_interfaces ):
454         extcap_version()
455         sys.exit(0)
456
457     if ( args.extcap_interfaces == False and args.extcap_interface == None ):
458         parser.exit("An interface must be provided or the selection must be displayed")
459     if ( args.extcap_capture_filter and not args.capture ):
460         validate_capture_filter(args.extcap_capture_filter)
461         sys.exit(0)
462
463     if ( args.extcap_interfaces == True or args.extcap_interface == None ):
464         extcap_interfaces()
465         sys.exit(0)
466
467     if ( len(unknown) > 1 ):
468         print("Extcap Example %d unknown arguments given" % len(unknown) )
469
470     m = re.match ( 'example(\d+)', args.extcap_interface )
471     if not m:
472         sys.exit(ERROR_INTERFACE)
473     interface = m.group(1)
474
475     message = args.message
476     if ( args.message == None or len(args.message) == 0 ):
477         message = "Extcap Test"
478
479     fake_ip = args.fake_ip
480     if ( args.fake_ip == None or len(args.fake_ip) < 7 or len(args.fake_ip.split('.')) != 4 ):
481         fake_ip = "127.0.0.1"
482
483     ts = args.ts
484
485     if ( args.extcap_reload_option and len(args.extcap_reload_option) > 0 ):
486         option = args.extcap_reload_option
487
488     if args.extcap_config:
489         extcap_config(interface, option)
490     elif args.extcap_dlts:
491         extcap_dlts(interface)
492     elif args.capture:
493         if args.fifo is None:
494             sys.exit(ERROR_FIFO)
495         # The following code demonstrates error management with extcap
496         if args.delay > 5:
497             print("Value for delay [%d] too high" % args.delay, file=sys.stderr)
498             extcap_close_fifo(args.fifo)
499             sys.exit(ERROR_DELAY)
500
501         try:
502             extcap_capture(interface, args.fifo, args.extcap_control_in, args.extcap_control_out, args.delay, args.verify, message, args.remote, fake_ip)
503         except KeyboardInterrupt:
504             pass
505     else:
506         usage()
507         sys.exit(ERROR_USAGE)