forked from netbirdio/netbird
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial modification to support mobile client Export necessary interfaces for Android framework
- Loading branch information
Showing
55 changed files
with
2,551 additions
and
1,072 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package android | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
|
||
log "github.com/sirupsen/logrus" | ||
|
||
"github.com/netbirdio/netbird/client/internal" | ||
"github.com/netbirdio/netbird/client/internal/peer" | ||
"github.com/netbirdio/netbird/client/system" | ||
"github.com/netbirdio/netbird/formatter" | ||
"github.com/netbirdio/netbird/iface" | ||
) | ||
|
||
// ConnectionListener export internal Listener for mobile | ||
type ConnectionListener interface { | ||
peer.Listener | ||
} | ||
|
||
// TunAdapter export internal TunAdapter for mobile | ||
type TunAdapter interface { | ||
iface.TunAdapter | ||
} | ||
|
||
func init() { | ||
formatter.SetLogcatFormatter(log.StandardLogger()) | ||
} | ||
|
||
// Client struct manage the life circle of background service | ||
type Client struct { | ||
cfgFile string | ||
tunAdapter iface.TunAdapter | ||
recorder *peer.Status | ||
ctxCancel context.CancelFunc | ||
ctxCancelLock *sync.Mutex | ||
deviceName string | ||
} | ||
|
||
// NewClient instantiate a new Client | ||
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter) *Client { | ||
lvl, _ := log.ParseLevel("trace") | ||
log.SetLevel(lvl) | ||
|
||
return &Client{ | ||
cfgFile: cfgFile, | ||
deviceName: deviceName, | ||
tunAdapter: tunAdapter, | ||
recorder: peer.NewRecorder(""), | ||
ctxCancelLock: &sync.Mutex{}, | ||
} | ||
} | ||
|
||
// Run start the internal client. It is a blocker function | ||
func (c *Client) Run(urlOpener URLOpener) error { | ||
cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ | ||
ConfigPath: c.cfgFile, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
c.recorder.UpdateManagementAddress(cfg.ManagementURL.String()) | ||
|
||
var ctx context.Context | ||
//nolint | ||
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName) | ||
c.ctxCancelLock.Lock() | ||
ctx, c.ctxCancel = context.WithCancel(ctxWithValues) | ||
defer c.ctxCancel() | ||
c.ctxCancelLock.Unlock() | ||
|
||
auth := NewAuthWithConfig(ctx, cfg) | ||
err = auth.Login(urlOpener) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// todo do not throw error in case of cancelled context | ||
ctx = internal.CtxInitState(ctx) | ||
return internal.RunClient(ctx, cfg, c.recorder, c.tunAdapter) | ||
} | ||
|
||
// Stop the internal client and free the resources | ||
func (c *Client) Stop() { | ||
c.ctxCancelLock.Lock() | ||
defer c.ctxCancelLock.Unlock() | ||
if c.ctxCancel == nil { | ||
return | ||
} | ||
|
||
c.ctxCancel() | ||
} | ||
|
||
// PeersList return with the list of the PeerInfos | ||
func (c *Client) PeersList() *PeerInfoArray { | ||
|
||
fullStatus := c.recorder.GetFullStatus() | ||
|
||
peerInfos := make([]PeerInfo, len(fullStatus.Peers)) | ||
for n, p := range fullStatus.Peers { | ||
pi := PeerInfo{ | ||
p.IP, | ||
p.FQDN, | ||
p.ConnStatus.String(), | ||
p.Direct, | ||
} | ||
peerInfos[n] = pi | ||
} | ||
|
||
return &PeerInfoArray{items: peerInfos} | ||
} | ||
|
||
// AddConnectionListener add new network connection listener | ||
func (c *Client) AddConnectionListener(listener ConnectionListener) { | ||
c.recorder.AddConnectionListener(listener) | ||
} | ||
|
||
// RemoveConnectionListener remove connection listener | ||
func (c *Client) RemoveConnectionListener(listener ConnectionListener) { | ||
c.recorder.RemoveConnectionListener(listener) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package android | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/cenkalti/backoff/v4" | ||
"github.com/netbirdio/netbird/client/cmd" | ||
"time" | ||
|
||
"github.com/netbirdio/netbird/client/internal" | ||
log "github.com/sirupsen/logrus" | ||
"google.golang.org/grpc/codes" | ||
gstatus "google.golang.org/grpc/status" | ||
) | ||
|
||
// URLOpener it is a callback interface. The Open function will be triggered if | ||
// the backend want to show an url for the user | ||
type URLOpener interface { | ||
Open(string) | ||
} | ||
|
||
// Auth can register or login new client | ||
type Auth struct { | ||
ctx context.Context | ||
config *internal.Config | ||
cfgPath string | ||
} | ||
|
||
// NewAuth instantiate Auth struct and validate the management URL | ||
func NewAuth(cfgPath string, mgmURL string) (*Auth, error) { | ||
inputCfg := internal.ConfigInput{ | ||
ManagementURL: mgmURL, | ||
} | ||
|
||
cfg, err := internal.CreateInMemoryConfig(inputCfg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &Auth{ | ||
ctx: context.Background(), | ||
config: cfg, | ||
cfgPath: cfgPath, | ||
}, nil | ||
} | ||
|
||
// NewAuthWithConfig instantiate Auth based on existing config | ||
func NewAuthWithConfig(ctx context.Context, config *internal.Config) *Auth { | ||
return &Auth{ | ||
ctx: ctx, | ||
config: config, | ||
} | ||
} | ||
|
||
// LoginAndSaveConfigIfSSOSupported test the connectivity with the management server. | ||
// If the SSO is supported than save the configuration. Return with the SSO login is supported or not. | ||
func (a *Auth) LoginAndSaveConfigIfSSOSupported() (bool, error) { | ||
var needsLogin bool | ||
err := a.withBackOff(a.ctx, func() (err error) { | ||
needsLogin, err = internal.IsLoginRequired(a.ctx, a.config.PrivateKey, a.config.ManagementURL, a.config.SSHKey) | ||
return | ||
}) | ||
if err != nil { | ||
return false, fmt.Errorf("backoff cycle failed: %v", err) | ||
} | ||
if !needsLogin { | ||
return false, nil | ||
} | ||
err = internal.WriteOutConfig(a.cfgPath, a.config) | ||
return needsLogin, err | ||
} | ||
|
||
// LoginWithSetupKeyAndSaveConfig test the connectivity with the management server with the setup key. | ||
func (a *Auth) LoginWithSetupKeyAndSaveConfig(setupKey string) error { | ||
err := a.withBackOff(a.ctx, func() error { | ||
err := internal.Login(a.ctx, a.config, setupKey, "") | ||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) { | ||
return nil | ||
} | ||
return err | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("backoff cycle failed: %v", err) | ||
} | ||
|
||
return internal.WriteOutConfig(a.cfgPath, a.config) | ||
} | ||
|
||
// Login try register the client on the server | ||
func (a *Auth) Login(urlOpener URLOpener) error { | ||
var needsLogin bool | ||
|
||
// check if we need to generate JWT token | ||
err := a.withBackOff(a.ctx, func() (err error) { | ||
needsLogin, err = internal.IsLoginRequired(a.ctx, a.config.PrivateKey, a.config.ManagementURL, a.config.SSHKey) | ||
return | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("backoff cycle failed: %v", err) | ||
} | ||
|
||
jwtToken := "" | ||
if needsLogin { | ||
tokenInfo, err := a.foregroundGetTokenInfo(urlOpener) | ||
if err != nil { | ||
return fmt.Errorf("interactive sso login failed: %v", err) | ||
} | ||
jwtToken = tokenInfo.AccessToken | ||
} | ||
|
||
err = a.withBackOff(a.ctx, func() error { | ||
err := internal.Login(a.ctx, a.config, "", jwtToken) | ||
if s, ok := gstatus.FromError(err); ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) { | ||
return nil | ||
} | ||
return err | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("backoff cycle failed: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (a *Auth) foregroundGetTokenInfo(urlOpener URLOpener) (*internal.TokenInfo, error) { | ||
providerConfig, err := internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL) | ||
if err != nil { | ||
s, ok := gstatus.FromError(err) | ||
if ok && s.Code() == codes.NotFound { | ||
return nil, fmt.Errorf("no SSO provider returned from management. " + | ||
"If you are using hosting Netbird see documentation at " + | ||
"https://github.com/netbirdio/netbird/tree/main/management for details") | ||
} else if ok && s.Code() == codes.Unimplemented { | ||
return nil, fmt.Errorf("the management server, %s, does not support SSO providers, "+ | ||
"please update your servver or use Setup Keys to login", a.config.ManagementURL) | ||
} else { | ||
return nil, fmt.Errorf("getting device authorization flow info failed with error: %v", err) | ||
} | ||
} | ||
|
||
hostedClient := internal.NewHostedDeviceFlow( | ||
providerConfig.ProviderConfig.Audience, | ||
providerConfig.ProviderConfig.ClientID, | ||
providerConfig.ProviderConfig.TokenEndpoint, | ||
providerConfig.ProviderConfig.DeviceAuthEndpoint, | ||
) | ||
|
||
flowInfo, err := hostedClient.RequestDeviceCode(context.TODO()) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting a request device code failed: %v", err) | ||
} | ||
|
||
go urlOpener.Open(flowInfo.VerificationURIComplete) | ||
|
||
waitTimeout := time.Duration(flowInfo.ExpiresIn) | ||
waitCTX, cancel := context.WithTimeout(a.ctx, waitTimeout*time.Second) | ||
defer cancel() | ||
tokenInfo, err := hostedClient.WaitToken(waitCTX, flowInfo) | ||
if err != nil { | ||
return nil, fmt.Errorf("waiting for browser login failed: %v", err) | ||
} | ||
|
||
return &tokenInfo, nil | ||
} | ||
|
||
func (a *Auth) withBackOff(ctx context.Context, bf func() error) error { | ||
return backoff.RetryNotify( | ||
bf, | ||
backoff.WithContext(cmd.CLIBackOffSettings, ctx), | ||
func(err error, duration time.Duration) { | ||
log.Warnf("retrying Login to the Management service in %v due to error %v", duration, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package android | ||
|
||
// PeerInfo describe information about the peers. It designed for the UI usage | ||
type PeerInfo struct { | ||
IP string | ||
FQDN string | ||
ConnStatus string // Todo replace to enum | ||
Direct bool | ||
} | ||
|
||
// PeerInfoCollection made for Java layer to get non default types as collection | ||
type PeerInfoCollection interface { | ||
Add(s string) PeerInfoCollection | ||
Get(i int) string | ||
Size() int | ||
} | ||
|
||
// PeerInfoArray is the implementation of the PeerInfoCollection | ||
type PeerInfoArray struct { | ||
items []PeerInfo | ||
} | ||
|
||
// Add new PeerInfo to the collection | ||
func (array PeerInfoArray) Add(s PeerInfo) PeerInfoArray { | ||
array.items = append(array.items, s) | ||
return array | ||
} | ||
|
||
// Get return an element of the collection | ||
func (array PeerInfoArray) Get(i int) *PeerInfo { | ||
return &array.items[i] | ||
} | ||
|
||
// Size return with the size of the collection | ||
func (array PeerInfoArray) Size() int { | ||
return len(array.items) | ||
} |
Oops, something went wrong.