|
|
|
|
@ -1542,6 +1542,11 @@ func TestEngineReconfigOnStateChange(t *testing.T) {
|
|
|
|
|
tt.steps(t, lb, cc)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(bradfitz): this whole event bus settling thing
|
|
|
|
|
// should be unnecessary once the bogus uses of eventbus
|
|
|
|
|
// are removed. (https://github.com/tailscale/tailscale/issues/16369)
|
|
|
|
|
lb.settleEventBus()
|
|
|
|
|
|
|
|
|
|
if gotState := lb.State(); gotState != tt.wantState {
|
|
|
|
|
t.Errorf("State: got %v; want %v", gotState, tt.wantState)
|
|
|
|
|
}
|
|
|
|
|
@ -1572,35 +1577,30 @@ func TestEngineReconfigOnStateChange(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestStateMachineURLRace tests that wgengine updates arriving in the middle of
|
|
|
|
|
// TestSendPreservesAuthURL tests that wgengine updates arriving in the middle of
|
|
|
|
|
// processing an auth URL doesn't result in the auth URL being cleared.
|
|
|
|
|
func TestStateMachineURLRace(t *testing.T) {
|
|
|
|
|
runTestStateMachineURLRace(t, false)
|
|
|
|
|
func TestSendPreservesAuthURL(t *testing.T) {
|
|
|
|
|
runTestSendPreservesAuthURL(t, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStateMachineURLRaceSeamless(t *testing.T) {
|
|
|
|
|
runTestStateMachineURLRace(t, true)
|
|
|
|
|
func TestSendPreservesAuthURLSeamless(t *testing.T) {
|
|
|
|
|
runTestSendPreservesAuthURL(t, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runTestStateMachineURLRace(t *testing.T, seamless bool) {
|
|
|
|
|
func runTestSendPreservesAuthURL(t *testing.T, seamless bool) {
|
|
|
|
|
var cc *mockControl
|
|
|
|
|
b := newLocalBackendWithTestControl(t, true, func(tb testing.TB, opts controlclient.Options) controlclient.Client {
|
|
|
|
|
cc = newClient(t, opts)
|
|
|
|
|
return cc
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
nw := newNotificationWatcher(t, b, &ipnauth.TestActor{})
|
|
|
|
|
|
|
|
|
|
t.Logf("Start")
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.NeedsLogin)})
|
|
|
|
|
b.Start(ipn.Options{
|
|
|
|
|
UpdatePrefs: &ipn.Prefs{
|
|
|
|
|
WantRunning: true,
|
|
|
|
|
ControlURL: "https://localhost:1/",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
t.Logf("LoginFinished")
|
|
|
|
|
cc.persist.UserProfile.LoginName = "user1"
|
|
|
|
|
@ -1610,72 +1610,16 @@ func runTestStateMachineURLRace(t *testing.T, seamless bool) {
|
|
|
|
|
b.sys.ControlKnobs().SeamlessKeyRenewal.Store(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.Starting)})
|
|
|
|
|
cc.send(sendOpt{loginFinished: true, nm: &netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{MachineAuthorized: true}).View(),
|
|
|
|
|
}})
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
t.Logf("Running")
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.Running)})
|
|
|
|
|
b.setWgengineStatus(&wgengine.Status{AsOf: time.Now(), DERPs: 1}, nil)
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
t.Logf("Re-auth (StartLoginInteractive)")
|
|
|
|
|
b.StartLoginInteractive(t.Context())
|
|
|
|
|
|
|
|
|
|
stop := make(chan struct{})
|
|
|
|
|
stopSpamming := sync.OnceFunc(func() {
|
|
|
|
|
stop <- struct{}{}
|
|
|
|
|
})
|
|
|
|
|
// if seamless renewal is enabled, the engine won't be disabled, and we won't
|
|
|
|
|
// ever call stopSpamming, so make sure it does get called
|
|
|
|
|
defer stopSpamming()
|
|
|
|
|
|
|
|
|
|
// Intercept updates between the engine and localBackend, so that we can see
|
|
|
|
|
// when the "stopped" update comes in and ensure we stop sending our "we're
|
|
|
|
|
// up" updates after that point.
|
|
|
|
|
b.e.SetStatusCallback(func(s *wgengine.Status, err error) {
|
|
|
|
|
// This is not one of our fake status updates, this is generated from the
|
|
|
|
|
// engine in response to LocalBackend calling RequestStatus. Stop spamming
|
|
|
|
|
// our fake statuses.
|
|
|
|
|
//
|
|
|
|
|
// TODO(zofrex): This is fragile, it works right now but would break if the
|
|
|
|
|
// calling pattern of RequestStatus changes. We should ensure that we keep
|
|
|
|
|
// sending "we're up" statuses right until Reconfig is called with
|
|
|
|
|
// zero-valued configs, and after that point only send "stopped" statuses.
|
|
|
|
|
stopSpamming()
|
|
|
|
|
|
|
|
|
|
// Once stopSpamming returns we are guaranteed to not send any more updates,
|
|
|
|
|
// so we can now send the real update (indicating shutdown) and be certain
|
|
|
|
|
// it will be received after any fake updates we sent. This is possibly a
|
|
|
|
|
// stronger guarantee than we get from the real engine?
|
|
|
|
|
b.setWgengineStatus(s, err)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// time needs to be >= last time for the status to be accepted, send all our
|
|
|
|
|
// spam with the same stale time so that when a real update comes in it will
|
|
|
|
|
// definitely be accepted.
|
|
|
|
|
time := b.lastStatusTime
|
|
|
|
|
|
|
|
|
|
// Flood localBackend with a lot of wgengine status updates, so if there are
|
|
|
|
|
// any race conditions in the multiple locks/unlocks that happen as we process
|
|
|
|
|
// the received auth URL, we will hit them.
|
|
|
|
|
go func() {
|
|
|
|
|
t.Logf("sending lots of fake wgengine status updates")
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-stop:
|
|
|
|
|
t.Logf("stopping fake wgengine status updates")
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
b.setWgengineStatus(&wgengine.Status{AsOf: time, DERPs: 1}, nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
t.Logf("Re-auth (receive URL)")
|
|
|
|
|
url1 := "https://localhost:1/1"
|
|
|
|
|
cc.send(sendOpt{url: url1})
|
|
|
|
|
@ -1685,120 +1629,9 @@ func runTestStateMachineURLRace(t *testing.T, seamless bool) {
|
|
|
|
|
// status update to trample it have ended as well.
|
|
|
|
|
if b.authURL == "" {
|
|
|
|
|
t.Fatalf("expected authURL to be set")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestWGEngineDownThenUpRace(t *testing.T) {
|
|
|
|
|
var cc *mockControl
|
|
|
|
|
b := newLocalBackendWithTestControl(t, true, func(tb testing.TB, opts controlclient.Options) controlclient.Client {
|
|
|
|
|
cc = newClient(t, opts)
|
|
|
|
|
return cc
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
nw := newNotificationWatcher(t, b, &ipnauth.TestActor{})
|
|
|
|
|
|
|
|
|
|
t.Logf("Start")
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.NeedsLogin)})
|
|
|
|
|
b.Start(ipn.Options{
|
|
|
|
|
UpdatePrefs: &ipn.Prefs{
|
|
|
|
|
WantRunning: true,
|
|
|
|
|
ControlURL: "https://localhost:1/",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
t.Logf("LoginFinished")
|
|
|
|
|
cc.persist.UserProfile.LoginName = "user1"
|
|
|
|
|
cc.persist.NodeID = "node1"
|
|
|
|
|
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.Starting)})
|
|
|
|
|
cc.send(sendOpt{loginFinished: true, nm: &netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{MachineAuthorized: true}).View(),
|
|
|
|
|
}})
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
nw.watch(0, []wantedNotification{
|
|
|
|
|
wantStateNotify(ipn.Running)})
|
|
|
|
|
b.setWgengineStatus(&wgengine.Status{AsOf: time.Now(), DERPs: 1}, nil)
|
|
|
|
|
nw.check()
|
|
|
|
|
|
|
|
|
|
t.Logf("Re-auth (StartLoginInteractive)")
|
|
|
|
|
b.StartLoginInteractive(t.Context())
|
|
|
|
|
|
|
|
|
|
var timeLock sync.RWMutex
|
|
|
|
|
timestamp := b.lastStatusTime
|
|
|
|
|
|
|
|
|
|
engineShutdown := make(chan struct{})
|
|
|
|
|
gotShutdown := sync.OnceFunc(func() {
|
|
|
|
|
t.Logf("engineShutdown")
|
|
|
|
|
engineShutdown <- struct{}{}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
b.e.SetStatusCallback(func(s *wgengine.Status, err error) {
|
|
|
|
|
timeLock.Lock()
|
|
|
|
|
if s.AsOf.After(timestamp) {
|
|
|
|
|
timestamp = s.AsOf
|
|
|
|
|
}
|
|
|
|
|
timeLock.Unlock()
|
|
|
|
|
|
|
|
|
|
if err != nil || (s.DERPs == 0 && len(s.Peers) == 0) {
|
|
|
|
|
gotShutdown()
|
|
|
|
|
} else {
|
|
|
|
|
b.setWgengineStatus(s, err)
|
|
|
|
|
t.Log("authURL was set")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Logf("Re-auth (receive URL)")
|
|
|
|
|
url1 := "https://localhost:1/1"
|
|
|
|
|
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
|
|
wg.Go(func() {
|
|
|
|
|
t.Log("cc.send starting")
|
|
|
|
|
cc.send(sendOpt{url: url1}) // will block until engine stops
|
|
|
|
|
t.Log("cc.send returned")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
<-engineShutdown // will get called once cc.send is blocked
|
|
|
|
|
gotShutdown = sync.OnceFunc(func() {
|
|
|
|
|
t.Logf("engineShutdown")
|
|
|
|
|
engineShutdown <- struct{}{}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
wg.Go(func() {
|
|
|
|
|
t.Log("StartLoginInteractive starting")
|
|
|
|
|
b.StartLoginInteractive(t.Context()) // will also block until engine stops
|
|
|
|
|
t.Log("StartLoginInteractive returned")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
<-engineShutdown // will get called once StartLoginInteractive is blocked
|
|
|
|
|
|
|
|
|
|
st := controlclient.Status{}
|
|
|
|
|
st.SetStateForTest(controlclient.StateAuthenticated)
|
|
|
|
|
b.SetControlClientStatus(cc, st)
|
|
|
|
|
|
|
|
|
|
timeLock.RLock()
|
|
|
|
|
b.setWgengineStatus(&wgengine.Status{AsOf: timestamp}, nil) // engine is down event finally arrives
|
|
|
|
|
b.setWgengineStatus(&wgengine.Status{AsOf: timestamp, DERPs: 1}, nil) // engine is back up
|
|
|
|
|
timeLock.RUnlock()
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
wg.Wait()
|
|
|
|
|
done <- struct{}{}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
t.Log("waiting for .send and .StartLoginInteractive to return")
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-done:
|
|
|
|
|
case <-time.After(10 * time.Second):
|
|
|
|
|
t.Fatalf("timed out waiting")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.Log("both returned")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildNetmapWithPeers(self tailcfg.NodeView, peers ...tailcfg.NodeView) *netmap.NetworkMap {
|
|
|
|
|
@ -2033,6 +1866,14 @@ func (e *mockEngine) RequestStatus() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *mockEngine) ResetAndStop() (*wgengine.Status, error) {
|
|
|
|
|
err := e.Reconfig(&wgcfg.Config{}, &router.Config{}, &dns.Config{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &wgengine.Status{AsOf: time.Now()}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *mockEngine) PeerByKey(key.NodePublic) (_ wgint.Peer, ok bool) {
|
|
|
|
|
return wgint.Peer{}, false
|
|
|
|
|
}
|
|
|
|
|
|