I've found reassembly module exists strange behavior, which seems to exhibit different behavior compared to the tcpassembly module.
According to issue, reassembly is intended as the successor to tcpassembly. However, upon testing, I found that reassembly behaves oddly, whereas tcpassembly functions as expected.
Here are two minimal code snippets to reproduce the issue:
For tcpassembly:
package main
import (
"log"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/tcpassembly"
)
// tcpassembly.StreamFactory
type streamFactory struct{}
// tcpassembly.Stream
type tcpStream struct{}
func (factory *streamFactory) New(netFlow gopacket.Flow, tcpFlow gopacket.Flow) tcpassembly.Stream {
log.Printf("New stream found: %v:%v->%v:%v\n", netFlow.Src(), tcpFlow.Src(), netFlow.Dst(), tcpFlow.Dst())
return &tcpStream{}
}
func (stream *tcpStream) Reassembled(reassemblies []tcpassembly.Reassembly) {}
func (stream *tcpStream) ReassemblyComplete() {}
func main() {
// Open a pcap file
handle, err := pcap.OpenOffline("./test.pcap")
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// Create a pool of TCP streams
pool := tcpassembly.NewStreamPool(&streamFactory{})
assembler := tcpassembly.NewAssembler(pool)
// Loop through packets in the file
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
assembler.AssembleWithTimestamp(packet.NetworkLayer().NetworkFlow(), tcp, packet.Metadata().Timestamp)
}
}
// Flush remaining packets
assembler.FlushAll()
}
Output:
2024/05/26 16:11:34 New stream found: 10.0.2.15:22->10.0.2.2:51734
2024/05/26 16:11:34 New stream found: 10.0.2.15:22->10.0.2.2:50112
2024/05/26 16:11:34 New stream found: 10.0.2.2:50112->10.0.2.15:22
2024/05/26 16:11:34 New stream found: 10.0.2.2:51734->10.0.2.15:22
2024/05/26 16:11:34 New stream found: 10.0.2.15:36888->104.131.8.184:443
2024/05/26 16:11:34 New stream found: 104.131.8.184:443->10.0.2.15:36888
For reassembly:
package main
import (
"log"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/reassembly"
)
// reassembly.StreamFactory
type streamFactory struct{}
// reassembly.Stream
type tcpStream struct{}
// Assemble context
type AssemblerContext struct {
CaptureInfo gopacket.CaptureInfo
}
func (c *AssemblerContext) GetCaptureInfo() gopacket.CaptureInfo {
return c.CaptureInfo
}
func (factory *streamFactory) New(netFlow, tcpFlow gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
log.Printf("New stream found: %v:%v->%v:%v SYN: %v, ACK: %v, FIN: %v, RST: %v, Seq: %v, Ack: %v\n",
netFlow.Src(), tcpFlow.Src(), netFlow.Dst(), tcpFlow.Dst(),
tcp.SYN, tcp.ACK, tcp.FIN, tcp.RST, tcp.Seq, tcp.Ack)
return &tcpStream{}
}
func (stream *tcpStream) Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool {
return true
}
func (stream *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext) {
// Handle reassembled segments
}
func (stream *tcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool {
// Complete handling for the stream
return true
}
func main() {
// Open a pcap file
handle, err := pcap.OpenOffline("./test.pcap")
if err != nil {
log.Fatal(err)
}
defer handle.Close()
// Create a pool of TCP streams
pool := reassembly.NewStreamPool(&streamFactory{})
assembler := reassembly.NewAssembler(pool)
// Loop through packets in the file
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
assembler.AssembleWithContext(packet.NetworkLayer().NetworkFlow(), tcp, &AssemblerContext{
CaptureInfo: packet.Metadata().CaptureInfo,
})
}
}
// Flush remaining packets
assembler.FlushAll()
}
Output
2024/05/26 16:21:52 New stream found: 10.0.2.15:22->10.0.2.2:51734 SYN: false, ACK: true, FIN: false, RST: false, Seq: 1760721761, Ack: 332436703
2024/05/26 16:21:52 New stream found: 10.0.2.15:22->10.0.2.2:50112 SYN: false, ACK: true, FIN: false, RST: false, Seq: 2397881296, Ack: 7564907
2024/05/26 16:21:52 New stream found: 10.0.2.15:36888->104.131.8.184:443 SYN: true, ACK: false, FIN: false, RST: false, Seq: 956211534, Ack: 0
2024/05/26 16:21:52 New stream found: 10.0.2.15:36888->104.131.8.184:443 SYN: false, ACK: true, FIN: false, RST: false, Seq: 956212383, Ack: 832772637
In the test pcap, reassembly seems to call the New function multiple times for the same stream, which is unexpected. Also, the New function should be triggered by the first packet of a stream, but it being called with a sequence number 956212383 that is not the first in the stream.
test.pcap.zip