Skip to content

Commit 214edb4

Browse files
committed
Optimise packet capture thread
Ensure that thread which capture packets as fast as possible. Packet parsing logic moved to different threads. Additionally using os.LockOsThread to reduce CPU context switching
1 parent 79ff882 commit 214edb4

File tree

6 files changed

+211
-223
lines changed

6 files changed

+211
-223
lines changed

capture/capture.go

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"syscall"
1616
"time"
1717

18+
"github.com/buger/goreplay/proto"
1819
"github.com/buger/goreplay/size"
1920
"github.com/buger/goreplay/tcp"
2021

@@ -59,9 +60,13 @@ type Listener struct {
5960
loopIndex int
6061
Reading chan bool // this channel is closed when the listener has started reading packets
6162
PcapOptions
62-
Engine EngineType
63-
ports []uint16 // src or/and dst ports
64-
trackResponse bool
63+
Engine EngineType
64+
ports []uint16 // src or/and dst ports
65+
trackResponse bool
66+
expiry time.Duration
67+
allowIncomplete bool
68+
messages chan *tcp.Message
69+
protocol tcp.TCPProtocol
6570

6671
host string // pcap file name or interface (name, hardware addr, index or ip address)
6772

@@ -121,7 +126,7 @@ func (eng *EngineType) String() (e string) {
121126
// NewListener creates and initialize a new Listener. if transport or/and engine are invalid/unsupported
122127
// is "tcp" and "pcap", are assumed. l.Engine and l.Transport can help to get the values used.
123128
// if there is an error it will be associated with getting network interfaces
124-
func NewListener(host string, ports []uint16, transport string, engine EngineType, trackResponse bool) (l *Listener, err error) {
129+
func NewListener(host string, ports []uint16, transport string, engine EngineType, protocol tcp.TCPProtocol, trackResponse bool, expiry time.Duration, allowIncomplete bool) (l *Listener, err error) {
125130
l = &Listener{}
126131

127132
l.host = host
@@ -139,6 +144,11 @@ func NewListener(host string, ports []uint16, transport string, engine EngineTyp
139144
l.closeDone = make(chan struct{})
140145
l.quit = make(chan struct{})
141146
l.Reading = make(chan bool)
147+
l.expiry = expiry
148+
l.allowIncomplete = allowIncomplete
149+
l.protocol = protocol
150+
l.messages = make(chan *tcp.Message, 10000)
151+
142152
switch engine {
143153
default:
144154
l.Engine = EnginePcap
@@ -171,8 +181,8 @@ func (l *Listener) SetPcapOptions(opts PcapOptions) {
171181
// Listen listens for packets from the handles, and call handler on every packet received
172182
// until the context done signal is sent or there is unrecoverable error on all handles.
173183
// this function must be called after activating pcap handles
174-
func (l *Listener) Listen(ctx context.Context, handler PacketHandler) (err error) {
175-
l.read(handler)
184+
func (l *Listener) Listen(ctx context.Context) (err error) {
185+
l.read()
176186
done := ctx.Done()
177187
select {
178188
case <-done:
@@ -185,11 +195,11 @@ func (l *Listener) Listen(ctx context.Context, handler PacketHandler) (err error
185195
}
186196

187197
// ListenBackground is like listen but can run concurrently and signal error through channel
188-
func (l *Listener) ListenBackground(ctx context.Context, handler PacketHandler) chan error {
198+
func (l *Listener) ListenBackground(ctx context.Context) chan error {
189199
err := make(chan error, 1)
190200
go func() {
191201
defer close(err)
192-
if e := l.Listen(ctx, handler); err != nil {
202+
if e := l.Listen(ctx); err != nil {
193203
err <- e
194204
}
195205
}()
@@ -332,11 +342,34 @@ func (l *Listener) SocketHandle(ifi pcap.Interface) (handle Socket, err error) {
332342
return
333343
}
334344

335-
func (l *Listener) read(handler PacketHandler) {
345+
func http1StartHint(pckt *tcp.Packet) (isRequest, isResponse bool) {
346+
if proto.HasRequestTitle(pckt.Payload) {
347+
return true, false
348+
}
349+
350+
if proto.HasResponseTitle(pckt.Payload) {
351+
return false, true
352+
}
353+
354+
// No request or response detected
355+
return false, false
356+
}
357+
358+
func http1EndHint(m *tcp.Message) bool {
359+
if m.MissingChunk() {
360+
return false
361+
}
362+
363+
return proto.HasFullPayload(m, m.PacketData()...)
364+
}
365+
366+
func (l *Listener) read() {
336367
l.Lock()
337368
defer l.Unlock()
338369
for key, handle := range l.Handles {
339370
go func(key string, hndl packetHandle) {
371+
runtime.LockOSThread()
372+
340373
defer l.closeHandles(key)
341374
linkSize := 14
342375
linkType := int(layers.LinkTypeEthernet)
@@ -351,6 +384,13 @@ func (l *Listener) read(handler PacketHandler) {
351384
}
352385
}
353386

387+
messageParser := tcp.NewMessageParser(l.messages, l.ports, hndl.ips, l.expiry, l.allowIncomplete)
388+
389+
if l.protocol == tcp.ProtocolHTTP {
390+
messageParser.Start = http1StartHint
391+
messageParser.End = http1EndHint
392+
}
393+
354394
timer := time.NewTicker(1 * time.Second)
355395

356396
for {
@@ -371,23 +411,12 @@ func (l *Listener) read(handler PacketHandler) {
371411
ci.Timestamp = time.Now()
372412
}
373413

374-
pckt, err := tcp.ParsePacket(data, linkType, linkSize, &ci, false)
375-
376-
if err == nil {
377-
for _, p := range l.ports {
378-
if pckt.DstPort == p {
379-
for _, ip := range hndl.ips {
380-
if pckt.DstIP.Equal(ip) {
381-
pckt.Direction = tcp.DirIncoming
382-
break
383-
}
384-
}
385-
break
386-
}
387-
}
388-
389-
handler(pckt)
390-
}
414+
messageParser.PacketHandler(&tcp.PcapPacket{
415+
Data: data,
416+
LType: linkType,
417+
LTypeLen: linkSize,
418+
Ci: &ci,
419+
})
391420
continue
392421
}
393422
if enext, ok := err.(pcap.NextError); ok && enext == pcap.NextErrorTimeoutExpired {
@@ -413,6 +442,10 @@ func (l *Listener) read(handler PacketHandler) {
413442
close(l.Reading)
414443
}
415444

445+
func (l *Listener) Messages() chan *tcp.Message {
446+
return l.messages
447+
}
448+
416449
func (l *Listener) closeHandles(key string) {
417450
l.Lock()
418451
defer l.Unlock()

input_raw.go

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,48 +16,14 @@ import (
1616
"github.com/buger/goreplay/tcp"
1717
)
1818

19-
// TCPProtocol is a number to indicate type of protocol
20-
type TCPProtocol uint8
21-
22-
const (
23-
// ProtocolHTTP ...
24-
ProtocolHTTP TCPProtocol = iota
25-
// ProtocolBinary ...
26-
ProtocolBinary
27-
)
28-
29-
// Set is here so that TCPProtocol can implement flag.Var
30-
func (protocol *TCPProtocol) Set(v string) error {
31-
switch v {
32-
case "", "http":
33-
*protocol = ProtocolHTTP
34-
case "binary":
35-
*protocol = ProtocolBinary
36-
default:
37-
return fmt.Errorf("unsupported protocol %s", v)
38-
}
39-
return nil
40-
}
41-
42-
func (protocol *TCPProtocol) String() string {
43-
switch *protocol {
44-
case ProtocolBinary:
45-
return "binary"
46-
case ProtocolHTTP:
47-
return "http"
48-
default:
49-
return ""
50-
}
51-
}
52-
5319
// RAWInputConfig represents configuration that can be applied on raw input
5420
type RAWInputConfig struct {
5521
capture.PcapOptions
5622
Expire time.Duration `json:"input-raw-expire"`
5723
CopyBufferSize size.Size `json:"copy-buffer-size"`
5824
Engine capture.EngineType `json:"input-raw-engine"`
5925
TrackResponse bool `json:"input-raw-track-response"`
60-
Protocol TCPProtocol `json:"input-raw-protocol"`
26+
Protocol tcp.TCPProtocol `json:"input-raw-protocol"`
6127
RealIPHeader string `json:"input-raw-realip-header"`
6228
Stats bool `json:"input-raw-stats"`
6329
AllowIncomplete bool `json:"input-raw-allow-incomplete"`
@@ -117,7 +83,7 @@ func (i *RAWInput) PluginRead() (*Message, error) {
11783
select {
11884
case <-i.quit:
11985
return nil, ErrorStopped
120-
case msgTCP = <-i.messageParser.Messages():
86+
case msgTCP = <-i.listener.Messages():
12187
msg.Data = msgTCP.Data()
12288
}
12389

@@ -142,14 +108,13 @@ func (i *RAWInput) PluginRead() (*Message, error) {
142108
stat := msgTCP.Stats
143109
go i.addStats(stat)
144110
}
145-
msgTCP.Finalize()
146111
msgTCP = nil
147112
return &msg, nil
148113
}
149114

150115
func (i *RAWInput) listen(address string) {
151116
var err error
152-
i.listener, err = capture.NewListener(i.host, i.ports, "", i.Engine, i.TrackResponse)
117+
i.listener, err = capture.NewListener(i.host, i.ports, "", i.Engine, i.Protocol, i.TrackResponse, i.Expire, i.AllowIncomplete)
153118
if err != nil {
154119
log.Fatal(err)
155120
}
@@ -158,15 +123,10 @@ func (i *RAWInput) listen(address string) {
158123
if err != nil {
159124
log.Fatal(err)
160125
}
161-
i.messageParser = tcp.NewMessageParser(i.CopyBufferSize, i.Expire, i.AllowIncomplete, Debug)
162126

163-
if i.Protocol == ProtocolHTTP {
164-
i.messageParser.Start = http1StartHint
165-
i.messageParser.End = http1EndHint
166-
}
167127
var ctx context.Context
168128
ctx, i.cancelListener = context.WithCancel(context.Background())
169-
errCh := i.listener.ListenBackground(ctx, i.messageParser.PacketHandler)
129+
errCh := i.listener.ListenBackground(ctx)
170130
<-i.listener.Reading
171131
Debug(1, i)
172132
go func() {
@@ -210,24 +170,3 @@ func (i *RAWInput) addStats(mStats tcp.Stats) {
210170
i.messageStats = append(i.messageStats, mStats)
211171
i.Unlock()
212172
}
213-
214-
func http1StartHint(pckt *tcp.Packet) (isRequest, isResponse bool) {
215-
if proto.HasRequestTitle(pckt.Payload) {
216-
return true, false
217-
}
218-
219-
if proto.HasResponseTitle(pckt.Payload) {
220-
return false, true
221-
}
222-
223-
// No request or response detected
224-
return false, false
225-
}
226-
227-
func http1EndHint(m *tcp.Message) bool {
228-
if m.MissingChunk() {
229-
return false
230-
}
231-
232-
return proto.HasFullPayload(m, m.PacketData()...)
233-
}

input_raw_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/buger/goreplay/capture"
1818
"github.com/buger/goreplay/proto"
19+
"github.com/buger/goreplay/tcp"
1920
)
2021

2122
const testRawExpire = time.Millisecond * 200
@@ -43,7 +44,7 @@ func TestRAWInputIPv4(t *testing.T) {
4344
conf := RAWInputConfig{
4445
Engine: capture.EnginePcap,
4546
Expire: 0,
46-
Protocol: ProtocolHTTP,
47+
Protocol: tcp.ProtocolHTTP,
4748
TrackResponse: true,
4849
RealIPHeader: "X-Real-IP",
4950
}
@@ -113,7 +114,7 @@ func TestRAWInputNoKeepAlive(t *testing.T) {
113114
conf := RAWInputConfig{
114115
Engine: capture.EnginePcap,
115116
Expire: testRawExpire,
116-
Protocol: ProtocolHTTP,
117+
Protocol: tcp.ProtocolHTTP,
117118
TrackResponse: true,
118119
}
119120
input := NewRAWInput(":"+port, conf)
@@ -178,7 +179,7 @@ func TestRAWInputIPv6(t *testing.T) {
178179
var respCounter, reqCounter int64
179180
conf := RAWInputConfig{
180181
Engine: capture.EnginePcap,
181-
Protocol: ProtocolHTTP,
182+
Protocol: tcp.ProtocolHTTP,
182183
TrackResponse: true,
183184
}
184185
input := NewRAWInput(originAddr, conf)
@@ -235,7 +236,7 @@ func TestInputRAWChunkedEncoding(t *testing.T) {
235236
conf := RAWInputConfig{
236237
Engine: capture.EnginePcap,
237238
Expire: time.Second,
238-
Protocol: ProtocolHTTP,
239+
Protocol: tcp.ProtocolHTTP,
239240
TrackResponse: true,
240241
AllowIncomplete: true,
241242
}
@@ -315,7 +316,7 @@ func BenchmarkRAWInputWithReplay(b *testing.B) {
315316
conf := RAWInputConfig{
316317
Engine: capture.EnginePcap,
317318
Expire: testRawExpire,
318-
Protocol: ProtocolHTTP,
319+
Protocol: tcp.ProtocolHTTP,
319320
TrackResponse: true,
320321
}
321322
input := NewRAWInput(originAddr, conf)

0 commit comments

Comments
 (0)