瀏覽代碼

add listen.send_recv_error config option (#670)

By default, Nebula replies to packets it has no tunnel for with a `recv_error` packet. This packet helps speed up re-connection
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 with SIGHUP.
Wade Simmons 3 年之前
父節點
當前提交
7b9287709c
共有 5 個文件被更改,包括 83 次插入1 次删除
  1. 5 0
      config/config.go
  2. 6 0
      examples/config.yml
  3. 63 0
      interface.go
  4. 2 0
      main.go
  5. 7 1
      outside.go

+ 5 - 0
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.

+ 6 - 0
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.

+ 63 - 0
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()

+ 2 - 0
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)
 	}

+ 7 - 1
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)