Repository navigation
Expand file tree
/
Copy pathflow_mtu.go
More file actions
143 lines (133 loc) · 5.18 KB
/
Copy pathflow_mtu.go
File metadata and controls
143 lines (133 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package tun
import (
"encoding/binary"
"github.com/sagernet/sing-tun/gtcpip/header"
)
// segmentRetainCount bounds how many segment buffers survive a Flush; the pool
// grows to the burst high-water mark within a batch and is trimmed afterwards.
const segmentRetainCount = 128
func tcpSegmentSize(packet *forwardPacket, raw []byte, effectiveMTU uint32) (headerLength int, segmentSize int, ok bool) {
if packet.fragment || len(packet.transport) < header.TCPMinimumSize {
return 0, 0, false
}
networkLength := len(raw) - len(packet.transport)
if packet.ipVersion == 6 && networkLength != header.IPv6MinimumSize {
return 0, 0, false
}
tcpHeaderLength := int(header.TCP(packet.transport).DataOffset())
if tcpHeaderLength < header.TCPMinimumSize || tcpHeaderLength > len(packet.transport) {
return 0, 0, false
}
headerLength = networkLength + tcpHeaderLength
segmentSize = int(effectiveMTU) - headerLength
return headerLength, segmentSize, segmentSize > 0
}
// Linux delivers TSO aggregates to the TUN even with IFF_VNET_HDR off
// (observed on 6.x: the pre-segmentation skb is handed to the fd as-is).
func segmentTCP(output relayOutput, packet *forwardPacket, raw []byte, headerLength int, segmentSize int) error {
headroom := output.relayHeadroom()
gsoType := GSOTCPv4
if packet.ipVersion == 6 {
gsoType = GSOTCPv6
}
neededSegments := max((len(raw)-headerLength+segmentSize-1)/segmentSize, 1)
buffers, sizes := output.relayReserve(neededSegments, headroom+headerLength+segmentSize)
n, err := GSOSplit(raw, GSOOptions{
GSOType: gsoType,
HdrLen: uint16(headerLength),
CsumStart: uint16(len(raw) - len(packet.transport)),
CsumOffset: header.TCPChecksumOffset,
GSOSize: uint16(segmentSize),
}, buffers, sizes, headroom)
if err != nil {
return err
}
for i := range n {
output.relaySegment(buffers[i][:headroom+sizes[i]])
}
return nil
}
func (s *ForwardStage) reserveSegments(count, size int) ([][]byte, []int) {
start := s.segmentUsed
end := start + count
for len(s.segmentBuffers) < end {
s.segmentBuffers = append(s.segmentBuffers, make([]byte, size))
s.segmentSizes = append(s.segmentSizes, 0)
}
for i := start; i < end; i++ {
if cap(s.segmentBuffers[i]) < size {
s.segmentBuffers[i] = make([]byte, size)
} else {
s.segmentBuffers[i] = s.segmentBuffers[i][:size]
}
}
s.segmentUsed = end
return s.segmentBuffers[start:end], s.segmentSizes[start:end]
}
const synthesizedTTL = 64
func fragmentIPv4Packet(packet header.IPv4, effectiveMTU uint32, headroom int) ([][]byte, bool) {
headerLength := int(packet.HeaderLength())
if headerLength < header.IPv4MinimumSize || headerLength >= len(packet) {
return nil, false
}
payload := packet[headerLength:]
maxFragmentPayload := (int(effectiveMTU) - headerLength) &^ 7
if maxFragmentPayload <= 0 {
return nil, false
}
baseOffset := packet.FragmentOffset()
originalMore := packet.Flags()&header.IPv4FlagMoreFragments != 0
baseFlags := packet.Flags() &^ header.IPv4FlagMoreFragments
fragments := make([][]byte, 0, (len(payload)+maxFragmentPayload-1)/maxFragmentPayload)
for start := 0; start < len(payload); start += maxFragmentPayload {
end := min(start+maxFragmentPayload, len(payload))
buffer := make([]byte, headroom+headerLength+end-start)
fragment := header.IPv4(buffer[headroom:])
copy(fragment, packet[:headerLength])
copy(fragment[headerLength:], payload[start:end])
flags := baseFlags
if originalMore || end < len(payload) {
flags |= header.IPv4FlagMoreFragments
}
fragment.SetFlagsFragmentOffset(flags, baseOffset+uint16(start))
fragment.SetTotalLength(uint16(len(fragment)))
fragment.SetChecksum(0)
fragment.SetChecksum(^fragment.CalculateChecksum())
fragments = append(fragments, buffer)
}
return fragments, true
}
func fragmentIPv6Packet(packet header.IPv6, effectiveMTU uint32, ident uint32, headroom int) ([][]byte, bool) {
if len(packet) < header.IPv6MinimumSize {
return nil, false
}
payload := packet.Payload()
maxFragmentPayload := (int(effectiveMTU) - header.IPv6MinimumSize - header.IPv6FragmentHeaderSize) &^ 7
if maxFragmentPayload <= 0 {
return nil, false
}
transportProtocol := packet.NextHeader()
fragments := make([][]byte, 0, (len(payload)+maxFragmentPayload-1)/maxFragmentPayload)
for start := 0; start < len(payload); start += maxFragmentPayload {
end := min(start+maxFragmentPayload, len(payload))
buffer := make([]byte, headroom+header.IPv6MinimumSize+header.IPv6FragmentHeaderSize+end-start)
fragment := header.IPv6(buffer[headroom:])
copy(fragment, packet[:header.IPv6MinimumSize])
fragment.SetNextHeader(header.IPv6FragmentHeader)
fragment.SetPayloadLength(uint16(header.IPv6FragmentHeaderSize + end - start))
encodeIPv6FragmentHeader(fragment[header.IPv6MinimumSize:], transportProtocol, start, end < len(payload), ident)
copy(fragment[header.IPv6MinimumSize+header.IPv6FragmentHeaderSize:], payload[start:end])
fragments = append(fragments, buffer)
}
return fragments, true
}
func encodeIPv6FragmentHeader(target []byte, nextHeader uint8, offset int, more bool, ident uint32) {
target[0] = nextHeader
target[1] = 0
value := uint16(offset)
if more {
value |= 1
}
binary.BigEndian.PutUint16(target[2:], value)
binary.BigEndian.PutUint32(target[4:], ident)
}