| Re | ACK loss diagnosis - missing dialog-aware fast-path |
---
## Root Cause Identified
Your **hypothesis (A)** is correct - ACKs are being dropped by the Layer-4 filter, but not for the reason you'd expect. It's not pattern-matching false-positives; it's **architectural - ACKs lack a dialog-aware fast-path**.
### The Bug
In `l4handler.go:128-333` (`Handle()` function), **every SIP request** goes through this pipeline:
```
1. Ban check → close if banned
2. Whitelist check → SKIP TO PROXY if whitelisted ← THIS IS KEY
3. GeoIP check → close if blocked country
4. Read buffer (4KB)
5. SIP validation → close if critical violation
6. Rate limiting (by method) → close if exceeded
7. Enumeration detection → close if pattern detected
8. Suspicious pattern matching → record failure
9. Pass to proxy
```
**ACK is NOT whitelisted**, so it runs through steps 5-8. Three failure modes:
**Problem:** `ExtractTargetExtension()` is parsing the ACK Request-URI. If Twilio's ACK has a Request-URI like `ACK sip:+14063256436@asterpbx-supsys.pstn.twilio.com`, the extension extractor might:
- **Trigger sequential/rapid-fire enumeration ban** after enough ACKs arrive
This would explain the **~64s timing** - Twilio sends ACK immediately after your 200 OK, so the first ACK arrives at t+0. Asterisk retransmits 200 OK at exponential backoff (t+0.5, t+1, t+2, t+4, t+8, t+16, t+32), and if Twilio re-sends ACK for each retransmission (RFC 3261 compliant behavior), you'd see:
If your rapid-fire threshold is 10 ACKs in 30s, it might take longer, but the **pattern fits**.
### Failure Mode 2: Rate Limiting (lines 232-248)
```go
method := ExtractSIPMethod(buf)
if method != "" {
rl := GetRateLimiter(h.logger)
if allowed, reason := rl.Allow(host, method); !allowed {
h.guardian.RecordFailure(host, reason)
return cx.Close()
}
}
```
If you have per-method rate limits and Twilio is sending multiple ACKs (retransmissions in response to Asterisk's 200 OK retransmissions), the ACK rate limit could be triggered.
Less likely for ACKs, but if the ACK body contains anything matching `sipvicious`, `friendly-scanner`, etc., it'd record a failure.
---
## Why This Doesn't Affect INVITE
Your INVITE flow works because:
1. First INVITE from Twilio arrives
2. Not banned, not whitelisted → runs through checks
3. Passes validation, rate limit, no enumeration (first extension)
4. Proxies to asterpbx
5. Asterisk sends 200 OK back
The 200 OK **response** doesn't go through the handler (it's a response, not a request), so Twilio receives it cleanly.
But the ACK that Twilio sends back **is a request**, so it hits the same handler, and the enumeration detector has been tracking Twilio's IP across multiple calls/extensions.
---
## Proof Path
### 1. Check sip-guardian logs for enumeration ban
```bash
docker logs caddy-sip-guardian 2>&1 | \
grep -E "(Enumeration|enumeration)" | \
grep -E "(twilio|5.163)" | \ # Twilio's source IP range
**For Twilio specifically:** Option C (whitelist) is the right operational fix - Twilio uses published IP ranges and your trunk contract already covers scanner protection.
**For the general case (dynamic IP clients):** This bug affects ANY client that can't be whitelisted:
- Residential users with dynamic IPs
- Mobile SIP clients
- Peer-to-peer SIP scenarios
- Any carrier that doesn't publish static ranges
**Therefore: Implement Option B + Option A regardless of Twilio fix**
The architectural fix benefits everyone, not just Twilio. The troubleshooting reveals a production-impacting bug for dynamic clients.
---
## Next Steps
**What I need from you:**
1. Confirm you want Option B quick-fix deployed
2. Test against Twilio trunk to verify calls survive past 64s
3. Report back via `003-...` whether fix resolves the issue
**What I'll deliver:**
- Patched `l4handler.go` with ACK enumeration exemption
- Test confirming ACKs pass through
- (Optional) Full dialog-aware fast-path implementation
Timeline: Quick-fix can be ready in 30 minutes. Full fix requires testing dialog state integration (~2 hours).
---
**Next steps for recipient (flextel):**
- [ ] Review diagnosis and choose fix option (A/B/C)
- [ ] Confirm I should proceed with implementation