@@ -69,29 +69,29 @@ type ControlHostInfo struct {
6969}
7070
7171// Start actually runs nebula, this is a nonblocking call.
72- // The returned function blocks until nebula has fully stopped and returns the
73- // first fatal reader error (if any). A nil error means nebula shut down
74- // gracefully; a non-nil error means a reader hit an unexpected failure that
75- // triggered the shutdown.
76- func (c * Control ) Start () (func () error , error ) {
72+ // Use Wait to block until nebula has fully stopped and to learn whether a fatal reader error caused the shutdown.
73+ func (c * Control ) Start () error {
7774 c .stateLock .Lock ()
7875 defer c .stateLock .Unlock ()
7976 switch c .state {
8077 case StateReady :
8178 //yay!
8279 case StateStopped , StateStopping :
83- return nil , ErrAlreadyStopped
80+ return ErrAlreadyStopped
8481 case StateStarted :
85- return nil , ErrAlreadyStarted
82+ return ErrAlreadyStarted
8683 default :
87- return nil , ErrUnknownState
84+ return ErrUnknownState
8885 }
8986
9087 // Activate the interface
9188 err := c .f .activate ()
9289 if err != nil {
90+ // Cancel before Close so a caller returning from Wait always observes a dead Context
91+ c .cancel ()
92+ _ = c .f .Close ()
9393 c .state = StateStopped
94- return nil , err
94+ return err
9595 }
9696
9797 // Call all the delayed funcs that waited patiently for the interface to be created.
@@ -114,13 +114,9 @@ func (c *Control) Start() (func() error, error) {
114114 c .f .triggerShutdown = c .Stop
115115
116116 // Start reading packets.
117- out , err := c .f .run ()
118- if err != nil {
119- c .state = StateStopped
120- return nil , err
121- }
117+ c .f .run ()
122118 c .state = StateStarted
123- return out , nil
119+ return nil
124120}
125121
126122func (c * Control ) State () RunState {
@@ -133,10 +129,26 @@ func (c *Control) Context() context.Context {
133129 return c .ctx
134130}
135131
136- // Stop is a non-blocking call that signals nebula to close all tunnels and shut down
132+ // Stop tears nebula down, closing all tunnels and releasing everything it holds.
133+ // Use Wait to block until the shutdown has completed.
134+ // A Control that has been stopped cannot be started again, Start will return ErrAlreadyStopped.
137135func (c * Control ) Stop () {
138136 c .stateLock .Lock ()
139- if c .state != StateStarted {
137+ switch c .state {
138+ case StateStarted :
139+ // Fall through to the full teardown below
140+
141+ case StateReady :
142+ // Never started
143+ c .cancel ()
144+ c .state = StateStopped
145+ if err := c .f .Close (); err != nil {
146+ c .l .Error ("Close interface failed" , "error" , err )
147+ }
148+ c .stateLock .Unlock ()
149+ return
150+
151+ default :
140152 c .stateLock .Unlock ()
141153 // We are stopping or stopped already
142154 return
@@ -145,19 +157,26 @@ func (c *Control) Stop() {
145157 c .state = StateStopping
146158 c .stateLock .Unlock ()
147159
148- // Stop the handshakeManager (and other services), to prevent new tunnels from
149- // being created while we're shutting them all down.
160+ // Closing tunnels can be slow with a large hostmap, don't hold the lock for it
150161 c .cancel ()
151-
152162 c .CloseAllTunnels (false )
163+
164+ c .stateLock .Lock ()
165+ c .state = StateStopped
153166 if err := c .f .Close (); err != nil {
154167 c .l .Error ("Close interface failed" , "error" , err )
155168 }
156- c .stateLock .Lock ()
157- c .state = StateStopped
158169 c .stateLock .Unlock ()
159170}
160171
172+ // Wait blocks until nebula has fully stopped, either via Stop or an internal fatal error,
173+ // and returns the first fatal packet reader error if there was one.
174+ // It is safe to call from multiple goroutines and at any point in the lifecycle,
175+ // but a Wait on a Control that is never started and never stopped will block forever.
176+ func (c * Control ) Wait () error {
177+ return c .f .wait ()
178+ }
179+
161180// ShutdownBlock will listen for and block on term and interrupt signals, calling Control.Stop() once signalled
162181func (c * Control ) ShutdownBlock () {
163182 sigChan := make (chan os.Signal , 1 )
@@ -170,8 +189,15 @@ func (c *Control) ShutdownBlock() {
170189 c .Stop ()
171190}
172191
173- // RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change
192+ // RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change.
174193func (c * Control ) RebindUDPServer () {
194+ c .stateLock .Lock ()
195+ defer c .stateLock .Unlock ()
196+
197+ if c .state != StateStarted {
198+ return
199+ }
200+
175201 _ = c .f .outside .Rebind ()
176202
177203 // Trigger a lighthouse update, useful for mobile clients that should have an update interval of 0
0 commit comments