From 15bf5f11d9768bd086311bc6435fe1e3ea5c9d78 Mon Sep 17 00:00:00 2001
From: Pierre LALET <pierre.lalet@cea.fr>
Date: Thu, 22 Dec 2016 03:44:27 +0100
Subject: [PATCH] PcapNg: support nanosec precision, simple packet and obsolete
 packet blocks

---
 scapy/utils.py      | 71 ++++++++++++++++++++++++++++++++++++---------
 test/regression.uts | 38 ++++++++++++++++++++++++
 2 files changed, 96 insertions(+), 13 deletions(-)

diff --git a/scapy/utils.py b/scapy/utils.py
index e5233457..af8eda4b 100644
--- a/scapy/utils.py
+++ b/scapy/utils.py
@@ -784,10 +784,12 @@ class RawPcapNgReader(RawPcapReader):
     def __init__(self, filename, fdesc, magic):
         self.filename = filename
         self.f = fdesc
-        # A list of (linktype, snaplen); will be populated by IDBs.
+        # A list of (linktype, snaplen, tsresol); will be populated by IDBs.
         self.interfaces = []
         self.blocktypes = {
             1: self.read_block_idb,
+            2: self.read_block_pkt,
+            3: self.read_block_spb,
             6: self.read_block_epb,
         }
         if magic != "\x0a\x0d\x0d\x0a": # PcapNg:
@@ -817,12 +819,14 @@ class RawPcapNgReader(RawPcapReader):
             except struct.error:
                 return None
             block = self.f.read(blocklen - 12)
+            if blocklen % 4:
+                pad = self.f.read(4 - (blocklen % 4))
+                warning("PcapNg: bad blocklen %d (MUST be a multiple of 4. "
+                        "Ignored padding %r" % (blocklen, pad))
             try:
                 if (blocklen,) != struct.unpack(self.endian + 'I',
                                                 self.f.read(4)):
-                    raise Scapy_Exception(
-                        "Invalid pcapng block (bad blocklen)"
-                    )
+                    warning("PcapNg: Invalid pcapng block (bad blocklen)")
             except struct.error:
                 return None
             res = self.blocktypes.get(blocktype,
@@ -832,15 +836,57 @@ class RawPcapNgReader(RawPcapReader):
 
     def read_block_idb(self, block, _):
         """Interface Description Block"""
-        # We should read options to set if_tsresol
-        self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8]))
+        options = block[16:]
+        tsresol = 1000000
+        while len(options) >= 4:
+            code, length = struct.unpack(self.endian + "HH", options[:4])
+            # PCAP Next Generation (pcapng) Capture File Format
+            # 4.2. - Interface Description Block
+            # http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc.cgi?url=https://raw.githubusercontent.com/pcapng/pcapng/master/draft-tuexen-opsawg-pcapng.xml&modeAsFormat=html/ascii&type=ascii#rfc.section.4.2
+            if code == 9 and length == 1 and len(options) >= 5:
+                tsresol = ord(options[4])
+                tsresol = (2 if tsresol & 128 else 10) ** (tsresol & 127)
+            if code == 0:
+                if length != 0:
+                    warning("PcapNg: invalid option length %d for end-of-option" % length)
+                break
+            if length % 4:
+                length += (4 - (length % 4))
+            options = options[4 + length:]
+        self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8])
+                               + (tsresol,))
 
     def read_block_epb(self, block, size):
         """Enhanced Packet Block"""
-        intid, tshigh, tslow, caplen, wirelen = struct.unpack(self.endian + "5I",
-                                                          block[:20])
+        intid, tshigh, tslow, caplen, wirelen = struct.unpack(
+            self.endian + "5I",
+            block[:20],
+        )
+        return (block[20:20 + caplen][:size],
+                (self.interfaces[intid][0], self.interfaces[intid][2],
+                 tshigh, tslow, wirelen))
+
+    def read_block_spb(self, block, size):
+        """Simple Packet Block"""
+        # "it MUST be assumed that all the Simple Packet Blocks have
+        # been captured on the interface previously specified in the
+        # first Interface Description Block."
+        intid = 0
+        wirelen, = struct.unpack(self.endian + "I", block[:4])
+        caplen = min(wirelen, self.interfaces[intid][1])
+        return (block[4:4 + caplen][:size],
+                (self.interfaces[intid][0], self.interfaces[intid][2],
+                 None, None, wirelen))
+
+    def read_block_pkt(self, block, size):
+        """(Obsolete) Packet Block"""
+        intid, drops, tshigh, tslow, caplen, wirelen = struct.unpack(
+            self.endian + "HH4I",
+            block[:20],
+        )
         return (block[20:20 + caplen][:size],
-                (self.interfaces[intid][0], tshigh, tslow, wirelen))
+                (self.interfaces[intid][0], self.interfaces[intid][2],
+                 tshigh, tslow, wirelen))
 
 
 class PcapNgReader(RawPcapNgReader):
@@ -854,7 +900,7 @@ class PcapNgReader(RawPcapNgReader):
         rp = RawPcapNgReader.read_packet(self, size=size)
         if rp is None:
             return None
-        s, (linktype, tshigh, tslow, wirelen) = rp
+        s, (linktype, tsresol, tshigh, tslow, wirelen) = rp
         try:
             p = conf.l2types[linktype](s)
         except KeyboardInterrupt:
@@ -863,9 +909,8 @@ class PcapNgReader(RawPcapNgReader):
             if conf.debug_dissector:
                 raise
             p = conf.raw_layer(s)
-        # We should use if_tsresol when available (see
-        # RawPcapNgReader.read_block_idb)
-        p.time = float((tshigh << 32) + tslow) / 1000000
+        if tshigh is not None:
+            p.time = float((tshigh << 32) + tslow) / tsresol
         return p
     def read_all(self,count=-1):
         res = RawPcapNgReader.read_all(self, count)
diff --git a/test/regression.uts b/test/regression.uts
index 5a649a2a..83cb7a45 100644
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -4668,6 +4668,44 @@ assert pktpcapnanoread[0].time == pktpcapnano[0].time
 assert pktpcapnanoread[0].time == pktpcap[0].time + 0.000000001
 os.unlink(filename)
 
+= Check PcapNg with nanosecond precision using obsolete packet block
+* first packet from capture file icmp2.ntar -- https://wiki.wireshark.org/Development/PcapNg?action=AttachFile&do=view&target=icmp2.ntar
+pcapngfile = cStringIO.StringIO('\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xa8\x03\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x01\x00\x00\x00(\x00\x00\x00\x01\x00\x00\x00\xff\xff\x00\x00\r\x00\x01\x00\x04\x04K\x00\t\x00\x01\x00\tK=N\x00\x00\x00\x00(\x00\x00\x00\x02\x00\x00\x00n\x00\x00\x00\x00\x00\x00\x00e\x14\x00\x00)4\'ON\x00\x00\x00N\x00\x00\x00\x00\x12\xf0\x11h\xd6\x00\x13r\t{\xea\x08\x00E\x00\x00<\x90\xa1\x00\x00\x80\x01\x8e\xad\xc0\xa8M\x07\xc0\xa8M\x1a\x08\x00r[\x03\x00\xd8\x00abcdefghijklmnopqrstuvwabcdefghi\xeay$\xf6\x00\x00n\x00\x00\x00')
+pktpcapng = rdpcap(pcapngfile)
+assert len(pktpcapng) == 1
+pkt = pktpcapng[0]
+# weird, but wireshark agrees
+assert pkt.time == 22425.352221737
+assert isinstance(pkt, Ether)
+pkt = pkt.payload
+assert isinstance(pkt, IP)
+pkt = pkt.payload
+assert isinstance(pkt, ICMP)
+pkt = pkt.payload
+assert isinstance(pkt, Raw) and pkt.load == 'abcdefghijklmnopqrstuvwabcdefghi'
+pkt = pkt.payload
+assert isinstance(pkt, Padding) and pkt.load == '\xeay$\xf6'
+pkt = pkt.payload
+assert isinstance(pkt, NoPayload)
+
+= Check PcapNg using Simple Packet Block
+* previous file with the (obsolete) packet block replaced by a Simple Packet Block
+pcapngfile = cStringIO.StringIO('\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xa8\x03\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x01\x00\x00\x00(\x00\x00\x00\x01\x00\x00\x00\xff\xff\x00\x00\r\x00\x01\x00\x04\x04K\x00\t\x00\x01\x00\tK=N\x00\x00\x00\x00(\x00\x00\x00\x03\x00\x00\x00`\x00\x00\x00N\x00\x00\x00\x00\x12\xf0\x11h\xd6\x00\x13r\t{\xea\x08\x00E\x00\x00<\x90\xa1\x00\x00\x80\x01\x8e\xad\xc0\xa8M\x07\xc0\xa8M\x1a\x08\x00r[\x03\x00\xd8\x00abcdefghijklmnopqrstuvwabcdefghi\xeay$\xf6\x00\x00`\x00\x00\x00')
+pktpcapng = rdpcap(pcapngfile)
+assert len(pktpcapng) == 1
+pkt = pktpcapng[0]
+assert isinstance(pkt, Ether)
+pkt = pkt.payload
+assert isinstance(pkt, IP)
+pkt = pkt.payload
+assert isinstance(pkt, ICMP)
+pkt = pkt.payload
+assert isinstance(pkt, Raw) and pkt.load == 'abcdefghijklmnopqrstuvwabcdefghi'
+pkt = pkt.payload
+assert isinstance(pkt, Padding) and pkt.load == '\xeay$\xf6'
+pkt = pkt.payload
+assert isinstance(pkt, NoPayload)
+
 
 ############
 ############
-- 
GitLab