summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tstest/integration/integration_test.go95
-rw-r--r--tstest/integration/testcontrol/testcontrol.go43
-rw-r--r--tstest/integration/vms/harness_test.go7
-rw-r--r--tstest/integration/vms/vm_setup_test.go33
-rw-r--r--tstest/integration/vms/vms_test.go64
5 files changed, 222 insertions, 20 deletions
diff --git a/tstest/integration/integration_test.go b/tstest/integration/integration_test.go
index 39c391fb4..de676b8cf 100644
--- a/tstest/integration/integration_test.go
+++ b/tstest/integration/integration_test.go
@@ -20,6 +20,7 @@ import (
"net/http/httptest"
"os"
"os/exec"
+ "path"
"path/filepath"
"regexp"
"runtime"
@@ -311,6 +312,100 @@ func TestAddPingRequest(t *testing.T) {
t.Error("all ping attempts failed")
}
+func TestTaildrop(t *testing.T) {
+ // TODO: currently taildrop doesn't work with userspace networking
+ // but when it does, this test should just work.
+ t.Skip()
+
+ t.Parallel()
+ bins := BuildTestBinaries(t)
+
+ env := newTestEnv(t, bins, configureControl(func(control *testcontrol.Server) {
+ control.AllNodesSameUser = true
+ }))
+ defer env.Close()
+
+ n1 := newTestNode(t, env)
+ n1SocksAddrCh := n1.socks5AddrChan()
+ d1 := n1.StartDaemon(t)
+ defer d1.Kill()
+
+ n2 := newTestNode(t, env)
+ n2SocksAddrCh := n2.socks5AddrChan()
+ d2 := n2.StartDaemon(t)
+ defer d2.Kill()
+
+ n1Socks := n1.AwaitSocksAddr(t, n1SocksAddrCh)
+ n2Socks := n1.AwaitSocksAddr(t, n2SocksAddrCh)
+ t.Logf("node1 SOCKS5 addr: %v", n1Socks)
+ t.Logf("node2 SOCKS5 addr: %v", n2Socks)
+
+ for _, n := range env.Control.AllNodes() {
+ n.Capabilities = append(n.Capabilities, tailcfg.CapabilityFileSharing)
+ }
+
+ n1.AwaitListening(t)
+ n2.AwaitListening(t)
+ n1.MustUp()
+ n2.MustUp()
+ n1.AwaitRunning(t)
+ n2.AwaitRunning(t)
+
+ target := n2.AwaitIP(t)
+
+ srcDir := t.TempDir()
+ dstDir := t.TempDir()
+
+ fileName := "taildrop.txt"
+ filePath := path.Join(srcDir, fileName)
+ contents := []byte("Taildrop drop bop 💧 ??%@#@123˙©∆∆˚ 水平線")
+ if err := ioutil.WriteFile(filePath, contents, 0666); err != nil {
+ t.Errorf("Failed to write to file: %v", err)
+ }
+
+ targetsOutput, err := n1.Tailscale("file", "cp", "-targets").CombinedOutput()
+ if err != nil {
+ t.Fatal(string(targetsOutput), err)
+ }
+ if !bytes.Contains(targetsOutput, []byte(target.String())) {
+ t.Errorf("Missing target from cp -targets, want: %v, in: %v", target, targetsOutput)
+ }
+
+ cpCmd := n1.Tailscale("file", "cp", "-proxy", "socks5://"+n1Socks, filePath, fmt.Sprintf("%s:", target))
+ out, err := cpCmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(string(out), err)
+ }
+
+ getCmd := n2.Tailscale("file", "get", dstDir)
+ if output, err := getCmd.CombinedOutput(); err != nil {
+ t.Fatal(string(output), err)
+ }
+
+ files, err := ioutil.ReadDir(dstDir)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(files) != 1 {
+ t.Fatalf("want 1 file, got %d", len(files))
+ }
+
+ gotFile := files[0]
+ if !strings.Contains(fileName, gotFile.Name()) {
+ t.Errorf("want file name %s, got %s", fileName, gotFile.Name())
+ }
+ got, err := ioutil.ReadFile(path.Join(dstDir, gotFile.Name()))
+ if err != nil {
+ t.Errorf("Failed to read from cp'd file: %v", err)
+ }
+ if !bytes.Equal(got, contents) {
+ t.Errorf("mismatched taildrop contents, want %s, got %s", contents, got)
+ }
+
+ d1.MustCleanShutdown(t)
+ d2.MustCleanShutdown(t)
+}
+
// Issue 2434: when "down" (WantRunning false), tailscaled shouldn't
// be connected to control.
func TestNoControlConnectionWhenDown(t *testing.T) {
diff --git a/tstest/integration/testcontrol/testcontrol.go b/tstest/integration/testcontrol/testcontrol.go
index 7f6792f3d..a845040df 100644
--- a/tstest/integration/testcontrol/testcontrol.go
+++ b/tstest/integration/testcontrol/testcontrol.go
@@ -41,7 +41,10 @@ type Server struct {
Logf logger.Logf // nil means to use the log package
DERPMap *tailcfg.DERPMap // nil means to use prod DERP map
RequireAuth bool
- Verbose bool
+ // AllNodesSameUser will start all nodes with the same user,
+ // and is set while testing taildrop which requires nodes with the same user.
+ AllNodesSameUser bool
+ Verbose bool
// ExplicitBaseURL or HTTPTestServer must be set.
ExplicitBaseURL string // e.g. "http://127.0.0.1:1234" with no trailing URL
@@ -269,6 +272,7 @@ func (s *Server) nodeLocked(nodeKey tailcfg.NodeKey) *tailcfg.Node {
return s.nodes[nodeKey].Clone()
}
+// AllNodes returns the set of all nodes that are currently active on this server.
func (s *Server) AllNodes() (nodes []*tailcfg.Node) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -293,12 +297,16 @@ func (s *Server) getUser(nodeKey tailcfg.NodeKey) (*tailcfg.User, *tailcfg.Login
if u, ok := s.users[nodeKey]; ok {
return u, s.logins[nodeKey]
}
- id := tailcfg.UserID(len(s.users) + 1)
+ userID := tailcfg.UserID(len(s.users) + 1)
+ if s.AllNodesSameUser {
+ userID = 42
+ }
+ nodeID := tailcfg.NodeID(len(s.nodes) + 1)
domain := "fake-control.example.net"
- loginName := fmt.Sprintf("user-%d@%s", id, domain)
- displayName := fmt.Sprintf("User %d", id)
+ loginName := fmt.Sprintf("user-%d@%s", userID, domain)
+ displayName := fmt.Sprintf("User %d", userID)
login := &tailcfg.Login{
- ID: tailcfg.LoginID(id),
+ ID: tailcfg.LoginID(nodeID),
Provider: "testcontrol",
LoginName: loginName,
DisplayName: displayName,
@@ -306,7 +314,7 @@ func (s *Server) getUser(nodeKey tailcfg.NodeKey) (*tailcfg.User, *tailcfg.Login
Domain: domain,
}
user := &tailcfg.User{
- ID: id,
+ ID: userID,
LoginName: loginName,
DisplayName: displayName,
Domain: domain,
@@ -408,23 +416,22 @@ func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey tail
machineAuthorized := true // TODO: add Server.RequireMachineAuth
- v4Prefix := netaddr.IPPrefixFrom(netaddr.IPv4(100, 64, uint8(tailcfg.NodeID(user.ID)>>8), uint8(tailcfg.NodeID(user.ID))), 32)
+ v4Prefix := netaddr.IPPrefixFrom(netaddr.IPv4(100, 64, uint8(login.ID>>8), uint8(login.ID)), 32)
v6Prefix := netaddr.IPPrefixFrom(tsaddr.Tailscale4To6(v4Prefix.IP()), 128)
- allowedIPs := []netaddr.IPPrefix{
- v4Prefix,
- v6Prefix,
- }
+ allowedIPs := []netaddr.IPPrefix{v4Prefix, v6Prefix}
s.nodes[req.NodeKey] = &tailcfg.Node{
ID: tailcfg.NodeID(user.ID),
- StableID: tailcfg.StableNodeID(fmt.Sprintf("TESTCTRL%08x", int(user.ID))),
+ StableID: tailcfg.StableNodeID(fmt.Sprintf("TESTCTRL%08x", int(login.ID))),
User: user.ID,
Machine: mkey,
Key: req.NodeKey,
MachineAuthorized: machineAuthorized,
Addresses: allowedIPs,
AllowedIPs: allowedIPs,
+ Capabilities: []string{"https://tailscale.com/cap/file-sharing"},
+ Hostinfo: *req.Hostinfo,
}
requireAuth := s.RequireAuth
if requireAuth && s.nodeKeyAuthed[req.NodeKey] {
@@ -536,6 +543,9 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.M
jitter := time.Duration(rand.Intn(8000)) * time.Millisecond
keepAlive := 50*time.Second + jitter
+ s.mu.Lock()
+ s.nodes[req.NodeKey].Hostinfo = *req.Hostinfo
+ s.mu.Unlock()
node := s.Node(req.NodeKey)
if node == nil {
http.Error(w, "node not found", 400)
@@ -645,7 +655,7 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
// node key rotated away (once test server supports that)
return nil, nil
}
- user, _ := s.getUser(req.NodeKey)
+ user, login := s.getUser(req.NodeKey)
res = &tailcfg.MapResponse{
Node: node,
DERPMap: s.DERPMap,
@@ -662,13 +672,10 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
}
}
- v4Prefix := netaddr.IPPrefixFrom(netaddr.IPv4(100, 64, uint8(tailcfg.NodeID(user.ID)>>8), uint8(tailcfg.NodeID(user.ID))), 32)
+ v4Prefix := netaddr.IPPrefixFrom(netaddr.IPv4(100, 64, uint8(login.ID>>8), uint8(login.ID)), 32)
v6Prefix := netaddr.IPPrefixFrom(tsaddr.Tailscale4To6(v4Prefix.IP()), 128)
- res.Node.Addresses = []netaddr.IPPrefix{
- v4Prefix,
- v6Prefix,
- }
+ res.Node.Addresses = []netaddr.IPPrefix{v4Prefix, v6Prefix}
res.Node.AllowedIPs = res.Node.Addresses
// Consume the PingRequest while protected by mutex if it exists
diff --git a/tstest/integration/vms/harness_test.go b/tstest/integration/vms/harness_test.go
index ea5a7bba0..faa5b5d09 100644
--- a/tstest/integration/vms/harness_test.go
+++ b/tstest/integration/vms/harness_test.go
@@ -54,7 +54,10 @@ func newHarness(t *testing.T) *Harness {
})
t.Logf("host:port: %s", ln.Addr())
- cs := &testcontrol.Server{}
+ cs := &testcontrol.Server{
+ // TODO should this be set for all tests?
+ AllNodesSameUser: true,
+ }
derpMap := integration.RunDERPAndSTUN(t, t.Logf, bindHost)
cs.DERPMap = derpMap
@@ -139,7 +142,7 @@ func (h *Harness) Tailscale(t *testing.T, args ...string) []byte {
cmd := exec.Command(h.bins.CLI, args...)
out, err := cmd.CombinedOutput()
if err != nil {
- t.Fatal(err)
+ t.Fatalf("cmd %v failed: %v, out: %s", args, err, out)
}
return out
diff --git a/tstest/integration/vms/vm_setup_test.go b/tstest/integration/vms/vm_setup_test.go
index d5aa0f1a1..d19ba1e27 100644
--- a/tstest/integration/vms/vm_setup_test.go
+++ b/tstest/integration/vms/vm_setup_test.go
@@ -303,6 +303,7 @@ func mkdir(t *testing.T, cli *sftp.Client, name string) {
}
}
+// copyFile copies a file from the local machine to the remote machine.
func copyFile(t *testing.T, cli *sftp.Client, localSrc, remoteDest string) {
t.Helper()
@@ -344,6 +345,38 @@ func copyFile(t *testing.T, cli *sftp.Client, localSrc, remoteDest string) {
}
}
+// copyFileFrom copies a file from the remote machine to the local machine
+func copyFileFrom(t *testing.T, cli *sftp.Client, localDest, remoteSrc string) {
+ t.Helper()
+
+ remoteFile, err := cli.Open(remoteSrc)
+ if err != nil {
+ t.Fatalf("can't open: %v", err)
+ }
+ defer remoteFile.Close()
+
+ localFile, err := os.Create(localDest)
+ if err != nil {
+ t.Fatalf("can't open: %v", err)
+ }
+ defer localFile.Close()
+
+ rfStat, err := remoteFile.Stat()
+ if err != nil {
+ t.Fatalf("can't stat: %v", err)
+ }
+
+ n, err := io.Copy(localFile, remoteFile)
+ if err != nil {
+ t.Fatalf("copy failed: %v", err)
+ }
+
+ if rfStat.Size() != n {
+ t.Fatalf("incorrect number of bytes copied: wanted: %d, got: %d", rfStat.Size(), n)
+ }
+
+}
+
const metaDataTemplate = `instance-id: {{.ID}}
local-hostname: {{.Hostname}}`
diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go
index 689cd5f8e..4ac7de732 100644
--- a/tstest/integration/vms/vms_test.go
+++ b/tstest/integration/vms/vms_test.go
@@ -11,9 +11,11 @@ import (
"context"
"flag"
"fmt"
+ "io/ioutil"
"net"
"os"
"os/exec"
+ "path"
"path/filepath"
"regexp"
"strconv"
@@ -487,6 +489,8 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
}
})
+ t.Run("taildrop", func(t *testing.T) { testTaildrop(t, h, cli) })
+
t.Run("outgoing-udp-ipv4", func(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
@@ -617,6 +621,66 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
})
}
+func testTaildrop(t *testing.T, h *Harness, cli *ssh.Client) {
+ // local setup
+ src := t.TempDir()
+ dstDir := t.TempDir()
+ contents := []byte("Taildrop drop bop 💧 ??%@#@123˙©∆∆˚ 水平線")
+
+ filename := "taildrop.txt"
+ filePath := path.Join(src, filename)
+ if err := ioutil.WriteFile(filePath, contents, 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ ipBytes, err := getSession(t, cli).Output("tailscale ip -4")
+ if err != nil {
+ t.Fatalf("can't run `tailscale ip -4`: %v", err)
+ }
+ target := string(bytes.TrimSpace(ipBytes))
+
+ // check that targets contains the IP we're sending to
+ output := h.Tailscale(t, "file", "cp", "-targets")
+
+ if !bytes.Contains(output, []byte(target)) {
+ t.Errorf("Missing target from cp -targets, want: %s, in: %s", target, output)
+ }
+
+ h.Tailscale(t, "file", "cp", filePath, target+":")
+
+ out, err := getSession(t, cli).CombinedOutput(
+ fmt.Sprintf("tailscale file get -wait %s", dstDir),
+ )
+ if err != nil {
+ t.Fatal(string(out), err)
+ }
+
+ sftpDst, err := sftp.NewClient(cli)
+ if err != nil {
+ t.Fatalf("can't connect over sftp to copy file : %v", err)
+ }
+ defer sftpDst.Close()
+ copyFileFrom(t, sftpDst, path.Join(dstDir, filename), filename)
+
+ files, err := ioutil.ReadDir(dstDir)
+ if err != nil {
+ t.Error(err)
+ }
+ if len(files) != 1 {
+ t.Fatalf("want 1 file, got %d", len(files))
+ }
+
+ gotFile := files[0]
+ got, err := ioutil.ReadFile(path.Join(dstDir, gotFile.Name()))
+ if err != nil {
+ t.Errorf("Failed to read from cp'd file: %v", err)
+ }
+
+ if !bytes.Equal(got, contents) {
+ t.Errorf("mismatched taildrop contents, want %s, got %s", contents, got)
+ }
+}
+
func runTestCommands(t *testing.T, timeout time.Duration, cli *ssh.Client, batch []expect.Batcher) {
e, _, err := expect.SpawnSSH(cli, timeout,
expect.Verbose(true),