mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-20 18:11:27 -07:00
6.9 KiB
6.9 KiB
TURN Gotchas & Troubleshooting
Common mistakes, security best practices, and troubleshooting for Cloudflare TURN.
Quick Reference
| Issue | Solution | Details |
|---|---|---|
| Credentials not working | Check TTL ≤ 48hrs | See Troubleshooting |
| Connection drops after ~48hrs | Implement credential refresh | See Connection Drops |
| Port 53 fails in browser | Filter server-side | See Port 53 |
| High packet loss | Check rate limits | See Rate Limits |
| Connection fails after maintenance | Implement ICE restart | See ICE Restart |
Critical Constraints
| Constraint | Value | Consequence if Violated |
|---|---|---|
| Max credential TTL | 48 hours (172800s) | API rejects request |
| Credential revocation delay | ~seconds | Billing stops immediately, connection drops shortly |
| IP allowlist update window | 14 days (if IPs change) | Connection fails if IPs change |
| Packet rate | 5-10k pps per allocation | Packet drops |
| Data rate | 50-100 Mbps per allocation | Packet drops |
| Unique IP rate | >5 new IPs/sec | Packet drops |
Limits Per TURN Allocation
Per user (not account-wide):
- IP addresses: >5 new unique IPs per second
- Packet rate: 5-10k packets per second (inbound/outbound)
- Data rate: 50-100 Mbps (inbound/outbound)
- MTU: No specific limit
- Burst rates: Higher than documented
Exceeding limits results in packet drops.
Common Mistakes
Setting TTL > 48 hours
// ❌ BAD: API will reject
const creds = await generate({ ttl: 604800 }); // 7 days
// ✅ GOOD:
const creds = await generate({ ttl: 86400 }); // 24 hours
Hardcoding IPs without monitoring
// ❌ BAD: IPs can change with 14-day notice
const iceServers = [{ urls: 'turn:141.101.90.1:3478' }];
// ✅ GOOD: Use DNS
const iceServers = [{ urls: 'turn:turn.cloudflare.com:3478' }];
Using port 53 in browsers
// ❌ BAD: Blocked by Chrome/Firefox
urls: ['turn:turn.cloudflare.com:53']
// ✅ GOOD: Filter port 53
urls: urls.filter(url => !url.includes(':53'))
Not handling credential expiry
// ❌ BAD: Credentials expire but call continues → connection drops
const creds = await fetchCreds();
const pc = new RTCPeerConnection({ iceServers: creds });
// ✅ GOOD: Refresh before expiry
setInterval(() => refreshCredentials(pc), 3000000); // 50 min
Missing ICE restart support
// ❌ BAD: No recovery from TURN maintenance
pc.addEventListener('iceconnectionstatechange', () => {
console.log('State changed:', pc.iceConnectionState);
});
// ✅ GOOD: Implement ICE restart
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed') {
await refreshCredentials(pc);
pc.restartIce();
}
});
Exposing TURN key secret client-side
// ❌ BAD: Secret exposed to client
const secret = 'your-turn-key-secret';
const response = await fetch(`https://rtc.live.cloudflare.com/v1/turn/...`, {
headers: { 'Authorization': `Bearer ${secret}` }
});
// ✅ GOOD: Generate credentials server-side
const response = await fetch('/api/turn-credentials');
ICE Restart Required Scenarios
These events require ICE restart (see patterns.md):
- TURN server maintenance (occasional on Cloudflare's network)
- Network topology changes (anycast routing changes)
- Credential refresh during long sessions (>1 hour)
- Connection failure (iceConnectionState === 'failed')
Implement in all production apps:
pc.addEventListener('iceconnectionstatechange', async () => {
if (pc.iceConnectionState === 'failed' ||
pc.iceConnectionState === 'disconnected') {
await refreshTURNCredentials(pc);
pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// Send offer to peer via signaling...
}
});
Reference: RFC 8445 Section 2.4
Security Checklist
- Credentials generated server-side only (never client-side)
- TURN_KEY_SECRET in wrangler secrets, not vars
- TTL ≤ expected session duration (and ≤ 48 hours)
- Rate limiting on credential generation endpoint
- Client authentication before issuing credentials
- Credential revocation API for compromised sessions
- No hardcoded IPs (or DNS monitoring in place)
- Port 53 filtered for browser clients
Troubleshooting
Issue: TURN credentials not working
Check:
- Key ID and secret are correct
- Credentials haven't expired (check TTL)
- TTL doesn't exceed 172800 seconds (48 hours)
- Server can reach rtc.live.cloudflare.com
- Network allows outbound HTTPS
Solution:
// Validate before using
if (ttl > 172800) {
throw new Error('TTL cannot exceed 48 hours');
}
Issue: Slow connection establishment
Solutions:
- Ensure proper ICE candidate gathering
- Check network latency to Cloudflare edge
- Verify firewall allows WebRTC ports (3478, 5349, 443)
- Consider using TURN over TLS (port 443) for corporate networks
Issue: High packet loss
Check:
- Not exceeding rate limits (5-10k pps)
- Not exceeding bandwidth limits (50-100 Mbps)
- Not connecting to too many unique IPs (>5/sec)
- Client network quality
Issue: Connection drops after ~48 hours
Cause: Credentials expired (48hr max)
Solution:
- Set TTL to expected session duration
- Implement credential refresh with setConfiguration()
- Use ICE restart if connection fails
// Refresh credentials before expiry
const refreshInterval = ttl * 1000 - 60000; // 1 min early
setInterval(async () => {
await refreshTURNCredentials(pc);
}, refreshInterval);
Issue: Port 53 URLs in browser fail silently
Cause: Chrome/Firefox block port 53
Solution: Filter port 53 URLs server-side:
const filtered = urls.filter(url => !url.includes(':53'));
Issue: Hardcoded IPs stop working
Cause: Cloudflare changed IP addresses (14-day notice)
Solution:
- Use DNS hostnames (
turn.cloudflare.com) - Monitor DNS changes with automated alerts
- Update allowlists within 14 days if using IP allowlisting
Cost Optimization
- Use appropriate TTLs (don't over-provision)
- Implement credential caching
- Set
iceTransportPolicy: 'all'to try direct first (use'relay'only when necessary) - Monitor bandwidth usage
- Free when used with Cloudflare Calls SFU
See Also
- api.md - Credential generation API, revocation
- configuration.md - IP allowlisting, monitoring
- patterns.md - ICE restart, credential refresh patterns