Repository navigation
Expand file tree
/
Copy pathserver_udp_linux.go
More file actions
139 lines (125 loc) · 4.26 KB
/
Copy pathserver_udp_linux.go
File metadata and controls
139 lines (125 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// File: server_udp_linux.go
// Version: 1.6.0
// Last Updated: 15-Oct-2026 18:55 CEST
// Description: Linux-only SO_REUSEPORT UDP listener pool.
// Changes:
// 1.6.0 - [SECURITY/FIX] Wrapped each SO_REUSEPORT UDP worker in `superviseListener`
// and tied socket closure to `shutdownCh`. Previously, any transient runtime
// error from `ActivateAndServe()` triggered `log.Fatalf`, killing the entire
// daemon (including healthy TCP/DoT/DoH/DoQ listeners), and `Shutdown()` did
// not close active UDP sockets.
// 1.5.0 - [SECURITY/FIX] handleUDPDirect performed a bare type assertion to
// *net.UDPAddr and left the client IP empty on failure, producing an
// unidentifiable origin. Combined with the fail-open admission gate
// that existed prior to process_security.go 1.15.0, that meant such a
// query bypassed both the ACL and the rate limiter. Now uses the
// shared, type-agnostic remoteIPFromAddr() helper (server.go 1.36.0).
// 1.4.0 - [LOGGING] Bound port multiplexing allocations organically to `logSystem`.
// 1.3.0 - [PERF/SECURITY] Implemented dynamically scaling UDP ring-buffers
// (`SO_RCVBUF`, `SO_SNDBUF`) bounded natively to the configured
// system `memory_limit_mb`. Severely neutralizes embedded router
// Out-Of-Memory (OOM) killer crashes instigated by 8MB unswappable allocations.
//go:build linux
package main
import (
"context"
"fmt"
"log"
"net"
"syscall"
"github.com/miekg/dns"
"golang.org/x/sys/unix"
)
func startUDPServers(addrs []string, workers int) {
if len(addrs) == 0 {
return
}
if workers <= 0 {
workers = 10
}
for _, addr := range addrs {
addr := addr
for i := 0; i < workers; i++ {
workerID := i
protoLabel := fmt.Sprintf("UDP-worker-%d", workerID)
go superviseListener(protoLabel, addr, func(attempt int) error {
err := runReusePortWorker(addr, workerID, workers)
if err != nil && attempt == 0 {
log.Fatalf("[FATAL] UDP SO_REUSEPORT worker %d on %s: %v", workerID, addr, err)
}
return err
})
}
if logSystem {
log.Printf("[LISTEN] UDP on %s (%d SO_REUSEPORT workers)", addr, workers)
}
}
}
func runReusePortWorker(addr string, _ int, workers int) error {
conn, err := newReusePortConn(addr, workers)
if err != nil {
return err
}
srv := &dns.Server{
PacketConn: conn,
Net: "udp",
Handler: dns.HandlerFunc(handleUDPDirect),
}
served := make(chan struct{})
defer close(served)
go func() {
select {
case <-shutdownCh:
_ = srv.Shutdown()
case <-served:
}
}()
return srv.ActivateAndServe()
}
func handleUDPDirect(w dns.ResponseWriter, r *dns.Msg) {
// [SECURITY 1.5.0] Type-agnostic extraction.
ip := remoteIPFromAddr(w.RemoteAddr())
ProcessDNS(w, r, ip, "UDP", "", "")
}
func newReusePortConn(addr string, workers int) (net.PacketConn, error) {
lc := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
var setSockOptErr error
err := c.Control(func(fd uintptr) {
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil {
setSockOptErr = err
return
}
if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil {
setSockOptErr = err
return
}
totalBudget := 8 * 1024 * 1024 // 8MB total default (unbounded or >=1024MB host)
switch {
case cfg.Server.MemoryLimitMB > 0 && cfg.Server.MemoryLimitMB < 128:
totalBudget = 1024 * 1024
case cfg.Server.MemoryLimitMB >= 128 && cfg.Server.MemoryLimitMB < 512:
totalBudget = 2 * 1024 * 1024
case cfg.Server.MemoryLimitMB >= 512 && cfg.Server.MemoryLimitMB < 1024:
totalBudget = 4 * 1024 * 1024
}
w := workers
if w <= 0 {
w = 1
}
bufferSize := totalBudget / w
const minBufferSize = 64 * 1024 // floor — below this, kernel buffering stops helping under burst load
if bufferSize < minBufferSize {
bufferSize = minBufferSize
}
_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF, bufferSize)
_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF, bufferSize)
})
if err != nil {
return err
}
return setSockOptErr
},
}
return lc.ListenPacket(context.Background(), "udp", addr)
}