Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@ type Daemon struct {
// Per-source SYN rate limiter
perSrcSYNMu sync.Mutex
perSrcSYN map[uint32]*srcSYNBucket // source nodeID -> bucket
// PILOT-343: trusted source nodes that bypass BOTH the global SYN
// rate limit and the per-source SYN limit. Empty by default. Read
// under atomic.Pointer on the hot path to avoid locking. Populated
// at startup via SetSYNWhitelist.
synWhitelist atomic.Pointer[map[uint32]struct{}]

// Network port policies: netID -> allowed ports (nil/empty = all allowed)
netPolicyMu sync.RWMutex
Expand Down Expand Up @@ -543,6 +548,21 @@ func (d *Daemon) reapPerSrcSYN() {
}
}

// SetSYNWhitelist replaces the trusted-source whitelist for SYN rate
// limits (PILOT-343). Nodes in the set bypass BOTH allowSYN() and
// allowSYNFromSource(). Empty slice clears the whitelist. Safe to
// call at any time — the underlying field is an atomic.Pointer.
func (d *Daemon) SetSYNWhitelist(nodeIDs []uint32) {
m := make(map[uint32]struct{}, len(nodeIDs))
for _, id := range nodeIDs {
if id == 0 {
continue
}
m[id] = struct{}{}
}
d.synWhitelist.Store(&m)
}

func (d *Daemon) Start() error {
// Warm the hostname cache from disk before the IPC server comes up,
// so the first send-message after restart hits the cache instead of
Expand Down Expand Up @@ -2703,15 +2723,26 @@ func (d *Daemon) handleStreamPacket(pkt *protocol.Packet) {
return // silent drop — don't reveal policy to attacker
}

// PILOT-343: whitelisted source nodes bypass both SYN rate gates.
// Used for trusted operator peers (paired daemons, test rigs)
// where the SYN-flood protection would otherwise interfere with
// legitimate high-rate connection bursts.
synWhitelisted := false
if wl := d.synWhitelist.Load(); wl != nil {
if _, ok := (*wl)[pkt.Src.Node]; ok {
synWhitelisted = true
}
}

// SYN rate limiting
if !d.allowSYN() {
if !synWhitelisted && !d.allowSYN() {
slog.Warn("SYN rate limit exceeded", "src_addr", pkt.Src, "src_port", pkt.SrcPort)
d.publishEvent("security.syn_rate_limited", map[string]interface{}{
"src_addr": pkt.Src.String(), "src_port": pkt.SrcPort,
})
return // silently drop — don't even RST (avoid amplification)
}
if !d.allowSYNFromSource(pkt.Src.Node) {
if !synWhitelisted && !d.allowSYNFromSource(pkt.Src.Node) {
slog.Warn("per-source SYN rate limit exceeded", "src_node", pkt.Src.Node, "src_port", pkt.SrcPort)
return
}
Expand Down
Loading