net/packet: remove the custom IP4/IP6 types in favor of netaddr.IP.
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>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
d0baece5fa
commit
cb96b14bf4
+81
-93
@@ -25,45 +25,33 @@ type Filter struct {
|
||||
// tailscale must have a destination within local4 or local6,
|
||||
// regardless of the policy filter below. Zero values reject
|
||||
// all incoming traffic.
|
||||
local4 []net4
|
||||
local6 []net6
|
||||
local4 []netaddr.IPPrefix
|
||||
local6 []netaddr.IPPrefix
|
||||
// matches4 and matches6 are lists of match->action rules
|
||||
// applied to all packets arriving over tailscale
|
||||
// tunnels. Matches are checked in order, and processing stops
|
||||
// at the first matching rule. The default policy if no rules
|
||||
// match is to drop the packet.
|
||||
matches4 matches4
|
||||
matches6 matches6
|
||||
matches4 matches
|
||||
matches6 matches
|
||||
// state is the connection tracking state attached to this
|
||||
// filter. It is used to allow incoming traffic that is a response
|
||||
// to an outbound connection that this node made, even if those
|
||||
// incoming packets don't get accepted by matches above.
|
||||
state4 *filterState
|
||||
state6 *filterState
|
||||
state *filterState
|
||||
}
|
||||
|
||||
// tuple4 is a 4-tuple of source and destination IPv4 and port. It's
|
||||
// used as a lookup key in filterState.
|
||||
type tuple4 struct {
|
||||
SrcIP packet.IP4
|
||||
DstIP packet.IP4
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
}
|
||||
|
||||
// tuple6 is a 4-tuple of source and destination IPv6 and port. It's
|
||||
// used as a lookup key in filterState.
|
||||
type tuple6 struct {
|
||||
SrcIP packet.IP6
|
||||
DstIP packet.IP6
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
// tuple is a 4-tuple of source and destination IP and port. It's used
|
||||
// as a lookup key in filterState.
|
||||
type tuple struct {
|
||||
Src netaddr.IPPort
|
||||
Dst netaddr.IPPort
|
||||
}
|
||||
|
||||
// filterState is a state cache of past seen packets.
|
||||
type filterState struct {
|
||||
mu sync.Mutex
|
||||
lru *lru.Cache // of tuple4 or tuple6
|
||||
lru *lru.Cache // of tuple
|
||||
}
|
||||
|
||||
// lruMax is the size of the LRU cache in filterState.
|
||||
@@ -148,30 +136,58 @@ func NewAllowNone(logf logger.Logf) *Filter {
|
||||
// shares state with the previous one, to enable changing rules at
|
||||
// runtime without breaking existing stateful flows.
|
||||
func New(matches []Match, localNets []netaddr.IPPrefix, shareStateWith *Filter, logf logger.Logf) *Filter {
|
||||
var state4, state6 *filterState
|
||||
var state *filterState
|
||||
if shareStateWith != nil {
|
||||
state4 = shareStateWith.state4
|
||||
state6 = shareStateWith.state6
|
||||
state = shareStateWith.state
|
||||
} else {
|
||||
state4 = &filterState{
|
||||
lru: lru.New(lruMax),
|
||||
}
|
||||
state6 = &filterState{
|
||||
state = &filterState{
|
||||
lru: lru.New(lruMax),
|
||||
}
|
||||
}
|
||||
f := &Filter{
|
||||
logf: logf,
|
||||
matches4: newMatches4(matches),
|
||||
matches6: newMatches6(matches),
|
||||
local4: nets4FromIPPrefixes(localNets),
|
||||
local6: nets6FromIPPrefixes(localNets),
|
||||
state4: state4,
|
||||
state6: state6,
|
||||
matches4: matchesFamily(matches, netaddr.IP.Is4),
|
||||
matches6: matchesFamily(matches, netaddr.IP.Is6),
|
||||
local4: netsFamily(localNets, netaddr.IP.Is4),
|
||||
local6: netsFamily(localNets, netaddr.IP.Is6),
|
||||
state: state,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func netsFamily(nets []netaddr.IPPrefix, keep func(netaddr.IP) bool) []netaddr.IPPrefix {
|
||||
var ret []netaddr.IPPrefix
|
||||
for _, net := range nets {
|
||||
if keep(net.IP) {
|
||||
ret = append(ret, net)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// matchesFamily returns the subset of ms for which keep(srcNet.IP)
|
||||
// and keep(dstNet.IP) are both true.
|
||||
func matchesFamily(ms matches, keep func(netaddr.IP) bool) matches {
|
||||
var ret matches
|
||||
for _, m := range ms {
|
||||
var retm Match
|
||||
for _, src := range m.Srcs {
|
||||
if keep(src.IP) {
|
||||
retm.Srcs = append(retm.Srcs, src)
|
||||
}
|
||||
}
|
||||
for _, dst := range m.Dsts {
|
||||
if keep(dst.Net.IP) {
|
||||
retm.Dsts = append(retm.Dsts, dst)
|
||||
}
|
||||
}
|
||||
if len(retm.Srcs) > 0 && len(retm.Dsts) > 0 {
|
||||
ret = append(ret, retm)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func maybeHexdump(flag RunFlags, b []byte) string {
|
||||
if flag == 0 {
|
||||
return ""
|
||||
@@ -229,19 +245,17 @@ func (f *Filter) CheckTCP(srcIP, dstIP netaddr.IP, dstPort uint16) Response {
|
||||
return Drop
|
||||
case srcIP.Is4():
|
||||
pkt.IPVersion = 4
|
||||
pkt.SrcIP4 = packet.IP4FromNetaddr(srcIP)
|
||||
pkt.DstIP4 = packet.IP4FromNetaddr(dstIP)
|
||||
case srcIP.Is6():
|
||||
pkt.IPVersion = 6
|
||||
pkt.SrcIP6 = packet.IP6FromNetaddr(srcIP)
|
||||
pkt.DstIP6 = packet.IP6FromNetaddr(dstIP)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
pkt.Src.IP = srcIP
|
||||
pkt.Dst.IP = dstIP
|
||||
pkt.IPProto = packet.TCP
|
||||
pkt.TCPFlags = packet.TCPSyn
|
||||
pkt.SrcPort = 0
|
||||
pkt.DstPort = dstPort
|
||||
pkt.Src.Port = 0
|
||||
pkt.Dst.Port = dstPort
|
||||
|
||||
return f.RunIn(pkt, 0)
|
||||
}
|
||||
@@ -287,7 +301,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
|
||||
// A compromised peer could try to send us packets for
|
||||
// destinations we didn't explicitly advertise. This check is to
|
||||
// prevent that.
|
||||
if !ip4InList(q.DstIP4, f.local4) {
|
||||
if !ipInList(q.Dst.IP, f.local4) {
|
||||
return Drop, "destination not allowed"
|
||||
}
|
||||
|
||||
@@ -320,11 +334,11 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) {
|
||||
return Accept, "tcp ok"
|
||||
}
|
||||
case packet.UDP:
|
||||
t := tuple4{q.SrcIP4, q.DstIP4, q.SrcPort, q.DstPort}
|
||||
t := tuple{q.Src, q.Dst}
|
||||
|
||||
f.state4.mu.Lock()
|
||||
_, ok := f.state4.lru.Get(t)
|
||||
f.state4.mu.Unlock()
|
||||
f.state.mu.Lock()
|
||||
_, ok := f.state.lru.Get(t)
|
||||
f.state.mu.Unlock()
|
||||
|
||||
if ok {
|
||||
return Accept, "udp cached"
|
||||
@@ -342,7 +356,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
||||
// A compromised peer could try to send us packets for
|
||||
// destinations we didn't explicitly advertise. This check is to
|
||||
// prevent that.
|
||||
if !ip6InList(q.DstIP6, f.local6) {
|
||||
if !ipInList(q.Dst.IP, f.local6) {
|
||||
return Drop, "destination not allowed"
|
||||
}
|
||||
|
||||
@@ -375,11 +389,11 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) {
|
||||
return Accept, "tcp ok"
|
||||
}
|
||||
case packet.UDP:
|
||||
t := tuple6{q.SrcIP6, q.DstIP6, q.SrcPort, q.DstPort}
|
||||
t := tuple{q.Src, q.Dst}
|
||||
|
||||
f.state6.mu.Lock()
|
||||
_, ok := f.state6.lru.Get(t)
|
||||
f.state6.mu.Unlock()
|
||||
f.state.mu.Lock()
|
||||
_, ok := f.state.lru.Get(t)
|
||||
f.state.mu.Unlock()
|
||||
|
||||
if ok {
|
||||
return Accept, "udp cached"
|
||||
@@ -399,20 +413,11 @@ func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) {
|
||||
return Accept, "ok out"
|
||||
}
|
||||
|
||||
switch q.IPVersion {
|
||||
case 4:
|
||||
t := tuple4{q.DstIP4, q.SrcIP4, q.DstPort, q.SrcPort}
|
||||
var ti interface{} = t // allocate once, rather than twice inside mutex
|
||||
f.state4.mu.Lock()
|
||||
f.state4.lru.Add(ti, ti)
|
||||
f.state4.mu.Unlock()
|
||||
case 6:
|
||||
t := tuple6{q.DstIP6, q.SrcIP6, q.DstPort, q.SrcPort}
|
||||
var ti interface{} = t // allocate once, rather than twice inside mutex
|
||||
f.state6.mu.Lock()
|
||||
f.state6.lru.Add(ti, ti)
|
||||
f.state6.mu.Unlock()
|
||||
}
|
||||
t := tuple{q.Dst, q.Src}
|
||||
var ti interface{} = t // allocate once, rather than twice inside mutex
|
||||
f.state.mu.Lock()
|
||||
f.state.lru.Add(ti, ti)
|
||||
f.state.mu.Unlock()
|
||||
return Accept, "ok out"
|
||||
}
|
||||
|
||||
@@ -436,6 +441,8 @@ func (d direction) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
var gcpDNSAddr = netaddr.IPv4(169, 254, 169, 254)
|
||||
|
||||
// pre runs the direction-agnostic filter logic. dir is only used for
|
||||
// logging.
|
||||
func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
|
||||
@@ -448,25 +455,13 @@ func (f *Filter) pre(q *packet.Parsed, rf RunFlags, dir direction) Response {
|
||||
return Drop
|
||||
}
|
||||
|
||||
switch q.IPVersion {
|
||||
case 4:
|
||||
if q.DstIP4.IsMulticast() {
|
||||
f.logRateLimit(rf, q, dir, Drop, "multicast")
|
||||
return Drop
|
||||
}
|
||||
if q.DstIP4.IsMostLinkLocalUnicast() {
|
||||
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
|
||||
return Drop
|
||||
}
|
||||
case 6:
|
||||
if q.DstIP6.IsMulticast() {
|
||||
f.logRateLimit(rf, q, dir, Drop, "multicast")
|
||||
return Drop
|
||||
}
|
||||
if q.DstIP6.IsLinkLocalUnicast() {
|
||||
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
|
||||
return Drop
|
||||
}
|
||||
if q.Dst.IP.IsMulticast() {
|
||||
f.logRateLimit(rf, q, dir, Drop, "multicast")
|
||||
return Drop
|
||||
}
|
||||
if q.Dst.IP.IsLinkLocalUnicast() && q.Dst.IP != gcpDNSAddr {
|
||||
f.logRateLimit(rf, q, dir, Drop, "link-local-unicast")
|
||||
return Drop
|
||||
}
|
||||
|
||||
switch q.IPProto {
|
||||
@@ -493,12 +488,5 @@ func omitDropLogging(p *packet.Parsed, dir direction) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
switch p.IPVersion {
|
||||
case 4:
|
||||
return p.DstIP4.IsMulticast() || p.DstIP4.IsMostLinkLocalUnicast() || p.IPProto == packet.IGMP
|
||||
case 6:
|
||||
return p.DstIP6.IsMulticast() || p.DstIP6.IsLinkLocalUnicast()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return p.Dst.IP.IsMulticast() || (p.Dst.IP.IsLinkLocalUnicast() && p.Dst.IP != gcpDNSAddr) || p.IPProto == packet.IGMP
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ func TestFilter(t *testing.T) {
|
||||
if test.p.IPProto == packet.TCP {
|
||||
var got Response
|
||||
if test.p.IPVersion == 4 {
|
||||
got = acl.CheckTCP(test.p.SrcIP4.Netaddr(), test.p.DstIP4.Netaddr(), test.p.DstPort)
|
||||
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
|
||||
} else {
|
||||
got = acl.CheckTCP(test.p.SrcIP6.Netaddr(), test.p.DstIP6.Netaddr(), test.p.DstPort)
|
||||
got = acl.CheckTCP(test.p.Src.IP, test.p.Dst.IP, test.p.Dst.Port)
|
||||
}
|
||||
if test.want != got {
|
||||
t.Errorf("#%d CheckTCP got=%v want=%v packet:%v", i, got, test.want, test.p)
|
||||
@@ -345,19 +345,19 @@ func TestOmitDropLogging(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "v4_multicast_out_low",
|
||||
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("224.0.0.0")},
|
||||
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("224.0.0.0:0")},
|
||||
dir: out,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "v4_multicast_out_high",
|
||||
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("239.255.255.255")},
|
||||
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("239.255.255.255:0")},
|
||||
dir: out,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "v4_link_local_unicast",
|
||||
pkt: &packet.Parsed{IPVersion: 4, DstIP4: mustIP4("169.254.1.2")},
|
||||
pkt: &packet.Parsed{IPVersion: 4, Dst: mustIPPort("169.254.1.2:0")},
|
||||
dir: out,
|
||||
want: true,
|
||||
},
|
||||
@@ -387,18 +387,16 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
|
||||
var ret packet.Parsed
|
||||
ret.Decode(dummyPacket)
|
||||
ret.IPProto = proto
|
||||
ret.SrcPort = sport
|
||||
ret.DstPort = dport
|
||||
ret.Src.IP = sip
|
||||
ret.Src.Port = sport
|
||||
ret.Dst.IP = dip
|
||||
ret.Dst.Port = dport
|
||||
ret.TCPFlags = packet.TCPSyn
|
||||
|
||||
if sip.Is4() {
|
||||
ret.IPVersion = 4
|
||||
ret.SrcIP4 = packet.IP4FromNetaddr(sip)
|
||||
ret.DstIP4 = packet.IP4FromNetaddr(dip)
|
||||
} else {
|
||||
ret.IPVersion = 6
|
||||
ret.SrcIP6 = packet.IP6FromNetaddr(sip)
|
||||
ret.DstIP6 = packet.IP6FromNetaddr(dip)
|
||||
}
|
||||
|
||||
return ret
|
||||
@@ -407,8 +405,8 @@ func parsed(proto packet.IPProto, src, dst string, sport, dport uint16) packet.P
|
||||
func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen int) []byte {
|
||||
u := packet.UDP6Header{
|
||||
IP6Header: packet.IP6Header{
|
||||
SrcIP: packet.IP6FromNetaddr(mustIP(src)),
|
||||
DstIP: packet.IP6FromNetaddr(mustIP(dst)),
|
||||
Src: mustIP(src),
|
||||
Dst: mustIP(dst),
|
||||
},
|
||||
SrcPort: sport,
|
||||
DstPort: dport,
|
||||
@@ -436,8 +434,8 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in
|
||||
func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength int) []byte {
|
||||
u := packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
SrcIP: packet.IP4FromNetaddr(mustIP(src)),
|
||||
DstIP: packet.IP4FromNetaddr(mustIP(dst)),
|
||||
Src: mustIP(src),
|
||||
Dst: mustIP(dst),
|
||||
},
|
||||
SrcPort: sport,
|
||||
DstPort: dport,
|
||||
@@ -488,12 +486,12 @@ func parseHexPkt(t *testing.T, h string) *packet.Parsed {
|
||||
return p
|
||||
}
|
||||
|
||||
func mustIP4(s string) packet.IP4 {
|
||||
ip, err := netaddr.ParseIP(s)
|
||||
func mustIPPort(s string) netaddr.IPPort {
|
||||
ipp, err := netaddr.ParseIPPort(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return packet.IP4FromNetaddr(ip)
|
||||
return ipp
|
||||
}
|
||||
|
||||
func pfx(strs ...string) (ret []netaddr.IPPrefix) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
)
|
||||
|
||||
// PortRange is a range of TCP and UDP ports.
|
||||
@@ -71,3 +72,46 @@ func (m Match) String() string {
|
||||
}
|
||||
return fmt.Sprintf("%v=>%v", ss, ds)
|
||||
}
|
||||
|
||||
type matches []Match
|
||||
|
||||
func (ms matches) match(q *packet.Parsed) bool {
|
||||
for _, m := range ms {
|
||||
if !ipInList(q.Src.IP, m.Srcs) {
|
||||
continue
|
||||
}
|
||||
for _, dst := range m.Dsts {
|
||||
if !dst.Net.Contains(q.Dst.IP) {
|
||||
continue
|
||||
}
|
||||
if !dst.Ports.contains(q.Dst.Port) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms matches) matchIPsOnly(q *packet.Parsed) bool {
|
||||
for _, m := range ms {
|
||||
if !ipInList(q.Src.IP, m.Srcs) {
|
||||
continue
|
||||
}
|
||||
for _, dst := range m.Dsts {
|
||||
if dst.Net.Contains(q.Dst.IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ipInList(ip netaddr.IP, netlist []netaddr.IPPrefix) bool {
|
||||
for _, net := range netlist {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
// 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 filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strings"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
)
|
||||
|
||||
type net4 struct {
|
||||
ip packet.IP4
|
||||
mask packet.IP4
|
||||
}
|
||||
|
||||
func net4FromIPPrefix(pfx netaddr.IPPrefix) net4 {
|
||||
if !pfx.IP.Is4() {
|
||||
panic("net4FromIPPrefix given non-ipv4 prefix")
|
||||
}
|
||||
return net4{
|
||||
ip: packet.IP4FromNetaddr(pfx.IP),
|
||||
mask: netmask4(pfx.Bits),
|
||||
}
|
||||
}
|
||||
|
||||
func nets4FromIPPrefixes(pfxs []netaddr.IPPrefix) (ret []net4) {
|
||||
for _, pfx := range pfxs {
|
||||
if pfx.IP.Is4() {
|
||||
ret = append(ret, net4FromIPPrefix(pfx))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (n net4) Contains(ip packet.IP4) bool {
|
||||
return (n.ip & n.mask) == (ip & n.mask)
|
||||
}
|
||||
|
||||
func (n net4) Bits() int {
|
||||
return 32 - bits.TrailingZeros32(uint32(n.mask))
|
||||
}
|
||||
|
||||
func (n net4) String() string {
|
||||
b := n.Bits()
|
||||
if b == 32 {
|
||||
return n.ip.String()
|
||||
} else if b == 0 {
|
||||
return "*"
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%d", n.ip, b)
|
||||
}
|
||||
}
|
||||
|
||||
type npr4 struct {
|
||||
net net4
|
||||
ports PortRange
|
||||
}
|
||||
|
||||
func (npr npr4) String() string {
|
||||
return fmt.Sprintf("%s:%s", npr.net, npr.ports)
|
||||
}
|
||||
|
||||
type match4 struct {
|
||||
srcs []net4
|
||||
dsts []npr4
|
||||
}
|
||||
|
||||
type matches4 []match4
|
||||
|
||||
func (ms matches4) String() string {
|
||||
var b strings.Builder
|
||||
for _, m := range ms {
|
||||
fmt.Fprintf(&b, "%s => %s\n", m.srcs, m.dsts)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func newMatches4(ms []Match) (ret matches4) {
|
||||
for _, m := range ms {
|
||||
var m4 match4
|
||||
for _, src := range m.Srcs {
|
||||
if src.IP.Is4() {
|
||||
m4.srcs = append(m4.srcs, net4FromIPPrefix(src))
|
||||
}
|
||||
}
|
||||
for _, dst := range m.Dsts {
|
||||
if dst.Net.IP.Is4() {
|
||||
m4.dsts = append(m4.dsts, npr4{net4FromIPPrefix(dst.Net), dst.Ports})
|
||||
}
|
||||
}
|
||||
if len(m4.srcs) > 0 && len(m4.dsts) > 0 {
|
||||
ret = append(ret, m4)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// match returns whether q's source IP and destination IP:port match
|
||||
// any of ms.
|
||||
func (ms matches4) match(q *packet.Parsed) bool {
|
||||
for _, m := range ms {
|
||||
if !ip4InList(q.SrcIP4, m.srcs) {
|
||||
continue
|
||||
}
|
||||
for _, dst := range m.dsts {
|
||||
if !dst.net.Contains(q.DstIP4) {
|
||||
continue
|
||||
}
|
||||
if !dst.ports.contains(q.DstPort) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchIPsOnly returns whether q's source and destination IP match
|
||||
// any of ms.
|
||||
func (ms matches4) matchIPsOnly(q *packet.Parsed) bool {
|
||||
for _, m := range ms {
|
||||
if !ip4InList(q.SrcIP4, m.srcs) {
|
||||
continue
|
||||
}
|
||||
for _, dst := range m.dsts {
|
||||
if dst.net.Contains(q.DstIP4) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func netmask4(bits uint8) packet.IP4 {
|
||||
b := ^uint32((1 << (32 - bits)) - 1)
|
||||
return packet.IP4(b)
|
||||
}
|
||||
|
||||
func ip4InList(ip packet.IP4, netlist []net4) bool {
|
||||
for _, net := range netlist {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
// 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 filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"strings"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
)
|
||||
|
||||
type net6 struct {
|
||||
ip packet.IP6
|
||||
mask packet.IP6
|
||||
}
|
||||
|
||||
func net6FromIPPrefix(pfx netaddr.IPPrefix) net6 {
|
||||
if !pfx.IP.Is6() {
|
||||
panic("net6FromIPPrefix given non-ipv6 prefix")
|
||||
}
|
||||
var mask packet.IP6
|
||||
if pfx.Bits > 64 {
|
||||
mask.Hi = ^uint64(0)
|
||||
mask.Lo = (^uint64(0) << (128 - pfx.Bits))
|
||||
} else {
|
||||
mask.Hi = (^uint64(0) << (64 - pfx.Bits))
|
||||
}
|
||||
|
||||
return net6{
|
||||
ip: packet.IP6FromNetaddr(pfx.IP),
|
||||
mask: mask,
|
||||
}
|
||||
}
|
||||
|
||||
func nets6FromIPPrefixes(pfxs []netaddr.IPPrefix) (ret []net6) {
|
||||
for _, pfx := range pfxs {
|
||||
if pfx.IP.Is6() {
|
||||
ret = append(ret, net6FromIPPrefix(pfx))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (n net6) Contains(ip packet.IP6) bool {
|
||||
// This is equivalent to the more straightforward implementation:
|
||||
// ((n.ip.Hi & n.mask.Hi) == (ip.Hi & n.mask.Hi) &&
|
||||
// (n.ip.Lo & n.mask.Lo) == (ip.Lo & n.mask.Lo))
|
||||
//
|
||||
// This implementation runs significantly faster because it
|
||||
// eliminates branches and minimizes the required
|
||||
// bit-twiddling.
|
||||
a := (n.ip.Hi ^ ip.Hi) & n.mask.Hi
|
||||
b := (n.ip.Lo ^ ip.Lo) & n.mask.Lo
|
||||
return (a | b) == 0
|
||||
}
|
||||
|
||||
func (n net6) Bits() int {
|
||||
return 128 - bits.TrailingZeros64(n.mask.Hi) - bits.TrailingZeros64(n.mask.Lo)
|
||||
}
|
||||
|
||||
func (n net6) String() string {
|
||||
switch n.Bits() {
|
||||
case 128:
|
||||
return n.ip.String()
|
||||
case 0:
|
||||
return "*"
|
||||
default:
|
||||
return fmt.Sprintf("%s/%d", n.ip, n.Bits())
|
||||
}
|
||||
}
|
||||
|
||||
type npr6 struct {
|
||||
net net6
|
||||
ports PortRange
|
||||
}
|
||||
|
||||
func (npr npr6) String() string {
|
||||
return fmt.Sprintf("%s:%s", npr.net, npr.ports)
|
||||
}
|
||||
|
||||
type match6 struct {
|
||||
srcs []net6
|
||||
dsts []npr6
|
||||
}
|
||||
|
||||
type matches6 []match6
|
||||
|
||||
func (ms matches6) String() string {
|
||||
var b strings.Builder
|
||||
for _, m := range ms {
|
||||
fmt.Fprintf(&b, "%s => %s\n", m.srcs, m.dsts)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func newMatches6(ms []Match) (ret matches6) {
|
||||
for _, m := range ms {
|
||||
var m6 match6
|
||||
for _, src := range m.Srcs {
|
||||
if src.IP.Is6() {
|
||||
m6.srcs = append(m6.srcs, net6FromIPPrefix(src))
|
||||
}
|
||||
}
|
||||
for _, dst := range m.Dsts {
|
||||
if dst.Net.IP.Is6() {
|
||||
m6.dsts = append(m6.dsts, npr6{net6FromIPPrefix(dst.Net), dst.Ports})
|
||||
}
|
||||
}
|
||||
if len(m6.srcs) > 0 && len(m6.dsts) > 0 {
|
||||
ret = append(ret, m6)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ms matches6) match(q *packet.Parsed) bool {
|
||||
outer:
|
||||
for i := range ms {
|
||||
srcs := ms[i].srcs
|
||||
for j := range srcs {
|
||||
if srcs[j].Contains(q.SrcIP6) {
|
||||
dsts := ms[i].dsts
|
||||
for k := range dsts {
|
||||
if dsts[k].net.Contains(q.DstIP6) && dsts[k].ports.contains(q.DstPort) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// We hit on src, but missed on all
|
||||
// dsts. No need to try other srcs,
|
||||
// they'll never fully match.
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms matches6) matchIPsOnly(q *packet.Parsed) bool {
|
||||
outer:
|
||||
for i := range ms {
|
||||
srcs := ms[i].srcs
|
||||
for j := range srcs {
|
||||
if srcs[j].Contains(q.SrcIP6) {
|
||||
dsts := ms[i].dsts
|
||||
for k := range dsts {
|
||||
if dsts[k].net.Contains(q.DstIP6) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// We hit on src, but missed on all
|
||||
// dsts. No need to try other srcs,
|
||||
// they'll never fully match.
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ip6InList(ip packet.IP6, netlist []net6) bool {
|
||||
for _, net := range netlist {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// 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 filter
|
||||
|
||||
import "testing"
|
||||
|
||||
// Verifies that the fast bit-twiddling implementation of Contains
|
||||
// works the same as the easy-to-read implementation. Since we can't
|
||||
// sensibly check it on 128 bits, the test runs over 4-bit
|
||||
// "IPs". Bit-twiddling is the same at any width, so this adequately
|
||||
// proves that the implementations are equivalent.
|
||||
func TestOptimizedContains(t *testing.T) {
|
||||
for ipHi := 0; ipHi < 0xf; ipHi++ {
|
||||
for ipLo := 0; ipLo < 0xf; ipLo++ {
|
||||
for nIPHi := 0; nIPHi < 0xf; nIPHi++ {
|
||||
for nIPLo := 0; nIPLo < 0xf; nIPLo++ {
|
||||
for maskHi := 0; maskHi < 0xf; maskHi++ {
|
||||
for maskLo := 0; maskLo < 0xf; maskLo++ {
|
||||
|
||||
a := (nIPHi ^ ipHi) & maskHi
|
||||
b := (nIPLo ^ ipLo) & maskLo
|
||||
got := (a | b) == 0
|
||||
|
||||
want := ((nIPHi&maskHi) == (ipHi&maskHi) && (nIPLo&maskLo) == (ipLo&maskLo))
|
||||
|
||||
if got != want {
|
||||
t.Errorf("mask %1x%1x/%1x%1x %1x%1x got=%v want=%v", nIPHi, nIPLo, maskHi, maskLo, ipHi, ipLo, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-17
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/filter"
|
||||
@@ -67,8 +68,7 @@ type TUN struct {
|
||||
|
||||
lastActivityAtomic int64 // unix seconds of last send or receive
|
||||
|
||||
destIPActivity4 atomic.Value // of map[packet.IP4]func()
|
||||
destIPActivity6 atomic.Value // of map[packet.IP6]func()
|
||||
destIPActivity atomic.Value // of map[netaddr.IP]func()
|
||||
|
||||
// buffer stores the oldest unconsumed packet from tdev.
|
||||
// It is made a static buffer in order to avoid allocations.
|
||||
@@ -137,9 +137,8 @@ func WrapTUN(logf logger.Logf, tdev tun.Device) *TUN {
|
||||
// destination (the map keys).
|
||||
//
|
||||
// The map ownership passes to the TUN. It must be non-nil.
|
||||
func (t *TUN) SetDestIPActivityFuncs(m4 map[packet.IP4]func(), m6 map[packet.IP6]func()) {
|
||||
t.destIPActivity4.Store(m4)
|
||||
t.destIPActivity6.Store(m6)
|
||||
func (t *TUN) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
|
||||
t.destIPActivity.Store(m)
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
@@ -284,18 +283,9 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) {
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(buf[offset : offset+n])
|
||||
|
||||
switch p.IPVersion {
|
||||
case 4:
|
||||
if m, ok := t.destIPActivity4.Load().(map[packet.IP4]func()); ok {
|
||||
if fn := m[p.DstIP4]; fn != nil {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
case 6:
|
||||
if m, ok := t.destIPActivity6.Load().(map[packet.IP6]func()); ok {
|
||||
if fn := m[p.DstIP6]; fn != nil {
|
||||
fn()
|
||||
}
|
||||
if m, ok := t.destIPActivity.Load().(map[netaddr.IP]func()); ok {
|
||||
if fn := m[p.Dst.IP]; fn != nil {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-11
@@ -20,12 +20,20 @@ import (
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
func udp(src, dst packet.IP4, sport, dport uint16) []byte {
|
||||
func udp4(src, dst string, sport, dport uint16) []byte {
|
||||
sip, err := netaddr.ParseIP(src)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dip, err := netaddr.ParseIP(dst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
header := &packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
SrcIP: src,
|
||||
DstIP: dst,
|
||||
IPID: 0,
|
||||
Src: sip,
|
||||
Dst: dip,
|
||||
IPID: 0,
|
||||
},
|
||||
SrcPort: sport,
|
||||
DstPort: dport,
|
||||
@@ -252,12 +260,12 @@ func TestFilter(t *testing.T) {
|
||||
}{
|
||||
{"junk_in", in, true, []byte("\x45not a valid IPv4 packet")},
|
||||
{"junk_out", out, true, []byte("\x45not a valid IPv4 packet")},
|
||||
{"bad_port_in", in, true, udp(0x05060708, 0x01020304, 22, 22)},
|
||||
{"bad_port_out", out, false, udp(0x01020304, 0x05060708, 22, 22)},
|
||||
{"bad_ip_in", in, true, udp(0x08010101, 0x01020304, 89, 89)},
|
||||
{"bad_ip_out", out, false, udp(0x01020304, 0x08010101, 98, 98)},
|
||||
{"good_packet_in", in, false, udp(0x05060708, 0x01020304, 89, 89)},
|
||||
{"good_packet_out", out, false, udp(0x01020304, 0x05060708, 98, 98)},
|
||||
{"bad_port_in", in, true, udp4("5.6.7.8", "1.2.3.4", 22, 22)},
|
||||
{"bad_port_out", out, false, udp4("1.2.3.4", "5.6.7.8", 22, 22)},
|
||||
{"bad_ip_in", in, true, udp4("8.1.1.1", "1.2.3.4", 89, 89)},
|
||||
{"bad_ip_out", out, false, udp4("1.2.3.4", "8.1.1.1", 98, 98)},
|
||||
{"good_packet_in", in, false, udp4("5.6.7.8", "1.2.3.4", 89, 89)},
|
||||
{"good_packet_out", out, false, udp4("1.2.3.4", "5.6.7.8", 98, 98)},
|
||||
}
|
||||
|
||||
// A reader on the other end of the TUN.
|
||||
@@ -337,7 +345,7 @@ func BenchmarkWrite(b *testing.B) {
|
||||
ftun, tun := newFakeTUN(b.Logf, true)
|
||||
defer tun.Close()
|
||||
|
||||
packet := udp(0x05060708, 0x01020304, 89, 89)
|
||||
packet := udp4("5.6.7.8", "1.2.3.4", 89, 89)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ftun.Write(packet, 0)
|
||||
if err != nil {
|
||||
|
||||
+49
-85
@@ -8,7 +8,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -58,10 +57,9 @@ import (
|
||||
// discovery.
|
||||
const minimalMTU = 1280
|
||||
|
||||
const (
|
||||
magicDNSIP = 0x64646464 // 100.100.100.100
|
||||
magicDNSPort = 53
|
||||
)
|
||||
const magicDNSPort = 53
|
||||
|
||||
var magicDNSIP = netaddr.IPv4(100, 100, 100, 100)
|
||||
|
||||
// Lazy wireguard-go configuration parameters.
|
||||
const (
|
||||
@@ -99,19 +97,17 @@ type userspaceEngine struct {
|
||||
// localAddrs is the set of IP addresses assigned to the local
|
||||
// tunnel interface. It's used to reflect local packets
|
||||
// incorrectly sent to us.
|
||||
localAddrs atomic.Value // of map[packet.IP4]bool
|
||||
localAddrs atomic.Value // of map[netaddr.IP]bool
|
||||
|
||||
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
|
||||
lastCfgFull wgcfg.Config
|
||||
lastRouterSig string // of router.Config
|
||||
lastEngineSigFull string // of full wireguard config
|
||||
lastEngineSigTrim string // of trimmed wireguard config
|
||||
recvActivityAt map[tailcfg.DiscoKey]time.Time
|
||||
trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config
|
||||
sentActivityAt4 map[packet.IP4]*int64 // value is atomic int64 of unixtime
|
||||
destIPActivityFuncs4 map[packet.IP4]func()
|
||||
sentActivityAt6 map[packet.IP6]*int64 // value is atomic int64 of unixtime
|
||||
destIPActivityFuncs6 map[packet.IP6]func()
|
||||
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
|
||||
lastCfgFull wgcfg.Config
|
||||
lastRouterSig string // of router.Config
|
||||
lastEngineSigFull string // of full wireguard config
|
||||
lastEngineSigTrim string // of trimmed wireguard config
|
||||
recvActivityAt map[tailcfg.DiscoKey]time.Time
|
||||
trimmedDisco map[tailcfg.DiscoKey]bool // set of disco keys of peers currently excluded from wireguard config
|
||||
sentActivityAt map[netaddr.IP]*int64 // value is atomic int64 of unixtime
|
||||
destIPActivityFuncs map[netaddr.IP]func()
|
||||
|
||||
mu sync.Mutex // guards following; see lock order comment below
|
||||
closing bool // Close was called (even if we're still closing)
|
||||
@@ -208,7 +204,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
|
||||
resolver: tsdns.NewResolver(rconf),
|
||||
pingers: make(map[wgcfg.Key]*pinger),
|
||||
}
|
||||
e.localAddrs.Store(map[packet.IP4]bool{})
|
||||
e.localAddrs.Store(map[netaddr.IP]bool{})
|
||||
e.linkState, _ = getLinkState()
|
||||
logf("link state: %+v", e.linkState)
|
||||
|
||||
@@ -399,7 +395,7 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) fil
|
||||
return filter.Drop
|
||||
}
|
||||
|
||||
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.DstIP4) {
|
||||
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && e.isLocalAddr(p.Dst.IP) {
|
||||
// macOS NetworkExtension directs packets destined to the
|
||||
// tunnel's local IP address into the tunnel, instead of
|
||||
// looping back within the kernel network stack. We have to
|
||||
@@ -412,8 +408,8 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.TUN) fil
|
||||
return filter.Accept
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) isLocalAddr(ip packet.IP4) bool {
|
||||
localAddrs, ok := e.localAddrs.Load().(map[packet.IP4]bool)
|
||||
func (e *userspaceEngine) isLocalAddr(ip netaddr.IP) bool {
|
||||
localAddrs, ok := e.localAddrs.Load().(map[netaddr.IP]bool)
|
||||
if !ok {
|
||||
e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet")
|
||||
return false
|
||||
@@ -423,10 +419,10 @@ func (e *userspaceEngine) isLocalAddr(ip packet.IP4) bool {
|
||||
|
||||
// handleDNS is an outbound pre-filter resolving Tailscale domains.
|
||||
func (e *userspaceEngine) handleDNS(p *packet.Parsed, t *tstun.TUN) filter.Response {
|
||||
if p.DstIP4 == magicDNSIP && p.DstPort == magicDNSPort && p.IPProto == packet.UDP {
|
||||
if p.Dst.IP == magicDNSIP && p.Dst.Port == magicDNSPort && p.IPProto == packet.UDP {
|
||||
request := tsdns.Packet{
|
||||
Payload: append([]byte(nil), p.Payload()...),
|
||||
Addr: netaddr.IPPort{IP: p.SrcIP4.Netaddr(), Port: p.SrcPort},
|
||||
Addr: netaddr.IPPort{IP: p.Src.IP, Port: p.Src.Port},
|
||||
}
|
||||
err := e.resolver.EnqueueRequest(request)
|
||||
if err != nil {
|
||||
@@ -451,8 +447,8 @@ func (e *userspaceEngine) pollResolver() {
|
||||
|
||||
h := packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
SrcIP: packet.IP4(magicDNSIP),
|
||||
DstIP: packet.IP4FromNetaddr(resp.Addr.IP),
|
||||
Src: magicDNSIP,
|
||||
Dst: resp.Addr.IP,
|
||||
},
|
||||
SrcPort: magicDNSPort,
|
||||
DstPort: resp.Addr.Port,
|
||||
@@ -489,7 +485,7 @@ func (p *pinger) close() {
|
||||
<-p.done
|
||||
}
|
||||
|
||||
func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, srcIP packet.IP4) {
|
||||
func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, srcIP netaddr.IP) {
|
||||
defer func() {
|
||||
p.e.mu.Lock()
|
||||
if p.e.pingers[peerKey] == p {
|
||||
@@ -502,7 +498,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
|
||||
|
||||
header := packet.ICMP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
SrcIP: srcIP,
|
||||
Src: srcIP,
|
||||
},
|
||||
Type: packet.ICMP4EchoRequest,
|
||||
Code: packet.ICMP4NoCode,
|
||||
@@ -515,7 +511,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
|
||||
const stopAfter = 3 * time.Second
|
||||
|
||||
start := time.Now()
|
||||
var dstIPs []packet.IP4
|
||||
var dstIPs []netaddr.IP
|
||||
for _, ip := range ips {
|
||||
if ip.Is6() {
|
||||
// This code is only used for legacy (pre-discovery)
|
||||
@@ -524,7 +520,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
|
||||
// work.
|
||||
continue
|
||||
}
|
||||
dstIPs = append(dstIPs, packet.IP4FromNetaddr(netaddr.IPFrom16(ip.Addr)))
|
||||
dstIPs = append(dstIPs, netaddr.IPFrom16(ip.Addr))
|
||||
}
|
||||
|
||||
payload := []byte("magicsock_spray") // no meaning
|
||||
@@ -542,7 +538,7 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
|
||||
return
|
||||
}
|
||||
for _, dstIP := range dstIPs {
|
||||
header.DstIP = dstIP
|
||||
header.Dst = dstIP
|
||||
// InjectOutbound take ownership of the packet, so we allocate.
|
||||
b := packet.Generate(&header, payload)
|
||||
p.e.tundev.InjectOutbound(b)
|
||||
@@ -560,15 +556,15 @@ func (p *pinger) run(ctx context.Context, peerKey wgcfg.Key, ips []wgcfg.IP, src
|
||||
// have advertised discovery keys.
|
||||
func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []wgcfg.IP) {
|
||||
e.logf("[v1] generating initial ping traffic to %s (%v)", peerKey.ShortString(), ips)
|
||||
var srcIP packet.IP4
|
||||
var srcIP netaddr.IP
|
||||
|
||||
e.wgLock.Lock()
|
||||
if len(e.lastCfgFull.Addresses) > 0 {
|
||||
srcIP = packet.IP4FromNetaddr(netaddr.IPFrom16(e.lastCfgFull.Addresses[0].IP.Addr))
|
||||
srcIP = netaddr.IPFrom16(e.lastCfgFull.Addresses[0].IP.Addr)
|
||||
}
|
||||
e.wgLock.Unlock()
|
||||
|
||||
if srcIP == 0 {
|
||||
if srcIP.IsZero() {
|
||||
e.logf("generating initial ping traffic: no source IP")
|
||||
return
|
||||
}
|
||||
@@ -694,17 +690,8 @@ func (e *userspaceEngine) isActiveSince(dk tailcfg.DiscoKey, ip wgcfg.IP, t time
|
||||
if e.recvActivityAt[dk].After(t) {
|
||||
return true
|
||||
}
|
||||
var (
|
||||
timePtr *int64
|
||||
ok bool
|
||||
)
|
||||
if ip.Is4() {
|
||||
pip := packet.IP4(binary.BigEndian.Uint32(ip.Addr[12:]))
|
||||
timePtr, ok = e.sentActivityAt4[pip]
|
||||
} else {
|
||||
pip := packet.IP6FromRaw16(ip.Addr)
|
||||
timePtr, ok = e.sentActivityAt6[pip]
|
||||
}
|
||||
pip := netaddr.IPFrom16(ip.Addr)
|
||||
timePtr, ok := e.sentActivityAt[pip]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -845,14 +832,10 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
|
||||
}
|
||||
e.recvActivityAt = mr
|
||||
|
||||
oldTime4 := e.sentActivityAt4
|
||||
e.sentActivityAt4 = make(map[packet.IP4]*int64, len(oldTime4))
|
||||
oldFunc4 := e.destIPActivityFuncs4
|
||||
e.destIPActivityFuncs4 = make(map[packet.IP4]func(), len(oldFunc4))
|
||||
oldTime6 := e.sentActivityAt6
|
||||
e.sentActivityAt6 = make(map[packet.IP6]*int64, len(oldTime6))
|
||||
oldFunc6 := e.destIPActivityFuncs6
|
||||
e.destIPActivityFuncs6 = make(map[packet.IP6]func(), len(oldFunc6))
|
||||
oldTime := e.sentActivityAt
|
||||
e.sentActivityAt = make(map[netaddr.IP]*int64, len(oldTime))
|
||||
oldFunc := e.destIPActivityFuncs
|
||||
e.destIPActivityFuncs = make(map[netaddr.IP]func(), len(oldFunc))
|
||||
|
||||
updateFn := func(timePtr *int64) func() {
|
||||
return func() {
|
||||
@@ -877,35 +860,20 @@ func (e *userspaceEngine) updateActivityMapsLocked(trackDisco []tailcfg.DiscoKey
|
||||
}
|
||||
|
||||
for _, wip := range trackIPs {
|
||||
if wip.Is4() {
|
||||
pip := packet.IP4(binary.BigEndian.Uint32(wip.Addr[12:]))
|
||||
timePtr := oldTime4[pip]
|
||||
if timePtr == nil {
|
||||
timePtr = new(int64)
|
||||
}
|
||||
e.sentActivityAt4[pip] = timePtr
|
||||
|
||||
fn := oldFunc4[pip]
|
||||
if fn == nil {
|
||||
fn = updateFn(timePtr)
|
||||
}
|
||||
e.destIPActivityFuncs4[pip] = fn
|
||||
} else {
|
||||
pip := packet.IP6FromRaw16(wip.Addr)
|
||||
timePtr := oldTime6[pip]
|
||||
if timePtr == nil {
|
||||
timePtr = new(int64)
|
||||
}
|
||||
e.sentActivityAt6[pip] = timePtr
|
||||
|
||||
fn := oldFunc6[pip]
|
||||
if fn == nil {
|
||||
fn = updateFn(timePtr)
|
||||
}
|
||||
e.destIPActivityFuncs6[pip] = fn
|
||||
pip := netaddr.IPFrom16(wip.Addr)
|
||||
timePtr := oldTime[pip]
|
||||
if timePtr == nil {
|
||||
timePtr = new(int64)
|
||||
}
|
||||
e.sentActivityAt[pip] = timePtr
|
||||
|
||||
fn := oldFunc[pip]
|
||||
if fn == nil {
|
||||
fn = updateFn(timePtr)
|
||||
}
|
||||
e.destIPActivityFuncs[pip] = fn
|
||||
}
|
||||
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs4, e.destIPActivityFuncs6)
|
||||
e.tundev.SetDestIPActivityFuncs(e.destIPActivityFuncs)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error {
|
||||
@@ -913,13 +881,9 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
|
||||
panic("routerCfg must not be nil")
|
||||
}
|
||||
|
||||
localAddrs := map[packet.IP4]bool{}
|
||||
localAddrs := map[netaddr.IP]bool{}
|
||||
for _, addr := range routerCfg.LocalAddrs {
|
||||
// TODO: ipv6
|
||||
if !addr.IP.Is4() {
|
||||
continue
|
||||
}
|
||||
localAddrs[packet.IP4FromNetaddr(addr.IP)] = true
|
||||
localAddrs[addr.IP] = true
|
||||
}
|
||||
e.localAddrs.Store(localAddrs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user