diff --git a/ipn/e2e_test.go b/ipn/e2e_test.go deleted file mode 100644 index a3b7acd9a..000000000 --- a/ipn/e2e_test.go +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (c) 2020 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 depends_on_currently_unreleased - -package ipn - -import ( - "bytes" - "context" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "net/url" - "os" - "strings" - "testing" - "time" - - "github.com/tailscale/wireguard-go/tun/tuntest" - "github.com/tailscale/wireguard-go/wgcfg" - "tailscale.com/control/controlclient" - "tailscale.com/tailcfg" - "tailscale.com/tstest" - "tailscale.com/types/logger" - "tailscale.com/wgengine" - "tailscale.com/wgengine/magicsock" - "tailscale.com/wgengine/router" - "tailscale.com/wgengine/tstun" - "tailscale.io/control" // not yet released -) - -func init() { - // Hacky way to signal to magicsock for now not to bind on the - // unspecified address. TODO(bradfitz): clean up wgengine's - // constructors. - os.Setenv("IN_TS_TEST", "1") -} - -func TestIPN(t *testing.T) { - tstest.PanicOnLog() - rc := tstest.NewResourceCheck() - defer rc.Assert(t) - - ctx, cancel := context.WithCancel(context.Background()) - - // This gets reassigned inside every test, so that the connections - // all log using the "current" t.Logf function. Sigh. - current_t := t - logf := func(s string, args ...interface{}) { - current_t.Helper() - current_t.Logf(s, args...) - } - - // Turn off STUN for the test to make it hermetic. - // TODO(crawshaw): add a test that runs against a local STUN server. - magicsock.DisableSTUNForTesting = true - defer func() { magicsock.DisableSTUNForTesting = false }() - - // TODO(apenwarr): Make resource checks actually pass. - // They don't right now, because (at least) wgengine doesn't fully - // shut down. - // rc := tstest.NewResourceCheck() - // defer rc.Assert(t) - - var ctl *control.Server - - ctlHandler := func(w http.ResponseWriter, r *http.Request) { - ctl.ServeHTTP(w, r) - } - https := httptest.NewServer(http.HandlerFunc(ctlHandler)) - https.Config.ErrorLog = logger.StdLogger(logf) - serverURL := https.URL - - tmpdir, err := ioutil.TempDir("", "ipntest") - if err != nil { - t.Fatalf("create tempdir: %v\n", err) - } - - ctl, err = control.New(tmpdir, tmpdir, tmpdir, serverURL, true, logf) - if err != nil { - t.Fatalf("create control server: %v\n", ctl) - } - defer ctl.Shutdown() - defer https.Close() - defer https.CloseClientConnections() - defer cancel() - - if _, err := ctl.DB().FindOrCreateUser("google", "test1@example.com", "", ""); err != nil { - t.Fatal(err) - } - - n1 := newNode(t, ctx, logf, "n1", https, false) - defer n1.Backend.Shutdown() - n1.Backend.StartLoginInteractive() - - n2 := newNode(t, ctx, logf, "n2", https, true) - defer n2.Backend.Shutdown() - n2.Backend.StartLoginInteractive() - - t.Run("login", func(t *testing.T) { - current_t = t - - var s1, s2 State - for { - logf("\n\nn1.state=%v n2.state=%v\n\n", s1, s2) - - // TODO(crawshaw): switch from || to &&. To do this we need to - // transmit some data so that the handshake completes on both - // sides. (Because handshakes are 1RTT, it is the data - // transmission that completes the handshake.) - if s1 == Running || s2 == Running { - // TODO(apenwarr): ensure state sequence. - // Right now we'll just exit as soon as - // state==Running, even if the backend is lying or - // something. Not a great test. - break - } - - select { - case n := <-n1.NotifyCh: - logf("n1n: %v\n", n) - if n.State != nil { - s1 = *n.State - if s1 == NeedsMachineAuth { - authNode(t, ctl, n1.Backend) - } - } - case n := <-n2.NotifyCh: - logf("n2n: %v\n", n) - if n.State != nil { - s2 = *n.State - if s2 == NeedsMachineAuth { - authNode(t, ctl, n2.Backend) - } - } - case <-time.After(5 * time.Second): - t.Fatalf("\n\n\nFATAL: timed out waiting for notifications.\n\n\n") - } - } - }) - current_t = t - - n1addr := n1.Backend.NetMap().Addresses[0].IP - n2addr := n2.Backend.NetMap().Addresses[0].IP - - t.Run("ping n2", func(t *testing.T) { - current_t = t - t.Skip("TODO(crawshaw): skipping ping test, it is flaky") - msg := tuntest.Ping(n2addr.IP(), n1addr.IP()) - n1.ChannelTUN.Outbound <- msg - select { - case msgRecv := <-n2.ChannelTUN.Inbound: - if !bytes.Equal(msg, msgRecv) { - t.Error("bad ping") - } - case <-time.After(1 * time.Second): - t.Error("no ping seen") - } - }) - current_t = t - - t.Run("ping n1", func(t *testing.T) { - current_t = t - t.Skip("TODO(crawshaw): skipping ping test, it is flaky") - msg := tuntest.Ping(n1addr.IP(), n2addr.IP()) - n2.ChannelTUN.Outbound <- msg - select { - case msgRecv := <-n1.ChannelTUN.Inbound: - if !bytes.Equal(msg, msgRecv) { - t.Error("bad ping") - } - case <-time.After(1 * time.Second): - t.Error("no ping seen") - } - }) - current_t = t - -drain: - for { - select { - case <-n1.NotifyCh: - case <-n2.NotifyCh: - default: - break drain - } - } - - n1.Backend.Logout() - - t.Run("logout", func(t *testing.T) { - current_t = t - - var s State - for { - select { - case n := <-n1.NotifyCh: - if n.State == nil { - continue - } - s = *n.State - logf("n.State=%v", s) - if s == NeedsLogin { - return - } - case <-time.After(3 * time.Second): - t.Fatalf("timeout waiting for logout State=NeedsLogin, got State=%v", s) - } - } - }) - current_t = t -} - -type testNode struct { - Backend *LocalBackend - ChannelTUN *tuntest.ChannelTUN - NotifyCh <-chan Notify -} - -// Create a new IPN node. -func newNode(t *testing.T, ctx context.Context, logfx logger.Logf, prefix string, https *httptest.Server, weirdPrefs bool) testNode { - t.Helper() - - logfe := logger.WithPrefix(logfx, prefix+"e: ") - logf := logger.WithPrefix(logfx, prefix+": ") - - var err error - httpc := https.Client() - httpc.Jar, err = cookiejar.New(nil) - if err != nil { - t.Fatal(err) - } - - tun := tuntest.NewChannelTUN() - tundev := tstun.WrapTUN(logfe, tun.TUN()) - e1, err := wgengine.NewUserspaceEngineAdvanced(logfe, tundev, router.NewFake, 0) - if err != nil { - t.Fatalf("NewFakeEngine: %v\n", err) - } - n, err := NewLocalBackend(logf, prefix, &MemoryStore{}, e1) - if err != nil { - t.Fatalf("NewLocalBackend: %v\n", err) - } - nch := make(chan Notify, 1000) - c := controlclient.Persist{ - Provider: "google", - LoginName: "test1@example.com", - } - prefs := NewPrefs() - prefs.ControlURL = https.URL - prefs.Persist = &c - - if weirdPrefs { - // Let's test some nonempty extra prefs fields to make sure - // the server can handle them. - prefs.AdvertiseTags = []string{"tag:abc"} - cidr, err := wgcfg.ParseCIDR("1.2.3.4/24") - if err != nil { - t.Fatalf("ParseCIDR: %v", err) - } - prefs.AdvertiseRoutes = []wgcfg.CIDR{cidr} - } - - n.Start(Options{ - HTTPTestClient: httpc, - FrontendLogID: prefix + "-f", - Prefs: prefs, - Notify: func(n Notify) { - // Automatically visit auth URLs - if n.BrowseToURL != nil { - logf("BrowseToURL: %v", *n.BrowseToURL) - - authURL := *n.BrowseToURL - i := strings.Index(authURL, "/a/") - if i == -1 { - panic("bad authURL: " + authURL) - } - authURL = authURL[:i] + "/login?refresh=true&next_url=" + url.PathEscape(authURL[i:]) - - form := url.Values{"user": []string{c.LoginName}} - req, err := http.NewRequest("POST", authURL, strings.NewReader(form.Encode())) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - if _, err := httpc.Do(req.WithContext(ctx)); err != nil { - logf("BrowseToURL: %v\n", err) - } - } - nch <- n - }, - }) - - return testNode{ - Backend: n, - ChannelTUN: tun, - NotifyCh: nch, - } -} - -// Tell the control server to authorize the given node. -func authNode(t *testing.T, ctl *control.Server, n *LocalBackend) { - mk := n.prefs.Persist.PrivateMachineKey.Public() - nk := n.prefs.Persist.PrivateNodeKey.Public() - ctl.AuthorizeMachine(tailcfg.MachineKey(mk), tailcfg.NodeKey(nk)) -} diff --git a/ipn/local.go b/ipn/local.go index e608fc601..7e9aee85d 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -1026,3 +1026,20 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) { } c.SetNetInfo(ni) } + +// TestOnlyPublicKeys returns the current machine and node public +// keys. Used in tests only to facilitate automated node authorization +// in the test harness. +func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, nodeKey tailcfg.NodeKey) { + b.mu.Lock() + prefs := b.prefs + b.mu.Unlock() + + if prefs == nil { + return + } + + mk := prefs.Persist.PrivateMachineKey.Public() + nk := prefs.Persist.PrivateNodeKey.Public() + return tailcfg.MachineKey(mk), tailcfg.NodeKey(nk) +}