gogopacket

gopacket: DecodeLayers on an IP-in-IP Packet


If I have data I'm putting through a DecodingLayerParser and some of that data could have IP Protocol 4 (IP-in-IP) packets included, how would I get it to capture BOTH IPv4 headers in the packet? I can only seem to get it to capture one of them.

type Decoder struct {
    eth   layers.Ethernet
    ip4   layers.IPv4
    ipip4 layers.IPv4
    ip6   layers.IPv6
    icmp4 layers.ICMPv4
    icmp6 layers.ICMPv6
    tcp   layers.TCP
    udp   layers.UDP
    //sip      layers.SIP
    //dns      layers.DNS
    //ntp      layers.NTP
    pay      gopacket.Payload
    parser   *gopacket.DecodingLayerParser
    types    []gopacket.LayerType
    unknowns map[string]uint
}

// NewDecoder allocates and initialises a new Decoder.
func NewDecoder() *Decoder {
    d := new(Decoder)
    d.parser = gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet,
        &d.eth, &d.ip4, &d.ipip4, &d.ip6, &d.icmp4, &d.icmp6, &d.tcp, &d.udp, &d.pay)
    //&d.sip, &d.dns, &d.ntp, &d.pay)
    d.types = make([]gopacket.LayerType, 10, 10)
    d.parser.IgnoreUnsupported = true
    d.unknowns = make(map[string]uint)
    return d
}

How may I modify this in order to do this when DecodeLayers is called from the parser? It only seems to store the second IPv4 header's information in ipip4.


Solution

  • Why it does not work

    The interface DecodingLayerContainer is designed to index DecodingLayer by its LayerType (See Decoder(LayerType) (DecodingLayer, bool)). Since ip4 and ipip4 have the same LayerType (layers.LayerTypeIPv4), the latter will overwrite the former in the container. And every time DecodingLayerParser gets a decoder from the container for layers.LayerTypeIPv4, it gets ipip4. So the state of ipip4 will be changed again and again.

    A workaround

    A workaround is to give ip4 and ipip4 different LayerType. And make ip4 choose ipip4 as its next decoder when the packet is an IP in IP packet. Here comes the demo:

    package main
    
    import (
        "fmt"
        "io"
    
        "github.com/google/gopacket"
        "github.com/google/gopacket/layers"
        "github.com/google/gopacket/pcap"
    )
    
    // 0-999 are reserved for the gopacket library. Numbers 1000-1999 should be
    // used for common application-specific types.
    var LayerTypeIPv4Inner = gopacket.RegisterLayerType(1000, gopacket.LayerTypeMetadata{Name: "IP_in_IP", Decoder: nil})
    
    // IPv4Outer is like layers.IPv4 but it recognizes IP in IP and will choose
    // its next decoder accordingly.
    type IPv4Outer struct {
        layers.IPv4
    }
    
    // NextLayerType overrides (*layers.IPv4).NextLayerType to recognize IP in IP.
    func (i *IPv4Outer) NextLayerType() gopacket.LayerType {
        if i.Flags&layers.IPv4MoreFragments != 0 || i.FragOffset != 0 {
            return gopacket.LayerTypeFragment
        }
        // This is an IP in IP packet.
        // See https://datatracker.ietf.org/doc/html/rfc2003#section-3.1
        if i.Protocol == 4 {
            return LayerTypeIPv4Inner
        }
        return i.Protocol.LayerType()
    }
    
    // IPv4Inner is like layers.IPv4 except that its type is LayerTypeIPv4Inner.
    // gopacket.DecodingLayerParser finds next decoder based on this type.
    type IPv4Inner struct {
        layers.IPv4
    }
    
    // CanDecode overrides (*layers.IPv4).CanDecode to choose a type other than
    // layers.LayerTypeIPv4.
    func (i *IPv4Inner) CanDecode() gopacket.LayerClass {
        return LayerTypeIPv4Inner
    }
    
    func main() {
        handle, err := pcap.OpenOffline("./IP_in_IP.cap")
        if err != nil {
            panic(err)
        }
    
        var (
            eth     layers.Ethernet
            ip4     IPv4Outer
            ipip4   IPv4Inner
            tcp     layers.TCP
            icmpv4  layers.ICMPv4
            payload gopacket.Payload
        )
        parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, &eth, &ip4, &ipip4, &tcp, &icmpv4, &payload)
        decodedLayers := make([]gopacket.LayerType, 0, 10)
        for {
            data, _, err := handle.ZeroCopyReadPacketData()
            if err == io.EOF {
                fmt.Println("done")
                return
            }
    
            if err != nil {
                panic(err)
            }
    
            err = parser.DecodeLayers(data, &decodedLayers)
            if err != nil {
                panic(err)
            }
    
            for _, typ := range decodedLayers {
                if typ == layers.LayerTypeIPv4 {
                    fmt.Printf("Ipv4: %s => %s\n", ip4.SrcIP, ip4.DstIP)
                } else if typ == LayerTypeIPv4Inner {
                    fmt.Printf("IP in IP: %s => %s\n", ipip4.SrcIP, ipip4.DstIP)
                }
            }
    
            fmt.Printf("%v\n\n", decodedLayers)
        }
    }
    

    I have tested with https://packetlife.net/media/captures/IP_in_IP.cap and test_ethernet.pcap in gopacket. The output looks like this:

    Ipv4: 10.0.0.1 => 10.0.0.2
    IP in IP: 1.1.1.1 => 2.2.2.2
    [Ethernet IPv4 IP_in_IP ICMPv4 Payload]
    
    Ipv4: 10.0.0.2 => 10.0.0.1
    IP in IP: 2.2.2.2 => 1.1.1.1
    [Ethernet IPv4 IP_in_IP ICMPv4 Payload]
    
    Ipv4: 10.1.1.2 => 10.1.1.1
    [Ethernet IPv4 TCP]
    
    Ipv4: 10.1.1.1 => 10.1.1.2
    [Ethernet IPv4 TCP]
    

    Notes

    1. DecodingLayerParser is faster, but it is also more rigid. while PacketSource is slower, it will handle any known type of packet safely and easily. (I'm sure you already know this).
    2. This is only a workaround. I'm not sure if this is a best practice.
    3. The purpose of the demo is to illustrate an idea to workaround such kind of issue. It does not work for other cases that a packet has multiple IPv4 headers. For example, it can not parse this file correctly: https://packetlife.net/media/captures/GRE.cap. We know that DecodingLayerParser is rigid and we have to handle other corner cases ourself.