Skip to content

Commit 6dde6ae

Browse files
micolousgpotter2
authored andcommitted
Adds Wireshark pipes support, cleanup OSX tcpdump handling. (#1959)
* Adds Wireshark pipes support, cleanup OSX tcpdump handling. * SniffSource now only opens a socket on calling `start`. * WrpcapSink only opens a PcapWriter on calling `start`. * Adds WiresharkSink for streaming packets to Wireshark through a pipe. * Adds `fd` parameter to `utils.get_temp_file`, which allows a temporary file to be used without closing and re-opening it. * Adds `utils.get_temp_dir` (used for tests) * Adds `use_tempfile` parameter to `utils.tcpdump`, which causes it to use a temporary file to store packets. * `utils.tcpdump` now only uses a temporary file by default for calling tcpdump on OSX (to work around Apple's broken version of tcpdump). stdin is now used with other tools which are not impacted by this bug (eg: tshark). * Adds `read_stdin_opts` parameter to `utils.tcpdump`, which allows callers to control the options used for reading packets over stdin (previously hard-coded to `-r -`). * `utils.wireshark` now uses a pipe rather than a temporary file. Wireshark itself has options to save this file to disk if desired. Also cleans up the tests a little: * Removes `os.remove("test.png")` (which seems to be unused). * Uses a temporary directory for some pipetool tests. Includes fixup: * spelling * Documentation for WiresharkSink, Pipetools sinks and wireshark(): * Enable `manpages_url`, so we can use manpage links. * Pipetools: Revise Sinks section to use `py:class` style documentation. This makes cross-references to specific sinks much easier. * Add documentation for `WiresharkSink`. * Pipetools: Revise Link object section with some word-smithing. * Usage/wireshark: * improve wordsmithing * remove hard coded references to `google.com` (which trigger DNS lookups), and use an RFC 5737 netmask instead. * set a source IP address (otherwise, this gets the host's IP address). * remove `Ether` layer, as Wireshark supports `DLT_RAW` (which triggers getting the host's MAC address). * elaborate on mixed `linktype` issues. * Pipetool: fixups: * `QueueSink.recv`: add `block` and `timeout` parameters * `WrpcapSink`, `WiresharkSink`: add `linktype` parameter * `WiresharkSink`: add `args` parameter * tests: sleep longer to work-around race conditions on Windows * utils: many fixups * `{Raw,}PcapWriter._write_packet`: * Remove unused support for `packet` as tuple, as `write` will always unroll iterators for us (and do it better). * Always set the `usec` parameter if `sec` was unset. * Set `usec=0` if `sec` was set, but `usec` was unset (instead of using the current time's usec value * PEP-8 fixup, add docstring. * Only write the header if there is a packet * `PcapWriter._write_packet`: support packet as bytes * `RawPcapWriter.close`: write the file header here if not already written * `tcpdump()`: * Add `linktype` parameter, like `wrpcap(linktype=...)` * Add `wait` parameter, which controls whether a program should be run in the background. Defaults to `True` (run in foreground). * Throw an error if `prog` is not a string. * Copy `read_stdin_opts` (for thread safety). * `tdecode()`: Add `args` parameter (defaults to `-V`, as before), pass other `tcpdump()` kwargs. * `wireshark()`: Run in the background by default, pass other `tcpdump()` kwargs. * Add tests that hit `wireshark`, `tdecode`, `tcpdump` with new parameters, and try to pass packets as bytes.
1 parent a645e7a commit 6dde6ae

File tree

8 files changed

+835
-133
lines changed

8 files changed

+835
-133
lines changed

doc/scapy/advanced_usage.rst

Lines changed: 186 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,7 @@ Two methods are hooks to be overloaded:
790790

791791
* The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition.
792792

793+
.. _pipetools:
793794

794795
PipeTools
795796
=========
@@ -882,7 +883,10 @@ class ConsoleSink(Sink)
882883
Sources
883884
^^^^^^^
884885

885-
A Source is a class that generates some data. They are several source types integrated with Scapy, usable as-is, but you may also create yours.
886+
A Source is a class that generates some data.
887+
888+
There are several source types integrated with Scapy, usable as-is, but you may
889+
also create yours.
886890

887891
Default Source classes
888892
~~~~~~~~~~~~~~~~~~~~~~
@@ -951,25 +955,165 @@ For instance, here is how TransformDrain is implemented::
951955
Sinks
952956
^^^^^
953957

958+
Sinks are destinations for messages.
959+
960+
A :py:class:`Sink` receives data like a :py:class:`Drain`, but doesn't send any
961+
messages after it.
962+
963+
Messages on the low entry come from :py:meth:`~Sink.push`, and messages on the
964+
high entry come from :py:meth:`~Sink.high_push`.
965+
954966
Default Sink classes
955967
~~~~~~~~~~~~~~~~~~~~
956968

957-
- Sink : does not do anything. This must be extended to create custom sinks
958-
- ConsoleSink : Print messages on low and high entries
959-
- RawConsoleSink : Print messages on low and high entries, using os.write
960-
- TermSink : Print messages on low and high entries on a separate terminal
961-
- QueueSink: Collect messages from high and low entries and queue them. Messages are unqueued with the .recv() method.
969+
.. py:class:: Sink
970+
971+
Does nothing; interface to extend for custom sinks.
972+
973+
All sinks have the following constructor parameters:
974+
975+
:param name: a human-readable name for the element
976+
:type name: str
977+
978+
All sinks should implement at least one of these methods:
979+
980+
.. py:method:: push
981+
982+
Called by :py:class:`PipeEngine` when there is a new message for the
983+
low entry.
984+
985+
:param msg: The message data
986+
:returns: None
987+
:rtype: None
988+
989+
.. py:method:: high_push
990+
991+
Called by :py:class:`PipeEngine` when there is a new message for the
992+
high entry.
993+
994+
:param msg: The message data
995+
:returns: None
996+
:rtype: None
997+
998+
.. py:class:: ConsoleSink
999+
1000+
Prints messages on the low and high entries to ``stdout``.
1001+
1002+
.. py:class:: RawConsoleSink
1003+
1004+
Prints messages on the low and high entries, using :py:func:`os.write`.
1005+
1006+
:param newlines: Include a new-line character after printing each packet.
1007+
Defaults to True.
1008+
:type newlines: bool
1009+
1010+
.. py:class:: TermSink
1011+
1012+
Prints messages on the low and high entries, on a separate terminal (xterm
1013+
or cmd).
1014+
1015+
:param keepterm: Leaves the terminal window open after :py:meth:`~Pipe.stop`
1016+
is called. Defaults to True.
1017+
:type keepterm: bool
1018+
:param newlines: Include a new-line character after printing each packet.
1019+
Defaults to True.
1020+
:type newlines: bool
1021+
:param openearly: Automatically starts the terminal when the constructor is
1022+
called, rather than waiting for :py:meth:`~Pipe.start`.
1023+
Defaults to True.
1024+
:type openearly: bool
1025+
1026+
.. py:class:: QueueSink
1027+
1028+
Collects messages on the low and high entries into a :py:class:`Queue`.
1029+
1030+
Messages are dequeued with :py:meth:`recv`.
1031+
1032+
Both high and low entries share the same :py:class:`Queue`.
1033+
1034+
.. py:method:: recv
1035+
1036+
Reads the next message from the queue.
1037+
1038+
If no message is available in the queue, returns None.
1039+
1040+
:param block: Blocks execution until a packet is available in the queue.
1041+
Defaults to True.
1042+
:type block: bool
1043+
:param timeout: Controls how long to wait if ``block=True``. If None
1044+
(the default), this method will wait forever. If a
1045+
non-negative number, this is a number of seconds to
1046+
wait before giving up (and returning None).
1047+
:type timeout: None, int or float
1048+
1049+
.. py:class:: WiresharkSink
1050+
1051+
Streams :py:class:`Packet` from the low entry to Wireshark.
1052+
1053+
Packets are written into a ``pcap`` stream (like :py:class:`WrpcapSink`),
1054+
and streamed to a new Wireshark process on its ``stdin``.
1055+
1056+
Wireshark is run with the ``-ki -`` arguments, which cause it to treat
1057+
``stdin`` as a capture device. Arguments in :py:attr:`args` will be
1058+
appended after this.
1059+
1060+
Extends :py:mod:`WrpcapSink`.
1061+
1062+
:param linktype: See :py:attr:`WrpcapSink.linktype`.
1063+
:type linktype: None or int
1064+
:param args: See :py:attr:`args`.
1065+
:type args: None or list[str]
1066+
1067+
.. py:attribute:: args
1068+
1069+
Additional arguments for the Wireshark process.
1070+
1071+
This must be either ``None`` (the default), or a ``list`` of ``str``.
1072+
1073+
This attribute has no effect after calling :py:meth:`PipeEngine.start`.
1074+
1075+
See :manpage:`wireshark(1)` for more details.
1076+
1077+
.. py:class:: WrpcapSink
1078+
1079+
Writes :py:class:`Packet` on the low entry to a ``pcap`` file.
1080+
1081+
Ignores all messages on the high entry.
1082+
1083+
.. note::
1084+
1085+
Due to limitations of the ``pcap`` format, all packets **must** be of
1086+
the same link type. This class will not mutate packets to conform with
1087+
the expected link type.
1088+
1089+
:param fname: Filename to write packets to.
1090+
:type fname: str
1091+
:param linktype: See :py:attr:`linktype`.
1092+
:type linktype: None or int
1093+
1094+
.. py:attribute:: linktype
1095+
1096+
Set an explicit link-type (``DLT_``) for packets. This must be an
1097+
``int`` or ``None``.
1098+
1099+
This is the same as the :py:func:`wrpcap` ``linktype`` parameter.
1100+
1101+
If ``None`` (the default), the linktype will be auto-detected on the
1102+
first packet. This field will *not* be updated with the result of this
1103+
auto-detection.
1104+
1105+
This attribute has no effect after calling :py:meth:`PipeEngine.start`.
1106+
9621107

9631108
Create a custom Sink
9641109
~~~~~~~~~~~~~~~~~~~~
9651110

966-
To create a custom sink, one must extend the ``Sink`` class.
1111+
To create a custom sink, one must extend :py:class:`Sink` and implement
1112+
:py:meth:`~Sink.push` and/or :py:meth:`~Sink.high_push`.
9671113

968-
A ``Sink`` class receives data like a ``Drain``, from the lower canal in its ``push`` method, and from the higher canal from its ``high_push`` method.
1114+
This is a simplified version of :py:class:`ConsoleSink`:
9691115

970-
A ``Sink`` is the dead end of data, it won't be sent anywhere after it.
971-
972-
For instance, here is how ConsoleSink is implemented::
1116+
.. code-block:: python3
9731117
9741118
class ConsoleSink(Sink):
9751119
def push(self, msg):
@@ -980,32 +1124,44 @@ For instance, here is how ConsoleSink is implemented::
9801124
Link objects
9811125
------------
9821126

983-
As shown in the example, most sources can be linked to any drain, on both lower and higher canals.
1127+
As shown in the example, most sources can be linked to any drain, on both low
1128+
and high entry.
9841129

985-
The use of ``>`` indicates a link on the low canal, and ``>>`` on the higher one.
1130+
The use of ``>`` indicates a link on the low entry, and ``>>`` on the high
1131+
entry.
9861132

987-
For instance
1133+
For example, to link ``a``, ``b`` and ``c`` on the low entries:
9881134

989-
>>> a = CLIFeeder()
990-
>>> b = Drain()
991-
>>> c = ConsoleSink()
992-
>>> a > b > c
993-
>>> p = PipeEngine()
994-
>>> p.add(a)
1135+
.. code-block:: pycon
1136+
1137+
>>> a = CLIFeeder()
1138+
>>> b = Drain()
1139+
>>> c = ConsoleSink()
1140+
>>> a > b > c
1141+
>>> p = PipeEngine()
1142+
>>> p.add(a)
1143+
1144+
This wouldn't link the high entries, so something like this would do nothing:
1145+
1146+
.. code-block:: pycon
1147+
1148+
>>> a2 = CLIHighFeeder()
1149+
>>> a2 >> b
1150+
>>> a2.send("hello")
9951151
996-
This links a, b, and c on the lower canal. If you tried to send anything on the higher canal, for instance by adding
1152+
Because ``b`` (:py:class:`Drain`) and ``c`` (:py:class:`ConsoleSink`) are not
1153+
linked on the high entry.
9971154

998-
>>> a2 = CLIHighFeeder()
999-
>>> a2 >> b
1000-
>>> a2.send("hello")
1155+
However, using a :py:class:`DownDrain` would bring the high messages from
1156+
:py:class:`CLIHighFeeder` to the lower channel:
10011157

1002-
It would not do anything as the Drain is not linked to the Sink on the upper canal. However, one could do
1158+
.. code-block:: pycon
10031159
1004-
>>> a2 = CLIHighFeeder()
1005-
>>> b2 = DownDrain()
1006-
>>> a2 >> b2
1007-
>>> b2 > b
1008-
>>> a2.send("hello")
1160+
>>> a2 = CLIHighFeeder()
1161+
>>> b2 = DownDrain()
1162+
>>> a2 >> b2
1163+
>>> b2 > b
1164+
>>> a2.send("hello")
10091165
10101166
The PipeEngine class
10111167
--------------------

doc/scapy/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@
106106
]
107107
}
108108

109+
# Make :manpage directive work on HTML output.
110+
manpages_url = 'https://manpages.debian.org/{path}'
109111

110112
# -- Options for HTMLHelp output ------------------------------------------
111113

doc/scapy/usage.rst

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,28 +1533,76 @@ Viewing packets with Wireshark
15331533
Problem
15341534
^^^^^^^
15351535

1536-
You have generated or sniffed some packets with Scapy and want to view them with `Wireshark <http://www.wireshark.org>`_, because of its advanced packet dissection abilities.
1536+
You have generated or sniffed some packets with Scapy.
1537+
1538+
Now you want to view them with `Wireshark <https://www.wireshark.org>`_, because
1539+
of its advanced packet dissection capabilities.
15371540

15381541
Solution
15391542
^^^^^^^^
15401543

1541-
That's what the ``wireshark()`` function is for:
1544+
That's what :py:func:`wireshark` is for!
1545+
1546+
.. code-block:: python3
15421547
1543-
>>> packets = Ether()/IP(dst=Net("google.com/30"))/ICMP() # first generate some packets
1544-
>>> wireshark(packets) # show them with Wireshark
1548+
# First, generate some packets...
1549+
packets = IP(src="192.0.2.9", dst=Net("192.0.2.10/30"))/ICMP()
15451550
1546-
Wireshark will start in the background and show your packets.
1551+
# Show them with Wireshark
1552+
wireshark(packets)
1553+
1554+
Wireshark will start in the background, and show your packets.
15471555

15481556
Discussion
15491557
^^^^^^^^^^
15501558

1551-
The ``wireshark()`` function generates a temporary pcap-file containing your packets, starts Wireshark in the background and makes it read the file on startup.
1559+
.. py:function:: wireshark(pktlist, ...)
1560+
1561+
With a :py:class:`Packet` or :py:class:`PacketList`, serialises your
1562+
packets, and streams this into Wireshark via ``stdin`` as if it were a
1563+
capture device.
1564+
1565+
Because this uses ``pcap`` format to serialise the packets, there are some
1566+
limitations:
1567+
1568+
* Packets must be all of the same ``linktype``.
1569+
1570+
For example, you can't mix :py:class:`Ether` and :py:class:`IP` at the
1571+
top layer.
1572+
1573+
* Packets must have an assigned (and supported) ``DLT_*`` constant for the
1574+
``linktype``. An unsupported ``linktype`` is replaced with ``DLT_EN10MB``
1575+
(Ethernet), and will display incorrectly in Wireshark.
1576+
1577+
For example, can't pass a bare :py:class:`ICMP` packet, but you can send
1578+
it as a payload of an :py:class:`IP` or :py:class:`IPv6` packet.
1579+
1580+
With a filename (passed as a string), this loads the given file in
1581+
Wireshark. This needs to be in a format that Wireshark supports.
1582+
1583+
You can tell Scapy where to find the Wireshark executable by changing the
1584+
``conf.prog.wireshark`` configuration setting.
1585+
1586+
This accepts the same extra parameters as :py:func:`tcpdump`.
1587+
1588+
.. seealso::
1589+
1590+
:py:class:`WiresharkSink`
1591+
A :ref:`PipeTools sink <pipetools>` for live-streaming packets.
15521592

1553-
Please remember that Wireshark works with Layer 2 packets (usually called "frames"). So we had to add an ``Ether()`` header to our ICMP packets. Passing just IP packets (layer 3) to Wireshark will give strange results.
1593+
:manpage:`wireshark(1)`
1594+
Additional description of Wireshark's functionality, and its
1595+
command-line arguments.
15541596

1555-
You can tell Scapy where to find the Wireshark executable by changing the ``conf.prog.wireshark`` configuration setting.
1597+
`Wireshark's website`__
1598+
For up-to-date releases of Wireshark.
15561599

1600+
`Wireshark Protocol Reference`__
1601+
Contains detailed information about Wireshark's protocol dissectors, and
1602+
reference documentation for various network protocols.
15571603

1604+
__ https://www.wireshark.org
1605+
__ https://wiki.wireshark.org/ProtocolReference
15581606

15591607
OS Fingerprinting
15601608
-----------------

scapy/pipetool.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -652,12 +652,11 @@ def push(self, msg):
652652
def high_push(self, msg):
653653
self.q.put(msg)
654654

655-
def recv(self):
656-
while True:
657-
try:
658-
return self.q.get(True, timeout=0.1)
659-
except six.moves.queue.Empty:
660-
pass
655+
def recv(self, block=True, timeout=None):
656+
try:
657+
return self.q.get(block=block, timeout=timeout)
658+
except six.moves.queue.Empty:
659+
pass
661660

662661

663662
class TransformDrain(Drain):

0 commit comments

Comments
 (0)