Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions cmd/nebula-service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ func main() {
l := logging.NewLogger(os.Stdout)

if *serviceFlag != "" {
if err := doService(configPath, configTest, Build, serviceFlag); err != nil {
if *configTest {
fmt.Println("-test is not supported with -service, run the config test without -service")
os.Exit(1)
}

if err := doService(configPath, Build, serviceFlag); err != nil {
l.Error("Service command failed", "error", err)
os.Exit(1)
}
Expand Down Expand Up @@ -93,15 +98,14 @@ func main() {
}

if !*configTest {
wait, err := ctrl.Start()
if err != nil {
if err := ctrl.Start(); err != nil {
util.LogWithContextIfNeeded("Error while running", err, l)
os.Exit(1)
}

go ctrl.ShutdownBlock()

if err := wait(); err != nil {
if err := ctrl.Wait(); err != nil {
l.Error("Nebula stopped due to fatal error", "error", err)
os.Exit(2)
}
Expand Down
28 changes: 22 additions & 6 deletions cmd/nebula-service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"log"
"os"

"github.com/kardianos/service"
"github.com/slackhq/nebula"
Expand All @@ -14,7 +15,6 @@ var logger service.Logger

type program struct {
configPath *string
configTest *bool
build string
control *nebula.Control
}
Expand All @@ -40,22 +40,38 @@ func (p *program) Start(s service.Service) error {
}
})

p.control, err = nebula.Main(c, *p.configTest, Build, l, nil)
p.control, err = nebula.Main(c, false, Build, l, nil)
if err != nil {
return err
}

p.control.Start()
if err := p.control.Start(); err != nil {
return err
}

// Nebula can stop itself on a fatal packet reader error, make sure to log it if it happens.
go func() {
if err := p.control.Wait(); err != nil {
logger.Error(fmt.Sprintf("Nebula stopped due to fatal error: %v", err))
os.Exit(2)
}
}()

return nil
}

func (p *program) Stop(s service.Service) error {
logger.Info("Nebula service stopping.")
if p.control == nil {
return nil
}

p.control.Stop()
_ = p.control.Wait()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the error from Wait be returned here?

If not, maybe include a comment as to why not?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment. Basically this is kardianos api forcing our hand (Stop returns an error) but we want to know if nebula exits without having called Stop and that thing wants to log an error. If we log from both the catch-all and Stop then we get 2 log statements. Doing it this way lets us avoid knowing if we called Stop vs nebula fatal'd to change where the log output happens.

return nil
}

func doService(configPath *string, configTest *bool, build string, serviceFlag *string) error {
func doService(configPath *string, build string, serviceFlag *string) error {
if *configPath == "" {
p, err := config.DefaultPath()
if err != nil {
Expand All @@ -73,7 +89,6 @@ func doService(configPath *string, configTest *bool, build string, serviceFlag *

prg := &program{
configPath: configPath,
configTest: configTest,
build: build,
}

Expand Down Expand Up @@ -105,8 +120,9 @@ func doService(configPath *string, configTest *bool, build string, serviceFlag *
switch *serviceFlag {
case "run":
if err := s.Run(); err != nil {
// Route any errors to the system logger
// Route any errors to the system logger and report the failure
logger.Error(err)
return err
}
default:
if err := service.Control(s, *serviceFlag); err != nil {
Expand Down
5 changes: 2 additions & 3 deletions cmd/nebula/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,15 @@ func main() {
}

if !*configTest {
wait, err := ctrl.Start()
if err != nil {
if err := ctrl.Start(); err != nil {
util.LogWithContextIfNeeded("Error while running", err, l)
os.Exit(1)
}

go ctrl.ShutdownBlock()
notifyReady(l)

if err := wait(); err != nil {
if err := ctrl.Wait(); err != nil {
l.Error("Nebula stopped due to fatal error", "error", err)
os.Exit(2)
}
Expand Down
72 changes: 49 additions & 23 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,29 @@ type ControlHostInfo struct {
}

// Start actually runs nebula, this is a nonblocking call.
// The returned function blocks until nebula has fully stopped and returns the
// first fatal reader error (if any). A nil error means nebula shut down
// gracefully; a non-nil error means a reader hit an unexpected failure that
// triggered the shutdown.
func (c *Control) Start() (func() error, error) {
// Use Wait to block until nebula has fully stopped and to learn whether a fatal reader error caused the shutdown.
func (c *Control) Start() error {
c.stateLock.Lock()
defer c.stateLock.Unlock()
switch c.state {
case StateReady:
//yay!
case StateStopped, StateStopping:
return nil, ErrAlreadyStopped
return ErrAlreadyStopped
case StateStarted:
return nil, ErrAlreadyStarted
return ErrAlreadyStarted
default:
return nil, ErrUnknownState
return ErrUnknownState
}

// Activate the interface
err := c.f.activate()
if err != nil {
// Cancel before Close so a caller returning from Wait always observes a dead Context
c.cancel()
_ = c.f.Close()
c.state = StateStopped
return nil, err
return err
}

// Call all the delayed funcs that waited patiently for the interface to be created.
Expand All @@ -114,13 +114,9 @@ func (c *Control) Start() (func() error, error) {
c.f.triggerShutdown = c.Stop

// Start reading packets.
out, err := c.f.run()
if err != nil {
c.state = StateStopped
return nil, err
}
c.f.run()
c.state = StateStarted
return out, nil
return nil
}

func (c *Control) State() RunState {
Expand All @@ -133,10 +129,26 @@ func (c *Control) Context() context.Context {
return c.ctx
}

// Stop is a non-blocking call that signals nebula to close all tunnels and shut down
// Stop tears nebula down, closing all tunnels and releasing everything it holds.
// Use Wait to block until the shutdown has completed.
// A Control that has been stopped cannot be started again, Start will return ErrAlreadyStopped.
func (c *Control) Stop() {
c.stateLock.Lock()
if c.state != StateStarted {
switch c.state {
case StateStarted:
// Fall through to the full teardown below

case StateReady:
// Never started
c.cancel()
c.state = StateStopped
if err := c.f.Close(); err != nil {
c.l.Error("Close interface failed", "error", err)
}
c.stateLock.Unlock()
return

default:
c.stateLock.Unlock()
// We are stopping or stopped already
return
Expand All @@ -145,19 +157,26 @@ func (c *Control) Stop() {
c.state = StateStopping
c.stateLock.Unlock()

// Stop the handshakeManager (and other services), to prevent new tunnels from
// being created while we're shutting them all down.
// Closing tunnels can be slow with a large hostmap, don't hold the lock for it
c.cancel()

c.CloseAllTunnels(false)

c.stateLock.Lock()
c.state = StateStopped
if err := c.f.Close(); err != nil {
c.l.Error("Close interface failed", "error", err)
}
c.stateLock.Lock()
c.state = StateStopped
c.stateLock.Unlock()
}

// Wait blocks until nebula has fully stopped, either via Stop or an internal fatal error,
// and returns the first fatal packet reader error if there was one.
// It is safe to call from multiple goroutines and at any point in the lifecycle,
// but a Wait on a Control that is never started and never stopped will block forever.
func (c *Control) Wait() error {
return c.f.wait()
}

// ShutdownBlock will listen for and block on term and interrupt signals, calling Control.Stop() once signalled
func (c *Control) ShutdownBlock() {
sigChan := make(chan os.Signal, 1)
Expand All @@ -170,8 +189,15 @@ func (c *Control) ShutdownBlock() {
c.Stop()
}

// RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change
// RebindUDPServer asks the UDP listener to rebind it's listener. Mainly used on mobile clients when interfaces change.
func (c *Control) RebindUDPServer() {
c.stateLock.Lock()
defer c.stateLock.Unlock()

if c.state != StateStarted {
return
}

_ = c.f.outside.Rebind()

// Trigger a lighthouse update, useful for mobile clients that should have an update interval of 0
Expand Down
Loading