mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
lazy packet parser (#870)
benchmarks of packet parser with `-cpu=1` packet (IPv6 with 2 extension header) master: ``` 337463 3300 ns/op 1624 B/op 24 allocs/op ``` current: ``` 2014885 576 ns/op 384 B/op 3 allocs/op ```
This commit is contained in:
+87
-107
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -14,10 +16,11 @@ import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Handler is a function that is used to handle packets
|
||||
type Handler func(gopacket.Packet)
|
||||
type Handler func(*Packet)
|
||||
|
||||
// PcapOptions options that can be set on a pcap capture handle,
|
||||
// these options take effect on inactive pcap handles
|
||||
@@ -31,19 +34,13 @@ type PcapOptions struct {
|
||||
Snaplen bool `json:"input-raw-override-snaplen"`
|
||||
}
|
||||
|
||||
// NetInterface represents network interface
|
||||
type NetInterface struct {
|
||||
net.Interface
|
||||
IPs []string
|
||||
}
|
||||
|
||||
// Listener handle traffic capture, this is its representation.
|
||||
type Listener struct {
|
||||
sync.Mutex
|
||||
Transport string // transport layer default to tcp
|
||||
Activate func() error // function is used to activate the engine. it must be called before reading packets
|
||||
Handles map[string]gopacket.PacketDataSource
|
||||
Interfaces []NetInterface
|
||||
Interfaces []net.Interface
|
||||
loopIndex int
|
||||
Reading chan bool // this channel is closed when the listener has started reading packets
|
||||
PcapOptions
|
||||
@@ -53,8 +50,8 @@ type Listener struct {
|
||||
|
||||
host string // pcap file name or interface (name, hardware addr, index or ip address)
|
||||
|
||||
quit chan bool
|
||||
packets chan gopacket.Packet
|
||||
closeDone chan struct{}
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// EngineType ...
|
||||
@@ -110,9 +107,9 @@ func NewListener(host string, port uint16, transport string, engine EngineType,
|
||||
}
|
||||
l.Handles = make(map[string]gopacket.PacketDataSource)
|
||||
l.trackResponse = trackResponse
|
||||
l.packets = make(chan gopacket.Packet, 1000)
|
||||
l.quit = make(chan bool, 1)
|
||||
l.Reading = make(chan bool, 1)
|
||||
l.closeDone = make(chan struct{})
|
||||
l.quit = make(chan struct{})
|
||||
l.Reading = make(chan bool)
|
||||
switch engine {
|
||||
default:
|
||||
l.Engine = EnginePcap
|
||||
@@ -139,30 +136,19 @@ func (l *Listener) SetPcapOptions(opts PcapOptions) {
|
||||
}
|
||||
|
||||
// Listen listens for packets from the handles, and call handler on every packet received
|
||||
// until the context done signal is sent or EOF on handles.
|
||||
// this function should be called after activating pcap handles
|
||||
// until the context done signal is sent or there is unrecoverable error on all handles.
|
||||
// this function must be called after activating pcap handles
|
||||
func (l *Listener) Listen(ctx context.Context, handler Handler) (err error) {
|
||||
l.read()
|
||||
l.read(handler)
|
||||
done := ctx.Done()
|
||||
var p gopacket.Packet
|
||||
var ok bool
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
l.quit <- true
|
||||
close(l.quit)
|
||||
err = ctx.Err()
|
||||
done = nil
|
||||
case p, ok = <-l.packets:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
handler(p)
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
close(l.quit) // signal close on all handles
|
||||
<-l.closeDone // wait all handles to be closed
|
||||
err = ctx.Err()
|
||||
case <-l.closeDone: // all handles closed voluntarily
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ListenBackground is like listen but can run concurrently and signal error through channel
|
||||
@@ -179,7 +165,7 @@ func (l *Listener) ListenBackground(ctx context.Context, handler Handler) chan e
|
||||
|
||||
// Filter returns automatic filter applied by goreplay
|
||||
// to a pcap handle of a specific interface
|
||||
func (l *Listener) Filter(ifi NetInterface) (filter string) {
|
||||
func (l *Listener) Filter(ifi net.Interface) (filter string) {
|
||||
// https://www.tcpdump.org/manpages/pcap-filter.7.html
|
||||
|
||||
port := fmt.Sprintf("portrange 0-%d", 1<<16-1)
|
||||
@@ -201,7 +187,7 @@ func (l *Listener) Filter(ifi NetInterface) (filter string) {
|
||||
// PcapDumpHandler returns a handler to write packet data in PCAP
|
||||
// format, See http://wiki.wireshark.org/Development/LibpcapFileFormathandler.
|
||||
// if link layer is invalid Ethernet is assumed
|
||||
func PcapDumpHandler(file *os.File, link layers.LinkType, debugger func(int, ...interface{})) (handler func(packet gopacket.Packet), err error) {
|
||||
func PcapDumpHandler(file *os.File, link layers.LinkType) (handler func(packet *Packet) error, err error) {
|
||||
if link.String() == "" {
|
||||
link = layers.LinkTypeEthernet
|
||||
}
|
||||
@@ -210,25 +196,20 @@ func PcapDumpHandler(file *os.File, link layers.LinkType, debugger func(int, ...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(packet gopacket.Packet) {
|
||||
err = w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
|
||||
if err != nil && debugger != nil {
|
||||
go debugger(3, err)
|
||||
}
|
||||
return func(packet *Packet) error {
|
||||
return w.WritePacket(*packet.Info, packet.Data)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PcapHandle returns new pcap Handle from dev on success.
|
||||
// this function should be called after setting all necessary options for this listener
|
||||
func (l *Listener) PcapHandle(ifi NetInterface) (handle *pcap.Handle, err error) {
|
||||
func (l *Listener) PcapHandle(ifi net.Interface) (handle *pcap.Handle, err error) {
|
||||
var inactive *pcap.InactiveHandle
|
||||
inactive, err = pcap.NewInactiveHandle(ifi.Name)
|
||||
if inactive != nil && err != nil {
|
||||
defer inactive.CleanUp()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inactive handle error: %q, interface: %q", err, ifi.Name)
|
||||
}
|
||||
defer inactive.CleanUp()
|
||||
if l.TimestampType != "" {
|
||||
var ts pcap.TimestampSource
|
||||
ts, err = pcap.TimestampSourceFromString(l.TimestampType)
|
||||
@@ -290,8 +271,8 @@ func (l *Listener) PcapHandle(ifi NetInterface) (handle *pcap.Handle, err error)
|
||||
}
|
||||
|
||||
// SocketHandle returns new unix ethernet handle associated with this listener settings
|
||||
func (l *Listener) SocketHandle(ifi NetInterface) (handle Socket, err error) {
|
||||
handle, err = NewSocket(ifi.Interface)
|
||||
func (l *Listener) SocketHandle(ifi net.Interface) (handle Socket, err error) {
|
||||
handle, err = NewSocket(ifi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sock raw error: %q, interface: %q", err, ifi.Name)
|
||||
}
|
||||
@@ -313,35 +294,54 @@ func (l *Listener) SocketHandle(ifi NetInterface) (handle Socket, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Listener) read() {
|
||||
func (l *Listener) read(handler Handler) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
for key, handle := range l.Handles {
|
||||
var source *gopacket.PacketSource
|
||||
linkType := layers.LinkTypeEthernet
|
||||
if _, ok := handle.(*pcap.Handle); ok {
|
||||
linkType = handle.(*pcap.Handle).LinkType()
|
||||
}
|
||||
source = gopacket.NewPacketSource(handle, linkType)
|
||||
source.Lazy = true
|
||||
source.NoCopy = true
|
||||
ch := source.Packets()
|
||||
go func(key string) {
|
||||
go func(key string, hndl gopacket.PacketDataSource) {
|
||||
defer l.closeHandles(key)
|
||||
linkSize := 14
|
||||
linkType := int(layers.LinkTypeEthernet)
|
||||
if _, ok := hndl.(*pcap.Handle); ok {
|
||||
linkType = int(hndl.(*pcap.Handle).LinkType())
|
||||
linkSize, ok = pcapLinkTypeLength(linkType)
|
||||
if !ok {
|
||||
if os.Getenv("GORDEBUG") != "0" {
|
||||
log.Printf("can not identify link type of an interface '%s'\n", key)
|
||||
}
|
||||
return // can't find the linktype size
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-l.quit:
|
||||
return
|
||||
case p, ok := <-ch:
|
||||
if !ok {
|
||||
default:
|
||||
data, ci, err := hndl.ReadPacketData()
|
||||
if err == nil {
|
||||
handler(NewPacket(data, linkType, linkSize, &ci))
|
||||
continue
|
||||
}
|
||||
if enext, ok := err.(pcap.NextError); ok && enext == pcap.NextErrorTimeoutExpired {
|
||||
continue
|
||||
}
|
||||
if eno, ok := err.(unix.Errno); ok && eno.Temporary() {
|
||||
continue
|
||||
}
|
||||
if enet, ok := err.(*net.OpError); ok && (enet.Temporary() || enet.Timeout()) {
|
||||
continue
|
||||
}
|
||||
if err == io.EOF || err == io.ErrClosedPipe {
|
||||
return
|
||||
}
|
||||
l.packets <- p
|
||||
if os.Getenv("GORDEBUG") != "0" {
|
||||
log.Printf("stopped reading from %s interface with error %s\n", key, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}(key)
|
||||
}(key, handle)
|
||||
}
|
||||
l.Reading <- true
|
||||
close(l.Reading)
|
||||
}
|
||||
|
||||
@@ -356,7 +356,7 @@ func (l *Listener) closeHandles(key string) {
|
||||
}
|
||||
delete(l.Handles, key)
|
||||
if len(l.Handles) == 0 {
|
||||
close(l.packets)
|
||||
close(l.closeDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,7 +394,10 @@ func (l *Listener) activateRawSocket() error {
|
||||
}
|
||||
l.Handles[ifi.Name] = handle
|
||||
}
|
||||
return e
|
||||
if len(l.Handles) == 0 {
|
||||
return fmt.Errorf("raw socket handles error:%s", msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Listener) activatePcapFile() (err error) {
|
||||
@@ -410,7 +413,7 @@ func (l *Listener) activatePcapFile() (err error) {
|
||||
} else {
|
||||
addr := l.host
|
||||
l.host = ""
|
||||
l.BPFFilter = l.Filter(NetInterface{})
|
||||
l.BPFFilter = l.Filter(net.Interface{})
|
||||
l.host = addr
|
||||
}
|
||||
if e = handle.SetBPFFilter(l.BPFFilter); e != nil {
|
||||
@@ -422,59 +425,36 @@ func (l *Listener) activatePcapFile() (err error) {
|
||||
}
|
||||
|
||||
func (l *Listener) setInterfaces() (err error) {
|
||||
var Ifis []NetInterface
|
||||
var ifis []net.Interface
|
||||
ifis, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(ifis); i++ {
|
||||
for i := range ifis {
|
||||
if ifis[i].Flags&net.FlagLoopback != 0 {
|
||||
l.loopIndex = ifis[i].Index
|
||||
}
|
||||
if ifis[i].Flags&net.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
var addrs []net.Addr
|
||||
addrs, err = ifis[i].Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
ifi := NetInterface{}
|
||||
ifi.Interface = ifis[i]
|
||||
ifi.IPs = make([]string, len(addrs))
|
||||
for j, addr := range addrs {
|
||||
ifi.IPs[j] = cutMask(addr)
|
||||
}
|
||||
Ifis = append(Ifis, ifi)
|
||||
}
|
||||
|
||||
if listenAll(l.host) {
|
||||
l.Interfaces = Ifis
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for _, ifi := range Ifis {
|
||||
if isDevice(l.host, ifi) {
|
||||
found = true
|
||||
}
|
||||
for _, ip := range ifi.IPs {
|
||||
if ip == l.host {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
l.Interfaces = []NetInterface{ifi}
|
||||
if isDevice(l.host, ifis[i]) {
|
||||
l.Interfaces = []net.Interface{ifis[i]}
|
||||
return
|
||||
}
|
||||
addrs, e := ifis[i].Addrs()
|
||||
if e != nil {
|
||||
// don't give up on a failure from a single interface
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if cutMask(addr) == l.host {
|
||||
l.Interfaces = []net.Interface{ifis[i]}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("can not find interface with addr, name or index %s", l.host)
|
||||
return err
|
||||
l.Interfaces = ifis
|
||||
return
|
||||
}
|
||||
|
||||
func cutMask(addr net.Addr) string {
|
||||
@@ -487,7 +467,7 @@ func cutMask(addr net.Addr) string {
|
||||
return mask
|
||||
}
|
||||
|
||||
func isDevice(addr string, ifi NetInterface) bool {
|
||||
func isDevice(addr string, ifi net.Interface) bool {
|
||||
return addr == ifi.Name || addr == fmt.Sprintf("%d", ifi.Index) || addr == ifi.HardwareAddr.String()
|
||||
}
|
||||
|
||||
|
||||
+16
-63
@@ -10,7 +10,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
)
|
||||
@@ -61,69 +60,23 @@ func TestBPFFilter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var decodeOpts = gopacket.DecodeOptions{Lazy: true, NoCopy: true}
|
||||
|
||||
func generateHeaders(seq uint32, length uint16) (headers [44]byte) {
|
||||
// set ethernet headers
|
||||
binary.BigEndian.PutUint32(headers[0:4], uint32(layers.ProtocolFamilyIPv4))
|
||||
|
||||
// set ip header
|
||||
ip := headers[4:]
|
||||
copy(ip[0:2], []byte{4<<4 | 5, 0x28<<2 | 0x00})
|
||||
binary.BigEndian.PutUint16(ip[2:4], length+54)
|
||||
ip[9] = uint8(layers.IPProtocolTCP)
|
||||
copy(ip[12:16], []byte{127, 0, 0, 1})
|
||||
copy(ip[16:], []byte{127, 0, 0, 1})
|
||||
|
||||
// set tcp header
|
||||
tcp := ip[20:]
|
||||
binary.BigEndian.PutUint16(tcp[0:2], 45678)
|
||||
binary.BigEndian.PutUint16(tcp[2:4], 8000)
|
||||
tcp[12] = 5 << 4
|
||||
return
|
||||
}
|
||||
|
||||
func randomPackets(start uint32, _len int, length uint16) []gopacket.Packet {
|
||||
var packets = make([]gopacket.Packet, _len)
|
||||
for i := start; i < start+uint32(_len); i++ {
|
||||
h := generateHeaders(i, length)
|
||||
d := make([]byte, int(length)+len(h))
|
||||
copy(d, h[0:])
|
||||
packet := gopacket.NewPacket(d, layers.LinkTypeLoop, decodeOpts)
|
||||
packets[i-start] = packet
|
||||
inf := packets[i-start].Metadata()
|
||||
_len := len(d)
|
||||
inf.CaptureInfo = gopacket.CaptureInfo{CaptureLength: _len, Length: _len, Timestamp: time.Now()}
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func TestPcapDump(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "pcap_file")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
waiter := make(chan bool, 1)
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop, func(level int, a ...interface{}) {
|
||||
if level != 3 {
|
||||
t.Errorf("expected debug level to be 3, got %d", level)
|
||||
}
|
||||
waiter <- true
|
||||
})
|
||||
packets := randomPackets(1, 5, 5)
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop)
|
||||
packets := Packets(1, 5, 5, 4)
|
||||
for i := 0; i < len(packets); i++ {
|
||||
if i == 1 {
|
||||
tcp := packets[i].Data()[4:][20:]
|
||||
// change dst port
|
||||
binary.BigEndian.PutUint16(tcp[2:], 8001)
|
||||
binary.BigEndian.PutUint16(packets[i].TransLayer[2:], 8001)
|
||||
}
|
||||
if i == 4 {
|
||||
inf := packets[i].Metadata()
|
||||
inf.CaptureLength = 40
|
||||
packets[i].Info.CaptureLength = 40
|
||||
}
|
||||
h(packets[i])
|
||||
}
|
||||
<-waiter
|
||||
name := f.Name()
|
||||
f.Close()
|
||||
testPcapDumpEngine(name, t)
|
||||
@@ -140,9 +93,9 @@ func testPcapDumpEngine(f string, t *testing.T) {
|
||||
pckts := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
err = l.Listen(ctx, func(packet gopacket.Packet) {
|
||||
if packet.Metadata().CaptureLength != 49 {
|
||||
t.Errorf("expected packet length to be %d, got %d", 49, packet.Metadata().CaptureLength)
|
||||
err = l.Listen(ctx, func(packet *Packet) {
|
||||
if packet.Info.CaptureLength != 57 {
|
||||
t.Errorf("expected packet length to be %d, got %d", 57, packet.Info.CaptureLength)
|
||||
}
|
||||
pckts++
|
||||
})
|
||||
@@ -208,8 +161,8 @@ func BenchmarkPcapDump(b *testing.B) {
|
||||
}
|
||||
now := time.Now()
|
||||
defer os.Remove(f.Name())
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop, nil)
|
||||
packets := randomPackets(1, b.N, 5)
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop)
|
||||
packets := Packets(1, b.N, 5, 4)
|
||||
for i := 0; i < len(packets); i++ {
|
||||
h(packets[i])
|
||||
}
|
||||
@@ -224,8 +177,8 @@ func BenchmarkPcapFile(b *testing.B) {
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop, nil)
|
||||
packets := randomPackets(1, b.N, 5)
|
||||
h, _ := PcapDumpHandler(f, layers.LinkTypeLoop)
|
||||
packets := Packets(1, b.N, 5, 4)
|
||||
for i := 0; i < len(packets); i++ {
|
||||
h(packets[i])
|
||||
}
|
||||
@@ -247,9 +200,9 @@ func BenchmarkPcapFile(b *testing.B) {
|
||||
pckts := 0
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if err = l.Listen(ctx, func(packet gopacket.Packet) {
|
||||
if packet.Metadata().CaptureLength != 49 {
|
||||
b.Errorf("expected packet length to be %d, got %d", 49, packet.Metadata().CaptureLength)
|
||||
if err = l.Listen(ctx, func(packet *Packet) {
|
||||
if packet.Info.CaptureLength != 49 {
|
||||
b.Errorf("expected packet length to be %d, got %d", 49, packet.Info.CaptureLength)
|
||||
}
|
||||
pckts++
|
||||
}); err != nil {
|
||||
@@ -268,8 +221,8 @@ func init() {
|
||||
}
|
||||
|
||||
func handler(n, counter *int32) Handler {
|
||||
return func(p gopacket.Packet) {
|
||||
nn := int32(len(p.Data()))
|
||||
return func(p *Packet) {
|
||||
nn := int32(len(p.Data))
|
||||
atomic.AddInt32(n, nn)
|
||||
atomic.AddInt32(counter, 1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
// Packet properties of a pcaket
|
||||
type Packet struct {
|
||||
Data []byte
|
||||
LinkLayer []byte
|
||||
NetLayer []byte
|
||||
NetOptsLen int // length of extension headers(IPV6) or options(IPV4)
|
||||
TransLayer []byte
|
||||
TransOptsLen int // length of tcp options
|
||||
Payload []byte
|
||||
|
||||
LinkType int
|
||||
Info *gopacket.CaptureInfo
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewPacket parses packet and stop at the first error encountered
|
||||
// pckt.Error will be nil if packet was parsed successfully
|
||||
func NewPacket(data []byte, lType, lTypeLen int, cp *gopacket.CaptureInfo) (p *Packet) {
|
||||
p = new(Packet)
|
||||
p.Info = cp
|
||||
p.Data = data
|
||||
if len(data) < lTypeLen {
|
||||
p.Err = ErrHdrLength("Link")
|
||||
return
|
||||
}
|
||||
p.LinkType = lType
|
||||
p.LinkLayer = data[:lTypeLen]
|
||||
if len(data) <= lTypeLen {
|
||||
p.Err = ErrHdrMissing("IPv4 or IPv6")
|
||||
return
|
||||
}
|
||||
data = data[lTypeLen:]
|
||||
var proto byte
|
||||
if data[0]>>4 == 4 {
|
||||
// IPv4 header
|
||||
if len(data) < 20 {
|
||||
p.Err = ErrHdrLength("IPv4")
|
||||
return
|
||||
}
|
||||
proto = data[9]
|
||||
ihl := int(data[0]&0x0F) * 4
|
||||
if ihl < 20 {
|
||||
p.Err = ErrHdrInvalid("IPv4's IHL")
|
||||
return
|
||||
}
|
||||
if len(data) < ihl {
|
||||
p.Err = ErrHdrLength("IPv4 opts")
|
||||
p.NetLayer = data[:20]
|
||||
return
|
||||
}
|
||||
p.NetOptsLen = ihl - 20
|
||||
p.NetLayer = data[:ihl]
|
||||
} else if data[0]>>4 == 6 {
|
||||
if len(data) < 40 {
|
||||
p.Err = ErrHdrLength("IPv6")
|
||||
return
|
||||
}
|
||||
proto = data[6]
|
||||
totalLen := 40
|
||||
for ipv6ExtensionHdr(proto) {
|
||||
hdr := len(data) - totalLen
|
||||
if hdr < 8 {
|
||||
p.Err = ErrHdrExpected("IPv6 opts")
|
||||
break
|
||||
}
|
||||
extLen := 8
|
||||
if proto != 44 {
|
||||
extLen = int(data[totalLen+1]+1) * 8
|
||||
}
|
||||
if hdr < extLen {
|
||||
p.Err = ErrHdrLength("IPv6 opts")
|
||||
break
|
||||
}
|
||||
p.NetOptsLen += extLen
|
||||
proto = data[totalLen]
|
||||
totalLen += extLen
|
||||
}
|
||||
p.NetLayer = data[:totalLen]
|
||||
if p.Err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
p.Err = ErrHdrExpected("IPv4 or IPv6")
|
||||
return
|
||||
}
|
||||
if proto != 6 {
|
||||
p.Err = ErrHdrExpected("TCP")
|
||||
return
|
||||
}
|
||||
if len(data) <= len(p.NetLayer) {
|
||||
p.Err = ErrHdrMissing("TCP")
|
||||
return
|
||||
}
|
||||
data = data[len(p.NetLayer):]
|
||||
// TCP header
|
||||
if len(data) < 20 {
|
||||
p.Err = ErrHdrLength("TCP")
|
||||
return
|
||||
}
|
||||
dOf := int(data[12]>>4) * 4
|
||||
if dOf < 20 {
|
||||
p.Err = ErrHdrInvalid("TCP's data offset")
|
||||
return
|
||||
}
|
||||
if len(data) < dOf {
|
||||
p.Err = ErrHdrLength("TCP opts")
|
||||
p.TransLayer = data[:20]
|
||||
return
|
||||
}
|
||||
p.TransLayer = data[:dOf]
|
||||
p.TransOptsLen = dOf - 20
|
||||
if len(data) > dOf {
|
||||
p.Payload = data[dOf:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ErrHdrLength returned on short header length
|
||||
type ErrHdrLength string
|
||||
|
||||
func (err ErrHdrLength) Error() string {
|
||||
return "short " + string(err) + " length"
|
||||
}
|
||||
|
||||
// ErrHdrMissing returned on missing header(s)
|
||||
type ErrHdrMissing string
|
||||
|
||||
func (err ErrHdrMissing) Error() string {
|
||||
return "missing " + string(err) + " header(s)"
|
||||
}
|
||||
|
||||
// ErrHdrExpected returned when header(s) are different from the one expected
|
||||
type ErrHdrExpected string
|
||||
|
||||
func (err ErrHdrExpected) Error() string {
|
||||
return "expected " + string(err) + " header(s)"
|
||||
}
|
||||
|
||||
// ErrHdrInvalid returned when header(s) are different from the one expected
|
||||
type ErrHdrInvalid string
|
||||
|
||||
func (err ErrHdrInvalid) Error() string {
|
||||
return "invalid " + string(err) + " value"
|
||||
}
|
||||
|
||||
func pcapLinkTypeLength(lType int) (int, bool) {
|
||||
switch layers.LinkType(lType) {
|
||||
case layers.LinkTypeEthernet:
|
||||
return 14, true
|
||||
case layers.LinkTypeNull, layers.LinkTypeLoop:
|
||||
return 4, true
|
||||
case layers.LinkTypeRaw, 12, 14:
|
||||
return 0, true
|
||||
case layers.LinkTypeIPv4, layers.LinkTypeIPv6:
|
||||
// (TODO:) look out for IP encapsulation?
|
||||
return 0, true
|
||||
case layers.LinkTypeLinuxSLL:
|
||||
return 16, true
|
||||
case layers.LinkTypeFDDI:
|
||||
return 13, true
|
||||
case 226 /*DLT_IPNET*/ :
|
||||
// https://www.tcpdump.org/linktypes/LINKTYPE_IPNET.html
|
||||
return 24, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/IPv6_packet#Extension_headers
|
||||
func ipv6ExtensionHdr(b byte) bool {
|
||||
// TODO: support all extension headers
|
||||
return b == 0 || b == 43 || b == 44
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
func generateHeader4(seq uint32, length uint16) []byte {
|
||||
hdr := make([]byte, 4+24+24, 4+24+24)
|
||||
binary.BigEndian.PutUint32(hdr, uint32(layers.ProtocolFamilyIPv4))
|
||||
|
||||
ip := hdr[4:]
|
||||
ip[0] = 4<<4 | 6
|
||||
binary.BigEndian.PutUint16(ip[2:4], length+24+24)
|
||||
ip[9] = uint8(layers.IPProtocolTCP)
|
||||
copy(ip[12:16], []byte{127, 0, 0, 1})
|
||||
copy(ip[16:], []byte{127, 0, 0, 1})
|
||||
|
||||
// set tcp header
|
||||
tcp := ip[24:]
|
||||
tcp[12] = 6 << 4
|
||||
binary.BigEndian.PutUint16(tcp, 5535)
|
||||
binary.BigEndian.PutUint16(tcp[2:], 8000)
|
||||
binary.BigEndian.PutUint32(tcp[4:], seq)
|
||||
return hdr
|
||||
}
|
||||
|
||||
func generateHeader6(seq uint32, length uint16) []byte {
|
||||
hdr := make([]byte, 4+40+32+24, 4+40+32+24)
|
||||
binary.BigEndian.PutUint32(hdr, uint32(layers.ProtocolFamilyIPv6Linux))
|
||||
|
||||
ip := hdr[4:]
|
||||
ip[0] = 6 << 4
|
||||
binary.BigEndian.PutUint16(ip[4:], length+32+24)
|
||||
var ipAddr [16]byte
|
||||
ipAddr[15] = 0x01
|
||||
copy(ip[8:], ip[:])
|
||||
copy(ip[24:], ip[:])
|
||||
copy(ip[40:], []byte{
|
||||
// net-layer (IPv6-Opts)
|
||||
0x2b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
uint8(layers.IPProtocolTCP), 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
|
||||
// set tcp header
|
||||
tcp := ip[40+32:]
|
||||
tcp[12] = 6 << 4
|
||||
binary.BigEndian.PutUint16(tcp, 5535)
|
||||
binary.BigEndian.PutUint16(tcp[2:], 8000)
|
||||
binary.BigEndian.PutUint32(tcp[4:], seq)
|
||||
return hdr
|
||||
}
|
||||
|
||||
func Packets(start uint32, _len int, length uint16, version byte) []*Packet {
|
||||
var packets = make([]*Packet, _len)
|
||||
for i := start; i < start+uint32(_len); i++ {
|
||||
var h []byte
|
||||
if version == 4 {
|
||||
h = generateHeader4(i, length)
|
||||
} else {
|
||||
h = generateHeader6(i, length)
|
||||
}
|
||||
d := append(h, make([]byte, int(length))...)
|
||||
ci := &gopacket.CaptureInfo{Length: len(d), CaptureLength: len(d), Timestamp: time.Now()}
|
||||
packets[i-start] = NewPacket(d, int(layers.LinkTypeLoop), 4, ci)
|
||||
}
|
||||
return packets
|
||||
}
|
||||
|
||||
func TestIPv4Packet(t *testing.T) {
|
||||
pckt := packet(append(generateHeader4(1024, 10), make([]byte, 10)...))
|
||||
if pckt.Err != nil {
|
||||
t.Error(pckt)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:2]).Err; !errors.Is(err, ErrHdrLength("Link")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("Link"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:20]).Err; !errors.Is(err, ErrHdrLength("IPv4")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("IPv4"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:27]).Err; !errors.Is(err, ErrHdrLength("IPv4 opts")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("IPv4 opts"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:40]).Err; !errors.Is(err, ErrHdrLength("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("TCP opts"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:50]).Err; !errors.Is(err, ErrHdrLength("TCP opts")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("TCP opts"), err)
|
||||
return
|
||||
}
|
||||
pckt.TransLayer[12] = 0x10
|
||||
if err := packet(pckt.Data[:50]).Err; !errors.Is(err, ErrHdrInvalid("TCP's data offset")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrInvalid("TCP's data offset"), err)
|
||||
return
|
||||
}
|
||||
pckt.TransLayer[12] = 0x60
|
||||
if err := packet(pckt.Data[:28]).Err; !errors.Is(err, ErrHdrMissing("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrMissing("TCP"), err)
|
||||
return
|
||||
}
|
||||
pckt.NetLayer[9] = 0x02
|
||||
if err := packet(pckt.Data).Err; !errors.Is(err, ErrHdrExpected("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrExpected("TCP"), err)
|
||||
return
|
||||
}
|
||||
pckt.NetLayer[9] = 0x06
|
||||
pckt.NetLayer[0] = 0x44
|
||||
if err := packet(pckt.Data).Err; !errors.Is(err, ErrHdrInvalid("IPv4's IHL")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrInvalid("IPv4's IHL"), err)
|
||||
return
|
||||
}
|
||||
pckt.NetLayer[0] = 0x56
|
||||
if err := packet(pckt.Data).Err; !errors.Is(err, ErrHdrExpected("IPv4 or IPv6")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrExpected("IPv4 or IPv6"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv6Packet(t *testing.T) {
|
||||
pckt := packet(append(generateHeader6(1024, 10), make([]byte, 10)...))
|
||||
if pckt.Err != nil {
|
||||
t.Error(pckt)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:4]).Err; !errors.Is(err, ErrHdrMissing("IPv4 or IPv6")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrMissing("IPv4 or IPv6"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:40]).Err; !errors.Is(err, ErrHdrLength("IPv6")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("IPv6"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:52]).Err; !errors.Is(err, ErrHdrLength("IPv6 opts")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("IPv6 opts"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:80]).Err; !errors.Is(err, ErrHdrLength("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("TCP opts"), err)
|
||||
return
|
||||
}
|
||||
if err := packet(pckt.Data[:98]).Err; !errors.Is(err, ErrHdrLength("TCP opts")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrLength("TCP opts"), err)
|
||||
return
|
||||
}
|
||||
pckt.TransLayer[12] = 0x10
|
||||
if err := packet(pckt.Data).Err; !errors.Is(err, ErrHdrInvalid("TCP's data offset")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrInvalid("TCP's data offset"), err)
|
||||
return
|
||||
}
|
||||
pckt.TransLayer[12] = 0x60
|
||||
if err := packet(pckt.Data[:76]).Err; !errors.Is(err, ErrHdrMissing("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrMissing("TCP"), err)
|
||||
return
|
||||
}
|
||||
pckt.NetLayer[56] = 0x02
|
||||
if err := packet(pckt.Data).Err; !errors.Is(err, ErrHdrExpected("TCP")) {
|
||||
t.Errorf("should fail with %q, got %q", ErrHdrExpected("TCP"), err)
|
||||
return
|
||||
}
|
||||
pckt.NetLayer[56] = 0x06
|
||||
}
|
||||
|
||||
func packet(data []byte) *Packet {
|
||||
return NewPacket(data, int(layers.LinkTypeLoop), 4, &gopacket.CaptureInfo{})
|
||||
}
|
||||
|
||||
func BenchmarkNewPacketIPv4(b *testing.B) {
|
||||
data := append(generateHeader4(1204, 10), make([]byte, 10)...)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := packet(data).Err; err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewPacketIPv6(b *testing.B) {
|
||||
data := append(generateHeader6(1024, 10), make([]byte, 10)...)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := packet(data).Err; err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -72,13 +72,14 @@ type RAWInput struct {
|
||||
listener *capture.Listener
|
||||
message chan *tcp.Message
|
||||
cancelListener context.CancelFunc
|
||||
closed bool
|
||||
}
|
||||
|
||||
// NewRAWInput constructor for RAWInput. Accepts raw input config as arguments.
|
||||
func NewRAWInput(address string, config RAWInputConfig) (i *RAWInput) {
|
||||
i = new(RAWInput)
|
||||
i.RAWInputConfig = config
|
||||
i.message = make(chan *tcp.Message, 1000)
|
||||
i.message = make(chan *tcp.Message, 10000)
|
||||
i.quit = make(chan bool)
|
||||
var host, _port string
|
||||
var err error
|
||||
@@ -157,12 +158,12 @@ func (i *RAWInput) listen(address string) {
|
||||
var ctx context.Context
|
||||
ctx, i.cancelListener = context.WithCancel(context.Background())
|
||||
errCh := i.listener.ListenBackground(ctx, pool.Handler)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
log.Fatal(err)
|
||||
case <-i.listener.Reading:
|
||||
Debug(1, i)
|
||||
}
|
||||
<-i.listener.Reading
|
||||
Debug(1, i)
|
||||
go func() {
|
||||
<-errCh // the listener closed voluntarily
|
||||
i.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
func (i *RAWInput) handler(m *tcp.Message) {
|
||||
@@ -185,8 +186,14 @@ func (i *RAWInput) GetStats() []tcp.Stats {
|
||||
|
||||
// Close closes the input raw listener
|
||||
func (i *RAWInput) Close() error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
if i.closed {
|
||||
return nil
|
||||
}
|
||||
i.cancelListener()
|
||||
close(i.quit)
|
||||
i.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -9,8 +9,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/buger/goreplay/capture"
|
||||
"github.com/buger/goreplay/size"
|
||||
"github.com/google/gopacket"
|
||||
)
|
||||
|
||||
// Stats every message carry its own stats object
|
||||
@@ -164,14 +164,11 @@ func NewMessagePool(maxSize size.Size, messageExpire time.Duration, debugger Deb
|
||||
}
|
||||
|
||||
// Handler returns packet handler
|
||||
func (pool *MessagePool) Handler(packet gopacket.Packet) {
|
||||
func (pool *MessagePool) Handler(packet *capture.Packet) {
|
||||
var in, out bool
|
||||
pckt, err := ParsePacket(packet)
|
||||
if err != nil {
|
||||
go pool.say(4, fmt.Sprintf("error decoding packet(%dBytes):%s\n", packet.Metadata().CaptureLength, err))
|
||||
return
|
||||
}
|
||||
if pckt == nil {
|
||||
go pool.say(4, fmt.Sprintf("error decoding packet(%dBytes):%s\n", packet.Info.CaptureLength, err))
|
||||
return
|
||||
}
|
||||
pool.Lock()
|
||||
|
||||
+36
-157
@@ -2,12 +2,12 @@ package tcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/buger/goreplay/capture"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -17,81 +17,51 @@ calllers must make sure that ParsePacket has'nt returned any error before callin
|
||||
function.
|
||||
*/
|
||||
type Packet struct {
|
||||
// Link layer
|
||||
gopacket.LinkLayer
|
||||
|
||||
// IP Header
|
||||
Version uint8 // Ip version
|
||||
SrcIP net.IP
|
||||
DstIP net.IP
|
||||
IHL uint8
|
||||
Length uint16
|
||||
|
||||
// TCP Segment Header
|
||||
*layers.TCP
|
||||
|
||||
// Data info
|
||||
Lost uint16
|
||||
Timestamp time.Time
|
||||
SrcIP, DstIP net.IP
|
||||
Version uint8
|
||||
SrcPort, DstPort uint16
|
||||
Ack, Seq uint32
|
||||
ACK, SYN, FIN, RST bool
|
||||
Lost uint32
|
||||
Timestamp time.Time
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// ParsePacket parse raw packets
|
||||
func ParsePacket(packet gopacket.Packet) (pckt *Packet, err error) {
|
||||
func ParsePacket(packet *capture.Packet) (pckt *Packet, err error) {
|
||||
// early check of error
|
||||
if packet == nil {
|
||||
return
|
||||
return nil, errors.New("empty packet")
|
||||
}
|
||||
defer func() {
|
||||
if packet.ErrorLayer() != nil {
|
||||
err = packet.ErrorLayer().Error()
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// initialization
|
||||
pckt = new(Packet)
|
||||
pckt.Timestamp = packet.Metadata().Timestamp
|
||||
if pckt.Timestamp.IsZero() {
|
||||
pckt.Timestamp = time.Now()
|
||||
if packet.Err != nil {
|
||||
return nil, packet.Err
|
||||
}
|
||||
|
||||
// parsing link layer
|
||||
pckt.LinkLayer = packet.LinkLayer()
|
||||
|
||||
// parsing network layer
|
||||
if net4, ok := packet.NetworkLayer().(*layers.IPv4); ok {
|
||||
var t Packet
|
||||
pckt = &t
|
||||
// TODO: check resolution
|
||||
pckt.Timestamp = packet.Info.Timestamp
|
||||
if (packet.NetLayer[0] >> 4) == 4 {
|
||||
// IPv4 header
|
||||
pckt.Version = 4
|
||||
pckt.SrcIP = net4.SrcIP
|
||||
pckt.DstIP = net4.DstIP
|
||||
pckt.IHL = net4.IHL * 4
|
||||
pckt.Length = net4.Length
|
||||
} else if net6, ok := packet.NetworkLayer().(*layers.IPv6); ok {
|
||||
pckt.SrcIP = packet.NetLayer[12:16]
|
||||
pckt.DstIP = packet.NetLayer[16:20]
|
||||
} else {
|
||||
// IPv6 header
|
||||
pckt.Version = 6
|
||||
pckt.SrcIP = net6.SrcIP
|
||||
pckt.DstIP = net6.DstIP
|
||||
pckt.IHL = 40
|
||||
pckt.Length = net6.Length
|
||||
} else {
|
||||
pckt = nil
|
||||
return
|
||||
pckt.SrcIP = packet.NetLayer[8:24]
|
||||
pckt.DstIP = packet.NetLayer[24:40]
|
||||
}
|
||||
|
||||
// parsing tcp header(transportation layer)
|
||||
if tcp, ok := packet.TransportLayer().(*layers.TCP); ok {
|
||||
pckt.TCP = tcp
|
||||
} else {
|
||||
pckt = nil
|
||||
return
|
||||
}
|
||||
pckt.DataOffset *= 4
|
||||
|
||||
// calculating lost data
|
||||
headerSize := int(uint32(pckt.DataOffset) + uint32(pckt.IHL))
|
||||
if pckt.Version == 6 {
|
||||
headerSize -= 40 // in ipv6 the length of payload doesn't include the IPheader size
|
||||
}
|
||||
pckt.Lost = pckt.Length - uint16(headerSize+len(pckt.Payload))
|
||||
|
||||
pckt.SrcPort = binary.BigEndian.Uint16(packet.TransLayer[0:2])
|
||||
pckt.DstPort = binary.BigEndian.Uint16(packet.TransLayer[2:4])
|
||||
pckt.Seq = binary.BigEndian.Uint32(packet.TransLayer[4:8])
|
||||
pckt.Ack = binary.BigEndian.Uint32(packet.TransLayer[8:12])
|
||||
pckt.FIN = packet.TransLayer[13]&0x01 != 0
|
||||
pckt.SYN = packet.TransLayer[13]&0x02 != 0
|
||||
pckt.RST = packet.TransLayer[13]&0x04 != 0
|
||||
pckt.ACK = packet.TransLayer[13]&0x10 != 0
|
||||
pckt.Lost = uint32(packet.Info.Length - packet.Info.CaptureLength)
|
||||
pckt.Payload = packet.Payload
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,94 +74,3 @@ func (pckt *Packet) Src() string {
|
||||
func (pckt *Packet) Dst() string {
|
||||
return fmt.Sprintf("%s:%d", pckt.DstIP, pckt.DstPort)
|
||||
}
|
||||
|
||||
// SYNOptions returns MSS and windowscale of syn packets
|
||||
func (pckt *Packet) SYNOptions() (mss uint16, windowscale byte) {
|
||||
if !pckt.SYN {
|
||||
return
|
||||
}
|
||||
for _, v := range pckt.Options {
|
||||
if v.OptionType == layers.TCPOptionKindMSS {
|
||||
mss = binary.BigEndian.Uint16(v.OptionData)
|
||||
continue
|
||||
}
|
||||
if v.OptionType == layers.TCPOptionKindWindowScale {
|
||||
if v.OptionLength > 0 {
|
||||
windowscale = v.OptionData[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LinkInfo returns info about the link layer
|
||||
func (pckt *Packet) LinkInfo() string {
|
||||
if l, ok := pckt.LinkLayer.(*layers.Ethernet); ok {
|
||||
return fmt.Sprintf(
|
||||
"Source Mac: %s\nDestination Mac: %s\nProtocol: %s",
|
||||
l.SrcMAC,
|
||||
l.DstMAC,
|
||||
l.EthernetType,
|
||||
)
|
||||
}
|
||||
return "<Not Ethernet>"
|
||||
}
|
||||
|
||||
// Flag returns formatted tcp flags
|
||||
func (pckt *Packet) Flag() (flag string) {
|
||||
if pckt.FIN {
|
||||
flag += "FIN, "
|
||||
}
|
||||
if pckt.SYN {
|
||||
flag += "SYN, "
|
||||
}
|
||||
if pckt.RST {
|
||||
flag += "RST, "
|
||||
}
|
||||
if pckt.PSH {
|
||||
flag += "PSH, "
|
||||
}
|
||||
if pckt.ACK {
|
||||
flag += "ACK, "
|
||||
}
|
||||
if pckt.URG {
|
||||
flag += "URG, "
|
||||
}
|
||||
if len(flag) != 0 {
|
||||
return flag[:len(flag)-2]
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
// String output for a TCP Packet
|
||||
func (pckt *Packet) String() string {
|
||||
return fmt.Sprintf(`Time: %s
|
||||
%s
|
||||
Source: %s
|
||||
Destination: %s
|
||||
IHL: %d
|
||||
Total Length: %d
|
||||
Sequence: %d
|
||||
Acknowledgment: %d
|
||||
DataOffset: %d
|
||||
Window: %d
|
||||
Flag: %s
|
||||
Options: %s
|
||||
Data Size: %d
|
||||
Lost Data: %d`,
|
||||
pckt.Timestamp.Format(time.StampNano),
|
||||
pckt.LinkInfo(),
|
||||
pckt.Src(),
|
||||
pckt.Dst(),
|
||||
pckt.IHL,
|
||||
pckt.Length,
|
||||
pckt.Seq,
|
||||
pckt.Ack,
|
||||
pckt.DataOffset,
|
||||
pckt.Window,
|
||||
pckt.Flag(),
|
||||
pckt.Options,
|
||||
len(pckt.Payload),
|
||||
pckt.Lost,
|
||||
)
|
||||
}
|
||||
|
||||
+59
-41
@@ -6,41 +6,38 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/buger/goreplay/capture"
|
||||
"github.com/buger/goreplay/proto"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
var decodeOpts = gopacket.DecodeOptions{Lazy: true, NoCopy: true}
|
||||
func generateHeader(seq uint32, length uint16) []byte {
|
||||
hdr := make([]byte, 4+24+24, 4+24+24)
|
||||
binary.BigEndian.PutUint32(hdr, uint32(layers.ProtocolFamilyIPv4))
|
||||
|
||||
func headersIP4(seq uint32, length uint16) (headers [54]byte) {
|
||||
// set ethernet headers
|
||||
binary.BigEndian.PutUint16(headers[12:14], uint16(layers.EthernetTypeIPv4))
|
||||
|
||||
// set ip header
|
||||
ip := headers[14:]
|
||||
copy(ip[0:2], []byte{4<<4 | 5, 0x28<<2 | 0x00})
|
||||
binary.BigEndian.PutUint16(ip[2:4], length+40)
|
||||
ip := hdr[4:]
|
||||
ip[0] = 4<<4 | 6
|
||||
binary.BigEndian.PutUint16(ip[2:4], length+24+24)
|
||||
ip[9] = uint8(layers.IPProtocolTCP)
|
||||
copy(ip[12:16], []byte{192, 168, 1, 2})
|
||||
copy(ip[16:], []byte{192, 168, 1, 3})
|
||||
copy(ip[12:16], []byte{127, 0, 0, 1})
|
||||
copy(ip[16:], []byte{127, 0, 0, 1})
|
||||
|
||||
// set tcp header
|
||||
tcp := ip[20:]
|
||||
binary.BigEndian.PutUint16(tcp[0:2], 45678)
|
||||
binary.BigEndian.PutUint16(tcp[2:4], 8001)
|
||||
tcp[12] = 5 << 4
|
||||
return
|
||||
tcp := ip[24:]
|
||||
tcp[12] = 6 << 4
|
||||
binary.BigEndian.PutUint16(tcp, 5535)
|
||||
binary.BigEndian.PutUint16(tcp[2:], 8000)
|
||||
binary.BigEndian.PutUint32(tcp[4:], seq)
|
||||
return hdr
|
||||
}
|
||||
|
||||
func GetPackets(start uint32, _len int, payload []byte) []gopacket.Packet {
|
||||
var packets = make([]gopacket.Packet, _len)
|
||||
func GetPackets(start uint32, _len int, payload []byte) []*capture.Packet {
|
||||
var packets = make([]*capture.Packet, _len)
|
||||
for i := start; i < start+uint32(_len); i++ {
|
||||
data := make([]byte, 54+len(payload))
|
||||
h := headersIP4(i, uint16(len(payload)))
|
||||
copy(data, h[:])
|
||||
copy(data[len(h):], payload)
|
||||
packets[i-start] = gopacket.NewPacket(data, layers.LinkTypeEthernet, decodeOpts)
|
||||
d := append(generateHeader(i, uint16(len(payload))), payload...)
|
||||
ci := &gopacket.CaptureInfo{Length: len(d), CaptureLength: len(d), Timestamp: time.Now()}
|
||||
packets[i-start] = capture.NewPacket(d, int(layers.LinkTypeLoop), 4, ci)
|
||||
}
|
||||
return packets
|
||||
}
|
||||
@@ -55,9 +52,9 @@ func TestMessageParserWithHint(t *testing.T) {
|
||||
return proto.HasFullPayload(m.Data(), m)
|
||||
}
|
||||
packets := GetPackets(1, 30, nil)
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[10].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[29].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[10].TransLayer[13] = 2 // SYN flag
|
||||
packets[29].TransLayer[13] = 1 // FIN flag
|
||||
packets[4] = GetPackets(5, 1, []byte("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n7"))[0]
|
||||
packets[5] = GetPackets(6, 1, []byte("\r\nMozilla\r\n9\r\nDeveloper\r"))[0]
|
||||
packets[6] = GetPackets(7, 1, []byte("\n7\r\nNetwork\r\n0\r\n\r\n"))[0]
|
||||
@@ -105,8 +102,8 @@ func TestMessageParserWithoutHint(t *testing.T) {
|
||||
var mssg = make(chan *Message, 1)
|
||||
var data [63 << 10]byte
|
||||
packets := GetPackets(1, 10, data[:])
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[9].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[9].TransLayer[13] = 1 // FIN flag
|
||||
p := NewMessagePool(63<<10*10, time.Second, nil, func(m *Message) { mssg <- m })
|
||||
for _, v := range packets {
|
||||
p.Handler(v)
|
||||
@@ -128,9 +125,9 @@ func TestMessageMaxSizeReached(t *testing.T) {
|
||||
var data [63 << 10]byte
|
||||
packets := GetPackets(1, 2, data[:])
|
||||
packets = append(packets, GetPackets(3, 1, make([]byte, 63<<10+10))...)
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[2].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[2].Data()[14:][15] = 3 // changing address
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[2].TransLayer[13] = 2 // SYN flag
|
||||
packets[2].NetLayer[15] = 3 // changing address
|
||||
p := NewMessagePool(63<<10+10, time.Second, nil, func(m *Message) { mssg <- m })
|
||||
for _, v := range packets {
|
||||
p.Handler(v)
|
||||
@@ -167,7 +164,7 @@ func TestMessageTimeoutReached(t *testing.T) {
|
||||
var mssg = make(chan *Message, 2)
|
||||
var data [63 << 10]byte
|
||||
packets := GetPackets(1, 2, data[:])
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
p := NewMessagePool(1<<20, 0, nil, func(m *Message) { mssg <- m })
|
||||
p.Handler(packets[0])
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
@@ -183,10 +180,10 @@ func TestMessageTimeoutReached(t *testing.T) {
|
||||
|
||||
func TestMessageUUID(t *testing.T) {
|
||||
packets := GetPackets(1, 10, nil)
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[4].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[5].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[9].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[4].TransLayer[13] = 1 // FIN flag
|
||||
packets[5].TransLayer[13] = 2 // SYN flag
|
||||
packets[9].TransLayer[13] = 1 // FIN flag
|
||||
var uuid, uuid1 []byte
|
||||
pool := NewMessagePool(0, 0, nil, func(msg *Message) {
|
||||
if len(uuid) == 0 {
|
||||
@@ -207,8 +204,8 @@ func TestMessageUUID(t *testing.T) {
|
||||
|
||||
func BenchmarkMessageUUID(b *testing.B) {
|
||||
packets := GetPackets(1, 5, nil)
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[4].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[4].TransLayer[13] = 1 // FIN flag
|
||||
var uuid []byte
|
||||
var msg *Message
|
||||
pool := NewMessagePool(0, 0, nil, func(m *Message) {
|
||||
@@ -241,8 +238,8 @@ func BenchmarkMessageParserWithoutHint(b *testing.B) {
|
||||
var mssg = make(chan *Message, 1)
|
||||
var chunk = []byte("111111111111111111111111111111")
|
||||
packets := GetPackets(1, 1000, chunk)
|
||||
packets[0].Data()[14:][20:][13] = 2 // SYN flag
|
||||
packets[1000-1].Data()[14:][20:][13] = 1 // FIN flag
|
||||
packets[0].TransLayer[13] = 2 // SYN flag
|
||||
packets[1000-1].TransLayer[13] = 1 // FIN flag
|
||||
p := NewMessagePool(1<<20, time.Second*2, nil, func(m *Message) {
|
||||
mssg <- m
|
||||
})
|
||||
@@ -264,7 +261,7 @@ func BenchmarkMessageParserWithHint(b *testing.B) {
|
||||
buf[i] = chunk
|
||||
}
|
||||
buf[1001] = []byte("0\r\n\r\n")
|
||||
packets := make([]gopacket.Packet, len(buf))
|
||||
packets := make([]*capture.Packet, len(buf))
|
||||
for i := 0; i < len(buf); i++ {
|
||||
packets[i] = GetPackets(uint32(i+10), 1, buf[i])[0]
|
||||
}
|
||||
@@ -286,3 +283,24 @@ func BenchmarkMessageParserWithHint(b *testing.B) {
|
||||
<-mssg
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewAndParsePacket(b *testing.B) {
|
||||
data := append(generateHeader(1024, 10), make([]byte, 10)...)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParsePacket(capture.NewPacket(data, int(layers.LinkTypeLoop), 4, &gopacket.CaptureInfo{}))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewPacket(b *testing.B) {
|
||||
packet := capture.NewPacket(
|
||||
append(generateHeader(1024, 10), make([]byte, 10)...),
|
||||
int(layers.LinkTypeLoop),
|
||||
4,
|
||||
&gopacket.CaptureInfo{},
|
||||
)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParsePacket(packet)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user