diff --git a/changelog.md b/changelog.md index 2cd3675..6a26914 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # 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) Fixed typographical errors in comments within PortProxy class. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 7ba21e1..07d6e64 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { 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.' } diff --git a/ts/classes.portproxy.ts b/ts/classes.portproxy.ts index 1444e1b..df2cdf5 100644 --- a/ts/classes.portproxy.ts +++ b/ts/classes.portproxy.ts @@ -4,6 +4,7 @@ import * as plugins from './plugins.js'; export interface IDomainConfig { domains: string[]; // Glob patterns for domain(s) allowedIPs: string[]; // Glob patterns for allowed IPs + blockedIPs?: string[]; // Glob patterns for blocked IPs targetIPs?: string[]; // If multiple targetIPs are given, use round robin. portRanges?: Array<{ from: number; to: number }>; // Optional port ranges } @@ -16,6 +17,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions { domainConfigs: IDomainConfig[]; sniEnabled?: boolean; defaultAllowedIPs?: string[]; + defaultBlockedIPs?: string[]; preserveSourceIP?: boolean; maxConnectionLifetime?: number; // (ms) force cleanup of long-lived connections 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 } +// 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 { private netServers: plugins.net.Server[] = []; settings: IPortProxySettings; @@ -231,17 +263,25 @@ export class PortProxy { config.domains.some(d => plugins.minimatch(serverName, d)) ) : 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 (!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(', ')}`); } } else if (this.settings.defaultAllowedIPs) { - // Only check default allowed IPs if no domain config matched. - if (!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) { + if (!isGlobIPAllowed(remoteIP, this.settings.defaultAllowedIPs, this.settings.defaultBlockedIPs || [])) { return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`); } } + const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!; const connectionOptions: plugins.net.NetConnectOpts = { host: targetHost, @@ -341,6 +381,7 @@ export class PortProxy { setupConnection('', undefined, { domains: ['global'], allowedIPs: this.settings.defaultAllowedIPs || [], + blockedIPs: this.settings.defaultBlockedIPs || [], targetIPs: [this.settings.targetIP!], portRanges: [] }, localPort); @@ -466,28 +507,4 @@ export class PortProxy { } 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)) - ); -}; \ No newline at end of file +} \ No newline at end of file