I am trying to implement my own decoding layer ontop of TCP, so far it only works when I create a packet without any Eth/IP/TCP header and set its layer to my custom layer manually. The data of the custom protocol is inside an ordinary TCP payload.
How do I decode only the payload of the TCP layer as another layer?
package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
)
var (
pcapFile string = "capt.pcap"
handle *pcap.Handle
err error
)
type CustomLayer struct {
SomeByte byte
AnotherByte byte
restOfData []byte
}
var CustomLayerType = gopacket.RegisterLayerType(
2001,
gopacket.LayerTypeMetadata{
"CustomLayerType",
gopacket.DecodeFunc(decodeCustomLayer),
},
)
func (l CustomLayer) LayerType() gopacket.LayerType {
return CustomLayerType
}
func (l CustomLayer) LayerContents() []byte {
return []byte{l.SomeByte, l.AnotherByte}
}
func (l CustomLayer) LayerPayload() []byte {
return l.restOfData
}
func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
// nil means this is the last layer. No more decoding
return nil
}
func main() {
handle, err = pcap.OpenOffline(pcapFile)
if err != nil {
log.Fatal(err)
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("TCP layer detected.")
tcp, _ := tcpLayer.(*layers.TCP)
fmt.Println("Sequence number: ", tcp.Seq)
customLayer := packet.Layer(CustomLayerType)
if customLayer != nil { // always nil
customLayerContent, _ := customLayer.(*CustomLayer)
// Now we can access the elements of the custom struct
fmt.Println("Payload: ", customLayerContent.LayerPayload())
fmt.Println("SomeByte element:", customLayerContent.SomeByte)
fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)
}
}
fmt.Println()
}
}
Most of the code is from this great post by devdungeon.
As no one responded I am going to answer it myself now.
Basically we have 3 options to handle this:
Create an extended TCP layer that handles our additional bytes and override default one by setting layers.LinkTypeMetadata[layers.LinkTypeTCP]
to our extended version. Have a look at this example.
Create a new packet from the TCP payload using gopacket.NewPacket
setting firstLayerDecoder
to CustomLayerType
and decode it normally.
As you mostly don't need an actual layer but instead a filled CustomLayer struct simply write a DecodeBytesToCustomStruct
function where you pass TCP payload. This way we can even return multiple structs from one packets payload which wouldn't be possible otherwise.
Omit all CustomLayer code from above.
type CustomStruct struct {
SomeByte byte
AnotherByte byte
restOfData []byte
}
func (customStruct *CustomStruct) DecodeStructFromBytes(data []byte) error {
customStruct.SomeByte = data[0]
customStruct.AnotherByte = data[1]
customStruct.restOfData = data[2:]
return nil
}
In your main.go
for packet := range packetSource.Packets() {
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
if tcp.Payload != nil && len(tcpLayer.Payload) > 0 {
customStruct := CustomStruct{}
customStruct.DecodeStructFromBytes(tcp.Payload)
fmt.Println("SomeByte element:", customStruct.SomeByte)
}
}
}
tcp.Payload
is the same as packet.ApplicationLayer().Payload()