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:
Ryan Malloy 2025-12-24 21:53:28 -07:00
parent 265c606169
commit a9d938c64c
4 changed files with 913 additions and 49 deletions

View file

@ -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: