Skip to content

Commit e60fbd3

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 ICMP checksumming. 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 e60fbd3

13 files changed

Lines changed: 761 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: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,34 @@ 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+
ProtocolICMP4 = 1
11+
ProtocolTCP = 6
12+
ProtocolUDP = 17
13+
ProtocolICMP6 = 58
14+
)
15+
16+
const (
17+
IPv4SrcAddrOffset = 12
18+
IPv6SrcAddrOffset = 8
19+
)
20+
21+
var (
22+
// PseudoHeaderProtocolTCP TCP protocol field of the TCP pseudoheader
23+
PseudoHeaderProtocolTCP = []byte{0, ProtocolTCP}
24+
// PseudoHeaderProtocolUDP UDP protocol field of the UDP pseudoheader
25+
PseudoHeaderProtocolUDP = []byte{0, ProtocolUDP}
26+
// PseudoHeaderProtocolMap provides dispatch for IP protocol to the corresponding protocol pseudo-header field
27+
PseudoHeaderProtocolMap = map[uint8][]byte{
28+
ProtocolTCP: PseudoHeaderProtocolTCP,
29+
ProtocolUDP: PseudoHeaderProtocolUDP,
30+
}
31+
)
32+
33+
// ChecksumNoFold performs intermediate checksum computation per RFC 1071
34+
func ChecksumNoFold(b []byte, initial uint64) uint64 {
35+
// TODO: Explore SIMD and/or other assembly optimizations.
1036
tmp := make([]byte, 8)
1137
binary.NativeEndian.PutUint64(tmp, initial)
1238
ac := binary.BigEndian.Uint64(tmp)
@@ -83,20 +109,76 @@ func checksumNoFold(b []byte, initial uint64) uint64 {
83109
return binary.BigEndian.Uint64(tmp)
84110
}
85111

86-
func checksum(b []byte, initial uint64) uint16 {
87-
ac := checksumNoFold(b, initial)
112+
// Checksum performs final checksum computation per RFC 1071
113+
func Checksum(b []byte, initial uint64) uint16 {
114+
ac := ChecksumNoFold(b, initial)
88115
ac = (ac >> 16) + (ac & 0xffff)
89116
ac = (ac >> 16) + (ac & 0xffff)
90117
ac = (ac >> 16) + (ac & 0xffff)
91118
ac = (ac >> 16) + (ac & 0xffff)
92119
return uint16(ac)
93120
}
94121

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

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)