smartproxy/Connection-Termination-Issues.md
2025-05-19 12:04:26 +00:00

248 lines
5.9 KiB
Markdown

# Connection Termination Issues and Solutions in SmartProxy/NetworkProxy
## Common Connection Termination Scenarios
### 1. Normal Connection Closure
**Flow**:
- Client or server initiates graceful close
- 'close' event triggers cleanup
- Connection removed from tracking
- Resources freed
**Code Path**:
```typescript
// In ConnectionManager
handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) {
record.incomingTerminationReason = 'normal';
this.initiateCleanupOnce(record, 'closed_' + side);
}
```
### 2. Error-Based Termination
**Common Errors**:
- ECONNRESET: Connection reset by peer
- ETIMEDOUT: Connection timed out
- ECONNREFUSED: Connection refused
- EHOSTUNREACH: Host unreachable
**Handling**:
```typescript
handleError(side: 'incoming' | 'outgoing', record: IConnectionRecord) {
return (err: Error) => {
const code = (err as any).code;
let reason = 'error';
if (code === 'ECONNRESET') {
reason = 'econnreset';
} else if (code === 'ETIMEDOUT') {
reason = 'etimedout';
}
this.initiateCleanupOnce(record, reason);
};
}
```
### 3. Inactivity Timeout
**Detection**:
```typescript
performInactivityCheck(): void {
const now = Date.now();
for (const record of this.connectionRecords.values()) {
const inactivityTime = now - record.lastActivity;
if (inactivityTime > effectiveTimeout) {
this.cleanupConnection(record, 'inactivity');
}
}
}
```
**Special Cases**:
- Keep-alive connections get extended timeouts
- "Immortal" connections bypass inactivity checks
- Warning issued before closure for keep-alive connections
### 4. NFTables-Handled Connections
**Special Handling**:
```typescript
if (route.action.forwardingEngine === 'nftables') {
socket.end();
record.nftablesHandled = true;
this.connectionManager.initiateCleanupOnce(record, 'nftables_handled');
return;
}
```
These connections are:
- Handled at kernel level
- Closed immediately at application level
- Tracked for metrics only
### 5. NetworkProxy Bridge Termination
**Bridge Cleanup**:
```typescript
const cleanup = (reason: string) => {
socket.unpipe(proxySocket);
proxySocket.unpipe(socket);
proxySocket.destroy();
cleanupCallback(reason);
};
socket.on('end', () => cleanup('socket_end'));
socket.on('error', () => cleanup('socket_error'));
proxySocket.on('end', () => cleanup('proxy_end'));
proxySocket.on('error', () => cleanup('proxy_error'));
```
## Preventing Connection Leaks
### 1. Always Remove Event Listeners
```typescript
cleanupConnection(record: IConnectionRecord, reason: string): void {
if (record.incoming) {
record.incoming.removeAllListeners('data');
record.renegotiationHandler = undefined;
}
}
```
### 2. Clear Timers
```typescript
if (record.cleanupTimer) {
clearTimeout(record.cleanupTimer);
record.cleanupTimer = undefined;
}
```
### 3. Proper Socket Cleanup
```typescript
private cleanupSocket(record: IConnectionRecord, side: string, socket: net.Socket): void {
try {
if (!socket.destroyed) {
socket.end(); // Graceful
setTimeout(() => {
if (!socket.destroyed) {
socket.destroy(); // Forced
}
}, 1000);
}
} catch (err) {
console.log(`Error closing ${side} socket: ${err}`);
}
}
```
### 4. Connection Record Cleanup
```typescript
// Clear pending data to prevent memory leaks
record.pendingData = [];
record.pendingDataSize = 0;
// Remove from tracking map
this.connectionRecords.delete(record.id);
```
## Monitoring and Debugging
### 1. Termination Statistics
```typescript
private terminationStats: {
incoming: Record<string, number>;
outgoing: Record<string, number>;
} = { incoming: {}, outgoing: {} };
incrementTerminationStat(side: 'incoming' | 'outgoing', reason: string): void {
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
}
```
### 2. Connection Logging
**Detailed Logging**:
```typescript
console.log(
`[${record.id}] Connection from ${record.remoteIP} terminated (${reason}).` +
` Duration: ${prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}`
);
```
### 3. Active Connection Tracking
```typescript
getConnectionCount(): number {
return this.connectionRecords.size;
}
// In NetworkProxy
metrics = {
activeConnections: this.connectedClients,
portProxyConnections: this.portProxyConnections,
tlsTerminatedConnections: this.tlsTerminatedConnections
};
```
## Best Practices for Connection Termination
1. **Always Use initiateCleanupOnce()**:
- Prevents duplicate cleanup operations
- Ensures proper termination reason tracking
2. **Handle All Socket Events**:
- 'error', 'close', 'end' events
- Both incoming and outgoing sockets
3. **Implement Proper Timeouts**:
- Initial data timeout
- Inactivity timeout
- Maximum connection lifetime
4. **Track Resources**:
- Connection records
- Socket maps
- Timer references
5. **Log Termination Reasons**:
- Helps debug connection issues
- Provides metrics for monitoring
6. **Graceful Shutdown**:
- Try socket.end() before socket.destroy()
- Allow time for graceful closure
7. **Memory Management**:
- Clear pending data buffers
- Remove event listeners
- Delete connection records
## Common Issues and Solutions
### Issue: Memory Leaks from Event Listeners
**Solution**: Always call removeAllListeners() during cleanup
### Issue: Orphaned Connections
**Solution**: Implement multiple cleanup triggers (timeout, error, close)
### Issue: Duplicate Cleanup Operations
**Solution**: Use connectionClosed flag and initiateCleanupOnce()
### Issue: Hanging Connections
**Solution**: Implement inactivity checks and maximum lifetime limits
### Issue: Resource Exhaustion
**Solution**: Track connection counts and implement limits
### Issue: Lost Data During Cleanup
**Solution**: Use proper unpipe operations and graceful shutdown
### Issue: Debugging Connection Issues
**Solution**: Track termination reasons and maintain detailed logs