diff --git a/tstest/integration/vms/udp_tester.go b/tstest/integration/vms/udp_tester.go new file mode 100644 index 000000000..cd04ba5fa --- /dev/null +++ b/tstest/integration/vms/udp_tester.go @@ -0,0 +1,80 @@ +// 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. + +// +build ignore + +// Command udp_tester exists because all of these distros being tested don't +// have a consistent tool for doing UDP traffic. This is a very hacked up tool +// that does that UDP traffic so these tests can be done. +package main + +import ( + "flag" + "fmt" + "io" + "log" + "net" + "os" +) + +var ( + client = flag.String("client", "", "host:port to connect to for sending UDP") + server = flag.String("server", "", "host:port to bind to for receiving UDP") +) + +func main() { + flag.Parse() + + if *client == "" && *server == "" { + log.Fatal("specify -client or -server") + } + + if *client != "" { + conn, err := net.Dial("udp", *client) + if err != nil { + log.Fatalf("can't dial %s: %v", *client, err) + } + log.Printf("dialed to %s", conn.RemoteAddr()) + defer conn.Close() + + buf := make([]byte, 1024) + n, err := os.Stdin.Read(buf) + if err != nil && err != io.EOF { + log.Fatalf("can't read from stdin: %v", err) + } + + nn, err := conn.Write(buf[:n]) + if err != nil { + log.Fatalf("can't write to %s: %v", conn.RemoteAddr(), err) + } + + if n != nn { + log.Fatalf("wanted to write %d bytes, wrote %d bytes", n, nn) + } + } + + if *server != "" { + addr, err := net.ResolveUDPAddr("udp", *server) + if err != nil { + log.Fatalf("can't resolve %s: %v", *server, err) + } + ln, err := net.ListenUDP("udp", addr) + if err != nil { + log.Fatalf("can't listen %s: %v", *server, err) + } + defer ln.Close() + + buf := make([]byte, 1024) + + n, _, err := ln.ReadFromUDP(buf) + if err != nil { + log.Fatal(err) + } + if n == 0 { + log.Fatal("got nothing") + } + + fmt.Print(string(buf[:n])) + } +} diff --git a/tstest/integration/vms/vms_test.go b/tstest/integration/vms/vms_test.go index 911222418..385ffb669 100644 --- a/tstest/integration/vms/vms_test.go +++ b/tstest/integration/vms/vms_test.go @@ -859,6 +859,122 @@ func (h Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) { t.Fatalf("wanted reported ip to be %q, got: %q", string(ipBytes), string(testIPBytes)) } }) + + t.Run("outgoing-udp-ipv4", func(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("can't get working directory: %v", err) + } + dir := t.TempDir() + run(t, cwd, "go", "build", "-o", filepath.Join(dir, "udp_tester"), "./udp_tester.go") + + sftpCli, err := sftp.NewClient(cli) + if err != nil { + t.Fatalf("can't connect over sftp to copy binaries: %v", err) + } + defer sftpCli.Close() + + copyFile(t, sftpCli, filepath.Join(dir, "udp_tester"), "/udp_tester") + + uaddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("::", "0")) + if err != nil { + t.Fatalf("can't resolve udp listener addr: %v", err) + } + + buf := make([]byte, 1024) + + ln, err := net.ListenUDP("udp", uaddr) + if err != nil { + t.Fatalf("can't listen for UDP traffic: %v", err) + } + defer ln.Close() + + sess, err := cli.NewSession() + if err != nil { + t.Fatalf("can't open session: %v", err) + } + defer sess.Close() + + sess.Stdin = strings.NewReader("hi") + sess.Stdout = logger.FuncWriter(t.Logf) + sess.Stderr = logger.FuncWriter(t.Logf) + + _, port, _ := net.SplitHostPort(ln.LocalAddr().String()) + + go func() { + cmd := fmt.Sprintf("/udp_tester -client %s\n", net.JoinHostPort("100.64.0.1", port)) + time.Sleep(10 * time.Millisecond) + t.Logf("sending packet: %s", cmd) + err := sess.Run(cmd) + if err != nil { + t.Errorf("can't send UDP packet: %v", err) + } + }() + + t.Log("listening for packet") + n, _, err := ln.ReadFromUDP(buf) + if err != nil { + t.Fatal(err) + } + + if n == 0 { + t.Fatal("got nothing") + } + + if !bytes.Contains(buf, []byte("hi")) { + t.Fatal("did not get UDP message") + } + }) + + t.Run("incoming-udp-ipv4", func(t *testing.T) { + // vms_test.go:947: can't dial: socks connect udp 127.0.0.1:36497->100.64.0.2:33409: network not implemented + t.Skip("can't make outgoing sockets over UDP with our socks server") + + sess, err := cli.NewSession() + if err != nil { + t.Fatalf("can't open session: %v", err) + } + defer sess.Close() + + ip, err := sess.Output("tailscale ip -4") + if err != nil { + t.Fatalf("can't nab ipv4 address: %v", err) + } + + port, err := getProbablyFreePortNumber() + if err != nil { + t.Fatalf("unable to fetch port number: %v", err) + } + + go func() { + time.Sleep(10 * time.Millisecond) + + conn, err := h.testerDialer.Dial("udp", net.JoinHostPort(string(bytes.TrimSpace(ip)), strconv.Itoa(port))) + if err != nil { + t.Errorf("can't dial: %v", err) + } + + fmt.Fprint(conn, securePassword) + }() + + sess, err = cli.NewSession() + if err != nil { + t.Fatalf("can't open session: %v", err) + } + defer sess.Close() + sess.Stderr = logger.FuncWriter(t.Logf) + + msg, err := sess.Output( + fmt.Sprintf( + "/udp_tester -server %s", + net.JoinHostPort(string(bytes.TrimSpace(ip)), strconv.Itoa(port)), + ), + ) + + if msg := string(bytes.TrimSpace(msg)); msg != securePassword { + t.Fatalf("wanted %q from vm, got: %q", securePassword, msg) + } + }) } func runTestCommands(t *testing.T, timeout time.Duration, cli *ssh.Client, batch []expect.Batcher) {