// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build linux // +build linux package vms import ( "bytes" "context" "fmt" "net" "net/http" "strings" "testing" "time" "golang.org/x/crypto/ssh" "inet.af/netaddr" ) const timeout = 15 * time.Second func retry(t *testing.T, fn func() error) { t.Helper() const tries = 3 var err error for i := 0; i < tries; i++ { err = fn() if err != nil { t.Logf("%dth invocation failed, trying again: %v", i, err) time.Sleep(50 * time.Millisecond) } if err == nil { return } } t.Fatalf("tried %d times, got: %v", tries, err) } func (h *Harness) testPing(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) { var outp []byte var err error retry(t, func() error { sess := getSession(t, cli) outp, err = sess.CombinedOutput(fmt.Sprintf("tailscale ping -c 1 %s", ipAddr)) return err }) if !bytes.Contains(outp, []byte("pong")) { t.Log(string(outp)) t.Fatal("no pong") } retry(t, func() error { sess := getSession(t, cli) // NOTE(Xe): the ping command is inconsistent across distros. Joy. pingCmd := fmt.Sprintf("sh -c 'ping -c 1 %[1]s || ping -6 -c 1 %[1]s || ping6 -c 1 %[1]s\n'", ipAddr) t.Logf("running %q", pingCmd) outp, err = sess.CombinedOutput(pingCmd) return err }) if !bytes.Contains(outp, []byte("bytes")) { t.Log(string(outp)) t.Fatalf("wanted output to contain %q, it did not", "bytes") } } func getSession(t *testing.T, cli *ssh.Client) *ssh.Session { sess, err := cli.NewSession() if err != nil { t.Fatal(err) } t.Cleanup(func() { sess.Close() }) return sess } func (h *Harness) testOutgoingTCP(t *testing.T, ipAddr netaddr.IP, cli *ssh.Client) { const sendmsg = "this is a message that curl won't print" ctx, cancel := context.WithCancel(context.Background()) s := &http.Server{ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Logf("http connection from %s", r.RemoteAddr) cancel() fmt.Fprintln(w, sendmsg) }), } ln, err := net.Listen("tcp", net.JoinHostPort("::", "0")) if err != nil { t.Fatalf("can't make HTTP server: %v", err) } _, port, _ := net.SplitHostPort(ln.Addr().String()) go s.Serve(ln) // sess := getSession(t, cli) // sess.Stderr = logger.FuncWriter(t.Logf) // sess.Stdout = logger.FuncWriter(t.Logf) // sess.Run("ip route show table all") // sess = getSession(t, cli) // sess.Stderr = logger.FuncWriter(t.Logf) // sess.Stdout = logger.FuncWriter(t.Logf) // sess.Run("sysctl -a") var outp []byte retry(t, func() error { var err error sess := getSession(t, cli) v6Arg := "" if ipAddr.Is6() { v6Arg = "-6 -g" } cmd := fmt.Sprintf("curl -v %s -s -f http://%s\n", v6Arg, net.JoinHostPort(ipAddr.String(), port)) t.Logf("running: %s", cmd) outp, err = sess.CombinedOutput(cmd) if err != nil { t.Log(string(outp)) } return err }) if msg := string(bytes.TrimSpace(outp)); !strings.Contains(msg, sendmsg) { t.Fatalf("wanted %q, got: %q", sendmsg, msg) } <-ctx.Done() }