localapi.go 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package localapi contains the HTTP server handlers for tailscaled's API server.
  4. package localapi
  5. import (
  6. "bytes"
  7. "cmp"
  8. "crypto/subtle"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "net"
  14. "net/http"
  15. "net/netip"
  16. "net/url"
  17. "runtime"
  18. "slices"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "time"
  23. "golang.org/x/net/dns/dnsmessage"
  24. "tailscale.com/client/tailscale/apitype"
  25. "tailscale.com/envknob"
  26. "tailscale.com/feature"
  27. "tailscale.com/feature/buildfeatures"
  28. "tailscale.com/health/healthmsg"
  29. "tailscale.com/hostinfo"
  30. "tailscale.com/ipn"
  31. "tailscale.com/ipn/ipnauth"
  32. "tailscale.com/ipn/ipnlocal"
  33. "tailscale.com/ipn/ipnstate"
  34. "tailscale.com/logtail"
  35. "tailscale.com/net/netns"
  36. "tailscale.com/net/netutil"
  37. "tailscale.com/tailcfg"
  38. "tailscale.com/tstime"
  39. "tailscale.com/types/appctype"
  40. "tailscale.com/types/key"
  41. "tailscale.com/types/logger"
  42. "tailscale.com/types/logid"
  43. "tailscale.com/types/ptr"
  44. "tailscale.com/util/clientmetric"
  45. "tailscale.com/util/eventbus"
  46. "tailscale.com/util/httpm"
  47. "tailscale.com/util/mak"
  48. "tailscale.com/util/osdiag"
  49. "tailscale.com/util/rands"
  50. "tailscale.com/util/syspolicy/pkey"
  51. "tailscale.com/version"
  52. "tailscale.com/wgengine/magicsock"
  53. )
  54. var (
  55. metricInvalidRequests = clientmetric.NewCounter("localapi_invalid_requests")
  56. metricDebugMetricsCalls = clientmetric.NewCounter("localapi_debugmetric_requests")
  57. metricUserMetricsCalls = clientmetric.NewCounter("localapi_usermetric_requests")
  58. metricBugReportRequests = clientmetric.NewCounter("localapi_bugreport_requests")
  59. )
  60. type LocalAPIHandler func(*Handler, http.ResponseWriter, *http.Request)
  61. // handler is the set of LocalAPI handlers, keyed by the part of the
  62. // Request.URL.Path after "/localapi/v0/". If the key ends with a trailing slash
  63. // then it's a prefix match.
  64. var handler = map[string]LocalAPIHandler{
  65. // The prefix match handlers end with a slash:
  66. "profiles/": (*Handler).serveProfiles,
  67. // The other /localapi/v0/NAME handlers are exact matches and contain only NAME
  68. // without a trailing slash:
  69. "check-prefs": (*Handler).serveCheckPrefs,
  70. "check-so-mark-in-use": (*Handler).serveCheckSOMarkInUse,
  71. "derpmap": (*Handler).serveDERPMap,
  72. "goroutines": (*Handler).serveGoroutines,
  73. "login-interactive": (*Handler).serveLoginInteractive,
  74. "logout": (*Handler).serveLogout,
  75. "ping": (*Handler).servePing,
  76. "prefs": (*Handler).servePrefs,
  77. "reload-config": (*Handler).reloadConfig,
  78. "reset-auth": (*Handler).serveResetAuth,
  79. "set-expiry-sooner": (*Handler).serveSetExpirySooner,
  80. "shutdown": (*Handler).serveShutdown,
  81. "start": (*Handler).serveStart,
  82. "status": (*Handler).serveStatus,
  83. "whois": (*Handler).serveWhoIs,
  84. }
  85. func init() {
  86. if buildfeatures.HasAppConnectors {
  87. Register("appc-route-info", (*Handler).serveGetAppcRouteInfo)
  88. }
  89. if buildfeatures.HasAdvertiseRoutes {
  90. Register("check-ip-forwarding", (*Handler).serveCheckIPForwarding)
  91. Register("check-udp-gro-forwarding", (*Handler).serveCheckUDPGROForwarding)
  92. Register("set-udp-gro-forwarding", (*Handler).serveSetUDPGROForwarding)
  93. }
  94. if buildfeatures.HasUseExitNode && runtime.GOOS == "linux" {
  95. Register("check-reverse-path-filtering", (*Handler).serveCheckReversePathFiltering)
  96. }
  97. if buildfeatures.HasClientMetrics {
  98. Register("upload-client-metrics", (*Handler).serveUploadClientMetrics)
  99. }
  100. if buildfeatures.HasClientUpdate {
  101. Register("update/check", (*Handler).serveUpdateCheck)
  102. }
  103. if buildfeatures.HasUseExitNode {
  104. Register("suggest-exit-node", (*Handler).serveSuggestExitNode)
  105. Register("set-use-exit-node-enabled", (*Handler).serveSetUseExitNodeEnabled)
  106. }
  107. if buildfeatures.HasACME {
  108. Register("set-dns", (*Handler).serveSetDNS)
  109. }
  110. if buildfeatures.HasDebug {
  111. Register("bugreport", (*Handler).serveBugReport)
  112. Register("pprof", (*Handler).servePprof)
  113. }
  114. if buildfeatures.HasDebug || buildfeatures.HasServe {
  115. Register("watch-ipn-bus", (*Handler).serveWatchIPNBus)
  116. }
  117. if buildfeatures.HasDNS {
  118. Register("dns-osconfig", (*Handler).serveDNSOSConfig)
  119. Register("dns-query", (*Handler).serveDNSQuery)
  120. }
  121. if buildfeatures.HasUserMetrics {
  122. Register("usermetrics", (*Handler).serveUserMetrics)
  123. }
  124. if buildfeatures.HasServe {
  125. Register("query-feature", (*Handler).serveQueryFeature)
  126. }
  127. if buildfeatures.HasOutboundProxy || buildfeatures.HasSSH {
  128. Register("dial", (*Handler).serveDial)
  129. }
  130. if buildfeatures.HasClientMetrics || buildfeatures.HasDebug {
  131. Register("metrics", (*Handler).serveMetrics)
  132. }
  133. if buildfeatures.HasDebug || buildfeatures.HasAdvertiseRoutes {
  134. Register("disconnect-control", (*Handler).disconnectControl)
  135. }
  136. // Alpha/experimental/debug features. These should be moved to
  137. // their own features if/when they graduate.
  138. if buildfeatures.HasDebug {
  139. Register("id-token", (*Handler).serveIDToken)
  140. Register("alpha-set-device-attrs", (*Handler).serveSetDeviceAttrs) // see tailscale/corp#24690
  141. Register("handle-push-message", (*Handler).serveHandlePushMessage)
  142. Register("set-push-device-token", (*Handler).serveSetPushDeviceToken)
  143. }
  144. if buildfeatures.HasDebug || runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
  145. Register("set-gui-visible", (*Handler).serveSetGUIVisible)
  146. }
  147. if buildfeatures.HasLogTail {
  148. // TODO(bradfitz): separate out logtail tap functionality from upload
  149. // functionality to make this possible? But seems unlikely people would
  150. // want just this. They could "tail -f" or "journalctl -f" their logs
  151. // themselves.
  152. Register("logtap", (*Handler).serveLogTap)
  153. }
  154. }
  155. // Register registers a new LocalAPI handler for the given name.
  156. func Register(name string, fn LocalAPIHandler) {
  157. if _, ok := handler[name]; ok {
  158. panic("duplicate LocalAPI handler registration: " + name)
  159. }
  160. handler[name] = fn
  161. }
  162. var (
  163. // The clientmetrics package is stateful, but we want to expose a simple
  164. // imperative API to local clients, so we need to keep track of
  165. // clientmetric.Metric instances that we've created for them. These need to
  166. // be globals because we end up creating many Handler instances for the
  167. // lifetime of a client.
  168. metricsMu sync.Mutex
  169. metrics = map[string]*clientmetric.Metric{}
  170. )
  171. // NewHandler creates a new LocalAPI HTTP handler from the given config.
  172. func NewHandler(cfg HandlerConfig) *Handler {
  173. return &Handler{
  174. Actor: cfg.Actor,
  175. b: cfg.Backend,
  176. logf: cfg.Logf,
  177. backendLogID: cfg.LogID,
  178. clock: tstime.StdClock{},
  179. eventBus: cfg.EventBus,
  180. }
  181. }
  182. // HandlerConfig carries the settings for a local API handler.
  183. // All fields are required.
  184. type HandlerConfig struct {
  185. Actor ipnauth.Actor
  186. Backend *ipnlocal.LocalBackend
  187. Logf logger.Logf
  188. LogID logid.PublicID
  189. EventBus *eventbus.Bus
  190. }
  191. type Handler struct {
  192. // RequiredPassword, if non-empty, forces all HTTP
  193. // requests to have HTTP basic auth with this password.
  194. // It's used by the sandboxed macOS sameuserproof GUI auth mechanism.
  195. RequiredPassword string
  196. // PermitRead is whether read-only HTTP handlers are allowed.
  197. PermitRead bool
  198. // PermitWrite is whether mutating HTTP handlers are allowed.
  199. // If PermitWrite is true, everything is allowed.
  200. // It effectively means that the user is root or the admin
  201. // (operator user).
  202. PermitWrite bool
  203. // PermitCert is whether the client is additionally granted
  204. // cert fetching access.
  205. PermitCert bool
  206. // Actor is the identity of the client connected to the Handler.
  207. Actor ipnauth.Actor
  208. b *ipnlocal.LocalBackend
  209. logf logger.Logf
  210. backendLogID logid.PublicID
  211. clock tstime.Clock
  212. eventBus *eventbus.Bus // read-only after initialization
  213. }
  214. func (h *Handler) Logf(format string, args ...any) {
  215. h.logf(format, args...)
  216. }
  217. func (h *Handler) LocalBackend() *ipnlocal.LocalBackend {
  218. return h.b
  219. }
  220. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  221. if h.b == nil {
  222. http.Error(w, "server has no local backend", http.StatusInternalServerError)
  223. return
  224. }
  225. if r.Referer() != "" || r.Header.Get("Origin") != "" || !h.validHost(r.Host) {
  226. metricInvalidRequests.Add(1)
  227. http.Error(w, "invalid localapi request", http.StatusForbidden)
  228. return
  229. }
  230. w.Header().Set("Tailscale-Version", version.Long())
  231. w.Header().Set("Tailscale-Cap", strconv.Itoa(int(tailcfg.CurrentCapabilityVersion)))
  232. w.Header().Set("Content-Security-Policy", `default-src 'none'; frame-ancestors 'none'; script-src 'none'; script-src-elem 'none'; script-src-attr 'none'`)
  233. w.Header().Set("X-Frame-Options", "DENY")
  234. w.Header().Set("X-Content-Type-Options", "nosniff")
  235. if h.RequiredPassword != "" {
  236. _, pass, ok := r.BasicAuth()
  237. if !ok {
  238. metricInvalidRequests.Add(1)
  239. http.Error(w, "auth required", http.StatusUnauthorized)
  240. return
  241. }
  242. if subtle.ConstantTimeCompare([]byte(pass), []byte(h.RequiredPassword)) == 0 {
  243. metricInvalidRequests.Add(1)
  244. http.Error(w, "bad password", http.StatusForbidden)
  245. return
  246. }
  247. }
  248. if fn, route, ok := handlerForPath(r.URL.Path); ok {
  249. h.logRequest(r.Method, route)
  250. fn(h, w, r)
  251. } else {
  252. http.NotFound(w, r)
  253. }
  254. }
  255. // validLocalHostForTesting allows loopback handlers without RequiredPassword for testing.
  256. var validLocalHostForTesting = false
  257. // validHost reports whether h is a valid Host header value for a LocalAPI request.
  258. func (h *Handler) validHost(hostname string) bool {
  259. // The client code sends a hostname of "local-tailscaled.sock".
  260. switch hostname {
  261. case "", apitype.LocalAPIHost:
  262. return true
  263. }
  264. if !validLocalHostForTesting && h.RequiredPassword == "" {
  265. return false // only allow localhost with basic auth or in tests
  266. }
  267. host, _, err := net.SplitHostPort(hostname)
  268. if err != nil {
  269. return false
  270. }
  271. if host == "localhost" {
  272. return true
  273. }
  274. addr, err := netip.ParseAddr(host)
  275. if err != nil {
  276. return false
  277. }
  278. return addr.IsLoopback()
  279. }
  280. // handlerForPath returns the LocalAPI handler for the provided Request.URI.Path.
  281. // (the path doesn't include any query parameters)
  282. func handlerForPath(urlPath string) (h LocalAPIHandler, route string, ok bool) {
  283. if urlPath == "/" {
  284. return (*Handler).serveLocalAPIRoot, "/", true
  285. }
  286. suff, ok := strings.CutPrefix(urlPath, "/localapi/v0/")
  287. if !ok {
  288. // Currently all LocalAPI methods start with "/localapi/v0/" to signal
  289. // to people that they're not necessarily stable APIs. In practice we'll
  290. // probably need to keep them pretty stable anyway, but for now treat
  291. // them as an internal implementation detail.
  292. return nil, "", false
  293. }
  294. if fn, ok := handler[suff]; ok {
  295. // Here we match exact handler suffixes like "status" or ones with a
  296. // slash already in their name, like "tka/status".
  297. return fn, "/localapi/v0/" + suff, true
  298. }
  299. // Otherwise, it might be a prefix match like "files/*" which we look up
  300. // by the prefix including first trailing slash.
  301. if i := strings.IndexByte(suff, '/'); i != -1 {
  302. suff = suff[:i+1]
  303. if fn, ok := handler[suff]; ok {
  304. return fn, "/localapi/v0/" + suff, true
  305. }
  306. }
  307. return nil, "", false
  308. }
  309. func (h *Handler) logRequest(method, route string) {
  310. switch method {
  311. case httpm.GET, httpm.HEAD, httpm.OPTIONS:
  312. // don't log safe methods
  313. default:
  314. h.Logf("localapi: [%s] %s", method, route)
  315. }
  316. }
  317. func (*Handler) serveLocalAPIRoot(w http.ResponseWriter, r *http.Request) {
  318. io.WriteString(w, "tailscaled\n")
  319. }
  320. // serveIDToken handles requests to get an OIDC ID token.
  321. func (h *Handler) serveIDToken(w http.ResponseWriter, r *http.Request) {
  322. if !h.PermitWrite {
  323. http.Error(w, "id-token access denied", http.StatusForbidden)
  324. return
  325. }
  326. nm := h.b.NetMap()
  327. if nm == nil {
  328. http.Error(w, "no netmap", http.StatusServiceUnavailable)
  329. return
  330. }
  331. aud := strings.TrimSpace(r.FormValue("aud"))
  332. if len(aud) == 0 {
  333. http.Error(w, "no audience requested", http.StatusBadRequest)
  334. return
  335. }
  336. req := &tailcfg.TokenRequest{
  337. CapVersion: tailcfg.CurrentCapabilityVersion,
  338. Audience: aud,
  339. NodeKey: nm.NodeKey,
  340. }
  341. b, err := json.Marshal(req)
  342. if err != nil {
  343. http.Error(w, err.Error(), http.StatusInternalServerError)
  344. return
  345. }
  346. httpReq, err := http.NewRequest(httpm.POST, "https://unused/machine/id-token", bytes.NewReader(b))
  347. if err != nil {
  348. http.Error(w, err.Error(), http.StatusInternalServerError)
  349. return
  350. }
  351. resp, err := h.b.DoNoiseRequest(httpReq)
  352. if err != nil {
  353. http.Error(w, err.Error(), http.StatusInternalServerError)
  354. return
  355. }
  356. defer resp.Body.Close()
  357. w.WriteHeader(resp.StatusCode)
  358. if _, err := io.Copy(w, resp.Body); err != nil {
  359. http.Error(w, err.Error(), http.StatusInternalServerError)
  360. return
  361. }
  362. }
  363. func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
  364. if !h.PermitRead {
  365. http.Error(w, "bugreport access denied", http.StatusForbidden)
  366. return
  367. }
  368. if r.Method != httpm.POST {
  369. http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
  370. return
  371. }
  372. defer h.b.TryFlushLogs() // kick off upload after bugreport's done logging
  373. logMarker := func() string {
  374. return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, h.clock.Now().UTC().Format("20060102150405Z"), rands.HexString(16))
  375. }
  376. if envknob.NoLogsNoSupport() {
  377. logMarker = func() string { return "BUG-NO-LOGS-NO-SUPPORT-this-node-has-had-its-logging-disabled" }
  378. }
  379. startMarker := logMarker()
  380. h.logf("user bugreport: %s", startMarker)
  381. if note := r.URL.Query().Get("note"); len(note) > 0 {
  382. h.logf("user bugreport note: %s", note)
  383. }
  384. hi, _ := json.Marshal(hostinfo.New())
  385. h.logf("user bugreport hostinfo: %s", hi)
  386. if err := h.b.HealthTracker().OverallError(); err != nil {
  387. h.logf("user bugreport health: %s", err.Error())
  388. } else {
  389. h.logf("user bugreport health: ok")
  390. }
  391. // Information about the current node from the netmap
  392. if nm := h.b.NetMap(); nm != nil {
  393. if self := nm.SelfNode; self.Valid() {
  394. h.logf("user bugreport node info: nodeid=%q stableid=%q expiry=%q", self.ID(), self.StableID(), self.KeyExpiry().Format(time.RFC3339))
  395. }
  396. h.logf("user bugreport public keys: machine=%q node=%q", nm.MachineKey, nm.NodeKey)
  397. } else {
  398. h.logf("user bugreport netmap: no active netmap")
  399. }
  400. // Print all envknobs; we otherwise only print these on startup, and
  401. // printing them here ensures we don't have to go spelunking through
  402. // logs for them.
  403. envknob.LogCurrent(logger.WithPrefix(h.logf, "user bugreport: "))
  404. // OS-specific details
  405. h.logf.JSON(1, "UserBugReportOS", osdiag.SupportInfo(osdiag.LogSupportInfoReasonBugReport))
  406. // Tailnet Lock details
  407. st := h.b.NetworkLockStatus()
  408. if st.Enabled {
  409. h.logf.JSON(1, "UserBugReportTailnetLockStatus", st)
  410. if st.NodeKeySignature != nil {
  411. h.logf("user bugreport tailnet lock signature: %s", st.NodeKeySignature.String())
  412. }
  413. }
  414. if defBool(r.URL.Query().Get("diagnose"), false) {
  415. if f, ok := ipnlocal.HookDoctor.GetOk(); ok {
  416. f(r.Context(), h.b, logger.WithPrefix(h.logf, "diag: "))
  417. }
  418. }
  419. w.Header().Set("Content-Type", "text/plain")
  420. fmt.Fprintln(w, startMarker)
  421. // Nothing else to do if we're not in record mode; we wrote the marker
  422. // above, so we can just finish our response now.
  423. if !defBool(r.URL.Query().Get("record"), false) {
  424. return
  425. }
  426. until := h.clock.Now().Add(12 * time.Hour)
  427. var changed map[string]bool
  428. for _, component := range []string{"magicsock"} {
  429. if h.b.GetComponentDebugLogging(component).IsZero() {
  430. if err := h.b.SetComponentDebugLogging(component, until); err != nil {
  431. h.logf("bugreport: error setting component %q logging: %v", component, err)
  432. continue
  433. }
  434. mak.Set(&changed, component, true)
  435. }
  436. }
  437. defer func() {
  438. for component := range changed {
  439. h.b.SetComponentDebugLogging(component, time.Time{})
  440. }
  441. }()
  442. // NOTE(andrew): if we have anything else we want to do while recording
  443. // a bugreport, we can add it here.
  444. metricBugReportRequests.Add(1)
  445. // Read from the client; this will also return when the client closes
  446. // the connection.
  447. var buf [1]byte
  448. _, err := r.Body.Read(buf[:])
  449. switch {
  450. case err == nil:
  451. // good
  452. case errors.Is(err, io.EOF):
  453. // good
  454. case errors.Is(err, io.ErrUnexpectedEOF):
  455. // this happens when Ctrl-C'ing the tailscale client; don't
  456. // bother logging an error
  457. default:
  458. // Log but continue anyway.
  459. h.logf("user bugreport: error reading body: %v", err)
  460. }
  461. // Generate another log marker and return it to the client.
  462. endMarker := logMarker()
  463. h.logf("user bugreport end: %s", endMarker)
  464. fmt.Fprintln(w, endMarker)
  465. }
  466. func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
  467. h.serveWhoIsWithBackend(w, r, h.b)
  468. }
  469. // serveSetDeviceAttrs is (as of 2024-12-30) an experimental LocalAPI handler to
  470. // set device attributes via the control plane.
  471. //
  472. // See tailscale/corp#24690.
  473. func (h *Handler) serveSetDeviceAttrs(w http.ResponseWriter, r *http.Request) {
  474. ctx := r.Context()
  475. if !h.PermitWrite {
  476. http.Error(w, "set-device-attrs access denied", http.StatusForbidden)
  477. return
  478. }
  479. if r.Method != httpm.PATCH {
  480. http.Error(w, "only PATCH allowed", http.StatusMethodNotAllowed)
  481. return
  482. }
  483. var req map[string]any
  484. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  485. http.Error(w, err.Error(), http.StatusBadRequest)
  486. return
  487. }
  488. if err := h.b.SetDeviceAttrs(ctx, req); err != nil {
  489. http.Error(w, err.Error(), http.StatusInternalServerError)
  490. return
  491. }
  492. w.Header().Set("Content-Type", "application/json")
  493. io.WriteString(w, "{}\n")
  494. }
  495. // localBackendWhoIsMethods is the subset of ipn.LocalBackend as needed
  496. // by the localapi WhoIs method.
  497. type localBackendWhoIsMethods interface {
  498. WhoIs(string, netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
  499. WhoIsNodeKey(key.NodePublic) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
  500. PeerCaps(netip.Addr) tailcfg.PeerCapMap
  501. }
  502. func (h *Handler) serveWhoIsWithBackend(w http.ResponseWriter, r *http.Request, b localBackendWhoIsMethods) {
  503. if !h.PermitRead {
  504. http.Error(w, "whois access denied", http.StatusForbidden)
  505. return
  506. }
  507. var (
  508. n tailcfg.NodeView
  509. u tailcfg.UserProfile
  510. ok bool
  511. )
  512. var ipp netip.AddrPort
  513. if v := r.FormValue("addr"); v != "" {
  514. if strings.HasPrefix(v, "nodekey:") {
  515. var k key.NodePublic
  516. if err := k.UnmarshalText([]byte(v)); err != nil {
  517. http.Error(w, "invalid nodekey in 'addr' parameter", http.StatusBadRequest)
  518. return
  519. }
  520. n, u, ok = b.WhoIsNodeKey(k)
  521. } else if ip, err := netip.ParseAddr(v); err == nil {
  522. ipp = netip.AddrPortFrom(ip, 0)
  523. } else {
  524. var err error
  525. ipp, err = netip.ParseAddrPort(v)
  526. if err != nil {
  527. http.Error(w, "invalid 'addr' parameter", http.StatusBadRequest)
  528. return
  529. }
  530. }
  531. if ipp.IsValid() {
  532. n, u, ok = b.WhoIs(r.FormValue("proto"), ipp)
  533. }
  534. } else {
  535. http.Error(w, "missing 'addr' parameter", http.StatusBadRequest)
  536. return
  537. }
  538. if !ok {
  539. http.Error(w, "no match for IP:port", http.StatusNotFound)
  540. return
  541. }
  542. res := &apitype.WhoIsResponse{
  543. Node: n.AsStruct(), // always non-nil per WhoIsResponse contract
  544. UserProfile: &u, // always non-nil per WhoIsResponse contract
  545. }
  546. if n.Addresses().Len() > 0 {
  547. res.CapMap = b.PeerCaps(n.Addresses().At(0).Addr())
  548. }
  549. j, err := json.MarshalIndent(res, "", "\t")
  550. if err != nil {
  551. http.Error(w, "JSON encoding error", http.StatusInternalServerError)
  552. return
  553. }
  554. w.Header().Set("Content-Type", "application/json")
  555. w.Write(j)
  556. }
  557. func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
  558. // Require write access out of paranoia that the goroutine dump
  559. // (at least its arguments) might contain something sensitive.
  560. if !h.PermitWrite {
  561. http.Error(w, "goroutine dump access denied", http.StatusForbidden)
  562. return
  563. }
  564. buf := make([]byte, 2<<20)
  565. buf = buf[:runtime.Stack(buf, true)]
  566. w.Header().Set("Content-Type", "text/plain")
  567. w.Write(buf)
  568. }
  569. // serveLogTap taps into the tailscaled/logtail server output and streams
  570. // it to the client.
  571. func (h *Handler) serveLogTap(w http.ResponseWriter, r *http.Request) {
  572. ctx := r.Context()
  573. // Require write access (~root) as the logs could contain something
  574. // sensitive.
  575. if !h.PermitWrite {
  576. http.Error(w, "logtap access denied", http.StatusForbidden)
  577. return
  578. }
  579. if r.Method != httpm.GET {
  580. http.Error(w, "GET required", http.StatusMethodNotAllowed)
  581. return
  582. }
  583. f, ok := w.(http.Flusher)
  584. if !ok {
  585. http.Error(w, "streaming unsupported", http.StatusInternalServerError)
  586. return
  587. }
  588. io.WriteString(w, `{"text":"[logtap connected]\n"}`+"\n")
  589. f.Flush()
  590. msgc := make(chan string, 16)
  591. unreg := logtail.RegisterLogTap(msgc)
  592. defer unreg()
  593. for {
  594. select {
  595. case <-ctx.Done():
  596. return
  597. case msg := <-msgc:
  598. io.WriteString(w, msg)
  599. f.Flush()
  600. }
  601. }
  602. }
  603. func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) {
  604. metricDebugMetricsCalls.Add(1)
  605. // Require write access out of paranoia that the metrics
  606. // might contain something sensitive.
  607. if !h.PermitWrite {
  608. http.Error(w, "metric access denied", http.StatusForbidden)
  609. return
  610. }
  611. w.Header().Set("Content-Type", "text/plain")
  612. clientmetric.WritePrometheusExpositionFormat(w)
  613. }
  614. // serveUserMetrics returns user-facing metrics in Prometheus text
  615. // exposition format.
  616. func (h *Handler) serveUserMetrics(w http.ResponseWriter, r *http.Request) {
  617. metricUserMetricsCalls.Add(1)
  618. h.b.UserMetricsRegistry().Handler(w, r)
  619. }
  620. // servePprofFunc is the implementation of Handler.servePprof, after auth,
  621. // for platforms where we want to link it in.
  622. var servePprofFunc func(http.ResponseWriter, *http.Request)
  623. func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
  624. // Require write access out of paranoia that the profile dump
  625. // might contain something sensitive.
  626. if !h.PermitWrite {
  627. http.Error(w, "profile access denied", http.StatusForbidden)
  628. return
  629. }
  630. if servePprofFunc == nil {
  631. http.Error(w, "not implemented on this platform", http.StatusServiceUnavailable)
  632. return
  633. }
  634. servePprofFunc(w, r)
  635. }
  636. // disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that
  637. // node no longer communicates with control. Doing this makes control consider this node inactive. This can be used
  638. // before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
  639. // peers to switch over to another replica whilst still maintaining th existing peer connections.
  640. func (h *Handler) disconnectControl(w http.ResponseWriter, r *http.Request) {
  641. if !h.PermitWrite {
  642. http.Error(w, "access denied", http.StatusForbidden)
  643. return
  644. }
  645. if r.Method != httpm.POST {
  646. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  647. return
  648. }
  649. h.b.DisconnectControl()
  650. }
  651. func (h *Handler) reloadConfig(w http.ResponseWriter, r *http.Request) {
  652. if !h.PermitWrite {
  653. http.Error(w, "access denied", http.StatusForbidden)
  654. return
  655. }
  656. if r.Method != httpm.POST {
  657. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  658. return
  659. }
  660. ok, err := h.b.ReloadConfig()
  661. var res apitype.ReloadConfigResponse
  662. res.Reloaded = ok
  663. if err != nil {
  664. res.Err = err.Error()
  665. return
  666. }
  667. w.Header().Set("Content-Type", "application/json")
  668. json.NewEncoder(w).Encode(&res)
  669. }
  670. func (h *Handler) serveResetAuth(w http.ResponseWriter, r *http.Request) {
  671. if !h.PermitWrite {
  672. http.Error(w, "reset-auth modify access denied", http.StatusForbidden)
  673. return
  674. }
  675. if r.Method != httpm.POST {
  676. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  677. return
  678. }
  679. if err := h.b.ResetAuth(); err != nil {
  680. http.Error(w, "reset-auth failed: "+err.Error(), http.StatusInternalServerError)
  681. return
  682. }
  683. w.WriteHeader(http.StatusNoContent)
  684. }
  685. func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request) {
  686. if !h.PermitRead {
  687. http.Error(w, "IP forwarding check access denied", http.StatusForbidden)
  688. return
  689. }
  690. var warning string
  691. if err := h.b.CheckIPForwarding(); err != nil {
  692. warning = err.Error()
  693. }
  694. w.Header().Set("Content-Type", "application/json")
  695. json.NewEncoder(w).Encode(struct {
  696. Warning string
  697. }{
  698. Warning: warning,
  699. })
  700. }
  701. // serveCheckSOMarkInUse reports whether SO_MARK is in use on the linux while
  702. // running without TUN. For any other OS, it reports false.
  703. func (h *Handler) serveCheckSOMarkInUse(w http.ResponseWriter, r *http.Request) {
  704. if !h.PermitRead {
  705. http.Error(w, "SO_MARK check access denied", http.StatusForbidden)
  706. return
  707. }
  708. usingSOMark := netns.UseSocketMark()
  709. usingUserspaceNetworking := h.b.Sys().IsNetstack()
  710. w.Header().Set("Content-Type", "application/json")
  711. json.NewEncoder(w).Encode(struct {
  712. UseSOMark bool
  713. }{
  714. UseSOMark: usingSOMark || usingUserspaceNetworking,
  715. })
  716. }
  717. func (h *Handler) serveCheckReversePathFiltering(w http.ResponseWriter, r *http.Request) {
  718. if !h.PermitRead {
  719. http.Error(w, "reverse path filtering check access denied", http.StatusForbidden)
  720. return
  721. }
  722. var warning string
  723. state := h.b.Sys().NetMon.Get().InterfaceState()
  724. warn, err := netutil.CheckReversePathFiltering(state)
  725. if err == nil && len(warn) > 0 {
  726. var msg strings.Builder
  727. msg.WriteString(healthmsg.WarnExitNodeUsage + ":\n")
  728. for _, w := range warn {
  729. msg.WriteString("- " + w + "\n")
  730. }
  731. msg.WriteString(healthmsg.DisableRPFilter)
  732. warning = msg.String()
  733. }
  734. w.Header().Set("Content-Type", "application/json")
  735. json.NewEncoder(w).Encode(struct {
  736. Warning string
  737. }{
  738. Warning: warning,
  739. })
  740. }
  741. func (h *Handler) serveCheckUDPGROForwarding(w http.ResponseWriter, r *http.Request) {
  742. if !h.PermitRead {
  743. http.Error(w, "UDP GRO forwarding check access denied", http.StatusForbidden)
  744. return
  745. }
  746. var warning string
  747. if err := h.b.CheckUDPGROForwarding(); err != nil {
  748. warning = err.Error()
  749. }
  750. w.Header().Set("Content-Type", "application/json")
  751. json.NewEncoder(w).Encode(struct {
  752. Warning string
  753. }{
  754. Warning: warning,
  755. })
  756. }
  757. func (h *Handler) serveSetUDPGROForwarding(w http.ResponseWriter, r *http.Request) {
  758. if !buildfeatures.HasGRO {
  759. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  760. return
  761. }
  762. if !h.PermitWrite {
  763. http.Error(w, "UDP GRO forwarding set access denied", http.StatusForbidden)
  764. return
  765. }
  766. var warning string
  767. if err := h.b.SetUDPGROForwarding(); err != nil {
  768. warning = err.Error()
  769. }
  770. w.Header().Set("Content-Type", "application/json")
  771. json.NewEncoder(w).Encode(struct {
  772. Warning string
  773. }{
  774. Warning: warning,
  775. })
  776. }
  777. func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
  778. if !h.PermitRead {
  779. http.Error(w, "status access denied", http.StatusForbidden)
  780. return
  781. }
  782. w.Header().Set("Content-Type", "application/json")
  783. var st *ipnstate.Status
  784. if defBool(r.FormValue("peers"), true) {
  785. st = h.b.Status()
  786. } else {
  787. st = h.b.StatusWithoutPeers()
  788. }
  789. e := json.NewEncoder(w)
  790. e.SetIndent("", "\t")
  791. e.Encode(st)
  792. }
  793. // InUseOtherUserIPNStream reports whether r is a request for the watch-ipn-bus
  794. // handler. If so, it writes an ipn.Notify InUseOtherUser message to the user
  795. // and returns true. Otherwise it returns false, in which case it doesn't write
  796. // to w.
  797. //
  798. // Unlike the regular watch-ipn-bus handler, this one doesn't block. The caller
  799. // (in ipnserver.Server) provides the blocking until the connection is no longer
  800. // in use.
  801. func InUseOtherUserIPNStream(w http.ResponseWriter, r *http.Request, err error) (handled bool) {
  802. if r.Method != httpm.GET || r.URL.Path != "/localapi/v0/watch-ipn-bus" {
  803. return false
  804. }
  805. js, err := json.Marshal(&ipn.Notify{
  806. Version: version.Long(),
  807. State: ptr.To(ipn.InUseOtherUser),
  808. ErrMessage: ptr.To(err.Error()),
  809. })
  810. if err != nil {
  811. return false
  812. }
  813. js = append(js, '\n')
  814. w.Header().Set("Content-Type", "application/json")
  815. w.Write(js)
  816. return true
  817. }
  818. func (h *Handler) serveWatchIPNBus(w http.ResponseWriter, r *http.Request) {
  819. if !h.PermitRead {
  820. http.Error(w, "watch ipn bus access denied", http.StatusForbidden)
  821. return
  822. }
  823. f, ok := w.(http.Flusher)
  824. if !ok {
  825. http.Error(w, "not a flusher", http.StatusInternalServerError)
  826. return
  827. }
  828. var mask ipn.NotifyWatchOpt
  829. if s := r.FormValue("mask"); s != "" {
  830. v, err := strconv.ParseUint(s, 10, 64)
  831. if err != nil {
  832. http.Error(w, "bad mask", http.StatusBadRequest)
  833. return
  834. }
  835. mask = ipn.NotifyWatchOpt(v)
  836. }
  837. w.Header().Set("Content-Type", "application/json")
  838. ctx := r.Context()
  839. enc := json.NewEncoder(w)
  840. h.b.WatchNotificationsAs(ctx, h.Actor, mask, f.Flush, func(roNotify *ipn.Notify) (keepGoing bool) {
  841. err := enc.Encode(roNotify)
  842. if err != nil {
  843. h.logf("json.Encode: %v", err)
  844. return false
  845. }
  846. f.Flush()
  847. return true
  848. })
  849. }
  850. func (h *Handler) serveLoginInteractive(w http.ResponseWriter, r *http.Request) {
  851. if !h.PermitWrite {
  852. http.Error(w, "login access denied", http.StatusForbidden)
  853. return
  854. }
  855. if r.Method != httpm.POST {
  856. http.Error(w, "want POST", http.StatusBadRequest)
  857. return
  858. }
  859. if err := h.b.StartLoginInteractiveAs(r.Context(), h.Actor); err != nil {
  860. http.Error(w, err.Error(), http.StatusInternalServerError)
  861. return
  862. }
  863. w.WriteHeader(http.StatusNoContent)
  864. return
  865. }
  866. func (h *Handler) serveStart(w http.ResponseWriter, r *http.Request) {
  867. if !h.PermitWrite {
  868. http.Error(w, "access denied", http.StatusForbidden)
  869. return
  870. }
  871. if r.Method != httpm.POST {
  872. http.Error(w, "want POST", http.StatusBadRequest)
  873. return
  874. }
  875. var o ipn.Options
  876. if err := json.NewDecoder(r.Body).Decode(&o); err != nil {
  877. http.Error(w, err.Error(), http.StatusBadRequest)
  878. return
  879. }
  880. if h.b.HealthTracker().IsUnhealthy(ipn.StateStoreHealth) {
  881. http.Error(w, "cannot start backend when state store is unhealthy", http.StatusInternalServerError)
  882. return
  883. }
  884. err := h.b.Start(o)
  885. if err != nil {
  886. // TODO(bradfitz): map error to a good HTTP error
  887. http.Error(w, err.Error(), http.StatusInternalServerError)
  888. return
  889. }
  890. w.WriteHeader(http.StatusNoContent)
  891. }
  892. func (h *Handler) serveLogout(w http.ResponseWriter, r *http.Request) {
  893. if !h.PermitWrite {
  894. http.Error(w, "logout access denied", http.StatusForbidden)
  895. return
  896. }
  897. if r.Method != httpm.POST {
  898. http.Error(w, "want POST", http.StatusBadRequest)
  899. return
  900. }
  901. err := h.b.Logout(r.Context(), h.Actor)
  902. if err == nil {
  903. w.WriteHeader(http.StatusNoContent)
  904. return
  905. }
  906. http.Error(w, err.Error(), http.StatusInternalServerError)
  907. }
  908. func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
  909. if !h.PermitRead {
  910. http.Error(w, "prefs access denied", http.StatusForbidden)
  911. return
  912. }
  913. var prefs ipn.PrefsView
  914. switch r.Method {
  915. case httpm.PATCH:
  916. if !h.PermitWrite {
  917. http.Error(w, "prefs write access denied", http.StatusForbidden)
  918. return
  919. }
  920. mp := new(ipn.MaskedPrefs)
  921. if err := json.NewDecoder(r.Body).Decode(mp); err != nil {
  922. http.Error(w, err.Error(), http.StatusBadRequest)
  923. return
  924. }
  925. if buildfeatures.HasAppConnectors {
  926. if err := h.b.MaybeClearAppConnector(mp); err != nil {
  927. w.Header().Set("Content-Type", "application/json")
  928. w.WriteHeader(http.StatusInternalServerError)
  929. json.NewEncoder(w).Encode(resJSON{Error: err.Error()})
  930. return
  931. }
  932. }
  933. var err error
  934. prefs, err = h.b.EditPrefsAs(mp, h.Actor)
  935. if err != nil {
  936. w.Header().Set("Content-Type", "application/json")
  937. w.WriteHeader(http.StatusBadRequest)
  938. json.NewEncoder(w).Encode(resJSON{Error: err.Error()})
  939. return
  940. }
  941. case httpm.GET, httpm.HEAD:
  942. prefs = h.b.Prefs()
  943. default:
  944. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  945. return
  946. }
  947. w.Header().Set("Content-Type", "application/json")
  948. e := json.NewEncoder(w)
  949. e.SetIndent("", "\t")
  950. e.Encode(prefs)
  951. }
  952. type resJSON struct {
  953. Error string `json:",omitempty"`
  954. }
  955. func (h *Handler) serveCheckPrefs(w http.ResponseWriter, r *http.Request) {
  956. if !h.PermitWrite {
  957. http.Error(w, "checkprefs access denied", http.StatusForbidden)
  958. return
  959. }
  960. if r.Method != httpm.POST {
  961. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  962. return
  963. }
  964. p := new(ipn.Prefs)
  965. if err := json.NewDecoder(r.Body).Decode(p); err != nil {
  966. http.Error(w, "invalid JSON body", http.StatusBadRequest)
  967. return
  968. }
  969. err := h.b.CheckPrefs(p)
  970. var res resJSON
  971. if err != nil {
  972. res.Error = err.Error()
  973. }
  974. w.Header().Set("Content-Type", "application/json")
  975. json.NewEncoder(w).Encode(res)
  976. }
  977. // WriteErrorJSON writes a JSON object (with a single "error" string field) to w
  978. // with the given error. If err is nil, "unexpected nil error" is used for the
  979. // stringification instead.
  980. func WriteErrorJSON(w http.ResponseWriter, err error) {
  981. if err == nil {
  982. err = errors.New("unexpected nil error")
  983. }
  984. w.Header().Set("Content-Type", "application/json")
  985. w.WriteHeader(http.StatusInternalServerError)
  986. type E struct {
  987. Error string `json:"error"`
  988. }
  989. json.NewEncoder(w).Encode(E{err.Error()})
  990. }
  991. func (h *Handler) serveSetDNS(w http.ResponseWriter, r *http.Request) {
  992. if !h.PermitWrite {
  993. http.Error(w, "access denied", http.StatusForbidden)
  994. return
  995. }
  996. if r.Method != httpm.POST {
  997. http.Error(w, "want POST", http.StatusBadRequest)
  998. return
  999. }
  1000. ctx := r.Context()
  1001. err := h.b.SetDNS(ctx, r.FormValue("name"), r.FormValue("value"))
  1002. if err != nil {
  1003. WriteErrorJSON(w, err)
  1004. return
  1005. }
  1006. w.Header().Set("Content-Type", "application/json")
  1007. json.NewEncoder(w).Encode(struct{}{})
  1008. }
  1009. func (h *Handler) serveDERPMap(w http.ResponseWriter, r *http.Request) {
  1010. if r.Method != httpm.GET {
  1011. http.Error(w, "want GET", http.StatusBadRequest)
  1012. return
  1013. }
  1014. w.Header().Set("Content-Type", "application/json")
  1015. e := json.NewEncoder(w)
  1016. e.SetIndent("", "\t")
  1017. e.Encode(h.b.DERPMap())
  1018. }
  1019. // serveSetExpirySooner sets the expiry date on the current machine, specified
  1020. // by an `expiry` unix timestamp as POST or query param.
  1021. func (h *Handler) serveSetExpirySooner(w http.ResponseWriter, r *http.Request) {
  1022. if !h.PermitWrite {
  1023. http.Error(w, "access denied", http.StatusForbidden)
  1024. return
  1025. }
  1026. if r.Method != httpm.POST {
  1027. http.Error(w, "POST required", http.StatusMethodNotAllowed)
  1028. return
  1029. }
  1030. var expiryTime time.Time
  1031. if v := r.FormValue("expiry"); v != "" {
  1032. expiryInt, err := strconv.ParseInt(v, 10, 64)
  1033. if err != nil {
  1034. http.Error(w, "can't parse expiry time, expects a unix timestamp", http.StatusBadRequest)
  1035. return
  1036. }
  1037. expiryTime = time.Unix(expiryInt, 0)
  1038. } else {
  1039. http.Error(w, "missing 'expiry' parameter, a unix timestamp", http.StatusBadRequest)
  1040. return
  1041. }
  1042. err := h.b.SetExpirySooner(r.Context(), expiryTime)
  1043. if err != nil {
  1044. http.Error(w, err.Error(), http.StatusBadRequest)
  1045. return
  1046. }
  1047. w.Header().Set("Content-Type", "text/plain")
  1048. io.WriteString(w, "done\n")
  1049. }
  1050. func (h *Handler) servePing(w http.ResponseWriter, r *http.Request) {
  1051. ctx := r.Context()
  1052. if r.Method != httpm.POST {
  1053. http.Error(w, "want POST", http.StatusBadRequest)
  1054. return
  1055. }
  1056. ipStr := r.FormValue("ip")
  1057. if ipStr == "" {
  1058. http.Error(w, "missing 'ip' parameter", http.StatusBadRequest)
  1059. return
  1060. }
  1061. ip, err := netip.ParseAddr(ipStr)
  1062. if err != nil {
  1063. http.Error(w, "invalid IP", http.StatusBadRequest)
  1064. return
  1065. }
  1066. pingTypeStr := r.FormValue("type")
  1067. if pingTypeStr == "" {
  1068. http.Error(w, "missing 'type' parameter", http.StatusBadRequest)
  1069. return
  1070. }
  1071. size := 0
  1072. sizeStr := r.FormValue("size")
  1073. if sizeStr != "" {
  1074. size, err = strconv.Atoi(sizeStr)
  1075. if err != nil {
  1076. http.Error(w, "invalid 'size' parameter", http.StatusBadRequest)
  1077. return
  1078. }
  1079. if size != 0 && tailcfg.PingType(pingTypeStr) != tailcfg.PingDisco {
  1080. http.Error(w, "'size' parameter is only supported with disco pings", http.StatusBadRequest)
  1081. return
  1082. }
  1083. if size > magicsock.MaxDiscoPingSize {
  1084. http.Error(w, fmt.Sprintf("maximum value for 'size' is %v", magicsock.MaxDiscoPingSize), http.StatusBadRequest)
  1085. return
  1086. }
  1087. }
  1088. res, err := h.b.Ping(ctx, ip, tailcfg.PingType(pingTypeStr), size)
  1089. if err != nil {
  1090. WriteErrorJSON(w, err)
  1091. return
  1092. }
  1093. w.Header().Set("Content-Type", "application/json")
  1094. json.NewEncoder(w).Encode(res)
  1095. }
  1096. func (h *Handler) serveDial(w http.ResponseWriter, r *http.Request) {
  1097. if r.Method != httpm.POST {
  1098. http.Error(w, "POST required", http.StatusMethodNotAllowed)
  1099. return
  1100. }
  1101. const upgradeProto = "ts-dial"
  1102. if !strings.Contains(r.Header.Get("Connection"), "upgrade") ||
  1103. r.Header.Get("Upgrade") != upgradeProto {
  1104. http.Error(w, "bad ts-dial upgrade", http.StatusBadRequest)
  1105. return
  1106. }
  1107. hostStr, portStr := r.Header.Get("Dial-Host"), r.Header.Get("Dial-Port")
  1108. if hostStr == "" || portStr == "" {
  1109. http.Error(w, "missing Dial-Host or Dial-Port header", http.StatusBadRequest)
  1110. return
  1111. }
  1112. hijacker, ok := w.(http.Hijacker)
  1113. if !ok {
  1114. http.Error(w, "make request over HTTP/1", http.StatusBadRequest)
  1115. return
  1116. }
  1117. network := cmp.Or(r.Header.Get("Dial-Network"), "tcp")
  1118. addr := net.JoinHostPort(hostStr, portStr)
  1119. outConn, err := h.b.Dialer().UserDial(r.Context(), network, addr)
  1120. if err != nil {
  1121. http.Error(w, "dial failure: "+err.Error(), http.StatusBadGateway)
  1122. return
  1123. }
  1124. defer outConn.Close()
  1125. w.Header().Set("Upgrade", upgradeProto)
  1126. w.Header().Set("Connection", "upgrade")
  1127. w.WriteHeader(http.StatusSwitchingProtocols)
  1128. reqConn, brw, err := hijacker.Hijack()
  1129. if err != nil {
  1130. h.logf("localapi dial Hijack error: %v", err)
  1131. return
  1132. }
  1133. defer reqConn.Close()
  1134. if err := brw.Flush(); err != nil {
  1135. return
  1136. }
  1137. reqConn = netutil.NewDrainBufConn(reqConn, brw.Reader)
  1138. errc := make(chan error, 1)
  1139. go func() {
  1140. _, err := io.Copy(reqConn, outConn)
  1141. errc <- err
  1142. }()
  1143. go func() {
  1144. _, err := io.Copy(outConn, reqConn)
  1145. errc <- err
  1146. }()
  1147. <-errc
  1148. }
  1149. func (h *Handler) serveSetPushDeviceToken(w http.ResponseWriter, r *http.Request) {
  1150. if !h.PermitWrite {
  1151. http.Error(w, "set push device token access denied", http.StatusForbidden)
  1152. return
  1153. }
  1154. if r.Method != httpm.POST {
  1155. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  1156. return
  1157. }
  1158. var params apitype.SetPushDeviceTokenRequest
  1159. if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
  1160. http.Error(w, "invalid JSON body", http.StatusBadRequest)
  1161. return
  1162. }
  1163. h.b.SetPushDeviceToken(params.PushDeviceToken)
  1164. w.WriteHeader(http.StatusOK)
  1165. }
  1166. func (h *Handler) serveHandlePushMessage(w http.ResponseWriter, r *http.Request) {
  1167. if !h.PermitWrite {
  1168. http.Error(w, "handle push message not allowed", http.StatusForbidden)
  1169. return
  1170. }
  1171. if r.Method != httpm.POST {
  1172. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  1173. return
  1174. }
  1175. var pushMessageBody map[string]any
  1176. if err := json.NewDecoder(r.Body).Decode(&pushMessageBody); err != nil {
  1177. http.Error(w, "failed to decode JSON body: "+err.Error(), http.StatusBadRequest)
  1178. return
  1179. }
  1180. // TODO(bradfitz): do something with pushMessageBody
  1181. h.logf("localapi: got push message: %v", logger.AsJSON(pushMessageBody))
  1182. w.WriteHeader(http.StatusNoContent)
  1183. }
  1184. func (h *Handler) serveUploadClientMetrics(w http.ResponseWriter, r *http.Request) {
  1185. if r.Method != httpm.POST {
  1186. http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
  1187. return
  1188. }
  1189. var clientMetrics []clientmetric.MetricUpdate
  1190. if err := json.NewDecoder(r.Body).Decode(&clientMetrics); err != nil {
  1191. http.Error(w, "invalid JSON body", http.StatusBadRequest)
  1192. return
  1193. }
  1194. metricsMu.Lock()
  1195. defer metricsMu.Unlock()
  1196. for _, m := range clientMetrics {
  1197. metric, ok := metrics[m.Name]
  1198. if !ok {
  1199. if clientmetric.HasPublished(m.Name) {
  1200. http.Error(w, "Already have a metric named "+m.Name, http.StatusBadRequest)
  1201. return
  1202. }
  1203. switch m.Type {
  1204. case "counter":
  1205. metric = clientmetric.NewCounter(m.Name)
  1206. case "gauge":
  1207. metric = clientmetric.NewGauge(m.Name)
  1208. default:
  1209. http.Error(w, "Unknown metric type "+m.Type, http.StatusBadRequest)
  1210. return
  1211. }
  1212. metrics[m.Name] = metric
  1213. }
  1214. switch m.Op {
  1215. case "add", "":
  1216. metric.Add(int64(m.Value))
  1217. case "set":
  1218. metric.Set(int64(m.Value))
  1219. default:
  1220. http.Error(w, "Unknown metric op "+m.Op, http.StatusBadRequest)
  1221. return
  1222. }
  1223. }
  1224. w.Header().Set("Content-Type", "application/json")
  1225. json.NewEncoder(w).Encode(struct{}{})
  1226. }
  1227. func (h *Handler) serveSetGUIVisible(w http.ResponseWriter, r *http.Request) {
  1228. if r.Method != httpm.POST {
  1229. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  1230. return
  1231. }
  1232. type setGUIVisibleRequest struct {
  1233. IsVisible bool // whether the Tailscale client UI is now presented to the user
  1234. SessionID string // the last SessionID sent to the client in ipn.Notify.SessionID
  1235. }
  1236. var req setGUIVisibleRequest
  1237. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  1238. http.Error(w, "invalid JSON body", http.StatusBadRequest)
  1239. return
  1240. }
  1241. // TODO(bradfitz): use `req.IsVisible == true` to flush netmap
  1242. w.WriteHeader(http.StatusOK)
  1243. }
  1244. func (h *Handler) serveSetUseExitNodeEnabled(w http.ResponseWriter, r *http.Request) {
  1245. if !buildfeatures.HasUseExitNode {
  1246. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  1247. return
  1248. }
  1249. if r.Method != httpm.POST {
  1250. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  1251. return
  1252. }
  1253. if !h.PermitWrite {
  1254. http.Error(w, "access denied", http.StatusForbidden)
  1255. return
  1256. }
  1257. v, err := strconv.ParseBool(r.URL.Query().Get("enabled"))
  1258. if err != nil {
  1259. http.Error(w, "invalid 'enabled' parameter", http.StatusBadRequest)
  1260. return
  1261. }
  1262. prefs, err := h.b.SetUseExitNodeEnabled(h.Actor, v)
  1263. if err != nil {
  1264. http.Error(w, err.Error(), http.StatusInternalServerError)
  1265. return
  1266. }
  1267. w.Header().Set("Content-Type", "application/json")
  1268. e := json.NewEncoder(w)
  1269. e.SetIndent("", "\t")
  1270. e.Encode(prefs)
  1271. }
  1272. // serveProfiles serves profile switching-related endpoints. Supported methods
  1273. // and paths are:
  1274. // - GET /profiles/: list all profiles (JSON-encoded array of ipn.LoginProfiles)
  1275. // - PUT /profiles/: add new profile (no response). A separate
  1276. // StartLoginInteractive() is needed to populate and persist the new profile.
  1277. // - GET /profiles/current: current profile (JSON-ecoded ipn.LoginProfile)
  1278. // - GET /profiles/<id>: output profile (JSON-ecoded ipn.LoginProfile)
  1279. // - POST /profiles/<id>: switch to profile (no response)
  1280. // - DELETE /profiles/<id>: delete profile (no response)
  1281. func (h *Handler) serveProfiles(w http.ResponseWriter, r *http.Request) {
  1282. if !h.PermitWrite {
  1283. http.Error(w, "profiles access denied", http.StatusForbidden)
  1284. return
  1285. }
  1286. suffix, ok := strings.CutPrefix(r.URL.EscapedPath(), "/localapi/v0/profiles/")
  1287. if !ok {
  1288. http.Error(w, "misconfigured", http.StatusInternalServerError)
  1289. return
  1290. }
  1291. if suffix == "" {
  1292. switch r.Method {
  1293. case httpm.GET:
  1294. w.Header().Set("Content-Type", "application/json")
  1295. json.NewEncoder(w).Encode(h.b.ListProfiles())
  1296. case httpm.PUT:
  1297. err := h.b.NewProfile()
  1298. if err != nil {
  1299. http.Error(w, err.Error(), http.StatusInternalServerError)
  1300. return
  1301. }
  1302. w.WriteHeader(http.StatusCreated)
  1303. default:
  1304. http.Error(w, "use GET or PUT", http.StatusMethodNotAllowed)
  1305. }
  1306. return
  1307. }
  1308. suffix, err := url.PathUnescape(suffix)
  1309. if err != nil {
  1310. http.Error(w, "bad profile ID", http.StatusBadRequest)
  1311. return
  1312. }
  1313. if suffix == "current" {
  1314. switch r.Method {
  1315. case httpm.GET:
  1316. w.Header().Set("Content-Type", "application/json")
  1317. json.NewEncoder(w).Encode(h.b.CurrentProfile())
  1318. default:
  1319. http.Error(w, "use GET", http.StatusMethodNotAllowed)
  1320. }
  1321. return
  1322. }
  1323. profileID := ipn.ProfileID(suffix)
  1324. switch r.Method {
  1325. case httpm.GET:
  1326. profiles := h.b.ListProfiles()
  1327. profileIndex := slices.IndexFunc(profiles, func(p ipn.LoginProfileView) bool {
  1328. return p.ID() == profileID
  1329. })
  1330. if profileIndex == -1 {
  1331. http.Error(w, "Profile not found", http.StatusNotFound)
  1332. return
  1333. }
  1334. w.Header().Set("Content-Type", "application/json")
  1335. json.NewEncoder(w).Encode(profiles[profileIndex])
  1336. case httpm.POST:
  1337. err := h.b.SwitchProfile(profileID)
  1338. if err != nil {
  1339. http.Error(w, err.Error(), http.StatusInternalServerError)
  1340. return
  1341. }
  1342. w.WriteHeader(http.StatusNoContent)
  1343. case httpm.DELETE:
  1344. err := h.b.DeleteProfile(profileID)
  1345. if err != nil {
  1346. http.Error(w, err.Error(), http.StatusInternalServerError)
  1347. return
  1348. }
  1349. w.WriteHeader(http.StatusNoContent)
  1350. default:
  1351. http.Error(w, "use POST or DELETE", http.StatusMethodNotAllowed)
  1352. }
  1353. }
  1354. // serveQueryFeature makes a request to the "/machine/feature/query"
  1355. // Noise endpoint to get instructions on how to enable a feature, such as
  1356. // Funnel, for the node's tailnet.
  1357. //
  1358. // This request itself does not directly enable the feature on behalf of
  1359. // the node, but rather returns information that can be presented to the
  1360. // acting user about where/how to enable the feature. If relevant, this
  1361. // includes a control URL the user can visit to explicitly consent to
  1362. // using the feature.
  1363. //
  1364. // See tailcfg.QueryFeatureResponse for full response structure.
  1365. func (h *Handler) serveQueryFeature(w http.ResponseWriter, r *http.Request) {
  1366. feature := r.FormValue("feature")
  1367. switch {
  1368. case !h.PermitRead:
  1369. http.Error(w, "access denied", http.StatusForbidden)
  1370. return
  1371. case r.Method != httpm.POST:
  1372. http.Error(w, "use POST", http.StatusMethodNotAllowed)
  1373. return
  1374. case feature == "":
  1375. http.Error(w, "missing feature", http.StatusInternalServerError)
  1376. return
  1377. }
  1378. nm := h.b.NetMap()
  1379. if nm == nil {
  1380. http.Error(w, "no netmap", http.StatusServiceUnavailable)
  1381. return
  1382. }
  1383. b, err := json.Marshal(&tailcfg.QueryFeatureRequest{
  1384. NodeKey: nm.NodeKey,
  1385. Feature: feature,
  1386. })
  1387. if err != nil {
  1388. http.Error(w, err.Error(), http.StatusInternalServerError)
  1389. return
  1390. }
  1391. req, err := http.NewRequestWithContext(r.Context(),
  1392. httpm.POST, "https://unused/machine/feature/query", bytes.NewReader(b))
  1393. if err != nil {
  1394. http.Error(w, err.Error(), http.StatusInternalServerError)
  1395. return
  1396. }
  1397. resp, err := h.b.DoNoiseRequest(req)
  1398. if err != nil {
  1399. http.Error(w, err.Error(), http.StatusInternalServerError)
  1400. return
  1401. }
  1402. defer resp.Body.Close()
  1403. w.Header().Set("Content-Type", "application/json")
  1404. w.WriteHeader(resp.StatusCode)
  1405. if _, err := io.Copy(w, resp.Body); err != nil {
  1406. http.Error(w, err.Error(), http.StatusInternalServerError)
  1407. return
  1408. }
  1409. }
  1410. func defBool(a string, def bool) bool {
  1411. if a == "" {
  1412. return def
  1413. }
  1414. v, err := strconv.ParseBool(a)
  1415. if err != nil {
  1416. return def
  1417. }
  1418. return v
  1419. }
  1420. // serveUpdateCheck returns the ClientVersion from Status, which contains
  1421. // information on whether an update is available, and if so, what version,
  1422. // *if* we support auto-updates on this platform. If we don't, this endpoint
  1423. // always returns a ClientVersion saying we're running the newest version.
  1424. // Effectively, it tells us whether serveUpdateInstall will be able to install
  1425. // an update for us.
  1426. func (h *Handler) serveUpdateCheck(w http.ResponseWriter, r *http.Request) {
  1427. if r.Method != httpm.GET {
  1428. http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
  1429. return
  1430. }
  1431. cv := h.b.StatusWithoutPeers().ClientVersion
  1432. // ipnstate.Status documentation notes that ClientVersion may be nil on some
  1433. // platforms where this information is unavailable. In that case, return a
  1434. // ClientVersion that says we're up to date, since we have no information on
  1435. // whether an update is possible.
  1436. if cv == nil {
  1437. cv = &tailcfg.ClientVersion{RunningLatest: true}
  1438. }
  1439. json.NewEncoder(w).Encode(cv)
  1440. }
  1441. // serveDNSOSConfig serves the current system DNS configuration as a JSON object, if
  1442. // supported by the OS.
  1443. func (h *Handler) serveDNSOSConfig(w http.ResponseWriter, r *http.Request) {
  1444. if !buildfeatures.HasDNS {
  1445. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  1446. return
  1447. }
  1448. if r.Method != httpm.GET {
  1449. http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
  1450. return
  1451. }
  1452. // Require write access for privacy reasons.
  1453. if !h.PermitWrite {
  1454. http.Error(w, "dns-osconfig dump access denied", http.StatusForbidden)
  1455. return
  1456. }
  1457. bCfg, err := h.b.GetDNSOSConfig()
  1458. if err != nil {
  1459. http.Error(w, err.Error(), http.StatusInternalServerError)
  1460. return
  1461. }
  1462. w.Header().Set("Content-Type", "application/json")
  1463. nameservers := make([]string, 0, len(bCfg.Nameservers))
  1464. for _, ns := range bCfg.Nameservers {
  1465. nameservers = append(nameservers, ns.String())
  1466. }
  1467. searchDomains := make([]string, 0, len(bCfg.SearchDomains))
  1468. for _, sd := range bCfg.SearchDomains {
  1469. searchDomains = append(searchDomains, sd.WithoutTrailingDot())
  1470. }
  1471. matchDomains := make([]string, 0, len(bCfg.MatchDomains))
  1472. for _, md := range bCfg.MatchDomains {
  1473. matchDomains = append(matchDomains, md.WithoutTrailingDot())
  1474. }
  1475. response := apitype.DNSOSConfig{
  1476. Nameservers: nameservers,
  1477. SearchDomains: searchDomains,
  1478. MatchDomains: matchDomains,
  1479. }
  1480. json.NewEncoder(w).Encode(response)
  1481. }
  1482. // serveDNSQuery provides the ability to perform DNS queries using the internal
  1483. // DNS forwarder. This is useful for debugging and testing purposes.
  1484. // URL parameters:
  1485. // - name: the domain name to query
  1486. // - type: the DNS record type to query as a number (default if empty: A = '1')
  1487. //
  1488. // The response if successful is a DNSQueryResponse JSON object.
  1489. func (h *Handler) serveDNSQuery(w http.ResponseWriter, r *http.Request) {
  1490. if !buildfeatures.HasDNS {
  1491. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  1492. return
  1493. }
  1494. if r.Method != httpm.GET {
  1495. http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
  1496. return
  1497. }
  1498. // Require write access for privacy reasons.
  1499. if !h.PermitWrite {
  1500. http.Error(w, "dns-query access denied", http.StatusForbidden)
  1501. return
  1502. }
  1503. q := r.URL.Query()
  1504. name := q.Get("name")
  1505. queryType := q.Get("type")
  1506. qt := dnsmessage.TypeA
  1507. if queryType != "" {
  1508. t, err := dnsMessageTypeForString(queryType)
  1509. if err != nil {
  1510. http.Error(w, err.Error(), http.StatusBadRequest)
  1511. return
  1512. }
  1513. qt = t
  1514. }
  1515. res, rrs, err := h.b.QueryDNS(name, qt)
  1516. if err != nil {
  1517. http.Error(w, err.Error(), http.StatusInternalServerError)
  1518. return
  1519. }
  1520. w.Header().Set("Content-Type", "application/json")
  1521. json.NewEncoder(w).Encode(&apitype.DNSQueryResponse{
  1522. Bytes: res,
  1523. Resolvers: rrs,
  1524. })
  1525. }
  1526. // dnsMessageTypeForString returns the dnsmessage.Type for the given string.
  1527. // For example, DNSMessageTypeForString("A") returns dnsmessage.TypeA.
  1528. func dnsMessageTypeForString(s string) (t dnsmessage.Type, err error) {
  1529. s = strings.TrimSpace(strings.ToUpper(s))
  1530. switch s {
  1531. case "AAAA":
  1532. return dnsmessage.TypeAAAA, nil
  1533. case "ALL":
  1534. return dnsmessage.TypeALL, nil
  1535. case "A":
  1536. return dnsmessage.TypeA, nil
  1537. case "CNAME":
  1538. return dnsmessage.TypeCNAME, nil
  1539. case "HINFO":
  1540. return dnsmessage.TypeHINFO, nil
  1541. case "MINFO":
  1542. return dnsmessage.TypeMINFO, nil
  1543. case "MX":
  1544. return dnsmessage.TypeMX, nil
  1545. case "NS":
  1546. return dnsmessage.TypeNS, nil
  1547. case "OPT":
  1548. return dnsmessage.TypeOPT, nil
  1549. case "PTR":
  1550. return dnsmessage.TypePTR, nil
  1551. case "SOA":
  1552. return dnsmessage.TypeSOA, nil
  1553. case "SRV":
  1554. return dnsmessage.TypeSRV, nil
  1555. case "TXT":
  1556. return dnsmessage.TypeTXT, nil
  1557. case "WKS":
  1558. return dnsmessage.TypeWKS, nil
  1559. }
  1560. return 0, errors.New("unknown DNS message type: " + s)
  1561. }
  1562. // serveSuggestExitNode serves a POST endpoint for returning a suggested exit node.
  1563. func (h *Handler) serveSuggestExitNode(w http.ResponseWriter, r *http.Request) {
  1564. if !buildfeatures.HasUseExitNode {
  1565. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  1566. return
  1567. }
  1568. if r.Method != httpm.GET {
  1569. http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
  1570. return
  1571. }
  1572. res, err := h.b.SuggestExitNode()
  1573. if err != nil {
  1574. WriteErrorJSON(w, err)
  1575. return
  1576. }
  1577. w.Header().Set("Content-Type", "application/json")
  1578. json.NewEncoder(w).Encode(res)
  1579. }
  1580. // Shutdown is an eventbus value published when tailscaled shutdown
  1581. // is requested via LocalAPI. Its only consumer is [ipnserver.Server].
  1582. type Shutdown struct{}
  1583. // serveShutdown shuts down tailscaled. It requires write access
  1584. // and the [pkey.AllowTailscaledRestart] policy to be enabled.
  1585. // See tailscale/corp#32674.
  1586. func (h *Handler) serveShutdown(w http.ResponseWriter, r *http.Request) {
  1587. if r.Method != httpm.POST {
  1588. http.Error(w, "only POST allowed", http.StatusMethodNotAllowed)
  1589. return
  1590. }
  1591. if !h.PermitWrite {
  1592. http.Error(w, "shutdown access denied", http.StatusForbidden)
  1593. return
  1594. }
  1595. polc := h.b.Sys().PolicyClientOrDefault()
  1596. if permitShutdown, _ := polc.GetBoolean(pkey.AllowTailscaledRestart, false); !permitShutdown {
  1597. http.Error(w, "shutdown access denied by policy", http.StatusForbidden)
  1598. return
  1599. }
  1600. ec := h.eventBus.Client("localapi.Handler")
  1601. defer ec.Close()
  1602. w.WriteHeader(http.StatusOK)
  1603. if f, ok := w.(http.Flusher); ok {
  1604. f.Flush()
  1605. }
  1606. eventbus.Publish[Shutdown](ec).Publish(Shutdown{})
  1607. }
  1608. func (h *Handler) serveGetAppcRouteInfo(w http.ResponseWriter, r *http.Request) {
  1609. if !buildfeatures.HasAppConnectors {
  1610. http.Error(w, feature.ErrUnavailable.Error(), http.StatusNotImplemented)
  1611. return
  1612. }
  1613. if r.Method != httpm.GET {
  1614. http.Error(w, "only GET allowed", http.StatusMethodNotAllowed)
  1615. return
  1616. }
  1617. res, err := h.b.ReadRouteInfo()
  1618. if err != nil {
  1619. if errors.Is(err, ipn.ErrStateNotExist) {
  1620. res = &appctype.RouteInfo{}
  1621. } else {
  1622. WriteErrorJSON(w, err)
  1623. return
  1624. }
  1625. }
  1626. w.Header().Set("Content-Type", "application/json")
  1627. json.NewEncoder(w).Encode(res)
  1628. }