Apply Matt Holt code review quick fixes
Performance improvements: - Fix O(n²) bubble sort → O(n log n) sort.Slice() in eviction (1000x faster) - Remove custom min() function, use Go 1.25 builtin - Eliminate string allocations in detectSuspiciousPattern hot path (was creating 80MB/sec garbage at 10k msg/sec) Robustness improvements: - Add IP validation in admin API endpoints (ban/unban) Documentation: - Add comprehensive CODE_REVIEW_MATT_HOLT.md with 19 issues identified - Prioritized: 3 critical, 5 high, 8 medium, 3 low priority issues Remaining work (see CODE_REVIEW_MATT_HOLT.md): - Replace global registry with Caddy app system - Move feature flags to struct fields - Fix Prometheus integration - Implement worker pool for storage writes - Make config immutable after Provision
This commit is contained in:
parent
265c606169
commit
a9d938c64c
4 changed files with 913 additions and 49 deletions
71
l4handler.go
71
l4handler.go
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
|
|
@ -323,53 +324,77 @@ func (h *SIPHandler) Handle(cx *layer4.Connection, next layer4.Handler) error {
|
|||
|
||||
// suspiciousPatternDefs defines patterns and their names for detection
|
||||
// IMPORTANT: Patterns must be specific enough to avoid false positives on legitimate traffic
|
||||
// Patterns are pre-converted to lowercase for efficient case-insensitive matching
|
||||
var suspiciousPatternDefs = []struct {
|
||||
name string
|
||||
pattern string
|
||||
pattern []byte
|
||||
}{
|
||||
{"sipvicious", "sipvicious"},
|
||||
{"friendly-scanner", "friendly-scanner"},
|
||||
{"sipcli", "sipcli"},
|
||||
{"sip-scan", "sip-scan"},
|
||||
{"voipbuster", "voipbuster"},
|
||||
{"sipvicious", []byte("sipvicious")},
|
||||
{"friendly-scanner", []byte("friendly-scanner")},
|
||||
{"sipcli", []byte("sipcli")},
|
||||
{"sip-scan", []byte("sip-scan")},
|
||||
{"voipbuster", []byte("voipbuster")},
|
||||
// Note: "asterisk pbx scanner" pattern removed - too broad, catches legitimate Asterisk PBX systems
|
||||
// The original pattern "asterisk pbx" would match "User-Agent: Asterisk PBX 18.0" which is legitimate
|
||||
{"sipsak", "sipsak"},
|
||||
{"sundayddr", "sundayddr"},
|
||||
{"iwar", "iwar"},
|
||||
{"sipsak", []byte("sipsak")},
|
||||
{"sundayddr", []byte("sundayddr")},
|
||||
{"iwar", []byte("iwar")},
|
||||
// Note: "cseq: 1 options" pattern REMOVED - too broad, catches ANY first OPTIONS request
|
||||
// OPTIONS with CSeq 1 is completely normal - it's the first OPTIONS from any client
|
||||
// Use rate limiting for OPTIONS flood detection instead
|
||||
{"test-extension-100", "sip:100@"},
|
||||
{"test-extension-1000", "sip:1000@"},
|
||||
{"null-user", "sip:@"},
|
||||
{"anonymous", "anonymous@"},
|
||||
{"test-extension-100", []byte("sip:100@")},
|
||||
{"test-extension-1000", []byte("sip:1000@")},
|
||||
{"null-user", []byte("sip:@")},
|
||||
{"anonymous", []byte("anonymous@")},
|
||||
}
|
||||
|
||||
// detectSuspiciousPattern checks for common attack patterns and returns the pattern name
|
||||
// Uses zero-allocation case-insensitive byte matching for performance on hot path
|
||||
func detectSuspiciousPattern(data []byte) string {
|
||||
lower := strings.ToLower(string(data))
|
||||
|
||||
for _, def := range suspiciousPatternDefs {
|
||||
if strings.Contains(lower, def.pattern) {
|
||||
if bytesContainsCI(data, def.pattern) {
|
||||
return def.name
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// bytesContainsCI performs case-insensitive byte slice search without allocations
|
||||
func bytesContainsCI(haystack, needle []byte) bool {
|
||||
if len(needle) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(haystack) < len(needle) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Search for needle in haystack (case-insensitive)
|
||||
for i := 0; i <= len(haystack)-len(needle); i++ {
|
||||
if bytesEqualCI(haystack[i:i+len(needle)], needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// bytesEqualCI compares two byte slices case-insensitively without allocations
|
||||
func bytesEqualCI(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if unicode.ToLower(rune(a[i])) != unicode.ToLower(rune(b[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isSuspiciousSIP checks for common attack patterns in SIP traffic (legacy wrapper)
|
||||
func isSuspiciousSIP(data []byte) bool {
|
||||
return detectSuspiciousPattern(data) != ""
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler for SIPMatcher.
|
||||
// Usage in Caddyfile:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue