├── COPYING ├── MANIFEST.in ├── Makefile ├── README ├── README.md ├── configure ├── configure.ac ├── docs ├── Makefile ├── conf.py ├── index.rst └── modules.rst ├── examples ├── capture_and_dissect.py ├── create_cube_events.py ├── list_interfaces.py ├── show_fields.py ├── show_protocols.py └── show_values.py ├── requirements.txt ├── setup.py ├── tox.ini └── wirepy ├── __init__.py ├── compat.py ├── lib ├── __init__.py ├── cdata.py ├── cdef.py ├── column.py ├── dfilter.py ├── dumpcap.py ├── epan.py ├── ftypes.py ├── glib2.py ├── prefs.py ├── timestamp.py ├── wireshark.py ├── wsutil.py └── wtap.py ├── platform.py.in └── tests ├── __init__.py ├── sample_files ├── README └── http.cap.gz ├── test_cdata.py ├── test_column.py ├── test_dfilter.py ├── test_dumpcap.py ├── test_epan.py ├── test_ftypes.py ├── test_glib2.py ├── test_prefs.py ├── test_timestamp.py ├── test_wsutil.py └── test_wtap.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include configure.ac 2 | include configure 3 | include install-sh 4 | include missing 5 | include Makefile 6 | include wirepy/platform.py.in 7 | include README 8 | include INSTALL 9 | include tox.ini 10 | include requirements.txt 11 | 12 | include wirepy/tests/sample_files/*.gz 13 | include wirepy/tests/sample_files/README 14 | 15 | prune wirepy/platform.py 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON=python3 2 | 3 | .PHONY: clean coverage nosetests tox sdist 4 | .DEFAULT: nosetests 5 | 6 | nosetests: 7 | $(PYTHON) -c "import nose; nose.run()" --where . wirepy 8 | 9 | coverage: 10 | $(PYTHON) -c "import nose; nose.run()" --where . wirepy --with-coverage --cover-html --cover-package=wirepy --cover-branches --cover-inclusive --cover-erase 11 | 12 | clean: 13 | -rm -r .tox/ 14 | -rm -r build/ 15 | -rm -r cover/ 16 | -rm -r dist/ 17 | -rm -r wirepy.egg-info/ 18 | -rm MANIFEST 19 | -rm `find . -name \*.py[co]` 20 | -rm -r `find . -name __pycache__` 21 | -rm -r autom4te.cache 22 | -rm aclocal.m4 23 | -rm config.{status,log} 24 | -cd docs && make clean 25 | 26 | tox: 27 | tox 28 | 29 | sdist: 30 | $(PYTHON) setup.py sdist 31 | 32 | README: docs/index.rst 33 | cd docs && make text && cp _build/text/index.txt ../README 34 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | wirepy 3 | ****** 4 | 5 | *Wirepy* aims to remedy the disastrous situation of packet-dissection- 6 | libraries available to the Python programming language. It is a 7 | foreign function interface to use Wireshark within Python as 8 | implemented by CPython and PyPy. 9 | 10 | The currently available options are either painfully slow or lack 11 | features. Wireshark provides support for more than 1.300 protocols, 12 | more than 125.000 fields within those protocols and more than 13 | 1.500.000 defined values and is actively maintained. 14 | 15 | Note: This library is created out of pure necessity. I dont' know know 16 | where it is headed or even feasible to create a direct binding to 17 | "libwireshark". The best current source of documentation are the 18 | unittests. 19 | 20 | * Module reference 21 | * "cdata" Module 22 | * "column" Module 23 | * "dfilter" Module 24 | * "dumpcap" Module 25 | * "epan" Module 26 | * "ftypes" Module 27 | * "glib2" Module 28 | * "prefs" Module 29 | * "timestamp" Module 30 | * "wireshark" Module 31 | * "wsutil" Module 32 | * "wtap" Module 33 | 34 | Installation 35 | ============ 36 | 37 | 38 | Requirements 39 | ------------ 40 | 41 | * CPython 3.x or later 42 | 43 | * CFFI 0.6 or later 44 | 45 | * Wireshark 1.10 or later 46 | 47 | * GLib2 2.16.0 or later 48 | 49 | * nose and tox are used for testing 50 | 51 | 52 | Configuring Wireshark 53 | --------------------- 54 | 55 | * If you are using a Linux distribution, CPython-, Wireshark and their 56 | headers can be usually be installed from the package repository 57 | (e.g. via yum). 58 | 59 | * Otherwise you may configure and build a **minimal** Wireshark 60 | library like this: 61 | 62 | ./configure -q --prefix=$HOME/wireshark --disable-wireshark --disable-packet-editor --disable-editcap --disable-capinfos --disable-mergecap --disable-text2pcap --disable-dftest --disable-airpcap --disable-rawshark --without-lua 63 | make -sj9 64 | make install 65 | 66 | 67 | Configuring wirepy 68 | ------------------ 69 | 70 | 1. Run "./configure" to configure *Wirepy*'s sourcecode: 71 | 72 | * Running "./configure" as it is should work if you have wireshark 73 | installed through *pkg-config*. 74 | 75 | * **Otherwise** you need to specify the paths to wireshark's and 76 | glib's header files yourself. You may also want to use a locally 77 | installed version of wireshark. The command may look something 78 | like this: 79 | 80 | DEPS_CFLAGS="-I/path/to/wireshark-headers -I/path/to/glib-2.0-headers" DEPS_LIBS="-L/path/to/wireshark/lib" ./configure 81 | 82 | Executing may look like this: 83 | 84 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/wireshark/lib PATH=$PATH:/path/to/wireshark/bin make 85 | 86 | 2. Take a look at the "Makefile" and use "make". 87 | 88 | 89 | Development 90 | =========== 91 | 92 | *Wirepy* uses CFFI to create an interface to "libwireshark", 93 | "libwsutil" and "libwiretap". Class-based representations of the 94 | defined C-structs and -types are used to bind behavior, state and 95 | documentation. Instead of returning error values, all functions raise 96 | exceptions of appropriate type. Memory is handled by python's garbage 97 | collector most of the time. 98 | 99 | The entire wireshark-interface can be found in "/lib"; one may need 100 | special knowledge about how to use classes there. Once things quiet 101 | down in "/lib", a more pythonic API is to be created outside of 102 | "/lib". 103 | 104 | * What (at least in part) works: 105 | 106 | * Enumerating live interfaces and their capabilities. 107 | 108 | * Reading packets from live interfaces. 109 | 110 | * Reading packet dumps using the wiretap library. 111 | 112 | * Compiling and using display-filters to filter the resulting frame 113 | data. 114 | 115 | * Inspection of the resulting protocol-tree ("epan.ProtoTree"), 116 | 117 | * inspection of it's fields ("ftypes.FieldType"). 118 | 119 | * and their values ("epan.FieldValue"). 120 | 121 | * Working with columns, including "COL_CUSTOM". 122 | 123 | * What does not: 124 | 125 | * Putting it all together. 126 | 127 | * We probably want to create class-based representations of 128 | protocols, fields and their known values; one might create a 129 | class factory that uses the functions from "/lib" to create 130 | classes for protocols as they pop into existence in a proto-tree 131 | and keep a weakref to those. 132 | 133 | * It should be fairly easy to use the above for class-based 134 | comparision of values and create a simple compiler for display- 135 | filter strings (e.g. 136 | ""DisplayFilter(IP.proto==IP.proto.IPv4)""). 137 | 138 | * A "FieldType" should have it's own subclass that is able to 139 | interpret common python objects, preserving it's type as closely 140 | as possible. 141 | 142 | * A "INT8" should do arithmetic mod 2**8 143 | 144 | * A "IPv4" or "IPv6" may take values from the "ipaddr"-module 145 | 146 | * etc 147 | 148 | This should live outside of "/lib". 149 | 150 | * Writing packet dumps through "wtap_dump...". 151 | 152 | * Taps and the other ~95% of the more useful functions of wireshark. 153 | 154 | * Plugins will not load because they expect the symbols from 155 | "libwireshark" in the global namespace. We hack this situation by 156 | flooding the namespace with a call to dlopen(). 157 | 158 | * A backport to Python 2.x (using a compat module) should be easy. 159 | 160 | * To be considered: 161 | 162 | * There are many ways in which "libwireshark" handles memory 163 | allocation. From within Python, everything should be garbage- 164 | collected though; 165 | 166 | * There are many ways in which "libwireshark" handles memory 167 | deallocation. Once some or the other function is called or state 168 | is reached, memory represented by reachable objects becomes 169 | invalid garbage. 170 | 171 | * The raw C-API very much expects C-like behavior from it's user; 172 | there are many de-facto global states and carry-on-your-back 173 | variables. Hide those 174 | 175 | 176 | Contact 177 | ======= 178 | 179 | Via lukas.lueg@gmail.com. Please use github to report problems and 180 | submit comments (which are very welcome). Patches should conform to 181 | **PEP 8** and have appropriate unittests attached. 182 | 183 | 184 | Indices and tables 185 | ================== 186 | 187 | * *Index* 188 | 189 | * *Module Index* 190 | 191 | * *Search Page* 192 | 193 | Generated January 21, 2014. 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wirepy 2 | ====== 3 | 4 | A foreign function interface to use Wireshark within Python 5 | 6 | Documentation at http://wirepy.readthedocs.org/ 7 | 8 | 9 | Example: 10 | ```python 11 | '''Capture traffic from all interfaces for 30 seconds and look for HTTP traffic. 12 | 13 | Note the use of column.Type.CUSTOM in conjunction with field "http.user_agent". 14 | The dictionary created in _show() will contain a string like 15 | "Mozilla/5.0 (X11; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0" when a 16 | HTTP request is made on the network. We get to this without ever having to really 17 | look at any of the packets by ourselves. 18 | ''' 19 | 20 | import pprint 21 | from wirepy.lib import column, dfilter, dumpcap, epan, wtap, prefs 22 | 23 | # Some required setup 24 | epan.epan_init() 25 | prefs.read_prefs() 26 | epan.init_dissection() 27 | 28 | # Some general information we are interested in for every packet 29 | CINFO = column.ColumnInfo([column.Format(column.Type.NUMBER, 30 | title='Packet number'), 31 | column.Format(column.Type.INFO, title='Info'), 32 | column.Format(column.Type.PACKET_LENGTH, 33 | title='Length'), 34 | column.Format(column.Type.EXPERT, title='Expert'), 35 | column.Format(column.Type.PROTOCOL, 36 | title='The protocol'), 37 | column.Format(column.Type.ABS_TIME, 38 | title='The time'), 39 | column.Format(column.Type.UNRES_DST, 40 | title='Destination'), 41 | column.Format(column.Type.CUSTOM, 42 | title='The user agent', 43 | custom_field='http.user_agent')]) 44 | 45 | # We will only be interested in http traffic 46 | FILTER_HTTP = dfilter.DisplayFilter('http') 47 | 48 | 49 | def _display_tree_fi(node, lvl=0): 50 | '''Display FieldInfo representations''' 51 | fi = node.field_info 52 | if fi is not None: 53 | # Skip text fields 54 | if fi.hfinfo.abbrev == 'text': 55 | return 56 | print((' ' * lvl) + fi.hfinfo.abbrev + ": " + fi.rep) 57 | # Depth-first into the protocol-tree 58 | if node.first_child is not None: 59 | _display_tree_fi(node.first_child, lvl + 1) 60 | if node.next is not None: 61 | _display_tree_fi(node.next, lvl) 62 | 63 | 64 | def _show(wt, frame): 65 | '''Dissect a single frame using the wtap's current state''' 66 | edt = epan.Dissect() 67 | edt.prime_dfilter(FILTER_HTTP) 68 | edt.prime_cinfo(CINFO) 69 | edt.run(wt, frame, CINFO) 70 | if not FILTER_HTTP.apply_edt(edt): 71 | # No http traffic in the packet, throw it away 72 | return 73 | edt.fill_in_columns() 74 | _display_tree_fi(edt.tree) 75 | pprint.pprint(dict(((CINFO.titles[i], 76 | epan.iface.string(CINFO._col_datap[i])) 77 | for i in range(len(CINFO))))) 78 | 79 | 80 | def _iter(): 81 | with dumpcap.CaptureSession(interfaces=('any', ), 82 | autostop_duration=30) as cap: 83 | try: 84 | event_type, event_msg = cap.wait_for_event(timeout=10) 85 | if event_type != cap.SP_FILE: 86 | # pipe out of sync 87 | raise RuntimeError('Unexpected event from dumpcap') 88 | except dumpcap.NoEvents: 89 | # Child did not start capturing... 90 | errmsg = ('Dumpcap did not start receiving packets for some time.' 91 | ' Giving up.') 92 | raise RuntimeError(errmsg) 93 | # Received the first file dumpcap is writing to. Since we didnt request 94 | # a ringbuffer, dumpcap will write to only one file for the entire 95 | # session. 96 | fname = event_msg 97 | while True: 98 | with wtap.WTAP.open_offline(fname) as wt: 99 | frameiter = iter(wt) 100 | # Started to read from the current savefile. Now wait for 101 | # dumpcap to report about written packets. 102 | for event_type, event_msg in iter(cap): 103 | if event_type == cap.SP_PACKET_COUNT: 104 | # Dissect as many packets as have been written 105 | for i in range(event_msg): 106 | wt.clear_eof() 107 | try: 108 | frame = next(frameiter) 109 | except StopIteration: 110 | errmsg = ('Dumpcap reported new packets, but' 111 | ' the capture-file does not have' 112 | ' them.') 113 | raise RuntimeError(errmsg) 114 | yield wt, frame 115 | elif event_type == cap.SP_FILE: 116 | # A new savefile has been created, stop reading from 117 | # the current file. 118 | fname = event_msg 119 | break 120 | else: 121 | # The iterator on cap reaches this point if there are 122 | # no more events from dumpcap - capturing has stopped, 123 | # quit the loop 124 | break 125 | 126 | 127 | def read(): 128 | for wt, frame in _iter(): 129 | _show(wt, frame) 130 | 131 | 132 | if __name__ == '__main__': 133 | read() 134 | ``` 135 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.60) 2 | AC_INIT(wirepy, 0.1) 3 | 4 | AC_LANG(C) 5 | AC_PROG_CC 6 | 7 | # required to read va_list 8 | AC_FUNC_ALLOCA() 9 | 10 | AC_CHECK_HEADERS([stdarg.h]) 11 | 12 | # Check for wireshark 13 | PKG_CHECK_MODULES([DEPS], [wireshark >= 1.10 glib-2.0 >= 2.16]) 14 | CFLAGS="$CFLAGS $DEPS_CFLAGS" 15 | CPPFLAGS="$CPPFLAGS $DEPS_CFLAGS" 16 | LDFLAGS="$LDFLAGS $DEPS_LIBS" 17 | 18 | # Check glib2 19 | AC_CHECK_HEADERS([glib.h], 20 | [], 21 | [AC_MSG_ERROR(["Glib2 headers not found. Check config.log"])]) 22 | 23 | # g[u]int64 are already defined as "long" instead of "long long" on 64bit 24 | AC_CHECK_SIZEOF([long]) 25 | AC_CHECK_SIZEOF([long long]) 26 | AC_CHECK_SIZEOF([gint64], [], [[#include ]]) 27 | case $ac_cv_sizeof_gint64 in 28 | $ac_cv_sizeof_long) GINT64TYPE="long";; 29 | $ac_cv_sizeof_long_long) GINT64TYPE="long long";; 30 | esac 31 | if test -z "$GINT64TYPE"; then 32 | AC_MSG_ERROR([Failed to detect size of gint64]) 33 | fi 34 | AC_SUBST([GINT64TYPE]) 35 | 36 | # Check wireshark 37 | AC_CHECK_HEADERS([config.h], 38 | [], 39 | [AC_MSG_ERROR(["Wireshark's config.h not found. Check config.log"])]) 40 | AC_CHECK_HEADERS([wiretap/wtap.h register.h epan/epan.h \ 41 | epan/ipv4.h epan/ipv6-utils.h epan/guid-utils.h epan/tvbuff.h \ 42 | epan/nstime.h epan/dfilter/drange.h epan/ftypes/ftypes.h \ 43 | epan/proto.h wsutil/privileges.h epan/prefs.h], 44 | [], 45 | [AC_MSG_ERROR(['Wireshark headers not found. Check config.log'])], 46 | [[#ifdef HAVE_STDARG_H 47 | #include 48 | #endif 49 | #ifdef HAVE_GLIB_H 50 | #include 51 | #endif 52 | #ifdef HAVE_CONFIG_H 53 | #include 54 | #endif 55 | ]]) 56 | AC_SUBST([DEPS_CFLAGS]) 57 | 58 | # Check wireshark libraries 59 | AC_CHECK_LIB(wireshark, epan_get_version, 60 | [], 61 | [AC_MSG_ERROR(['Wireshark libraries not found. Check config.log'])]) 62 | AC_CHECK_LIB(wiretap, wtap_read, 63 | [], 64 | [AC_MSG_ERROR(['Wiretap was not found. Check config.log'])]) 65 | AC_CHECK_LIB(wsutil, init_process_policies, 66 | [], 67 | [AC_MSG_ERROR(['WSUtil was not found. Check config.log'])]) 68 | AC_SUBST([DEPS_LIBS]) 69 | 70 | AC_CONFIG_FILES([wirepy/platform.py]) 71 | 72 | AC_MSG_RESULT([ 73 | ########################################## 74 | # Building with following configuration 75 | # CFLAGS: $DEPS_CFLAGS 76 | # LIBS: $DEPS_LIBS 77 | ########################################## 78 | ]) 79 | 80 | AC_OUTPUT() 81 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build-3.3 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wirepy.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wirepy.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/wirepy" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wirepy" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | 8 | class Mock(object): 9 | def __init__(self, *args, **kwargs): 10 | pass 11 | 12 | def __call__(self, *args, **kwargs): 13 | return Mock() 14 | 15 | @classmethod 16 | def __getattr__(cls, name): 17 | if name in ('__file__', '__path__'): 18 | return '/dev/null' 19 | elif name[0] == name[0].upper(): 20 | mockType = type(name, (cls, ), {}) 21 | mockType.__module__ = __name__ 22 | return mockType 23 | else: 24 | return Mock() 25 | 26 | 27 | for mod_name in ('cffi', 'wirepy.platform'): 28 | sys.modules[mod_name] = Mock() 29 | 30 | # If extensions (or modules to document with autodoc) are in another directory, 31 | # add these directories to sys.path here. If the directory is relative to the 32 | # documentation root, use os.path.abspath to make it absolute, like shown here. 33 | #sys.path.insert(0, os.path.abspath('.')) 34 | 35 | sys.path.insert(0, os.path.abspath('..')) 36 | 37 | import wirepy 38 | wirepy.platform = Mock() 39 | 40 | # -- General configuration ----------------------------------------------------- 41 | 42 | # If your documentation needs a minimal Sphinx version, state it here. 43 | needs_sphinx = '1.0' 44 | autodoc_member_order = 'groupwise' 45 | autodoc_default_flags = ['members', 'special-members', 'show-inheritance'] 46 | 47 | 48 | # Add any Sphinx extension module names here, as strings. They can be extensions 49 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 50 | extensions = ['sphinx.ext.autodoc'] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix of source filenames. 56 | source_suffix = '.rst' 57 | 58 | # The encoding of source files. 59 | #source_encoding = 'utf-8-sig' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # General information about the project. 65 | 66 | project = 'wirepy' 67 | copyright = '2013, Lukas Lueg' 68 | 69 | # The version info for the project you're documenting, acts as replacement for 70 | # |version| and |release|, also used in various other places throughout the 71 | # built documents. 72 | # 73 | # The short X.Y version. 74 | import wirepy.platform 75 | version = wirepy.platform.__version__ 76 | # The full version, including alpha/beta/rc tags. 77 | release = version 78 | 79 | # The language for content autogenerated by Sphinx. Refer to documentation 80 | # for a list of supported languages. 81 | #language = None 82 | 83 | # There are two options for replacing |today|: either, you set today to some 84 | # non-false value, then it is used: 85 | #today = '' 86 | # Else, today_fmt is used as the format for a strftime call. 87 | #today_fmt = '%B %d, %Y' 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | exclude_patterns = ['_build'] 92 | 93 | # The reST default role (used for this markup: `text`) to use for all documents. 94 | #default_role = None 95 | 96 | # If true, '()' will be appended to :func: etc. cross-reference text. 97 | #add_function_parentheses = True 98 | 99 | # If true, the current module name will be prepended to all description 100 | # unit titles (such as .. function::). 101 | #add_module_names = True 102 | 103 | # If true, sectionauthor and moduleauthor directives will be shown in the 104 | # output. They are ignored by default. 105 | #show_authors = False 106 | 107 | # The name of the Pygments (syntax highlighting) style to use. 108 | pygments_style = 'sphinx' 109 | 110 | # A list of ignored prefixes for module index sorting. 111 | #modindex_common_prefix = [] 112 | 113 | 114 | # -- Options for HTML output --------------------------------------------------- 115 | 116 | # The theme to use for HTML and HTML Help pages. See the documentation for 117 | # a list of builtin themes. 118 | #html_theme = 'sphinx_rtd_theme' 119 | 120 | # Theme options are theme-specific and customize the look and feel of a theme 121 | # further. For a list of options available for each theme, see the 122 | # documentation. 123 | #html_theme_options = {} 124 | 125 | # Add any paths that contain custom themes here, relative to this directory. 126 | #html_theme_path = ['_themes', ] 127 | 128 | # The name for this set of Sphinx documents. If None, it defaults to 129 | # " v documentation". 130 | #html_title = None 131 | 132 | # A shorter title for the navigation bar. Default is the same as html_title. 133 | #html_short_title = None 134 | 135 | # The name of an image file (relative to this directory) to place at the top 136 | # of the sidebar. 137 | #html_logo = None 138 | 139 | # The name of an image file (within the static path) to use as favicon of the 140 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 141 | # pixels large. 142 | #html_favicon = None 143 | 144 | # Add any paths that contain custom static files (such as style sheets) here, 145 | # relative to this directory. They are copied after the builtin static files, 146 | # so a file named "default.css" will overwrite the builtin "default.css". 147 | html_static_path = ['_static'] 148 | 149 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 150 | # using the given strftime format. 151 | #html_last_updated_fmt = '%b %d, %Y' 152 | 153 | # If true, SmartyPants will be used to convert quotes and dashes to 154 | # typographically correct entities. 155 | #html_use_smartypants = True 156 | 157 | # Custom sidebar templates, maps document names to template names. 158 | #html_sidebars = {} 159 | 160 | # Additional templates that should be rendered to pages, maps page names to 161 | # template names. 162 | #html_additional_pages = {} 163 | 164 | # If false, no module index is generated. 165 | #html_domain_indices = True 166 | 167 | # If false, no index is generated. 168 | #html_use_index = True 169 | 170 | # If true, the index is split into individual pages for each letter. 171 | #html_split_index = False 172 | 173 | # If true, links to the reST sources are added to the pages. 174 | #html_show_sourcelink = True 175 | 176 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 177 | #html_show_sphinx = True 178 | 179 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 180 | #html_show_copyright = True 181 | 182 | # If true, an OpenSearch description file will be output, and all pages will 183 | # contain a tag referring to it. The value of this option must be the 184 | # base URL from which the finished HTML is served. 185 | #html_use_opensearch = '' 186 | 187 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 188 | #html_file_suffix = None 189 | 190 | # Output file base name for HTML help builder. 191 | htmlhelp_basename = 'wirepydoc' 192 | 193 | 194 | # -- Options for LaTeX output -------------------------------------------------- 195 | 196 | latex_elements = { 197 | # The paper size ('letterpaper' or 'a4paper'). 198 | #'papersize': 'letterpaper', 199 | 200 | # The font size ('10pt', '11pt' or '12pt'). 201 | #'pointsize': '10pt', 202 | 203 | # Additional stuff for the LaTeX preamble. 204 | #'preamble': '', 205 | } 206 | 207 | # Grouping the document tree into LaTeX files. List of tuples 208 | # (source start file, target name, title, author, documentclass [howto/manual]). 209 | latex_documents = [ 210 | ('index', 'wirepy.tex', 'wirepy Documentation', 211 | 'Lukas Lueg', 'manual'), 212 | ] 213 | 214 | # The name of an image file (relative to this directory) to place at the top of 215 | # the title page. 216 | #latex_logo = None 217 | 218 | # For "manual" documents, if this is true, then toplevel headings are parts, 219 | # not chapters. 220 | #latex_use_parts = False 221 | 222 | # If true, show page references after internal links. 223 | #latex_show_pagerefs = False 224 | 225 | # If true, show URL addresses after external links. 226 | #latex_show_urls = False 227 | 228 | # Documents to append as an appendix to all manuals. 229 | #latex_appendices = [] 230 | 231 | # If false, no module index is generated. 232 | #latex_domain_indices = True 233 | 234 | 235 | # -- Options for manual page output -------------------------------------------- 236 | 237 | # One entry per manual page. List of tuples 238 | # (source start file, name, description, authors, manual section). 239 | man_pages = [ 240 | ('index', 'wirepy', 'wirepy Documentation', 241 | ['Lukas Lueg'], 1) 242 | ] 243 | 244 | # If true, show URL addresses after external links. 245 | #man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ------------------------------------------------ 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | texinfo_documents = [ 254 | ('index', 'wirepy', 'wirepy Documentation', 255 | 'Lukas Lueg', 'wirepy', 'One line description of project.', 256 | 'Miscellaneous'), 257 | ] 258 | 259 | # Documents to append as an appendix to all manuals. 260 | #texinfo_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | #texinfo_domain_indices = True 264 | 265 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 266 | #texinfo_show_urls = 'footnote' 267 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | wirepy 2 | ====== 3 | 4 | `Wirepy` aims to remedy the disastrous situation of packet-dissection-libraries 5 | available to the Python programming language. It is a foreign function 6 | interface to use `Wireshark`_ within Python as implemented by `CPython`_ and 7 | `PyPy`_. 8 | 9 | The currently available options are either painfully slow or lack 10 | features. `Wireshark`_ provides support for more than 1.300 protocols, more 11 | than 125.000 fields within those protocols and more than 1.500.000 defined 12 | values and is actively maintained. 13 | 14 | Get the code from `GitHub`_. 15 | 16 | .. note:: 17 | This library is created out of pure necessity. I dont' know know where it 18 | is headed or even feasible to create a direct binding to ``libwireshark``. 19 | The best current source of documentation are the unittests. 20 | 21 | 22 | .. toctree:: 23 | modules 24 | 25 | Installation 26 | ------------ 27 | 28 | Requirements 29 | ~~~~~~~~~~~~ 30 | 31 | * CPython_ 3.x or later 32 | * CFFI_ 0.6 or later 33 | * Wireshark_ 1.10 or later 34 | * GLib2_ 2.16.0 or later 35 | * nose_ and tox_ are used for testing 36 | 37 | 38 | Configuring Wireshark 39 | ~~~~~~~~~~~~~~~~~~~~~ 40 | 41 | * If you are using a Linux distribution, `CPython`_-, `Wireshark`_ and their 42 | headers can be usually be installed from the package repository (e.g. via 43 | yum). 44 | * Otherwise you may configure and build a **minimal** Wireshark library like 45 | this:: 46 | 47 | ./configure -q --prefix=$HOME/wireshark --disable-wireshark --disable-packet-editor --disable-editcap --disable-capinfos --disable-mergecap --disable-text2pcap --disable-dftest --disable-airpcap --disable-rawshark --without-lua 48 | make -sj9 49 | make install 50 | 51 | 52 | Configuring wirepy 53 | ~~~~~~~~~~~~~~~~~~ 54 | 55 | #. Run ``./configure`` to configure `Wirepy`'s sourcecode: 56 | 57 | * Running ``./configure`` as it is should work if you have wireshark 58 | installed through `pkg-config`. 59 | * **Otherwise** you need to specify the paths to wireshark's and glib's 60 | header files yourself. You may also want to use a locally installed 61 | version of wireshark. The command may look something like this:: 62 | 63 | DEPS_CFLAGS="-I/path/to/wireshark-headers -I/path/to/glib-2.0-headers" DEPS_LIBS="-L/path/to/wireshark/lib" ./configure 64 | 65 | Executing may look like this:: 66 | 67 | LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/wireshark/lib PATH=$PATH:/path/to/wireshark/bin make 68 | 69 | #. Take a look at the ``Makefile`` and use ``make``. 70 | 71 | 72 | Development 73 | ----------- 74 | 75 | `Wirepy` uses CFFI_ to create an interface to ``libwireshark``, ``libwsutil`` 76 | and ``libwiretap``. Class-based representations of the defined C-structs and 77 | -types are used to bind behavior, state and documentation. Instead of 78 | returning error values, all functions raise exceptions of appropriate type. 79 | Memory is handled by python's garbage collector most of the time. 80 | 81 | The entire wireshark-interface can be found in ``/lib``; one may need special 82 | knowledge about how to use classes there. Once things quiet down in ``/lib``, a 83 | more pythonic API is to be created outside of ``/lib``. 84 | 85 | 86 | * What (at least in part) works: 87 | 88 | + Enumerating live interfaces and their capabilities. 89 | + Reading packets from live interfaces. 90 | + Reading packet dumps using the wiretap library. 91 | + Compiling and using display-filters to filter the resulting frame data. 92 | + Inspection of the resulting protocol-tree (``epan.ProtoTree``), 93 | 94 | - inspection of it's fields (``ftypes.FieldType``). 95 | - and their values (``epan.FieldValue``). 96 | 97 | + Working with columns, including ``COL_CUSTOM``. 98 | 99 | * What does not: 100 | 101 | + Putting it all together. 102 | 103 | - We probably want to create class-based representations of protocols, 104 | fields and their known values; one might create a class factory that uses 105 | the functions from ``/lib`` to create classes for protocols as they pop 106 | into existence in a proto-tree and keep a weakref to those. 107 | - It should be fairly easy to use the above for class-based comparision of 108 | values and create a simple compiler for display-filter strings (e.g. 109 | "``DisplayFilter(IP.proto==IP.proto.IPv4)``"). 110 | - A ``FieldType`` should have it's own subclass that is able to interpret 111 | common python objects, preserving it's type as closely as possible. 112 | 113 | - A ``INT8`` should do arithmetic mod 2**8 114 | - A ``IPv4`` or ``IPv6`` may take values from the ``ipaddr``-module 115 | - etc 116 | 117 | This should live outside of ``/lib``. 118 | 119 | + Writing packet dumps through ``wtap_dump...``. 120 | + Taps and the other ~95% of the more useful functions of wireshark. 121 | + Plugins will not load because they expect the symbols from ``libwireshark`` 122 | in the global namespace. We hack this situation by flooding the namespace 123 | with a call to dlopen(). 124 | + A backport to Python 2.x (using a compat module) should be easy. 125 | 126 | * To be considered: 127 | 128 | + There are many ways in which ``libwireshark`` handles memory allocation. 129 | From within Python, everything should be garbage-collected though; 130 | + There are many ways in which ``libwireshark`` handles memory deallocation. 131 | Once some or the other function is called or state is reached, memory 132 | represented by reachable objects becomes invalid garbage. 133 | + The raw C-API very much expects C-like behavior from it's user; there are 134 | many de-facto global states and carry-on-your-back variables. Hide those 135 | 136 | 137 | Contact 138 | ------- 139 | 140 | Via lukas.lueg@gmail.com. Please use github to report problems and submit 141 | comments (which are very welcome). Patches should conform to :pep:`8` and have 142 | appropriate unittests attached. 143 | 144 | Indices and tables 145 | ------------------ 146 | 147 | * :ref:`genindex` 148 | * :ref:`modindex` 149 | * :ref:`search` 150 | 151 | Generated |today|. 152 | 153 | .. _GitHub: https://github.com/lukaslueg/wirepy 154 | .. _CFFI: https://cffi.readthedocs.org 155 | .. _`Wireshark`: https://www.wireshark.org 156 | .. _`CPython`: http://www.python.org 157 | .. _`PyPy`: http://www.pypy.org 158 | .. _Wireshark: https://www.wireshark.org 159 | .. _nose: https://nose.readthedocs.org/en/latest/ 160 | .. _tox: http://tox.readthedocs.org/en/latest/ 161 | .. _Glib2: https://developer.gnome.org/glib/ 162 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | Module reference 2 | ================ 3 | 4 | :mod:`cdata` Module 5 | --------------------- 6 | .. automodule:: wirepy.lib.cdata 7 | 8 | :mod:`column` Module 9 | --------------------- 10 | .. automodule:: wirepy.lib.column 11 | 12 | :mod:`dfilter` Module 13 | --------------------- 14 | .. automodule:: wirepy.lib.dfilter 15 | 16 | :mod:`dumpcap` Module 17 | --------------------- 18 | .. automodule:: wirepy.lib.dumpcap 19 | 20 | :mod:`epan` Module 21 | ------------------ 22 | .. automodule:: wirepy.lib.epan 23 | 24 | :mod:`ftypes` Module 25 | -------------------- 26 | .. automodule:: wirepy.lib.ftypes 27 | 28 | :mod:`glib2` Module 29 | ------------------- 30 | .. automodule:: wirepy.lib.glib2 31 | 32 | :mod:`prefs` Module 33 | ------------------- 34 | .. automodule:: wirepy.lib.prefs 35 | 36 | :mod:`timestamp` Module 37 | ----------------------- 38 | .. automodule:: wirepy.lib.timestamp 39 | 40 | :mod:`wireshark` Module 41 | ----------------------- 42 | .. automodule:: wirepy.lib.wireshark 43 | 44 | :mod:`wsutil` Module 45 | -------------------- 46 | .. automodule:: wirepy.lib.wsutil 47 | 48 | :mod:`wtap` Module 49 | ------------------ 50 | .. automodule:: wirepy.lib.wtap 51 | -------------------------------------------------------------------------------- /examples/capture_and_dissect.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | from wirepy.lib import column, dfilter, dumpcap, epan, wtap, prefs 3 | 4 | # Some required setup 5 | epan.epan_init() 6 | prefs.read_prefs() 7 | epan.cleanup_dissection() 8 | epan.init_dissection() 9 | 10 | # Some general information we are interested in for every packet 11 | CINFO = column.ColumnInfo([column.Format(column.Type.NUMBER, 12 | title='Packet number'), 13 | column.Format(column.Type.INFO, title='Info'), 14 | column.Format(column.Type.PACKET_LENGTH, 15 | title='Length'), 16 | column.Format(column.Type.EXPERT, title='Expert'), 17 | column.Format(column.Type.PROTOCOL, 18 | title='The protocol'), 19 | column.Format(column.Type.ABS_TIME, 20 | title='The time'), 21 | column.Format(column.Type.UNRES_DST, 22 | title='Destination'), 23 | column.Format(column.Type.CUSTOM, 24 | title='The user agent', 25 | custom_field='http.user_agent')]) 26 | 27 | # We will only be interested in http traffic 28 | FILTER_HTTP = dfilter.DisplayFilter('http') 29 | 30 | 31 | def _display_tree_fi(node, lvl=0): 32 | '''Display FieldInfo representations''' 33 | fi = node.field_info 34 | if fi is not None: 35 | # Skip text fields 36 | if fi.hfinfo.abbrev == 'text': 37 | return 38 | print((' ' * lvl) + fi.hfinfo.abbrev + ": " + fi.rep) 39 | # Depth-first into the protocol-tree 40 | if node.first_child is not None: 41 | _display_tree_fi(node.first_child, lvl + 1) 42 | if node.next is not None: 43 | _display_tree_fi(node.next, lvl) 44 | 45 | 46 | def _show(wt, frame): 47 | # Dissect a single frame using the wtap's current state 48 | edt = epan.Dissect() 49 | edt.prime_dfilter(FILTER_HTTP) 50 | edt.prime_cinfo(CINFO) 51 | edt.run(wt, frame, CINFO) 52 | if not FILTER_HTTP.apply_edt(edt): 53 | # No http traffic in the packet, throw it away 54 | return 55 | edt.fill_in_columns() 56 | _display_tree_fi(edt.tree) 57 | pprint.pprint(dict(((CINFO.titles[i], 58 | epan.iface.string(CINFO._col_datap[i])) 59 | for i in range(len(CINFO))))) 60 | 61 | 62 | def _iter(): 63 | with dumpcap.CaptureSession(interfaces=('any', ), 64 | autostop_duration=30) as cap: 65 | try: 66 | event_type, event_msg = cap.wait_for_event(timeout=10) 67 | if event_type != cap.SP_FILE: 68 | # pipe out of sync 69 | raise RuntimeError('Unexpected event from dumpcap') 70 | except dumpcap.NoEvents: 71 | # Child did not start capturing... 72 | errmsg = ('Dumpcap did not start receiving packets for some time.' 73 | ' Giving up.') 74 | raise RuntimeError(errmsg) 75 | # Received the first file dumpcap is writing to. Since we didnt request 76 | # a ringbuffer, dumpcap will write to only one file for the entire 77 | # session. 78 | fname = event_msg 79 | while True: 80 | with wtap.WTAP.open_offline(fname) as wt: 81 | frameiter = iter(wt) 82 | # Started to read from the current savefile. Now wait for 83 | # dumpcap to report about written packets. 84 | for event_type, event_msg in iter(cap): 85 | if event_type == cap.SP_PACKET_COUNT: 86 | # Dissect as many packets as have been written 87 | for i in range(event_msg): 88 | wt.clear_eof() 89 | try: 90 | frame = next(frameiter) 91 | except StopIteration: 92 | errmsg = ('Dumpcap reported new packets, but' 93 | ' the capture-file does not have' 94 | ' them.') 95 | raise RuntimeError 96 | yield wt, frame 97 | elif event_type == cap.SP_FILE: 98 | # A new savefile has been created, stop reading from 99 | # the current file. 100 | fname = event_msg 101 | break 102 | else: 103 | # The iterator on cap reaches this point if there are 104 | # no more events from dumpcap - capturing has stopped, 105 | # quit the loop 106 | break 107 | 108 | 109 | def read(): 110 | for wt, frame in _iter(): 111 | _show(wt, frame) 112 | 113 | 114 | if __name__ == '__main__': 115 | read() 116 | -------------------------------------------------------------------------------- /examples/create_cube_events.py: -------------------------------------------------------------------------------- 1 | '''Capture network traffic and generate event-reports about it's content. 2 | 3 | Events are reported to Cube (http://square.github.io/cube/), which stores them 4 | in it's database for later analysis. 5 | 6 | This example requires a running Cube daemon on localhost; note that naively 7 | executing it will flood your mongodb instance with useless events. Also see 8 | https://github.com/tsileo/cube-client for one extra dependency. 9 | 10 | The main process starts ``dumpcap`` in order to write captured traffic to a 11 | series of temporary files. Sub-processes handled by the multiprocessing module 12 | take care of dissection and reporting. Note that this allows us to use multiple 13 | CPUs for dissection at the expense of not having a single - readily available - 14 | global state e.g. for stream reconstruction. 15 | 16 | We report every single captured packet as an event to Cube: This is madness. 17 | A more reality-bound tool would examine the protocol-tree itself or use a 18 | series of DisplayFilter-instances to look for conditions worthy of reporting. 19 | 20 | You may be interested in Cube and Cubism (http://square.github.io/cubism/) in 21 | case you want to take this example more seriously for yourself. 22 | Cube generates a report about the captured HTTP-traffic (bytes per second) in 23 | a series of ten second intervals using a request like 24 | http//.../metric?expression=sum(packet(length).eq(protocol, "HTTP"))&step=1e4 25 | ''' 26 | import os 27 | import multiprocessing 28 | import tempfile 29 | import cube 30 | from wirepy.lib import column, dumpcap, epan, wtap, prefs 31 | 32 | # Some required setup 33 | epan.epan_init() 34 | prefs.read_prefs() 35 | epan.init_dissection() 36 | 37 | # Use columns to quickly get some general information without having 38 | # to dive into the protocol-tree 39 | CINFO = column.ColumnInfo([column.Format(column.Type.PACKET_LENGTH), 40 | column.Format(column.Type.PROTOCOL), 41 | column.Format(column.Type.ABS_DATE_TIME)]) 42 | 43 | 44 | def _dissect_file(fname): 45 | '''Dissect a savefile and report events about information found within to 46 | Cube. 47 | 48 | This function is executed as a subprocess. 49 | ''' 50 | print('Starting to dissect file %s' % (fname, )) 51 | try: 52 | c = cube.Cube() 53 | with wtap.WTAP.open_offline(fname) as wt: 54 | for frame in wt: 55 | edt = epan.Dissect() 56 | edt.prime_cinfo(CINFO) 57 | edt.run(wt, frame, CINFO) 58 | edt.fill_in_columns() 59 | # We could dive into edt.tree, the protocol-tree, but are 60 | # only interested in the column-info readily provided 61 | length = int(epan.iface.string(CINFO._col_datap[0])) 62 | proto = epan.iface.string(CINFO._col_datap[1]) 63 | timestamp = epan.iface.string(CINFO._col_datap[2]) 64 | # The event-timestamp is the time the packet was logged, 65 | # ctime is the time the packet was captured 66 | c.put('packet', {'protocol': proto, 'length': length, 67 | 'ctime': timestamp}) 68 | print('Successfully dissected file %s' % (fname, )) 69 | except Exception as exp: 70 | print('Failed with file %s\n\%s' % (fname, exp)) 71 | raise 72 | finally: 73 | os.unlink(fname) 74 | 75 | 76 | def _report_packet_drops(drop_count): 77 | print('Dropped %i packets' % (drop_count, )) 78 | 79 | 80 | def _report_packet_count(packet_count): 81 | print('Captured %i packets' % (packet_count, )) 82 | 83 | 84 | def _dump_to_tempfiles(process_pool): 85 | '''Capture traffic to tempfiles and submit new filenames to the queue''' 86 | # A random prefix for all tempfiles 87 | temp_prefix = tempfile.NamedTemporaryFile().name 88 | # Capture on all interfaces, use a ringbuffer, exclude localhost to prevent 89 | # an endless loop around the event collector 90 | cap_args = dict(interfaces=('any', ), savefile=temp_prefix, 91 | ringbuffer_filesize=1 * 1024, # one megabyte per file 92 | capture_filter='host not 127.0.0.1') 93 | with dumpcap.CaptureSession(**cap_args) as cap: 94 | # Register some event handlers so we dont have to deal with those 95 | # events in the loop below (if we are interested at all...) 96 | cap.register_eventhandler(cap.SP_PACKET_COUNT, _report_packet_count) 97 | cap.register_eventhandler(cap.SP_DROPS, _report_packet_drops) 98 | # Deal with the first file-event outside the loop, so we can break 99 | # away in case dumpcap never sees any traffic due to some unknown 100 | # error condition... 101 | try: 102 | event_type, event_msg = cap.wait_for_event(timeout=10) 103 | # SP_FILE should always be the first event 104 | if event_type != cap.SP_FILE: 105 | raise RuntimeError('Unexpected event from dumpcap') 106 | except dumpcap.NoEvents: 107 | errmsg = ('Dumpcap did not start receiving packets for some time.' 108 | ' Giving up.') 109 | raise RuntimeError(errmsg) 110 | fname = event_msg 111 | print('Started writing packets to %s' % (fname, )) 112 | # Now wait for dumpcap to finish writing to the current file 113 | for event_type, event_msg in iter(cap): 114 | if event_type == cap.SP_FILE: 115 | print('Switched writing to %s' % (event_msg, )) 116 | # Now that dumpcap has switched files, it is time to 117 | # dissect the previously completed file 118 | process_pool.apply_async(_dissect_file, (fname, )) 119 | fname = event_msg 120 | 121 | 122 | def dump_dissect_and_report(): 123 | '''Dump network traffic and report events to Cube''' 124 | # Create one subprocess per cpu but restart for every dissection 125 | pool = multiprocessing.Pool(maxtasksperchild=1) 126 | print('Starting to capture') 127 | _dump_to_tempfiles(pool) # Start submitting work 128 | print('Stopped capturing, stopping dissection') 129 | pool.close() 130 | print('Waiting for dissectors') 131 | pool.join() 132 | print('All done') 133 | 134 | 135 | if __name__ == '__main__': 136 | dump_dissect_and_report() 137 | -------------------------------------------------------------------------------- /examples/list_interfaces.py: -------------------------------------------------------------------------------- 1 | '''List interfaces available to dumpcap to stdout''' 2 | from wirepy.lib import dumpcap 3 | 4 | for idx, iface in enumerate(dumpcap.Interface.list_interfaces()): 5 | print('%i: %s\t%s%s' % (idx, iface.name, iface.interface_type_string, 6 | ('', '\tloopback')[iface.loopback])) 7 | print('\tDoes %ssupport monitor mode' % ('not ', '')[iface.can_rfmon]) 8 | print('\tSupports %s' % (', '.join((ltype.name for ltype in 9 | iface.supported_link_layer_types)))) 10 | print() 11 | -------------------------------------------------------------------------------- /examples/show_fields.py: -------------------------------------------------------------------------------- 1 | '''Print known fields to stdout, somewhat like ``tshark -G fields``''' 2 | import wirepy.lib.epan 3 | 4 | wirepy.lib.epan.epan_init() 5 | 6 | for field in wirepy.lib.epan.iter_fields(): 7 | print('%s\t%s\t%s' % (field.name, field.abbrev, field.blurb)) 8 | -------------------------------------------------------------------------------- /examples/show_protocols.py: -------------------------------------------------------------------------------- 1 | '''Print all known protocols to stdout, just like ``tshark -G protocols``''' 2 | import wirepy.lib.epan 3 | 4 | wirepy.lib.epan.epan_init() 5 | 6 | for proto in wirepy.lib.epan.iter_protocols(): 7 | print('%s\t%s\t%s' % (proto.name, proto.short_name, proto.filter_name)) 8 | -------------------------------------------------------------------------------- /examples/show_values.py: -------------------------------------------------------------------------------- 1 | '''Show known field-values, somewhat like ``tshark -G values``''' 2 | from wirepy.lib import epan 3 | 4 | epan.epan_init() 5 | 6 | for field in epan.iter_fields(): 7 | print(repr(field)) 8 | for value in field: 9 | print(' ' + repr(value)) 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cffi>=0.6 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | import setuptools 3 | import sys 4 | from setuptools.command.test import test as TestCommand 5 | 6 | import wirepy.lib.wireshark 7 | import wirepy.platform 8 | 9 | #TODO Not really used right now 10 | 11 | 12 | def pip_to_requirements(): 13 | pattern = re.compile('(.*)([>=]=[.0-9]*).*') 14 | with open('requirements.txt') as f: 15 | for line in f: 16 | m = pattern.match(line) 17 | if m: 18 | yield '%s (%s)' % m.groups() 19 | else: 20 | yield line.strip() 21 | 22 | with open('README', 'r') as f: 23 | description = f.read() 24 | 25 | 26 | class Tox(TestCommand): 27 | 28 | def finalize_options(self): 29 | TestCommand.finalize_options(self) 30 | self.test_args = [] 31 | self.test_suite = True 32 | 33 | def run_tests(self): 34 | import tox 35 | errno = tox.cmdline(self.test_args) 36 | sys.exit(errno) 37 | 38 | 39 | setuptools.setup(name=wirepy.platform.__package_name__, 40 | version=wirepy.platform.__package_name__, 41 | description='A foreign function interface to Wireshark', 42 | long_description=description, 43 | author='Lukas Lueg', 44 | author_email='lukas.lueg@gmail.com', 45 | url='https://github.com/lukaslueg/wirepy', 46 | zip_safe=False, # for cffi 47 | requires=list(pip_to_requirements()), 48 | setup_requires=['cffi>=0.6'], 49 | tests_require=['tox', 'nose'], 50 | packages=['wirepy', 'wirepy.lib', 'wirepy.tests'], 51 | ext_package='wirepy', 52 | ext_modules=[wirepy.lib.wireshark.iface.verifier.get_extension()], 53 | cmdclass={'test': Tox}) 54 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # TODO: Nothing to see here, move along. Tox will become useful when a port 2 | # to Python 2.x is made 3 | 4 | [tox] 5 | envlist=py33 6 | 7 | [testenv] 8 | deps= 9 | nose 10 | cffi 11 | commands=nosetests [] 12 | -------------------------------------------------------------------------------- /wirepy/__init__.py: -------------------------------------------------------------------------------- 1 | from platform import __version__ 2 | -------------------------------------------------------------------------------- /wirepy/compat.py: -------------------------------------------------------------------------------- 1 | '''Helper module for py2/py3k''' 2 | 3 | 4 | def add_metaclass(metaclass): 5 | """Class decorator for creating a class with a metaclass.""" 6 | # Taken from six.py 7 | def wrapper(cls): 8 | orig_vars = cls.__dict__.copy() 9 | orig_vars.pop('__dict__', None) 10 | orig_vars.pop('__weakref__', None) 11 | for slots_var in orig_vars.get('__slots__', ()): 12 | orig_vars.pop(slots_var) 13 | return metaclass(cls.__name__, cls.__bases__, orig_vars) 14 | return wrapper 15 | -------------------------------------------------------------------------------- /wirepy/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaslueg/wirepy/b7178d04cb4eef5e08a97ac20017856c52ababe4/wirepy/lib/__init__.py -------------------------------------------------------------------------------- /wirepy/lib/cdata.py: -------------------------------------------------------------------------------- 1 | '''Helper module to make working with CFFI more convenient. 2 | 3 | Classes that mainly wrap c-like *struct* may subclass :py:class:`CDataObject` 4 | which carries :py:class:`MetaCDataObject` as it's metaclass. When a deriving 5 | class is created, all class-level attributes that derive from 6 | :py:class:`BaseAttribute` are replaced with standard python properties that 7 | access the wrapped struct-members, automatically cast to python types, raise 8 | Exceptions and keep references to allocated memory in order to handle garbage 9 | collection. 10 | 11 | .. note:: 12 | It's not clear wether to keep this module at all, the overhead during 13 | runtime is probably significant. It does however provide convenience 14 | until design decisions quite down. 15 | ''' 16 | 17 | 18 | import re 19 | from wirepy import compat 20 | from wirepy.lib.wireshark import iface, mod 21 | 22 | 23 | def get_mod_definitions(patterns): 24 | defs = {} 25 | for pattern in patterns: 26 | for attr in dir(mod): 27 | if re.match(pattern, attr): 28 | if defs.get(attr, None) is not None: 29 | raise ValueError('Multiple definitions for "%s"' % (attr, )) 30 | defs[attr] = getattr(mod, attr) 31 | return defs 32 | 33 | 34 | def update_from_mod_definitions(d, patterns): 35 | for k, v in get_mod_definitions(patterns).items(): 36 | if d.get(k, None) is not None: 37 | raise ValueError('Multiple definitions for "%s"' % (k, )) 38 | d[k] = v 39 | 40 | 41 | class AttributeAccessError(AttributeError): 42 | '''Indicates access to an attribute that can't be accessed that way.''' 43 | 44 | 45 | class AttributeSizeError(AttributeError): 46 | '''A list-like attribute was set to an incorrect size.''' 47 | 48 | 49 | class BaseAttribute(object): 50 | '''An attribute on a cdata-object. 51 | 52 | An attribute defines methods to read, write and delete values. These 53 | methods end up as property()s on the final class. 54 | ''' 55 | 56 | can_never_read = False 57 | can_never_write = False 58 | can_never_delete = False 59 | 60 | def __init__(self, structmember=None, can_read=None, can_write=None, 61 | can_del=None, doc=None): 62 | ''' 63 | :param structmember: 64 | Name of the member to access by this attribute. 65 | :py:class:`MetaCDataObject` will use the attribute's name in 66 | case **structmember** is None. 67 | :param can_read: 68 | Indicates wether this attribute should provide read access to the 69 | underlying member or raise an :py:exc:`AttributeAccessError`. 70 | :param can_write: 71 | Same as **can_read** for write access. 72 | :param can_del: 73 | Sam as **can_del** for deletion. 74 | :param doc: 75 | docstring to be placed on the final property. 76 | ''' 77 | if can_read is None: 78 | can_read = not self.can_never_read 79 | else: 80 | if can_read and self.can_never_read: 81 | raise ValueError('This attribute-type can never read') 82 | if can_write is None: 83 | can_write = not self.can_never_write 84 | else: 85 | if can_write and self.can_never_write: 86 | raise ValueError('This attribute-type can never write') 87 | if can_del is None: 88 | can_del = not self.can_never_delete 89 | else: 90 | if can_del and self.can_never_delete: 91 | raise ValueError('This attribute-type can never delete') 92 | self.structmember = structmember 93 | self.can_read = can_read 94 | self.can_write = can_write 95 | self.can_del = can_del 96 | self.doc = doc 97 | self.attrname = None 98 | self.wrapped_attrname = None 99 | 100 | def getter(self): 101 | '''Generate a function that serves as a getter.''' 102 | raise NotImplementedError 103 | 104 | def getter_cant_get(self): 105 | '''Generate a function that indicates an access-error while reading.''' 106 | 107 | def get(_self): 108 | errmsg = 'Attribute "%s" can\'t be read from' % (self.attrname, ) 109 | raise AttributeAccessError(errmsg) 110 | return get 111 | 112 | def setter(self): 113 | '''Generate a function that serves as a setter.''' 114 | raise NotImplementedError 115 | 116 | def setter_cant_set(self): 117 | '''Generate a function that indicates an access-error while writing.''' 118 | 119 | def set(_self, value): 120 | errmsg = 'Attribute "%s" is marked read-only' % (self.attrname, ) 121 | raise AttributeAccessError(errmsg) 122 | return set 123 | 124 | def deleter(self): 125 | '''Generate a function that serves as a deleter.''' 126 | 127 | def delete(instance): 128 | # TODO ??? 129 | self.set_backing_object(instance, self.wrapped_attrname, 130 | iface.NULL, None) 131 | return delete 132 | 133 | def deleter_cant_delete(self): 134 | '''Generate a function that indicates an access-error while deleting''' 135 | 136 | def delete(_self): 137 | errmsg = 'Attribute \"%s\" can\'t be deleted' % (self.attrname, ) 138 | raise AttributeAccessError(errmsg) 139 | return delete 140 | 141 | def get_backing_object(self, instance): 142 | return getattr(instance.cdata, self.structmember) 143 | 144 | def set_backing_object(self, instance, wrapped_attrname=None, 145 | backing_value=None, wrapped_value=None): 146 | if wrapped_attrname is not None: 147 | if wrapped_value is None: 148 | wrapped_value = backing_value 149 | setattr(instance, wrapped_attrname, wrapped_value) 150 | setattr(instance.cdata, self.structmember, backing_value) 151 | 152 | 153 | class Attribute(BaseAttribute): 154 | '''An basic attribute that sets and gets the raw value.''' 155 | can_never_delete = True 156 | 157 | def getter(self): 158 | 159 | def get(instance): 160 | return self.get_backing_object(instance) 161 | return get 162 | 163 | def setter(self): 164 | 165 | def set(instance, value): 166 | self.set_backing_object(instance, backing_value=value) 167 | return set 168 | 169 | 170 | class ROAttribute(Attribute): 171 | '''A basic attribute that can only read but never write.''' 172 | can_never_write = True 173 | 174 | 175 | class BytesAttribute(BaseAttribute): 176 | 177 | def __init__(self, sizeattr, *args, **kwargs): 178 | BaseAttribute.__init__(self, *args, **kwargs) 179 | self.sizeattr = sizeattr 180 | 181 | def getter(self): 182 | 183 | def get(instance): 184 | buf = self.get_backing_object(instance) 185 | if buf == iface.NULL: 186 | return None 187 | else: 188 | bufsize = getattr(instance, self.sizeattr) 189 | return bytes(buf[0:bufsize]) # TODO py2/3k 190 | return get 191 | 192 | def setter(self): 193 | 194 | def set(instance, buf): 195 | if buf is None: 196 | cdata = iface.NULL 197 | else: 198 | cdata = iface.new('unsigned char[]', bytes(buf)) 199 | self.set_backing_object(instance, self.wrapped_attrname, cdata) 200 | return set 201 | 202 | 203 | class StringAttribute(BaseAttribute): 204 | '''A null-terminated string.''' 205 | 206 | # TODO add a size argument coming from the struct?! 207 | 208 | def getter(self): 209 | 210 | def get(instance): 211 | string = self.get_backing_object(instance) 212 | return iface.string(string) if string != iface.NULL else None 213 | return get 214 | 215 | def setter(self): 216 | 217 | def set(instance, string): 218 | if string is None: 219 | cdata = iface.NULL 220 | else: 221 | cdata = iface.new('char[]', string.encode()) # TODO py2/py3k 222 | self.set_backing_object(instance, self.wrapped_attrname, cdata) 223 | return set 224 | 225 | 226 | class ROStringAttribute(StringAttribute): 227 | '''A zero-terminated string that can only be read but never be written.''' 228 | can_never_write = True 229 | 230 | 231 | class BooleanAttribute(BaseAttribute): 232 | '''A boolean value.''' 233 | can_never_delete = True 234 | 235 | def getter(self): 236 | 237 | def get(instance): 238 | b = self.get_backing_object(instance) 239 | return bool(b) 240 | return get 241 | 242 | def setter(self): 243 | 244 | def set(instance, b): 245 | self.set_backing_object(instance, backing_value=bool(b)) 246 | return set 247 | 248 | 249 | class AttributeList(object): 250 | 251 | def __init__(self, listattr, instance): 252 | self.listattr = listattr 253 | self.instance = instance 254 | 255 | def _check_idx(self, idx): 256 | try: 257 | size = getattr(self.instance, self.listattr.sizeattr) 258 | except TypeError: 259 | size = self.listattr.sizeattr 260 | assert isinstance(size, int) 261 | if idx not in range(size): 262 | raise IndexError('Index %i, array is size %i' % (idx, size)) 263 | 264 | def _get(self, idx): 265 | self._check_idx(idx) 266 | return self.listattr.get_backing_object(self.instance)[idx] 267 | 268 | def __getitem__(self, idx): 269 | return self._get(idx) 270 | 271 | def __setitem__(self, idx, value): 272 | self._check_idx(idx) 273 | cdata = getattr(self.instance, self.listattr.wrapped_attrname) 274 | cdata[idx] = value 275 | 276 | 277 | class BooleanAttributeList(AttributeList): 278 | 279 | def __getitem__(self, idx): 280 | return bool(self._get(idx)) 281 | 282 | 283 | class StringAttributeList(AttributeList): 284 | 285 | def __getitem__(self, idx): 286 | string = self._get(idx) 287 | return iface.string(string) if string != iface.NULL else None 288 | 289 | def __setitem__(self, idx, string): 290 | self._check_idx(idx) 291 | if string is None: 292 | buf = iface.NULL 293 | else: 294 | buf = iface.new('char[]', string.encode()) 295 | cdata, bufs = getattr(self.instance, self.listattr.wrapped_attrname) 296 | cdata[idx] = buf 297 | bufs[idx] = buf 298 | backing_object = getattr(getattr(self.instance, 299 | self.instance._backing_object), 300 | self.listattr.backing) 301 | backing_object[idx] = buf 302 | 303 | 304 | class InstanceAttributeList(AttributeList): 305 | pass 306 | 307 | 308 | class ListAttribute(BaseAttribute): 309 | '''A list-like attribute, such as "char \*\*" or "int\*"''' 310 | can_never_delete = True 311 | 312 | def __init__(self, sizeattr, *args, **kwargs): 313 | BaseAttribute.__init__(self, *args, **kwargs) 314 | self.sizeattr = sizeattr 315 | 316 | def _check_size(self, instance, collection): 317 | try: 318 | size = getattr(instance, self.sizeattr) 319 | except TypeError: 320 | size = self.sizeattr 321 | assert isinstance(size, int) 322 | if len(collection) != size: 323 | errmsg = 'Collection has size %i, should be %i' % (len(collection), 324 | size) 325 | raise AttributeSizeError(errmsg) 326 | 327 | def getter(self): 328 | def get(instance): 329 | if self.get_backing_object(instance) == iface.NULL: 330 | return None 331 | else: 332 | return AttributeList(self, instance) 333 | return get 334 | 335 | 336 | class IntListAttribute(ListAttribute): 337 | '''A list of integers like "int*". 338 | 339 | A new int[] is created and kept upon assigning to the attribute. 340 | ''' 341 | 342 | def setter(self): 343 | 344 | def set(instance, ints): 345 | ints = [int(i) for i in ints] 346 | self._check_size(instance, ints) 347 | cdata_obj = iface.new('int[]', ints) 348 | self.set_backing_object(instance, self.wrapped_attrname, 349 | backing_value=cdata_obj) 350 | return set 351 | 352 | 353 | class BooleanListAttribute(ListAttribute): 354 | 355 | def getter(self): 356 | 357 | def get(instance): 358 | if self.get_backing_object(instance) == iface.NULL: 359 | return None 360 | else: 361 | return BooleanAttributeList(self, instance) 362 | return get 363 | 364 | def setter(self): 365 | 366 | def set(instance, bools): 367 | bools = [bool(b) for b in bools] 368 | self._check_size(instance, bools) 369 | cdata = iface.new('unsigned char[]', bools) 370 | self.set_backing_object(instance, self.wrapped_attrname, 371 | backing_value=cdata) 372 | return set 373 | 374 | 375 | class StringListAttribute(ListAttribute): 376 | 377 | def getter(self): 378 | 379 | def get(instance): 380 | if self.get_backing_object(instance) == iface.NULL: 381 | return None 382 | else: 383 | return StringAttributeList(self, instance) 384 | return get 385 | 386 | def setter(self): 387 | 388 | def set(instance, strings): 389 | bufs = [] 390 | for string in strings: 391 | if string is None: 392 | buf = iface.NULL 393 | else: 394 | buf = iface.new('char[]', string.encode()) 395 | bufs.append(buf) 396 | self._check_size(instance, bufs) 397 | cdata_obj = iface.new('char*[]', bufs) 398 | self.set_backing_object(instance, self.wrapped_attrname, 399 | cdata_obj, (cdata_obj, bufs)) 400 | return set 401 | 402 | 403 | class InstanceAttribute(BaseAttribute): 404 | 405 | def __init__(self, klass, null_is_none=True, keep_reference=False, 406 | *args, **kwargs): 407 | BaseAttribute.__init__(self, *args, **kwargs) 408 | self.klass = klass 409 | self.null_is_none = null_is_none 410 | self.keep_reference = keep_reference 411 | 412 | def getter(self): 413 | 414 | def get(instance): 415 | cdata_obj = self.get_backing_object(instance) 416 | if self.null_is_none and cdata_obj == iface.NULL: 417 | return 418 | klass = type(instance) if self.klass is None else self.klass 419 | return klass(cdata_obj) 420 | return get 421 | 422 | def setter(self): 423 | 424 | def set(instance, value): 425 | try: 426 | cdata_obj = value.cdata 427 | except AttributeError: 428 | cdata_obj = value 429 | self.set_backing_object(instance, backing_value=cdata_obj) 430 | # TODO wrapped_attrname if self.keep_reference else None, 431 | return set 432 | 433 | 434 | class InstanceListAttribute(BaseAttribute): 435 | 436 | def __init__(self, klass, sizeattr, null_is_none=True, 437 | keep_reference=False, *args, **kwargs): 438 | BaseAttribute.__init__(self, *args, **kwargs) 439 | self.klass = klass 440 | self.sizeattr = sizeattr 441 | self.null_is_none = null_is_none 442 | self.keep_reference = keep_reference 443 | 444 | def getter(self): 445 | 446 | def get(instance): 447 | cdata_obj = self.get_backing_object(instance) 448 | if self.null_is_none and cdata_obj == iface.NULL: 449 | return 450 | return InstanceAttributeList(self, instance) 451 | return get 452 | 453 | def setter(self): 454 | 455 | def set(instance, objects): 456 | cdata_objs = [obj.cdata if obj is not None else iface.NULL 457 | for obj in objects] 458 | size = getattr(instance, self.sizeattr) 459 | if len(cdata_objs) != size: 460 | errmsg = 'List has size %i, should be %i' % (len(cdata_objs), 461 | size) 462 | raise AttributeSizeError(errmsg) 463 | t = self.klass._struct + '*[]' 464 | cdata_objsp = iface.new(t, cdata_objs) 465 | self.set_backing_object(instance, self.wrapped_attrname, 466 | cdata_objsp, (cdata_objsp, cdata_objs)) 467 | return set 468 | 469 | 470 | class MetaCDataObject(type): 471 | '''Metaclass that automatically creates accessors to the underlying 472 | c-level *struct*. 473 | 474 | A class using this metaclass should define a single "_struct" attribute 475 | that names the to-be-wrapped *struct*. All instances of objects deriving 476 | from :py:class:`BaseAttribute` are **replaced** by standard python 477 | properties that may keep a reference to their :py:class:`BaseAttribute`- 478 | instance. 479 | Instances of such class should have a instance-attribute named "cdata" 480 | that references an instance of the wrapped *struct*. 481 | ''' 482 | 483 | def __new__(meta, name, bases, attrs): 484 | for attrname, attr in attrs.items(): 485 | if isinstance(attr, BaseAttribute): 486 | if attr.structmember is None: 487 | attr.structmember = attrname 488 | wrapped_attrname = '_' + attrname 489 | assert wrapped_attrname not in attrs 490 | attr.attrname = attrname 491 | attr.wrapped_attrname = wrapped_attrname 492 | if attr.can_read: 493 | getter = attr.getter() 494 | else: 495 | getter = attr.getter_cant_get() 496 | if attr.can_write: 497 | setter = attr.setter() 498 | else: 499 | setter = attr.setter_cant_set() 500 | if attr.can_del: 501 | deleter = attr.deleter() 502 | else: 503 | deleter = attr.deleter_cant_delete() 504 | prop = property(getter, setter, deleter, attr.doc) 505 | attrs[attrname] = prop 506 | try: 507 | patterns = attrs['_mod_defs'] 508 | except KeyError: 509 | pass 510 | else: 511 | del attrs['_mod_defs'] 512 | update_from_mod_definitions(attrs, patterns) 513 | return type.__new__(meta, name, bases, attrs) 514 | 515 | 516 | @compat.add_metaclass(MetaCDataObject) 517 | class CDataObject(object): 518 | '''Base class for objects wrapping *struct*''' 519 | _struct = None 520 | -------------------------------------------------------------------------------- /wirepy/lib/column.py: -------------------------------------------------------------------------------- 1 | '''Wireshark displays generic information about a packet's content in it's GUI 2 | using a set of columns. Each column has one of several pre-defined column-types 3 | which ``libwireshark`` knows about and fills with content while dissecting a 4 | packets. This allows dissectors of all kinds to provide information about a 5 | packet, no matter where in the protocol this information is ultimately 6 | retrieved from. 7 | 8 | For example, :py:attr:`Type.PROTOCOL` provides the name of the deepest protocol 9 | found within a frame; a raw ethernet frame may provide "eth" for PROTOCOL, a IP 10 | packet within the ethernet packet overrules this to "ip", a TCP packet within 11 | the IP-packet again overrules to 'tcp' and a HTTP packet within the TCP packet 12 | finally overrules to 'http'. 13 | 14 | .. note:: 15 | Wireshark uses columns in concert with it's preferences, the API reading 16 | column-settings directly from the global preferences object. To make this 17 | concept more flexible, we avoid this binding. 18 | ''' 19 | 20 | from .wireshark import iface, mod 21 | from . import dfilter 22 | from .cdata import (CDataObject, Attribute, BooleanAttribute, StringAttribute, 23 | InstanceAttribute, IntListAttribute, StringListAttribute, 24 | InstanceListAttribute) 25 | 26 | 27 | class ColumnError(Exception): 28 | '''Base class for all column-related errors.''' 29 | pass 30 | 31 | 32 | class InvalidColumnType(ColumnError): 33 | '''An invalid column-type was provided.''' 34 | pass 35 | 36 | 37 | class Type(object): 38 | '''A column-type.''' # TODO 39 | _802IQ_VLAN_ID = mod.COL_8021Q_VLAN_ID #: 802.1Q vlan ID 40 | ABS_DATE_TIME = mod.COL_ABS_DATE_TIME #: Absolute date and time 41 | ABS_TIME = mod.COL_ABS_TIME #: Absolute time 42 | CIRCUIT_ID = mod.COL_CIRCUIT_ID #: Circuit ID 43 | DSTIDX = mod.COL_DSTIDX 44 | #: !! DEPRECATED !! - Dst port idx - Cisco MDS-specific 45 | SRCIDX = mod.COL_SRCIDX 46 | #: !! DEPRECATED !! - Src port idx - Cisco MDS-specific 47 | VSAN = mod.COL_VSAN #: VSAN - Cisco MDS-specific 48 | CUMULATIVE_BYTES = mod.COL_CUMULATIVE_BYTES #: Cumulative number of bytes 49 | CUSTOM = mod.COL_CUSTOM #: Custom column (any filter name's contents) 50 | DCE_CALL = mod.COL_DCE_CALL 51 | #: DCE/RPC connection orientated call id OR datagram sequence number 52 | DCE_CTX = mod.COL_DCE_CTX 53 | #: !! DEPRECATED !! - DCE/RPC connection oriented context id 54 | DELTA_TIME = mod.COL_DELTA_TIME #: Delta time 55 | DELTA_CONV_TIME = mod.COL_DELTA_CONV_TIME 56 | #: Delta time to last frame in conversation 57 | REST_DST = mod.COL_RES_DST #: Resolved destination 58 | UNRES_DST = mod.COL_UNRES_DST #: Unresolved destination 59 | REST_DST_PORT = mod.COL_RES_DST_PORT #: Resolved destination port 60 | UNRES_DST_PORT = mod.COL_UNRES_DST_PORT #: Unresolved destination port 61 | DEF_DST = mod.COL_DEF_DST #: Destination address 62 | DEF_DST_PORT = mod.COL_DEF_DST_PORT #: Destination port 63 | EXPERT = mod.COL_EXPERT #: Expert info 64 | IF_DIR = mod.COL_IF_DIR #: FW-1 monitor interface/direction 65 | OXID = mod.COL_OXID #: !! DEPRECATED !! - Fibre Channel OXID 66 | RXID = mod.COL_RXID #: !! DEPRECATED !! - Fibre Channel RXID 67 | FR_DLCI = mod.COL_FR_DLCI #: !! DEPRECATED !! - Frame Relay DLCI 68 | FREQ_CHAN = mod.COL_FREQ_CHAN #: IEEE 802.11 (and WiMax?) - Channel 69 | BSSGP_TLLI = mod.COL_BSSGP_TLLI #: !! DEPRECATED !! - GPRS BSSGP IE TLLI 70 | HPUX_DEVID = mod.COL_HPUX_DEVID 71 | #: !! DEPRECATED !! - HP-UX Nettl Device ID 72 | HPUX_SUBSYS = mod.COL_HPUX_SUBSYS 73 | #: !! DEPRECATED !! - HP-UX Nettl Subsystem 74 | DEF_DL_DST = mod.COL_DEF_DL_DST #: Data link layer destination address 75 | DEF_DL_SRC = mod.COL_DEF_DL_SRC #: Data link layer source address 76 | RES_DL_DST = mod.COL_RES_DL_DST #: Unresolved DL destination 77 | UNRES_DL_DST = mod.COL_UNRES_DL_DST #: Unresolved DL destination 78 | RES_DL_SRC = mod.COL_RES_DL_SRC #: Resolved DL source 79 | UNRES_DL_SRC = mod.COL_UNRES_DL_SRC #: Unresolved DL source 80 | RSSI = mod.COL_RSSI #: IEEE 802.11 - received signal strength 81 | TX_RATE = mod.COL_TX_RATE #: IEEE 802.11 - TX rate in Mbps 82 | DSCP_VALUE = mod.COL_DSCP_VALUE #: IP DSCP Value 83 | INFO = mod.COL_INFO #: Description 84 | COS_VALUE = mod.COL_COS_VALUE #: !! DEPRECATED !! - L2 COS Value 85 | RES_NET_DST = mod.COL_RES_NET_DST #: Resolved net destination 86 | UNRES_NET_DST = mod.COL_UNRES_NET_DST #: Unresolved net destination 87 | RES_NET_SRC = mod.COL_RES_NET_SRC #: Resolved net source 88 | UNRES_NET_SRC = mod.COL_UNRES_NET_SRC #: Unresolved net source 89 | DEF_NET_DST = mod.COL_DEF_NET_DST #: Network layer destination address 90 | DEF_NET_SRC = mod.COL_DEF_NET_SRC #: Network layer source address 91 | NUMBER = mod.COL_NUMBER #: Packet list item number 92 | PACKET_LENGTH = mod.COL_PACKET_LENGTH #: Packet length in bytes 93 | PROTOCOL = mod.COL_PROTOCOL #: Protocol 94 | REL_TIME = mod.COL_REL_TIME #: Relative time 95 | REL_CONV_TIME = mod.COL_REL_CONV_TIME #: blurp 96 | DEF_SRC = mod.COL_DEF_SRC #: Source address 97 | DEF_SRC_PORT = mod.COL_DEF_SRC_PORT #: Source port 98 | RES_SRC = mod.COL_RES_SRC #: Resolved source 99 | UNRES_SRC = mod.COL_UNRES_SRC #: Unresolved source 100 | RES_SRC_PORT = mod.COL_RES_SRC_PORT #: Resolved source port 101 | UNRES_SRC_PORT = mod.COL_UNRES_SRC_PORT #: Unresolved source Port 102 | TEI = mod.COL_TEI #: Q.921 TEI 103 | UTC_DATE_TIME = mod.COL_UTC_DATE_TIME #: UTC date and time 104 | UTC_TIME = mod.COL_UTC_TIME #: UTC time 105 | CLS_TIME = mod.COL_CLS_TIME 106 | #: Command line specific time (default relative) 107 | 108 | NUM_COL_FMTS = mod.NUM_COL_FMTS 109 | MAX_INFO_LEN = mod.COL_MAX_INFO_LEN 110 | MAX_LEN = mod.COL_MAX_LEN 111 | 112 | def __init__(self, fmt): 113 | '''Get a reference to specific column-type. 114 | 115 | :param fmt: 116 | One of the defined column-types, e.g. :py:attr:`Number` 117 | ''' 118 | if fmt not in range(self.NUM_COL_FMTS): 119 | raise InvalidColumnType(fmt) 120 | self.fmt = fmt 121 | 122 | def __repr__(self): 123 | r = '' % (self.format_desc, 124 | self.format_string) 125 | return r 126 | 127 | def __int__(self): 128 | return self.fmt 129 | 130 | def __eq__(self, other): 131 | return int(other) == int(self) 132 | 133 | def __hash__(self): 134 | return hash(self.fmt) 135 | 136 | @classmethod 137 | def from_string(cls, format_string): 138 | fmt = mod.get_column_format_from_str(format_string.encode()) 139 | if fmt == -1: 140 | raise InvalidColumnType(format_string) 141 | return cls(fmt) 142 | 143 | @classmethod 144 | def iter_column_formats(cls): 145 | '''Iterate over all available column formats. 146 | 147 | :returns: 148 | An iterator that yields instances of :py:class:`Type`. 149 | ''' 150 | for fmt in range(cls.NUM_COL_FMTS): 151 | yield cls(fmt) 152 | 153 | @property 154 | def format_desc(self): 155 | return iface.string(mod.col_format_desc(self.fmt)) 156 | 157 | @property 158 | def format_string(self): 159 | return iface.string(mod.col_format_to_string(self.fmt)) 160 | 161 | @property 162 | def MAX_BUFFER_LEN(self): 163 | if self.fmt == self.INFO: 164 | return self.MAX_INFO_LEN 165 | else: 166 | return self.MAX_LEN 167 | 168 | 169 | class Format(CDataObject): 170 | '''A fmt_data''' 171 | 172 | _struct = 'fmt_data' 173 | title = StringAttribute(doc='Title of the column.') 174 | type_ = InstanceAttribute(Type, structmember='fmt', 175 | doc=('The column\'s type, one of ' 176 | ':py:class:`Type`.')) 177 | custom_field = StringAttribute(doc='Field-name for custom columns.') 178 | custom_occurrence = Attribute(doc=('Optional ordinal of occcurrence ' 179 | 'of the custom field.')) 180 | visible = BooleanAttribute(doc=('True if the column should be ' 181 | 'hidden in GUI.')) 182 | resolved = BooleanAttribute(doc=('True to show a more human-' 183 | 'readable name.')) 184 | 185 | def __init__(self, type_=None, init=None, title=None, custom_field=None, 186 | custom_occurrence=None, visible=None, resolved=None): 187 | ''' 188 | param init: 189 | The underlying fmt_data-object to wrap or None to create a new one. 190 | ''' 191 | self.cdata = init if init is not None else iface.new('fmt_data*') 192 | if title is not None: 193 | self.title = title 194 | if type_ is not None: 195 | self.type_ = type_ 196 | if custom_field is not None: 197 | self.custom_field = custom_field 198 | if custom_occurrence is not None: 199 | self.custom_occurrence = custom_occurrence 200 | if visible is not None: 201 | self.visible = visible 202 | if resolved is not None: 203 | self.resolved = resolved 204 | 205 | def __repr__(self): 206 | return '' % (self.title, self.type_) 207 | 208 | 209 | class ColumnInfo(CDataObject): 210 | _struct = 'column_info' 211 | num_cols = Attribute() 212 | fmts = IntListAttribute('num_cols', 'col_fmt') 213 | firsts = IntListAttribute(Type.NUM_COL_FMTS, 'col_first') 214 | lasts = IntListAttribute(Type.NUM_COL_FMTS, 'col_last') 215 | titles = StringListAttribute('num_cols', 'col_title') 216 | custom_fields = StringListAttribute('num_cols', 'col_custom_field') 217 | custom_occurrences = IntListAttribute('num_cols', 'col_custom_occurrence') 218 | custom_field_ids = IntListAttribute('num_cols', 'col_custom_field_id') 219 | custom_dfilters = InstanceListAttribute(dfilter.DisplayFilter, 220 | sizeattr='num_cols', 221 | structmember='col_custom_dfilter') 222 | fences = IntListAttribute('num_cols', 'col_fence') 223 | writeable = BooleanAttribute() 224 | 225 | def __init__(self, init): 226 | '''Create a new ColumnInfo-descriptor. 227 | 228 | :param init: 229 | Either a cdata-object to be wrapped or an iterable of 230 | :py:class:`Format` instances. 231 | ''' 232 | if isinstance(init, iface.CData): 233 | self.cdata = init 234 | else: 235 | self.cdata = iface.new('column_info*') 236 | self.num_cols = len(init) 237 | self.firsts = [-1 for i in range(Type.NUM_COL_FMTS)] 238 | self.lasts = [-1 for i in range(Type.NUM_COL_FMTS)] 239 | self.fmts = [fmt.type_ for fmt in init] 240 | self.titles = [fmt.title for fmt in init] 241 | self.custom_fields = [fmt.custom_field if fmt.type_ == Type.CUSTOM 242 | else None for fmt in init] 243 | self.custom_occurrences = [fmt.custom_occurrence 244 | if fmt.type_ == Type.CUSTOM else 0 245 | for fmt in init] 246 | self.custom_field_ids = [-1 for fmt in init] 247 | self.custom_dfilters = [dfilter.DisplayFilter(fmt.custom_field) 248 | if fmt.type_ == Type.CUSTOM else None 249 | for fmt in init] 250 | self.fences = [0 for fmt in init] 251 | 252 | self._matx = [] 253 | for i in range(self.num_cols): 254 | self._matx.append(iface.new('gboolean[]', Type.NUM_COL_FMTS)) 255 | self._matxp = iface.new('gboolean*[]', self._matx) 256 | self.cdata.fmt_matx = self._matxp 257 | for i in range(self.num_cols): 258 | mod.get_column_format_matches(self.cdata.fmt_matx[i], 259 | self.fmts[i]) 260 | 261 | self._col_data = [iface.NULL for fmt in init] 262 | self._col_datap = iface.new('gchar*[]', self._col_data) 263 | self.cdata.col_data = self._col_datap 264 | 265 | self._col_buf = [iface.new('gchar[]', fmt.type_.MAX_BUFFER_LEN) 266 | for fmt in init] 267 | self._col_bufp = iface.new('gchar*[]', self._col_buf) 268 | self.cdata.col_buf = self._col_bufp 269 | 270 | self._col_expr = [iface.new('gchar[]', Type.MAX_LEN) 271 | for fmt in init] + [iface.NULL] 272 | self._col_exprp = iface.new('gchar*[]', self._col_expr) 273 | self.cdata.col_expr.col_expr = self._col_exprp 274 | 275 | self._col_expr_val = [iface.new('gchar[]', Type.MAX_LEN) 276 | for fmt in init] + [iface.NULL] 277 | self._col_expr_valp = iface.new('gchar*[]', self._col_expr_val) 278 | self.cdata.col_expr.col_expr_val = self._col_expr_valp 279 | 280 | for i in range(self.num_cols): 281 | for j in range(Type.NUM_COL_FMTS): 282 | if self._matxp[i][j]: 283 | if self.firsts[j] == -1: 284 | self.firsts[j] = i 285 | self.lasts[j] = i 286 | 287 | def __len__(self): 288 | '''Equal to the number of columns in this descriptor''' 289 | return self.num_cols 290 | 291 | @property 292 | def have_custom_cols(self): 293 | '''''' 294 | # TODO do we really need this through the API ? 295 | return bool(mod.have_custom_cols(self.cdata)) 296 | -------------------------------------------------------------------------------- /wirepy/lib/dfilter.py: -------------------------------------------------------------------------------- 1 | '''Wireshark uses display filters for packet filtering within the GUI. The 2 | rich syntax makes them very useful for filtering packets without manual 3 | inspection of a packet's protocol tree. Because display filters are compiled 4 | to bytecode and executed within wireshark's own VM, complex filters also 5 | perform much better than inspection from within Python. 6 | 7 | See `the official documentation `_ 8 | for for information about their syntax. 9 | 10 | Example:: 11 | 12 | # wt is a wtap.WTAP-instance, frame is a epan.Frame-instance 13 | filter_islocal = dfilter.DisplayFilter('ip.src==192.168.0.0/16') 14 | edt = epan.Dissect() 15 | edt.prime_dfilter(filter_islocal) 16 | edt.run(wt, frame) 17 | passed = filter_islocal.apply_edt(edt) 18 | if passed: 19 | ... 20 | 21 | .. DisplayDocs: 22 | ''' 23 | from .wireshark import iface, mod 24 | 25 | 26 | class DisplayFilterError(Exception): 27 | '''Base-class for display-filter-related errors''' 28 | pass 29 | 30 | 31 | class DisplayFilter(object): 32 | '''A display-filter''' 33 | _struct = 'dfilter_t' 34 | 35 | def __init__(self, init): 36 | '''Create a new or wrap an existing struct. 37 | 38 | :param init: 39 | A dfilter_t-object or a string 40 | 41 | :raises: 42 | :py:exc:`DisplayFilterError` in case a string was supplied and the 43 | new display filter failed to compile. 44 | ''' 45 | if isinstance(init, iface.CData): 46 | self.cdata = init 47 | else: 48 | if not mod.WIREPY_EPAN_INITIALIZED: 49 | raise RuntimeError('EPAN must be initialized') 50 | dfp = iface.new('dfilter_t **dfp') 51 | # dfilter_compile() sets a global pointer to *char in case of an 52 | # error. This is a perfectly good race conditions that we do not 53 | # care to avoid because Wireshark is not thread-safe anyway. 54 | # The string had to be regex-parsed in case we want subclasses of 55 | # DisplayFilterError. Meh... 56 | res = mod.dfilter_compile(str(init).encode(), dfp) 57 | if not res: 58 | msg = iface.string(mod.wrapped_dfilter_get_error_msg()) 59 | raise DisplayFilterError(msg) 60 | self.cdata = iface.gc(dfp[0], mod.dfilter_free) 61 | 62 | def apply_edt(self, edt): 63 | '''Apply this DisplayFilter to a Dissect-instance''' 64 | return bool(mod.dfilter_apply_edt(self.cdata, edt.cdata)) 65 | 66 | def apply(self, proto_tree): 67 | '''Apply this DisplayFilter to a ProtoTree-instance''' 68 | return bool(mod.dfilter_apply(self.cdata, proto_tree.cdata)) 69 | 70 | def prime_proto_tree(self, proto_tree): 71 | '''Prime a ProtoTree-instance using the fields/protocols used in this 72 | DisplayFilter''' 73 | mod.dfilter_prime_proto_tree(self.cdata, proto_tree.cdata) 74 | 75 | @staticmethod 76 | def free(self, cdata): 77 | mod.dfilter_free(cdata) 78 | 79 | def dump(self): 80 | '''Print bytecode to stdout''' 81 | mod.dfilter_dump(self.cdata) 82 | -------------------------------------------------------------------------------- /wirepy/lib/epan.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | import functools 3 | from .cdata import (CDataObject, Attribute, BooleanAttribute, ROAttribute, 4 | ROStringAttribute, InstanceAttribute) 5 | from .wireshark import iface, mod 6 | from . import glib2, ftypes, timestamp, wsutil 7 | 8 | 9 | class EPANError(Exception): 10 | pass 11 | 12 | 13 | class FieldError(EPANError): 14 | pass 15 | 16 | 17 | class InvalidFieldError(FieldError): 18 | pass 19 | 20 | 21 | class ProtocolError(EPANError): 22 | pass 23 | 24 | 25 | class InvalidProtocolError(ProtocolError): 26 | pass 27 | 28 | 29 | class FieldValue(object): 30 | pass 31 | 32 | 33 | class TrueFalseString(FieldValue): 34 | '''A true_false_string''' 35 | 36 | def __init__(self, true_string, false_string): 37 | self.true_string = true_string 38 | self.false_string = false_string 39 | 40 | def __repr__(self): 41 | return '' % (self.true_string, 42 | self.false_string) 43 | 44 | 45 | @functools.total_ordering 46 | class RangeValue(FieldValue): 47 | '''A range_string''' 48 | 49 | def __init__(self, value_min, value_max, string): 50 | self.value_min = value_min 51 | self.value_max = value_max 52 | self.string = string 53 | 54 | def __repr__(self): 55 | return '' % (self.value_min, 56 | self.value_max, self.string) 57 | 58 | def __eq__(self, other): 59 | if isinstance(other, RangeValue): 60 | return ((other.value_min, other.value_max, other.string) == 61 | (self.value_min, self.value_max, self.string)) 62 | else: 63 | False 64 | 65 | def __lt__(self, other): 66 | if isinstance(other, RangeValue): 67 | return ((other.value_max, other.value_min, other.string) > 68 | (self.value_max, other.value_min, other.string)) 69 | return False 70 | 71 | 72 | @functools.total_ordering 73 | class StringValue(FieldValue): 74 | '''A value_string''' 75 | 76 | def __init__(self, cdata): 77 | self.cdata = cdata 78 | 79 | def __repr__(self): 80 | return '' % (self.value, self.string) 81 | 82 | def __eq__(self, other): 83 | if isinstance(other, StringValue): 84 | return (self.value, self.string) == (other.value, other.string) 85 | return False 86 | 87 | def __lt__(self, other): 88 | if isinstance(other, StringValue): 89 | return (self.value, self.string) < (other.value, other.string) 90 | return False 91 | 92 | @property 93 | def value(self): 94 | return self.cdata.value 95 | 96 | @property 97 | def string(self): 98 | return iface.string(self.cdata.strptr) 99 | 100 | 101 | class ExtValueString(FieldValue, CDataObject): 102 | '''A value_string_ext''' 103 | 104 | _struct = '_value_string_ext' 105 | num_entries = ROAttribute('_vs_num_entries') 106 | name = ROStringAttribute('_vs_name') 107 | # TODO add more 108 | 109 | def __init__(self, cdata): 110 | self.cdata = cdata 111 | 112 | def __repr__(self): 113 | return '' % (self.id_, self.abbrev, 172 | self.name) 173 | 174 | @staticmethod 175 | def _iter_string_vals(strings): 176 | vals = iface.cast('value_string*', strings) 177 | i = 0 178 | while vals[i].strptr != iface.NULL: 179 | yield StringValue(vals[i]) 180 | i += 1 181 | 182 | def _iter_integer_vals(self): 183 | if self.display & self.BASE_EXT_STRING: 184 | vse = ExtValueString(iface.cast('value_string_ext*', 185 | self.strings)) 186 | vse.try_val_to_str_ext(0) # "prime" the vse 187 | yield vse 188 | for v in self._iter_string_vals(vse.strings): 189 | yield v 190 | elif self.display & self.BASE_RANGE_STRING == 0: 191 | for v in self._iter_string_vals(self.strings): 192 | yield v 193 | else: 194 | rs = iface.cast('range_string*', self.strings) 195 | i = 0 196 | while rs[i].strptr != iface.NULL: 197 | yield RangeValue(rs[i].value_min, rs[i].value_max, 198 | iface.string(rs[i].strptr)) 199 | i += 1 200 | 201 | def __iter__(self): 202 | if self.type_ == ftypes.FieldType.PROTOCOL or \ 203 | self.strings == iface.NULL: 204 | raise StopIteration 205 | if self.type_is_integer: 206 | for v in self._iter_integer_vals(): 207 | yield v 208 | elif self.type_ == ftypes.FieldType.BOOLEAN: 209 | tfs = iface.cast('true_false_string*', self.strings) 210 | yield TrueFalseString(iface.string(tfs.true_string), 211 | iface.string(tfs.false_string)) 212 | else: 213 | # Should not happen, but wireshark has a few of them... 214 | pass 215 | #raise NotImplementedError 216 | 217 | @property 218 | def base(self): 219 | return self.display & self.BASE_DISPLAY_E_MASK 220 | 221 | @property 222 | def type_is_integer(self): 223 | """True if type is one of FT_INT or FT_UINT""" 224 | return self.base != self.BASE_CUSTOM and \ 225 | self.type_ in (ftypes.FieldType.INT8, ftypes.FieldType.INT16, 226 | ftypes.FieldType.INT32, ftypes.FieldType.INT32, 227 | ftypes.FieldType.INT64, ftypes.FieldType.UINT8, 228 | ftypes.FieldType.UINT16, ftypes.FieldType.UINT32, 229 | ftypes.FieldType.UINT64) 230 | 231 | @property 232 | def strings(self): 233 | '''value_string, range_string or true_false_string, typically converted 234 | by VALS(), RVALS() or TFS(). If this is an FT_PROTOCOL then it points 235 | to the associated protocol_t structure''' # TODO 236 | return self.cdata.strings 237 | 238 | @property 239 | def parent(self): 240 | '''parent protocol''' 241 | return self.cdata.parent # TODO either a field or a protocol 242 | 243 | 244 | class Protocol(object): 245 | 246 | def __init__(self, id): 247 | self.id = id 248 | 249 | def __iter__(self): 250 | cookie = iface.new('void **cookie') 251 | f = mod.proto_get_first_protocol_field(self.id, cookie) 252 | while f != iface.NULL: 253 | yield Field(f) 254 | f = mod.proto_get_next_protocol_field(cookie) 255 | 256 | def __getitem__(self, fieldname): 257 | for field in self: 258 | if field.abbrev == fieldname: 259 | return field 260 | raise KeyError 261 | 262 | def __repr__(self): 263 | s = '' 271 | 272 | @classmethod 273 | def by_id(cls, id): 274 | return cls(id) 275 | 276 | @classmethod 277 | def by_filter_name(cls, name): 278 | id = mod.proto_get_id_by_filter_name(name.encode()) 279 | if id == -1: 280 | raise InvalidProtocolError 281 | return cls.by_id(id) 282 | 283 | @property 284 | def is_private(self): 285 | return bool(mod.proto_is_private(self.id)) 286 | 287 | @property 288 | def can_toggle_protection(self): 289 | return bool(mod.proto_can_toggle_protocol(self.id)) 290 | 291 | @property 292 | def name(self): 293 | return iface.string(mod.proto_get_protocol_name(self.id)) 294 | 295 | @property 296 | def short_name(self): 297 | proto_t = mod.find_protocol_by_id(self.id) 298 | return iface.string(mod.proto_get_protocol_short_name(proto_t)) 299 | 300 | @property 301 | def long_name(self): 302 | proto_t = mod.find_protocol_by_id(self.id) 303 | return iface.string(mod.proto_get_protocol_long_name(proto_t)) 304 | 305 | @property 306 | def is_enabled(self): 307 | proto_t = mod.find_protocol_by_id(self.id) 308 | return bool(mod.proto_is_protocol_enabled(proto_t)) 309 | 310 | @property 311 | def filter_name(self): 312 | return iface.string(mod.proto_get_protocol_filter_name(self.id)) 313 | 314 | def set_decoding(self, enabled): 315 | mod.proto_set_decoding(self.id, enabled) 316 | 317 | def set_cant_toggle(self): 318 | mod.proto_set_cant_toggle(self.id) 319 | 320 | 321 | class FieldInfo(CDataObject): 322 | _struct = 'field_info' 323 | _mods = ('ETT_',) 324 | BIG_ENDIAN = mod.FI_BIG_ENDIAN 325 | GENERATED = mod.FI_GENERATED 326 | HIDDEN = mod.FI_HIDDEN 327 | LITTLE_ENDIAN = mod.FI_LITTLE_ENDIAN 328 | URL = mod.FI_URL 329 | start = Attribute(doc='Current start of data in data source.') 330 | length = Attribute(doc='Current data length in data source.') 331 | appendix_start = Attribute(doc='Start of appendix data.') 332 | appendix_length = Attribute(doc='Length of appendix data.') 333 | tree_type = Attribute(doc='One of FieldInfo.ETT_ or -1') 334 | hfinfo = InstanceAttribute(Field, doc='Registered field information.') 335 | 336 | def __init__(self, cdata): 337 | self.cdata = cdata 338 | 339 | def __repr__(self): 340 | rep = '' 389 | 390 | 391 | class ProtoNode(CDataObject): 392 | _struct = 'proto_node' 393 | first_child = InstanceAttribute(None, doc='The first child in the tree.') 394 | last_child = InstanceAttribute(None, doc='The last child in the tree.') 395 | next = InstanceAttribute(None, doc='The sibling of this node.') 396 | parent = InstanceAttribute(None, doc='The parent of this node.') 397 | field_info = InstanceAttribute(FieldInfo, structmember='finfo', 398 | doc='A FieldInfo describing this field.') 399 | tree_data = InstanceAttribute(TreeData) 400 | 401 | def __init__(self, cdata): 402 | self.cdata = cdata 403 | 404 | def __repr__(self): 405 | r = '' 415 | 416 | @property 417 | def is_hidden(self): 418 | '''True if this node should be hidden''' 419 | return bool(mod.wrapped_proto_item_is_hidden(self.cdata)) 420 | 421 | 422 | class ProtoTree(ProtoNode): 423 | pass 424 | 425 | 426 | class ProtoItem(ProtoNode): 427 | pass 428 | 429 | 430 | class Dissect(CDataObject): 431 | '''Object encapsulation for type epan_dissect_t''' 432 | 433 | _struct = 'epan_dissect_t' 434 | tree = InstanceAttribute(ProtoTree) 435 | # TODO tvb, a packet_info - should be of great value but very detailed 436 | 437 | def __init__(self, cdata_obj=None, create_proto_tree=True, 438 | proto_tree_visible=True): 439 | if cdata_obj is None: 440 | cdata_obj = iface.new('epan_dissect_t*') 441 | self.init(cdata_obj, create_proto_tree, proto_tree_visible) 442 | self.cdata = iface.gc(cdata_obj, self.cleanup) 443 | else: 444 | self.cdata = cdata_obj 445 | 446 | @staticmethod 447 | def init(cdata_obj, create_proto_tree, proto_tree_visible): 448 | '''initialize an existing single packet dissection''' 449 | mod.epan_dissect_init(cdata_obj, create_proto_tree, proto_tree_visible) 450 | 451 | def fake_protocols(self, fake_protocols): 452 | '''Indicate whether we should fake protocols or not''' 453 | mod.epan_dissect_fake_protocols(self.cdata, fake_protocols) 454 | 455 | def run(self, wtap, frame, column_info=None): 456 | '''run a single packet dissection''' 457 | header = wtap.packetheader.cdata 458 | data = wtap.buf_ptr 459 | frame_data = frame.cdata 460 | if column_info is not None: 461 | column_info = column_info.cdata 462 | else: 463 | column_info = iface.NULL 464 | mod.epan_dissect_run(self.cdata, header, data, frame_data, column_info) 465 | 466 | def prime_cinfo(self, cinfo): 467 | mod.col_custom_prime_edt(self.cdata, cinfo.cdata) 468 | 469 | def prime_dfilter(self, dfp): 470 | '''Prime a proto_tree using the fields/protocols used in a dfilter.''' 471 | mod.epan_dissect_prime_dfilter(self.cdata, dfp.cdata) 472 | 473 | def fill_in_columns(self, fill_col_exprs=True, fill_fd_columns=True): 474 | '''fill the dissect run output into the packet list columns''' 475 | # TODO fill_fd_columns == fill FrameData-columns 476 | if not timestamp.is_initialized(): 477 | # we need this because wireshark will assert-fail in 478 | # col_fill_in_frame_data() 479 | raise RuntimeError('Timestamps need to be initialized.') 480 | mod.epan_dissect_fill_in_columns(self.cdata, fill_col_exprs, 481 | fill_fd_columns) 482 | 483 | @staticmethod 484 | def cleanup(cdata_obj): 485 | '''releases resources attached to the packet dissection. DOES NOT free 486 | the actual pointer''' 487 | mod.epan_dissect_cleanup(cdata_obj) 488 | 489 | @staticmethod 490 | def free(cdata_obj): 491 | '''Free a single packet dissection. 492 | 493 | This is basically the same as .cleanup() with another call to 494 | g_free() on the pointer. 495 | ''' 496 | mod.epan_dissect_free(cdata_obj) 497 | 498 | 499 | def iter_fields(): 500 | for id_ in range(mod.proto_registrar_n()): 501 | yield Field(id_) 502 | 503 | 504 | def iter_protocols(): 505 | cookie = iface.new('void **') 506 | i = mod.proto_get_first_protocol(cookie) 507 | while i != -1: 508 | yield Protocol(i) 509 | i = mod.proto_get_next_protocol(cookie) 510 | 511 | 512 | def get_version(): 513 | return iface.string(mod.epan_get_version()) 514 | 515 | 516 | def get_compiled_version_info(string=''): 517 | s = glib2.String(string) 518 | mod.epan_get_compiled_version_info(s.cdata) 519 | return str(s) 520 | 521 | 522 | def report_open_failure_message(filename, err, for_writing): 523 | print(('open_failure', filename, err, for_writing)) 524 | 525 | 526 | def report_read_failure_message(filename, err): 527 | print(('read_failure', filename, err)) 528 | 529 | 530 | def report_write_failure_message(filename, err): 531 | print(('write_failure', filename, err)) 532 | 533 | 534 | def report_failure_message(msg): 535 | print('failure', msg) 536 | 537 | 538 | def epan_init(init_timestamp=True): 539 | if mod.WIREPY_EPAN_INITIALIZED: 540 | # a second call to epan_init() will raise an assertion error inside 541 | # wireshark 542 | return 543 | if 'with Python' in get_compiled_version_info(): 544 | raise RuntimeError('Wireshark is no good') 545 | 546 | @iface.callback('void(const char *, int, gboolean)') 547 | def open_failure_message_cb(filename, err, for_writing): 548 | report_open_failure_message(filename, err, for_writing) 549 | 550 | @iface.callback('void(const char *, int)') 551 | def read_failure_message_cb(filename, err): 552 | report_read_failure_message(filename, err) 553 | 554 | @iface.callback('void(const char*, int)') 555 | def write_failure_message_cb(filename, err): 556 | report_write_failure_message(filename, err) 557 | 558 | @iface.callback('void(const char *, int)') 559 | def failure_message_cb(msg, size): 560 | report_failure_message(iface.string(msg, size)) 561 | 562 | mod.failure_message = failure_message_cb 563 | 564 | # One MUST call init_process_policies before epan_init 565 | wsutil.init_process_policies() 566 | 567 | mod.wrapped_epan_init(open_failure_message_cb, 568 | read_failure_message_cb, 569 | write_failure_message_cb) 570 | mod.WIREPY_EPAN_INITIALIZED = True 571 | atexit.register(mod.epan_cleanup) 572 | 573 | if init_timestamp: 574 | timestamp.set_type(timestamp.RELATIVE) 575 | timestamp.set_precision(timestamp.PREC_FIXED_SEC) 576 | timestamp.set_seconds_type(timestamp.SECONDS_DEFAULT) 577 | 578 | 579 | def init_dissection(): 580 | '''Initialize all data structures used for dissection.''' 581 | mod.init_dissection() 582 | 583 | 584 | def cleanup_dissection(): 585 | '''extern void init_dissection''' # TODO 586 | mod.cleanup_dissection() 587 | -------------------------------------------------------------------------------- /wirepy/lib/ftypes.py: -------------------------------------------------------------------------------- 1 | from .wireshark import iface, mod 2 | from . import cdata 3 | 4 | # libwireshark segfaults if ftypes_initialize() has not been called 5 | # which is only exposed through epan_init() 6 | 7 | 8 | class FTypeError(Exception): 9 | pass 10 | 11 | 12 | class OperationNotPossible(FTypeError): 13 | pass 14 | 15 | 16 | class CantParseValue(FTypeError): 17 | 18 | def __init__(self, messages): 19 | self.messages = messages 20 | 21 | 22 | class InvalidFieldType(FTypeError): 23 | pass 24 | 25 | 26 | # TODO Memoize this, it's just an integer after all 27 | class FieldType(object): 28 | '''A ftenum_t''' 29 | ABSOLUTE_TIME = mod.FT_ABSOLUTE_TIME #: Absolute time 30 | BOOLEAN = mod.FT_BOOLEAN #: Bool 31 | BYTES = mod.FT_BYTES #: Raw bytes 32 | DOUBLE = mod.FT_DOUBLE #: Double 33 | ETHER = mod.FT_ETHER #: Ethernet 34 | ETHER_LEN = mod.FT_ETHER_LEN #: Ethernet 35 | EUI64 = mod.FT_EUI64 #: 64-Bit extended unique identifier 36 | EUI64_LEN = mod.FT_EUI64_LEN #: eui64_len 37 | FLOAT = mod.FT_FLOAT #: Float 38 | FRAMENUM = mod.FT_FRAMENUM #: Frame number 39 | GUID = mod.FT_GUID #: GUID 40 | GUID_LEN = mod.FT_GUID_LEN #: GUID 41 | INT16 = mod.FT_INT16 #: 16 bit wide integer 42 | INT24 = mod.FT_INT24 #: 24 bit wide integer 43 | INT32 = mod.FT_INT32 #: 32 bit wide integer 44 | INT64 = mod.FT_INT64 #: 64 bit wide integer 45 | INT8 = mod.FT_INT8 #: 8 bit wide integer 46 | IPXNET = mod.FT_IPXNET #: IPX 47 | IPXNET_LEN = mod.FT_IPXNET_LEN #: IPX 48 | IPv4 = mod.FT_IPv4 #: IPv4 49 | IPv4_LEN = mod.FT_IPv4_LEN #: IPv4 50 | IPv6 = mod.FT_IPv6 #: IPv6 51 | IPv6_LEN = mod.FT_IPv6_LEN #: IPv6 52 | NONE = mod.FT_NONE #: Special 53 | OID = mod.FT_OID #: OID 54 | PCRE = mod.FT_PCRE #: PCRE 55 | PROTOCOL = mod.FT_PROTOCOL #: Protocol 56 | RELATIVE_TIME = mod.FT_RELATIVE_TIME #: Relative time 57 | STRING = mod.FT_STRING #: String 58 | STRINGZ = mod.FT_STRINGZ #: String 59 | UINT16 = mod.FT_UINT16 #: Unsigned 16 bit wide integer 60 | UINT24 = mod.FT_UINT24 #: Unsigned 24 bit wide integer 61 | UINT32 = mod.FT_UINT32 #: Unsigned 32 bit wide integer 62 | UINT64 = mod.FT_UINT64 #: Unsigned 64 bit wide integer 63 | UINT8 = mod.FT_UINT8 #: Unsigned 8 bit wide integer 64 | UINT_BYTES = mod.FT_UINT_BYTES #: Raw bytes 65 | UINT_STRING = mod.FT_UINT_STRING #: Raw bytes 66 | NUM_TYPES = mod.FT_NUM_TYPES #: The number of field types 67 | 68 | def __init__(self, ftenum): 69 | if ftenum not in range(self.NUM_TYPES): 70 | raise InvalidFieldType(ftenum) 71 | self.ftenum = ftenum 72 | 73 | def __repr__(self): 74 | r = '' 81 | 82 | def __int__(self): 83 | return self.ftenum 84 | 85 | def __hash__(self): 86 | return hash(self.ftenum) 87 | 88 | def __eq__(self, other): 89 | return int(self) == int(other) 90 | 91 | def value_from_unparsed(self, s, allow_partial_value=False): 92 | '''Create a new Value from an unparsed string representation''' 93 | logs = mod.LogFuncWrapper() 94 | with logs: 95 | fvalue_t = mod.fvalue_from_unparsed(self.ftenum, str(s).encode(), 96 | allow_partial_value, 97 | mod.logfunc_wrapper) 98 | if fvalue_t == iface.NULL: 99 | raise CantParseValue(logs.messages) 100 | # TODO what to do if value has been parsed and there are messages? 101 | # stick them into python's warning system? 102 | return Value(fvalue_t) 103 | 104 | @property 105 | def name(self): 106 | '''The name of this FieldType''' 107 | return iface.string(mod.ftype_name(self.ftenum)) 108 | 109 | @property 110 | def pretty_name(self): 111 | '''A more human-friendly name of this FieldType''' 112 | return iface.string(mod.ftype_pretty_name(self.ftenum)) 113 | 114 | # TODO do we really need these? It's only a null-check... 115 | @property 116 | def can_slice(self): 117 | return bool(mod.ftype_can_slice(self.ftenum)) 118 | 119 | @property 120 | def can_eq(self): 121 | return bool(mod.ftype_can_eq(self.ftenum)) 122 | 123 | @property 124 | def can_ne(self): 125 | return bool(mod.ftype_can_ne(self.ftenum)) 126 | 127 | @property 128 | def can_gt(self): 129 | return bool(mod.ftype_can_gt(self.ftenum)) 130 | 131 | @property 132 | def can_ge(self): 133 | return bool(mod.ftype_can_ge(self.ftenum)) 134 | 135 | @property 136 | def can_lt(self): 137 | return bool(mod.ftype_can_lt(self.ftenum)) 138 | 139 | @property 140 | def can_le(self): 141 | return bool(mod.ftype_can_le(self.ftenum)) 142 | 143 | @property 144 | def can_contains(self): 145 | return bool(mod.ftype_can_contains(self.ftenum)) 146 | 147 | @property 148 | def can_matches(self): 149 | return bool(mod.ftype_can_matches(self.ftenum)) 150 | 151 | 152 | def iter_ftypes(): 153 | for i in range(FieldType.NUM_TYPES): 154 | yield FieldType(i) 155 | 156 | 157 | class Type(cdata.CDataObject): 158 | '''A _ftype_t''' 159 | 160 | _struct = '_ftype_t*' 161 | ftype = cdata.InstanceAttribute(FieldType) 162 | name = cdata.StringAttribute() 163 | pretty_name = cdata.StringAttribute() 164 | wire_size = cdata.Attribute() 165 | 166 | def __init__(self, cdata): 167 | self.cdata = cdata 168 | 169 | def __repr__(self): 170 | return '' % (self.name, ) 171 | 172 | def new(self): 173 | fv = iface.new('fvalue_t*') 174 | fv.ftype = self.cdata 175 | if self.new_value is not None: 176 | self.new_value(fv) 177 | return Value(fv) 178 | 179 | @property 180 | def new_value(self): 181 | return self.cdata.new_value 182 | 183 | @property 184 | def free_value(self): 185 | return self.cdata.free_value 186 | 187 | @property 188 | def from_unparsed(self): 189 | return self.cdata.val_from_unparsed 190 | 191 | @property 192 | def from_string(self): 193 | return self.cdata.val_from_string 194 | 195 | @property 196 | def to_string_repr(self): 197 | return self.cdata.val_to_string_repr 198 | 199 | @property 200 | def len_string_repr(self): 201 | f = self.cdata.len_string_repr 202 | return None if f == iface.NULL else f 203 | 204 | @property 205 | def set(self): 206 | return self.cdata.set_value 207 | 208 | @property 209 | def set_uinteger(self): 210 | return self.cdata.set_value_uinteger 211 | 212 | # TODO 213 | 214 | @property 215 | def get_value(self): 216 | return self.cdata.get_value or None 217 | 218 | @property 219 | def len(self): 220 | return self.cdata.len or None 221 | 222 | @property 223 | def cmp_eq(self): 224 | return self.cdata.cmp_eq or None 225 | 226 | @property 227 | def cmp_ne(self): 228 | return self.cdata.cmp_ne or None 229 | 230 | @property 231 | def cmp_gt(self): 232 | return self.cdata.cmp_gt or None 233 | 234 | @property 235 | def cmp_ge(self): 236 | return self.cdata.cmp_ge or None 237 | 238 | @property 239 | def cmp_lt(self): 240 | return self.cdata.cmp_lt or None 241 | 242 | @property 243 | def cmp_le(self): 244 | return self.cdata.cmp_le or None 245 | 246 | 247 | class Value(object): 248 | '''A fvalue_t''' 249 | REPR_DISPLAY = mod.FTREPR_DISPLAY 250 | REPR_DFILTER = mod.FTREPR_DFILTER 251 | 252 | def __init__(self, cdata): 253 | self.cdata = cdata 254 | 255 | def __repr__(self): 256 | try: 257 | r = 'displayed as "%s"' % (self.to_string_repr(self.REPR_DISPLAY), ) 258 | except OperationNotPossible: 259 | try: 260 | r = 'filtered as "%s"' % (self.to_string_repr(self.REPR_DFILTER), ) 261 | except OperationNotPossible: 262 | r = '' 263 | return '' % (self.type_.name, r) 264 | 265 | def __len__(self): 266 | '''The length in bytes of this value. Falls back to the wire_size if 267 | the true length is not available''' 268 | try: 269 | return self.len() 270 | except OperationNotPossible: 271 | return self.type_.wire_size 272 | 273 | def __eq__(self, other): 274 | if isinstance(other, Value): 275 | return self.cmp_eq(other.cdata) 276 | else: 277 | return False 278 | 279 | def __ne__(self, other): 280 | if isinstance(other, Value): 281 | return self.cmp_ne(other.cdata) 282 | else: 283 | return False 284 | 285 | def __gt__(self, other): 286 | if isinstance(other, Value): 287 | return self.cmp_gt(other.cdata) 288 | else: 289 | return False 290 | 291 | def __ge__(self, other): 292 | if isinstance(other, Value): 293 | return self.cmp_ge(other.cdata) 294 | else: 295 | return False 296 | 297 | def __lt__(self, other): 298 | if isinstance(other, Value): 299 | return self.cmp_lt(other.cdata) 300 | else: 301 | return False 302 | 303 | def __le__(self, other): 304 | if isinstance(other, Value): 305 | return self.cmp_le(other.cdata) 306 | else: 307 | return False 308 | 309 | @property 310 | def type_(self): 311 | return Type(self.cdata.ftype) 312 | 313 | def new(self): 314 | '''Allocate and initialize a Value''' 315 | # TODO: you get the real pointer... 316 | self.type.new_value(cdata) 317 | return Value(cdata) 318 | 319 | def free(self): 320 | self.type.free_value(self.cdata) 321 | 322 | def to_string_repr(self, rtype=None): 323 | '''A human-readable string representation of this value. Raises 324 | OperationNotPossible if the value cannot be represented in the given 325 | rtype.''' # TODO 326 | if rtype is None: 327 | rtype = self.REPR_DISPLAY 328 | l = self.len_string_repr(rtype) 329 | if l == -1: 330 | raise OperationNotPossible 331 | buf = iface.new('char[]', l + 1) 332 | self.type_.to_string_repr(self.cdata, rtype, buf) 333 | return iface.string(buf, l) 334 | 335 | def len_string_repr(self, rtype): 336 | '''Returns the length of the string required to hold the string 337 | representation of the field value. 338 | 339 | Returns -1 if the string cannot be represented in the given rtype. 340 | 341 | The length DOES NOT include the terminating NUL.''' 342 | f = self.type_.len_string_repr 343 | if f is None: 344 | raise OperationNotPossible 345 | return f(self.cdata, rtype) 346 | 347 | def len(self): 348 | func = self.type_.len 349 | if func is None: 350 | raise OperationNotPossible 351 | return func(self.cdata) 352 | 353 | def cmp_eq(self, other): 354 | func = self.type_.cmp_eq 355 | if func is None: 356 | raise OperationNotPossible 357 | return bool(func(self.cdata, other)) 358 | 359 | def cmp_ne(self, other): 360 | func = self.type_.cmp_ne 361 | if func is None: 362 | raise OperationNotPossible 363 | return bool(func(self.cdata, other)) 364 | 365 | def cmp_gt(self, other): 366 | func = self.type_.cmp_gt 367 | if func is None: 368 | raise OperationNotPossible 369 | return bool(func(self.cdata, other)) 370 | 371 | def cmp_ge(self, other): 372 | func = self.type_.cmp_ge 373 | if func is None: 374 | raise OperationNotPossible 375 | return bool(func(self.cdata, other)) 376 | 377 | def cmp_lt(self, other): 378 | func = self.type_.cmp_lt 379 | if func is None: 380 | raise OperationNotPossible 381 | return bool(func(self.cdata, other)) 382 | 383 | def cmp_le(self, other): 384 | func = self.type_.cmp_le 385 | if func is None: 386 | raise OperationNotPossible 387 | return bool(func(self.cdata, other)) 388 | 389 | def cmp_bitwise_and(self, other): 390 | return bool(self.type_.cmp_bitwise_and(self.cdata, self)) 391 | -------------------------------------------------------------------------------- /wirepy/lib/glib2.py: -------------------------------------------------------------------------------- 1 | """GLib2-related objects used by libwireshark.""" 2 | 3 | from . import cdata 4 | from .wireshark import iface, mod 5 | 6 | 7 | def from_gchar(cdata, free=True): 8 | '''Build a python-string from a gchar*''' 9 | s = iface.string(cdata) 10 | if free: 11 | mod.g_free(cdata) 12 | return s 13 | 14 | 15 | class String(cdata.CDataObject): 16 | """A GString""" 17 | 18 | _struct = 'GString' 19 | string = cdata.ROStringAttribute('str') 20 | len = cdata.ROAttribute('len', doc='The length of the string.') 21 | allocated_len = cdata.ROAttribute('allocated_len', 22 | doc='Amount of allocated memory.') 23 | 24 | def __init__(self, string): 25 | buf = str(string).encode() # TODO py2/py3k 26 | gstring = mod.g_string_new_len(buf, len(buf)) 27 | if gstring == iface.NULL: 28 | raise MemoryError # pragma: no cover 29 | self.cdata = iface.gc(gstring, self.free) 30 | 31 | def __len__(self): 32 | return self.len 33 | 34 | def __str__(self): 35 | return self.string 36 | 37 | @staticmethod 38 | def free(cdata_obj): 39 | """Frees the memory allocated for the GString.""" 40 | # always free_segment 41 | mod.g_string_free(cdata_obj, True) 42 | 43 | 44 | class SinglyLinkedListIterator(cdata.CDataObject): 45 | """A singly-linked list (GSList).""" 46 | 47 | _struct = 'GSList' 48 | next = cdata.InstanceAttribute(None, doc='The next item in the list.') 49 | 50 | def __init__(self, init, callable=None, gc=True): 51 | self.callable = callable 52 | if not isinstance(init, iface.CData): 53 | raise TypeError(type(init)) 54 | if gc: 55 | self.cdata = iface.gc(cdata, mod.g_slist_free(init)) 56 | else: 57 | self.cdata = init 58 | 59 | @staticmethod 60 | def free(cdata_obj): 61 | mod.g_slist_free(cdata_obj) 62 | 63 | def _cast(self, data): 64 | if data == iface.NULL: 65 | return 66 | if self.callable is not None: 67 | return self.callable(data) 68 | return data 69 | 70 | def __len__(self): 71 | return mod.g_slist_length(self.cdata) 72 | 73 | def __iter__(self): 74 | """Iterate of all data-items in the list.""" 75 | yield self._cast(self.cdata.data) 76 | n = self.cdata.next 77 | while n != iface.NULL: 78 | yield self._cast(n.data) 79 | n = n.next 80 | -------------------------------------------------------------------------------- /wirepy/lib/prefs.py: -------------------------------------------------------------------------------- 1 | from .wireshark import iface, mod 2 | from . import cdata 3 | 4 | 5 | class PreferencesError(Exception): 6 | pass 7 | 8 | 9 | class PrefSyntaxError(PreferencesError): 10 | pass 11 | 12 | 13 | class NoSuchPreference(PreferencesError): 14 | pass 15 | 16 | 17 | class PrefObsolete(PreferencesError): 18 | pass 19 | 20 | 21 | class Preferences(cdata.CDataObject): 22 | 23 | _mod_defs = ('PREFS_', ) 24 | file_ = cdata.ROStringAttribute('file') 25 | num_cols = cdata.ROAttribute() 26 | capture_device = cdata.ROStringAttribute() 27 | capture_devices_linktype = cdata.ROStringAttribute() 28 | capture_devices_desc = cdata.ROStringAttribute() 29 | capture_prom_mode = cdata.BooleanAttribute(can_write=False) 30 | 31 | def __init__(self, cdata): 32 | self.cdata = cdata 33 | 34 | @property 35 | def is_global_prefs(self): 36 | '''Returns True if this instance points to the global preferences''' 37 | return self.cdata == mod.prefs 38 | 39 | ''' 40 | class FormatList(glib2.DoublyLinkedList): 41 | 42 | @staticmethod 43 | def cast_to_object(data): 44 | fmt_data = iface.cast('fmt_data*', data) 45 | return Format(fmt_data) 46 | 47 | @property 48 | def col_list(self): 49 | l = self.cdata.col_list 50 | if l != iface.NULL: 51 | return self.FormatList(l, backref=self) 52 | # TODO colum.FormatData entries 53 | ''' 54 | 55 | def set_pref(self, prefstring): 56 | '''Given a string of the form ":", parse it and 57 | set the preference in question''' 58 | prefs_set_pref_e = mod.prefs_set_pref(prefstring.encode()) 59 | print(prefs_set_pref_e) 60 | if prefs_set_pref_e == self.PREFS_SET_OK: 61 | return 62 | elif prefs_set_pref_e == self.PREFS_SET_SYNTAX_ERR: 63 | raise PrefSyntaxError 64 | elif prefs_set_pref_e == self.PREFS_SET_NO_SUCH_PREF: 65 | raise NoSuchPreference 66 | elif prefs_set_pref_e == self.PREFS_SET_OBSOLETE: 67 | raise PrefObsolete 68 | else: 69 | raise PreferencesError('Unknown error') 70 | 71 | 72 | def register_modules(): 73 | mod.prefs_register_modules() 74 | 75 | 76 | def apply_all(): 77 | '''Call the "apply"-callback function for each module if any of its 78 | preferences have changed.''' 79 | mod.prefs_apply_all() 80 | 81 | 82 | def copy(src): 83 | '''Copy a set of preferences''' 84 | e_prefs = iface.new('e_prefs*') 85 | iface.gc(mod.copy_prefs(e_prefs, src.cdata), mod.prefs_free) 86 | return Preferences(e_prefs) 87 | 88 | 89 | def write(to_stdout=False): 90 | '''Write the global preferences to the user's preference-file; write to 91 | stdout if to_stdout is True.''' 92 | if to_stdout: 93 | pf_path_return = iface.NULL 94 | else: 95 | pf_path_return = iface.new('char**') 96 | res = mod.write_prefs(pf_path_return) 97 | if res == 0: 98 | return 99 | # TODO res == errno 100 | # TODO the filepath is leaking 101 | raise OSError('Failed on %s' % (iface.string(pf_path_return[0]), )) 102 | 103 | 104 | def get_global_prefs(): 105 | '''''' # TODO 106 | return Preferences(iface.addressof(mod.prefs)) 107 | 108 | 109 | def read_prefs(): 110 | '''Read the preferences file, make it global and return a new 111 | Preferences-instance''' 112 | gpf_open_errno = iface.new('int *') 113 | gpf_read_errno = iface.new('int *') 114 | gpf_path = iface.new('char **') 115 | pf_open_errno = iface.new('int *') 116 | pf_read_errno = iface.new('int *') 117 | pf_path = iface.new('char **') 118 | e_prefs = mod.read_prefs(gpf_open_errno, gpf_read_errno, gpf_path, 119 | pf_open_errno, pf_read_errno, pf_path) 120 | if gpf_path[0] != iface.NULL: 121 | if gpf_open_errno[0] != 0: 122 | raise NotImplementedError # TODO cant open global preference file 123 | elif gpf_read_errno[0] != 0: 124 | raise NotImplementedError # TODO i/o errror reading global prefs file 125 | else: 126 | raise AssertionError 127 | if pf_path[0] != iface.NULL: 128 | if pf_open_errno[0] != 0: 129 | raise NotImplementedError # TODO cant open your preference file 130 | elif pf_read_errno[0] != 0: 131 | raise NotImplementedError # TODO i/o error reading your prefs file 132 | else: 133 | raise AssertionError 134 | #TODO g_free(pf_path) 135 | #pf_path = NULL 136 | assert e_prefs != iface.NULL 137 | return Preferences(e_prefs) 138 | 139 | 140 | def get_column_format(col_idx): 141 | fmt = mod.get_column_format(col_idx) 142 | if fmt == -1: 143 | raise IndexError 144 | return fmt 145 | 146 | 147 | def set_column_format(col_idx, fmt): 148 | mod.set_column_format(col_idx, fmt) 149 | 150 | 151 | def get_column_title(col_idx): 152 | title = mod.get_column_title(col_idx) 153 | # These functions mask IndexErrors. Meh 154 | return iface.string(title) if title != iface.NULL else None 155 | 156 | 157 | def set_column_title(col_idx, title): 158 | mod.set_column_title(col_idx, title.encode()) 159 | 160 | 161 | def get_column_visible(col_idx): 162 | return bool(mod.get_column_visible(col_idx)) 163 | 164 | 165 | def set_column_visible(col_idx, visible): 166 | mod.set_column_visible(col_idx, visible) 167 | 168 | 169 | def get_column_resolved(col_idx): 170 | # Note: Returns True for invalid col_idx. Meh 171 | return mod.get_column_resolved(col_idx) 172 | 173 | 174 | def set_column_resolved(col_idx, resolved): 175 | mod.set_column_resolved(col_idx, resolved) 176 | 177 | 178 | def get_column_custom_field(col_idx): 179 | f = mod.get_column_custom_field(col_idx) 180 | return iface.string(f) if f != iface.NULL else None 181 | 182 | 183 | def set_column_custom_field(col_idx, custom_field): 184 | mod.set_column_custom_field(col_idx, custom_field.encode()) 185 | 186 | 187 | def get_column_custom_occurrence(col_idx): 188 | return mod.get_column_custom_occurrence(col_idx) 189 | 190 | 191 | def set_column_custom_occurrence(col_idx, custom_occurrence): 192 | mod.set_column_custom_occurrence(col_idx, custom_occurrence) 193 | -------------------------------------------------------------------------------- /wirepy/lib/timestamp.py: -------------------------------------------------------------------------------- 1 | """Functions to get/set the timestamp-type behaviour of wireshark.""" 2 | from .wireshark import mod 3 | 4 | RELATIVE = mod.TS_RELATIVE #: Since start of capture 5 | ABSOLUTE = mod.TS_ABSOLUTE #: Absolute 6 | ABSOLUTE_WITH_DATE = mod.TS_ABSOLUTE_WITH_DATE #: Absolute with date 7 | DELTA = mod.TS_DELTA #: Since previously captured packet 8 | DELTA_DIS = mod.TS_DELTA_DIS #: Since previously displayed packet 9 | EPOCH = mod.TS_EPOCH #: Seconds (and fractions) since epoch 10 | UTC = mod.TS_UTC #: UTC time 11 | UTC_WITH_DATE = mod.TS_UTC_WITH_DATE #: UTC time with date 12 | 13 | NOT_SET = mod.TS_NOT_SET #: Special value, timestamp type not set 14 | PREC_AUTO = mod.TS_PREC_AUTO #: Special value, automatic precision 15 | PREC_FIXED_SEC = mod.TS_PREC_FIXED_SEC #: Fixed to-seconds precision 16 | #TODO add more 17 | SECONDS_DEFAULT = mod.TS_SECONDS_DEFAULT #: . 18 | SECONDS_HOUR_MIN_SEC = mod.TS_SECONDS_HOUR_MIN_SEC #: . 19 | SECONDS_NOT_SET = mod.TS_SECONDS_NOT_SET #: . 20 | 21 | 22 | class TimestampError(Exception): 23 | '''Base-class for all timestamp-related errors.''' 24 | pass 25 | 26 | 27 | class InvalidTimestampValue(TimestampError): 28 | '''An invalid timestamp-type was used.''' 29 | 30 | 31 | def get_type(): 32 | '''Get the currently set timestamp-type. 33 | 34 | :returns: 35 | an opaque integer, e.g. :py:const:`NOT_SET` 36 | ''' 37 | return mod.timestamp_get_type() 38 | 39 | 40 | def set_type(ts_type): 41 | '''Set the globally used timestamp-type. 42 | 43 | :params ts_type: 44 | A timestamp-type from this module, e.g. :py:const:`RELATIVE`. 45 | ''' 46 | if ts_type < 0 or ts_type > NOT_SET: 47 | raise InvalidTimestampValue 48 | mod.timestamp_set_type(ts_type) 49 | 50 | 51 | def get_precision(): 52 | '''Get the currently set timestamp-precision. 53 | 54 | :returns: 55 | an opaque integer, e.g. :py:const:`PREC_FIXED_SEC` 56 | ''' 57 | return mod.timestamp_get_precision() 58 | 59 | 60 | def set_precision(tsp): 61 | '''Set the globally used timestamp-precision. 62 | 63 | :param tsp: 64 | A timestamp-precision constant like :py:const:`PREC_FIXED_SEC`. 65 | ''' 66 | # TODO check tsp against ... what? 67 | return mod.timestamp_set_precision(tsp) 68 | 69 | 70 | def get_seconds_type(): 71 | '''Get the currently set seconds-type. 72 | 73 | :returns: 74 | an opaque int, e.g. of :py:const:`SECONDS_DEFAULT`. 75 | ''' 76 | return mod.timestamp_get_seconds_type() 77 | 78 | 79 | def set_seconds_type(ts_seconds_type): 80 | '''Set the globally used timestamp-second-precision. 81 | 82 | :params ts_seconds_type: 83 | A timestamp-second-type, e.g. :py:const:`SECONDS_DEFAULT`. 84 | ''' 85 | if ts_seconds_type < 0 or ts_seconds_type > SECONDS_NOT_SET: 86 | raise InvalidTimestampValue 87 | mod.timestamp_set_seconds_type(ts_seconds_type) 88 | 89 | 90 | def is_initialized(): 91 | '''Check if the globally used timestamp settings have been set. 92 | 93 | :returns: 94 | True if the timestamp-type and seconds-type are set. 95 | ''' 96 | return get_type() != NOT_SET and get_seconds_type() != SECONDS_NOT_SET 97 | -------------------------------------------------------------------------------- /wirepy/lib/wireshark.py: -------------------------------------------------------------------------------- 1 | '''Stub module''' 2 | 3 | import threading 4 | 5 | from .cdef import iface 6 | from .. import platform 7 | 8 | #: The cffi-module to libwireshark, libwsutils and libwtap 9 | mod = iface.verify(''' 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | static const int COLUMN_FORMATS = NUM_COL_FMTS; 37 | // Declare these here instead of module variables so they 38 | // can be accessed without having to import the respective 39 | // module 40 | static int WIREPY_EPAN_INITIALIZED = 0; 41 | static int WIREPY_INIT_PROCESS_POLICIES_CALLED = 0; 42 | 43 | static void (*logfunc_python_callback)(char *msg, int size); 44 | static void logfunc_wrapper(const char*msg, ...) { 45 | char buf[2048+1]; 46 | va_list ap; 47 | va_start(ap, msg); 48 | logfunc_python_callback(buf, vsnprintf(buf, sizeof(buf), msg, ap)); 49 | va_end(ap); 50 | } 51 | 52 | static void (*failure_message)(const char *msg, int size); 53 | static void open_failure_cb(const char *msg, va_list ap) { 54 | char buf[2048+1]; 55 | failure_message(buf, vsnprintf(buf, sizeof(buf), msg, ap)); 56 | } 57 | 58 | void wrapped_epan_init(void (*report_open_failure_fcn_p)(const char *, int, gboolean), 59 | void (*report_read_failure_fcn_p)(const char *, int), 60 | void (*report_write_failure_fcn_p)(const char *, int)) { 61 | const char errmsg[] = "Failed to re-export libwireshark. Plugins will not load..."; 62 | // Hackish way to provide symbols to wireshark's plugins 63 | // that don't link in their symbols themselves 64 | if (dlopen("libwireshark.so", RTLD_NOW|RTLD_GLOBAL) == NULL) 65 | failure_message(errmsg, sizeof(errmsg)); 66 | epan_init(register_all_protocols, 67 | register_all_protocol_handoffs, 68 | NULL, NULL, 69 | open_failure_cb, 70 | report_open_failure_fcn_p, 71 | report_read_failure_fcn_p, 72 | report_write_failure_fcn_p); 73 | } 74 | 75 | const char* wrapped_dfilter_get_error_msg(void) { 76 | return dfilter_error_msg; 77 | } 78 | 79 | gboolean wrapped_proto_item_is_hidden(proto_item* item) { 80 | return PROTO_ITEM_IS_HIDDEN(item); 81 | }''', 82 | libraries=['glib-2.0', 'wiretap', 'wsutil', 'wireshark'], 83 | extra_compile_args=platform.CFLAGS, 84 | extra_link_args=platform.LIBS, 85 | ext_package='wirepy') 86 | 87 | 88 | class LogFuncWrapper(object): 89 | logfunc_lock = threading.Lock() 90 | messages = [] 91 | 92 | def __init__(self): 93 | self.messages = None 94 | 95 | def __enter__(self): 96 | LogFuncWrapper.logfunc_lock.acquire() 97 | del LogFuncWrapper.messages[:] 98 | 99 | def __exit__(self, exc_type, exc_value, traceback): 100 | try: 101 | self.messages = tuple(LogFuncWrapper.messages) 102 | finally: 103 | LogFuncWrapper.logfunc_lock.release() 104 | mod.LogFuncWrapper = LogFuncWrapper 105 | 106 | 107 | @iface.callback('void(char *msg, int size)') 108 | def logfunc_callback(msg, size): 109 | logmsg = iface.string(msg, size) 110 | LogFuncWrapper.messages.append(logmsg) 111 | mod.logfunc_python_callback = logfunc_callback 112 | -------------------------------------------------------------------------------- /wirepy/lib/wsutil.py: -------------------------------------------------------------------------------- 1 | from .wireshark import mod 2 | from . import glib2 3 | 4 | 5 | def init_process_policies(): 6 | '''Called when the program starts, to enable security features and save 7 | whatever credential information we'll need later.''' 8 | if mod.WIREPY_INIT_PROCESS_POLICIES_CALLED: 9 | return 10 | mod.init_process_policies() 11 | mod.WIREPY_INIT_PROCESS_POLICIES_CALLED = 1 12 | 13 | 14 | def started_with_special_privs(): 15 | '''Return True if this program started with special privileges. 16 | 17 | :py:func:`init_process_policies` must have been called before calling 18 | this. 19 | ''' 20 | if not mod.WIREPY_INIT_PROCESS_POLICIES_CALLED: 21 | raise RuntimeError('init_process_policies() must have been called') 22 | return bool(mod.started_with_special_privs()) 23 | 24 | 25 | def running_with_special_privs(): 26 | '''Return True if this program is running with special privileges. 27 | 28 | :py:func:`init_process_policies` must have been called before calling 29 | this. 30 | ''' 31 | if not mod.WIREPY_INIT_PROCESS_POLICIES_CALLED: 32 | raise RuntimeError('init_process_policies() must have been called') 33 | return bool(mod.running_with_special_privs()) 34 | 35 | 36 | def relinquish_special_privs_perm(): 37 | '''Permanently relinquish special privileges. 38 | 39 | :py:func:`init_process_policies` must have been called before calling 40 | this. 41 | ''' 42 | if not mod.WIREPY_INIT_PROCESS_POLICIES_CALLED: 43 | raise RuntimeError('init_process_policies() must have been called') 44 | mod.relinquish_special_privs_perm() 45 | 46 | 47 | def get_cur_username(): 48 | '''Get the current username or "UNKNOWN" on failure.''' 49 | return glib2.from_gchar(mod.get_cur_username()) 50 | 51 | 52 | def get_cur_groupname(): 53 | '''Get the current group or "UNKNOWN" on failure.''' 54 | return glib2.from_gchar(mod.get_cur_groupname()) 55 | -------------------------------------------------------------------------------- /wirepy/lib/wtap.py: -------------------------------------------------------------------------------- 1 | '''The wiretap-library is used to read capture files of various formats and 2 | encapsulation types. 3 | ''' 4 | import os 5 | from .cdata import CDataObject, Attribute, InstanceAttribute, StringAttribute 6 | from .wireshark import iface, mod 7 | from . import glib2 8 | 9 | 10 | class WTAPError(Exception): 11 | '''Base-class for all wtap-errors.''' 12 | pass 13 | 14 | 15 | class InvalidFileType(WTAPError): 16 | pass 17 | 18 | 19 | class InvalidEncapsulationType(WTAPError): 20 | pass 21 | 22 | 23 | class FileError(WTAPError): 24 | errno_to_class = {} 25 | 26 | def __init__(self, err_info, for_writing): 27 | self.err_info = err_info 28 | self.for_writing = for_writing 29 | #TODO err_info leaked and has has to be free()'d *sometimes* 30 | 31 | @staticmethod 32 | def from_errno(errno): 33 | return FileError.errno_to_class.get(errno, FileError) 34 | 35 | 36 | def _register_err(cls): 37 | '''Decorator to register an error-class into the errno_to_class-dict on 38 | class FileError''' 39 | # Let's avoid using a metaclass for this 40 | if not issubclass(cls, FileError): 41 | raise TypeError('Decorated class is not a subclass of FileError') 42 | errno = getattr(cls, 'errno') 43 | if errno is None: 44 | raise TypeError('Decorated class has no errno-attribute') 45 | if errno in FileError.errno_to_class: 46 | raise ValueError('Error %i already registered' % (errno, )) 47 | else: 48 | if errno in FileError.errno_to_class: 49 | raise ValueError(errno) 50 | FileError.errno_to_class[errno] = cls 51 | return cls 52 | 53 | 54 | @_register_err 55 | class NotRegularFile(FileError): 56 | '''The file being opened for reading isn't a plain file (or pipe)''' 57 | errno = mod.WTAP_ERR_NOT_REGULAR_FILE 58 | 59 | 60 | @_register_err 61 | class RandomOpenPipe(FileError): 62 | '''The file is being opened for random access and it's a pipe''' 63 | errno = mod.WTAP_ERR_RANDOM_OPEN_PIPE 64 | 65 | 66 | @_register_err 67 | class UnknownFormat(FileError): 68 | '''The file being opened is not a capture file in a known format''' 69 | errno = mod.WTAP_ERR_FILE_UNKNOWN_FORMAT 70 | 71 | 72 | @_register_err 73 | class Unsupported(FileError): 74 | "Supported file type, but there's something in the file we can't support" 75 | errno = mod.WTAP_ERR_UNSUPPORTED 76 | 77 | 78 | @_register_err 79 | class CantWriteToPipe(FileError): 80 | '''Wiretap can't save to a pipe in the specified format''' 81 | errno = mod.WTAP_ERR_CANT_WRITE_TO_PIPE 82 | 83 | 84 | @_register_err 85 | class CantOpen(FileError): 86 | '''The file couldn't be opened, reason unknown''' 87 | errno = mod.WTAP_ERR_CANT_OPEN 88 | 89 | 90 | @_register_err 91 | class UnsupportedFileType(FileError): 92 | '''Wiretap can't save files in the specified format''' 93 | errno = mod.WTAP_ERR_UNSUPPORTED_FILE_TYPE 94 | 95 | 96 | @_register_err 97 | class UnsupportedEncap(FileError): 98 | '''Wiretap can't read or save files in the specified format with the 99 | specified encapsulation''' 100 | errno = mod.WTAP_ERR_UNSUPPORTED_ENCAP 101 | 102 | 103 | @_register_err 104 | class EncapPerPacketUnsupported(FileError): 105 | '''The specified format doesn't support per-packet encapsulations''' 106 | errno = mod.WTAP_ERR_ENCAP_PER_PACKET_UNSUPPORTED 107 | 108 | 109 | @_register_err 110 | class CantClose(FileError): 111 | '''The file couldn't be closed, reason unknown''' 112 | errno = mod.WTAP_ERR_CANT_CLOSE 113 | 114 | 115 | @_register_err 116 | class CantRead(FileError): 117 | '''An attempt to read failed, reason unknown''' 118 | errno = mod.WTAP_ERR_CANT_READ 119 | 120 | 121 | @_register_err 122 | class ShortRead(FileError): 123 | '''An attempt to read read less data than it should have''' 124 | errno = mod.WTAP_ERR_SHORT_READ 125 | 126 | 127 | @_register_err 128 | class BadFile(FileError): 129 | '''The file appears to be damaged or corrupted or otherwise bogus''' 130 | errno = mod.WTAP_ERR_BAD_FILE 131 | 132 | 133 | @_register_err 134 | class ShortWrite(FileError): 135 | '''An attempt to write wrote less data than it should have''' 136 | errno = mod.WTAP_ERR_SHORT_WRITE 137 | 138 | 139 | @_register_err 140 | class UncompressTruncated(FileError): 141 | '''Sniffer compressed data was oddly truncated''' 142 | errno = mod.WTAP_ERR_UNC_TRUNCATED 143 | 144 | 145 | @_register_err 146 | class UncompressOverflow(FileError): 147 | '''Uncompressing Sniffer data would overflow buffer''' 148 | errno = mod.WTAP_ERR_UNC_OVERFLOW 149 | 150 | 151 | @_register_err 152 | class UncompressBadOffset(FileError): 153 | '''LZ77 compressed data has bad offset to string''' 154 | errno = mod.WTAP_ERR_UNC_BAD_OFFSET 155 | 156 | 157 | @_register_err 158 | class RandomOpenStdin(FileError): 159 | '''We're trying to open the standard input for random access''' 160 | errno = mod.WTAP_ERR_RANDOM_OPEN_STDIN 161 | 162 | 163 | @_register_err 164 | class CompressionUnsupported(FileError): 165 | '''The filetype doesn't support output compression''' 166 | errno = mod.WTAP_ERR_COMPRESSION_NOT_SUPPORTED 167 | 168 | 169 | @_register_err 170 | class CantSeek(FileError): 171 | '''An attempt to seek failed, reason unknown''' 172 | errno = mod.WTAP_ERR_CANT_SEEK 173 | 174 | 175 | @_register_err 176 | class Decompress(FileError): 177 | '''Error decompressing''' 178 | errno = mod.WTAP_ERR_DECOMPRESS 179 | 180 | 181 | @_register_err 182 | class Internal(FileError): 183 | '''''' 184 | errno = mod.WTAP_ERR_INTERNAL 185 | 186 | 187 | class EncapsulationType(object): 188 | '''An encapsulation type like "ether"''' 189 | 190 | def __init__(self, encap): 191 | # encap -1 == PER_PACKET, 0 == UNKNOWN, ... 192 | if encap not in range(-1, mod.wtap_get_num_encap_types()): 193 | errmsg = '%i is not a valid encapsulation' % (encap, ) 194 | raise InvalidEncapsulationType(errmsg) 195 | self.encap = encap 196 | 197 | def __repr__(self): 198 | r = '' 199 | return r % (self.string, self.short_string) 200 | 201 | @classmethod 202 | def from_short_string(cls, short_string): 203 | encap = mod.wtap_short_string_to_encap(short_string.encode()) 204 | if encap == -1: 205 | errmsg = 'Encapsulation-type "%s" unknown' % (short_string, ) 206 | raise InvalidEncapsulationType(errmsg) 207 | return cls(encap) 208 | 209 | @property 210 | def string(self): 211 | return iface.string(mod.wtap_encap_string(self.encap)) 212 | 213 | @property 214 | def short_string(self): 215 | return iface.string(mod.wtap_encap_short_string(self.encap)) 216 | 217 | 218 | class Frame(CDataObject): 219 | _struct = 'frame_data' 220 | frame_number = Attribute('num', doc='The frame number.') 221 | interface_id = Attribute(doc='Identifier of the interface.') 222 | pack_flags = Attribute() 223 | pkt_len = Attribute(doc='Packet length.') 224 | cap_len = Attribute(doc='Amount actually captured.') 225 | cum_bytes = Attribute(doc='Cumulative bytes into the capture') 226 | file_offset = Attribute('file_off', doc='File offset.') 227 | subnum = Attribute(doc=('Subframe number, for protocols that ' 228 | 'require this.')) 229 | link_type = InstanceAttribute(EncapsulationType, structmember='lnk_t', 230 | doc='Per-packet encapsulation/datalink type') 231 | comment = StringAttribute('opt_comment') 232 | 233 | def __init__(self, cdata): 234 | self.cdata = cdata 235 | 236 | def __repr__(self): 237 | r = '' 238 | return r % (self.frame_number, self.pkt_len, self.cap_len) 239 | 240 | @classmethod 241 | def new(cls, num, pkthdr, offset, cum_bytes): 242 | '''Constructor for a new Frame-instance''' 243 | fd = iface.gc(iface.new('frame_data *'), cls.destroy) 244 | cls.init(fd, num, pkthdr, offset, cum_bytes) 245 | return cls(fd) 246 | 247 | @staticmethod 248 | def init(cdata, num, pkthdr, offset, cum_bytes): 249 | return mod.frame_data_init(cdata, num, pkthdr, offset, cum_bytes) 250 | 251 | @staticmethod 252 | def destroy(cdata): 253 | return mod.frame_data_destroy(cdata) 254 | 255 | def compare(self, other, field): 256 | mod.frame_data_compare(self.cdata, other, field) 257 | 258 | def set_before_dissect(self, elapsed_time, first_ts, prev_dis, prev_cap): 259 | mod.frame_data_set_before_dissect(self.cdata, elapsed_time, first_ts, 260 | prev_dis, prev_cap) 261 | 262 | def set_after_dissect(self, cum_bytes, prev_dis_ts): 263 | mod.frame_data_set_after_dissect(self.cdata, cum_bytes, prev_dis_ts) 264 | 265 | @property 266 | def proto_data(self): 267 | '''Per-frame proto data''' 268 | return self.cdata.pfd # TODO a GSList of some type 269 | 270 | @property 271 | def flags(self): 272 | '''''' # TODO 273 | return self.cdata.flags 274 | 275 | @property 276 | def color_filter(self): 277 | '''Per-packet matching color_t_filter_t object''' # TODO 278 | return self.cdata.color_filter 279 | 280 | @property 281 | def abs_ts(self): 282 | '''Absolute timestamp''' 283 | return self.cdata.abs_ts # TODO A nstime_t 284 | 285 | @property 286 | def shift_offset(self): 287 | '''How much the abs_ts of the frame is shifted''' 288 | return self.cdata.shift_offset 289 | 290 | @property 291 | def rel_ts(self): 292 | '''Relative timestamp (yes, it can be negative)''' 293 | return self.cdata.rel_ts 294 | 295 | @property 296 | def del_dis_ts(self): 297 | '''Delta timestamp to previous displayed frame (yes, can be negative''' 298 | return self.cdata.del_ts_ts 299 | 300 | @property 301 | def del_cap_ts(self): 302 | '''Delta timestamp to previous captured frame (yes, can be negative''' 303 | return self.cdata.del_cap_ts 304 | 305 | 306 | class PacketHeader(CDataObject): 307 | '''A wtap_pkthdr from wtap.h''' 308 | 309 | _struct = 'wtap_pkthdr' 310 | presence_flags = Attribute(doc='What stuff do we have?') 311 | # timestamp 312 | caplen = Attribute(doc='Data length in the file.') 313 | len = Attribute(doc='Data length on the wire.') 314 | pkt_encap = InstanceAttribute(EncapsulationType, 315 | doc=('The EncapsulationType of the ' 316 | 'current packet.')) 317 | interface_id = Attribute(doc='Identifier of the interface.') 318 | comment = StringAttribute('opt_comment', doc='Optional comment.') 319 | drop_count = Attribute(doc='Number of packets lost.') 320 | 321 | HAS_TS = mod.WTAP_HAS_TS 322 | HAS_CAP_LEN = mod.WTAP_HAS_CAP_LEN 323 | HAS_INTERFACE_ID = mod.WTAP_HAS_INTERFACE_ID 324 | HAS_COMMENTS = mod.WTAP_HAS_COMMENTS 325 | HAS_DROP_COUNT = mod.WTAP_HAS_DROP_COUNT 326 | HAS_PACK_FLAGS = mod.WTAP_HAS_PACK_FLAGS 327 | 328 | def __init__(self, cdata_obj): 329 | self.cdata = cdata_obj 330 | 331 | def is_flag_set(self, flag): 332 | return bool(self.presence_flags & flag) 333 | 334 | @property 335 | def timestamp(self): 336 | return self.cdata.ts # TODO a wtap_nstime 337 | 338 | 339 | # TODO garbage collection 340 | class WTAP(object): 341 | '''A wtap from wtap.h''' 342 | FILE_TSPREC_SEC = mod.WTAP_FILE_TSPREC_SEC 343 | FILE_TSPREC_DSEC = mod.WTAP_FILE_TSPREC_DSEC 344 | FILE_TSPREC_CSEC = mod.WTAP_FILE_TSPREC_CSEC 345 | FILE_TSPREC_MSEC = mod.WTAP_FILE_TSPREC_MSEC 346 | FILE_TSPREC_USEC = mod.WTAP_FILE_TSPREC_USEC 347 | FILE_TSPREC_NSEC = mod.WTAP_FILE_TSPREC_NSEC 348 | MAX_PACKET_SIZE = mod.WTAP_MAX_PACKET_SIZE 349 | 350 | def __init__(self, cdata): 351 | self.cdata = cdata 352 | 353 | @classmethod 354 | def open_offline(cls, filename, random=False): 355 | '''Open a file and return a WTAP-instance. If random is True, the file 356 | is opened twice; the second open allows the application to do random- 357 | access I/O without moving the seek offset for sequential I/O, which is 358 | used by Wireshark to write packets as they arrive''' 359 | err = iface.new('int *') 360 | err_info = iface.new('char **') 361 | cdata = mod.wtap_open_offline(filename.encode(), err, err_info, random) 362 | if cdata == iface.NULL: 363 | errno = err[0] 364 | # If the file is exactly zero bytes, wtap_open_offline returns NULL 365 | # but does not set any error condition. Raise ShortRead by hand? 366 | if errno < 0: 367 | raise FileError.from_errno(errno)(err_info, False) # TODO Fal? 368 | else: 369 | msg = os.strerror(errno) 370 | raise OSError(errno, msg, filename) 371 | return cls(cdata) 372 | 373 | def __enter__(self): 374 | return self 375 | 376 | def __exit__(self, exc_type, exc_value, traceback): 377 | self.close() 378 | 379 | def __iter__(self): 380 | framenum = 0 381 | res, data_offset = self.read() 382 | if res is None: 383 | # TODO raise a subclass of StopIteration, so we can listen for that 384 | # event 385 | raise StopIteration 386 | cum_bytes = iface.new('guint32*') 387 | elapsed_time = iface.new('nstime_t*') 388 | first_ts = iface.new('nstime_t*') 389 | prev_dis = iface.new('nstime_t*') 390 | prev_cap = iface.new('nstime_t*') 391 | while res: 392 | framenum += 1 393 | frame = Frame.new(framenum, self.packetheader.cdata, data_offset, 394 | cum_bytes[0]) 395 | # TODO iface.NULL on next line 396 | frame.set_before_dissect(elapsed_time, first_ts, iface.NULL, iface.NULL) 397 | try: 398 | passed = yield frame 399 | except GeneratorExit: 400 | break 401 | if passed: 402 | frame.set_after_dissect(cum_bytes, prev_dis_ts) 403 | res, data_offset = self.read() 404 | 405 | @property 406 | def packetheader(self): 407 | '''The packet header from the current packet''' 408 | p = mod.wtap_phdr(self.cdata) 409 | return PacketHeader(p) if p != iface.NULL else None 410 | 411 | @property 412 | def read_so_far(self): 413 | '''The approximate amount of data read sequentially so far''' 414 | return mod.wtap_read_so_far(self.cdata) 415 | 416 | @property 417 | def file_size(self): 418 | '''The file-size as reported by the OS''' 419 | err = iface.new('int *') 420 | res = mod.wtap_file_size(self.cdata, err) 421 | errno = err[0] 422 | if errno == 0: 423 | return res 424 | else: 425 | msg = os.strerror(errno) 426 | raise OSError(errno, msg) 427 | 428 | @property 429 | def is_compressed(self): 430 | '''True if the file is compressed (e.g. via gzip)''' 431 | return bool(mod.wtap_iscompressed(self.cdata)) 432 | 433 | @property 434 | def snapshot_length(self): 435 | return mod.wtap_snapshot_length(self.cdata) 436 | 437 | @property 438 | def effective_snapshot_length(self): 439 | return self.snapshot_length or self.WTAP_MAX_PACKET_SIZE 440 | 441 | @property 442 | def file_type(self): 443 | '''The type of the file''' 444 | return FileType(mod.wtap_file_type(self.cdata)) 445 | 446 | @property 447 | def file_encap(self): 448 | '''The encapsulation-type of the file''' 449 | return EncapsulationType(mod.wtap_file_encap(self.cdata)) 450 | 451 | @property 452 | def tsprecision(self): 453 | '''The timestamp precision, a value like FILE_TSPREC_SEC''' 454 | return mod.wtap_file_tsprecision(self.cdata) 455 | 456 | @property 457 | def buf_ptr(self): 458 | return mod.wtap_buf_ptr(self.cdata) 459 | 460 | @property 461 | def shb_info(self): 462 | return mod.wtap_file_get_shb_info(self.cdata) # TODO 463 | 464 | def read(self): 465 | err = iface.new('int *') 466 | err_info = iface.new('gchar **') 467 | data_offset = iface.new('gint64 *') 468 | res = mod.wtap_read(self.cdata, err, err_info, data_offset) 469 | if not res: 470 | errno = err[0] 471 | if errno != 0: 472 | raise OSError(errno, iface.string(err_info[0])) 473 | else: 474 | return None, None 475 | return res, data_offset[0] 476 | 477 | def seek_read(self, seek_off, len): 478 | pseudo_header = iface.new('union wtap_pseudo_header *') 479 | pd = iface.new('guint8 *') 480 | err = iface.new('int *') 481 | err_info = iface.new('gchar **') 482 | res = mod.wtap_seek_read(self.cdata, seek_off, pseudo_header, pd, len, 483 | err, err_info) 484 | errno = err[0] 485 | if errno != 0: 486 | raise OSError(errno, iface.string(err_info[0])) 487 | if not res: 488 | return None, None 489 | return pseudo_header, pd 490 | 491 | def clear_eof(self): 492 | mod.wtap_cleareof(self.cdata) 493 | 494 | def close(self): 495 | '''Close the current file''' 496 | mod.wtap_close(self.cdata) 497 | 498 | def fdclose(self): 499 | '''Close the file descriptor for the current file''' 500 | mod.wtap_fdclose(self.cdata) 501 | 502 | def sequential_close(self): 503 | '''Close the current file''' 504 | mod.wtap_sequential_close(self.cdata) 505 | 506 | 507 | class FileType(object): 508 | 509 | def __init__(self, ft): 510 | if ft not in range(mod.wtap_get_num_file_types()): 511 | raise InvalidFileType('%i is not a valid file type' % (ft, )) 512 | self.ft = ft 513 | 514 | def __repr__(self): 515 | return '' % (self.string, 516 | self.short_string) 517 | 518 | @classmethod 519 | def from_short_string(cls, short_string): 520 | if short_string is None: 521 | return cls(0) # Zero is the unknown file-type 522 | ft = mod.wtap_short_string_to_file_type(short_string.encode()) 523 | if ft == -1: 524 | raise InvalidFileType('File-type "%s" unknown' % (short_string, )) 525 | return cls(ft) 526 | 527 | @property 528 | def string(self): 529 | s = mod.wtap_file_type_string(self.ft) 530 | return iface.string(s) if s != iface.NULL else None 531 | 532 | @property 533 | def short_string(self): 534 | s = mod.wtap_file_type_short_string(self.ft) 535 | return iface.string(s) if s != iface.NULL else None 536 | 537 | @property 538 | def default_file_extension(self): 539 | '''The default file extension used by this file-type (e.g. pcap for 540 | libpcap)''' 541 | s = mod.wtap_default_file_extension(self.ft) 542 | return iface.string(s) if s != iface.NULL else None 543 | 544 | def get_file_extensions(self, include_compressed=True): 545 | '''Returns the file extensions that are used by this file type. 546 | If include_compressed is True, the returned values include compressed 547 | extensions (e.g.'pcap.gz')''' 548 | gslist = mod.wtap_get_file_extensions_list(self.ft, include_compressed) 549 | cast = lambda c: iface.string(iface.cast('gchar*', c)) 550 | try: 551 | l = tuple(glib2.SinglyLinkedListIterator(gslist, cast, False)) 552 | finally: 553 | mod.wtap_free_file_extensions_list(gslist) 554 | return l 555 | 556 | @property 557 | def file_extensions(self): 558 | '''A tuple of all file extensions that are used by this file type. 559 | Includes compressed extensions (e.g. 'pcap.gz')''' 560 | return self.get_file_extensions(include_compressed=True) 561 | 562 | @property 563 | def dump_can_open(self): 564 | '''''' # TODO 565 | return bool(mod.wtap_dump_can_open(self.ft)) 566 | 567 | def dump_can_write_encap(self, encap): 568 | '''''' # TODO 569 | return bool(mod.wtap_dump_can_write_encap(self.ft, encap.encap)) 570 | 571 | @property 572 | def dump_can_compress(self): 573 | '''''' # TODO 574 | return bool(mod.wtap_dump_can_compress(self.ft)) 575 | 576 | 577 | def iter_file_types(): 578 | '''Iterates over all file-types wireshark can understand''' 579 | # Increment from one because zero is the unknown type... 580 | for ft in range(1, mod.wtap_get_num_file_types()): 581 | yield FileType(ft) 582 | 583 | 584 | def iter_encapsulation_types(): 585 | '''Iterates over all encapsulation-types wireshark can understand''' 586 | # it should be safe that -1 (PER_PACKET) be the smallest value... 587 | for encap in range(-1, mod.wtap_get_num_encap_types()): 588 | yield EncapsulationType(encap) 589 | -------------------------------------------------------------------------------- /wirepy/platform.py.in: -------------------------------------------------------------------------------- 1 | '''This file is automatically generated by autoconf's configure''' 2 | 3 | __package_name__ = '@PACKAGE_NAME@' 4 | __version__ = '@PACKAGE_VERSION@' 5 | 6 | CFLAGS = '@DEPS_CFLAGS@' 7 | LIBS = '@DEPS_LIBS@' 8 | 9 | # basic type to expect for gint64 10 | GINT64TYPE = '@GINT64TYPE@' 11 | 12 | # TODO: This is not portable at all... 13 | CFLAGS = list(filter(None, map(str.strip, CFLAGS.split(' ')))) 14 | LIBS = list(filter(None, map(str.strip, LIBS.split(' ')))) 15 | -------------------------------------------------------------------------------- /wirepy/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaslueg/wirepy/b7178d04cb4eef5e08a97ac20017856c52ababe4/wirepy/tests/__init__.py -------------------------------------------------------------------------------- /wirepy/tests/sample_files/README: -------------------------------------------------------------------------------- 1 | All files collected from http://wiki.wireshark.org/SampleCaptures 2 | To be considered public domain 3 | -------------------------------------------------------------------------------- /wirepy/tests/sample_files/http.cap.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukaslueg/wirepy/b7178d04cb4eef5e08a97ac20017856c52ababe4/wirepy/tests/sample_files/http.cap.gz -------------------------------------------------------------------------------- /wirepy/tests/test_cdata.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import cffi 3 | from wirepy.lib import cdata, wireshark 4 | 5 | test_iface = cffi.FFI() 6 | test_iface.cdef(''' 7 | typedef struct { 8 | int barint; 9 | } barobject; 10 | 11 | typedef struct _fooobject fooobject; 12 | 13 | struct _fooobject { 14 | int fooint; 15 | char *foostring; 16 | unsigned char foobool; 17 | char **foostringarray; 18 | int *foointarray; 19 | unsigned char *fooboolarray; 20 | int fooarraysize; 21 | barobject *foobar; 22 | fooobject *next; 23 | barobject **foobars; 24 | }; 25 | ''') 26 | wireshark.iface.include(test_iface) 27 | 28 | 29 | class TestCData(object): 30 | 31 | def _make_class(self): 32 | 33 | class Foo(cdata.CDataObject): 34 | fooarraysize = cdata.Attribute() 35 | if hasattr(self, 'testattr'): 36 | testattr = self.testattr 37 | _mod_defs = ('FT_', ) 38 | 39 | def __init__(self, testvalue=None): 40 | if isinstance(testvalue, test_iface.CData): 41 | self.cdata = testvalue 42 | else: 43 | self.cdata = wireshark.iface.new('fooobject*') 44 | if testvalue is not None: 45 | self.testattr = testvalue 46 | return Foo 47 | 48 | def _make_object(self, testvalue=None): 49 | return self.klass(testvalue) 50 | 51 | def setUp(self): 52 | self.klass = self._make_class() 53 | self.inst = self._make_object() 54 | 55 | 56 | class TestModDefs(TestCData, unittest.TestCase): 57 | 58 | def test_defs(self): 59 | self.assertTrue('FT_NONE' in dir(self.inst)) 60 | 61 | 62 | class TestAttribute(TestCData, unittest.TestCase): 63 | pass 64 | 65 | 66 | class TestIntAttribute(TestAttribute): 67 | testattr = cdata.Attribute(structmember='fooint') 68 | 69 | def test_read(self): 70 | self.assertEqual(self.inst.testattr, 0) 71 | 72 | def test_write(self): 73 | self.inst.testfield = 12345 74 | self.assertEqual(self.inst.testfield, 12345) 75 | 76 | def test_delete(self): 77 | self.assertRaises(cdata.AttributeAccessError, delattr, self.inst, 78 | 'testattr') 79 | 80 | 81 | class TestStumpAttribute(TestAttribute): 82 | testattr = cdata.Attribute(structmember='fooint', can_read=False, 83 | can_write=False) 84 | 85 | def test_read(self): 86 | self.assertRaises(cdata.AttributeAccessError, getattr, self.inst, 87 | 'testattr') 88 | 89 | def test_write(self): 90 | self.assertRaises(cdata.AttributeAccessError, setattr, self.inst, 91 | 'testattr', 123) 92 | 93 | 94 | class TestBytesAttribute(TestAttribute): 95 | testattr = cdata.BytesAttribute(structmember='fooboolarray', 96 | sizeattr='fooarraysize') 97 | 98 | def test_read(self): 99 | self.assertEqual(self.inst.testattr, None) 100 | 101 | def test_write(self): 102 | crib = b'\x47\x11\x49\x11' 103 | self.inst.testattr = crib 104 | self.inst.fooarraysize = len(crib) 105 | self.assertEqual(self.inst.testattr, crib) 106 | self.inst.testattr = None 107 | self.assertEqual(self.inst.testattr, None) 108 | 109 | def test_delete(self): 110 | self.inst.testattr = b'\x11\x12' 111 | del self.inst.testattr 112 | self.assertEqual(self.inst.testattr, None) 113 | 114 | 115 | class TestStringAttribute(TestAttribute): 116 | testattr = cdata.StringAttribute(structmember='foostring') 117 | 118 | def test_read(self): 119 | self.assertEqual(self.inst.testattr, None) 120 | 121 | def test_write(self): 122 | self.inst.testattr = 'foostring' 123 | self.assertEqual(self.inst.testattr, 'foostring') 124 | self.inst.testattr = None 125 | self.assertEqual(self.inst.testattr, None) 126 | 127 | def test_delete(self): 128 | self.inst.testattr = 'foostring' 129 | del self.inst.testattr 130 | self.assertEqual(self.inst.testattr, None) 131 | 132 | 133 | class TestBoolAttribute(TestAttribute): 134 | testattr = cdata.BooleanAttribute(structmember='foobool') 135 | 136 | def test_read(self): 137 | self.assertEqual(self.inst.testattr, False) 138 | 139 | def test_write(self): 140 | self.inst.testattr = True 141 | self.assertEqual(self.inst.testattr, True) 142 | 143 | def test_delete(self): 144 | self.assertRaises(cdata.AttributeAccessError, delattr, self.inst, 145 | 'testattr') 146 | 147 | 148 | class TestIntListAttribute(TestAttribute): 149 | testattr = cdata.IntListAttribute(structmember='foointarray', 150 | sizeattr='fooarraysize') 151 | 152 | def test_empty(self): 153 | self.assertEqual(self.inst.testattr, None) 154 | 155 | def test_write(self): 156 | self.inst.fooarraysize = 2 157 | self.inst.testattr = [4711, 4911] 158 | obj = self.inst.testattr 159 | self.assertTrue(isinstance(obj, cdata.AttributeList)) 160 | self.assertEqual(obj[0], 4711) 161 | self.assertEqual(obj[1], 4911) 162 | 163 | def test_sizeerror(self): 164 | self.inst.fooarraysize = 5 165 | self.assertRaises(cdata.AttributeSizeError, setattr, self.inst, 166 | 'testattr', [4711, 4911]) 167 | 168 | def test_indexerror(self): 169 | self.inst.fooarraysize = 2 170 | self.inst.testattr = [4711, 4911] 171 | self.assertRaises(IndexError, self.inst.testattr.__setitem__, 172 | 3, 4711) 173 | 174 | 175 | class TestFixedIntListAttribute(TestAttribute): 176 | testattr = cdata.IntListAttribute(structmember='foointarray', 177 | sizeattr=5) 178 | 179 | def test_write(self): 180 | self.inst.testattr = list(range(5)) 181 | self.assertEqual(list(range(5)), list(self.inst.testattr)) 182 | 183 | def test_sizeerror(self): 184 | self.assertRaises(cdata.AttributeSizeError, setattr, self.inst, 185 | 'testattr', [1, 2, 3]) 186 | 187 | 188 | class TestBoolListAttribute(TestAttribute): 189 | testattr = cdata.BooleanListAttribute('fooarraysize', 'fooboolarray') 190 | 191 | def test_empty(self): 192 | self.assertEqual(self.inst.testattr, None) 193 | 194 | def test_write(self): 195 | self.inst.fooarraysize = 4 196 | self.inst.testattr = [True, False, 0, object()] 197 | obj = self.inst.testattr 198 | self.assertTrue(isinstance(obj, cdata.BooleanAttributeList)) 199 | self.assertEqual(obj[0], True) 200 | self.assertEqual(obj[1], False) 201 | self.assertEqual(obj[2], False) 202 | self.assertEqual(obj[3], True) 203 | 204 | def test_sizeerror(self): 205 | self.inst.fooarraysize = 5 206 | self.assertRaises(cdata.AttributeSizeError, setattr, self.inst, 207 | 'testattr', [True, False]) 208 | 209 | def test_indexerror(self): 210 | self.inst.fooarraysize = 1 211 | self.inst.testattr = [True] 212 | self.assertRaises(IndexError, self.inst.testattr.__setitem__, 213 | 1, True) 214 | 215 | 216 | class TestStringListAttribute(TestAttribute): 217 | testattr = cdata.StringListAttribute('fooarraysize', 'foostringarray') 218 | 219 | def test_empty(self): 220 | self.assertEqual(self.inst.testattr, None) 221 | 222 | def test_write(self): 223 | self.inst.fooarraysize = 2 224 | self.inst.testattr = ['foo', 'bar'] 225 | obj = self.inst.testattr 226 | self.assertTrue(isinstance(obj, cdata.StringAttributeList)) 227 | self.assertEqual(obj[0], 'foo') 228 | self.assertEqual(obj[1], 'bar') 229 | 230 | def test_sizeerror(self): 231 | self.inst.fooarraysize = 5 232 | self.assertRaises(cdata.AttributeSizeError, setattr, self.inst, 233 | 'testattr', ['foo', 'bar']) 234 | 235 | def test_indexerror(self): 236 | self.inst.fooarraysize = 2 237 | self.inst.testattr = ['foo', 'bar'] 238 | self.assertRaises(IndexError, self.inst.testattr.__setitem__, 239 | 3, 'foo') 240 | 241 | 242 | class TestInstanceAttribute(TestAttribute): 243 | 244 | class Foocls(cdata.CDataObject): 245 | _struct = 'barobject' 246 | barint = cdata.Attribute() 247 | 248 | def __init__(self, init): 249 | if isinstance(init, test_iface.CData): 250 | self.cdata = init 251 | elif isinstance(init, int): 252 | self.cdata = test_iface.new('barobject*') 253 | self.barint = init 254 | else: 255 | raise TypeError(type(init)) 256 | 257 | testattr = cdata.InstanceAttribute(Foocls, structmember='foobar') 258 | 259 | def test_empty(self): 260 | self.assertEqual(self.inst.testattr, None) 261 | 262 | def test_write(self): 263 | inst = self.Foocls(5) 264 | self.inst.testattr = inst 265 | self.assertEqual(self.inst.testattr.barint, 5) 266 | 267 | 268 | class TestInstanceListAttribute(TestAttribute): 269 | testattr = cdata.InstanceListAttribute(TestInstanceAttribute.Foocls, 270 | structmember='foobars', 271 | sizeattr='fooarraysize') 272 | 273 | def test_empty(self): 274 | self.assertEqual(self.inst.testattr, None) 275 | 276 | def test_write(self): 277 | self.inst.fooarraysize = 5 278 | self.inst.testattr = [TestInstanceAttribute.Foocls(i) 279 | for i in range(5)] 280 | objs = list(self.inst.testattr) 281 | self.assertEqual(len(objs), 5) 282 | self.assertEqual([obj.barint for obj in objs], list(range(5))) 283 | 284 | 285 | class TestSelfReferringInstanceAttribute(TestAttribute): 286 | testattr = cdata.InstanceAttribute(None, structmember='next') 287 | 288 | def test_empty(self): 289 | self.assertEqual(self.inst.testattr, None) 290 | 291 | def test_write(self): 292 | obj = self._make_object() 293 | self.inst.testattr = obj 294 | self.assertEqual(self.klass, type(self.inst.testattr)) 295 | -------------------------------------------------------------------------------- /wirepy/tests/test_column.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import column, epan 3 | 4 | epan.epan_init() 5 | 6 | 7 | class TestType(unittest.TestCase): 8 | 9 | def test_init(self): 10 | col = column.Type(column.Type.ABS_TIME) 11 | self.assertTrue(isinstance(col, column.Type)) 12 | self.assertTrue(isinstance(col.format_desc, str)) 13 | self.assertTrue(isinstance(col.format_string, str)) 14 | 15 | def test_from_string(self): 16 | col = column.Type.from_string('%At') 17 | self.assertTrue(isinstance(col, column.Type)) 18 | 19 | def test_iter(self): 20 | for col in column.Type.iter_column_formats(): 21 | self.assertTrue(isinstance(col, column.Type)) 22 | repr(col) 23 | 24 | def test_invalid_col(self): 25 | self.assertRaises(column.InvalidColumnType, column.Type, -1) 26 | self.assertRaises(column.InvalidColumnType, column.Type.from_string, 27 | '_B0RK_') 28 | 29 | 30 | class TestFormat(unittest.TestCase): 31 | 32 | def test_init(self): 33 | f = column.Format(title='The time', type_=column.Type.ABS_TIME, 34 | custom_field='eth.src', custom_occurrence=1, 35 | visible=True, resolved=True) 36 | self.assertEqual(f.title, 'The time') 37 | self.assertEqual(f.type_, column.Type.ABS_TIME) 38 | self.assertEqual(f.custom_field, 'eth.src') 39 | self.assertEqual(f.custom_occurrence, 1) 40 | self.assertEqual(f.visible, True) 41 | self.assertEqual(f.resolved, True) 42 | 43 | 44 | class TestColumn(unittest.TestCase): 45 | 46 | def test_init(self): 47 | fmts = [column.Format(column.Type.ABS_TIME, title='The time'), 48 | column.Format(column.Type.UNRES_DST, title='Destination'), 49 | column.Format(column.Type.CUSTOM, title='Foobar', 50 | custom_field='eth.src')] 51 | info = column.ColumnInfo(fmts) 52 | del fmts 53 | self.assertEqual(info.fmts[0], column.Type.ABS_TIME) 54 | self.assertEqual(info.fmts[1], column.Type.UNRES_DST) 55 | self.assertEqual(info.titles[0], 'The time') 56 | self.assertEqual(info.titles[1], 'Destination') 57 | self.assertEqual(info.custom_fields[2], 'eth.src') 58 | self.assertTrue(info.have_custom_cols) 59 | -------------------------------------------------------------------------------- /wirepy/tests/test_dfilter.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import dfilter 3 | 4 | 5 | class TestDisplayFilter(unittest.TestCase): 6 | 7 | def test_compile(self): 8 | dfilter.DisplayFilter('eth') 9 | dfilter.DisplayFilter('ip.src==192.168.100.2') 10 | 11 | def test_compile_empty(self): 12 | # The null filter results in a null-pointer which is still a valid 13 | # value for this struct 14 | df = dfilter.DisplayFilter('') 15 | self.assertEqual(df.cdata, dfilter.iface.NULL) 16 | 17 | def test_compile_error(self): 18 | self.assertRaises(dfilter.DisplayFilterError, dfilter.DisplayFilter, 19 | '_B0RK_') 20 | self.assertRaises(dfilter.DisplayFilterError, dfilter.DisplayFilter, 21 | 'ip.proto == -1') 22 | -------------------------------------------------------------------------------- /wirepy/tests/test_dumpcap.py: -------------------------------------------------------------------------------- 1 | MOCK_FAIL_FAST = '--FAIL_FAST' # Mock exit right away, no messages 2 | MOCK_FAIL_EXIT = '--FAIL_EXIT' 3 | MOCK_FAIL_FILTER = '--FAIL_FILTER' 4 | MOCK_EMPTY_OUTPUT = '--EMPTY_OUTPUT' 5 | MOCK_ILLEGAL_OUTPUT = '--ILLEGAL_OUTPUT' 6 | 7 | if __name__ == '__main__': 8 | # We are here to mock dumpcap... 9 | 10 | import getopt 11 | import struct 12 | import sys 13 | import time 14 | 15 | def _build_message(indicator, msgbytes=b''): 16 | msglen = len(msgbytes) 17 | header = struct.pack('4B', ord(indicator), (msglen & (255 << 16)), 18 | (msglen & (255 << 8)), (msglen & (255 << 0))) 19 | return header + msgbytes 20 | 21 | def _write_error_message(pipe): 22 | err1 = _build_message('E', b'Oh, no interfaces!\x00') 23 | err2 = _build_message('E', b'\x00') 24 | pipe.write(_build_message('E', err1 + err2)) 25 | pipe.flush() 26 | 27 | def mock_capture_empty(**kwargs): 28 | # never give any output, as if no traffic arrives 29 | while True: 30 | time.sleep(1) 31 | 32 | def mock_capture_fail(pipe, **kwargs): 33 | _write_error_message(pipe) 34 | 35 | def mock_capture_filter(pipe, **kwargs): 36 | errmsg = _build_message('B', b'0:bad filter\x00') 37 | pipe.write(errmsg) 38 | pipe.flush() 39 | 40 | def mock_capture(pipe, **kwargs): 41 | pipe.write(_build_message('F', b'foobar\x00')) 42 | pipe.write(_build_message('P', b'4711\x00')) 43 | pipe.write(_build_message('D', b'4811\x00')) 44 | 45 | def mock_capture_illegal(pipe, **kwargs): 46 | pipe.write(_build_message('X')) 47 | pipe.flush() 48 | while True: 49 | time.sleep(1) 50 | 51 | def mock_layers_empty(pipe, **kwargs): 52 | pipe.write(_build_message('S')) 53 | 54 | def mock_layers_fail(pipe, **kwargs): 55 | _write_error_message(pipe) 56 | 57 | def mock_layers(pipe, interfaces, **kwargs): 58 | if 'em1' in interfaces: 59 | pipe.write(_build_message('S')) 60 | sys.stdout.write(('1\n' # em1 can mock-rfmon... 61 | '1\tEN10MB\tEthernet\n' 62 | '143\tDOCSIS\tDOCSIS\n')) 63 | if 'lo' in interfaces: 64 | pipe.write(_build_message('S')) 65 | sys.stdout.write(('0\n' 66 | '1\tEN10MB\tEthernet\n')) 67 | 68 | def mock_interfaces_empty(**kwargs): 69 | pass # TODO No output at all - correct? 70 | 71 | def mock_interfaces_fail(flags, **kwargs): 72 | flags.remove('fail') # dumpcap just exits with exit status 0. Meh 73 | 74 | def mock_interfaces_illegal(**kwargs): 75 | sys.stdout.write('foobar') 76 | 77 | def mock_interfaces(**kwargs): 78 | sys.stdout.write(('1. em1\t\t\t0\t\tnetwork\n' 79 | '2. lo\t\tLoopback\t0\t' 80 | '127.0.0.1,::1\tloopback\n')) 81 | 82 | def mock_stats(pipe, **kwargs): 83 | pipe.write(_build_message('S')) 84 | pipe.flush() 85 | sys.stdout.write(('em1\t4711\t123\n' 86 | 'lo\t4811\t124\n')) 87 | while True: 88 | sys.stdout.write(('em1\t0\t0\n' 89 | 'lo\t0\t0\n')) 90 | sys.stdout.flush() 91 | time.sleep(1) 92 | 93 | def mock_stats_fail(pipe, **kwargs): 94 | _write_error_message(pipe) 95 | 96 | def mock_stats_empty(pipe, **kwargs): 97 | pipe.write(_build_message('S')) 98 | pipe.flush() 99 | while True: 100 | time.sleep(1) 101 | 102 | def mock_stats_illegal(pipe, **kwargs): 103 | pipe.write(_build_message('S')) 104 | pipe.flush() 105 | sys.stdout.write('foobar') 106 | 107 | opts, _ = getopt.getopt(sys.argv[1:], 'Z:DLMSi:', 108 | [s[2:] for s in (MOCK_FAIL_FAST, MOCK_FAIL_EXIT, 109 | MOCK_EMPTY_OUTPUT, 110 | MOCK_FAIL_FILTER, 111 | MOCK_ILLEGAL_OUTPUT)]) 112 | interfaces = [] 113 | command = 'capture' 114 | flags = set() 115 | pipe = None 116 | for o, a in opts: 117 | if o == MOCK_FAIL_FAST: 118 | sys.exit(1) 119 | elif o == '-M': 120 | pass # machine-readable is assumed for list_interfaces() 121 | elif o == '-i': 122 | interfaces.append(a) 123 | elif o == '-Z': 124 | if a == 'none': 125 | pipe = sys.stderr.buffer 126 | else: 127 | raise NotImplementedError # TODO Windows 128 | else: 129 | try: 130 | flags.add({MOCK_FAIL_EXIT: 'fail', MOCK_EMPTY_OUTPUT: 'empty', 131 | MOCK_FAIL_FILTER: 'filter', 132 | MOCK_ILLEGAL_OUTPUT: 'illegal'}[o]) 133 | except KeyError: 134 | command = {'-D': 'interfaces', 135 | '-L': 'layers', 136 | '-S': 'stats'}[o] 137 | for name, mock_function in tuple(globals().items()): 138 | if not name.startswith('mock_'): 139 | continue 140 | comps = name[5:].split('_') 141 | if comps[0] == command and flags == set(comps[1:]): 142 | mock_function(pipe=pipe, interfaces=interfaces, flags=flags) 143 | break 144 | else: 145 | raise NotImplementedError((command, flags)) 146 | sys.exit(int('fail' in flags)) 147 | 148 | # END OF MOCK 149 | 150 | import functools 151 | import os 152 | import sys 153 | import unittest 154 | from wirepy.lib import dumpcap 155 | 156 | 157 | def mock_dumpcap(*extra_dumpcap_args): 158 | def decorator(method): 159 | @functools.wraps(method) 160 | def f(*args, **kwargs): 161 | dumpcap_bin = [sys.executable, os.path.abspath(__file__)] 162 | dumpcap_bin.extend(extra_dumpcap_args) 163 | dumpcap.DUMPCAP_BIN = dumpcap_bin 164 | dumpcap.DUMPCAP_CHECK_INTERVAL = 0.1 165 | return method(*args, **kwargs) 166 | return f 167 | return decorator 168 | 169 | 170 | class TestInterfaceList(unittest.TestCase): 171 | 172 | @mock_dumpcap(MOCK_EMPTY_OUTPUT) 173 | def test_empty_list(self): 174 | interfaces = dumpcap.Interface.list_interfaces() 175 | self.assertEqual(len(interfaces), 0) 176 | 177 | @mock_dumpcap(MOCK_FAIL_EXIT) 178 | def test_list_fail(self): 179 | interfaces = dumpcap.Interface.list_interfaces() 180 | self.assertEqual(len(interfaces), 0) 181 | 182 | @mock_dumpcap() 183 | def test_list(self): 184 | interfaces = dumpcap.Interface.list_interfaces() 185 | self.assertEqual(len(interfaces), 2) 186 | dev = interfaces[0] 187 | repr(dev) 188 | self.assertEqual(dev.name, 'em1') 189 | self.assertEqual(dev.name, str(dev)) 190 | self.assertEqual(dev.vendor_name, None) 191 | self.assertEqual(dev.friendly_name, None) 192 | self.assertEqual(dev.interface_type_string, 'WIRED') 193 | self.assertEqual(len(dev.addresses), 0) 194 | self.assertFalse(dev.loopback) 195 | self.assertTrue(dev.can_rfmon) 196 | self.assertTrue(all(isinstance(ltype, dumpcap.LinkLayerType) 197 | for ltype in dev.supported_link_layer_types)) 198 | dev = interfaces[1] 199 | self.assertEqual(dev.name, 'lo') 200 | self.assertEqual(dev.name, str(dev)) 201 | self.assertEqual(dev.friendly_name, 'Loopback') 202 | self.assertEqual(dev.interface_type_string, 'WIRED') 203 | self.assertEqual(len(dev.addresses), 2) 204 | self.assertEqual(dev.addresses[0], '127.0.0.1') 205 | self.assertEqual(dev.addresses[1], '::1') 206 | self.assertTrue(dev.loopback) 207 | self.assertFalse(dev.can_rfmon) 208 | self.assertTrue(all(isinstance(ltype, dumpcap.LinkLayerType) 209 | for ltype in dev.supported_link_layer_types)) 210 | 211 | @mock_dumpcap(MOCK_FAIL_FAST) 212 | def test_list_fails_fast(self): 213 | self.assertRaises(dumpcap.ChildError, 214 | dumpcap.Interface.list_interfaces) 215 | 216 | 217 | class TestInterfaceCapabilities(unittest.TestCase): 218 | 219 | @mock_dumpcap() 220 | def test_get(self): 221 | rfmon, linktypes = dumpcap.Interface.get_interface_capabilities('em1') 222 | self.assertTrue(rfmon) 223 | self.assertTrue(len(linktypes), 2) 224 | ltype = linktypes[0] 225 | repr(ltype) 226 | self.assertEqual(ltype.dlt, 1) 227 | self.assertEqual(ltype.name, 'EN10MB') 228 | self.assertEqual(ltype.description, 'Ethernet') 229 | ltype = linktypes[1] 230 | self.assertEqual(ltype.dlt, 143) 231 | self.assertEqual(ltype.name, 'DOCSIS') 232 | self.assertEqual(ltype.description, 'DOCSIS') 233 | 234 | @mock_dumpcap(MOCK_FAIL_EXIT) 235 | def test_fail(self): 236 | self.assertRaises(dumpcap.ChildError, 237 | dumpcap.Interface.get_interface_capabilities, 'em1') 238 | 239 | 240 | class TestStats(unittest.TestCase): 241 | 242 | @mock_dumpcap() 243 | def test_stats(self): 244 | with dumpcap.LiveInterfaceStats() as stats: 245 | stats.clear_tick() 246 | stats.wait_for_tick() 247 | self.assertEqual(stats['em1'][0], 4711) 248 | self.assertEqual(stats['em1'][1], 123) 249 | self.assertEqual(stats['lo'][0], 4811) 250 | self.assertEqual(stats['lo'][1], 124) 251 | 252 | @mock_dumpcap(MOCK_FAIL_EXIT) 253 | def test_fail(self): 254 | self.assertRaises(dumpcap.ChildError, dumpcap.LiveInterfaceStats) 255 | 256 | @mock_dumpcap(MOCK_EMPTY_OUTPUT) 257 | def test_empty(self): 258 | with dumpcap.LiveInterfaceStats() as stats: 259 | self.assertRaises(dumpcap.NoEvents, stats.wait_for_tick, 260 | timeout=1.0) 261 | 262 | @mock_dumpcap(MOCK_ILLEGAL_OUTPUT) 263 | def test_illegal(self): 264 | with dumpcap.LiveInterfaceStats() as stats: 265 | self.assertRaises(dumpcap.BrokenPipe, stats.wait_for_tick, 266 | timeout=1.0) 267 | 268 | 269 | class TestCapture(unittest.TestCase): 270 | 271 | @mock_dumpcap() 272 | def test_capture(self): 273 | with dumpcap.CaptureSession() as cap: 274 | events = iter(cap) 275 | event_type, event_msg = next(events) 276 | self.assertEqual(event_type, cap.SP_FILE) 277 | self.assertEqual(event_msg, 'foobar') 278 | event_type, event_msg = next(events) 279 | self.assertEqual(event_type, cap.SP_PACKET_COUNT) 280 | self.assertEqual(event_msg, 4711) 281 | event_type, event_msg = next(events) 282 | self.assertEqual(event_type, cap.SP_DROPS) 283 | self.assertEqual(event_msg, 4811) 284 | self.assertRaises(StopIteration, next, events) 285 | 286 | @mock_dumpcap(MOCK_EMPTY_OUTPUT) 287 | def test_empty(self): 288 | with dumpcap.CaptureSession() as cap: 289 | self.assertRaises(dumpcap.NoEvents, cap.wait_for_unhandled_event, 290 | timeout=1.0) 291 | self.assertRaises(dumpcap.NoEvents, cap.wait_for_unhandled_event, 292 | block=False) 293 | 294 | @mock_dumpcap(MOCK_FAIL_FILTER) 295 | def test_fail_filter(self): 296 | with dumpcap.CaptureSession() as cap: 297 | self.assertRaises(dumpcap.BadFilterError, 298 | cap.wait_for_unhandled_event) 299 | 300 | @mock_dumpcap(MOCK_FAIL_FAST) 301 | def test_fail_fast(self): 302 | with dumpcap.CaptureSession() as cap: 303 | self.assertRaises(dumpcap.ChildError, cap.wait_for_unhandled_event) 304 | 305 | @mock_dumpcap(MOCK_FAIL_EXIT) 306 | def test_fail(self): 307 | with dumpcap.CaptureSession() as cap: 308 | self.assertRaises(dumpcap.ChildError, cap.wait_for_unhandled_event) 309 | 310 | @mock_dumpcap(MOCK_ILLEGAL_OUTPUT) 311 | def test_illegal(self): 312 | with dumpcap.CaptureSession() as cap: 313 | self.assertRaises(dumpcap.BrokenPipe, cap.wait_for_unhandled_event) 314 | -------------------------------------------------------------------------------- /wirepy/tests/test_epan.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from wirepy.lib import epan, ftypes, prefs, wtap 4 | from wirepy.lib.wireshark import mod 5 | 6 | epan.epan_init() 7 | epan.init_dissection() 8 | prefs.read_prefs() 9 | 10 | 11 | class TestFunctions(unittest.TestCase): 12 | 13 | def test_version(self): 14 | self.assertTrue(isinstance(epan.get_version(), str)) 15 | 16 | def test_compiled_version(self): 17 | v = epan.get_compiled_version_info('foobar') 18 | self.assertTrue(isinstance(v, str)) 19 | self.assertTrue(v.startswith('foobar')) 20 | 21 | def test_iter_protocols(self): 22 | cribs = ['eth', 'smb', 'x11'] 23 | for p in epan.iter_protocols(): 24 | self.assertTrue(isinstance(p, epan.Protocol)) 25 | if p.filter_name in cribs: 26 | cribs.remove(p.filter_name) 27 | if len(cribs) == 0: 28 | break 29 | else: 30 | self.fail('Protocols eth, smb and x11 should always be there') 31 | 32 | def test_iter_fields(self): 33 | for f in epan.iter_fields(): 34 | self.assertTrue(isinstance(f, epan.Field)) 35 | if f.abbrev == 'eth.src': 36 | break 37 | else: 38 | self.fail('Field eth.src should always be there') 39 | 40 | 41 | class TestProtocol(unittest.TestCase): 42 | 43 | def test_by_filter_name(self): 44 | p = epan.Protocol.by_filter_name('eth') 45 | self.assertTrue(isinstance(p, epan.Protocol)) 46 | self.assertRaises(epan.InvalidProtocolError, 47 | epan.Protocol.by_filter_name, 'foobarprotocol') 48 | 49 | def test_by_id(self): 50 | pid = mod.proto_get_id_by_filter_name('eth'.encode()) 51 | self.assertTrue(isinstance(epan.Protocol.by_id(pid), epan.Protocol)) 52 | 53 | def test_repr(self): 54 | for n in ('eth', 'smb', 'x11'): 55 | p = epan.Protocol.by_filter_name(n) 56 | self.assertTrue(isinstance(repr(p), str)) 57 | 58 | def test_attributes(self): 59 | p = epan.Protocol.by_filter_name('eth') 60 | self.assertTrue(p.is_private or True) 61 | self.assertTrue(p.can_toggle_protection or True) 62 | self.assertEqual(p.name, 'Ethernet') 63 | self.assertEqual(p.short_name, 'Ethernet') 64 | self.assertEqual(p.long_name, 'Ethernet') 65 | self.assertTrue(p.is_enabled or True) 66 | self.assertEqual(p.filter_name, 'eth') 67 | 68 | def test_iter_fields(self): 69 | p = epan.Protocol.by_filter_name('eth') 70 | cribs = ['eth.src', 'eth.dst'] 71 | for field in p: 72 | self.assertTrue(isinstance(field, epan.Field)) 73 | if field.abbrev in cribs: 74 | cribs.remove(field.abbrev) 75 | if len(cribs) == 0: 76 | break 77 | else: 78 | self.fail('Fields src and dst should always be in protocol eth') 79 | 80 | 81 | class TestField(unittest.TestCase): 82 | 83 | def test_by_name(self): 84 | self.assertTrue(isinstance(epan.Field('eth.src'), epan.Field)) 85 | self.assertRaises(epan.InvalidFieldError, epan.Field, 'foobarfield') 86 | 87 | def test_getitem(self): 88 | fieldname = 'eth.src' 89 | parent = epan.Field('eth') 90 | child = parent[fieldname] 91 | self.assertEqual(child.abbrev, fieldname) 92 | 93 | def test_getitemerror(self): 94 | field = epan.Field('eth') 95 | self.assertRaises(KeyError, field.__getitem__, '_B0RK_') 96 | 97 | def test_repr(self): 98 | for n in ('eth.src', 'eth.dst', 'eth.addr'): 99 | f = epan.Field(n) 100 | self.assertTrue(isinstance(f, epan.Field)) 101 | self.assertTrue(isinstance(repr(f), str)) 102 | 103 | def test_attributes(self): 104 | f = epan.Field('eth.len') 105 | self.assertEqual(f.name, 'Length') 106 | self.assertEqual(f.abbrev, 'eth.len') 107 | self.assertEqual(f.type_, ftypes.FieldType.UINT16) 108 | self.assertEqual(f.display, epan.Field.BASE_DEC) 109 | self.assertEqual(f.base, epan.Field.BASE_DEC) 110 | self.assertTrue(isinstance(f.bitmask, int)) 111 | self.assertTrue(isinstance(f.id_, int)) 112 | self.assertTrue(isinstance(f.parent, int)) 113 | self.assertIsNone(f.same_name_next) 114 | self.assertIsNone(f.same_name_prev) 115 | 116 | f = epan.Field('eth.src') 117 | self.assertTrue(isinstance(f.blurb, str)) 118 | 119 | f = epan.Field('ieee17221.message_type') 120 | self.assertTrue(isinstance(f.same_name_next or f.same_name_prev, 121 | epan.Field)) 122 | 123 | def test_iter(self): 124 | l = list(epan.Field('ip.flags.sf')) 125 | self.assertGreater(len(l), 0) 126 | self.assertTrue(all(isinstance(v, epan.FieldValue) for v in l)) 127 | 128 | 129 | class TestFieldValues(unittest.TestCase): 130 | 131 | def test_tfs(self): 132 | field = epan.Field('ip.flags.sf') 133 | valuelist = list(field) 134 | self.assertEqual(len(valuelist), 1) 135 | tfs = valuelist[0] 136 | self.assertTrue(isinstance(tfs, epan.TrueFalseString)) 137 | self.assertEqual(tfs.true_string, 'Evil') 138 | self.assertEqual(tfs.false_string, 'Not evil') 139 | 140 | def test_range(self): 141 | field = epan.Field('ip.opt.ra') 142 | valuelist = list(sorted(field)) 143 | self.assertEqual(len(valuelist), 2) 144 | self.assertTrue(all(isinstance(v.string, str) for v in valuelist)) 145 | v = valuelist[1] # Reserved 146 | self.assertEqual(v.value_min, 1) 147 | self.assertEqual(v.value_max, 65535) 148 | v = valuelist[0] # Shall examine 149 | self.assertEqual(v.value_min, 0) 150 | self.assertEqual(v.value_max, 0) 151 | 152 | def test_string(self): 153 | field = epan.Field('dns.nsec3.algo') 154 | valuelist = list(sorted(field)) 155 | self.assertEqual(len(valuelist), 2) 156 | self.assertTrue(all(isinstance(v.string, str) for v in valuelist)) 157 | self.assertTrue(all(isinstance(v.value, int) for v in valuelist)) 158 | v = valuelist[0] 159 | self.assertEqual(v.value, 0) 160 | self.assertEqual(v.string, 'Reserved') 161 | v = valuelist[1] 162 | self.assertEqual(v.value, 1) 163 | self.assertEqual(v.string, 'SHA-1') 164 | 165 | def test_vse(self): 166 | field = epan.Field('ftp.response.code') 167 | values_it = iter(field) 168 | vse = next(values_it) 169 | self.assertTrue(isinstance(vse, epan.ExtValueString)) 170 | self.assertEqual(vse.name, 'response_table') 171 | self.assertGreater(vse.num_entries, 0) 172 | values = list(values_it) 173 | self.assertEqual(len(values), vse.num_entries) 174 | self.assertTrue(all((isinstance(v, epan.StringValue) for v in values))) 175 | 176 | 177 | class TestDissect(unittest.TestCase): 178 | testpath = os.path.dirname(os.path.abspath(__file__)) 179 | testfile = os.path.join(testpath, 'sample_files/http.cap.gz') 180 | 181 | def setUp(self): 182 | self.wt = wtap.WTAP.open_offline(self.testfile) 183 | 184 | def _walk_tree(self, node): 185 | self.assertTrue(isinstance(node, epan.ProtoNode)) 186 | fi = node.field_info 187 | if fi is not None: 188 | self.assertTrue(isinstance(fi, epan.FieldInfo)) 189 | self.assertTrue(isinstance(fi.rep, str)) 190 | repr(fi) 191 | self.assertTrue(isinstance(fi.value, ftypes.Value)) 192 | if node.next is not None: 193 | self._walk_tree(node.next) 194 | if node.first_child is not None: 195 | self._walk_tree(node.first_child) 196 | 197 | def testRun(self): 198 | with self.wt: 199 | for frame in self.wt: 200 | edt = epan.Dissect() 201 | edt.run(self.wt, frame) 202 | self.assertTrue(isinstance(edt.tree, epan.ProtoNode)) 203 | repr(edt.tree) 204 | self._walk_tree(edt.tree) 205 | -------------------------------------------------------------------------------- /wirepy/tests/test_ftypes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import epan, ftypes 3 | 4 | epan.epan_init() 5 | 6 | 7 | class TestModule(unittest.TestCase): 8 | 9 | def test_iter_ftypes(self): 10 | cribs = [ftypes.FieldType.NONE, ftypes.FieldType.ABSOLUTE_TIME, 11 | ftypes.FieldType.ETHER] 12 | for ft in ftypes.iter_ftypes(): 13 | self.assertTrue(isinstance(ft, ftypes.FieldType)) 14 | repr(ft) 15 | if ft in cribs: 16 | cribs.remove(ft) 17 | if len(cribs) == 0: 18 | break 19 | else: 20 | self.fail('Types NONE, ABSOLUTE_TIME and ETHER should be there') 21 | 22 | 23 | class TestFieldType(unittest.TestCase): 24 | 25 | def test_invalid_new(self): 26 | self.assertRaises(ftypes.InvalidFieldType, ftypes.FieldType, -1) 27 | 28 | def test_ftype(self): 29 | ft = ftypes.FieldType(ftypes.FieldType.BOOLEAN) 30 | self.assertFalse(ft.can_slice) 31 | self.assertTrue(ft.can_eq) 32 | self.assertTrue(ft.can_ne) 33 | self.assertFalse(ft.can_gt) 34 | self.assertFalse(ft.can_ge) 35 | self.assertFalse(ft.can_lt) 36 | self.assertFalse(ft.can_le) 37 | self.assertFalse(ft.can_contains) 38 | self.assertFalse(ft.can_matches) 39 | 40 | def test_value_from_unparsed(self): 41 | ft = ftypes.FieldType(ftypes.FieldType.BOOLEAN) 42 | self.assertTrue(isinstance(ft.value_from_unparsed('0'), ftypes.Value)) 43 | 44 | ft = ftypes.FieldType(ftypes.FieldType.IPv4) 45 | self.assertTrue(isinstance(ft.value_from_unparsed('127.0.0.1'), 46 | ftypes.Value)) 47 | with self.assertRaises(ftypes.CantParseValue) as ctx: 48 | ft.value_from_unparsed('') 49 | self.assertTrue('is not a valid hostname' in ctx.exception.messages[0]) 50 | 51 | 52 | class TestBooleanValues(unittest.TestCase): 53 | 54 | def test_cmp(self): 55 | v1 = ftypes.FieldType(ftypes.FieldType.BOOLEAN).value_from_unparsed('0') 56 | v2 = ftypes.FieldType(ftypes.FieldType.BOOLEAN).value_from_unparsed('1') 57 | self.assertEqual(v1, v1) 58 | self.assertNotEqual(v1, v2) 59 | for f in ('__gt__', '__ge__', '__lt__', '__le__'): 60 | self.assertRaises(ftypes.OperationNotPossible, getattr(v1, f), v2) 61 | 62 | 63 | class TestStringValues(unittest.TestCase): 64 | 65 | def setUp(self): 66 | self.ft = ftypes.FieldType(ftypes.FieldType.STRING) 67 | self.value = self.ft.value_from_unparsed('foobar') 68 | 69 | def test_repr(self): 70 | repr(self.value) 71 | self.assertEqual(self.value.to_string_repr(self.value.REPR_DISPLAY), 72 | 'foobar') 73 | self.assertEqual(self.value.to_string_repr(self.value.REPR_DFILTER), 74 | '"foobar"') 75 | 76 | def test_len(self): 77 | self.assertEqual(len(self.value), len('foobar')) 78 | 79 | def test_type(self): 80 | self.assertEqual(self.value.type_.name, 'FT_STRING') 81 | 82 | 83 | class TestInt32Values(unittest.TestCase): 84 | 85 | def setUp(self): 86 | self.ft = ftypes.FieldType(ftypes.FieldType.INT32) 87 | self.value = self.ft.value_from_unparsed('4711') 88 | 89 | def test_repr(self): 90 | repr(self.value) 91 | self.assertEqual(self.value.to_string_repr(self.value.REPR_DISPLAY), 92 | '4711') 93 | self.assertEqual(self.value.to_string_repr(self.value.REPR_DFILTER), 94 | '4711') 95 | 96 | def test_len(self): 97 | self.assertRaises(ftypes.OperationNotPossible, self.value.len) 98 | self.assertEqual(len(self.value), 4) 99 | 100 | def test_cmp(self): 101 | v1 = self.ft.value_from_unparsed('100') 102 | v2 = self.ft.value_from_unparsed('200') 103 | self.assertEqual(v1, v1) 104 | self.assertNotEqual(v1, v2) 105 | self.assertTrue(v1 < v2) 106 | self.assertTrue(v1 <= v2) 107 | self.assertTrue(v2 > v1) 108 | self.assertTrue(v2 >= v1) 109 | -------------------------------------------------------------------------------- /wirepy/tests/test_glib2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import glib2 3 | 4 | 5 | class TestString(unittest.TestCase): 6 | 7 | def test_empty(self): 8 | s = glib2.String('') 9 | self.assertEqual(len(s), 0) 10 | self.assertEqual(str(s), '') 11 | 12 | def test_str(self): 13 | crib = 'foobar' 14 | s = glib2.String(crib) 15 | self.assertEqual(len(s), len(crib)) 16 | self.assertEqual(s.string, crib) 17 | self.assertEqual(str(s), crib) 18 | self.assertGreaterEqual(s.allocated_len, len(crib)) 19 | del s 20 | -------------------------------------------------------------------------------- /wirepy/tests/test_prefs.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from wirepy.lib import prefs, epan 4 | 5 | epan.epan_init() 6 | 7 | 8 | class TestPreferences(unittest.TestCase): 9 | 10 | def test_set_pref(self): 11 | p = prefs.read_prefs() 12 | self.assertRaises(prefs.PrefSyntaxError, p.set_pref, '_B0RK_') 13 | self.assertRaises(prefs.NoSuchPreference, p.set_pref, 'foo.bar:""') 14 | p.set_pref('column.format: "No.", "%m"') 15 | self.assertEqual(p.num_cols, 1) 16 | #self.assertEqual(len(p.col_list), 1) 17 | 18 | def test_global_prefs(self): 19 | p = prefs.get_global_prefs() 20 | self.assertTrue(p.is_global_prefs) 21 | -------------------------------------------------------------------------------- /wirepy/tests/test_timestamp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import timestamp 3 | 4 | 5 | class TestTimestamp(unittest.TestCase): 6 | 7 | def test_type(self): 8 | timestamp.set_type(timestamp.UTC) 9 | self.assertEqual(timestamp.get_type(), timestamp.UTC) 10 | 11 | def test_precision(self): 12 | timestamp.set_precision(timestamp.PREC_FIXED_SEC) 13 | self.assertEqual(timestamp.get_precision(), 14 | timestamp.PREC_FIXED_SEC) 15 | 16 | def test_seconds_type(self): 17 | timestamp.set_seconds_type(timestamp.SECONDS_HOUR_MIN_SEC) 18 | self.assertEqual(timestamp.get_seconds_type(), 19 | timestamp.SECONDS_HOUR_MIN_SEC) 20 | 21 | def test_errors(self): 22 | self.assertRaises(timestamp.InvalidTimestampValue, 23 | timestamp.set_type, -1) 24 | self.assertRaises(timestamp.InvalidTimestampValue, 25 | timestamp.set_seconds_type, -1) 26 | -------------------------------------------------------------------------------- /wirepy/tests/test_wsutil.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from wirepy.lib import wsutil 3 | 4 | wsutil.init_process_policies() 5 | 6 | 7 | class TestWSUtil(unittest.TestCase): 8 | 9 | def test_started_with_privs(self): 10 | self.assertTrue(isinstance(wsutil.started_with_special_privs(), bool)) 11 | 12 | def test_running_with_privs(self): 13 | self.assertTrue(isinstance(wsutil.running_with_special_privs(), bool)) 14 | 15 | def test_cur_username(self): 16 | self.assertTrue(isinstance(wsutil.get_cur_username(), str)) 17 | 18 | def test_cur_groupname(self): 19 | self.assertTrue(isinstance(wsutil.get_cur_groupname(), str)) 20 | -------------------------------------------------------------------------------- /wirepy/tests/test_wtap.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import tempfile 4 | from wirepy.lib import wtap 5 | 6 | 7 | class TestFunctions(unittest.TestCase): 8 | 9 | def test_iter_ftypes(self): 10 | for ftype in wtap.iter_file_types(): 11 | self.assertTrue(isinstance(ftype, wtap.FileType)) 12 | self.assertTrue(isinstance(repr(ftype), str)) 13 | 14 | def test_iter_etypes(self): 15 | for encap in wtap.iter_encapsulation_types(): 16 | self.assertTrue(isinstance(encap, wtap.EncapsulationType)) 17 | self.assertTrue(isinstance(repr(encap), str)) 18 | 19 | 20 | class TestFileType(unittest.TestCase): 21 | 22 | def test_unknown(self): 23 | ft = wtap.FileType.from_short_string(None) 24 | self.assertEqual(ft.string, None) 25 | self.assertEqual(ft.short_string, None) 26 | self.assertEqual(ft.ft, 0) 27 | 28 | def test_new(self): 29 | ftype = wtap.FileType.from_short_string('pcap') 30 | self.assertTrue(isinstance(ftype, wtap.FileType)) 31 | self.assertEqual(ftype.short_string, 'pcap') 32 | self.assertTrue(isinstance(ftype.string, str)) 33 | self.assertEqual(ftype.default_file_extension, 'pcap') 34 | self.assertTrue(ftype.dump_can_open or True) 35 | self.assertTrue(ftype.dump_can_compress or True) 36 | 37 | def test_invalid(self): 38 | self.assertRaises(wtap.InvalidFileType, wtap.FileType, -1) 39 | self.assertRaises(wtap.InvalidFileType, 40 | wtap.FileType.from_short_string, '_B0RK_') 41 | 42 | def test_file_extension(self): 43 | ftype = wtap.FileType.from_short_string('libpcap') 44 | extensions = ftype.file_extensions 45 | self.assertTrue(all((isinstance(e, str) for e in extensions))) 46 | self.assertGreater(len(extensions), 0) 47 | self.assertTrue('cap' in extensions) 48 | 49 | 50 | class TestEncapsulationType(unittest.TestCase): 51 | 52 | def test_new(self): 53 | encap = wtap.EncapsulationType.from_short_string('ether') 54 | self.assertTrue(isinstance(encap, wtap.EncapsulationType)) 55 | self.assertEqual(encap.short_string, 'ether') 56 | self.assertTrue(isinstance(encap.string, str)) 57 | 58 | def test_invalid(self): 59 | self.assertRaises(wtap.InvalidEncapsulationType, 60 | wtap.EncapsulationType, -2) 61 | self.assertRaises(wtap.InvalidEncapsulationType, 62 | wtap.EncapsulationType.from_short_string, '_B0RK_') 63 | 64 | 65 | class TestWTAP(unittest.TestCase): 66 | testpath = os.path.dirname(os.path.abspath(__file__)) 67 | testfile = os.path.join(testpath, 'sample_files/http.cap.gz') 68 | 69 | def test_open_not_existing_file(self): 70 | self.assertRaises(OSError, wtap.WTAP.open_offline, '_B0RK_') 71 | 72 | def test_open_truncated_file(self): 73 | with tempfile.NamedTemporaryFile() as tfile: 74 | tfile.write('_B0RK_'.encode()) 75 | tfile.flush() 76 | self.assertRaises(wtap.UnknownFormat, wtap.WTAP.open_offline, 77 | tfile.name) 78 | 79 | def test_open_offline(self): 80 | with wtap.WTAP.open_offline(self.testfile) as w: 81 | self.assertTrue(isinstance(w, wtap.WTAP)) 82 | self.assertTrue(w.is_compressed) 83 | self.assertEqual(w.file_type.short_string, 'pcap') 84 | self.assertEqual(w.file_encap.short_string, 'ether') 85 | self.assertEqual(w.tsprecision, w.FILE_TSPREC_USEC) 86 | self.assertGreater(w.read_so_far, 0) 87 | self.assertGreater(w.file_size, 0) 88 | 89 | def test_iter(self): 90 | with wtap.WTAP.open_offline(self.testfile) as w: 91 | frame = next(iter(w)) 92 | self.assertTrue(isinstance(frame, wtap.Frame)) 93 | self.assertTrue(isinstance(frame.link_type, 94 | wtap.EncapsulationType)) 95 | 96 | def test_read(self): 97 | with wtap.WTAP.open_offline(self.testfile) as w: 98 | res, data_offset = w.read() 99 | header = w.packetheader 100 | self.assertTrue(isinstance(header, wtap.PacketHeader)) 101 | self.assertTrue(header.is_flag_set(header.HAS_TS)) 102 | self.assertTrue(header.is_flag_set(header.HAS_CAP_LEN)) 103 | self.assertGreater(header.caplen, 0) 104 | self.assertGreater(header.len, 0) 105 | --------------------------------------------------------------------------------