Skip to content

Commit b7fdc64

Browse files
committed
chore: reduce data copying in quic sniffer and better handle data fragmentation and overlap
1 parent a7a796b commit b7fdc64

File tree

4 files changed

+155
-55
lines changed

4 files changed

+155
-55
lines changed

common/buf/sing.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
)
77

88
const BufferSize = buf.BufferSize
9+
const UDPBufferSize = buf.UDPBufferSize
910

1011
type Buffer = buf.Buffer
1112

common/utils/ranges.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package utils
33
import (
44
"errors"
55
"fmt"
6+
"sort"
67
"strconv"
78
"strings"
89

@@ -149,3 +150,24 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
149150
}
150151
}
151152
}
153+
154+
func (ranges IntRanges[T]) Merge() (mergedRanges IntRanges[T]) {
155+
if len(ranges) == 0 {
156+
return
157+
}
158+
sort.Slice(ranges, func(i, j int) bool {
159+
return ranges[i].Start() < ranges[j].Start()
160+
})
161+
mergedRanges = ranges[:1]
162+
var rangeIndex int
163+
for _, r := range ranges[1:] {
164+
if mergedRanges[rangeIndex].End()+1 > mergedRanges[rangeIndex].End() && // integer overflow
165+
r.Start() > mergedRanges[rangeIndex].End()+1 {
166+
mergedRanges = append(mergedRanges, r)
167+
rangeIndex++
168+
} else if r.End() > mergedRanges[rangeIndex].End() {
169+
mergedRanges[rangeIndex].end = r.End()
170+
}
171+
}
172+
return
173+
}

common/utils/ranges_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package utils
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestMergeRanges(t *testing.T) {
9+
t.Parallel()
10+
for _, testRange := range []struct {
11+
ranges IntRanges[uint16]
12+
expected IntRanges[uint16]
13+
}{
14+
{
15+
ranges: IntRanges[uint16]{
16+
NewRange[uint16](0, 1),
17+
NewRange[uint16](1, 2),
18+
},
19+
expected: IntRanges[uint16]{
20+
NewRange[uint16](0, 2),
21+
},
22+
},
23+
{
24+
ranges: IntRanges[uint16]{
25+
NewRange[uint16](0, 3),
26+
NewRange[uint16](5, 7),
27+
NewRange[uint16](8, 9),
28+
NewRange[uint16](10, 10),
29+
},
30+
expected: IntRanges[uint16]{
31+
NewRange[uint16](0, 3),
32+
NewRange[uint16](5, 10),
33+
},
34+
},
35+
{
36+
ranges: IntRanges[uint16]{
37+
NewRange[uint16](1, 3),
38+
NewRange[uint16](2, 6),
39+
NewRange[uint16](8, 10),
40+
NewRange[uint16](15, 18),
41+
},
42+
expected: IntRanges[uint16]{
43+
NewRange[uint16](1, 6),
44+
NewRange[uint16](8, 10),
45+
NewRange[uint16](15, 18),
46+
},
47+
},
48+
{
49+
ranges: IntRanges[uint16]{
50+
NewRange[uint16](1, 3),
51+
NewRange[uint16](2, 7),
52+
NewRange[uint16](2, 6),
53+
},
54+
expected: IntRanges[uint16]{
55+
NewRange[uint16](1, 7),
56+
},
57+
},
58+
{
59+
ranges: IntRanges[uint16]{
60+
NewRange[uint16](1, 3),
61+
NewRange[uint16](2, 6),
62+
NewRange[uint16](2, 7),
63+
},
64+
expected: IntRanges[uint16]{
65+
NewRange[uint16](1, 7),
66+
},
67+
},
68+
{
69+
ranges: IntRanges[uint16]{
70+
NewRange[uint16](1, 3),
71+
NewRange[uint16](2, 65535),
72+
NewRange[uint16](2, 7),
73+
NewRange[uint16](3, 16),
74+
},
75+
expected: IntRanges[uint16]{
76+
NewRange[uint16](1, 65535),
77+
},
78+
},
79+
} {
80+
assert.Equal(t, testRange.expected, testRange.ranges.Merge())
81+
}
82+
}

component/sniffer/quic_sniffer.go

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -72,27 +72,22 @@ func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
7272
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender {
7373
return &quicPacketSender{
7474
sender: packetSender,
75-
buffer: make([]quicDataBlock, 0),
7675
chClose: make(chan struct{}),
7776
override: override,
7877
}
7978
}
8079

81-
type quicDataBlock struct {
82-
offset uint64
83-
length uint64
84-
data []byte
85-
}
86-
8780
var _ constant.PacketSender = (*quicPacketSender)(nil)
8881

8982
type quicPacketSender struct {
9083
lock sync.RWMutex
91-
buffer []quicDataBlock
92-
sender constant.PacketSender
84+
ranges utils.IntRanges[uint64]
85+
buffer *buf.Buffer
9386
result string
9487
override bool
9588

89+
sender constant.PacketSender
90+
9691
chClose chan struct{}
9792
closed bool
9893
}
@@ -144,11 +139,18 @@ func (q *quicPacketSender) Close() {
144139

145140
func (q *quicPacketSender) close() {
146141
q.lock.Lock()
142+
q.closeLocked()
143+
q.lock.Unlock()
144+
}
145+
146+
func (q *quicPacketSender) closeLocked() {
147147
if !q.closed {
148148
close(q.chClose)
149149
q.closed = true
150+
q.buffer.Release()
151+
q.buffer = nil
152+
q.ranges = nil
150153
}
151-
q.lock.Unlock()
152154
}
153155

154156
func (q *quicPacketSender) readQuicData(b []byte) error {
@@ -287,6 +289,14 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
287289
buffer = buf.As(decrypted)
288290

289291
for i := 0; !buffer.IsEmpty(); i++ {
292+
q.lock.RLock()
293+
if q.closed {
294+
q.lock.RUnlock()
295+
// close() was called, just return
296+
return nil
297+
}
298+
q.lock.RUnlock()
299+
290300
frameType := byte(0x0) // Default to PADDING frame
291301
for frameType == 0x0 && !buffer.IsEmpty() {
292302
frameType, _ = buffer.ReadByte()
@@ -337,26 +347,32 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
337347
return io.ErrUnexpectedEOF
338348
}
339349

340-
q.lock.RLock()
341-
if q.buffer == nil {
342-
q.lock.RUnlock()
343-
// sniffDone() was called, return the connection
344-
return nil
350+
end := offset + length
351+
if end > buf.UDPBufferSize { // same as buf.NewPacket().Cap()
352+
return io.ErrShortBuffer
345353
}
346-
q.lock.RUnlock()
347354

348-
data = make([]byte, length)
355+
q.lock.Lock()
356+
if q.closed {
357+
q.lock.Unlock()
358+
// close() was called, just return
359+
return nil
360+
}
361+
if q.buffer == nil {
362+
q.buffer = buf.NewPacket()
363+
}
349364

350-
if _, err := buffer.Read(data); err != nil { // Field: Crypto Data
365+
extendSize := int(end) - q.buffer.Len()
366+
if extendSize > 0 {
367+
q.buffer.Extend(extendSize)
368+
}
369+
target := q.buffer.Range(int(offset), int(end))
370+
if _, err := buffer.Read(target); err != nil { // Field: Crypto Data
371+
q.lock.Unlock()
351372
return io.ErrUnexpectedEOF
352373
}
353-
354-
q.lock.Lock()
355-
q.buffer = append(q.buffer, quicDataBlock{
356-
offset: offset,
357-
length: length,
358-
data: data,
359-
})
374+
q.ranges = append(q.ranges, utils.NewRange(offset, end))
375+
q.ranges = q.ranges.Merge()
360376
q.lock.Unlock()
361377
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
362378
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
@@ -387,50 +403,29 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
387403
func (q *quicPacketSender) tryAssemble() error {
388404
q.lock.RLock()
389405

390-
if q.buffer == nil {
406+
if q.closed {
391407
q.lock.RUnlock()
408+
// close() was called, just return
392409
return nil
393410
}
394411

395-
var frameLen uint64
396-
for _, fragment := range q.buffer {
397-
frameLen += fragment.length
398-
}
399-
400-
buffer := buf.NewSize(int(frameLen))
401-
402-
var index uint64
403-
var length int
404-
405-
loop:
406-
for {
407-
for _, fragment := range q.buffer {
408-
if fragment.offset == index {
409-
if _, err := buffer.Write(fragment.data); err != nil {
410-
return err
411-
}
412-
index = fragment.offset + fragment.length
413-
length++
414-
continue loop
415-
}
416-
}
417-
418-
break
412+
if q.buffer == nil || len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(q.buffer.Len()) {
413+
q.lock.RUnlock()
414+
return ErrNoClue
419415
}
420416

421-
domain, err := ReadClientHello(buffer.Bytes())
417+
domain, err := ReadClientHello(q.buffer.Bytes())
418+
q.lock.RUnlock()
422419
if err != nil {
423-
q.lock.RUnlock()
424420
return err
425421
}
426-
q.lock.RUnlock()
427422

428423
q.lock.Lock()
429424
q.result = *domain
425+
q.closeLocked()
430426
q.lock.Unlock()
431-
q.close()
432427

433-
return err
428+
return nil
434429
}
435430

436431
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {

0 commit comments

Comments
 (0)