mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-07 05:06:30 +02:00
Move tailscaled's in-tree reactive users from of IPN bus Notify.NetMap updates to the narrower Notify.SelfChange signal introduced earlier in this series. Consumers that need additional state (peers, DNS config, etc.) fetch it on demand via the LocalAPI. It is a step toward the larger goal of not fanning Notify.NetMap out to every bus watcher on Linux/non-GUI hosts. A future change stops sending Notify.NetMap entirely on Linux and non-GUI platforms. (eventually once macOS/iOS/Windows migrate to the upcoming new Notify APIs, we'll remove ipn.Notify.NetMap entirely) Updates #12542 Change-Id: I51ea9d86bdca1909d6ac0e7d5bd3934a3a4e8516 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
150 lines
3.7 KiB
Go
150 lines
3.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package tsconsensus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
|
|
"tailscale.com/ipn/ipnstate"
|
|
"tailscale.com/tsnet"
|
|
"tailscale.com/util/dnsname"
|
|
)
|
|
|
|
type status struct {
|
|
Status *ipnstate.Status
|
|
RaftState string
|
|
}
|
|
|
|
type monitor struct {
|
|
ts *tsnet.Server
|
|
con *Consensus
|
|
sg statusGetter
|
|
}
|
|
|
|
func (m *monitor) getStatus(ctx context.Context) (status, error) {
|
|
tStatus, err := m.sg.getStatus(ctx)
|
|
if err != nil {
|
|
return status{}, err
|
|
}
|
|
return status{Status: tStatus, RaftState: m.con.raft.State().String()}, nil
|
|
}
|
|
|
|
func serveMonitor(c *Consensus, ts *tsnet.Server, listenAddr string) (*http.Server, error) {
|
|
ln, err := ts.Listen("tcp", listenAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m := &monitor{con: c, ts: ts, sg: &tailscaleStatusGetter{
|
|
ts: ts,
|
|
}}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /full", m.handleFullStatus)
|
|
mux.HandleFunc("GET /{$}", m.handleSummaryStatus)
|
|
mux.HandleFunc("GET /netmap", m.handleNetmap)
|
|
mux.HandleFunc("POST /dial", m.handleDial)
|
|
srv := &http.Server{Handler: mux}
|
|
go func() {
|
|
err := srv.Serve(ln)
|
|
log.Printf("MonitorHTTP stopped serving with error: %v", err)
|
|
}()
|
|
return srv, nil
|
|
}
|
|
|
|
func (m *monitor) handleFullStatus(w http.ResponseWriter, r *http.Request) {
|
|
s, err := m.getStatus(r.Context())
|
|
if err != nil {
|
|
log.Printf("monitor: error getStatus: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if err := json.NewEncoder(w).Encode(s); err != nil {
|
|
log.Printf("monitor: error encoding full status: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (m *monitor) handleSummaryStatus(w http.ResponseWriter, r *http.Request) {
|
|
s, err := m.getStatus(r.Context())
|
|
if err != nil {
|
|
log.Printf("monitor: error getStatus: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
lines := []string{}
|
|
for _, p := range s.Status.Peer {
|
|
if p.Online {
|
|
name := dnsname.FirstLabel(p.DNSName)
|
|
lines = append(lines, fmt.Sprintf("%s\t\t%d\t%d\t%t", name, p.RxBytes, p.TxBytes, p.Active))
|
|
}
|
|
}
|
|
_, err = w.Write(fmt.Appendf(nil, "RaftState: %s\n", s.RaftState))
|
|
if err != nil {
|
|
log.Printf("monitor: error writing status: %v", err)
|
|
return
|
|
}
|
|
|
|
slices.Sort(lines)
|
|
for _, ln := range lines {
|
|
_, err = w.Write(fmt.Appendf(nil, "%s\n", ln))
|
|
if err != nil {
|
|
log.Printf("monitor: error writing status: %v", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *monitor) handleNetmap(w http.ResponseWriter, r *http.Request) {
|
|
lc, err := m.ts.LocalClient()
|
|
if err != nil {
|
|
log.Printf("monitor: error LocalClient: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
st, err := lc.Status(r.Context())
|
|
if err != nil {
|
|
log.Printf("monitor: error fetching status: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
encoder := json.NewEncoder(w)
|
|
encoder.SetIndent("", "\t")
|
|
if err := encoder.Encode(st); err != nil {
|
|
log.Printf("monitor: error encoding status: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (m *monitor) handleDial(w http.ResponseWriter, r *http.Request) {
|
|
var dialParams struct {
|
|
Addr string
|
|
}
|
|
defer r.Body.Close()
|
|
bs, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBodyBytes))
|
|
if err != nil {
|
|
log.Printf("monitor: error reading body: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
err = json.Unmarshal(bs, &dialParams)
|
|
if err != nil {
|
|
log.Printf("monitor: error unmarshalling json: %v", err)
|
|
http.Error(w, "", http.StatusBadRequest)
|
|
return
|
|
}
|
|
c, err := m.ts.Dial(r.Context(), "tcp", dialParams.Addr)
|
|
if err != nil {
|
|
log.Printf("monitor: error dialing: %v", err)
|
|
http.Error(w, "", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
c.Close()
|
|
w.Write([]byte("ok\n"))
|
|
}
|