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>
This commit is contained in:
+11
-2
@@ -565,7 +565,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
||||
|
||||
// Call our linkChange code once with the current state.
|
||||
// Following changes are triggered via the eventbus.
|
||||
cd, err := netmon.NewChangeDelta(nil, b.interfaceState, false, netMon.TailscaleInterfaceName(), false)
|
||||
cd, err := netmon.NewChangeDelta(nil, b.interfaceState, false, false)
|
||||
if err != nil {
|
||||
b.logf("[unexpected] setting initial netmon state failed: %v", err)
|
||||
} else {
|
||||
@@ -5321,7 +5321,11 @@ func (b *LocalBackend) initPeerAPIListenerLocked() {
|
||||
var err error
|
||||
skipListen := i > 0 && isNetstack
|
||||
if !skipListen {
|
||||
ln, err = ps.listen(a.Addr(), b.interfaceState.TailscaleInterfaceIndex)
|
||||
// We don't care about the error here. Not all platforms set this.
|
||||
// If ps.listen needs it, it will check for zero values and error out.
|
||||
tsIfIndex, _ := netmon.TailscaleInterfaceIndex()
|
||||
|
||||
ln, err = ps.listen(a.Addr(), tsIfIndex)
|
||||
if err != nil {
|
||||
if peerAPIListenAsync {
|
||||
b.logf("[v1] possibly transient peerapi listen(%q) error, will try again on linkChange: %v", a.Addr(), err)
|
||||
@@ -5329,6 +5333,11 @@ func (b *LocalBackend) initPeerAPIListenerLocked() {
|
||||
// ("peerAPIListeners too low").
|
||||
continue
|
||||
}
|
||||
// Sandboxed macOS specifically requires the interface index to be non-zero.
|
||||
if version.IsSandboxedMacOS() && tsIfIndex == 0 {
|
||||
b.logf("[v1] peerapi listen(%q) error: interface index is 0 on darwin; try restarting tailscaled", a.Addr())
|
||||
continue
|
||||
}
|
||||
b.logf("[unexpected] peerapi listen(%q) error: %v", a.Addr(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import (
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
// initListenConfig, if non-nil, is called during peerAPIListener setup. It is used only
|
||||
// on iOS and macOS to set socket options to bind the listener to the Tailscale interface.
|
||||
var initListenConfig func(config *net.ListenConfig, addr netip.Addr, tunIfIndex int) error
|
||||
|
||||
// peerDNSQueryHandler is implemented by tsdns.Resolver.
|
||||
@@ -69,6 +71,13 @@ func (s *peerAPIServer) listen(ip netip.Addr, tunIfIndex int) (ln net.Listener,
|
||||
// On iOS/macOS, this sets the lc.Control hook to
|
||||
// setsockopt the interface index to bind to, to get
|
||||
// out of the network sandbox.
|
||||
|
||||
// A zero tunIfIndex is invalid for peerapi. A zero value will not get us
|
||||
// out of the network sandbox. Caller should log and retry.
|
||||
if tunIfIndex == 0 {
|
||||
return nil, fmt.Errorf("peerapi: cannot listen on %s with tunIfIndex 0", ipStr)
|
||||
}
|
||||
|
||||
if err := initListenConfig(&lc, ip, tunIfIndex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+13
-4
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/pires/go-proxyproto"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -166,16 +167,24 @@ func (s *localListener) Run() {
|
||||
|
||||
var lc net.ListenConfig
|
||||
if initListenConfig != nil {
|
||||
ifIndex, err := netmon.TailscaleInterfaceIndex()
|
||||
if err != nil {
|
||||
s.logf("localListener failed to get Tailscale interface index %v, backing off: %v", s.ap, err)
|
||||
s.bo.BackOff(s.ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// On macOS, this sets the lc.Control hook to
|
||||
// setsockopt the interface index to bind to. This is
|
||||
// required by the network sandbox to allow binding to
|
||||
// a specific interface. Without this hook, the system
|
||||
// chooses a default interface to bind to.
|
||||
if err := initListenConfig(&lc, ip, s.b.interfaceState.TailscaleInterfaceIndex); err != nil {
|
||||
// required by the network sandbox which will not automatically
|
||||
// bind to the tailscale interface to prevent routing loops.
|
||||
// Explicit binding allows us to bypass that restriction.
|
||||
if err := initListenConfig(&lc, ip, ifIndex); err != nil {
|
||||
s.logf("localListener failed to init listen config %v, backing off: %v", s.ap, err)
|
||||
s.bo.BackOff(s.ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// On macOS (AppStore or macsys) and if we're binding to a privileged port,
|
||||
if version.IsSandboxedMacOS() && s.ap.Port() < 1024 {
|
||||
// On macOS, we need to bind to ""/all-interfaces due to
|
||||
|
||||
Reference in New Issue
Block a user