@ -71,36 +71,20 @@ var handler = map[string]LocalAPIHandler{
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
// without a trailing slash:
"alpha-set-device-attrs" : ( * Handler ) . serveSetDeviceAttrs , // see tailscale/corp#24690
"check-prefs" : ( * Handler ) . serveCheckPrefs ,
"check-reverse-path-filtering" : ( * Handler ) . serveCheckReversePathFiltering ,
"check-udp-gro-forwarding" : ( * Handler ) . serveCheckUDPGROForwarding ,
"derpmap" : ( * Handler ) . serveDERPMap ,
"dial" : ( * Handler ) . serveDial ,
"disconnect-control" : ( * Handler ) . disconnectControl ,
"goroutines" : ( * Handler ) . serveGoroutines ,
"handle-push-message" : ( * Handler ) . serveHandlePushMessage ,
"id-token" : ( * Handler ) . serveIDToken ,
"login-interactive" : ( * Handler ) . serveLoginInteractive ,
"logout" : ( * Handler ) . serveLogout ,
"logtap" : ( * Handler ) . serveLogTap ,
"metrics" : ( * Handler ) . serveMetrics ,
"ping" : ( * Handler ) . servePing ,
"prefs" : ( * Handler ) . servePrefs ,
"query-feature" : ( * Handler ) . serveQueryFeature ,
"reload-config" : ( * Handler ) . reloadConfig ,
"reset-auth" : ( * Handler ) . serveResetAuth ,
"set-expiry-sooner" : ( * Handler ) . serveSetExpirySooner ,
"set-gui-visible" : ( * Handler ) . serveSetGUIVisible ,
"set-push-device-token" : ( * Handler ) . serveSetPushDeviceToken ,
"set-udp-gro-forwarding" : ( * Handler ) . serveSetUDPGROForwarding ,
"shutdown" : ( * Handler ) . serveShutdown ,
"start" : ( * Handler ) . serveStart ,
"status" : ( * Handler ) . serveStatus ,
"update/check" : ( * Handler ) . serveUpdateCheck ,
"upload-client-metrics" : ( * Handler ) . serveUploadClientMetrics ,
"watch-ipn-bus" : ( * Handler ) . serveWatchIPNBus ,
"whois" : ( * Handler ) . serveWhoIs ,
"check-prefs" : ( * Handler ) . serveCheckPrefs ,
"derpmap" : ( * Handler ) . serveDERPMap ,
"goroutines" : ( * Handler ) . serveGoroutines ,
"login-interactive" : ( * Handler ) . serveLoginInteractive ,
"logout" : ( * Handler ) . serveLogout ,
"ping" : ( * Handler ) . servePing ,
"prefs" : ( * Handler ) . servePrefs ,
"reload-config" : ( * Handler ) . reloadConfig ,
"reset-auth" : ( * Handler ) . serveResetAuth ,
"set-expiry-sooner" : ( * Handler ) . serveSetExpirySooner ,
"shutdown" : ( * Handler ) . serveShutdown ,
"start" : ( * Handler ) . serveStart ,
"status" : ( * Handler ) . serveStatus ,
"whois" : ( * Handler ) . serveWhoIs ,
}
func init ( ) {
@ -109,6 +93,17 @@ func init() {
}
if buildfeatures . HasAdvertiseRoutes {
Register ( "check-ip-forwarding" , ( * Handler ) . serveCheckIPForwarding )
Register ( "check-udp-gro-forwarding" , ( * Handler ) . serveCheckUDPGROForwarding )
Register ( "set-udp-gro-forwarding" , ( * Handler ) . serveSetUDPGROForwarding )
}
if buildfeatures . HasUseExitNode && runtime . GOOS == "linux" {
Register ( "check-reverse-path-filtering" , ( * Handler ) . serveCheckReversePathFiltering )
}
if buildfeatures . HasClientMetrics {
Register ( "upload-client-metrics" , ( * Handler ) . serveUploadClientMetrics )
}
if buildfeatures . HasClientUpdate {
Register ( "update/check" , ( * Handler ) . serveUpdateCheck )
}
if buildfeatures . HasUseExitNode {
Register ( "suggest-exit-node" , ( * Handler ) . serveSuggestExitNode )
@ -121,6 +116,9 @@ func init() {
Register ( "bugreport" , ( * Handler ) . serveBugReport )
Register ( "pprof" , ( * Handler ) . servePprof )
}
if buildfeatures . HasDebug || buildfeatures . HasServe {
Register ( "watch-ipn-bus" , ( * Handler ) . serveWatchIPNBus )
}
if buildfeatures . HasDNS {
Register ( "dns-osconfig" , ( * Handler ) . serveDNSOSConfig )
Register ( "dns-query" , ( * Handler ) . serveDNSQuery )
@ -128,6 +126,36 @@ func init() {
if buildfeatures . HasUserMetrics {
Register ( "usermetrics" , ( * Handler ) . serveUserMetrics )
}
if buildfeatures . HasServe {
Register ( "query-feature" , ( * Handler ) . serveQueryFeature )
}
if buildfeatures . HasOutboundProxy || buildfeatures . HasSSH {
Register ( "dial" , ( * Handler ) . serveDial )
}
if buildfeatures . HasClientMetrics || buildfeatures . HasDebug {
Register ( "metrics" , ( * Handler ) . serveMetrics )
}
if buildfeatures . HasDebug || buildfeatures . HasAdvertiseRoutes {
Register ( "disconnect-control" , ( * Handler ) . disconnectControl )
}
// Alpha/experimental/debug features. These should be moved to
// their own features if/when they graduate.
if buildfeatures . HasDebug {
Register ( "id-token" , ( * Handler ) . serveIDToken )
Register ( "alpha-set-device-attrs" , ( * Handler ) . serveSetDeviceAttrs ) // see tailscale/corp#24690
Register ( "handle-push-message" , ( * Handler ) . serveHandlePushMessage )
Register ( "set-push-device-token" , ( * Handler ) . serveSetPushDeviceToken )
}
if buildfeatures . HasDebug || runtime . GOOS == "windows" || runtime . GOOS == "darwin" {
Register ( "set-gui-visible" , ( * Handler ) . serveSetGUIVisible )
}
if buildfeatures . HasLogTail {
// TODO(bradfitz): separate out logtail tap functionality from upload
// functionality to make this possible? But seems unlikely people would
// want just this. They could "tail -f" or "journalctl -f" their logs
// themselves.
Register ( "logtap" , ( * Handler ) . serveLogTap )
}
}
// Register registers a new LocalAPI handler for the given name.
@ -580,15 +608,6 @@ func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
func ( h * Handler ) serveLogTap ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
if ! buildfeatures . HasLogTail {
// TODO(bradfitz): separate out logtail tap functionality from upload
// functionality to make this possible? But seems unlikely people would
// want just this. They could "tail -f" or "journalctl -f" their logs
// themselves.
http . Error ( w , "logtap not supported in this build" , http . StatusNotImplemented )
return
}
// Require write access (~root) as the logs could contain something
// sensitive.
if ! h . PermitWrite {
@ -662,7 +681,7 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
// disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that
// node no longer communicates with control. Doing this makes control consider this node inactive. This can be used
// before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
// before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
// peers to switch over to another replica whilst still maintaining th existing peer connections.
func ( h * Handler ) disconnectControl ( w http . ResponseWriter , r * http . Request ) {
if ! h . PermitWrite {
@ -1230,11 +1249,6 @@ func (h *Handler) serveHandlePushMessage(w http.ResponseWriter, r *http.Request)
}
func ( h * Handler ) serveUploadClientMetrics ( w http . ResponseWriter , r * http . Request ) {
if ! buildfeatures . HasClientMetrics {
w . Header ( ) . Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ) . Encode ( struct { } { } )
return
}
if r . Method != httpm . POST {
http . Error ( w , "unsupported method" , http . StatusMethodNotAllowed )
return
@ -1498,13 +1512,6 @@ func (h *Handler) serveUpdateCheck(w http.ResponseWriter, r *http.Request) {
http . Error ( w , "only GET allowed" , http . StatusMethodNotAllowed )
return
}
if ! feature . CanAutoUpdate ( ) {
// if we don't support auto-update, just say that we're up to date
json . NewEncoder ( w ) . Encode ( tailcfg . ClientVersion { RunningLatest : true } )
return
}
cv := h . b . StatusWithoutPeers ( ) . ClientVersion
// ipnstate.Status documentation notes that ClientVersion may be nil on some
// platforms where this information is unavailable. In that case, return a