diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index f02c3b830..dad0a15f9 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1820,6 +1820,13 @@ func (b *LocalBackend) pollRequestEngineStatus(ctx context.Context) { } } +// DebugNotify injects a fake notify message to clients. +// +// It should only be used via the LocalAPI's debug handler. +func (b *LocalBackend) DebugNotify(n ipn.Notify) { + b.send(n) +} + // send delivers n to the connected frontend and any API watchers from // LocalBackend.WatchNotifications (via the LocalAPI). // @@ -1828,7 +1835,9 @@ func (b *LocalBackend) pollRequestEngineStatus(ctx context.Context) { // // b.mu must not be held. func (b *LocalBackend) send(n ipn.Notify) { - n.Version = version.Long + if n.Version == "" { + n.Version = version.Long + } b.mu.Lock() notifyFunc := b.notify diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index f11217666..b5c5a364a 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -434,7 +434,16 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) { http.Error(w, "POST required", http.StatusMethodNotAllowed) return } - action := r.FormValue("action") + // The action is normally in a POST form parameter, but + // some actions (like "notify") want a full JSON body, so + // permit some to have their action in a header. + var action string + switch v := r.Header.Get("Debug-Action"); v { + case "notify": + action = v + default: + action = r.FormValue("action") + } var err error switch action { case "rebind": @@ -455,6 +464,14 @@ func (h *Handler) serveDebug(w http.ResponseWriter, r *http.Request) { // client already did. A future change will remove this, so don't depend // on it. h.b.RequestEngineStatus() + case "notify": + var n ipn.Notify + err = json.NewDecoder(r.Body).Decode(&n) + if err != nil { + break + } + h.b.DebugNotify(n) + case "": err = fmt.Errorf("missing parameter 'action'") default: