Skip to content

Commit 74691ad

Browse files
committed
comments pass and helpers
Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com> Change-Id: I99d814cf413752d481920f71d2430be56a6a6964
1 parent be7596d commit 74691ad

4 files changed

Lines changed: 58 additions & 32 deletions

File tree

buffer/buffer.go

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
/* SPDX-License-Identifier: MIT
22
*
33
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
4-
*
5-
* Package buffer implements a reusable buffer abstraction.
6-
*
7-
* Wireguard-go's data processing is constrained by both the hosts API,
8-
* and the transformations performed during encapsulation:
9-
*
10-
* 1. Encryption requires tail- and headroom for extra headers and padding.
11-
* Available via winrio, and pread(2).
12-
* 2. Systems are moving towards coalesced reads for both TCP and UDP.
13-
* The read data has no gaps for individual slices.
14-
* 3. crypto.AEAD interface requires a contiguous dst []byte for Sealing.
15-
* So we can't use scatter-gather to inject the gaps.
16-
*
17-
* Until one of these three conditions is changed, the encryption strategy
18-
* is to copy on read into buffers with the required gaps.
19-
* The buffers are right-sized for the packet to avoid memory inflation.
20-
* To recycle said allocations, each buffer carries a recycle function
21-
* that routes it back to its originating pool.
22-
*
23-
* Decryption shrinks each fragment instead of growing, so buffers can pass
24-
* through the pipeline without copying till the egress coalescion.
25-
* Depending on the chosen head of the coalescion, there may or may be no room
26-
* and reallocation is a necessary fallback until we start passing
27-
* buffers in batches.
284
*/
5+
6+
// Package buffer implements a reusable buffer abstraction.
7+
//
8+
// Wireguard-go's data processing is constrained by both the hosts API,
9+
// and the transformations performed during encapsulation:
10+
//
11+
// 1. Encryption requires tail- and headroom for extra headers and padding.
12+
// Available via winrio, and pread(2).
13+
// 2. Systems are moving towards coalesced reads for both TCP and UDP.
14+
// The read data has no gaps for individual slices.
15+
// 3. crypto.AEAD interface requires a contiguous dst []byte for Sealing.
16+
// So we can't use scatter-gather to inject the gaps.
17+
//
18+
// Until one of these three conditions is changed, the encryption strategy
19+
// is to copy on read into buffers with the required gaps.
20+
// The buffers are right-sized for the packet to avoid memory inflation.
21+
// To recycle said allocations, each buffer carries a recycle function
22+
// that routes it back to its originating pool.
23+
//
24+
// Decryption shrinks each fragment instead of growing, so buffers can pass
25+
// through the pipeline without copying till the egress coalescion.
26+
// Depending on the chosen head of the coalescion, there may or may be no room
27+
// and reallocation is a necessary fallback until we start passing
28+
// buffers in batches.
29+
2930
package buffer
3031

3132
const (
@@ -34,9 +35,13 @@ const (
3435

3536
// Source produces new Buffers.
3637
type Source interface {
38+
39+
// Get returns a Buffer of at least the requested size.
40+
// Implementations may chose to return error if the request can not be fulfilled.
3741
Get(size int) (*Buffer, error)
3842
}
3943

44+
// Recycler holds state necessary for a correct Buffer return to its originating Source
4045
type Recycler interface {
4146
Recycle(*Buffer)
4247
}
@@ -47,29 +52,41 @@ type Buffer struct {
4752
data []byte
4853
recycler Recycler
4954

50-
Size int // size of the read, excluding offset
55+
// Size is the length of the valid data within the buffer. It does not account for any head- or tailroom.
56+
// Typically implementations set it to the number of bytes read into the buffer.
57+
Size int
5158
}
5259

5360
// New creates a standalone Buffer.
5461
func New(b []byte, recycler Recycler) *Buffer {
5562
return &Buffer{data: b, recycler: recycler}
5663
}
5764

65+
// Make creates a standalone Buffer with a new byte slice of the requested size.
66+
func Make(size int) *Buffer {
67+
return &Buffer{data: make([]byte, size), recycler: nil}
68+
}
69+
70+
// Data returns the full underlying byte slice of the Buffer.
5871
func (b *Buffer) Data() []byte {
5972
return b.data
6073
}
6174

62-
func (b *Buffer) Len() int {
63-
return len(b.data)
75+
// Packet returns a slice starting at the given offset and ending at the end of the valid data in the buffer.
76+
func (b *Buffer) Packet(offset int) []byte {
77+
return b.data[offset : offset+b.Size]
6478
}
6579

80+
// Release returns the Buffer to its originating Source for reuse.
81+
// The Buffer must not be used after calling Release.
6682
func (b *Buffer) Release() {
6783
if b.recycler != nil {
6884
clear(b.data)
6985
b.recycler.Recycle(b)
7086
}
7187
}
7288

89+
// ReleaseAll calls Release on each non-nil Buffer in the slice, and sets the slice elements to nil.
7390
func ReleaseAll(bs []*Buffer) {
7491
for i := range bs {
7592
if bs[i] != nil {
@@ -79,11 +96,14 @@ func ReleaseAll(bs []*Buffer) {
7996
}
8097
}
8198

99+
// Arena is a Buffer with an internal watermark for sequential allocations.
100+
// FIXME Arena needs a graceful fallback on overflow.
82101
type Arena struct {
83102
*Buffer
84103
watermark int
85104
}
86105

106+
// Get returns a slice of the Arena's Buffer of the requested size, and advances the watermark.
87107
func (a *Arena) Get(size int) []byte {
88108
if a.watermark+size > len(a.Buffer.Data()) {
89109
panic("arena overflow") // or return a heap-allocated fallback
@@ -93,6 +113,7 @@ func (a *Arena) Get(size int) []byte {
93113
return b
94114
}
95115

116+
// Flush resets the Arena's watermark to zero, and clears the valid data in the Buffer.
96117
func (a *Arena) Flush() {
97118
clear(a.Buffer.Data()[:a.watermark])
98119
a.watermark = 0

buffer/pool.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package buffer
33
import "sync"
44

55
const (
6-
min = 2 << 10
7-
mid = 10 << 10
8-
max = 65 << 10
6+
min = 2 << 10 // 2KB, enough for a typical MTU-sized packet
7+
mid = 10 << 10 // 10KB, enough for a jumbo frame
8+
max = 65 << 10 // 65KB, enough for the maximum possible UDP datagram size
99
)
1010

1111
var _ Source = (*FragmentPool)(nil)
1212

13+
// FragmentPool is a tiered source of buffers. Tiers are balanced
14+
// to accomodate regular MTU sizes, jumbo frames, and the maximum possible UDP datagram size.
1315
type FragmentPool struct {
1416
minPool sync.Pool
1517
midPool sync.Pool
@@ -38,6 +40,8 @@ func NewFragmentPool() *FragmentPool {
3840
return p
3941
}
4042

43+
// Get returns a Buffer of at least the requested size. Implementations may chose to return error if the request can not be fulfilled.
44+
// FIXME current code ignores err return
4145
func (p *FragmentPool) Get(size int) (*Buffer, error) {
4246
var buf *Buffer
4347
switch {

conn/conn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
// sizes may be zero, and callers should ignore them. Callers must pass a sizes
2828
// and eps slice with a length greater than or equal to the length of packets.
2929
// These lengths must not exceed the length of the associated Bind.BatchSize().
30+
// Nil entries in bufs are allocated by the implementation.
3031
type ReceiveFunc func(bufs []*buffer.Buffer, eps []Endpoint) (n int, err error)
3132

3233
// A Bind listens on a port for both IPv6 and IPv4 UDP traffic.

tun/tun_linux.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
588588
// CreateTUNFromFile creates a Device from an os.File with the provided MTU.
589589
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
590590
bufPool := buffer.NewFragmentPool()
591-
arenaBuf, _ := bufPool.Get(32 * 16 << 10)
591+
arenaBuf, _ := bufPool.Get(32 * 65 << 10) // 32 buffers of coalesced size
592592
tun := &NativeTun{
593593
tunFile: file,
594594
events: make(chan Event, 5),
@@ -649,7 +649,7 @@ func CreateUnmonitoredTUNFromFD(fd int) (Device, string, error) {
649649
}
650650
file := os.NewFile(uintptr(fd), "/dev/tun")
651651
bufPool := buffer.NewFragmentPool()
652-
arenaBuf, _ := bufPool.Get(32 * 16 << 10)
652+
arenaBuf, _ := bufPool.Get(32 * 65 << 10) // 32 buffers of coalesced size
653653
tun := &NativeTun{
654654
tunFile: file,
655655
events: make(chan Event, 5),

0 commit comments

Comments
 (0)