mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 17:47:02 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 334dd7a85d | |||
| c9d5a6e35a | |||
| 8fd724d762 | |||
| 6e23fe4d46 | |||
| 90f2938f9c | |||
| f76ac2e216 | |||
| 382b15ac52 | |||
| 4104a48a86 | |||
| 35212c21b9 | |||
| 370a7f50af | |||
| 50d6632845 | |||
| 78af44068f | |||
| ad6b918e4d | |||
| bf4e37e99d | |||
| d0825514a0 | |||
| 6ee5e18d84 |
@@ -82,7 +82,7 @@ docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host
|
|||||||
|
|
||||||
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host4 ncat -e '/usr/bin/echo helloagainfromhost4' -nkluv 0.0.0.0 4000 &
|
docker exec host4 ncat -nkluv 0.0.0.0 4000 &
|
||||||
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
||||||
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
||||||
|
|
||||||
@@ -155,11 +155,11 @@ echo " *** Testing conntrack"
|
|||||||
echo
|
echo
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
# host4's outbound firewall only allows ICMP to the lighthouse, so host4
|
# host2 speaking to host4 on UDP 4000 should allow it to reply, when firewall rules would normally not permit this
|
||||||
# cannot initiate UDP to host2. Once host2 initiates a flow to host4:4000,
|
docker exec host2 sh -c "/usr/bin/echo host2 | ncat -nuv 192.168.100.4 4000"
|
||||||
# conntrack must let host4's listener reply on that flow. If it doesn't,
|
docker exec host2 ncat -e '/usr/bin/echo helloagainfromhost2' -nkluv 0.0.0.0 4000 &
|
||||||
# the echo back from host4 never reaches host2.
|
sleep 1
|
||||||
docker exec host2 sh -c "(/usr/bin/echo host2; sleep 2) | ncat -nuv 192.168.100.4 4000" | grep -q helloagainfromhost4
|
docker exec host4 sh -c "/usr/bin/echo host4 | ncat -nuv 192.168.100.2 4000"
|
||||||
|
|
||||||
docker exec host4 sh -c 'kill 1'
|
docker exec host4 sh -c 'kill 1'
|
||||||
docker exec host3 sh -c 'kill 1'
|
docker exec host3 sh -c 'kill 1'
|
||||||
|
|||||||
@@ -2,21 +2,7 @@ version: "2"
|
|||||||
linters:
|
linters:
|
||||||
default: none
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- sloglint
|
|
||||||
- testifylint
|
- testifylint
|
||||||
settings:
|
|
||||||
sloglint:
|
|
||||||
# Enforce key-value pair form for Info/Debug/Warn/Error/Log/With and
|
|
||||||
# the package-level slog equivalents. Use l.Log(ctx, level, ...) for
|
|
||||||
# custom levels instead of LogAttrs when you can.
|
|
||||||
#
|
|
||||||
# LogAttrs is also flagged by this rule because it takes ...slog.Attr;
|
|
||||||
# the few legitimate sites (where attrs is built up as a []slog.Attr)
|
|
||||||
# carry a //nolint:sloglint with rationale.
|
|
||||||
kv-only: true
|
|
||||||
# no-mixed-args is on by default: forbids mixing kv and attrs in one call.
|
|
||||||
# discard-handler is on by default (since Go 1.24): suggests
|
|
||||||
# slog.DiscardHandler over slog.NewTextHandler(io.Discard, nil).
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
presets:
|
presets:
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import "net/netip"
|
||||||
|
|
||||||
|
// sendBatchCap is the maximum number of encrypted packets accumulated before a
|
||||||
|
// flush is forced. TSO superpackets segment to at most ~45 packets on
|
||||||
|
// reasonable MTUs, so 128 leaves headroom without bloating the backing
|
||||||
|
// allocation.
|
||||||
|
const sendBatchCap = 128
|
||||||
|
|
||||||
|
// sendBatch accumulates encrypted UDP packets for a single sendmmsg flush.
|
||||||
|
// One sendBatch is owned by each listenIn goroutine; no locking is needed.
|
||||||
|
// The backing storage holds up to batchCap packets of slotCap bytes each;
|
||||||
|
// bufs and dsts are parallel slices of committed slots.
|
||||||
|
type sendBatch struct {
|
||||||
|
bufs [][]byte
|
||||||
|
dsts []netip.AddrPort
|
||||||
|
backing []byte
|
||||||
|
slotCap int
|
||||||
|
batchCap int
|
||||||
|
nextSlot int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSendBatch(batchCap, slotCap int) *sendBatch {
|
||||||
|
return &sendBatch{
|
||||||
|
bufs: make([][]byte, 0, batchCap),
|
||||||
|
dsts: make([]netip.AddrPort, 0, batchCap),
|
||||||
|
backing: make([]byte, batchCap*slotCap),
|
||||||
|
slotCap: slotCap,
|
||||||
|
batchCap: batchCap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns a zero-length slice with slotCap capacity over the next unused
|
||||||
|
// slot's backing bytes. The caller writes into the returned slice and then
|
||||||
|
// calls Commit with the final length and destination. Next returns nil when
|
||||||
|
// the batch is full.
|
||||||
|
func (b *sendBatch) Next() []byte {
|
||||||
|
if b.nextSlot >= b.batchCap {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := b.nextSlot * b.slotCap
|
||||||
|
return b.backing[start : start : start+b.slotCap]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit records the slot just returned by Next as a packet of length n
|
||||||
|
// destined for dst.
|
||||||
|
func (b *sendBatch) Commit(n int, dst netip.AddrPort) {
|
||||||
|
start := b.nextSlot * b.slotCap
|
||||||
|
b.bufs = append(b.bufs, b.backing[start:start+n])
|
||||||
|
b.dsts = append(b.dsts, dst)
|
||||||
|
b.nextSlot++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears committed slots; backing storage is retained for reuse.
|
||||||
|
func (b *sendBatch) Reset() {
|
||||||
|
b.bufs = b.bufs[:0]
|
||||||
|
b.dsts = b.dsts[:0]
|
||||||
|
b.nextSlot = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of committed packets.
|
||||||
|
func (b *sendBatch) Len() int {
|
||||||
|
return len(b.bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap returns the maximum number of slots in the batch.
|
||||||
|
func (b *sendBatch) Cap() int {
|
||||||
|
return b.batchCap
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package batch
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestSendBatchBookkeeping(t *testing.T) {
|
func TestSendBatchBookkeeping(t *testing.T) {
|
||||||
b := NewSendBatch(4, 32)
|
b := newSendBatch(4, 32)
|
||||||
if b.Len() != 0 || b.Cap() != 4 {
|
if b.Len() != 0 || b.Cap() != 4 {
|
||||||
t.Fatalf("fresh batch: len=%d cap=%d", b.Len(), b.Cap())
|
t.Fatalf("fresh batch: len=%d cap=%d", b.Len(), b.Cap())
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func TestSendBatchBookkeeping(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSendBatchSlotsDoNotOverlap(t *testing.T) {
|
func TestSendBatchSlotsDoNotOverlap(t *testing.T) {
|
||||||
b := NewSendBatch(3, 8)
|
b := newSendBatch(3, 8)
|
||||||
ap := netip.MustParseAddrPort("10.0.0.1:80")
|
ap := netip.MustParseAddrPort("10.0.0.1:80")
|
||||||
|
|
||||||
// Fill three slots, each with its own sentinel byte.
|
// Fill three slots, each with its own sentinel byte.
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bits struct {
|
type Bits struct {
|
||||||
@@ -32,7 +30,7 @@ func NewBits(bits uint64) *Bits {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bits) Check(l *slog.Logger, i uint64) bool {
|
func (b *Bits) Check(l *logrus.Logger, i uint64) bool {
|
||||||
// If i is the next number, return true.
|
// If i is the next number, return true.
|
||||||
if i > b.current {
|
if i > b.current {
|
||||||
return true
|
return true
|
||||||
@@ -44,16 +42,13 @@ func (b *Bits) Check(l *slog.Logger, i uint64) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not within the window
|
// Not within the window
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
l.Debug("rejected a packet (top)",
|
l.Debugf("rejected a packet (top) %d %d\n", b.current, i)
|
||||||
"current", b.current,
|
|
||||||
"incoming", i,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bits) Update(l *slog.Logger, i uint64) bool {
|
func (b *Bits) Update(l *logrus.Logger, i uint64) bool {
|
||||||
// If i is the next number, return true and update current.
|
// If i is the next number, return true and update current.
|
||||||
if i == b.current+1 {
|
if i == b.current+1 {
|
||||||
// Check if the oldest bit was lost since we are shifting the window by 1 and occupying it with this counter
|
// Check if the oldest bit was lost since we are shifting the window by 1 and occupying it with this counter
|
||||||
@@ -92,13 +87,9 @@ func (b *Bits) Update(l *slog.Logger, i uint64) bool {
|
|||||||
// Check to see if it's a duplicate
|
// Check to see if it's a duplicate
|
||||||
if i > b.current-b.length || i < b.length && b.current < b.length {
|
if i > b.current-b.length || i < b.length && b.current < b.length {
|
||||||
if b.current == i || b.bits[i%b.length] == true {
|
if b.current == i || b.bits[i%b.length] == true {
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
l.Debug("Receive window",
|
l.WithField("receiveWindow", m{"accepted": false, "currentCounter": b.current, "incomingCounter": i, "reason": "duplicate"}).
|
||||||
"accepted", false,
|
Debug("Receive window")
|
||||||
"currentCounter", b.current,
|
|
||||||
"incomingCounter", i,
|
|
||||||
"reason", "duplicate",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
b.dupeCounter.Inc(1)
|
b.dupeCounter.Inc(1)
|
||||||
return false
|
return false
|
||||||
@@ -110,13 +101,12 @@ func (b *Bits) Update(l *slog.Logger, i uint64) bool {
|
|||||||
|
|
||||||
// In all other cases, fail and don't change current.
|
// In all other cases, fail and don't change current.
|
||||||
b.outOfWindowCounter.Inc(1)
|
b.outOfWindowCounter.Inc(1)
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
l.Debug("Receive window",
|
l.WithField("accepted", false).
|
||||||
"accepted", false,
|
WithField("currentCounter", b.current).
|
||||||
"currentCounter", b.current,
|
WithField("incomingCounter", i).
|
||||||
"incomingCounter", i,
|
WithField("reason", "nonsense").
|
||||||
"reason", "nonsense",
|
Debug("Receive window")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,8 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/sirupsen/logrus"
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/logging"
|
func HookLogger(l *logrus.Logger) {
|
||||||
)
|
// Do nothing, let the logs flow to stdout/stderr
|
||||||
|
|
||||||
// newPlatformLogger returns a *slog.Logger that writes to stdout. Non-Windows
|
|
||||||
// platforms have no special sink to integrate with.
|
|
||||||
func newPlatformLogger() *slog.Logger {
|
|
||||||
return logging.NewLogger(os.Stdout)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,86 +1,54 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
"log/slog"
|
"io/ioutil"
|
||||||
"strings"
|
"os"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/logging"
|
"github.com/kardianos/service"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newPlatformLogger returns a *slog.Logger that routes every log record
|
// HookLogger routes the logrus logs through the service logger so that they end up in the Windows Event Viewer
|
||||||
// through the Windows service logger so records end up in the Windows
|
// logrus output will be discarded
|
||||||
// Event Log. All the heavy lifting (level management, format swap,
|
func HookLogger(l *logrus.Logger) {
|
||||||
// timestamp toggle, WithAttrs/WithGroup) comes from logging.NewHandler;
|
l.AddHook(newLogHook(logger))
|
||||||
// this file only contributes:
|
l.SetOutput(ioutil.Discard)
|
||||||
//
|
|
||||||
// - an io.Writer that forwards each formatted line to the service
|
|
||||||
// logger at the current record's Event Log severity, and
|
|
||||||
// - a thin severityTag that embeds *logging.Handler and overrides
|
|
||||||
// only Handle / WithAttrs / WithGroup, so Event Viewer's severity
|
|
||||||
// column and severity-based filters keep working the way they did
|
|
||||||
// before the slog migration.
|
|
||||||
//
|
|
||||||
// Format (text vs json) is carried by the embedded *logging.Handler, so
|
|
||||||
// logging.format: json in config still produces JSON lines in Event
|
|
||||||
// Viewer, same as the pre-slog logrus setup.
|
|
||||||
func newPlatformLogger() *slog.Logger {
|
|
||||||
w := &eventLogWriter{}
|
|
||||||
return slog.New(&severityTag{Handler: logging.NewHandler(w), w: w})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventLogWriter forwards slog-formatted lines to the Windows service
|
type logHook struct {
|
||||||
// logger at the severity most recently stashed by severityTag.Handle.
|
sl service.Logger
|
||||||
// The mutex serializes the stash + inner.Handle + Write cycle per record
|
|
||||||
// across all concurrent goroutines; slog's builtin text/json handlers
|
|
||||||
// each hold their own mutex around Write, but that only protects the
|
|
||||||
// Write call itself, not our stash-then-handle sequence.
|
|
||||||
type eventLogWriter struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
level slog.Level
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *eventLogWriter) Write(p []byte) (int, error) {
|
func newLogHook(sl service.Logger) *logHook {
|
||||||
line := strings.TrimRight(string(p), "\n")
|
return &logHook{sl: sl}
|
||||||
switch {
|
}
|
||||||
case w.level >= slog.LevelError:
|
|
||||||
return len(p), logger.Error(line)
|
func (h *logHook) Fire(entry *logrus.Entry) error {
|
||||||
case w.level >= slog.LevelWarn:
|
line, err := entry.String()
|
||||||
return len(p), logger.Warning(line)
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry.Level {
|
||||||
|
case logrus.PanicLevel:
|
||||||
|
return h.sl.Error(line)
|
||||||
|
case logrus.FatalLevel:
|
||||||
|
return h.sl.Error(line)
|
||||||
|
case logrus.ErrorLevel:
|
||||||
|
return h.sl.Error(line)
|
||||||
|
case logrus.WarnLevel:
|
||||||
|
return h.sl.Warning(line)
|
||||||
|
case logrus.InfoLevel:
|
||||||
|
return h.sl.Info(line)
|
||||||
|
case logrus.DebugLevel:
|
||||||
|
return h.sl.Info(line)
|
||||||
default:
|
default:
|
||||||
return len(p), logger.Info(line)
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// severityTag embeds *logging.Handler to pick up everything it does for
|
func (h *logHook) Levels() []logrus.Level {
|
||||||
// free (Enabled, SetLevel, GetLevel, SetFormat, GetFormat,
|
return logrus.AllLevels
|
||||||
// SetDisableTimestamp) and overrides only Handle / WithAttrs / WithGroup
|
|
||||||
// so each record's slog.Level is stashed on the writer before formatting
|
|
||||||
// and so derived handlers stay wrapped as severityTag rather than
|
|
||||||
// downgrading to bare *logging.Handler.
|
|
||||||
type severityTag struct {
|
|
||||||
*logging.Handler
|
|
||||||
w *eventLogWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *severityTag) Handle(ctx context.Context, r slog.Record) error {
|
|
||||||
s.w.mu.Lock()
|
|
||||||
defer s.w.mu.Unlock()
|
|
||||||
s.w.level = r.Level
|
|
||||||
return s.Handler.Handle(ctx, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *severityTag) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
if len(attrs) == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return &severityTag{Handler: s.Handler.WithAttrs(attrs).(*logging.Handler), w: s.w}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *severityTag) WithGroup(name string) slog.Handler {
|
|
||||||
if name == "" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return &severityTag{Handler: s.Handler.WithGroup(name).(*logging.Handler), w: s.w}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/util"
|
"github.com/slackhq/nebula/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,15 +50,10 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
l := logging.NewLogger(os.Stdout)
|
|
||||||
|
|
||||||
if *serviceFlag != "" {
|
if *serviceFlag != "" {
|
||||||
if err := doService(configPath, configTest, Build, serviceFlag); err != nil {
|
doService(configPath, configTest, Build, serviceFlag)
|
||||||
l.Error("Service command failed", "error", err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if *configPath == "" {
|
if *configPath == "" {
|
||||||
fmt.Println("-config flag must be set")
|
fmt.Println("-config flag must be set")
|
||||||
@@ -66,6 +61,9 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l := logrus.New()
|
||||||
|
l.Out = os.Stdout
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
err := c.Load(*configPath)
|
err := c.Load(*configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,16 +71,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
fmt.Printf("failed to apply logging config: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
l.Error("Failed to reconfigure logger on reload", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ctrl, err := nebula.Main(c, *configTest, Build, l, nil)
|
ctrl, err := nebula.Main(c, *configTest, Build, l, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.LogWithContextIfNeeded("Failed to start", err, l)
|
util.LogWithContextIfNeeded("Failed to start", err, l)
|
||||||
@@ -99,7 +87,7 @@ func main() {
|
|||||||
go ctrl.ShutdownBlock()
|
go ctrl.ShutdownBlock()
|
||||||
|
|
||||||
if err := wait(); err != nil {
|
if err := wait(); err != nil {
|
||||||
l.Error("Nebula stopped due to fatal error", "error", err)
|
l.WithError(err).Error("Nebula stopped due to fatal error")
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger service.Logger
|
var logger service.Logger
|
||||||
@@ -25,7 +25,8 @@ func (p *program) Start(s service.Service) error {
|
|||||||
// Start should not block.
|
// Start should not block.
|
||||||
logger.Info("Nebula service starting.")
|
logger.Info("Nebula service starting.")
|
||||||
|
|
||||||
l := newPlatformLogger()
|
l := logrus.New()
|
||||||
|
HookLogger(l)
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
err := c.Load(*p.configPath)
|
err := c.Load(*p.configPath)
|
||||||
@@ -33,15 +34,6 @@ func (p *program) Start(s service.Service) error {
|
|||||||
return fmt.Errorf("failed to load config: %s", err)
|
return fmt.Errorf("failed to load config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
return fmt.Errorf("failed to apply logging config: %s", err)
|
|
||||||
}
|
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
l.Error("Failed to reconfigure logger on reload", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
p.control, err = nebula.Main(c, *p.configTest, Build, l, nil)
|
p.control, err = nebula.Main(c, *p.configTest, Build, l, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -65,11 +57,11 @@ func fileExists(filename string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func doService(configPath *string, configTest *bool, build string, serviceFlag *string) error {
|
func doService(configPath *string, configTest *bool, build string, serviceFlag *string) {
|
||||||
if *configPath == "" {
|
if *configPath == "" {
|
||||||
ex, err := os.Executable()
|
ex, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
panic(err)
|
||||||
}
|
}
|
||||||
*configPath = filepath.Dir(ex) + "/config.yaml"
|
*configPath = filepath.Dir(ex) + "/config.yaml"
|
||||||
if !fileExists(*configPath) {
|
if !fileExists(*configPath) {
|
||||||
@@ -93,16 +85,16 @@ func doService(configPath *string, configTest *bool, build string, serviceFlag *
|
|||||||
// Here are what the different loggers are doing:
|
// Here are what the different loggers are doing:
|
||||||
// - `log` is the standard go log utility, meant to be used while the process is still attached to stdout/stderr
|
// - `log` is the standard go log utility, meant to be used while the process is still attached to stdout/stderr
|
||||||
// - `logger` is the service log utility that may be attached to a special place depending on OS (Windows will have it attached to the event log)
|
// - `logger` is the service log utility that may be attached to a special place depending on OS (Windows will have it attached to the event log)
|
||||||
// - in program.Start we build a *slog.Logger via newPlatformLogger; on non-Windows that is a stdout-backed slog logger, on Windows it routes records through the service logger
|
// - above, in `Run` we create a `logrus.Logger` which is what nebula expects to use
|
||||||
s, err := service.New(prg, svcConfig)
|
s, err := service.New(prg, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := make(chan error, 5)
|
errs := make(chan error, 5)
|
||||||
logger, err = s.Logger(errs)
|
logger, err = s.Logger(errs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -117,16 +109,18 @@ func doService(configPath *string, configTest *bool, build string, serviceFlag *
|
|||||||
|
|
||||||
switch *serviceFlag {
|
switch *serviceFlag {
|
||||||
case "run":
|
case "run":
|
||||||
if err := s.Run(); err != nil {
|
err = s.Run()
|
||||||
|
if err != nil {
|
||||||
// Route any errors to the system logger
|
// Route any errors to the system logger
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if err := service.Control(s, *serviceFlag); err != nil {
|
err := service.Control(s, *serviceFlag)
|
||||||
|
if err != nil {
|
||||||
log.Printf("Valid actions: %q\n", service.ControlAction)
|
log.Printf("Valid actions: %q\n", service.ControlAction)
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-13
@@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/util"
|
"github.com/slackhq/nebula/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,7 +55,8 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
l := logging.NewLogger(os.Stdout)
|
l := logrus.New()
|
||||||
|
l.Out = os.Stdout
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
err := c.Load(*configPath)
|
err := c.Load(*configPath)
|
||||||
@@ -64,16 +65,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
fmt.Printf("failed to apply logging config: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
|
||||||
if err := logging.ApplyConfig(l, c); err != nil {
|
|
||||||
l.Error("Failed to reconfigure logger on reload", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ctrl, err := nebula.Main(c, *configTest, Build, l, nil)
|
ctrl, err := nebula.Main(c, *configTest, Build, l, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.LogWithContextIfNeeded("Failed to start", err, l)
|
util.LogWithContextIfNeeded("Failed to start", err, l)
|
||||||
@@ -91,7 +82,7 @@ func main() {
|
|||||||
notifyReady(l)
|
notifyReady(l)
|
||||||
|
|
||||||
if err := wait(); err != nil {
|
if err := wait(); err != nil {
|
||||||
l.Error("Nebula stopped due to fatal error", "error", err)
|
l.WithError(err).Error("Nebula stopped due to fatal error")
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SdNotifyReady tells systemd the service is ready and dependent services can now be started
|
// SdNotifyReady tells systemd the service is ready and dependent services can now be started
|
||||||
@@ -12,30 +13,30 @@ import (
|
|||||||
// https://www.freedesktop.org/software/systemd/man/systemd.service.html
|
// https://www.freedesktop.org/software/systemd/man/systemd.service.html
|
||||||
const SdNotifyReady = "READY=1"
|
const SdNotifyReady = "READY=1"
|
||||||
|
|
||||||
func notifyReady(l *slog.Logger) {
|
func notifyReady(l *logrus.Logger) {
|
||||||
sockName := os.Getenv("NOTIFY_SOCKET")
|
sockName := os.Getenv("NOTIFY_SOCKET")
|
||||||
if sockName == "" {
|
if sockName == "" {
|
||||||
l.Debug("NOTIFY_SOCKET systemd env var not set, not sending ready signal")
|
l.Debugln("NOTIFY_SOCKET systemd env var not set, not sending ready signal")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.DialTimeout("unixgram", sockName, time.Second)
|
conn, err := net.DialTimeout("unixgram", sockName, time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("failed to connect to systemd notification socket", "error", err)
|
l.WithError(err).Error("failed to connect to systemd notification socket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.SetWriteDeadline(time.Now().Add(time.Second))
|
err = conn.SetWriteDeadline(time.Now().Add(time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("failed to set the write deadline for the systemd notification socket", "error", err)
|
l.WithError(err).Error("failed to set the write deadline for the systemd notification socket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = conn.Write([]byte(SdNotifyReady)); err != nil {
|
if _, err = conn.Write([]byte(SdNotifyReady)); err != nil {
|
||||||
l.Error("failed to signal the systemd notification socket", "error", err)
|
l.WithError(err).Error("failed to signal the systemd notification socket")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Debug("notified systemd the service is ready")
|
l.Debugln("notified systemd the service is ready")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "log/slog"
|
import "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
func notifyReady(_ *slog.Logger) {
|
func notifyReady(_ *logrus.Logger) {
|
||||||
// No init service to notify
|
// No init service to notify
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-15
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -17,6 +16,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,11 +26,11 @@ type C struct {
|
|||||||
Settings map[string]any
|
Settings map[string]any
|
||||||
oldSettings map[string]any
|
oldSettings map[string]any
|
||||||
callbacks []func(*C)
|
callbacks []func(*C)
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
reloadLock sync.Mutex
|
reloadLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewC(l *slog.Logger) *C {
|
func NewC(l *logrus.Logger) *C {
|
||||||
return &C{
|
return &C{
|
||||||
Settings: make(map[string]any),
|
Settings: make(map[string]any),
|
||||||
l: l,
|
l: l,
|
||||||
@@ -107,18 +107,12 @@ func (c *C) HasChanged(k string) bool {
|
|||||||
|
|
||||||
newVals, err := yaml.Marshal(nv)
|
newVals, err := yaml.Marshal(nv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.l.Error("Error while marshaling new config",
|
c.l.WithField("config_path", k).WithError(err).Error("Error while marshaling new config")
|
||||||
"config_path", k,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oldVals, err := yaml.Marshal(ov)
|
oldVals, err := yaml.Marshal(ov)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.l.Error("Error while marshaling old config",
|
c.l.WithField("config_path", k).WithError(err).Error("Error while marshaling old config")
|
||||||
"config_path", k,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(newVals) != string(oldVals)
|
return string(newVals) != string(oldVals)
|
||||||
@@ -160,10 +154,7 @@ func (c *C) ReloadConfig() {
|
|||||||
|
|
||||||
err := c.Load(c.path)
|
err := c.Load(c.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.l.Error("Error occurred while reloading config",
|
c.l.WithField("config_path", c.path).WithError(err).Error("Error occurred while reloading config")
|
||||||
"config_path", c.path,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+57
-65
@@ -5,13 +5,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
@@ -47,10 +47,10 @@ type connectionManager struct {
|
|||||||
|
|
||||||
metricsTxPunchy metrics.Counter
|
metricsTxPunchy metrics.Counter
|
||||||
|
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConnectionManagerFromConfig(l *slog.Logger, c *config.C, hm *HostMap, p *Punchy) *connectionManager {
|
func newConnectionManagerFromConfig(l *logrus.Logger, c *config.C, hm *HostMap, p *Punchy) *connectionManager {
|
||||||
cm := &connectionManager{
|
cm := &connectionManager{
|
||||||
hostMap: hm,
|
hostMap: hm,
|
||||||
l: l,
|
l: l,
|
||||||
@@ -85,10 +85,9 @@ func (cm *connectionManager) reload(c *config.C, initial bool) {
|
|||||||
old := cm.getInactivityTimeout()
|
old := cm.getInactivityTimeout()
|
||||||
cm.inactivityTimeout.Store((int64)(c.GetDuration("tunnels.inactivity_timeout", 10*time.Minute)))
|
cm.inactivityTimeout.Store((int64)(c.GetDuration("tunnels.inactivity_timeout", 10*time.Minute)))
|
||||||
if !initial {
|
if !initial {
|
||||||
cm.l.Info("Inactivity timeout has changed",
|
cm.l.WithField("oldDuration", old).
|
||||||
"oldDuration", old,
|
WithField("newDuration", cm.getInactivityTimeout()).
|
||||||
"newDuration", cm.getInactivityTimeout(),
|
Info("Inactivity timeout has changed")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,10 +95,9 @@ func (cm *connectionManager) reload(c *config.C, initial bool) {
|
|||||||
old := cm.dropInactive.Load()
|
old := cm.dropInactive.Load()
|
||||||
cm.dropInactive.Store(c.GetBool("tunnels.drop_inactive", false))
|
cm.dropInactive.Store(c.GetBool("tunnels.drop_inactive", false))
|
||||||
if !initial {
|
if !initial {
|
||||||
cm.l.Info("Drop inactive setting has changed",
|
cm.l.WithField("oldBool", old).
|
||||||
"oldBool", old,
|
WithField("newBool", cm.dropInactive.Load()).
|
||||||
"newBool", cm.dropInactive.Load(),
|
Info("Drop inactive setting has changed")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,7 +256,7 @@ func (cm *connectionManager) migrateRelayUsed(oldhostinfo, newhostinfo *HostInfo
|
|||||||
var err error
|
var err error
|
||||||
index, err = AddRelay(cm.l, newhostinfo, cm.hostMap, r.PeerAddr, nil, r.Type, Requested)
|
index, err = AddRelay(cm.l, newhostinfo, cm.hostMap, r.PeerAddr, nil, r.Type, Requested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cm.l.Error("failed to migrate relay to new hostinfo", "error", err)
|
cm.l.WithError(err).Error("failed to migrate relay to new hostinfo")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
@@ -306,16 +304,16 @@ func (cm *connectionManager) migrateRelayUsed(oldhostinfo, newhostinfo *HostInfo
|
|||||||
|
|
||||||
msg, err := req.Marshal()
|
msg, err := req.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cm.l.Error("failed to marshal Control message to migrate relay", "error", err)
|
cm.l.WithError(err).Error("failed to marshal Control message to migrate relay")
|
||||||
} else {
|
} else {
|
||||||
cm.intf.SendMessageToHostInfo(header.Control, 0, newhostinfo, msg, make([]byte, 12), make([]byte, mtu))
|
cm.intf.SendMessageToHostInfo(header.Control, 0, newhostinfo, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
cm.l.Info("send CreateRelayRequest",
|
cm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", req.RelayFromAddr,
|
"relayFrom": req.RelayFromAddr,
|
||||||
"relayTo", req.RelayToAddr,
|
"relayTo": req.RelayToAddr,
|
||||||
"initiatorRelayIndex", req.InitiatorRelayIndex,
|
"initiatorRelayIndex": req.InitiatorRelayIndex,
|
||||||
"responderRelayIndex", req.ResponderRelayIndex,
|
"responderRelayIndex": req.ResponderRelayIndex,
|
||||||
"vpnAddrs", newhostinfo.vpnAddrs,
|
"vpnAddrs": newhostinfo.vpnAddrs}).
|
||||||
)
|
Info("send CreateRelayRequest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +325,7 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
|||||||
|
|
||||||
hostinfo := cm.hostMap.Indexes[localIndex]
|
hostinfo := cm.hostMap.Indexes[localIndex]
|
||||||
if hostinfo == nil {
|
if hostinfo == nil {
|
||||||
cm.l.Debug("Not found in hostmap", "localIndex", localIndex)
|
cm.l.WithField("localIndex", localIndex).Debugln("Not found in hostmap")
|
||||||
return doNothing, nil, nil
|
return doNothing, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,10 +345,10 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
|||||||
// A hostinfo is determined alive if there is incoming traffic
|
// A hostinfo is determined alive if there is incoming traffic
|
||||||
if inTraffic {
|
if inTraffic {
|
||||||
decision := doNothing
|
decision := doNothing
|
||||||
if cm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if cm.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(cm.l).Debug("Tunnel status",
|
hostinfo.logger(cm.l).
|
||||||
"tunnelCheck", m{"state": "alive", "method": "passive"},
|
WithField("tunnelCheck", m{"state": "alive", "method": "passive"}).
|
||||||
)
|
Debug("Tunnel status")
|
||||||
}
|
}
|
||||||
hostinfo.pendingDeletion.Store(false)
|
hostinfo.pendingDeletion.Store(false)
|
||||||
|
|
||||||
@@ -377,9 +375,9 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
|||||||
|
|
||||||
if hostinfo.pendingDeletion.Load() {
|
if hostinfo.pendingDeletion.Load() {
|
||||||
// We have already sent a test packet and nothing was returned, this hostinfo is dead
|
// We have already sent a test packet and nothing was returned, this hostinfo is dead
|
||||||
hostinfo.logger(cm.l).Info("Tunnel status",
|
hostinfo.logger(cm.l).
|
||||||
"tunnelCheck", m{"state": "dead", "method": "active"},
|
WithField("tunnelCheck", m{"state": "dead", "method": "active"}).
|
||||||
)
|
Info("Tunnel status")
|
||||||
|
|
||||||
return deleteTunnel, hostinfo, nil
|
return deleteTunnel, hostinfo, nil
|
||||||
}
|
}
|
||||||
@@ -390,10 +388,10 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
|||||||
inactiveFor, isInactive := cm.isInactive(hostinfo, now)
|
inactiveFor, isInactive := cm.isInactive(hostinfo, now)
|
||||||
if isInactive {
|
if isInactive {
|
||||||
// Tunnel is inactive, tear it down
|
// Tunnel is inactive, tear it down
|
||||||
hostinfo.logger(cm.l).Info("Dropping tunnel due to inactivity",
|
hostinfo.logger(cm.l).
|
||||||
"inactiveDuration", inactiveFor,
|
WithField("inactiveDuration", inactiveFor).
|
||||||
"primary", mainHostInfo,
|
WithField("primary", mainHostInfo).
|
||||||
)
|
Info("Dropping tunnel due to inactivity")
|
||||||
|
|
||||||
return closeTunnel, hostinfo, primary
|
return closeTunnel, hostinfo, primary
|
||||||
}
|
}
|
||||||
@@ -412,18 +410,18 @@ func (cm *connectionManager) makeTrafficDecision(localIndex uint32, now time.Tim
|
|||||||
cm.sendPunch(hostinfo)
|
cm.sendPunch(hostinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if cm.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(cm.l).Debug("Tunnel status",
|
hostinfo.logger(cm.l).
|
||||||
"tunnelCheck", m{"state": "testing", "method": "active"},
|
WithField("tunnelCheck", m{"state": "testing", "method": "active"}).
|
||||||
)
|
Debug("Tunnel status")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues
|
// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues
|
||||||
decision = sendTestPacket
|
decision = sendTestPacket
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if cm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if cm.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(cm.l).Debug("Hostinfo sadness")
|
hostinfo.logger(cm.l).Debugf("Hostinfo sadness")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,16 +493,14 @@ func (cm *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostI
|
|||||||
return false //cert is still valid! yay!
|
return false //cert is still valid! yay!
|
||||||
} else if err == cert.ErrBlockListed { //avoiding errors.Is for speed
|
} else if err == cert.ErrBlockListed { //avoiding errors.Is for speed
|
||||||
// Block listed certificates should always be disconnected
|
// Block listed certificates should always be disconnected
|
||||||
hostinfo.logger(cm.l).Info("Remote certificate is blocked, tearing down the tunnel",
|
hostinfo.logger(cm.l).WithError(err).
|
||||||
"error", err,
|
WithField("fingerprint", remoteCert.Fingerprint).
|
||||||
"fingerprint", remoteCert.Fingerprint,
|
Info("Remote certificate is blocked, tearing down the tunnel")
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
} else if cm.intf.disconnectInvalid.Load() {
|
} else if cm.intf.disconnectInvalid.Load() {
|
||||||
hostinfo.logger(cm.l).Info("Remote certificate is no longer valid, tearing down the tunnel",
|
hostinfo.logger(cm.l).WithError(err).
|
||||||
"error", err,
|
WithField("fingerprint", remoteCert.Fingerprint).
|
||||||
"fingerprint", remoteCert.Fingerprint,
|
Info("Remote certificate is no longer valid, tearing down the tunnel")
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
//if we reach here, the cert is no longer valid, but we're configured to keep tunnels from now-invalid certs open
|
//if we reach here, the cert is no longer valid, but we're configured to keep tunnels from now-invalid certs open
|
||||||
@@ -543,11 +539,10 @@ func (cm *connectionManager) tryRehandshake(hostinfo *HostInfo) {
|
|||||||
curCrtVersion := curCrt.Version()
|
curCrtVersion := curCrt.Version()
|
||||||
myCrt := cs.getCertificate(curCrtVersion)
|
myCrt := cs.getCertificate(curCrtVersion)
|
||||||
if myCrt == nil {
|
if myCrt == nil {
|
||||||
cm.l.Info("Re-handshaking with remote",
|
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("version", curCrtVersion).
|
||||||
"version", curCrtVersion,
|
WithField("reason", "local certificate removed").
|
||||||
"reason", "local certificate removed",
|
Info("Re-handshaking with remote")
|
||||||
)
|
|
||||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -555,12 +550,11 @@ func (cm *connectionManager) tryRehandshake(hostinfo *HostInfo) {
|
|||||||
if peerCrt != nil && curCrtVersion < peerCrt.Certificate.Version() {
|
if peerCrt != nil && curCrtVersion < peerCrt.Certificate.Version() {
|
||||||
// if our certificate version is less than theirs, and we have a matching version available, rehandshake?
|
// if our certificate version is less than theirs, and we have a matching version available, rehandshake?
|
||||||
if cs.getCertificate(peerCrt.Certificate.Version()) != nil {
|
if cs.getCertificate(peerCrt.Certificate.Version()) != nil {
|
||||||
cm.l.Info("Re-handshaking with remote",
|
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("version", curCrtVersion).
|
||||||
"version", curCrtVersion,
|
WithField("peerVersion", peerCrt.Certificate.Version()).
|
||||||
"peerVersion", peerCrt.Certificate.Version(),
|
WithField("reason", "local certificate version lower than peer, attempting to correct").
|
||||||
"reason", "local certificate version lower than peer, attempting to correct",
|
Info("Re-handshaking with remote")
|
||||||
)
|
|
||||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(hh *HandshakeHostInfo) {
|
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], func(hh *HandshakeHostInfo) {
|
||||||
hh.initiatingVersionOverride = peerCrt.Certificate.Version()
|
hh.initiatingVersionOverride = peerCrt.Certificate.Version()
|
||||||
})
|
})
|
||||||
@@ -568,19 +562,17 @@ func (cm *connectionManager) tryRehandshake(hostinfo *HostInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !bytes.Equal(curCrt.Signature(), myCrt.Signature()) {
|
if !bytes.Equal(curCrt.Signature(), myCrt.Signature()) {
|
||||||
cm.l.Info("Re-handshaking with remote",
|
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("reason", "local certificate is not current").
|
||||||
"reason", "local certificate is not current",
|
Info("Re-handshaking with remote")
|
||||||
)
|
|
||||||
|
|
||||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if curCrtVersion < cs.initiatingVersion {
|
if curCrtVersion < cs.initiatingVersion {
|
||||||
cm.l.Info("Re-handshaking with remote",
|
cm.l.WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("reason", "current cert version < pki.initiatingVersion").
|
||||||
"reason", "current cert version < pki.initiatingVersion",
|
Info("Re-handshaking with remote")
|
||||||
)
|
|
||||||
|
|
||||||
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
cm.intf.handshakeManager.StartHandshake(hostinfo.vpnAddrs[0], nil)
|
||||||
return
|
return
|
||||||
|
|||||||
+17
-17
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/flynn/noise"
|
"github.com/flynn/noise"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/overlaytest"
|
"github.com/slackhq/nebula/overlay"
|
||||||
"github.com/slackhq/nebula/test"
|
"github.com/slackhq/nebula/test"
|
||||||
"github.com/slackhq/nebula/udp"
|
"github.com/slackhq/nebula/udp"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -53,7 +53,7 @@ func Test_NewConnectionManagerTest(t *testing.T) {
|
|||||||
lh := newTestLighthouse()
|
lh := newTestLighthouse()
|
||||||
ifce := &Interface{
|
ifce := &Interface{
|
||||||
hostMap: hostMap,
|
hostMap: hostMap,
|
||||||
inside: &overlaytest.NoopTun{},
|
inside: &overlay.NoopTun{},
|
||||||
outside: &udp.NoopConn{},
|
outside: &udp.NoopConn{},
|
||||||
firewall: &Firewall{},
|
firewall: &Firewall{},
|
||||||
lightHouse: lh,
|
lightHouse: lh,
|
||||||
@@ -64,9 +64,9 @@ func Test_NewConnectionManagerTest(t *testing.T) {
|
|||||||
ifce.pki.cs.Store(cs)
|
ifce.pki.cs.Store(cs)
|
||||||
|
|
||||||
// Create manager
|
// Create manager
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
punchy := NewPunchyFromConfig(test.NewLogger(), conf)
|
punchy := NewPunchyFromConfig(l, conf)
|
||||||
nc := newConnectionManagerFromConfig(test.NewLogger(), conf, hostMap, punchy)
|
nc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)
|
||||||
nc.intf = ifce
|
nc.intf = ifce
|
||||||
p := []byte("")
|
p := []byte("")
|
||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
@@ -136,7 +136,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
|
|||||||
lh := newTestLighthouse()
|
lh := newTestLighthouse()
|
||||||
ifce := &Interface{
|
ifce := &Interface{
|
||||||
hostMap: hostMap,
|
hostMap: hostMap,
|
||||||
inside: &overlaytest.NoopTun{},
|
inside: &overlay.NoopTun{},
|
||||||
outside: &udp.NoopConn{},
|
outside: &udp.NoopConn{},
|
||||||
firewall: &Firewall{},
|
firewall: &Firewall{},
|
||||||
lightHouse: lh,
|
lightHouse: lh,
|
||||||
@@ -147,9 +147,9 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
|
|||||||
ifce.pki.cs.Store(cs)
|
ifce.pki.cs.Store(cs)
|
||||||
|
|
||||||
// Create manager
|
// Create manager
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
punchy := NewPunchyFromConfig(test.NewLogger(), conf)
|
punchy := NewPunchyFromConfig(l, conf)
|
||||||
nc := newConnectionManagerFromConfig(test.NewLogger(), conf, hostMap, punchy)
|
nc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)
|
||||||
nc.intf = ifce
|
nc.intf = ifce
|
||||||
p := []byte("")
|
p := []byte("")
|
||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
@@ -221,7 +221,7 @@ func Test_NewConnectionManager_DisconnectInactive(t *testing.T) {
|
|||||||
lh := newTestLighthouse()
|
lh := newTestLighthouse()
|
||||||
ifce := &Interface{
|
ifce := &Interface{
|
||||||
hostMap: hostMap,
|
hostMap: hostMap,
|
||||||
inside: &overlaytest.NoopTun{},
|
inside: &overlay.NoopTun{},
|
||||||
outside: &udp.NoopConn{},
|
outside: &udp.NoopConn{},
|
||||||
firewall: &Firewall{},
|
firewall: &Firewall{},
|
||||||
lightHouse: lh,
|
lightHouse: lh,
|
||||||
@@ -232,12 +232,12 @@ func Test_NewConnectionManager_DisconnectInactive(t *testing.T) {
|
|||||||
ifce.pki.cs.Store(cs)
|
ifce.pki.cs.Store(cs)
|
||||||
|
|
||||||
// Create manager
|
// Create manager
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
conf.Settings["tunnels"] = map[string]any{
|
conf.Settings["tunnels"] = map[string]any{
|
||||||
"drop_inactive": true,
|
"drop_inactive": true,
|
||||||
}
|
}
|
||||||
punchy := NewPunchyFromConfig(test.NewLogger(), conf)
|
punchy := NewPunchyFromConfig(l, conf)
|
||||||
nc := newConnectionManagerFromConfig(test.NewLogger(), conf, hostMap, punchy)
|
nc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)
|
||||||
assert.True(t, nc.dropInactive.Load())
|
assert.True(t, nc.dropInactive.Load())
|
||||||
nc.intf = ifce
|
nc.intf = ifce
|
||||||
|
|
||||||
@@ -348,7 +348,7 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
|
|||||||
lh := newTestLighthouse()
|
lh := newTestLighthouse()
|
||||||
ifce := &Interface{
|
ifce := &Interface{
|
||||||
hostMap: hostMap,
|
hostMap: hostMap,
|
||||||
inside: &overlaytest.NoopTun{},
|
inside: &overlay.NoopTun{},
|
||||||
outside: &udp.NoopConn{},
|
outside: &udp.NoopConn{},
|
||||||
firewall: &Firewall{},
|
firewall: &Firewall{},
|
||||||
lightHouse: lh,
|
lightHouse: lh,
|
||||||
@@ -361,9 +361,9 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
|
|||||||
ifce.disconnectInvalid.Store(true)
|
ifce.disconnectInvalid.Store(true)
|
||||||
|
|
||||||
// Create manager
|
// Create manager
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
punchy := NewPunchyFromConfig(test.NewLogger(), conf)
|
punchy := NewPunchyFromConfig(l, conf)
|
||||||
nc := newConnectionManagerFromConfig(test.NewLogger(), conf, hostMap, punchy)
|
nc := newConnectionManagerFromConfig(l, conf, hostMap, punchy)
|
||||||
nc.intf = ifce
|
nc.intf = ifce
|
||||||
ifce.connectionManager = nc
|
ifce.connectionManager = nc
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -8,11 +8,12 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/flynn/noise"
|
"github.com/flynn/noise"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/noiseutil"
|
"github.com/slackhq/nebula/noiseutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ReplayWindow = 1024 //todo I've started seeing out-of-window messages in testing?
|
const ReplayWindow = 1024
|
||||||
|
|
||||||
type ConnectionState struct {
|
type ConnectionState struct {
|
||||||
eKey *NebulaCipherState
|
eKey *NebulaCipherState
|
||||||
@@ -26,7 +27,7 @@ type ConnectionState struct {
|
|||||||
writeLock sync.Mutex
|
writeLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnectionState(cs *CertState, crt cert.Certificate, initiator bool, pattern noise.HandshakePattern) (*ConnectionState, error) {
|
func NewConnectionState(l *logrus.Logger, cs *CertState, crt cert.Certificate, initiator bool, pattern noise.HandshakePattern) (*ConnectionState, error) {
|
||||||
var dhFunc noise.DHFunc
|
var dhFunc noise.DHFunc
|
||||||
switch crt.Curve() {
|
switch crt.Curve() {
|
||||||
case cert.Curve_CURVE25519:
|
case cert.Curve_CURVE25519:
|
||||||
|
|||||||
+6
-8
@@ -3,13 +3,13 @@ package nebula
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
@@ -46,7 +46,7 @@ type Control struct {
|
|||||||
state RunState
|
state RunState
|
||||||
|
|
||||||
f *Interface
|
f *Interface
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
sshStart func()
|
sshStart func()
|
||||||
@@ -151,7 +151,7 @@ func (c *Control) Stop() {
|
|||||||
|
|
||||||
c.CloseAllTunnels(false)
|
c.CloseAllTunnels(false)
|
||||||
if err := c.f.Close(); err != nil {
|
if err := c.f.Close(); err != nil {
|
||||||
c.l.Error("Close interface failed", "error", err)
|
c.l.WithError(err).Error("Close interface failed")
|
||||||
}
|
}
|
||||||
c.stateLock.Lock()
|
c.stateLock.Lock()
|
||||||
c.state = StateStopped
|
c.state = StateStopped
|
||||||
@@ -166,7 +166,7 @@ func (c *Control) ShutdownBlock() {
|
|||||||
|
|
||||||
rawSig := <-sigChan
|
rawSig := <-sigChan
|
||||||
sig := rawSig.String()
|
sig := rawSig.String()
|
||||||
c.l.Info("Caught signal, shutting down", "signal", sig)
|
c.l.WithField("signal", sig).Info("Caught signal, shutting down")
|
||||||
c.Stop()
|
c.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,10 +303,8 @@ func (c *Control) CloseAllTunnels(excludeLighthouses bool) (closed int) {
|
|||||||
c.f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
|
c.f.send(header.CloseTunnel, 0, h.ConnectionState, h, []byte{}, make([]byte, 12, 12), make([]byte, mtu))
|
||||||
c.f.closeTunnel(h)
|
c.f.closeTunnel(h)
|
||||||
|
|
||||||
c.l.Debug("Sending close tunnel message",
|
c.l.WithField("vpnAddrs", h.vpnAddrs).WithField("udpAddr", h.remote).
|
||||||
"vpnAddrs", h.vpnAddrs,
|
Debug("Sending close tunnel message")
|
||||||
"udpAddr", h.remote,
|
|
||||||
)
|
|
||||||
closed++
|
closed++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/test"
|
"github.com/slackhq/nebula/test"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -82,7 +83,7 @@ func TestControl_GetHostInfoByVpnIp(t *testing.T) {
|
|||||||
f: &Interface{
|
f: &Interface{
|
||||||
hostMap: hm,
|
hostMap: hm,
|
||||||
},
|
},
|
||||||
l: test.NewLogger(),
|
l: logrus.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
thi := c.GetHostInfoByVpnAddr(vpnIp, false)
|
thi := c.GetHostInfoByVpnAddr(vpnIp, false)
|
||||||
|
|||||||
+63
-236
@@ -1,249 +1,63 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dnsServer struct {
|
// This whole thing should be rewritten to use context
|
||||||
|
|
||||||
|
var dnsR *dnsRecords
|
||||||
|
var dnsServer *dns.Server
|
||||||
|
var dnsAddr string
|
||||||
|
|
||||||
|
type dnsRecords struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
ctx context.Context
|
|
||||||
dnsMap4 map[string]netip.Addr
|
dnsMap4 map[string]netip.Addr
|
||||||
dnsMap6 map[string]netip.Addr
|
dnsMap6 map[string]netip.Addr
|
||||||
hostMap *HostMap
|
hostMap *HostMap
|
||||||
myVpnAddrsTable *bart.Lite
|
myVpnAddrsTable *bart.Lite
|
||||||
|
|
||||||
mux *dns.ServeMux
|
|
||||||
|
|
||||||
// enabled mirrors `lighthouse.serve_dns && lighthouse.am_lighthouse`.
|
|
||||||
// Start, Add, and reload consult it so callers don't need to know the
|
|
||||||
// gating rules. When it toggles off via reload, accumulated records are
|
|
||||||
// cleared so a later re-enable starts with a fresh map populated from
|
|
||||||
// new handshakes.
|
|
||||||
enabled atomic.Bool
|
|
||||||
|
|
||||||
serverMu sync.Mutex
|
|
||||||
server *dns.Server
|
|
||||||
// started is closed once `server` has finished binding (or after
|
|
||||||
// ListenAndServe returns on a bind failure). Stop waits on it before
|
|
||||||
// calling Shutdown to avoid the miekg/dns "server not started" race
|
|
||||||
// where a Shutdown that arrives before bind completes is silently
|
|
||||||
// ignored, leaving the listener running forever.
|
|
||||||
started chan struct{}
|
|
||||||
addr string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDnsServerFromConfig builds a dnsServer, applies the initial config, and
|
func newDnsRecords(l *logrus.Logger, cs *CertState, hostMap *HostMap) *dnsRecords {
|
||||||
// registers a reload callback. The reload callback is registered before the
|
return &dnsRecords{
|
||||||
// initial config is applied, so a SIGHUP can later enable, fix, or disable
|
|
||||||
// DNS even if the initial application failed.
|
|
||||||
//
|
|
||||||
// The dnsServer internally gates on `lighthouse.serve_dns &&
|
|
||||||
// lighthouse.am_lighthouse`. Start and Add are safe to call unconditionally,
|
|
||||||
// they no-op when DNS isn't enabled. Each Start invocation owns a ctx-cancel
|
|
||||||
// watcher that tears the listener down on nebula shutdown. The returned
|
|
||||||
// pointer is always non-nil, even on error.
|
|
||||||
func newDnsServerFromConfig(ctx context.Context, l *slog.Logger, cs *CertState, hostMap *HostMap, c *config.C) (*dnsServer, error) {
|
|
||||||
ds := &dnsServer{
|
|
||||||
l: l,
|
l: l,
|
||||||
ctx: ctx,
|
|
||||||
dnsMap4: make(map[string]netip.Addr),
|
dnsMap4: make(map[string]netip.Addr),
|
||||||
dnsMap6: make(map[string]netip.Addr),
|
dnsMap6: make(map[string]netip.Addr),
|
||||||
hostMap: hostMap,
|
hostMap: hostMap,
|
||||||
myVpnAddrsTable: cs.myVpnAddrsTable,
|
myVpnAddrsTable: cs.myVpnAddrsTable,
|
||||||
}
|
}
|
||||||
ds.mux = dns.NewServeMux()
|
|
||||||
ds.mux.HandleFunc(".", ds.handleDnsRequest)
|
|
||||||
|
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
|
||||||
if err := ds.reload(c, false); err != nil {
|
|
||||||
ds.l.Error("Failed to reload DNS responder from config", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := ds.reload(c, true); err != nil {
|
|
||||||
return ds, err
|
|
||||||
}
|
|
||||||
return ds, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload applies the latest config and reconciles the running state with it:
|
func (d *dnsRecords) Query(q uint16, data string) netip.Addr {
|
||||||
// - enabled toggled on -> spawn a runner
|
|
||||||
// - enabled toggled off -> stop the runner
|
|
||||||
// - listen address changed (while running) -> restart on the new address
|
|
||||||
// - everything else -> no-op
|
|
||||||
//
|
|
||||||
// On the initial call it only records configuration; Control.Start is what
|
|
||||||
// launches the first runner via dnsStart.
|
|
||||||
func (d *dnsServer) reload(c *config.C, initial bool) error {
|
|
||||||
wantsDns := c.GetBool("lighthouse.serve_dns", false)
|
|
||||||
amLighthouse := c.GetBool("lighthouse.am_lighthouse", false)
|
|
||||||
enabled := wantsDns && amLighthouse
|
|
||||||
newAddr := getDnsServerAddr(c)
|
|
||||||
|
|
||||||
d.serverMu.Lock()
|
|
||||||
running := d.server
|
|
||||||
runningStarted := d.started
|
|
||||||
sameAddr := d.addr == newAddr
|
|
||||||
d.addr = newAddr
|
|
||||||
d.enabled.Store(enabled)
|
|
||||||
d.serverMu.Unlock()
|
|
||||||
|
|
||||||
if initial {
|
|
||||||
if wantsDns && !amLighthouse {
|
|
||||||
d.l.Warn("DNS server refusing to run because this host is not a lighthouse.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enabled {
|
|
||||||
if running != nil {
|
|
||||||
d.Stop()
|
|
||||||
}
|
|
||||||
// Drop any records that accumulated while enabled; a later re-enable
|
|
||||||
// will repopulate from fresh handshakes.
|
|
||||||
d.clearRecords()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if running == nil {
|
|
||||||
// Was disabled (or never started); bring it up now.
|
|
||||||
go d.Start()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if sameAddr {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.shutdownServer(running, runningStarted, "reload")
|
|
||||||
// Old Start goroutine has now exited; bring up a fresh listener on the
|
|
||||||
// new address.
|
|
||||||
go d.Start()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// shutdownServer waits for the server to finish binding (so Shutdown actually
|
|
||||||
// stops it rather than no-oping) and then shuts it down.
|
|
||||||
func (d *dnsServer) shutdownServer(srv *dns.Server, started chan struct{}, reason string) {
|
|
||||||
if srv == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if started != nil {
|
|
||||||
<-started
|
|
||||||
}
|
|
||||||
if err := srv.Shutdown(); err != nil {
|
|
||||||
d.l.Warn("Failed to shut down the DNS responder", "reason", reason, "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start binds and serves the DNS responder. Blocks until Stop is called or
|
|
||||||
// the listener errors. Safe to call when DNS is disabled (returns
|
|
||||||
// immediately). This is what Control.dnsStart points at.
|
|
||||||
//
|
|
||||||
// Must be invoked after the tun device is active so that lighthouse.dns.host
|
|
||||||
// may bind to a nebula IP.
|
|
||||||
func (d *dnsServer) Start() {
|
|
||||||
if !d.enabled.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
started := make(chan struct{})
|
|
||||||
d.serverMu.Lock()
|
|
||||||
if d.ctx.Err() != nil {
|
|
||||||
d.serverMu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr := d.addr
|
|
||||||
server := &dns.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Net: "udp",
|
|
||||||
Handler: d.mux,
|
|
||||||
NotifyStartedFunc: func() { close(started) },
|
|
||||||
}
|
|
||||||
d.server = server
|
|
||||||
d.started = started
|
|
||||||
d.serverMu.Unlock()
|
|
||||||
|
|
||||||
// Per-invocation ctx watcher. Exits when Start does, so we don't leak a
|
|
||||||
// watcher per reload-driven restart.
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-d.ctx.Done():
|
|
||||||
d.shutdownServer(server, started, "shutdown")
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
d.l.Info("Starting DNS responder", "dnsListener", addr)
|
|
||||||
err := server.ListenAndServe()
|
|
||||||
close(done)
|
|
||||||
|
|
||||||
// If the listener never bound (bind error) NotifyStartedFunc never fires,
|
|
||||||
// so close started here to release any Stop caller waiting on it.
|
|
||||||
select {
|
|
||||||
case <-started:
|
|
||||||
default:
|
|
||||||
close(started)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
d.l.Warn("Failed to run the DNS responder", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop shuts down the active server, if any. Idempotent.
|
|
||||||
func (d *dnsServer) Stop() {
|
|
||||||
d.serverMu.Lock()
|
|
||||||
srv := d.server
|
|
||||||
started := d.started
|
|
||||||
d.server = nil
|
|
||||||
d.started = nil
|
|
||||||
d.serverMu.Unlock()
|
|
||||||
d.shutdownServer(srv, started, "stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query returns the address for the given name and query type. The second
|
|
||||||
// return value reports whether the name is known at all (in either A or AAAA),
|
|
||||||
// which lets callers distinguish NODATA from NXDOMAIN.
|
|
||||||
func (d *dnsServer) Query(q uint16, data string) (netip.Addr, bool) {
|
|
||||||
data = strings.ToLower(data)
|
data = strings.ToLower(data)
|
||||||
d.RLock()
|
d.RLock()
|
||||||
defer d.RUnlock()
|
defer d.RUnlock()
|
||||||
addr4, haveV4 := d.dnsMap4[data]
|
|
||||||
addr6, haveV6 := d.dnsMap6[data]
|
|
||||||
nameExists := haveV4 || haveV6
|
|
||||||
switch q {
|
switch q {
|
||||||
case dns.TypeA:
|
case dns.TypeA:
|
||||||
if haveV4 {
|
if r, ok := d.dnsMap4[data]; ok {
|
||||||
return addr4, nameExists
|
return r
|
||||||
}
|
}
|
||||||
case dns.TypeAAAA:
|
case dns.TypeAAAA:
|
||||||
if haveV6 {
|
if r, ok := d.dnsMap6[data]; ok {
|
||||||
return addr6, nameExists
|
return r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return netip.Addr{}, nameExists
|
return netip.Addr{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dnsServer) QueryCert(data string) string {
|
func (d *dnsRecords) QueryCert(data string) string {
|
||||||
if len(data) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ip, err := netip.ParseAddr(data[:len(data)-1])
|
ip, err := netip.ParseAddr(data[:len(data)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -266,19 +80,8 @@ func (d *dnsServer) QueryCert(data string) string {
|
|||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearRecords drops all DNS records.
|
|
||||||
func (d *dnsServer) clearRecords() {
|
|
||||||
d.Lock()
|
|
||||||
defer d.Unlock()
|
|
||||||
clear(d.dnsMap4)
|
|
||||||
clear(d.dnsMap6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the first IPv4 and IPv6 address that appears in `addresses` as the record for `host`
|
// Add adds the first IPv4 and IPv6 address that appears in `addresses` as the record for `host`
|
||||||
func (d *dnsServer) Add(host string, addresses []netip.Addr) {
|
func (d *dnsRecords) Add(host string, addresses []netip.Addr) {
|
||||||
if !d.enabled.Load() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host = strings.ToLower(host)
|
host = strings.ToLower(host)
|
||||||
d.Lock()
|
d.Lock()
|
||||||
defer d.Unlock()
|
defer d.Unlock()
|
||||||
@@ -298,7 +101,7 @@ func (d *dnsServer) Add(host string, addresses []netip.Addr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dnsServer) isSelfNebulaOrLocalhost(addr string) bool {
|
func (d *dnsRecords) isSelfNebulaOrLocalhost(addr string) bool {
|
||||||
a, _, _ := net.SplitHostPort(addr)
|
a, _, _ := net.SplitHostPort(addr)
|
||||||
b, err := netip.ParseAddr(a)
|
b, err := netip.ParseAddr(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -313,24 +116,13 @@ func (d *dnsServer) isSelfNebulaOrLocalhost(addr string) bool {
|
|||||||
return d.myVpnAddrsTable.Contains(b)
|
return d.myVpnAddrsTable.Contains(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dnsServer) parseQuery(m *dns.Msg, w dns.ResponseWriter) {
|
func (d *dnsRecords) parseQuery(m *dns.Msg, w dns.ResponseWriter) {
|
||||||
debugEnabled := d.l.Enabled(context.Background(), slog.LevelDebug)
|
|
||||||
// Per RFC 2308 §2.2, a name that exists but has no record of the requested
|
|
||||||
// type must be answered with NOERROR and an empty answer section (NODATA),
|
|
||||||
// not NXDOMAIN (RFC 2308 §2.1), which is reserved for names that do not
|
|
||||||
// exist at all.
|
|
||||||
anyNameExists := false
|
|
||||||
for _, q := range m.Question {
|
for _, q := range m.Question {
|
||||||
switch q.Qtype {
|
switch q.Qtype {
|
||||||
case dns.TypeA, dns.TypeAAAA:
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
qType := dns.TypeToString[q.Qtype]
|
qType := dns.TypeToString[q.Qtype]
|
||||||
if debugEnabled {
|
d.l.Debugf("Query for %s %s", qType, q.Name)
|
||||||
d.l.Debug("DNS query", "type", qType, "name", q.Name)
|
ip := d.Query(q.Qtype, q.Name)
|
||||||
}
|
|
||||||
ip, nameExists := d.Query(q.Qtype, q.Name)
|
|
||||||
if nameExists {
|
|
||||||
anyNameExists = true
|
|
||||||
}
|
|
||||||
if ip.IsValid() {
|
if ip.IsValid() {
|
||||||
rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", q.Name, qType, ip))
|
rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", q.Name, qType, ip))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -342,9 +134,7 @@ func (d *dnsServer) parseQuery(m *dns.Msg, w dns.ResponseWriter) {
|
|||||||
if !d.isSelfNebulaOrLocalhost(w.RemoteAddr().String()) {
|
if !d.isSelfNebulaOrLocalhost(w.RemoteAddr().String()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if debugEnabled {
|
d.l.Debugf("Query for TXT %s", q.Name)
|
||||||
d.l.Debug("DNS query", "type", "TXT", "name", q.Name)
|
|
||||||
}
|
|
||||||
ip := d.QueryCert(q.Name)
|
ip := d.QueryCert(q.Name)
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
rr, err := dns.NewRR(fmt.Sprintf("%s TXT %s", q.Name, ip))
|
rr, err := dns.NewRR(fmt.Sprintf("%s TXT %s", q.Name, ip))
|
||||||
@@ -355,12 +145,12 @@ func (d *dnsServer) parseQuery(m *dns.Msg, w dns.ResponseWriter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.Answer) == 0 && !anyNameExists {
|
if len(m.Answer) == 0 {
|
||||||
m.Rcode = dns.RcodeNameError
|
m.Rcode = dns.RcodeNameError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
|
func (d *dnsRecords) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetReply(r)
|
m.SetReply(r)
|
||||||
m.Compress = false
|
m.Compress = false
|
||||||
@@ -373,6 +163,21 @@ func (d *dnsServer) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
w.WriteMsg(m)
|
w.WriteMsg(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dnsMain(l *logrus.Logger, cs *CertState, hostMap *HostMap, c *config.C) func() {
|
||||||
|
dnsR = newDnsRecords(l, cs, hostMap)
|
||||||
|
|
||||||
|
// attach request handler func
|
||||||
|
dns.HandleFunc(".", dnsR.handleDnsRequest)
|
||||||
|
|
||||||
|
c.RegisterReloadCallback(func(c *config.C) {
|
||||||
|
reloadDns(l, c)
|
||||||
|
})
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
startDns(l, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getDnsServerAddr(c *config.C) string {
|
func getDnsServerAddr(c *config.C) string {
|
||||||
dnsHost := strings.TrimSpace(c.GetString("lighthouse.dns.host", ""))
|
dnsHost := strings.TrimSpace(c.GetString("lighthouse.dns.host", ""))
|
||||||
// Old guidance was to provide the literal `[::]` in `lighthouse.dns.host` but that won't resolve.
|
// Old guidance was to provide the literal `[::]` in `lighthouse.dns.host` but that won't resolve.
|
||||||
@@ -381,3 +186,25 @@ func getDnsServerAddr(c *config.C) string {
|
|||||||
}
|
}
|
||||||
return net.JoinHostPort(dnsHost, strconv.Itoa(c.GetInt("lighthouse.dns.port", 53)))
|
return net.JoinHostPort(dnsHost, strconv.Itoa(c.GetInt("lighthouse.dns.port", 53)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startDns(l *logrus.Logger, c *config.C) {
|
||||||
|
dnsAddr = getDnsServerAddr(c)
|
||||||
|
dnsServer = &dns.Server{Addr: dnsAddr, Net: "udp"}
|
||||||
|
l.WithField("dnsListener", dnsAddr).Info("Starting DNS responder")
|
||||||
|
err := dnsServer.ListenAndServe()
|
||||||
|
defer dnsServer.Shutdown()
|
||||||
|
if err != nil {
|
||||||
|
l.Errorf("Failed to start server: %s\n ", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadDns(l *logrus.Logger, c *config.C) {
|
||||||
|
if dnsAddr == getDnsServerAddr(c) {
|
||||||
|
l.Debug("No DNS server config change detected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Debug("Restarting DNS server")
|
||||||
|
dnsServer.Shutdown()
|
||||||
|
go startDns(l, c)
|
||||||
|
}
|
||||||
|
|||||||
+3
-270
@@ -1,43 +1,19 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type stubDNSWriter struct{}
|
|
||||||
|
|
||||||
func (stubDNSWriter) LocalAddr() net.Addr { return &net.UDPAddr{} }
|
|
||||||
func (stubDNSWriter) RemoteAddr() net.Addr {
|
|
||||||
return &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5353}
|
|
||||||
}
|
|
||||||
func (stubDNSWriter) Write([]byte) (int, error) { return 0, nil }
|
|
||||||
func (stubDNSWriter) WriteMsg(*dns.Msg) error { return nil }
|
|
||||||
func (stubDNSWriter) Close() error { return nil }
|
|
||||||
func (stubDNSWriter) TsigStatus() error { return nil }
|
|
||||||
func (stubDNSWriter) TsigTimersOnly(bool) {}
|
|
||||||
func (stubDNSWriter) Hijack() {}
|
|
||||||
|
|
||||||
func TestParsequery(t *testing.T) {
|
func TestParsequery(t *testing.T) {
|
||||||
l := slog.New(slog.DiscardHandler)
|
l := logrus.New()
|
||||||
hostMap := &HostMap{}
|
hostMap := &HostMap{}
|
||||||
ds := &dnsServer{
|
ds := newDnsRecords(l, &CertState{}, hostMap)
|
||||||
l: l,
|
|
||||||
dnsMap4: make(map[string]netip.Addr),
|
|
||||||
dnsMap6: make(map[string]netip.Addr),
|
|
||||||
hostMap: hostMap,
|
|
||||||
}
|
|
||||||
ds.enabled.Store(true)
|
|
||||||
addrs := []netip.Addr{
|
addrs := []netip.Addr{
|
||||||
netip.MustParseAddr("1.2.3.4"),
|
netip.MustParseAddr("1.2.3.4"),
|
||||||
netip.MustParseAddr("1.2.3.5"),
|
netip.MustParseAddr("1.2.3.5"),
|
||||||
@@ -45,56 +21,18 @@ func TestParsequery(t *testing.T) {
|
|||||||
netip.MustParseAddr("fd01::25"),
|
netip.MustParseAddr("fd01::25"),
|
||||||
}
|
}
|
||||||
ds.Add("test.com.com", addrs)
|
ds.Add("test.com.com", addrs)
|
||||||
ds.Add("v4only.com.com", []netip.Addr{netip.MustParseAddr("1.2.3.6")})
|
|
||||||
ds.Add("v6only.com.com", []netip.Addr{netip.MustParseAddr("fd01::26")})
|
|
||||||
|
|
||||||
m := &dns.Msg{}
|
m := &dns.Msg{}
|
||||||
m.SetQuestion("test.com.com", dns.TypeA)
|
m.SetQuestion("test.com.com", dns.TypeA)
|
||||||
ds.parseQuery(m, nil)
|
ds.parseQuery(m, nil)
|
||||||
assert.NotNil(t, m.Answer)
|
assert.NotNil(t, m.Answer)
|
||||||
assert.Equal(t, "1.2.3.4", m.Answer[0].(*dns.A).A.String())
|
assert.Equal(t, "1.2.3.4", m.Answer[0].(*dns.A).A.String())
|
||||||
assert.Equal(t, dns.RcodeSuccess, m.Rcode)
|
|
||||||
|
|
||||||
m = &dns.Msg{}
|
m = &dns.Msg{}
|
||||||
m.SetQuestion("test.com.com", dns.TypeAAAA)
|
m.SetQuestion("test.com.com", dns.TypeAAAA)
|
||||||
ds.parseQuery(m, nil)
|
ds.parseQuery(m, nil)
|
||||||
assert.NotNil(t, m.Answer)
|
assert.NotNil(t, m.Answer)
|
||||||
assert.Equal(t, "fd01::24", m.Answer[0].(*dns.AAAA).AAAA.String())
|
assert.Equal(t, "fd01::24", m.Answer[0].(*dns.AAAA).AAAA.String())
|
||||||
assert.Equal(t, dns.RcodeSuccess, m.Rcode)
|
|
||||||
|
|
||||||
// A known name with no record of the requested type should return NODATA
|
|
||||||
// (NOERROR with empty answer), not NXDOMAIN.
|
|
||||||
m = &dns.Msg{}
|
|
||||||
m.SetQuestion("v4only.com.com", dns.TypeAAAA)
|
|
||||||
ds.parseQuery(m, nil)
|
|
||||||
assert.Empty(t, m.Answer)
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, m.Rcode)
|
|
||||||
|
|
||||||
m = &dns.Msg{}
|
|
||||||
m.SetQuestion("v6only.com.com", dns.TypeA)
|
|
||||||
ds.parseQuery(m, nil)
|
|
||||||
assert.Empty(t, m.Answer)
|
|
||||||
assert.Equal(t, dns.RcodeSuccess, m.Rcode)
|
|
||||||
|
|
||||||
// An unknown name should still return NXDOMAIN.
|
|
||||||
m = &dns.Msg{}
|
|
||||||
m.SetQuestion("unknown.com.com", dns.TypeA)
|
|
||||||
ds.parseQuery(m, nil)
|
|
||||||
assert.Empty(t, m.Answer)
|
|
||||||
assert.Equal(t, dns.RcodeNameError, m.Rcode)
|
|
||||||
|
|
||||||
// short lookups should not fail
|
|
||||||
m = &dns.Msg{}
|
|
||||||
m.Question = []dns.Question{{Name: "", Qtype: dns.TypeTXT, Qclass: dns.ClassINET}}
|
|
||||||
ds.parseQuery(m, stubDNSWriter{})
|
|
||||||
assert.Empty(t, m.Answer)
|
|
||||||
assert.Equal(t, dns.RcodeNameError, m.Rcode)
|
|
||||||
|
|
||||||
m = &dns.Msg{}
|
|
||||||
m.Question = []dns.Question{{Name: ".", Qtype: dns.TypeTXT, Qclass: dns.ClassINET}}
|
|
||||||
ds.parseQuery(m, stubDNSWriter{})
|
|
||||||
assert.Empty(t, m.Answer)
|
|
||||||
assert.Equal(t, dns.RcodeNameError, m.Rcode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_getDnsServerAddr(t *testing.T) {
|
func Test_getDnsServerAddr(t *testing.T) {
|
||||||
@@ -133,208 +71,3 @@ func Test_getDnsServerAddr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.Equal(t, "[::]:1", getDnsServerAddr(c))
|
assert.Equal(t, "[::]:1", getDnsServerAddr(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestDnsServer(t *testing.T) (*dnsServer, *config.C) {
|
|
||||||
t.Helper()
|
|
||||||
sl := slog.New(slog.DiscardHandler)
|
|
||||||
ds := &dnsServer{
|
|
||||||
l: sl,
|
|
||||||
ctx: context.Background(),
|
|
||||||
dnsMap4: make(map[string]netip.Addr),
|
|
||||||
dnsMap6: make(map[string]netip.Addr),
|
|
||||||
hostMap: &HostMap{},
|
|
||||||
}
|
|
||||||
ds.mux = dns.NewServeMux()
|
|
||||||
ds.mux.HandleFunc(".", ds.handleDnsRequest)
|
|
||||||
return ds, config.NewC(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDnsConfig(c *config.C, host string, port string, amLighthouse, serveDns bool) {
|
|
||||||
c.Settings["lighthouse"] = map[string]any{
|
|
||||||
"am_lighthouse": amLighthouse,
|
|
||||||
"serve_dns": serveDns,
|
|
||||||
"dns": map[string]any{
|
|
||||||
"host": host,
|
|
||||||
"port": port,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_reload_initial_disabled(t *testing.T) {
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", "0", true, false)
|
|
||||||
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
assert.False(t, ds.enabled.Load())
|
|
||||||
assert.Equal(t, "127.0.0.1:0", ds.addr)
|
|
||||||
assert.Nil(t, ds.server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_reload_initial_enabled(t *testing.T) {
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", "0", true, true)
|
|
||||||
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
assert.True(t, ds.enabled.Load())
|
|
||||||
assert.Equal(t, "127.0.0.1:0", ds.addr)
|
|
||||||
// initial never starts a runner; that's Control.Start's job
|
|
||||||
assert.Nil(t, ds.server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_reload_initial_serveDnsWithoutLighthouse(t *testing.T) {
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", "0", false, true)
|
|
||||||
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
// Wants DNS but isn't a lighthouse: gated off, no runner.
|
|
||||||
assert.False(t, ds.enabled.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_reload_sameAddr_noOp(t *testing.T) {
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", "0", true, true)
|
|
||||||
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
// No server running yet, no addr change. Reload should not spawn anything.
|
|
||||||
require.NoError(t, ds.reload(c, false))
|
|
||||||
assert.True(t, ds.enabled.Load())
|
|
||||||
assert.Nil(t, ds.server)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_StartStop_lifecycle(t *testing.T) {
|
|
||||||
// Bind to a real (random) UDP port so we exercise the actual
|
|
||||||
// ListenAndServe + Shutdown plumbing including the started-chan race fix.
|
|
||||||
port := freeUDPPort(t)
|
|
||||||
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", port, true, true)
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
ds.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
waitFor(t, func() bool {
|
|
||||||
ds.serverMu.Lock()
|
|
||||||
started := ds.started
|
|
||||||
ds.serverMu.Unlock()
|
|
||||||
if started == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-started:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ds.Stop()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after Stop")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_Stop_beforeBind_doesNotHang(t *testing.T) {
|
|
||||||
// Stop called immediately after Start should not deadlock even if bind
|
|
||||||
// hasn't completed yet. This exercises the started-chan close-on-bind-fail
|
|
||||||
// path: by binding to an obviously bad port (privileged) we get a fast
|
|
||||||
// bind error before NotifyStartedFunc fires.
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
// Use a port that should fail to bind (negative would be invalid, use a
|
|
||||||
// host that won't resolve to ensure listenUDP fails quickly).
|
|
||||||
setDnsConfig(c, "256.256.256.256", "53", true, true)
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
ds.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Give Start a moment to attempt the bind and fail.
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
// Bind failed and Start returned; Stop should be a no-op.
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("Start did not return after a bad bind")
|
|
||||||
}
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
ds.Stop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("Stop hung after a failed bind")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDnsServer_reload_disable_stopsRunningServer(t *testing.T) {
|
|
||||||
port := freeUDPPort(t)
|
|
||||||
ds, c := newTestDnsServer(t)
|
|
||||||
setDnsConfig(c, "127.0.0.1", port, true, true)
|
|
||||||
require.NoError(t, ds.reload(c, true))
|
|
||||||
|
|
||||||
startReturned := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
ds.Start()
|
|
||||||
close(startReturned)
|
|
||||||
}()
|
|
||||||
waitForBind(t, ds)
|
|
||||||
|
|
||||||
// Toggle serve_dns off; reload should shut the running server down.
|
|
||||||
setDnsConfig(c, "127.0.0.1", port, true, false)
|
|
||||||
require.NoError(t, ds.reload(c, false))
|
|
||||||
select {
|
|
||||||
case <-startReturned:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after reload disabled DNS")
|
|
||||||
}
|
|
||||||
assert.False(t, ds.enabled.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func freeUDPPort(t *testing.T) string {
|
|
||||||
t.Helper()
|
|
||||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
port := conn.LocalAddr().(*net.UDPAddr).Port
|
|
||||||
require.NoError(t, conn.Close())
|
|
||||||
return strconv.Itoa(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForBind(t *testing.T, ds *dnsServer) {
|
|
||||||
t.Helper()
|
|
||||||
waitFor(t, func() bool {
|
|
||||||
ds.serverMu.Lock()
|
|
||||||
started := ds.started
|
|
||||||
ds.serverMu.Unlock()
|
|
||||||
if started == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-started:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitFor(t *testing.T, cond func() bool) {
|
|
||||||
t.Helper()
|
|
||||||
deadline := time.Now().Add(5 * time.Second)
|
|
||||||
for time.Now().Before(deadline) {
|
|
||||||
if cond() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
t.Fatal("timed out waiting for condition")
|
|
||||||
}
|
|
||||||
|
|||||||
+23
-87
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/cert_test"
|
"github.com/slackhq/nebula/cert_test"
|
||||||
@@ -748,6 +749,7 @@ func TestStage1RaceRelays2(t *testing.T) {
|
|||||||
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me ", "10.128.0.1/24", m{"relay": m{"use_relays": true}})
|
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me ", "10.128.0.1/24", m{"relay": m{"use_relays": true}})
|
||||||
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "relay ", "10.128.0.128/24", m{"relay": m{"am_relay": true}})
|
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "relay ", "10.128.0.128/24", m{"relay": m{"am_relay": true}})
|
||||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them ", "10.128.0.2/24", m{"relay": m{"use_relays": true}})
|
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them ", "10.128.0.2/24", m{"relay": m{"use_relays": true}})
|
||||||
|
l := NewTestLogger()
|
||||||
|
|
||||||
// Teach my how to get to the relay and that their can be reached via the relay
|
// Teach my how to get to the relay and that their can be reached via the relay
|
||||||
myControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)
|
myControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)
|
||||||
@@ -769,41 +771,49 @@ func TestStage1RaceRelays2(t *testing.T) {
|
|||||||
theirControl.Start()
|
theirControl.Start()
|
||||||
|
|
||||||
r.Log("Get a tunnel between me and relay")
|
r.Log("Get a tunnel between me and relay")
|
||||||
|
l.Info("Get a tunnel between me and relay")
|
||||||
assertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)
|
assertTunnel(t, myVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), myControl, relayControl, r)
|
||||||
|
|
||||||
r.Log("Get a tunnel between them and relay")
|
r.Log("Get a tunnel between them and relay")
|
||||||
|
l.Info("Get a tunnel between them and relay")
|
||||||
assertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)
|
assertTunnel(t, theirVpnIpNet[0].Addr(), relayVpnIpNet[0].Addr(), theirControl, relayControl, r)
|
||||||
|
|
||||||
r.Log("Trigger a handshake from both them and me via relay to them and me")
|
r.Log("Trigger a handshake from both them and me via relay to them and me")
|
||||||
|
l.Info("Trigger a handshake from both them and me via relay to them and me")
|
||||||
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
myControl.InjectTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me"))
|
||||||
theirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte("Hi from them"))
|
theirControl.InjectTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte("Hi from them"))
|
||||||
|
|
||||||
//r.RouteUntilAfterMsgType(myControl, header.Control, header.MessageNone)
|
//r.RouteUntilAfterMsgType(myControl, header.Control, header.MessageNone)
|
||||||
//r.RouteUntilAfterMsgType(theirControl, header.Control, header.MessageNone)
|
//r.RouteUntilAfterMsgType(theirControl, header.Control, header.MessageNone)
|
||||||
|
|
||||||
r.Log("Wait for a packet from them to me; myControl")
|
r.Log("Wait for a packet from them to me")
|
||||||
|
l.Info("Wait for a packet from them to me; myControl")
|
||||||
r.RouteForAllUntilTxTun(myControl)
|
r.RouteForAllUntilTxTun(myControl)
|
||||||
r.Log("Wait for a packet from them to me; theirControl")
|
l.Info("Wait for a packet from them to me; theirControl")
|
||||||
r.RouteForAllUntilTxTun(theirControl)
|
r.RouteForAllUntilTxTun(theirControl)
|
||||||
|
|
||||||
r.Log("Assert the tunnel works")
|
r.Log("Assert the tunnel works")
|
||||||
|
l.Info("Assert the tunnel works")
|
||||||
assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
||||||
|
|
||||||
t.Log("Wait until we remove extra tunnels")
|
t.Log("Wait until we remove extra tunnels")
|
||||||
t.Logf("Waiting for hostinfos to be removed... myControl=%d theirControl=%d relayControl=%d",
|
l.Info("Wait until we remove extra tunnels")
|
||||||
len(myControl.GetHostmap().Indexes),
|
l.WithFields(
|
||||||
len(theirControl.GetHostmap().Indexes),
|
logrus.Fields{
|
||||||
len(relayControl.GetHostmap().Indexes),
|
"myControl": len(myControl.GetHostmap().Indexes),
|
||||||
)
|
"theirControl": len(theirControl.GetHostmap().Indexes),
|
||||||
|
"relayControl": len(relayControl.GetHostmap().Indexes),
|
||||||
|
}).Info("Waiting for hostinfos to be removed...")
|
||||||
hostInfos := len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)
|
hostInfos := len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)
|
||||||
retries := 60
|
retries := 60
|
||||||
for hostInfos > 6 && retries > 0 {
|
for hostInfos > 6 && retries > 0 {
|
||||||
hostInfos = len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)
|
hostInfos = len(myControl.GetHostmap().Indexes) + len(theirControl.GetHostmap().Indexes) + len(relayControl.GetHostmap().Indexes)
|
||||||
t.Logf("Waiting for hostinfos to be removed... myControl=%d theirControl=%d relayControl=%d",
|
l.WithFields(
|
||||||
len(myControl.GetHostmap().Indexes),
|
logrus.Fields{
|
||||||
len(theirControl.GetHostmap().Indexes),
|
"myControl": len(myControl.GetHostmap().Indexes),
|
||||||
len(relayControl.GetHostmap().Indexes),
|
"theirControl": len(theirControl.GetHostmap().Indexes),
|
||||||
)
|
"relayControl": len(relayControl.GetHostmap().Indexes),
|
||||||
|
}).Info("Waiting for hostinfos to be removed...")
|
||||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
||||||
t.Log("Connection manager hasn't ticked yet")
|
t.Log("Connection manager hasn't ticked yet")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -811,6 +821,7 @@ func TestStage1RaceRelays2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.Log("Assert the tunnel works")
|
r.Log("Assert the tunnel works")
|
||||||
|
l.Info("Assert the tunnel works")
|
||||||
assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
assertTunnel(t, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), theirControl, myControl, r)
|
||||||
|
|
||||||
myControl.Stop()
|
myControl.Stop()
|
||||||
@@ -1358,81 +1369,6 @@ func TestV2NonPrimaryWithOffNetLighthouse(t *testing.T) {
|
|||||||
theirControl.Stop()
|
theirControl.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLighthouseUpdateOnReload(t *testing.T) {
|
|
||||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
|
||||||
|
|
||||||
// Create the lighthouse
|
|
||||||
lhControl, lhVpnIpNet, lhUdpAddr, _ := newSimpleServer(cert.Version2, ca, caKey, "lh", "10.128.0.1/24", m{"lighthouse": m{"am_lighthouse": true}})
|
|
||||||
|
|
||||||
// Create a client with NO lighthouse configured and a long update interval.
|
|
||||||
// The initial SendUpdate at startup will be a no-op since no lighthouses are known.
|
|
||||||
myControl, myVpnIpNet, _, myConfig := newSimpleServer(cert.Version2, ca, caKey, "me", "10.128.0.2/24", m{
|
|
||||||
"lighthouse": m{
|
|
||||||
"interval": 600,
|
|
||||||
"local_allow_list": m{
|
|
||||||
"10.0.0.0/24": true,
|
|
||||||
"::/0": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
r := router.NewR(t, lhControl, myControl)
|
|
||||||
defer r.RenderFlow()
|
|
||||||
|
|
||||||
lhControl.Start()
|
|
||||||
myControl.Start()
|
|
||||||
|
|
||||||
// Drain any startup packets (there should be none meaningful)
|
|
||||||
r.FlushAll()
|
|
||||||
|
|
||||||
// Verify lighthouse has no knowledge of the client
|
|
||||||
assert.Nil(t, lhControl.QueryLighthouse(myVpnIpNet[0].Addr()))
|
|
||||||
|
|
||||||
// Build a new config that adds the lighthouse
|
|
||||||
newSettings := make(m)
|
|
||||||
for k, v := range myConfig.Settings {
|
|
||||||
newSettings[k] = v
|
|
||||||
}
|
|
||||||
newSettings["static_host_map"] = m{
|
|
||||||
lhVpnIpNet[0].Addr().String(): []any{lhUdpAddr.String()},
|
|
||||||
}
|
|
||||||
newSettings["lighthouse"] = m{
|
|
||||||
"hosts": []any{lhVpnIpNet[0].Addr().String()},
|
|
||||||
"interval": 600,
|
|
||||||
"local_allow_list": m{
|
|
||||||
"10.0.0.0/24": true,
|
|
||||||
"::/0": false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
newCfg, err := yaml.Marshal(newSettings)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Reload the config. The lighthouse.hosts change triggers TriggerUpdate,
|
|
||||||
// which wakes the update worker. It calls SendUpdate, initiating a
|
|
||||||
// handshake to the new lighthouse and caching the HostUpdateNotification.
|
|
||||||
require.NoError(t, myConfig.ReloadConfigString(string(newCfg)))
|
|
||||||
|
|
||||||
// Route until the lighthouse receives the HostUpdateNotification.
|
|
||||||
// This covers: handshake stage 1, stage 2, then the cached update.
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
r.RouteForAllUntilAfterMsgTypeTo(lhControl, header.LightHouse, 0)
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("timed out waiting for lighthouse update after config reload")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify lighthouse now has the client's addresses
|
|
||||||
assert.NotNil(t, lhControl.QueryLighthouse(myVpnIpNet[0].Addr()))
|
|
||||||
|
|
||||||
r.RenderHostmaps("Final hostmaps", lhControl, myControl)
|
|
||||||
lhControl.Stop()
|
|
||||||
myControl.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGoodHandshakeUnsafeDest(t *testing.T) {
|
func TestGoodHandshakeUnsafeDest(t *testing.T) {
|
||||||
unsafePrefix := "192.168.6.0/24"
|
unsafePrefix := "192.168.6.0/24"
|
||||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version2, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
||||||
|
|||||||
+17
-25
@@ -4,6 +4,7 @@
|
|||||||
package e2e
|
package e2e
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,18 +12,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
"github.com/google/gopacket"
|
"github.com/google/gopacket"
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/cert_test"
|
"github.com/slackhq/nebula/cert_test"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/e2e/router"
|
"github.com/slackhq/nebula/e2e/router"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v3"
|
||||||
@@ -134,7 +132,8 @@ func newSimpleServerWithUdpAndUnsafeNetworks(v cert.Version, caCrt cert.Certific
|
|||||||
"port": udpAddr.Port(),
|
"port": udpAddr.Port(),
|
||||||
},
|
},
|
||||||
"logging": m{
|
"logging": m{
|
||||||
"level": testLogLevelName(),
|
"timestamp_format": fmt.Sprintf("%v 15:04:05.000000", name),
|
||||||
|
"level": l.Level.String(),
|
||||||
},
|
},
|
||||||
"timers": m{
|
"timers": m{
|
||||||
"pending_deletion_interval": 2,
|
"pending_deletion_interval": 2,
|
||||||
@@ -235,7 +234,8 @@ func newServer(caCrt []cert.Certificate, certs []cert.Certificate, key []byte, o
|
|||||||
"port": udpAddr.Port(),
|
"port": udpAddr.Port(),
|
||||||
},
|
},
|
||||||
"logging": m{
|
"logging": m{
|
||||||
"level": testLogLevelName(),
|
"timestamp_format": fmt.Sprintf("%v 15:04:05.000000", certs[0].Name()),
|
||||||
|
"level": l.Level.String(),
|
||||||
},
|
},
|
||||||
"timers": m{
|
"timers": m{
|
||||||
"pending_deletion_interval": 2,
|
"pending_deletion_interval": 2,
|
||||||
@@ -379,32 +379,24 @@ func getAddrs(ns []netip.Prefix) []netip.Addr {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestLogger() *slog.Logger {
|
func NewTestLogger() *logrus.Logger {
|
||||||
|
l := logrus.New()
|
||||||
|
|
||||||
v := os.Getenv("TEST_LOGS")
|
v := os.Getenv("TEST_LOGS")
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
l.SetOutput(io.Discard)
|
||||||
|
l.SetLevel(logrus.PanicLevel)
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
level := slog.LevelInfo
|
|
||||||
switch v {
|
switch v {
|
||||||
case "2":
|
case "2":
|
||||||
level = slog.LevelDebug
|
l.SetLevel(logrus.DebugLevel)
|
||||||
case "3":
|
case "3":
|
||||||
level = logging.LevelTrace
|
l.SetLevel(logrus.TraceLevel)
|
||||||
}
|
default:
|
||||||
return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level}))
|
l.SetLevel(logrus.InfoLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testLogLevelName returns the level name string accepted by logging.ApplyConfig
|
return l
|
||||||
// for the current TEST_LOGS setting. Kept in sync with NewTestLogger.
|
|
||||||
func testLogLevelName() string {
|
|
||||||
switch os.Getenv("TEST_LOGS") {
|
|
||||||
case "2":
|
|
||||||
return "debug"
|
|
||||||
case "3":
|
|
||||||
return "trace"
|
|
||||||
case "":
|
|
||||||
return "info"
|
|
||||||
}
|
|
||||||
return "info"
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -292,21 +292,24 @@ tun:
|
|||||||
|
|
||||||
# Configure logging level
|
# Configure logging level
|
||||||
logging:
|
logging:
|
||||||
# trace, debug, info, warn, or error. Default is info and is reloadable.
|
# panic, fatal, error, warning, info, or debug. Default is info and is reloadable.
|
||||||
# fatal and panic are accepted for backwards compatibility and map to error.
|
#NOTE: Debug mode can log remotely controlled/untrusted data which can quickly fill a disk in some
|
||||||
#NOTE: Debug and trace modes can log remotely controlled/untrusted data which can quickly fill a disk in some
|
# scenarios. Debug logging is also CPU intensive and will decrease performance overall.
|
||||||
# scenarios. Debug and trace logging are also CPU intensive and will decrease performance overall.
|
# Only enable debug logging while actively investigating an issue.
|
||||||
# Only enable debug or trace logging while actively investigating an issue.
|
|
||||||
level: info
|
level: info
|
||||||
# json or text formats currently available. Default is text.
|
# json or text formats currently available. Default is text
|
||||||
format: text
|
format: text
|
||||||
# Disable timestamp logging. Useful when output is redirected to a logging system that already adds timestamps. Default is false.
|
# Disable timestamp logging. useful when output is redirected to logging system that already adds timestamps. Default is false
|
||||||
#disable_timestamp: true
|
#disable_timestamp: true
|
||||||
# Timestamps use RFC3339Nano ("2006-01-02T15:04:05.999999999Z07:00") and are not configurable.
|
# timestamp format is specified in Go time format, see:
|
||||||
|
# https://golang.org/pkg/time/#pkg-constants
|
||||||
|
# default when `format: json`: "2006-01-02T15:04:05Z07:00" (RFC3339)
|
||||||
|
# default when `format: text`:
|
||||||
|
# when TTY attached: seconds since beginning of execution
|
||||||
|
# otherwise: "2006-01-02T15:04:05Z07:00" (RFC3339)
|
||||||
|
# As an example, to log as RFC3339 with millisecond precision, set to:
|
||||||
|
#timestamp_format: "2006-01-02T15:04:05.000Z07:00"
|
||||||
|
|
||||||
# The stats section is reloadable. A HUP may change the backend, toggle stats
|
|
||||||
# on or off, switch the listen/host address, or pick up new DNS for the
|
|
||||||
# configured graphite host.
|
|
||||||
#stats:
|
#stats:
|
||||||
#type: graphite
|
#type: graphite
|
||||||
#prefix: nebula
|
#prefix: nebula
|
||||||
@@ -324,12 +327,10 @@ logging:
|
|||||||
# enables counter metrics for meta packets
|
# enables counter metrics for meta packets
|
||||||
# e.g.: `messages.tx.handshake`
|
# e.g.: `messages.tx.handshake`
|
||||||
# NOTE: `message.{tx,rx}.recv_error` is always emitted
|
# NOTE: `message.{tx,rx}.recv_error` is always emitted
|
||||||
# Not reloadable.
|
|
||||||
#message_metrics: false
|
#message_metrics: false
|
||||||
|
|
||||||
# enables detailed counter metrics for lighthouse packets
|
# enables detailed counter metrics for lighthouse packets
|
||||||
# e.g.: `lighthouse.rx.HostQuery`
|
# e.g.: `lighthouse.rx.HostQuery`
|
||||||
# Not reloadable.
|
|
||||||
#lighthouse_metrics: false
|
#lighthouse_metrics: false
|
||||||
|
|
||||||
# Handshake Manager Settings
|
# Handshake Manager Settings
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
"github.com/slackhq/nebula/service"
|
"github.com/slackhq/nebula/service"
|
||||||
)
|
)
|
||||||
@@ -64,7 +64,8 @@ pki:
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logging.NewLogger(os.Stdout)
|
logger := logrus.New()
|
||||||
|
logger.Out = os.Stdout
|
||||||
|
|
||||||
ctrl, err := nebula.Main(&cfg, false, "custom-app", logger, overlay.NewUserDeviceFromConfig)
|
ctrl, err := nebula.Main(&cfg, false, "custom-app", logger, overlay.NewUserDeviceFromConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+28
-37
@@ -1,13 +1,11 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -18,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
@@ -68,7 +67,7 @@ type Firewall struct {
|
|||||||
incomingMetrics firewallMetrics
|
incomingMetrics firewallMetrics
|
||||||
outgoingMetrics firewallMetrics
|
outgoingMetrics firewallMetrics
|
||||||
|
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type firewallMetrics struct {
|
type firewallMetrics struct {
|
||||||
@@ -132,7 +131,7 @@ type firewallLocalCIDR struct {
|
|||||||
|
|
||||||
// NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.
|
// NewFirewall creates a new Firewall object. A TimerWheel is created for you from the provided timeouts.
|
||||||
// The certificate provided should be the highest version loaded in memory.
|
// The certificate provided should be the highest version loaded in memory.
|
||||||
func NewFirewall(l *slog.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {
|
func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Duration, c cert.Certificate) *Firewall {
|
||||||
//TODO: error on 0 duration
|
//TODO: error on 0 duration
|
||||||
var tmin, tmax time.Duration
|
var tmin, tmax time.Duration
|
||||||
|
|
||||||
@@ -192,7 +191,7 @@ func NewFirewall(l *slog.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.Dur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFirewallFromConfig(l *slog.Logger, cs *CertState, c *config.C) (*Firewall, error) {
|
func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firewall, error) {
|
||||||
certificate := cs.getCertificate(cert.Version2)
|
certificate := cs.getCertificate(cert.Version2)
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
certificate = cs.getCertificate(cert.Version1)
|
certificate = cs.getCertificate(cert.Version1)
|
||||||
@@ -220,7 +219,7 @@ func NewFirewallFromConfig(l *slog.Logger, cs *CertState, c *config.C) (*Firewal
|
|||||||
case "drop":
|
case "drop":
|
||||||
fw.InSendReject = false
|
fw.InSendReject = false
|
||||||
default:
|
default:
|
||||||
l.Warn("invalid firewall.inbound_action, defaulting to `drop`", "action", inboundAction)
|
l.WithField("action", inboundAction).Warn("invalid firewall.inbound_action, defaulting to `drop`")
|
||||||
fw.InSendReject = false
|
fw.InSendReject = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +230,7 @@ func NewFirewallFromConfig(l *slog.Logger, cs *CertState, c *config.C) (*Firewal
|
|||||||
case "drop":
|
case "drop":
|
||||||
fw.OutSendReject = false
|
fw.OutSendReject = false
|
||||||
default:
|
default:
|
||||||
l.Warn("invalid firewall.outbound_action, defaulting to `drop`", "action", outboundAction)
|
l.WithField("action", outboundAction).Warn("invalid firewall.outbound_action, defaulting to `drop`")
|
||||||
fw.OutSendReject = false
|
fw.OutSendReject = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +268,7 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
||||||
//ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
|
//ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
|
||||||
if startPort != firewall.PortAny {
|
if startPort != firewall.PortAny {
|
||||||
f.l.Warn("ignoring port specification for ICMP firewall rule", "startPort", startPort)
|
f.l.WithField("startPort", startPort).Warn("ignoring port specification for ICMP firewall rule")
|
||||||
}
|
}
|
||||||
startPort = firewall.PortAny
|
startPort = firewall.PortAny
|
||||||
endPort = firewall.PortAny
|
endPort = firewall.PortAny
|
||||||
@@ -291,9 +290,8 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
if !incoming {
|
if !incoming {
|
||||||
direction = "outgoing"
|
direction = "outgoing"
|
||||||
}
|
}
|
||||||
f.l.Info("Firewall rule added",
|
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
||||||
"firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha},
|
Info("Firewall rule added")
|
||||||
)
|
|
||||||
|
|
||||||
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
||||||
}
|
}
|
||||||
@@ -316,7 +314,7 @@ func (f *Firewall) GetRuleHashes() string {
|
|||||||
return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
|
return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddFirewallRulesFromConfig(l *slog.Logger, inbound bool, c *config.C, fw FirewallInterface) error {
|
func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw FirewallInterface) error {
|
||||||
var table string
|
var table string
|
||||||
if inbound {
|
if inbound {
|
||||||
table = "firewall.inbound"
|
table = "firewall.inbound"
|
||||||
@@ -374,7 +372,7 @@ func AddFirewallRulesFromConfig(l *slog.Logger, inbound bool, c *config.C, fw Fi
|
|||||||
startPort = firewall.PortAny
|
startPort = firewall.PortAny
|
||||||
endPort = firewall.PortAny
|
endPort = firewall.PortAny
|
||||||
if sPort != "" {
|
if sPort != "" {
|
||||||
l.Warn("ignoring port specification for ICMP firewall rule", "port", sPort)
|
l.WithField("port", sPort).Warn("ignoring port specification for ICMP firewall rule")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
||||||
@@ -398,11 +396,7 @@ func AddFirewallRulesFromConfig(l *slog.Logger, inbound bool, c *config.C, fw Fi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if warning := r.sanity(); warning != nil {
|
if warning := r.sanity(); warning != nil {
|
||||||
l.Warn("firewall rule sanity check",
|
l.Warnf("%s rule #%v; %s", table, i, warning)
|
||||||
"table", table,
|
|
||||||
"rule", i,
|
|
||||||
"warning", warning,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
|
err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
|
||||||
@@ -534,26 +528,26 @@ func (f *Firewall) inConns(fp firewall.Packet, h *HostInfo, caPool *cert.CAPool,
|
|||||||
|
|
||||||
// We now know which firewall table to check against
|
// We now know which firewall table to check against
|
||||||
if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
|
if !table.match(fp, c.incoming, h.ConnectionState.peerCert, caPool) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
h.logger(f.l).Debug("dropping old conntrack entry, does not match new ruleset",
|
h.logger(f.l).
|
||||||
"fwPacket", fp,
|
WithField("fwPacket", fp).
|
||||||
"incoming", c.incoming,
|
WithField("incoming", c.incoming).
|
||||||
"rulesVersion", f.rulesVersion,
|
WithField("rulesVersion", f.rulesVersion).
|
||||||
"oldRulesVersion", c.rulesVersion,
|
WithField("oldRulesVersion", c.rulesVersion).
|
||||||
)
|
Debugln("dropping old conntrack entry, does not match new ruleset")
|
||||||
}
|
}
|
||||||
delete(conntrack.Conns, fp)
|
delete(conntrack.Conns, fp)
|
||||||
conntrack.Unlock()
|
conntrack.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
h.logger(f.l).Debug("keeping old conntrack entry, does match new ruleset",
|
h.logger(f.l).
|
||||||
"fwPacket", fp,
|
WithField("fwPacket", fp).
|
||||||
"incoming", c.incoming,
|
WithField("incoming", c.incoming).
|
||||||
"rulesVersion", f.rulesVersion,
|
WithField("rulesVersion", f.rulesVersion).
|
||||||
"oldRulesVersion", c.rulesVersion,
|
WithField("oldRulesVersion", c.rulesVersion).
|
||||||
)
|
Debugln("keeping old conntrack entry, does match new ruleset")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.rulesVersion = f.rulesVersion
|
c.rulesVersion = f.rulesVersion
|
||||||
@@ -941,7 +935,7 @@ type rule struct {
|
|||||||
CASha string
|
CASha string
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertRule(l *slog.Logger, p any, table string, i int) (rule, error) {
|
func convertRule(l *logrus.Logger, p any, table string, i int) (rule, error) {
|
||||||
r := rule{}
|
r := rule{}
|
||||||
|
|
||||||
m, ok := p.(map[string]any)
|
m, ok := p.(map[string]any)
|
||||||
@@ -972,10 +966,7 @@ func convertRule(l *slog.Logger, p any, table string, i int) (rule, error) {
|
|||||||
return r, errors.New("group should contain a single value, an array with more than one entry was provided")
|
return r, errors.New("group should contain a single value, an array with more than one entry was provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Warn("group was an array with a single value, converting to simple value",
|
l.Warnf("%s rule #%v; group was an array with a single value, converting to simple value", table, i)
|
||||||
"table", table,
|
|
||||||
"rule", i,
|
|
||||||
)
|
|
||||||
m["group"] = v[0]
|
m["group"] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-17
@@ -1,10 +1,10 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConntrackCache is used as a local routine cache to know if a given flow
|
// ConntrackCache is used as a local routine cache to know if a given flow
|
||||||
@@ -15,49 +15,41 @@ type ConntrackCacheTicker struct {
|
|||||||
cacheV uint64
|
cacheV uint64
|
||||||
cacheTick atomic.Uint64
|
cacheTick atomic.Uint64
|
||||||
|
|
||||||
l *slog.Logger
|
|
||||||
cache ConntrackCache
|
cache ConntrackCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConntrackCacheTicker(ctx context.Context, l *slog.Logger, d time.Duration) *ConntrackCacheTicker {
|
func NewConntrackCacheTicker(d time.Duration) *ConntrackCacheTicker {
|
||||||
if d == 0 {
|
if d == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &ConntrackCacheTicker{
|
c := &ConntrackCacheTicker{
|
||||||
l: l,
|
|
||||||
cache: ConntrackCache{},
|
cache: ConntrackCache{},
|
||||||
}
|
}
|
||||||
|
|
||||||
go c.tick(ctx, d)
|
go c.tick(d)
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConntrackCacheTicker) tick(ctx context.Context, d time.Duration) {
|
func (c *ConntrackCacheTicker) tick(d time.Duration) {
|
||||||
t := time.NewTicker(d)
|
|
||||||
defer t.Stop()
|
|
||||||
for {
|
for {
|
||||||
select {
|
time.Sleep(d)
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
c.cacheTick.Add(1)
|
c.cacheTick.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Get checks if the cache ticker has moved to the next version before returning
|
// Get checks if the cache ticker has moved to the next version before returning
|
||||||
// the map. If it has moved, we reset the map.
|
// the map. If it has moved, we reset the map.
|
||||||
func (c *ConntrackCacheTicker) Get() ConntrackCache {
|
func (c *ConntrackCacheTicker) Get(l *logrus.Logger) ConntrackCache {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if tick := c.cacheTick.Load(); tick != c.cacheV {
|
if tick := c.cacheTick.Load(); tick != c.cacheV {
|
||||||
c.cacheV = tick
|
c.cacheV = tick
|
||||||
if ll := len(c.cache); ll > 0 {
|
if ll := len(c.cache); ll > 0 {
|
||||||
if c.l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level == logrus.DebugLevel {
|
||||||
c.l.Debug("resetting conntrack cache", "len", ll)
|
l.WithField("len", ll).Debug("resetting conntrack cache")
|
||||||
}
|
}
|
||||||
c.cache = make(ConntrackCache, ll)
|
c.cache = make(ConntrackCache, ll)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The tests below pin the log format produced by ConntrackCacheTicker.Get
|
|
||||||
// so changes cannot silently break what operators are grepping for. The
|
|
||||||
// ticker's internal state (cache + cacheTick) is poked directly to avoid
|
|
||||||
// racing a goroutine-driven tick in tests.
|
|
||||||
|
|
||||||
func newFixedTicker(t *testing.T, l *slog.Logger, cacheLen int) *ConntrackCacheTicker {
|
|
||||||
t.Helper()
|
|
||||||
c := &ConntrackCacheTicker{
|
|
||||||
l: l,
|
|
||||||
cache: make(ConntrackCache, cacheLen),
|
|
||||||
}
|
|
||||||
for i := 0; i < cacheLen; i++ {
|
|
||||||
c.cache[Packet{LocalPort: uint16(i) + 1}] = struct{}{}
|
|
||||||
}
|
|
||||||
c.cacheTick.Store(1) // cacheV starts at 0, so Get() takes the reset path
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConntrackCacheTicker_Get_TextFormat(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
l := test.NewLoggerWithOutputAndLevel(buf, slog.LevelDebug)
|
|
||||||
|
|
||||||
c := newFixedTicker(t, l, 3)
|
|
||||||
c.Get()
|
|
||||||
|
|
||||||
assert.Equal(t, "level=DEBUG msg=\"resetting conntrack cache\" len=3\n", buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConntrackCacheTicker_Get_JSONFormat(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
l := test.NewJSONLoggerWithOutput(buf, slog.LevelDebug)
|
|
||||||
|
|
||||||
c := newFixedTicker(t, l, 2)
|
|
||||||
c.Get()
|
|
||||||
|
|
||||||
assert.JSONEq(t, `{"level":"DEBUG","msg":"resetting conntrack cache","len":2}`, strings.TrimSpace(buf.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConntrackCacheTicker_Get_QuietBelowDebug(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
l := test.NewLoggerWithOutputAndLevel(buf, slog.LevelInfo)
|
|
||||||
|
|
||||||
c := newFixedTicker(t, l, 5)
|
|
||||||
c.Get()
|
|
||||||
|
|
||||||
assert.Empty(t, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConntrackCacheTicker_Get_QuietWhenCacheEmpty(t *testing.T) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
l := test.NewLoggerWithOutputAndLevel(buf, slog.LevelDebug)
|
|
||||||
|
|
||||||
c := newFixedTicker(t, l, 0)
|
|
||||||
c.Get()
|
|
||||||
|
|
||||||
assert.Empty(t, buf.String())
|
|
||||||
}
|
|
||||||
+55
-45
@@ -3,13 +3,13 @@ package nebula
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
|
||||||
"math"
|
"math"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
@@ -58,8 +58,9 @@ func TestNewFirewall(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_AddRule(t *testing.T) {
|
func TestFirewall_AddRule(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
|
|
||||||
c := &dummyCert{}
|
c := &dummyCert{}
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
@@ -176,8 +177,9 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop(t *testing.T) {
|
func TestFirewall_Drop(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||||
p := firewall.Packet{
|
p := firewall.Packet{
|
||||||
@@ -252,8 +254,9 @@ func TestFirewall_Drop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropV6(t *testing.T) {
|
func TestFirewall_DropV6(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
|
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
||||||
@@ -482,8 +485,9 @@ func BenchmarkFirewallTable_match(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop2(t *testing.T) {
|
func TestFirewall_Drop2(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||||
|
|
||||||
@@ -540,8 +544,9 @@ func TestFirewall_Drop2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop3(t *testing.T) {
|
func TestFirewall_Drop3(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||||
|
|
||||||
@@ -628,8 +633,9 @@ func TestFirewall_Drop3(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop3V6(t *testing.T) {
|
func TestFirewall_Drop3V6(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("fd00::/7"))
|
||||||
|
|
||||||
@@ -665,8 +671,9 @@ func TestFirewall_Drop3V6(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropConntrackReload(t *testing.T) {
|
func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||||
|
|
||||||
@@ -729,8 +736,9 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_ICMPPortBehavior(t *testing.T) {
|
func TestFirewall_ICMPPortBehavior(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
||||||
|
|
||||||
@@ -872,8 +880,9 @@ func TestFirewall_ICMPPortBehavior(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("192.0.2.1/24"))
|
myVpnNetworksTable.Insert(netip.MustParsePrefix("192.0.2.1/24"))
|
||||||
|
|
||||||
@@ -1036,25 +1045,25 @@ func TestNewFirewallFromConfig(t *testing.T) {
|
|||||||
cs, err := newCertState(cert.Version2, nil, c, false, cert.Curve_CURVE25519, nil)
|
cs, err := newCertState(cert.Version2, nil, c, false, cert.Curve_CURVE25519, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": "asdf"}
|
conf.Settings["firewall"] = map[string]any{"outbound": "asdf"}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound failed to parse, should be an array of rules")
|
require.EqualError(t, err, "firewall.outbound failed to parse, should be an array of rules")
|
||||||
|
|
||||||
// Test both port and code
|
// Test both port and code
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "code": "2"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "code": "2"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; only one of port or code should be provided")
|
require.EqualError(t, err, "firewall.outbound rule #0; only one of port or code should be provided")
|
||||||
|
|
||||||
// Test missing host, group, cidr, ca_name and ca_sha
|
// Test missing host, group, cidr, ca_name and ca_sha
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided")
|
require.EqualError(t, err, "firewall.outbound rule #0; at least one of host, group, cidr, local_cidr, ca_name, or ca_sha must be provided")
|
||||||
|
|
||||||
// Test code/port error
|
// Test code/port error
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
||||||
@@ -1064,25 +1073,25 @@ func TestNewFirewallFromConfig(t *testing.T) {
|
|||||||
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
||||||
|
|
||||||
// Test proto error
|
// Test proto error
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "host": "testh"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "host": "testh"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; proto was not understood; ``")
|
require.EqualError(t, err, "firewall.outbound rule #0; proto was not understood; ``")
|
||||||
|
|
||||||
// Test cidr parse error
|
// Test cidr parse error
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "cidr": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "cidr": "testh", "proto": "any"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
|
require.EqualError(t, err, "firewall.outbound rule #0; cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
|
||||||
|
|
||||||
// Test local_cidr parse error
|
// Test local_cidr parse error
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "local_cidr": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "1", "local_cidr": "testh", "proto": "any"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
|
require.EqualError(t, err, "firewall.outbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"testh\"): no '/'")
|
||||||
|
|
||||||
// Test both group and groups
|
// Test both group and groups
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a", "groups": []string{"b", "c"}}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a", "groups": []string{"b", "c"}}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.inbound rule #0; only one of group or groups should be defined, both provided")
|
require.EqualError(t, err, "firewall.inbound rule #0; only one of group or groups should be defined, both provided")
|
||||||
@@ -1091,35 +1100,35 @@ func TestNewFirewallFromConfig(t *testing.T) {
|
|||||||
func TestAddFirewallRulesFromConfig(t *testing.T) {
|
func TestAddFirewallRulesFromConfig(t *testing.T) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
// Test adding tcp rule
|
// Test adding tcp rule
|
||||||
conf := config.NewC(test.NewLogger())
|
conf := config.NewC(l)
|
||||||
mf := &mockFirewall{}
|
mf := &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "tcp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoTCP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding udp rule
|
// Test adding udp rule
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "udp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoUDP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding icmp rule
|
// Test adding icmp rule
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding icmp rule no port
|
// Test adding icmp rule no port
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding any rule
|
// Test adding any rule
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
@@ -1127,14 +1136,14 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
// Test adding rule with cidr
|
// Test adding rule with cidr
|
||||||
cidr := netip.MustParsePrefix("10.0.0.0/8")
|
cidr := netip.MustParsePrefix("10.0.0.0/8")
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr.String(), localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with local_cidr
|
// Test adding rule with local_cidr
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
@@ -1142,82 +1151,82 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
// Test adding rule with cidr ipv6
|
// Test adding rule with cidr ipv6
|
||||||
cidr6 := netip.MustParsePrefix("fd00::/8")
|
cidr6 := netip.MustParsePrefix("fd00::/8")
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6.String(), localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with any cidr
|
// Test adding rule with any cidr
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "any"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "any", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with junk cidr
|
// Test adding rule with junk cidr
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": "junk/junk"}}}
|
||||||
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
||||||
|
|
||||||
// Test adding rule with local_cidr ipv6
|
// Test adding rule with local_cidr ipv6
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: cidr6.String()}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with any local_cidr
|
// Test adding rule with any local_cidr
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "any"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, localIp: "any"}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with junk local_cidr
|
// Test adding rule with junk local_cidr
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": "junk/junk"}}}
|
||||||
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
require.EqualError(t, AddFirewallRulesFromConfig(l, true, conf, mf), "firewall.inbound rule #0; local_cidr did not parse; netip.ParsePrefix(\"junk/junk\"): ParseAddr(\"junk\"): unable to parse IP")
|
||||||
|
|
||||||
// Test adding rule with ca_sha
|
// Test adding rule with ca_sha
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_sha": "12312313123"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caSha: "12312313123"}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding rule with ca_name
|
// Test adding rule with ca_name
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "ca_name": "root01"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: "", localIp: "", caName: "root01"}, mf.lastCall)
|
||||||
|
|
||||||
// Test single group
|
// Test single group
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "group": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test single groups
|
// Test single groups
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test multiple AND groups
|
// Test multiple AND groups
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "groups": []string{"a", "b"}}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: []string{"a", "b"}, ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test Add error
|
// Test Add error
|
||||||
conf = config.NewC(test.NewLogger())
|
conf = config.NewC(l)
|
||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
mf.nextCallReturn = errors.New("test error")
|
mf.nextCallReturn = errors.New("test error")
|
||||||
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "host": "a"}}}
|
||||||
@@ -1225,8 +1234,9 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_convertRule(t *testing.T) {
|
func TestFirewall_convertRule(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
|
|
||||||
// Ensure group array of 1 is converted and a warning is printed
|
// Ensure group array of 1 is converted and a warning is printed
|
||||||
c := map[string]any{
|
c := map[string]any{
|
||||||
@@ -1234,9 +1244,7 @@ func TestFirewall_convertRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r, err := convertRule(l, c, "test", 1)
|
r, err := convertRule(l, c, "test", 1)
|
||||||
assert.Contains(t, ob.String(), "group was an array with a single value, converting to simple value")
|
assert.Contains(t, ob.String(), "test rule #1; group was an array with a single value, converting to simple value")
|
||||||
assert.Contains(t, ob.String(), "table=test")
|
|
||||||
assert.Contains(t, ob.String(), "rule=1")
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, []string{"group1"}, r.Groups)
|
assert.Equal(t, []string{"group1"}, r.Groups)
|
||||||
|
|
||||||
@@ -1262,8 +1270,9 @@ func TestFirewall_convertRule(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_convertRuleSanity(t *testing.T) {
|
func TestFirewall_convertRuleSanity(t *testing.T) {
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
|
|
||||||
noWarningPlease := []map[string]any{
|
noWarningPlease := []map[string]any{
|
||||||
{"group": "group1"},
|
{"group": "group1"},
|
||||||
@@ -1377,7 +1386,7 @@ type testsetup struct {
|
|||||||
fw *Firewall
|
fw *Firewall
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSetup(t *testing.T, l *slog.Logger, myPrefixes ...netip.Prefix) testsetup {
|
func newSetup(t *testing.T, l *logrus.Logger, myPrefixes ...netip.Prefix) testsetup {
|
||||||
c := dummyCert{
|
c := dummyCert{
|
||||||
name: "me",
|
name: "me",
|
||||||
networks: myPrefixes,
|
networks: myPrefixes,
|
||||||
@@ -1388,7 +1397,7 @@ func newSetup(t *testing.T, l *slog.Logger, myPrefixes ...netip.Prefix) testsetu
|
|||||||
return newSetupFromCert(t, l, c)
|
return newSetupFromCert(t, l, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSetupFromCert(t *testing.T, l *slog.Logger, c dummyCert) testsetup {
|
func newSetupFromCert(t *testing.T, l *logrus.Logger, c dummyCert) testsetup {
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
myVpnNetworksTable := new(bart.Lite)
|
||||||
for _, prefix := range c.Networks() {
|
for _, prefix := range c.Networks() {
|
||||||
myVpnNetworksTable.Insert(prefix)
|
myVpnNetworksTable.Insert(prefix)
|
||||||
@@ -1405,8 +1414,9 @@ func newSetupFromCert(t *testing.T, l *slog.Logger, c dummyCert) testsetup {
|
|||||||
|
|
||||||
func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
|
func TestFirewall_Drop_EnforceIPMatch(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
l := test.NewLogger()
|
||||||
ob := &bytes.Buffer{}
|
ob := &bytes.Buffer{}
|
||||||
l := test.NewLoggerWithOutput(ob)
|
l.SetOutput(ob)
|
||||||
|
|
||||||
myPrefix := netip.MustParsePrefix("1.1.1.1/8")
|
myPrefix := netip.MustParsePrefix("1.1.1.1/8")
|
||||||
// for now, it's okay that these are all "incoming", the logic this test tries to check doesn't care about in/out
|
// for now, it's okay that these are all "incoming", the logic this test tries to check doesn't care about in/out
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ require (
|
|||||||
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
github.com/nbrownus/go-metrics-prometheus v0.0.0-20210712211119-974a6260965f
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||||
|
github.com/sirupsen/logrus v1.9.4
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
|||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
|
||||||
|
|||||||
+208
-343
@@ -2,12 +2,11 @@ package nebula
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/flynn/noise"
|
"github.com/flynn/noise"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
)
|
)
|
||||||
@@ -19,11 +18,8 @@ import (
|
|||||||
func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
||||||
err := f.handshakeManager.allocateIndex(hh)
|
err := f.handshakeManager.allocateIndex(hh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to generate index",
|
f.l.WithError(err).WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).Error("Failed to generate index")
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,32 +39,28 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
|||||||
|
|
||||||
crt := cs.getCertificate(v)
|
crt := cs.getCertificate(v)
|
||||||
if crt == nil {
|
if crt == nil {
|
||||||
f.l.Error("Unable to handshake with host because no certificate is available",
|
f.l.WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
WithField("certVersion", v).
|
||||||
"certVersion", v,
|
Error("Unable to handshake with host because no certificate is available")
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
crtHs := cs.getHandshakeBytes(v)
|
crtHs := cs.getHandshakeBytes(v)
|
||||||
if crtHs == nil {
|
if crtHs == nil {
|
||||||
f.l.Error("Unable to handshake with host because no certificate handshake bytes is available",
|
f.l.WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
WithField("certVersion", v).
|
||||||
"certVersion", v,
|
Error("Unable to handshake with host because no certificate handshake bytes is available")
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, err := NewConnectionState(cs, crt, true, noise.HandshakeIX)
|
ci, err := NewConnectionState(f.l, cs, crt, true, noise.HandshakeIX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to create connection state",
|
f.l.WithError(err).WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
WithField("certVersion", v).
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
Error("Failed to create connection state")
|
||||||
"certVersion", v,
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
hh.hostinfo.ConnectionState = ci
|
hh.hostinfo.ConnectionState = ci
|
||||||
@@ -84,12 +76,9 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
|||||||
|
|
||||||
hsBytes, err := hs.Marshal()
|
hsBytes, err := hs.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to marshal handshake message",
|
f.l.WithError(err).WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"error", err,
|
WithField("certVersion", v).
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).Error("Failed to marshal handshake message")
|
||||||
"certVersion", v,
|
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +86,8 @@ func ixHandshakeStage0(f *Interface, hh *HandshakeHostInfo) bool {
|
|||||||
|
|
||||||
msg, _, _, err := ci.H.WriteMessage(h, hsBytes)
|
msg, _, _, err := ci.H.WriteMessage(h, hsBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to call noise.WriteMessage",
|
f.l.WithError(err).WithField("vpnAddrs", hh.hostinfo.vpnAddrs).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).Error("Failed to call noise.WriteMessage")
|
||||||
"vpnAddrs", hh.hostinfo.vpnAddrs,
|
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,21 +104,18 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
cs := f.pki.getCertState()
|
cs := f.pki.getCertState()
|
||||||
crt := cs.GetDefaultCertificate()
|
crt := cs.GetDefaultCertificate()
|
||||||
if crt == nil {
|
if crt == nil {
|
||||||
f.l.Error("Unable to handshake with host because no certificate is available",
|
f.l.WithField("from", via).
|
||||||
"from", via,
|
WithField("handshake", m{"stage": 0, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 0, "style": "ix_psk0"},
|
WithField("certVersion", cs.initiatingVersion).
|
||||||
"certVersion", cs.initiatingVersion,
|
Error("Unable to handshake with host because no certificate is available")
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ci, err := NewConnectionState(cs, crt, false, noise.HandshakeIX)
|
ci, err := NewConnectionState(f.l, cs, crt, false, noise.HandshakeIX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to create connection state",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"from", via,
|
Error("Failed to create connection state")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,32 +124,26 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
|
|
||||||
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
msg, _, _, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to call noise.ReadMessage",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"from", via,
|
Error("Failed to call noise.ReadMessage")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := &NebulaHandshake{}
|
hs := &NebulaHandshake{}
|
||||||
err = hs.Unmarshal(msg)
|
err = hs.Unmarshal(msg)
|
||||||
if err != nil || hs.Details == nil {
|
if err != nil || hs.Details == nil {
|
||||||
f.l.Error("Failed unmarshal handshake message",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"from", via,
|
Error("Failed unmarshal handshake message")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Info("Handshake did not contain a certificate",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"from", via,
|
Info("Handshake did not contain a certificate")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,30 +154,23 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
fp = "<error generating certificate fingerprint>"
|
fp = "<error generating certificate fingerprint>"
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := []slog.Attr{
|
e := f.l.WithError(err).WithField("from", via).
|
||||||
slog.Any("error", err),
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
slog.Any("from", via),
|
WithField("certVpnNetworks", rc.Networks()).
|
||||||
slog.Any("handshake", m{"stage": 1, "style": "ix_psk0"}),
|
WithField("certFingerprint", fp)
|
||||||
slog.Any("certVpnNetworks", rc.Networks()),
|
|
||||||
slog.String("certFingerprint", fp),
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
}
|
e = e.WithField("cert", rc)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
|
||||||
attrs = append(attrs, slog.Any("cert", rc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogAttrs is intentional: attrs is a pre-built []slog.Attr slice that
|
e.Info("Invalid certificate from host")
|
||||||
// callers grow conditionally, which has no pair-form equivalent.
|
|
||||||
//nolint:sloglint
|
|
||||||
f.l.LogAttrs(context.Background(), slog.LevelInfo, "Invalid certificate from host", attrs...)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||||
f.l.Info("public key mismatch between certificate and handshake",
|
f.l.WithField("from", via).
|
||||||
"from", via,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||||
"cert", remoteCert,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,13 +178,12 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
// We started off using the wrong certificate version, lets see if we can match the version that was sent to us
|
// We started off using the wrong certificate version, lets see if we can match the version that was sent to us
|
||||||
myCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())
|
myCertOtherVersion := cs.getCertificate(remoteCert.Certificate.Version())
|
||||||
if myCertOtherVersion == nil {
|
if myCertOtherVersion == nil {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Might be unable to handshake with host due to missing certificate version",
|
f.l.WithError(err).WithFields(m{
|
||||||
"error", err,
|
"from": via,
|
||||||
"from", via,
|
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
"cert": remoteCert,
|
||||||
"cert", remoteCert,
|
}).Debug("Might be unable to handshake with host due to missing certificate version")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Record the certificate we are actually using
|
// Record the certificate we are actually using
|
||||||
@@ -223,12 +192,10 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||||
f.l.Info("No networks in certificate",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("cert", remoteCert).
|
||||||
"from", via,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"cert", remoteCert,
|
Info("No networks in certificate")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,15 +209,12 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
vpnAddrs := make([]netip.Addr, len(vpnNetworks))
|
||||||
for i, network := range vpnNetworks {
|
for i, network := range vpnNetworks {
|
||||||
if f.myVpnAddrsTable.Contains(network.Addr()) {
|
if f.myVpnAddrsTable.Contains(network.Addr()) {
|
||||||
f.l.Error("Refusing to handshake with myself",
|
f.l.WithField("vpnNetworks", vpnNetworks).WithField("from", via).
|
||||||
"vpnNetworks", vpnNetworks,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Refusing to handshake with myself")
|
||||||
"issuer", issuer,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vpnAddrs[i] = network.Addr()
|
vpnAddrs[i] = network.Addr()
|
||||||
@@ -262,28 +226,20 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
// We only want to apply the remote allow list for direct tunnels here
|
// We only want to apply the remote allow list for direct tunnels here
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, via.UdpAddr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
f.l.Debug("lighthouse.remote_allow_list denied incoming handshake",
|
Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
"vpnAddrs", vpnAddrs,
|
|
||||||
"from", via,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
myIndex, err := generateIndex(f.l)
|
myIndex, err := generateIndex(f.l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to generate index",
|
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("certName", certName).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certVersion", certVersion).
|
||||||
"from", via,
|
WithField("fingerprint", fingerprint).
|
||||||
"certName", certName,
|
WithField("issuer", issuer).
|
||||||
"certVersion", certVersion,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to generate index")
|
||||||
"fingerprint", fingerprint,
|
|
||||||
"issuer", issuer,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,18 +257,18 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
msgRxL := f.l.With(
|
msgRxL := f.l.WithFields(m{
|
||||||
"vpnAddrs", vpnAddrs,
|
"vpnAddrs": vpnAddrs,
|
||||||
"from", via,
|
"from": via,
|
||||||
"certName", certName,
|
"certName": certName,
|
||||||
"certVersion", certVersion,
|
"certVersion": certVersion,
|
||||||
"fingerprint", fingerprint,
|
"fingerprint": fingerprint,
|
||||||
"issuer", issuer,
|
"issuer": issuer,
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
"initiatorIndex": hs.Details.InitiatorIndex,
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
"responderIndex": hs.Details.ResponderIndex,
|
||||||
"remoteIndex", h.RemoteIndex,
|
"remoteIndex": h.RemoteIndex,
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
"handshake": m{"stage": 1, "style": "ix_psk0"},
|
||||||
)
|
})
|
||||||
|
|
||||||
if anyVpnAddrsInCommon {
|
if anyVpnAddrsInCommon {
|
||||||
msgRxL.Info("Handshake message received")
|
msgRxL.Info("Handshake message received")
|
||||||
@@ -324,9 +280,8 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
hs.Details.ResponderIndex = myIndex
|
hs.Details.ResponderIndex = myIndex
|
||||||
hs.Details.Cert = cs.getHandshakeBytes(ci.myCert.Version())
|
hs.Details.Cert = cs.getHandshakeBytes(ci.myCert.Version())
|
||||||
if hs.Details.Cert == nil {
|
if hs.Details.Cert == nil {
|
||||||
msgRxL.Error("Unable to handshake with host because no certificate handshake bytes is available",
|
msgRxL.WithField("myCertVersion", ci.myCert.Version()).
|
||||||
"myCertVersion", ci.myCert.Version(),
|
Error("Unable to handshake with host because no certificate handshake bytes is available")
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,43 +291,32 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
|
|
||||||
hsBytes, err := hs.Marshal()
|
hsBytes, err := hs.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to marshal handshake message",
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("certName", certName).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("certVersion", certVersion).
|
||||||
"from", via,
|
WithField("fingerprint", fingerprint).
|
||||||
"certName", certName,
|
WithField("issuer", issuer).
|
||||||
"certVersion", certVersion,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to marshal handshake message")
|
||||||
"fingerprint", fingerprint,
|
|
||||||
"issuer", issuer,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nh := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, hs.Details.InitiatorIndex, 2)
|
nh := header.Encode(make([]byte, header.Len), header.Version, header.Handshake, header.HandshakeIXPSK0, hs.Details.InitiatorIndex, 2)
|
||||||
msg, dKey, eKey, err := ci.H.WriteMessage(nh, hsBytes)
|
msg, dKey, eKey, err := ci.H.WriteMessage(nh, hsBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to call noise.WriteMessage",
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("certName", certName).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("certVersion", certVersion).
|
||||||
"from", via,
|
WithField("fingerprint", fingerprint).
|
||||||
"certName", certName,
|
WithField("issuer", issuer).
|
||||||
"certVersion", certVersion,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Failed to call noise.WriteMessage")
|
||||||
"fingerprint", fingerprint,
|
|
||||||
"issuer", issuer,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
} else if dKey == nil || eKey == nil {
|
} else if dKey == nil || eKey == nil {
|
||||||
f.l.Error("Noise did not arrive at a key",
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).Error("Noise did not arrive at a key")
|
||||||
"issuer", issuer,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,20 +358,13 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
err := f.outside.WriteTo(msg, via.UdpAddr)
|
err := f.outside.WriteTo(msg, via.UdpAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to send handshake message",
|
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", existing.vpnAddrs,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||||
"from", via,
|
WithError(err).Error("Failed to send handshake message")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
"cached", true,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
f.l.Info("Handshake message sent",
|
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", existing.vpnAddrs,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||||
"from", via,
|
Info("Handshake message sent")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
"cached", true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -437,67 +374,50 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
}
|
}
|
||||||
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
|
||||||
f.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)
|
f.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)
|
||||||
f.l.Info("Handshake message sent",
|
f.l.WithField("vpnAddrs", existing.vpnAddrs).WithField("relay", via.relayHI.vpnAddrs[0]).
|
||||||
"vpnAddrs", existing.vpnAddrs,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("cached", true).
|
||||||
"relay", via.relayHI.vpnAddrs[0],
|
Info("Handshake message sent")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
"cached", true,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case ErrExistingHostInfo:
|
case ErrExistingHostInfo:
|
||||||
// This means there was an existing tunnel and this handshake was older than the one we are currently based on
|
// This means there was an existing tunnel and this handshake was older than the one we are currently based on
|
||||||
f.l.Info("Handshake too old",
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("oldHandshakeTime", existing.lastHandshakeTime).
|
||||||
"certVersion", certVersion,
|
WithField("newHandshakeTime", hostinfo.lastHandshakeTime).
|
||||||
"oldHandshakeTime", existing.lastHandshakeTime,
|
WithField("fingerprint", fingerprint).
|
||||||
"newHandshakeTime", hostinfo.lastHandshakeTime,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"issuer", issuer,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
Info("Handshake too old")
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues
|
// Send a test packet to trigger an authenticated tunnel test, this should suss out any lingering tunnel issues
|
||||||
f.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(""), make([]byte, 12, 12), make([]byte, mtu))
|
f.SendMessageToVpnAddr(header.Test, header.TestRequest, vpnAddrs[0], []byte(""), make([]byte, 12, 12), make([]byte, mtu))
|
||||||
return
|
return
|
||||||
case ErrLocalIndexCollision:
|
case ErrLocalIndexCollision:
|
||||||
// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry
|
// This means we failed to insert because of collision on localIndexId. Just let the next handshake packet retry
|
||||||
f.l.Error("Failed to add HostInfo due to localIndex collision",
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"issuer", issuer,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
WithField("localIndex", hostinfo.localIndexId).WithField("collision", existing.vpnAddrs).
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
Error("Failed to add HostInfo due to localIndex collision")
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
"localIndex", hostinfo.localIndexId,
|
|
||||||
"collision", existing.vpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete
|
// Shouldn't happen, but just in case someone adds a new error type to CheckAndComplete
|
||||||
// And we forget to update it here
|
// And we forget to update it here
|
||||||
f.l.Error("Failed to add HostInfo to HostMap",
|
f.l.WithError(err).WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("certName", certName).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certVersion", certVersion).
|
||||||
"from", via,
|
WithField("fingerprint", fingerprint).
|
||||||
"certName", certName,
|
WithField("issuer", issuer).
|
||||||
"certVersion", certVersion,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"fingerprint", fingerprint,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"issuer", issuer,
|
Error("Failed to add HostInfo to HostMap")
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,20 +426,15 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
f.messageMetrics.Tx(header.Handshake, header.MessageSubType(msg[1]), 1)
|
||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
err = f.outside.WriteTo(msg, via.UdpAddr)
|
err = f.outside.WriteTo(msg, via.UdpAddr)
|
||||||
log := f.l.With(
|
log := f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"issuer", issuer,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"})
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to send handshake", "error", err)
|
log.WithError(err).Error("Failed to send handshake")
|
||||||
} else {
|
} else {
|
||||||
log.Info("Handshake message sent")
|
log.Info("Handshake message sent")
|
||||||
}
|
}
|
||||||
@@ -533,29 +448,20 @@ func ixHandshakeStage1(f *Interface, via ViaSender, packet []byte, h *header.H)
|
|||||||
// it's correctly marked as working.
|
// it's correctly marked as working.
|
||||||
via.relayHI.relayState.UpdateRelayForByIdxState(via.remoteIdx, Established)
|
via.relayHI.relayState.UpdateRelayForByIdxState(via.remoteIdx, Established)
|
||||||
f.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)
|
f.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)
|
||||||
f.l.Info("Handshake message sent",
|
f.l.WithField("vpnAddrs", vpnAddrs).WithField("relay", via.relayHI.vpnAddrs[0]).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certName", certName).
|
||||||
"relay", via.relayHI.vpnAddrs[0],
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"issuer", issuer,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
Info("Handshake message sent")
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.connectionManager.AddTrafficWatch(hostinfo)
|
f.connectionManager.AddTrafficWatch(hostinfo)
|
||||||
|
|
||||||
hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
|
hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
|
||||||
|
|
||||||
// Don't wait for UpdateWorker
|
|
||||||
if f.lightHouse.IsAnyLighthouseAddr(vpnAddrs) {
|
|
||||||
f.lightHouse.TriggerUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,12 +478,7 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
// The vpnAddr we know about is the one we tried to handshake with, use it to apply the remote allow list.
|
// The vpnAddr we know about is the one we tried to handshake with, use it to apply the remote allow list.
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
f.l.Debug("lighthouse.remote_allow_list denied incoming handshake",
|
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
"from", via,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,24 +486,18 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
ci := hostinfo.ConnectionState
|
ci := hostinfo.ConnectionState
|
||||||
msg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
msg, eKey, dKey, err := ci.H.ReadMessage(nil, packet[header.Len:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to call noise.ReadMessage",
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).WithField("header", h).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
Error("Failed to call noise.ReadMessage")
|
||||||
"from", via,
|
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
"header", h,
|
|
||||||
)
|
|
||||||
|
|
||||||
// We don't want to tear down the connection on a bad ReadMessage because it could be an attacker trying
|
// We don't want to tear down the connection on a bad ReadMessage because it could be an attacker trying
|
||||||
// to DOS us. Every other error condition after should to allow a possible good handshake to complete in the
|
// to DOS us. Every other error condition after should to allow a possible good handshake to complete in the
|
||||||
// near future
|
// near future
|
||||||
return false
|
return false
|
||||||
} else if dKey == nil || eKey == nil {
|
} else if dKey == nil || eKey == nil {
|
||||||
f.l.Error("Noise did not arrive at a key",
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"from", via,
|
Error("Noise did not arrive at a key")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// This should be impossible in IX but just in case, if we get here then there is no chance to recover
|
// This should be impossible in IX but just in case, if we get here then there is no chance to recover
|
||||||
// the handshake state machine. Tear it down
|
// the handshake state machine. Tear it down
|
||||||
@@ -612,12 +507,8 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
hs := &NebulaHandshake{}
|
hs := &NebulaHandshake{}
|
||||||
err = hs.Unmarshal(msg)
|
err = hs.Unmarshal(msg)
|
||||||
if err != nil || hs.Details == nil {
|
if err != nil || hs.Details == nil {
|
||||||
f.l.Error("Failed unmarshal handshake message",
|
f.l.WithError(err).WithField("vpnAddrs", hostinfo.vpnAddrs).WithField("from", via).
|
||||||
"error", err,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).Error("Failed unmarshal handshake message")
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
"from", via,
|
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// The handshake state machine is complete, if things break now there is no chance to recover. Tear down and start again
|
// The handshake state machine is complete, if things break now there is no chance to recover. Tear down and start again
|
||||||
return true
|
return true
|
||||||
@@ -625,12 +516,10 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
|
|
||||||
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
rc, err := cert.Recombine(cert.Version(hs.Details.CertVersion), hs.Details.Cert, ci.H.PeerStatic(), ci.Curve())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Info("Handshake did not contain a certificate",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"from", via,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
Info("Handshake did not contain a certificate")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,41 +530,32 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
fp = "<error generating certificate fingerprint>"
|
fp = "<error generating certificate fingerprint>"
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := []slog.Attr{
|
e := f.l.WithError(err).WithField("from", via).
|
||||||
slog.Any("error", err),
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
slog.Any("from", via),
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
slog.Any("vpnAddrs", hostinfo.vpnAddrs),
|
WithField("certFingerprint", fp).
|
||||||
slog.Any("handshake", m{"stage": 2, "style": "ix_psk0"}),
|
WithField("certVpnNetworks", rc.Networks())
|
||||||
slog.String("certFingerprint", fp),
|
|
||||||
slog.Any("certVpnNetworks", rc.Networks()),
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
}
|
e = e.WithField("cert", rc)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
|
||||||
attrs = append(attrs, slog.Any("cert", rc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogAttrs is intentional: attrs is a pre-built []slog.Attr slice that
|
e.Info("Invalid certificate from host")
|
||||||
// callers grow conditionally, which has no pair-form equivalent.
|
|
||||||
//nolint:sloglint
|
|
||||||
f.l.LogAttrs(context.Background(), slog.LevelInfo, "Invalid certificate from host", attrs...)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
if !bytes.Equal(remoteCert.Certificate.PublicKey(), ci.H.PeerStatic()) {
|
||||||
f.l.Info("public key mismatch between certificate and handshake",
|
f.l.WithField("from", via).
|
||||||
"from", via,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
WithField("cert", remoteCert).Info("public key mismatch between certificate and handshake")
|
||||||
"cert", remoteCert,
|
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remoteCert.Certificate.Networks()) == 0 {
|
if len(remoteCert.Certificate.Networks()) == 0 {
|
||||||
f.l.Info("No networks in certificate",
|
f.l.WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("vpnAddrs", hostinfo.vpnAddrs).
|
||||||
"from", via,
|
WithField("cert", remoteCert).
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"cert", remoteCert,
|
Info("No networks in certificate")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,14 +596,12 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
|
|
||||||
// Ensure the right host responded
|
// Ensure the right host responded
|
||||||
if !correctHostResponded {
|
if !correctHostResponded {
|
||||||
f.l.Info("Incorrect host responded to handshake",
|
f.l.WithField("intendedVpnAddrs", hostinfo.vpnAddrs).WithField("haveVpnNetworks", vpnNetworks).
|
||||||
"intendedVpnAddrs", hostinfo.vpnAddrs,
|
WithField("from", via).
|
||||||
"haveVpnNetworks", vpnNetworks,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"certVersion", certVersion,
|
Info("Incorrect host responded to handshake")
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Release our old handshake from pending, it should not continue
|
// Release our old handshake from pending, it should not continue
|
||||||
f.handshakeManager.DeleteHostInfo(hostinfo)
|
f.handshakeManager.DeleteHostInfo(hostinfo)
|
||||||
@@ -735,11 +613,10 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
newHH.hostinfo.remotes = hostinfo.remotes
|
newHH.hostinfo.remotes = hostinfo.remotes
|
||||||
newHH.hostinfo.remotes.BlockRemote(via)
|
newHH.hostinfo.remotes.BlockRemote(via)
|
||||||
|
|
||||||
f.l.Info("Blocked addresses for handshakes",
|
f.l.WithField("blockedUdpAddrs", newHH.hostinfo.remotes.CopyBlockedRemotes()).
|
||||||
"blockedUdpAddrs", newHH.hostinfo.remotes.CopyBlockedRemotes(),
|
WithField("vpnNetworks", vpnNetworks).
|
||||||
"vpnNetworks", vpnNetworks,
|
WithField("remotes", newHH.hostinfo.remotes.CopyAddrs(f.hostMap.GetPreferredRanges())).
|
||||||
"remotes", newHH.hostinfo.remotes.CopyAddrs(f.hostMap.GetPreferredRanges()),
|
Info("Blocked addresses for handshakes")
|
||||||
)
|
|
||||||
|
|
||||||
// Swap the packet store to benefit the original intended recipient
|
// Swap the packet store to benefit the original intended recipient
|
||||||
newHH.packetStore = hh.packetStore
|
newHH.packetStore = hh.packetStore
|
||||||
@@ -757,20 +634,15 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
ci.window.Update(f.l, 2)
|
ci.window.Update(f.l, 2)
|
||||||
|
|
||||||
duration := time.Since(hh.startTime).Nanoseconds()
|
duration := time.Since(hh.startTime).Nanoseconds()
|
||||||
msgRxL := f.l.With(
|
msgRxL := f.l.WithField("vpnAddrs", vpnAddrs).WithField("from", via).
|
||||||
"vpnAddrs", vpnAddrs,
|
WithField("certName", certName).
|
||||||
"from", via,
|
WithField("certVersion", certVersion).
|
||||||
"certName", certName,
|
WithField("fingerprint", fingerprint).
|
||||||
"certVersion", certVersion,
|
WithField("issuer", issuer).
|
||||||
"fingerprint", fingerprint,
|
WithField("initiatorIndex", hs.Details.InitiatorIndex).WithField("responderIndex", hs.Details.ResponderIndex).
|
||||||
"issuer", issuer,
|
WithField("remoteIndex", h.RemoteIndex).WithField("handshake", m{"stage": 2, "style": "ix_psk0"}).
|
||||||
"initiatorIndex", hs.Details.InitiatorIndex,
|
WithField("durationNs", duration).
|
||||||
"responderIndex", hs.Details.ResponderIndex,
|
WithField("sentCachedPackets", len(hh.packetStore))
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
"handshake", m{"stage": 2, "style": "ix_psk0"},
|
|
||||||
"durationNs", duration,
|
|
||||||
"sentCachedPackets", len(hh.packetStore),
|
|
||||||
)
|
|
||||||
if anyVpnAddrsInCommon {
|
if anyVpnAddrsInCommon {
|
||||||
msgRxL.Info("Handshake message received")
|
msgRxL.Info("Handshake message received")
|
||||||
} else {
|
} else {
|
||||||
@@ -786,10 +658,8 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
f.handshakeManager.Complete(hostinfo, f)
|
f.handshakeManager.Complete(hostinfo, f)
|
||||||
f.connectionManager.AddTrafficWatch(hostinfo)
|
f.connectionManager.AddTrafficWatch(hostinfo)
|
||||||
|
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).Debug("Sending stored packets",
|
hostinfo.logger(f.l).Debugf("Sending %d stored packets", len(hh.packetStore))
|
||||||
"count", len(hh.packetStore),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hh.packetStore) > 0 {
|
if len(hh.packetStore) > 0 {
|
||||||
@@ -804,10 +674,5 @@ func ixHandshakeStage2(f *Interface, via ViaSender, hh *HandshakeHostInfo, packe
|
|||||||
hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
|
hostinfo.remotes.RefreshFromHandshake(vpnAddrs)
|
||||||
f.metricHandshakes.Update(duration)
|
f.metricHandshakes.Update(duration)
|
||||||
|
|
||||||
// Don't wait for UpdateWorker
|
|
||||||
if f.lightHouse.IsAnyLighthouseAddr(vpnAddrs) {
|
|
||||||
f.lightHouse.TriggerUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-80
@@ -6,13 +6,13 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/udp"
|
"github.com/slackhq/nebula/udp"
|
||||||
@@ -59,7 +59,7 @@ type HandshakeManager struct {
|
|||||||
metricInitiated metrics.Counter
|
metricInitiated metrics.Counter
|
||||||
metricTimedOut metrics.Counter
|
metricTimedOut metrics.Counter
|
||||||
f *Interface
|
f *Interface
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
// can be used to trigger outbound handshake for the given vpnIp
|
// can be used to trigger outbound handshake for the given vpnIp
|
||||||
trigger chan netip.Addr
|
trigger chan netip.Addr
|
||||||
@@ -78,32 +78,32 @@ type HandshakeHostInfo struct {
|
|||||||
hostinfo *HostInfo
|
hostinfo *HostInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hh *HandshakeHostInfo) cachePacket(l *slog.Logger, t header.MessageType, st header.MessageSubType, packet []byte, f packetCallback, m *cachedPacketMetrics) {
|
func (hh *HandshakeHostInfo) cachePacket(l *logrus.Logger, t header.MessageType, st header.MessageSubType, packet []byte, f packetCallback, m *cachedPacketMetrics) {
|
||||||
if len(hh.packetStore) < 100 {
|
if len(hh.packetStore) < 100 {
|
||||||
tempPacket := make([]byte, len(packet))
|
tempPacket := make([]byte, len(packet))
|
||||||
copy(tempPacket, packet)
|
copy(tempPacket, packet)
|
||||||
|
|
||||||
hh.packetStore = append(hh.packetStore, &cachedPacket{t, st, f, tempPacket})
|
hh.packetStore = append(hh.packetStore, &cachedPacket{t, st, f, tempPacket})
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
hh.hostinfo.logger(l).Debug("Packet store",
|
hh.hostinfo.logger(l).
|
||||||
"length", len(hh.packetStore),
|
WithField("length", len(hh.packetStore)).
|
||||||
"stored", true,
|
WithField("stored", true).
|
||||||
)
|
Debugf("Packet store")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
m.dropped.Inc(1)
|
m.dropped.Inc(1)
|
||||||
|
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
hh.hostinfo.logger(l).Debug("Packet store",
|
hh.hostinfo.logger(l).
|
||||||
"length", len(hh.packetStore),
|
WithField("length", len(hh.packetStore)).
|
||||||
"stored", false,
|
WithField("stored", false).
|
||||||
)
|
Debugf("Packet store")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandshakeManager(l *slog.Logger, mainHostMap *HostMap, lightHouse *LightHouse, outside udp.Conn, config HandshakeConfig) *HandshakeManager {
|
func NewHandshakeManager(l *logrus.Logger, mainHostMap *HostMap, lightHouse *LightHouse, outside udp.Conn, config HandshakeConfig) *HandshakeManager {
|
||||||
return &HandshakeManager{
|
return &HandshakeManager{
|
||||||
vpnIps: map[netip.Addr]*HandshakeHostInfo{},
|
vpnIps: map[netip.Addr]*HandshakeHostInfo{},
|
||||||
indexes: map[uint32]*HandshakeHostInfo{},
|
indexes: map[uint32]*HandshakeHostInfo{},
|
||||||
@@ -140,7 +140,7 @@ func (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *head
|
|||||||
// First remote allow list check before we know the vpnIp
|
// First remote allow list check before we know the vpnIp
|
||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(via.UdpAddr.Addr()) {
|
if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnAddr(via.UdpAddr.Addr()) {
|
||||||
hm.l.Debug("lighthouse.remote_allow_list denied incoming handshake", "from", via)
|
hm.l.WithField("from", via).Debug("lighthouse.remote_allow_list denied incoming handshake")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,13 +183,12 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
hostinfo := hh.hostinfo
|
hostinfo := hh.hostinfo
|
||||||
// If we are out of time, clean up
|
// If we are out of time, clean up
|
||||||
if hh.counter >= hm.config.retries {
|
if hh.counter >= hm.config.retries {
|
||||||
hh.hostinfo.logger(hm.l).Info("Handshake timed out",
|
hh.hostinfo.logger(hm.l).WithField("udpAddrs", hh.hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges())).
|
||||||
"udpAddrs", hh.hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges()),
|
WithField("initiatorIndex", hh.hostinfo.localIndexId).
|
||||||
"initiatorIndex", hh.hostinfo.localIndexId,
|
WithField("remoteIndex", hh.hostinfo.remoteIndexId).
|
||||||
"remoteIndex", hh.hostinfo.remoteIndexId,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
WithField("durationNs", time.Since(hh.startTime).Nanoseconds()).
|
||||||
"durationNs", time.Since(hh.startTime).Nanoseconds(),
|
Info("Handshake timed out")
|
||||||
)
|
|
||||||
hm.metricTimedOut.Inc(1)
|
hm.metricTimedOut.Inc(1)
|
||||||
hm.DeleteHostInfo(hostinfo)
|
hm.DeleteHostInfo(hostinfo)
|
||||||
return
|
return
|
||||||
@@ -242,12 +241,10 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
hm.messageMetrics.Tx(header.Handshake, header.MessageSubType(hostinfo.HandshakePacket[0][1]), 1)
|
hm.messageMetrics.Tx(header.Handshake, header.MessageSubType(hostinfo.HandshakePacket[0][1]), 1)
|
||||||
err := hm.outside.WriteTo(hostinfo.HandshakePacket[0], addr)
|
err := hm.outside.WriteTo(hostinfo.HandshakePacket[0], addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(hm.l).Error("Failed to send handshake message",
|
hostinfo.logger(hm.l).WithField("udpAddr", addr).
|
||||||
"udpAddr", addr,
|
WithField("initiatorIndex", hostinfo.localIndexId).
|
||||||
"initiatorIndex", hostinfo.localIndexId,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
WithError(err).Error("Failed to send handshake message")
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
sentTo = append(sentTo, addr)
|
sentTo = append(sentTo, addr)
|
||||||
@@ -257,21 +254,19 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
// Don't be too noisy or confusing if we fail to send a handshake - if we don't get through we'll eventually log a timeout,
|
// Don't be too noisy or confusing if we fail to send a handshake - if we don't get through we'll eventually log a timeout,
|
||||||
// so only log when the list of remotes has changed
|
// so only log when the list of remotes has changed
|
||||||
if remotesHaveChanged {
|
if remotesHaveChanged {
|
||||||
hostinfo.logger(hm.l).Info("Handshake message sent",
|
hostinfo.logger(hm.l).WithField("udpAddrs", sentTo).
|
||||||
"udpAddrs", sentTo,
|
WithField("initiatorIndex", hostinfo.localIndexId).
|
||||||
"initiatorIndex", hostinfo.localIndexId,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
Info("Handshake message sent")
|
||||||
)
|
} else if hm.l.Level >= logrus.DebugLevel {
|
||||||
} else if hm.l.Enabled(context.Background(), slog.LevelDebug) {
|
hostinfo.logger(hm.l).WithField("udpAddrs", sentTo).
|
||||||
hostinfo.logger(hm.l).Debug("Handshake message sent",
|
WithField("initiatorIndex", hostinfo.localIndexId).
|
||||||
"udpAddrs", sentTo,
|
WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
|
||||||
"initiatorIndex", hostinfo.localIndexId,
|
Debug("Handshake message sent")
|
||||||
"handshake", m{"stage": 1, "style": "ix_psk0"},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if hm.config.useRelays && len(hostinfo.remotes.relays) > 0 {
|
if hm.config.useRelays && len(hostinfo.remotes.relays) > 0 {
|
||||||
hostinfo.logger(hm.l).Info("Attempt to relay through hosts", "relays", hostinfo.remotes.relays)
|
hostinfo.logger(hm.l).WithField("relays", hostinfo.remotes.relays).Info("Attempt to relay through hosts")
|
||||||
// Send a RelayRequest to all known Relay IP's
|
// Send a RelayRequest to all known Relay IP's
|
||||||
for _, relay := range hostinfo.remotes.relays {
|
for _, relay := range hostinfo.remotes.relays {
|
||||||
// Don't relay through the host I'm trying to connect to
|
// Don't relay through the host I'm trying to connect to
|
||||||
@@ -286,7 +281,7 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
|
|
||||||
relayHostInfo := hm.mainHostMap.QueryVpnAddr(relay)
|
relayHostInfo := hm.mainHostMap.QueryVpnAddr(relay)
|
||||||
if relayHostInfo == nil || !relayHostInfo.remote.IsValid() {
|
if relayHostInfo == nil || !relayHostInfo.remote.IsValid() {
|
||||||
hostinfo.logger(hm.l).Info("Establish tunnel to relay target", "relay", relay.String())
|
hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Establish tunnel to relay target")
|
||||||
hm.f.Handshake(relay)
|
hm.f.Handshake(relay)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -297,7 +292,7 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
if relayHostInfo.remote.IsValid() {
|
if relayHostInfo.remote.IsValid() {
|
||||||
idx, err := AddRelay(hm.l, relayHostInfo, hm.mainHostMap, vpnIp, nil, TerminalType, Requested)
|
idx, err := AddRelay(hm.l, relayHostInfo, hm.mainHostMap, vpnIp, nil, TerminalType, Requested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(hm.l).Info("Failed to add relay to hostmap", "relay", relay.String(), "error", err)
|
hostinfo.logger(hm.l).WithField("relay", relay.String()).WithError(err).Info("Failed to add relay to hostmap")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := NebulaControl{
|
m := NebulaControl{
|
||||||
@@ -331,15 +326,17 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
|
|
||||||
msg, err := m.Marshal()
|
msg, err := m.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(hm.l).Error("Failed to marshal Control message to create relay", "error", err)
|
hostinfo.logger(hm.l).
|
||||||
|
WithError(err).
|
||||||
|
Error("Failed to marshal Control message to create relay")
|
||||||
} else {
|
} else {
|
||||||
hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
hm.l.Info("send CreateRelayRequest",
|
hm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", hm.f.myVpnAddrs[0],
|
"relayFrom": hm.f.myVpnAddrs[0],
|
||||||
"relayTo", vpnIp,
|
"relayTo": vpnIp,
|
||||||
"initiatorRelayIndex", idx,
|
"initiatorRelayIndex": idx,
|
||||||
"relay", relay,
|
"relay": relay}).
|
||||||
)
|
Info("send CreateRelayRequest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -347,14 +344,14 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
|
|
||||||
switch existingRelay.State {
|
switch existingRelay.State {
|
||||||
case Established:
|
case Established:
|
||||||
hostinfo.logger(hm.l).Info("Send handshake via relay", "relay", relay.String())
|
hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Send handshake via relay")
|
||||||
hm.f.SendVia(relayHostInfo, existingRelay, hostinfo.HandshakePacket[0], make([]byte, 12), make([]byte, mtu), false)
|
hm.f.SendVia(relayHostInfo, existingRelay, hostinfo.HandshakePacket[0], make([]byte, 12), make([]byte, mtu), false)
|
||||||
case Disestablished:
|
case Disestablished:
|
||||||
// Mark this relay as 'requested'
|
// Mark this relay as 'requested'
|
||||||
relayHostInfo.relayState.UpdateRelayForByIpState(vpnIp, Requested)
|
relayHostInfo.relayState.UpdateRelayForByIpState(vpnIp, Requested)
|
||||||
fallthrough
|
fallthrough
|
||||||
case Requested:
|
case Requested:
|
||||||
hostinfo.logger(hm.l).Info("Re-send CreateRelay request", "relay", relay.String())
|
hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Re-send CreateRelay request")
|
||||||
// Re-send the CreateRelay request, in case the previous one was lost.
|
// Re-send the CreateRelay request, in case the previous one was lost.
|
||||||
m := NebulaControl{
|
m := NebulaControl{
|
||||||
Type: NebulaControl_CreateRelayRequest,
|
Type: NebulaControl_CreateRelayRequest,
|
||||||
@@ -386,26 +383,28 @@ func (hm *HandshakeManager) handleOutbound(vpnIp netip.Addr, lighthouseTriggered
|
|||||||
}
|
}
|
||||||
msg, err := m.Marshal()
|
msg, err := m.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(hm.l).Error("Failed to marshal Control message to create relay", "error", err)
|
hostinfo.logger(hm.l).
|
||||||
|
WithError(err).
|
||||||
|
Error("Failed to marshal Control message to create relay")
|
||||||
} else {
|
} else {
|
||||||
// This must send over the hostinfo, not over hm.Hosts[ip]
|
// This must send over the hostinfo, not over hm.Hosts[ip]
|
||||||
hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
hm.l.Info("send CreateRelayRequest",
|
hm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", hm.f.myVpnAddrs[0],
|
"relayFrom": hm.f.myVpnAddrs[0],
|
||||||
"relayTo", vpnIp,
|
"relayTo": vpnIp,
|
||||||
"initiatorRelayIndex", existingRelay.LocalIndex,
|
"initiatorRelayIndex": existingRelay.LocalIndex,
|
||||||
"relay", relay,
|
"relay": relay}).
|
||||||
)
|
Info("send CreateRelayRequest")
|
||||||
}
|
}
|
||||||
case PeerRequested:
|
case PeerRequested:
|
||||||
// PeerRequested only occurs in Forwarding relays, not Terminal relays, and this is a Terminal relay case.
|
// PeerRequested only occurs in Forwarding relays, not Terminal relays, and this is a Terminal relay case.
|
||||||
fallthrough
|
fallthrough
|
||||||
default:
|
default:
|
||||||
hostinfo.logger(hm.l).Error("Relay unexpected state",
|
hostinfo.logger(hm.l).
|
||||||
"vpnIp", vpnIp,
|
WithField("vpnIp", vpnIp).
|
||||||
"state", existingRelay.State,
|
WithField("state", existingRelay.State).
|
||||||
"relay", relay,
|
WithField("relay", relay).
|
||||||
)
|
Errorf("Relay unexpected state")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,10 +549,9 @@ func (hm *HandshakeManager) CheckAndComplete(hostinfo *HostInfo, handshakePacket
|
|||||||
if found && existingRemoteIndex != nil && existingRemoteIndex.vpnAddrs[0] != hostinfo.vpnAddrs[0] {
|
if found && existingRemoteIndex != nil && existingRemoteIndex.vpnAddrs[0] != hostinfo.vpnAddrs[0] {
|
||||||
// We have a collision, but this can happen since we can't control
|
// We have a collision, but this can happen since we can't control
|
||||||
// the remote ID. Just log about the situation as a note.
|
// the remote ID. Just log about the situation as a note.
|
||||||
hostinfo.logger(hm.l).Info("New host shadows existing host remoteIndex",
|
hostinfo.logger(hm.l).
|
||||||
"remoteIndex", hostinfo.remoteIndexId,
|
WithField("remoteIndex", hostinfo.remoteIndexId).WithField("collision", existingRemoteIndex.vpnAddrs).
|
||||||
"collision", existingRemoteIndex.vpnAddrs,
|
Info("New host shadows existing host remoteIndex")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hm.mainHostMap.unlockedAddHostInfo(hostinfo, f)
|
hm.mainHostMap.unlockedAddHostInfo(hostinfo, f)
|
||||||
@@ -573,10 +571,9 @@ func (hm *HandshakeManager) Complete(hostinfo *HostInfo, f *Interface) {
|
|||||||
if found && existingRemoteIndex != nil {
|
if found && existingRemoteIndex != nil {
|
||||||
// We have a collision, but this can happen since we can't control
|
// We have a collision, but this can happen since we can't control
|
||||||
// the remote ID. Just log about the situation as a note.
|
// the remote ID. Just log about the situation as a note.
|
||||||
hostinfo.logger(hm.l).Info("New host shadows existing host remoteIndex",
|
hostinfo.logger(hm.l).
|
||||||
"remoteIndex", hostinfo.remoteIndexId,
|
WithField("remoteIndex", hostinfo.remoteIndexId).WithField("collision", existingRemoteIndex.vpnAddrs).
|
||||||
"collision", existingRemoteIndex.vpnAddrs,
|
Info("New host shadows existing host remoteIndex")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to remove from the pending hostmap first to avoid undoing work when after to the main hostmap.
|
// We need to remove from the pending hostmap first to avoid undoing work when after to the main hostmap.
|
||||||
@@ -632,11 +629,10 @@ func (hm *HandshakeManager) unlockedDeleteHostInfo(hostinfo *HostInfo) {
|
|||||||
hm.indexes = map[uint32]*HandshakeHostInfo{}
|
hm.indexes = map[uint32]*HandshakeHostInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if hm.l.Level >= logrus.DebugLevel {
|
||||||
hm.l.Debug("Pending hostmap hostInfo deleted",
|
hm.l.WithField("hostMap", m{"mapTotalSize": len(hm.vpnIps),
|
||||||
"hostMap", m{"mapTotalSize": len(hm.vpnIps),
|
"vpnAddrs": hostinfo.vpnAddrs, "indexNumber": hostinfo.localIndexId, "remoteIndexNumber": hostinfo.remoteIndexId}).
|
||||||
"vpnAddrs": hostinfo.vpnAddrs, "indexNumber": hostinfo.localIndexId, "remoteIndexNumber": hostinfo.remoteIndexId},
|
Debug("Pending hostmap hostInfo deleted")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +700,7 @@ func (hm *HandshakeManager) EmitStats() {
|
|||||||
|
|
||||||
// Utility functions below
|
// Utility functions below
|
||||||
|
|
||||||
func generateIndex(l *slog.Logger) (uint32, error) {
|
func generateIndex(l *logrus.Logger) (uint32, error) {
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
|
|
||||||
// Let zero mean we don't know the ID, so don't generate zero
|
// Let zero mean we don't know the ID, so don't generate zero
|
||||||
@@ -712,15 +708,16 @@ func generateIndex(l *slog.Logger) (uint32, error) {
|
|||||||
for index == 0 {
|
for index == 0 {
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("Failed to generate index", "error", err)
|
l.Errorln(err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
index = binary.BigEndian.Uint32(b)
|
index = binary.BigEndian.Uint32(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
l.Debug("Generated index", "index", index)
|
l.WithField("index", index).
|
||||||
|
Debug("Generated index")
|
||||||
}
|
}
|
||||||
return index, nil
|
return index, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-49
@@ -1,11 +1,9 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -15,10 +13,10 @@ import (
|
|||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPromoteEvery = 1000 // Count of packets sent before we try moving a tunnel to a preferred underlay ip address
|
const defaultPromoteEvery = 1000 // Count of packets sent before we try moving a tunnel to a preferred underlay ip address
|
||||||
@@ -62,7 +60,7 @@ type HostMap struct {
|
|||||||
RemoteIndexes map[uint32]*HostInfo
|
RemoteIndexes map[uint32]*HostInfo
|
||||||
Hosts map[netip.Addr]*HostInfo
|
Hosts map[netip.Addr]*HostInfo
|
||||||
preferredRanges atomic.Pointer[[]netip.Prefix]
|
preferredRanges atomic.Pointer[[]netip.Prefix]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// For synchronization, treat the pointed-to Relay struct as immutable. To edit the Relay
|
// For synchronization, treat the pointed-to Relay struct as immutable. To edit the Relay
|
||||||
@@ -315,7 +313,7 @@ type cachedPacketMetrics struct {
|
|||||||
dropped metrics.Counter
|
dropped metrics.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHostMapFromConfig(l *slog.Logger, c *config.C) *HostMap {
|
func NewHostMapFromConfig(l *logrus.Logger, c *config.C) *HostMap {
|
||||||
hm := newHostMap(l)
|
hm := newHostMap(l)
|
||||||
|
|
||||||
hm.reload(c, true)
|
hm.reload(c, true)
|
||||||
@@ -323,12 +321,13 @@ func NewHostMapFromConfig(l *slog.Logger, c *config.C) *HostMap {
|
|||||||
hm.reload(c, false)
|
hm.reload(c, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
l.Info("Main HostMap created", "preferredRanges", hm.GetPreferredRanges())
|
l.WithField("preferredRanges", hm.GetPreferredRanges()).
|
||||||
|
Info("Main HostMap created")
|
||||||
|
|
||||||
return hm
|
return hm
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostMap(l *slog.Logger) *HostMap {
|
func newHostMap(l *logrus.Logger) *HostMap {
|
||||||
return &HostMap{
|
return &HostMap{
|
||||||
Indexes: map[uint32]*HostInfo{},
|
Indexes: map[uint32]*HostInfo{},
|
||||||
Relays: map[uint32]*HostInfo{},
|
Relays: map[uint32]*HostInfo{},
|
||||||
@@ -347,10 +346,7 @@ func (hm *HostMap) reload(c *config.C, initial bool) {
|
|||||||
preferredRange, err := netip.ParsePrefix(rawPreferredRange)
|
preferredRange, err := netip.ParsePrefix(rawPreferredRange)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hm.l.Warn("Failed to parse preferred ranges, ignoring",
|
hm.l.WithError(err).WithField("range", rawPreferredRanges).Warn("Failed to parse preferred ranges, ignoring")
|
||||||
"error", err,
|
|
||||||
"range", rawPreferredRanges,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,10 +355,7 @@ func (hm *HostMap) reload(c *config.C, initial bool) {
|
|||||||
|
|
||||||
oldRanges := hm.preferredRanges.Swap(&preferredRanges)
|
oldRanges := hm.preferredRanges.Swap(&preferredRanges)
|
||||||
if !initial {
|
if !initial {
|
||||||
hm.l.Info("preferred_ranges changed",
|
hm.l.WithField("oldPreferredRanges", *oldRanges).WithField("newPreferredRanges", preferredRanges).Info("preferred_ranges changed")
|
||||||
"oldPreferredRanges", *oldRanges,
|
|
||||||
"newPreferredRanges", preferredRanges,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,11 +488,10 @@ func (hm *HostMap) unlockedInnerDeleteHostInfo(hostinfo *HostInfo, addr netip.Ad
|
|||||||
hm.Indexes = map[uint32]*HostInfo{}
|
hm.Indexes = map[uint32]*HostInfo{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if hm.l.Level >= logrus.DebugLevel {
|
||||||
hm.l.Debug("Hostmap hostInfo deleted",
|
hm.l.WithField("hostMap", m{"mapTotalSize": len(hm.Hosts),
|
||||||
"hostMap", m{"mapTotalSize": len(hm.Hosts),
|
"vpnAddrs": hostinfo.vpnAddrs, "indexNumber": hostinfo.localIndexId, "remoteIndexNumber": hostinfo.remoteIndexId}).
|
||||||
"vpnAddrs": hostinfo.vpnAddrs, "indexNumber": hostinfo.localIndexId, "remoteIndexNumber": hostinfo.remoteIndexId},
|
Debug("Hostmap hostInfo deleted")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isLastHostinfo {
|
if isLastHostinfo {
|
||||||
@@ -612,9 +604,9 @@ func (hm *HostMap) queryVpnAddr(vpnIp netip.Addr, promoteIfce *Interface) *HostI
|
|||||||
// unlockedAddHostInfo assumes you have a write-lock and will add a hostinfo object to the hostmap Indexes and RemoteIndexes maps.
|
// unlockedAddHostInfo assumes you have a write-lock and will add a hostinfo object to the hostmap Indexes and RemoteIndexes maps.
|
||||||
// If an entry exists for the Hosts table (vpnIp -> hostinfo) then the provided hostinfo will be made primary
|
// If an entry exists for the Hosts table (vpnIp -> hostinfo) then the provided hostinfo will be made primary
|
||||||
func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) {
|
func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) {
|
||||||
if f.dnsServer != nil {
|
if f.serveDns {
|
||||||
remoteCert := hostinfo.ConnectionState.peerCert
|
remoteCert := hostinfo.ConnectionState.peerCert
|
||||||
f.dnsServer.Add(remoteCert.Certificate.Name()+".", hostinfo.vpnAddrs)
|
dnsR.Add(remoteCert.Certificate.Name()+".", hostinfo.vpnAddrs)
|
||||||
}
|
}
|
||||||
for _, addr := range hostinfo.vpnAddrs {
|
for _, addr := range hostinfo.vpnAddrs {
|
||||||
hm.unlockedInnerAddHostInfo(addr, hostinfo, f)
|
hm.unlockedInnerAddHostInfo(addr, hostinfo, f)
|
||||||
@@ -623,11 +615,10 @@ func (hm *HostMap) unlockedAddHostInfo(hostinfo *HostInfo, f *Interface) {
|
|||||||
hm.Indexes[hostinfo.localIndexId] = hostinfo
|
hm.Indexes[hostinfo.localIndexId] = hostinfo
|
||||||
hm.RemoteIndexes[hostinfo.remoteIndexId] = hostinfo
|
hm.RemoteIndexes[hostinfo.remoteIndexId] = hostinfo
|
||||||
|
|
||||||
if hm.l.Enabled(context.Background(), slog.LevelDebug) {
|
if hm.l.Level >= logrus.DebugLevel {
|
||||||
hm.l.Debug("Hostmap vpnIp added",
|
hm.l.WithField("hostMap", m{"vpnAddrs": hostinfo.vpnAddrs, "mapTotalSize": len(hm.Hosts),
|
||||||
"hostMap", m{"vpnAddrs": hostinfo.vpnAddrs, "mapTotalSize": len(hm.Hosts),
|
"hostinfo": m{"existing": true, "localIndexId": hostinfo.localIndexId, "vpnAddrs": hostinfo.vpnAddrs}}).
|
||||||
"hostinfo": m{"existing": true, "localIndexId": hostinfo.localIndexId, "vpnAddrs": hostinfo.vpnAddrs}},
|
Debug("Hostmap vpnIp added")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,21 +784,18 @@ func (i *HostInfo) buildNetworks(myVpnNetworksTable *bart.Lite, c cert.Certifica
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// logger returns a derived slog.Logger with per-hostinfo fields pre-bound.
|
func (i *HostInfo) logger(l *logrus.Logger) *logrus.Entry {
|
||||||
func (i *HostInfo) logger(l *slog.Logger) *slog.Logger {
|
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return l
|
return logrus.NewEntry(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
li := l.With(
|
li := l.WithField("vpnAddrs", i.vpnAddrs).
|
||||||
"vpnAddrs", i.vpnAddrs,
|
WithField("localIndex", i.localIndexId).
|
||||||
"localIndex", i.localIndexId,
|
WithField("remoteIndex", i.remoteIndexId)
|
||||||
"remoteIndex", i.remoteIndexId,
|
|
||||||
)
|
|
||||||
|
|
||||||
if connState := i.ConnectionState; connState != nil {
|
if connState := i.ConnectionState; connState != nil {
|
||||||
if peerCert := connState.peerCert; peerCert != nil {
|
if peerCert := connState.peerCert; peerCert != nil {
|
||||||
li = li.With("certName", peerCert.Certificate.Name())
|
li = li.WithField("certName", peerCert.Certificate.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,17 +804,14 @@ func (i *HostInfo) logger(l *slog.Logger) *slog.Logger {
|
|||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
|
|
||||||
func localAddrs(l *slog.Logger, allowList *LocalAllowList) []netip.Addr {
|
func localAddrs(l *logrus.Logger, allowList *LocalAllowList) []netip.Addr {
|
||||||
//FIXME: This function is pretty garbage
|
//FIXME: This function is pretty garbage
|
||||||
var finalAddrs []netip.Addr
|
var finalAddrs []netip.Addr
|
||||||
ifaces, _ := net.Interfaces()
|
ifaces, _ := net.Interfaces()
|
||||||
for _, i := range ifaces {
|
for _, i := range ifaces {
|
||||||
allow := allowList.AllowName(i.Name)
|
allow := allowList.AllowName(i.Name)
|
||||||
if l.Enabled(context.Background(), logging.LevelTrace) {
|
if l.Level >= logrus.TraceLevel {
|
||||||
l.Log(context.Background(), logging.LevelTrace, "localAllowList.AllowName",
|
l.WithField("interfaceName", i.Name).WithField("allow", allow).Trace("localAllowList.AllowName")
|
||||||
"interfaceName", i.Name,
|
|
||||||
"allow", allow,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allow {
|
if !allow {
|
||||||
@@ -844,8 +829,8 @@ func localAddrs(l *slog.Logger, allowList *LocalAllowList) []netip.Addr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !addr.IsValid() {
|
if !addr.IsValid() {
|
||||||
if l.Enabled(context.Background(), slog.LevelDebug) {
|
if l.Level >= logrus.DebugLevel {
|
||||||
l.Debug("addr was invalid", "localAddr", rawAddr)
|
l.WithField("localAddr", rawAddr).Debug("addr was invalid")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -853,11 +838,8 @@ func localAddrs(l *slog.Logger, allowList *LocalAllowList) []netip.Addr {
|
|||||||
|
|
||||||
if addr.IsLoopback() == false && addr.IsLinkLocalUnicast() == false {
|
if addr.IsLoopback() == false && addr.IsLinkLocalUnicast() == false {
|
||||||
isAllowed := allowList.Allow(addr)
|
isAllowed := allowList.Allow(addr)
|
||||||
if l.Enabled(context.Background(), logging.LevelTrace) {
|
if l.Level >= logrus.TraceLevel {
|
||||||
l.Log(context.Background(), logging.LevelTrace, "localAllowList.Allow",
|
l.WithField("localAddr", addr).WithField("allowed", isAllowed).Trace("localAllowList.Allow")
|
||||||
"localAddr", addr,
|
|
||||||
"allowed", isAllowed,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if !isAllowed {
|
if !isAllowed {
|
||||||
continue
|
continue
|
||||||
|
|||||||
+1
-1
@@ -196,7 +196,7 @@ func TestHostMap_DeleteHostInfo(t *testing.T) {
|
|||||||
|
|
||||||
func TestHostMap_reload(t *testing.T) {
|
func TestHostMap_reload(t *testing.T) {
|
||||||
l := test.NewLogger()
|
l := test.NewLogger()
|
||||||
c := config.NewC(test.NewLogger())
|
c := config.NewC(l)
|
||||||
|
|
||||||
hm := NewHostMapFromConfig(l, c)
|
hm := NewHostMapFromConfig(l, c)
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/iputil"
|
"github.com/slackhq/nebula/iputil"
|
||||||
"github.com/slackhq/nebula/noiseutil"
|
"github.com/slackhq/nebula/noiseutil"
|
||||||
"github.com/slackhq/nebula/overlay/batch"
|
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb []byte, sendBatch batch.TxBatcher, rejectBuf []byte, q int, localCache firewall.ConntrackCache) {
|
func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet, nb []byte, batch *sendBatch, rejectBuf []byte, q int, localCache firewall.ConntrackCache) {
|
||||||
err := newPacket(packet, false, fwPacket)
|
err := newPacket(packet, false, fwPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Error while validating outbound packet",
|
f.l.WithField("packet", packet).Debugf("Error while validating outbound packet: %s", err)
|
||||||
"packet", packet,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -38,9 +33,9 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
// routes packets from the Nebula addr to the Nebula addr through the Nebula
|
// routes packets from the Nebula addr to the Nebula addr through the Nebula
|
||||||
// TUN device.
|
// TUN device.
|
||||||
if immediatelyForwardToSelf {
|
if immediatelyForwardToSelf {
|
||||||
_, err := f.readers[q].Write(packet)
|
_, err := f.readers[q].WriteReject(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to forward to tun", "error", err)
|
f.l.WithError(err).Error("Failed to forward to tun")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise, drop. On linux, we should never see these packets - Linux
|
// Otherwise, drop. On linux, we should never see these packets - Linux
|
||||||
@@ -59,11 +54,10 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
|
|
||||||
if hostinfo == nil {
|
if hostinfo == nil {
|
||||||
f.rejectInside(packet, rejectBuf, q)
|
f.rejectInside(packet, rejectBuf, q)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("dropping outbound packet, vpnAddr not in our vpn networks or in unsafe networks",
|
f.l.WithField("vpnAddr", fwPacket.RemoteAddr).
|
||||||
"vpnAddr", fwPacket.RemoteAddr,
|
WithField("fwPacket", fwPacket).
|
||||||
"fwPacket", fwPacket,
|
Debugln("dropping outbound packet, vpnAddr not in our vpn networks or in unsafe networks")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -74,14 +68,15 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
|
|
||||||
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
||||||
if dropReason == nil {
|
if dropReason == nil {
|
||||||
f.sendInsideMessage(hostinfo, packet, nb, sendBatch, rejectBuf, q)
|
f.sendInsideMessage(hostinfo, packet, nb, batch, rejectBuf, q)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
f.rejectInside(packet, rejectBuf, q)
|
f.rejectInside(packet, rejectBuf, q)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).Debug("dropping outbound packet",
|
hostinfo.logger(f.l).
|
||||||
"fwPacket", fwPacket,
|
WithField("fwPacket", fwPacket).
|
||||||
"reason", dropReason,
|
WithField("reason", dropReason).
|
||||||
)
|
Debugln("dropping outbound packet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +85,7 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
// caller's batch slot for later sendmmsg flush. When hostinfo.remote is not
|
// caller's batch slot for later sendmmsg flush. When hostinfo.remote is not
|
||||||
// valid we fall through to the relay slow path via the unbatched sendNoMetrics
|
// valid we fall through to the relay slow path via the unbatched sendNoMetrics
|
||||||
// so relay behavior is unchanged.
|
// so relay behavior is unchanged.
|
||||||
func (f *Interface) sendInsideMessage(hostinfo *HostInfo, p, nb []byte, sendBatch batch.TxBatcher, rejectBuf []byte, q int) {
|
func (f *Interface) sendInsideMessage(hostinfo *HostInfo, p, nb []byte, batch *sendBatch, rejectBuf []byte, q int) {
|
||||||
ci := hostinfo.ConnectionState
|
ci := hostinfo.ConnectionState
|
||||||
if ci.eKey == nil {
|
if ci.eKey == nil {
|
||||||
return
|
return
|
||||||
@@ -103,7 +98,7 @@ func (f *Interface) sendInsideMessage(hostinfo *HostInfo, p, nb []byte, sendBatc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scratch := sendBatch.Next()
|
scratch := batch.Next()
|
||||||
if scratch == nil {
|
if scratch == nil {
|
||||||
// Batch full: bypass batching and send this packet directly so we
|
// Batch full: bypass batching and send this packet directly so we
|
||||||
// never drop traffic on over-subscribed iterations.
|
// never drop traffic on over-subscribed iterations.
|
||||||
@@ -124,10 +119,8 @@ func (f *Interface) sendInsideMessage(hostinfo *HostInfo, p, nb []byte, sendBatc
|
|||||||
// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.
|
// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.
|
||||||
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
||||||
hostinfo.lastRebindCount = f.rebindCount
|
hostinfo.lastRebindCount = f.rebindCount
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).Debug("Lighthouse update triggered for punch due to rebind counter",
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).Debug("Lighthouse update triggered for punch due to rebind counter")
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,15 +129,13 @@ func (f *Interface) sendInsideMessage(hostinfo *HostInfo, p, nb []byte, sendBatc
|
|||||||
ci.writeLock.Unlock()
|
ci.writeLock.Unlock()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to encrypt outgoing packet",
|
hostinfo.logger(f.l).WithError(err).
|
||||||
"error", err,
|
WithField("udpAddr", hostinfo.remote).WithField("counter", c).
|
||||||
"udpAddr", hostinfo.remote,
|
Error("Failed to encrypt outgoing packet")
|
||||||
"counter", c,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendBatch.Commit(len(out), hostinfo.remote)
|
batch.Commit(len(out), hostinfo.remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
||||||
@@ -157,9 +148,9 @@ func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := f.readers[q].Write(out)
|
_, err := f.readers[q].WriteReject(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to write to tun", "error", err)
|
f.l.WithError(err).Error("Failed to write to tun")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,11 +165,11 @@ func (f *Interface) rejectOutside(packet []byte, ci *ConnectionState, hostinfo *
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(out) > iputil.MaxRejectPacketSize {
|
if len(out) > iputil.MaxRejectPacketSize {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelInfo) {
|
if f.l.GetLevel() >= logrus.InfoLevel {
|
||||||
f.l.Info("rejectOutside: packet too big, not sending",
|
f.l.
|
||||||
"packet", packet,
|
WithField("packet", packet).
|
||||||
"outPacket", out,
|
WithField("outPacket", out).
|
||||||
)
|
Info("rejectOutside: packet too big, not sending")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -250,11 +241,10 @@ func (f *Interface) getOrHandshakeConsiderRouting(fwPacket *firewall.Packet, cac
|
|||||||
// This would also need to interact with unsafe_route updates through reloading the config or
|
// This would also need to interact with unsafe_route updates through reloading the config or
|
||||||
// use of the use_system_route_table option
|
// use of the use_system_route_table option
|
||||||
|
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Calculated gateway for ECMP not available, attempting other gateways",
|
f.l.WithField("destination", destinationAddr).
|
||||||
"destination", destinationAddr,
|
WithField("originalGateway", gatewayAddr).
|
||||||
"originalGateway", gatewayAddr,
|
Debugln("Calculated gateway for ECMP not available, attempting other gateways")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range gateways {
|
for i := range gateways {
|
||||||
@@ -280,18 +270,17 @@ func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubTyp
|
|||||||
fp := &firewall.Packet{}
|
fp := &firewall.Packet{}
|
||||||
err := newPacket(p, false, fp)
|
err := newPacket(p, false, fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Warn("error while parsing outgoing packet for firewall check", "error", err)
|
f.l.Warnf("error while parsing outgoing packet for firewall check; %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if packet is in outbound fw rules
|
// check if packet is in outbound fw rules
|
||||||
dropReason := f.firewall.Drop(*fp, false, hostinfo, f.pki.GetCAPool(), nil)
|
dropReason := f.firewall.Drop(*fp, false, hostinfo, f.pki.GetCAPool(), nil)
|
||||||
if dropReason != nil {
|
if dropReason != nil {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("dropping cached packet",
|
f.l.WithField("fwPacket", fp).
|
||||||
"fwPacket", fp,
|
WithField("reason", dropReason).
|
||||||
"reason", dropReason,
|
Debugln("dropping cached packet")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -307,10 +296,9 @@ func (f *Interface) SendMessageToVpnAddr(t header.MessageType, st header.Message
|
|||||||
})
|
})
|
||||||
|
|
||||||
if hostInfo == nil {
|
if hostInfo == nil {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("dropping SendMessageToVpnAddr, vpnAddr not in our vpn networks or in unsafe routes",
|
f.l.WithField("vpnAddr", vpnAddr).
|
||||||
"vpnAddr", vpnAddr,
|
Debugln("dropping SendMessageToVpnAddr, vpnAddr not in our vpn networks or in unsafe routes")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -366,12 +354,12 @@ func (f *Interface) SendVia(via *HostInfo,
|
|||||||
if noiseutil.EncryptLockNeeded {
|
if noiseutil.EncryptLockNeeded {
|
||||||
via.ConnectionState.writeLock.Unlock()
|
via.ConnectionState.writeLock.Unlock()
|
||||||
}
|
}
|
||||||
via.logger(f.l).Error("SendVia out buffer not large enough for relay",
|
via.logger(f.l).
|
||||||
"outCap", cap(out),
|
WithField("outCap", cap(out)).
|
||||||
"payloadLen", len(ad),
|
WithField("payloadLen", len(ad)).
|
||||||
"headerLen", len(out),
|
WithField("headerLen", len(out)).
|
||||||
"cipherOverhead", via.ConnectionState.eKey.Overhead(),
|
WithField("cipherOverhead", via.ConnectionState.eKey.Overhead()).
|
||||||
)
|
Error("SendVia out buffer not large enough for relay")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,12 +379,12 @@ func (f *Interface) SendVia(via *HostInfo,
|
|||||||
via.ConnectionState.writeLock.Unlock()
|
via.ConnectionState.writeLock.Unlock()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
via.logger(f.l).Info("Failed to EncryptDanger in sendVia", "error", err)
|
via.logger(f.l).WithError(err).Info("Failed to EncryptDanger in sendVia")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = f.writers[0].WriteTo(out, via.remote)
|
err = f.writers[0].WriteTo(out, via.remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
via.logger(f.l).Info("Failed to WriteTo in sendVia", "error", err)
|
via.logger(f.l).WithError(err).Info("Failed to WriteTo in sendVia")
|
||||||
}
|
}
|
||||||
f.connectionManager.RelayUsed(relay.LocalIndex)
|
f.connectionManager.RelayUsed(relay.LocalIndex)
|
||||||
}
|
}
|
||||||
@@ -435,10 +423,8 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
|
|||||||
// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.
|
// finally used again. This tunnel would eventually be torn down and recreated if this action didn't help.
|
||||||
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
f.lightHouse.QueryServer(hostinfo.vpnAddrs[0])
|
||||||
hostinfo.lastRebindCount = f.rebindCount
|
hostinfo.lastRebindCount = f.rebindCount
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Lighthouse update triggered for punch due to rebind counter",
|
f.l.WithField("vpnAddrs", hostinfo.vpnAddrs).Debug("Lighthouse update triggered for punch due to rebind counter")
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,30 +434,24 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
|
|||||||
ci.writeLock.Unlock()
|
ci.writeLock.Unlock()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to encrypt outgoing packet",
|
hostinfo.logger(f.l).WithError(err).
|
||||||
"error", err,
|
WithField("udpAddr", remote).WithField("counter", c).
|
||||||
"udpAddr", remote,
|
WithField("attemptedCounter", c).
|
||||||
"counter", c,
|
Error("Failed to encrypt outgoing packet")
|
||||||
"attemptedCounter", c,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if remote.IsValid() {
|
if remote.IsValid() {
|
||||||
err = f.writers[q].WriteTo(out, remote)
|
err = f.writers[q].WriteTo(out, remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to write outgoing packet",
|
hostinfo.logger(f.l).WithError(err).
|
||||||
"error", err,
|
WithField("udpAddr", remote).Error("Failed to write outgoing packet")
|
||||||
"udpAddr", remote,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if hostinfo.remote.IsValid() {
|
} else if hostinfo.remote.IsValid() {
|
||||||
err = f.writers[q].WriteTo(out, hostinfo.remote)
|
err = f.writers[q].WriteTo(out, hostinfo.remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to write outgoing packet",
|
hostinfo.logger(f.l).WithError(err).
|
||||||
"error", err,
|
WithField("udpAddr", remote).Error("Failed to write outgoing packet")
|
||||||
"udpAddr", remote,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Try to send via a relay
|
// Try to send via a relay
|
||||||
@@ -479,10 +459,7 @@ func (f *Interface) sendNoMetrics(t header.MessageType, st header.MessageSubType
|
|||||||
relayHostInfo, relay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relayIP)
|
relayHostInfo, relay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relayIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.relayState.DeleteRelay(relayIP)
|
hostinfo.relayState.DeleteRelay(relayIP)
|
||||||
hostinfo.logger(f.l).Info("sendNoMetrics failed to find HostInfo",
|
hostinfo.logger(f.l).WithField("relay", relayIP).WithError(err).Info("sendNoMetrics failed to find HostInfo")
|
||||||
"relay", relayIP,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)
|
f.SendVia(relayHostInfo, relay, out, nb, fullOut[:header.Len+len(out)], true)
|
||||||
|
|||||||
+68
-69
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -12,12 +11,12 @@ import (
|
|||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
"github.com/slackhq/nebula/overlay/batch"
|
"github.com/slackhq/nebula/overlay/coalesce"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/udp"
|
"github.com/slackhq/nebula/udp"
|
||||||
)
|
)
|
||||||
@@ -31,7 +30,7 @@ type InterfaceConfig struct {
|
|||||||
pki *PKI
|
pki *PKI
|
||||||
Cipher string
|
Cipher string
|
||||||
Firewall *Firewall
|
Firewall *Firewall
|
||||||
DnsServer *dnsServer
|
ServeDns bool
|
||||||
HandshakeManager *HandshakeManager
|
HandshakeManager *HandshakeManager
|
||||||
lightHouse *LightHouse
|
lightHouse *LightHouse
|
||||||
connectionManager *connectionManager
|
connectionManager *connectionManager
|
||||||
@@ -48,7 +47,7 @@ type InterfaceConfig struct {
|
|||||||
reQueryWait time.Duration
|
reQueryWait time.Duration
|
||||||
|
|
||||||
ConntrackCacheTimeout time.Duration
|
ConntrackCacheTimeout time.Duration
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type Interface struct {
|
type Interface struct {
|
||||||
@@ -59,7 +58,7 @@ type Interface struct {
|
|||||||
firewall *Firewall
|
firewall *Firewall
|
||||||
connectionManager *connectionManager
|
connectionManager *connectionManager
|
||||||
handshakeManager *HandshakeManager
|
handshakeManager *HandshakeManager
|
||||||
dnsServer *dnsServer
|
serveDns bool
|
||||||
createTime time.Time
|
createTime time.Time
|
||||||
lightHouse *LightHouse
|
lightHouse *LightHouse
|
||||||
myBroadcastAddrsTable *bart.Lite
|
myBroadcastAddrsTable *bart.Lite
|
||||||
@@ -87,13 +86,12 @@ type Interface struct {
|
|||||||
|
|
||||||
conntrackCacheTimeout time.Duration
|
conntrackCacheTimeout time.Duration
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
writers []udp.Conn
|
writers []udp.Conn
|
||||||
readers []tio.Queue
|
readers []tio.Queue
|
||||||
// batchers is one per tun queue, wrapping readers[i].
|
// tunCoalescers is one tcpCoalescer per tun queue, wrapping readers[i].
|
||||||
// decryptToTun sends plaintext into the batch.RxBatcher;
|
// decryptToTun sends plaintext into the coalescer; listenOut calls its
|
||||||
// listenOut calls its Flush at the end of each UDP recvmmsg batch.
|
// Flush at the end of each UDP recvmmsg batch.
|
||||||
batchers []batch.RxBatcher
|
tunCoalescers []*coalesce.TCPCoalescer
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
// fatalErr holds the first unexpected reader error that caused shutdown.
|
// fatalErr holds the first unexpected reader error that caused shutdown.
|
||||||
@@ -106,7 +104,7 @@ type Interface struct {
|
|||||||
messageMetrics *MessageMetrics
|
messageMetrics *MessageMetrics
|
||||||
cachedPacketMetrics *cachedPacketMetrics
|
cachedPacketMetrics *cachedPacketMetrics
|
||||||
|
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type EncWriter interface {
|
type EncWriter interface {
|
||||||
@@ -177,13 +175,12 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
|||||||
|
|
||||||
cs := c.pki.getCertState()
|
cs := c.pki.getCertState()
|
||||||
ifce := &Interface{
|
ifce := &Interface{
|
||||||
ctx: ctx,
|
|
||||||
pki: c.pki,
|
pki: c.pki,
|
||||||
hostMap: c.HostMap,
|
hostMap: c.HostMap,
|
||||||
outside: c.Outside,
|
outside: c.Outside,
|
||||||
inside: c.Inside,
|
inside: c.Inside,
|
||||||
firewall: c.Firewall,
|
firewall: c.Firewall,
|
||||||
dnsServer: c.DnsServer,
|
serveDns: c.ServeDns,
|
||||||
handshakeManager: c.HandshakeManager,
|
handshakeManager: c.HandshakeManager,
|
||||||
createTime: time.Now(),
|
createTime: time.Now(),
|
||||||
lightHouse: c.lightHouse,
|
lightHouse: c.lightHouse,
|
||||||
@@ -193,7 +190,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
|||||||
version: c.version,
|
version: c.version,
|
||||||
writers: make([]udp.Conn, c.routines),
|
writers: make([]udp.Conn, c.routines),
|
||||||
readers: make([]tio.Queue, c.routines),
|
readers: make([]tio.Queue, c.routines),
|
||||||
batchers: make([]batch.RxBatcher, c.routines),
|
tunCoalescers: make([]*coalesce.TCPCoalescer, c.routines),
|
||||||
myVpnNetworks: cs.myVpnNetworks,
|
myVpnNetworks: cs.myVpnNetworks,
|
||||||
myVpnNetworksTable: cs.myVpnNetworksTable,
|
myVpnNetworksTable: cs.myVpnNetworksTable,
|
||||||
myVpnAddrs: cs.myVpnAddrs,
|
myVpnAddrs: cs.myVpnAddrs,
|
||||||
@@ -230,16 +227,13 @@ func (f *Interface) activate() error {
|
|||||||
|
|
||||||
addr, err := f.outside.LocalAddr()
|
addr, err := f.outside.LocalAddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to get udp listen address", "error", err)
|
f.l.WithError(err).Error("Failed to get udp listen address")
|
||||||
}
|
}
|
||||||
|
|
||||||
f.l.Info("Nebula interface is active",
|
f.l.WithField("interface", f.inside.Name()).WithField("networks", f.myVpnNetworks).
|
||||||
"interface", f.inside.Name(),
|
WithField("build", f.version).WithField("udpAddr", addr).
|
||||||
"networks", f.myVpnNetworks,
|
WithField("boringcrypto", boringEnabled()).
|
||||||
"build", f.version,
|
Info("Nebula interface is active")
|
||||||
"udpAddr", addr,
|
|
||||||
"boringcrypto", boringEnabled(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if f.routines > 1 {
|
if f.routines > 1 {
|
||||||
if !f.inside.SupportsMultiqueue() || !f.outside.SupportsMultipleReaders() {
|
if !f.inside.SupportsMultiqueue() || !f.outside.SupportsMultipleReaders() {
|
||||||
@@ -253,14 +247,15 @@ func (f *Interface) activate() error {
|
|||||||
// Prepare n tun queues
|
// Prepare n tun queues
|
||||||
for i := 0; i < f.routines; i++ {
|
for i := 0; i < f.routines; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
if err = f.inside.NewMultiQueueReader(); err != nil {
|
err = f.inside.NewMultiQueueReader()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.readers = f.inside.Readers()
|
f.readers = f.inside.Readers()
|
||||||
for i := range f.readers {
|
for i := range f.readers {
|
||||||
f.batchers[i] = batch.NewTCPCoalescer(f.readers[i])
|
f.tunCoalescers[i] = coalesce.NewTCPCoalescer(f.readers[i]) //todo don't always do this
|
||||||
}
|
}
|
||||||
|
|
||||||
f.wg.Add(1) // for us to wait on Close() to return
|
f.wg.Add(1) // for us to wait on Close() to return
|
||||||
@@ -316,73 +311,77 @@ func (f *Interface) listenOut(i int) {
|
|||||||
li = f.outside
|
li = f.outside
|
||||||
}
|
}
|
||||||
|
|
||||||
ctCache := firewall.NewConntrackCacheTicker(f.ctx, f.l, f.conntrackCacheTimeout)
|
ctCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||||
lhh := f.lightHouse.NewRequestHandler()
|
lhh := f.lightHouse.NewRequestHandler()
|
||||||
h := &header.H{}
|
h := &header.H{}
|
||||||
fwPacket := &firewall.Packet{}
|
fwPacket := &firewall.Packet{}
|
||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
|
|
||||||
coalescer := f.batchers[i]
|
// plaintexts is a ring of decrypt scratches, one per packet in a UDP
|
||||||
|
// recvmmsg batch. The coalescer borrows payload slices from here and
|
||||||
listener := func(fromUdpAddr netip.AddrPort, payload []byte) {
|
// requires they stay valid until Flush — so we rotate each packet and
|
||||||
plaintext := f.batchers[i].Reserve(len(payload))
|
// reset only in the batch-end flush callback.
|
||||||
f.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get())
|
var plaintexts [][]byte
|
||||||
|
idx := 0
|
||||||
|
coalescer := f.tunCoalescers[i]
|
||||||
|
err := li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) {
|
||||||
|
if idx >= len(plaintexts) {
|
||||||
|
plaintexts = append(plaintexts, make([]byte, udp.MTU))
|
||||||
}
|
}
|
||||||
|
f.readOutsidePackets(ViaSender{UdpAddr: fromUdpAddr}, plaintexts[idx][:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l))
|
||||||
flusher := func() {
|
idx++
|
||||||
|
}, func() {
|
||||||
if err := coalescer.Flush(); err != nil {
|
if err := coalescer.Flush(); err != nil {
|
||||||
f.l.Error("Failed to flush tun coalescer", "error", err)
|
f.l.WithError(err).Error("Failed to flush tun coalescer")
|
||||||
}
|
}
|
||||||
}
|
idx = 0
|
||||||
|
})
|
||||||
err := li.ListenOut(listener, flusher)
|
|
||||||
|
|
||||||
if err != nil && !f.closed.Load() {
|
if err != nil && !f.closed.Load() {
|
||||||
f.l.Error("Error while reading inbound packet, closing", "error", err)
|
f.l.WithError(err).Error("Error while reading inbound packet, closing")
|
||||||
f.onFatal(err)
|
f.onFatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.l.Debug("underlay reader is done", "reader", i)
|
f.l.Debugf("underlay reader %v is done", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) listenIn(reader tio.Queue, i int) {
|
func (f *Interface) listenIn(reader tio.Queue, i int) {
|
||||||
rejectBuf := make([]byte, mtu)
|
rejectBuf := make([]byte, mtu)
|
||||||
sb := batch.NewSendBatch(batch.SendBatchCap, udp.MTU+32)
|
batch := newSendBatch(sendBatchCap, udp.MTU+32)
|
||||||
fwPacket := &firewall.Packet{}
|
fwPacket := &firewall.Packet{}
|
||||||
nb := make([]byte, 12, 12)
|
nb := make([]byte, 12, 12)
|
||||||
|
|
||||||
conntrackCache := firewall.NewConntrackCacheTicker(f.ctx, f.l, f.conntrackCacheTimeout)
|
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
pkts, err := reader.Read()
|
pkts, err := reader.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !f.closed.Load() {
|
if !f.closed.Load() {
|
||||||
f.l.Error("Error while reading outbound packet, closing", "error", err, "reader", i)
|
f.l.WithError(err).WithField("reader", i).Error("Error while reading outbound packet, closing")
|
||||||
f.onFatal(err)
|
f.onFatal(err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Reset()
|
batch.Reset()
|
||||||
for _, pkt := range pkts {
|
for _, pkt := range pkts {
|
||||||
if sb.Len() >= sb.Cap() {
|
if batch.Len() >= batch.Cap() {
|
||||||
f.flushBatch(sb, i)
|
f.flushBatch(batch, i)
|
||||||
sb.Reset()
|
batch.Reset()
|
||||||
}
|
}
|
||||||
f.consumeInsidePacket(pkt, fwPacket, nb, sb, rejectBuf, i, conntrackCache.Get())
|
f.consumeInsidePacket(pkt, fwPacket, nb, batch, rejectBuf, i, conntrackCache.Get(f.l))
|
||||||
}
|
}
|
||||||
if sb.Len() > 0 {
|
if batch.Len() > 0 {
|
||||||
f.flushBatch(sb, i)
|
f.flushBatch(batch, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.l.Debug("overlay reader is done", "reader", i)
|
f.l.Debugf("overlay reader %v is done", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) flushBatch(sb batch.TxBatcher, q int) {
|
func (f *Interface) flushBatch(batch *sendBatch, q int) {
|
||||||
bufs, dsts := sb.Get()
|
if err := f.writers[q].WriteBatch(batch.bufs, batch.dsts); err != nil {
|
||||||
if err := f.writers[q].WriteBatch(bufs, dsts); err != nil {
|
f.l.WithError(err).WithField("writer", q).Error("Failed to write outgoing batch")
|
||||||
f.l.Error("Failed to write outgoing batch", "error", err, "writer", q)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +402,7 @@ func (f *Interface) reloadDisconnectInvalid(c *config.C) {
|
|||||||
if initial || c.HasChanged("pki.disconnect_invalid") {
|
if initial || c.HasChanged("pki.disconnect_invalid") {
|
||||||
f.disconnectInvalid.Store(c.GetBool("pki.disconnect_invalid", true))
|
f.disconnectInvalid.Store(c.GetBool("pki.disconnect_invalid", true))
|
||||||
if !initial {
|
if !initial {
|
||||||
f.l.Info("pki.disconnect_invalid changed", "value", f.disconnectInvalid.Load())
|
f.l.Infof("pki.disconnect_invalid changed to %v", f.disconnectInvalid.Load())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,7 +416,7 @@ func (f *Interface) reloadFirewall(c *config.C) {
|
|||||||
|
|
||||||
fw, err := NewFirewallFromConfig(f.l, f.pki.getCertState(), c)
|
fw, err := NewFirewallFromConfig(f.l, f.pki.getCertState(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Error while creating firewall during reload", "error", err)
|
f.l.WithError(err).Error("Error while creating firewall during reload")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,11 +429,10 @@ func (f *Interface) reloadFirewall(c *config.C) {
|
|||||||
// If rulesVersion is back to zero, we have wrapped all the way around. Be
|
// If rulesVersion is back to zero, we have wrapped all the way around. Be
|
||||||
// safe and just reset conntrack in this case.
|
// safe and just reset conntrack in this case.
|
||||||
if fw.rulesVersion == 0 {
|
if fw.rulesVersion == 0 {
|
||||||
f.l.Warn("firewall rulesVersion has overflowed, resetting conntrack",
|
f.l.WithField("firewallHashes", fw.GetRuleHashes()).
|
||||||
"firewallHashes", fw.GetRuleHashes(),
|
WithField("oldFirewallHashes", oldFw.GetRuleHashes()).
|
||||||
"oldFirewallHashes", oldFw.GetRuleHashes(),
|
WithField("rulesVersion", fw.rulesVersion).
|
||||||
"rulesVersion", fw.rulesVersion,
|
Warn("firewall rulesVersion has overflowed, resetting conntrack")
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
fw.Conntrack = conntrack
|
fw.Conntrack = conntrack
|
||||||
}
|
}
|
||||||
@@ -442,11 +440,10 @@ func (f *Interface) reloadFirewall(c *config.C) {
|
|||||||
f.firewall = fw
|
f.firewall = fw
|
||||||
|
|
||||||
oldFw.Destroy()
|
oldFw.Destroy()
|
||||||
f.l.Info("New firewall has been installed",
|
f.l.WithField("firewallHashes", fw.GetRuleHashes()).
|
||||||
"firewallHashes", fw.GetRuleHashes(),
|
WithField("oldFirewallHashes", oldFw.GetRuleHashes()).
|
||||||
"oldFirewallHashes", oldFw.GetRuleHashes(),
|
WithField("rulesVersion", fw.rulesVersion).
|
||||||
"rulesVersion", fw.rulesVersion,
|
Info("New firewall has been installed")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) reloadSendRecvError(c *config.C) {
|
func (f *Interface) reloadSendRecvError(c *config.C) {
|
||||||
@@ -468,7 +465,8 @@ func (f *Interface) reloadSendRecvError(c *config.C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.l.Info("Loaded send_recv_error config", "sendRecvError", f.sendRecvErrorConfig.String())
|
f.l.WithField("sendRecvError", f.sendRecvErrorConfig.String()).
|
||||||
|
Info("Loaded send_recv_error config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +489,8 @@ func (f *Interface) reloadAcceptRecvError(c *config.C) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.l.Info("Loaded accept_recv_error config", "acceptRecvError", f.acceptRecvErrorConfig.String())
|
f.l.WithField("acceptRecvError", f.acceptRecvErrorConfig.String()).
|
||||||
|
Info("Loaded accept_recv_error config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +564,7 @@ func (f *Interface) Close() error {
|
|||||||
for i, u := range f.writers {
|
for i, u := range f.writers {
|
||||||
err := u.Close()
|
err := u.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Error while closing udp socket", "error", err, "writer", i)
|
f.l.WithError(err).WithField("writer", i).Error("Error while closing udp socket")
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-170
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -16,10 +15,10 @@ import (
|
|||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/udp"
|
"github.com/slackhq/nebula/udp"
|
||||||
"github.com/slackhq/nebula/util"
|
"github.com/slackhq/nebula/util"
|
||||||
)
|
)
|
||||||
@@ -70,19 +69,18 @@ type LightHouse struct {
|
|||||||
// Addr's of relays that can be used by peers to access me
|
// Addr's of relays that can be used by peers to access me
|
||||||
relaysForMe atomic.Pointer[[]netip.Addr]
|
relaysForMe atomic.Pointer[[]netip.Addr]
|
||||||
|
|
||||||
updateTrigger chan struct{}
|
|
||||||
queryChan chan netip.Addr
|
queryChan chan netip.Addr
|
||||||
|
|
||||||
calculatedRemotes atomic.Pointer[bart.Table[[]*calculatedRemote]] // Maps VpnAddr to []*calculatedRemote
|
calculatedRemotes atomic.Pointer[bart.Table[[]*calculatedRemote]] // Maps VpnAddr to []*calculatedRemote
|
||||||
|
|
||||||
metrics *MessageMetrics
|
metrics *MessageMetrics
|
||||||
metricHolepunchTx metrics.Counter
|
metricHolepunchTx metrics.Counter
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLightHouseFromConfig will build a Lighthouse struct from the values provided in the config object
|
// NewLightHouseFromConfig will build a Lighthouse struct from the values provided in the config object
|
||||||
// addrMap should be nil unless this is during a config reload
|
// addrMap should be nil unless this is during a config reload
|
||||||
func NewLightHouseFromConfig(ctx context.Context, l *slog.Logger, c *config.C, cs *CertState, pc udp.Conn, p *Punchy) (*LightHouse, error) {
|
func NewLightHouseFromConfig(ctx context.Context, l *logrus.Logger, c *config.C, cs *CertState, pc udp.Conn, p *Punchy) (*LightHouse, error) {
|
||||||
amLighthouse := c.GetBool("lighthouse.am_lighthouse", false)
|
amLighthouse := c.GetBool("lighthouse.am_lighthouse", false)
|
||||||
nebulaPort := uint32(c.GetInt("listen.port", 0))
|
nebulaPort := uint32(c.GetInt("listen.port", 0))
|
||||||
if amLighthouse && nebulaPort == 0 {
|
if amLighthouse && nebulaPort == 0 {
|
||||||
@@ -107,7 +105,6 @@ func NewLightHouseFromConfig(ctx context.Context, l *slog.Logger, c *config.C, c
|
|||||||
nebulaPort: nebulaPort,
|
nebulaPort: nebulaPort,
|
||||||
punchConn: pc,
|
punchConn: pc,
|
||||||
punchy: p,
|
punchy: p,
|
||||||
updateTrigger: make(chan struct{}, 1),
|
|
||||||
queryChan: make(chan netip.Addr, c.GetUint32("handshakes.query_buffer", 64)),
|
queryChan: make(chan netip.Addr, c.GetUint32("handshakes.query_buffer", 64)),
|
||||||
l: l,
|
l: l,
|
||||||
}
|
}
|
||||||
@@ -134,7 +131,7 @@ func NewLightHouseFromConfig(ctx context.Context, l *slog.Logger, c *config.C, c
|
|||||||
case *util.ContextualError:
|
case *util.ContextualError:
|
||||||
v.Log(l)
|
v.Log(l)
|
||||||
case error:
|
case error:
|
||||||
l.Error("failed to reload lighthouse", "error", err)
|
l.WithError(err).Error("failed to reload lighthouse")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -206,10 +203,8 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|||||||
//TODO: we could technically insert all returned addrs instead of just the first one if a dns lookup was used
|
//TODO: we could technically insert all returned addrs instead of just the first one if a dns lookup was used
|
||||||
addr := addrs[0].Unmap()
|
addr := addrs[0].Unmap()
|
||||||
if lh.myVpnNetworksTable.Contains(addr) {
|
if lh.myVpnNetworksTable.Contains(addr) {
|
||||||
lh.l.Warn("Ignoring lighthouse.advertise_addrs report because it is within the nebula network range",
|
lh.l.WithField("addr", rawAddr).WithField("entry", i+1).
|
||||||
"addr", rawAddr,
|
Warn("Ignoring lighthouse.advertise_addrs report because it is within the nebula network range")
|
||||||
"entry", i+1,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,9 +222,7 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|||||||
lh.interval.Store(int64(c.GetInt("lighthouse.interval", 10)))
|
lh.interval.Store(int64(c.GetInt("lighthouse.interval", 10)))
|
||||||
|
|
||||||
if !initial {
|
if !initial {
|
||||||
lh.l.Info("lighthouse.interval changed",
|
lh.l.Infof("lighthouse.interval changed to %v", lh.interval.Load())
|
||||||
"interval", lh.interval.Load(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if lh.updateCancel != nil {
|
if lh.updateCancel != nil {
|
||||||
// May not always have a running routine
|
// May not always have a running routine
|
||||||
@@ -323,7 +316,6 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|||||||
if !initial {
|
if !initial {
|
||||||
//NOTE: we are not tearing down existing lighthouse connections because they might be used for non lighthouse traffic
|
//NOTE: we are not tearing down existing lighthouse connections because they might be used for non lighthouse traffic
|
||||||
lh.l.Info("lighthouse.hosts has changed")
|
lh.l.Info("lighthouse.hosts has changed")
|
||||||
lh.TriggerUpdate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,12 +333,9 @@ func (lh *LightHouse) reload(c *config.C, initial bool) error {
|
|||||||
for _, v := range c.GetStringSlice("relay.relays", nil) {
|
for _, v := range c.GetStringSlice("relay.relays", nil) {
|
||||||
configRIP, err := netip.ParseAddr(v)
|
configRIP, err := netip.ParseAddr(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lh.l.Warn("Parse relay from config failed",
|
lh.l.WithField("relay", v).WithError(err).Warn("Parse relay from config failed")
|
||||||
"relay", v,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
lh.l.Info("Read relay from config", "relay", v)
|
lh.l.WithField("relay", v).Info("Read relay from config")
|
||||||
relaysForMe = append(relaysForMe, configRIP)
|
relaysForMe = append(relaysForMe, configRIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,10 +360,8 @@ func (lh *LightHouse) parseLighthouses(c *config.C) ([]netip.Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !lh.myVpnNetworksTable.Contains(addr) {
|
if !lh.myVpnNetworksTable.Contains(addr) {
|
||||||
lh.l.Warn("lighthouse host is not within our networks, lighthouse functionality will work but layer 3 network traffic to the lighthouse will not",
|
lh.l.WithFields(m{"vpnAddr": addr, "networks": lh.myVpnNetworks}).
|
||||||
"vpnAddr", addr,
|
Warn("lighthouse host is not within our networks, lighthouse functionality will work but layer 3 network traffic to the lighthouse will not")
|
||||||
"networks", lh.myVpnNetworks,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
out[i] = addr
|
out[i] = addr
|
||||||
}
|
}
|
||||||
@@ -445,11 +432,8 @@ func (lh *LightHouse) loadStaticMap(c *config.C, staticList map[netip.Addr]struc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !lh.myVpnNetworksTable.Contains(vpnAddr) {
|
if !lh.myVpnNetworksTable.Contains(vpnAddr) {
|
||||||
lh.l.Warn("static_host_map key is not within our networks, layer 3 network traffic to this host will not work",
|
lh.l.WithFields(m{"vpnAddr": vpnAddr, "networks": lh.myVpnNetworks, "entry": i + 1}).
|
||||||
"vpnAddr", vpnAddr,
|
Warn("static_host_map key is not within our networks, layer 3 network traffic to this host will not work")
|
||||||
"networks", lh.myVpnNetworks,
|
|
||||||
"entry", i+1,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vals, ok := v.([]any)
|
vals, ok := v.([]any)
|
||||||
@@ -550,13 +534,12 @@ func (lh *LightHouse) DeleteVpnAddrs(allVpnAddrs []netip.Addr) {
|
|||||||
lh.Lock()
|
lh.Lock()
|
||||||
rm, ok := lh.addrMap[allVpnAddrs[0]]
|
rm, ok := lh.addrMap[allVpnAddrs[0]]
|
||||||
if ok {
|
if ok {
|
||||||
debugEnabled := lh.l.Enabled(context.Background(), slog.LevelDebug)
|
|
||||||
for _, addr := range allVpnAddrs {
|
for _, addr := range allVpnAddrs {
|
||||||
srm := lh.addrMap[addr]
|
srm := lh.addrMap[addr]
|
||||||
if srm == rm {
|
if srm == rm {
|
||||||
delete(lh.addrMap, addr)
|
delete(lh.addrMap, addr)
|
||||||
if debugEnabled {
|
if lh.l.Level >= logrus.DebugLevel {
|
||||||
lh.l.Debug("deleting from lighthouse", "vpnAddr", addr)
|
lh.l.Debugf("deleting %s from lighthouse.", addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -673,12 +656,9 @@ func (lh *LightHouse) unlockedGetRemoteList(allAddrs []netip.Addr) *RemoteList {
|
|||||||
|
|
||||||
func (lh *LightHouse) shouldAdd(vpnAddrs []netip.Addr, to netip.Addr) bool {
|
func (lh *LightHouse) shouldAdd(vpnAddrs []netip.Addr, to netip.Addr) bool {
|
||||||
allow := lh.GetRemoteAllowList().AllowAll(vpnAddrs, to)
|
allow := lh.GetRemoteAllowList().AllowAll(vpnAddrs, to)
|
||||||
if lh.l.Enabled(context.Background(), logging.LevelTrace) {
|
if lh.l.Level >= logrus.TraceLevel {
|
||||||
lh.l.Log(context.Background(), logging.LevelTrace, "remoteAllowList.Allow",
|
lh.l.WithField("vpnAddrs", vpnAddrs).WithField("udpAddr", to).WithField("allow", allow).
|
||||||
"vpnAddrs", vpnAddrs,
|
Trace("remoteAllowList.Allow")
|
||||||
"udpAddr", to,
|
|
||||||
"allow", allow,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if !allow {
|
if !allow {
|
||||||
return false
|
return false
|
||||||
@@ -695,12 +675,9 @@ func (lh *LightHouse) shouldAdd(vpnAddrs []netip.Addr, to netip.Addr) bool {
|
|||||||
func (lh *LightHouse) unlockedShouldAddV4(vpnAddr netip.Addr, to *V4AddrPort) bool {
|
func (lh *LightHouse) unlockedShouldAddV4(vpnAddr netip.Addr, to *V4AddrPort) bool {
|
||||||
udpAddr := protoV4AddrPortToNetAddrPort(to)
|
udpAddr := protoV4AddrPortToNetAddrPort(to)
|
||||||
allow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())
|
allow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())
|
||||||
if lh.l.Enabled(context.Background(), logging.LevelTrace) {
|
if lh.l.Level >= logrus.TraceLevel {
|
||||||
lh.l.Log(context.Background(), logging.LevelTrace, "remoteAllowList.Allow",
|
lh.l.WithField("vpnAddr", vpnAddr).WithField("udpAddr", udpAddr).WithField("allow", allow).
|
||||||
"vpnAddr", vpnAddr,
|
Trace("remoteAllowList.Allow")
|
||||||
"udpAddr", udpAddr,
|
|
||||||
"allow", allow,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allow {
|
if !allow {
|
||||||
@@ -718,12 +695,9 @@ func (lh *LightHouse) unlockedShouldAddV4(vpnAddr netip.Addr, to *V4AddrPort) bo
|
|||||||
func (lh *LightHouse) unlockedShouldAddV6(vpnAddr netip.Addr, to *V6AddrPort) bool {
|
func (lh *LightHouse) unlockedShouldAddV6(vpnAddr netip.Addr, to *V6AddrPort) bool {
|
||||||
udpAddr := protoV6AddrPortToNetAddrPort(to)
|
udpAddr := protoV6AddrPortToNetAddrPort(to)
|
||||||
allow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())
|
allow := lh.GetRemoteAllowList().Allow(vpnAddr, udpAddr.Addr())
|
||||||
if lh.l.Enabled(context.Background(), logging.LevelTrace) {
|
if lh.l.Level >= logrus.TraceLevel {
|
||||||
lh.l.Log(context.Background(), logging.LevelTrace, "remoteAllowList.Allow",
|
lh.l.WithField("vpnAddr", vpnAddr).WithField("udpAddr", udpAddr).WithField("allow", allow).
|
||||||
"vpnAddr", vpnAddr,
|
Trace("remoteAllowList.Allow")
|
||||||
"udpAddr", udpAddr,
|
|
||||||
"allow", allow,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allow {
|
if !allow {
|
||||||
@@ -798,10 +772,8 @@ func (lh *LightHouse) innerQueryServer(addr netip.Addr, nb, out []byte) {
|
|||||||
|
|
||||||
if v == cert.Version1 {
|
if v == cert.Version1 {
|
||||||
if !addr.Is4() {
|
if !addr.Is4() {
|
||||||
lh.l.Error("Can't query lighthouse for v6 address using a v1 protocol",
|
lh.l.WithField("queryVpnAddr", addr).WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"queryVpnAddr", addr,
|
Error("Can't query lighthouse for v6 address using a v1 protocol")
|
||||||
"lighthouseAddr", lhVpnAddr,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,11 +784,9 @@ func (lh *LightHouse) innerQueryServer(addr netip.Addr, nb, out []byte) {
|
|||||||
|
|
||||||
v1Query, err = msg.Marshal()
|
v1Query, err = msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lh.l.Error("Failed to marshal lighthouse v1 query payload",
|
lh.l.WithError(err).WithField("queryVpnAddr", addr).
|
||||||
"error", err,
|
WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"queryVpnAddr", addr,
|
Error("Failed to marshal lighthouse v1 query payload")
|
||||||
"lighthouseAddr", lhVpnAddr,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -831,11 +801,9 @@ func (lh *LightHouse) innerQueryServer(addr netip.Addr, nb, out []byte) {
|
|||||||
|
|
||||||
v2Query, err = msg.Marshal()
|
v2Query, err = msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lh.l.Error("Failed to marshal lighthouse v2 query payload",
|
lh.l.WithError(err).WithField("queryVpnAddr", addr).
|
||||||
"error", err,
|
WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"queryVpnAddr", addr,
|
Error("Failed to marshal lighthouse v2 query payload")
|
||||||
"lighthouseAddr", lhVpnAddr,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -844,11 +812,7 @@ func (lh *LightHouse) innerQueryServer(addr netip.Addr, nb, out []byte) {
|
|||||||
queried++
|
queried++
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
lh.l.Debug("unsupported protocol version",
|
lh.l.Debugf("Can not query lighthouse for %v using unknown protocol version: %v", addr, v)
|
||||||
"op", "query",
|
|
||||||
"queryVpnAddr", addr,
|
|
||||||
"version", v,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -877,24 +841,11 @@ func (lh *LightHouse) StartUpdateWorker() {
|
|||||||
return
|
return
|
||||||
case <-clockSource.C:
|
case <-clockSource.C:
|
||||||
continue
|
continue
|
||||||
case <-lh.updateTrigger:
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TriggerUpdate requests an immediate lighthouse update. This is a non-blocking
|
|
||||||
// operation intended to be called after a handshake completes with a lighthouse,
|
|
||||||
// so the lighthouse has our current addresses without waiting for the next
|
|
||||||
// periodic update.
|
|
||||||
func (lh *LightHouse) TriggerUpdate() {
|
|
||||||
select {
|
|
||||||
case lh.updateTrigger <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lh *LightHouse) SendUpdate() {
|
func (lh *LightHouse) SendUpdate() {
|
||||||
var v4 []*V4AddrPort
|
var v4 []*V4AddrPort
|
||||||
var v6 []*V6AddrPort
|
var v6 []*V6AddrPort
|
||||||
@@ -940,9 +891,8 @@ func (lh *LightHouse) SendUpdate() {
|
|||||||
if v == cert.Version1 {
|
if v == cert.Version1 {
|
||||||
if v1Update == nil {
|
if v1Update == nil {
|
||||||
if !lh.myVpnNetworks[0].Addr().Is4() {
|
if !lh.myVpnNetworks[0].Addr().Is4() {
|
||||||
lh.l.Warn("cannot update lighthouse using v1 protocol without an IPv4 address",
|
lh.l.WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"lighthouseAddr", lhVpnAddr,
|
Warn("cannot update lighthouse using v1 protocol without an IPv4 address")
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var relays []uint32
|
var relays []uint32
|
||||||
@@ -966,10 +916,8 @@ func (lh *LightHouse) SendUpdate() {
|
|||||||
|
|
||||||
v1Update, err = msg.Marshal()
|
v1Update, err = msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lh.l.Error("Error while marshaling for lighthouse v1 update",
|
lh.l.WithError(err).WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"error", err,
|
Error("Error while marshaling for lighthouse v1 update")
|
||||||
"lighthouseAddr", lhVpnAddr,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -995,10 +943,8 @@ func (lh *LightHouse) SendUpdate() {
|
|||||||
|
|
||||||
v2Update, err = msg.Marshal()
|
v2Update, err = msg.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lh.l.Error("Error while marshaling for lighthouse v2 update",
|
lh.l.WithError(err).WithField("lighthouseAddr", lhVpnAddr).
|
||||||
"error", err,
|
Error("Error while marshaling for lighthouse v2 update")
|
||||||
"lighthouseAddr", lhVpnAddr,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,10 +953,7 @@ func (lh *LightHouse) SendUpdate() {
|
|||||||
updated++
|
updated++
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
lh.l.Debug("unsupported protocol version",
|
lh.l.Debugf("Can not update lighthouse using unknown protocol version: %v", v)
|
||||||
"op", "update",
|
|
||||||
"version", v,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1024,7 +967,7 @@ type LightHouseHandler struct {
|
|||||||
out []byte
|
out []byte
|
||||||
pb []byte
|
pb []byte
|
||||||
meta *NebulaMeta
|
meta *NebulaMeta
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lh *LightHouse) NewRequestHandler() *LightHouseHandler {
|
func (lh *LightHouse) NewRequestHandler() *LightHouseHandler {
|
||||||
@@ -1073,19 +1016,14 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr netip.AddrPort, fromVpnAddrs [
|
|||||||
n := lhh.resetMeta()
|
n := lhh.resetMeta()
|
||||||
err := n.Unmarshal(p)
|
err := n.Unmarshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lhh.l.Error("Failed to unmarshal lighthouse packet",
|
lhh.l.WithError(err).WithField("vpnAddrs", fromVpnAddrs).WithField("udpAddr", rAddr).
|
||||||
"error", err,
|
Error("Failed to unmarshal lighthouse packet")
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
"udpAddr", rAddr,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Details == nil {
|
if n.Details == nil {
|
||||||
lhh.l.Error("Invalid lighthouse update",
|
lhh.l.WithField("vpnAddrs", fromVpnAddrs).WithField("udpAddr", rAddr).
|
||||||
"vpnAddrs", fromVpnAddrs,
|
Error("Invalid lighthouse update")
|
||||||
"udpAddr", rAddr,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1113,29 +1051,25 @@ func (lhh *LightHouseHandler) HandleRequest(rAddr netip.AddrPort, fromVpnAddrs [
|
|||||||
func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []netip.Addr, addr netip.AddrPort, w EncWriter) {
|
func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []netip.Addr, addr netip.AddrPort, w EncWriter) {
|
||||||
// Exit if we don't answer queries
|
// Exit if we don't answer queries
|
||||||
if !lhh.lh.amLighthouse {
|
if !lhh.lh.amLighthouse {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("I don't answer queries, but received one", "from", addr)
|
lhh.l.Debugln("I don't answer queries, but received from: ", addr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
queryVpnAddr, useVersion, err := n.Details.GetVpnAddrAndVersion()
|
queryVpnAddr, useVersion, err := n.Details.GetVpnAddrAndVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("Dropping malformed HostQuery",
|
lhh.l.WithField("from", fromVpnAddrs).WithField("details", n.Details).
|
||||||
"from", fromVpnAddrs,
|
Debugln("Dropping malformed HostQuery")
|
||||||
"details", n.Details,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if useVersion == cert.Version1 && queryVpnAddr.Is6() {
|
if useVersion == cert.Version1 && queryVpnAddr.Is6() {
|
||||||
// this case really shouldn't be possible to represent, but reject it anyway.
|
// this case really shouldn't be possible to represent, but reject it anyway.
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("invalid vpn addr for v1 handleHostQuery",
|
lhh.l.WithField("vpnAddrs", fromVpnAddrs).WithField("queryVpnAddr", queryVpnAddr).
|
||||||
"vpnAddrs", fromVpnAddrs,
|
Debugln("invalid vpn addr for v1 handleHostQuery")
|
||||||
"queryVpnAddr", queryVpnAddr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1160,10 +1094,7 @@ func (lhh *LightHouseHandler) handleHostQuery(n *NebulaMeta, fromVpnAddrs []neti
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lhh.l.Error("Failed to marshal lighthouse host query reply",
|
lhh.l.WithError(err).WithField("vpnAddrs", fromVpnAddrs).Error("Failed to marshal lighthouse host query reply")
|
||||||
"error", err,
|
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1191,10 +1122,8 @@ func (lhh *LightHouseHandler) sendHostPunchNotification(n *NebulaMeta, fromVpnAd
|
|||||||
if ok {
|
if ok {
|
||||||
whereToPunch = newDest
|
whereToPunch = newDest
|
||||||
} else {
|
} else {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("unable to punch to host, no addresses in common",
|
lhh.l.WithField("to", crt.Networks()).Debugln("unable to punch to host, no addresses in common")
|
||||||
"to", crt.Networks(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1220,10 +1149,7 @@ func (lhh *LightHouseHandler) sendHostPunchNotification(n *NebulaMeta, fromVpnAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lhh.l.Error("Failed to marshal lighthouse host was queried for",
|
lhh.l.WithError(err).WithField("vpnAddrs", fromVpnAddrs).Error("Failed to marshal lighthouse host was queried for")
|
||||||
"error", err,
|
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1265,11 +1191,8 @@ func (lhh *LightHouseHandler) coalesceAnswers(v cert.Version, c *cache, n *Nebul
|
|||||||
n.Details.RelayVpnAddrs = append(n.Details.RelayVpnAddrs, netAddrToProtoAddr(r))
|
n.Details.RelayVpnAddrs = append(n.Details.RelayVpnAddrs, netAddrToProtoAddr(r))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("unsupported protocol version",
|
lhh.l.WithField("version", v).Debug("unsupported protocol version")
|
||||||
"op", "coalesceAnswers",
|
|
||||||
"version", v,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1282,11 +1205,8 @@ func (lhh *LightHouseHandler) handleHostQueryReply(n *NebulaMeta, fromVpnAddrs [
|
|||||||
|
|
||||||
certVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()
|
certVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Error("dropping malformed HostQueryReply",
|
lhh.l.WithError(err).WithField("vpnAddrs", fromVpnAddrs).Error("dropping malformed HostQueryReply")
|
||||||
"error", err,
|
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1311,8 +1231,8 @@ func (lhh *LightHouseHandler) handleHostQueryReply(n *NebulaMeta, fromVpnAddrs [
|
|||||||
|
|
||||||
func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVpnAddrs []netip.Addr, w EncWriter) {
|
func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVpnAddrs []netip.Addr, w EncWriter) {
|
||||||
if !lhh.lh.amLighthouse {
|
if !lhh.lh.amLighthouse {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("I am not a lighthouse, do not take host updates", "from", fromVpnAddrs)
|
lhh.l.Debugln("I am not a lighthouse, do not take host updates: ", fromVpnAddrs)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1335,11 +1255,8 @@ func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVp
|
|||||||
|
|
||||||
//Simple check that the host sent this not someone else, if detailsVpnAddr is filled
|
//Simple check that the host sent this not someone else, if detailsVpnAddr is filled
|
||||||
if detailsVpnAddr.IsValid() && !slices.Contains(fromVpnAddrs, detailsVpnAddr) {
|
if detailsVpnAddr.IsValid() && !slices.Contains(fromVpnAddrs, detailsVpnAddr) {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("Host sent invalid update",
|
lhh.l.WithField("vpnAddrs", fromVpnAddrs).WithField("answer", detailsVpnAddr).Debugln("Host sent invalid update")
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
"answer", detailsVpnAddr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1361,9 +1278,7 @@ func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVp
|
|||||||
switch useVersion {
|
switch useVersion {
|
||||||
case cert.Version1:
|
case cert.Version1:
|
||||||
if !fromVpnAddrs[0].Is4() {
|
if !fromVpnAddrs[0].Is4() {
|
||||||
lhh.l.Error("Can not send HostUpdateNotificationAck for a ipv6 vpn ip in a v1 message",
|
lhh.l.WithField("vpnAddrs", fromVpnAddrs).Error("Can not send HostUpdateNotificationAck for a ipv6 vpn ip in a v1 message")
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
vpnAddrB := fromVpnAddrs[0].As4()
|
vpnAddrB := fromVpnAddrs[0].As4()
|
||||||
@@ -1371,16 +1286,13 @@ func (lhh *LightHouseHandler) handleHostUpdateNotification(n *NebulaMeta, fromVp
|
|||||||
case cert.Version2:
|
case cert.Version2:
|
||||||
// do nothing, we want to send a blank message
|
// do nothing, we want to send a blank message
|
||||||
default:
|
default:
|
||||||
lhh.l.Error("invalid protocol version", "useVersion", useVersion)
|
lhh.l.WithField("useVersion", useVersion).Error("invalid protocol version")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, err := n.MarshalTo(lhh.pb)
|
ln, err := n.MarshalTo(lhh.pb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lhh.l.Error("Failed to marshal lighthouse host update ack",
|
lhh.l.WithError(err).WithField("vpnAddrs", fromVpnAddrs).Error("Failed to marshal lighthouse host update ack")
|
||||||
"error", err,
|
|
||||||
"vpnAddrs", fromVpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1397,11 +1309,8 @@ func (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, fromVpn
|
|||||||
|
|
||||||
detailsVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()
|
detailsVpnAddr, _, err := n.Details.GetVpnAddrAndVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("dropping invalid HostPunchNotification",
|
lhh.l.WithField("details", n.Details).WithError(err).Debugln("dropping invalid HostPunchNotification")
|
||||||
"details", n.Details,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1418,11 +1327,8 @@ func (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, fromVpn
|
|||||||
lhh.lh.punchConn.WriteTo(empty, vpnPeer)
|
lhh.lh.punchConn.WriteTo(empty, vpnPeer)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("Punching",
|
lhh.l.Debugf("Punching on %v for %v", vpnPeer, logVpnAddr)
|
||||||
"vpnPeer", vpnPeer,
|
|
||||||
"logVpnAddr", logVpnAddr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1447,10 +1353,8 @@ func (lhh *LightHouseHandler) handleHostPunchNotification(n *NebulaMeta, fromVpn
|
|||||||
if lhh.lh.punchy.GetRespond() {
|
if lhh.lh.punchy.GetRespond() {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(lhh.lh.punchy.GetRespondDelay())
|
time.Sleep(lhh.lh.punchy.GetRespondDelay())
|
||||||
if lhh.l.Enabled(context.Background(), slog.LevelDebug) {
|
if lhh.l.Level >= logrus.DebugLevel {
|
||||||
lhh.l.Debug("Sending a nebula test packet",
|
lhh.l.Debugf("Sending a nebula test packet to vpn addr %s", detailsVpnAddr)
|
||||||
"vpnAddr", detailsVpnAddr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
//NOTE: we have to allocate a new output buffer here since we are spawning a new goroutine
|
//NOTE: we have to allocate a new output buffer here since we are spawning a new goroutine
|
||||||
// for each punchBack packet. We should move this into a timerwheel or a single goroutine
|
// for each punchBack packet. We should move this into a timerwheel or a single goroutine
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package nebula
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/slackhq/nebula/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func configLogger(l *logrus.Logger, c *config.C) error {
|
||||||
|
// set up our logging level
|
||||||
|
logLevel, err := logrus.ParseLevel(strings.ToLower(c.GetString("logging.level", "info")))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s; possible levels: %s", err, logrus.AllLevels)
|
||||||
|
}
|
||||||
|
l.SetLevel(logLevel)
|
||||||
|
|
||||||
|
disableTimestamp := c.GetBool("logging.disable_timestamp", false)
|
||||||
|
timestampFormat := c.GetString("logging.timestamp_format", "")
|
||||||
|
fullTimestamp := (timestampFormat != "")
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = time.RFC3339
|
||||||
|
}
|
||||||
|
|
||||||
|
logFormat := strings.ToLower(c.GetString("logging.format", "text"))
|
||||||
|
switch logFormat {
|
||||||
|
case "text":
|
||||||
|
l.Formatter = &logrus.TextFormatter{
|
||||||
|
TimestampFormat: timestampFormat,
|
||||||
|
FullTimestamp: fullTimestamp,
|
||||||
|
DisableTimestamp: disableTimestamp,
|
||||||
|
}
|
||||||
|
case "json":
|
||||||
|
l.Formatter = &logrus.JSONFormatter{
|
||||||
|
TimestampFormat: timestampFormat,
|
||||||
|
DisableTimestamp: disableTimestamp,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown log format `%s`. possible formats: %s", logFormat, []string{"text", "json"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
// Package logging wires the nebula runtime-reconfigurable slog handler used
|
|
||||||
// by nebula.Main and the nebula CLI binaries. Callers build a logger with
|
|
||||||
// NewLogger, then call ApplyConfig at startup and from a config reload
|
|
||||||
// callback to push logging.level, logging.format, and
|
|
||||||
// logging.disable_timestamp changes onto the logger without rebuilding it.
|
|
||||||
package logging
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config is the subset of *config.C that ApplyConfig reads. Declaring it
|
|
||||||
// here keeps the logging package from depending on config directly, which
|
|
||||||
// would cycle through the shared test helpers (test.NewLogger imports
|
|
||||||
// logging, and config's tests import test). *config.C satisfies this
|
|
||||||
// interface structurally with no adapter.
|
|
||||||
type Config interface {
|
|
||||||
GetString(key, def string) string
|
|
||||||
GetBool(key string, def bool) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// LevelTrace is a custom slog level below Debug, used when logging.level is
|
|
||||||
// "trace". slog has no builtin trace level; the value is one step below
|
|
||||||
// slog.LevelDebug in slog's 4-point spacing.
|
|
||||||
const LevelTrace = slog.Level(-8)
|
|
||||||
|
|
||||||
// NewLogger returns a *slog.Logger whose level, format, and timestamp
|
|
||||||
// emission can be reconfigured at runtime via ApplyConfig and the SSH debug
|
|
||||||
// commands. The default configuration is info-level text output so log
|
|
||||||
// calls made before ApplyConfig runs still produce output. Timestamps
|
|
||||||
// follow slog's default RFC3339Nano format; set logging.disable_timestamp
|
|
||||||
// in config to suppress them.
|
|
||||||
//
|
|
||||||
// ApplyConfig and the SSH commands discover the reconfig surface via
|
|
||||||
// structural type-assertion on l.Handler(), so replacement implementations
|
|
||||||
// (tests, platform-specific sinks) need only implement the subset of
|
|
||||||
// {SetLevel(slog.Level), SetFormat(string) error, SetDisableTimestamp(bool)}
|
|
||||||
// they care about. Callers that pass a plain *slog.Logger without these
|
|
||||||
// methods get a silent no-op; reconfiguration is always opt-in.
|
|
||||||
func NewLogger(w io.Writer) *slog.Logger {
|
|
||||||
return slog.New(NewHandler(w))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHandler builds the *Handler that NewLogger wraps. Exported for
|
|
||||||
// platform-specific sinks (notably cmd/nebula-service/logs_windows.go)
|
|
||||||
// that want to wrap the handler with extra behavior, such as tagging each
|
|
||||||
// record with its Event Log severity, while still benefiting from all the
|
|
||||||
// level / format / timestamp / WithAttrs machinery implemented here.
|
|
||||||
func NewHandler(w io.Writer) *Handler {
|
|
||||||
root := &handlerRoot{}
|
|
||||||
root.level.Set(slog.LevelInfo)
|
|
||||||
opts := &slog.HandlerOptions{Level: &root.level}
|
|
||||||
return &Handler{
|
|
||||||
root: root,
|
|
||||||
text: slog.NewTextHandler(w, opts),
|
|
||||||
json: slog.NewJSONHandler(w, opts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlerRoot carries the reconfiguration state shared by every logger
|
|
||||||
// derived from a NewHandler call. All fields are consulted on the log
|
|
||||||
// path and updated lock-free.
|
|
||||||
type handlerRoot struct {
|
|
||||||
level slog.LevelVar
|
|
||||||
disableTimestamp atomic.Bool
|
|
||||||
// jsonMode picks which of the pre-derived inner handlers Handler.Handle
|
|
||||||
// dispatches to. Flipping it propagates instantly to every derived logger
|
|
||||||
// without rebuilding or chain-replaying anything.
|
|
||||||
jsonMode atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler is the slog.Handler returned by NewHandler. It holds two
|
|
||||||
// pre-derived slog handlers -- one text, one json -- both built from the
|
|
||||||
// same accumulated WithAttrs/WithGroup state. Handle picks which one to
|
|
||||||
// dispatch to based on handlerRoot.jsonMode, so a SetFormat call takes
|
|
||||||
// effect immediately across the whole process without having to rebuild
|
|
||||||
// any derived loggers.
|
|
||||||
type Handler struct {
|
|
||||||
root *handlerRoot
|
|
||||||
text slog.Handler
|
|
||||||
json slog.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) Enabled(_ context.Context, l slog.Level) bool {
|
|
||||||
return h.root.level.Level() <= l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
|
|
||||||
if h.root.disableTimestamp.Load() {
|
|
||||||
r.Time = time.Time{}
|
|
||||||
}
|
|
||||||
if h.root.jsonMode.Load() {
|
|
||||||
return h.json.Handle(ctx, r)
|
|
||||||
}
|
|
||||||
return h.text.Handle(ctx, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
if len(attrs) == 0 {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &Handler{
|
|
||||||
root: h.root,
|
|
||||||
text: h.text.WithAttrs(attrs),
|
|
||||||
json: h.json.WithAttrs(attrs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) WithGroup(name string) slog.Handler {
|
|
||||||
if name == "" {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return &Handler{
|
|
||||||
root: h.root,
|
|
||||||
text: h.text.WithGroup(name),
|
|
||||||
json: h.json.WithGroup(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel updates the effective log level. Propagates to every derived
|
|
||||||
// logger via the shared LevelVar.
|
|
||||||
func (h *Handler) SetLevel(level slog.Level) { h.root.level.Set(level) }
|
|
||||||
|
|
||||||
// GetLevel reports the current log level.
|
|
||||||
func (h *Handler) GetLevel() slog.Level { return h.root.level.Level() }
|
|
||||||
|
|
||||||
// SetFormat flips the output format atomically. Valid formats are "text"
|
|
||||||
// and "json". Every derived logger sees the new format on its next Handle
|
|
||||||
// call; no rebuild or registration is required.
|
|
||||||
func (h *Handler) SetFormat(format string) error {
|
|
||||||
switch format {
|
|
||||||
case "text":
|
|
||||||
h.root.jsonMode.Store(false)
|
|
||||||
case "json":
|
|
||||||
h.root.jsonMode.Store(true)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown log format `%s`. possible formats: %s", format, []string{"text", "json"})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFormat reports the currently selected format name.
|
|
||||||
func (h *Handler) GetFormat() string {
|
|
||||||
if h.root.jsonMode.Load() {
|
|
||||||
return "json"
|
|
||||||
}
|
|
||||||
return "text"
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDisableTimestamp toggles whether Handle zeroes r.Time before
|
|
||||||
// dispatching (slog's builtin text/json handlers skip emitting the time
|
|
||||||
// attribute on a zero time).
|
|
||||||
func (h *Handler) SetDisableTimestamp(v bool) { h.root.disableTimestamp.Store(v) }
|
|
||||||
|
|
||||||
// ApplyConfig reads logging.level, logging.format, and (optionally)
|
|
||||||
// logging.disable_timestamp from c and applies them to l. The reconfig
|
|
||||||
// surface is discovered via structural type-assertion on l.Handler(), so
|
|
||||||
// foreign handlers silently opt out of whichever capabilities they do not
|
|
||||||
// implement.
|
|
||||||
//
|
|
||||||
// nebula.Main does NOT call this function on your behalf; callers that want
|
|
||||||
// config-driven log level / format / timestamp updates invoke it at
|
|
||||||
// startup and register it as a reload callback themselves. This keeps the
|
|
||||||
// library from mutating an embedder's logger without their say-so.
|
|
||||||
func ApplyConfig(l *slog.Logger, c Config) error {
|
|
||||||
h := l.Handler()
|
|
||||||
|
|
||||||
lvl, err := ParseLevel(strings.ToLower(c.GetString("logging.level", "info")))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ls, ok := h.(interface{ SetLevel(slog.Level) }); ok {
|
|
||||||
ls.SetLevel(lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
format := strings.ToLower(c.GetString("logging.format", "text"))
|
|
||||||
if fs, ok := h.(interface{ SetFormat(string) error }); ok {
|
|
||||||
if err := fs.SetFormat(format); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ts, ok := h.(interface{ SetDisableTimestamp(bool) }); ok {
|
|
||||||
ts.SetDisableTimestamp(c.GetBool("logging.disable_timestamp", false))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLevel converts a config-string level name ("trace", "debug", "info",
|
|
||||||
// "warn"/"warning", "error", "fatal"/"panic") to a slog.Level. "fatal" and
|
|
||||||
// "panic" are accepted for backwards compatibility with pre-slog configs
|
|
||||||
// and both map to slog.LevelError.
|
|
||||||
func ParseLevel(s string) (slog.Level, error) {
|
|
||||||
switch s {
|
|
||||||
case "trace":
|
|
||||||
return LevelTrace, nil
|
|
||||||
case "debug":
|
|
||||||
return slog.LevelDebug, nil
|
|
||||||
case "info":
|
|
||||||
return slog.LevelInfo, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return slog.LevelWarn, nil
|
|
||||||
case "error":
|
|
||||||
return slog.LevelError, nil
|
|
||||||
case "fatal", "panic":
|
|
||||||
return slog.LevelError, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("not a valid logging level: %q", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LevelName returns a human-readable name for a slog.Level matching the
|
|
||||||
// strings accepted by ParseLevel.
|
|
||||||
func LevelName(l slog.Level) string {
|
|
||||||
switch {
|
|
||||||
case l <= LevelTrace:
|
|
||||||
return "trace"
|
|
||||||
case l <= slog.LevelDebug:
|
|
||||||
return "debug"
|
|
||||||
case l <= slog.LevelInfo:
|
|
||||||
return "info"
|
|
||||||
case l <= slog.LevelWarn:
|
|
||||||
return "warn"
|
|
||||||
default:
|
|
||||||
return "error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package logging
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BenchmarkLogger_* compare the handler returned by NewLogger against a
|
|
||||||
// stock slog text handler. The key thing we care about is the per-log
|
|
||||||
// cost on a logger that has been derived via .With(), because that is the
|
|
||||||
// shape subsystems store on their structs (HostInfo.logger(),
|
|
||||||
// lh.l.With("subsystem", ...), etc.) and call from hot paths.
|
|
||||||
|
|
||||||
func BenchmarkLogger_Stock_RootInfo(b *testing.B) {
|
|
||||||
l := slog.New(slog.DiscardHandler)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
l.Info("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLogger_Nebula_RootInfo(b *testing.B) {
|
|
||||||
l := NewLogger(io.Discard)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
l.Info("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLogger_Stock_DerivedInfo(b *testing.B) {
|
|
||||||
l := slog.New(slog.DiscardHandler).With(
|
|
||||||
"subsystem", "bench",
|
|
||||||
"localIndex", 1234,
|
|
||||||
)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
l.Info("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLogger_Nebula_DerivedInfo(b *testing.B) {
|
|
||||||
l := NewLogger(io.Discard).With(
|
|
||||||
"subsystem", "bench",
|
|
||||||
"localIndex", 1234,
|
|
||||||
)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
l.Info("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gated-off-path benchmarks: mimic the typical hot-path shape
|
|
||||||
// `if l.Enabled(ctx, slog.LevelDebug) { ... }` where the log is gated below
|
|
||||||
// the active level. This is the dominant pattern in inside.go/outside.go and
|
|
||||||
// what we pay on every packet.
|
|
||||||
func BenchmarkLogger_Stock_DerivedEnabledGateMiss(b *testing.B) {
|
|
||||||
l := slog.New(slog.DiscardHandler).With(
|
|
||||||
"subsystem", "bench",
|
|
||||||
"localIndex", 1234,
|
|
||||||
)
|
|
||||||
ctx := context.Background()
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if l.Enabled(ctx, slog.LevelDebug) {
|
|
||||||
l.Debug("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLogger_Nebula_DerivedEnabledGateMiss(b *testing.B) {
|
|
||||||
l := NewLogger(io.Discard).With(
|
|
||||||
"subsystem", "bench",
|
|
||||||
"localIndex", 1234,
|
|
||||||
)
|
|
||||||
ctx := context.Background()
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if l.Enabled(ctx, slog.LevelDebug) {
|
|
||||||
l.Debug("hello", "i", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,16 @@ package nebula
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
"github.com/slackhq/nebula/sshd"
|
"github.com/slackhq/nebula/sshd"
|
||||||
@@ -20,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type m = map[string]any
|
type m = map[string]any
|
||||||
|
|
||||||
func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, deviceFactory overlay.DeviceFactory) (retcon *Control, reterr error) {
|
func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logger, deviceFactory overlay.DeviceFactory) (retcon *Control, reterr error) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
// Automatically cancel the context if Main returns an error, to signal all created goroutines to quit.
|
// Automatically cancel the context if Main returns an error, to signal all created goroutines to quit.
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -33,6 +36,11 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
buildVersion = moduleVersion()
|
buildVersion = moduleVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l := logger
|
||||||
|
l.Formatter = &logrus.TextFormatter{
|
||||||
|
FullTimestamp: true,
|
||||||
|
}
|
||||||
|
|
||||||
// Print the config if in test, the exit comes later
|
// Print the config if in test, the exit comes later
|
||||||
if configTest {
|
if configTest {
|
||||||
b, err := yaml.Marshal(c.Settings)
|
b, err := yaml.Marshal(c.Settings)
|
||||||
@@ -41,9 +49,26 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the final config
|
// Print the final config
|
||||||
l.Info(string(b))
|
l.Println(string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo!!!
|
||||||
|
go func() {
|
||||||
|
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := configLogger(l, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, util.ContextualizeIfNeeded("Failed to configure the logger", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RegisterReloadCallback(func(c *config.C) {
|
||||||
|
err := configLogger(l, c)
|
||||||
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Failed to configure the logger")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
pki, err := NewPKIFromConfig(l, c)
|
pki, err := NewPKIFromConfig(l, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, util.ContextualizeIfNeeded("Failed to load PKI from config", err)
|
return nil, util.ContextualizeIfNeeded("Failed to load PKI from config", err)
|
||||||
@@ -53,9 +78,9 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, util.ContextualizeIfNeeded("Error while loading firewall rules", err)
|
return nil, util.ContextualizeIfNeeded("Error while loading firewall rules", err)
|
||||||
}
|
}
|
||||||
l.Info("Firewall started", "firewallHashes", fw.GetRuleHashes())
|
l.WithField("firewallHashes", fw.GetRuleHashes()).Info("Firewall started")
|
||||||
|
|
||||||
ssh, err := sshd.NewSSHServer(l.With("subsystem", "sshd"))
|
ssh, err := sshd.NewSSHServer(l.WithField("subsystem", "sshd"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, util.ContextualizeIfNeeded("Error while creating SSH server", err)
|
return nil, util.ContextualizeIfNeeded("Error while creating SSH server", err)
|
||||||
}
|
}
|
||||||
@@ -64,7 +89,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
if c.GetBool("sshd.enabled", false) {
|
if c.GetBool("sshd.enabled", false) {
|
||||||
sshStart, err = configSSH(l, ssh, c)
|
sshStart, err = configSSH(l, ssh, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("Failed to configure sshd, ssh debugging will not be available", "error", err)
|
l.WithError(err).Warn("Failed to configure sshd, ssh debugging will not be available")
|
||||||
sshStart = nil
|
sshStart = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +107,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
routines = 1
|
routines = 1
|
||||||
}
|
}
|
||||||
if routines > 1 {
|
if routines > 1 {
|
||||||
l.Info("Using multiple routines", "routines", routines)
|
l.WithField("routines", routines).Info("Using multiple routines")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// deprecated and undocumented
|
// deprecated and undocumented
|
||||||
@@ -90,7 +115,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
udpQueues := c.GetInt("listen.routines", 1)
|
udpQueues := c.GetInt("listen.routines", 1)
|
||||||
routines = max(tunQueues, udpQueues)
|
routines = max(tunQueues, udpQueues)
|
||||||
if routines != 1 {
|
if routines != 1 {
|
||||||
l.Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead", "routines", routines)
|
l.WithField("routines", routines).Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +128,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
conntrackCacheTimeout = 1 * time.Second
|
conntrackCacheTimeout = 1 * time.Second
|
||||||
}
|
}
|
||||||
if conntrackCacheTimeout > 0 {
|
if conntrackCacheTimeout > 0 {
|
||||||
l.Info("Using routine-local conntrack cache", "duration", conntrackCacheTimeout)
|
l.WithField("duration", conntrackCacheTimeout).Info("Using routine-local conntrack cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tun overlay.Device
|
var tun overlay.Device
|
||||||
@@ -149,7 +174,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < routines; i++ {
|
for i := 0; i < routines; i++ {
|
||||||
l.Info("listening", "addr", netip.AddrPortFrom(listenHost, uint16(port)))
|
l.Infof("listening on %v", netip.AddrPortFrom(listenHost, uint16(port)))
|
||||||
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 64))
|
udpServer, err := udp.NewListener(l, listenHost, port, routines > 1, c.GetInt("listen.batch", 64))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, util.NewContextualError("Failed to open udp listener", m{"queue": i}, err)
|
return nil, util.NewContextualError("Failed to open udp listener", m{"queue": i}, err)
|
||||||
@@ -198,9 +223,13 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
handshakeManager := NewHandshakeManager(l, hostMap, lightHouse, udpConns[0], handshakeConfig)
|
handshakeManager := NewHandshakeManager(l, hostMap, lightHouse, udpConns[0], handshakeConfig)
|
||||||
lightHouse.handshakeTrigger = handshakeManager.trigger
|
lightHouse.handshakeTrigger = handshakeManager.trigger
|
||||||
|
|
||||||
ds, err := newDnsServerFromConfig(ctx, l, pki.getCertState(), hostMap, c)
|
serveDns := false
|
||||||
if err != nil {
|
if c.GetBool("lighthouse.serve_dns", false) {
|
||||||
l.Warn("Failed to start DNS responder", "error", err)
|
if c.GetBool("lighthouse.am_lighthouse", false) {
|
||||||
|
serveDns = true
|
||||||
|
} else {
|
||||||
|
l.Warn("DNS server refusing to run because this host is not a lighthouse.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ifConfig := &InterfaceConfig{
|
ifConfig := &InterfaceConfig{
|
||||||
@@ -209,7 +238,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
Outside: udpConns[0],
|
Outside: udpConns[0],
|
||||||
pki: pki,
|
pki: pki,
|
||||||
Firewall: fw,
|
Firewall: fw,
|
||||||
DnsServer: ds,
|
ServeDns: serveDns,
|
||||||
HandshakeManager: handshakeManager,
|
HandshakeManager: handshakeManager,
|
||||||
connectionManager: connManager,
|
connectionManager: connManager,
|
||||||
lightHouse: lightHouse,
|
lightHouse: lightHouse,
|
||||||
@@ -246,7 +275,7 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
go handshakeManager.Run(ctx)
|
go handshakeManager.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := newStatsServerFromConfig(ctx, l, c, buildVersion, configTest)
|
statsStart, err := startStats(l, c, buildVersion, configTest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, util.ContextualizeIfNeeded("Failed to start stats emitter", err)
|
return nil, util.ContextualizeIfNeeded("Failed to start stats emitter", err)
|
||||||
}
|
}
|
||||||
@@ -259,6 +288,13 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
|
|
||||||
attachCommands(l, c, ssh, ifce)
|
attachCommands(l, c, ssh, ifce)
|
||||||
|
|
||||||
|
// Start DNS server last to allow using the nebula IP as lighthouse.dns.host
|
||||||
|
var dnsStart func()
|
||||||
|
if lightHouse.amLighthouse && serveDns {
|
||||||
|
l.Debugln("Starting dns server")
|
||||||
|
dnsStart = dnsMain(l, pki.getCertState(), hostMap, c)
|
||||||
|
}
|
||||||
|
|
||||||
return &Control{
|
return &Control{
|
||||||
state: StateReady,
|
state: StateReady,
|
||||||
f: ifce,
|
f: ifce,
|
||||||
@@ -266,8 +302,8 @@ func Main(c *config.C, configTest bool, buildVersion string, l *slog.Logger, dev
|
|||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
sshStart: sshStart,
|
sshStart: sshStart,
|
||||||
statsStart: stats.Start,
|
statsStart: statsStart,
|
||||||
dnsStart: ds.Start,
|
dnsStart: dnsStart,
|
||||||
lighthouseStart: lightHouse.StartUpdateWorker,
|
lighthouseStart: lightHouse.StartUpdateWorker,
|
||||||
connectionManagerStart: connManager.Start,
|
connectionManagerStart: connManager.Start,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ var noiseEndianness endianness = binary.BigEndian
|
|||||||
|
|
||||||
type NebulaCipherState struct {
|
type NebulaCipherState struct {
|
||||||
c cipher.AEAD
|
c cipher.AEAD
|
||||||
|
//k [32]byte
|
||||||
|
//n uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNebulaCipherState(s *noise.CipherState) *NebulaCipherState {
|
func NewNebulaCipherState(s *noise.CipherState) *NebulaCipherState {
|
||||||
|
|||||||
+54
-97
@@ -1,16 +1,15 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/gopacket/layers"
|
"github.com/google/gopacket/layers"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
@@ -25,11 +24,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors
|
// Hole punch packets are 0 or 1 byte big, so lets ignore printing those errors
|
||||||
if len(packet) > 1 {
|
if len(packet) > 1 {
|
||||||
f.l.Info("Error while parsing inbound packet",
|
f.l.WithField("packet", packet).Infof("Error while parsing inbound packet from %s: %s", via, err)
|
||||||
"from", via,
|
|
||||||
"error", err,
|
|
||||||
"packet", packet,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -37,8 +32,8 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
//l.Error("in packet ", header, packet[HeaderLen:])
|
//l.Error("in packet ", header, packet[HeaderLen:])
|
||||||
if !via.IsRelayed {
|
if !via.IsRelayed {
|
||||||
if f.myVpnNetworksTable.Contains(via.UdpAddr.Addr()) {
|
if f.myVpnNetworksTable.Contains(via.UdpAddr.Addr()) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Refusing to process double encrypted packet", "from", via)
|
f.l.WithField("from", via).Debug("Refusing to process double encrypted packet")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -92,10 +87,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
if !ok {
|
if !ok {
|
||||||
// The only way this happens is if hostmap has an index to the correct HostInfo, but the HostInfo is missing
|
// The only way this happens is if hostmap has an index to the correct HostInfo, but the HostInfo is missing
|
||||||
// its internal mapping. This should never happen.
|
// its internal mapping. This should never happen.
|
||||||
hostinfo.logger(f.l).Error("HostInfo missing remote relay index",
|
hostinfo.logger(f.l).WithFields(logrus.Fields{"vpnAddrs": hostinfo.vpnAddrs, "remoteIndex": h.RemoteIndex}).Error("HostInfo missing remote relay index")
|
||||||
"vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
"remoteIndex", h.RemoteIndex,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,11 +108,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
// Find the target HostInfo relay object
|
// Find the target HostInfo relay object
|
||||||
targetHI, targetRelay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relay.PeerAddr)
|
targetHI, targetRelay, err := f.hostMap.QueryVpnAddrsRelayFor(hostinfo.vpnAddrs, relay.PeerAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Info("Failed to find target host info by ip",
|
hostinfo.logger(f.l).WithField("relayTo", relay.PeerAddr).WithError(err).WithField("hostinfo.vpnAddrs", hostinfo.vpnAddrs).Info("Failed to find target host info by ip")
|
||||||
"relayTo", relay.PeerAddr,
|
|
||||||
"error", err,
|
|
||||||
"hostinfo.vpnAddrs", hostinfo.vpnAddrs,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +124,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
hostinfo.logger(f.l).Error("Unexpected Relay Type of Terminal")
|
hostinfo.logger(f.l).Error("Unexpected Relay Type of Terminal")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hostinfo.logger(f.l).Info("Unexpected target relay state",
|
hostinfo.logger(f.l).WithFields(logrus.Fields{"relayTo": relay.PeerAddr, "relayFrom": hostinfo.vpnAddrs[0], "targetRelayState": targetRelay.State}).Info("Unexpected target relay state")
|
||||||
"relayTo", relay.PeerAddr,
|
|
||||||
"relayFrom", hostinfo.vpnAddrs[0],
|
|
||||||
"targetRelayState", targetRelay.State,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,11 +138,9 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to decrypt lighthouse packet",
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("packet", packet).
|
||||||
"from", via,
|
Error("Failed to decrypt lighthouse packet")
|
||||||
"packet", packet,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +157,9 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to decrypt test packet",
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("packet", packet).
|
||||||
"from", via,
|
Error("Failed to decrypt test packet")
|
||||||
"packet", packet,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,15 +192,14 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
}
|
}
|
||||||
_, err = f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
_, err = f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to decrypt CloseTunnel packet",
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("packet", packet).
|
||||||
"from", via,
|
Error("Failed to decrypt CloseTunnel packet")
|
||||||
"packet", packet,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hostinfo.logger(f.l).Info("Close tunnel received, tearing down.", "from", via)
|
hostinfo.logger(f.l).WithField("from", via).
|
||||||
|
Info("Close tunnel received, tearing down.")
|
||||||
|
|
||||||
f.closeTunnel(hostinfo)
|
f.closeTunnel(hostinfo)
|
||||||
return
|
return
|
||||||
@@ -232,11 +211,9 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
|
|
||||||
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
d, err := f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to decrypt Control packet",
|
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
||||||
"error", err,
|
WithField("packet", packet).
|
||||||
"from", via,
|
Error("Failed to decrypt Control packet")
|
||||||
"packet", packet,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,9 +221,7 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
f.messageMetrics.Rx(h.Type, h.Subtype, 1)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
hostinfo.logger(f.l).Debugf("Unexpected packet received from %s", via)
|
||||||
hostinfo.logger(f.l).Debug("Unexpected packet received", "from", via)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,27 +247,20 @@ func (f *Interface) sendCloseTunnel(h *HostInfo) {
|
|||||||
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
|
func (f *Interface) handleHostRoaming(hostinfo *HostInfo, via ViaSender) {
|
||||||
if !via.IsRelayed && hostinfo.remote != via.UdpAddr {
|
if !via.IsRelayed && hostinfo.remote != via.UdpAddr {
|
||||||
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
if !f.lightHouse.GetRemoteAllowList().AllowAll(hostinfo.vpnAddrs, via.UdpAddr.Addr()) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
hostinfo.logger(f.l).WithField("newAddr", via.UdpAddr).Debug("lighthouse.remote_allow_list denied roaming")
|
||||||
hostinfo.logger(f.l).Debug("lighthouse.remote_allow_list denied roaming", "newAddr", via.UdpAddr)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hostinfo.lastRoam.IsZero() && via.UdpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
if !hostinfo.lastRoam.IsZero() && via.UdpAddr == hostinfo.lastRoamRemote && time.Since(hostinfo.lastRoam) < RoamingSuppressSeconds*time.Second {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).Debug("Suppressing roam back to previous remote",
|
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||||
"suppressSeconds", RoamingSuppressSeconds,
|
Debugf("Suppressing roam back to previous remote for %d seconds", RoamingSuppressSeconds)
|
||||||
"udpAddr", hostinfo.remote,
|
|
||||||
"newAddr", via.UdpAddr,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hostinfo.logger(f.l).Info("Host roamed to new udp ip/port.",
|
hostinfo.logger(f.l).WithField("udpAddr", hostinfo.remote).WithField("newAddr", via.UdpAddr).
|
||||||
"udpAddr", hostinfo.remote,
|
Info("Host roamed to new udp ip/port.")
|
||||||
"newAddr", via.UdpAddr,
|
|
||||||
)
|
|
||||||
hostinfo.lastRoam = time.Now()
|
hostinfo.lastRoam = time.Now()
|
||||||
hostinfo.lastRoamRemote = hostinfo.remote
|
hostinfo.lastRoamRemote = hostinfo.remote
|
||||||
hostinfo.SetRemote(via.UdpAddr)
|
hostinfo.SetRemote(via.UdpAddr)
|
||||||
@@ -523,9 +491,8 @@ func (f *Interface) decrypt(hostinfo *HostInfo, mc uint64, out []byte, packet []
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !hostinfo.ConnectionState.window.Update(f.l, mc) {
|
if !hostinfo.ConnectionState.window.Update(f.l, mc) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
hostinfo.logger(f.l).WithField("header", h).
|
||||||
hostinfo.logger(f.l).Debug("dropping out of window packet", "header", h)
|
Debugln("dropping out of window packet")
|
||||||
}
|
|
||||||
return nil, errors.New("out of window packet")
|
return nil, errors.New("out of window packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,23 +504,20 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
|||||||
|
|
||||||
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)
|
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Error("Failed to decrypt packet", "error", err)
|
hostinfo.logger(f.l).WithError(err).Error("Failed to decrypt packet")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = newPacket(out, true, fwPacket)
|
err = newPacket(out, true, fwPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hostinfo.logger(f.l).Warn("Error while validating inbound packet",
|
hostinfo.logger(f.l).WithError(err).WithField("packet", out).
|
||||||
"error", err,
|
Warnf("Error while validating inbound packet")
|
||||||
"packet", out,
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hostinfo.ConnectionState.window.Update(f.l, messageCounter) {
|
if !hostinfo.ConnectionState.window.Update(f.l, messageCounter) {
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).
|
||||||
hostinfo.logger(f.l).Debug("dropping out of window packet", "fwPacket", fwPacket)
|
Debugln("dropping out of window packet")
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,19 +526,18 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
|||||||
// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore
|
// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore
|
||||||
// This gives us a buffer to build the reject packet in
|
// This gives us a buffer to build the reject packet in
|
||||||
f.rejectOutside(out, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
f.rejectOutside(out, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).Debug("dropping inbound packet",
|
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).
|
||||||
"fwPacket", fwPacket,
|
WithField("reason", dropReason).
|
||||||
"reason", dropReason,
|
Debugln("dropping inbound packet")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
f.connectionManager.In(hostinfo)
|
f.connectionManager.In(hostinfo)
|
||||||
err = f.batchers[q].Commit(out)
|
err = f.tunCoalescers[q].Add(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.l.Error("Failed to write to tun", "error", err)
|
f.l.WithError(err).Error("Failed to write to tun")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -590,41 +553,35 @@ func (f *Interface) sendRecvError(endpoint netip.AddrPort, index uint32) {
|
|||||||
|
|
||||||
b := header.Encode(make([]byte, header.Len), header.Version, header.RecvError, 0, index, 0)
|
b := header.Encode(make([]byte, header.Len), header.Version, header.RecvError, 0, index, 0)
|
||||||
_ = f.outside.WriteTo(b, endpoint)
|
_ = f.outside.WriteTo(b, endpoint)
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Recv error sent",
|
f.l.WithField("index", index).
|
||||||
"index", index,
|
WithField("udpAddr", endpoint).
|
||||||
"udpAddr", endpoint,
|
Debug("Recv error sent")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
func (f *Interface) handleRecvError(addr netip.AddrPort, h *header.H) {
|
||||||
if !f.acceptRecvErrorConfig.ShouldRecvError(addr) {
|
if !f.acceptRecvErrorConfig.ShouldRecvError(addr) {
|
||||||
f.l.Debug("Recv error received, ignoring",
|
f.l.WithField("index", h.RemoteIndex).
|
||||||
"index", h.RemoteIndex,
|
WithField("udpAddr", addr).
|
||||||
"udpAddr", addr,
|
Debug("Recv error received, ignoring")
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.l.Enabled(context.Background(), slog.LevelDebug) {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.Debug("Recv error received",
|
f.l.WithField("index", h.RemoteIndex).
|
||||||
"index", h.RemoteIndex,
|
WithField("udpAddr", addr).
|
||||||
"udpAddr", addr,
|
Debug("Recv error received")
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostinfo := f.hostMap.QueryReverseIndex(h.RemoteIndex)
|
hostinfo := f.hostMap.QueryReverseIndex(h.RemoteIndex)
|
||||||
if hostinfo == nil {
|
if hostinfo == nil {
|
||||||
f.l.Debug("Did not find remote index in main hostmap", "remoteIndex", h.RemoteIndex)
|
f.l.WithField("remoteIndex", h.RemoteIndex).Debugln("Did not find remote index in main hostmap")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostinfo.remote.IsValid() && hostinfo.remote != addr {
|
if hostinfo.remote.IsValid() && hostinfo.remote != addr {
|
||||||
f.l.Info("Someone spoofing recv_errors?",
|
f.l.Infoln("Someone spoofing recv_errors? ", addr, hostinfo.remote)
|
||||||
"addr", addr,
|
|
||||||
"hostinfoRemote", hostinfo.remote,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package batch
|
|
||||||
|
|
||||||
import "net/netip"
|
|
||||||
|
|
||||||
type RxBatcher interface {
|
|
||||||
// Reserve creates a pkt to borrow
|
|
||||||
Reserve(sz int) []byte
|
|
||||||
// Commit borrows pkt. The caller must keep pkt valid until the next Flush
|
|
||||||
Commit(pkt []byte) error
|
|
||||||
// Flush emits every queued packet in arrival order. Returns the
|
|
||||||
// first error observed; keeps draining so one bad packet doesn't hold up
|
|
||||||
// the rest. After Flush returns, borrowed payload slices may be recycled.
|
|
||||||
Flush() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TxBatcher interface {
|
|
||||||
// Next returns a zero-length slice with slotCap capacity over the next unused
|
|
||||||
// slot's backing bytes. The caller writes into the returned slice and then
|
|
||||||
// calls Commit with the final length and destination. Next returns nil when
|
|
||||||
// the batch is full.
|
|
||||||
Next() []byte
|
|
||||||
// Commit records the slot just returned by Next as a packet of length n
|
|
||||||
// destined for dst.
|
|
||||||
Commit(n int, dst netip.AddrPort)
|
|
||||||
// Reset clears committed slots; backing storage is retained for reuse.
|
|
||||||
Reset()
|
|
||||||
// Len returns the number of committed packets.
|
|
||||||
Len() int
|
|
||||||
// Cap returns the maximum number of slots in the batch.
|
|
||||||
Cap() int
|
|
||||||
// Get returns the buffers needed to send the batch
|
|
||||||
Get() ([][]byte, []netip.AddrPort)
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package batch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/udp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Passthrough is a RxBatcher that doesn't batch anything, it just accumulates and then sends packets.
|
|
||||||
type Passthrough struct {
|
|
||||||
out io.Writer
|
|
||||||
slots [][]byte
|
|
||||||
backing []byte
|
|
||||||
cursor int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPassthrough(w io.Writer) *Passthrough {
|
|
||||||
const baseNumSlots = 128
|
|
||||||
return &Passthrough{
|
|
||||||
out: w,
|
|
||||||
slots: make([][]byte, 0, baseNumSlots),
|
|
||||||
backing: make([]byte, 0, baseNumSlots*udp.MTU),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Passthrough) Reserve(sz int) []byte {
|
|
||||||
if len(p.backing)+sz > cap(p.backing) {
|
|
||||||
// Grow: allocate a fresh backing. Already-committed slices still
|
|
||||||
// reference the old array and remain valid until Flush drops them.
|
|
||||||
newCap := max(cap(p.backing)*2, sz)
|
|
||||||
p.backing = make([]byte, 0, newCap)
|
|
||||||
}
|
|
||||||
start := len(p.backing)
|
|
||||||
p.backing = p.backing[:start+sz]
|
|
||||||
return p.backing[start : start+sz : start+sz] //return zero length, sz-cap slice
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Passthrough) Commit(pkt []byte) error {
|
|
||||||
p.slots = append(p.slots, pkt)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Passthrough) Flush() error {
|
|
||||||
var firstErr error
|
|
||||||
for _, s := range p.slots {
|
|
||||||
_, err := p.out.Write(s)
|
|
||||||
if err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range p.slots {
|
|
||||||
p.slots[i] = nil
|
|
||||||
}
|
|
||||||
p.slots = p.slots[:0]
|
|
||||||
p.backing = p.backing[:0]
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package batch
|
|
||||||
|
|
||||||
import "net/netip"
|
|
||||||
|
|
||||||
const SendBatchCap = 128
|
|
||||||
|
|
||||||
// SendBatch accumulates encrypted UDP packets for potential TX offloading.
|
|
||||||
// One SendBatch is owned by each listenIn goroutine; no locking is needed.
|
|
||||||
// The backing storage holds up to batchCap packets of slotCap bytes each;
|
|
||||||
// bufs and dsts are parallel slices of committed slots.
|
|
||||||
type SendBatch struct {
|
|
||||||
bufs [][]byte
|
|
||||||
dsts []netip.AddrPort
|
|
||||||
backing []byte
|
|
||||||
slotCap int
|
|
||||||
batchCap int
|
|
||||||
nextSlot int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSendBatch(batchCap, slotCap int) *SendBatch {
|
|
||||||
return &SendBatch{
|
|
||||||
bufs: make([][]byte, 0, batchCap),
|
|
||||||
dsts: make([]netip.AddrPort, 0, batchCap),
|
|
||||||
backing: make([]byte, batchCap*slotCap),
|
|
||||||
slotCap: slotCap,
|
|
||||||
batchCap: batchCap,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Next() []byte {
|
|
||||||
if b.nextSlot >= b.batchCap {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
start := b.nextSlot * b.slotCap
|
|
||||||
return b.backing[start : start : start+b.slotCap] //set len to 0 but cap to slotCap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Commit(n int, dst netip.AddrPort) {
|
|
||||||
start := b.nextSlot * b.slotCap
|
|
||||||
b.bufs = append(b.bufs, b.backing[start:start+n])
|
|
||||||
b.dsts = append(b.dsts, dst)
|
|
||||||
b.nextSlot++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Reset() {
|
|
||||||
b.bufs = b.bufs[:0]
|
|
||||||
b.dsts = b.dsts[:0]
|
|
||||||
b.nextSlot = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Len() int {
|
|
||||||
return len(b.bufs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Cap() int {
|
|
||||||
return b.batchCap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *SendBatch) Get() ([][]byte, []netip.AddrPort) {
|
|
||||||
return b.bufs, b.dsts
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package batch
|
package coalesce
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -84,8 +84,6 @@ type TCPCoalescer struct {
|
|||||||
// when a non-admissible packet for that flow arrives, or in Flush.
|
// when a non-admissible packet for that flow arrives, or in Flush.
|
||||||
openSlots map[flowKey]*coalesceSlot
|
openSlots map[flowKey]*coalesceSlot
|
||||||
pool []*coalesceSlot // free list for reuse
|
pool []*coalesceSlot // free list for reuse
|
||||||
|
|
||||||
backing []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPCoalescer(w io.Writer) *TCPCoalescer {
|
func NewTCPCoalescer(w io.Writer) *TCPCoalescer {
|
||||||
@@ -94,7 +92,6 @@ func NewTCPCoalescer(w io.Writer) *TCPCoalescer {
|
|||||||
slots: make([]*coalesceSlot, 0, initialSlots),
|
slots: make([]*coalesceSlot, 0, initialSlots),
|
||||||
openSlots: make(map[flowKey]*coalesceSlot, initialSlots),
|
openSlots: make(map[flowKey]*coalesceSlot, initialSlots),
|
||||||
pool: make([]*coalesceSlot, 0, initialSlots),
|
pool: make([]*coalesceSlot, 0, initialSlots),
|
||||||
backing: make([]byte, 0, initialSlots*65535),
|
|
||||||
}
|
}
|
||||||
if gw, ok := w.(tio.GSOWriter); ok && gw.GSOSupported() {
|
if gw, ok := w.(tio.GSOWriter); ok && gw.GSOSupported() {
|
||||||
c.gsoW = gw
|
c.gsoW = gw
|
||||||
@@ -197,22 +194,10 @@ func (p parsedTCP) coalesceable() bool {
|
|||||||
return p.payLen > 0
|
return p.payLen > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TCPCoalescer) Reserve(sz int) []byte {
|
// Add borrows pkt. The caller must keep pkt valid until the next Flush,
|
||||||
if len(c.backing)+sz > cap(c.backing) {
|
|
||||||
// Grow: allocate a fresh backing. Already-committed slices still
|
|
||||||
// reference the old array and remain valid until Flush drops them.
|
|
||||||
newCap := max(cap(c.backing)*2, sz)
|
|
||||||
c.backing = make([]byte, 0, newCap)
|
|
||||||
}
|
|
||||||
start := len(c.backing)
|
|
||||||
c.backing = c.backing[:start+sz]
|
|
||||||
return c.backing[start : start+sz : start+sz] //return zero length, sz-cap slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit borrows pkt. The caller must keep pkt valid until the next Flush,
|
|
||||||
// whether or not the packet was coalesced — passthrough (non-admissible)
|
// whether or not the packet was coalesced — passthrough (non-admissible)
|
||||||
// packets are queued and written at Flush time, not synchronously.
|
// packets are queued and written at Flush time, not synchronously.
|
||||||
func (c *TCPCoalescer) Commit(pkt []byte) error {
|
func (c *TCPCoalescer) Add(pkt []byte) error {
|
||||||
if c.gsoW == nil {
|
if c.gsoW == nil {
|
||||||
c.addPassthrough(pkt)
|
c.addPassthrough(pkt)
|
||||||
return nil
|
return nil
|
||||||
@@ -273,8 +258,6 @@ func (c *TCPCoalescer) Flush() error {
|
|||||||
for k := range c.openSlots {
|
for k := range c.openSlots {
|
||||||
delete(c.openSlots, k)
|
delete(c.openSlots, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.backing = c.backing[:0]
|
|
||||||
return first
|
return first
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,11 +327,6 @@ func (c *TCPCoalescer) appendPayload(s *coalesceSlot, pkt []byte, info parsedTCP
|
|||||||
s.numSeg++
|
s.numSeg++
|
||||||
s.totalPay += info.payLen
|
s.totalPay += info.payLen
|
||||||
s.nextSeq = info.seq + uint32(info.payLen)
|
s.nextSeq = info.seq + uint32(info.payLen)
|
||||||
if info.flags&0x08 != 0 {
|
|
||||||
// Propagate PSH into the seed header so kernel TSO sets it on the
|
|
||||||
// last segment. Without this the sender's push signal is dropped.
|
|
||||||
s.hdrBuf[s.ipHdrLen+13] |= 0x08
|
|
||||||
}
|
|
||||||
if info.payLen < s.gsoSize || info.flags&0x08 != 0 {
|
if info.payLen < s.gsoSize || info.flags&0x08 != 0 {
|
||||||
s.psh = true
|
s.psh = true
|
||||||
}
|
}
|
||||||
@@ -402,7 +380,7 @@ func (c *TCPCoalescer) flushSlot(s *coalesceSlot) error {
|
|||||||
tcsum := s.ipHdrLen + 16
|
tcsum := s.ipHdrLen + 16
|
||||||
binary.BigEndian.PutUint16(hdr[tcsum:tcsum+2], foldOnceNoInvert(psum))
|
binary.BigEndian.PutUint16(hdr[tcsum:tcsum+2], foldOnceNoInvert(psum))
|
||||||
|
|
||||||
return c.gsoW.WriteGSO(hdr[:s.ipHdrLen], hdr[s.ipHdrLen:], s.payIovs)
|
return c.gsoW.WriteGSO(hdr, s.payIovs, uint16(s.gsoSize), s.isV6, uint16(s.ipHdrLen))
|
||||||
}
|
}
|
||||||
|
|
||||||
// headersMatch compares two IP+TCP header prefixes for byte-for-byte
|
// headersMatch compares two IP+TCP header prefixes for byte-for-byte
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package batch
|
package coalesce
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
@@ -6,17 +6,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// fakeTunWriter records plain Writes and WriteGSO calls without touching a
|
// fakeTunWriter records plain Writes and WriteGSO calls without touching a
|
||||||
// real TUN fd. WriteGSO records the IP header, transport header, and
|
// real TUN fd. WriteGSO preserves the split between hdr and borrowed pays
|
||||||
// borrowed payload fragments separately so tests can inspect each.
|
// so tests can inspect each independently.
|
||||||
type fakeTunWriter struct {
|
type fakeTunWriter struct {
|
||||||
gsoEnabled bool
|
gsoEnabled bool
|
||||||
writes [][]byte
|
writes [][]byte
|
||||||
gsoWrites []fakeGSOWrite
|
gsoWrites []fakeGSOWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// fakeGSOWrite captures one WriteGSO call. hdr is the concatenation of the
|
|
||||||
// IP and transport headers (in that order), gsoSize / isV6 / csumStart are
|
|
||||||
// derived from the call so existing assertions keep working unchanged.
|
|
||||||
type fakeGSOWrite struct {
|
type fakeGSOWrite struct {
|
||||||
hdr []byte
|
hdr []byte
|
||||||
pays [][]byte
|
pays [][]byte
|
||||||
@@ -50,27 +47,21 @@ func (w *fakeTunWriter) Write(p []byte) (int, error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *fakeTunWriter) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error {
|
func (w *fakeTunWriter) WriteGSO(hdr []byte, pays [][]byte, gsoSize uint16, isV6 bool, csumStart uint16) error {
|
||||||
hcopy := make([]byte, len(hdr)+len(transportHdr))
|
hcopy := make([]byte, len(hdr))
|
||||||
copy(hcopy, hdr)
|
copy(hcopy, hdr)
|
||||||
copy(hcopy[len(hdr):], transportHdr)
|
|
||||||
paysCopy := make([][]byte, len(pays))
|
paysCopy := make([][]byte, len(pays))
|
||||||
for i, p := range pays {
|
for i, p := range pays {
|
||||||
pc := make([]byte, len(p))
|
pc := make([]byte, len(p))
|
||||||
copy(pc, p)
|
copy(pc, p)
|
||||||
paysCopy[i] = pc
|
paysCopy[i] = pc
|
||||||
}
|
}
|
||||||
var gsoSize uint16
|
|
||||||
if len(pays) > 1 {
|
|
||||||
gsoSize = uint16(len(pays[0]))
|
|
||||||
}
|
|
||||||
isV6 := len(hdr) > 0 && hdr[0]>>4 == 6
|
|
||||||
w.gsoWrites = append(w.gsoWrites, fakeGSOWrite{
|
w.gsoWrites = append(w.gsoWrites, fakeGSOWrite{
|
||||||
hdr: hcopy,
|
hdr: hcopy,
|
||||||
pays: paysCopy,
|
pays: paysCopy,
|
||||||
gsoSize: gsoSize,
|
gsoSize: gsoSize,
|
||||||
isV6: isV6,
|
isV6: isV6,
|
||||||
csumStart: uint16(len(hdr)),
|
csumStart: csumStart,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -125,7 +116,7 @@ func TestCoalescerPassthroughWhenGSOUnavailable(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: false}
|
w := &fakeTunWriter{gsoEnabled: false}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pkt := buildTCPv4(1000, tcpAck, []byte("hello"))
|
pkt := buildTCPv4(1000, tcpAck, []byte("hello"))
|
||||||
if err := c.Commit(pkt); err != nil {
|
if err := c.Add(pkt); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// No sync write — passthrough is deferred to Flush.
|
// No sync write — passthrough is deferred to Flush.
|
||||||
@@ -149,7 +140,7 @@ func TestCoalescerNonTCPPassthrough(t *testing.T) {
|
|||||||
pkt[9] = 1
|
pkt[9] = 1
|
||||||
copy(pkt[12:16], []byte{10, 0, 0, 1})
|
copy(pkt[12:16], []byte{10, 0, 0, 1})
|
||||||
copy(pkt[16:20], []byte{10, 0, 0, 2})
|
copy(pkt[16:20], []byte{10, 0, 0, 2})
|
||||||
if err := c.Commit(pkt); err != nil {
|
if err := c.Add(pkt); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -164,7 +155,7 @@ func TestCoalescerSeedThenFlushAlone(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pkt := buildTCPv4(1000, tcpAck, make([]byte, 1000))
|
pkt := buildTCPv4(1000, tcpAck, make([]byte, 1000))
|
||||||
if err := c.Commit(pkt); err != nil {
|
if err := c.Add(pkt); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(w.writes) != 0 || len(w.gsoWrites) != 0 {
|
if len(w.writes) != 0 || len(w.gsoWrites) != 0 {
|
||||||
@@ -191,13 +182,13 @@ func TestCoalescerCoalescesAdjacentACKs(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(2200, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(2200, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(3400, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -231,10 +222,10 @@ func TestCoalescerRejectsSeqGap(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(3000, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(3000, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -250,13 +241,13 @@ func TestCoalescerRejectsFlagMismatch(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// SYN|ACK is non-admissible. Must flush matching flow's slot (gso)
|
// SYN|ACK is non-admissible. Must flush matching flow's slot (gso)
|
||||||
// and then plain-write the SYN packet itself.
|
// and then plain-write the SYN packet itself.
|
||||||
syn := buildTCPv4(2200, tcpSyn|tcpAck, pay)
|
syn := buildTCPv4(2200, tcpSyn|tcpAck, pay)
|
||||||
if err := c.Commit(syn); err != nil {
|
if err := c.Add(syn); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -271,7 +262,7 @@ func TestCoalescerRejectsFIN(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
fin := buildTCPv4(1000, tcpAck|tcpFin, []byte("x"))
|
fin := buildTCPv4(1000, tcpAck|tcpFin, []byte("x"))
|
||||||
if err := c.Commit(fin); err != nil {
|
if err := c.Add(fin); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -288,15 +279,15 @@ func TestCoalescerShortLastSegmentClosesChain(t *testing.T) {
|
|||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
full := make([]byte, 1200)
|
full := make([]byte, 1200)
|
||||||
half := make([]byte, 500)
|
half := make([]byte, 500)
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, full)); err != nil {
|
if err := c.Add(buildTCPv4(1000, tcpAck, full)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(2200, tcpAck, half)); err != nil {
|
if err := c.Add(buildTCPv4(2200, tcpAck, half)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Chain now closed; next packet seeds a new slot on the same flow
|
// Chain now closed; next packet seeds a new slot on the same flow
|
||||||
// after flushing the old one.
|
// after flushing the old one.
|
||||||
if err := c.Commit(buildTCPv4(2700, tcpAck, full)); err != nil {
|
if err := c.Add(buildTCPv4(2700, tcpAck, full)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -322,13 +313,13 @@ func TestCoalescerPSHFinalizesChain(t *testing.T) {
|
|||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
|
if err := c.Add(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4(3400, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(3400, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -343,39 +334,6 @@ func TestCoalescerPSHFinalizesChain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCoalescerPropagatesPSHFromAppended ensures that when an appended
|
|
||||||
// segment carries PSH (or is short, sealing the chain), the PSH bit ends
|
|
||||||
// up in the emitted superpacket's TCP flags. The kernel TSO path keeps
|
|
||||||
// PSH only on the last segment iff the input header has it set; if the
|
|
||||||
// coalescer drops it the sender's push signal never reaches the receiver.
|
|
||||||
func TestCoalescerPropagatesPSHFromAppended(t *testing.T) {
|
|
||||||
w := &fakeTunWriter{gsoEnabled: true}
|
|
||||||
c := NewTCPCoalescer(w)
|
|
||||||
pay := make([]byte, 1200)
|
|
||||||
// Seed has no PSH; second segment carries PSH and seals the chain.
|
|
||||||
if err := c.Commit(buildTCPv4(1000, tcpAck, pay)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := c.Commit(buildTCPv4(2200, tcpAckPsh, pay)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := c.Flush(0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(w.gsoWrites) != 1 {
|
|
||||||
t.Fatalf("want 1 gso write got %d", len(w.gsoWrites))
|
|
||||||
}
|
|
||||||
g := w.gsoWrites[0]
|
|
||||||
const ipHdrLen = 20
|
|
||||||
flags := g.hdr[ipHdrLen+13]
|
|
||||||
if flags&tcpPsh == 0 {
|
|
||||||
t.Fatalf("PSH lost from coalesced superpacket: flags=0x%02x", flags)
|
|
||||||
}
|
|
||||||
if flags&tcpAck == 0 {
|
|
||||||
t.Fatalf("ACK missing from coalesced superpacket: flags=0x%02x", flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCoalescerRejectsDifferentFlow(t *testing.T) {
|
func TestCoalescerRejectsDifferentFlow(t *testing.T) {
|
||||||
w := &fakeTunWriter{gsoEnabled: true}
|
w := &fakeTunWriter{gsoEnabled: true}
|
||||||
c := NewTCPCoalescer(w)
|
c := NewTCPCoalescer(w)
|
||||||
@@ -383,10 +341,10 @@ func TestCoalescerRejectsDifferentFlow(t *testing.T) {
|
|||||||
p1 := buildTCPv4(1000, tcpAck, pay)
|
p1 := buildTCPv4(1000, tcpAck, pay)
|
||||||
p2 := buildTCPv4(2200, tcpAck, pay)
|
p2 := buildTCPv4(2200, tcpAck, pay)
|
||||||
binary.BigEndian.PutUint16(p2[20:22], 9999)
|
binary.BigEndian.PutUint16(p2[20:22], 9999)
|
||||||
if err := c.Commit(p1); err != nil {
|
if err := c.Add(p1); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(p2); err != nil {
|
if err := c.Add(p2); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -406,7 +364,7 @@ func TestCoalescerRejectsIPOptions(t *testing.T) {
|
|||||||
// Bump IHL to 6 to simulate 4 bytes of IP options. Don't actually add
|
// Bump IHL to 6 to simulate 4 bytes of IP options. Don't actually add
|
||||||
// bytes — parser should bail before it matters.
|
// bytes — parser should bail before it matters.
|
||||||
pkt[0] = 0x46
|
pkt[0] = 0x46
|
||||||
if err := c.Commit(pkt); err != nil {
|
if err := c.Add(pkt); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -424,7 +382,7 @@ func TestCoalescerCapBySegments(t *testing.T) {
|
|||||||
pay := make([]byte, 512)
|
pay := make([]byte, 512)
|
||||||
seq := uint32(1000)
|
seq := uint32(1000)
|
||||||
for i := 0; i < tcpCoalesceMaxSegs+5; i++ {
|
for i := 0; i < tcpCoalesceMaxSegs+5; i++ {
|
||||||
if err := c.Commit(buildTCPv4(seq, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4(seq, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
seq += uint32(len(pay))
|
seq += uint32(len(pay))
|
||||||
@@ -448,22 +406,22 @@ func TestCoalescerMultipleFlowsInSameBatch(t *testing.T) {
|
|||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
|
|
||||||
// Flow A: sport 1000. Flow B: sport 3000.
|
// Flow A: sport 1000. Flow B: sport 3000.
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 2500, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 2500, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -505,7 +463,7 @@ func TestCoalescerPreservesArrivalOrder(t *testing.T) {
|
|||||||
// Sequence: coalesceable TCP, ICMP (passthrough), coalesceable TCP on
|
// Sequence: coalesceable TCP, ICMP (passthrough), coalesceable TCP on
|
||||||
// a different flow. Expected emit order: gso(X), plain(ICMP), gso(Y).
|
// a different flow. Expected emit order: gso(X), plain(ICMP), gso(Y).
|
||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
icmp := make([]byte, 28)
|
icmp := make([]byte, 28)
|
||||||
@@ -514,10 +472,10 @@ func TestCoalescerPreservesArrivalOrder(t *testing.T) {
|
|||||||
icmp[9] = 1
|
icmp[9] = 1
|
||||||
copy(icmp[12:16], []byte{10, 0, 0, 1})
|
copy(icmp[12:16], []byte{10, 0, 0, 1})
|
||||||
copy(icmp[16:20], []byte{10, 0, 0, 3})
|
copy(icmp[16:20], []byte{10, 0, 0, 3})
|
||||||
if err := c.Commit(icmp); err != nil {
|
if err := c.Add(icmp); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Nothing should have hit the writer synchronously.
|
// Nothing should have hit the writer synchronously.
|
||||||
@@ -544,7 +502,7 @@ func (w *orderedFakeWriter) Write(p []byte) (int, error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *orderedFakeWriter) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error {
|
func (w *orderedFakeWriter) WriteGSO(hdr []byte, pays [][]byte, gsoSize uint16, isV6 bool, csumStart uint16) error {
|
||||||
w.events = append(w.events, "gso")
|
w.events = append(w.events, "gso")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -571,26 +529,26 @@ func TestCoalescerInterleavedFlowsPreserveOrdering(t *testing.T) {
|
|||||||
pay := make([]byte, 1200)
|
pay := make([]byte, 1200)
|
||||||
|
|
||||||
// Flow A two segments.
|
// Flow A two segments.
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 100, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(1000, 2000, 1300, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Flow B two segments.
|
// Flow B two segments.
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 500, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 1700, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Flow A SYN (non-admissible) — must flush only flow A's slot.
|
// Flow A SYN (non-admissible) — must flush only flow A's slot.
|
||||||
syn := buildTCPv4Ports(1000, 2000, 9999, tcpSyn|tcpAck, pay)
|
syn := buildTCPv4Ports(1000, 2000, 9999, tcpSyn|tcpAck, pay)
|
||||||
if err := c.Commit(syn); err != nil {
|
if err := c.Add(syn); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// Flow B continues — should still be coalesced with its seed.
|
// Flow B continues — should still be coalesced with its seed.
|
||||||
if err := c.Commit(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
|
if err := c.Add(buildTCPv4Ports(3000, 2000, 2900, tcpAck, pay)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := c.Flush(); err != nil {
|
if err := c.Flush(); err != nil {
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
// Package overlaytest provides fakes of overlay.Device for tests that do
|
package overlay
|
||||||
// not want to touch a real tun device or route table.
|
|
||||||
package overlaytest
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -10,9 +8,6 @@ import (
|
|||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoopTun is an overlay.Device that silently discards every read and write.
|
|
||||||
// Useful in tests that need to construct a nebula Interface but do not
|
|
||||||
// exercise the datapath.
|
|
||||||
type NoopTun struct{}
|
type NoopTun struct{}
|
||||||
|
|
||||||
func (NoopTun) RoutesFor(addr netip.Addr) routing.Gateways {
|
func (NoopTun) RoutesFor(addr netip.Addr) routing.Gateways {
|
||||||
@@ -39,6 +34,10 @@ func (NoopTun) Write([]byte) (int, error) {
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (NoopTun) WriteReject(p []byte) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (NoopTun) SupportsMultiqueue() bool {
|
func (NoopTun) SupportsMultiqueue() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
+3
-6
@@ -2,7 +2,6 @@ package overlay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -10,6 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
)
|
)
|
||||||
@@ -48,14 +48,11 @@ func (r Route) String() string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRouteTree(l *slog.Logger, routes []Route, allowMTU bool) (*bart.Table[routing.Gateways], error) {
|
func makeRouteTree(l *logrus.Logger, routes []Route, allowMTU bool) (*bart.Table[routing.Gateways], error) {
|
||||||
routeTree := new(bart.Table[routing.Gateways])
|
routeTree := new(bart.Table[routing.Gateways])
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
if !allowMTU && r.MTU > 0 {
|
if !allowMTU && r.MTU > 0 {
|
||||||
l.Warn("route MTU is not supported on this platform",
|
l.WithField("route", r).Warnf("route MTU is not supported in %s", runtime.GOOS)
|
||||||
"goos", runtime.GOOS,
|
|
||||||
"route", r,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gateways := r.Via
|
gateways := r.Via
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ func Test_makeRouteTree(t *testing.T) {
|
|||||||
routes, err := parseUnsafeRoutes(c, []netip.Prefix{n})
|
routes, err := parseUnsafeRoutes(c, []netip.Prefix{n})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, routes, 2)
|
assert.Len(t, routes, 2)
|
||||||
routeTree, err := makeRouteTree(test.NewLogger(), routes, true)
|
routeTree, err := makeRouteTree(l, routes, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ip, err := netip.ParseAddr("1.0.0.2")
|
ip, err := netip.ParseAddr("1.0.0.2")
|
||||||
@@ -367,7 +367,7 @@ func Test_makeMultipathUnsafeRouteTree(t *testing.T) {
|
|||||||
routes, err := parseUnsafeRoutes(c, []netip.Prefix{n})
|
routes, err := parseUnsafeRoutes(c, []netip.Prefix{n})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, routes, 3)
|
assert.Len(t, routes, 3)
|
||||||
routeTree, err := makeRouteTree(test.NewLogger(), routes, true)
|
routeTree, err := makeRouteTree(l, routes, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ip, err := netip.ParseAddr("192.168.86.1")
|
ip, err := netip.ParseAddr("192.168.86.1")
|
||||||
|
|||||||
+15
-19
@@ -1,19 +1,13 @@
|
|||||||
package tio
|
package tio
|
||||||
|
|
||||||
import (
|
import "io"
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// defaultBatchBufSize is the per-Queue scratch size for Read on backends
|
// defaultBatchBufSize is the per-Queue scratch size for Read on backends
|
||||||
// that don't do TSO segmentation. 65535 covers any single IP packet.
|
// that don't do TSO segmentation. 65535 covers any single IP packet.
|
||||||
const defaultBatchBufSize = 65535
|
const defaultBatchBufSize = 65535
|
||||||
|
|
||||||
// Container holds one or many Queue objects and helps close them in an orderly way
|
|
||||||
type Container interface {
|
type Container interface {
|
||||||
io.Closer
|
|
||||||
Queues() []Queue
|
Queues() []Queue
|
||||||
|
|
||||||
// Add takes a tun fd, adds it to the container, and prepares it for use as a Queue
|
|
||||||
Add(fd int) error
|
Add(fd int) error
|
||||||
|
|
||||||
io.Closer
|
io.Closer
|
||||||
@@ -26,13 +20,22 @@ type Queue interface {
|
|||||||
|
|
||||||
// Read returns one or more packets. The returned slices are borrowed
|
// Read returns one or more packets. The returned slices are borrowed
|
||||||
// from the Queue's internal buffer and are only valid until the next
|
// from the Queue's internal buffer and are only valid until the next
|
||||||
// Read or Close on this Queue - callers must encrypt or copy each
|
// Read or Close on this Queue — callers must encrypt or copy each
|
||||||
// slice before the next call. Not safe for concurrent Reads.
|
// slice before the next call. Not safe for concurrent Reads; exactly
|
||||||
|
// one goroutine per Queue reads.
|
||||||
Read() ([][]byte, error)
|
Read() ([][]byte, error)
|
||||||
|
|
||||||
// Write emits a single packet on the plaintext (outside→inside)
|
// Write emits a single packet on the plaintext (outside→inside)
|
||||||
// delivery path. Not safe for concurrent Writes.
|
// delivery path. May run concurrently with WriteReject on the same
|
||||||
|
// Queue, but not with itself.
|
||||||
Write(p []byte) (int, error)
|
Write(p []byte) (int, error)
|
||||||
|
|
||||||
|
// WriteReject writes a single packet that originated from the inside
|
||||||
|
// path (reject replies or self-forward) using scratch state distinct
|
||||||
|
// from Write, so it can run concurrently with Write on the same Queue
|
||||||
|
// without a data race. On backends without a shared-scratch Write, a
|
||||||
|
// trivial delegation to Write is acceptable.
|
||||||
|
WriteReject(p []byte) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GSOWriter is implemented by Queues that can emit a TCP TSO superpacket
|
// GSOWriter is implemented by Queues that can emit a TCP TSO superpacket
|
||||||
@@ -42,7 +45,7 @@ type Queue interface {
|
|||||||
// between the caller's decrypt buffer and the TUN. Backends without GSO
|
// between the caller's decrypt buffer and the TUN. Backends without GSO
|
||||||
// support return false from GSOSupported and coalescing is skipped.
|
// support return false from GSOSupported and coalescing is skipped.
|
||||||
//
|
//
|
||||||
// hdr contains the IPv4/IPv6 + TCP header prefix (mutable - callers will
|
// hdr contains the IPv4/IPv6 + TCP header prefix (mutable — callers will
|
||||||
// have filled in total length and pseudo-header partial). pays are
|
// have filled in total length and pseudo-header partial). pays are
|
||||||
// non-overlapping payload fragments whose concatenation is the full
|
// non-overlapping payload fragments whose concatenation is the full
|
||||||
// superpacket payload; they are read-only from the writer's perspective
|
// superpacket payload; they are read-only from the writer's perspective
|
||||||
@@ -55,13 +58,6 @@ type Queue interface {
|
|||||||
// hdr's TCP checksum field must already hold the pseudo-header partial
|
// hdr's TCP checksum field must already hold the pseudo-header partial
|
||||||
// sum (single-fold, not inverted), per virtio NEEDS_CSUM semantics.
|
// sum (single-fold, not inverted), per virtio NEEDS_CSUM semantics.
|
||||||
type GSOWriter interface {
|
type GSOWriter interface {
|
||||||
// WriteGSO emits a TCP TSO superpacket in a single writev. hdr is the
|
WriteGSO(hdr []byte, pays [][]byte, gsoSize uint16, isV6 bool, csumStart uint16) error
|
||||||
// IPv4/IPv6 + TCP header prefix (already finalized — total length, IP csum,
|
|
||||||
// and TCP pseudo-header partial set by the caller). pays are payload
|
|
||||||
// fragments whose concatenation forms the full coalesced payload; each
|
|
||||||
// slice is read-only and must stay valid until return.
|
|
||||||
// every segment in pays except possibly the last is exactly the same size.
|
|
||||||
// csumStart is the byte offset where the TCP header begins within hdr.
|
|
||||||
WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error
|
|
||||||
GSOSupported() bool
|
GSOSupported() bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -35,8 +34,8 @@ const gsoInitialPayIovs = 66
|
|||||||
|
|
||||||
// validVnetHdr is the 10-byte virtio_net_hdr we prepend to every non-GSO TUN
|
// validVnetHdr is the 10-byte virtio_net_hdr we prepend to every non-GSO TUN
|
||||||
// write. Only flag set is VIRTIO_NET_HDR_F_DATA_VALID, which marks the skb
|
// write. Only flag set is VIRTIO_NET_HDR_F_DATA_VALID, which marks the skb
|
||||||
// CHECKSUM_UNNECESSARY so the receiving network stack skips L4 checks
|
// CHECKSUM_UNNECESSARY so the receiving network stack skips L4 checksum
|
||||||
// verification. All packets that reach the plain Write paths
|
// verification. All packets that reach the plain Write / WriteReject paths
|
||||||
// already carry a valid L4 checksum (either supplied by a remote peer whose
|
// already carry a valid L4 checksum (either supplied by a remote peer whose
|
||||||
// ciphertext we AEAD-authenticated, or produced by finishChecksum during TSO
|
// ciphertext we AEAD-authenticated, or produced by finishChecksum during TSO
|
||||||
// segmentation, or built locally by CreateRejectPacket), so trusting them is
|
// segmentation, or built locally by CreateRejectPacket), so trusting them is
|
||||||
@@ -50,12 +49,17 @@ type Offload struct {
|
|||||||
shutdownFd int
|
shutdownFd int
|
||||||
readPoll [2]unix.PollFd
|
readPoll [2]unix.PollFd
|
||||||
writePoll [2]unix.PollFd
|
writePoll [2]unix.PollFd
|
||||||
writeLock sync.Mutex //there's more than one potential write source per-routine, so we need this to protect writePoll
|
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
readBuf []byte // scratch for a single raw read (virtio hdr + superpacket)
|
readBuf []byte // scratch for a single raw read (virtio hdr + superpacket)
|
||||||
segBuf []byte // backing store for segmented output
|
segBuf []byte // backing store for segmented output
|
||||||
segOff int // cursor into segBuf for the current Read drain
|
segOff int // cursor into segBuf for the current Read drain
|
||||||
pending [][]byte // segments returned from the most recent Read
|
pending [][]byte // segments returned from the most recent Read
|
||||||
|
writeIovs [2]unix.Iovec // preallocated iovecs for Write (coalescer passthrough); iovs[0] is fixed to validVnetHdr
|
||||||
|
// rejectIovs is a second preallocated iovec scratch used exclusively by
|
||||||
|
// WriteReject (reject + self-forward from the inside path). It mirrors
|
||||||
|
// writeIovs but lets listenIn goroutines emit reject packets without
|
||||||
|
// racing with the listenOut coalescer that owns writeIovs.
|
||||||
|
rejectIovs [2]unix.Iovec
|
||||||
|
|
||||||
// gsoHdrBuf is a per-queue 10-byte scratch for the virtio_net_hdr emitted
|
// gsoHdrBuf is a per-queue 10-byte scratch for the virtio_net_hdr emitted
|
||||||
// by WriteGSO. Separate from validVnetHdr so a concurrent non-GSO Write on
|
// by WriteGSO. Separate from validVnetHdr so a concurrent non-GSO Write on
|
||||||
@@ -76,7 +80,7 @@ func newOffload(fd int, shutdownFd int) (*Offload, error) {
|
|||||||
fd: fd,
|
fd: fd,
|
||||||
shutdownFd: shutdownFd,
|
shutdownFd: shutdownFd,
|
||||||
closed: atomic.Bool{},
|
closed: atomic.Bool{},
|
||||||
readBuf: make([]byte, virtioNetHdrLen+tunReadBufSize),
|
readBuf: make([]byte, tunReadBufSize),
|
||||||
readPoll: [2]unix.PollFd{
|
readPoll: [2]unix.PollFd{
|
||||||
{Fd: int32(fd), Events: unix.POLLIN},
|
{Fd: int32(fd), Events: unix.POLLIN},
|
||||||
{Fd: int32(shutdownFd), Events: unix.POLLIN},
|
{Fd: int32(shutdownFd), Events: unix.POLLIN},
|
||||||
@@ -85,12 +89,15 @@ func newOffload(fd int, shutdownFd int) (*Offload, error) {
|
|||||||
{Fd: int32(fd), Events: unix.POLLOUT},
|
{Fd: int32(fd), Events: unix.POLLOUT},
|
||||||
{Fd: int32(shutdownFd), Events: unix.POLLIN},
|
{Fd: int32(shutdownFd), Events: unix.POLLIN},
|
||||||
},
|
},
|
||||||
writeLock: sync.Mutex{},
|
|
||||||
|
|
||||||
segBuf: make([]byte, tunSegBufCap),
|
segBuf: make([]byte, tunSegBufCap),
|
||||||
gsoIovs: make([]unix.Iovec, 2, 2+gsoInitialPayIovs),
|
gsoIovs: make([]unix.Iovec, 2, 2+gsoInitialPayIovs),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.writeIovs[0].Base = &validVnetHdr[0]
|
||||||
|
out.writeIovs[0].SetLen(virtioNetHdrLen)
|
||||||
|
out.rejectIovs[0].Base = &validVnetHdr[0]
|
||||||
|
out.rejectIovs[0].SetLen(virtioNetHdrLen)
|
||||||
out.gsoIovs[0].Base = &out.gsoHdrBuf[0]
|
out.gsoIovs[0].Base = &out.gsoHdrBuf[0]
|
||||||
out.gsoIovs[0].SetLen(virtioNetHdrLen)
|
out.gsoIovs[0].SetLen(virtioNetHdrLen)
|
||||||
|
|
||||||
@@ -133,12 +140,10 @@ func (r *Offload) blockOnWrite() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//always reset these!
|
//always reset these!
|
||||||
r.writeLock.Lock()
|
|
||||||
tunEvents := r.writePoll[0].Revents
|
tunEvents := r.writePoll[0].Revents
|
||||||
shutdownEvents := r.writePoll[1].Revents
|
shutdownEvents := r.writePoll[1].Revents
|
||||||
r.writePoll[0].Revents = 0
|
r.writePoll[0].Revents = 0
|
||||||
r.writePoll[1].Revents = 0
|
r.writePoll[1].Revents = 0
|
||||||
r.writeLock.Unlock()
|
|
||||||
//do the err check before trusting the potentially bogus bits we just got
|
//do the err check before trusting the potentially bogus bits we just got
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -220,6 +225,7 @@ func (r *Offload) Read() ([][]byte, error) {
|
|||||||
|
|
||||||
// decodeRead decodes the virtio header plus payload in r.readBuf[:n], appends
|
// decodeRead decodes the virtio header plus payload in r.readBuf[:n], appends
|
||||||
// the segments to r.pending, and advances r.segOff by the total scratch used.
|
// the segments to r.pending, and advances r.segOff by the total scratch used.
|
||||||
|
// Caller must have already ensured r.vnetHdr is true.
|
||||||
func (r *Offload) decodeRead(n int) error {
|
func (r *Offload) decodeRead(n int) error {
|
||||||
if n < virtioNetHdrLen {
|
if n < virtioNetHdrLen {
|
||||||
return fmt.Errorf("short tun read: %d < %d", n, virtioNetHdrLen)
|
return fmt.Errorf("short tun read: %d < %d", n, virtioNetHdrLen)
|
||||||
@@ -237,13 +243,16 @@ func (r *Offload) decodeRead(n int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Offload) Write(buf []byte) (int, error) {
|
func (r *Offload) Write(buf []byte) (int, error) {
|
||||||
iovs := [2]unix.Iovec{
|
return r.writeWithScratch(buf, &r.writeIovs)
|
||||||
{Base: &validVnetHdr[0]},
|
|
||||||
{Base: &buf[0]},
|
|
||||||
}
|
}
|
||||||
iovs[0].SetLen(virtioNetHdrLen)
|
|
||||||
iovs[1].SetLen(len(buf))
|
// WriteReject emits a packet using a dedicated iovec scratch (rejectIovs)
|
||||||
return r.writeWithScratch(buf, &iovs)
|
// distinct from the one used by the coalescer's Write path. This avoids a
|
||||||
|
// data race between the inside (listenIn) goroutine emitting reject or
|
||||||
|
// self-forward packets and the outside (listenOut) goroutine flushing TCP
|
||||||
|
// coalescer passthroughs on the same Offload.
|
||||||
|
func (r *Offload) WriteReject(buf []byte) (int, error) {
|
||||||
|
return r.writeWithScratch(buf, &r.rejectIovs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Offload) writeWithScratch(buf []byte, iovs *[2]unix.Iovec) (int, error) {
|
func (r *Offload) writeWithScratch(buf []byte, iovs *[2]unix.Iovec) (int, error) {
|
||||||
@@ -287,26 +296,37 @@ func (r *Offload) rawWrite(iovs []unix.Iovec) (int, error) {
|
|||||||
// Write calls.
|
// Write calls.
|
||||||
func (r *Offload) GSOSupported() bool { return true }
|
func (r *Offload) GSOSupported() bool { return true }
|
||||||
|
|
||||||
func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error {
|
// WriteGSO emits a TCP TSO superpacket in a single writev. hdr is the
|
||||||
if len(hdr) == 0 || len(pays) == 0 || len(transportHdr) == 0 {
|
// IPv4/IPv6 + TCP header prefix (already finalized — total length, IP csum,
|
||||||
|
// and TCP pseudo-header partial set by the caller). pays are payload
|
||||||
|
// fragments whose concatenation forms the full coalesced payload; each
|
||||||
|
// slice is read-only and must stay valid until return. gsoSize is the MSS;
|
||||||
|
// every segment except possibly the last is exactly gsoSize bytes.
|
||||||
|
// csumStart is the byte offset where the TCP header begins within hdr.
|
||||||
|
func (r *Offload) WriteGSO(hdr []byte, pays [][]byte, gsoSize uint16, isV6 bool, csumStart uint16) error {
|
||||||
|
if len(hdr) == 0 || len(pays) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the virtio_net_hdr. When pays total to <= gsoSize the kernel
|
||||||
|
// would produce a single segment; keep NEEDS_CSUM semantics but skip
|
||||||
|
// the GSO type so the kernel doesn't spuriously mark this as TSO.
|
||||||
vhdr := VirtioNetHdr{
|
vhdr := VirtioNetHdr{
|
||||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||||
HdrLen: uint16(len(hdr) + len(transportHdr)),
|
HdrLen: uint16(len(hdr)),
|
||||||
GSOSize: uint16(len(pays[0])),
|
GSOSize: gsoSize,
|
||||||
CsumStart: uint16(len(hdr)),
|
CsumStart: csumStart,
|
||||||
CsumOffset: 16, // TCP checksum field lives 16 bytes into the TCP header
|
CsumOffset: 16, // TCP checksum field lives 16 bytes into the TCP header
|
||||||
}
|
}
|
||||||
if len(pays) > 1 {
|
var totalPay int
|
||||||
ipVer := hdr[0] >> 4
|
for _, p := range pays {
|
||||||
if ipVer == 6 {
|
totalPay += len(p)
|
||||||
|
}
|
||||||
|
if totalPay > int(gsoSize) {
|
||||||
|
if isV6 {
|
||||||
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV6
|
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV6
|
||||||
} else if ipVer == 4 {
|
|
||||||
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV4
|
|
||||||
} else {
|
} else {
|
||||||
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_NONE
|
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV4
|
||||||
vhdr.GSOSize = 0
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_NONE
|
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_NONE
|
||||||
@@ -314,9 +334,9 @@ func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error
|
|||||||
}
|
}
|
||||||
vhdr.encode(r.gsoHdrBuf[:])
|
vhdr.encode(r.gsoHdrBuf[:])
|
||||||
|
|
||||||
// Build the iovec array: [virtio_hdr, hdr, transportHdr, pays...]. r.gsoIovs[0] is
|
// Build the iovec array: [virtio_hdr, hdr, pays...]. r.gsoIovs[0] is
|
||||||
// wired to gsoHdrBuf at construction and never changes.
|
// wired to gsoHdrBuf at construction and never changes.
|
||||||
need := 3 + len(pays)
|
need := 2 + len(pays)
|
||||||
if cap(r.gsoIovs) < need {
|
if cap(r.gsoIovs) < need {
|
||||||
grown := make([]unix.Iovec, need)
|
grown := make([]unix.Iovec, need)
|
||||||
grown[0] = r.gsoIovs[0]
|
grown[0] = r.gsoIovs[0]
|
||||||
@@ -326,11 +346,9 @@ func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte) error
|
|||||||
}
|
}
|
||||||
r.gsoIovs[1].Base = &hdr[0]
|
r.gsoIovs[1].Base = &hdr[0]
|
||||||
r.gsoIovs[1].SetLen(len(hdr))
|
r.gsoIovs[1].SetLen(len(hdr))
|
||||||
r.gsoIovs[2].Base = &transportHdr[0]
|
|
||||||
r.gsoIovs[2].SetLen(len(transportHdr))
|
|
||||||
for i, p := range pays {
|
for i, p := range pays {
|
||||||
r.gsoIovs[3+i].Base = &p[0]
|
r.gsoIovs[2+i].Base = &p[0]
|
||||||
r.gsoIovs[3+i].SetLen(len(p))
|
r.gsoIovs[2+i].SetLen(len(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := r.rawWrite(r.gsoIovs)
|
_, err := r.rawWrite(r.gsoIovs)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -98,6 +100,9 @@ func (t *Poll) blockOnWrite() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Poll) Read() ([][]byte, error) {
|
func (t *Poll) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.readOne(t.readBuf)
|
n, err := t.readOne(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -107,10 +112,20 @@ func (t *Poll) Read() ([][]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Poll) readOne(to []byte) (int, error) {
|
func (t *Poll) readOne(to []byte) (int, error) {
|
||||||
|
// first 4 bytes is protocol family, in network byte order
|
||||||
|
var head [4]byte
|
||||||
|
iovecs := [2]syscall.Iovec{ //todo plat-specific
|
||||||
|
{&head[0], 4},
|
||||||
|
{&to[0], uint64(len(to))},
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
n, errno := unix.Read(t.fd, to)
|
n, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(t.fd), uintptr(unsafe.Pointer(&iovecs[0])), 2)
|
||||||
if errno == nil {
|
if errno == 0 {
|
||||||
return n, nil
|
bytesRead := int(n)
|
||||||
|
if bytesRead < 4 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return bytesRead - 4, nil
|
||||||
}
|
}
|
||||||
switch errno {
|
switch errno {
|
||||||
case unix.EAGAIN:
|
case unix.EAGAIN:
|
||||||
@@ -129,10 +144,30 @@ func (t *Poll) readOne(to []byte) (int, error) {
|
|||||||
|
|
||||||
// Write is only valid for single threaded use
|
// Write is only valid for single threaded use
|
||||||
func (t *Poll) Write(from []byte) (int, error) {
|
func (t *Poll) Write(from []byte) (int, error) {
|
||||||
|
if len(from) <= 1 {
|
||||||
|
return 0, syscall.EIO
|
||||||
|
}
|
||||||
|
|
||||||
|
ipVer := from[0] >> 4
|
||||||
|
var head [4]byte
|
||||||
|
// first 4 bytes is protocol family, in network byte order
|
||||||
|
switch ipVer {
|
||||||
|
case 4:
|
||||||
|
head[3] = syscall.AF_INET
|
||||||
|
case 6:
|
||||||
|
head[3] = syscall.AF_INET6
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
iovecs := [2]syscall.Iovec{ //todo plat specific
|
||||||
|
{&head[0], 4},
|
||||||
|
{&from[0], uint64(len(from))},
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
n, errno := unix.Write(t.fd, from)
|
n, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(t.fd), uintptr(unsafe.Pointer(&iovecs[0])), 2)
|
||||||
if errno == nil {
|
if errno == 0 {
|
||||||
return n, nil
|
return int(n) - 4, nil
|
||||||
}
|
}
|
||||||
switch errno {
|
switch errno {
|
||||||
case unix.EAGAIN:
|
case unix.EAGAIN:
|
||||||
@@ -153,7 +188,9 @@ func (t *Poll) Close() error {
|
|||||||
if t.closed.Swap(true) {
|
if t.closed.Swap(true) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//shutdownFd is owned by the container, so we should not close it
|
//shutdownFd is owned by the container, so we should not close it
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if t.fd >= 0 {
|
if t.fd >= 0 {
|
||||||
err = unix.Close(t.fd)
|
err = unix.Close(t.fd)
|
||||||
@@ -162,3 +199,7 @@ func (t *Poll) Close() error {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Poll) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.Write(p)
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ func newReadPipe(t *testing.T) int {
|
|||||||
return fds[0]
|
return fds[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoll_WakeForShutdown_WakesFriends(t *testing.T) {
|
func TestOffload_WakeForShutdown_WakesFriends(t *testing.T) {
|
||||||
pipe1 := newReadPipe(t)
|
pipe1 := newReadPipe(t)
|
||||||
pipe2 := newReadPipe(t)
|
pipe2 := newReadPipe(t)
|
||||||
parent, err := NewPollContainer()
|
parent, err := NewOffloadContainer()
|
||||||
require.NoError(t, err)
|
if err != nil {
|
||||||
|
t.Fatalf("newOffload: %v", err)
|
||||||
|
}
|
||||||
require.NoError(t, parent.Add(pipe1))
|
require.NoError(t, parent.Add(pipe1))
|
||||||
require.NoError(t, parent.Add(pipe2))
|
require.NoError(t, parent.Add(pipe2))
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
@@ -70,9 +72,11 @@ func TestPoll_WakeForShutdown_WakesFriends(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoll_Close_Idempotent(t *testing.T) {
|
func TestTunFile_Close_Idempotent(t *testing.T) {
|
||||||
tf, err := newPoll(newReadPipe(t), 1)
|
tf, err := newOffload(newReadPipe(t), 1)
|
||||||
require.NoError(t, err)
|
if err != nil {
|
||||||
|
t.Fatalf("newOffload: %v", err)
|
||||||
|
}
|
||||||
if err := tf.Close(); err != nil {
|
if err := tf.Close(); err != nil {
|
||||||
t.Fatalf("first Close: %v", err)
|
t.Fatalf("first Close: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package tio
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -48,7 +47,10 @@ const (
|
|||||||
// tcpFinPshMask is cleared on every segment except the last of a TSO burst.
|
// tcpFinPshMask is cleared on every segment except the last of a TSO burst.
|
||||||
const tcpFinPshMask = 0x09 // FIN(0x01) | PSH(0x08)
|
const tcpFinPshMask = 0x09 // FIN(0x01) | PSH(0x08)
|
||||||
|
|
||||||
func checkVirtioValid(pkt []byte, hdr VirtioNetHdr) error {
|
// segmentInto splits a TUN-side packet described by hdr into one or more
|
||||||
|
// IP packets, each appended to *out as a slice of scratch. scratch must be
|
||||||
|
// sized to hold every segment (including replicated headers).
|
||||||
|
func segmentInto(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte) error {
|
||||||
// When RSC_INFO is set the csum_start/csum_offset fields are repurposed to
|
// When RSC_INFO is set the csum_start/csum_offset fields are repurposed to
|
||||||
// carry coalescing info rather than checksum offsets. A TUN writing via
|
// carry coalescing info rather than checksum offsets. A TUN writing via
|
||||||
// IFF_VNET_HDR should never emit this, but if it did we would silently
|
// IFF_VNET_HDR should never emit this, but if it did we would silently
|
||||||
@@ -56,29 +58,9 @@ func checkVirtioValid(pkt []byte, hdr VirtioNetHdr) error {
|
|||||||
if hdr.Flags&unix.VIRTIO_NET_HDR_F_RSC_INFO != 0 {
|
if hdr.Flags&unix.VIRTIO_NET_HDR_F_RSC_INFO != 0 {
|
||||||
return fmt.Errorf("virtio RSC_INFO flag not supported on TUN reads")
|
return fmt.Errorf("virtio RSC_INFO flag not supported on TUN reads")
|
||||||
}
|
}
|
||||||
if len(pkt) < ipv4HeaderMinLen {
|
|
||||||
return fmt.Errorf("packet too short")
|
|
||||||
}
|
|
||||||
ipVersion := pkt[0] >> 4
|
|
||||||
switch hdr.GSOType {
|
switch hdr.GSOType {
|
||||||
case unix.VIRTIO_NET_HDR_GSO_TCPV4:
|
case unix.VIRTIO_NET_HDR_GSO_NONE:
|
||||||
if ipVersion != 4 {
|
|
||||||
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType)
|
|
||||||
}
|
|
||||||
case unix.VIRTIO_NET_HDR_GSO_TCPV6:
|
|
||||||
if ipVersion != 6 {
|
|
||||||
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if !(ipVersion == 6 || ipVersion == 4) {
|
|
||||||
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGSONone(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte) error {
|
|
||||||
if len(pkt) > len(scratch) {
|
if len(pkt) > len(scratch) {
|
||||||
return fmt.Errorf("packet larger than segment buffer: %d > %d", len(pkt), len(scratch))
|
return fmt.Errorf("packet larger than segment buffer: %d > %d", len(pkt), len(scratch))
|
||||||
}
|
}
|
||||||
@@ -91,59 +73,7 @@ func handleGSONone(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte)
|
|||||||
}
|
}
|
||||||
*out = append(*out, seg)
|
*out = append(*out, seg)
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func correctHdrLen(pkt []byte, hdr *VirtioNetHdr) error {
|
|
||||||
// Thank you wireguard-go for documenting these edge-cases
|
|
||||||
// Don't trust hdr.hdrLen from the kernel as it can be equal to the length
|
|
||||||
// of the entire first packet when the kernel is handling it as part of a
|
|
||||||
// FORWARD path. Instead, parse the transport header length and add it onto
|
|
||||||
// csumStart, which is synonymous for IP header length.
|
|
||||||
const tcpDataOffset = 12
|
|
||||||
|
|
||||||
if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
|
||||||
hdr.HdrLen = hdr.CsumStart + 8
|
|
||||||
} else {
|
|
||||||
if len(pkt) <= int(hdr.CsumStart+tcpDataOffset) {
|
|
||||||
return errors.New("packet is too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpHLen := uint16(pkt[hdr.CsumStart+tcpDataOffset] >> 4 * 4)
|
|
||||||
if tcpHLen < 20 || tcpHLen > 60 {
|
|
||||||
// A TCP header must be between 20 and 60 bytes in length.
|
|
||||||
return fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
|
||||||
}
|
|
||||||
hdr.HdrLen = hdr.CsumStart + tcpHLen
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pkt) < int(hdr.HdrLen) {
|
|
||||||
return fmt.Errorf("length of packet (%d) < virtioNetHdr.HdrLen (%d)", len(pkt), hdr.HdrLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hdr.HdrLen < hdr.CsumStart {
|
|
||||||
return fmt.Errorf("virtioNetHdr.HdrLen (%d) < virtioNetHdr.CsumStart (%d)", hdr.HdrLen, hdr.CsumStart)
|
|
||||||
}
|
|
||||||
cSumAt := int(hdr.CsumStart + hdr.CsumStart)
|
|
||||||
if cSumAt+1 >= len(pkt) {
|
|
||||||
return fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(pkt))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// segmentInto splits a TUN-side packet described by hdr into one or more
|
|
||||||
// IP packets, each appended to *out as a slice of scratch. scratch must be
|
|
||||||
// sized to hold every segment (including replicated headers).
|
|
||||||
func segmentInto(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte) error {
|
|
||||||
if err := checkVirtioValid(pkt, hdr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_NONE {
|
|
||||||
return handleGSONone(pkt, hdr, out, scratch)
|
|
||||||
}
|
|
||||||
if err := correctHdrLen(pkt, &hdr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch hdr.GSOType {
|
|
||||||
case unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV6:
|
case unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV6:
|
||||||
return segmentTCP(pkt, hdr, out, scratch)
|
return segmentTCP(pkt, hdr, out, scratch)
|
||||||
|
|
||||||
@@ -188,15 +118,35 @@ func segmentTCP(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
isV4 := hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_TCPV4
|
isV4 := hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_TCPV4
|
||||||
headerLen := int(hdr.HdrLen) // already corrected by the caller
|
|
||||||
csumStart := int(hdr.CsumStart)
|
csumStart := int(hdr.CsumStart)
|
||||||
|
|
||||||
|
if isV4 && csumStart < ipv4HeaderMinLen {
|
||||||
|
return fmt.Errorf("csum_start %d too small for IPv4", csumStart)
|
||||||
|
}
|
||||||
|
if !isV4 && csumStart < ipv6FixedLen {
|
||||||
|
return fmt.Errorf("csum_start %d too small for IPv6", csumStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't trust hdr.HdrLen from the kernel: on some paths it can be set
|
||||||
|
// to the full length of the first packet rather than the true L3+L4 header length.
|
||||||
|
// Instead, read the TCP data-offset field from the packet itself and derive
|
||||||
|
// headerLen = csum_start + tcpHdrLen. Matches wireguard-go's approach.
|
||||||
|
if csumStart+tcpFlagsOff+1 > len(pkt) {
|
||||||
|
return fmt.Errorf("packet too short for tcp header at csum_start=%d (pkt %d)", csumStart, len(pkt))
|
||||||
|
}
|
||||||
tcpHdrLen := int(pkt[csumStart+tcpDataOffOff]>>4) * 4
|
tcpHdrLen := int(pkt[csumStart+tcpDataOffOff]>>4) * 4
|
||||||
|
if tcpHdrLen < tcpHeaderMinLen || tcpHdrLen > tcpHeaderMaxLen {
|
||||||
|
return fmt.Errorf("tcp data-offset out of range: %d", tcpHdrLen)
|
||||||
|
}
|
||||||
|
headerLen := csumStart + tcpHdrLen
|
||||||
|
if headerLen > len(pkt) {
|
||||||
|
return fmt.Errorf("derived hdr_len %d > pkt %d", headerLen, len(pkt))
|
||||||
|
}
|
||||||
|
|
||||||
payload := pkt[headerLen:]
|
payload := pkt[headerLen:]
|
||||||
payLen := len(payload)
|
payLen := len(payload)
|
||||||
gsoSize := int(hdr.GSOSize)
|
gso := int(hdr.GSOSize)
|
||||||
numSeg := (payLen + gsoSize - 1) / gsoSize
|
numSeg := (payLen + gso - 1) / gso
|
||||||
if numSeg == 0 {
|
if numSeg == 0 {
|
||||||
numSeg = 1
|
numSeg = 1
|
||||||
}
|
}
|
||||||
@@ -247,8 +197,8 @@ func segmentTCP(pkt []byte, hdr VirtioNetHdr, out *[][]byte, scratch []byte) err
|
|||||||
|
|
||||||
off := 0
|
off := 0
|
||||||
for i := 0; i < numSeg; i++ {
|
for i := 0; i < numSeg; i++ {
|
||||||
segStart := i * gsoSize
|
segStart := i * gso
|
||||||
segEnd := segStart + gsoSize
|
segEnd := segStart + gso
|
||||||
if segEnd > payLen {
|
if segEnd > payLen {
|
||||||
segEnd = payLen
|
segEnd = payLen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,6 +310,8 @@ func TestTunFileWriteVnetHdrNoAlloc(t *testing.T) {
|
|||||||
t.Cleanup(func() { _ = unix.Close(fd) })
|
t.Cleanup(func() { _ = unix.Close(fd) })
|
||||||
|
|
||||||
tf := &Offload{fd: fd}
|
tf := &Offload{fd: fd}
|
||||||
|
tf.writeIovs[0].Base = &validVnetHdr[0]
|
||||||
|
tf.writeIovs[0].SetLen(virtioNetHdrLen)
|
||||||
|
|
||||||
payload := make([]byte, 1400)
|
payload := make([]byte, 1400)
|
||||||
// Warm up (first call may trigger one-time internal allocations elsewhere).
|
// Warm up (first call may trigger one-time internal allocations elsewhere).
|
||||||
|
|||||||
+4
-4
@@ -2,10 +2,10 @@ package overlay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/util"
|
"github.com/slackhq/nebula/util"
|
||||||
)
|
)
|
||||||
@@ -22,9 +22,9 @@ func (e *NameError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We may be able to remove routines
|
// TODO: We may be able to remove routines
|
||||||
type DeviceFactory func(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
||||||
|
|
||||||
func NewDeviceFromConfig(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
func NewDeviceFromConfig(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
||||||
switch {
|
switch {
|
||||||
case c.GetBool("tun.disabled", false):
|
case c.GetBool("tun.disabled", false):
|
||||||
tun := newDisabledTun(vpnNetworks, c.GetInt("tun.tx_queue", 500), c.GetBool("stats.message_metrics", false), l)
|
tun := newDisabledTun(vpnNetworks, c.GetInt("tun.tx_queue", 500), c.GetBool("stats.message_metrics", false), l)
|
||||||
@@ -36,7 +36,7 @@ func NewDeviceFromConfig(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewFdDeviceFromConfig(fd *int) DeviceFactory {
|
func NewFdDeviceFromConfig(fd *int) DeviceFactory {
|
||||||
return func(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
return func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
||||||
return newTunFromFd(c, l, *fd, vpnNetworks)
|
return newTunFromFd(c, l, *fd, vpnNetworks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-12
@@ -6,12 +6,12 @@ package overlay
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -24,13 +24,16 @@ type tun struct {
|
|||||||
vpnNetworks []netip.Prefix
|
vpnNetworks []netip.Prefix
|
||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
readBuf []byte
|
readBuf []byte
|
||||||
batchRet [1][]byte
|
batchRet [1][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.rwc.Read(t.readBuf)
|
n, err := t.rwc.Read(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -43,11 +46,15 @@ func (t *tun) Write(p []byte) (int, error) {
|
|||||||
return t.rwc.Write(p)
|
return t.rwc.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.rwc.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tun) Close() error {
|
func (t *tun) Close() error {
|
||||||
return t.rwc.Close()
|
return t.rwc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||||
// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.
|
// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.
|
||||||
// Be sure not to call file.Fd() as it will set the fd to blocking mode.
|
// Be sure not to call file.Fd() as it will set the fd to blocking mode.
|
||||||
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
||||||
@@ -57,7 +64,6 @@ func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip
|
|||||||
fd: deviceFd,
|
fd: deviceFd,
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
l: l,
|
l: l,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := t.reload(c, true)
|
err := t.reload(c, true)
|
||||||
@@ -75,7 +81,7 @@ func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(_ *config.C, _ *slog.Logger, _ []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ bool) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTun not supported in Android")
|
return nil, fmt.Errorf("newTun not supported in Android")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +90,7 @@ func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Activate() error {
|
func (t tun) Activate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,10 +127,6 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for android")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for android")
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-15
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -31,7 +31,7 @@ type tun struct {
|
|||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
linkAddr *netroute.LinkAddr
|
linkAddr *netroute.LinkAddr
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
||||||
out []byte
|
out []byte
|
||||||
@@ -83,7 +83,7 @@ type ifreqAlias6 struct {
|
|||||||
Lifetime addrLifetime
|
Lifetime addrLifetime
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||||
name := c.GetString("tun.dev", "")
|
name := c.GetString("tun.dev", "")
|
||||||
ifIndex := -1
|
ifIndex := -1
|
||||||
if name != "" && name != "utun" {
|
if name != "" && name != "utun" {
|
||||||
@@ -133,7 +133,6 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
DefaultMTU: c.GetInt("tun.mtu", DefaultMTU),
|
DefaultMTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
l: l,
|
l: l,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.reload(c, true)
|
err = t.reload(c, true)
|
||||||
@@ -158,7 +157,7 @@ func (t *tun) deviceBytes() (o [16]byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported in Darwin")
|
return nil, fmt.Errorf("newTunFromFd not supported in Darwin")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +393,8 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
err := addRoute(r.Cidr, t.linkAddr)
|
err := addRoute(r.Cidr, t.linkAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, unix.EEXIST) {
|
if errors.Is(err, unix.EEXIST) {
|
||||||
t.l.Warn("unable to add unsafe_route, identical route already exists", "route", r.Cidr)
|
t.l.WithField("route", r.Cidr).
|
||||||
|
Warnf("unable to add unsafe_route, identical route already exists")
|
||||||
} else {
|
} else {
|
||||||
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
||||||
if logErrors {
|
if logErrors {
|
||||||
@@ -404,7 +404,7 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,9 +419,9 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||||||
|
|
||||||
err := delRoute(r.Cidr, t.linkAddr)
|
err := delRoute(r.Cidr, t.linkAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -517,6 +517,9 @@ func (t *tun) readOne(to []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.readOne(t.readBuf)
|
n, err := t.readOne(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -525,6 +528,10 @@ func (t *tun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
// Write is only valid for single threaded use
|
// Write is only valid for single threaded use
|
||||||
func (t *tun) Write(from []byte) (int, error) {
|
func (t *tun) Write(from []byte) (int, error) {
|
||||||
buf := t.out
|
buf := t.out
|
||||||
@@ -566,10 +573,6 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for darwin")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for darwin")
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-11
@@ -1,14 +1,13 @@
|
|||||||
package overlay
|
package overlay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/iputil"
|
"github.com/slackhq/nebula/iputil"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -21,7 +20,7 @@ type disabledTun struct {
|
|||||||
// Track these metrics since we don't have the tun device to do it for us
|
// Track these metrics since we don't have the tun device to do it for us
|
||||||
tx metrics.Counter
|
tx metrics.Counter
|
||||||
rx metrics.Counter
|
rx metrics.Counter
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
numReaders int
|
numReaders int
|
||||||
|
|
||||||
batchRet [1][]byte
|
batchRet [1][]byte
|
||||||
@@ -34,15 +33,15 @@ func (t *disabledTun) Read() ([][]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.tx.Inc(1)
|
t.tx.Inc(1)
|
||||||
if t.l.Enabled(context.Background(), slog.LevelDebug) {
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.Debug("Write payload", "raw", prettyPacket(r))
|
t.l.WithField("raw", prettyPacket(r)).Debugf("Write payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.batchRet[0] = r
|
t.batchRet[0] = r
|
||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *slog.Logger) *disabledTun {
|
func newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {
|
||||||
tun := &disabledTun{
|
tun := &disabledTun{
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
read: make(chan []byte, queueLen),
|
read: make(chan []byte, queueLen),
|
||||||
@@ -88,7 +87,7 @@ func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
|
|||||||
select {
|
select {
|
||||||
case t.read <- out:
|
case t.read <- out:
|
||||||
default:
|
default:
|
||||||
t.l.Debug("tun_disabled: dropped ICMP Echo Reply response")
|
t.l.Debugf("tun_disabled: dropped ICMP Echo Reply response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -99,15 +98,19 @@ func (t *disabledTun) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
// Check for ICMP Echo Request before spending time doing the full parsing
|
// Check for ICMP Echo Request before spending time doing the full parsing
|
||||||
if t.handleICMPEchoRequest(b) {
|
if t.handleICMPEchoRequest(b) {
|
||||||
if t.l.Enabled(context.Background(), slog.LevelDebug) {
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.Debug("Disabled tun responded to ICMP Echo Request", "raw", prettyPacket(b))
|
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
|
||||||
}
|
}
|
||||||
} else if t.l.Enabled(context.Background(), slog.LevelDebug) {
|
} else if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.Debug("Disabled tun received unexpected payload", "raw", prettyPacket(b))
|
t.l.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *disabledTun) WriteReject(b []byte) (int, error) {
|
||||||
|
return t.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *disabledTun) SupportsMultiqueue() bool {
|
func (t *disabledTun) SupportsMultiqueue() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-17
@@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -17,7 +16,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -94,7 +93,7 @@ type tun struct {
|
|||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
linkAddr *netroute.LinkAddr
|
linkAddr *netroute.LinkAddr
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
fd int
|
fd int
|
||||||
shutdownR int // read end of the shutdown pipe; closing the write end wakes blocked polls
|
shutdownR int // read end of the shutdown pipe; closing the write end wakes blocked polls
|
||||||
@@ -160,6 +159,9 @@ func (t *tun) blockOnWrite() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.readOne(t.readBuf)
|
n, err := t.readOne(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -168,6 +170,10 @@ func (t *tun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tun) readOne(to []byte) (int, error) {
|
func (t *tun) readOne(to []byte) (int, error) {
|
||||||
// first 4 bytes is protocol family, in network byte order
|
// first 4 bytes is protocol family, in network byte order
|
||||||
var head [4]byte
|
var head [4]byte
|
||||||
@@ -256,7 +262,7 @@ func (t *tun) Close() error {
|
|||||||
|
|
||||||
if t.fd >= 0 {
|
if t.fd >= 0 {
|
||||||
if err := unix.Close(t.fd); err != nil {
|
if err := unix.Close(t.fd); err != nil {
|
||||||
t.l.Error("Error closing device", "error", err)
|
t.l.WithError(err).Error("Error closing device")
|
||||||
}
|
}
|
||||||
t.fd = -1
|
t.fd = -1
|
||||||
}
|
}
|
||||||
@@ -277,7 +283,7 @@ func (t *tun) Close() error {
|
|||||||
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
|
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Error destroying tunnel", "error", err)
|
t.l.WithError(err).Error("Error destroying tunnel")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -290,11 +296,11 @@ func (t *tun) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD")
|
return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||||
// Try to open existing tun device
|
// Try to open existing tun device
|
||||||
var fd int
|
var fd int
|
||||||
var err error
|
var err error
|
||||||
@@ -386,7 +392,6 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
l: l,
|
l: l,
|
||||||
fd: fd,
|
fd: fd,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
shutdownR: shutdownR,
|
shutdownR: shutdownR,
|
||||||
shutdownW: shutdownW,
|
shutdownW: shutdownW,
|
||||||
readPoll: [2]unix.PollFd{
|
readPoll: [2]unix.PollFd{
|
||||||
@@ -577,8 +582,8 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for freebsd")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) addRoutes(logErrors bool) error {
|
func (t *tun) addRoutes(logErrors bool) error {
|
||||||
@@ -598,17 +603,13 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tun) removeRoutes(routes []Route) error {
|
func (t *tun) removeRoutes(routes []Route) error {
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
if !r.Install {
|
if !r.Install {
|
||||||
@@ -617,9 +618,9 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||||||
|
|
||||||
err := delRoute(r.Cidr, t.linkAddr)
|
err := delRoute(r.Cidr, t.linkAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+13
-11
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -26,13 +26,16 @@ type tun struct {
|
|||||||
vpnNetworks []netip.Prefix
|
vpnNetworks []netip.Prefix
|
||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
readBuf []byte
|
readBuf []byte
|
||||||
batchRet [1][]byte
|
batchRet [1][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.rwc.Read(t.readBuf)
|
n, err := t.rwc.Read(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -45,21 +48,24 @@ func (t *tun) Write(p []byte) (int, error) {
|
|||||||
return t.rwc.Write(p)
|
return t.rwc.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.rwc.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *tun) Close() error {
|
func (t *tun) Close() error {
|
||||||
return t.rwc.Close()
|
return t.rwc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(_ *config.C, _ *slog.Logger, _ []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ bool) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTun not supported in iOS")
|
return nil, fmt.Errorf("newTun not supported in iOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||||
file := os.NewFile(uintptr(deviceFd), "/dev/tun")
|
file := os.NewFile(uintptr(deviceFd), "/dev/tun")
|
||||||
t := &tun{
|
t := &tun{
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
rwc: &tunReadCloser{f: file},
|
rwc: &tunReadCloser{f: file},
|
||||||
l: l,
|
l: l,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := t.reload(c, true)
|
err := t.reload(c, true)
|
||||||
@@ -177,10 +183,6 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for ios")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for ios")
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-35
@@ -5,7 +5,6 @@ package overlay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,6 +15,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -47,7 +47,7 @@ type tun struct {
|
|||||||
routesFromSystem map[netip.Prefix]routing.Gateways
|
routesFromSystem map[netip.Prefix]routing.Gateways
|
||||||
routesFromSystemLock sync.Mutex
|
routesFromSystemLock sync.Mutex
|
||||||
|
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Networks() []netip.Prefix {
|
func (t *tun) Networks() []netip.Prefix {
|
||||||
@@ -72,7 +72,7 @@ type ifreqQLEN struct {
|
|||||||
pad [8]byte
|
pad [8]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||||
// We don't know what flags the caller opened this fd with and can't turn
|
// We don't know what flags the caller opened this fd with and can't turn
|
||||||
// on IFF_VNET_HDR after TUNSETIFF, so skip offload on inherited fds.
|
// on IFF_VNET_HDR after TUNSETIFF, so skip offload on inherited fds.
|
||||||
t, err := newTunGeneric(c, l, deviceFd, false, vpnNetworks)
|
t, err := newTunGeneric(c, l, deviceFd, false, vpnNetworks)
|
||||||
@@ -124,7 +124,7 @@ func tunSetIff(fd int, name string, flags uint16) (string, error) {
|
|||||||
// TSO-capable TUN is available. CSUM is required as a prerequisite for TSO.
|
// TSO-capable TUN is available. CSUM is required as a prerequisite for TSO.
|
||||||
const tsoOffloadFlags = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
const tsoOffloadFlags = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
||||||
baseFlags := uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
baseFlags := uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
||||||
if multiqueue {
|
if multiqueue {
|
||||||
baseFlags |= unix.IFF_MULTI_QUEUE
|
baseFlags |= unix.IFF_MULTI_QUEUE
|
||||||
@@ -144,7 +144,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue
|
|||||||
_ = unix.Close(fd)
|
_ = unix.Close(fd)
|
||||||
vnetHdr = false
|
vnetHdr = false
|
||||||
} else if err = ioctl(uintptr(fd), unix.TUNSETOFFLOAD, uintptr(tsoOffloadFlags)); err != nil {
|
} else if err = ioctl(uintptr(fd), unix.TUNSETOFFLOAD, uintptr(tsoOffloadFlags)); err != nil {
|
||||||
l.Warn("Failed to enable TUN offload (TSO); proceeding without virtio headers", "error", err)
|
l.WithError(err).Warn("Failed to enable TUN offload (TSO); proceeding without virtio headers")
|
||||||
_ = unix.Close(fd)
|
_ = unix.Close(fd)
|
||||||
vnetHdr = false
|
vnetHdr = false
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newTunGeneric does all the stuff common to different tun initialization paths. It will close your files on error.
|
// newTunGeneric does all the stuff common to different tun initialization paths. It will close your files on error.
|
||||||
func newTunGeneric(c *config.C, l *slog.Logger, fd int, vnetHdr bool, vpnNetworks []netip.Prefix) (*tun, error) {
|
func newTunGeneric(c *config.C, l *logrus.Logger, fd int, vnetHdr bool, vpnNetworks []netip.Prefix) (*tun, error) {
|
||||||
var container tio.Container
|
var container tio.Container
|
||||||
var err error
|
var err error
|
||||||
if vnetHdr {
|
if vnetHdr {
|
||||||
@@ -264,16 +264,16 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|||||||
if !initial {
|
if !initial {
|
||||||
if oldMaxMTU != newMaxMTU {
|
if oldMaxMTU != newMaxMTU {
|
||||||
t.setMTU()
|
t.setMTU()
|
||||||
t.l.Info("Set max MTU", "mtu", t.MaxMTU, "oldMTU", oldMaxMTU)
|
t.l.Infof("Set max MTU to %v was %v", t.MaxMTU, oldMaxMTU)
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldDefaultMTU != newDefaultMTU {
|
if oldDefaultMTU != newDefaultMTU {
|
||||||
for i := range t.vpnNetworks {
|
for i := range t.vpnNetworks {
|
||||||
err := t.setDefaultRoute(t.vpnNetworks[i])
|
err := t.setDefaultRoute(t.vpnNetworks[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Warn(err.Error())
|
t.l.Warn(err)
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Set default MTU", "mtu", t.DefaultMTU, "oldMTU", oldDefaultMTU)
|
t.l.Infof("Set default MTU to %v was %v", t.DefaultMTU, oldDefaultMTU)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,9 +384,9 @@ func (t *tun) addIPs(link netlink.Link) error {
|
|||||||
}
|
}
|
||||||
err = netlink.AddrDel(link, &al[i])
|
err = netlink.AddrDel(link, &al[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("failed to remove address from tun address list", "error", err)
|
t.l.WithError(err).Error("failed to remove address from tun address list")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("removed address not listed in cert(s)", "removed", al[i].String())
|
t.l.WithField("removed", al[i].String()).Info("removed address not listed in cert(s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,12 +430,12 @@ func (t *tun) Activate() error {
|
|||||||
ifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}
|
ifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}
|
||||||
if err = ioctl(t.ioctlFd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {
|
if err = ioctl(t.ioctlFd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {
|
||||||
// If we can't set the queue length nebula will still work but it may lead to packet loss
|
// If we can't set the queue length nebula will still work but it may lead to packet loss
|
||||||
t.l.Error("Failed to set tun tx queue length", "error", err)
|
t.l.WithError(err).Error("Failed to set tun tx queue length")
|
||||||
}
|
}
|
||||||
|
|
||||||
const modeNone = 1
|
const modeNone = 1
|
||||||
if err = netlink.LinkSetIP6AddrGenMode(link, modeNone); err != nil {
|
if err = netlink.LinkSetIP6AddrGenMode(link, modeNone); err != nil {
|
||||||
t.l.Warn("Failed to disable link local address generation", "error", err)
|
t.l.WithError(err).Warn("Failed to disable link local address generation")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = t.addIPs(link); err != nil {
|
if err = t.addIPs(link); err != nil {
|
||||||
@@ -474,7 +474,7 @@ func (t *tun) setMTU() {
|
|||||||
ifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MaxMTU)}
|
ifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MaxMTU)}
|
||||||
if err := ioctl(t.ioctlFd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {
|
if err := ioctl(t.ioctlFd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {
|
||||||
// This is currently a non fatal condition because the route table must have the MTU set appropriately as well
|
// This is currently a non fatal condition because the route table must have the MTU set appropriately as well
|
||||||
t.l.Error("Failed to set tun mtu", "error", err)
|
t.l.WithError(err).Error("Failed to set tun mtu")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +497,7 @@ func (t *tun) setDefaultRoute(cidr netip.Prefix) error {
|
|||||||
}
|
}
|
||||||
err := netlink.RouteReplace(&nr)
|
err := netlink.RouteReplace(&nr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Warn("Failed to set default route MTU, retrying", "error", err, "cidr", cidr)
|
t.l.WithError(err).WithField("cidr", cidr).Warn("Failed to set default route MTU, retrying")
|
||||||
//retry twice more -- on some systems there appears to be a race condition where if we set routes too soon, netlink says `invalid argument`
|
//retry twice more -- on some systems there appears to be a race condition where if we set routes too soon, netlink says `invalid argument`
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
@@ -505,11 +505,7 @@ func (t *tun) setDefaultRoute(cidr netip.Prefix) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
t.l.Warn("Failed to set default route MTU, retrying",
|
t.l.WithError(err).WithField("cidr", cidr).WithField("mtu", t.DefaultMTU).Warn("Failed to set default route MTU, retrying")
|
||||||
"error", err,
|
|
||||||
"cidr", cidr,
|
|
||||||
"mtu", t.DefaultMTU,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -554,7 +550,7 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,9 +582,9 @@ func (t *tun) removeRoutes(routes []Route) {
|
|||||||
|
|
||||||
err := netlink.RouteDel(&nr)
|
err := netlink.RouteDel(&nr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,11 +613,11 @@ func (t *tun) watchRoutes() {
|
|||||||
netlinkOptions := netlink.RouteSubscribeOptions{
|
netlinkOptions := netlink.RouteSubscribeOptions{
|
||||||
ReceiveBufferSize: t.useSystemRoutesBufferSize,
|
ReceiveBufferSize: t.useSystemRoutesBufferSize,
|
||||||
ReceiveBufferForceSize: t.useSystemRoutesBufferSize != 0,
|
ReceiveBufferForceSize: t.useSystemRoutesBufferSize != 0,
|
||||||
ErrorCallback: func(e error) { t.l.Error("netlink error", "error", e) },
|
ErrorCallback: func(e error) { t.l.WithError(e).Errorf("netlink error") },
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := netlink.RouteSubscribeWithOptions(rch, doneChan, netlinkOptions); err != nil {
|
if err := netlink.RouteSubscribeWithOptions(rch, doneChan, netlinkOptions); err != nil {
|
||||||
t.l.Error("failed to subscribe to system route changes", "error", err)
|
t.l.WithError(err).Errorf("failed to subscribe to system route changes")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,7 +659,7 @@ func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
|||||||
|
|
||||||
link, err := netlink.LinkByName(t.Device)
|
link, err := netlink.LinkByName(t.Device)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Ignoring route update: failed to get link by name", "deviceName", t.Device)
|
t.l.WithField("deviceName", t.Device).Error("Ignoring route update: failed to get link by name")
|
||||||
return gateways
|
return gateways
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,10 +671,10 @@ func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
|||||||
gateways = append(gateways, routing.NewGateway(gwAddr, 1))
|
gateways = append(gateways, routing.NewGateway(gwAddr, 1))
|
||||||
} else {
|
} else {
|
||||||
// Gateway isn't in our overlay network, ignore
|
// Gateway isn't in our overlay network, ignore
|
||||||
t.l.Debug("Ignoring route update, gateway is not in our network", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, gateway is not in our network")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Debug("Ignoring route update, invalid gateway or via address", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway or via address")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,10 +687,10 @@ func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
|||||||
gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
|
gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
|
||||||
} else {
|
} else {
|
||||||
// Gateway isn't in our overlay network, ignore
|
// Gateway isn't in our overlay network, ignore
|
||||||
t.l.Debug("Ignoring route update, gateway is not in our network", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, gateway is not in our network")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Debug("Ignoring route update, invalid gateway or via address", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway or via address")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,18 +722,18 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|||||||
gateways := t.getGatewaysFromRoute(&r.Route)
|
gateways := t.getGatewaysFromRoute(&r.Route)
|
||||||
if len(gateways) == 0 {
|
if len(gateways) == 0 {
|
||||||
// No gateways relevant to our network, no routing changes required.
|
// No gateways relevant to our network, no routing changes required.
|
||||||
t.l.Debug("Ignoring route update, no gateways", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, no gateways")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Dst == nil {
|
if r.Dst == nil {
|
||||||
t.l.Debug("Ignoring route update, no destination address", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, no destination address")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dstAddr, ok := netip.AddrFromSlice(r.Dst.IP)
|
dstAddr, ok := netip.AddrFromSlice(r.Dst.IP)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.l.Debug("Ignoring route update, invalid destination address", "route", r)
|
t.l.WithField("route", r).Debug("Ignoring route update, invalid destination address")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,12 +744,12 @@ func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|||||||
|
|
||||||
t.routesFromSystemLock.Lock()
|
t.routesFromSystemLock.Lock()
|
||||||
if r.Type == unix.RTM_NEWROUTE {
|
if r.Type == unix.RTM_NEWROUTE {
|
||||||
t.l.Info("Adding route", "destination", dst, "via", gateways)
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
||||||
t.routesFromSystem[dst] = gateways
|
t.routesFromSystem[dst] = gateways
|
||||||
newTree.Insert(dst, gateways)
|
newTree.Insert(dst, gateways)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removing route", "destination", dst, "via", gateways)
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
||||||
delete(t.routesFromSystem, dst)
|
delete(t.routesFromSystem, dst)
|
||||||
newTree.Delete(dst)
|
newTree.Delete(dst)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -6,7 +6,6 @@ package overlay
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -63,7 +63,7 @@ type tun struct {
|
|||||||
MTU int
|
MTU int
|
||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
f *os.File
|
f *os.File
|
||||||
fd int
|
fd int
|
||||||
|
|
||||||
@@ -72,6 +72,9 @@ type tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.readOne(t.readBuf)
|
n, err := t.readOne(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -80,17 +83,17 @@ func (t *tun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
return []tio.Queue{t}
|
return t.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported in NetBSD")
|
return nil, fmt.Errorf("newTunFromFd not supported in NetBSD")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||||
// Try to open tun device
|
// Try to open tun device
|
||||||
var err error
|
var err error
|
||||||
deviceName := c.GetString("tun.dev", "")
|
deviceName := c.GetString("tun.dev", "")
|
||||||
@@ -108,7 +111,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
|
|
||||||
err = unix.SetNonblock(fd, true)
|
err = unix.SetNonblock(fd, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("Failed to set the tun device as nonblocking", "error", err)
|
l.WithError(err).Warn("Failed to set the tun device as nonblocking")
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
@@ -118,7 +121,6 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
l: l,
|
l: l,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.reload(c, true)
|
err = t.reload(c, true)
|
||||||
@@ -411,8 +413,8 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for netbsd")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for netbsd")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) addRoutes(logErrors bool) error {
|
func (t *tun) addRoutes(logErrors bool) error {
|
||||||
@@ -433,7 +435,7 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,9 +450,9 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||||||
|
|
||||||
err := delRoute(r.Cidr, t.vpnNetworks)
|
err := delRoute(r.Cidr, t.vpnNetworks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+17
-15
@@ -6,7 +6,6 @@ package overlay
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -15,6 +14,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -54,7 +54,7 @@ type tun struct {
|
|||||||
MTU int
|
MTU int
|
||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
f *os.File
|
f *os.File
|
||||||
fd int
|
fd int
|
||||||
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
||||||
@@ -65,6 +65,9 @@ type tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Read() ([][]byte, error) {
|
func (t *tun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.readOne(t.readBuf)
|
n, err := t.readOne(t.readBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -73,13 +76,17 @@ func (t *tun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported in openbsd")
|
return nil, fmt.Errorf("newTunFromFd not supported in openbsd")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
|
||||||
// Try to open tun device
|
// Try to open tun device
|
||||||
var err error
|
var err error
|
||||||
deviceName := c.GetString("tun.dev", "")
|
deviceName := c.GetString("tun.dev", "")
|
||||||
@@ -97,7 +104,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
|
|
||||||
err = unix.SetNonblock(fd, true)
|
err = unix.SetNonblock(fd, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("Failed to set the tun device as nonblocking", "error", err)
|
l.WithError(err).Warn("Failed to set the tun device as nonblocking")
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
@@ -107,7 +114,6 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
l: l,
|
l: l,
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.reload(c, true)
|
err = t.reload(c, true)
|
||||||
@@ -327,8 +333,8 @@ func (t *tun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) NewMultiQueueReader() error {
|
func (t *tun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for openbsd")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for openbsd")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) addRoutes(logErrors bool) error {
|
func (t *tun) addRoutes(logErrors bool) error {
|
||||||
@@ -349,7 +355,7 @@ func (t *tun) addRoutes(logErrors bool) error {
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,9 +370,9 @@ func (t *tun) removeRoutes(routes []Route) error {
|
|||||||
|
|
||||||
err := delRoute(r.Cidr, t.vpnNetworks)
|
err := delRoute(r.Cidr, t.vpnNetworks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -379,10 +385,6 @@ func (t *tun) deviceBytes() (o [16]byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
func addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
|
||||||
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+12
-13
@@ -4,15 +4,14 @@
|
|||||||
package overlay
|
package overlay
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -23,7 +22,7 @@ type TestTun struct {
|
|||||||
vpnNetworks []netip.Prefix
|
vpnNetworks []netip.Prefix
|
||||||
Routes []Route
|
Routes []Route
|
||||||
routeTree *bart.Table[routing.Gateways]
|
routeTree *bart.Table[routing.Gateways]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
rxPackets chan []byte // Packets to receive into nebula
|
rxPackets chan []byte // Packets to receive into nebula
|
||||||
@@ -41,7 +40,7 @@ func (t *TestTun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*TestTun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*TestTun, error) {
|
||||||
_, routes, err := getAllRoutesFromConfig(c, vpnNetworks, true)
|
_, routes, err := getAllRoutesFromConfig(c, vpnNetworks, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -62,7 +61,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*T
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*TestTun, error) {
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*TestTun, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported")
|
return nil, fmt.Errorf("newTunFromFd not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +73,8 @@ func (t *TestTun) Send(packet []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.l.Enabled(context.Background(), slog.LevelDebug) {
|
if t.l.Level >= logrus.DebugLevel {
|
||||||
t.l.Debug("Tun receiving injected packet", "dataLen", len(packet))
|
t.l.WithField("dataLen", len(packet)).Debug("Tun receiving injected packet")
|
||||||
}
|
}
|
||||||
t.rxPackets <- packet
|
t.rxPackets <- packet
|
||||||
}
|
}
|
||||||
@@ -128,6 +127,10 @@ func (t *TestTun) Write(b []byte) (n int, err error) {
|
|||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TestTun) WriteReject(b []byte) (int, error) {
|
||||||
|
return t.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TestTun) Close() error {
|
func (t *TestTun) Close() error {
|
||||||
if t.closed.CompareAndSwap(false, true) {
|
if t.closed.CompareAndSwap(false, true) {
|
||||||
close(t.rxPackets)
|
close(t.rxPackets)
|
||||||
@@ -136,14 +139,10 @@ func (t *TestTun) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestTun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TestTun) SupportsMultiqueue() bool {
|
func (t *TestTun) SupportsMultiqueue() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestTun) NewMultiQueueReader() error {
|
func (t *TestTun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented")
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-15
@@ -6,7 +6,6 @@ package overlay
|
|||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -16,6 +15,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
@@ -33,7 +33,7 @@ type winTun struct {
|
|||||||
MTU int
|
MTU int
|
||||||
Routes atomic.Pointer[[]Route]
|
Routes atomic.Pointer[[]Route]
|
||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
tun *wintun.NativeTun
|
tun *wintun.NativeTun
|
||||||
|
|
||||||
@@ -42,6 +42,9 @@ type winTun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *winTun) Read() ([][]byte, error) {
|
func (t *winTun) Read() ([][]byte, error) {
|
||||||
|
if t.readBuf == nil {
|
||||||
|
t.readBuf = make([]byte, defaultBatchBufSize)
|
||||||
|
}
|
||||||
n, err := t.tun.Read(t.readBuf, 0)
|
n, err := t.tun.Read(t.readBuf, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -50,11 +53,15 @@ func (t *winTun) Read() ([][]byte, error) {
|
|||||||
return t.batchRet[:], nil
|
return t.batchRet[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (Device, error) {
|
func (t *winTun) WriteReject(p []byte) (int, error) {
|
||||||
|
return t.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (Device, error) {
|
||||||
return nil, fmt.Errorf("newTunFromFd not supported in Windows")
|
return nil, fmt.Errorf("newTunFromFd not supported in Windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*winTun, error) {
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*winTun, error) {
|
||||||
err := checkWinTunExists()
|
err := checkWinTunExists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can not load the wintun driver: %w", err)
|
return nil, fmt.Errorf("can not load the wintun driver: %w", err)
|
||||||
@@ -67,7 +74,6 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*w
|
|||||||
}
|
}
|
||||||
|
|
||||||
t := &winTun{
|
t := &winTun{
|
||||||
readBuf: make([]byte, defaultBatchBufSize),
|
|
||||||
Device: deviceName,
|
Device: deviceName,
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
@@ -84,7 +90,7 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*w
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Windows 10 has an issue with unclean shutdowns not fully cleaning up the wintun device.
|
// Windows 10 has an issue with unclean shutdowns not fully cleaning up the wintun device.
|
||||||
// Trying a second time resolves the issue.
|
// Trying a second time resolves the issue.
|
||||||
l.Debug("Failed to create wintun device, retrying", "error", err)
|
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
||||||
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &NameError{
|
return nil, &NameError{
|
||||||
@@ -183,7 +189,7 @@ func (t *winTun) addRoutes(logErrors bool) error {
|
|||||||
return retErr
|
return retErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Added route", "route", r)
|
t.l.WithField("route", r).Info("Added route")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundDefault4 {
|
if !foundDefault4 {
|
||||||
@@ -221,9 +227,9 @@ func (t *winTun) removeRoutes(routes []Route) error {
|
|||||||
// See comment on luid.AddRoute
|
// See comment on luid.AddRoute
|
||||||
err := luid.DeleteRoute(r.Cidr, r.Via[0].Addr())
|
err := luid.DeleteRoute(r.Cidr, r.Via[0].Addr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.l.Error("Failed to remove route", "error", err, "route", r)
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
||||||
} else {
|
} else {
|
||||||
t.l.Info("Removed route", "route", r)
|
t.l.WithField("route", r).Info("Removed route")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -250,12 +256,8 @@ func (t *winTun) SupportsMultiqueue() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *winTun) NewMultiQueueReader() error {
|
func (t *winTun) NewMultiQueueReader() (tio.Queue, error) {
|
||||||
return fmt.Errorf("TODO: multiqueue not implemented for windows")
|
return nil, fmt.Errorf("TODO: multiqueue not implemented for windows")
|
||||||
}
|
|
||||||
|
|
||||||
func (t *winTun) Readers() []tio.Queue {
|
|
||||||
return []tio.Queue{t}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *winTun) Close() error {
|
func (t *winTun) Close() error {
|
||||||
|
|||||||
+5
-3
@@ -2,15 +2,15 @@ package overlay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/overlay/tio"
|
"github.com/slackhq/nebula/overlay/tio"
|
||||||
"github.com/slackhq/nebula/routing"
|
"github.com/slackhq/nebula/routing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUserDeviceFromConfig(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
func NewUserDeviceFromConfig(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error) {
|
||||||
return NewUserDevice(vpnNetworks)
|
return NewUserDevice(vpnNetworks)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,9 @@ func (d *UserDevice) Pipe() (*io.PipeReader, *io.PipeWriter) {
|
|||||||
func (d *UserDevice) Write(p []byte) (n int, err error) {
|
func (d *UserDevice) Write(p []byte) (n int, err error) {
|
||||||
return d.inboundWriter.Write(p)
|
return d.inboundWriter.Write(p)
|
||||||
}
|
}
|
||||||
|
func (d *UserDevice) WriteReject(p []byte) (n int, err error) {
|
||||||
|
return d.Write(p)
|
||||||
|
}
|
||||||
func (d *UserDevice) Close() error {
|
func (d *UserDevice) Close() error {
|
||||||
d.inboundWriter.Close()
|
d.inboundWriter.Close()
|
||||||
d.outboundWriter.Close()
|
d.outboundWriter.Close()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gaissmai/bart"
|
"github.com/gaissmai/bart"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/util"
|
"github.com/slackhq/nebula/util"
|
||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
type PKI struct {
|
type PKI struct {
|
||||||
cs atomic.Pointer[CertState]
|
cs atomic.Pointer[CertState]
|
||||||
caPool atomic.Pointer[cert.CAPool]
|
caPool atomic.Pointer[cert.CAPool]
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertState struct {
|
type CertState struct {
|
||||||
@@ -46,7 +46,7 @@ type CertState struct {
|
|||||||
myVpnBroadcastAddrsTable *bart.Lite
|
myVpnBroadcastAddrsTable *bart.Lite
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPKIFromConfig(l *slog.Logger, c *config.C) (*PKI, error) {
|
func NewPKIFromConfig(l *logrus.Logger, c *config.C) (*PKI, error) {
|
||||||
pki := &PKI{l: l}
|
pki := &PKI{l: l}
|
||||||
err := pki.reload(c, true)
|
err := pki.reload(c, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -182,9 +182,9 @@ func (p *PKI) reloadCerts(c *config.C, initial bool) *util.ContextualError {
|
|||||||
p.cs.Store(newState)
|
p.cs.Store(newState)
|
||||||
|
|
||||||
if initial {
|
if initial {
|
||||||
p.l.Debug("Client nebula certificate(s)", "cert", newState)
|
p.l.WithField("cert", newState).Debug("Client nebula certificate(s)")
|
||||||
} else {
|
} else {
|
||||||
p.l.Info("Client certificate(s) refreshed from disk", "cert", newState)
|
p.l.WithField("cert", newState).Info("Client certificate(s) refreshed from disk")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func (p *PKI) reloadCAPool(c *config.C) *util.ContextualError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.caPool.Store(caPool)
|
p.caPool.Store(caPool)
|
||||||
p.l.Debug("Trusted CA fingerprints", "fingerprints", caPool.GetFingerprints())
|
p.l.WithField("fingerprints", caPool.GetFingerprints()).Debug("Trusted CA fingerprints")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ func loadCertificate(b []byte) (cert.Certificate, []byte, error) {
|
|||||||
return c, b, nil
|
return c, b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {
|
func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.CAPool, error) {
|
||||||
caPathOrPEM := c.GetString("pki.ca", "")
|
caPathOrPEM := c.GetString("pki.ca", "")
|
||||||
if caPathOrPEM == "" {
|
if caPathOrPEM == "" {
|
||||||
return nil, errors.New("no pki.ca path or PEM data provided")
|
return nil, errors.New("no pki.ca path or PEM data provided")
|
||||||
@@ -512,7 +512,7 @@ func loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {
|
|||||||
for _, crt := range caPool.CAs {
|
for _, crt := range caPool.CAs {
|
||||||
if crt.Certificate.Expired(time.Now()) {
|
if crt.Certificate.Expired(time.Now()) {
|
||||||
expired++
|
expired++
|
||||||
l.Warn("expired certificate present in CA pool", "cert", crt)
|
l.WithField("cert", crt).Warn("expired certificate present in CA pool")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +530,7 @@ func loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {
|
|||||||
caPool.BlocklistFingerprint(fp)
|
caPool.BlocklistFingerprint(fp)
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Info("Blocklisted certificates", "fingerprintCount", len(bl))
|
l.WithField("fingerprintCount", len(bl)).Info("Blocklisted certificates")
|
||||||
}
|
}
|
||||||
|
|
||||||
return caPool, nil
|
return caPool, nil
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func BenchmarkReloadConfigWithCAs(b *testing.B) {
|
|||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
require.NoError(b, c.Load(dir))
|
require.NoError(b, c.Load(dir))
|
||||||
|
|
||||||
_, err := NewPKIFromConfig(test.NewLogger(), c)
|
_, err := NewPKIFromConfig(l, c)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,10 +14,10 @@ type Punchy struct {
|
|||||||
delay atomic.Int64
|
delay atomic.Int64
|
||||||
respondDelay atomic.Int64
|
respondDelay atomic.Int64
|
||||||
punchEverything atomic.Bool
|
punchEverything atomic.Bool
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPunchyFromConfig(l *slog.Logger, c *config.C) *Punchy {
|
func NewPunchyFromConfig(l *logrus.Logger, c *config.C) *Punchy {
|
||||||
p := &Punchy{l: l}
|
p := &Punchy{l: l}
|
||||||
|
|
||||||
p.reload(c, true)
|
p.reload(c, true)
|
||||||
@@ -62,7 +62,7 @@ func (p *Punchy) reload(c *config.C, initial bool) {
|
|||||||
p.respond.Store(yes)
|
p.respond.Store(yes)
|
||||||
|
|
||||||
if !initial {
|
if !initial {
|
||||||
p.l.Info("punchy.respond changed", "respond", p.GetRespond())
|
p.l.Infof("punchy.respond changed to %v", p.GetRespond())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,21 +70,21 @@ func (p *Punchy) reload(c *config.C, initial bool) {
|
|||||||
if initial || c.HasChanged("punchy.delay") {
|
if initial || c.HasChanged("punchy.delay") {
|
||||||
p.delay.Store((int64)(c.GetDuration("punchy.delay", time.Second)))
|
p.delay.Store((int64)(c.GetDuration("punchy.delay", time.Second)))
|
||||||
if !initial {
|
if !initial {
|
||||||
p.l.Info("punchy.delay changed", "delay", p.GetDelay())
|
p.l.Infof("punchy.delay changed to %s", p.GetDelay())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if initial || c.HasChanged("punchy.target_all_remotes") {
|
if initial || c.HasChanged("punchy.target_all_remotes") {
|
||||||
p.punchEverything.Store(c.GetBool("punchy.target_all_remotes", false))
|
p.punchEverything.Store(c.GetBool("punchy.target_all_remotes", false))
|
||||||
if !initial {
|
if !initial {
|
||||||
p.l.Info("punchy.target_all_remotes changed", "target_all_remotes", p.GetTargetEverything())
|
p.l.WithField("target_all_remotes", p.GetTargetEverything()).Info("punchy.target_all_remotes changed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if initial || c.HasChanged("punchy.respond_delay") {
|
if initial || c.HasChanged("punchy.respond_delay") {
|
||||||
p.respondDelay.Store((int64)(c.GetDuration("punchy.respond_delay", 5*time.Second)))
|
p.respondDelay.Store((int64)(c.GetDuration("punchy.respond_delay", 5*time.Second)))
|
||||||
if !initial {
|
if !initial {
|
||||||
p.l.Info("punchy.respond_delay changed", "respond_delay", p.GetRespondDelay())
|
p.l.Infof("punchy.respond_delay changed to %s", p.GetRespondDelay())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-165
@@ -1,8 +1,6 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -17,7 +15,7 @@ func TestNewPunchyFromConfig(t *testing.T) {
|
|||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
|
|
||||||
// Test defaults
|
// Test defaults
|
||||||
p := NewPunchyFromConfig(test.NewLogger(), c)
|
p := NewPunchyFromConfig(l, c)
|
||||||
assert.False(t, p.GetPunch())
|
assert.False(t, p.GetPunch())
|
||||||
assert.False(t, p.GetRespond())
|
assert.False(t, p.GetRespond())
|
||||||
assert.Equal(t, time.Second, p.GetDelay())
|
assert.Equal(t, time.Second, p.GetDelay())
|
||||||
@@ -25,33 +23,33 @@ func TestNewPunchyFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
// punchy deprecation
|
// punchy deprecation
|
||||||
c.Settings["punchy"] = true
|
c.Settings["punchy"] = true
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.True(t, p.GetPunch())
|
assert.True(t, p.GetPunch())
|
||||||
|
|
||||||
// punchy.punch
|
// punchy.punch
|
||||||
c.Settings["punchy"] = map[string]any{"punch": true}
|
c.Settings["punchy"] = map[string]any{"punch": true}
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.True(t, p.GetPunch())
|
assert.True(t, p.GetPunch())
|
||||||
|
|
||||||
// punch_back deprecation
|
// punch_back deprecation
|
||||||
c.Settings["punch_back"] = true
|
c.Settings["punch_back"] = true
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.True(t, p.GetRespond())
|
assert.True(t, p.GetRespond())
|
||||||
|
|
||||||
// punchy.respond
|
// punchy.respond
|
||||||
c.Settings["punchy"] = map[string]any{"respond": true}
|
c.Settings["punchy"] = map[string]any{"respond": true}
|
||||||
c.Settings["punch_back"] = false
|
c.Settings["punch_back"] = false
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.True(t, p.GetRespond())
|
assert.True(t, p.GetRespond())
|
||||||
|
|
||||||
// punchy.delay
|
// punchy.delay
|
||||||
c.Settings["punchy"] = map[string]any{"delay": "1m"}
|
c.Settings["punchy"] = map[string]any{"delay": "1m"}
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.Equal(t, time.Minute, p.GetDelay())
|
assert.Equal(t, time.Minute, p.GetDelay())
|
||||||
|
|
||||||
// punchy.respond_delay
|
// punchy.respond_delay
|
||||||
c.Settings["punchy"] = map[string]any{"respond_delay": "1m"}
|
c.Settings["punchy"] = map[string]any{"respond_delay": "1m"}
|
||||||
p = NewPunchyFromConfig(test.NewLogger(), c)
|
p = NewPunchyFromConfig(l, c)
|
||||||
assert.Equal(t, time.Minute, p.GetRespondDelay())
|
assert.Equal(t, time.Minute, p.GetRespondDelay())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ punchy:
|
|||||||
delay: 1m
|
delay: 1m
|
||||||
respond: false
|
respond: false
|
||||||
`))
|
`))
|
||||||
p := NewPunchyFromConfig(test.NewLogger(), c)
|
p := NewPunchyFromConfig(l, c)
|
||||||
assert.Equal(t, delay, p.GetDelay())
|
assert.Equal(t, delay, p.GetDelay())
|
||||||
assert.False(t, p.GetRespond())
|
assert.False(t, p.GetRespond())
|
||||||
|
|
||||||
@@ -78,158 +76,3 @@ punchy:
|
|||||||
assert.Equal(t, newDelay, p.GetDelay())
|
assert.Equal(t, newDelay, p.GetDelay())
|
||||||
assert.True(t, p.GetRespond())
|
assert.True(t, p.GetRespond())
|
||||||
}
|
}
|
||||||
|
|
||||||
// The tests below pin the shape of each log line Punchy produces so changes
|
|
||||||
// cannot silently break whatever operators are grepping for. The assertions
|
|
||||||
// are on the structured message + attrs (e.g. "punchy.respond changed" with
|
|
||||||
// a respond=true field) rather than a formatted string.
|
|
||||||
//
|
|
||||||
// Punchy.reload also emits a spurious "Changing punchy.punch with reload is
|
|
||||||
// not supported" warning whenever any key under punchy changes, because of
|
|
||||||
// the c.HasChanged("punchy") fallback kept for the deprecated top-level
|
|
||||||
// punchy form. The tests filter by message rather than asserting total
|
|
||||||
// entry counts so that warning is tolerated without being locked into
|
|
||||||
// the format.
|
|
||||||
|
|
||||||
type capturedEntry struct {
|
|
||||||
Level slog.Level
|
|
||||||
Msg string
|
|
||||||
Attrs map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
// capturingHandler is a slog.Handler that records each Record it receives so
|
|
||||||
// tests can assert on the level, message, and attribute map of individual log
|
|
||||||
// lines without coupling to any specific text format.
|
|
||||||
type capturingHandler struct {
|
|
||||||
entries []capturedEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *capturingHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
|
|
||||||
|
|
||||||
func (h *capturingHandler) Handle(_ context.Context, r slog.Record) error {
|
|
||||||
e := capturedEntry{
|
|
||||||
Level: r.Level,
|
|
||||||
Msg: r.Message,
|
|
||||||
Attrs: make(map[string]any),
|
|
||||||
}
|
|
||||||
r.Attrs(func(a slog.Attr) bool {
|
|
||||||
e.Attrs[a.Key] = a.Value.Resolve().Any()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
h.entries = append(h.entries, e)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *capturingHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h }
|
|
||||||
func (h *capturingHandler) WithGroup(_ string) slog.Handler { return h }
|
|
||||||
|
|
||||||
func newCapturingPunchyLogger(t *testing.T) (*slog.Logger, *capturingHandler) {
|
|
||||||
t.Helper()
|
|
||||||
hook := &capturingHandler{}
|
|
||||||
return slog.New(hook), hook
|
|
||||||
}
|
|
||||||
|
|
||||||
func findEntry(t *testing.T, entries []capturedEntry, msg string) capturedEntry {
|
|
||||||
t.Helper()
|
|
||||||
for _, e := range entries {
|
|
||||||
if e.Msg == msg {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Fatalf("no entry with message %q among %d entries", msg, len(entries))
|
|
||||||
return capturedEntry{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_InitialEnabled(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {punch: true}`))
|
|
||||||
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy enabled")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Empty(t, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_InitialDisabled(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {punch: false}`))
|
|
||||||
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy disabled")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Empty(t, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_ReloadPunchUnsupported(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {punch: false}`))
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
hook.entries = nil
|
|
||||||
|
|
||||||
require.NoError(t, c.ReloadConfigString(`punchy: {punch: true}`))
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "Changing punchy.punch with reload is not supported, ignoring.")
|
|
||||||
assert.Equal(t, slog.LevelWarn, entry.Level)
|
|
||||||
assert.Empty(t, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_ReloadRespond(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {respond: false}`))
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
hook.entries = nil
|
|
||||||
|
|
||||||
require.NoError(t, c.ReloadConfigString(`punchy: {respond: true}`))
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy.respond changed")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Equal(t, map[string]any{"respond": true}, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_ReloadDelay(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {delay: 1s}`))
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
hook.entries = nil
|
|
||||||
|
|
||||||
require.NoError(t, c.ReloadConfigString(`punchy: {delay: 10s}`))
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy.delay changed")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Equal(t, map[string]any{"delay": 10 * time.Second}, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_ReloadTargetAllRemotes(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {target_all_remotes: false}`))
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
hook.entries = nil
|
|
||||||
|
|
||||||
require.NoError(t, c.ReloadConfigString(`punchy: {target_all_remotes: true}`))
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy.target_all_remotes changed")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Equal(t, map[string]any{"target_all_remotes": true}, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPunchy_LogFormat_ReloadRespondDelay(t *testing.T) {
|
|
||||||
l, hook := newCapturingPunchyLogger(t)
|
|
||||||
c := config.NewC(test.NewLogger())
|
|
||||||
require.NoError(t, c.LoadString(`punchy: {respond_delay: 5s}`))
|
|
||||||
NewPunchyFromConfig(l, c)
|
|
||||||
hook.entries = nil
|
|
||||||
|
|
||||||
require.NoError(t, c.ReloadConfigString(`punchy: {respond_delay: 15s}`))
|
|
||||||
|
|
||||||
entry := findEntry(t, hook.entries, "punchy.respond_delay changed")
|
|
||||||
assert.Equal(t, slog.LevelInfo, entry.Level)
|
|
||||||
assert.Equal(t, map[string]any{"respond_delay": 15 * time.Second}, entry.Attrs)
|
|
||||||
}
|
|
||||||
|
|||||||
+88
-87
@@ -5,22 +5,22 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
type relayManager struct {
|
type relayManager struct {
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
hostmap *HostMap
|
hostmap *HostMap
|
||||||
amRelay atomic.Bool
|
amRelay atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRelayManager(ctx context.Context, l *slog.Logger, hostmap *HostMap, c *config.C) *relayManager {
|
func NewRelayManager(ctx context.Context, l *logrus.Logger, hostmap *HostMap, c *config.C) *relayManager {
|
||||||
rm := &relayManager{
|
rm := &relayManager{
|
||||||
l: l,
|
l: l,
|
||||||
hostmap: hostmap,
|
hostmap: hostmap,
|
||||||
@@ -29,7 +29,7 @@ func NewRelayManager(ctx context.Context, l *slog.Logger, hostmap *HostMap, c *c
|
|||||||
c.RegisterReloadCallback(func(c *config.C) {
|
c.RegisterReloadCallback(func(c *config.C) {
|
||||||
err := rm.reload(c, false)
|
err := rm.reload(c, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rm.l.Error("Failed to reload relay_manager", "error", err)
|
l.WithError(err).Error("Failed to reload relay_manager")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return rm
|
return rm
|
||||||
@@ -52,7 +52,7 @@ func (rm *relayManager) setAmRelay(v bool) {
|
|||||||
|
|
||||||
// AddRelay finds an available relay index on the hostmap, and associates the relay info with it.
|
// AddRelay finds an available relay index on the hostmap, and associates the relay info with it.
|
||||||
// relayHostInfo is the Nebula peer which can be used as a relay to access the target vpnIp.
|
// relayHostInfo is the Nebula peer which can be used as a relay to access the target vpnIp.
|
||||||
func AddRelay(l *slog.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
func AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
for range 32 {
|
for range 32 {
|
||||||
@@ -92,24 +92,24 @@ func AddRelay(l *slog.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.
|
|||||||
func (rm *relayManager) EstablishRelay(relayHostInfo *HostInfo, m *NebulaControl) (*Relay, error) {
|
func (rm *relayManager) EstablishRelay(relayHostInfo *HostInfo, m *NebulaControl) (*Relay, error) {
|
||||||
relay, ok := relayHostInfo.relayState.CompleteRelayByIdx(m.InitiatorRelayIndex, m.ResponderRelayIndex)
|
relay, ok := relayHostInfo.relayState.CompleteRelayByIdx(m.InitiatorRelayIndex, m.ResponderRelayIndex)
|
||||||
if !ok {
|
if !ok {
|
||||||
var relayFrom, relayTo any
|
fields := logrus.Fields{
|
||||||
if m.RelayFromAddr == nil {
|
"relay": relayHostInfo.vpnAddrs[0],
|
||||||
relayFrom = m.OldRelayFromAddr
|
"initiatorRelayIndex": m.InitiatorRelayIndex,
|
||||||
} else {
|
|
||||||
relayFrom = m.RelayFromAddr
|
|
||||||
}
|
|
||||||
if m.RelayToAddr == nil {
|
|
||||||
relayTo = m.OldRelayToAddr
|
|
||||||
} else {
|
|
||||||
relayTo = m.RelayToAddr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rm.l.Info("relayManager failed to update relay",
|
if m.RelayFromAddr == nil {
|
||||||
"relay", relayHostInfo.vpnAddrs[0],
|
fields["relayFrom"] = m.OldRelayFromAddr
|
||||||
"initiatorRelayIndex", m.InitiatorRelayIndex,
|
} else {
|
||||||
"relayFrom", relayFrom,
|
fields["relayFrom"] = m.RelayFromAddr
|
||||||
"relayTo", relayTo,
|
}
|
||||||
)
|
|
||||||
|
if m.RelayToAddr == nil {
|
||||||
|
fields["relayTo"] = m.OldRelayToAddr
|
||||||
|
} else {
|
||||||
|
fields["relayTo"] = m.RelayToAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
rm.l.WithFields(fields).Info("relayManager failed to update relay")
|
||||||
return nil, fmt.Errorf("unknown relay")
|
return nil, fmt.Errorf("unknown relay")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ func (rm *relayManager) HandleControlMsg(h *HostInfo, d []byte, f *Interface) {
|
|||||||
msg := &NebulaControl{}
|
msg := &NebulaControl{}
|
||||||
err := msg.Unmarshal(d)
|
err := msg.Unmarshal(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger(f.l).Error("Failed to unmarshal control message", "error", err)
|
h.logger(f.l).WithError(err).Error("Failed to unmarshal control message")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,20 +147,20 @@ func (rm *relayManager) HandleControlMsg(h *HostInfo, d []byte, f *Interface) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f *Interface, m *NebulaControl) {
|
func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f *Interface, m *NebulaControl) {
|
||||||
rm.l.Info("handleCreateRelayResponse",
|
rm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", protoAddrToNetAddr(m.RelayFromAddr),
|
"relayFrom": protoAddrToNetAddr(m.RelayFromAddr),
|
||||||
"relayTo", protoAddrToNetAddr(m.RelayToAddr),
|
"relayTo": protoAddrToNetAddr(m.RelayToAddr),
|
||||||
"initiatorRelayIndex", m.InitiatorRelayIndex,
|
"initiatorRelayIndex": m.InitiatorRelayIndex,
|
||||||
"responderRelayIndex", m.ResponderRelayIndex,
|
"responderRelayIndex": m.ResponderRelayIndex,
|
||||||
"vpnAddrs", h.vpnAddrs,
|
"vpnAddrs": h.vpnAddrs}).
|
||||||
)
|
Info("handleCreateRelayResponse")
|
||||||
|
|
||||||
target := m.RelayToAddr
|
target := m.RelayToAddr
|
||||||
targetAddr := protoAddrToNetAddr(target)
|
targetAddr := protoAddrToNetAddr(target)
|
||||||
|
|
||||||
relay, err := rm.EstablishRelay(h, m)
|
relay, err := rm.EstablishRelay(h, m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rm.l.Error("Failed to update relay for relayTo", "error", err)
|
rm.l.WithError(err).Error("Failed to update relay for relayTo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Do I need to complete the relays now?
|
// Do I need to complete the relays now?
|
||||||
@@ -170,12 +170,12 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f
|
|||||||
// I'm the middle man. Let the initiator know that the I've established the relay they requested.
|
// I'm the middle man. Let the initiator know that the I've established the relay they requested.
|
||||||
peerHostInfo := rm.hostmap.QueryVpnAddr(relay.PeerAddr)
|
peerHostInfo := rm.hostmap.QueryVpnAddr(relay.PeerAddr)
|
||||||
if peerHostInfo == nil {
|
if peerHostInfo == nil {
|
||||||
rm.l.Error("Can't find a HostInfo for peer", "relayTo", relay.PeerAddr)
|
rm.l.WithField("relayTo", relay.PeerAddr).Error("Can't find a HostInfo for peer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peerRelay, ok := peerHostInfo.relayState.QueryRelayForByIp(targetAddr)
|
peerRelay, ok := peerHostInfo.relayState.QueryRelayForByIp(targetAddr)
|
||||||
if !ok {
|
if !ok {
|
||||||
rm.l.Error("peerRelay does not have Relay state for relayTo", "relayTo", peerHostInfo.vpnAddrs[0])
|
rm.l.WithField("relayTo", peerHostInfo.vpnAddrs[0]).Error("peerRelay does not have Relay state for relayTo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch peerRelay.State {
|
switch peerRelay.State {
|
||||||
@@ -193,13 +193,12 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f
|
|||||||
if v == cert.Version1 {
|
if v == cert.Version1 {
|
||||||
peer := peerHostInfo.vpnAddrs[0]
|
peer := peerHostInfo.vpnAddrs[0]
|
||||||
if !peer.Is4() {
|
if !peer.Is4() {
|
||||||
rm.l.Error("Refusing to CreateRelayResponse for a v1 relay with an ipv6 address",
|
rm.l.WithField("relayFrom", peer).
|
||||||
"relayFrom", peer,
|
WithField("relayTo", target).
|
||||||
"relayTo", target,
|
WithField("initiatorRelayIndex", resp.InitiatorRelayIndex).
|
||||||
"initiatorRelayIndex", resp.InitiatorRelayIndex,
|
WithField("responderRelayIndex", resp.ResponderRelayIndex).
|
||||||
"responderRelayIndex", resp.ResponderRelayIndex,
|
WithField("vpnAddrs", peerHostInfo.vpnAddrs).
|
||||||
"vpnAddrs", peerHostInfo.vpnAddrs,
|
Error("Refusing to CreateRelayResponse for a v1 relay with an ipv6 address")
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,16 +213,17 @@ func (rm *relayManager) handleCreateRelayResponse(v cert.Version, h *HostInfo, f
|
|||||||
|
|
||||||
msg, err := resp.Marshal()
|
msg, err := resp.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rm.l.Error("relayManager Failed to marshal Control CreateRelayResponse message to create relay", "error", err)
|
rm.l.WithError(err).
|
||||||
|
Error("relayManager Failed to marshal Control CreateRelayResponse message to create relay")
|
||||||
} else {
|
} else {
|
||||||
f.SendMessageToHostInfo(header.Control, 0, peerHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
f.SendMessageToHostInfo(header.Control, 0, peerHostInfo, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
rm.l.Info("send CreateRelayResponse",
|
rm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", resp.RelayFromAddr,
|
"relayFrom": resp.RelayFromAddr,
|
||||||
"relayTo", resp.RelayToAddr,
|
"relayTo": resp.RelayToAddr,
|
||||||
"initiatorRelayIndex", resp.InitiatorRelayIndex,
|
"initiatorRelayIndex": resp.InitiatorRelayIndex,
|
||||||
"responderRelayIndex", resp.ResponderRelayIndex,
|
"responderRelayIndex": resp.ResponderRelayIndex,
|
||||||
"vpnAddrs", peerHostInfo.vpnAddrs,
|
"vpnAddrs": peerHostInfo.vpnAddrs}).
|
||||||
)
|
Info("send CreateRelayResponse")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,18 +232,17 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
from := protoAddrToNetAddr(m.RelayFromAddr)
|
from := protoAddrToNetAddr(m.RelayFromAddr)
|
||||||
target := protoAddrToNetAddr(m.RelayToAddr)
|
target := protoAddrToNetAddr(m.RelayToAddr)
|
||||||
|
|
||||||
logMsg := rm.l.With(
|
logMsg := rm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", from,
|
"relayFrom": from,
|
||||||
"relayTo", target,
|
"relayTo": target,
|
||||||
"initiatorRelayIndex", m.InitiatorRelayIndex,
|
"initiatorRelayIndex": m.InitiatorRelayIndex,
|
||||||
"vpnAddrs", h.vpnAddrs,
|
"vpnAddrs": h.vpnAddrs})
|
||||||
)
|
|
||||||
|
|
||||||
logMsg.Info("handleCreateRelayRequest")
|
logMsg.Info("handleCreateRelayRequest")
|
||||||
// Is the source of the relay me? This should never happen, but did happen due to
|
// Is the source of the relay me? This should never happen, but did happen due to
|
||||||
// an issue migrating relays over to newly re-handshaked host info objects.
|
// an issue migrating relays over to newly re-handshaked host info objects.
|
||||||
if f.myVpnAddrsTable.Contains(from) {
|
if f.myVpnAddrsTable.Contains(from) {
|
||||||
logMsg.Error("Discarding relay request from myself", "myIP", from)
|
logMsg.WithField("myIP", from).Error("Discarding relay request from myself")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,37 +261,37 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
if existingRelay.RemoteIndex != m.InitiatorRelayIndex {
|
if existingRelay.RemoteIndex != m.InitiatorRelayIndex {
|
||||||
// We got a brand new Relay request, because its index is different than what we saw before.
|
// We got a brand new Relay request, because its index is different than what we saw before.
|
||||||
// This should never happen. The peer should never change an index, once created.
|
// This should never happen. The peer should never change an index, once created.
|
||||||
logMsg.Error("Existing relay mismatch with CreateRelayRequest",
|
logMsg.WithFields(logrus.Fields{
|
||||||
"existingRemoteIndex", existingRelay.RemoteIndex)
|
"existingRemoteIndex": existingRelay.RemoteIndex}).Error("Existing relay mismatch with CreateRelayRequest")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case Disestablished:
|
case Disestablished:
|
||||||
if existingRelay.RemoteIndex != m.InitiatorRelayIndex {
|
if existingRelay.RemoteIndex != m.InitiatorRelayIndex {
|
||||||
// We got a brand new Relay request, because its index is different than what we saw before.
|
// We got a brand new Relay request, because its index is different than what we saw before.
|
||||||
// This should never happen. The peer should never change an index, once created.
|
// This should never happen. The peer should never change an index, once created.
|
||||||
logMsg.Error("Existing relay mismatch with CreateRelayRequest",
|
logMsg.WithFields(logrus.Fields{
|
||||||
"existingRemoteIndex", existingRelay.RemoteIndex)
|
"existingRemoteIndex": existingRelay.RemoteIndex}).Error("Existing relay mismatch with CreateRelayRequest")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Mark the relay as 'Established' because it's safe to use again
|
// Mark the relay as 'Established' because it's safe to use again
|
||||||
h.relayState.UpdateRelayForByIpState(from, Established)
|
h.relayState.UpdateRelayForByIpState(from, Established)
|
||||||
case PeerRequested:
|
case PeerRequested:
|
||||||
// I should never be in this state, because I am terminal, not forwarding.
|
// I should never be in this state, because I am terminal, not forwarding.
|
||||||
logMsg.Error("Unexpected Relay State found",
|
logMsg.WithFields(logrus.Fields{
|
||||||
"existingRemoteIndex", existingRelay.RemoteIndex,
|
"existingRemoteIndex": existingRelay.RemoteIndex,
|
||||||
"state", existingRelay.State)
|
"state": existingRelay.State}).Error("Unexpected Relay State found")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := AddRelay(rm.l, h, f.hostMap, from, &m.InitiatorRelayIndex, TerminalType, Established)
|
_, err := AddRelay(rm.l, h, f.hostMap, from, &m.InitiatorRelayIndex, TerminalType, Established)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logMsg.Error("Failed to add relay", "error", err)
|
logMsg.WithError(err).Error("Failed to add relay")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
relay, ok := h.relayState.QueryRelayForByIp(from)
|
relay, ok := h.relayState.QueryRelayForByIp(from)
|
||||||
if !ok {
|
if !ok {
|
||||||
logMsg.Error("Relay State not found", "from", from)
|
logMsg.WithField("from", from).Error("Relay State not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,16 +313,17 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
|
|
||||||
msg, err := resp.Marshal()
|
msg, err := resp.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logMsg.Error("relayManager Failed to marshal Control CreateRelayResponse message to create relay", "error", err)
|
logMsg.
|
||||||
|
WithError(err).Error("relayManager Failed to marshal Control CreateRelayResponse message to create relay")
|
||||||
} else {
|
} else {
|
||||||
f.SendMessageToHostInfo(header.Control, 0, h, msg, make([]byte, 12), make([]byte, mtu))
|
f.SendMessageToHostInfo(header.Control, 0, h, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
rm.l.Info("send CreateRelayResponse",
|
rm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", from,
|
"relayFrom": from,
|
||||||
"relayTo", target,
|
"relayTo": target,
|
||||||
"initiatorRelayIndex", resp.InitiatorRelayIndex,
|
"initiatorRelayIndex": resp.InitiatorRelayIndex,
|
||||||
"responderRelayIndex", resp.ResponderRelayIndex,
|
"responderRelayIndex": resp.ResponderRelayIndex,
|
||||||
"vpnAddrs", h.vpnAddrs,
|
"vpnAddrs": h.vpnAddrs}).
|
||||||
)
|
Info("send CreateRelayResponse")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -363,13 +363,12 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
|
|
||||||
if v == cert.Version1 {
|
if v == cert.Version1 {
|
||||||
if !h.vpnAddrs[0].Is4() {
|
if !h.vpnAddrs[0].Is4() {
|
||||||
rm.l.Error("Refusing to CreateRelayRequest for a v1 relay with an ipv6 address",
|
rm.l.WithField("relayFrom", h.vpnAddrs[0]).
|
||||||
"relayFrom", h.vpnAddrs[0],
|
WithField("relayTo", target).
|
||||||
"relayTo", target,
|
WithField("initiatorRelayIndex", req.InitiatorRelayIndex).
|
||||||
"initiatorRelayIndex", req.InitiatorRelayIndex,
|
WithField("responderRelayIndex", req.ResponderRelayIndex).
|
||||||
"responderRelayIndex", req.ResponderRelayIndex,
|
WithField("vpnAddr", target).
|
||||||
"vpnAddr", target,
|
Error("Refusing to CreateRelayRequest for a v1 relay with an ipv6 address")
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,16 +383,17 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
|
|
||||||
msg, err := req.Marshal()
|
msg, err := req.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logMsg.Error("relayManager Failed to marshal Control message to create relay", "error", err)
|
logMsg.
|
||||||
|
WithError(err).Error("relayManager Failed to marshal Control message to create relay")
|
||||||
} else {
|
} else {
|
||||||
f.SendMessageToHostInfo(header.Control, 0, peer, msg, make([]byte, 12), make([]byte, mtu))
|
f.SendMessageToHostInfo(header.Control, 0, peer, msg, make([]byte, 12), make([]byte, mtu))
|
||||||
rm.l.Info("send CreateRelayRequest",
|
rm.l.WithFields(logrus.Fields{
|
||||||
"relayFrom", h.vpnAddrs[0],
|
"relayFrom": h.vpnAddrs[0],
|
||||||
"relayTo", target,
|
"relayTo": target,
|
||||||
"initiatorRelayIndex", req.InitiatorRelayIndex,
|
"initiatorRelayIndex": req.InitiatorRelayIndex,
|
||||||
"responderRelayIndex", req.ResponderRelayIndex,
|
"responderRelayIndex": req.ResponderRelayIndex,
|
||||||
"vpnAddr", target,
|
"vpnAddr": target}).
|
||||||
)
|
Info("send CreateRelayRequest")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also track the half-created Relay state just received
|
// Also track the half-created Relay state just received
|
||||||
@@ -401,7 +401,8 @@ func (rm *relayManager) handleCreateRelayRequest(v cert.Version, h *HostInfo, f
|
|||||||
if !ok {
|
if !ok {
|
||||||
_, err := AddRelay(rm.l, h, f.hostMap, target, &m.InitiatorRelayIndex, ForwardingType, PeerRequested)
|
_, err := AddRelay(rm.l, h, f.hostMap, target, &m.InitiatorRelayIndex, ForwardingType, PeerRequested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logMsg.Error("relayManager Failed to allocate a local index for relay", "error", err)
|
logMsg.
|
||||||
|
WithError(err).Error("relayManager Failed to allocate a local index for relay")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-12
@@ -2,7 +2,6 @@ package nebula
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
"slices"
|
||||||
@@ -11,6 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// forEachFunc is used to benefit folks that want to do work inside the lock
|
// forEachFunc is used to benefit folks that want to do work inside the lock
|
||||||
@@ -65,11 +66,11 @@ type hostnamesResults struct {
|
|||||||
network string
|
network string
|
||||||
lookupTimeout time.Duration
|
lookupTimeout time.Duration
|
||||||
cancelFn func()
|
cancelFn func()
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
ips atomic.Pointer[map[netip.AddrPort]struct{}]
|
ips atomic.Pointer[map[netip.AddrPort]struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHostnameResults(ctx context.Context, l *slog.Logger, d time.Duration, network string, timeout time.Duration, hostPorts []string, onUpdate func()) (*hostnamesResults, error) {
|
func NewHostnameResults(ctx context.Context, l *logrus.Logger, d time.Duration, network string, timeout time.Duration, hostPorts []string, onUpdate func()) (*hostnamesResults, error) {
|
||||||
r := &hostnamesResults{
|
r := &hostnamesResults{
|
||||||
hostnames: make([]hostnamePort, len(hostPorts)),
|
hostnames: make([]hostnamePort, len(hostPorts)),
|
||||||
network: network,
|
network: network,
|
||||||
@@ -120,11 +121,7 @@ func NewHostnameResults(ctx context.Context, l *slog.Logger, d time.Duration, ne
|
|||||||
addrs, err := net.DefaultResolver.LookupNetIP(timeoutCtx, r.network, hostPort.name)
|
addrs, err := net.DefaultResolver.LookupNetIP(timeoutCtx, r.network, hostPort.name)
|
||||||
timeoutCancel()
|
timeoutCancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("DNS resolution failed for static_map host",
|
l.WithFields(logrus.Fields{"hostname": hostPort.name, "network": r.network}).WithError(err).Error("DNS resolution failed for static_map host")
|
||||||
"hostname", hostPort.name,
|
|
||||||
"network", r.network,
|
|
||||||
"error", err,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
@@ -148,10 +145,7 @@ func NewHostnameResults(ctx context.Context, l *slog.Logger, d time.Duration, ne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if different {
|
if different {
|
||||||
l.Info("DNS results changed for host list",
|
l.WithFields(logrus.Fields{"origSet": origSet, "newSet": netipAddrs}).Info("DNS results changed for host list")
|
||||||
"origSet", origSet,
|
|
||||||
"newSet", netipAddrs,
|
|
||||||
)
|
|
||||||
r.ips.Store(&netipAddrs)
|
r.ips.Store(&netipAddrs)
|
||||||
onUpdate()
|
onUpdate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula"
|
"github.com/slackhq/nebula"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/cert_test"
|
"github.com/slackhq/nebula/cert_test"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v3"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -75,7 +75,8 @@ func newSimpleService(caCrt cert.Certificate, caKey []byte, name string, udpIp n
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := logging.NewLogger(os.Stdout)
|
logger := logrus.New()
|
||||||
|
logger.Out = os.Stdout
|
||||||
|
|
||||||
control, err := nebula.Main(&c, false, "custom-app", logger, overlay.NewUserDeviceFromConfig)
|
control, err := nebula.Main(&c, false, "custom-app", logger, overlay.NewUserDeviceFromConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"maps"
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/logging"
|
|
||||||
"github.com/slackhq/nebula/sshd"
|
"github.com/slackhq/nebula/sshd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,12 +57,12 @@ type sshDeviceInfoFlags struct {
|
|||||||
Pretty bool
|
Pretty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func wireSSHReload(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) {
|
func wireSSHReload(l *logrus.Logger, ssh *sshd.SSHServer, c *config.C) {
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
c.RegisterReloadCallback(func(c *config.C) {
|
||||||
if c.GetBool("sshd.enabled", false) {
|
if c.GetBool("sshd.enabled", false) {
|
||||||
sshRun, err := configSSH(l, ssh, c)
|
sshRun, err := configSSH(l, ssh, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Error("Failed to reconfigure the sshd", "error", err)
|
l.WithError(err).Error("Failed to reconfigure the sshd")
|
||||||
ssh.Stop()
|
ssh.Stop()
|
||||||
}
|
}
|
||||||
if sshRun != nil {
|
if sshRun != nil {
|
||||||
@@ -78,7 +78,7 @@ func wireSSHReload(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) {
|
|||||||
// updates the passed-in SSHServer. On success, it returns a function
|
// updates the passed-in SSHServer. On success, it returns a function
|
||||||
// that callers may invoke to run the configured ssh server. On
|
// that callers may invoke to run the configured ssh server. On
|
||||||
// failure, it returns nil, error.
|
// failure, it returns nil, error.
|
||||||
func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error) {
|
func configSSH(l *logrus.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error) {
|
||||||
listen := c.GetString("sshd.listen", "")
|
listen := c.GetString("sshd.listen", "")
|
||||||
if listen == "" {
|
if listen == "" {
|
||||||
return nil, fmt.Errorf("sshd.listen must be provided")
|
return nil, fmt.Errorf("sshd.listen must be provided")
|
||||||
@@ -120,7 +120,7 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
for _, caAuthorizedKey := range rawCAs {
|
for _, caAuthorizedKey := range rawCAs {
|
||||||
err := ssh.AddTrustedCA(caAuthorizedKey)
|
err := ssh.AddTrustedCA(caAuthorizedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("SSH CA had an error, ignoring", "error", err, "sshCA", caAuthorizedKey)
|
l.WithError(err).WithField("sshCA", caAuthorizedKey).Warn("SSH CA had an error, ignoring")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,13 +131,13 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
for _, rk := range keys {
|
for _, rk := range keys {
|
||||||
kDef, ok := rk.(map[string]any)
|
kDef, ok := rk.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
l.Warn("Authorized user had an error, ignoring", "sshKeyConfig", rk)
|
l.WithField("sshKeyConfig", rk).Warn("Authorized user had an error, ignoring")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
user, ok := kDef["user"].(string)
|
user, ok := kDef["user"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
l.Warn("Authorized user is missing the user field", "sshKeyConfig", rk)
|
l.WithField("sshKeyConfig", rk).Warn("Authorized user is missing the user field")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,11 +146,7 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
case string:
|
case string:
|
||||||
err := ssh.AddAuthorizedKey(user, v)
|
err := ssh.AddAuthorizedKey(user, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("Failed to authorize key",
|
l.WithError(err).WithField("sshKeyConfig", rk).WithField("sshKey", v).Warn("Failed to authorize key")
|
||||||
"error", err,
|
|
||||||
"sshKeyConfig", rk,
|
|
||||||
"sshKey", v,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,25 +154,19 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
for _, subK := range v {
|
for _, subK := range v {
|
||||||
sk, ok := subK.(string)
|
sk, ok := subK.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
l.Warn("Did not understand ssh key",
|
l.WithField("sshKeyConfig", rk).WithField("sshKey", subK).Warn("Did not understand ssh key")
|
||||||
"sshKeyConfig", rk,
|
|
||||||
"sshKey", subK,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ssh.AddAuthorizedKey(user, sk)
|
err := ssh.AddAuthorizedKey(user, sk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn("Failed to authorize key",
|
l.WithError(err).WithField("sshKeyConfig", sk).Warn("Failed to authorize key")
|
||||||
"error", err,
|
|
||||||
"sshKeyConfig", sk,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
l.Warn("Authorized user is missing the keys field or was not understood", "sshKeyConfig", rk)
|
l.WithField("sshKeyConfig", rk).Warn("Authorized user is missing the keys field or was not understood")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -188,7 +178,7 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
ssh.Stop()
|
ssh.Stop()
|
||||||
runner = func() {
|
runner = func() {
|
||||||
if err := ssh.Run(listen); err != nil {
|
if err := ssh.Run(listen); err != nil {
|
||||||
l.Warn("Failed to run the SSH server", "error", err)
|
l.WithField("err", err).Warn("Failed to run the SSH server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -198,7 +188,7 @@ func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) (func(), error)
|
|||||||
return runner, nil
|
return runner, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachCommands(l *slog.Logger, c *config.C, ssh *sshd.SSHServer, f *Interface) {
|
func attachCommands(l *logrus.Logger, c *config.C, ssh *sshd.SSHServer, f *Interface) {
|
||||||
// sandboxDir defaults to a dir in temp. The intention is that end user will
|
// sandboxDir defaults to a dir in temp. The intention is that end user will
|
||||||
// create this dir as needed. Overriding this config value to "" allows
|
// create this dir as needed. Overriding this config value to "" allows
|
||||||
// writing to anywhere in the system.
|
// writing to anywhere in the system.
|
||||||
@@ -799,45 +789,36 @@ func sshGetMutexProfile(sandboxDir string, fs any, a []string, w sshd.StringWrit
|
|||||||
return w.WriteLine(fmt.Sprintf("Mutex profile created at %s", a))
|
return w.WriteLine(fmt.Sprintf("Mutex profile created at %s", a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func sshLogLevel(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
|
func sshLogLevel(l *logrus.Logger, fs any, a []string, w sshd.StringWriter) error {
|
||||||
ctrl, ok := l.Handler().(interface {
|
|
||||||
GetLevel() slog.Level
|
|
||||||
SetLevel(slog.Level)
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
return w.WriteLine("Log level is not reconfigurable on this logger")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a) == 0 {
|
if len(a) == 0 {
|
||||||
return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
|
return w.WriteLine(fmt.Sprintf("Log level is: %s", l.Level))
|
||||||
}
|
}
|
||||||
|
|
||||||
level, err := logging.ParseLevel(strings.ToLower(a[0]))
|
level, err := logrus.ParseLevel(a[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return w.WriteLine(fmt.Sprintf("Unknown log level %s. Possible log levels: trace, debug, info, warn, error", a))
|
return w.WriteLine(fmt.Sprintf("Unknown log level %s. Possible log levels: %s", a, logrus.AllLevels))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl.SetLevel(level)
|
l.SetLevel(level)
|
||||||
return w.WriteLine(fmt.Sprintf("Log level is: %s", logging.LevelName(ctrl.GetLevel())))
|
return w.WriteLine(fmt.Sprintf("Log level is: %s", l.Level))
|
||||||
}
|
|
||||||
|
|
||||||
func sshLogFormat(l *slog.Logger, fs any, a []string, w sshd.StringWriter) error {
|
|
||||||
ctrl, ok := l.Handler().(interface {
|
|
||||||
GetFormat() string
|
|
||||||
SetFormat(string) error
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
return w.WriteLine("Log format is not reconfigurable on this logger")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshLogFormat(l *logrus.Logger, fs any, a []string, w sshd.StringWriter) error {
|
||||||
if len(a) == 0 {
|
if len(a) == 0 {
|
||||||
return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
|
return w.WriteLine(fmt.Sprintf("Log format is: %s", reflect.TypeOf(l.Formatter)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctrl.SetFormat(strings.ToLower(a[0])); err != nil {
|
logFormat := strings.ToLower(a[0])
|
||||||
return err
|
switch logFormat {
|
||||||
|
case "text":
|
||||||
|
l.Formatter = &logrus.TextFormatter{}
|
||||||
|
case "json":
|
||||||
|
l.Formatter = &logrus.JSONFormatter{}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown log format `%s`. possible formats: %s", logFormat, []string{"text", "json"})
|
||||||
}
|
}
|
||||||
return w.WriteLine(fmt.Sprintf("Log format is: %s", ctrl.GetFormat()))
|
|
||||||
|
return w.WriteLine(fmt.Sprintf("Log format is: %s", reflect.TypeOf(l.Formatter)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func sshPrintCert(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
|
func sshPrintCert(ifce *Interface, fs any, a []string, w sshd.StringWriter) error {
|
||||||
|
|||||||
+39
-45
@@ -2,19 +2,19 @@ package sshd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/armon/go-radix"
|
"github.com/armon/go-radix"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHServer struct {
|
type SSHServer struct {
|
||||||
config *ssh.ServerConfig
|
config *ssh.ServerConfig
|
||||||
l *slog.Logger
|
l *logrus.Entry
|
||||||
|
|
||||||
certChecker *ssh.CertChecker
|
certChecker *ssh.CertChecker
|
||||||
|
|
||||||
@@ -27,21 +27,20 @@ type SSHServer struct {
|
|||||||
commands *radix.Tree
|
commands *radix.Tree
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
|
||||||
// Call the cancel() function to stop all active sessions
|
// Locks the conns/counter to avoid concurrent map access
|
||||||
ctx context.Context
|
connsLock sync.Mutex
|
||||||
cancel func()
|
conns map[int]*session
|
||||||
|
counter int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen
|
// NewSSHServer creates a new ssh server rigged with default commands and prepares to listen
|
||||||
func NewSSHServer(l *slog.Logger) (*SSHServer, error) {
|
func NewSSHServer(l *logrus.Entry) (*SSHServer, error) {
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
s := &SSHServer{
|
s := &SSHServer{
|
||||||
trustedKeys: make(map[string]map[string]bool),
|
trustedKeys: make(map[string]map[string]bool),
|
||||||
l: l,
|
l: l,
|
||||||
commands: radix.New(),
|
commands: radix.New(),
|
||||||
ctx: ctx,
|
conns: make(map[int]*session),
|
||||||
cancel: cancel,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := ssh.CertChecker{
|
cc := ssh.CertChecker{
|
||||||
@@ -121,7 +120,7 @@ func (s *SSHServer) AddTrustedCA(pubKey string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.trustedCAs = append(s.trustedCAs, pk)
|
s.trustedCAs = append(s.trustedCAs, pk)
|
||||||
s.l.Info("Trusted CA key", "sshKey", pubKey)
|
s.l.WithField("sshKey", pubKey).Info("Trusted CA key")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,10 +138,7 @@ func (s *SSHServer) AddAuthorizedKey(user, pubKey string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tk[string(pk.Marshal())] = true
|
tk[string(pk.Marshal())] = true
|
||||||
s.l.Info("Authorized ssh key",
|
s.l.WithField("sshKey", pubKey).WithField("sshUser", user).Info("Authorized ssh key")
|
||||||
"sshKey", pubKey,
|
|
||||||
"sshUser", user,
|
|
||||||
)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +155,7 @@ func (s *SSHServer) Run(addr string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.l.Info("SSH server is listening", "sshListener", addr)
|
s.l.WithField("sshListener", addr).Info("SSH server is listening")
|
||||||
|
|
||||||
// Run loops until there is an error
|
// Run loops until there is an error
|
||||||
s.run()
|
s.run()
|
||||||
@@ -175,20 +171,11 @@ func (s *SSHServer) run() {
|
|||||||
c, err := s.listener.Accept()
|
c, err := s.listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, net.ErrClosed) {
|
if !errors.Is(err, net.ErrClosed) {
|
||||||
s.l.Warn("Error in listener, shutting down", "error", err)
|
s.l.WithError(err).Warn("Error in listener, shutting down")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func(c net.Conn) {
|
|
||||||
// NewServerConn may block while waiting for the client to complete the handshake.
|
|
||||||
// Ensure that a bad client doesn't hurt us by checking for the parent context
|
|
||||||
// cancellation before calling NewServerConn, and forcing the socket to close when
|
|
||||||
// the context is cancelled.
|
|
||||||
sessionContext, sessionCancel := context.WithCancel(s.ctx)
|
|
||||||
go func() {
|
|
||||||
<-sessionContext.Done()
|
|
||||||
c.Close()
|
|
||||||
}()
|
|
||||||
conn, chans, reqs, err := ssh.NewServerConn(c, s.config)
|
conn, chans, reqs, err := ssh.NewServerConn(c, s.config)
|
||||||
fp := ""
|
fp := ""
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
@@ -196,33 +183,36 @@ func (s *SSHServer) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l := s.l.With(
|
l := s.l.WithError(err).WithField("remoteAddress", c.RemoteAddr())
|
||||||
"error", err,
|
|
||||||
"remoteAddress", c.RemoteAddr(),
|
|
||||||
)
|
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
l = l.With("sshUser", conn.User())
|
l = l.WithField("sshUser", conn.User())
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
if fp != "" {
|
if fp != "" {
|
||||||
l = l.With("sshFingerprint", fp)
|
l = l.WithField("sshFingerprint", fp)
|
||||||
}
|
}
|
||||||
l.Warn("failed to handshake")
|
l.Warn("failed to handshake")
|
||||||
sessionCancel()
|
continue
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l := s.l.With("sshUser", conn.User())
|
l := s.l.WithField("sshUser", conn.User())
|
||||||
l.Info("ssh user logged in",
|
l.WithField("remoteAddress", c.RemoteAddr()).WithField("sshFingerprint", fp).Info("ssh user logged in")
|
||||||
"remoteAddress", c.RemoteAddr(),
|
|
||||||
"sshFingerprint", fp,
|
|
||||||
)
|
|
||||||
|
|
||||||
NewSession(s.commands, conn, chans, sessionCancel, l.With("subsystem", "sshd.session"))
|
session := NewSession(s.commands, conn, chans, l.WithField("subsystem", "sshd.session"))
|
||||||
|
s.connsLock.Lock()
|
||||||
|
s.counter++
|
||||||
|
counter := s.counter
|
||||||
|
s.conns[counter] = session
|
||||||
|
s.connsLock.Unlock()
|
||||||
|
|
||||||
go ssh.DiscardRequests(reqs)
|
go ssh.DiscardRequests(reqs)
|
||||||
|
go func() {
|
||||||
}(c)
|
<-session.exitChan
|
||||||
|
s.l.WithField("id", counter).Debug("closing conn")
|
||||||
|
s.connsLock.Lock()
|
||||||
|
delete(s.conns, counter)
|
||||||
|
s.connsLock.Unlock()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,11 +220,15 @@ func (s *SSHServer) Stop() {
|
|||||||
// Close the listener, this will cause all session to terminate as well, see SSHServer.Run
|
// Close the listener, this will cause all session to terminate as well, see SSHServer.Run
|
||||||
if s.listener != nil {
|
if s.listener != nil {
|
||||||
if err := s.listener.Close(); err != nil {
|
if err := s.listener.Close(); err != nil {
|
||||||
s.l.Warn("Failed to close the sshd listener", "error", err)
|
s.l.WithError(err).Warn("Failed to close the sshd listener")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHServer) closeSessions() {
|
func (s *SSHServer) closeSessions() {
|
||||||
s.cancel()
|
s.connsLock.Lock()
|
||||||
|
for _, c := range s.conns {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
s.connsLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -2,30 +2,30 @@ package sshd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anmitsu/go-shlex"
|
"github.com/anmitsu/go-shlex"
|
||||||
"github.com/armon/go-radix"
|
"github.com/armon/go-radix"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
l *slog.Logger
|
l *logrus.Entry
|
||||||
c *ssh.ServerConn
|
c *ssh.ServerConn
|
||||||
term *term.Terminal
|
term *term.Terminal
|
||||||
commands *radix.Tree
|
commands *radix.Tree
|
||||||
cancel func()
|
exitChan chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, cancel func(), l *slog.Logger) *session {
|
func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, l *logrus.Entry) *session {
|
||||||
s := &session{
|
s := &session{
|
||||||
commands: radix.NewFromMap(commands.ToMap()),
|
commands: radix.NewFromMap(commands.ToMap()),
|
||||||
l: l,
|
l: l,
|
||||||
c: conn,
|
c: conn,
|
||||||
cancel: cancel,
|
exitChan: make(chan bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.commands.Insert("logout", &Command{
|
s.commands.Insert("logout", &Command{
|
||||||
@@ -42,17 +42,16 @@ func NewSession(commands *radix.Tree, conn *ssh.ServerConn, chans <-chan ssh.New
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) handleChannels(chans <-chan ssh.NewChannel) {
|
func (s *session) handleChannels(chans <-chan ssh.NewChannel) {
|
||||||
defer s.Close()
|
|
||||||
for newChannel := range chans {
|
for newChannel := range chans {
|
||||||
if newChannel.ChannelType() != "session" {
|
if newChannel.ChannelType() != "session" {
|
||||||
s.l.Error("unknown channel type", "sshChannelType", newChannel.ChannelType())
|
s.l.WithField("sshChannelType", newChannel.ChannelType()).Error("unknown channel type")
|
||||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, requests, err := newChannel.Accept()
|
channel, requests, err := newChannel.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.l.Warn("could not accept channel", "error", err)
|
s.l.WithError(err).Warn("could not accept channel")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,12 +94,13 @@ func (s *session) handleRequests(in <-chan *ssh.Request, channel ssh.Channel) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
s.l.Debug("Rejected unknown request", "sshRequest", req.Type)
|
s.l.WithField("sshRequest", req.Type).Debug("Rejected unknown request")
|
||||||
err = req.Reply(false, nil)
|
err = req.Reply(false, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.l.Info("Error handling ssh session requests", "error", err)
|
s.l.WithError(err).Info("Error handling ssh session requests")
|
||||||
|
s.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,11 +123,12 @@ func (s *session) createTerm(channel ssh.Channel) *term.Terminal {
|
|||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.handleInput()
|
go s.handleInput(channel)
|
||||||
return term
|
return term
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) handleInput() {
|
func (s *session) handleInput(channel ssh.Channel) {
|
||||||
|
defer s.Close()
|
||||||
w := &stringWriter{w: s.term}
|
w := &stringWriter{w: s.term}
|
||||||
for {
|
for {
|
||||||
line, err := s.term.ReadLine()
|
line, err := s.term.ReadLine()
|
||||||
@@ -169,9 +170,10 @@ func (s *session) dispatchCommand(line string, w StringWriter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = execCommand(c, args[1:], w)
|
_ = execCommand(c, args[1:], w)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) Close() {
|
func (s *session) Close() {
|
||||||
s.c.Close()
|
s.c.Close()
|
||||||
s.cancel()
|
s.exitChan <- true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
graphite "github.com/cyberdelia/go-metrics-graphite"
|
graphite "github.com/cyberdelia/go-metrics-graphite"
|
||||||
@@ -18,277 +15,98 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statsServer owns nebula's stats subsystem: the periodic metric capture
|
// startStats initializes stats from config. On success, if any further work
|
||||||
// goroutine and (for prometheus) an HTTP listener. It mirrors the lifecycle
|
// is needed to serve stats, it returns a func to handle that work. If no
|
||||||
// shape of dnsServer: constructor wires the reload callback, reload records
|
// work is needed, it'll return nil. On failure, it returns nil, error.
|
||||||
// config, Start builds and runs the runtime, Stop tears it down.
|
func startStats(l *logrus.Logger, c *config.C, buildVersion string, configTest bool) (func(), error) {
|
||||||
type statsServer struct {
|
mType := c.GetString("stats.type", "")
|
||||||
l *slog.Logger
|
if mType == "" || mType == "none" {
|
||||||
ctx context.Context
|
return nil, nil
|
||||||
buildVersion string
|
|
||||||
configTest bool
|
|
||||||
|
|
||||||
// enabled mirrors "stats configured to a real backend". Start consults
|
|
||||||
// it so callers don't need to know the gating rules.
|
|
||||||
enabled atomic.Bool
|
|
||||||
|
|
||||||
runMu sync.Mutex
|
|
||||||
runCfg *statsConfig
|
|
||||||
run *statsRuntime // non-nil while a runtime is live
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// statsRuntime is the live state owned by a single Start invocation. Start
|
interval := c.GetDuration("stats.interval", 0)
|
||||||
// stashes a pointer under runMu; Stop and Start's own exit path use pointer
|
if interval == 0 {
|
||||||
// equality to tell "my runtime" apart from one that replaced it after a
|
return nil, fmt.Errorf("stats.interval was an invalid duration: %s", c.GetString("stats.interval", ""))
|
||||||
// reload.
|
|
||||||
type statsRuntime struct {
|
|
||||||
cancel context.CancelFunc
|
|
||||||
listener *http.Server // nil for graphite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// statsConfig is the snapshot of stats-related config that drives the runtime.
|
var startFn func()
|
||||||
// It is comparable with == so reload can detect "no change" cheaply.
|
switch mType {
|
||||||
type statsConfig struct {
|
case "graphite":
|
||||||
typ string
|
err := startGraphiteStats(l, interval, c, configTest)
|
||||||
interval time.Duration
|
|
||||||
graphite graphiteConfig
|
|
||||||
prom promConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type graphiteConfig struct {
|
|
||||||
protocol string
|
|
||||||
host string
|
|
||||||
// resolvedAddr is the string form of host resolved at config-load time.
|
|
||||||
// Including it in the struct means a SIGHUP picks up DNS changes even
|
|
||||||
// when stats.host hasn't been edited.
|
|
||||||
resolvedAddr string
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
type promConfig struct {
|
|
||||||
listen string
|
|
||||||
path string
|
|
||||||
namespace string
|
|
||||||
subsystem string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newStatsServerFromConfig builds a statsServer, applies the initial config,
|
|
||||||
// and registers a reload callback. The reload callback is registered before
|
|
||||||
// the initial config is applied so a SIGHUP can later enable, fix, or disable
|
|
||||||
// stats even if the initial application failed.
|
|
||||||
//
|
|
||||||
// Start is safe to call unconditionally: it no-ops when stats are disabled.
|
|
||||||
// The returned pointer is always non-nil, even on error.
|
|
||||||
func newStatsServerFromConfig(ctx context.Context, l *slog.Logger, c *config.C, buildVersion string, configTest bool) (*statsServer, error) {
|
|
||||||
s := &statsServer{
|
|
||||||
l: l,
|
|
||||||
ctx: ctx,
|
|
||||||
buildVersion: buildVersion,
|
|
||||||
configTest: configTest,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.RegisterReloadCallback(func(c *config.C) {
|
|
||||||
if err := s.reload(c, false); err != nil {
|
|
||||||
s.l.Error("Failed to reload stats from config", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := s.reload(c, true); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload records the latest config. On the initial call it only records it;
|
|
||||||
// Control.Start is what launches the first runtime via statsStart. On later
|
|
||||||
// calls it reconciles the running runtime with the new config:
|
|
||||||
//
|
|
||||||
// - newly enabled -> spawn Start
|
|
||||||
// - newly disabled -> Stop the runtime
|
|
||||||
// - config changed (still enabled) -> Stop the old, Start the new
|
|
||||||
// - no change -> no-op
|
|
||||||
func (s *statsServer) reload(c *config.C, initial bool) error {
|
|
||||||
newCfg, err := loadStatsConfig(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
enabled := newCfg.typ != "" && newCfg.typ != "none"
|
case "prometheus":
|
||||||
|
var err error
|
||||||
s.runMu.Lock()
|
startFn, err = startPrometheusStats(l, interval, c, buildVersion, configTest)
|
||||||
sameCfg := s.runCfg != nil && *s.runCfg == newCfg
|
if err != nil {
|
||||||
s.runCfg = &newCfg
|
return nil, err
|
||||||
running := s.run != nil
|
}
|
||||||
s.runMu.Unlock()
|
default:
|
||||||
|
return nil, fmt.Errorf("stats.type was not understood: %s", mType)
|
||||||
s.enabled.Store(enabled)
|
|
||||||
|
|
||||||
if initial || sameCfg {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if running {
|
|
||||||
s.Stop()
|
|
||||||
}
|
|
||||||
if enabled && !s.configTest {
|
|
||||||
go s.Start()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start builds the runtime from the latest config, spawns the capture loop,
|
|
||||||
// and blocks until Stop is called or ctx fires. For prometheus it also serves
|
|
||||||
// the HTTP listener. For graphite it blocks on the capture loop's context.
|
|
||||||
// Safe to call when stats are disabled or already running (both no-op).
|
|
||||||
func (s *statsServer) Start() {
|
|
||||||
if !s.enabled.Load() || s.configTest {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.runMu.Lock()
|
|
||||||
if s.ctx.Err() != nil || s.run != nil || s.runCfg == nil {
|
|
||||||
s.runMu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg := *s.runCfg
|
|
||||||
captureFns, listener := s.buildRuntime(cfg)
|
|
||||||
runCtx, cancel := context.WithCancel(s.ctx)
|
|
||||||
rt := &statsRuntime{cancel: cancel, listener: listener}
|
|
||||||
s.run = rt
|
|
||||||
s.runMu.Unlock()
|
|
||||||
|
|
||||||
go captureStatsLoop(runCtx, cfg.interval, captureFns)
|
|
||||||
|
|
||||||
cleanExit := true
|
|
||||||
if listener == nil {
|
|
||||||
// Graphite: no HTTP listener to serve; block until teardown.
|
|
||||||
<-runCtx.Done()
|
|
||||||
} else {
|
|
||||||
cleanExit = s.serveListener(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear our runtime only if nothing has replaced it. Stop races through
|
|
||||||
// here too but leaves s.run == nil, so the pointer check skips.
|
|
||||||
s.runMu.Lock()
|
|
||||||
if s.run == rt {
|
|
||||||
rt.cancel()
|
|
||||||
s.run = nil
|
|
||||||
// A listener that exited with an error (e.g., bind conflict) leaves
|
|
||||||
// runCfg cached as if it were applied. Drop it so a SIGHUP with the
|
|
||||||
// same config re-triggers Start once the user fixes the underlying
|
|
||||||
// problem.
|
|
||||||
if !cleanExit {
|
|
||||||
s.runCfg = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.runMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// serveListener runs ListenAndServe and ensures ctx cancellation unblocks it.
|
|
||||||
// Returns true if the listener exited cleanly (Stop, ctx cancellation, or any
|
|
||||||
// other http.ErrServerClosed path), false on an unexpected error.
|
|
||||||
func (s *statsServer) serveListener(listener *http.Server) bool {
|
|
||||||
// Per-invocation watcher: ctx cancellation triggers a listener shutdown
|
|
||||||
// which in turn unblocks ListenAndServe. Closing `done` on exit keeps
|
|
||||||
// the watcher from outliving this call.
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := listener.Shutdown(shutdownCtx); err != nil {
|
|
||||||
s.l.Warn("Failed to shut down prometheus stats listener", "error", err)
|
|
||||||
}
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
s.l.Info("Starting prometheus stats listener", "addr", listener.Addr)
|
|
||||||
err := listener.ListenAndServe()
|
|
||||||
if err == nil || errors.Is(err, http.ErrServerClosed) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s.l.Error("Prometheus stats listener exited", "error", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop tears down the active runtime, if any. Idempotent.
|
|
||||||
func (s *statsServer) Stop() {
|
|
||||||
s.runMu.Lock()
|
|
||||||
rt := s.run
|
|
||||||
s.run = nil
|
|
||||||
s.runMu.Unlock()
|
|
||||||
if rt == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rt.cancel()
|
|
||||||
if rt.listener != nil {
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
if err := rt.listener.Shutdown(shutdownCtx); err != nil {
|
|
||||||
s.l.Warn("Failed to shut down prometheus stats listener", "error", err)
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildRuntime produces the capture functions and, for prometheus, an un-served
|
|
||||||
// http.Server from cfg. cfg has already been validated by loadStatsConfig.
|
|
||||||
func (s *statsServer) buildRuntime(cfg statsConfig) ([]func(), *http.Server) {
|
|
||||||
// rcrowley/go-metrics guards these registrations with a private sync.Once,
|
|
||||||
// so subsequent reloads are no-ops.
|
|
||||||
metrics.RegisterDebugGCStats(metrics.DefaultRegistry)
|
metrics.RegisterDebugGCStats(metrics.DefaultRegistry)
|
||||||
metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
|
metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
|
||||||
|
|
||||||
captureFns := []func(){
|
go metrics.CaptureDebugGCStats(metrics.DefaultRegistry, interval)
|
||||||
func() { metrics.CaptureDebugGCStatsOnce(metrics.DefaultRegistry) },
|
go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, interval)
|
||||||
func() { metrics.CaptureRuntimeMemStatsOnce(metrics.DefaultRegistry) },
|
|
||||||
|
return startFn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch cfg.typ {
|
func startGraphiteStats(l *logrus.Logger, i time.Duration, c *config.C, configTest bool) error {
|
||||||
case "graphite":
|
proto := c.GetString("stats.protocol", "tcp")
|
||||||
// loadStatsConfig already resolved and validated the address; re-parse
|
host := c.GetString("stats.host", "")
|
||||||
// the resolved form (no DNS lookup) to get a *net.TCPAddr.
|
if host == "" {
|
||||||
addr, _ := net.ResolveTCPAddr(cfg.graphite.protocol, cfg.graphite.resolvedAddr)
|
return errors.New("stats.host can not be empty")
|
||||||
gcfg := graphite.Config{
|
}
|
||||||
Addr: addr,
|
|
||||||
Registry: metrics.DefaultRegistry,
|
prefix := c.GetString("stats.prefix", "nebula")
|
||||||
FlushInterval: cfg.interval,
|
addr, err := net.ResolveTCPAddr(proto, host)
|
||||||
DurationUnit: time.Nanosecond,
|
if err != nil {
|
||||||
Prefix: cfg.graphite.prefix,
|
return fmt.Errorf("error while setting up graphite sink: %s", err)
|
||||||
Percentiles: []float64{0.5, 0.75, 0.95, 0.99, 0.999},
|
}
|
||||||
|
|
||||||
|
if !configTest {
|
||||||
|
l.Infof("Starting graphite. Interval: %s, prefix: %s, addr: %s", i, prefix, addr)
|
||||||
|
go graphite.Graphite(metrics.DefaultRegistry, i, prefix, addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startPrometheusStats(l *logrus.Logger, i time.Duration, c *config.C, buildVersion string, configTest bool) (func(), error) {
|
||||||
|
namespace := c.GetString("stats.namespace", "")
|
||||||
|
subsystem := c.GetString("stats.subsystem", "")
|
||||||
|
|
||||||
|
listen := c.GetString("stats.listen", "")
|
||||||
|
if listen == "" {
|
||||||
|
return nil, fmt.Errorf("stats.listen should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := c.GetString("stats.path", "")
|
||||||
|
if path == "" {
|
||||||
|
return nil, fmt.Errorf("stats.path should not be empty")
|
||||||
}
|
}
|
||||||
captureFns = append(captureFns, func() {
|
|
||||||
if err := graphite.Once(gcfg); err != nil {
|
|
||||||
s.l.Error("Graphite export failed", "error", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
s.l.Info("Starting graphite stats",
|
|
||||||
"interval", cfg.interval,
|
|
||||||
"prefix", cfg.graphite.prefix,
|
|
||||||
"addr", addr,
|
|
||||||
)
|
|
||||||
return captureFns, nil
|
|
||||||
|
|
||||||
case "prometheus":
|
|
||||||
pr := prometheus.NewRegistry()
|
pr := prometheus.NewRegistry()
|
||||||
pClient := mp.NewPrometheusProvider(metrics.DefaultRegistry, cfg.prom.namespace, cfg.prom.subsystem, pr, cfg.interval)
|
pClient := mp.NewPrometheusProvider(metrics.DefaultRegistry, namespace, subsystem, pr, i)
|
||||||
captureFns = append(captureFns, func() {
|
if !configTest {
|
||||||
if err := pClient.UpdatePrometheusMetricsOnce(); err != nil {
|
go pClient.UpdatePrometheusMetrics()
|
||||||
s.l.Error("Prometheus metrics update failed", "error", err)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
// Export our version information as labels on a static gauge
|
||||||
g := prometheus.NewGauge(prometheus.GaugeOpts{
|
g := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
Namespace: cfg.prom.namespace,
|
Namespace: namespace,
|
||||||
Subsystem: cfg.prom.subsystem,
|
Subsystem: subsystem,
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Help: "Version information for the Nebula binary",
|
Help: "Version information for the Nebula binary",
|
||||||
ConstLabels: prometheus.Labels{
|
ConstLabels: prometheus.Labels{
|
||||||
"version": s.buildVersion,
|
"version": buildVersion,
|
||||||
"goversion": runtime.Version(),
|
"goversion": runtime.Version(),
|
||||||
"boringcrypto": strconv.FormatBool(boringEnabled()),
|
"boringcrypto": strconv.FormatBool(boringEnabled()),
|
||||||
},
|
},
|
||||||
@@ -296,72 +114,14 @@ func (s *statsServer) buildRuntime(cfg statsConfig) ([]func(), *http.Server) {
|
|||||||
pr.MustRegister(g)
|
pr.MustRegister(g)
|
||||||
g.Set(1)
|
g.Set(1)
|
||||||
|
|
||||||
// promhttp.HandlerOpts.ErrorLog needs a stdlib-shaped Println logger,
|
var startFn func()
|
||||||
// so bridge our slog.Logger back to a *log.Logger that emits at Error.
|
if !configTest {
|
||||||
errLog := slog.NewLogLogger(s.l.Handler(), slog.LevelError)
|
startFn = func() {
|
||||||
mux := http.NewServeMux()
|
l.Infof("Prometheus stats listening on %s at %s", listen, path)
|
||||||
mux.Handle(cfg.prom.path, promhttp.HandlerFor(pr, promhttp.HandlerOpts{ErrorLog: errLog}))
|
http.Handle(path, promhttp.HandlerFor(pr, promhttp.HandlerOpts{ErrorLog: l}))
|
||||||
return captureFns, &http.Server{Addr: cfg.prom.listen, Handler: mux}
|
log.Fatal(http.ListenAndServe(listen, nil))
|
||||||
}
|
|
||||||
return captureFns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// captureStatsLoop runs each fn on every tick of d until ctx is cancelled.
|
|
||||||
func captureStatsLoop(ctx context.Context, d time.Duration, fns []func()) {
|
|
||||||
t := time.NewTicker(d)
|
|
||||||
defer t.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-t.C:
|
|
||||||
for _, fn := range fns {
|
|
||||||
fn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadStatsConfig(c *config.C) (statsConfig, error) {
|
return startFn, nil
|
||||||
cfg := statsConfig{
|
|
||||||
typ: c.GetString("stats.type", ""),
|
|
||||||
}
|
|
||||||
if cfg.typ == "" || cfg.typ == "none" {
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.interval = c.GetDuration("stats.interval", 0)
|
|
||||||
if cfg.interval == 0 {
|
|
||||||
return cfg, fmt.Errorf("stats.interval was an invalid duration: %s", c.GetString("stats.interval", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cfg.typ {
|
|
||||||
case "graphite":
|
|
||||||
cfg.graphite.protocol = c.GetString("stats.protocol", "tcp")
|
|
||||||
cfg.graphite.host = c.GetString("stats.host", "")
|
|
||||||
if cfg.graphite.host == "" {
|
|
||||||
return cfg, errors.New("stats.host can not be empty")
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveTCPAddr(cfg.graphite.protocol, cfg.graphite.host)
|
|
||||||
if err != nil {
|
|
||||||
return cfg, fmt.Errorf("error while setting up graphite sink: %s", err)
|
|
||||||
}
|
|
||||||
cfg.graphite.resolvedAddr = addr.String()
|
|
||||||
cfg.graphite.prefix = c.GetString("stats.prefix", "nebula")
|
|
||||||
case "prometheus":
|
|
||||||
cfg.prom.listen = c.GetString("stats.listen", "")
|
|
||||||
if cfg.prom.listen == "" {
|
|
||||||
return cfg, errors.New("stats.listen should not be empty")
|
|
||||||
}
|
|
||||||
cfg.prom.path = c.GetString("stats.path", "")
|
|
||||||
if cfg.prom.path == "" {
|
|
||||||
return cfg, errors.New("stats.path should not be empty")
|
|
||||||
}
|
|
||||||
cfg.prom.namespace = c.GetString("stats.namespace", "")
|
|
||||||
cfg.prom.subsystem = c.GetString("stats.subsystem", "")
|
|
||||||
default:
|
|
||||||
return cfg, fmt.Errorf("stats.type was not understood: %s", cfg.typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
-410
@@ -1,410 +0,0 @@
|
|||||||
package nebula
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/config"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTestStatsServer(t *testing.T) (*statsServer, *config.C) {
|
|
||||||
t.Helper()
|
|
||||||
l := slog.New(slog.DiscardHandler)
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
t.Cleanup(cancel)
|
|
||||||
return &statsServer{
|
|
||||||
l: l,
|
|
||||||
ctx: ctx,
|
|
||||||
}, config.NewC(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setStatsConfig(c *config.C, m map[string]any) {
|
|
||||||
c.Settings["stats"] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
func currentRuntime(s *statsServer) *statsRuntime {
|
|
||||||
s.runMu.Lock()
|
|
||||||
defer s.runMu.Unlock()
|
|
||||||
return s.run
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_initial_disabled(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{"type": "none"})
|
|
||||||
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
assert.False(t, s.enabled.Load())
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_initial_invalidInterval(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "graphite",
|
|
||||||
"host": "127.0.0.1:0",
|
|
||||||
"prefix": "test",
|
|
||||||
})
|
|
||||||
|
|
||||||
err := s.reload(c, true)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.False(t, s.enabled.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_initial_unknownType(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "carbon",
|
|
||||||
"interval": "1s",
|
|
||||||
})
|
|
||||||
|
|
||||||
err := s.reload(c, true)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.False(t, s.enabled.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_unchanged_noOp(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{"type": "none"})
|
|
||||||
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
require.NoError(t, s.reload(c, false))
|
|
||||||
assert.False(t, s.enabled.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_initial_graphite(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "graphite",
|
|
||||||
"interval": "1s",
|
|
||||||
"protocol": "tcp",
|
|
||||||
"host": "127.0.0.1:2003",
|
|
||||||
"prefix": "test",
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
assert.True(t, s.enabled.Load())
|
|
||||||
// reload only records config; Start builds the runtime.
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_initial_prometheus(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:0",
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
assert.True(t, s.enabled.Load())
|
|
||||||
// reload only records config; Start builds the runtime.
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_Start_graphite_blocksUntilStop(t *testing.T) {
|
|
||||||
sink := newGraphiteSink(t)
|
|
||||||
defer sink.Close()
|
|
||||||
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "graphite",
|
|
||||||
"interval": "1s",
|
|
||||||
"protocol": "tcp",
|
|
||||||
"host": sink.Addr(),
|
|
||||||
"prefix": "test",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for Start to publish runtime state.
|
|
||||||
waitFor(t, func() bool { return currentRuntime(s) != nil })
|
|
||||||
rt := currentRuntime(s)
|
|
||||||
require.NotNil(t, rt)
|
|
||||||
assert.Nil(t, rt.listener, "graphite has no listener")
|
|
||||||
|
|
||||||
s.Stop()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("graphite Start did not return after Stop")
|
|
||||||
}
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_StartStop_lifecycle(t *testing.T) {
|
|
||||||
port := freeTCPPort(t)
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
waitForListening(t, "127.0.0.1:"+port)
|
|
||||||
rt := currentRuntime(s)
|
|
||||||
require.NotNil(t, rt)
|
|
||||||
require.NotNil(t, rt.listener)
|
|
||||||
|
|
||||||
s.Stop()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after Stop")
|
|
||||||
}
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_disable_stopsRunningRuntime(t *testing.T) {
|
|
||||||
port := freeTCPPort(t)
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
waitForListening(t, "127.0.0.1:"+port)
|
|
||||||
|
|
||||||
setStatsConfig(c, map[string]any{"type": "none"})
|
|
||||||
require.NoError(t, s.reload(c, false))
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after reload disabled stats")
|
|
||||||
}
|
|
||||||
assert.False(t, s.enabled.Load())
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_reload_changeListener_restartsListener(t *testing.T) {
|
|
||||||
port1 := freeTCPPort(t)
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port1,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
firstDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(firstDone)
|
|
||||||
}()
|
|
||||||
waitForListening(t, "127.0.0.1:"+port1)
|
|
||||||
first := currentRuntime(s)
|
|
||||||
require.NotNil(t, first)
|
|
||||||
|
|
||||||
port2 := freeTCPPort(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port2,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, false))
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-firstDone:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("old Start did not return after reload")
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForListening(t, "127.0.0.1:"+port2)
|
|
||||||
second := currentRuntime(s)
|
|
||||||
require.NotNil(t, second)
|
|
||||||
assert.NotSame(t, first, second, "expected a new runtime after listen address change")
|
|
||||||
|
|
||||||
s.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_Stop_beforeStart_doesNotBlock(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:0",
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Stop()
|
|
||||||
close(stopped)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-stopped:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("Stop hung with no runtime started")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_configTest_validatesWithoutSpawning(t *testing.T) {
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
s.configTest = true
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:0",
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
s.Start()
|
|
||||||
assert.Nil(t, currentRuntime(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_ctxCancel_unblocksStart(t *testing.T) {
|
|
||||||
// Ensures ctx cancellation alone (no explicit Stop) tears down both
|
|
||||||
// graphite and prom Start invocations.
|
|
||||||
port := freeTCPPort(t)
|
|
||||||
l := slog.New(slog.DiscardHandler)
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
s := &statsServer{l: l, ctx: ctx}
|
|
||||||
c := config.NewC(l)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
waitForListening(t, "127.0.0.1:"+port)
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after ctx cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatsServer_listenerBindFailure_sameCfgReloadRetries(t *testing.T) {
|
|
||||||
// Hold the port so ListenAndServe will fail on first Start.
|
|
||||||
blocker, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
port := strconv.Itoa(blocker.Addr().(*net.TCPAddr).Port)
|
|
||||||
|
|
||||||
s, c := newTestStatsServer(t)
|
|
||||||
setStatsConfig(c, map[string]any{
|
|
||||||
"type": "prometheus",
|
|
||||||
"interval": "1s",
|
|
||||||
"listen": "127.0.0.1:" + port,
|
|
||||||
"path": "/metrics",
|
|
||||||
})
|
|
||||||
require.NoError(t, s.reload(c, true))
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
s.Start()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Start did not return after bind failure")
|
|
||||||
}
|
|
||||||
// Bind failure should have dropped the cached config so a same-cfg
|
|
||||||
// SIGHUP can retry.
|
|
||||||
s.runMu.Lock()
|
|
||||||
cfgAfterFailure := s.runCfg
|
|
||||||
s.runMu.Unlock()
|
|
||||||
assert.Nil(t, cfgAfterFailure)
|
|
||||||
|
|
||||||
// Free the port and reload with the same config; Start should fire again.
|
|
||||||
require.NoError(t, blocker.Close())
|
|
||||||
require.NoError(t, s.reload(c, false))
|
|
||||||
|
|
||||||
waitForListening(t, "127.0.0.1:"+port)
|
|
||||||
require.NotNil(t, currentRuntime(s))
|
|
||||||
|
|
||||||
s.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForListening(t *testing.T, addr string) {
|
|
||||||
t.Helper()
|
|
||||||
waitFor(t, func() bool {
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_ = conn.Close()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// graphiteSink is a minimal TCP accept-and-discard server so graphite.Once
|
|
||||||
// calls in tests don't spam error logs or wedge on connection refused.
|
|
||||||
type graphiteSink struct {
|
|
||||||
ln net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGraphiteSink(t *testing.T) *graphiteSink {
|
|
||||||
t.Helper()
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
g := &graphiteSink{ln: ln}
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
conn, err := ln.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func(c net.Conn) {
|
|
||||||
_, _ = io.Copy(io.Discard, c)
|
|
||||||
_ = c.Close()
|
|
||||||
}(conn)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *graphiteSink) Addr() string { return g.ln.Addr().String() }
|
|
||||||
func (g *graphiteSink) Close() { _ = g.ln.Close() }
|
|
||||||
|
|
||||||
func freeTCPPort(t *testing.T) string {
|
|
||||||
t.Helper()
|
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
port := ln.Addr().(*net.TCPAddr).Port
|
|
||||||
require.NoError(t, ln.Close())
|
|
||||||
return strconv.Itoa(port)
|
|
||||||
}
|
|
||||||
+11
-55
@@ -1,73 +1,29 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/logging"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLogger returns a *slog.Logger suitable for use in tests. Output goes to
|
func NewLogger() *logrus.Logger {
|
||||||
// io.Discard by default; set TEST_LOGS=1 (info), 2 (debug), or 3 (trace) to
|
l := logrus.New()
|
||||||
// stream output to stderr for local debugging.
|
|
||||||
func NewLogger() *slog.Logger {
|
|
||||||
v := os.Getenv("TEST_LOGS")
|
v := os.Getenv("TEST_LOGS")
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return slog.New(slog.DiscardHandler)
|
l.SetOutput(io.Discard)
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
level := slog.LevelInfo
|
|
||||||
switch v {
|
switch v {
|
||||||
case "2":
|
case "2":
|
||||||
level = slog.LevelDebug
|
l.SetLevel(logrus.DebugLevel)
|
||||||
case "3":
|
case "3":
|
||||||
level = logging.LevelTrace
|
l.SetLevel(logrus.TraceLevel)
|
||||||
}
|
default:
|
||||||
return slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level}))
|
l.SetLevel(logrus.InfoLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLoggerWithOutput returns a *slog.Logger whose text output is captured by
|
return l
|
||||||
// w. Timestamps are suppressed so tests can assert on exact output without
|
|
||||||
// baking the current time into expected strings.
|
|
||||||
func NewLoggerWithOutput(w io.Writer) *slog.Logger {
|
|
||||||
return slog.New(&stripTimeHandler{inner: slog.NewTextHandler(w, nil)})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLoggerWithOutputAndLevel is NewLoggerWithOutput with an explicit level
|
|
||||||
// so tests can exercise Enabled-gated paths.
|
|
||||||
func NewLoggerWithOutputAndLevel(w io.Writer, level slog.Level) *slog.Logger {
|
|
||||||
return slog.New(&stripTimeHandler{inner: slog.NewTextHandler(w, &slog.HandlerOptions{Level: level})})
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSONLoggerWithOutput returns a *slog.Logger emitting JSON to w with
|
|
||||||
// timestamps suppressed, for tests that pin the JSON shape.
|
|
||||||
func NewJSONLoggerWithOutput(w io.Writer, level slog.Level) *slog.Logger {
|
|
||||||
return slog.New(&stripTimeHandler{inner: slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level})})
|
|
||||||
}
|
|
||||||
|
|
||||||
// stripTimeHandler zeros each record's time before delegating so slog's
|
|
||||||
// built-in handlers skip emitting the time attribute. Used to avoid
|
|
||||||
// timestamp-dependent assertions in tests without resorting to ReplaceAttr.
|
|
||||||
type stripTimeHandler struct {
|
|
||||||
inner slog.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *stripTimeHandler) Enabled(ctx context.Context, l slog.Level) bool {
|
|
||||||
return h.inner.Enabled(ctx, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *stripTimeHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
||||||
r.Time = time.Time{}
|
|
||||||
return h.inner.Handle(ctx, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *stripTimeHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
return &stripTimeHandler{inner: h.inner.WithAttrs(attrs)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *stripTimeHandler) WithGroup(name string) slog.Handler {
|
|
||||||
return &stripTimeHandler{inner: h.inner.WithGroup(name)}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -9,12 +9,11 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"log/slog"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
return NewGenericListener(l, ip, port, multi, batch)
|
return NewGenericListener(l, ip, port, multi, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -12,12 +12,11 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"log/slog"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
return NewGenericListener(l, ip, port, multi, batch)
|
return NewGenericListener(l, ip, port, multi, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-5
@@ -8,12 +8,12 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -22,12 +22,12 @@ type StdConn struct {
|
|||||||
*net.UDPConn
|
*net.UDPConn
|
||||||
isV4 bool
|
isV4 bool
|
||||||
sysFd uintptr
|
sysFd uintptr
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Conn = &StdConn{}
|
var _ Conn = &StdConn{}
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
lc := NewListenConfig(multi)
|
lc := NewListenConfig(multi)
|
||||||
pc, err := lc.ListenPacket(context.TODO(), "udp", net.JoinHostPort(ip.String(), fmt.Sprintf("%v", port)))
|
pc, err := lc.ListenPacket(context.TODO(), "udp", net.JoinHostPort(ip.String(), fmt.Sprintf("%v", port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -149,6 +149,17 @@ func (u *StdConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) WriteSegmented(bufs [][]byte, addr netip.AddrPort, _ int) error {
|
||||||
|
for _, b := range bufs {
|
||||||
|
if err := u.WriteTo(b, addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *StdConn) SupportsGSO() bool { return false }
|
||||||
|
|
||||||
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
func (u *StdConn) LocalAddr() (netip.AddrPort, error) {
|
||||||
a := u.UDPConn.LocalAddr()
|
a := u.UDPConn.LocalAddr()
|
||||||
|
|
||||||
@@ -185,7 +196,7 @@ func (u *StdConn) ListenOut(r EncReader, flush func()) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.l.Error("unexpected udp socket receive error", "error", err)
|
u.l.WithError(err).Error("unexpected udp socket receive error")
|
||||||
}
|
}
|
||||||
|
|
||||||
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n])
|
||||||
@@ -206,7 +217,7 @@ func (u *StdConn) Rebind() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.l.Error("Failed to rebind udp socket", "error", err)
|
u.l.WithError(err).Error("Failed to rebind udp socket")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+15
-4
@@ -12,22 +12,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GenericConn struct {
|
type GenericConn struct {
|
||||||
*net.UDPConn
|
*net.UDPConn
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Conn = &GenericConn{}
|
var _ Conn = &GenericConn{}
|
||||||
|
|
||||||
func NewGenericListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewGenericListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
lc := NewListenConfig(multi)
|
lc := NewListenConfig(multi)
|
||||||
pc, err := lc.ListenPacket(context.TODO(), "udp", net.JoinHostPort(ip.String(), fmt.Sprintf("%v", port)))
|
pc, err := lc.ListenPacket(context.TODO(), "udp", net.JoinHostPort(ip.String(), fmt.Sprintf("%v", port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,6 +53,17 @@ func (u *GenericConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *GenericConn) WriteSegmented(bufs [][]byte, addr netip.AddrPort, _ int) error {
|
||||||
|
for _, b := range bufs {
|
||||||
|
if _, err := u.UDPConn.WriteToUDPAddrPort(b, addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *GenericConn) SupportsGSO() bool { return false }
|
||||||
|
|
||||||
func (u *GenericConn) LocalAddr() (netip.AddrPort, error) {
|
func (u *GenericConn) LocalAddr() (netip.AddrPort, error) {
|
||||||
a := u.UDPConn.LocalAddr()
|
a := u.UDPConn.LocalAddr()
|
||||||
|
|
||||||
@@ -97,7 +108,7 @@ func (u *GenericConn) ListenOut(r EncReader, flush func()) error {
|
|||||||
// Dampen unexpected message warns to once per minute
|
// Dampen unexpected message warns to once per minute
|
||||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||||
lastRecvErr = time.Now()
|
lastRecvErr = time.Now()
|
||||||
u.l.Warn("unexpected udp socket receive error", "error", err)
|
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -7,13 +7,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/rcrowley/go-metrics"
|
"github.com/rcrowley/go-metrics"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,7 @@ type StdConn struct {
|
|||||||
udpConn *net.UDPConn
|
udpConn *net.UDPConn
|
||||||
rawConn syscall.RawConn
|
rawConn syscall.RawConn
|
||||||
isV4 bool
|
isV4 bool
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
batch int
|
batch int
|
||||||
|
|
||||||
// sendmmsg scratch. Each queue has its own StdConn, so no locking is
|
// sendmmsg scratch. Each queue has its own StdConn, so no locking is
|
||||||
@@ -75,7 +75,7 @@ func setReusePort(network, address string, c syscall.RawConn) error {
|
|||||||
return opErr
|
return opErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
listen := netip.AddrPortFrom(ip, uint16(port))
|
listen := netip.AddrPortFrom(ip, uint16(port))
|
||||||
lc := net.ListenConfig{}
|
lc := net.ListenConfig{}
|
||||||
if multi {
|
if multi {
|
||||||
@@ -643,12 +643,12 @@ func (u *StdConn) ReloadConfig(c *config.C) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
s, err := u.GetRecvBuffer()
|
s, err := u.GetRecvBuffer()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.l.Info("listen.read_buffer was set", "size", s)
|
u.l.WithField("size", s).Info("listen.read_buffer was set")
|
||||||
} else {
|
} else {
|
||||||
u.l.Warn("Failed to get listen.read_buffer", "error", err)
|
u.l.WithError(err).Warn("Failed to get listen.read_buffer")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u.l.Error("Failed to set listen.read_buffer", "error", err)
|
u.l.WithError(err).Error("Failed to set listen.read_buffer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,12 +658,12 @@ func (u *StdConn) ReloadConfig(c *config.C) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
s, err := u.GetSendBuffer()
|
s, err := u.GetSendBuffer()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.l.Info("listen.write_buffer was set", "size", s)
|
u.l.WithField("size", s).Info("listen.write_buffer was set")
|
||||||
} else {
|
} else {
|
||||||
u.l.Warn("Failed to get listen.write_buffer", "error", err)
|
u.l.WithError(err).Warn("Failed to get listen.write_buffer")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u.l.Error("Failed to set listen.write_buffer", "error", err)
|
u.l.WithError(err).Error("Failed to set listen.write_buffer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,12 +674,12 @@ func (u *StdConn) ReloadConfig(c *config.C) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
s, err := u.GetSoMark()
|
s, err := u.GetSoMark()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.l.Info("listen.so_mark was set", "mark", s)
|
u.l.WithField("mark", s).Info("listen.so_mark was set")
|
||||||
} else {
|
} else {
|
||||||
u.l.Warn("Failed to get listen.so_mark", "error", err)
|
u.l.WithError(err).Warn("Failed to get listen.so_mark")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u.l.Error("Failed to set listen.so_mark", "error", err)
|
u.l.WithError(err).Error("Failed to set listen.so_mark")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -11,12 +11,11 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"log/slog"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
return NewGenericListener(l, ip, port, multi, batch)
|
return NewGenericListener(l, ip, port, multi, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -9,7 +9,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -18,6 +17,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/conn/winrio"
|
"golang.zx2c4.com/wireguard/conn/winrio"
|
||||||
@@ -53,14 +53,14 @@ type ringBuffer struct {
|
|||||||
|
|
||||||
type RIOConn struct {
|
type RIOConn struct {
|
||||||
isOpen atomic.Bool
|
isOpen atomic.Bool
|
||||||
l *slog.Logger
|
l *logrus.Logger
|
||||||
sock windows.Handle
|
sock windows.Handle
|
||||||
rx, tx ringBuffer
|
rx, tx ringBuffer
|
||||||
rq winrio.Rq
|
rq winrio.Rq
|
||||||
results [packetsPerRing]winrio.Result
|
results [packetsPerRing]winrio.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRIOListener(l *slog.Logger, addr netip.Addr, port int) (*RIOConn, error) {
|
func NewRIOListener(l *logrus.Logger, addr netip.Addr, port int) (*RIOConn, error) {
|
||||||
if !winrio.Initialize() {
|
if !winrio.Initialize() {
|
||||||
return nil, errors.New("could not initialize winrio")
|
return nil, errors.New("could not initialize winrio")
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ func NewRIOListener(l *slog.Logger, addr netip.Addr, port int) (*RIOConn, error)
|
|||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *RIOConn) bind(l *slog.Logger, sa windows.Sockaddr) error {
|
func (u *RIOConn) bind(l *logrus.Logger, sa windows.Sockaddr) error {
|
||||||
var err error
|
var err error
|
||||||
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
u.sock, err = winrio.Socket(windows.AF_INET6, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,7 +103,7 @@ func (u *RIOConn) bind(l *slog.Logger, sa windows.Sockaddr) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||||
// Quietly log a failure and continue.
|
// Quietly log a failure and continue.
|
||||||
l.Debug("failed to set UDP_CONNRESET ioctl", "error", err)
|
l.WithError(err).Debug("failed to set UDP_CONNRESET ioctl")
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0
|
ret = 0
|
||||||
@@ -114,7 +114,7 @@ func (u *RIOConn) bind(l *slog.Logger, sa windows.Sockaddr) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
// This is a best-effort to prevent errors from being returned by the udp recv operation.
|
||||||
// Quietly log a failure and continue.
|
// Quietly log a failure and continue.
|
||||||
l.Debug("failed to set UDP_NETRESET ioctl", "error", err)
|
l.WithError(err).Debug("failed to set UDP_NETRESET ioctl")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = u.rx.Open()
|
err = u.rx.Open()
|
||||||
@@ -156,7 +156,7 @@ func (u *RIOConn) ListenOut(r EncReader, flush func()) error {
|
|||||||
// Dampen unexpected message warns to once per minute
|
// Dampen unexpected message warns to once per minute
|
||||||
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
if lastRecvErr.IsZero() || time.Since(lastRecvErr) > time.Minute {
|
||||||
lastRecvErr = time.Now()
|
lastRecvErr = time.Now()
|
||||||
u.l.Warn("unexpected udp socket receive error", "error", err)
|
u.l.WithError(err).Warn("unexpected udp socket receive error")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -326,6 +326,17 @@ func (u *RIOConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *RIOConn) WriteSegmented(bufs [][]byte, addr netip.AddrPort, _ int) error {
|
||||||
|
for _, b := range bufs {
|
||||||
|
if err := u.WriteTo(b, addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RIOConn) SupportsGSO() bool { return false }
|
||||||
|
|
||||||
func (u *RIOConn) LocalAddr() (netip.AddrPort, error) {
|
func (u *RIOConn) LocalAddr() (netip.AddrPort, error) {
|
||||||
sa, err := windows.Getsockname(u.sock)
|
sa, err := windows.Getsockname(u.sock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+39
-43
@@ -4,13 +4,12 @@
|
|||||||
package udp
|
package udp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/config"
|
"github.com/slackhq/nebula/config"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
)
|
)
|
||||||
@@ -38,24 +37,15 @@ type TesterConn struct {
|
|||||||
RxPackets chan *Packet // Packets to receive into nebula
|
RxPackets chan *Packet // Packets to receive into nebula
|
||||||
TxPackets chan *Packet // Packets transmitted outside by nebula
|
TxPackets chan *Packet // Packets transmitted outside by nebula
|
||||||
|
|
||||||
// done is closed exactly once by Close. Senders select on it so they
|
closed atomic.Bool
|
||||||
// never race with a channel close; readers exit when it fires. The
|
l *logrus.Logger
|
||||||
// packet channels are intentionally never closed - that was the source
|
|
||||||
// of `send on closed channel` panics when a WriteTo/Send from another
|
|
||||||
// goroutine passed the close check and reached the send just after
|
|
||||||
// Close ran.
|
|
||||||
done chan struct{}
|
|
||||||
closeOnce sync.Once
|
|
||||||
|
|
||||||
l *slog.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, _ bool, _ int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, _ bool, _ int) (Conn, error) {
|
||||||
return &TesterConn{
|
return &TesterConn{
|
||||||
Addr: netip.AddrPortFrom(ip, uint16(port)),
|
Addr: netip.AddrPortFrom(ip, uint16(port)),
|
||||||
RxPackets: make(chan *Packet, 10),
|
RxPackets: make(chan *Packet, 10),
|
||||||
TxPackets: make(chan *Packet, 10),
|
TxPackets: make(chan *Packet, 10),
|
||||||
done: make(chan struct{}),
|
|
||||||
l: l,
|
l: l,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -64,21 +54,21 @@ func NewListener(l *slog.Logger, ip netip.Addr, port int, _ bool, _ int) (Conn,
|
|||||||
// this is an encrypted packet or a handshake message in most cases
|
// this is an encrypted packet or a handshake message in most cases
|
||||||
// packets were transmitted from another nebula node, you can send them with Tun.Send
|
// packets were transmitted from another nebula node, you can send them with Tun.Send
|
||||||
func (u *TesterConn) Send(packet *Packet) {
|
func (u *TesterConn) Send(packet *Packet) {
|
||||||
|
if u.closed.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h := &header.H{}
|
h := &header.H{}
|
||||||
if err := h.Parse(packet.Data); err != nil {
|
if err := h.Parse(packet.Data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if u.l.Enabled(context.Background(), slog.LevelDebug) {
|
if u.l.Level >= logrus.DebugLevel {
|
||||||
u.l.Debug("UDP receiving injected packet",
|
u.l.WithField("header", h).
|
||||||
"header", h,
|
WithField("udpAddr", packet.From).
|
||||||
"udpAddr", packet.From,
|
WithField("dataLen", len(packet.Data)).
|
||||||
"dataLen", len(packet.Data),
|
Debug("UDP receiving injected packet")
|
||||||
)
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-u.done:
|
|
||||||
case u.RxPackets <- packet:
|
|
||||||
}
|
}
|
||||||
|
u.RxPackets <- packet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get will pull a UdpPacket from the transmit queue
|
// Get will pull a UdpPacket from the transmit queue
|
||||||
@@ -86,12 +76,7 @@ func (u *TesterConn) Send(packet *Packet) {
|
|||||||
// packets were ingested from the tun side (in most cases), you can send them with Tun.Send
|
// packets were ingested from the tun side (in most cases), you can send them with Tun.Send
|
||||||
func (u *TesterConn) Get(block bool) *Packet {
|
func (u *TesterConn) Get(block bool) *Packet {
|
||||||
if block {
|
if block {
|
||||||
select {
|
return <-u.TxPackets
|
||||||
case <-u.done:
|
|
||||||
return nil
|
|
||||||
case p := <-u.TxPackets:
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -107,6 +92,10 @@ func (u *TesterConn) Get(block bool) *Packet {
|
|||||||
//********************************************************************************************************************//
|
//********************************************************************************************************************//
|
||||||
|
|
||||||
func (u *TesterConn) WriteTo(b []byte, addr netip.AddrPort) error {
|
func (u *TesterConn) WriteTo(b []byte, addr netip.AddrPort) error {
|
||||||
|
if u.closed.Load() {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
|
||||||
p := &Packet{
|
p := &Packet{
|
||||||
Data: make([]byte, len(b), len(b)),
|
Data: make([]byte, len(b), len(b)),
|
||||||
From: u.Addr,
|
From: u.Addr,
|
||||||
@@ -114,13 +103,9 @@ func (u *TesterConn) WriteTo(b []byte, addr netip.AddrPort) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy(p.Data, b)
|
copy(p.Data, b)
|
||||||
select {
|
u.TxPackets <- p
|
||||||
case <-u.done:
|
|
||||||
return io.ErrClosedPipe
|
|
||||||
case u.TxPackets <- p:
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (u *TesterConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
func (u *TesterConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
||||||
for i, b := range bufs {
|
for i, b := range bufs {
|
||||||
@@ -131,17 +116,27 @@ func (u *TesterConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *TesterConn) WriteSegmented(bufs [][]byte, addr netip.AddrPort, _ int) error {
|
||||||
|
for _, b := range bufs {
|
||||||
|
if err := u.WriteTo(b, addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *TesterConn) SupportsGSO() bool { return false }
|
||||||
|
|
||||||
func (u *TesterConn) ListenOut(r EncReader, flush func()) error {
|
func (u *TesterConn) ListenOut(r EncReader, flush func()) error {
|
||||||
for {
|
for {
|
||||||
select {
|
p, ok := <-u.RxPackets
|
||||||
case <-u.done:
|
if !ok {
|
||||||
return os.ErrClosed
|
return os.ErrClosed
|
||||||
case p := <-u.RxPackets:
|
}
|
||||||
r(p.From, p.Data)
|
r(p.From, p.Data)
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (u *TesterConn) ReloadConfig(*config.C) {}
|
func (u *TesterConn) ReloadConfig(*config.C) {}
|
||||||
|
|
||||||
@@ -163,8 +158,9 @@ func (u *TesterConn) Rebind() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *TesterConn) Close() error {
|
func (u *TesterConn) Close() error {
|
||||||
u.closeOnce.Do(func() {
|
if u.closed.CompareAndSwap(false, true) {
|
||||||
close(u.done)
|
close(u.RxPackets)
|
||||||
})
|
close(u.TxPackets)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -5,13 +5,14 @@ package udp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
func NewListener(l *logrus.Logger, ip netip.Addr, port int, multi bool, batch int) (Conn, error) {
|
||||||
if multi {
|
if multi {
|
||||||
//NOTE: Technically we can support it with RIO but it wouldn't be at the socket level
|
//NOTE: Technically we can support it with RIO but it wouldn't be at the socket level
|
||||||
// The udp stack would need to be reworked to hide away the implementation differences between
|
// The udp stack would need to be reworked to hide away the implementation differences between
|
||||||
@@ -24,7 +25,7 @@ func NewListener(l *slog.Logger, ip netip.Addr, port int, multi bool, batch int)
|
|||||||
return rc, nil
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Error("Falling back to standard udp sockets", "error", err)
|
l.WithError(err).Error("Falling back to standard udp sockets")
|
||||||
return NewGenericListener(l, ip, port, multi, batch)
|
return NewGenericListener(l, ip, port, multi, batch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-17
@@ -1,10 +1,10 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextualError struct {
|
type ContextualError struct {
|
||||||
@@ -28,12 +28,12 @@ func ContextualizeIfNeeded(msg string, err error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LogWithContextIfNeeded is a helper function to log an error line for an error or ContextualError
|
// LogWithContextIfNeeded is a helper function to log an error line for an error or ContextualError
|
||||||
func LogWithContextIfNeeded(msg string, err error, l *slog.Logger) {
|
func LogWithContextIfNeeded(msg string, err error, l *logrus.Logger) {
|
||||||
switch v := err.(type) {
|
switch v := err.(type) {
|
||||||
case *ContextualError:
|
case *ContextualError:
|
||||||
v.Log(l)
|
v.Log(l)
|
||||||
default:
|
default:
|
||||||
l.Error(msg, "error", err)
|
l.WithError(err).Error(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,19 +51,10 @@ func (ce *ContextualError) Unwrap() error {
|
|||||||
return ce.RealError
|
return ce.RealError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log emits ce as a single error-level log line with Fields and RealError
|
func (ce *ContextualError) Log(lr *logrus.Logger) {
|
||||||
// promoted to top-level attributes, producing a flat shape callers can grep
|
|
||||||
// or parse without walking into a nested object.
|
|
||||||
func (ce *ContextualError) Log(l *slog.Logger) {
|
|
||||||
attrs := make([]slog.Attr, 0, len(ce.Fields)+1)
|
|
||||||
for k, v := range ce.Fields {
|
|
||||||
attrs = append(attrs, slog.Any(k, v))
|
|
||||||
}
|
|
||||||
if ce.RealError != nil {
|
if ce.RealError != nil {
|
||||||
attrs = append(attrs, slog.Any("error", ce.RealError))
|
lr.WithFields(ce.Fields).WithError(ce.RealError).Error(ce.Context)
|
||||||
|
} else {
|
||||||
|
lr.WithFields(ce.Fields).Error(ce.Context)
|
||||||
}
|
}
|
||||||
// LogAttrs is intentional: attrs is built from a map[string]any so it has
|
|
||||||
// no pair-form equivalent.
|
|
||||||
//nolint:sloglint
|
|
||||||
l.LogAttrs(context.Background(), slog.LevelError, ce.Context, attrs...)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-20
@@ -1,67 +1,95 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/test"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type m = map[string]any
|
type m = map[string]any
|
||||||
|
|
||||||
|
type TestLogWriter struct {
|
||||||
|
Logs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestLogWriter() *TestLogWriter {
|
||||||
|
return &TestLogWriter{Logs: make([]string, 0)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl *TestLogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
tl.Logs = append(tl.Logs, string(p))
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl *TestLogWriter) Reset() {
|
||||||
|
tl.Logs = tl.Logs[:0]
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextualError_Log(t *testing.T) {
|
func TestContextualError_Log(t *testing.T) {
|
||||||
buf := &bytes.Buffer{}
|
l := logrus.New()
|
||||||
l := test.NewLoggerWithOutput(buf)
|
l.Formatter = &logrus.TextFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
tl := NewTestLogWriter()
|
||||||
|
l.Out = tl
|
||||||
|
|
||||||
// Test a full context line
|
// Test a full context line
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e := NewContextualError("test message", m{"field": "1"}, errors.New("error"))
|
e := NewContextualError("test message", m{"field": "1"}, errors.New("error"))
|
||||||
e.Log(l)
|
e.Log(l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"test message\" field=1 error=error\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"test message\" error=error field=1\n"}, tl.Logs)
|
||||||
|
|
||||||
// Test a line with an error and msg but no fields
|
// Test a line with an error and msg but no fields
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e = NewContextualError("test message", nil, errors.New("error"))
|
e = NewContextualError("test message", nil, errors.New("error"))
|
||||||
e.Log(l)
|
e.Log(l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"test message\" error=error\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"test message\" error=error\n"}, tl.Logs)
|
||||||
|
|
||||||
// Test just a context and fields
|
// Test just a context and fields
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e = NewContextualError("test message", m{"field": "1"}, nil)
|
e = NewContextualError("test message", m{"field": "1"}, nil)
|
||||||
e.Log(l)
|
e.Log(l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"test message\" field=1\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"test message\" field=1\n"}, tl.Logs)
|
||||||
|
|
||||||
// Test just a context
|
// Test just a context
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e = NewContextualError("test message", nil, nil)
|
e = NewContextualError("test message", nil, nil)
|
||||||
e.Log(l)
|
e.Log(l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"test message\"\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"test message\"\n"}, tl.Logs)
|
||||||
|
|
||||||
// Test just an error
|
// Test just an error
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e = NewContextualError("", nil, errors.New("error"))
|
e = NewContextualError("", nil, errors.New("error"))
|
||||||
e.Log(l)
|
e.Log(l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"\" error=error\n", buf.String())
|
assert.Equal(t, []string{"level=error error=error\n"}, tl.Logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogWithContextIfNeeded(t *testing.T) {
|
func TestLogWithContextIfNeeded(t *testing.T) {
|
||||||
buf := &bytes.Buffer{}
|
l := logrus.New()
|
||||||
l := test.NewLoggerWithOutput(buf)
|
l.Formatter = &logrus.TextFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
DisableColors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
tl := NewTestLogWriter()
|
||||||
|
l.Out = tl
|
||||||
|
|
||||||
// Test ignoring fallback context
|
// Test ignoring fallback context
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
e := NewContextualError("test message", m{"field": "1"}, errors.New("error"))
|
e := NewContextualError("test message", m{"field": "1"}, errors.New("error"))
|
||||||
LogWithContextIfNeeded("This should get thrown away", e, l)
|
LogWithContextIfNeeded("This should get thrown away", e, l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"test message\" field=1 error=error\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"test message\" error=error field=1\n"}, tl.Logs)
|
||||||
|
|
||||||
// Test using fallback context
|
// Test using fallback context
|
||||||
buf.Reset()
|
tl.Reset()
|
||||||
err := fmt.Errorf("this is a normal error")
|
err := fmt.Errorf("this is a normal error")
|
||||||
LogWithContextIfNeeded("Fallback context woo", err, l)
|
LogWithContextIfNeeded("Fallback context woo", err, l)
|
||||||
assert.Equal(t, "level=ERROR msg=\"Fallback context woo\" error=\"this is a normal error\"\n", buf.String())
|
assert.Equal(t, []string{"level=error msg=\"Fallback context woo\" error=\"this is a normal error\"\n"}, tl.Logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextualizeIfNeeded(t *testing.T) {
|
func TestContextualizeIfNeeded(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user