@@ -2,6 +2,7 @@ package tun
2
2
3
3
import (
4
4
"errors"
5
+ "fmt"
5
6
"math/rand"
6
7
"net"
7
8
"net/netip"
@@ -35,13 +36,15 @@ type NativeTun struct {
35
36
interfaceCallback * list.Element [DefaultInterfaceUpdateCallback ]
36
37
options Options
37
38
ruleIndex6 []int
38
- gsoEnabled bool
39
- gsoBuffer []byte
39
+ readAccess sync.Mutex
40
+ writeAccess sync.Mutex
41
+ vnetHdr bool
42
+ writeBuffer []byte
40
43
gsoToWrite []int
41
- gsoReadAccess sync. Mutex
42
- tcpGROAccess sync.Mutex
43
- tcp4GROTable * tcpGROTable
44
- tcp6GROTable * tcpGROTable
44
+ tcpGROTable * tcpGROTable
45
+ udpGroAccess sync.Mutex
46
+ udpGROTable * udpGROTable
47
+ gro groDisablementFlags
45
48
txChecksumOffload bool
46
49
}
47
50
@@ -81,20 +84,23 @@ func New(options Options) (Tun, error) {
81
84
}
82
85
83
86
func (t * NativeTun ) FrontHeadroom () int {
84
- if t .gsoEnabled {
87
+ if t .vnetHdr {
85
88
return virtioNetHdrLen
86
89
}
87
90
return 0
88
91
}
89
92
90
93
func (t * NativeTun ) Read (p []byte ) (n int , err error ) {
91
- if t .gsoEnabled {
92
- n , err = t .tunFile .Read (t .gsoBuffer )
94
+ if t .vnetHdr {
95
+ n , err = t .tunFile .Read (t .writeBuffer )
93
96
if err != nil {
97
+ if errors .Is (err , syscall .EBADFD ) {
98
+ err = os .ErrClosed
99
+ }
94
100
return
95
101
}
96
102
var sizes [1 ]int
97
- n , err = handleVirtioRead (t .gsoBuffer [:n ], [][]byte {p }, sizes [:], 0 )
103
+ n , err = handleVirtioRead (t .writeBuffer [:n ], [][]byte {p }, sizes [:], 0 )
98
104
if err != nil {
99
105
return
100
106
}
@@ -108,9 +114,50 @@ func (t *NativeTun) Read(p []byte) (n int, err error) {
108
114
}
109
115
}
110
116
117
+ // handleVirtioRead splits in into bufs, leaving offset bytes at the front of
118
+ // each buffer. It mutates sizes to reflect the size of each element of bufs,
119
+ // and returns the number of packets read.
120
+ func handleVirtioRead (in []byte , bufs [][]byte , sizes []int , offset int ) (int , error ) {
121
+ var hdr virtioNetHdr
122
+ err := hdr .decode (in )
123
+ if err != nil {
124
+ return 0 , err
125
+ }
126
+ in = in [virtioNetHdrLen :]
127
+
128
+ options , err := hdr .toGSOOptions ()
129
+ if err != nil {
130
+ return 0 , err
131
+ }
132
+
133
+ // Don't trust HdrLen from the kernel as it can be equal to the length
134
+ // of the entire first packet when the kernel is handling it as part of a
135
+ // FORWARD path. Instead, parse the transport header length and add it onto
136
+ // CsumStart, which is synonymous for IP header length.
137
+ if options .GSOType == GSOUDPL4 {
138
+ options .HdrLen = options .CsumStart + 8
139
+ } else if options .GSOType != GSONone {
140
+ if len (in ) <= int (options .CsumStart + 12 ) {
141
+ return 0 , errors .New ("packet is too short" )
142
+ }
143
+
144
+ tcpHLen := uint16 (in [options .CsumStart + 12 ] >> 4 * 4 )
145
+ if tcpHLen < 20 || tcpHLen > 60 {
146
+ // A TCP header must be between 20 and 60 bytes in length.
147
+ return 0 , fmt .Errorf ("tcp header len is invalid: %d" , tcpHLen )
148
+ }
149
+ options .HdrLen = options .CsumStart + tcpHLen
150
+ }
151
+
152
+ return GSOSplit (in , options , bufs , sizes , offset )
153
+ }
154
+
111
155
func (t * NativeTun ) Write (p []byte ) (n int , err error ) {
112
- if t .gsoEnabled {
113
- err = t .BatchWrite ([][]byte {p }, virtioNetHdrLen )
156
+ if t .vnetHdr {
157
+ buffer := buf .Get (virtioNetHdrLen + len (p ))
158
+ copy (buffer [virtioNetHdrLen :], p )
159
+ _ , err = t .BatchWrite ([][]byte {buffer }, virtioNetHdrLen )
160
+ buf .Put (buffer )
114
161
if err != nil {
115
162
return
116
163
}
@@ -121,7 +168,7 @@ func (t *NativeTun) Write(p []byte) (n int, err error) {
121
168
}
122
169
123
170
func (t * NativeTun ) WriteVectorised (buffers []* buf.Buffer ) error {
124
- if t .gsoEnabled {
171
+ if t .vnetHdr {
125
172
n := buf .LenMulti (buffers )
126
173
buffer := buf .NewSize (virtioNetHdrLen + n )
127
174
buffer .Truncate (virtioNetHdrLen )
@@ -135,7 +182,7 @@ func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
135
182
}
136
183
137
184
func (t * NativeTun ) BatchSize () int {
138
- if ! t .gsoEnabled {
185
+ if ! t .vnetHdr {
139
186
return 1
140
187
}
141
188
/* // Not works on some devices: https://github.com/SagerNet/sing-box/issues/1605
@@ -147,36 +194,67 @@ func (t *NativeTun) BatchSize() int {
147
194
return idealBatchSize
148
195
}
149
196
197
+ // DisableUDPGRO disables UDP GRO if it is enabled. See the GRODevice interface
198
+ // for cases where it should be called.
199
+ func (t * NativeTun ) DisableUDPGRO () {
200
+ t .writeAccess .Lock ()
201
+ t .gro .disableUDPGRO ()
202
+ t .writeAccess .Unlock ()
203
+ }
204
+
205
+ // DisableTCPGRO disables TCP GRO if it is enabled. See the GRODevice interface
206
+ // for cases where it should be called.
207
+ func (t * NativeTun ) DisableTCPGRO () {
208
+ t .writeAccess .Lock ()
209
+ t .gro .disableTCPGRO ()
210
+ t .writeAccess .Unlock ()
211
+ }
212
+
150
213
func (t * NativeTun ) BatchRead (buffers [][]byte , offset int , readN []int ) (n int , err error ) {
151
- t .gsoReadAccess .Lock ()
152
- defer t .gsoReadAccess .Unlock ()
153
- n , err = t .tunFile .Read (t .gsoBuffer )
214
+ t .readAccess .Lock ()
215
+ defer t .readAccess .Unlock ()
216
+ n , err = t .tunFile .Read (t .writeBuffer )
154
217
if err != nil {
155
218
return
156
219
}
157
- return handleVirtioRead (t .gsoBuffer [:n ], buffers , readN , offset )
220
+ return handleVirtioRead (t .writeBuffer [:n ], buffers , readN , offset )
158
221
}
159
222
160
- func (t * NativeTun ) BatchWrite (buffers [][]byte , offset int ) error {
161
- t .tcpGROAccess .Lock ()
223
+ func (t * NativeTun ) BatchWrite (buffers [][]byte , offset int ) ( int , error ) {
224
+ t .writeAccess .Lock ()
162
225
defer func () {
163
- t .tcp4GROTable .reset ()
164
- t .tcp6GROTable .reset ()
165
- t .tcpGROAccess .Unlock ()
226
+ t .tcpGROTable .reset ()
227
+ t .udpGROTable .reset ()
228
+ t .writeAccess .Unlock ()
166
229
}()
230
+ var (
231
+ errs error
232
+ total int
233
+ )
167
234
t .gsoToWrite = t .gsoToWrite [:0 ]
168
- err := handleGRO (buffers , offset , t .tcp4GROTable , t .tcp6GROTable , & t .gsoToWrite )
169
- if err != nil {
170
- return err
235
+ if t .vnetHdr {
236
+ err := handleGRO (buffers , offset , t .tcpGROTable , t .udpGROTable , t .gro , & t .gsoToWrite )
237
+ if err != nil {
238
+ return 0 , err
239
+ }
240
+ offset -= virtioNetHdrLen
241
+ } else {
242
+ for i := range buffers {
243
+ t .gsoToWrite = append (t .gsoToWrite , i )
244
+ }
171
245
}
172
- offset -= virtioNetHdrLen
173
- for _ , bufferIndex := range t .gsoToWrite {
174
- _ , err = t .tunFile .Write (buffers [bufferIndex ][offset :])
246
+ for _ , toWrite := range t .gsoToWrite {
247
+ n , err := t .tunFile .Write (buffers [toWrite ][offset :])
248
+ if errors .Is (err , syscall .EBADFD ) {
249
+ return total , os .ErrClosed
250
+ }
175
251
if err != nil {
176
- return err
252
+ errs = errors .Join (errs , err )
253
+ } else {
254
+ total += n
177
255
}
178
256
}
179
- return nil
257
+ return total , errs
180
258
}
181
259
182
260
var controlPath string
@@ -262,10 +340,14 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
262
340
if err != nil {
263
341
return err
264
342
}
265
- t .gsoEnabled = true
266
- t .gsoBuffer = make ([]byte , virtioNetHdrLen + int (gsoMaxSize ))
267
- t .tcp4GROTable = newTCPGROTable ()
268
- t .tcp6GROTable = newTCPGROTable ()
343
+ t .vnetHdr = true
344
+ t .writeBuffer = make ([]byte , virtioNetHdrLen + int (gsoMaxSize ))
345
+ t .tcpGROTable = newTCPGROTable ()
346
+ t .udpGROTable = newUDPGROTable ()
347
+ err = setUDPOffload (t .tunFd )
348
+ if err != nil {
349
+ t .gro .disableUDPGRO ()
350
+ }
269
351
}
270
352
271
353
var rxChecksumOffload bool
@@ -280,7 +362,7 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
280
362
if err != nil {
281
363
return err
282
364
}
283
- if err == nil && ! txChecksumOffload {
365
+ if ! txChecksumOffload {
284
366
err = setChecksumOffload (t .options .Name , unix .ETHTOOL_STXCSUM )
285
367
if err != nil {
286
368
return err
0 commit comments