@ -127,8 +127,10 @@ import (
"tailscale.com/kube/services"
"tailscale.com/kube/services"
"tailscale.com/tailcfg"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/types/ptr"
"tailscale.com/types/ptr"
"tailscale.com/util/deephash"
"tailscale.com/util/deephash"
"tailscale.com/util/dnsname"
"tailscale.com/util/linuxfw"
"tailscale.com/util/linuxfw"
)
)
@ -526,27 +528,14 @@ runLoop:
}
}
}
}
if cfg . TailnetTargetFQDN != "" {
if cfg . TailnetTargetFQDN != "" {
var (
egressAddrs , err := resolveTailnetFQDN ( n . NetMap , cfg . TailnetTargetFQDN )
egressAddrs [ ] netip . Prefix
if err != nil {
newCurentEgressIPs deephash . Sum
log . Print ( err . Error ( ) )
egressIPsHaveChanged bool
node tailcfg . NodeView
nodeFound bool
)
for _ , n := range n . NetMap . Peers {
if strings . EqualFold ( n . Name ( ) , cfg . TailnetTargetFQDN ) {
node = n
nodeFound = true
break
}
}
if ! nodeFound {
log . Printf ( "Tailscale node %q not found; it either does not exist, or not reachable because of ACLs" , cfg . TailnetTargetFQDN )
break
break
}
}
egressAddrs = node . Addresses ( ) . AsSlice ( )
newCurentEgressIPs = deephash . Hash ( & egressAddrs )
newCurentEgressIPs := deephash . Hash ( & egressAddrs )
egressIPsHaveChanged = newCurentEgressIPs != currentEgressIPs
egressIPsHaveChanged := newCurentEgressIPs != currentEgressIPs
// The firewall rules get (re-)installed:
// The firewall rules get (re-)installed:
// - on startup
// - on startup
// - when the tailnet IPs of the tailnet target have changed
// - when the tailnet IPs of the tailnet target have changed
@ -892,3 +881,64 @@ func runHTTPServer(mux *http.ServeMux, addr string) (close func() error) {
return errors . Join ( err , ln . Close ( ) )
return errors . Join ( err , ln . Close ( ) )
}
}
}
}
// resolveTailnetFQDN resolves a tailnet FQDN to a list of IP prefixes, which
// can be either a peer device or a Tailscale Service.
func resolveTailnetFQDN ( nm * netmap . NetworkMap , fqdn string ) ( [ ] netip . Prefix , error ) {
// Check all peer devices first.
for _ , p := range nm . Peers {
if strings . EqualFold ( p . Name ( ) , fqdn ) {
return p . Addresses ( ) . AsSlice ( ) , nil
}
}
// If not found yet, check for a matching Tailscale Service.
dnsFQDN , err := dnsname . ToFQDN ( fqdn )
if err != nil {
return nil , fmt . Errorf ( "error parsing %q as FQDN: %w" , fqdn , err )
}
if svcIPs := serviceIPsFromNetMap ( nm , dnsFQDN ) ; len ( svcIPs ) != 0 {
return svcIPs , nil
}
return nil , fmt . Errorf ( "could not find Tailscale node or service %q; it either does not exist, or not reachable because of ACLs" , fqdn )
}
// serviceIPsFromNetMap returns all IPs of a Tailscale Service if its FQDN is
// found in the netmap. Note that Tailscale Services are not a first-class
// object in the netmap, so we guess based on DNS ExtraRecords and AllowedIPs.
func serviceIPsFromNetMap ( nm * netmap . NetworkMap , fqdn dnsname . FQDN ) [ ] netip . Prefix {
var extraRecords [ ] tailcfg . DNSRecord
for _ , rec := range nm . DNS . ExtraRecords {
recFQDN , err := dnsname . ToFQDN ( rec . Name )
if err != nil {
continue
}
if strings . EqualFold ( fqdn . WithTrailingDot ( ) , recFQDN . WithTrailingDot ( ) ) {
extraRecords = append ( extraRecords , rec )
}
}
if len ( extraRecords ) == 0 {
return nil
}
// Validate we can see a peer advertising the Tailscale Service.
var prefixes [ ] netip . Prefix
for _ , extraRecord := range extraRecords {
ip , err := netip . ParseAddr ( extraRecord . Value )
if err != nil {
continue
}
ipPrefix := netip . PrefixFrom ( ip , ip . BitLen ( ) )
for _ , ps := range nm . Peers {
for _ , allowedIP := range ps . AllowedIPs ( ) . All ( ) {
if allowedIP == ipPrefix {
prefixes = append ( prefixes , ipPrefix )
}
}
}
}
return prefixes
}