mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 21:15:39 +00:00
cb96b14bf4
Upstream netaddr has a change that makes it alloc-free, so it's safe to use in hot codepaths. This gets rid of one of the many IP types in our codebase. Performance is currently worse across the board. This is likely due in part to netaddr.IP being a larger value type (4b -> 24b for IPv4, 16b -> 24b for IPv6), and in other part due to missing low-hanging fruit optimizations in netaddr. However, the regression is less bad than it looks at first glance, because we'd micro-optimized packet.IP* in the past few weeks. This change drops us back to roughly where we were at the 1.2 release, but with the benefit of a significant code and architectural simplification. name old time/op new time/op delta pkg:tailscale.com/net/packet goos:linux goarch:amd64 Decode/tcp4-8 12.2ns ± 5% 29.7ns ± 2% +142.32% (p=0.008 n=5+5) Decode/tcp6-8 12.6ns ± 3% 65.1ns ± 2% +418.47% (p=0.008 n=5+5) Decode/udp4-8 11.8ns ± 3% 30.5ns ± 2% +157.94% (p=0.008 n=5+5) Decode/udp6-8 27.1ns ± 1% 65.7ns ± 2% +142.36% (p=0.016 n=4+5) Decode/icmp4-8 24.6ns ± 2% 30.5ns ± 2% +23.65% (p=0.016 n=4+5) Decode/icmp6-8 22.9ns ±51% 65.5ns ± 2% +186.19% (p=0.008 n=5+5) Decode/igmp-8 18.1ns ±44% 30.2ns ± 1% +66.89% (p=0.008 n=5+5) Decode/unknown-8 20.8ns ± 1% 10.6ns ± 9% -49.11% (p=0.016 n=4+5) pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64 Filter/icmp4-8 30.5ns ± 1% 77.9ns ± 3% +155.01% (p=0.008 n=5+5) Filter/tcp4_syn_in-8 43.7ns ± 3% 123.0ns ± 3% +181.72% (p=0.008 n=5+5) Filter/tcp4_syn_out-8 24.5ns ± 2% 45.7ns ± 6% +86.22% (p=0.008 n=5+5) Filter/udp4_in-8 64.8ns ± 1% 210.0ns ± 2% +223.87% (p=0.008 n=5+5) Filter/udp4_out-8 119ns ± 0% 278ns ± 0% +133.78% (p=0.016 n=4+5) Filter/icmp6-8 40.3ns ± 2% 204.4ns ± 4% +407.70% (p=0.008 n=5+5) Filter/tcp6_syn_in-8 35.3ns ± 3% 199.2ns ± 2% +464.95% (p=0.008 n=5+5) Filter/tcp6_syn_out-8 32.8ns ± 2% 81.0ns ± 2% +147.10% (p=0.008 n=5+5) Filter/udp6_in-8 106ns ± 2% 290ns ± 2% +174.48% (p=0.008 n=5+5) Filter/udp6_out-8 184ns ± 2% 314ns ± 3% +70.43% (p=0.016 n=4+5) pkg:tailscale.com/wgengine/tstun goos:linux goarch:amd64 Write-8 9.02ns ± 3% 8.92ns ± 1% ~ (p=0.421 n=5+5) name old alloc/op new alloc/op delta pkg:tailscale.com/net/packet goos:linux goarch:amd64 Decode/tcp4-8 0.00B 0.00B ~ (all equal) Decode/tcp6-8 0.00B 0.00B ~ (all equal) Decode/udp4-8 0.00B 0.00B ~ (all equal) Decode/udp6-8 0.00B 0.00B ~ (all equal) Decode/icmp4-8 0.00B 0.00B ~ (all equal) Decode/icmp6-8 0.00B 0.00B ~ (all equal) Decode/igmp-8 0.00B 0.00B ~ (all equal) Decode/unknown-8 0.00B 0.00B ~ (all equal) pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64 Filter/icmp4-8 0.00B 0.00B ~ (all equal) Filter/tcp4_syn_in-8 0.00B 0.00B ~ (all equal) Filter/tcp4_syn_out-8 0.00B 0.00B ~ (all equal) Filter/udp4_in-8 0.00B 0.00B ~ (all equal) Filter/udp4_out-8 16.0B ± 0% 64.0B ± 0% +300.00% (p=0.008 n=5+5) Filter/icmp6-8 0.00B 0.00B ~ (all equal) Filter/tcp6_syn_in-8 0.00B 0.00B ~ (all equal) Filter/tcp6_syn_out-8 0.00B 0.00B ~ (all equal) Filter/udp6_in-8 0.00B 0.00B ~ (all equal) Filter/udp6_out-8 48.0B ± 0% 64.0B ± 0% +33.33% (p=0.008 n=5+5) name old allocs/op new allocs/op delta pkg:tailscale.com/net/packet goos:linux goarch:amd64 Decode/tcp4-8 0.00 0.00 ~ (all equal) Decode/tcp6-8 0.00 0.00 ~ (all equal) Decode/udp4-8 0.00 0.00 ~ (all equal) Decode/udp6-8 0.00 0.00 ~ (all equal) Decode/icmp4-8 0.00 0.00 ~ (all equal) Decode/icmp6-8 0.00 0.00 ~ (all equal) Decode/igmp-8 0.00 0.00 ~ (all equal) Decode/unknown-8 0.00 0.00 ~ (all equal) pkg:tailscale.com/wgengine/filter goos:linux goarch:amd64 Filter/icmp4-8 0.00 0.00 ~ (all equal) Filter/tcp4_syn_in-8 0.00 0.00 ~ (all equal) Filter/tcp4_syn_out-8 0.00 0.00 ~ (all equal) Filter/udp4_in-8 0.00 0.00 ~ (all equal) Filter/udp4_out-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) Filter/icmp6-8 0.00 0.00 ~ (all equal) Filter/tcp6_syn_in-8 0.00 0.00 ~ (all equal) Filter/tcp6_syn_out-8 0.00 0.00 ~ (all equal) Filter/udp6_in-8 0.00 0.00 ~ (all equal) Filter/udp6_out-8 1.00 ± 0% 1.00 ± 0% ~ (all equal) Signed-off-by: David Anderson <danderson@tailscale.com>
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package packet
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
"inet.af/netaddr"
|
|
)
|
|
|
|
// ip4HeaderLength is the length of an IPv4 header with no IP options.
|
|
const ip4HeaderLength = 20
|
|
|
|
// IP4Header represents an IPv4 packet header.
|
|
type IP4Header struct {
|
|
IPProto IPProto
|
|
IPID uint16
|
|
Src netaddr.IP
|
|
Dst netaddr.IP
|
|
}
|
|
|
|
// Len implements Header.
|
|
func (h IP4Header) Len() int {
|
|
return ip4HeaderLength
|
|
}
|
|
|
|
var errWrongFamily = errors.New("wrong address family for src/dst IP")
|
|
|
|
// Marshal implements Header.
|
|
func (h IP4Header) Marshal(buf []byte) error {
|
|
if len(buf) < h.Len() {
|
|
return errSmallBuffer
|
|
}
|
|
if len(buf) > maxPacketLength {
|
|
return errLargePacket
|
|
}
|
|
if !h.Src.Is4() || !h.Dst.Is4() {
|
|
return errWrongFamily
|
|
}
|
|
|
|
buf[0] = 0x40 | (byte(h.Len() >> 2)) // IPv4 + IHL
|
|
buf[1] = 0x00 // DSCP + ECN
|
|
binary.BigEndian.PutUint16(buf[2:4], uint16(len(buf))) // Total length
|
|
binary.BigEndian.PutUint16(buf[4:6], h.IPID) // ID
|
|
binary.BigEndian.PutUint16(buf[6:8], 0) // Flags + fragment offset
|
|
buf[8] = 64 // TTL
|
|
buf[9] = uint8(h.IPProto) // Inner protocol
|
|
// Blank checksum. This is necessary even though we overwrite
|
|
// it later, because the checksum computation runs over these
|
|
// bytes and expects them to be zero.
|
|
binary.BigEndian.PutUint16(buf[10:12], 0)
|
|
src := h.Src.As4()
|
|
dst := h.Dst.As4()
|
|
copy(buf[12:16], src[:])
|
|
copy(buf[16:20], dst[:])
|
|
|
|
binary.BigEndian.PutUint16(buf[10:12], ip4Checksum(buf[0:20])) // Checksum
|
|
|
|
return nil
|
|
}
|
|
|
|
// ToResponse implements Header.
|
|
func (h *IP4Header) ToResponse() {
|
|
h.Src, h.Dst = h.Dst, h.Src
|
|
// Flip the bits in the IPID. If incoming IPIDs are distinct, so are these.
|
|
h.IPID = ^h.IPID
|
|
}
|
|
|
|
// ip4Checksum computes an IPv4 checksum, as specified in
|
|
// https://tools.ietf.org/html/rfc1071
|
|
func ip4Checksum(b []byte) uint16 {
|
|
var ac uint32
|
|
i := 0
|
|
n := len(b)
|
|
for n >= 2 {
|
|
ac += uint32(binary.BigEndian.Uint16(b[i : i+2]))
|
|
n -= 2
|
|
i += 2
|
|
}
|
|
if n == 1 {
|
|
ac += uint32(b[i]) << 8
|
|
}
|
|
for (ac >> 16) > 0 {
|
|
ac = (ac >> 16) + (ac & 0xffff)
|
|
}
|
|
return uint16(^ac)
|
|
}
|
|
|
|
// ip4PseudoHeaderOffset is the number of bytes by which the IPv4 UDP
|
|
// pseudo-header is smaller than the real IPv4 header.
|
|
const ip4PseudoHeaderOffset = 8
|
|
|
|
// marshalPseudo serializes h into buf in the "pseudo-header" form
|
|
// required when calculating UDP checksums. The pseudo-header starts
|
|
// at buf[ip4PseudoHeaderOffset] so as to abut the following UDP
|
|
// header, while leaving enough space in buf for a full IPv4 header.
|
|
func (h IP4Header) marshalPseudo(buf []byte) error {
|
|
if len(buf) < h.Len() {
|
|
return errSmallBuffer
|
|
}
|
|
if len(buf) > maxPacketLength {
|
|
return errLargePacket
|
|
}
|
|
|
|
length := len(buf) - h.Len()
|
|
src, dst := h.Src.As4(), h.Dst.As4()
|
|
copy(buf[8:12], src[:])
|
|
copy(buf[12:16], dst[:])
|
|
buf[16] = 0x0
|
|
buf[17] = uint8(h.IPProto)
|
|
binary.BigEndian.PutUint16(buf[18:20], uint16(length))
|
|
return nil
|
|
}
|