tailscale/net/netmon/interfaces.go
Jonathan Nobels 643e91f2eb
net/netmon: move TailscaleInterfaceIndex out of netmon.State (#18428)
fixes tailscale/tailscale#18418

Both Serve and PeerAPI broke when we moved the TailscaleInterfaceName
into State, which is updated asynchronously and may not be
available when we configure the listeners.

This extracts the explicit interface name property from netmon.State
and adds as a static struct with getters that have proper error
handling.

The bug is only found in sandboxed Darwin clients, where we
need to know the Tailscale interface details in order to set up the
listeners correctly (they must bind to our interface explicitly to escape
the network sandboxing that is applied by NECP).

Currently set only sandboxed macOS and Plan9 set this but it will
also be useful on Windows to simplify interface filtering in netns.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
2026-01-16 14:53:23 -05:00

104 lines
2.7 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package netmon
import (
"errors"
"net"
"tailscale.com/syncs"
)
type ifProps struct {
mu syncs.Mutex
name string // interface name, if known/set
index int // interface index, if known/set
}
// tsIfProps tracks the properties (name and index) of the tailscale interface.
// There is only one tailscale interface per tailscaled instance.
var tsIfProps ifProps
func (p *ifProps) tsIfName() string {
p.mu.Lock()
defer p.mu.Unlock()
return p.name
}
func (p *ifProps) tsIfIndex() int {
p.mu.Lock()
defer p.mu.Unlock()
return p.index
}
func (p *ifProps) set(ifName string, ifIndex int) {
p.mu.Lock()
defer p.mu.Unlock()
p.name = ifName
p.index = ifIndex
}
// TODO (barnstar): This doesn't need the Monitor receiver anymore but we're
// keeping it for API compatibility to avoid a breaking change.  This can be
// removed when the various clients have switched to SetTailscaleInterfaceProps
func (m *Monitor) SetTailscaleInterfaceName(ifName string) {
SetTailscaleInterfaceProps(ifName, 0)
}
// SetTailscaleInterfaceProps sets the name of the Tailscale interface and
// its index for use by various listeners/dialers. If the index is zero,
// an attempt will be made to look it up by name. This makes no attempt
// to validate that the interface exists at the time of calling.
//
// If this method is called, it is the responsibility of the caller to
// update the interface name and index if they change.
//
// This should be called as early as possible during tailscaled startup.
func SetTailscaleInterfaceProps(ifName string, ifIndex int) {
if ifIndex != 0 {
tsIfProps.set(ifName, ifIndex)
return
}
ifaces, err := net.Interfaces()
if err != nil {
return
}
for _, iface := range ifaces {
if iface.Name == ifName {
ifIndex = iface.Index
break
}
}
tsIfProps.set(ifName, ifIndex)
}
// TailscaleInterfaceName returns the name of the Tailscale interface.
// For example, "tailscale0", "tun0", "utun3", etc or an error if unset.
//
// Callers must handle errors, as the Tailscale interface
// name may not be set in some environments.
func TailscaleInterfaceName() (string, error) {
name := tsIfProps.tsIfName()
if name == "" {
return "", errors.New("Tailscale interface name not set")
}
return name, nil
}
// TailscaleInterfaceIndex returns the index of the Tailscale interface or
// an error if unset.
//
// Callers must handle errors, as the Tailscale interface
// index may not be set in some environments.
func TailscaleInterfaceIndex() (int, error) {
index := tsIfProps.tsIfIndex()
if index == 0 {
return 0, errors.New("Tailscale interface index not set")
}
return index, nil
}