|
|
@@ -51,6 +51,7 @@ import (
|
|
|
"tailscale.com/types/logger"
|
|
|
"tailscale.com/types/netmap"
|
|
|
"tailscale.com/types/nettype"
|
|
|
+ "tailscale.com/types/views"
|
|
|
"tailscale.com/util/clientmetric"
|
|
|
"tailscale.com/util/set"
|
|
|
"tailscale.com/version"
|
|
|
@@ -200,6 +201,10 @@ type Impl struct {
|
|
|
lb *ipnlocal.LocalBackend // or nil
|
|
|
dns *dns.Manager
|
|
|
|
|
|
+ // Before Start is called, there can IPv6 Neighbor Discovery from the
|
|
|
+ // OS landing on netstack. We need to drop those packets until Start.
|
|
|
+ ready atomic.Bool // set to true once Start has been called
|
|
|
+
|
|
|
// loopbackPort, if non-nil, will enable Impl to loop back (dnat to
|
|
|
// <address-family-loopback>:loopbackPort) TCP & UDP flows originally
|
|
|
// destined to serviceIP{v6}:loopbackPort.
|
|
|
@@ -216,6 +221,10 @@ type Impl struct {
|
|
|
|
|
|
atomicIsVIPServiceIPFunc syncs.AtomicValue[func(netip.Addr) bool]
|
|
|
|
|
|
+ atomicIPVIPServiceMap syncs.AtomicValue[netmap.IPServiceMappings]
|
|
|
+ // make this a set of strings for faster lookup
|
|
|
+ atomicActiveVIPServices syncs.AtomicValue[set.Set[tailcfg.ServiceName]]
|
|
|
+
|
|
|
// forwardDialFunc, if non-nil, is the net.Dialer.DialContext-style
|
|
|
// function that is used to make outgoing connections when forwarding a
|
|
|
// TCP connection to another host (e.g. in subnet router mode).
|
|
|
@@ -608,6 +617,9 @@ func (ns *Impl) Start(b LocalBackend) error {
|
|
|
ns.ipstack.SetTransportProtocolHandler(tcp.ProtocolNumber, ns.wrapTCPProtocolHandler(tcpFwd.HandlePacket))
|
|
|
ns.ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, ns.wrapUDPProtocolHandler(udpFwd.HandlePacket))
|
|
|
go ns.inject()
|
|
|
+ if ns.ready.Swap(true) {
|
|
|
+ panic("already started")
|
|
|
+ }
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -765,6 +777,25 @@ func (ns *Impl) UpdateNetstackIPs(nm *netmap.NetworkMap) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// UpdateIPServiceMappings updates the IPServiceMappings when there is a change
|
|
|
+// in this value in localbackend. This is usually triggered from a netmap update.
|
|
|
+func (ns *Impl) UpdateIPServiceMappings(mappings netmap.IPServiceMappings) {
|
|
|
+ ns.mu.Lock()
|
|
|
+ defer ns.mu.Unlock()
|
|
|
+ ns.atomicIPVIPServiceMap.Store(mappings)
|
|
|
+}
|
|
|
+
|
|
|
+// UpdateActiveVIPServices updates the set of active VIP services names.
|
|
|
+func (ns *Impl) UpdateActiveVIPServices(activeServices views.Slice[string]) {
|
|
|
+ ns.mu.Lock()
|
|
|
+ defer ns.mu.Unlock()
|
|
|
+ activeServicesSet := make(set.Set[tailcfg.ServiceName], activeServices.Len())
|
|
|
+ for _, s := range activeServices.All() {
|
|
|
+ activeServicesSet.Add(tailcfg.AsServiceName(s))
|
|
|
+ }
|
|
|
+ ns.atomicActiveVIPServices.Store(activeServicesSet)
|
|
|
+}
|
|
|
+
|
|
|
func (ns *Impl) isLoopbackPort(port uint16) bool {
|
|
|
if ns.loopbackPort != nil && int(port) == *ns.loopbackPort {
|
|
|
return true
|
|
|
@@ -775,13 +806,15 @@ func (ns *Impl) isLoopbackPort(port uint16) bool {
|
|
|
// handleLocalPackets is hooked into the tun datapath for packets leaving
|
|
|
// the host and arriving at tailscaled. This method returns filter.DropSilently
|
|
|
// to intercept a packet for handling, for instance traffic to quad-100.
|
|
|
+// Caution: can be called before Start
|
|
|
func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
|
|
- if ns.ctx.Err() != nil {
|
|
|
+ if !ns.ready.Load() || ns.ctx.Err() != nil {
|
|
|
return filter.DropSilently, gro
|
|
|
}
|
|
|
|
|
|
// Determine if we care about this local packet.
|
|
|
dst := p.Dst.Addr()
|
|
|
+ serviceName, isVIPServiceIP := ns.atomicIPVIPServiceMap.Load()[dst]
|
|
|
switch {
|
|
|
case dst == serviceIP || dst == serviceIPv6:
|
|
|
// We want to intercept some traffic to the "service IP" (e.g.
|
|
|
@@ -798,6 +831,25 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro.
|
|
|
return filter.Accept, gro
|
|
|
}
|
|
|
}
|
|
|
+ case isVIPServiceIP:
|
|
|
+ // returns all active VIP services in a set, since the IPVIPServiceMap
|
|
|
+ // contains inactive service IPs when node hosts the service, we need to
|
|
|
+ // check the service is active or not before dropping the packet.
|
|
|
+ activeServices := ns.atomicActiveVIPServices.Load()
|
|
|
+ if !activeServices.Contains(serviceName) {
|
|
|
+ // Other host might have the service active, so we let the packet go through.
|
|
|
+ return filter.Accept, gro
|
|
|
+ }
|
|
|
+ if p.IPProto != ipproto.TCP {
|
|
|
+ // We currenly only support VIP services over TCP. If service is in Tun mode,
|
|
|
+ // it's up to the service host to set up local packet handling which shouldn't
|
|
|
+ // arrive here.
|
|
|
+ return filter.DropSilently, gro
|
|
|
+ }
|
|
|
+ if debugNetstack() {
|
|
|
+ ns.logf("netstack: intercepting local VIP service packet: proto=%v dst=%v src=%v",
|
|
|
+ p.IPProto, p.Dst, p.Src)
|
|
|
+ }
|
|
|
case viaRange.Contains(dst):
|
|
|
// We need to handle 4via6 packets leaving the host if the via
|
|
|
// route is for this host; otherwise the packet will be dropped
|
|
|
@@ -1009,12 +1061,32 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
+ if ns.isVIPServiceIP(srcIP) {
|
|
|
+ dstIP := netip.AddrFrom4(v.DestinationAddress().As4())
|
|
|
+ if ns.isLocalIP(dstIP) {
|
|
|
+ if debugNetstack() {
|
|
|
+ ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP)
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
case header.IPv6:
|
|
|
srcIP := netip.AddrFrom16(v.SourceAddress().As16())
|
|
|
if srcIP == serviceIPv6 {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
+ if ns.isVIPServiceIP(srcIP) {
|
|
|
+ dstIP := netip.AddrFrom16(v.DestinationAddress().As16())
|
|
|
+ if ns.isLocalIP(dstIP) {
|
|
|
+ if debugNetstack() {
|
|
|
+ ns.logf("netstack: sending VIP service packet to host: src=%v dst=%v", srcIP, dstIP)
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if viaRange.Contains(srcIP) {
|
|
|
// Only send to the host if this 4via6 route is
|
|
|
// something this node handles.
|
|
|
@@ -1233,8 +1305,9 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte, direction userPing
|
|
|
// continue normally (typically being delivered to the host networking stack),
|
|
|
// whereas returning filter.DropSilently is done when netstack intercepts the
|
|
|
// packet and no further processing towards to host should be done.
|
|
|
+// Caution: can be called before Start
|
|
|
func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
|
|
- if ns.ctx.Err() != nil {
|
|
|
+ if !ns.ready.Load() || ns.ctx.Err() != nil {
|
|
|
return filter.DropSilently, gro
|
|
|
}
|
|
|
|