mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-31 00:03:47 +00:00
140 lines
3.1 KiB
Go
140 lines
3.1 KiB
Go
package tsconsensus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/ipn/ipnstate"
|
|
"tailscale.com/tsnet"
|
|
)
|
|
|
|
type status struct {
|
|
Status *ipnstate.Status
|
|
RaftState string
|
|
}
|
|
|
|
type monitor struct {
|
|
ts *tsnet.Server
|
|
con *Consensus
|
|
}
|
|
|
|
func (m *monitor) getStatus(ctx context.Context) (status, error) {
|
|
lc, err := m.ts.LocalClient()
|
|
if err != nil {
|
|
return status{}, err
|
|
}
|
|
tStatus, err := lc.Status(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}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/full", m.handleFullStatus)
|
|
mux.HandleFunc("/", m.handleSummaryStatus)
|
|
mux.HandleFunc("/netmap", m.handleNetmap)
|
|
mux.HandleFunc("/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 {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
if err := json.NewEncoder(w).Encode(s); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (m *monitor) handleSummaryStatus(w http.ResponseWriter, r *http.Request) {
|
|
s, err := m.getStatus(r.Context())
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
lines := []string{}
|
|
for _, p := range s.Status.Peer {
|
|
if p.Online {
|
|
lines = append(lines, fmt.Sprintf("%s\t\t%d\t%d\t%t", strings.Split(p.DNSName, ".")[0], p.RxBytes, p.TxBytes, p.Active))
|
|
}
|
|
}
|
|
slices.Sort(lines)
|
|
lines = append([]string{fmt.Sprintf("RaftState: %s", s.RaftState)}, lines...)
|
|
txt := strings.Join(lines, "\n") + "\n"
|
|
w.Write([]byte(txt))
|
|
}
|
|
|
|
func (m *monitor) handleNetmap(w http.ResponseWriter, r *http.Request) {
|
|
var mask ipn.NotifyWatchOpt = ipn.NotifyInitialNetMap
|
|
mask |= ipn.NotifyNoPrivateKeys
|
|
lc, err := m.ts.LocalClient()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
watcher, err := lc.WatchIPNBus(r.Context(), mask)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
defer watcher.Close()
|
|
|
|
n, err := watcher.Next()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
j, _ := json.MarshalIndent(n.NetMap, "", "\t")
|
|
w.Write([]byte(j))
|
|
return
|
|
}
|
|
|
|
func (m *monitor) handleDial(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Println("FRAN handle ping")
|
|
var dialParams struct {
|
|
Addr string
|
|
}
|
|
bs, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
err = json.Unmarshal(bs, &dialParams)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
fmt.Println("dialing", dialParams.Addr)
|
|
c, err := m.ts.Dial(r.Context(), "tcp", dialParams.Addr)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
fmt.Println("ping success", c)
|
|
defer c.Close()
|
|
w.Write([]byte("ok\n"))
|
|
return
|
|
}
|