diff --git a/internal/vpn/client.go b/internal/vpn/client.go index 0b164a495..f18f53514 100644 --- a/internal/vpn/client.go +++ b/internal/vpn/client.go @@ -43,8 +43,8 @@ type Client struct { prevTUNGateway net.IP prevTUNGatewayMu sync.Mutex - suidMu sync.Mutex - suid int + suidMu sync.Mutex //nolint + suid int //nolint tunMu sync.Mutex tun TUNDevice @@ -128,47 +128,30 @@ func NewClient(cfg ClientConfig, appCl *app.Client) (*Client, error) { // Serve dials VPN server, sets up TUN and establishes VPN session. func (c *Client) Serve() error { + c.setAppStatus(appserver.AppDetailedStatusStarting) - if err := c.setSysPrivileges(); err != nil { - c.setAppError(err) - return fmt.Errorf("failed to setup system privileges: %w", err) - } + // we setup direct routes to skywire services once for all the client lifetime since routes don't change. // but if they change, new routes get delivered to the app via callbacks. if err := c.setupDirectRoutes(); err != nil { - c.releaseSysPrivileges() c.setAppError(err) return fmt.Errorf("error setting up direct routes: %w", err) } - c.releaseSysPrivileges() defer func() { - if err := c.setSysPrivileges(); err != nil { - fmt.Printf("failed to setup system privileges: %v\n", err) - return - } - defer c.releaseSysPrivileges() - c.removeDirectRoutes() }() // we call this preliminary, so it will be called on app stop defer func() { if c.cfg.Killswitch { - err := c.setSysPrivileges() - if err != nil { - print(fmt.Sprintf("Error setting up system privileges: %v\n", err)) - } else { - c.prevTUNGatewayMu.Lock() - if len(c.prevTUNGateway) > 0 { - fmt.Printf("Routing traffic directly, previous TUN gateway: %s\n", c.prevTUNGateway.String()) - c.routeTrafficDirectly(c.prevTUNGateway) - } - c.prevTUNGateway = nil - c.prevTUNGatewayMu.Unlock() - - c.releaseSysPrivileges() + c.prevTUNGatewayMu.Lock() + if len(c.prevTUNGateway) > 0 { + fmt.Printf("Routing traffic directly, previous TUN gateway: %s\n", c.prevTUNGateway.String()) + c.routeTrafficDirectly(c.prevTUNGateway) } + c.prevTUNGateway = nil + c.prevTUNGatewayMu.Unlock() } if err := c.closeTUN(); err != nil { @@ -263,22 +246,12 @@ func (c *Client) AddDirectRoute(ip net.IP) error { c.directIPs = append(c.directIPs, ip) - if err := c.setSysPrivileges(); err != nil { - return fmt.Errorf("failed to setup system privileges: %w", err) - } - defer c.releaseSysPrivileges() - return c.setupDirectRoute(ip) } func (c *Client) removeDirectRouteFn(ip net.IP, i int) error { c.directIPs = append(c.directIPs[:i], c.directIPs[i+1:]...) - if err := c.setSysPrivileges(); err != nil { - return fmt.Errorf("failed to setup system privileges: %w", err) - } - defer c.releaseSysPrivileges() - return c.removeDirectRoute(ip) } @@ -300,7 +273,7 @@ func (c *Client) RemoveDirectRoute(ip net.IP) error { return nil } -func (c *Client) setSysPrivileges() error { +func (c *Client) setSysPrivileges() error { //nolint if runtime.GOOS != "windows" { c.suidMu.Lock() @@ -358,7 +331,7 @@ func (c *Client) setupTUN(tunIP, tunGateway net.IP) error { return errors.New("TUN is not created") } - return SetupTUN(c.tun.Name(), tunIP.String()+TUNNetmaskCIDR, tunGateway.String(), TUNMTU) + return c.SetupTUN(c.tun.Name(), tunIP.String()+TUNNetmaskCIDR, tunGateway.String(), TUNMTU) } func (c *Client) serveConn(conn net.Conn) error { @@ -372,17 +345,6 @@ func (c *Client) serveConn(conn net.Conn) error { fmt.Printf("Local TUN IP: %s\n", tunIP.String()) fmt.Printf("Local TUN gateway: %s\n", tunGateway.String()) - if err := c.setSysPrivileges(); err != nil { - return fmt.Errorf("failed to setup system privileges: %w", err) - } - - // this call is important. it will either run on an error down the line, - // or, in case VPN sessions finishes, it will be the last call in deferred stack, - // releasing system privileges after cleanup - defer func() { - c.releaseSysPrivileges() - }() - fmt.Println("CREATING TUN INTERFACE") tun, err := c.createTUN() if err != nil { @@ -438,7 +400,6 @@ func (c *Client) serveConn(conn net.Conn) error { }() // we release privileges here (user is not root for Mac OS systems from here on) - c.releaseSysPrivileges() connToTunDoneCh := make(chan struct{}) tunToConnCh := make(chan struct{}) @@ -482,11 +443,6 @@ serveLoop: } } - // here we setup system privileges again, so deferred calls may be done safely - if err := c.setSysPrivileges(); err != nil { - print(fmt.Sprintf("Failed to setup system privileges for cleanup: %v\n", err)) - } - return nil } @@ -520,17 +476,17 @@ func (c *Client) dialServeConn() error { func (c *Client) routeTrafficThroughTUN(tunGateway net.IP, isNewRoute bool) error { // route all traffic through TUN gateway if isNewRoute { - if err := AddRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { + if err := c.AddRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { return err } - if err := AddRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { + if err := c.AddRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { return err } } else { - if err := ChangeRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { + if err := c.ChangeRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { return err } - if err := ChangeRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { + if err := c.ChangeRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { return err } } @@ -542,10 +498,10 @@ func (c *Client) routeTrafficDirectly(tunGateway net.IP) { fmt.Println("Routing all traffic through default network gateway") // remove main route - if err := DeleteRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { + if err := c.DeleteRoute(ipv4FirstHalfAddr, tunGateway.String()); err != nil { print(fmt.Sprintf("Error routing traffic through default network gateway: %v\n", err)) } - if err := DeleteRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { + if err := c.DeleteRoute(ipv4SecondHalfAddr, tunGateway.String()); err != nil { print(fmt.Sprintf("Error routing traffic through default network gateway: %v\n", err)) } } @@ -566,7 +522,7 @@ func (c *Client) setupDirectRoutes() error { func (c *Client) setupDirectRoute(ip net.IP) error { if !ip.IsLoopback() { fmt.Printf("Adding direct route to %s, via %s\n", ip.String(), c.defaultGateway.String()) - if err := AddRoute(ip.String()+directRouteNetmaskCIDR, c.defaultGateway.String()); err != nil { + if err := c.AddRoute(ip.String()+directRouteNetmaskCIDR, c.defaultGateway.String()); err != nil { return fmt.Errorf("error adding direct route to %s: %w", ip.String(), err) } } @@ -577,7 +533,7 @@ func (c *Client) setupDirectRoute(ip net.IP) error { func (c *Client) removeDirectRoute(ip net.IP) error { if !ip.IsLoopback() { fmt.Printf("Removing direct route to %s\n", ip.String()) - if err := DeleteRoute(ip.String()+directRouteNetmaskCIDR, c.defaultGateway.String()); err != nil { + if err := c.DeleteRoute(ip.String()+directRouteNetmaskCIDR, c.defaultGateway.String()); err != nil { return err } } diff --git a/internal/vpn/client_unix.go b/internal/vpn/client_unix.go index fbf38688e..b776a22d3 100644 --- a/internal/vpn/client_unix.go +++ b/internal/vpn/client_unix.go @@ -5,7 +5,7 @@ package vpn import "fmt" -func (c *Client) releaseSysPrivileges() { +func (c *Client) releaseSysPrivileges() { // nolint defer c.suidMu.Unlock() if err := releaseClientSysPrivileges(c.suid); err != nil { diff --git a/internal/vpn/client_windows.go b/internal/vpn/client_windows.go index 3fac9cd9c..7f3e5c475 100644 --- a/internal/vpn/client_windows.go +++ b/internal/vpn/client_windows.go @@ -2,6 +2,3 @@ // +build windows package vpn - -func (c *Client) releaseSysPrivileges() { -} diff --git a/internal/vpn/os.go b/internal/vpn/os.go index 10868c67e..03f788c1b 100644 --- a/internal/vpn/os.go +++ b/internal/vpn/os.go @@ -5,7 +5,7 @@ import ( "net" ) -func parseCIDR(ipCIDR string) (ipStr, netmask string, err error) { +func parseCIDR(ipCIDR string) (ipStr, netmask string, err error) { //nolint : actually used in os_windows.go ip, net, err := net.ParseCIDR(ipCIDR) if err != nil { return "", "", err diff --git a/internal/vpn/os_client_linux.go b/internal/vpn/os_client_linux.go index 126177241..6123db6ca 100644 --- a/internal/vpn/os_client_linux.go +++ b/internal/vpn/os_client_linux.go @@ -7,7 +7,6 @@ import ( "bytes" "fmt" "net" - "sync" "github.com/syndtr/gocapability/capability" "golang.org/x/sys/unix" @@ -45,42 +44,38 @@ func DefaultNetworkGateway() (net.IP, error) { return nil, errCouldFindDefaultNetworkGateway } -var setupClientOnce sync.Once - func setupClientSysPrivileges() (int, error) { var err error - setupClientOnce.Do(func() { - var caps capability.Capabilities - caps, err = capability.NewPid2(0) - if err != nil { - err = fmt.Errorf("failed to init capabilities: %w", err) - return - } + var caps capability.Capabilities - err = caps.Load() - if err != nil { - err = fmt.Errorf("failed to load capabilities: %w", err) - return - } + caps, err = capability.NewPid2(0) + if err != nil { + err = fmt.Errorf("failed to init capabilities: %w", err) + return 0, err + } - // set `CAP_NET_ADMIN` capability to needed caps sets. - caps.Set(capability.CAPS|capability.BOUNDS|capability.AMBIENT, capability.CAP_NET_ADMIN) - err = caps.Apply(capability.CAPS | capability.BOUNDS | capability.AMBIENT) - if err != nil { - err = fmt.Errorf("failed to apply capabilities: %w", err) + err = caps.Load() + if err != nil { + err = fmt.Errorf("failed to load capabilities: %w", err) + return 0, err + } - return - } + // set `CAP_NET_ADMIN` capability to needed caps sets. + caps.Set(capability.CAPS|capability.BOUNDS|capability.AMBIENT, capability.CAP_NET_ADMIN) + err = caps.Apply(capability.CAPS | capability.BOUNDS | capability.AMBIENT) + if err != nil { + err = fmt.Errorf("failed to apply capabilties: %w", err) + return 0, err + } - // let child process keep caps sets from the parent, so we may do calls to - // system utilities with these caps. - err = unix.Prctl(unix.PR_SET_KEEPCAPS, 1, 0, 0, 0) - if err != nil { - err = fmt.Errorf("failed to set PR_SET_KEEPCAPS: %w", err) - return - } - }) + // let child process keep caps sets from the parent, so we may do calls to + // system utilities with these caps. + err = unix.Prctl(unix.PR_SET_KEEPCAPS, 1, 0, 0, 0) + if err != nil { + err = fmt.Errorf("failed to set PR_SET_KEEPCAPS: %w", err) + return 0, err + } return 0, nil } diff --git a/internal/vpn/os_client_windows.go b/internal/vpn/os_client_windows.go index fe990d285..057da38d6 100644 --- a/internal/vpn/os_client_windows.go +++ b/internal/vpn/os_client_windows.go @@ -51,6 +51,6 @@ func DefaultNetworkGateway() (net.IP, error) { return nil, errCouldFindDefaultNetworkGateway } -func setupClientSysPrivileges() (suid int, err error) { +func setupClientSysPrivileges() (suid int, err error) { //nolint return 0, nil } diff --git a/internal/vpn/os_darwin.go b/internal/vpn/os_darwin.go index b7062281a..a213adc0e 100644 --- a/internal/vpn/os_darwin.go +++ b/internal/vpn/os_darwin.go @@ -13,24 +13,29 @@ import ( ) // SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. -func SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { +func (c *Client) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { ip, netmask, err := parseCIDR(ipCIDR) if err != nil { return fmt.Errorf("error parsing IP CIDR: %w", err) } - + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for SetupTUN: %v\n", err)) + return err + } + defer c.releaseSysPrivileges() return osutil.Run("ifconfig", ifcName, ip, gateway, "mtu", strconv.Itoa(mtu), "netmask", netmask, "up") + } // ChangeRoute changes current route to `ipCIDR` to go through the `gateway` // in the OS routing table. -func ChangeRoute(ipCIDR, gateway string) error { - return modifyRoutingTable("change", ipCIDR, gateway) +func (c *Client) ChangeRoute(ipCIDR, gateway string) error { + return c.modifyRoutingTable("change", ipCIDR, gateway) } // AddRoute adds route to `ipCIDR` through the `gateway` to the OS routing table. -func AddRoute(ipCIDR, gateway string) error { - if err := modifyRoutingTable("add", ipCIDR, gateway); err != nil { +func (c *Client) AddRoute(ipCIDR, gateway string) error { + if err := c.modifyRoutingTable("add", ipCIDR, gateway); err != nil { var e *osutil.ErrorWithStderr if errors.As(err, &e) { if strings.Contains(string(e.Stderr), "File exists") { @@ -43,14 +48,30 @@ func AddRoute(ipCIDR, gateway string) error { } // DeleteRoute removes route to `ipCIDR` through the `gateway` from the OS routing table. -func DeleteRoute(ipCIDR, gateway string) error { - return modifyRoutingTable("delete", ipCIDR, gateway) +func (c *Client) DeleteRoute(ipCIDR, gateway string) error { + return c.modifyRoutingTable("delete", ipCIDR, gateway) } -func modifyRoutingTable(action, ipCIDR, gateway string) error { +func (c *Client) modifyRoutingTable(action, ipCIDR, gateway string) error { ip, netmask, err := parseCIDR(ipCIDR) if err != nil { return fmt.Errorf("error parsing IP CIDR: %w", err) } + + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for %s: %v\n", action, err)) + return err + } + defer c.releaseSysPrivileges() return osutil.Run("route", action, "-net", ip, gateway, netmask) } + +// SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. +func (s *Server) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { + ip, netmask, err := parseCIDR(ipCIDR) + if err != nil { + return fmt.Errorf("error parsing IP CIDR: %w", err) + } + + return osutil.Run("ifconfig", ifcName, ip, gateway, "mtu", strconv.Itoa(mtu), "netmask", netmask, "up") +} diff --git a/internal/vpn/os_linux.go b/internal/vpn/os_linux.go index 37caaa00f..6bc94314a 100644 --- a/internal/vpn/os_linux.go +++ b/internal/vpn/os_linux.go @@ -12,26 +12,43 @@ import ( "github.com/skycoin/skywire/pkg/util/osutil" ) +// Client + // SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. -func SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { +func (c *Client) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for SetupTUN: %v\n", err)) + return err + } if err := osutil.Run("ip", "a", "add", ipCIDR, "dev", ifcName); err != nil { return fmt.Errorf("error assigning IP: %w", err) } + c.releaseSysPrivileges() + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for SetupTUN: %v\n", err)) + return err + } if err := osutil.Run("ip", "link", "set", "dev", ifcName, "mtu", strconv.Itoa(mtu)); err != nil { return fmt.Errorf("error setting MTU: %w", err) } + c.releaseSysPrivileges() ip, _, err := parseCIDR(ipCIDR) if err != nil { return fmt.Errorf("error parsing IP CIDR: %w", err) } + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for SetupTUN: %v\n", err)) + return err + } if err := osutil.Run("ip", "link", "set", ifcName, "up"); err != nil { return fmt.Errorf("error setting interface up: %w", err) } + c.releaseSysPrivileges() - if err := AddRoute(ip, gateway); err != nil { + if err := c.AddRoute(ip, gateway); err != nil { return fmt.Errorf("error setting gateway for interface: %w", err) } @@ -40,12 +57,22 @@ func SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { // ChangeRoute changes current route to `ip` to go through the `gateway` // in the OS routing table. -func ChangeRoute(ip, gateway string) error { +func (c *Client) ChangeRoute(ip, gateway string) error { + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for ChangeRoute: %v\n", err)) + return err + } + defer c.releaseSysPrivileges() return osutil.Run("ip", "r", "change", ip, "via", gateway) } // AddRoute adds route to `ip` with `netmask` through the `gateway` to the OS routing table. -func AddRoute(ip, gateway string) error { +func (c *Client) AddRoute(ip, gateway string) error { + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for AddRoute: %v\n", err)) + return err + } + defer c.releaseSysPrivileges() err := osutil.Run("ip", "r", "add", ip, "via", gateway) var e *osutil.ErrorWithStderr @@ -65,6 +92,59 @@ func AddRoute(ip, gateway string) error { } // DeleteRoute removes route to `ip` with `netmask` through the `gateway` from the OS routing table. -func DeleteRoute(ip, gateway string) error { +func (c *Client) DeleteRoute(ip, gateway string) error { + if err := c.setSysPrivileges(); err != nil { + print(fmt.Sprintf("Failed to setup system privileges for DeleteRoute: %v\n", err)) + return err + } + defer c.releaseSysPrivileges() return osutil.Run("ip", "r", "del", ip, "via", gateway) } + +// Server + +// SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. +func (s *Server) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { + if err := osutil.Run("ip", "a", "add", ipCIDR, "dev", ifcName); err != nil { + return fmt.Errorf("error assigning IP: %w", err) + } + + if err := osutil.Run("ip", "link", "set", "dev", ifcName, "mtu", strconv.Itoa(mtu)); err != nil { + return fmt.Errorf("error setting MTU: %w", err) + } + + ip, _, err := parseCIDR(ipCIDR) + if err != nil { + return fmt.Errorf("error parsing IP CIDR: %w", err) + } + + if err := osutil.Run("ip", "link", "set", ifcName, "up"); err != nil { + return fmt.Errorf("error setting interface up: %w", err) + } + + if err := s.AddRoute(ip, gateway); err != nil { + return fmt.Errorf("error setting gateway for interface: %w", err) + } + + return nil +} + +// AddRoute adds route to `ip` with `netmask` through the `gateway` to the OS routing table. +func (s *Server) AddRoute(ip, gateway string) error { + err := osutil.Run("ip", "r", "add", ip, "via", gateway) + + var e *osutil.ErrorWithStderr + if errors.As(err, &e) { + if strings.Contains(string(e.Stderr), "File exists") { + return nil + } + } + + if errors.As(err, &e) { + if strings.Contains(string(e.Stderr), "Operation not permitted") { + return errPermissionDenied + } + } + + return err +} diff --git a/internal/vpn/os_windows.go b/internal/vpn/os_windows.go index 109dee684..e7f454a9d 100644 --- a/internal/vpn/os_windows.go +++ b/internal/vpn/os_windows.go @@ -16,7 +16,7 @@ const ( ) // SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. -func SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { +func (c *Client) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { ip, netmask, err := parseCIDR(ipCIDR) if err != nil { return fmt.Errorf("error parsing IP CIDR: %w", err) @@ -37,17 +37,17 @@ func SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { // ChangeRoute changes current route to `ipCIDR` to go through the `gateway` // in the OS routing table. -func ChangeRoute(ipCIDR, gateway string) error { +func (c *Client) ChangeRoute(ipCIDR, gateway string) error { return modifyRoutingTable("change", ipCIDR, gateway) } // AddRoute adds route to `ipCIDR` through the `gateway` to the OS routing table. -func AddRoute(ipCIDR, gateway string) error { +func (c *Client) AddRoute(ipCIDR, gateway string) error { return modifyRoutingTable("add", ipCIDR, gateway) } // DeleteRoute removes route to `ipCIDR` through the `gateway` from the OS routing table. -func DeleteRoute(ipCIDR, gateway string) error { +func (c *Client) DeleteRoute(ipCIDR, gateway string) error { return modifyRoutingTable("delete", ipCIDR, gateway) } @@ -64,3 +64,23 @@ func modifyRoutingTable(action, ipCIDR, gateway string) error { } return nil } + +// SetupTUN sets the allocated TUN interface up, setting its IP, gateway, netmask and MTU. +func (s *Server) SetupTUN(ifcName, ipCIDR, gateway string, mtu int) error { + ip, netmask, err := parseCIDR(ipCIDR) + if err != nil { + return fmt.Errorf("error parsing IP CIDR: %w", err) + } + + setupCmd := fmt.Sprintf(tunSetupCMDFmt, ifcName, ip, netmask, gateway) + if err := osutil.Run("cmd", "/C", setupCmd); err != nil { + return fmt.Errorf("error running command %s: %w", setupCmd, err) + } + + mtuSetupCmd := fmt.Sprintf(tunMTUSetupCMDFmt, ifcName, mtu) + if err := osutil.Run("cmd", "/C", mtuSetupCmd); err != nil { + return fmt.Errorf("error running command %s: %w", mtuSetupCmd, err) + } + + return nil +} diff --git a/internal/vpn/server.go b/internal/vpn/server.go index 5808e42e9..f0d7de6f2 100644 --- a/internal/vpn/server.go +++ b/internal/vpn/server.go @@ -234,8 +234,8 @@ func (s *Server) serveConn(conn net.Conn) { fmt.Printf("Allocated TUN %s", tun.Name()) - if err := SetupTUN(tun.Name(), tunIP.String()+TUNNetmaskCIDR, tunGateway.String(), TUNMTU); err != nil { - print(fmt.Sprintf("Error setting up TUN %s: %v\n", tun.Name(), err)) + if err := s.SetupTUN(tun.Name(), tunIP.String()+TUNNetmaskCIDR, tunGateway.String(), TUNMTU); err != nil { + print(fmt.Sprintf("Error setting up TUN %s: %v", tun.Name(), err)) return }