// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package netstack import ( "html/template" "net/http" "net/netip" "slices" "time" xmaps "golang.org/x/exp/maps" "gvisor.dev/gvisor/pkg/tcpip" ) var tcpForwarderTemplate = template.Must(template.New("").Parse(`

TCP Forwarder

TCP Statistics

{{ range .Stats }} {{ end }}
Metric Value
{{ .Key }}{{ .Value }}

In-Flight Outbound Connections

{{ range .InFlightDials }} {{ end }}
Start Time Client IP Remote IP
{{ .Start.Format "2006-01-02T15:04:05Z07:00" }} ({{ printf "%.2f" .DurationSecs }} seconds ago) {{ .ClientIP }} {{ .RemoteAddr }}
`)) // DebugTCPForwarder writes debug information about this netstack // implementation's current TCP forwarder in HTML format. func (ns *Impl) DebugTCPForwarder(w http.ResponseWriter, r *http.Request) { // Grab data while holding the mutex ns.tcpDebugMu.Lock() tcpDials := xmaps.Values(ns.inFlightDials) ns.tcpDebugMu.Unlock() slices.SortFunc(tcpDials, func(a, b tcpDialInfo) int { return a.start.Compare(b.start) }) type templateDataStats struct { Key string Value uint64 } type templateDataDial struct { Start time.Time DurationSecs float64 ClientIP netip.Addr RemoteAddr netip.AddrPort } type templateData struct { Stats []templateDataStats InFlightDials []templateDataDial } var data templateData // Statistics from gVisor tcpStats := ns.ipstack.Stats().TCP tcpMetrics := []struct { name string field *tcpip.StatCounter }{ {"Active Connection Openings", tcpStats.ActiveConnectionOpenings}, {"Passive Connection Openings", tcpStats.PassiveConnectionOpenings}, {"Established Connections", tcpStats.CurrentEstablished}, {"Connected Connections", tcpStats.CurrentConnected}, {"Dropped In-Flight Forwarder Connections", tcpStats.ForwardMaxInFlightDrop}, {"Established Resets", tcpStats.EstablishedResets}, {"Established Timeout", tcpStats.EstablishedTimedout}, {"Failed Connection Attempts", tcpStats.FailedConnectionAttempts}, {"Retransmits", tcpStats.Retransmits}, {"Timeouts", tcpStats.Timeouts}, {"Checksum Errors", tcpStats.ChecksumErrors}, {"Failed Port Reservations", tcpStats.FailedPortReservations}, } for _, metric := range tcpMetrics { data.Stats = append(data.Stats, templateDataStats{ Key: metric.name, Value: metric.field.Value(), }) } // Any in-flight DialContext calls in the TCP forwarding path. now := time.Now() for _, dial := range tcpDials { elapsed := now.Sub(dial.start) data.InFlightDials = append(data.InFlightDials, templateDataDial{ Start: dial.start, DurationSecs: elapsed.Seconds(), ClientIP: dial.clientRemoteIP, RemoteAddr: dial.dialAddr, }) } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Cache-Control", "no-cache") tcpForwarderTemplate.Execute(w, &data) }