Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix cap problem (rootless vpn-client) #1273

Merged
merged 7 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 18 additions & 64 deletions internal/vpn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -129,46 +129,27 @@ 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(launcher.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 {
Expand Down Expand Up @@ -264,22 +245,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)
}

Expand All @@ -301,7 +272,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()

Expand Down Expand Up @@ -359,7 +330,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 {
Expand All @@ -373,17 +344,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 {
Expand Down Expand Up @@ -439,7 +399,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{})
Expand Down Expand Up @@ -483,11 +442,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
}

Expand Down Expand Up @@ -521,17 +475,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
}
}
Expand All @@ -543,10 +497,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))
}
}
Expand All @@ -567,7 +521,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)
}
}
Expand All @@ -578,7 +532,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
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/vpn/client_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 0 additions & 3 deletions internal/vpn/client_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,3 @@
// +build windows

package vpn

func (c *Client) releaseSysPrivileges() {
}
2 changes: 1 addition & 1 deletion internal/vpn/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 25 additions & 30 deletions internal/vpn/os_client_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"bytes"
"fmt"
"net"
"sync"

"github.com/syndtr/gocapability/capability"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -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 capabilties: %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
}
Expand Down
2 changes: 1 addition & 1 deletion internal/vpn/os_client_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
38 changes: 29 additions & 9 deletions internal/vpn/os_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand All @@ -45,15 +50,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")
}
Loading