pythonpcappacket-capturevlandpkt

How to use dpkt with 802.1Q and SLL?


I am working on a PCAP in python and using dpkt to read it. Data in PCAP file is Linux Cooked Capture, SLL for friends. This is an example packet as seen in Wireshark:

Frame 3: 578 bytes on wire (4624 bits), 578 bytes captured (4624 bits)
Linux cooked capture
    Packet type: Unicast to another host (3)
    Link-layer address type: 1
    Link-layer address length: 6
    Source: VMware_ZZ:ZZ:ZZ (ZZ:ZZ:ZZ:ZZ:ZZ:ZZ)
    Unused: 0000
    Protocol: IPv4 (0x0800)
Internet Protocol Version 4, Src: XX.X.XX.XX, Dst: YYY.YY.YYY.YYY
Transmission Control Protocol, Src Port: 65382, Dst Port: 443, Seq: 1, Ack: 1, Len: 522
Transport Layer Security

This is the code I use to get to TCP data:

import dpkt

pcap_path = 'example-packet.pcap'
with open(pcap_path, 'rb') as fp:
    try:
        capture = dpkt.pcap.Reader(fp)
    except ValueError as e:
        raise Exception("Wrong file: %s" % e)

    for timestamp, buf in capture:
        eth = dpkt.sll.SLL(buf)
        print("Ethernet: ", eth)
        ip = eth.data
        print("IP: ", ip)
        tcp = ip.data         # <-- This is line 15, for error reference
        print("TCP: ", tcp)

and this is that code output, which is byte-coded, but that's fine as it is, since rest of the program doesn't care and I don't need to have it human-readable:

Ethernet: b'\x00\x03\x00\x01\x00\x06\x00PV\x9auT\x00\x00\x08\x00E\x00\x05\x8cT3@\x00...
IP: b'E\x00\x05\x8cT3@\x00\x80\x06O\x1f\xac\x1f\xaed\xc6\x8f1\x06\x01\xbb%\x7f\xccz...
TCP: b'\x01\xbb%\x7f\xccz\xdf\x9d\xe5\xbe\xb98\x80\x10\x01\xfd\x047\x00\x00\x01\x01...

Now, the issue. I move to another PCAP, still a Linux Cooked Capture, but this one has IP over 802.1Q VLAN. So here we have the problem packet:

Frame 11: 577 bytes on wire (4616 bits), 577 bytes captured (4616 bits)
Linux cooked capture
    Packet type: Unicast to another host (3)
    Link-layer address type: 1
    Link-layer address length: 6
    Source: Cisco_ZZ:ZZ:ZZ (ZZ:ZZ:ZZ:ZZ:ZZ:ZZ)
    Unused: 0000
    Protocol: 802.1Q Virtual LAN (0x8100)
802.1Q Virtual LAN, PRI: 0, DEI: 0, ID: 1328
    000. .... .... .... = Priority: Best Effort (default) (0)
    ...0 .... .... .... = DEI: Ineligible
    .... 0101 0011 0000 = ID: 1328
    Type: IPv4 (0x0800)
Internet Protocol Version 4, Src: XXX.XX.XXX.XX, Dst: YY.YYY.YYY.YY
Transmission Control Protocol, Src Port: 54545, Dst Port: 443, Seq: 1, Ack: 1, Len: 517
Transport Layer Security

but if I run the same code, this is the output I get:

Traceback (most recent call last):
  File "myproject.py", line 15, in <module>
    tcp = ip.data
AttributeError: 'bytes' object has no attribute 'data'
Ethernet: b'\x00\x03\x00\x01\x00\x06\x00PV\xa7(\x93\x00\x00\x81\x00\x050\x08\x00E\x00\x00...
IP: b'\x050\x08\x00E\x00\x00\xb2\xba\xd6@\x004\x06y\xf7_\xf9H \xac\x15\xbdI...

Now, I suppose the issue lies in those 4 byte 802.1Q add to the packet: eth.data is read just as bytes since no IP Header is recognized, so when I go and do ip.data there is, as said, no attribute 'data'. But I do not know how to circumvent this issue. Was this another capture, I know dpkt has a dpkt.ethernet.VLANTag8021Q class, but this does not work with Linux Cooked Capture as far as I have tried. I can consider moving to a library other than dpkt but I would really prefer not to do so, because this is part of a work project with quite a strict deadline.

So, how do I access TCP data when they are over IP, over 802.1Q, "over" SLL with dpkt?


Solution

  • I'm almost ashamed I didn't think this before. Since eth.data is seen as bytes because of the vlan tag it is sliceable.Therefore:

    for timestamp, buf in capture:
        eth = dpkt.sll.SLL(buf)
        print("Ethernet: ", eth)
        ip = eth.data
        print("IP: ", ip)
        tcp = ip.data
        print("TCP: ", tcp)
    

    became this:

    for timestamp, buf in capture:
        eth = dpkt.sll.SLL(buf)
        print("Ethernet: ", eth)
    
        if not isinstance(eth.data, dpkt.ip.IP):
            vlan_tag = dpkt.ethernet.VLANtag8021Q(eth.data[:4])
            ip = dpkt.ip.IP(eth.data[4:])
        else:
            ip = eth.data
    
        print("IP: ", ip)
        tcp = ip.data
        print("TCP: ", tcp)
    

    And that's all.

    Please notice for further reference that this work for me because I already know my whole capture is composed of IP packets only. If you have other protocols in your capture this will have a different behavior and you will have to modify the if-else statement so that you check those 4 byte looking for a IP Header or a VLAN Tag. I could actually do it that way anyway since it is more correct, but that's beyond the scope of this question.