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
Caddy SIP Guardian
A comprehensive Caddy module providing SIP-aware security at Layer 4. Protects your VoIP infrastructure with intelligent rate limiting, attack detection, message validation, and topology hiding.
Why SIP Guardian?
Traditional SIP security (like fail2ban) parses logs after attacks reach your PBX. SIP Guardian operates at Layer 4, blocking threats before they touch your infrastructure:
| Traditional Approach | SIP Guardian |
|---|---|
| Log parsing delay | Real-time blocking |
| Regex-based detection | Protocol-aware analysis |
| Separate fail2ban config | Single Caddyfile |
| No topology protection | Full B2BUA-lite hiding |
| Manual IP management | Auto-ban with API control |
Features
🛡️ Core Protection
- Layer 4 SIP Proxying — Handle SIP traffic (UDP/TCP/TLS) before it reaches your PBX
- Intelligent Rate Limiting — Per-method token bucket rate limiting with burst support
- Automatic Banning — Ban IPs that exceed failure thresholds
- Attack Detection — Detect common SIP scanning tools (SIPVicious, friendly-scanner, etc.)
- CIDR Whitelisting — Whitelist trusted networks by IP range
- DNS-aware Whitelisting — Whitelist SIP trunks by hostname or SRV record with auto-refresh
- GeoIP Blocking — Block traffic by country using MaxMind databases
🔍 Extension Enumeration Detection
- Count-based Detection — Block IPs probing too many unique extensions
- Sequential Pattern Detection — Detect numeric extension scanning (100, 101, 102...)
- Rapid-fire Detection — Catch high-speed enumeration attempts
- Configurable Exemptions — Whitelist common extensions like voicemail
✅ SIP Message Validation
- RFC 3261 Compliance — Enforce required headers and message structure
- Injection Prevention — Block NULL bytes and binary injection attacks
- Content-Length Validation — Detect body/header mismatches
- Multiple Modes — Permissive, strict, or paranoid validation
🔒 Topology Hiding (B2BUA-lite)
- Via Header Rewriting — Hide internal proxy chain
- Contact Header Rewriting — Mask internal IP addresses
- Sensitive Header Stripping — Remove P-Asserted-Identity, Server, etc.
- Call-ID Anonymization — Prevent dialog correlation attacks
- Private IP Masking — Automatically hide RFC 1918 addresses
📊 Observability
- Prometheus Metrics — Comprehensive metrics for monitoring
- Webhook Notifications — Real-time alerts for security events
- SQLite Persistence — Durable ban storage across restarts
- Admin API — RESTful API for management and stats
Architecture
Internet
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Caddy SIP Guardian (Layer 4) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. SIP Matcher ─ Identifies SIP traffic │ │
│ │ 2. Ban Check ─ Reject banned IPs │ │
│ │ 3. Whitelist Check ─ Skip checks for trusted IPs │ │
│ │ 4. GeoIP Check ─ Block by country │ │
│ │ 5. Validation ─ RFC 3261 compliance │ │
│ │ 6. Pattern Detection ─ Scanner fingerprinting │ │
│ │ 7. Enumeration Check ─ Extension scanning detection │ │
│ │ 8. Rate Limiting ─ Per-method token buckets │ │
│ │ 9. Topology Hiding ─ Header rewriting (optional) │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ FreePBX / Asterisk / Kamailio │
│ (Protected from scanners, enumeration, topology leaks) │
└──────────────────────────────────────────────────────────────────┘
Quick Start
# Clone the repository
git clone https://git.supported.systems/rsp2k/caddy-sip-guardian.git
cd caddy-sip-guardian
# Build the custom Caddy image
make build
# Start the stack
make run
# View logs
make logs
# Run tests (60 tests)
make test
Configuration
Minimal Configuration
{
layer4 {
udp/:5060 {
@sip sip
route @sip {
sip_guardian {
max_failures 5
ban_time 1h
whitelist 10.0.0.0/8 192.168.0.0/16
}
proxy udp/asterisk:5060
}
}
}
}
Full Configuration
Click to expand complete Caddyfile example
{
layer4 {
# UDP SIP (standard port)
udp/:5060 {
@sip sip {
methods REGISTER INVITE OPTIONS ACK BYE CANCEL INFO NOTIFY SUBSCRIBE MESSAGE
}
route @sip {
sip_guardian {
# Core settings
max_failures 5
find_time 10m
ban_time 1h
whitelist 10.0.0.0/8 192.168.0.0/16
# GeoIP blocking (optional)
geoip_db /etc/caddy/GeoLite2-Country.mmdb
blocked_countries CN RU
# Per-method rate limiting
rate_limit {
register 10/s burst 20
invite 5/s burst 10
options 20/s burst 50
}
# Extension enumeration detection
enumeration {
max_extensions 20
extension_window 5m
sequential_threshold 5
rapid_fire_count 10
rapid_fire_window 30s
ban_time 2h
exempt_extensions 100 200 9999
}
# SIP message validation
validation {
enabled true
mode strict
max_message_size 65535
ban_on_null_bytes true
ban_on_binary_injection true
}
# Prometheus metrics
metrics {
enabled true
}
# Webhook notifications
webhooks {
url https://hooks.example.com/sip-alerts
events ban unban attack enumeration
}
# SQLite persistence
storage {
path /var/lib/caddy/sip_guardian.db
}
}
# Optional: Topology hiding
sip_topology_hider {
proxy_host 203.0.113.1
proxy_port 5060
upstream udp/192.168.1.100:5060
rewrite_via
rewrite_contact
strip_headers P-Preferred-Identity P-Asserted-Identity Server User-Agent
hide_private_ips
# anonymize_call_id # Optional: randomize Call-IDs
}
proxy udp/freepbx:5060
}
}
# TCP SIP
tcp/:5060 {
@sip sip
route @sip {
sip_guardian { ... }
proxy tcp/freepbx:5060
}
}
# SIP over TLS
tcp/:5061 {
@sip tls sni sip.example.com
route @sip {
sip_guardian { ... }
tls
proxy tcp/freepbx:5060
}
}
}
}
# Admin API
:2020 {
handle /api/sip-guardian/* {
sip_guardian_admin
}
# Prometheus metrics endpoint
handle /metrics {
metrics
}
}
Environment Variables
| Variable | Default | Description |
|---|---|---|
SIP_UPSTREAM_HOST |
freepbx |
Upstream SIP server hostname |
SIP_UPSTREAM_PORT |
5060 |
Upstream SIP port |
SIP_GUARDIAN_MAX_FAILURES |
5 |
Failures before ban |
SIP_GUARDIAN_FIND_TIME |
10m |
Time window for counting failures |
SIP_GUARDIAN_BAN_TIME |
1h |
Ban duration |
Feature Details
Extension Enumeration Detection
Protects against tools like SIPVicious svwar that scan for valid extensions:
enumeration {
max_extensions 20 # Ban after 20 unique extensions probed
extension_window 5m # Within this time window
sequential_threshold 5 # Ban if 5+ consecutive extensions (100,101,102...)
rapid_fire_count 10 # Ban if 10+ extensions in rapid_fire_window
rapid_fire_window 30s
ban_time 2h # Enumeration bans last longer
exempt_extensions 100 200 9999 # Don't count these (voicemail, etc.)
}
Detection Methods:
| Method | Trigger | Use Case |
|---|---|---|
| Count Threshold | 20+ unique extensions | Catches slow scanners |
| Sequential Pattern | 5+ consecutive numbers | Detects svwar immediately |
| Rapid Fire | 10+ in 30 seconds | Catches fast automated scans |
DNS-aware Whitelisting
Whitelist SIP trunks and providers by hostname or SRV record. IPs are automatically resolved and refreshed:
sip_guardian {
# Static CIDR whitelist (always available)
whitelist 10.0.0.0/8 192.168.0.0/16
# DNS-aware whitelist - resolved to IPs automatically
whitelist_hosts pbx.example.com trunk.sipcarrier.net
whitelist_srv _sip._udp.provider.com _sip._tcp.carrier.net
dns_refresh 5m # How often to refresh DNS lookups (default: 5m)
}
Why DNS-aware whitelisting?
| Static IP Whitelisting | DNS-aware Whitelisting |
|---|---|
| Breaks when provider changes IPs | Auto-updates when IPs change |
| Must manually track carrier IPs | Just use their SRV record |
| Fails silently on changes | Logs refresh events |
SRV Record Support:
SIP trunks commonly use SRV records for load balancing and failover. SIP Guardian resolves the full chain:
_sip._udp.carrier.com → sip1.carrier.com, sip2.carrier.com → 203.0.113.10, 203.0.113.11
Admin API Endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/sip-guardian/dns-whitelist |
List all resolved DNS entries |
POST |
/api/sip-guardian/dns-whitelist/refresh |
Force immediate DNS refresh |
SIP Message Validation
Enforces RFC 3261 compliance and blocks malformed/malicious packets:
validation {
enabled true
mode strict # permissive, strict, or paranoid
max_message_size 65535 # Reject oversized messages
ban_on_null_bytes true # Immediate ban for NULL byte injection
ban_on_binary_injection true
disabled_rules via_invalid_branch # Skip specific rules
}
Validation Modes:
| Mode | Behavior |
|---|---|
| Permissive | Log violations, only block critical attacks |
| Strict | Enforce RFC 3261 required headers |
| Paranoid | Additional heuristics for edge cases |
Validation Rules:
| Rule | Severity | Action |
|---|---|---|
null_bytes |
🔴 CRITICAL | Immediate ban |
binary_injection |
🔴 CRITICAL | Immediate ban |
missing_via |
🟠 HIGH | Count toward ban |
missing_from |
🟠 HIGH | Count toward ban |
missing_to |
🟠 HIGH | Count toward ban |
missing_call_id |
🟠 HIGH | Count toward ban |
missing_cseq |
🟠 HIGH | Count toward ban |
content_length_mismatch |
🟠 HIGH | Count toward ban |
oversized_message |
🟠 HIGH | Count toward ban |
invalid_request_uri |
🟡 MEDIUM | Reject only |
Topology Hiding
Hide your internal infrastructure from external attackers:
sip_topology_hider {
# Your public-facing address
proxy_host 203.0.113.1
proxy_port 5060
# Internal PBX (never exposed)
upstream udp/192.168.1.100:5060
rewrite_via # Add proxy Via, remove on response
rewrite_contact # Replace internal Contact URIs
strip_headers P-Preferred-Identity P-Asserted-Identity Server User-Agent
hide_private_ips # Replace RFC 1918 addresses
# anonymize_call_id # Optional: randomize Call-IDs
}
What It Hides:
| Header/Field | Before | After |
|---|---|---|
| Via | 192.168.1.100:5060 |
proxy.example.com:5060 |
| Contact | <sip:100@192.168.1.100> |
<sip:100@proxy.example.com> |
| Server | Asterisk PBX 18.x |
(removed) |
| P-Asserted-Identity | <sip:100@internal.lan> |
(removed) |
Request Flow:
External UA ──────► Caddy ──────► Asterisk
│
├─ Adds Via: SIP/2.0/UDP proxy.example.com;branch=z9hG4bK...
├─ Rewrites Contact: <sip:proxy.example.com:5060>
└─ Strips: Server, P-Asserted-Identity
Response Flow:
Asterisk ──────► Caddy ──────► External UA
│
├─ Removes top Via (proxy's)
└─ Dialog state routes response correctly
Admin API
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/sip-guardian/bans |
List all banned IPs |
GET |
/api/sip-guardian/stats |
View statistics |
POST |
/api/sip-guardian/ban/{ip} |
Manually ban an IP |
DELETE |
/api/sip-guardian/unban/{ip} |
Remove IP from ban list |
GET |
/api/sip-guardian/enumeration/stats |
Enumeration statistics |
GET |
/api/sip-guardian/validation/stats |
Validation statistics |
Examples
List Banned IPs
curl http://localhost:2020/api/sip-guardian/bans
{
"bans": [
{
"ip": "185.224.128.0",
"reason": "scanner_detected",
"banned_at": "2024-01-15T10:30:00Z",
"expires_at": "2024-01-15T11:30:00Z"
}
],
"total": 1
}
View Stats
curl http://localhost:2020/api/sip-guardian/stats
{
"total_requests": 15234,
"blocked_requests": 423,
"active_bans": 12,
"enumeration_detections": 5,
"validation_failures": 89,
"rate_limited": 156
}
Manually Ban IP
curl -X POST http://localhost:2020/api/sip-guardian/ban/192.168.1.100 \
-H "Content-Type: application/json" \
-d '{"reason": "manual_ban", "duration": "24h"}'
Unban IP
curl -X DELETE http://localhost:2020/api/sip-guardian/unban/192.168.1.100
Prometheus Metrics
# Core metrics
sip_guardian_requests_total{method="REGISTER"}
sip_guardian_blocked_total{reason="banned"}
sip_guardian_active_bans
# Rate limiting
sip_guardian_rate_limited_total{method="INVITE"}
# Enumeration detection
sip_guardian_enumeration_detections_total{reason="sequential_pattern"}
sip_guardian_enumeration_tracked_ips
# Validation
sip_guardian_validation_violations_total{rule="missing_via"}
sip_guardian_validation_results_total{result="valid"}
sip_guardian_message_size_bytes
# GeoIP
sip_guardian_geoip_blocked_total{country="CN"}
Integration Examples
FreePBX / Asterisk
{
layer4 {
udp/:5060 {
@sip sip
route @sip {
sip_guardian {
max_failures 3
ban_time 24h
whitelist 10.0.0.0/8
}
proxy udp/freepbx:5060
}
}
}
}
Kamailio (as SBC)
{
layer4 {
udp/:5060 {
@sip sip
route @sip {
sip_guardian {
max_failures 5
ban_time 1h
}
sip_topology_hider {
proxy_host sbc.example.com
proxy_port 5060
upstream udp/kamailio:5060
rewrite_via
rewrite_contact
}
proxy udp/kamailio:5060
}
}
}
}
High Availability Setup
{
layer4 {
udp/:5060 {
@sip sip
route @sip {
sip_guardian {
storage {
# Shared storage for HA
path /shared/sip_guardian.db
}
}
# Load balance across PBX cluster
proxy udp/pbx1:5060 udp/pbx2:5060 udp/pbx3:5060 {
lb_policy round_robin
health_check interval=30s
}
}
}
}
}
Troubleshooting
Common Issues
Legitimate users getting banned
Symptoms: Users report being unable to connect; they appear in ban list.
Solutions:
- Add their network to the whitelist:
whitelist 10.0.0.0/8 192.168.0.0/16 YOUR.NETWORK.0.0/16 - Increase
max_failuresthreshold - Check if their client sends malformed packets (validation logs)
- Temporarily unban:
curl -X DELETE http://localhost:2020/api/sip-guardian/unban/IP
SIP traffic not being matched
Symptoms: Traffic passes through but SIP Guardian doesn't process it.
Solutions:
- Verify the matcher syntax:
@sip sip { methods REGISTER INVITE OPTIONS } - Check Caddy logs for matcher errors
- Ensure traffic is arriving on the correct port (UDP vs TCP)
- Test with
tcpdump -i any port 5060to verify traffic flow
Topology hiding breaks calls
Symptoms: Calls connect but audio is one-way or missing.
Solutions:
- This is usually an RTP/media issue, not SIP signaling
- Ensure RTP ports are forwarded through your firewall
- Check if
hide_private_ipsis replacing IPs in SDP body - Verify NAT traversal settings on your PBX
High memory usage
Symptoms: Caddy process memory grows over time.
Solutions:
- Dialog/transaction state cleanup runs automatically, but check TTL settings
- Reduce
extension_windowto clean up enumeration tracking faster - Check for memory leaks with
go tool pprof - Ensure cleanup goroutines are running (check logs)
GeoIP database not loading
Symptoms: Country blocking not working; errors about mmdb file.
Solutions:
- Download the database:
wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&suffix=tar.gz - Verify path in config matches actual file location
- Check file permissions (Caddy needs read access)
- Ensure you're using GeoLite2-Country, not City database
Debug Mode
Enable verbose logging:
{
debug
layer4 {
# ... your config
}
}
Check logs:
docker logs caddy-sip-guardian 2>&1 | grep -E "(sip_guardian|topology)"
Building from Source
# Using xcaddy
xcaddy build \
--with github.com/mholt/caddy-l4 \
--with git.supported.systems/rsp2k/caddy-sip-guardian
# Or with local development
xcaddy build \
--with github.com/mholt/caddy-l4 \
--with git.supported.systems/rsp2k/caddy-sip-guardian=/path/to/local/module
Running Tests
# Run all unit tests (60 tests)
make test
# Run with verbose output
docker run --rm -v $(pwd):/app -w /app golang:1.25 go test -v ./...
# Test specific feature
go test -v -run TestEnumeration ./...
go test -v -run TestValidation ./...
go test -v -run TestTopology ./...
Changelog
v0.3.0 (2024-12)
- ✨ Topology Hiding — B2BUA-lite functionality for hiding internal infrastructure
- ✨ SIP Message Parsing — Full RFC 3261 compliant parser with header manipulation
- ✨ Dialog State Management — Stateful response routing for topology hiding
- 🐛 Fixed PRNG overflow in branch generation
v0.2.0 (2024-12)
- ✨ SIP Message Validation — RFC 3261 compliance checking
- ✨ Injection Detection — NULL byte and binary injection blocking
- ✨ Validation Modes — Permissive, strict, and paranoid modes
v0.1.0 (2024-12)
- ✨ Extension Enumeration Detection — Sequential pattern and rapid-fire detection
- ✨ Per-method Rate Limiting — Token bucket with configurable burst
- ✨ GeoIP Blocking — Country-based blocking with MaxMind
- ✨ Prometheus Metrics — Comprehensive observability
- ✨ Webhook Notifications — Real-time security alerts
- ✨ SQLite Persistence — Durable ban storage
Security Considerations
| Consideration | Recommendation |
|---|---|
| Whitelist internal networks | Always add your LAN to prevent self-blocking |
| Start permissive | Begin with permissive validation, tighten after monitoring |
| Monitor false positives | Watch metrics for legitimate traffic being blocked |
| Keep GeoIP updated | Refresh MaxMind databases monthly |
| Use webhooks | Configure alerts for immediate security notification |
| Protect admin API | Don't expose :2020 to the internet |
Detected Attack Patterns
SIP Guardian automatically detects:
| Pattern | Detection Method |
|---|---|
| SIPVicious | User-Agent fingerprinting |
| friendly-scanner | User-Agent fingerprinting |
| sipcli / sip-scan | User-Agent fingerprinting |
| Sequential scanning | Extension pattern analysis |
| NULL byte injection | Binary content inspection |
| Malformed packets | RFC 3261 validation |
License
MIT — see LICENSE for details.
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
Related Projects
- caddy-l4 — Layer 4 proxy for Caddy
- Caddy — The HTTP/2 web server with automatic HTTPS
- SIPVicious — SIP security testing tools (for testing your setup)