mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-31 04:59:54 +00:00
Compare commits
3 Commits
feat/dev_v
...
fix/ios-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3352c8402 | ||
|
|
557b611b02 | ||
|
|
485fa06c94 |
@@ -117,6 +117,7 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsManager dns.IosDnsManager,
|
dnsManager dns.IosDnsManager,
|
||||||
stateFilePath string,
|
stateFilePath string,
|
||||||
|
cacheDir string,
|
||||||
) error {
|
) error {
|
||||||
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||||
debug.SetGCPercent(5)
|
debug.SetGCPercent(5)
|
||||||
@@ -126,6 +127,7 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
NetworkChangeListener: networkChangeListener,
|
NetworkChangeListener: networkChangeListener,
|
||||||
DnsManager: dnsManager,
|
DnsManager: dnsManager,
|
||||||
StateFilePath: stateFilePath,
|
StateFilePath: stateFilePath,
|
||||||
|
TempDir: cacheDir,
|
||||||
}
|
}
|
||||||
return c.run(mobileDependency, nil, "")
|
return c.run(mobileDependency, nil, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ type BundleGenerator struct {
|
|||||||
syncResponse *mgmProto.SyncResponse
|
syncResponse *mgmProto.SyncResponse
|
||||||
logPath string
|
logPath string
|
||||||
tempDir string
|
tempDir string
|
||||||
|
statePath string
|
||||||
cpuProfile []byte
|
cpuProfile []byte
|
||||||
capturePath string
|
capturePath string
|
||||||
refreshStatus func() // Optional callback to refresh status before bundle generation
|
refreshStatus func() // Optional callback to refresh status before bundle generation
|
||||||
@@ -274,6 +275,7 @@ type GeneratorDependencies struct {
|
|||||||
SyncResponse *mgmProto.SyncResponse
|
SyncResponse *mgmProto.SyncResponse
|
||||||
LogPath string
|
LogPath string
|
||||||
TempDir string // Directory for temporary bundle zip files. If empty, os.TempDir() is used.
|
TempDir string // Directory for temporary bundle zip files. If empty, os.TempDir() is used.
|
||||||
|
StatePath string // Path to the state file. If empty, the ServiceManager default path is used.
|
||||||
CPUProfile []byte
|
CPUProfile []byte
|
||||||
CapturePath string
|
CapturePath string
|
||||||
RefreshStatus func()
|
RefreshStatus func()
|
||||||
@@ -295,6 +297,7 @@ func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGen
|
|||||||
syncResponse: deps.SyncResponse,
|
syncResponse: deps.SyncResponse,
|
||||||
logPath: deps.LogPath,
|
logPath: deps.LogPath,
|
||||||
tempDir: deps.TempDir,
|
tempDir: deps.TempDir,
|
||||||
|
statePath: deps.StatePath,
|
||||||
cpuProfile: deps.CPUProfile,
|
cpuProfile: deps.CPUProfile,
|
||||||
capturePath: deps.CapturePath,
|
capturePath: deps.CapturePath,
|
||||||
refreshStatus: deps.RefreshStatus,
|
refreshStatus: deps.RefreshStatus,
|
||||||
@@ -811,8 +814,11 @@ func (g *BundleGenerator) addSyncResponse() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *BundleGenerator) addStateFile() error {
|
func (g *BundleGenerator) addStateFile() error {
|
||||||
|
path := g.statePath
|
||||||
|
if path == "" {
|
||||||
sm := profilemanager.NewServiceManager("")
|
sm := profilemanager.NewServiceManager("")
|
||||||
path := sm.GetStatePath()
|
path = sm.GetStatePath()
|
||||||
|
}
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
36
client/internal/debug/debug_ios.go
Normal file
36
client/internal/debug/debug_ios.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//go:build ios
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swiftLogFile is the Swift app log written by the iOS app into the same log
|
||||||
|
// directory as the Go client log, so it can be collected into the bundle.
|
||||||
|
const swiftLogFile = "swift-log.log"
|
||||||
|
|
||||||
|
// addPlatformLog collects logs for the iOS debug bundle. iOS has no logcat or
|
||||||
|
// systemd journal, so we rely on file-based logs. addLogfile handles the Go
|
||||||
|
// client log (logPath) with rotation, the stderr/stdout companions and
|
||||||
|
// anonymization. The iOS app writes its own Swift log into the same directory,
|
||||||
|
// so we add it alongside the Go log.
|
||||||
|
func (g *BundleGenerator) addPlatformLog() error {
|
||||||
|
if err := g.addLogfile(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.logPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
swiftLogPath := filepath.Join(filepath.Dir(g.logPath), swiftLogFile)
|
||||||
|
if err := g.addSingleLogfile(swiftLogPath, swiftLogFile); err != nil {
|
||||||
|
// The Swift log is best-effort: the app may not have written it yet.
|
||||||
|
log.Warnf("failed to add %s to debug bundle: %v", swiftLogFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !android
|
//go:build !android && !ios
|
||||||
|
|
||||||
package debug
|
package debug
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/debug"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
"github.com/netbirdio/netbird/client/internal/peer"
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/formatter"
|
"github.com/netbirdio/netbird/formatter"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
|
types "github.com/netbirdio/netbird/upload-server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnectionListener export internal Listener for mobile
|
// ConnectionListener export internal Listener for mobile
|
||||||
@@ -65,6 +67,7 @@ func init() {
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
cfgFile string
|
cfgFile string
|
||||||
stateFile string
|
stateFile string
|
||||||
|
cacheDir string
|
||||||
recorder *peer.Status
|
recorder *peer.Status
|
||||||
ctxCancel context.CancelFunc
|
ctxCancel context.CancelFunc
|
||||||
ctxCancelLock *sync.Mutex
|
ctxCancelLock *sync.Mutex
|
||||||
@@ -75,16 +78,20 @@ type Client struct {
|
|||||||
onHostDnsFn func([]string)
|
onHostDnsFn func([]string)
|
||||||
dnsManager dns.IosDnsManager
|
dnsManager dns.IosDnsManager
|
||||||
loginComplete bool
|
loginComplete bool
|
||||||
connectClient *internal.ConnectClient
|
|
||||||
// preloadedConfig holds config loaded from JSON (used on tvOS where file writes are blocked)
|
// preloadedConfig holds config loaded from JSON (used on tvOS where file writes are blocked)
|
||||||
preloadedConfig *profilemanager.Config
|
preloadedConfig *profilemanager.Config
|
||||||
|
|
||||||
|
stateMu sync.RWMutex
|
||||||
|
connectClient *internal.ConnectClient
|
||||||
|
config *profilemanager.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient instantiate a new Client
|
// NewClient instantiate a new Client
|
||||||
func NewClient(cfgFile, stateFile, deviceName string, osVersion string, osName string, networkChangeListener NetworkChangeListener, dnsManager DnsManager) *Client {
|
func NewClient(cfgFile, stateFile, cacheDir, deviceName string, osVersion string, osName string, networkChangeListener NetworkChangeListener, dnsManager DnsManager) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
cfgFile: cfgFile,
|
cfgFile: cfgFile,
|
||||||
stateFile: stateFile,
|
stateFile: stateFile,
|
||||||
|
cacheDir: cacheDir,
|
||||||
deviceName: deviceName,
|
deviceName: deviceName,
|
||||||
osName: osName,
|
osName: osName,
|
||||||
osVersion: osVersion,
|
osVersion: osVersion,
|
||||||
@@ -161,8 +168,9 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error {
|
|||||||
c.onHostDnsFn = func([]string) {}
|
c.onHostDnsFn = func([]string) {}
|
||||||
cfg.WgIface = interfaceName
|
cfg.WgIface = interfaceName
|
||||||
|
|
||||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile)
|
c.setState(cfg, connectClient)
|
||||||
|
return connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile, c.cacheDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
@@ -174,6 +182,83 @@ func (c *Client) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.ctxCancel()
|
c.ctxCancel()
|
||||||
|
c.setState(nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugBundle generates a debug bundle, uploads it and returns the upload key.
|
||||||
|
// It works with or without a running engine: when the engine is up it reuses
|
||||||
|
// the live config, sync response and client metrics; otherwise it loads the
|
||||||
|
// config from disk (or the preloaded tvOS config).
|
||||||
|
func (c *Client) DebugBundle(anonymize bool) (string, error) {
|
||||||
|
cfg, cc := c.stateSnapshot()
|
||||||
|
|
||||||
|
// If the engine hasn't been started, load config so we can reach management.
|
||||||
|
if cfg == nil {
|
||||||
|
if c.preloadedConfig != nil {
|
||||||
|
cfg = c.preloadedConfig
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
// Use DirectUpdateOrCreateConfig to avoid atomic file operations
|
||||||
|
// (temp file + rename) blocked by the tvOS sandbox.
|
||||||
|
cfg, err = profilemanager.DirectUpdateOrCreateConfig(profilemanager.ConfigInput{
|
||||||
|
ConfigPath: c.cfgFile,
|
||||||
|
StateFilePath: c.stateFile,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("load config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := debug.GeneratorDependencies{
|
||||||
|
InternalConfig: cfg,
|
||||||
|
StatusRecorder: c.recorder,
|
||||||
|
TempDir: c.cacheDir,
|
||||||
|
StatePath: c.stateFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc != nil {
|
||||||
|
resp, err := cc.GetLatestSyncResponse()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("get latest sync response: %v", err)
|
||||||
|
}
|
||||||
|
deps.SyncResponse = resp
|
||||||
|
|
||||||
|
if e := cc.Engine(); e != nil {
|
||||||
|
if cm := e.GetClientMetrics(); cm != nil {
|
||||||
|
deps.ClientMetrics = cm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleGenerator := debug.NewBundleGenerator(
|
||||||
|
deps,
|
||||||
|
debug.BundleConfig{
|
||||||
|
Anonymize: anonymize,
|
||||||
|
IncludeSystemInfo: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
path, err := bundleGenerator.Generate()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("generate debug bundle: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
log.Errorf("failed to remove debug bundle file: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
uploadCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
key, err := debug.UploadDebugBundle(uploadCtx, types.DefaultBundleURL, cfg.ManagementURL.String(), path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("upload debug bundle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("debug bundle uploaded with key %s", key)
|
||||||
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTraceLogLevel configure the logger to trace level
|
// SetTraceLogLevel configure the logger to trace level
|
||||||
@@ -354,11 +439,12 @@ func (c *Client) ClearLoginComplete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
|
func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
|
||||||
if c.connectClient == nil {
|
_, connectClient := c.stateSnapshot()
|
||||||
|
if connectClient == nil {
|
||||||
return nil, fmt.Errorf("not connected")
|
return nil, fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
engine := c.connectClient.Engine()
|
engine := connectClient.Engine()
|
||||||
if engine == nil {
|
if engine == nil {
|
||||||
return nil, fmt.Errorf("not connected")
|
return nil, fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
@@ -470,11 +556,12 @@ func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[dom
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SelectRoute(id string) error {
|
func (c *Client) SelectRoute(id string) error {
|
||||||
if c.connectClient == nil {
|
_, connectClient := c.stateSnapshot()
|
||||||
|
if connectClient == nil {
|
||||||
return fmt.Errorf("not connected")
|
return fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
engine := c.connectClient.Engine()
|
engine := connectClient.Engine()
|
||||||
if engine == nil {
|
if engine == nil {
|
||||||
return fmt.Errorf("not connected")
|
return fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
@@ -500,10 +587,11 @@ func (c *Client) SelectRoute(id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeselectRoute(id string) error {
|
func (c *Client) DeselectRoute(id string) error {
|
||||||
if c.connectClient == nil {
|
_, connectClient := c.stateSnapshot()
|
||||||
|
if connectClient == nil {
|
||||||
return fmt.Errorf("not connected")
|
return fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
engine := c.connectClient.Engine()
|
engine := connectClient.Engine()
|
||||||
if engine == nil {
|
if engine == nil {
|
||||||
return fmt.Errorf("not connected")
|
return fmt.Errorf("not connected")
|
||||||
}
|
}
|
||||||
@@ -527,6 +615,22 @@ func (c *Client) DeselectRoute(id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setState stores the running engine state so DebugBundle can reuse the live
|
||||||
|
// config and ConnectClient. It is cleared on Stop.
|
||||||
|
func (c *Client) setState(cfg *profilemanager.Config, cc *internal.ConnectClient) {
|
||||||
|
c.stateMu.Lock()
|
||||||
|
defer c.stateMu.Unlock()
|
||||||
|
c.config = cfg
|
||||||
|
c.connectClient = cc
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateSnapshot returns the current config and ConnectClient under the lock.
|
||||||
|
func (c *Client) stateSnapshot() (*profilemanager.Config, *internal.ConnectClient) {
|
||||||
|
c.stateMu.RLock()
|
||||||
|
defer c.stateMu.RUnlock()
|
||||||
|
return c.config, c.connectClient
|
||||||
|
}
|
||||||
|
|
||||||
func formatDuration(d time.Duration) string {
|
func formatDuration(d time.Duration) string {
|
||||||
ds := d.String()
|
ds := d.String()
|
||||||
dotIndex := strings.Index(ds, ".")
|
dotIndex := strings.Index(ds, ".")
|
||||||
|
|||||||
Reference in New Issue
Block a user