feat(socket-handler): implement direct socket passing for DNS and email services

- Add socket-handler mode eliminating internal port binding for improved performance
- Add `dnsDomain` config option for automatic DNS-over-HTTPS (DoH) setup
- Add `useSocketHandler` flag to email config for direct socket processing
- Update SmartProxy route generation to support socket-handler actions
- Integrate smartdns with manual HTTPS mode for DoH without port binding
- Add automatic route creation for DNS paths when dnsDomain is configured
- Update documentation with socket-handler configuration and benefits
- Improve resource efficiency by eliminating internal port forwarding
This commit is contained in:
2025-05-29 16:26:19 +00:00
parent 6c8458f63c
commit b11fea7334
9 changed files with 687 additions and 540 deletions

View File

@ -52,7 +52,10 @@ export interface IDcRouterOptions {
};
/** DNS server configuration */
dnsServerConfig?: plugins.smartdns.IDnsServerOptions;
dnsServerConfig?: plugins.smartdns.dnsServerMod.IDnsServerOptions;
/** DNS domain for automatic DNS server setup with DoH */
dnsDomain?: string;
/** DNS challenge configuration for ACME (optional) */
dnsChallenge?: {
@ -81,7 +84,7 @@ export class DcRouter {
// Core services
public smartProxy?: plugins.smartproxy.SmartProxy;
public dnsServer?: plugins.smartdns.DnsServer;
public dnsServer?: plugins.smartdns.dnsServerMod.DnsServer;
public emailServer?: UnifiedEmailServer;
@ -114,10 +117,13 @@ export class DcRouter {
}
}
// Set up DNS server if configured
if (this.options.dnsServerConfig) {
// Set up DNS server if configured by dnsDomain
if (this.options.dnsDomain) {
await this.setupDnsWithSocketHandler();
} else if (this.options.dnsServerConfig) {
// Legacy DNS server setup
const { records, ...dnsServerOptions } = this.options.dnsServerConfig as any;
this.dnsServer = new plugins.smartdns.DnsServer(dnsServerOptions);
this.dnsServer = new plugins.smartdns.dnsServerMod.DnsServer(dnsServerOptions);
// Register DNS record handlers if records provided
if (records && records.length > 0) {
@ -161,6 +167,13 @@ export class DcRouter {
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
}
// If DNS domain is configured, add DNS routes
if (this.options.dnsDomain) {
const dnsRoutes = this.generateDnsRoutes();
console.log(`DNS Routes for domain ${this.options.dnsDomain}:`, dnsRoutes);
routes = [...routes, ...dnsRoutes];
}
// Merge TLS/ACME configuration if provided at root level
if (this.options.tls && !acmeConfig) {
acmeConfig = {
@ -247,21 +260,8 @@ export class DcRouter {
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions): plugins.smartproxy.IRouteConfig[] {
const emailRoutes: plugins.smartproxy.IRouteConfig[] = [];
// Get the custom port mapping if available, otherwise use defaults
const defaultPortMapping = {
25: 10025, // SMTP
587: 10587, // Submission
465: 10465 // SMTPS
};
// Use custom port mapping if provided, otherwise fall back to defaults
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
// Create routes for each email port
for (const port of emailConfig.ports) {
// Calculate the internal port using the mapping
const internalPort = portMapping[port] || port + 10000;
// Create a descriptive name for the route based on the port
let routeName = 'email-route';
let tlsMode = 'passthrough';
@ -285,7 +285,6 @@ export class DcRouter {
default:
routeName = `email-port-${port}-route`;
// For unknown ports, assume passthrough by default
tlsMode = 'passthrough';
// Check if we have specific settings for this port
@ -306,13 +305,27 @@ export class DcRouter {
break;
}
// Create the route configuration
const routeConfig: plugins.smartproxy.IRouteConfig = {
name: routeName,
match: {
ports: [port]
},
action: {
// Create action based on mode
let action: any;
if (emailConfig.useSocketHandler) {
// Socket-handler mode
action = {
type: 'socket-handler' as any,
socketHandler: this.createMailSocketHandler(port)
};
} else {
// Traditional forwarding mode
const defaultPortMapping = {
25: 10025, // SMTP
587: 10587, // Submission
465: 10465 // SMTPS
};
const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping;
const internalPort = portMapping[port] || port + 10000;
action = {
type: 'forward',
target: {
host: 'localhost', // Forward to internal email server
@ -321,19 +334,28 @@ export class DcRouter {
tls: {
mode: tlsMode as any
}
}
};
};
}
// For TLS terminate mode, add certificate info
if (tlsMode === 'terminate') {
routeConfig.action.tls.certificate = 'auto';
if (tlsMode === 'terminate' && action.tls) {
action.tls.certificate = 'auto';
}
// Create the route configuration
const routeConfig: plugins.smartproxy.IRouteConfig = {
name: routeName,
match: {
ports: [port]
},
action: action
};
// Add the route to our list
emailRoutes.push(routeConfig);
}
// Add email routes if configured
// Add email domain-based routes if configured
if (emailConfig.routes) {
for (const route of emailConfig.routes) {
emailRoutes.push({
@ -358,6 +380,39 @@ export class DcRouter {
return emailRoutes;
}
/**
* Generate SmartProxy routes for DNS configuration
*/
private generateDnsRoutes(): plugins.smartproxy.IRouteConfig[] {
if (!this.options.dnsDomain) {
return [];
}
const dnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
// Create routes for DNS-over-HTTPS paths
const dohPaths = ['/dns-query', '/resolve'];
for (const path of dohPaths) {
const dohRoute: plugins.smartproxy.IRouteConfig = {
name: `dns-over-https-${path.replace('/', '')}`,
match: {
ports: [443], // HTTPS port for DoH
domains: [this.options.dnsDomain],
path: path
},
action: {
type: 'socket-handler' as any,
socketHandler: this.createDnsSocketHandler()
} as any
};
dnsRoutes.push(dohRoute);
}
return dnsRoutes;
}
/**
* Check if a domain matches a pattern (including wildcard support)
@ -663,6 +718,118 @@ export class DcRouter {
return value;
}
}
/**
* Set up DNS server with socket handler for DoH
*/
private async setupDnsWithSocketHandler(): Promise<void> {
if (!this.options.dnsDomain) {
throw new Error('dnsDomain is required for DNS socket handler setup');
}
logger.log('info', `Setting up DNS server with socket handler for domain: ${this.options.dnsDomain}`);
// Get VM IP address for UDP binding
const networkInterfaces = plugins.os.networkInterfaces();
let vmIpAddress = '0.0.0.0'; // Default to all interfaces
// Try to find the VM's internal IP address
for (const [name, interfaces] of Object.entries(networkInterfaces)) {
if (interfaces) {
for (const iface of interfaces) {
if (!iface.internal && iface.family === 'IPv4') {
vmIpAddress = iface.address;
break;
}
}
}
}
// Create DNS server instance with manual HTTPS mode
this.dnsServer = new plugins.smartdns.dnsServerMod.DnsServer({
udpPort: 53,
udpBindInterface: vmIpAddress,
httpsPort: 443, // Required but won't bind due to manual mode
manualHttpsMode: true, // Enable manual HTTPS socket handling
dnssecZone: this.options.dnsDomain,
// For now, use self-signed cert until we integrate with Let's Encrypt
httpsKey: '',
httpsCert: ''
});
// Start the DNS server (UDP only)
await this.dnsServer.start();
logger.log('info', `DNS server started on UDP ${vmIpAddress}:53`);
}
/**
* Create DNS socket handler for DoH
*/
private createDnsSocketHandler(): (socket: plugins.net.Socket) => Promise<void> {
return async (socket: plugins.net.Socket) => {
if (!this.dnsServer) {
logger.log('error', 'DNS socket handler called but DNS server not initialized');
socket.end();
return;
}
logger.log('debug', 'DNS socket handler: passing socket to DnsServer');
try {
// Use the built-in socket handler from smartdns
// This handles HTTP/2, DoH protocol, etc.
await (this.dnsServer as any).handleHttpsSocket(socket);
} catch (error) {
logger.log('error', `DNS socket handler error: ${error.message}`);
socket.destroy();
}
};
}
/**
* Create mail socket handler for email traffic
*/
private createMailSocketHandler(port: number): (socket: plugins.net.Socket) => Promise<void> {
return async (socket: plugins.net.Socket) => {
if (!this.emailServer) {
logger.log('error', 'Mail socket handler called but email server not initialized');
socket.end();
return;
}
logger.log('debug', `Mail socket handler: handling connection for port ${port}`);
try {
// Port 465 requires immediate TLS
if (port === 465) {
// Wrap the socket in TLS
const tlsOptions = {
isServer: true,
key: this.options.tls?.keyPath ? plugins.fs.readFileSync(this.options.tls.keyPath, 'utf8') : undefined,
cert: this.options.tls?.certPath ? plugins.fs.readFileSync(this.options.tls.certPath, 'utf8') : undefined
};
const tlsSocket = new plugins.tls.TLSSocket(socket, tlsOptions);
tlsSocket.on('secure', () => {
// Pass the secure socket to the email server
this.emailServer!.handleSocket(tlsSocket, port);
});
tlsSocket.on('error', (err) => {
logger.log('error', `TLS handshake error on port ${port}: ${err.message}`);
socket.destroy();
});
} else {
// For ports 25 and 587, pass raw socket (STARTTLS handled by email server)
await this.emailServer.handleSocket(socket, port);
}
} catch (error) {
logger.log('error', `Mail socket handler error on port ${port}: ${error.message}`);
socket.destroy();
}
};
}
}
// Re-export email server types for convenience