feat(proxy): Implement WrappedSocket class for PROXY protocol support and update connection handling
This commit is contained in:
155
readme.plan.md
155
readme.plan.md
@ -1,5 +1,16 @@
|
||||
# PROXY Protocol Implementation Plan
|
||||
|
||||
## ⚠️ CRITICAL: Implementation Order
|
||||
|
||||
**Phase 1 (ProxyProtocolSocket/WrappedSocket) MUST be completed first!**
|
||||
|
||||
The ProxyProtocolSocket class is the foundation that enables all PROXY protocol functionality. No protocol parsing or integration can happen until this wrapper class is fully implemented and tested.
|
||||
|
||||
1. **FIRST**: Implement ProxyProtocolSocket (the WrappedSocket)
|
||||
2. **THEN**: Add PROXY protocol parser
|
||||
3. **THEN**: Integrate with connection handlers
|
||||
4. **FINALLY**: Add security and validation
|
||||
|
||||
## Overview
|
||||
Implement PROXY protocol support in SmartProxy to preserve client IP information through proxy chains, solving the connection limit accumulation issue where inner proxies see all connections as coming from the outer proxy's IP.
|
||||
|
||||
@ -56,31 +67,60 @@ interface IRouteAction {
|
||||
|
||||
### 3. Implementation Steps
|
||||
|
||||
#### Phase 1: WrappedSocket Foundation
|
||||
1. Create minimal `ProxyProtocolSocket` class in `ts/core/models/proxy-protocol-socket.ts`
|
||||
2. Implement basic socket wrapper with real IP getters
|
||||
3. Update `ConnectionManager` to accept wrapped sockets
|
||||
4. Create tests for wrapped socket functionality
|
||||
#### IMPORTANT: Phase 1 Must Be Completed First
|
||||
The `ProxyProtocolSocket` (WrappedSocket) is the foundation for all PROXY protocol functionality. This wrapper class must be implemented and integrated BEFORE any PROXY protocol parsing can begin.
|
||||
|
||||
#### Phase 1: ProxyProtocolSocket (WrappedSocket) Foundation - ✅ COMPLETED
|
||||
This phase creates the socket wrapper infrastructure that all subsequent phases depend on.
|
||||
|
||||
1. **Create WrappedSocket class** in `ts/core/models/wrapped-socket.ts` ✅
|
||||
- Basic socket wrapper that extends EventEmitter
|
||||
- Properties for real client IP and port
|
||||
- Transparent getters that return real or socket IP/port
|
||||
- Pass-through methods for all socket operations
|
||||
|
||||
2. **Implement core wrapper functionality** ✅
|
||||
- Constructor accepts regular socket + optional metadata
|
||||
- `remoteAddress` getter returns real IP or falls back to socket IP
|
||||
- `remotePort` getter returns real port or falls back to socket port
|
||||
- `isFromTrustedProxy` property to check if it has real client info
|
||||
- `setProxyInfo()` method to update real client details
|
||||
|
||||
3. **Update ConnectionManager to handle wrapped sockets** ✅
|
||||
- Accept either `net.Socket` or `WrappedSocket`
|
||||
- Use duck typing or instanceof checks
|
||||
- Ensure all IP lookups use the getter methods
|
||||
|
||||
4. **Create comprehensive tests** ✅
|
||||
- Test wrapper with and without proxy info
|
||||
- Verify getter fallback behavior
|
||||
- Test event forwarding
|
||||
- Test socket method pass-through
|
||||
|
||||
**Deliverables**: ✅ Working WrappedSocket that can wrap any socket and provide transparent access to client info.
|
||||
|
||||
#### Phase 2: PROXY Protocol Parser - DEPENDS ON PHASE 1
|
||||
Only after WrappedSocket is working can we add protocol parsing.
|
||||
|
||||
#### Phase 2: PROXY Protocol Parser
|
||||
1. Create `ProxyProtocolParser` class in `ts/core/utils/proxy-protocol.ts`
|
||||
2. Implement v1 text format parsing
|
||||
3. Add validation and error handling
|
||||
4. Integrate parser into ProxyProtocolSocket
|
||||
4. Integrate parser to work WITH WrappedSocket (not into it)
|
||||
|
||||
#### Phase 3: Connection Handler Integration
|
||||
1. Modify `RouteConnectionHandler` to use ProxyProtocolSocket for trusted IPs
|
||||
2. Parse PROXY header before TLS detection
|
||||
3. Use wrapped socket throughout connection lifecycle
|
||||
4. Strip PROXY header before forwarding data
|
||||
#### Phase 3: Connection Handler Integration - DEPENDS ON PHASES 1 & 2
|
||||
1. ✅ Modify `RouteConnectionHandler` to create WrappedSocket for all connections
|
||||
2. Check if connection is from trusted proxy IP
|
||||
3. If trusted, attempt to parse PROXY protocol header
|
||||
4. Update wrapped socket with real client info
|
||||
5. Continue normal connection handling with wrapped socket
|
||||
|
||||
#### Phase 4: Outbound PROXY Protocol
|
||||
#### Phase 4: Outbound PROXY Protocol - DEPENDS ON PHASES 1-3
|
||||
1. Add PROXY header generation in `setupDirectConnection`
|
||||
2. Make it configurable per route
|
||||
3. Send header immediately after TCP connection
|
||||
4. Use ProxyProtocolSocket for outbound connections too
|
||||
|
||||
#### Phase 5: Security & Validation
|
||||
#### Phase 5: Security & Validation - FINAL PHASE
|
||||
1. Validate PROXY headers strictly
|
||||
2. Reject malformed headers
|
||||
3. Only accept from trusted IPs
|
||||
@ -112,37 +152,102 @@ Start with **Option A** (ProxyProtocolSocket) for immediate PROXY protocol suppo
|
||||
- Code simplification benefits
|
||||
- Team comfort with architectural change
|
||||
|
||||
### 5. Code Changes (Using Option A Initially)
|
||||
### 5. Code Implementation Details
|
||||
|
||||
#### 5.1 ProxyProtocolSocket (WrappedSocket) - PHASE 1 IMPLEMENTATION
|
||||
This is the foundational wrapper class that MUST be implemented first. It wraps a regular socket and provides transparent access to the real client IP/port.
|
||||
|
||||
#### 5.1 ProxyProtocolSocket (new file)
|
||||
```typescript
|
||||
// ts/core/models/proxy-protocol-socket.ts
|
||||
export class ProxyProtocolSocket {
|
||||
import { EventEmitter } from 'events';
|
||||
import * as plugins from '../../../plugins.js';
|
||||
|
||||
/**
|
||||
* ProxyProtocolSocket wraps a regular net.Socket to provide transparent access
|
||||
* to the real client IP and port when behind a proxy using PROXY protocol.
|
||||
*
|
||||
* This is the FOUNDATION for all PROXY protocol support and must be implemented
|
||||
* before any protocol parsing can occur.
|
||||
*/
|
||||
export class ProxyProtocolSocket extends EventEmitter {
|
||||
private realClientIP?: string;
|
||||
private realClientPort?: number;
|
||||
|
||||
constructor(
|
||||
public readonly socket: plugins.net.Socket,
|
||||
private realClientIP?: string,
|
||||
private realClientPort?: number
|
||||
) {}
|
||||
|
||||
get remoteAddress(): string {
|
||||
return this.realClientIP || this.socket.remoteAddress || '';
|
||||
realClientIP?: string,
|
||||
realClientPort?: number
|
||||
) {
|
||||
super();
|
||||
this.realClientIP = realClientIP;
|
||||
this.realClientPort = realClientPort;
|
||||
|
||||
// Forward all socket events
|
||||
this.forwardSocketEvents();
|
||||
}
|
||||
|
||||
get remotePort(): number {
|
||||
return this.realClientPort || this.socket.remotePort || 0;
|
||||
/**
|
||||
* Returns the real client IP if available, otherwise the socket's remote address
|
||||
*/
|
||||
get remoteAddress(): string | undefined {
|
||||
return this.realClientIP || this.socket.remoteAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the real client port if available, otherwise the socket's remote port
|
||||
*/
|
||||
get remotePort(): number | undefined {
|
||||
return this.realClientPort || this.socket.remotePort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this connection came through a trusted proxy
|
||||
*/
|
||||
get isFromTrustedProxy(): boolean {
|
||||
return !!this.realClientIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the real client information (called after parsing PROXY protocol)
|
||||
*/
|
||||
setProxyInfo(ip: string, port: number): void {
|
||||
this.realClientIP = ip;
|
||||
this.realClientPort = port;
|
||||
}
|
||||
|
||||
// Pass-through all socket methods
|
||||
write(data: any, encoding?: any, callback?: any): boolean {
|
||||
return this.socket.write(data, encoding, callback);
|
||||
}
|
||||
|
||||
end(data?: any, encoding?: any, callback?: any): this {
|
||||
this.socket.end(data, encoding, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
destroy(error?: Error): this {
|
||||
this.socket.destroy(error);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ... implement all other socket methods as pass-through
|
||||
|
||||
/**
|
||||
* Forward all events from the underlying socket
|
||||
*/
|
||||
private forwardSocketEvents(): void {
|
||||
const events = ['data', 'end', 'close', 'error', 'drain', 'timeout'];
|
||||
events.forEach(event => {
|
||||
this.socket.on(event, (...args) => {
|
||||
this.emit(event, ...args);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**KEY POINT**: This wrapper must be fully functional and tested BEFORE moving to Phase 2.
|
||||
|
||||
#### 4.2 ProxyProtocolParser (new file)
|
||||
```typescript
|
||||
// ts/core/utils/proxy-protocol.ts
|
||||
|
Reference in New Issue
Block a user