@ -7,9 +7,14 @@
package netns
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"golang.org/x/sys/unix"
@ -23,44 +28,126 @@ import (
// wgengine/router/router_linux.go.
const tailscaleBypassMark = 0x20000
// checkIPRule runs the ipRuleAvailable check exactly once.
var checkIPRule sync . Once
// ipRuleAvailable is true if and only if the 'ip rule' command works.
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
var ipRuleAvailable bool
// defaultRouteInterface returns the name of the network interface that owns
// the default route, not including any tailscale interfaces. We only use
// this in SO_BINDTODEVICE mode.
func defaultRouteInterface ( ) ( string , error ) {
b , err := ioutil . ReadFile ( "/proc/net/route" )
if err != nil {
return "" , err
}
for _ , line := range strings . Split ( string ( b ) , "\n" ) [ 1 : ] {
fields := strings . Fields ( line )
ifc := fields [ 0 ]
ip := fields [ 1 ]
netmask := fields [ 7 ]
if strings . HasPrefix ( ifc , "tailscale" ) ||
strings . HasPrefix ( ifc , "wg" ) {
continue
}
if ip == "00000000" && netmask == "00000000" {
// default route
return ifc , nil // interface name
}
}
return "" , errors . New ( "no default routes found" )
}
// ignoreErrors returns true if we should ignore setsocketopt errors in
// this instance.
func ignoreErrors ( ) bool {
if os . Getuid ( ) != 0 {
// only root can manipulate these socket flags
return true
}
// TODO(apenwarr): this snooping around in the args is way too magic.
// It would be better to explicitly activate, or not, this dialer
// by passing it from the toplevel program.
v , _ := os . Executable ( )
switch filepath . Base ( v ) {
case "tailscale" :
for _ , arg := range os . Args {
if arg == "netcheck" {
return true
}
}
case "tailscaled" :
for _ , arg := range os . Args {
if arg == "-fake" || arg == "--fake" {
return true
}
}
}
return false
}
// control marks c as necessary to dial in a separate network namespace.
//
// It's intentionally the same signature as net.Dialer.Control
// and net.ListenConfig.Control.
func control ( network , address string , c syscall . RawConn ) error {
checkIPRule . Do ( func ( ) {
_ , err := exec . Command ( "ip" , "rule" ) . Output ( )
ipRuleAvailable = ( err == nil )
} )
if skipPrivileged . Get ( ) {
// We can't set socket marks without CAP_NET_ADMIN on linux,
// skip as requested.
return nil
}
var controlErr error
err := c . Control ( func ( fd uintptr ) {
controlErr = unix . SetsockoptInt ( int ( fd ) , unix . SOL_SOCKET , unix . SO_MARK , tailscaleBypassMark )
} )
// Before returning some fatal error, skip it in some cases.
if ( err != nil || controlErr != nil ) && os . Getuid ( ) != 0 {
v , _ := os . Executable ( )
switch filepath . Base ( v ) {
case "tailscale" :
for _ , arg := range os . Args {
if arg == "netcheck" {
return nil
}
}
case "tailscaled" :
for _ , arg := range os . Args {
if arg == "-fake" || arg == "--fake" {
return nil
}
if ipRuleAvailable {
var controlErr error
err := c . Control ( func ( fd uintptr ) {
controlErr = unix . SetsockoptInt ( int ( fd ) ,
unix . SOL_SOCKET , unix . SO_MARK ,
tailscaleBypassMark )
} )
if ( err != nil || controlErr != nil ) && ignoreErrors ( ) {
return nil
}
if err != nil {
return fmt . Errorf ( "setting socket mark1: %w" , err )
}
if controlErr != nil {
return fmt . Errorf ( "setting socket mark2: %w" , controlErr )
}
} else {
var controlErr error
err := c . Control ( func ( fd uintptr ) {
ifc , err := defaultRouteInterface ( )
if err != nil {
// Make sure we bind to *some* interface,
// or we could get a routing loop.
// "lo" is always wrong, but if we don't have
// a default route anyway, it doesn't matter.
ifc = "lo"
}
controlErr = unix . SetsockoptString ( int ( fd ) ,
unix . SOL_SOCKET , unix . SO_BINDTODEVICE , ifc )
} )
if ( err != nil || controlErr != nil ) && ignoreErrors ( ) {
return nil
}
if err != nil {
return fmt . Errorf ( "setting SO_BINDTODEVICE 1: %w" , err )
}
if controlErr != nil {
return fmt . Errorf ( "setting SO_BINDTODEVICE 2: %w" , controlErr )
}
}
if err != nil {
return fmt . Errorf ( "setting socket mark: %w" , err )
}
if controlErr != nil {
return fmt . Errorf ( "setting socket mark: %w" , controlErr )
}
return nil
}