mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-19 19:38:40 +00:00
net/netns, net/interfaces: explicitly bind sockets to the default interface on all Darwin variants
We were previously only doing this for tailscaled-on-Darwin, but it also appears to help on iOS. Otherwise, when we rebind magicsock UDP connections after a cellular -> WiFi interface change they still keep using cellular one. To do this correctly when using exit nodes, we need to exclude the Tailscale interface when getting the default route, otherwise packets cannot leave the tunnel. There are native macOS/iOS APIs that we can use to do this, so we allow those clients to override the implementation of DefaultRouteInterfaceIndex. Updates #6565, may also help with #5156 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
parent
cb525a1aad
commit
79f3a5d753
@ -20,6 +20,7 @@ import (
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
@ -36,7 +37,17 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// DefaultRouteInterfaceIndex returns the index of the network interface that
|
||||
// owns the default route. It returns the first IPv4 or IPv6 default route it
|
||||
// finds (it does not prefer one or the other).
|
||||
func DefaultRouteInterfaceIndex() (int, error) {
|
||||
if f := defaultRouteInterfaceIndexFunc.Load(); f != nil {
|
||||
if ifIndex := f(); ifIndex != 0 {
|
||||
return ifIndex, nil
|
||||
}
|
||||
// Fallthrough if we can't use the alternate implementation.
|
||||
}
|
||||
|
||||
// $ netstat -nr
|
||||
// Routing tables
|
||||
// Internet:
|
||||
@ -71,6 +82,16 @@ func DefaultRouteInterfaceIndex() (int, error) {
|
||||
return 0, errors.New("no gateway index found")
|
||||
}
|
||||
|
||||
var defaultRouteInterfaceIndexFunc syncs.AtomicValue[func() int]
|
||||
|
||||
// SetDefaultRouteInterfaceIndexFunc allows an alternate implementation of
|
||||
// DefaultRouteInterfaceIndex to be provided. If none is set, or if f() returns a 0
|
||||
// (indicating an unknown interface index), then the default implementation (that parses
|
||||
// the routing table) will be used.
|
||||
func SetDefaultRouteInterfaceIndexFunc(f func() int) {
|
||||
defaultRouteInterfaceIndexFunc.Store(f)
|
||||
}
|
||||
|
||||
func init() {
|
||||
likelyHomeRouterIP = likelyHomeRouterIPBSDFetchRIB
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && !ts_macext
|
||||
//go:build darwin
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
@ -27,7 +30,7 @@ func control(logf logger.Logf) func(network, address string, c syscall.RawConn)
|
||||
// It's intentionally the same signature as net.Dialer.Control
|
||||
// and net.ListenConfig.Control.
|
||||
func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) error {
|
||||
if strings.HasPrefix(address, "127.") || address == "::1" {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections.
|
||||
return nil
|
||||
}
|
||||
@ -36,6 +39,26 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
|
||||
logf("[unexpected] netns: DefaultRouteInterfaceIndex: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return bindConnToInterface(c, network, address, idx, logf)
|
||||
}
|
||||
|
||||
// SetListenConfigInterfaceIndex sets lc.Control such that sockets are bound
|
||||
// to the provided interface index.
|
||||
func SetListenConfigInterfaceIndex(lc *net.ListenConfig, ifIndex int) error {
|
||||
if lc == nil {
|
||||
return errors.New("nil ListenConfig")
|
||||
}
|
||||
if lc.Control != nil {
|
||||
return errors.New("ListenConfig.Control already set")
|
||||
}
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
return bindConnToInterface(c, network, address, ifIndex, log.Printf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindConnToInterface(c syscall.RawConn, network, address string, ifIndex int, logf logger.Logf) error {
|
||||
v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
|
||||
proto := unix.IPPROTO_IP
|
||||
opt := unix.IP_BOUND_IF
|
||||
@ -45,14 +68,14 @@ func controlLogf(logf logger.Logf, network, address string, c syscall.RawConn) e
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err = c.Control(func(fd uintptr) {
|
||||
sockErr = unix.SetsockoptInt(int(fd), proto, opt, idx)
|
||||
err := c.Control(func(fd uintptr) {
|
||||
sockErr = unix.SetsockoptInt(int(fd), proto, opt, ifIndex)
|
||||
})
|
||||
if sockErr != nil {
|
||||
logf("[unexpected] netns: bindConnToInterface(%q, %q), v6=%v, index=%v: %v", network, address, v6, ifIndex, sockErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
|
||||
}
|
||||
if sockErr != nil {
|
||||
logf("[unexpected] netns: control(%q, %q), v6=%v, index=%v: %v", network, address, v6, idx, sockErr)
|
||||
}
|
||||
return sockErr
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!linux && !windows && !darwin) || (darwin && ts_macext)
|
||||
//go:build !linux && !windows && !darwin
|
||||
|
||||
package netns
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || ios
|
||||
|
||||
package netns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetListenConfigInterfaceIndex sets lc.Control such that sockets are bound
|
||||
// to the provided interface index.
|
||||
func SetListenConfigInterfaceIndex(lc *net.ListenConfig, ifIndex int) error {
|
||||
if lc == nil {
|
||||
return errors.New("nil ListenConfig")
|
||||
}
|
||||
if lc.Control != nil {
|
||||
return errors.New("ListenConfig.Control already set")
|
||||
}
|
||||
lc.Control = func(network, address string, c syscall.RawConn) error {
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
sockErr = bindInterface(fd, network, address, ifIndex)
|
||||
if sockErr != nil {
|
||||
log.Printf("netns: bind(%q, %q) on index %v: %v", network, address, ifIndex, sockErr)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sockErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindInterface(fd uintptr, network, address string, ifIndex int) error {
|
||||
v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6
|
||||
proto := unix.IPPROTO_IP
|
||||
opt := unix.IP_BOUND_IF
|
||||
if v6 {
|
||||
proto = unix.IPPROTO_IPV6
|
||||
opt = unix.IPV6_BOUND_IF
|
||||
}
|
||||
return unix.SetsockoptInt(int(fd), proto, opt, ifIndex)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user