Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
1185ea67d4 | |||
b187da507b | |||
3094c9d06c | |||
62b6fa26fa |
16
changelog.md
16
changelog.md
@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-28 - 7.4.0 - feat(manual socket handling)
|
||||
Add comprehensive manual socket handling documentation for advanced DNS server use cases
|
||||
|
||||
- Introduced detailed examples for configuring manual UDP and HTTPS socket handling
|
||||
- Provided sample code for load balancing, clustering, custom transport protocols, and multi-interface binding
|
||||
- Updated performance and best practices sections to reflect manual socket handling benefits
|
||||
|
||||
## 2025-05-28 - 7.3.0 - feat(dnsserver)
|
||||
Add manual socket mode support to enable external socket control for the DNS server.
|
||||
|
||||
- Introduced new manualUdpMode and manualHttpsMode options in the server options interface.
|
||||
- Added initializeServers, initializeUdpServer, and initializeHttpsServer methods for manual socket initialization.
|
||||
- Updated start() and stop() methods to handle both automatic and manual socket binding modes.
|
||||
- Enhanced UDP and HTTPS socket error handling and IP address validations.
|
||||
- Removed obsolete internal documentation file (readme.plan2.md).
|
||||
|
||||
## 2025-05-28 - 7.2.0 - feat(dns-server)
|
||||
Improve DNS server interface binding by adding explicit IP validation, configurable UDP/HTTPS binding, and enhanced logging.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartdns",
|
||||
"version": "7.2.0",
|
||||
"version": "7.4.0",
|
||||
"private": false,
|
||||
"description": "A robust TypeScript library providing advanced DNS management and resolution capabilities including support for DNSSEC, custom DNS servers, and integration with various DNS providers.",
|
||||
"exports": {
|
||||
|
213
readme.md
213
readme.md
@ -306,6 +306,215 @@ await dnsServer.start();
|
||||
console.log('DNS Server with Let\'s Encrypt SSL started!');
|
||||
```
|
||||
|
||||
### Manual Socket Handling
|
||||
|
||||
The DNS server supports manual socket handling for advanced use cases like clustering, load balancing, and custom transport implementations. You can control UDP and HTTPS socket handling independently.
|
||||
|
||||
#### Configuration Options
|
||||
|
||||
```typescript
|
||||
export interface IDnsServerOptions {
|
||||
// ... standard options ...
|
||||
manualUdpMode?: boolean; // Handle UDP sockets manually
|
||||
manualHttpsMode?: boolean; // Handle HTTPS sockets manually
|
||||
}
|
||||
```
|
||||
|
||||
#### Basic Manual Socket Usage
|
||||
|
||||
```typescript
|
||||
import { DnsServer } from '@push.rocks/smartdns/server';
|
||||
import * as dgram from 'dgram';
|
||||
import * as net from 'net';
|
||||
|
||||
// Create server with manual UDP mode
|
||||
const dnsServer = new DnsServer({
|
||||
httpsKey: '...',
|
||||
httpsCert: '...',
|
||||
httpsPort: 853,
|
||||
udpPort: 53,
|
||||
dnssecZone: 'example.com',
|
||||
manualUdpMode: true // UDP manual, HTTPS automatic
|
||||
});
|
||||
|
||||
await dnsServer.start(); // HTTPS binds, UDP doesn't
|
||||
|
||||
// Create your own UDP socket
|
||||
const udpSocket = dgram.createSocket('udp4');
|
||||
|
||||
// Handle incoming UDP messages
|
||||
udpSocket.on('message', (msg, rinfo) => {
|
||||
dnsServer.handleUdpMessage(msg, rinfo, (response, responseRinfo) => {
|
||||
// Send response using your socket
|
||||
udpSocket.send(response, responseRinfo.port, responseRinfo.address);
|
||||
});
|
||||
});
|
||||
|
||||
// Bind to custom port or multiple interfaces
|
||||
udpSocket.bind(5353, '0.0.0.0');
|
||||
```
|
||||
|
||||
#### Manual HTTPS Socket Handling
|
||||
|
||||
```typescript
|
||||
// Create server with manual HTTPS mode
|
||||
const dnsServer = new DnsServer({
|
||||
httpsKey: '...',
|
||||
httpsCert: '...',
|
||||
httpsPort: 853,
|
||||
udpPort: 53,
|
||||
dnssecZone: 'example.com',
|
||||
manualHttpsMode: true // HTTPS manual, UDP automatic
|
||||
});
|
||||
|
||||
await dnsServer.start(); // UDP binds, HTTPS doesn't
|
||||
|
||||
// Create your own TCP server
|
||||
const tcpServer = net.createServer((socket) => {
|
||||
// Pass TCP sockets to DNS server
|
||||
dnsServer.handleHttpsSocket(socket);
|
||||
});
|
||||
|
||||
tcpServer.listen(8853, '0.0.0.0');
|
||||
```
|
||||
|
||||
#### Full Manual Mode
|
||||
|
||||
Control both protocols manually for complete flexibility:
|
||||
|
||||
```typescript
|
||||
const dnsServer = new DnsServer({
|
||||
httpsKey: '...',
|
||||
httpsCert: '...',
|
||||
httpsPort: 853,
|
||||
udpPort: 53,
|
||||
dnssecZone: 'example.com',
|
||||
manualUdpMode: true,
|
||||
manualHttpsMode: true
|
||||
});
|
||||
|
||||
await dnsServer.start(); // Neither protocol binds
|
||||
|
||||
// Set up your own socket handling for both protocols
|
||||
// Perfect for custom routing, load balancing, or clustering
|
||||
```
|
||||
|
||||
#### Advanced Use Cases
|
||||
|
||||
##### Load Balancing Across Multiple UDP Sockets
|
||||
|
||||
```typescript
|
||||
// Create multiple UDP sockets for different CPU cores
|
||||
const sockets = [];
|
||||
const numCPUs = require('os').cpus().length;
|
||||
|
||||
for (let i = 0; i < numCPUs; i++) {
|
||||
const socket = dgram.createSocket({
|
||||
type: 'udp4',
|
||||
reuseAddr: true // Allow multiple sockets on same port
|
||||
});
|
||||
|
||||
socket.on('message', (msg, rinfo) => {
|
||||
dnsServer.handleUdpMessage(msg, rinfo, (response, rinfo) => {
|
||||
socket.send(response, rinfo.port, rinfo.address);
|
||||
});
|
||||
});
|
||||
|
||||
socket.bind(53);
|
||||
sockets.push(socket);
|
||||
}
|
||||
```
|
||||
|
||||
##### Clustering with Worker Processes
|
||||
|
||||
```typescript
|
||||
import cluster from 'cluster';
|
||||
import { DnsServer } from '@push.rocks/smartdns/server';
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
// Master process accepts connections
|
||||
const server = net.createServer({ pauseOnConnect: true });
|
||||
|
||||
// Distribute connections to workers
|
||||
server.on('connection', (socket) => {
|
||||
const worker = getNextWorker(); // Round-robin or custom logic
|
||||
worker.send('socket', socket);
|
||||
});
|
||||
|
||||
server.listen(853);
|
||||
} else {
|
||||
// Worker process handles DNS
|
||||
const dnsServer = new DnsServer({
|
||||
httpsKey: '...',
|
||||
httpsCert: '...',
|
||||
httpsPort: 853,
|
||||
udpPort: 53,
|
||||
dnssecZone: 'example.com',
|
||||
manualHttpsMode: true
|
||||
});
|
||||
|
||||
process.on('message', (msg, socket) => {
|
||||
if (msg === 'socket') {
|
||||
dnsServer.handleHttpsSocket(socket);
|
||||
}
|
||||
});
|
||||
|
||||
await dnsServer.start();
|
||||
}
|
||||
```
|
||||
|
||||
##### Custom Transport Protocol
|
||||
|
||||
```typescript
|
||||
// Use DNS server with custom transport (e.g., WebSocket)
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const wss = new WebSocket.Server({ port: 8080 });
|
||||
const dnsServer = new DnsServer({
|
||||
httpsKey: '...',
|
||||
httpsCert: '...',
|
||||
httpsPort: 853,
|
||||
udpPort: 53,
|
||||
dnssecZone: 'example.com',
|
||||
manualUdpMode: true,
|
||||
manualHttpsMode: true
|
||||
});
|
||||
|
||||
await dnsServer.start();
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', (data) => {
|
||||
// Process DNS query from WebSocket
|
||||
const response = dnsServer.processRawDnsPacket(Buffer.from(data));
|
||||
ws.send(response);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
##### Multi-Interface Binding
|
||||
|
||||
```typescript
|
||||
// Bind to multiple network interfaces manually
|
||||
const interfaces = [
|
||||
{ address: '192.168.1.100', type: 'udp4' },
|
||||
{ address: '10.0.0.50', type: 'udp4' },
|
||||
{ address: '::1', type: 'udp6' }
|
||||
];
|
||||
|
||||
interfaces.forEach(({ address, type }) => {
|
||||
const socket = dgram.createSocket(type);
|
||||
|
||||
socket.on('message', (msg, rinfo) => {
|
||||
console.log(`Query received on ${address}`);
|
||||
dnsServer.handleUdpMessage(msg, rinfo, (response, rinfo) => {
|
||||
socket.send(response, rinfo.port, rinfo.address);
|
||||
});
|
||||
});
|
||||
|
||||
socket.bind(53, address);
|
||||
});
|
||||
```
|
||||
|
||||
### Handling Different Protocols
|
||||
|
||||
#### UDP DNS Server
|
||||
@ -573,6 +782,7 @@ await tap.start();
|
||||
5. **Monitoring**: Log DNS queries for debugging and analytics
|
||||
6. **Rate Limiting**: Implement rate limiting for public DNS servers
|
||||
7. **Caching**: Respect TTL values and implement proper caching
|
||||
8. **Manual Sockets**: Use manual socket handling for clustering and load balancing
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
@ -580,6 +790,7 @@ await tap.start();
|
||||
- The DNS server handles concurrent UDP and HTTPS requests efficiently
|
||||
- DNSSEC signatures are generated on-demand to reduce memory usage
|
||||
- Pattern matching uses caching for improved performance
|
||||
- Manual socket handling enables horizontal scaling across CPU cores
|
||||
|
||||
### Security Considerations
|
||||
|
||||
@ -589,6 +800,8 @@ await tap.start();
|
||||
- Implement access controls for DNS server handlers
|
||||
- Use Let's Encrypt for automatic SSL certificate management
|
||||
- Never expose internal network information through DNS
|
||||
- Bind to specific interfaces in production environments
|
||||
- Use manual socket handling for custom security layers
|
||||
|
||||
This comprehensive library provides everything needed for both DNS client operations and running production-grade DNS servers with modern security features in TypeScript.
|
||||
|
||||
|
211
readme.plan2.md
211
readme.plan2.md
@ -1,211 +0,0 @@
|
||||
# DNS Server Manual Socket Handling Implementation Plan
|
||||
|
||||
Command to reread CLAUDE.md: `cat /home/philkunz/.claude/CLAUDE.md`
|
||||
|
||||
## Overview
|
||||
Enable manual socket handling for the DNSServer class to allow external socket management instead of internal socket creation and binding. This enables advanced use cases like socket pooling, custom networking layers, and integration with existing socket infrastructures.
|
||||
|
||||
## Current Implementation Analysis
|
||||
- DNSServer currently creates and manages its own UDP (`dgram.Socket`) and HTTPS (`https.Server`) sockets
|
||||
- Socket creation happens in `start()` method at lines ~728-752 in `ts_server/classes.dnsserver.ts`
|
||||
- No mechanism exists to inject pre-created sockets
|
||||
- Socket lifecycle is tightly coupled with server lifecycle
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Update IDnsServerOptions Interface
|
||||
- Add optional `udpSocket?: dgram.Socket` property
|
||||
- Add optional `httpsServer?: https.Server` property
|
||||
- Add optional `manualMode?: boolean` flag to indicate manual socket handling
|
||||
- Maintain backwards compatibility with existing automatic socket creation
|
||||
|
||||
### 2. Modify DnsServer Constructor
|
||||
- Accept pre-created sockets in options
|
||||
- Store references to manual sockets
|
||||
- Set internal flags to track manual vs automatic mode
|
||||
|
||||
### 3. Update start() Method Logic
|
||||
- Skip socket creation if manual sockets provided
|
||||
- Validate manual sockets are in correct state
|
||||
- Attach event listeners to provided sockets
|
||||
- Support hybrid mode (UDP manual, HTTPS automatic, or vice versa)
|
||||
|
||||
### 4. Update stop() Method Logic
|
||||
- For manual sockets: only remove event listeners, don't close sockets
|
||||
- For automatic sockets: close sockets as current behavior
|
||||
- Provide clear separation of lifecycle management
|
||||
|
||||
### 5. Add Socket Validation
|
||||
- Verify provided UDP socket is correct type (udp4/udp6)
|
||||
- Ensure HTTPS server is properly configured
|
||||
- Validate socket state (not already listening, etc.)
|
||||
|
||||
### 6. Error Handling & Events
|
||||
- Emit events for socket errors without stopping server
|
||||
- Allow graceful handling of external socket failures
|
||||
- Provide clear error messages for socket state issues
|
||||
|
||||
## Benefits
|
||||
- **Socket Pooling**: Share sockets across multiple DNS server instances
|
||||
- **Custom Networking**: Integration with custom network stacks
|
||||
- **Performance**: Reuse existing socket infrastructure
|
||||
- **Advanced Configuration**: Fine-grained control over socket options
|
||||
- **Testing**: Easier mocking and testing with provided sockets
|
||||
- **Container Integration**: Better integration with orchestration platforms
|
||||
|
||||
## Example Usage After Implementation
|
||||
|
||||
### Manual UDP Socket with Automatic HTTPS
|
||||
```typescript
|
||||
import * as dgram from 'dgram';
|
||||
import { DnsServer } from '@push.rocks/smartdns/server';
|
||||
|
||||
// Create and configure UDP socket externally
|
||||
const udpSocket = dgram.createSocket('udp4');
|
||||
udpSocket.bind(53, '0.0.0.0');
|
||||
|
||||
const dnsServer = new DnsServer({
|
||||
// Manual UDP socket
|
||||
udpSocket: udpSocket,
|
||||
manualMode: true,
|
||||
|
||||
// Automatic HTTPS server
|
||||
httpsPort: 443,
|
||||
httpsKey: cert.key,
|
||||
httpsCert: cert.cert,
|
||||
dnssecZone: 'example.com'
|
||||
});
|
||||
|
||||
await dnsServer.start(); // Won't create new UDP socket
|
||||
```
|
||||
|
||||
### Fully Manual Mode
|
||||
```typescript
|
||||
import * as https from 'https';
|
||||
import * as dgram from 'dgram';
|
||||
|
||||
// Create both sockets externally
|
||||
const udpSocket = dgram.createSocket('udp4');
|
||||
const httpsServer = https.createServer({ key: cert.key, cert: cert.cert });
|
||||
|
||||
udpSocket.bind(53, '0.0.0.0');
|
||||
httpsServer.listen(443, '0.0.0.0');
|
||||
|
||||
const dnsServer = new DnsServer({
|
||||
udpSocket: udpSocket,
|
||||
httpsServer: httpsServer,
|
||||
manualMode: true,
|
||||
dnssecZone: 'example.com'
|
||||
});
|
||||
|
||||
await dnsServer.start(); // Only attaches handlers, no socket creation
|
||||
```
|
||||
|
||||
### Socket Pooling Example
|
||||
```typescript
|
||||
// Shared socket pool
|
||||
const socketPool = {
|
||||
udp: dgram.createSocket('udp4'),
|
||||
https: https.createServer(sslOptions)
|
||||
};
|
||||
|
||||
// Multiple DNS servers sharing sockets
|
||||
const server1 = new DnsServer({
|
||||
udpSocket: socketPool.udp,
|
||||
httpsServer: socketPool.https,
|
||||
manualMode: true,
|
||||
dnssecZone: 'zone1.com'
|
||||
});
|
||||
|
||||
const server2 = new DnsServer({
|
||||
udpSocket: socketPool.udp,
|
||||
httpsServer: socketPool.https,
|
||||
manualMode: true,
|
||||
dnssecZone: 'zone2.com'
|
||||
});
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### New Interface Properties
|
||||
```typescript
|
||||
export interface IDnsServerOptions {
|
||||
// Existing properties...
|
||||
httpsKey: string;
|
||||
httpsCert: string;
|
||||
httpsPort: number;
|
||||
udpPort: number;
|
||||
dnssecZone: string;
|
||||
udpBindInterface?: string;
|
||||
httpsBindInterface?: string;
|
||||
|
||||
// New manual socket properties
|
||||
udpSocket?: plugins.dgram.Socket;
|
||||
httpsServer?: plugins.https.Server;
|
||||
manualMode?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Modified Constructor Logic
|
||||
```typescript
|
||||
constructor(private options: IDnsServerOptions) {
|
||||
// Existing DNSSEC initialization...
|
||||
|
||||
// Handle manual sockets
|
||||
if (options.manualMode) {
|
||||
if (options.udpSocket) {
|
||||
this.udpServer = options.udpSocket;
|
||||
}
|
||||
if (options.httpsServer) {
|
||||
this.httpsServer = options.httpsServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Updated start() Method
|
||||
```typescript
|
||||
public async start(): Promise<void> {
|
||||
// Manual socket mode handling
|
||||
if (this.options.manualMode) {
|
||||
await this.startManualMode();
|
||||
} else {
|
||||
await this.startAutomaticMode();
|
||||
}
|
||||
}
|
||||
|
||||
private async startManualMode(): Promise<void> {
|
||||
if (this.udpServer) {
|
||||
this.attachUdpHandlers();
|
||||
}
|
||||
if (this.httpsServer) {
|
||||
this.attachHttpsHandlers();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Modify
|
||||
1. `ts_server/classes.dnsserver.ts` - Core implementation
|
||||
2. `test/test.server.ts` - Add manual socket tests
|
||||
3. `readme.md` - Documentation updates with examples
|
||||
|
||||
## Testing Strategy
|
||||
- Unit tests for manual socket validation
|
||||
- Integration tests for mixed manual/automatic mode
|
||||
- Socket lifecycle tests (start/stop behavior)
|
||||
- Error handling tests for invalid socket states
|
||||
- Performance tests comparing manual vs automatic modes
|
||||
|
||||
## Migration Path
|
||||
- Feature is completely backwards compatible
|
||||
- Existing code continues to work unchanged
|
||||
- New `manualMode` flag clearly indicates intent
|
||||
- Gradual adoption possible (UDP manual, HTTPS automatic, etc.)
|
||||
|
||||
## Security Considerations
|
||||
- Validate provided sockets are properly configured
|
||||
- Ensure manual sockets have appropriate permissions
|
||||
- Document security implications of socket sharing
|
||||
- Provide guidance on proper socket isolation
|
||||
|
||||
This enhancement enables advanced DNS server deployments while maintaining full backwards compatibility with existing implementations.
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartdns',
|
||||
version: '7.2.0',
|
||||
version: '7.4.0',
|
||||
description: 'A robust TypeScript library providing advanced DNS management and resolution capabilities including support for DNSSEC, custom DNS servers, and integration with various DNS providers.'
|
||||
}
|
||||
|
@ -10,6 +10,9 @@ export interface IDnsServerOptions {
|
||||
dnssecZone: string;
|
||||
udpBindInterface?: string;
|
||||
httpsBindInterface?: string;
|
||||
// New options for independent manual socket control
|
||||
manualUdpMode?: boolean;
|
||||
manualHttpsMode?: boolean;
|
||||
}
|
||||
|
||||
export interface DnsAnswer {
|
||||
@ -33,7 +36,6 @@ interface DNSKEYData {
|
||||
key: Buffer;
|
||||
}
|
||||
|
||||
|
||||
// Let's Encrypt related interfaces
|
||||
interface LetsEncryptOptions {
|
||||
email?: string;
|
||||
@ -51,6 +53,10 @@ export class DnsServer {
|
||||
private dnskeyRecord: DNSKEYData;
|
||||
private keyTag: number;
|
||||
|
||||
// Track if servers are initialized
|
||||
private udpServerInitialized: boolean = false;
|
||||
private httpsServerInitialized: boolean = false;
|
||||
|
||||
constructor(private options: IDnsServerOptions) {
|
||||
// Initialize DNSSEC
|
||||
this.dnsSec = new DnsSec({
|
||||
@ -68,6 +74,118 @@ export class DnsServer {
|
||||
this.keyTag = this.computeKeyTag(this.dnskeyRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize servers without binding to ports
|
||||
* This is called automatically by start() or can be called manually
|
||||
*/
|
||||
public initializeServers(): void {
|
||||
this.initializeUdpServer();
|
||||
this.initializeHttpsServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize UDP server without binding
|
||||
*/
|
||||
public initializeUdpServer(): void {
|
||||
if (this.udpServerInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create UDP socket without binding
|
||||
const udpInterface = this.options.udpBindInterface || '0.0.0.0';
|
||||
const socketType = this.isIPv6(udpInterface) ? 'udp6' : 'udp4';
|
||||
this.udpServer = plugins.dgram.createSocket(socketType);
|
||||
|
||||
// Set up UDP message handler
|
||||
this.udpServer.on('message', (msg, rinfo) => {
|
||||
this.handleUdpMessage(msg, rinfo);
|
||||
});
|
||||
|
||||
this.udpServer.on('error', (err) => {
|
||||
console.error(`UDP Server error:\n${err.stack}`);
|
||||
this.udpServer.close();
|
||||
});
|
||||
|
||||
this.udpServerInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize HTTPS server without binding
|
||||
*/
|
||||
public initializeHttpsServer(): void {
|
||||
if (this.httpsServerInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create HTTPS server without listening
|
||||
this.httpsServer = plugins.https.createServer(
|
||||
{
|
||||
key: this.options.httpsKey,
|
||||
cert: this.options.httpsCert,
|
||||
},
|
||||
this.handleHttpsRequest.bind(this)
|
||||
);
|
||||
|
||||
this.httpsServerInitialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a raw TCP socket for HTTPS/DoH
|
||||
* @param socket The TCP socket to handle
|
||||
*/
|
||||
public handleHttpsSocket(socket: plugins.net.Socket): void {
|
||||
if (!this.httpsServer) {
|
||||
this.initializeHttpsServer();
|
||||
}
|
||||
|
||||
// Emit connection event on the HTTPS server
|
||||
this.httpsServer.emit('connection', socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a UDP message manually
|
||||
* @param msg The DNS message buffer
|
||||
* @param rinfo Remote address information
|
||||
* @param responseCallback Optional callback to handle the response
|
||||
*/
|
||||
public handleUdpMessage(
|
||||
msg: Buffer,
|
||||
rinfo: plugins.dgram.RemoteInfo,
|
||||
responseCallback?: (response: Buffer, rinfo: plugins.dgram.RemoteInfo) => void
|
||||
): void {
|
||||
try {
|
||||
const request = dnsPacket.decode(msg);
|
||||
const response = this.processDnsRequest(request);
|
||||
const responseData = dnsPacket.encode(response);
|
||||
|
||||
if (responseCallback) {
|
||||
// Use custom callback if provided
|
||||
responseCallback(responseData, rinfo);
|
||||
} else if (this.udpServer && !this.options.manualUdpMode) {
|
||||
// Use the internal UDP server to send response
|
||||
this.udpServer.send(responseData, rinfo.port, rinfo.address);
|
||||
}
|
||||
// In manual mode without callback, caller is responsible for sending response
|
||||
} catch (err) {
|
||||
console.error('Error processing UDP DNS request:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a raw DNS packet and return the response
|
||||
* This is useful for custom transport implementations
|
||||
*/
|
||||
public processRawDnsPacket(packet: Buffer): Buffer {
|
||||
try {
|
||||
const request = dnsPacket.decode(packet);
|
||||
const response = this.processDnsRequest(request);
|
||||
return dnsPacket.encode(response);
|
||||
} catch (err) {
|
||||
console.error('Error processing raw DNS packet:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public registerHandler(
|
||||
domainPattern: string,
|
||||
recordTypes: string[],
|
||||
@ -256,8 +374,10 @@ export class DnsServer {
|
||||
this.options.httpsCert = certificate;
|
||||
this.options.httpsKey = privateKey;
|
||||
|
||||
// Restart HTTPS server with new certificate
|
||||
// Restart HTTPS server with new certificate (only if not in manual HTTPS mode)
|
||||
if (!this.options.manualHttpsMode) {
|
||||
await this.restartHttpsServer();
|
||||
}
|
||||
|
||||
// Clean up challenge handlers
|
||||
for (const handler of challengeHandlers) {
|
||||
@ -325,11 +445,15 @@ export class DnsServer {
|
||||
this.handleHttpsRequest.bind(this)
|
||||
);
|
||||
|
||||
if (!this.options.manualHttpsMode) {
|
||||
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
|
||||
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
|
||||
console.log(`HTTPS DNS server restarted on ${httpsInterface}:${this.options.httpsPort} with test certificate`);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -343,11 +467,15 @@ export class DnsServer {
|
||||
this.handleHttpsRequest.bind(this)
|
||||
);
|
||||
|
||||
if (!this.options.manualHttpsMode) {
|
||||
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
|
||||
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
|
||||
console.log(`HTTPS DNS server restarted on ${httpsInterface}:${this.options.httpsPort} with new certificate`);
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error creating HTTPS server with new certificate:', err);
|
||||
reject(err);
|
||||
@ -695,6 +823,27 @@ export class DnsServer {
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {
|
||||
// Initialize servers based on what's needed
|
||||
if (!this.options.manualUdpMode) {
|
||||
this.initializeUdpServer();
|
||||
}
|
||||
if (!this.options.manualHttpsMode) {
|
||||
this.initializeHttpsServer();
|
||||
}
|
||||
|
||||
// Handle different mode combinations
|
||||
const udpManual = this.options.manualUdpMode || false;
|
||||
const httpsManual = this.options.manualHttpsMode || false;
|
||||
|
||||
if (udpManual && httpsManual) {
|
||||
console.log('DNS server started in full manual mode - ready to accept connections');
|
||||
return;
|
||||
} else if (udpManual && !httpsManual) {
|
||||
console.log('DNS server started with manual UDP mode and automatic HTTPS binding');
|
||||
} else if (!udpManual && httpsManual) {
|
||||
console.log('DNS server started with automatic UDP binding and manual HTTPS mode');
|
||||
}
|
||||
|
||||
// Validate interface addresses if provided
|
||||
const udpInterface = this.options.udpBindInterface || '0.0.0.0';
|
||||
const httpsInterface = this.options.httpsBindInterface || '0.0.0.0';
|
||||
@ -707,47 +856,43 @@ export class DnsServer {
|
||||
throw new Error(`Invalid HTTPS bind interface: ${this.options.httpsBindInterface}`);
|
||||
}
|
||||
|
||||
this.httpsServer = plugins.https.createServer(
|
||||
{
|
||||
key: this.options.httpsKey,
|
||||
cert: this.options.httpsCert,
|
||||
},
|
||||
this.handleHttpsRequest.bind(this)
|
||||
);
|
||||
|
||||
// Create appropriate socket type based on interface
|
||||
const socketType = this.isIPv6(udpInterface) ? 'udp6' : 'udp4';
|
||||
this.udpServer = plugins.dgram.createSocket(socketType);
|
||||
this.udpServer.on('message', (msg, rinfo) => {
|
||||
const request = dnsPacket.decode(msg);
|
||||
const response = this.processDnsRequest(request);
|
||||
const responseData = dnsPacket.encode(response);
|
||||
this.udpServer.send(responseData, rinfo.port, rinfo.address);
|
||||
});
|
||||
|
||||
this.udpServer.on('error', (err) => {
|
||||
console.error(`UDP Server error:\n${err.stack}`);
|
||||
this.udpServer.close();
|
||||
});
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
// Bind UDP if not in manual UDP mode
|
||||
if (!udpManual) {
|
||||
const udpListeningDeferred = plugins.smartpromise.defer<void>();
|
||||
const httpsListeningDeferred = plugins.smartpromise.defer<void>();
|
||||
promises.push(udpListeningDeferred.promise);
|
||||
|
||||
try {
|
||||
this.udpServer.bind(this.options.udpPort, udpInterface, () => {
|
||||
console.log(`UDP DNS server running on ${udpInterface}:${this.options.udpPort}`);
|
||||
udpListeningDeferred.resolve();
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error starting UDP DNS server:', err);
|
||||
udpListeningDeferred.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Bind HTTPS if not in manual HTTPS mode
|
||||
if (!httpsManual) {
|
||||
const httpsListeningDeferred = plugins.smartpromise.defer<void>();
|
||||
promises.push(httpsListeningDeferred.promise);
|
||||
|
||||
try {
|
||||
this.httpsServer.listen(this.options.httpsPort, httpsInterface, () => {
|
||||
console.log(`HTTPS DNS server running on ${httpsInterface}:${this.options.httpsPort}`);
|
||||
httpsListeningDeferred.resolve();
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error starting DNS server:', err);
|
||||
process.exit(1);
|
||||
console.error('Error starting HTTPS DNS server:', err);
|
||||
httpsListeningDeferred.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length > 0) {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
await Promise.all([udpListeningDeferred.promise, httpsListeningDeferred.promise]);
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
@ -781,6 +926,8 @@ export class DnsServer {
|
||||
}
|
||||
|
||||
await Promise.all([doneUdp.promise, doneHttps.promise]);
|
||||
this.udpServerInitialized = false;
|
||||
this.httpsServerInitialized = false;
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
@ -4,14 +4,16 @@ import dgram from 'dgram';
|
||||
import fs from 'fs';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import * as net from 'net';
|
||||
import * as path from 'path';
|
||||
|
||||
export {
|
||||
crypto,
|
||||
dgram,
|
||||
fs,
|
||||
http,
|
||||
https,
|
||||
dgram,
|
||||
net,
|
||||
path,
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user