fix(PortProxy): Improve SNI renegotiation handling by adding flexible domain configuration matching on rehandshake and session resumption events.

This commit is contained in:
Philipp Kunz 2025-03-11 03:56:09 +00:00
parent 98f1e0df4c
commit 119b643690
3 changed files with 92 additions and 5 deletions

View File

@ -1,5 +1,13 @@
# Changelog # Changelog
## 2025-03-11 - 3.31.2 - fix(PortProxy)
Improve SNI renegotiation handling by adding flexible domain configuration matching on rehandshake and session resumption events.
- When a rehandshake is detected with a changed SNI, first check existing domain config rules and log if allowed.
- If the exact domain config is not found, additionally attempt flexible matching using parent domain and wildcard patterns.
- For resumed sessions, try an exact match first and then use fallback logic to select a similar domain config based on matching target IP.
- Enhanced logging added to help diagnose missing or mismatched domain configurations.
## 2025-03-11 - 3.31.1 - fix(PortProxy) ## 2025-03-11 - 3.31.1 - fix(PortProxy)
Improve TLS handshake buffering and enhance debug logging for SNI forwarding in PortProxy Improve TLS handshake buffering and enhance debug logging for SNI forwarding in PortProxy

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '3.31.1', version: '3.31.2',
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.'
} }

View File

@ -1179,15 +1179,46 @@ export class PortProxy {
// Allow if the new SNI matches existing domain config or find a new matching config // Allow if the new SNI matches existing domain config or find a new matching config
let allowed = false; let allowed = false;
// First check if the new SNI is allowed under the existing domain config
// This is the preferred approach as it maintains the existing connection context
if (record.domainConfig) { if (record.domainConfig) {
allowed = record.domainConfig.domains.some(d => plugins.minimatch(newSNI, d)); allowed = record.domainConfig.domains.some(d => plugins.minimatch(newSNI, d));
if (allowed) {
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} allowed by existing domain config`);
}
} }
// If not allowed by existing config, try to find an alternative domain config
if (!allowed) { if (!allowed) {
const newDomainConfig = this.settings.domainConfigs.find((config) => // First try exact match
let newDomainConfig = this.settings.domainConfigs.find((config) =>
config.domains.some((d) => plugins.minimatch(newSNI, d)) config.domains.some((d) => plugins.minimatch(newSNI, d))
); );
// If no exact match, try flexible matching with domain parts (for wildcard domains)
if (!newDomainConfig) {
console.log(`[${connectionId}] No exact domain config match for rehandshake SNI: ${newSNI}, trying flexible matching`);
const domainParts = newSNI.split('.');
// Try matching with parent domains or wildcard patterns
if (domainParts.length > 2) {
const parentDomain = domainParts.slice(1).join('.');
const wildcardDomain = '*.' + parentDomain;
console.log(`[${connectionId}] Trying alternative patterns: ${parentDomain} or ${wildcardDomain}`);
newDomainConfig = this.settings.domainConfigs.find((config) =>
config.domains.some((d) =>
d === parentDomain ||
d === wildcardDomain ||
plugins.minimatch(parentDomain, d)
)
);
}
}
if (newDomainConfig) { if (newDomainConfig) {
const effectiveAllowedIPs = [ const effectiveAllowedIPs = [
...newDomainConfig.allowedIPs, ...newDomainConfig.allowedIPs,
@ -2002,15 +2033,63 @@ export class PortProxy {
// The resumed domain will be in serverName if this is a session resumption // The resumed domain will be in serverName if this is a session resumption
if (serverName && connectionRecord.lockedDomain === serverName && serverName !== '') { if (serverName && connectionRecord.lockedDomain === serverName && serverName !== '') {
// Override domain config lookup for session resumption - crucial for certificate selection // Override domain config lookup for session resumption - crucial for certificate selection
const resumedDomainConfig = this.settings.domainConfigs.find((config) =>
// First try an exact match
let resumedDomainConfig = this.settings.domainConfigs.find((config) =>
config.domains.some((d) => plugins.minimatch(serverName, d)) config.domains.some((d) => plugins.minimatch(serverName, d))
); );
// If no exact match found, try a more flexible approach using domain parts
if (!resumedDomainConfig) {
console.log(`[${connectionId}] No exact domain config match for resumed domain: ${serverName}, trying flexible matching`);
// Extract domain parts (e.g., for "sub.example.com" try matching with "*.example.com")
const domainParts = serverName.split('.');
// Try matching with parent domains or wildcard patterns
if (domainParts.length > 2) {
const parentDomain = domainParts.slice(1).join('.');
const wildcardDomain = '*.' + parentDomain;
console.log(`[${connectionId}] Trying alternative patterns: ${parentDomain} or ${wildcardDomain}`);
resumedDomainConfig = this.settings.domainConfigs.find((config) =>
config.domains.some((d) =>
d === parentDomain ||
d === wildcardDomain ||
plugins.minimatch(parentDomain, d)
)
);
}
}
if (resumedDomainConfig) { if (resumedDomainConfig) {
domainConfig = resumedDomainConfig; domainConfig = resumedDomainConfig;
console.log(`[${connectionId}] Using domain config for resumed session: ${serverName}`); console.log(`[${connectionId}] Found domain config for resumed session: ${serverName} -> ${resumedDomainConfig.domains.join(',')}`);
} else {
// As a fallback, use the first domain config with the same target IP if possible
if (domainConfig && domainConfig.targetIPs && domainConfig.targetIPs.length > 0) {
const targetIP = domainConfig.targetIPs[0];
const similarConfig = this.settings.domainConfigs.find((config) =>
config.targetIPs && config.targetIPs.includes(targetIP)
);
if (similarConfig && similarConfig !== domainConfig) {
console.log(`[${connectionId}] Using similar domain config with matching target IP for resumed domain: ${serverName}`);
domainConfig = similarConfig;
} else { } else {
console.log(`[${connectionId}] WARNING: Cannot find domain config for resumed domain: ${serverName}`); console.log(`[${connectionId}] WARNING: Cannot find domain config for resumed domain: ${serverName}`);
// Log available domains to help diagnose the issue
console.log(`[${connectionId}] Available domains:`,
this.settings.domainConfigs.map(config => config.domains.join(',')).join(' | '));
}
} else {
console.log(`[${connectionId}] WARNING: Cannot find domain config for resumed domain: ${serverName}`);
// Log available domains to help diagnose the issue
console.log(`[${connectionId}] Available domains:`,
this.settings.domainConfigs.map(config => config.domains.join(',')).join(' | '));
}
} }
} }