From 598bdae8c8f0a8c1786bb84718c55d38191d2bdb Mon Sep 17 00:00:00 2001
From: Pierre LALET <pierre.lalet@cea.fr>
Date: Fri, 23 Dec 2016 17:13:22 +0100
Subject: [PATCH] Introduce tcpdump() function

One can now use tcpdump or tshark to dissect packets from
Scapy. This can help, for example, to check Scapy's dissectors
---
 .travis/test.sh                |  3 +-
 appveyor.yml                   |  4 +-
 scapy/arch/windows/__init__.py |  1 +
 scapy/config.py                |  1 +
 scapy/utils.py                 | 70 ++++++++++++++++++++++++++++++++++
 test/regression.uts            | 16 ++++++++
 6 files changed, 92 insertions(+), 3 deletions(-)

diff --git a/.travis/test.sh b/.travis/test.sh
index b82d16c8..f9cf530a 100644
--- a/.travis/test.sh
+++ b/.travis/test.sh
@@ -39,8 +39,9 @@ then
   fi
 fi
 
-# Do we have tcpdump?
+# Do we have tcpdump or thsark?
 which tcpdump >/dev/null 2>&1 || UT_FLAGS+=" -K tcpdump"
+which tshark >/dev/null 2>&1 || UT_FLAGS+=" -K tshark"
 
 # Dump Environment (so that we can check PATH, UT_FLAGS, etc.)
 set
diff --git a/appveyor.yml b/appveyor.yml
index 9b8537ef..f290b714 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,7 +14,7 @@ install:
   # Installing WinPcap directly does not work,
   # see http://help.appveyor.com/discussions/problems/2280-winpcap-installation-issue
   # - choco install -y nmap
-  - choco install -y winpcap
+  - choco install -y winpcap wireshark
   - ps: wget http://www.winpcap.org/windump/install/bin/windump_3_9_5/WinDump.exe -UseBasicParsing -OutFile C:\Windows\System32\windump.exe
   - refreshenv
 
@@ -24,7 +24,7 @@ install:
 test_script:
   # Set environment variables
   - set PYTHONPATH=%APPVEYOR_BUILD_FOLDER%
-  - set PATH=%APPVEYOR_BUILD_FOLDER%;%PATH%
+  - set PATH="%APPVEYOR_BUILD_FOLDER%;C:\Program Files\Wireshark\;%PATH%"
   
   # Main unit tests
   - "%PYTHON%\\python bin\\UTscapy -f text -t test\\regression.uts -F -K automaton -K mock_read_routes6_bsd || exit /b 42"
diff --git a/scapy/arch/windows/__init__.py b/scapy/arch/windows/__init__.py
index 5ff08a02..ddb5487f 100755
--- a/scapy/arch/windows/__init__.py
+++ b/scapy/arch/windows/__init__.py
@@ -187,6 +187,7 @@ class WinProgPath(ConfClass):
     psreader = win_find_exe("gsview32.exe", "Ghostgum/gsview")
     dot = win_find_exe("dot", "ATT/Graphviz/bin")
     tcpdump = win_find_exe("windump")
+    tshark = win_find_exe("tshark")
     tcpreplay = win_find_exe("tcpreplay")
     display = _default
     hexedit = win_find_exe("hexer")
diff --git a/scapy/config.py b/scapy/config.py
index 568dada8..b97ee0bd 100755
--- a/scapy/config.py
+++ b/scapy/config.py
@@ -64,6 +64,7 @@ class ProgPath(ConfClass):
     tcpdump = "tcpdump"
     tcpreplay = "tcpreplay"
     hexedit = "hexer"
+    tshark = "tshark"
     wireshark = "wireshark"
     ifconfig = "ifconfig"
 
diff --git a/scapy/utils.py b/scapy/utils.py
index af8eda4b..3f6bb4b7 100644
--- a/scapy/utils.py
+++ b/scapy/utils.py
@@ -1088,6 +1088,76 @@ def wireshark(pktlist):
     wrpcap(f, pktlist)
     subprocess.Popen([conf.prog.wireshark, "-r", f])
 
+@conf.commands.register
+def tcpdump(pktlist, dump=False, getfd=False, args=None,
+            prog=None):
+    """Run tcpdump or tshark on a list of packets
+
+pktlist: a Packet instance, a PacketList instance or a list of Packet
+         instances. Can also be a filename (as a string) or an open
+         file-like object that must be a file format readable by
+         tshark (Pcap, PcapNg, etc.)
+
+dump:    when set to True, returns a string instead of displaying it.
+getfd:   when set to True, returns a file-like object to read data
+         from tcpdump or tshark from.
+args:    arguments (as a list) to pass to tshark (example for tshark:
+         args=["-T", "json"]). Defaults to ["-n"].
+prog:    program to use (defaults to tcpdump, will work with tshark)
+
+Examples:
+
+>>> tcpdump([IP()/TCP(), IP()/UDP()])
+reading from file -, link-type RAW (Raw IP)
+16:46:00.474515 IP 127.0.0.1.20 > 127.0.0.1.80: Flags [S], seq 0, win 8192, length 0
+16:46:00.475019 IP 127.0.0.1.53 > 127.0.0.1.53: [|domain]
+
+>>> tcpdump([IP()/TCP(), IP()/UDP()], prog=conf.prog.tshark)
+  1   0.000000    127.0.0.1 -> 127.0.0.1    TCP 40 20->80 [SYN] Seq=0 Win=8192 Len=0
+  2   0.000459    127.0.0.1 -> 127.0.0.1    UDP 28 53->53 Len=0
+
+To get a JSON representation of a tshark-parsed PacketList(), one can:
+>>> import json, pprint
+>>> json_data = json.load(tcpdump(IP(src="217.25.178.5", dst="45.33.32.156"),
+...                               prog=conf.prog.tshark, args=["-T", "json"],
+...                               getfd=True))
+>>> pprint.pprint(json_data)
+[{u'_index': u'packets-2016-12-23',
+  u'_score': None,
+  u'_source': {u'layers': {u'frame': {u'frame.cap_len': u'20',
+                                      u'frame.encap_type': u'7',
+[...]
+                                      u'frame.time_relative': u'0.000000000'},
+                           u'ip': {u'ip.addr': u'45.33.32.156',
+                                   u'ip.checksum': u'0x0000a20d',
+[...]
+                                   u'ip.ttl': u'64',
+                                   u'ip.version': u'4'},
+                           u'raw': u'Raw packet data'}},
+  u'_type': u'pcap_file'}]
+>>> json_data[0]['_source']['layers']['ip']['ip.ttl']
+u'64'
+
+    """
+    proc = subprocess.Popen(
+        [conf.prog.tcpdump if prog is None else prog, "-r",
+         pktlist if isinstance(pktlist, basestring) else "-"]
+        + (["-n"] if args is None else args),
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE if dump or getfd else None,
+    )
+    try:
+        proc.stdin.writelines(iter(lambda: pktlist.read(1048576), ""))
+    except AttributeError:
+        wrpcap(proc.stdin, pktlist)
+    else:
+        proc.stdin.close()
+    if dump:
+        return "".join(iter(lambda: proc.stdout.read(1048576), ""))
+    if getfd:
+        return proc.stdout
+    proc.wait()
+
 @conf.commands.register
 def hexedit(x):
     x = str(x)
diff --git a/test/regression.uts b/test/regression.uts
index 7c379a50..b6826195 100644
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -4706,6 +4706,22 @@ assert isinstance(pkt, Padding) and pkt.load == '\xeay$\xf6'
 pkt = pkt.payload
 assert isinstance(pkt, NoPayload)
 
+= Check tcpdump()
+~ tcpdump
+* No very specific tests because we do not want to depend on tcpdump output
+pcapfile = cStringIO.StringIO('\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00e\x00\x00\x00\xcf\xc5\xacVo*\n\x00(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00\xcf\xc5\xacV_-\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r\xcf\xc5\xacV\xf90\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00')
+data = tcpdump(pcapfile, dump=True, args=['-n']).split('\n')
+print data
+assert 'IP 127.0.0.1.20 > 127.0.0.1.80:' in data[0]
+assert 'IP 127.0.0.1.53 > 127.0.0.1.53:' in data[1]
+assert 'IP 127.0.0.1 > 127.0.0.1:' in data[2]
+
+= Check tcpdump() command with tshark
+~ tshark
+pcapfile = cStringIO.StringIO('\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00e\x00\x00\x00\xcf\xc5\xacVo*\n\x00(\x00\x00\x00(\x00\x00\x00E\x00\x00(\x00\x01\x00\x00@\x06|\xcd\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91|\x00\x00\xcf\xc5\xacV_-\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x11|\xce\x7f\x00\x00\x01\x7f\x00\x00\x01\x005\x005\x00\x08\x01r\xcf\xc5\xacV\xf90\n\x00\x1c\x00\x00\x00\x1c\x00\x00\x00E\x00\x00\x1c\x00\x01\x00\x00@\x01|\xde\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x00\xf7\xff\x00\x00\x00\x00')
+values = [tuple(int(val) for val in line[:-1].split('\t')) for line in tcpdump(pcapfile, prog=conf.prog.tshark, getfd=True, args=['-T', 'fields', '-e', 'ip.ttl', '-e', 'ip.proto'])]
+assert values == [(64, 6), (64, 17), (64, 1)]
+
 
 ############
 ############
-- 
GitLab