Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Nobels
82ca894ff5 wgengine: return explicit lo0 for loopback addrs on sandboxed macOS
fixes tailscale/tailscale#TODO

The source address link selection on sandboxed macOS doesn't deal
with loopback addresses correctly.  This adds an explicit check to ensure
we return the loopback interface for loopback addresses instead of the
default empty interface.

Specifcially, this allows the dns resolver to route queries to a loopback
IP which is a common tactic for local DNS proxies.
2025-03-24 15:35:18 -04:00
9 changed files with 17 additions and 1203 deletions

View File

@@ -1,18 +0,0 @@
{{- if .Values.grafanaDashboard.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-grafana-dashboard
namespace: {{ .Release.Namespace }}
labels:
{{- with .Values.grafanaDashboard.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
annotations:
{{- with .Values.grafanaDashboard.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
data:
tailscale.json: |-
{{ .Files.Get "monitoring/tailscale-dashboard.json" | indent 4 }}
{{- end }}

View File

@@ -29,16 +29,6 @@ oauthSecretVolume: {}
# https://helm.sh/docs/chart_best_practices/custom_resource_definitions/
installCRDs: true
# Grafana dashboard configuration
grafanaDashboard:
enabled: false
# Labels to be added to the ConfigMap
labels:
grafana_dashboard: "1"
# Annotations to be added to the ConfigMap
annotations:
grafana_folder: "Tailscale"
operatorConfig:
# ACL tag that operator will be tagged with. Operator must be made owner of
# these tags

View File

@@ -4968,7 +4968,7 @@ func (b *LocalBackend) authReconfig() {
return
}
oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.NetMon.Get(), b.sys.ControlKnobs(), version.OS())
oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS())
rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute)
err = b.e.Reconfig(cfg, rcfg, dcfg)
@@ -4992,7 +4992,7 @@ func (b *LocalBackend) authReconfig() {
//
// The versionOS is a Tailscale-style version ("iOS", "macOS") and not
// a runtime.GOOS.
func shouldUseOneCGNATRoute(logf logger.Logf, mon *netmon.Monitor, controlKnobs *controlknobs.Knobs, versionOS string) bool {
func shouldUseOneCGNATRoute(logf logger.Logf, controlKnobs *controlknobs.Knobs, versionOS string) bool {
if controlKnobs != nil {
// Explicit enabling or disabling always take precedence.
if v, ok := controlKnobs.OneCGNAT.Load().Get(); ok {
@@ -5007,7 +5007,7 @@ func shouldUseOneCGNATRoute(logf logger.Logf, mon *netmon.Monitor, controlKnobs
// use fine-grained routes if another interfaces is also using the CGNAT
// IP range.
if versionOS == "macOS" {
hasCGNATInterface, err := mon.HasCGNATInterface()
hasCGNATInterface, err := netmon.HasCGNATInterface()
if err != nil {
logf("shouldUseOneCGNATRoute: Could not determine if any interfaces use CGNAT: %v", err)
return false

View File

@@ -481,7 +481,7 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re
fmt.Fprintf(w, "<h3>Could not get the default route: %s</h3>\n", html.EscapeString(err.Error()))
}
if hasCGNATInterface, err := h.ps.b.sys.NetMon.Get().HasCGNATInterface(); hasCGNATInterface {
if hasCGNATInterface, err := netmon.HasCGNATInterface(); hasCGNATInterface {
fmt.Fprintln(w, "<p>There is another interface using the CGNAT range.</p>")
} else if err != nil {
fmt.Fprintf(w, "<p>Could not check for CGNAT interfaces: %s</p>\n", html.EscapeString(err.Error()))

View File

@@ -13,7 +13,7 @@ import (
)
func TestGetState(t *testing.T) {
st, err := getState("")
st, err := getState()
if err != nil {
t.Fatal(err)
}

View File

@@ -161,7 +161,7 @@ func (m *Monitor) InterfaceState() *State {
}
func (m *Monitor) interfaceStateUncached() (*State, error) {
return getState(m.tsIfName)
return getState()
}
// SetTailscaleInterfaceName sets the name of the Tailscale interface. For

View File

@@ -461,22 +461,21 @@ func isTailscaleInterface(name string, ips []netip.Prefix) bool {
// getPAC, if non-nil, returns the current PAC file URL.
var getPAC func() string
// getState returns the state of all the current machine's network interfaces.
// GetState returns the state of all the current machine's network interfaces.
//
// It does not set the returned State.IsExpensive. The caller can populate that.
//
// optTSInterfaceName is the name of the Tailscale interface, if known.
func getState(optTSInterfaceName string) (*State, error) {
// Deprecated: use netmon.Monitor.InterfaceState instead.
func getState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netip.Prefix),
Interface: make(map[string]Interface),
}
if err := ForeachInterface(func(ni Interface, pfxs []netip.Prefix) {
isTSInterfaceName := optTSInterfaceName != "" && ni.Name == optTSInterfaceName
ifUp := ni.IsUp()
s.Interface[ni.Name] = ni
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTSInterfaceName || isTailscaleInterface(ni.Name, pfxs) {
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return
}
for _, pfx := range pfxs {
@@ -756,12 +755,11 @@ func DefaultRoute() (DefaultRouteDetails, error) {
// HasCGNATInterface reports whether there are any non-Tailscale interfaces that
// use a CGNAT IP range.
func (m *Monitor) HasCGNATInterface() (bool, error) {
func HasCGNATInterface() (bool, error) {
hasCGNATInterface := false
cgnatRange := tsaddr.CGNATRange()
err := ForeachInterface(func(i Interface, pfxs []netip.Prefix) {
isTSInterfaceName := m.tsIfName != "" && i.Name == m.tsIfName
if hasCGNATInterface || !i.IsUp() || isTSInterfaceName || isTailscaleInterface(i.Name, pfxs) {
if hasCGNATInterface || !i.IsUp() || isTailscaleInterface(i.Name, pfxs) {
return
}
for _, pfx := range pfxs {

View File

@@ -1580,6 +1580,11 @@ type fwdDNSLinkSelector struct {
}
func (ls fwdDNSLinkSelector) PickLink(ip netip.Addr) (linkName string) {
// sandboxed macOS needs some extra hand-holding for loopback addresses.
if ip.IsLoopback() && version.IsSandboxedMacOS() {
return "lo0"
}
if ls.ue.isDNSIPOverTailscale.Load()(ip) {
return ls.tunName
}