2023-01-27 21:37:20 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-08-25 04:23:37 +00:00
|
|
|
|
2022-08-02 18:34:03 +00:00
|
|
|
//go:build go1.19
|
2022-03-18 14:44:05 +00:00
|
|
|
|
2021-03-25 15:59:00 +00:00
|
|
|
package main
|
2020-08-25 04:23:37 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-08-25 20:45:15 +00:00
|
|
|
"crypto/tls"
|
2020-08-25 04:23:37 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
2020-08-25 20:45:15 +00:00
|
|
|
"fmt"
|
2021-06-25 18:44:40 +00:00
|
|
|
"io"
|
2020-08-25 04:23:37 +00:00
|
|
|
"log"
|
2020-08-25 20:45:15 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptrace"
|
|
|
|
"net/url"
|
2020-08-25 04:23:37 +00:00
|
|
|
"os"
|
2020-09-03 16:09:23 +00:00
|
|
|
"time"
|
2020-08-25 04:23:37 +00:00
|
|
|
|
2020-09-03 16:09:23 +00:00
|
|
|
"tailscale.com/derp/derphttp"
|
tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing
This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.
In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.
The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).
Updates #11874
Updates #4136
Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 03:29:20 +00:00
|
|
|
"tailscale.com/health"
|
2021-06-25 18:44:40 +00:00
|
|
|
"tailscale.com/ipn"
|
2023-04-18 21:26:58 +00:00
|
|
|
"tailscale.com/net/netmon"
|
2020-08-25 20:45:15 +00:00
|
|
|
"tailscale.com/net/tshttpproxy"
|
2020-09-03 16:09:23 +00:00
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/types/key"
|
2020-08-25 04:23:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var debugArgs struct {
|
2021-08-26 18:59:52 +00:00
|
|
|
ifconfig bool // print network state once and exit
|
2020-09-03 16:09:23 +00:00
|
|
|
monitor bool
|
|
|
|
getURL string
|
|
|
|
derpCheck string
|
2021-08-03 05:09:03 +00:00
|
|
|
portmap bool
|
2020-08-25 04:23:37 +00:00
|
|
|
}
|
|
|
|
|
2021-02-15 05:11:06 +00:00
|
|
|
var debugModeFunc = debugMode // so it can be addressable
|
|
|
|
|
2021-02-04 20:20:07 +00:00
|
|
|
func debugMode(args []string) error {
|
|
|
|
fs := flag.NewFlagSet("debug", flag.ExitOnError)
|
2021-08-26 18:59:52 +00:00
|
|
|
fs.BoolVar(&debugArgs.ifconfig, "ifconfig", false, "If true, print network interface state")
|
2023-04-18 21:26:58 +00:00
|
|
|
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run network monitor forever. Precludes all other options.")
|
2021-08-03 05:09:03 +00:00
|
|
|
fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.")
|
2021-02-04 20:20:07 +00:00
|
|
|
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
|
|
|
|
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(fs.Args()) > 0 {
|
|
|
|
return errors.New("unknown non-flag debug subcommand arguments")
|
2020-08-25 04:23:37 +00:00
|
|
|
}
|
2021-02-04 20:20:07 +00:00
|
|
|
ctx := context.Background()
|
2020-09-03 16:09:23 +00:00
|
|
|
if debugArgs.derpCheck != "" {
|
|
|
|
return checkDerp(ctx, debugArgs.derpCheck)
|
|
|
|
}
|
2021-08-26 18:59:52 +00:00
|
|
|
if debugArgs.ifconfig {
|
|
|
|
return runMonitor(ctx, false)
|
|
|
|
}
|
2020-08-25 04:23:37 +00:00
|
|
|
if debugArgs.monitor {
|
2021-08-26 18:59:52 +00:00
|
|
|
return runMonitor(ctx, true)
|
2020-08-25 04:23:37 +00:00
|
|
|
}
|
2021-08-03 05:09:03 +00:00
|
|
|
if debugArgs.portmap {
|
|
|
|
return debugPortmap(ctx)
|
|
|
|
}
|
2020-08-25 20:45:15 +00:00
|
|
|
if debugArgs.getURL != "" {
|
|
|
|
return getURL(ctx, debugArgs.getURL)
|
|
|
|
}
|
2020-08-25 04:23:37 +00:00
|
|
|
return errors.New("only --monitor is available at the moment")
|
|
|
|
}
|
|
|
|
|
2021-08-26 18:59:52 +00:00
|
|
|
func runMonitor(ctx context.Context, loop bool) error {
|
2024-04-28 04:01:54 +00:00
|
|
|
dump := func(st *netmon.State) {
|
2020-08-25 04:23:37 +00:00
|
|
|
j, _ := json.MarshalIndent(st, "", " ")
|
|
|
|
os.Stderr.Write(j)
|
|
|
|
}
|
2023-04-18 21:26:58 +00:00
|
|
|
mon, err := netmon.New(log.Printf)
|
2020-08-25 04:23:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-26 22:37:27 +00:00
|
|
|
defer mon.Close()
|
|
|
|
|
2023-08-23 17:05:21 +00:00
|
|
|
mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) {
|
|
|
|
if !delta.Major {
|
|
|
|
log.Printf("Network monitor fired; not a major change")
|
2021-03-01 20:56:03 +00:00
|
|
|
return
|
|
|
|
}
|
2023-04-18 21:26:58 +00:00
|
|
|
log.Printf("Network monitor fired. New state:")
|
2023-08-23 17:05:21 +00:00
|
|
|
dump(delta.New)
|
2021-02-28 03:33:21 +00:00
|
|
|
})
|
2021-08-26 18:59:52 +00:00
|
|
|
if loop {
|
|
|
|
log.Printf("Starting link change monitor; initial state:")
|
|
|
|
}
|
2021-03-01 20:56:03 +00:00
|
|
|
dump(mon.InterfaceState())
|
2021-08-26 18:59:52 +00:00
|
|
|
if !loop {
|
|
|
|
return nil
|
|
|
|
}
|
2020-08-25 04:23:37 +00:00
|
|
|
mon.Start()
|
|
|
|
log.Printf("Started link change monitor; waiting...")
|
|
|
|
select {}
|
|
|
|
}
|
2020-08-25 20:45:15 +00:00
|
|
|
|
|
|
|
func getURL(ctx context.Context, urlStr string) error {
|
|
|
|
if urlStr == "login" {
|
|
|
|
urlStr = "https://login.tailscale.com"
|
|
|
|
}
|
|
|
|
log.SetOutput(os.Stdout)
|
|
|
|
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
|
|
|
|
GetConn: func(hostPort string) { log.Printf("GetConn(%q)", hostPort) },
|
|
|
|
GotConn: func(info httptrace.GotConnInfo) { log.Printf("GotConn: %+v", info) },
|
|
|
|
DNSStart: func(info httptrace.DNSStartInfo) { log.Printf("DNSStart: %+v", info) },
|
|
|
|
DNSDone: func(info httptrace.DNSDoneInfo) { log.Printf("DNSDoneInfo: %+v", info) },
|
|
|
|
TLSHandshakeStart: func() { log.Printf("TLSHandshakeStart") },
|
|
|
|
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { log.Printf("TLSHandshakeDone: %+v, %v", cs, err) },
|
|
|
|
WroteRequest: func(info httptrace.WroteRequestInfo) { log.Printf("WroteRequest: %+v", info) },
|
|
|
|
})
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("http.NewRequestWithContext: %v", err)
|
|
|
|
}
|
|
|
|
proxyURL, err := tshttpproxy.ProxyFromEnvironment(req)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("tshttpproxy.ProxyFromEnvironment: %v", err)
|
|
|
|
}
|
|
|
|
log.Printf("proxy: %v", proxyURL)
|
|
|
|
tr := &http.Transport{
|
|
|
|
Proxy: func(*http.Request) (*url.URL, error) { return proxyURL, nil },
|
|
|
|
ProxyConnectHeader: http.Header{},
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
}
|
|
|
|
if proxyURL != nil {
|
|
|
|
auth, err := tshttpproxy.GetAuthHeader(proxyURL)
|
|
|
|
if err == nil && auth != "" {
|
2020-08-29 03:58:14 +00:00
|
|
|
tr.ProxyConnectHeader.Set("Proxy-Authorization", auth)
|
2020-08-25 20:45:15 +00:00
|
|
|
}
|
2021-09-23 05:25:40 +00:00
|
|
|
log.Printf("tshttpproxy.GetAuthHeader(%v) got: auth of %d bytes, err=%v", proxyURL, len(auth), err)
|
2020-08-29 03:58:14 +00:00
|
|
|
const truncLen = 20
|
|
|
|
if len(auth) > truncLen {
|
|
|
|
auth = fmt.Sprintf("%s...(%d total bytes)", auth[:truncLen], len(auth))
|
|
|
|
}
|
2021-09-23 05:25:40 +00:00
|
|
|
if auth != "" {
|
|
|
|
// We used log.Printf above (for timestamps).
|
|
|
|
// Use fmt.Printf here instead just to appease
|
|
|
|
// a security scanner, despite log.Printf only
|
|
|
|
// going to stdout.
|
|
|
|
fmt.Printf("... Proxy-Authorization = %q\n", auth)
|
|
|
|
}
|
2020-08-25 20:45:15 +00:00
|
|
|
}
|
|
|
|
res, err := tr.RoundTrip(req)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Transport.RoundTrip: %v", err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
return res.Write(os.Stdout)
|
|
|
|
}
|
2020-09-03 16:09:23 +00:00
|
|
|
|
2022-09-26 22:37:27 +00:00
|
|
|
func checkDerp(ctx context.Context, derpRegion string) (err error) {
|
tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing
This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.
In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.
The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).
Updates #11874
Updates #4136
Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 03:29:20 +00:00
|
|
|
ht := new(health.Tracker)
|
2021-06-25 18:44:40 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("create derp map request: %w", err)
|
|
|
|
}
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("fetch derp map failed: %w", err)
|
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
2022-09-15 12:06:59 +00:00
|
|
|
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
|
2021-06-25 18:44:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("fetch derp map failed: %w", err)
|
|
|
|
}
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
return fmt.Errorf("fetch derp map: %v: %s", res.Status, b)
|
|
|
|
}
|
|
|
|
var dmap tailcfg.DERPMap
|
|
|
|
if err = json.Unmarshal(b, &dmap); err != nil {
|
|
|
|
return fmt.Errorf("fetch DERP map: %w", err)
|
|
|
|
}
|
2020-09-03 16:09:23 +00:00
|
|
|
getRegion := func() *tailcfg.DERPRegion {
|
|
|
|
for _, r := range dmap.Regions {
|
|
|
|
if r.RegionCode == derpRegion {
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, r := range dmap.Regions {
|
|
|
|
log.Printf("Known region: %q", r.RegionCode)
|
|
|
|
}
|
|
|
|
log.Fatalf("unknown region %q", derpRegion)
|
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
|
2021-10-28 22:42:50 +00:00
|
|
|
priv1 := key.NewNode()
|
|
|
|
priv2 := key.NewNode()
|
2020-09-03 16:09:23 +00:00
|
|
|
|
2023-04-17 23:01:41 +00:00
|
|
|
c1 := derphttp.NewRegionClient(priv1, log.Printf, nil, getRegion)
|
|
|
|
c2 := derphttp.NewRegionClient(priv2, log.Printf, nil, getRegion)
|
tsd, ipnlocal, etc: add tsd.System.HealthTracker, start some plumbing
This adds a health.Tracker to tsd.System, accessible via
a new tsd.System.HealthTracker method.
In the future, that new method will return a tsd.System-specific
HealthTracker, so multiple tsnet.Servers in the same process are
isolated. For now, though, it just always returns the temporary
health.Global value. That permits incremental plumbing over a number
of changes. When the second to last health.Global reference is gone,
then the tsd.System.HealthTracker implementation can return a private
Tracker.
The primary plumbing this does is adding it to LocalBackend and its
dozen and change health calls. A few misc other callers are also
plumbed. Subsequent changes will flesh out other parts of the tree
(magicsock, controlclient, etc).
Updates #11874
Updates #4136
Change-Id: Id51e73cfc8a39110425b6dc19d18b3975eac75ce
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2024-04-26 03:29:20 +00:00
|
|
|
c1.HealthTracker = ht
|
|
|
|
c2.HealthTracker = ht
|
2022-09-26 22:37:27 +00:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
c1.Close()
|
|
|
|
c2.Close()
|
|
|
|
}
|
|
|
|
}()
|
2020-09-03 16:09:23 +00:00
|
|
|
|
|
|
|
c2.NotePreferred(true) // just to open it
|
|
|
|
|
|
|
|
m, err := c2.Recv()
|
|
|
|
log.Printf("c2 got %T, %v", m, err)
|
|
|
|
|
|
|
|
t0 := time.Now()
|
|
|
|
if err := c1.Send(priv2.Public(), []byte("hello")); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Println(time.Since(t0))
|
|
|
|
|
|
|
|
m, err = c2.Recv()
|
|
|
|
log.Printf("c2 got %T, %v", m, err)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
log.Printf("ok")
|
|
|
|
return err
|
|
|
|
}
|
2021-08-03 05:09:03 +00:00
|
|
|
|
|
|
|
func debugPortmap(ctx context.Context) error {
|
2023-03-02 23:05:30 +00:00
|
|
|
return fmt.Errorf("this flag has been deprecated in favour of 'tailscale debug portmap'")
|
2021-08-03 05:09:03 +00:00
|
|
|
}
|