Skip to content

Commit 5f73685

Browse files
committed
conn: split read buffer on GSO
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com> Change-Id: If61dbfce4174bd07f12996c47f495e426a6a6964
1 parent 4b0fb08 commit 5f73685

4 files changed

Lines changed: 224 additions & 200 deletions

File tree

conn/bind_std.go

Lines changed: 73 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func (s *StdNetBind) putMessages(msgs *[]ipv6.Message) {
208208
// Non coalesced write paths access only batch.msgs[i].Buffers[0],
209209
// but we append more during [coalesceMessages].
210210
// Leave index zero accessible:
211-
(*msgs)[i] = ipv6.Message{Buffers: (*msgs)[i].Buffers[:1], OOB: (*msgs)[i].OOB}
211+
(*msgs)[i] = ipv6.Message{Buffers: (*msgs)[i].Buffers[:1], OOB: (*msgs)[i].OOB[:cap((*msgs)[i].OOB)]}
212212
}
213213
s.msgsPool.Put(msgs)
214214
}
@@ -236,53 +236,92 @@ func (s *StdNetBind) receiveIP(
236236
rxOffload bool,
237237
bufs []iobuf.View,
238238
eps []Endpoint,
239-
) (n int, err error) {
239+
) (int, error) {
240240
msgs := s.getMessages()
241-
// TODO: placeholder until bind implements right-sized buffers.
242-
iobuf.EnsureAllocated(bufs)
243-
for i := range bufs {
244-
(*msgs)[i].Buffers[0] = bufs[i].Bytes
245-
(*msgs)[i].OOB = (*msgs)[i].OOB[:cap((*msgs)[i].OOB)]
246-
}
247241
defer s.putMessages(msgs)
248-
var numMsgs int
249-
if runtime.GOOS == "linux" {
250-
if rxOffload {
251-
readAt := len(*msgs) - 2
252-
numMsgs, err = br.ReadBatch((*msgs)[readAt:], 0)
253-
if err != nil {
254-
return 0, err
242+
readData := make([]*iobuf.Shared, len(*msgs)) // TODO: optimize this allocation
243+
defer func() {
244+
for i := range readData {
245+
if readData[i] != nil {
246+
// release backing arrays for any unclaimed buffers
247+
readData[i].Release()
248+
readData[i] = nil
249+
}
250+
}
251+
}()
252+
253+
switch {
254+
case runtime.GOOS == "linux" && rxOffload:
255+
const readBatchSize = 2
256+
for i := range readBatchSize {
257+
readData[i] = iobuf.SharedBufPool.Get()
258+
(*msgs)[i].Buffers[0] = readData[i].Bytes[:]
259+
}
260+
msgsN, err := br.ReadBatch((*msgs)[:readBatchSize], 0)
261+
if err != nil {
262+
return 0, err // expect atomic reads
263+
}
264+
var n int
265+
for i, msg := range (*msgs)[:msgsN] {
266+
if msg.N == 0 {
267+
continue
255268
}
256-
numMsgs, err = splitCoalescedMessages(*msgs, readAt, getGSOSize)
269+
gsoSize, err := getGSOSize(msg.OOB[:msg.NN])
257270
if err != nil {
258-
return 0, err
271+
return n, err
259272
}
260-
} else {
261-
numMsgs, err = br.ReadBatch(*msgs, 0)
273+
if gsoSize == 0 {
274+
gsoSize = msg.N
275+
}
276+
split, err := readData[i].SplitCoalesced(bufs[n:], gsoSize, msg.N)
262277
if err != nil {
263-
return 0, err
278+
return n, err
279+
}
280+
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
281+
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
282+
getSrcFromControl(msg.OOB[:msg.NN], ep)
283+
for j := n; j < n+split; j++ {
284+
eps[j] = ep
264285
}
286+
n += split
265287
}
266-
} else {
288+
return n, nil
289+
case runtime.GOOS == "linux":
290+
readBatchSize := min(len(bufs), len(*msgs))
291+
for i := range readBatchSize {
292+
readData[i] = iobuf.SharedBufPool.Get()
293+
(*msgs)[i].Buffers[0] = readData[i].Bytes[:]
294+
}
295+
msgsN, err := br.ReadBatch((*msgs)[:readBatchSize], 0)
296+
if err != nil {
297+
return 0, err // expect atomic reads
298+
}
299+
var n int
300+
for i, msg := range (*msgs)[:msgsN] {
301+
if msg.N == 0 {
302+
continue
303+
}
304+
readData[i].Refer(&bufs[n], 0, msg.N)
305+
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
306+
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
307+
getSrcFromControl(msg.OOB[:msg.NN], ep)
308+
eps[n] = ep
309+
n++
310+
}
311+
return n, nil
312+
default:
267313
msg := &(*msgs)[0]
268-
msg.N, msg.NN, _, msg.Addr, err = conn.ReadMsgUDP(msg.Buffers[0], msg.OOB)
314+
readData[0] = iobuf.SharedBufPool.Get()
315+
n, _, _, addr, err := conn.ReadMsgUDP(readData[0].Bytes[:], msg.OOB)
269316
if err != nil {
270317
return 0, err
271318
}
272-
numMsgs = 1
273-
}
274-
for i := 0; i < numMsgs; i++ {
275-
msg := &(*msgs)[i]
276-
bufs[i].Bytes = bufs[i].Bytes[:msg.N]
277-
if len(bufs[i].Bytes) == 0 {
278-
continue
279-
}
280-
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
281-
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
319+
readData[0].Refer(&bufs[0], 0, n)
320+
ep := &StdNetEndpoint{AddrPort: addr.AddrPort()}
282321
getSrcFromControl(msg.OOB[:msg.NN], ep)
283-
eps[i] = ep
322+
eps[0] = ep
323+
return 1, nil
284324
}
285-
return numMsgs, nil
286325
}
287326

288327
func (s *StdNetBind) makeReceiveIPv4(pc *ipv4.PacketConn, conn *net.UDPConn, rxOffload bool) ReceiveFunc {
@@ -523,49 +562,3 @@ func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, offs
523562
}
524563
return base + 1
525564
}
526-
527-
type getGSOFunc func(control []byte) (int, error)
528-
529-
func splitCoalescedMessages(msgs []ipv6.Message, firstMsgAt int, getGSO getGSOFunc) (n int, err error) {
530-
for i := firstMsgAt; i < len(msgs); i++ {
531-
msg := &msgs[i]
532-
if msg.N == 0 {
533-
return n, err
534-
}
535-
var (
536-
gsoSize int
537-
start int
538-
end = msg.N
539-
numToSplit = 1
540-
)
541-
gsoSize, err = getGSO(msg.OOB[:msg.NN])
542-
if err != nil {
543-
return n, err
544-
}
545-
if gsoSize > 0 {
546-
numToSplit = (msg.N + gsoSize - 1) / gsoSize
547-
end = gsoSize
548-
}
549-
for j := 0; j < numToSplit; j++ {
550-
if n > i {
551-
return n, errors.New("splitting coalesced packet resulted in overflow")
552-
}
553-
copied := copy(msgs[n].Buffers[0], msg.Buffers[0][start:end])
554-
msgs[n].N = copied
555-
msgs[n].Addr = msg.Addr
556-
start = end
557-
end += gsoSize
558-
if end > msg.N {
559-
end = msg.N
560-
}
561-
n++
562-
}
563-
if i != n-1 {
564-
// It is legal for bytes to move within msg.Buffers[0] as a result
565-
// of splitting, so we only zero the source msg len when it is not
566-
// the destination of the last split operation above.
567-
msg.N = 0
568-
}
569-
}
570-
return n, nil
571-
}

conn/bind_std_test.go

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -135,123 +135,3 @@ func mockGetGSOSize(control []byte) (int, error) {
135135
}
136136
return int(binary.LittleEndian.Uint16(control)), nil
137137
}
138-
139-
func Test_splitCoalescedMessages(t *testing.T) {
140-
newMsg := func(n, gso int) ipv6.Message {
141-
msg := ipv6.Message{
142-
Buffers: [][]byte{make([]byte, 1<<16-1)},
143-
N: n,
144-
OOB: make([]byte, 2),
145-
}
146-
binary.LittleEndian.PutUint16(msg.OOB, uint16(gso))
147-
if gso > 0 {
148-
msg.NN = 2
149-
}
150-
return msg
151-
}
152-
153-
cases := []struct {
154-
name string
155-
msgs []ipv6.Message
156-
firstMsgAt int
157-
wantNumEval int
158-
wantMsgLens []int
159-
wantErr bool
160-
}{
161-
{
162-
name: "second last split last empty",
163-
msgs: []ipv6.Message{
164-
newMsg(0, 0),
165-
newMsg(0, 0),
166-
newMsg(3, 1),
167-
newMsg(0, 0),
168-
},
169-
firstMsgAt: 2,
170-
wantNumEval: 3,
171-
wantMsgLens: []int{1, 1, 1, 0},
172-
wantErr: false,
173-
},
174-
{
175-
name: "second last no split last empty",
176-
msgs: []ipv6.Message{
177-
newMsg(0, 0),
178-
newMsg(0, 0),
179-
newMsg(1, 0),
180-
newMsg(0, 0),
181-
},
182-
firstMsgAt: 2,
183-
wantNumEval: 1,
184-
wantMsgLens: []int{1, 0, 0, 0},
185-
wantErr: false,
186-
},
187-
{
188-
name: "second last no split last no split",
189-
msgs: []ipv6.Message{
190-
newMsg(0, 0),
191-
newMsg(0, 0),
192-
newMsg(1, 0),
193-
newMsg(1, 0),
194-
},
195-
firstMsgAt: 2,
196-
wantNumEval: 2,
197-
wantMsgLens: []int{1, 1, 0, 0},
198-
wantErr: false,
199-
},
200-
{
201-
name: "second last no split last split",
202-
msgs: []ipv6.Message{
203-
newMsg(0, 0),
204-
newMsg(0, 0),
205-
newMsg(1, 0),
206-
newMsg(3, 1),
207-
},
208-
firstMsgAt: 2,
209-
wantNumEval: 4,
210-
wantMsgLens: []int{1, 1, 1, 1},
211-
wantErr: false,
212-
},
213-
{
214-
name: "second last split last split",
215-
msgs: []ipv6.Message{
216-
newMsg(0, 0),
217-
newMsg(0, 0),
218-
newMsg(2, 1),
219-
newMsg(2, 1),
220-
},
221-
firstMsgAt: 2,
222-
wantNumEval: 4,
223-
wantMsgLens: []int{1, 1, 1, 1},
224-
wantErr: false,
225-
},
226-
{
227-
name: "second last no split last split overflow",
228-
msgs: []ipv6.Message{
229-
newMsg(0, 0),
230-
newMsg(0, 0),
231-
newMsg(1, 0),
232-
newMsg(4, 1),
233-
},
234-
firstMsgAt: 2,
235-
wantNumEval: 4,
236-
wantMsgLens: []int{1, 1, 1, 1},
237-
wantErr: true,
238-
},
239-
}
240-
241-
for _, tt := range cases {
242-
t.Run(tt.name, func(t *testing.T) {
243-
got, err := splitCoalescedMessages(tt.msgs, 2, mockGetGSOSize)
244-
if err != nil && !tt.wantErr {
245-
t.Fatalf("err: %v", err)
246-
}
247-
if got != tt.wantNumEval {
248-
t.Fatalf("got to eval: %d want: %d", got, tt.wantNumEval)
249-
}
250-
for i, msg := range tt.msgs {
251-
if msg.N != tt.wantMsgLens[i] {
252-
t.Fatalf("msg[%d].N: %d want: %d", i, msg.N, tt.wantMsgLens[i])
253-
}
254-
}
255-
})
256-
}
257-
}

0 commit comments

Comments
 (0)