Skip to content

Commit 33c471c

Browse files
committed
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 390d96a commit 33c471c

File tree

2 files changed

+280
-43
lines changed

2 files changed

+280
-43
lines changed

scapy/utils.py

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,35 +1278,48 @@ def _write_header(self, pkt):
12781278
self.f.flush()
12791279

12801280
def write(self, pkt):
1281-
"""accepts either a single packet or a list of packets to be
1282-
written to the dumpfile
1281+
"""
1282+
Writes a Packet or bytes to a pcap file.
12831283
1284+
:param pkt: Packet(s) to write (one record for each Packet), or raw
1285+
bytes to write (as one record).
1286+
:type pkt: iterable[Packet], Packet or bytes
12841287
"""
12851288
if isinstance(pkt, bytes):
12861289
if not self.header_present:
12871290
self._write_header(pkt)
12881291
self._write_packet(pkt)
12891292
else:
12901293
pkt = pkt.__iter__()
1291-
if not self.header_present:
1292-
try:
1293-
p = next(pkt)
1294-
except (StopIteration, RuntimeError):
1295-
self._write_header(None)
1296-
return
1297-
self._write_header(p)
1298-
self._write_packet(p)
12991294
for p in pkt:
1295+
if not self.header_present:
1296+
self._write_header(p)
13001297
self._write_packet(p)
13011298

1302-
def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None): # noqa: E501
1303-
"""writes a single packet to the pcap file
1299+
def _write_packet(self, packet, sec=None, usec=None, caplen=None,
1300+
wirelen=None):
1301+
"""
1302+
Writes a single packet to the pcap file.
1303+
1304+
:param packet: bytes for a single packet
1305+
:type packet: bytes
1306+
:param sec: time the packet was captured, in seconds since epoch. If
1307+
not supplied, defaults to now.
1308+
:type sec: int or long
1309+
:param usec: If ``nano=True``, then number of nanoseconds after the
1310+
second that the packet was captured. If ``nano=False``,
1311+
then the number of microseconds after the second the
1312+
packet was captured
1313+
:type usec: int or long
1314+
:param caplen: The length of the packet in the capture file. If not
1315+
specified, uses ``len(packet)``.
1316+
:type caplen: int
1317+
:param wirelen: The length of the packet on the wire. If not
1318+
specified, uses ``caplen``.
1319+
:type wirelen: int
1320+
:returns: None
1321+
:rtype: None
13041322
"""
1305-
if isinstance(packet, tuple):
1306-
for pkt in packet:
1307-
self._write_packet(pkt, sec=sec, usec=usec, caplen=caplen,
1308-
wirelen=wirelen)
1309-
return
13101323
if caplen is None:
13111324
caplen = len(packet)
13121325
if wirelen is None:
@@ -1316,9 +1329,13 @@ def _write_packet(self, packet, sec=None, usec=None, caplen=None, wirelen=None):
13161329
it = int(t)
13171330
if sec is None:
13181331
sec = it
1319-
if usec is None:
1320-
usec = int(round((t - it) * (1000000000 if self.nano else 1000000))) # noqa: E501
1321-
self.f.write(struct.pack(self.endian + "IIII", sec, usec, caplen, wirelen)) # noqa: E501
1332+
usec = int(round((t - it) *
1333+
(1000000000 if self.nano else 1000000)))
1334+
elif usec is None:
1335+
usec = 0
1336+
1337+
self.f.write(struct.pack(self.endian + "IIII",
1338+
sec, usec, caplen, wirelen))
13221339
self.f.write(packet)
13231340
if self.sync:
13241341
self.f.flush()
@@ -1327,6 +1344,8 @@ def flush(self):
13271344
return self.f.flush()
13281345

13291346
def close(self):
1347+
if not self.header_present:
1348+
self._write_header(None)
13301349
return self.f.close()
13311350

13321351
def __enter__(self):
@@ -1341,8 +1360,6 @@ class PcapWriter(RawPcapWriter):
13411360
"""A stream PCAP writer with more control than wrpcap()"""
13421361

13431362
def _write_header(self, pkt):
1344-
if isinstance(pkt, tuple) and pkt:
1345-
pkt = pkt[0]
13461363
if self.linktype is None:
13471364
try:
13481365
self.linktype = conf.l2types[pkt.__class__]
@@ -1351,17 +1368,51 @@ def _write_header(self, pkt):
13511368
self.linktype = DLT_EN10MB
13521369
RawPcapWriter._write_header(self, pkt)
13531370

1354-
def _write_packet(self, packet):
1355-
if isinstance(packet, tuple):
1356-
for pkt in packet:
1357-
self._write_packet(pkt)
1358-
return
1359-
sec = int(packet.time)
1360-
usec = int(round((packet.time - sec) * (1000000000 if self.nano else 1000000))) # noqa: E501
1371+
def _write_packet(self, packet, sec=None, usec=None, caplen=None,
1372+
wirelen=None):
1373+
"""
1374+
Writes a single packet to the pcap file.
1375+
1376+
:param packet: Packet, or bytes for a single packet
1377+
:type packet: Packet or bytes
1378+
:param sec: time the packet was captured, in seconds since epoch. If
1379+
not supplied, defaults to now.
1380+
:type sec: int or long
1381+
:param usec: If ``nano=True``, then number of nanoseconds after the
1382+
second that the packet was captured. If ``nano=False``,
1383+
then the number of microseconds after the second the
1384+
packet was captured. If ``sec`` is not specified,
1385+
this value is ignored.
1386+
:type usec: int or long
1387+
:param caplen: The length of the packet in the capture file. If not
1388+
specified, uses ``len(raw(packet))``.
1389+
:type caplen: int
1390+
:param wirelen: The length of the packet on the wire. If not
1391+
specified, tries ``packet.wirelen``, otherwise uses
1392+
``caplen``.
1393+
:type wirelen: int
1394+
:returns: None
1395+
:rtype: None
1396+
"""
1397+
if hasattr(packet, "time"):
1398+
if sec is None:
1399+
sec = int(packet.time)
1400+
usec = int(round((packet.time - sec) *
1401+
(1000000000 if self.nano else 1000000)))
1402+
if usec is None:
1403+
usec = 0
1404+
13611405
rawpkt = raw(packet)
1362-
caplen = len(rawpkt)
1363-
RawPcapWriter._write_packet(self, rawpkt, sec=sec, usec=usec, caplen=caplen, # noqa: E501
1364-
wirelen=packet.wirelen or caplen)
1406+
caplen = len(rawpkt) if caplen is None else caplen
1407+
1408+
if wirelen is None:
1409+
if hasattr(packet, "wirelen"):
1410+
wirelen = packet.wirelen
1411+
if wirelen is None:
1412+
wirelen = caplen
1413+
1414+
RawPcapWriter._write_packet(
1415+
self, rawpkt, sec=sec, usec=usec, caplen=caplen, wirelen=wirelen)
13651416

13661417

13671418
@conf.commands.register
@@ -1390,21 +1441,35 @@ def import_hexcap():
13901441

13911442

13921443
@conf.commands.register
1393-
def wireshark(pktlist):
1394-
"""Run wireshark on a list of packets"""
1395-
tcpdump(pktlist, prog=conf.prog.wireshark)
1444+
def wireshark(pktlist, wait=False, **kwargs):
1445+
"""
1446+
Runs Wireshark on a list of packets.
1447+
1448+
See :func:`tcpdump` for more parameter description.
1449+
1450+
Note: this defaults to wait=False, to run Wireshark in the background.
1451+
"""
1452+
return tcpdump(pktlist, prog=conf.prog.wireshark, wait=wait, **kwargs)
13961453

13971454

13981455
@conf.commands.register
1399-
def tdecode(pktlist):
1400-
"""Run tshark -V on a list of packets"""
1401-
tcpdump(pktlist, prog=conf.prog.tshark, args=["-V"])
1456+
def tdecode(pktlist, args=None, **kwargs):
1457+
"""
1458+
Run tshark on a list of packets.
1459+
1460+
:param args: If not specified, defaults to ``tshark -V``.
1461+
1462+
See :func:`tcpdump` for more parameters.
1463+
"""
1464+
if args is None:
1465+
args = ["-V"]
1466+
return tcpdump(pktlist, prog=conf.prog.tshark, args=args, **kwargs)
14021467

14031468

14041469
@conf.commands.register
14051470
def tcpdump(pktlist, dump=False, getfd=False, args=None,
14061471
prog=None, getproc=False, quiet=False, use_tempfile=None,
1407-
read_stdin_opts=None):
1472+
read_stdin_opts=None, linktype=None, wait=True):
14081473
"""Run tcpdump or tshark on a list of packets.
14091474
14101475
When using ``tcpdump`` on OSX (``prog == conf.prog.tcpdump``), this uses a
@@ -1444,6 +1509,13 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
14441509
``tcpdump`` on OSX.
14451510
read_stdin_opts: When set, a list of arguments needed to capture from stdin.
14461511
Otherwise, attempts to guess.
1512+
linktype: If a Packet (or list) is passed in the ``pktlist`` argument,
1513+
set the ``linktype`` parameter on ``wrpcap``. If ``pktlist`` is a
1514+
path to a pcap file, then this option will have no effect.
1515+
wait: If True (default), waits for the process to terminate before returning
1516+
to Scapy. If False, the process will be detached to the background. If
1517+
dump, getproc or getfd is True, these have the same effect as
1518+
``wait=False``.
14471519
14481520
Examples:
14491521
@@ -1485,9 +1557,16 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
14851557
elif isinstance(prog, six.string_types):
14861558
_prog_name = "{}()".format(prog)
14871559
prog = [prog]
1560+
else:
1561+
raise ValueError("prog must be a string")
14881562

14891563
# Build Popen arguments
1490-
args = args if args is not None else []
1564+
if args is None:
1565+
args = []
1566+
else:
1567+
# Make a copy of args
1568+
args = list(args)
1569+
14911570
stdout = subprocess.PIPE if dump or getfd else None
14921571
stderr = open(os.devnull) if quiet else None
14931572

@@ -1502,6 +1581,9 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
15021581
read_stdin_opts = ["-ki", "-"]
15031582
else:
15041583
read_stdin_opts = ["-r", "-"]
1584+
else:
1585+
# Make a copy of read_stdin_opts
1586+
read_stdin_opts = list(read_stdin_opts)
15051587

15061588
if pktlist is None:
15071589
# sniff
@@ -1524,7 +1606,7 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
15241606
try:
15251607
tmpfile.writelines(iter(lambda: pktlist.read(1048576), b""))
15261608
except AttributeError:
1527-
wrpcap(tmpfile, pktlist)
1609+
wrpcap(tmpfile, pktlist, linktype=linktype)
15281610
else:
15291611
tmpfile.close()
15301612
with ContextManagerSubprocess(_prog_name, prog[0]):
@@ -1545,7 +1627,7 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
15451627
try:
15461628
proc.stdin.writelines(iter(lambda: pktlist.read(1048576), b""))
15471629
except AttributeError:
1548-
wrpcap(proc.stdin, pktlist)
1630+
wrpcap(proc.stdin, pktlist, linktype=linktype)
15491631
except UnboundLocalError:
15501632
raise IOError("%s died unexpectedly !" % prog)
15511633
else:
@@ -1556,7 +1638,8 @@ def tcpdump(pktlist, dump=False, getfd=False, args=None,
15561638
return proc
15571639
if getfd:
15581640
return proc.stdout
1559-
proc.wait()
1641+
if wait:
1642+
proc.wait()
15601643

15611644

15621645
@conf.commands.register

0 commit comments

Comments
 (0)