mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
net/packet: implement methods for rewriting v6 addresses
Implements the ability for the address-rewriting code to support rewriting IPv6 addresses. Specifically, UpdateSrcAddr & UpdateDstAddr. Signed-off-by: Tom DNetto <tom@tailscale.com> Updates https://github.com/tailscale/corp/issues/11202
This commit is contained in:
parent
c26d91d6bd
commit
656a77ab4e
@ -17,6 +17,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/golang/protobuf/proto from github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header
|
||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
||||
@ -78,6 +79,22 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
google.golang.org/protobuf/runtime/protoimpl from github.com/golang/protobuf/proto+
|
||||
google.golang.org/protobuf/types/descriptorpb from google.golang.org/protobuf/reflect/protodesc
|
||||
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
|
||||
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/buffer
|
||||
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip+
|
||||
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs
|
||||
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/buffer
|
||||
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
||||
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from tailscale.com/net/packet
|
||||
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header
|
||||
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
|
||||
nhooyr.io/websocket from tailscale.com/cmd/derper+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
nhooyr.io/websocket/internal/xsync from nhooyr.io/websocket
|
||||
|
@ -17,6 +17,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||
L 💣 github.com/godbus/dbus/v5 from github.com/coreos/go-systemd/v22/dbus
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header
|
||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
L 💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
L 💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
||||
@ -64,6 +65,22 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
go4.org/netipx from tailscale.com/wgengine/filter+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+
|
||||
gopkg.in/yaml.v2 from sigs.k8s.io/yaml
|
||||
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/buffer
|
||||
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip+
|
||||
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs
|
||||
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/buffer
|
||||
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
||||
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from tailscale.com/net/packet
|
||||
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header
|
||||
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
|
||||
k8s.io/client-go/util/homedir from tailscale.com/cmd/tailscale/cli
|
||||
nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
nhooyr.io/websocket/internal/errd from nhooyr.io/websocket
|
||||
|
@ -238,13 +238,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/health/healthmsg from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/hostinfo from tailscale.com/control/controlclient+
|
||||
tailscale.com/ipn from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnserver+
|
||||
💣 tailscale.com/ipn/ipnauth from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/ipn/ipnlocal from tailscale.com/ssh/tailssh+
|
||||
tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/ipn/ipnstate from tailscale.com/control/controlclient+
|
||||
tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver
|
||||
tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/ipn/store from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/ipn/store from tailscale.com/ipn/ipnlocal+
|
||||
L tailscale.com/ipn/store/awsstore from tailscale.com/ipn/store
|
||||
L tailscale.com/ipn/store/kubestore from tailscale.com/ipn/store
|
||||
tailscale.com/ipn/store/mem from tailscale.com/ipn/store+
|
||||
@ -347,7 +347,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+
|
||||
W tailscale.com/util/pidowner from tailscale.com/ipn/ipnauth
|
||||
tailscale.com/util/racebuild from tailscale.com/logpolicy
|
||||
tailscale.com/util/rands from tailscale.com/ipn/localapi+
|
||||
tailscale.com/util/rands from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/util/ringbuffer from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/util/set from tailscale.com/health+
|
||||
tailscale.com/util/singleflight from tailscale.com/control/controlclient+
|
||||
@ -508,7 +508,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
regexp from github.com/coreos/go-iptables/iptables+
|
||||
regexp/syntax from regexp
|
||||
runtime/debug from github.com/klauspost/compress/zstd+
|
||||
runtime/pprof from net/http/pprof+
|
||||
runtime/pprof from tailscale.com/ipn/ipnlocal+
|
||||
runtime/trace from net/http/pprof
|
||||
slices from tailscale.com/wgengine/magicsock+
|
||||
sort from compress/flate+
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
@ -453,11 +455,14 @@ func (q *Parsed) IsEchoResponse() bool {
|
||||
}
|
||||
|
||||
// UpdateSrcAddr updates the source address in the packet buffer (e.g. during
|
||||
// SNAT). It also updates the checksum. Currently (2022-12-10) only TCP/UDP/ICMP
|
||||
// over IPv4 is supported. It panics if called with IPv6 addr.
|
||||
// SNAT). It also updates the checksum. Currently (2023-09-22) only TCP/UDP/ICMP
|
||||
// is supported. It panics if provided with an address in a different
|
||||
// family to the parsed packet.
|
||||
func (q *Parsed) UpdateSrcAddr(src netip.Addr) {
|
||||
if q.IPVersion != 4 || src.Is6() {
|
||||
panic("UpdateSrcAddr: only IPv4 is supported")
|
||||
if src.Is6() && q.IPVersion != 6 {
|
||||
panic("UpdateSrcAddr: cannot write IPv6 address to v4 packet")
|
||||
} else if src.Is4() && q.IPVersion != 4 {
|
||||
panic("UpdateSrcAddr: cannot write IPv4 address to v6 packet")
|
||||
}
|
||||
q.CaptureMeta.DidSNAT = true
|
||||
q.CaptureMeta.OriginalSrc = q.Src
|
||||
@ -466,19 +471,27 @@ func (q *Parsed) UpdateSrcAddr(src netip.Addr) {
|
||||
q.Src = netip.AddrPortFrom(src, q.Src.Port())
|
||||
|
||||
b := q.Buffer()
|
||||
v4 := src.As4()
|
||||
copy(b[12:16], v4[:])
|
||||
updateV4PacketChecksums(q, old, src)
|
||||
if src.Is6() {
|
||||
v6 := src.As16()
|
||||
copy(b[8:24], v6[:])
|
||||
updateV6PacketChecksums(q, old, src)
|
||||
} else {
|
||||
v4 := src.As4()
|
||||
copy(b[12:16], v4[:])
|
||||
updateV4PacketChecksums(q, old, src)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateDstAddr updates the source address in the packet buffer (e.g. during
|
||||
// UpdateDstAddr updates the destination address in the packet buffer (e.g. during
|
||||
// DNAT). It also updates the checksum. Currently (2022-12-10) only TCP/UDP/ICMP
|
||||
// over IPv4 is supported. It panics if called with IPv6 addr.
|
||||
// is supported. It panics if provided with an address in a different
|
||||
// family to the parsed packet.
|
||||
func (q *Parsed) UpdateDstAddr(dst netip.Addr) {
|
||||
if q.IPVersion != 4 || dst.Is6() {
|
||||
panic("UpdateDstAddr: only IPv4 is supported")
|
||||
if dst.Is6() && q.IPVersion != 6 {
|
||||
panic("UpdateDstAddr: cannot write IPv6 address to v4 packet")
|
||||
} else if dst.Is4() && q.IPVersion != 4 {
|
||||
panic("UpdateDstAddr: cannot write IPv4 address to v6 packet")
|
||||
}
|
||||
|
||||
q.CaptureMeta.DidDNAT = true
|
||||
q.CaptureMeta.OriginalDst = q.Dst
|
||||
|
||||
@ -486,9 +499,15 @@ func (q *Parsed) UpdateDstAddr(dst netip.Addr) {
|
||||
q.Dst = netip.AddrPortFrom(dst, q.Dst.Port())
|
||||
|
||||
b := q.Buffer()
|
||||
v4 := dst.As4()
|
||||
copy(b[16:20], v4[:])
|
||||
updateV4PacketChecksums(q, old, dst)
|
||||
if dst.Is6() {
|
||||
v6 := dst.As16()
|
||||
copy(b[24:36], v6[:])
|
||||
updateV6PacketChecksums(q, old, dst)
|
||||
} else {
|
||||
v4 := dst.As4()
|
||||
copy(b[16:20], v4[:])
|
||||
updateV4PacketChecksums(q, old, dst)
|
||||
}
|
||||
}
|
||||
|
||||
// EchoIDSeq extracts the identifier/sequence bytes from an ICMP Echo response,
|
||||
@ -572,13 +591,13 @@ func updateV4PacketChecksums(p *Parsed, old, new netip.Addr) {
|
||||
tr := p.Transport()
|
||||
switch p.IPProto {
|
||||
case ipproto.UDP, ipproto.DCCP:
|
||||
if len(tr) < 8 {
|
||||
if len(tr) < header.UDPMinimumSize {
|
||||
// Not enough space for a UDP header.
|
||||
return
|
||||
}
|
||||
updateV4Checksum(tr[6:8], o4[:], n4[:])
|
||||
case ipproto.TCP:
|
||||
if len(tr) < 18 {
|
||||
if len(tr) < header.TCPMinimumSize {
|
||||
// Not enough space for a TCP header.
|
||||
return
|
||||
}
|
||||
@ -596,6 +615,39 @@ func updateV4PacketChecksums(p *Parsed, old, new netip.Addr) {
|
||||
}
|
||||
}
|
||||
|
||||
// updateV6PacketChecksums updates the checksums in the packet buffer.
|
||||
// p is modified in place.
|
||||
// If p.IPProto is unknown, no checksums are updated.
|
||||
func updateV6PacketChecksums(p *Parsed, old, new netip.Addr) {
|
||||
if len(p.Buffer()) < 40 {
|
||||
// Not enough space for an IPv6 header.
|
||||
return
|
||||
}
|
||||
o6, n6 := tcpip.AddrFrom16Slice(old.AsSlice()), tcpip.AddrFrom16Slice(new.AsSlice())
|
||||
|
||||
// Now update the transport layer checksums, where applicable.
|
||||
tr := p.Transport()
|
||||
switch p.IPProto {
|
||||
case ipproto.ICMPv6:
|
||||
if len(tr) < header.ICMPv6MinimumSize {
|
||||
return
|
||||
}
|
||||
header.ICMPv6(tr).UpdateChecksumPseudoHeaderAddress(o6, n6)
|
||||
case ipproto.UDP, ipproto.DCCP:
|
||||
if len(tr) < header.UDPMinimumSize {
|
||||
return
|
||||
}
|
||||
header.UDP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
|
||||
case ipproto.TCP:
|
||||
if len(tr) < header.TCPMinimumSize {
|
||||
return
|
||||
}
|
||||
header.TCP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
|
||||
case ipproto.SCTP:
|
||||
// No transport layer update required.
|
||||
}
|
||||
}
|
||||
|
||||
// updateV4Checksum calculates and updates the checksum in the packet buffer for
|
||||
// a change between old and new. The oldSum must point to the 16-bit checksum
|
||||
// field in the packet buffer that holds the old checksum value, it will be
|
||||
|
@ -13,6 +13,9 @@ import (
|
||||
"testing"
|
||||
"unicode"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/util/must"
|
||||
@ -45,7 +48,7 @@ func fullHeaderChecksumV4(b []byte) uint16 {
|
||||
return ^uint16(s)
|
||||
}
|
||||
|
||||
func TestHeaderChecksums(t *testing.T) {
|
||||
func TestHeaderChecksumsV4(t *testing.T) {
|
||||
// This is not a good enough test, because it doesn't
|
||||
// check the various packet types or the many edge cases
|
||||
// of the checksum algorithm. But it's a start.
|
||||
@ -109,6 +112,108 @@ func TestHeaderChecksums(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNatChecksumsV6UDP(t *testing.T) {
|
||||
a1, a2 := netip.MustParseAddr("a::1"), netip.MustParseAddr("b::1")
|
||||
|
||||
// Make a fake UDP packet with 32 bytes of zeros as the datagram payload.
|
||||
b := header.IPv6(make([]byte, header.IPv6MinimumSize+header.UDPMinimumSize+32))
|
||||
b.Encode(&header.IPv6Fields{
|
||||
PayloadLength: header.UDPMinimumSize + 32,
|
||||
TransportProtocol: header.UDPProtocolNumber,
|
||||
HopLimit: 16,
|
||||
SrcAddr: tcpip.AddrFrom16Slice(a1.AsSlice()),
|
||||
DstAddr: tcpip.AddrFrom16Slice(a2.AsSlice()),
|
||||
})
|
||||
udp := header.UDP(b[header.IPv6MinimumSize:])
|
||||
udp.Encode(&header.UDPFields{
|
||||
SrcPort: 42,
|
||||
DstPort: 43,
|
||||
Length: header.UDPMinimumSize + 32,
|
||||
})
|
||||
xsum := header.PseudoHeaderChecksum(
|
||||
header.UDPProtocolNumber,
|
||||
tcpip.AddrFrom16Slice(a1.AsSlice()),
|
||||
tcpip.AddrFrom16Slice(a2.AsSlice()),
|
||||
uint16(header.UDPMinimumSize+32),
|
||||
)
|
||||
xsum = checksum.Checksum(b.Payload()[header.UDPMinimumSize:], xsum)
|
||||
udp.SetChecksum(^udp.CalculateChecksum(xsum))
|
||||
if !udp.IsChecksumValid(tcpip.AddrFrom16Slice(a1.AsSlice()), tcpip.AddrFrom16Slice(a2.AsSlice()), checksum.Checksum(b.Payload()[header.UDPMinimumSize:], 0)) {
|
||||
t.Fatal("test broken; initial packet has incorrect checksum")
|
||||
}
|
||||
|
||||
// Parse the packet.
|
||||
var p Parsed
|
||||
p.Decode(b)
|
||||
t.Log(p.String())
|
||||
|
||||
// Update the source address of the packet to be the same as the dest.
|
||||
p.UpdateSrcAddr(a2)
|
||||
if !udp.IsChecksumValid(tcpip.AddrFrom16Slice(a2.AsSlice()), tcpip.AddrFrom16Slice(a2.AsSlice()), checksum.Checksum(b.Payload()[header.UDPMinimumSize:], 0)) {
|
||||
t.Fatal("incorrect checksum after updating source address")
|
||||
}
|
||||
|
||||
// Update the dest address of the packet to be the original source address.
|
||||
p.UpdateDstAddr(a1)
|
||||
if !udp.IsChecksumValid(tcpip.AddrFrom16Slice(a2.AsSlice()), tcpip.AddrFrom16Slice(a1.AsSlice()), checksum.Checksum(b.Payload()[header.UDPMinimumSize:], 0)) {
|
||||
t.Fatal("incorrect checksum after updating destination address")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNatChecksumsV6TCP(t *testing.T) {
|
||||
a1, a2 := netip.MustParseAddr("a::1"), netip.MustParseAddr("b::1")
|
||||
|
||||
// Make a fake TCP packet with no payload.
|
||||
b := header.IPv6(make([]byte, header.IPv6MinimumSize+header.TCPMinimumSize))
|
||||
b.Encode(&header.IPv6Fields{
|
||||
PayloadLength: header.TCPMinimumSize,
|
||||
TransportProtocol: header.TCPProtocolNumber,
|
||||
HopLimit: 16,
|
||||
SrcAddr: tcpip.AddrFrom16Slice(a1.AsSlice()),
|
||||
DstAddr: tcpip.AddrFrom16Slice(a2.AsSlice()),
|
||||
})
|
||||
tcp := header.TCP(b[header.IPv6MinimumSize:])
|
||||
tcp.Encode(&header.TCPFields{
|
||||
SrcPort: 42,
|
||||
DstPort: 43,
|
||||
SeqNum: 1,
|
||||
AckNum: 2,
|
||||
DataOffset: header.TCPMinimumSize,
|
||||
Flags: 3,
|
||||
WindowSize: 4,
|
||||
Checksum: 0,
|
||||
UrgentPointer: 5,
|
||||
})
|
||||
xsum := header.PseudoHeaderChecksum(
|
||||
header.TCPProtocolNumber,
|
||||
tcpip.AddrFrom16Slice(a1.AsSlice()),
|
||||
tcpip.AddrFrom16Slice(a2.AsSlice()),
|
||||
uint16(header.TCPMinimumSize),
|
||||
)
|
||||
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
|
||||
|
||||
if !tcp.IsChecksumValid(tcpip.AddrFrom16Slice(a1.AsSlice()), tcpip.AddrFrom16Slice(a2.AsSlice()), 0, 0) {
|
||||
t.Fatal("test broken; initial packet has incorrect checksum")
|
||||
}
|
||||
|
||||
// Parse the packet.
|
||||
var p Parsed
|
||||
p.Decode(b)
|
||||
t.Log(p.String())
|
||||
|
||||
// Update the source address of the packet to be the same as the dest.
|
||||
p.UpdateSrcAddr(a2)
|
||||
if !tcp.IsChecksumValid(tcpip.AddrFrom16Slice(a2.AsSlice()), tcpip.AddrFrom16Slice(a2.AsSlice()), 0, 0) {
|
||||
t.Fatal("incorrect checksum after updating source address")
|
||||
}
|
||||
|
||||
// Update the dest address of the packet to be the original source address.
|
||||
p.UpdateDstAddr(a1)
|
||||
if !tcp.IsChecksumValid(tcpip.AddrFrom16Slice(a2.AsSlice()), tcpip.AddrFrom16Slice(a1.AsSlice()), 0, 0) {
|
||||
t.Fatal("incorrect checksum after updating destination address")
|
||||
}
|
||||
}
|
||||
|
||||
func mustIPPort(s string) netip.AddrPort {
|
||||
ipp, err := netip.ParseAddrPort(s)
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user