2022-07-29 00:49:46 +02:00
|
|
|
import * as plugins from './smartproxy.plugins.js';
|
2025-02-21 15:14:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
export interface DomainConfig {
|
|
|
|
domain: string; // glob pattern for domain
|
|
|
|
allowedIPs: string[]; // glob patterns for IPs allowed to access this domain
|
|
|
|
}
|
|
|
|
|
2025-02-21 17:01:02 +00:00
|
|
|
export interface ProxySettings extends plugins.tls.TlsOptions {
|
|
|
|
// Port configuration
|
|
|
|
fromPort: number;
|
|
|
|
toPort: number;
|
|
|
|
toHost?: string; // Target host to proxy to, defaults to 'localhost'
|
|
|
|
|
|
|
|
// Domain and security settings
|
2025-02-21 15:14:02 +00:00
|
|
|
domains: DomainConfig[];
|
|
|
|
sniEnabled?: boolean;
|
|
|
|
defaultAllowedIPs?: string[]; // Optional default IP patterns if no matching domain found
|
|
|
|
}
|
2019-08-22 15:09:48 +02:00
|
|
|
|
2022-07-29 00:49:46 +02:00
|
|
|
export class PortProxy {
|
2025-02-21 15:17:19 +00:00
|
|
|
netServer: plugins.net.Server | plugins.tls.Server;
|
2025-02-21 15:14:02 +00:00
|
|
|
settings: ProxySettings;
|
2022-07-29 00:49:46 +02:00
|
|
|
|
2025-02-21 17:01:02 +00:00
|
|
|
constructor(settings: ProxySettings) {
|
|
|
|
this.settings = {
|
|
|
|
...settings,
|
|
|
|
toHost: settings.toHost || 'localhost'
|
|
|
|
};
|
2022-07-29 00:49:46 +02:00
|
|
|
}
|
|
|
|
|
2022-07-29 01:52:34 +02:00
|
|
|
public async start() {
|
2021-02-02 21:59:24 +00:00
|
|
|
const cleanUpSockets = (from: plugins.net.Socket, to: plugins.net.Socket) => {
|
|
|
|
from.end();
|
|
|
|
to.end();
|
|
|
|
from.removeAllListeners();
|
2021-02-02 21:59:54 +00:00
|
|
|
to.removeAllListeners();
|
2021-02-02 21:59:24 +00:00
|
|
|
from.unpipe();
|
|
|
|
to.unpipe();
|
2021-02-03 00:13:29 +00:00
|
|
|
from.destroy();
|
|
|
|
to.destroy();
|
2022-07-29 01:52:34 +02:00
|
|
|
};
|
2025-02-21 15:14:02 +00:00
|
|
|
const isAllowed = (value: string, patterns: string[]): boolean => {
|
|
|
|
return patterns.some(pattern => plugins.minimatch(value, pattern));
|
|
|
|
};
|
|
|
|
|
|
|
|
const findMatchingDomain = (serverName: string): DomainConfig | undefined => {
|
|
|
|
return this.settings.domains.find(config => plugins.minimatch(serverName, config.domain));
|
|
|
|
};
|
|
|
|
|
2025-02-21 17:01:02 +00:00
|
|
|
const server = this.settings.sniEnabled
|
|
|
|
? plugins.tls.createServer(this.settings)
|
|
|
|
: plugins.net.createServer();
|
2025-02-21 15:14:02 +00:00
|
|
|
|
2025-02-21 15:17:19 +00:00
|
|
|
this.netServer = server.on('connection', (from: plugins.net.Socket) => {
|
2025-02-21 15:14:02 +00:00
|
|
|
const remoteIP = from.remoteAddress || '';
|
2025-02-21 15:17:19 +00:00
|
|
|
if (this.settings.sniEnabled && from instanceof plugins.tls.TLSSocket) {
|
2025-02-21 15:14:02 +00:00
|
|
|
const serverName = (from as any).servername || '';
|
|
|
|
const domainConfig = findMatchingDomain(serverName);
|
|
|
|
|
|
|
|
if (!domainConfig) {
|
|
|
|
// If no matching domain config found, check default IPs if available
|
|
|
|
if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
|
|
console.log(`Connection rejected: No matching domain config for ${serverName} from IP ${remoteIP}`);
|
|
|
|
from.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Check if IP is allowed for this domain
|
|
|
|
if (!isAllowed(remoteIP, domainConfig.allowedIPs)) {
|
|
|
|
console.log(`Connection rejected: IP ${remoteIP} not allowed for domain ${serverName}`);
|
|
|
|
from.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!this.settings.defaultAllowedIPs || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) {
|
|
|
|
console.log(`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`);
|
|
|
|
from.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-02-21 15:17:19 +00:00
|
|
|
const to = plugins.net.createConnection({
|
2025-02-21 17:01:02 +00:00
|
|
|
host: this.settings.toHost!,
|
|
|
|
port: this.settings.toPort,
|
2021-02-02 15:55:25 +00:00
|
|
|
});
|
2021-02-03 00:16:11 +00:00
|
|
|
from.setTimeout(120000);
|
2021-02-02 15:55:25 +00:00
|
|
|
from.pipe(to);
|
|
|
|
to.pipe(from);
|
|
|
|
from.on('error', () => {
|
2021-02-02 21:59:24 +00:00
|
|
|
cleanUpSockets(from, to);
|
2021-02-02 15:55:25 +00:00
|
|
|
});
|
|
|
|
to.on('error', () => {
|
2021-02-02 21:59:24 +00:00
|
|
|
cleanUpSockets(from, to);
|
2020-02-07 13:04:11 +00:00
|
|
|
});
|
2021-02-02 00:53:57 +00:00
|
|
|
from.on('close', () => {
|
2021-02-02 21:59:24 +00:00
|
|
|
cleanUpSockets(from, to);
|
2021-02-02 15:55:25 +00:00
|
|
|
});
|
2021-02-02 21:59:24 +00:00
|
|
|
to.on('close', () => {
|
|
|
|
cleanUpSockets(from, to);
|
|
|
|
});
|
|
|
|
from.on('timeout', () => {
|
|
|
|
cleanUpSockets(from, to);
|
|
|
|
});
|
|
|
|
to.on('timeout', () => {
|
|
|
|
cleanUpSockets(from, to);
|
|
|
|
});
|
|
|
|
from.on('end', () => {
|
|
|
|
cleanUpSockets(from, to);
|
2022-07-29 01:52:34 +02:00
|
|
|
});
|
2021-02-02 21:59:24 +00:00
|
|
|
to.on('end', () => {
|
|
|
|
cleanUpSockets(from, to);
|
2022-07-29 01:52:34 +02:00
|
|
|
});
|
2020-02-07 13:04:11 +00:00
|
|
|
})
|
2025-02-21 17:01:02 +00:00
|
|
|
.listen(this.settings.fromPort);
|
|
|
|
console.log(`PortProxy -> OK: Now listening on port ${this.settings.fromPort}`);
|
2022-07-29 00:49:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async stop() {
|
2019-08-22 15:09:48 +02:00
|
|
|
const done = plugins.smartpromise.defer();
|
2022-07-29 03:39:05 +02:00
|
|
|
this.netServer.close(() => {
|
|
|
|
done.resolve();
|
2019-08-22 15:09:48 +02:00
|
|
|
});
|
|
|
|
await done.promise;
|
2022-07-29 00:49:46 +02:00
|
|
|
}
|
2022-07-29 01:52:34 +02:00
|
|
|
}
|