mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-16 18:08:40 +00:00
tstest/integration: add tests for tun mode (requiring root)
Updates #7894 Change-Id: Iff0b07b21ae28c712dd665b12918fa28d6f601d0 Co-authored-by: Maisem Ali <maisem@tailscale.com> Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
a6270826a3
commit
c363b9055d
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -90,6 +90,8 @@ jobs:
|
|||||||
sudo apt-get -y install qemu-user
|
sudo apt-get -y install qemu-user
|
||||||
- name: build test wrapper
|
- name: build test wrapper
|
||||||
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
|
||||||
|
- name: integration tests as root
|
||||||
|
run: PATH=$PWD/tool:$PATH /tmp/testwrapper --sudo ./tstest/integration/ ${{matrix.buildflags}}
|
||||||
- name: test all
|
- name: test all
|
||||||
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}}
|
run: PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}}
|
||||||
env:
|
env:
|
||||||
|
@ -73,11 +73,15 @@ var debug = os.Getenv("TS_TESTWRAPPER_DEBUG") != ""
|
|||||||
// It calls close(ch) when it's done.
|
// It calls close(ch) when it's done.
|
||||||
func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []string, ch chan<- *testAttempt) error {
|
func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []string, ch chan<- *testAttempt) error {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
args := []string{"test", "-json", pt.Pattern}
|
args := []string{"test", "--json"}
|
||||||
|
if *flagSudo {
|
||||||
|
args = append(args, "--exec", "sudo -E")
|
||||||
|
}
|
||||||
|
args = append(args, pt.Pattern)
|
||||||
args = append(args, otherArgs...)
|
args = append(args, otherArgs...)
|
||||||
if len(pt.Tests) > 0 {
|
if len(pt.Tests) > 0 {
|
||||||
runArg := strings.Join(pt.Tests, "|")
|
runArg := strings.Join(pt.Tests, "|")
|
||||||
args = append(args, "-run", runArg)
|
args = append(args, "--run", runArg)
|
||||||
}
|
}
|
||||||
if debug {
|
if debug {
|
||||||
fmt.Println("running", strings.Join(args, " "))
|
fmt.Println("running", strings.Join(args, " "))
|
||||||
@ -177,6 +181,11 @@ func runTests(ctx context.Context, attempt int, pt *packageTests, otherArgs []st
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagVerbose = flag.Bool("v", false, "verbose")
|
||||||
|
flagSudo = flag.Bool("sudo", false, "run tests with -exec=sudo")
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@ -187,7 +196,6 @@ func main() {
|
|||||||
// We run `go test -json` which returns the same information as `go test -v`,
|
// We run `go test -json` which returns the same information as `go test -v`,
|
||||||
// but in a machine-readable format. So this flag is only for testwrapper's
|
// but in a machine-readable format. So this flag is only for testwrapper's
|
||||||
// output.
|
// output.
|
||||||
v := flag.Bool("v", false, "verbose")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Println("usage: testwrapper [testwrapper-flags] [pattern] [build/test flags & test binary flags]")
|
fmt.Println("usage: testwrapper [testwrapper-flags] [pattern] [build/test flags & test binary flags]")
|
||||||
@ -285,7 +293,7 @@ func main() {
|
|||||||
printPkgOutcome(tr.pkg, tr.outcome, thisRun.attempt)
|
printPkgOutcome(tr.pkg, tr.outcome, thisRun.attempt)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if *v || tr.outcome == "fail" {
|
if *flagVerbose || tr.outcome == "fail" {
|
||||||
io.Copy(os.Stdout, &tr.logs)
|
io.Copy(os.Stdout, &tr.logs)
|
||||||
}
|
}
|
||||||
if tr.outcome != "fail" {
|
if tr.outcome != "fail" {
|
||||||
|
@ -142,6 +142,16 @@ func findGo() (string, error) {
|
|||||||
// 2. Look for a go binary in runtime.GOROOT()/bin if runtime.GOROOT() is non-empty.
|
// 2. Look for a go binary in runtime.GOROOT()/bin if runtime.GOROOT() is non-empty.
|
||||||
// 3. Look for a go binary in $PATH.
|
// 3. Look for a go binary in $PATH.
|
||||||
|
|
||||||
|
// For tests we want to run as root on GitHub actions, we run with -exec=sudo,
|
||||||
|
// but that results in this test running with a different PATH and picking the
|
||||||
|
// wrong Go. So hard code the GitHub Actions case.
|
||||||
|
if os.Getuid() == 0 && os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||||
|
const sudoGithubGo = "/home/runner/.cache/tailscale-go/bin/go"
|
||||||
|
if _, err := os.Stat(sudoGithubGo); err == nil {
|
||||||
|
return sudoGithubGo, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
paths := strings.FieldsFunc(os.Getenv("PATH"), func(r rune) bool { return os.IsPathSeparator(uint8(r)) })
|
paths := strings.FieldsFunc(os.Getenv("PATH"), func(r rune) bool { return os.IsPathSeparator(uint8(r)) })
|
||||||
if len(paths) > 0 {
|
if len(paths) > 0 {
|
||||||
candidate := filepath.Join(paths[0], "go"+exe())
|
candidate := filepath.Join(paths[0], "go"+exe())
|
||||||
|
@ -68,6 +68,27 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that tailscaled starts up in TUN mode, and also without data races:
|
||||||
|
// https://github.com/tailscale/tailscale/issues/7894
|
||||||
|
func TestTUNMode(t *testing.T) {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
t.Skip("skipping when not root")
|
||||||
|
}
|
||||||
|
t.Parallel()
|
||||||
|
env := newTestEnv(t)
|
||||||
|
env.tunMode = true
|
||||||
|
n1 := newTestNode(t, env)
|
||||||
|
d1 := n1.StartDaemon()
|
||||||
|
|
||||||
|
n1.AwaitResponding()
|
||||||
|
n1.MustUp()
|
||||||
|
|
||||||
|
t.Logf("Got IP: %v", n1.AwaitIP4())
|
||||||
|
n1.AwaitRunning()
|
||||||
|
|
||||||
|
d1.MustCleanShutdown(t)
|
||||||
|
}
|
||||||
|
|
||||||
func TestOneNodeUpNoAuth(t *testing.T) {
|
func TestOneNodeUpNoAuth(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
env := newTestEnv(t)
|
env := newTestEnv(t)
|
||||||
@ -808,9 +829,10 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
|
|||||||
// testEnv contains the test environment (set of servers) used by one
|
// testEnv contains the test environment (set of servers) used by one
|
||||||
// or more nodes.
|
// or more nodes.
|
||||||
type testEnv struct {
|
type testEnv struct {
|
||||||
t testing.TB
|
t testing.TB
|
||||||
cli string
|
tunMode bool
|
||||||
daemon string
|
cli string
|
||||||
|
daemon string
|
||||||
|
|
||||||
LogCatcher *LogCatcher
|
LogCatcher *LogCatcher
|
||||||
LogCatcherServer *httptest.Server
|
LogCatcherServer *httptest.Server
|
||||||
@ -899,12 +921,25 @@ func newTestNode(t *testing.T, env *testEnv) *testNode {
|
|||||||
sockFile = filepath.Join(os.TempDir(), rands.HexString(8)+".sock")
|
sockFile = filepath.Join(os.TempDir(), rands.HexString(8)+".sock")
|
||||||
t.Cleanup(func() { os.Remove(sockFile) })
|
t.Cleanup(func() { os.Remove(sockFile) })
|
||||||
}
|
}
|
||||||
return &testNode{
|
n := &testNode{
|
||||||
env: env,
|
env: env,
|
||||||
dir: dir,
|
dir: dir,
|
||||||
sockFile: sockFile,
|
sockFile: sockFile,
|
||||||
stateFile: filepath.Join(dir, "tailscale.state"),
|
stateFile: filepath.Join(dir, "tailscale.state"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look for a data race. Once we see the start marker, start logging the rest.
|
||||||
|
var sawRace bool
|
||||||
|
n.addLogLineHook(func(line []byte) {
|
||||||
|
if mem.Contains(mem.B(line), mem.S("WARNING: DATA RACE")) {
|
||||||
|
sawRace = true
|
||||||
|
}
|
||||||
|
if sawRace {
|
||||||
|
t.Logf("%s", line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *testNode) diskPrefs() *ipn.Prefs {
|
func (n *testNode) diskPrefs() *ipn.Prefs {
|
||||||
@ -963,7 +998,7 @@ func (n *testNode) socks5AddrChan() <-chan string {
|
|||||||
if i == -1 {
|
if i == -1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addr := string(line)[i+len(sub):]
|
addr := strings.TrimSpace(string(line)[i+len(sub):])
|
||||||
select {
|
select {
|
||||||
case ch <- addr:
|
case ch <- addr:
|
||||||
default:
|
default:
|
||||||
@ -1010,11 +1045,10 @@ func (op *nodeOutputParser) parseLines() {
|
|||||||
}
|
}
|
||||||
line := buf[:nl+1]
|
line := buf[:nl+1]
|
||||||
buf = buf[nl+1:]
|
buf = buf[nl+1:]
|
||||||
lineTrim := bytes.TrimSpace(line)
|
|
||||||
|
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
for _, f := range n.onLogLine {
|
for _, f := range n.onLogLine {
|
||||||
f(lineTrim)
|
f(line)
|
||||||
}
|
}
|
||||||
n.mu.Unlock()
|
n.mu.Unlock()
|
||||||
}
|
}
|
||||||
@ -1048,8 +1082,8 @@ func (n *testNode) StartDaemon() *Daemon {
|
|||||||
|
|
||||||
func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
||||||
t := n.env.t
|
t := n.env.t
|
||||||
cmd := exec.Command(n.env.daemon,
|
cmd := exec.Command(n.env.daemon)
|
||||||
"--tun=userspace-networking",
|
cmd.Args = append(cmd.Args,
|
||||||
"--state="+n.stateFile,
|
"--state="+n.stateFile,
|
||||||
"--socket="+n.sockFile,
|
"--socket="+n.sockFile,
|
||||||
"--socks5-server=localhost:0",
|
"--socks5-server=localhost:0",
|
||||||
@ -1057,6 +1091,11 @@ func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
|||||||
if *verboseTailscaled {
|
if *verboseTailscaled {
|
||||||
cmd.Args = append(cmd.Args, "-verbose=2")
|
cmd.Args = append(cmd.Args, "-verbose=2")
|
||||||
}
|
}
|
||||||
|
if !n.env.tunMode {
|
||||||
|
cmd.Args = append(cmd.Args,
|
||||||
|
"--tun=userspace-networking",
|
||||||
|
)
|
||||||
|
}
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
"TS_DEBUG_PERMIT_HTTP_C2N=1",
|
"TS_DEBUG_PERMIT_HTTP_C2N=1",
|
||||||
"TS_LOG_TARGET="+n.env.LogCatcherServer.URL,
|
"TS_LOG_TARGET="+n.env.LogCatcherServer.URL,
|
||||||
@ -1067,10 +1106,7 @@ func (n *testNode) StartDaemonAsIPNGOOS(ipnGOOS string) *Daemon {
|
|||||||
"TS_NETCHECK_GENERATE_204_URL="+n.env.ControlServer.URL+"/generate_204",
|
"TS_NETCHECK_GENERATE_204_URL="+n.env.ControlServer.URL+"/generate_204",
|
||||||
)
|
)
|
||||||
if version.IsRace() {
|
if version.IsRace() {
|
||||||
const knownBroken = true // TODO(bradfitz,maisem): enable this once we fix all the races :(
|
cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1")
|
||||||
if !knownBroken {
|
|
||||||
cmd.Env = append(cmd.Env, "GORACE=halt_on_error=1")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cmd.Stderr = &nodeOutputParser{n: n}
|
cmd.Stderr = &nodeOutputParser{n: n}
|
||||||
if *verboseTailscaled {
|
if *verboseTailscaled {
|
||||||
@ -1143,11 +1179,10 @@ func (n *testNode) AwaitListening() {
|
|||||||
s := safesocket.DefaultConnectionStrategy(n.sockFile)
|
s := safesocket.DefaultConnectionStrategy(n.sockFile)
|
||||||
if err := tstest.WaitFor(20*time.Second, func() (err error) {
|
if err := tstest.WaitFor(20*time.Second, func() (err error) {
|
||||||
c, err := safesocket.Connect(s)
|
c, err := safesocket.Connect(s)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return err
|
c.Close()
|
||||||
}
|
}
|
||||||
c.Close()
|
return err
|
||||||
return nil
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -1241,7 +1276,8 @@ func (n *testNode) AwaitNeedsLogin() {
|
|||||||
// Tailscale returns a command that runs the tailscale CLI with the provided arguments.
|
// Tailscale returns a command that runs the tailscale CLI with the provided arguments.
|
||||||
// It does not start the process.
|
// It does not start the process.
|
||||||
func (n *testNode) Tailscale(arg ...string) *exec.Cmd {
|
func (n *testNode) Tailscale(arg ...string) *exec.Cmd {
|
||||||
cmd := exec.Command(n.env.cli, "--socket="+n.sockFile)
|
cmd := exec.Command(n.env.cli)
|
||||||
|
cmd.Args = append(cmd.Args, "--socket="+n.sockFile)
|
||||||
cmd.Args = append(cmd.Args, arg...)
|
cmd.Args = append(cmd.Args, arg...)
|
||||||
cmd.Dir = n.dir
|
cmd.Dir = n.dir
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
|
@ -484,9 +484,9 @@ func (p *pingResultAndCallback) reply() bool {
|
|||||||
return p != nil && p.taken.CompareAndSwap(false, true)
|
return p != nil && p.taken.CompareAndSwap(false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoPing starts a disc-level ping for the "tailscale ping" command (or other
|
// discoPing starts a disco-level ping for the "tailscale ping" command (or other
|
||||||
// callers, such as c2n). res is value to call cb with, already partially
|
// callers, such as c2n). res is value to call cb with, already partially
|
||||||
// filled. cb must be called at most once. Once called, ownership of res passes to db.
|
// filled. cb must be called at most once. Once called, ownership of res passes to cb.
|
||||||
func (de *endpoint) discoPing(res *ipnstate.PingResult, size int, cb func(*ipnstate.PingResult)) {
|
func (de *endpoint) discoPing(res *ipnstate.PingResult, size int, cb func(*ipnstate.PingResult)) {
|
||||||
de.mu.Lock()
|
de.mu.Lock()
|
||||||
defer de.mu.Unlock()
|
defer de.mu.Unlock()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user