@ -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
}