diff --git a/config/config.go b/config/config.go index c51a78c..966e905 100644 --- a/config/config.go +++ b/config/config.go @@ -76,6 +76,11 @@ func (c *C) RegisterReloadCallback(f func(*C)) { c.callbacks = append(c.callbacks, f) } +// InitialLoad returns true if this is the first load of the config, and ReloadConfig has not been called yet. +func (c *C) InitialLoad() bool { + return c.oldSettings == nil +} + // HasChanged checks if the underlying structure of the provided key has changed after a config reload. The value of // k in both the old and new settings will be serialized, the result of the string comparison is returned. // If k is an empty string the entire config is tested. diff --git a/examples/config.yml b/examples/config.yml index de43161..24e1bcc 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -105,6 +105,12 @@ listen: # max, net.core.rmem_max and net.core.wmem_max #read_buffer: 10485760 #write_buffer: 10485760 + # By default, Nebula replies to packets it has no tunnel for with a "recv_error" packet. This packet helps speed up reconnection + # in the case that Nebula on either side did not shut down cleanly. This response can be abused as a way to discover if Nebula is running + # on a host though. This option lets you configure if you want to send "recv_error" packets always, never, or only to private network remotes. + # valid values: always, never, private + # This setting is reloadable. + #send_recv_error: always # EXPERIMENTAL: This option is currently only supported on linux and may # change in future minor releases. diff --git a/interface.go b/interface.go index 0aea744..a84eb7f 100644 --- a/interface.go +++ b/interface.go @@ -3,7 +3,9 @@ package nebula import ( "context" "errors" + "fmt" "io" + "net" "os" "runtime" "sync/atomic" @@ -68,6 +70,8 @@ type Interface struct { closed int32 relayManager *relayManager + sendRecvErrorConfig sendRecvErrorConfig + // rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse rebindCount int8 version string @@ -84,6 +88,40 @@ type Interface struct { l *logrus.Logger } +type sendRecvErrorConfig uint8 + +const ( + sendRecvErrorAlways sendRecvErrorConfig = iota + sendRecvErrorNever + sendRecvErrorPrivate +) + +func (s sendRecvErrorConfig) ShouldSendRecvError(ip net.IP) bool { + switch s { + case sendRecvErrorPrivate: + return ip.IsPrivate() + case sendRecvErrorAlways: + return true + case sendRecvErrorNever: + return false + default: + panic(fmt.Errorf("invalid sendRecvErrorConfig value: %d", s)) + } +} + +func (s sendRecvErrorConfig) String() string { + switch s { + case sendRecvErrorAlways: + return "always" + case sendRecvErrorNever: + return "never" + case sendRecvErrorPrivate: + return "private" + default: + return fmt.Sprintf("invalid(%d)", s) + } +} + func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) { if c.Outside == nil { return nil, errors.New("no outside connection") @@ -232,6 +270,7 @@ func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) { c.RegisterReloadCallback(f.reloadCA) c.RegisterReloadCallback(f.reloadCertKey) c.RegisterReloadCallback(f.reloadFirewall) + c.RegisterReloadCallback(f.reloadSendRecvError) for _, udpConn := range f.writers { c.RegisterReloadCallback(udpConn.ReloadConfig) } @@ -309,6 +348,30 @@ func (f *Interface) reloadFirewall(c *config.C) { Info("New firewall has been installed") } +func (f *Interface) reloadSendRecvError(c *config.C) { + if c.InitialLoad() || c.HasChanged("listen.send_recv_error") { + stringValue := c.GetString("listen.send_recv_error", "always") + + switch stringValue { + case "always": + f.sendRecvErrorConfig = sendRecvErrorAlways + case "never": + f.sendRecvErrorConfig = sendRecvErrorNever + case "private": + f.sendRecvErrorConfig = sendRecvErrorPrivate + default: + if c.GetBool("listen.send_recv_error", true) { + f.sendRecvErrorConfig = sendRecvErrorAlways + } else { + f.sendRecvErrorConfig = sendRecvErrorNever + } + } + + f.l.WithField("sendRecvError", f.sendRecvErrorConfig.String()). + Info("Loaded send_recv_error config") + } +} + func (f *Interface) emitStats(ctx context.Context, i time.Duration) { ticker := time.NewTicker(i) defer ticker.Stop() diff --git a/main.go b/main.go index ad66788..e189dce 100644 --- a/main.go +++ b/main.go @@ -306,6 +306,8 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg ifce.RegisterConfigChangeCallbacks(c) + ifce.reloadSendRecvError(c) + go handshakeManager.Run(ctx, ifce) go lightHouse.LhUpdateWorker(ctx, ifce) } diff --git a/outside.go b/outside.go index 479cc37..d26d9f0 100644 --- a/outside.go +++ b/outside.go @@ -273,7 +273,7 @@ func (f *Interface) handleEncrypted(ci *ConnectionState, addr *udp.Addr, h *head // Else, send recv errors for 300 seconds after a restart to allow fast reconnection. if ci == nil || !ci.window.Check(f.l, h.MessageCounter) { if addr != nil { - f.sendRecvError(addr, h.RemoteIndex) + f.maybeSendRecvError(addr, h.RemoteIndex) return false } else { return false @@ -402,6 +402,12 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out } } +func (f *Interface) maybeSendRecvError(endpoint *udp.Addr, index uint32) { + if f.sendRecvErrorConfig.ShouldSendRecvError(endpoint.IP) { + f.sendRecvError(endpoint, index) + } +} + func (f *Interface) sendRecvError(endpoint *udp.Addr, index uint32) { f.messageMetrics.Tx(header.RecvError, 0, 1)