- Added ProxyProtocolParser class for parsing and generating PROXY protocol v1 headers. - Integrated PROXY protocol parsing into RouteConnectionHandler for handling incoming connections from trusted proxies. - Implemented WrappedSocket class to encapsulate real client information. - Configured SmartProxy to accept and send PROXY protocol headers in routing actions. - Developed comprehensive unit tests for PROXY protocol parsing and generation. - Documented usage patterns, configuration, and best practices for proxy chaining scenarios. - Added security and performance considerations for PROXY protocol implementation.
133 lines
5.0 KiB
TypeScript
133 lines
5.0 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as smartproxy from '../ts/index.js';
|
|
import { ProxyProtocolParser } from '../ts/core/utils/proxy-protocol.js';
|
|
|
|
tap.test('PROXY protocol v1 parser - valid headers', async () => {
|
|
// Test TCP4 format
|
|
const tcp4Header = Buffer.from('PROXY TCP4 192.168.1.1 10.0.0.1 56324 443\r\n', 'ascii');
|
|
const tcp4Result = ProxyProtocolParser.parse(tcp4Header);
|
|
|
|
expect(tcp4Result.proxyInfo).property('protocol').toEqual('TCP4');
|
|
expect(tcp4Result.proxyInfo).property('sourceIP').toEqual('192.168.1.1');
|
|
expect(tcp4Result.proxyInfo).property('sourcePort').toEqual(56324);
|
|
expect(tcp4Result.proxyInfo).property('destinationIP').toEqual('10.0.0.1');
|
|
expect(tcp4Result.proxyInfo).property('destinationPort').toEqual(443);
|
|
expect(tcp4Result.remainingData.length).toEqual(0);
|
|
|
|
// Test TCP6 format
|
|
const tcp6Header = Buffer.from('PROXY TCP6 2001:db8::1 2001:db8::2 56324 443\r\n', 'ascii');
|
|
const tcp6Result = ProxyProtocolParser.parse(tcp6Header);
|
|
|
|
expect(tcp6Result.proxyInfo).property('protocol').toEqual('TCP6');
|
|
expect(tcp6Result.proxyInfo).property('sourceIP').toEqual('2001:db8::1');
|
|
expect(tcp6Result.proxyInfo).property('sourcePort').toEqual(56324);
|
|
expect(tcp6Result.proxyInfo).property('destinationIP').toEqual('2001:db8::2');
|
|
expect(tcp6Result.proxyInfo).property('destinationPort').toEqual(443);
|
|
|
|
// Test UNKNOWN protocol
|
|
const unknownHeader = Buffer.from('PROXY UNKNOWN\r\n', 'ascii');
|
|
const unknownResult = ProxyProtocolParser.parse(unknownHeader);
|
|
|
|
expect(unknownResult.proxyInfo).property('protocol').toEqual('UNKNOWN');
|
|
expect(unknownResult.proxyInfo).property('sourceIP').toEqual('');
|
|
expect(unknownResult.proxyInfo).property('sourcePort').toEqual(0);
|
|
});
|
|
|
|
tap.test('PROXY protocol v1 parser - with remaining data', async () => {
|
|
const headerWithData = Buffer.concat([
|
|
Buffer.from('PROXY TCP4 192.168.1.1 10.0.0.1 56324 443\r\n', 'ascii'),
|
|
Buffer.from('GET / HTTP/1.1\r\n', 'ascii')
|
|
]);
|
|
|
|
const result = ProxyProtocolParser.parse(headerWithData);
|
|
|
|
expect(result.proxyInfo).property('protocol').toEqual('TCP4');
|
|
expect(result.proxyInfo).property('sourceIP').toEqual('192.168.1.1');
|
|
expect(result.remainingData.toString()).toEqual('GET / HTTP/1.1\r\n');
|
|
});
|
|
|
|
tap.test('PROXY protocol v1 parser - invalid headers', async () => {
|
|
// Not a PROXY protocol header
|
|
const notProxy = Buffer.from('GET / HTTP/1.1\r\n', 'ascii');
|
|
const notProxyResult = ProxyProtocolParser.parse(notProxy);
|
|
expect(notProxyResult.proxyInfo).toBeNull();
|
|
expect(notProxyResult.remainingData).toEqual(notProxy);
|
|
|
|
// Invalid protocol
|
|
expect(() => {
|
|
ProxyProtocolParser.parse(Buffer.from('PROXY INVALID 1.1.1.1 2.2.2.2 80 443\r\n', 'ascii'));
|
|
}).toThrow();
|
|
|
|
// Wrong number of fields
|
|
expect(() => {
|
|
ProxyProtocolParser.parse(Buffer.from('PROXY TCP4 192.168.1.1 10.0.0.1 56324\r\n', 'ascii'));
|
|
}).toThrow();
|
|
|
|
// Invalid port
|
|
expect(() => {
|
|
ProxyProtocolParser.parse(Buffer.from('PROXY TCP4 192.168.1.1 10.0.0.1 99999 443\r\n', 'ascii'));
|
|
}).toThrow();
|
|
|
|
// Invalid IP for protocol
|
|
expect(() => {
|
|
ProxyProtocolParser.parse(Buffer.from('PROXY TCP4 2001:db8::1 10.0.0.1 56324 443\r\n', 'ascii'));
|
|
}).toThrow();
|
|
});
|
|
|
|
tap.test('PROXY protocol v1 parser - incomplete headers', async () => {
|
|
// Header without terminator
|
|
const incomplete = Buffer.from('PROXY TCP4 192.168.1.1 10.0.0.1 56324 443', 'ascii');
|
|
const result = ProxyProtocolParser.parse(incomplete);
|
|
|
|
expect(result.proxyInfo).toBeNull();
|
|
expect(result.remainingData).toEqual(incomplete);
|
|
|
|
// Header exceeding max length - create a buffer that actually starts with PROXY
|
|
const longHeader = Buffer.from('PROXY TCP4 ' + '1'.repeat(100), 'ascii');
|
|
expect(() => {
|
|
ProxyProtocolParser.parse(longHeader);
|
|
}).toThrow();
|
|
});
|
|
|
|
tap.test('PROXY protocol v1 generator', async () => {
|
|
// Generate TCP4 header
|
|
const tcp4Info = {
|
|
protocol: 'TCP4' as const,
|
|
sourceIP: '192.168.1.1',
|
|
sourcePort: 56324,
|
|
destinationIP: '10.0.0.1',
|
|
destinationPort: 443
|
|
};
|
|
|
|
const tcp4Header = ProxyProtocolParser.generate(tcp4Info);
|
|
expect(tcp4Header.toString('ascii')).toEqual('PROXY TCP4 192.168.1.1 10.0.0.1 56324 443\r\n');
|
|
|
|
// Generate TCP6 header
|
|
const tcp6Info = {
|
|
protocol: 'TCP6' as const,
|
|
sourceIP: '2001:db8::1',
|
|
sourcePort: 56324,
|
|
destinationIP: '2001:db8::2',
|
|
destinationPort: 443
|
|
};
|
|
|
|
const tcp6Header = ProxyProtocolParser.generate(tcp6Info);
|
|
expect(tcp6Header.toString('ascii')).toEqual('PROXY TCP6 2001:db8::1 2001:db8::2 56324 443\r\n');
|
|
|
|
// Generate UNKNOWN header
|
|
const unknownInfo = {
|
|
protocol: 'UNKNOWN' as const,
|
|
sourceIP: '',
|
|
sourcePort: 0,
|
|
destinationIP: '',
|
|
destinationPort: 0
|
|
};
|
|
|
|
const unknownHeader = ProxyProtocolParser.generate(unknownInfo);
|
|
expect(unknownHeader.toString('ascii')).toEqual('PROXY UNKNOWN\r\n');
|
|
});
|
|
|
|
// Skipping integration tests for now - focus on unit tests
|
|
// Integration tests would require more complex setup and teardown
|
|
|
|
tap.start(); |