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:
@ -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
|
||||
|
Reference in New Issue
Block a user