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
.
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 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, ð, &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]
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).DecodingLayerParser
is rigid and we have to handle other corner cases ourself.