From 55698c8511cb2e52fb41fa89dd71093a02cdff93 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 18 Aug 2025 10:56:17 -0700 Subject: [PATCH] ipn/localapi: plumb an event bus through the localapi.Handler (#16892) Some of the operations of the local API need an event bus to correctly instantiate other components (notably including the portmapper). This commit adds that, and as the parameter list is starting to get a bit long and hard to read, I took the opportunity to move the arguments to a config type. Only a few call sites needed to be updated and this API is not intended for general use, so I did not bother to stage the change. Updates #15160 Updates #16842 Change-Id: I7b057d71161bd859f5acb96e2f878a34c85be0ef Signed-off-by: M. J. Fromberger --- ipn/ipnserver/server.go | 8 +++++++- ipn/localapi/localapi.go | 25 ++++++++++++++++++++++--- net/portmapper/portmapper.go | 7 ++----- tsnet/tsnet.go | 16 ++++++++++++++-- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index a7ded9c00..fdbd82b0b 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -199,7 +199,13 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { ci = actorWithAccessOverride(actor, string(reason)) } - lah := localapi.NewHandler(ci, lb, s.logf, s.backendLogID) + lah := localapi.NewHandler(localapi.HandlerConfig{ + Actor: ci, + Backend: lb, + Logf: s.logf, + LogID: s.backendLogID, + EventBus: lb.Sys().Bus.Get(), + }) if actor, ok := ci.(*actor); ok { lah.PermitRead, lah.PermitWrite = actor.Permissions(lb.OperatorUserID()) lah.PermitCert = actor.CanFetchCerts() diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 0acc5a65f..a199a2908 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -172,9 +172,26 @@ var ( metrics = map[string]*clientmetric.Metric{} ) -// NewHandler creates a new LocalAPI HTTP handler. All parameters are required. -func NewHandler(actor ipnauth.Actor, b *ipnlocal.LocalBackend, logf logger.Logf, logID logid.PublicID) *Handler { - return &Handler{Actor: actor, b: b, logf: logf, backendLogID: logID, clock: tstime.StdClock{}} +// NewHandler creates a new LocalAPI HTTP handler from the given config. +func NewHandler(cfg HandlerConfig) *Handler { + return &Handler{ + Actor: cfg.Actor, + b: cfg.Backend, + logf: cfg.Logf, + backendLogID: cfg.LogID, + clock: tstime.StdClock{}, + eventBus: cfg.EventBus, + } +} + +// HandlerConfig carries the settings for a local API handler. +// All fields are required. +type HandlerConfig struct { + Actor ipnauth.Actor + Backend *ipnlocal.LocalBackend + Logf logger.Logf + LogID logid.PublicID + EventBus *eventbus.Bus } type Handler struct { @@ -203,6 +220,7 @@ type Handler struct { logf logger.Logf backendLogID logid.PublicID clock tstime.Clock + eventBus *eventbus.Bus // read-only after initialization } func (h *Handler) Logf(format string, args ...any) { @@ -850,6 +868,7 @@ func (h *Handler) serveDebugPortmap(w http.ResponseWriter, r *http.Request) { NetMon: h.b.NetMon(), DebugKnobs: debugKnobs, ControlKnobs: h.b.ControlKnobs(), + EventBus: h.eventBus, OnChange: func() { logf("portmapping changed.") logf("have mapping: %v", c.HaveMapping()) diff --git a/net/portmapper/portmapper.go b/net/portmapper/portmapper.go index 30535157c..a1ab86815 100644 --- a/net/portmapper/portmapper.go +++ b/net/portmapper/portmapper.go @@ -209,11 +209,8 @@ func (m *pmpMapping) Release(ctx context.Context) { // Config carries the settings for a [Client]. type Config struct { - // EventBus, if non-nil, is used for event publication and subscription by - // portmapper clients created from this config. - // - // TODO(creachadair): As of 2025-03-19 this is optional, but is intended to - // become required non-nil. + // EventBus, which must be non-nil, is used for event publication and + // subscription by portmapper clients created from this config. EventBus *eventbus.Bus // Logf is called to generate text logs for the client. If nil, logger.Discard is used. diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 2715917a2..06709bf8b 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -274,7 +274,13 @@ func (s *Server) Loopback() (addr string, proxyCred, localAPICred string, err er // out the CONNECT code from tailscaled/proxy.go that uses // httputil.ReverseProxy and adding auth support. go func() { - lah := localapi.NewHandler(ipnauth.Self, s.lb, s.logf, s.logid) + lah := localapi.NewHandler(localapi.HandlerConfig{ + Actor: ipnauth.Self, + Backend: s.lb, + Logf: s.logf, + LogID: s.logid, + EventBus: s.sys.Bus.Get(), + }) lah.PermitWrite = true lah.PermitRead = true lah.RequiredPassword = s.localAPICred @@ -676,7 +682,13 @@ func (s *Server) start() (reterr error) { go s.printAuthURLLoop() // Run the localapi handler, to allow fetching LetsEncrypt certs. - lah := localapi.NewHandler(ipnauth.Self, lb, tsLogf, s.logid) + lah := localapi.NewHandler(localapi.HandlerConfig{ + Actor: ipnauth.Self, + Backend: lb, + Logf: tsLogf, + LogID: s.logid, + EventBus: sys.Bus.Get(), + }) lah.PermitWrite = true lah.PermitRead = true