Skip to content

Commit

Permalink
Add route selection to iOS (#1944)
Browse files Browse the repository at this point in the history
  • Loading branch information
pascal-fischer authored May 10, 2024
1 parent 263abe4 commit 272ade0
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 110 deletions.
6 changes: 4 additions & 2 deletions client/android/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead

// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
}

// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
Expand All @@ -126,7 +127,8 @@ func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener

// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
return internal.RunClientMobile(ctx, cfg, c.recorder, c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
return connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, dns.items, dnsReadyListener)
}

// Stop the internal client and free the resources
Expand Down
4 changes: 3 additions & 1 deletion client/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
SetupCloseHandler(ctx, cancel)
return internal.RunClient(ctx, config, peer.NewRecorder(config.ManagementURL.String()))

connectClient := internal.NewConnectClient(ctx, config, peer.NewRecorder(config.ManagementURL.String()))
return connectClient.Run()
}

func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
Expand Down
129 changes: 71 additions & 58 deletions client/internal/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"runtime"
"runtime/debug"
"strings"
"sync"
"time"

"github.com/cenkalti/backoff/v4"
Expand All @@ -29,30 +30,45 @@ import (
"github.com/netbirdio/netbird/version"
)

// RunClient with main logic.
func RunClient(ctx context.Context, config *Config, statusRecorder *peer.Status) error {
return runClient(ctx, config, statusRecorder, MobileDependency{}, nil, nil, nil, nil, nil)
type ConnectClient struct {
ctx context.Context
config *Config
statusRecorder *peer.Status
engine *Engine
engineMutex sync.Mutex
}

// RunClientWithProbes runs the client's main logic with probes attached
func RunClientWithProbes(
func NewConnectClient(
ctx context.Context,
config *Config,
statusRecorder *peer.Status,

) *ConnectClient {
return &ConnectClient{
ctx: ctx,
config: config,
statusRecorder: statusRecorder,
engineMutex: sync.Mutex{},
}
}

// Run with main logic.
func (c *ConnectClient) Run() error {
return c.run(MobileDependency{}, nil, nil, nil, nil)
}

// RunWithProbes runs the client's main logic with probes attached
func (c *ConnectClient) RunWithProbes(
mgmProbe *Probe,
signalProbe *Probe,
relayProbe *Probe,
wgProbe *Probe,
engineChan chan<- *Engine,
) error {
return runClient(ctx, config, statusRecorder, MobileDependency{}, mgmProbe, signalProbe, relayProbe, wgProbe, engineChan)
return c.run(MobileDependency{}, mgmProbe, signalProbe, relayProbe, wgProbe)
}

// RunClientMobile with main logic on mobile system
func RunClientMobile(
ctx context.Context,
config *Config,
statusRecorder *peer.Status,
// RunOnAndroid with main logic on mobile system
func (c *ConnectClient) RunOnAndroid(
tunAdapter iface.TunAdapter,
iFaceDiscover stdnet.ExternalIFaceDiscover,
networkChangeListener listener.NetworkChangeListener,
Expand All @@ -67,13 +83,10 @@ func RunClientMobile(
HostDNSAddresses: dnsAddresses,
DnsReadyListener: dnsReadyListener,
}
return runClient(ctx, config, statusRecorder, mobileDependency, nil, nil, nil, nil, nil)
return c.run(mobileDependency, nil, nil, nil, nil)
}

func RunClientiOS(
ctx context.Context,
config *Config,
statusRecorder *peer.Status,
func (c *ConnectClient) RunOniOS(
fileDescriptor int32,
networkChangeListener listener.NetworkChangeListener,
dnsManager dns.IosDnsManager,
Expand All @@ -83,19 +96,15 @@ func RunClientiOS(
NetworkChangeListener: networkChangeListener,
DnsManager: dnsManager,
}
return runClient(ctx, config, statusRecorder, mobileDependency, nil, nil, nil, nil, nil)
return c.run(mobileDependency, nil, nil, nil, nil)
}

func runClient(
ctx context.Context,
config *Config,
statusRecorder *peer.Status,
func (c *ConnectClient) run(
mobileDependency MobileDependency,
mgmProbe *Probe,
signalProbe *Probe,
relayProbe *Probe,
wgProbe *Probe,
engineChan chan<- *Engine,
) error {
defer func() {
if r := recover(); r != nil {
Expand All @@ -107,7 +116,7 @@ func runClient(

// Check if client was not shut down in a clean way and restore DNS config if required.
// Otherwise, we might not be able to connect to the management server to retrieve new config.
if err := dns.CheckUncleanShutdown(config.WgIface); err != nil {
if err := dns.CheckUncleanShutdown(c.config.WgIface); err != nil {
log.Errorf("checking unclean shutdown error: %s", err)
}

Expand All @@ -121,7 +130,7 @@ func runClient(
Clock: backoff.SystemClock,
}

state := CtxGetState(ctx)
state := CtxGetState(c.ctx)
defer func() {
s, err := state.Status()
if err != nil || s != StatusNeedsLogin {
Expand All @@ -130,49 +139,49 @@ func runClient(
}()

wrapErr := state.Wrap
myPrivateKey, err := wgtypes.ParseKey(config.PrivateKey)
myPrivateKey, err := wgtypes.ParseKey(c.config.PrivateKey)
if err != nil {
log.Errorf("failed parsing Wireguard key %s: [%s]", config.PrivateKey, err.Error())
log.Errorf("failed parsing Wireguard key %s: [%s]", c.config.PrivateKey, err.Error())
return wrapErr(err)
}

var mgmTlsEnabled bool
if config.ManagementURL.Scheme == "https" {
if c.config.ManagementURL.Scheme == "https" {
mgmTlsEnabled = true
}

publicSSHKey, err := ssh.GeneratePublicKey([]byte(config.SSHKey))
publicSSHKey, err := ssh.GeneratePublicKey([]byte(c.config.SSHKey))
if err != nil {
return err
}

defer statusRecorder.ClientStop()
defer c.statusRecorder.ClientStop()
operation := func() error {
// if context cancelled we not start new backoff cycle
select {
case <-ctx.Done():
case <-c.ctx.Done():
return nil
default:
}

state.Set(StatusConnecting)

engineCtx, cancel := context.WithCancel(ctx)
engineCtx, cancel := context.WithCancel(c.ctx)
defer func() {
statusRecorder.MarkManagementDisconnected(state.err)
statusRecorder.CleanLocalPeerState()
c.statusRecorder.MarkManagementDisconnected(state.err)
c.statusRecorder.CleanLocalPeerState()
cancel()
}()

log.Debugf("connecting to the Management service %s", config.ManagementURL.Host)
mgmClient, err := mgm.NewClient(engineCtx, config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
log.Debugf("connecting to the Management service %s", c.config.ManagementURL.Host)
mgmClient, err := mgm.NewClient(engineCtx, c.config.ManagementURL.Host, myPrivateKey, mgmTlsEnabled)
if err != nil {
return wrapErr(gstatus.Errorf(codes.FailedPrecondition, "failed connecting to Management Service : %s", err))
}
mgmNotifier := statusRecorderToMgmConnStateNotifier(statusRecorder)
mgmNotifier := statusRecorderToMgmConnStateNotifier(c.statusRecorder)
mgmClient.SetConnStateListener(mgmNotifier)

log.Debugf("connected to the Management service %s", config.ManagementURL.Host)
log.Debugf("connected to the Management service %s", c.config.ManagementURL.Host)
defer func() {
err = mgmClient.Close()
if err != nil {
Expand All @@ -190,7 +199,7 @@ func runClient(
}
return wrapErr(err)
}
statusRecorder.MarkManagementConnected()
c.statusRecorder.MarkManagementConnected()

localPeerState := peer.LocalPeerState{
IP: loginResp.GetPeerConfig().GetAddress(),
Expand All @@ -199,18 +208,18 @@ func runClient(
FQDN: loginResp.GetPeerConfig().GetFqdn(),
}

statusRecorder.UpdateLocalPeerState(localPeerState)
c.statusRecorder.UpdateLocalPeerState(localPeerState)

signalURL := fmt.Sprintf("%s://%s",
strings.ToLower(loginResp.GetWiretrusteeConfig().GetSignal().GetProtocol().String()),
loginResp.GetWiretrusteeConfig().GetSignal().GetUri(),
)

statusRecorder.UpdateSignalAddress(signalURL)
c.statusRecorder.UpdateSignalAddress(signalURL)

statusRecorder.MarkSignalDisconnected(nil)
c.statusRecorder.MarkSignalDisconnected(nil)
defer func() {
statusRecorder.MarkSignalDisconnected(state.err)
c.statusRecorder.MarkSignalDisconnected(state.err)
}()

// with the global Wiretrustee config in hand connect (just a connection, no stream yet) Signal
Expand All @@ -226,42 +235,38 @@ func runClient(
}
}()

signalNotifier := statusRecorderToSignalConnStateNotifier(statusRecorder)
signalNotifier := statusRecorderToSignalConnStateNotifier(c.statusRecorder)
signalClient.SetConnStateListener(signalNotifier)

statusRecorder.MarkSignalConnected()
c.statusRecorder.MarkSignalConnected()

peerConfig := loginResp.GetPeerConfig()

engineConfig, err := createEngineConfig(myPrivateKey, config, peerConfig)
engineConfig, err := createEngineConfig(myPrivateKey, c.config, peerConfig)
if err != nil {
log.Error(err)
return wrapErr(err)
}

engine := NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
err = engine.Start()
c.engineMutex.Lock()
c.engine = NewEngineWithProbes(engineCtx, cancel, signalClient, mgmClient, engineConfig, mobileDependency, c.statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
c.engineMutex.Unlock()

err = c.engine.Start()
if err != nil {
log.Errorf("error while starting Netbird Connection Engine: %s", err)
return wrapErr(err)
}
if engineChan != nil {
engineChan <- engine
}

log.Infof("Netbird engine started, the IP is: %s", peerConfig.GetAddress())
state.Set(StatusConnected)

<-engineCtx.Done()
statusRecorder.ClientTeardown()
c.statusRecorder.ClientTeardown()

backOff.Reset()

if engineChan != nil {
engineChan <- nil
}

err = engine.Stop()
err = c.engine.Stop()
if err != nil {
log.Errorf("failed stopping engine %v", err)
return wrapErr(err)
Expand All @@ -276,7 +281,7 @@ func runClient(
return nil
}

statusRecorder.ClientStart()
c.statusRecorder.ClientStart()
err = backoff.Retry(operation, backOff)
if err != nil {
log.Debugf("exiting client retry loop due to unrecoverable error: %s", err)
Expand All @@ -288,6 +293,14 @@ func runClient(
return nil
}

func (c *ConnectClient) Engine() *Engine {
var e *Engine
c.engineMutex.Lock()
e = c.engine
c.engineMutex.Unlock()
return e
}

// createEngineConfig converts configuration received from Management Service to EngineConfig
func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.PeerConfig) (*EngineConfig, error) {
engineConf := &EngineConfig{
Expand Down
3 changes: 3 additions & 0 deletions client/internal/routemanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ func (m *DefaultManager) TriggerSelection(networks route.HAMap) {
defer m.mux.Unlock()

networks = m.routeSelector.FilterSelected(networks)

m.notifier.onNewRoutes(networks)

m.stopObsoleteClients(networks)

for id, routes := range networks {
Expand Down
12 changes: 10 additions & 2 deletions client/internal/routemanager/notifier.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package routemanager

import (
"runtime"
"sort"
"strings"
"sync"
Expand Down Expand Up @@ -45,8 +46,15 @@ func (n *notifier) onNewRoutes(idMap route.HAMap) {
}

sort.Strings(newNets)
if !n.hasDiff(n.initialRouteRanges, newNets) {
return
switch runtime.GOOS {
case "android":
if !n.hasDiff(n.initialRouteRanges, newNets) {
return
}
default:
if !n.hasDiff(n.routeRanges, newNets) {
return
}
}

n.routeRanges = newNets
Expand Down
3 changes: 2 additions & 1 deletion client/internal/stdnet/filter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package stdnet

import (
"runtime"
"strings"

log "github.com/sirupsen/logrus"
Expand All @@ -19,7 +20,7 @@ func InterfaceFilter(disallowList []string) func(string) bool {
}

for _, s := range disallowList {
if strings.HasPrefix(iFace, s) {
if strings.HasPrefix(iFace, s) && runtime.GOOS != "ios" {
log.Tracef("ignoring interface %s - it is not allowed", iFace)
return false
}
Expand Down
Loading

0 comments on commit 272ade0

Please sign in to comment.