Просмотр исходного кода

tailcfg: add IP and Types field to PingRequest

Signed-off-by: Simeng He <[email protected]>
Simeng He 4 лет назад
Родитель
Сommit
e199e407d2
4 измененных файлов с 126 добавлено и 1 удалено
  1. 56 0
      control/controlclient/direct.go
  2. 57 0
      control/controlclient/direct_test.go
  3. 1 0
      ipn/ipnlocal/local.go
  4. 12 1
      tailcfg/tailcfg.go

+ 56 - 0
control/controlclient/direct.go

@@ -1302,3 +1302,59 @@ func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
 
 	return nil
 }
+
+// tsmpPing sends a Ping to pr.IP, and sends an http request back to pr.URL
+// with ping response data.
+func tsmpPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) error {
+	var err error
+	if pr.URL == "" {
+		return errors.New("invalid PingRequest with no URL")
+	}
+	if pr.IP.IsZero() {
+		return fmt.Errorf("PingRequest with no proper IP got %v", pr.IP)
+	}
+	if !strings.Contains(pr.Types, "TSMP") {
+		return fmt.Errorf("PingRequest with no TSMP in Types, got : %v", pr.Types)
+	}
+
+	now := time.Now()
+	pinger.Ping(pr.IP, true, func(res *ipnstate.PingResult) {
+		// Currently does not check for error since we just return if it fails.
+		err = postPingResult(now, logf, c, pr, res)
+	})
+	return err
+}
+
+func postPingResult(now time.Time, logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, res *ipnstate.PingResult) error {
+	if res.Err != "" {
+		return errors.New(res.Err)
+	}
+	duration := time.Since(now)
+	if pr.Log {
+		logf("TSMP ping to %v completed in %v seconds. pinger.Ping took %v seconds", pr.IP, res.LatencySeconds, duration.Seconds())
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+	defer cancel()
+
+	jsonPingRes, err := json.Marshal(res)
+	if err != nil {
+		return err
+	}
+	// Send the results of the Ping, back to control URL.
+	req, err := http.NewRequestWithContext(ctx, "POST", pr.URL, bytes.NewBuffer(jsonPingRes))
+	if err != nil {
+		return fmt.Errorf("http.NewRequestWithContext(%q): %w", pr.URL, err)
+	}
+	if pr.Log {
+		logf("tsmpPing: sending ping results to %v ...", pr.URL)
+	}
+	t0 := time.Now()
+	_, err = c.Do(req)
+	d := time.Since(t0).Round(time.Millisecond)
+	if err != nil {
+		return fmt.Errorf("tsmpPing error: %w to %v (after %v)", err, pr.URL, d)
+	} else if pr.Log {
+		logf("tsmpPing complete to %v (after %v)", pr.URL, d)
+	}
+	return nil
+}

+ 57 - 0
control/controlclient/direct_test.go

@@ -6,9 +6,13 @@ package controlclient
 
 import (
 	"encoding/json"
+	"net/http"
+	"net/http/httptest"
 	"testing"
+	"time"
 
 	"inet.af/netaddr"
+	"tailscale.com/ipn/ipnstate"
 	"tailscale.com/tailcfg"
 	"tailscale.com/types/wgkey"
 )
@@ -103,3 +107,56 @@ func TestNewHostinfo(t *testing.T) {
 	}
 	t.Logf("Got: %s", j)
 }
+
+func TestTsmpPing(t *testing.T) {
+	hi := NewHostinfo()
+	ni := tailcfg.NetInfo{LinkType: "wired"}
+	hi.NetInfo = &ni
+
+	key, err := wgkey.NewPrivate()
+	if err != nil {
+		t.Error(err)
+	}
+	opts := Options{
+		ServerURL: "https://example.com",
+		Hostinfo:  hi,
+		GetMachinePrivateKey: func() (wgkey.Private, error) {
+			return key, nil
+		},
+	}
+
+	c, err := NewDirect(opts)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pingRes := &ipnstate.PingResult{
+		IP:       "123.456.7890",
+		Err:      "",
+		NodeName: "testnode",
+	}
+
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		body := new(ipnstate.PingResult)
+		if err := json.NewDecoder(r.Body).Decode(body); err != nil {
+			t.Fatal(err)
+		}
+		if pingRes.IP != body.IP {
+			t.Fatalf("PingResult did not have the correct IP : got %v, expected : %v", body.IP, pingRes.IP)
+		}
+		w.WriteHeader(200)
+	}))
+	defer ts.Close()
+
+	now := time.Now()
+
+	pr := &tailcfg.PingRequest{
+		URL: ts.URL,
+	}
+
+	err = postPingResult(now, t.Logf, c.httpc, pr, pingRes)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 1 - 0
ipn/ipnlocal/local.go

@@ -851,6 +851,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
 		DiscoPublicKey:       discoPublic,
 		DebugFlags:           debugFlags,
 		LinkMonitor:          b.e.GetLinkMonitor(),
+		Pinger:               b.e,
 
 		// Don't warn about broken Linux IP forwading when
 		// netstack is being used.

+ 12 - 1
tailcfg/tailcfg.go

@@ -897,19 +897,30 @@ type DNSRecord struct {
 	Value string
 }
 
-// PingRequest is a request to send an HTTP request to prove the
+// PingRequest with no IP and Types is a request to send an HTTP request to prove the
 // long-polling client is still connected.
+// PingRequest with Types and IP, will send a ping to the IP and send a
+// POST request to the URL to prove that the ping succeeded.
 type PingRequest struct {
 	// URL is the URL to send a HEAD request to.
 	// It will be a unique URL each time. No auth headers are necessary.
 	//
 	// If the client sees multiple PingRequests with the same URL,
 	// subsequent ones should be ignored.
+	// If Types and IP are defined, then URL is the URL to send a POST request to.
 	URL string
 
 	// Log is whether to log about this ping in the success case.
 	// For failure cases, the client will log regardless.
 	Log bool `json:",omitempty"`
+
+	// Types is the types of ping that is initiated. Can be TSMP, ICMP or disco.
+	// Types will be comma separated, such as TSMP,disco.
+	Types string
+
+	// IP is the ping target.
+	// It is used in TSMP pings, if IP is invalid or empty then do a HEAD request to the URL.
+	IP netaddr.IP
 }
 
 type MapResponse struct {