Skip to content

Commit 5afb1e0

Browse files
End-application improvements for the tun library use.
Add backwards-compatible functional options support to native tun interfaces. Add support for indicating carrier presence [linux only]. Add `SetCarrier(bool) error` to tun.Device interface for changing carrier state [linux only]. Add option to create tun device without carrier, or with carrier present [linux only]. Merge src/dst fields in checksum computation to use larger loop unrolls. Export checksum functions to applications. Add option to disable tun offloads [linux only]. Add option to enforce tun checksum computation for outgoing packets [all platforms]. Add `MinOffset() int` to tun.Device interface, informing library users of the required offset. Signed-off-by: Alexander Tumin <iamtakingiteasy@eientei.org>
1 parent f333402 commit 5afb1e0

13 files changed

Lines changed: 756 additions & 354 deletions

device/device_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,8 @@ func (t *fakeTUNDeviceSized) Name() (string, error) { ret
446446
func (t *fakeTUNDeviceSized) Events() <-chan tun.Event { return nil }
447447
func (t *fakeTUNDeviceSized) Close() error { return nil }
448448
func (t *fakeTUNDeviceSized) BatchSize() int { return t.size }
449+
func (t *fakeTUNDeviceSized) MinOffset() int { return 0 }
450+
func (t *fakeTUNDeviceSized) SetCarrier(bool) error { return nil }
449451

450452
func TestBatchSize(t *testing.T) {
451453
d := Device{}

tun/checksum.go

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,32 @@ import (
55
"math/bits"
66
)
77

8-
// TODO: Explore SIMD and/or other assembly optimizations.
9-
func checksumNoFold(b []byte, initial uint64) uint64 {
8+
// IP protocol constants
9+
const (
10+
ProtocolTCP = 6
11+
ProtocolUDP = 17
12+
)
13+
14+
const (
15+
IPv4SrcAddrOffset = 12
16+
IPv6SrcAddrOffset = 8
17+
)
18+
19+
var (
20+
// PseudoHeaderProtocolTCP TCP protocol field of the TCP pseudoheader
21+
PseudoHeaderProtocolTCP = []byte{0, ProtocolTCP}
22+
// PseudoHeaderProtocolUDP UDP protocol field of the UDP pseudoheader
23+
PseudoHeaderProtocolUDP = []byte{0, ProtocolUDP}
24+
// PseudoHeaderProtocolMap provides dispatch for IP protocol to the corresponding protocol pseudo-header field
25+
PseudoHeaderProtocolMap = map[uint8][]byte{
26+
ProtocolTCP: PseudoHeaderProtocolTCP,
27+
ProtocolUDP: PseudoHeaderProtocolUDP,
28+
}
29+
)
30+
31+
// ChecksumNoFold performs intermediate checksum computation per RFC 1071
32+
func ChecksumNoFold(b []byte, initial uint64) uint64 {
33+
// TODO: Explore SIMD and/or other assembly optimizations.
1034
tmp := make([]byte, 8)
1135
binary.NativeEndian.PutUint64(tmp, initial)
1236
ac := binary.BigEndian.Uint64(tmp)
@@ -83,20 +107,73 @@ func checksumNoFold(b []byte, initial uint64) uint64 {
83107
return binary.BigEndian.Uint64(tmp)
84108
}
85109

86-
func checksum(b []byte, initial uint64) uint16 {
87-
ac := checksumNoFold(b, initial)
110+
// Checksum performs final checksum computation per RFC 1071
111+
func Checksum(b []byte, initial uint64) uint16 {
112+
ac := ChecksumNoFold(b, initial)
88113
ac = (ac >> 16) + (ac & 0xffff)
89114
ac = (ac >> 16) + (ac & 0xffff)
90115
ac = (ac >> 16) + (ac & 0xffff)
91116
ac = (ac >> 16) + (ac & 0xffff)
92117
return uint16(ac)
93118
}
94119

95-
func pseudoHeaderChecksumNoFold(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint64 {
96-
sum := checksumNoFold(srcAddr, 0)
97-
sum = checksumNoFold(dstAddr, sum)
98-
sum = checksumNoFold([]byte{0, protocol}, sum)
99-
tmp := make([]byte, 2)
100-
binary.BigEndian.PutUint16(tmp, totalLen)
101-
return checksumNoFold(tmp, sum)
120+
// PseudoHeaderChecksumNoFold performs intermediate checksum computation for TCP/UDP pseudoheader values
121+
func PseudoHeaderChecksumNoFold(protocol, srcDstAddr, totalLen []byte) uint64 {
122+
sum := ChecksumNoFold(srcDstAddr, 0)
123+
sum = ChecksumNoFold(protocol, sum)
124+
return ChecksumNoFold(totalLen, sum)
125+
}
126+
127+
// ComputeIPChecksum updates IP and TCP/UDP checksums
128+
func ComputeIPChecksum(pkt []byte) {
129+
ComputeIPChecksumBuffer(pkt, false)
130+
}
131+
132+
// ComputeIPChecksumBuffer updates IP and TCP/UDP checksums using the provided length buffer of size 2
133+
func ComputeIPChecksumBuffer(pkt []byte, partial bool) {
134+
var (
135+
lenbuf [2]byte
136+
addrsum uint64
137+
protocol uint8
138+
headerLen int
139+
totalLen uint16
140+
)
141+
142+
if pkt[0]>>4 == 4 {
143+
pkt[10], pkt[11] = 0, 0 // clear IP header checksum
144+
protocol = pkt[9]
145+
ihl := pkt[0] & 0xF
146+
headerLen = int(ihl * 4)
147+
totalLen = binary.BigEndian.Uint16(pkt[2:])
148+
addrsum = ChecksumNoFold(pkt[IPv4SrcAddrOffset:IPv4SrcAddrOffset+8], 0)
149+
binary.BigEndian.PutUint16(pkt[10:], ^Checksum(pkt[:IPv4SrcAddrOffset], addrsum))
150+
} else {
151+
protocol = pkt[6]
152+
headerLen = 40
153+
totalLen = binary.BigEndian.Uint16(pkt[4:])
154+
addrsum = ChecksumNoFold(pkt[IPv6SrcAddrOffset:IPv6SrcAddrOffset+32], 0)
155+
}
156+
157+
switch protocol {
158+
case ProtocolTCP:
159+
pkt[headerLen+16], pkt[headerLen+17] = 0, 0
160+
binary.BigEndian.PutUint16(lenbuf[:], totalLen-uint16(headerLen))
161+
tcpCSum := ChecksumNoFold(PseudoHeaderProtocolTCP, addrsum)
162+
tcpCSum = ChecksumNoFold(lenbuf[:], tcpCSum)
163+
if partial {
164+
binary.BigEndian.PutUint16(pkt[headerLen+16:], Checksum([]byte{}, tcpCSum))
165+
} else {
166+
binary.BigEndian.PutUint16(pkt[headerLen+16:], ^Checksum(pkt[headerLen:totalLen], tcpCSum))
167+
}
168+
case ProtocolUDP:
169+
pkt[headerLen+6], pkt[headerLen+7] = 0, 0
170+
binary.BigEndian.PutUint16(lenbuf[:], totalLen-uint16(headerLen))
171+
udpCSum := ChecksumNoFold(PseudoHeaderProtocolUDP, addrsum)
172+
udpCSum = ChecksumNoFold(lenbuf[:], udpCSum)
173+
if partial {
174+
binary.BigEndian.PutUint16(pkt[headerLen+6:], Checksum([]byte{}, udpCSum))
175+
} else {
176+
binary.BigEndian.PutUint16(pkt[headerLen+6:], ^Checksum(pkt[headerLen:totalLen], udpCSum))
177+
}
178+
}
102179
}

tun/checksum_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestChecksum(t *testing.T) {
4040
buf := make([]byte, length)
4141
rng := rand.New(rand.NewSource(1))
4242
rng.Read(buf)
43-
csum := checksum(buf, 0x1234)
43+
csum := Checksum(buf, 0x1234)
4444
csumRef := checksumRef(buf, 0x1234)
4545
if csum != csumRef {
4646
t.Error("Expected checksum", csumRef, "got", csum)
@@ -49,18 +49,20 @@ func TestChecksum(t *testing.T) {
4949
}
5050

5151
func TestPseudoHeaderChecksum(t *testing.T) {
52+
lenbuf := make([]byte, 2)
53+
5254
for _, addrLen := range []int{4, 16} {
5355
for length := 0; length <= 9001; length++ {
54-
srcAddr := make([]byte, addrLen)
55-
dstAddr := make([]byte, addrLen)
56-
buf := make([]byte, length)
56+
srcDstAddr := make([]byte, addrLen*2)
5757
rng := rand.New(rand.NewSource(1))
58-
rng.Read(srcAddr)
59-
rng.Read(dstAddr)
58+
rng.Read(srcDstAddr)
59+
rng.Read(srcDstAddr[addrLen:])
60+
buf := make([]byte, length)
6061
rng.Read(buf)
61-
phSum := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(length))
62-
csum := checksum(buf, phSum)
63-
phSumRef := pseudoHeaderChecksumRefNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(length))
62+
binary.BigEndian.PutUint16(lenbuf, uint16(length))
63+
phSum := PseudoHeaderChecksumNoFold(PseudoHeaderProtocolTCP, srcDstAddr, lenbuf)
64+
csum := Checksum(buf, phSum)
65+
phSumRef := pseudoHeaderChecksumRefNoFold(unix.IPPROTO_TCP, srcDstAddr[:addrLen], srcDstAddr[addrLen:], uint16(length))
6466
csumRef := checksumRef(buf, phSumRef)
6567
if csum != csumRef {
6668
t.Error("Expected checksumRef", csumRef, "got", csum)
@@ -91,7 +93,7 @@ func BenchmarkChecksum(b *testing.B) {
9193
rng.Read(buf)
9294
b.ResetTimer()
9395
for i := 0; i < b.N; i++ {
94-
checksum(buf, 0)
96+
Checksum(buf, 0)
9597
}
9698
})
9799
}

tun/netstack/tun.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ func (tun *netTun) BatchSize() int {
191191
return 1
192192
}
193193

194+
func (tun *netTun) MinOffset() int {
195+
return 0
196+
}
197+
198+
func (tun *netTun) SetCarrier(bool) error {
199+
return nil
200+
}
201+
194202
func convertToFullAddr(endpoint netip.AddrPort) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
195203
var protoNumber tcpip.NetworkProtocolNumber
196204
if endpoint.Addr().Is4() {

0 commit comments

Comments
 (0)