2025-06-06 13:45:44 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
|
|
|
import { logger } from './logger.js';
|
2025-07-21 22:37:45 +00:00
|
|
|
import { ProxyProtocolParser as ProtocolParser, type IProxyInfo, type IProxyParseResult } from '../../protocols/proxy/index.js';
|
2025-06-06 13:45:44 +00:00
|
|
|
|
2025-07-21 22:37:45 +00:00
|
|
|
// Re-export types from protocols for backward compatibility
|
|
|
|
export type { IProxyInfo, IProxyParseResult } from '../../protocols/proxy/index.js';
|
2025-06-06 13:45:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parser for PROXY protocol v1 (text format)
|
|
|
|
* Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
2025-07-21 22:37:45 +00:00
|
|
|
*
|
|
|
|
* This class now delegates to the protocol parser but adds
|
|
|
|
* smartproxy-specific features like socket reading and logging
|
2025-06-06 13:45:44 +00:00
|
|
|
*/
|
|
|
|
export class ProxyProtocolParser {
|
2025-07-21 22:37:45 +00:00
|
|
|
static readonly PROXY_V1_SIGNATURE = ProtocolParser.PROXY_V1_SIGNATURE;
|
|
|
|
static readonly MAX_HEADER_LENGTH = ProtocolParser.MAX_HEADER_LENGTH;
|
|
|
|
static readonly HEADER_TERMINATOR = ProtocolParser.HEADER_TERMINATOR;
|
2025-06-06 13:45:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse PROXY protocol v1 header from buffer
|
|
|
|
* Returns proxy info and remaining data after header
|
|
|
|
*/
|
|
|
|
static parse(data: Buffer): IProxyParseResult {
|
2025-07-21 22:37:45 +00:00
|
|
|
// Delegate to protocol parser
|
|
|
|
return ProtocolParser.parse(data);
|
2025-06-06 13:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate PROXY protocol v1 header
|
|
|
|
*/
|
|
|
|
static generate(info: IProxyInfo): Buffer {
|
2025-07-21 22:37:45 +00:00
|
|
|
// Delegate to protocol parser
|
|
|
|
return ProtocolParser.generate(info);
|
2025-06-06 13:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate IP address format
|
|
|
|
*/
|
|
|
|
private static isValidIP(ip: string, protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'): boolean {
|
2025-07-21 22:37:45 +00:00
|
|
|
return ProtocolParser.isValidIP(ip, protocol);
|
2025-06-06 13:45:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt to read a complete PROXY protocol header from a socket
|
|
|
|
* Returns null if no PROXY protocol detected or incomplete
|
|
|
|
*/
|
|
|
|
static async readFromSocket(socket: plugins.net.Socket, timeout: number = 5000): Promise<IProxyParseResult | null> {
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
let buffer = Buffer.alloc(0);
|
|
|
|
let resolved = false;
|
|
|
|
|
|
|
|
const cleanup = () => {
|
|
|
|
socket.removeListener('data', onData);
|
|
|
|
socket.removeListener('error', onError);
|
|
|
|
clearTimeout(timer);
|
|
|
|
};
|
|
|
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
if (!resolved) {
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve({
|
|
|
|
proxyInfo: null,
|
|
|
|
remainingData: buffer
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, timeout);
|
|
|
|
|
|
|
|
const onData = (chunk: Buffer) => {
|
|
|
|
buffer = Buffer.concat([buffer, chunk]);
|
|
|
|
|
|
|
|
// Check if we have enough data
|
|
|
|
if (!buffer.toString('ascii', 0, Math.min(6, buffer.length)).startsWith(this.PROXY_V1_SIGNATURE)) {
|
|
|
|
// Not PROXY protocol
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve({
|
|
|
|
proxyInfo: null,
|
|
|
|
remainingData: buffer
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to parse
|
|
|
|
try {
|
|
|
|
const result = this.parse(buffer);
|
|
|
|
if (result.proxyInfo) {
|
|
|
|
// Successfully parsed
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve(result);
|
|
|
|
} else if (buffer.length > this.MAX_HEADER_LENGTH) {
|
|
|
|
// Header too long
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve({
|
|
|
|
proxyInfo: null,
|
|
|
|
remainingData: buffer
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// Otherwise continue reading
|
|
|
|
} catch (error) {
|
|
|
|
// Parse error
|
|
|
|
logger.log('error', `PROXY protocol parse error: ${error.message}`);
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve({
|
|
|
|
proxyInfo: null,
|
|
|
|
remainingData: buffer
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onError = (error: Error) => {
|
|
|
|
logger.log('error', `Socket error while reading PROXY protocol: ${error.message}`);
|
|
|
|
resolved = true;
|
|
|
|
cleanup();
|
|
|
|
resolve({
|
|
|
|
proxyInfo: null,
|
|
|
|
remainingData: buffer
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.on('data', onData);
|
|
|
|
socket.on('error', onError);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|