Skip to content

Commit

Permalink
Enable userspace forwarder conditionally
Browse files Browse the repository at this point in the history
  • Loading branch information
lixmal committed Feb 11, 2025
1 parent 18f84f0 commit cef794a
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 30 deletions.
8 changes: 8 additions & 0 deletions client/firewall/iptables/manager_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ func (m *Manager) SetLogLevel(log.Level) {
// not supported
}

func (m *Manager) EnableRouting() error {
return nil
}

func (m *Manager) DisableRouting() error {
return nil
}

func getConntrackEstablished() []string {
return []string{"-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}
}
4 changes: 4 additions & 0 deletions client/firewall/manager/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ type Manager interface {
Flush() error

SetLogLevel(log.Level)

EnableRouting() error

DisableRouting() error
}

func GenKey(format string, pair RouterPair) string {
Expand Down
8 changes: 8 additions & 0 deletions client/firewall/nftables/manager_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ func (m *Manager) SetLogLevel(log.Level) {
// not supported
}

func (m *Manager) EnableRouting() error {
return nil
}

func (m *Manager) DisableRouting() error {
return nil
}

// Flush rule/chain/set operations from the buffer
//
// Method also get all rules after flush and refreshes handle values in the rulesets
Expand Down
105 changes: 80 additions & 25 deletions client/firewall/uspfilter/uspfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type Manager struct {

mutex sync.RWMutex

// indicates whether we server routes are disabled
disableServerRoutes bool
// indicates whether we forward packets not destined for ourselves
routingEnabled bool
// indicates whether we leave forwarding and filtering to the native firewall
Expand Down Expand Up @@ -149,15 +151,16 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
return d
},
},
nativeFirewall: nativeFirewall,
outgoingRules: make(map[string]RuleSet),
incomingRules: make(map[string]RuleSet),
wgIface: iface,
localipmanager: newLocalIPManager(),
routingEnabled: false,
stateful: !disableConntrack,
logger: nblog.NewFromLogrus(log.StandardLogger()),
netstack: netstack.IsEnabled(),
nativeFirewall: nativeFirewall,
outgoingRules: make(map[string]RuleSet),
incomingRules: make(map[string]RuleSet),
wgIface: iface,
localipmanager: newLocalIPManager(),
disableServerRoutes: disableServerRoutes,
routingEnabled: false,
stateful: !disableConntrack,
logger: nblog.NewFromLogrus(log.StandardLogger()),
netstack: netstack.IsEnabled(),
// default true for non-netstack, for netstack only if explicitly enabled
localForwarding: !netstack.IsEnabled() || enableLocalForwarding,
}
Expand All @@ -166,7 +169,6 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
return nil, fmt.Errorf("update local IPs: %w", err)
}

// Only initialize trackers if stateful mode is enabled
if disableConntrack {
log.Info("conntrack is disabled")
} else {
Expand All @@ -175,7 +177,12 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger)
}

m.determineRouting(iface, disableServerRoutes)
// netstack needs the forwarder for local traffic
if m.netstack && m.localForwarding {
if err := m.initForwarder(iface); err != nil {
log.Errorf("failed to initialize forwarder: %v", err)
}
}

if err := m.blockInvalidRouted(iface); err != nil {
log.Errorf("failed to block invalid routed traffic: %v", err)
Expand Down Expand Up @@ -213,9 +220,21 @@ func (m *Manager) blockInvalidRouted(iface common.IFaceMapper) error {
return nil
}

func (m *Manager) determineRouting(iface common.IFaceMapper, disableServerRoutes bool) {
disableUspRouting, _ := strconv.ParseBool(os.Getenv(EnvDisableUserspaceRouting))
forceUserspaceRouter, _ := strconv.ParseBool(os.Getenv(EnvForceUserspaceRouter))
func (m *Manager) determineRouting(iface common.IFaceMapper, disableServerRoutes bool) error {
var disableUspRouting, forceUserspaceRouter bool
var err error
if val := os.Getenv(EnvDisableUserspaceRouting); val != "" {
disableUspRouting, err = strconv.ParseBool(val)
if err != nil {
log.Warnf("failed to parse %s: %v", EnvDisableUserspaceRouting, err)
}
}
if val := os.Getenv(EnvForceUserspaceRouter); val != "" {
forceUserspaceRouter, err = strconv.ParseBool(val)
if err != nil {
log.Warnf("failed to parse %s: %v", EnvForceUserspaceRouter, err)
}
}

switch {
case disableUspRouting:
Expand Down Expand Up @@ -252,40 +271,45 @@ func (m *Manager) determineRouting(iface common.IFaceMapper, disableServerRoutes
log.Info("userspace routing enabled by default")
}

// netstack needs the forwarder for local traffic
if m.netstack && m.localForwarding ||
m.routingEnabled && !m.nativeRouter {

m.initForwarder(iface)
if m.routingEnabled && !m.nativeRouter {
return m.initForwarder(iface)
}

return nil
}

// initForwarder initializes the forwarder, it disables routing on errors
func (m *Manager) initForwarder(iface common.IFaceMapper) {
func (m *Manager) initForwarder(iface common.IFaceMapper) error {
if m.forwarder != nil {
return nil
}

// Only supported in userspace mode as we need to inject packets back into wireguard directly
intf := iface.GetWGDevice()
if intf == nil {
log.Info("forwarding not supported")
m.routingEnabled = false
return
return errors.New("forwarding not supported")
}

forwarder, err := forwarder.New(iface, m.logger, m.netstack)
if err != nil {
log.Errorf("failed to create forwarder: %v", err)
m.routingEnabled = false
return
return fmt.Errorf("create forwarder: %w", err)
}

m.forwarder = forwarder

log.Debug("forwarder initialized")

return nil
}

func (m *Manager) Init(*statemanager.Manager) error {
return nil
}

func (m *Manager) IsServerRouteSupported() bool {
return m.nativeFirewall != nil || m.routingEnabled && m.forwarder != nil
return true
}

func (m *Manager) AddNatRule(pair firewall.RouterPair) error {
Expand Down Expand Up @@ -953,3 +977,34 @@ func (m *Manager) SetLogLevel(level log.Level) {
m.logger.SetLevel(nblog.Level(level))
}
}

func (m *Manager) EnableRouting() error {
m.mutex.Lock()
defer m.mutex.Unlock()

return m.determineRouting(m.wgIface, m.disableServerRoutes)
}

func (m *Manager) DisableRouting() error {
m.mutex.Lock()
defer m.mutex.Unlock()

if m.forwarder == nil {
return nil
}

m.routingEnabled = false
m.nativeRouter = false

// don't stop forwarder if in use by netstack
if m.netstack && m.localForwarding {
return nil
}

m.forwarder.Stop()
m.forwarder = nil

log.Debug("Forwarder stopped")

return nil
}
20 changes: 15 additions & 5 deletions client/internal/routemanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,25 @@ func (m *DefaultManager) UpdateRoutes(updateSerial uint64, newRoutes []*route.Ro
m.updateClientNetworks(updateSerial, filteredClientRoutes)
m.notifier.OnNewRoutes(filteredClientRoutes)
}
m.clientRoutes = newClientRoutesIDMap

if m.serverRouter != nil {
err := m.serverRouter.updateRoutes(newServerRoutesMap)
if err != nil {
return err
if m.serverRouter == nil {
return nil
}

if len(newServerRoutesMap) > 0 {
if err := m.serverRouter.EnableRouting(); err != nil {

Check failure on line 296 in client/internal/routemanager/manager.go

View workflow job for this annotation

GitHub Actions / android_build

m.serverRouter.EnableRouting undefined (type *serverRouter has no field or method EnableRouting)
return fmt.Errorf("enable routing: %w", err)
}
} else {
if err := m.serverRouter.DisableRouting(); err != nil {

Check failure on line 300 in client/internal/routemanager/manager.go

View workflow job for this annotation

GitHub Actions / android_build

m.serverRouter.DisableRouting undefined (type *serverRouter has no field or method DisableRouting)
return fmt.Errorf("disable routing: %w", err)
}
}

m.clientRoutes = newClientRoutesIDMap
if err := m.serverRouter.updateRoutes(newServerRoutesMap); err != nil {
return fmt.Errorf("update routes: %w", err)
}

return nil
}
Expand Down
8 changes: 8 additions & 0 deletions client/internal/routemanager/server_nonandroid.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ func (m *serverRouter) cleanUp() {
m.statusRecorder.UpdateLocalPeerState(state)
}

func (r *serverRouter) EnableRouting() error {
return r.firewall.EnableRouting()
}

func (r *serverRouter) DisableRouting() error {
return r.firewall.DisableRouting()
}

func routeToRouterPair(route *route.Route) (firewall.RouterPair, error) {
// TODO: add ipv6
source := getDefaultPrefix(route.Network)
Expand Down

0 comments on commit cef794a

Please # to comment.