diff --git a/client/cmd/up.go b/client/cmd/up.go index 79e47ad99..4578a7158 100644 --- a/client/cmd/up.go +++ b/client/cmd/up.go @@ -205,7 +205,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr connectClient := internal.NewConnectClient(ctx, r) SetupDebugHandler(ctx, config, r, connectClient, "") - return connectClient.Run(config, nil, util.FindFirstLogPath(logFiles)) + return connectClient.Run(config, nil, nil, util.FindFirstLogPath(logFiles)) } func runInDaemonMode(ctx context.Context, cmd *cobra.Command, pm *profilemanager.ProfileManager, activeProf *profilemanager.Profile, profileSwitched bool) error { diff --git a/client/embed/embed.go b/client/embed/embed.go index e0d123a4e..13e7ebfbf 100644 --- a/client/embed/embed.go +++ b/client/embed/embed.go @@ -272,7 +272,7 @@ func (c *Client) Start(startCtx context.Context) error { run := make(chan struct{}) clientErr := make(chan error, 1) go func() { - if err := client.Run(c.config, run, ""); err != nil { + if err := client.Run(c.config, nil, run, ""); err != nil { clientErr <- err } }() diff --git a/client/internal/connect.go b/client/internal/connect.go index 48207d7ce..a3af9ffd4 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -18,6 +18,7 @@ import ( "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" gstatus "google.golang.org/grpc/status" "github.com/netbirdio/netbird/client/iface/wgaddr" @@ -85,12 +86,13 @@ func (c *ConnectClient) SetUpdateManager(um *updater.Manager) { c.updateManager = um } -// Run with main logic. -func (c *ConnectClient) Run(config *profilemanager.Config, runningChan chan struct{}, logPath string) error { +// Run with main logic. md carries optional gRPC metadata (e.g. the UI +// user-agent) to forward to the management/signal services; nil when none. +func (c *ConnectClient) Run(config *profilemanager.Config, md metadata.MD, runningChan chan struct{}, logPath string) error { if androidRunOverride != nil { return androidRunOverride(c, config, runningChan, logPath) } - return c.sup.start(config, MobileDependency{}, runningChan, logPath) + return c.sup.start(config, md, MobileDependency{}, runningChan, logPath) } // RunOnAndroid with main logic on mobile system @@ -114,7 +116,7 @@ func (c *ConnectClient) RunOnAndroid( StateFilePath: stateFilePath, TempDir: cacheDir, } - return c.sup.start(config, mobileDependency, nil, "") + return c.sup.start(config, nil, mobileDependency, nil, "") } func (c *ConnectClient) RunOniOS( @@ -133,7 +135,7 @@ func (c *ConnectClient) RunOniOS( DnsManager: dnsManager, StateFilePath: stateFilePath, } - return c.sup.start(config, mobileDependency, nil, "") + return c.sup.start(config, nil, mobileDependency, nil, "") } // run executes a single client run. runCtx is owned by the supervisor: cancelling diff --git a/client/internal/connect_android_embed.go b/client/internal/connect_android_embed.go index c91937759..d1e5666cf 100644 --- a/client/internal/connect_android_embed.go +++ b/client/internal/connect_android_embed.go @@ -30,5 +30,5 @@ func (c *ConnectClient) runOnAndroidEmbed( HostDNSAddresses: dnsAddresses, DnsReadyListener: dnsReadyListener, } - return c.sup.start(config, mobileDependency, runningChan, logPath) + return c.sup.start(config, nil, mobileDependency, runningChan, logPath) } diff --git a/client/internal/connect_lifecycle.go b/client/internal/connect_lifecycle.go index af3eb47bb..284a8abcd 100644 --- a/client/internal/connect_lifecycle.go +++ b/client/internal/connect_lifecycle.go @@ -4,6 +4,8 @@ import ( "context" "errors" + "google.golang.org/grpc/metadata" + "github.com/netbirdio/netbird/client/internal/profilemanager" ) @@ -33,6 +35,7 @@ const ( type lifecycleCmd struct { op lifecycleOp config *profilemanager.Config + md metadata.MD mobileDep MobileDependency runningChan chan struct{} logPath string @@ -108,6 +111,12 @@ func (s *supervisor) handleStart(cmd lifecycleCmd) { } runCtx, cancel := context.WithCancel(s.ctx) + if cmd.md != nil { + // Carry caller-supplied gRPC metadata (e.g. UI user-agent) into the run + // context so the engine's management/signal calls forward it. The cancel + // still drives runCtx (metadata wrapping preserves cancellation). + runCtx = metadata.NewOutgoingContext(runCtx, cmd.md) + } s.runCancel = cancel s.curStart = &cmd @@ -163,8 +172,8 @@ func (s *supervisor) shutdown() { // startAsync enqueues a start without blocking. If done is non-nil it receives // the run's end result (or errAlreadyRunning on rejection, or the context error // on shutdown). -func (s *supervisor) startAsync(config *profilemanager.Config, mobileDep MobileDependency, runningChan chan struct{}, logPath string, done chan error) { - cmd := lifecycleCmd{op: opStart, config: config, mobileDep: mobileDep, runningChan: runningChan, logPath: logPath, done: done} +func (s *supervisor) startAsync(config *profilemanager.Config, md metadata.MD, mobileDep MobileDependency, runningChan chan struct{}, logPath string, done chan error) { + cmd := lifecycleCmd{op: opStart, config: config, md: md, mobileDep: mobileDep, runningChan: runningChan, logPath: logPath, done: done} select { case s.cmdCh <- cmd: case <-s.ctx.Done(): @@ -174,9 +183,9 @@ func (s *supervisor) startAsync(config *profilemanager.Config, mobileDep MobileD // start enqueues a start and blocks until the run terminates, preserving the // blocking contract of the legacy Run entry points. -func (s *supervisor) start(config *profilemanager.Config, mobileDep MobileDependency, runningChan chan struct{}, logPath string) error { +func (s *supervisor) start(config *profilemanager.Config, md metadata.MD, mobileDep MobileDependency, runningChan chan struct{}, logPath string) error { done := make(chan error, 1) - s.startAsync(config, mobileDep, runningChan, logPath, done) + s.startAsync(config, md, mobileDep, runningChan, logPath, done) select { case err := <-done: return err diff --git a/client/server/server.go b/client/server/server.go index 67a4a065a..2d929500e 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -285,8 +285,12 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, client *internal.Conn } }() + // Forward any gRPC metadata the RPC caller attached (e.g. the UI + // user-agent in Up); nil for boot/MDM paths that have no incoming call. + md, _ := metadata.FromOutgoingContext(ctx) + runOperation := func() error { - err := s.connectOnce(client, profileConfig, runningChan) + err := s.connectOnce(client, profileConfig, md, runningChan) if err != nil { log.Debugf("will retry the connection in the background") return err @@ -1737,9 +1741,9 @@ func (s *Server) GetFeatures(ctx context.Context, msg *proto.GetFeaturesRequest) // connectOnce performs a single client run (Run blocks, retrying its own // internal backoff, until the run ends). The outer connectWithRetryRuns // backoff re-invokes it only when a run returns an error. -func (s *Server) connectOnce(client *internal.ConnectClient, config *profilemanager.Config, runningChan chan struct{}) error { +func (s *Server) connectOnce(client *internal.ConnectClient, config *profilemanager.Config, md metadata.MD, runningChan chan struct{}) error { log.Tracef("running client connection") - if err := client.Run(config, runningChan, s.logFile); err != nil { + if err := client.Run(config, md, runningChan, s.logFile); err != nil { log.Debugf("run client connection exited with error: %v", err) return err }