mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-27 19:19:55 +00:00
Compare commits
3 Commits
follow-up-
...
fix/ios-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3352c8402 | ||
|
|
557b611b02 | ||
|
|
485fa06c94 |
@@ -117,6 +117,7 @@ func (c *ConnectClient) RunOniOS(
|
||||
networkChangeListener listener.NetworkChangeListener,
|
||||
dnsManager dns.IosDnsManager,
|
||||
stateFilePath string,
|
||||
cacheDir string,
|
||||
) error {
|
||||
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||
debug.SetGCPercent(5)
|
||||
@@ -126,6 +127,7 @@ func (c *ConnectClient) RunOniOS(
|
||||
NetworkChangeListener: networkChangeListener,
|
||||
DnsManager: dnsManager,
|
||||
StateFilePath: stateFilePath,
|
||||
TempDir: cacheDir,
|
||||
}
|
||||
return c.run(mobileDependency, nil, "")
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ type BundleGenerator struct {
|
||||
syncResponse *mgmProto.SyncResponse
|
||||
logPath string
|
||||
tempDir string
|
||||
statePath string
|
||||
cpuProfile []byte
|
||||
capturePath string
|
||||
refreshStatus func() // Optional callback to refresh status before bundle generation
|
||||
@@ -274,6 +275,7 @@ type GeneratorDependencies struct {
|
||||
SyncResponse *mgmProto.SyncResponse
|
||||
LogPath string
|
||||
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
|
||||
CapturePath string
|
||||
RefreshStatus func()
|
||||
@@ -295,6 +297,7 @@ func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGen
|
||||
syncResponse: deps.SyncResponse,
|
||||
logPath: deps.LogPath,
|
||||
tempDir: deps.TempDir,
|
||||
statePath: deps.StatePath,
|
||||
cpuProfile: deps.CPUProfile,
|
||||
capturePath: deps.CapturePath,
|
||||
refreshStatus: deps.RefreshStatus,
|
||||
@@ -811,8 +814,11 @@ func (g *BundleGenerator) addSyncResponse() error {
|
||||
}
|
||||
|
||||
func (g *BundleGenerator) addStateFile() error {
|
||||
sm := profilemanager.NewServiceManager("")
|
||||
path := sm.GetStatePath()
|
||||
path := g.statePath
|
||||
if path == "" {
|
||||
sm := profilemanager.NewServiceManager("")
|
||||
path = sm.GetStatePath()
|
||||
}
|
||||
if path == "" {
|
||||
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
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"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/listener"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/netbirdio/netbird/formatter"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"github.com/netbirdio/netbird/shared/management/domain"
|
||||
types "github.com/netbirdio/netbird/upload-server/types"
|
||||
)
|
||||
|
||||
// ConnectionListener export internal Listener for mobile
|
||||
@@ -65,6 +67,7 @@ func init() {
|
||||
type Client struct {
|
||||
cfgFile string
|
||||
stateFile string
|
||||
cacheDir string
|
||||
recorder *peer.Status
|
||||
ctxCancel context.CancelFunc
|
||||
ctxCancelLock *sync.Mutex
|
||||
@@ -75,16 +78,20 @@ type Client struct {
|
||||
onHostDnsFn func([]string)
|
||||
dnsManager dns.IosDnsManager
|
||||
loginComplete bool
|
||||
connectClient *internal.ConnectClient
|
||||
// preloadedConfig holds config loaded from JSON (used on tvOS where file writes are blocked)
|
||||
preloadedConfig *profilemanager.Config
|
||||
|
||||
stateMu sync.RWMutex
|
||||
connectClient *internal.ConnectClient
|
||||
config *profilemanager.Config
|
||||
}
|
||||
|
||||
// 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{
|
||||
cfgFile: cfgFile,
|
||||
stateFile: stateFile,
|
||||
cacheDir: cacheDir,
|
||||
deviceName: deviceName,
|
||||
osName: osName,
|
||||
osVersion: osVersion,
|
||||
@@ -161,8 +168,9 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error {
|
||||
c.onHostDnsFn = func([]string) {}
|
||||
cfg.WgIface = interfaceName
|
||||
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile)
|
||||
connectClient := internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||
c.setState(cfg, connectClient)
|
||||
return connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile, c.cacheDir)
|
||||
}
|
||||
|
||||
// Stop the internal client and free the resources
|
||||
@@ -174,6 +182,83 @@ func (c *Client) Stop() {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -354,11 +439,12 @@ func (c *Client) ClearLoginComplete() {
|
||||
}
|
||||
|
||||
func (c *Client) GetRoutesSelectionDetails() (*RoutesSelectionDetails, error) {
|
||||
if c.connectClient == nil {
|
||||
_, connectClient := c.stateSnapshot()
|
||||
if connectClient == nil {
|
||||
return nil, fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
engine := c.connectClient.Engine()
|
||||
engine := connectClient.Engine()
|
||||
if engine == nil {
|
||||
return nil, fmt.Errorf("not connected")
|
||||
}
|
||||
@@ -470,11 +556,12 @@ func prepareRouteSelectionDetails(routes []*selectRoute, resolvedDomains map[dom
|
||||
}
|
||||
|
||||
func (c *Client) SelectRoute(id string) error {
|
||||
if c.connectClient == nil {
|
||||
_, connectClient := c.stateSnapshot()
|
||||
if connectClient == nil {
|
||||
return fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
engine := c.connectClient.Engine()
|
||||
engine := connectClient.Engine()
|
||||
if engine == nil {
|
||||
return fmt.Errorf("not connected")
|
||||
}
|
||||
@@ -500,10 +587,11 @@ func (c *Client) SelectRoute(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")
|
||||
}
|
||||
engine := c.connectClient.Engine()
|
||||
engine := connectClient.Engine()
|
||||
if engine == nil {
|
||||
return fmt.Errorf("not connected")
|
||||
}
|
||||
@@ -527,6 +615,22 @@ func (c *Client) DeselectRoute(id string) error {
|
||||
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 {
|
||||
ds := d.String()
|
||||
dotIndex := strings.Index(ds, ".")
|
||||
|
||||
Reference in New Issue
Block a user