diff --git a/scapy/utils.py b/scapy/utils.py index 0699c955e56b3b8a6dff99a3f36f609921b7c83e..90cb1bb5f5cfd1fd03329fe56144db42c9aa92c3 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -527,44 +527,87 @@ endianness: "<" or ">", force endianness""" @conf.commands.register def rdpcap(filename, count=-1): + """Read a pcap or pcapng file and return a packet list + +count: read only <count> packets + + """ + try: + return _rdpcap(filename, count=count) + except Scapy_Exception: + pass + try: + return _rdpcapng(filename, count=count) + except Scapy_Exception: + raise Scapy_Exception("Not a valid pcap or pcapng file") + + +def _rdpcap(filename, count=-1): """Read a pcap file and return a packet list -count: read only <count> packets""" + +count: read only <count> packets + + """ with PcapReader(filename) as fdesc: return fdesc.read_all(count=count) +def _rdpcapng(filename, count=-1): + """Read a pcapng file and return a packet list + +count: read only <count> packets + + """ + with PcapNgReader(filename) as fdesc: + return fdesc.read_all(count=count) class RawPcapReader: """A stateful pcap reader. Each packet is returned as a string""" def __init__(self, filename): - self.filename = filename - try: - self.f = gzip.open(filename,"rb") - magic = self.f.read(4) - except IOError: - self.f = open(filename,"rb") - magic = self.f.read(4) + magic = self.open(filename) if magic == "\xa1\xb2\xc3\xd4": #big endian self.endian = ">" - elif magic == "\xd4\xc3\xb2\xa1": #little endian + elif magic == "\xd4\xc3\xb2\xa1": #little endian self.endian = "<" else: - raise Scapy_Exception("Not a pcap capture file (bad magic)") + try: + self.f.seek(-4, 1) + except: + pass + raise Scapy_Exception( + "Not a pcap capture file (bad magic: %r)" % magic + ) hdr = self.f.read(20) if len(hdr)<20: raise Scapy_Exception("Invalid pcap file (too short)") - vermaj,vermin,tz,sig,snaplen,linktype = struct.unpack(self.endian+"HHIIII",hdr) - + vermaj, vermin, tz, sig, snaplen, linktype = struct.unpack( + self.endian + "HHIIII", hdr + ) self.linktype = linktype - + def open(self, filename): + """Open (if necessary) filename""" + if isinstance(filename, basestring): + self.filename = filename + try: + self.f = gzip.open(filename,"rb") + return self.f.read(4) + except IOError: + self.f = open(filename,"rb") + return self.f.read(4) + else: + self.f = filename + self.filename = (filename.name + if hasattr(filename, "name") else + "No name") + return self.f.read(4) def __iter__(self): return self def next(self): - """impliment the iterator protocol on a set of packets in a pcap file""" + """implement the iterator protocol on a set of packets in a pcap file""" pkt = self.read_packet() if pkt == None: raise StopIteration @@ -654,7 +697,102 @@ class PcapReader(RawPcapReader): return plist.PacketList(res,name = os.path.basename(self.filename)) def recv(self, size=MTU): return self.read_packet(size=size) - + + +class RawPcapNgReader(RawPcapReader): + """A stateful pcapng reader. Each packet is returned as a + string. + + """ + + def __init__(self, filename): + # A list of (linktype, snaplen); will be populated by IDBs. + self.interfaces = [] + self.blocktypes = { + 1: self.read_block_idb, + 6: self.read_block_epb, + } + magic = self.open(filename) + if magic != "\x0a\x0d\x0d\x0a": # PcapNg: + try: + self.f.seek(-4, 1) + except: + pass + raise Scapy_Exception( + "Not a pcapng capture file (bad magic: %r)" % magic + ) + # see https://github.com/pcapng/pcapng + blocklen, magic = self.f.read(4), self.f.read(4) + if magic == "\x1a\x2b\x3c\x4d": + self.endian = ">" + elif magic == "\x4d\x3c\x2b\x1a": + self.endian = "<" + else: + raise Scapy_Exception("Not a pcapng capture file (bad magic)") + self.f.seek(0) + + def read_packet(self, size=MTU): + """Read blocks until it reaches either EOF or a packet, and + returns None or (packet, (linktype, sec, usec, wirelen)), + where packet is a string. + + """ + while True: + try: + blocktype, blocklen = struct.unpack(self.endian + "2I", + self.f.read(8)) + except struct.error: + return None + block = self.f.read(blocklen - 12) + try: + if (blocklen,) != struct.unpack(self.endian + 'I', + self.f.read(4)): + raise Scapy_Exception( + "Invalid pcapng block (bad blocklen)" + ) + except struct.error: + return None + res = self.blocktypes.get(blocktype, + lambda block, size: None)(block, size) + if res is not None: + return res + + def read_block_idb(self, block, _): + """Interface Description Block""" + self.interfaces.append(struct.unpack(self.endian + "HxxI", block[:8])) + + def read_block_epb(self, block, size): + """Enhanced Packet Block""" + intid, sec, usec, caplen, wirelen = struct.unpack(self.endian + "5I", + block[:20]) + return (block[20:20 + caplen][:size], + (self.interfaces[intid][0], sec, usec, wirelen)) + + +class PcapNgReader(RawPcapNgReader): + def __init__(self, filename): + RawPcapNgReader.__init__(self, filename) + def read_packet(self, size=MTU): + rp = RawPcapNgReader.read_packet(self, size=size) + if rp is None: + return None + s, (linktype, sec, usec, wirelen) = rp + try: + p = conf.l2types[linktype](s) + except KeyboardInterrupt: + raise + except: + if conf.debug_dissector: + raise + p = conf.raw_layer(s) + p.time = sec+0.000001*usec + return p + def read_all(self,count=-1): + res = RawPcapNgReader.read_all(self, count) + import plist + return plist.PacketList(res, name=os.path.basename(self.filename)) + def recv(self, size=MTU): + return self.read_packet() class RawPcapWriter: diff --git a/test/regression.uts b/test/regression.uts index fb329a78b57fea54ac00c214f83646373bffe155..db4a836da78ccbcd9695283a8066b3adb568f5ed 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -2306,8 +2306,8 @@ c = ICMPv6NIQueryIPv6(flags="C") l = ICMPv6NIQueryIPv6(flags="L") s = ICMPv6NIQueryIPv6(flags="S") g = ICMPv6NIQueryIPv6(flags="G") -all = ICMPv6NIQueryIPv6(flags="TALCLSG") -t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and all.flags == 63 +allflags = ICMPv6NIQueryIPv6(flags="TALCLSG") +t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and allflags.flags == 63 = ICMPv6NIQuery* - flags handling (Test 2) @@ -2317,8 +2317,8 @@ c = str(ICMPv6NIQueryNOOP(flags="C", nonce="A"*8))[6:8] l = str(ICMPv6NIQueryNOOP(flags="L", nonce="A"*8))[6:8] s = str(ICMPv6NIQueryNOOP(flags="S", nonce="A"*8))[6:8] g = str(ICMPv6NIQueryNOOP(flags="G", nonce="A"*8))[6:8] -all = str(ICMPv6NIQueryNOOP(flags="TALCLSG", nonce="A"*8))[6:8] -t == '\x00\x01' and a == '\x00\x02' and c == '\x00\x04' and l == '\x00\x08' and s == '\x00\x10' and g == '\x00\x20' and all == '\x00\x3F' +allflags = str(ICMPv6NIQueryNOOP(flags="TALCLSG", nonce="A"*8))[6:8] +t == '\x00\x01' and a == '\x00\x02' and c == '\x00\x04' and l == '\x00\x08' and s == '\x00\x10' and g == '\x00\x20' and allflags == '\x00\x3F' = ICMPv6NIReply* - flags handling (Test 1) @@ -2328,8 +2328,8 @@ c = ICMPv6NIReplyIPv6(flags="C") l = ICMPv6NIReplyIPv6(flags="L") s = ICMPv6NIReplyIPv6(flags="S") g = ICMPv6NIReplyIPv6(flags="G") -all = ICMPv6NIReplyIPv6(flags="TALCLSG") -t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and all.flags == 63 +allflags = ICMPv6NIReplyIPv6(flags="TALCLSG") +t.flags == 1 and a.flags == 2 and c.flags == 4 and l.flags == 8 and s.flags == 16 and g.flags == 32 and allflags.flags == 63 = ICMPv6NIReply* - flags handling (Test 2) @@ -2339,8 +2339,8 @@ c = str(ICMPv6NIReplyNOOP(flags="C", nonce="A"*8))[6:8] l = str(ICMPv6NIReplyNOOP(flags="L", nonce="A"*8))[6:8] s = str(ICMPv6NIReplyNOOP(flags="S", nonce="A"*8))[6:8] g = str(ICMPv6NIReplyNOOP(flags="G", nonce="A"*8))[6:8] -all = str(ICMPv6NIReplyNOOP(flags="TALCLSG", nonce="A"*8))[6:8] -t == '\x00\x01' and a == '\x00\x02' and c == '\x00\x04' and l == '\x00\x08' and s == '\x00\x10' and g == '\x00\x20' and all == '\x00\x3F' +allflags = str(ICMPv6NIReplyNOOP(flags="TALCLSG", nonce="A"*8))[6:8] +t == '\x00\x01' and a == '\x00\x02' and c == '\x00\x04' and l == '\x00\x08' and s == '\x00\x10' and g == '\x00\x20' and allflags == '\x00\x3F' = ICMPv6NIQuery* - Flags Default values @@ -4382,3 +4382,30 @@ str(NetflowHeader()/NetflowHeaderV5(count=1)/NetflowRecordV5(dst="192.168.0.1")) nf5 = NetflowHeader('\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00') nf5.version == 5 and nf5[NetflowHeaderV5].count == 2 and isinstance(nf5[NetflowRecordV5].payload, NetflowRecordV5) + + +############ +############ ++ pcap / pcapng format support + += Variable creations +import cStringIO +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') +pcapngfile = cStringIO.StringIO('\n\r\r\n\\\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00,\x00File created by merging: \nFile1: test.pcap \n\x04\x00\x08\x00mergecap\x00\x00\x00\x00\\\x00\x00\x00\x01\x00\x00\x00\\\x00\x00\x00e\x00\x00\x00\xff\xff\x00\x00\x02\x006\x00Unknown/not available in original file format(libpcap)\x00\x00\t\x00\x01\x00\x06\x00\x00\x00\x00\x00\x00\x00\\\x00\x00\x00\x06\x00\x00\x00H\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00/\xfc[\xcd(\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\x00H\x00\x00\x00\x06\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00\x1f\xff[\xcd\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<\x00\x00\x00\x06\x00\x00\x00<\x00\x00\x00\x00\x00\x00\x00\x8d*\x05\x00\xb9\x02\\\xcd\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<\x00\x00\x00') + += Read a pcap file +pktpcap = utils._rdpcap(pcapfile) + += Read a pcapng file +pktpcapng = utils._rdpcapng(pcapngfile) + += Check both packet lists are the same +assert list(pktpcap) == list(pktpcapng) + += Check packets from pcap file +assert all(IP in pkt for pkt in pktpcap) +assert all(any(proto in pkt for pkt in pktpcap) for proto in [ICMP, UDP, TCP]) + += Check packets from pcap file +assert all(IP in pkt for pkt in pktpcapng) +assert all(any(proto in pkt for pkt in pktpcapng) for proto in [ICMP, UDP, TCP])