feat(PortProxy): Enhance PortProxy with default blocked IPs
This commit is contained in:
parent
f9a6e2d748
commit
0df26d4367
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-01 - 3.19.0 - feat(PortProxy)
|
||||||
|
Enhance PortProxy with default blocked IPs
|
||||||
|
|
||||||
|
- Introduced defaultBlockedIPs in IPortProxySettings to handle globally blocked IPs.
|
||||||
|
- Added logic for merging domain-specific and default allowed and blocked IPs for effective IP filtering.
|
||||||
|
- Refactored helper functions for IP and port range checks to improve modularity in PortProxy.
|
||||||
|
|
||||||
## 2025-02-27 - 3.18.2 - fix(portproxy)
|
## 2025-02-27 - 3.18.2 - fix(portproxy)
|
||||||
Fixed typographical errors in comments within PortProxy class.
|
Fixed typographical errors in comments within PortProxy class.
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '3.18.2',
|
version: '3.19.0',
|
||||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import * as plugins from './plugins.js';
|
|||||||
export interface IDomainConfig {
|
export interface IDomainConfig {
|
||||||
domains: string[]; // Glob patterns for domain(s)
|
domains: string[]; // Glob patterns for domain(s)
|
||||||
allowedIPs: string[]; // Glob patterns for allowed IPs
|
allowedIPs: string[]; // Glob patterns for allowed IPs
|
||||||
|
blockedIPs?: string[]; // Glob patterns for blocked IPs
|
||||||
targetIPs?: string[]; // If multiple targetIPs are given, use round robin.
|
targetIPs?: string[]; // If multiple targetIPs are given, use round robin.
|
||||||
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
|
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
|
||||||
}
|
}
|
||||||
@ -16,6 +17,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|||||||
domainConfigs: IDomainConfig[];
|
domainConfigs: IDomainConfig[];
|
||||||
sniEnabled?: boolean;
|
sniEnabled?: boolean;
|
||||||
defaultAllowedIPs?: string[];
|
defaultAllowedIPs?: string[];
|
||||||
|
defaultBlockedIPs?: string[];
|
||||||
preserveSourceIP?: boolean;
|
preserveSourceIP?: boolean;
|
||||||
maxConnectionLifetime?: number; // (ms) force cleanup of long-lived connections
|
maxConnectionLifetime?: number; // (ms) force cleanup of long-lived connections
|
||||||
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
|
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
|
||||||
@ -95,6 +97,36 @@ interface IConnectionRecord {
|
|||||||
cleanupTimer?: NodeJS.Timeout; // Timer to force cleanup after max lifetime/inactivity
|
cleanupTimer?: NodeJS.Timeout; // Timer to force cleanup after max lifetime/inactivity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Check if a port falls within any of the given port ranges.
|
||||||
|
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
||||||
|
return ranges.some(range => port >= range.from && port <= range.to);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: Check if a given IP matches any of the glob patterns.
|
||||||
|
const isAllowed = (ip: string, patterns: string[]): boolean => {
|
||||||
|
const normalizeIP = (ip: string): string[] => {
|
||||||
|
if (ip.startsWith('::ffff:')) {
|
||||||
|
const ipv4 = ip.slice(7);
|
||||||
|
return [ip, ipv4];
|
||||||
|
}
|
||||||
|
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
||||||
|
return [ip, `::ffff:${ip}`];
|
||||||
|
}
|
||||||
|
return [ip];
|
||||||
|
};
|
||||||
|
const normalizedIPVariants = normalizeIP(ip);
|
||||||
|
const expandedPatterns = patterns.flatMap(normalizeIP);
|
||||||
|
return normalizedIPVariants.some(ipVariant =>
|
||||||
|
expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper: Check if an IP is allowed considering allowed and blocked glob patterns.
|
||||||
|
const isGlobIPAllowed = (ip: string, allowed: string[], blocked: string[] = []): boolean => {
|
||||||
|
if (blocked.length > 0 && isAllowed(ip, blocked)) return false;
|
||||||
|
return isAllowed(ip, allowed);
|
||||||
|
};
|
||||||
|
|
||||||
export class PortProxy {
|
export class PortProxy {
|
||||||
private netServers: plugins.net.Server[] = [];
|
private netServers: plugins.net.Server[] = [];
|
||||||
settings: IPortProxySettings;
|
settings: IPortProxySettings;
|
||||||
@ -231,17 +263,25 @@ export class PortProxy {
|
|||||||
config.domains.some(d => plugins.minimatch(serverName, d))
|
config.domains.some(d => plugins.minimatch(serverName, d))
|
||||||
) : undefined);
|
) : undefined);
|
||||||
|
|
||||||
// If a matching domain config exists, check its allowedIPs.
|
// Effective IP check: merge allowed IPs with default allowed, and remove blocked IPs.
|
||||||
if (domainConfig) {
|
if (domainConfig) {
|
||||||
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
const effectiveAllowedIPs: string[] = [
|
||||||
|
...domainConfig.allowedIPs,
|
||||||
|
...(this.settings.defaultAllowedIPs || [])
|
||||||
|
];
|
||||||
|
const effectiveBlockedIPs: string[] = [
|
||||||
|
...(domainConfig.blockedIPs || []),
|
||||||
|
...(this.settings.defaultBlockedIPs || [])
|
||||||
|
];
|
||||||
|
if (!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) {
|
||||||
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`);
|
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`);
|
||||||
}
|
}
|
||||||
} else if (this.settings.defaultAllowedIPs) {
|
} else if (this.settings.defaultAllowedIPs) {
|
||||||
// Only check default allowed IPs if no domain config matched.
|
if (!isGlobIPAllowed(remoteIP, this.settings.defaultAllowedIPs, this.settings.defaultBlockedIPs || [])) {
|
||||||
if (!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
||||||
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`);
|
return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!;
|
const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!;
|
||||||
const connectionOptions: plugins.net.NetConnectOpts = {
|
const connectionOptions: plugins.net.NetConnectOpts = {
|
||||||
host: targetHost,
|
host: targetHost,
|
||||||
@ -341,6 +381,7 @@ export class PortProxy {
|
|||||||
setupConnection('', undefined, {
|
setupConnection('', undefined, {
|
||||||
domains: ['global'],
|
domains: ['global'],
|
||||||
allowedIPs: this.settings.defaultAllowedIPs || [],
|
allowedIPs: this.settings.defaultAllowedIPs || [],
|
||||||
|
blockedIPs: this.settings.defaultBlockedIPs || [],
|
||||||
targetIPs: [this.settings.targetIP!],
|
targetIPs: [this.settings.targetIP!],
|
||||||
portRanges: []
|
portRanges: []
|
||||||
}, localPort);
|
}, localPort);
|
||||||
@ -466,28 +507,4 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
await Promise.all(closePromises);
|
await Promise.all(closePromises);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Check if a port falls within any of the given port ranges.
|
|
||||||
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
|
||||||
return ranges.some(range => port >= range.from && port <= range.to);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper: Check if a given IP matches any of the glob patterns.
|
|
||||||
const isAllowed = (ip: string, patterns: string[]): boolean => {
|
|
||||||
const normalizeIP = (ip: string): string[] => {
|
|
||||||
if (ip.startsWith('::ffff:')) {
|
|
||||||
const ipv4 = ip.slice(7);
|
|
||||||
return [ip, ipv4];
|
|
||||||
}
|
|
||||||
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
||||||
return [ip, `::ffff:${ip}`];
|
|
||||||
}
|
|
||||||
return [ip];
|
|
||||||
};
|
|
||||||
const normalizedIPVariants = normalizeIP(ip);
|
|
||||||
const expandedPatterns = patterns.flatMap(normalizeIP);
|
|
||||||
return normalizedIPVariants.some(ipVariant =>
|
|
||||||
expandedPatterns.some(pattern => plugins.minimatch(ipVariant, pattern))
|
|
||||||
);
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user