Compare commits

...

14 Commits

Author SHA1 Message Date
Maycon Santos
65052e5cba use dns.Client.Exchange 2023-11-03 20:35:52 +01:00
Pascal Fischer
64084ca130 trying to bind the DNS resolver dialer to an interface 2023-11-03 14:26:07 +01:00
Pascal Fischer
79f60b86c4 fix route deletion 2023-10-27 17:44:58 +02:00
Pascal Fischer
de46393a7c updated 2023-10-23 18:31:40 +02:00
Pascal Fischer
c4c59ed3a7 fix 2023-10-09 14:59:21 +02:00
Pascal Fischer
7f958e9338 trying to add DNS 2023-10-09 14:58:48 +02:00
Pascal Fischer
91b45eab98 Merge remote-tracking branch 'origin/main' into local/engine-restart 2023-10-06 16:33:01 +02:00
Pascal Fischer
ec8eb76b42 small refactor for better code quality in swift 2023-10-06 16:32:30 +02:00
Pascal Fischer
8b8e4bbc6a support for routes and working connection 2023-10-05 20:15:44 +02:00
Pascal Fischer
e733cdcf33 first working connection 2023-09-25 13:02:56 +02:00
Pascal Fischer
cdbe9c4eef Merge branch 'main' into local/engine-restart 2023-09-21 16:43:03 +02:00
Pascal Fischer
8653c32367 logger and first client 2023-09-21 16:42:44 +02:00
Pascal Fischer
6743054451 inject logger that does not compile 2023-08-17 13:35:38 +02:00
Pascal Fischer
7f7e10121d starting engine by passing file descriptor on engine start 2023-08-09 15:21:53 +02:00
42 changed files with 1398 additions and 64 deletions

View File

@@ -42,6 +42,16 @@ func RunClientMobile(ctx context.Context, config *Config, statusRecorder *peer.S
return runClient(ctx, config, statusRecorder, mobileDependency)
}
func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, fileDescriptor int32, routeListener routemanager.RouteListener, dnsManager dns.IosDnsManager, interfaceName string) error {
mobileDependency := MobileDependency{
FileDescriptor: fileDescriptor,
InterfaceName: interfaceName,
RouteListener: routeListener,
DnsManager: dnsManager,
}
return runClient(ctx, config, statusRecorder, mobileDependency)
}
func runClient(ctx context.Context, config *Config, statusRecorder *peer.Status, mobileDependency MobileDependency) error {
backOff := &backoff.ExponentialBackOff{
InitialInterval: time.Second,

View File

@@ -39,7 +39,7 @@ func (f *fileConfigurator) supportCustomPort() bool {
return false
}
func (f *fileConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig) error {
backupFileExist := false
_, err := os.Stat(fileDefaultResolvConfBackupLocation)
if err == nil {

View File

@@ -8,12 +8,12 @@ import (
)
type hostManager interface {
applyDNSConfig(config hostDNSConfig) error
applyDNSConfig(config HostDNSConfig) error
restoreHostDNS() error
supportCustomPort() bool
}
type hostDNSConfig struct {
type HostDNSConfig struct {
domains []domainConfig
routeAll bool
serverIP string
@@ -27,12 +27,12 @@ type domainConfig struct {
}
type mockHostConfigurator struct {
applyDNSConfigFunc func(config hostDNSConfig) error
applyDNSConfigFunc func(config HostDNSConfig) error
restoreHostDNSFunc func() error
supportCustomPortFunc func() bool
}
func (m *mockHostConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (m *mockHostConfigurator) applyDNSConfig(config HostDNSConfig) error {
if m.applyDNSConfigFunc != nil {
return m.applyDNSConfigFunc(config)
}
@@ -55,14 +55,14 @@ func (m *mockHostConfigurator) supportCustomPort() bool {
func newNoopHostMocker() hostManager {
return &mockHostConfigurator{
applyDNSConfigFunc: func(config hostDNSConfig) error { return nil },
applyDNSConfigFunc: func(config HostDNSConfig) error { return nil },
restoreHostDNSFunc: func() error { return nil },
supportCustomPortFunc: func() bool { return true },
}
}
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) hostDNSConfig {
config := hostDNSConfig{
func dnsConfigToHostDNSConfig(dnsConfig nbdns.Config, ip string, port int) HostDNSConfig {
config := HostDNSConfig{
routeAll: false,
serverIP: ip,
serverPort: port,

View File

@@ -7,7 +7,7 @@ func newHostManager(wgInterface WGIface) (hostManager, error) {
return &androidHostManager{}, nil
}
func (a androidHostManager) applyDNSConfig(config hostDNSConfig) error {
func (a androidHostManager) applyDNSConfig(config HostDNSConfig) error {
return nil
}

View File

@@ -1,3 +1,5 @@
//go:build !ios
package dns
import (
@@ -42,7 +44,7 @@ func (s *systemConfigurator) supportCustomPort() bool {
return true
}
func (s *systemConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig) error {
var err error
if config.routeAll {

View File

@@ -0,0 +1,48 @@
package dns
import (
"strconv"
"strings"
log "github.com/sirupsen/logrus"
)
type iosHostManager struct {
dnsManager IosDnsManager
config HostDNSConfig
}
func newHostManager(wgInterface WGIface, dnsManager IosDnsManager) (hostManager, error) {
return &iosHostManager{
dnsManager: dnsManager,
}, nil
}
func (a iosHostManager) applyDNSConfig(config HostDNSConfig) error {
var configAsString []string
configAsString = append(configAsString, config.serverIP)
configAsString = append(configAsString, strconv.Itoa(config.serverPort))
configAsString = append(configAsString, strconv.FormatBool(config.routeAll))
var domainConfigAsString []string
for _, domain := range config.domains {
var domainAsString []string
domainAsString = append(domainAsString, strconv.FormatBool(domain.disabled))
domainAsString = append(domainAsString, domain.domain)
domainAsString = append(domainAsString, strconv.FormatBool(domain.matchOnly))
domainConfigAsString = append(domainConfigAsString, strings.Join(domainAsString, "|"))
}
domainConfig := strings.Join(domainConfigAsString, ";")
configAsString = append(configAsString, domainConfig)
outputString := strings.Join(configAsString, ",")
log.Debug("applyDNSConfig: " + outputString)
a.dnsManager.ApplyDns(outputString)
return nil
}
func (a iosHostManager) restoreHostDNS() error {
return nil
}
func (a iosHostManager) supportCustomPort() bool {
return false
}

View File

@@ -45,7 +45,7 @@ func (s *registryConfigurator) supportCustomPort() bool {
return false
}
func (r *registryConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (r *registryConfigurator) applyDNSConfig(config HostDNSConfig) error {
var err error
if config.routeAll {
err = r.addDNSSetupForAll(config.serverIP)

View File

@@ -93,7 +93,7 @@ func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
return false
}
func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
connSettings, configVersion, err := n.getAppliedConnectionSettings()
if err != nil {
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)

View File

@@ -27,7 +27,7 @@ func (r *resolvconf) supportCustomPort() bool {
return false
}
func (r *resolvconf) applyDNSConfig(config hostDNSConfig) error {
func (r *resolvconf) applyDNSConfig(config HostDNSConfig) error {
var err error
if !config.routeAll {
err = r.restoreHostDNS()

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/netip"
"runtime"
"sync"
"github.com/miekg/dns"
@@ -18,9 +19,14 @@ type ReadyListener interface {
OnReady()
}
// IosDnsManager is a dns manager interface for iosß
type IosDnsManager interface {
ApplyDns(string)
}
// Server is a dns server interface
type Server interface {
Initialize() error
Initialize(manager IosDnsManager) error
Stop()
DnsIP() string
UpdateDNSServer(serial uint64, update nbdns.Config) error
@@ -41,12 +47,15 @@ type DefaultServer struct {
hostManager hostManager
updateSerial uint64
previousConfigHash uint64
currentConfig hostDNSConfig
currentConfig HostDNSConfig
// permanent related properties
permanent bool
hostsDnsList []string
hostsDnsListLock sync.Mutex
interfaceName string
wgAddr string
}
type handlerWithStop interface {
@@ -60,7 +69,7 @@ type muxUpdate struct {
}
// NewDefaultServer returns a new dns server
func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string) (*DefaultServer, error) {
func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string, interfaceName string, wgAddr string) (*DefaultServer, error) {
var addrPort *netip.AddrPort
if customAddress != "" {
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
@@ -77,13 +86,13 @@ func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress st
dnsService = newServiceViaListener(wgInterface, addrPort)
}
return newDefaultServer(ctx, wgInterface, dnsService), nil
return newDefaultServer(ctx, wgInterface, dnsService, interfaceName, wgAddr), nil
}
// NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems
func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, hostsDnsList []string) *DefaultServer {
log.Debugf("host dns address list is: %v", hostsDnsList)
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface))
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), "", "")
ds.permanent = true
ds.hostsDnsList = hostsDnsList
ds.addHostRootZone()
@@ -91,7 +100,7 @@ func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface,
return ds
}
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service) *DefaultServer {
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service, interfaceName string, wgAddr string) *DefaultServer {
ctx, stop := context.WithCancel(ctx)
defaultServer := &DefaultServer{
ctx: ctx,
@@ -101,14 +110,16 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi
localResolver: &localResolver{
registeredMap: make(registrationMap),
},
wgInterface: wgInterface,
wgInterface: wgInterface,
interfaceName: interfaceName,
wgAddr: wgAddr,
}
return defaultServer
}
// Initialize instantiate host manager and the dns service
func (s *DefaultServer) Initialize() (err error) {
func (s *DefaultServer) Initialize(manager IosDnsManager) (err error) {
s.mux.Lock()
defer s.mux.Unlock()
@@ -123,7 +134,11 @@ func (s *DefaultServer) Initialize() (err error) {
}
}
s.hostManager, err = newHostManager(s.wgInterface)
if runtime.GOOS == "ios" {
s.hostManager, err = newHostManager(nil, manager)
} else {
s.hostManager, err = newHostManager(s.wgInterface, nil)
}
return
}
@@ -285,7 +300,7 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
continue
}
handler := newUpstreamResolver(s.ctx)
handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr)
for _, ns := range nsGroup.NameServers {
if ns.NSType != nbdns.UDPNameServerType {
log.Warnf("skiping nameserver %s with type %s, this peer supports only %s",
@@ -458,7 +473,7 @@ func (s *DefaultServer) upstreamCallbacks(
}
func (s *DefaultServer) addHostRootZone() {
handler := newUpstreamResolver(s.ctx)
handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr)
handler.upstreamServers = make([]string, len(s.hostsDnsList))
for n, ua := range s.hostsDnsList {
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua)

View File

@@ -527,7 +527,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
registeredMap: make(registrationMap),
},
hostManager: hostManager,
currentConfig: hostDNSConfig{
currentConfig: HostDNSConfig{
domains: []domainConfig{
{false, "domain0", false},
{false, "domain1", false},
@@ -537,7 +537,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
}
var domainsUpdate string
hostManager.applyDNSConfigFunc = func(config hostDNSConfig) error {
hostManager.applyDNSConfigFunc = func(config HostDNSConfig) error {
domains := []string{}
for _, item := range config.domains {
if item.disabled {

View File

@@ -81,7 +81,7 @@ func (s *systemdDbusConfigurator) supportCustomPort() bool {
return true
}
func (s *systemdDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig) error {
parsedIP, err := netip.ParseAddr(config.serverIP)
if err != nil {
return fmt.Errorf("unable to parse ip address, error: %s", err)

View File

@@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"net"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
@@ -35,23 +38,93 @@ type upstreamResolver struct {
mutex sync.Mutex
reactivatePeriod time.Duration
upstreamTimeout time.Duration
lIP net.IP
lName string
iIndex int
deactivate func()
reactivate func()
}
func newUpstreamResolver(parentCTX context.Context) *upstreamResolver {
// func newUpstreamResolver(parentCTX context.Context) *upstreamResolver {
// ctx, cancel := context.WithCancel(parentCTX)
// return &upstreamResolver{
// ctx: ctx,
// cancel: cancel,
// upstreamClient: &dns.Client{},
// upstreamTimeout: upstreamTimeout,
// reactivatePeriod: reactivatePeriod,
// failsTillDeact: failsTillDeact,
// }
// }
func getInterfaceIndex(interfaceName string) (int, error) {
iface, err := net.InterfaceByName(interfaceName)
if err != nil {
log.Errorf("unable to get interface by name error: %s", err)
return 0, err
}
return iface.Index, nil
}
func newUpstreamResolver(parentCTX context.Context, interfaceName string, wgAddr string) *upstreamResolver {
ctx, cancel := context.WithCancel(parentCTX)
// Specify the local IP address you want to bind to
localIP, _, err := net.ParseCIDR(wgAddr) // Should be our interface IP
if err != nil {
log.Errorf("error while parsing CIDR: %s", err)
}
index, err := getInterfaceIndex(interfaceName)
log.Debugf("UpstreamResolver interface name: %s, index: %d, ip: %s", interfaceName, index, localIP)
if err != nil {
log.Debugf("unable to get interface index for %s: %s", interfaceName, err)
}
localIFaceIndex := index // Should be our interface index
return &upstreamResolver{
ctx: ctx,
cancel: cancel,
upstreamClient: &dns.Client{},
upstreamTimeout: upstreamTimeout,
reactivatePeriod: reactivatePeriod,
failsTillDeact: failsTillDeact,
lIP: localIP,
iIndex: localIFaceIndex,
lName: interfaceName,
}
}
func (u *upstreamResolver) getClient() *dns.Client {
dialer := &net.Dialer{
LocalAddr: &net.UDPAddr{
IP: u.lIP,
Port: 0, // Let the OS pick a free port
},
Timeout: upstreamTimeout,
Control: func(network, address string, c syscall.RawConn) error {
var operr error
fn := func(s uintptr) {
operr = unix.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, u.iIndex)
}
if err := c.Control(fn); err != nil {
return err
}
if operr != nil {
log.Errorf("error while setting socket option: %s", operr)
}
return operr
},
}
client := &dns.Client{
Dialer: dialer,
}
return client
}
func (u *upstreamResolver) stop() {
log.Debugf("stoping serving DNS for upstreams %s", u.upstreamServers)
u.cancel()
@@ -61,7 +134,7 @@ func (u *upstreamResolver) stop() {
func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
defer u.checkUpstreamFails()
log.WithField("question", r.Question[0]).Trace("received an upstream question")
//log.WithField("question", r.Question[0]).Debug("received an upstream question")
select {
case <-u.ctx.Done():
@@ -70,10 +143,20 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
}
for _, upstream := range u.upstreamServers {
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
rm, t, err := u.upstreamClient.ExchangeContext(ctx, r, upstream)
cancel()
var (
err error
t time.Duration
rm *dns.Msg
)
upstreamExchangeClient := u.getClient()
if runtime.GOOS != "ios" {
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
rm, t, err = upstreamExchangeClient.ExchangeContext(ctx, r, upstream)
cancel()
} else {
log.Debugf("ios upstream resolver: %s", upstream)
rm, t, err = upstreamExchangeClient.Exchange(r, upstream)
}
if err != nil {
if err == context.DeadlineExceeded || isTimeout(err) {
@@ -83,11 +166,11 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
}
u.failsCount.Add(1)
log.WithError(err).WithField("upstream", upstream).
Error("got an error while querying the upstream")
Error("got other error while querying the upstream")
return
}
log.Tracef("took %s to query the upstream %s", t, upstream)
log.Debugf("took %s to query the upstream %s", t, upstream)
err = w.WriteMsg(rm)
if err != nil {
@@ -118,10 +201,11 @@ func (u *upstreamResolver) checkUpstreamFails() {
case <-u.ctx.Done():
return
default:
log.Warnf("upstream resolving is disabled for %v", reactivatePeriod)
u.deactivate()
u.disabled = true
go u.waitUntilResponse()
//todo test the deactivation logic, it seems to affect the client
//log.Warnf("upstream resolving is disabled for %v", reactivatePeriod)
//u.deactivate()
//u.disabled = true
//go u.waitUntilResponse()
}
}

View File

@@ -206,7 +206,7 @@ func (e *Engine) Start() error {
} else {
// todo fix custom address
if e.dnsServer == nil {
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.mobileDep.InterfaceName, wgAddr)
if err != nil {
e.close()
return err
@@ -214,16 +214,22 @@ func (e *Engine) Start() error {
}
}
log.Debugf("Initial routes contain %d routes", len(routes))
e.routeManager = routemanager.NewManager(e.ctx, e.config.WgPrivateKey.PublicKey().String(), e.wgInterface, e.statusRecorder, routes)
e.mobileDep.RouteListener.SetInterfaceIP(wgAddr)
e.routeManager.SetRouteChangeListener(e.mobileDep.RouteListener)
if runtime.GOOS != "android" {
err = e.wgInterface.Create()
} else {
err = e.wgInterface.CreateOnMobile(iface.MobileIFaceArguments{
switch runtime.GOOS {
case "android":
err = e.wgInterface.CreateOnAndroid(iface.MobileIFaceArguments{
Routes: e.routeManager.InitialRouteRange(),
Dns: e.dnsServer.DnsIP(),
})
case "ios":
err = e.wgInterface.CreateOniOS(e.mobileDep.FileDescriptor)
default:
err = e.wgInterface.Create()
}
if err != nil {
log.Errorf("failed creating tunnel interface %s: [%s]", wgIFaceName, err.Error())
@@ -264,7 +270,11 @@ func (e *Engine) Start() error {
e.acl = acl
}
err = e.dnsServer.Initialize()
if runtime.GOOS == "ios" {
err = e.dnsServer.Initialize(e.mobileDep.DnsManager)
} else {
err = e.dnsServer.Initialize(nil)
}
if err != nil {
e.close()
return err
@@ -466,7 +476,7 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error {
}
// start SSH server if it wasn't running
if isNil(e.sshServer) {
//nil sshServer means it has not yet been started
// nil sshServer means it has not yet been started
var err error
e.sshServer, err = e.sshServerFunc(e.config.SSHKey,
fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort))

View File

@@ -14,4 +14,7 @@ type MobileDependency struct {
RouteListener routemanager.RouteListener
HostDNSAddresses []string
DnsReadyListener dns.ReadyListener
DnsManager dns.IosDnsManager
FileDescriptor int32
InterfaceName string
}

View File

@@ -0,0 +1,31 @@
//go:build ios
package routemanager
import (
"context"
)
// newFirewall returns a nil manager
func newFirewall(context.Context) (firewallManager, error) {
return iOSFirewallManager{}, nil
}
type iOSFirewallManager struct {
}
func (i iOSFirewallManager) RestoreOrCreateContainers() error {
return nil
}
func (i iOSFirewallManager) InsertRoutingRules(pair routerPair) error {
return nil
}
func (i iOSFirewallManager) RemoveRoutingRules(pair routerPair) error {
return nil
}
func (i iOSFirewallManager) CleanRoutingRules() {
return
}

View File

@@ -1,5 +1,5 @@
//go:build !linux
// +build !linux
//go:build !linux && !ios
// +build !linux,!ios
package routemanager

View File

@@ -2,15 +2,19 @@ package routemanager
import (
"sort"
"strings"
"sync"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/route"
)
// RouteListener is a callback interface for mobile system
type RouteListener interface {
// OnNewRouteSetting invoke when new route setting has been arrived
OnNewRouteSetting()
OnNewRouteSetting(string)
SetInterfaceIP(string)
}
type notifier struct {
@@ -55,9 +59,6 @@ func (n *notifier) onNewRoutes(idMap map[string][]*route.Route) {
n.routeRangers = newNets
if !n.hasDiff(n.initialRouteRangers, newNets) {
return
}
n.notify()
}
@@ -69,7 +70,8 @@ func (n *notifier) notify() {
}
go func(l RouteListener) {
l.OnNewRouteSetting()
log.Debugf("notifying route listener with route ranges: %s", strings.Join(n.routeRangers, ","))
l.OnNewRouteSetting(strings.Join(n.routeRangers, ","))
}(n.routeListener)
}

View File

@@ -1,3 +1,5 @@
//go:build android
package routemanager
import (

View File

@@ -0,0 +1,15 @@
//go:build ios
package routemanager
import (
"net/netip"
)
func addToRouteTableIfNoExists(prefix netip.Prefix, addr string) error {
return nil
}
func removeFromRouteTableIfNonSystem(prefix netip.Prefix, addr string) error {
return nil
}

View File

@@ -1,4 +1,4 @@
//go:build !android
//go:build !android && !ios
package routemanager

View File

@@ -0,0 +1,200 @@
package NetBirdSDK
import (
"context"
"sync"
"time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/internal/dns"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/routemanager"
"github.com/netbirdio/netbird/client/system"
"github.com/netbirdio/netbird/formatter"
)
// RouteListener export internal RouteListener for mobile
type RouteListener interface {
routemanager.RouteListener
}
// DnsManager export internal dns Manager for mobile
type DnsManager interface {
dns.IosDnsManager
}
// CustomLogger export internal CustomLogger for mobile
type CustomLogger interface {
Debug(message string)
Info(message string)
Error(message string)
}
func init() {
formatter.SetLogcatFormatter(log.StandardLogger())
}
// Client struct manage the life circle of background service
type Client struct {
cfgFile string
recorder *peer.Status
ctxCancel context.CancelFunc
ctxCancelLock *sync.Mutex
deviceName string
osName string
osVersion string
routeListener routemanager.RouteListener
onHostDnsFn func([]string)
dnsManager dns.IosDnsManager
loginComplete bool
}
// NewClient instantiate a new Client
func NewClient(cfgFile, deviceName string, osVersion string, osName string, routeListener RouteListener, dnsManager DnsManager) *Client {
return &Client{
cfgFile: cfgFile,
deviceName: deviceName,
osName: osName,
osVersion: osVersion,
recorder: peer.NewRecorder(""),
ctxCancelLock: &sync.Mutex{},
routeListener: routeListener,
dnsManager: dnsManager,
}
}
// Run start the internal client. It is a blocker function
func (c *Client) Run(fd int32, interfaceName string) error {
log.Infof("Starting NetBird client")
log.Debugf("Tunnel uses interface: %s", interfaceName)
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)
ctxWithValues = context.WithValue(ctxWithValues, system.OsNameCtxKey, c.osName)
ctxWithValues = context.WithValue(ctxWithValues, system.OsVersionCtxKey, c.osVersion)
c.ctxCancelLock.Lock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
defer c.ctxCancel()
c.ctxCancelLock.Unlock()
auth := NewAuthWithConfig(ctx, cfg)
err = auth.Login()
if err != nil {
return err
}
log.Infof("Auth successful")
// todo do not throw error in case of cancelled context
ctx = internal.CtxInitState(ctx)
c.onHostDnsFn = func([]string) {}
return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.routeListener, c.dnsManager, interfaceName)
}
// 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()
}
// ÏSetTraceLogLevel configure the logger to trace level
func (c *Client) SetTraceLogLevel() {
log.SetLevel(log.TraceLevel)
}
// getStatusDetails return with the list of the PeerInfos
func (c *Client) GetStatusDetails() *StatusDetails {
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(),
}
peerInfos[n] = pi
}
return &StatusDetails{items: peerInfos, fqdn: fullStatus.LocalPeerState.FQDN, ip: fullStatus.LocalPeerState.IP}
}
func (c *Client) GetManagementStatus() bool {
return c.recorder.GetFullStatus().ManagementState.Connected
}
func (c *Client) IsLoginRequired() bool {
var ctx context.Context
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
c.ctxCancelLock.Lock()
defer c.ctxCancelLock.Unlock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
cfg, _ := internal.UpdateOrCreateConfig(internal.ConfigInput{
ConfigPath: c.cfgFile,
})
needsLogin, _ := internal.IsLoginRequired(ctx, cfg.PrivateKey, cfg.ManagementURL, cfg.SSHKey)
return needsLogin
}
func (c *Client) LoginForMobile() string {
var ctx context.Context
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
c.ctxCancelLock.Lock()
defer c.ctxCancelLock.Unlock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
cfg, _ := internal.UpdateOrCreateConfig(internal.ConfigInput{
ConfigPath: c.cfgFile,
})
oAuthFlow, err := auth.NewOAuthFlow(ctx, cfg, false)
if err != nil {
return err.Error()
}
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
if err != nil {
return err.Error()
}
// This could cause a potential race condition with loading the extension which need to be handled on swift side
go func() {
waitTimeout := time.Duration(flowInfo.ExpiresIn)
waitCTX, cancel := context.WithTimeout(ctx, waitTimeout*time.Second)
defer cancel()
tokenInfo, err := oAuthFlow.WaitToken(waitCTX, flowInfo)
if err != nil {
return
}
jwtToken := tokenInfo.GetTokenToUse()
_ = internal.Login(ctx, cfg, "", jwtToken)
c.loginComplete = true
}()
return flowInfo.VerificationURIComplete
}
func (c *Client) IsLoginComplete() bool {
return c.loginComplete
}
func (c *Client) ClearLoginComplete() {
c.loginComplete = false
}

View File

@@ -0,0 +1,5 @@
package NetBirdSDK
import _ "golang.org/x/mobile/bind"
// to keep our CI/CD that checks go.mod and go.sum files happy, we need to import the package above

View File

@@ -0,0 +1,16 @@
package NetBirdSDK
import (
"os"
"github.com/netbirdio/netbird/util"
)
var logFile *os.File
// InitializeLog initializes the log file.
func InitializeLog(logLevel string, filePath string) error {
var err error
err = util.InitLog(logLevel, filePath)
return err
}

View File

@@ -0,0 +1,184 @@
package NetBirdSDK
import (
"context"
"fmt"
"time"
"github.com/cenkalti/backoff/v4"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/cmd"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/auth"
"github.com/netbirdio/netbird/client/system"
)
// SSOListener is async listener for mobile framework
type SSOListener interface {
OnSuccess(bool)
OnError(error)
}
// ErrListener is async listener for mobile framework
type ErrListener interface {
OnSuccess()
OnError(error)
}
// 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,
}
}
// SaveConfigIfSSOSupported test the connectivity with the management server by retrieving the server device flow info.
// If it returns a flow info than save the configuration and return true. If it gets a codes.NotFound, it means that SSO
// is not supported and returns false without saving the configuration. For other errors return false.
func (a *Auth) SaveConfigIfSSOSupported() (bool, error) {
supportsSSO := true
err := a.withBackOff(a.ctx, func() (err error) {
_, err = internal.GetDeviceAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
_, err = internal.GetPKCEAuthorizationFlowInfo(a.ctx, a.config.PrivateKey, a.config.ManagementURL)
if s, ok := gstatus.FromError(err); ok && s.Code() == codes.NotFound {
supportsSSO = false
err = nil
}
return err
}
return err
})
if !supportsSSO {
return false, nil
}
if err != nil {
return false, fmt.Errorf("backoff cycle failed: %v", err)
}
err = internal.WriteOutConfig(a.cfgPath, a.config)
return true, err
}
// LoginWithSetupKeyAndSaveConfig test the connectivity with the management server with the setup key.
func (a *Auth) LoginWithSetupKeyAndSaveConfig(setupKey string, deviceName string) error {
//nolint
ctxWithValues := context.WithValue(a.ctx, system.DeviceNameCtxKey, deviceName)
err := a.withBackOff(a.ctx, func() error {
backoffErr := internal.Login(ctxWithValues, a.config, setupKey, "")
if s, ok := gstatus.FromError(backoffErr); ok && (s.Code() == codes.PermissionDenied) {
// we got an answer from management, exit backoff earlier
return backoff.Permanent(backoffErr)
}
return backoffErr
})
if err != nil {
return fmt.Errorf("backoff cycle failed: %v", err)
}
return internal.WriteOutConfig(a.cfgPath, a.config)
}
func (a *Auth) Login() 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 {
return fmt.Errorf("Not authenticated")
}
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) (*auth.TokenInfo, error) {
oAuthFlow, err := auth.NewOAuthFlow(a.ctx, a.config, false)
if err != nil {
return nil, err
}
flowInfo, err := oAuthFlow.RequestAuthInfo(context.TODO())
if err != nil {
return nil, fmt.Errorf("getting a request OAuth flow info 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 := oAuthFlow.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)
})
}

View File

@@ -0,0 +1,50 @@
package NetBirdSDK
// 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
}
// 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
GetFQDN() string
GetIP() string
}
// StatusDetails is the implementation of the PeerInfoCollection
type StatusDetails struct {
items []PeerInfo
fqdn string
ip string
}
// Add new PeerInfo to the collection
func (array StatusDetails) Add(s PeerInfo) StatusDetails {
array.items = append(array.items, s)
return array
}
// Get return an element of the collection
func (array StatusDetails) Get(i int) *PeerInfo {
return &array.items[i]
}
// Size return with the size of the collection
func (array StatusDetails) Size() int {
return len(array.items)
}
// GetFQDN return with the FQDN of the local peer
func (array StatusDetails) GetFQDN() string {
return array.fqdn
}
// GetIP return with the IP of the local peer
func (array StatusDetails) GetIP() string {
return array.ip
}

View File

@@ -0,0 +1,78 @@
package NetBirdSDK
import (
"github.com/netbirdio/netbird/client/internal"
)
// Preferences export a subset of the internal config for gomobile
type Preferences struct {
configInput internal.ConfigInput
}
// NewPreferences create new Preferences instance
func NewPreferences(configPath string) *Preferences {
ci := internal.ConfigInput{
ConfigPath: configPath,
}
return &Preferences{ci}
}
// GetManagementURL read url from config file
func (p *Preferences) GetManagementURL() (string, error) {
if p.configInput.ManagementURL != "" {
return p.configInput.ManagementURL, nil
}
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
if err != nil {
return "", err
}
return cfg.ManagementURL.String(), err
}
// SetManagementURL store the given url and wait for commit
func (p *Preferences) SetManagementURL(url string) {
p.configInput.ManagementURL = url
}
// GetAdminURL read url from config file
func (p *Preferences) GetAdminURL() (string, error) {
if p.configInput.AdminURL != "" {
return p.configInput.AdminURL, nil
}
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
if err != nil {
return "", err
}
return cfg.AdminURL.String(), err
}
// SetAdminURL store the given url and wait for commit
func (p *Preferences) SetAdminURL(url string) {
p.configInput.AdminURL = url
}
// GetPreSharedKey read preshared key from config file
func (p *Preferences) GetPreSharedKey() (string, error) {
if p.configInput.PreSharedKey != nil {
return *p.configInput.PreSharedKey, nil
}
cfg, err := internal.ReadConfig(p.configInput.ConfigPath)
if err != nil {
return "", err
}
return cfg.PreSharedKey, err
}
// SetPreSharedKey store the given key and wait for commit
func (p *Preferences) SetPreSharedKey(key string) {
p.configInput.PreSharedKey = &key
}
// Commit write out the changes into config file
func (p *Preferences) Commit() error {
_, err := internal.UpdateOrCreateConfig(p.configInput)
return err
}

View File

@@ -0,0 +1,120 @@
package NetBirdSDK
import (
"path/filepath"
"testing"
"github.com/netbirdio/netbird/client/internal"
)
func TestPreferences_DefaultValues(t *testing.T) {
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
p := NewPreferences(cfgFile)
defaultVar, err := p.GetAdminURL()
if err != nil {
t.Fatalf("failed to read default value: %s", err)
}
if defaultVar != internal.DefaultAdminURL {
t.Errorf("invalid default admin url: %s", defaultVar)
}
defaultVar, err = p.GetManagementURL()
if err != nil {
t.Fatalf("failed to read default management URL: %s", err)
}
if defaultVar != internal.DefaultManagementURL {
t.Errorf("invalid default management url: %s", defaultVar)
}
var preSharedKey string
preSharedKey, err = p.GetPreSharedKey()
if err != nil {
t.Fatalf("failed to read default preshared key: %s", err)
}
if preSharedKey != "" {
t.Errorf("invalid preshared key: %s", preSharedKey)
}
}
func TestPreferences_ReadUncommitedValues(t *testing.T) {
exampleString := "exampleString"
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
p := NewPreferences(cfgFile)
p.SetAdminURL(exampleString)
resp, err := p.GetAdminURL()
if err != nil {
t.Fatalf("failed to read admin url: %s", err)
}
if resp != exampleString {
t.Errorf("unexpected admin url: %s", resp)
}
p.SetManagementURL(exampleString)
resp, err = p.GetManagementURL()
if err != nil {
t.Fatalf("failed to read managmenet url: %s", err)
}
if resp != exampleString {
t.Errorf("unexpected managemenet url: %s", resp)
}
p.SetPreSharedKey(exampleString)
resp, err = p.GetPreSharedKey()
if err != nil {
t.Fatalf("failed to read preshared key: %s", err)
}
if resp != exampleString {
t.Errorf("unexpected preshared key: %s", resp)
}
}
func TestPreferences_Commit(t *testing.T) {
exampleURL := "https://myurl.com:443"
examplePresharedKey := "topsecret"
cfgFile := filepath.Join(t.TempDir(), "netbird.json")
p := NewPreferences(cfgFile)
p.SetAdminURL(exampleURL)
p.SetManagementURL(exampleURL)
p.SetPreSharedKey(examplePresharedKey)
err := p.Commit()
if err != nil {
t.Fatalf("failed to save changes: %s", err)
}
p = NewPreferences(cfgFile)
resp, err := p.GetAdminURL()
if err != nil {
t.Fatalf("failed to read admin url: %s", err)
}
if resp != exampleURL {
t.Errorf("unexpected admin url: %s", resp)
}
resp, err = p.GetManagementURL()
if err != nil {
t.Fatalf("failed to read managmenet url: %s", err)
}
if resp != exampleURL {
t.Errorf("unexpected managemenet url: %s", resp)
}
resp, err = p.GetPreSharedKey()
if err != nil {
t.Fatalf("failed to read preshared key: %s", err)
}
if resp != examplePresharedKey {
t.Errorf("unexpected preshared key: %s", resp)
}
}

View File

@@ -12,6 +12,12 @@ import (
// DeviceNameCtxKey context key for device name
const DeviceNameCtxKey = "deviceName"
// OsVersionCtxKey context key for operating system version
const OsVersionCtxKey = "OsVersion"
// OsNameCtxKey context key for operating system name
const OsNameCtxKey = "OsName"
// Info is an object that contains machine information
// Most of the code is taken from https://github.com/matishsiao/goInfo
type Info struct {
@@ -52,6 +58,24 @@ func extractDeviceName(ctx context.Context, defaultName string) string {
return v
}
// extractOsVersion extracts operating system version from context or returns the default
func extractOsVersion(ctx context.Context, defaultName string) string {
v, ok := ctx.Value(OsVersionCtxKey).(string)
if !ok {
return defaultName
}
return v
}
// extractOsName extracts operating system name from context or returns the default
func extractOsName(ctx context.Context, defaultName string) string {
v, ok := ctx.Value(OsNameCtxKey).(string)
if !ok {
return defaultName
}
return v
}
// GetDesktopUIUserAgent returns the Desktop ui user agent
func GetDesktopUIUserAgent() string {
return "netbird-desktop-ui/" + version.NetbirdVersion()

View File

@@ -1,3 +1,6 @@
//go:build !ios
// +build !ios
package system
import (

27
client/system/info_ios.go Normal file
View File

@@ -0,0 +1,27 @@
//go:build ios
// +build ios
package system
import (
"context"
"runtime"
"github.com/netbirdio/netbird/version"
)
// GetInfo retrieves and parses the system information
func GetInfo(ctx context.Context) *Info {
// Convert fixed-size byte arrays to Go strings
sysName := extractOsName(ctx, "sysName")
swVersion := extractOsVersion(ctx, "swVersion")
gio := &Info{Kernel: sysName, OSVersion: swVersion, Core: swVersion, Platform: "unknown", OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
// systemHostname, _ := os.Hostname()
gio.Hostname = extractDeviceName(ctx, "hostname")
gio.WiretrusteeVersion = version.NetbirdVersion()
gio.UIVersion = extractUserAgent(ctx)
return gio
}

View File

@@ -28,14 +28,20 @@ func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter
return wgIFace, nil
}
// CreateOnMobile creates a new Wireguard interface, sets a given IP and brings it up.
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
// Will reuse an existing one.
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
w.mu.Lock()
defer w.mu.Unlock()
return w.tun.Create(mIFaceArgs)
}
// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up.
// Will reuse an existing one.
func (w *WGIface) CreateOniOS(tunFd int32) error {
return fmt.Errorf("this function has not implemented on mobile")
}
// Create this function make sense on mobile only
func (w *WGIface) Create() error {
return fmt.Errorf("this function has not implemented on mobile")

51
iface/iface_ios.go Normal file
View File

@@ -0,0 +1,51 @@
//go:build ios
// +build ios
package iface
import (
"fmt"
"sync"
"github.com/pion/transport/v2"
)
// NewWGIFace Creates a new WireGuard interface instance
func NewWGIFace(ifaceName string, address string, mtu int, tunAdapter TunAdapter, transportNet transport.Net) (*WGIface, error) {
wgIFace := &WGIface{
mu: sync.Mutex{},
}
wgAddress, err := parseWGAddress(address)
if err != nil {
return wgIFace, err
}
tun := newTunDevice(wgAddress, mtu, tunAdapter, transportNet)
wgIFace.tun = tun
wgIFace.configurer = newWGConfigurer(tun)
wgIFace.userspaceBind = !WireGuardModuleIsLoaded()
return wgIFace, nil
}
// CreateOniOS creates a new Wireguard interface, sets a given IP and brings it up.
// Will reuse an existing one.
func (w *WGIface) CreateOniOS(tunFd int32) error {
w.mu.Lock()
defer w.mu.Unlock()
return w.tun.Create(tunFd)
}
// CreateOnAndroid creates a new Wireguard interface, sets a given IP and brings it up.
// Will reuse an existing one.
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
return fmt.Errorf("this function has not implemented on mobile")
}
// Create this function make sense on mobile only
func (w *WGIface) Create() error {
return fmt.Errorf("this function has not implemented on mobile")
}

View File

@@ -1,4 +1,5 @@
//go:build !android
//go:build !android && !ios
// +build !android,!ios
package iface
@@ -27,8 +28,13 @@ func NewWGIFace(iFaceName string, address string, mtu int, tunAdapter TunAdapter
return wgIFace, nil
}
// CreateOnMobile this function make sense on mobile only
func (w *WGIface) CreateOnMobile(mIFaceArgs MobileIFaceArguments) error {
// CreateOnAndroid this function make sense on mobile only
func (w *WGIface) CreateOnAndroid(mIFaceArgs MobileIFaceArguments) error {
return fmt.Errorf("this function has not implemented on non mobile")
}
// CreateOniOS this function make sense on mobile only
func (w *WGIface) CreateOniOS(tunFd int32) error {
return fmt.Errorf("this function has not implemented on non mobile")
}

View File

@@ -1,3 +1,6 @@
//go:build android
// +build android
package iface
import (

63
iface/ipc_parser_ios.go Normal file
View File

@@ -0,0 +1,63 @@
//go:build ios
// +build ios
package iface
import (
"encoding/hex"
"fmt"
"strings"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func toWgUserspaceString(wgCfg wgtypes.Config) string {
var sb strings.Builder
if wgCfg.PrivateKey != nil {
hexKey := hex.EncodeToString(wgCfg.PrivateKey[:])
sb.WriteString(fmt.Sprintf("private_key=%s\n", hexKey))
}
if wgCfg.ListenPort != nil {
sb.WriteString(fmt.Sprintf("listen_port=%d\n", *wgCfg.ListenPort))
}
if wgCfg.ReplacePeers {
sb.WriteString("replace_peers=true\n")
}
if wgCfg.FirewallMark != nil {
sb.WriteString(fmt.Sprintf("fwmark=%d\n", *wgCfg.FirewallMark))
}
for _, p := range wgCfg.Peers {
hexKey := hex.EncodeToString(p.PublicKey[:])
sb.WriteString(fmt.Sprintf("public_key=%s\n", hexKey))
if p.PresharedKey != nil {
preSharedHexKey := hex.EncodeToString(p.PresharedKey[:])
sb.WriteString(fmt.Sprintf("preshared_key=%s\n", preSharedHexKey))
}
if p.Remove {
sb.WriteString("remove=true")
}
if p.ReplaceAllowedIPs {
sb.WriteString("replace_allowed_ips=true\n")
}
for _, aip := range p.AllowedIPs {
sb.WriteString(fmt.Sprintf("allowed_ip=%s\n", aip.String()))
}
if p.Endpoint != nil {
sb.WriteString(fmt.Sprintf("endpoint=%s\n", p.Endpoint.String()))
}
if p.PersistentKeepaliveInterval != nil {
sb.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds())))
}
}
return sb.String()
}

View File

@@ -1,3 +1,6 @@
//go:build android
// +build android
package iface
import (
@@ -55,7 +58,7 @@ func (t *tunDevice) Create(mIFaceArgs MobileIFaceArguments) error {
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
// this helps with support for the older NetBird clients that had a hardcoded direct mode
//t.device.DisableSomeRoamingForBrokenMobileSemantics()
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
err = t.device.Up()
if err != nil {

View File

@@ -1,3 +1,6 @@
//go:build !ios
// +build !ios
package iface
import (

105
iface/tun_ios.go Normal file
View File

@@ -0,0 +1,105 @@
//go:build ios
// +build ios
package iface
import (
"os"
"strings"
"github.com/pion/transport/v2"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"github.com/netbirdio/netbird/iface/bind"
)
type tunDevice struct {
address WGAddress
mtu int
tunAdapter TunAdapter
iceBind *bind.ICEBind
fd int
name string
device *device.Device
wrapper *DeviceWrapper
}
func newTunDevice(address WGAddress, mtu int, tunAdapter TunAdapter, transportNet transport.Net) *tunDevice {
return &tunDevice{
address: address,
mtu: mtu,
tunAdapter: tunAdapter,
iceBind: bind.NewICEBind(transportNet),
}
}
func (t *tunDevice) Create(tunFd int32) error {
log.Infof("create tun interface")
dupTunFd, err := unix.Dup(int(tunFd))
if err != nil {
log.Errorf("Unable to dup tun fd: %v", err)
return err
}
err = unix.SetNonblock(dupTunFd, true)
if err != nil {
log.Errorf("Unable to set tun fd as non blocking: %v", err)
unix.Close(dupTunFd)
return err
}
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0)
if err != nil {
log.Errorf("Unable to create new tun device from fd: %v", err)
unix.Close(dupTunFd)
return err
}
t.wrapper = newDeviceWrapper(tun)
log.Debug("Attaching to interface")
t.device = device.NewDevice(t.wrapper, t.iceBind, device.NewLogger(device.LogLevelSilent, "[wiretrustee] "))
// without this property mobile devices can discover remote endpoints if the configured one was wrong.
// this helps with support for the older NetBird clients that had a hardcoded direct mode
// t.device.DisableSomeRoamingForBrokenMobileSemantics()
err = t.device.Up()
if err != nil {
t.device.Close()
return err
}
log.Debugf("device is ready to use: %s", t.name)
return nil
}
func (t *tunDevice) Device() *device.Device {
return t.device
}
func (t *tunDevice) DeviceName() string {
return t.name
}
func (t *tunDevice) WgAddress() WGAddress {
return t.address
}
func (t *tunDevice) UpdateAddr(addr WGAddress) error {
// todo implement
return nil
}
func (t *tunDevice) Close() (err error) {
if t.device != nil {
t.device.Close()
}
return
}
func (t *tunDevice) routesToString(routes []string) string {
return strings.Join(routes, ";")
}

View File

@@ -1,4 +1,4 @@
//go:build (linux || darwin) && !android
//go:build (linux || darwin) && !android && !ios
package iface

165
iface/wg_configurer_ios.go Normal file
View File

@@ -0,0 +1,165 @@
//go:build ios
// +build ios
package iface
import (
"encoding/hex"
"errors"
"fmt"
"net"
"strings"
"time"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var (
errFuncNotImplemented = errors.New("function not implemented")
)
type wGConfigurer struct {
tunDevice *tunDevice
}
func newWGConfigurer(tunDevice *tunDevice) wGConfigurer {
return wGConfigurer{
tunDevice: tunDevice,
}
}
func (c *wGConfigurer) configureInterface(privateKey string, port int) error {
log.Debugf("adding Wireguard private key")
key, err := wgtypes.ParseKey(privateKey)
if err != nil {
return err
}
fwmark := 0
config := wgtypes.Config{
PrivateKey: &key,
ReplacePeers: true,
FirewallMark: &fwmark,
ListenPort: &port,
}
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
}
func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
// parse allowed ips
_, ipNet, err := net.ParseCIDR(allowedIps)
if err != nil {
return err
}
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{*ipNet},
PersistentKeepaliveInterval: &keepAlive,
PresharedKey: preSharedKey,
Endpoint: endpoint,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
}
func (c *wGConfigurer) removePeer(peerKey string) error {
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
Remove: true,
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
}
func (c *wGConfigurer) addAllowedIP(peerKey string, allowedIP string) error {
_, ipNet, err := net.ParseCIDR(allowedIP)
if err != nil {
return err
}
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
if err != nil {
return err
}
peer := wgtypes.PeerConfig{
PublicKey: peerKeyParsed,
UpdateOnly: true,
ReplaceAllowedIPs: false,
AllowedIPs: []net.IPNet{*ipNet},
}
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{peer},
}
return c.tunDevice.Device().IpcSet(toWgUserspaceString(config))
}
func (c *wGConfigurer) removeAllowedIP(peerKey string, ip string) error {
ipc, err := c.tunDevice.Device().IpcGet()
if err != nil {
return err
}
peerKeyParsed, err := wgtypes.ParseKey(peerKey)
hexKey := hex.EncodeToString(peerKeyParsed[:])
lines := strings.Split(ipc, "\n")
output := ""
foundPeer := false
removedAllowedIP := false
for _, line := range lines {
line = strings.TrimSpace(line)
// If we're within the details of the found peer and encounter another public key,
// this means we're starting another peer's details. So, reset the flag.
if strings.HasPrefix(line, "public_key=") && foundPeer {
foundPeer = false
}
// Identify the peer with the specific public key
if line == fmt.Sprintf("public_key=%s", hexKey) {
foundPeer = true
}
// If we're within the details of the found peer and find the specific allowed IP, skip this line
if foundPeer && line == "allowed_ip="+ip {
removedAllowedIP = true
continue
}
// Append the line to the output string
if strings.HasPrefix(line, "private_key=") || strings.HasPrefix(line, "listen_port=") ||
strings.HasPrefix(line, "public_key=") || strings.HasPrefix(line, "preshared_key=") ||
strings.HasPrefix(line, "endpoint=") || strings.HasPrefix(line, "persistent_keepalive_interval=") ||
strings.HasPrefix(line, "allowed_ip=") {
output += line + "\n"
}
}
if !removedAllowedIP {
return fmt.Errorf("allowedIP not found")
} else {
return c.tunDevice.Device().IpcSet(output)
}
}

View File

@@ -1,4 +1,4 @@
//go:build !android
//go:build !android && !ios
package iface
@@ -44,7 +44,7 @@ func (c *wGConfigurer) configureInterface(privateKey string, port int) error {
}
func (c *wGConfigurer) updatePeer(peerKey string, allowedIps string, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error {
//parse allowed ips
// parse allowed ips
_, ipNet, err := net.ParseCIDR(allowedIps)
if err != nil {
return err