551 lines
16 KiB
Markdown
551 lines
16 KiB
Markdown
# @push.rocks/smartdns
|
|
|
|
A robust TypeScript library providing advanced DNS management and resolution capabilities including support for DNSSEC, custom DNS servers, and integration with various DNS providers.
|
|
|
|
## Install
|
|
|
|
To install `@push.rocks/smartdns`, use the following command with pnpm:
|
|
|
|
```bash
|
|
pnpm install @push.rocks/smartdns --save
|
|
```
|
|
|
|
Or with npm:
|
|
|
|
```bash
|
|
npm install @push.rocks/smartdns --save
|
|
```
|
|
|
|
Make sure you have a TypeScript environment set up to utilize the library effectively.
|
|
|
|
## Usage
|
|
|
|
`@push.rocks/smartdns` is a comprehensive library that provides both DNS client and server capabilities, leveraging TypeScript for enhanced development experience. The library is organized into three modules:
|
|
|
|
- **Client Module** (`@push.rocks/smartdns/client`): DNS resolution and record queries
|
|
- **Server Module** (`@push.rocks/smartdns/server`): Full DNS server implementation with DNSSEC
|
|
- **Main Module** (`@push.rocks/smartdns`): Convenience exports for both client and server
|
|
|
|
### Getting Started
|
|
|
|
You can import the modules based on your needs:
|
|
|
|
```typescript
|
|
// For DNS client operations
|
|
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
|
|
// For DNS server operations
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
|
|
// Or import from the main module (note the different syntax)
|
|
import { dnsClientMod, dnsServerMod } from '@push.rocks/smartdns';
|
|
const dnsClient = new dnsClientMod.Smartdns({});
|
|
const dnsServer = new dnsServerMod.DnsServer({ /* options */ });
|
|
```
|
|
|
|
### DNS Client Operations
|
|
|
|
The DNS client (`Smartdns` class) provides methods to query various DNS record types using DNS-over-HTTPS (DoH) with Cloudflare as the primary provider, with fallback to Node.js DNS resolver.
|
|
|
|
#### Fetching A Records
|
|
|
|
To fetch "A" records (IPv4 addresses) for a domain:
|
|
|
|
```typescript
|
|
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
|
|
const dnsClient = new Smartdns({});
|
|
const aRecords = await dnsClient.getRecordsA('example.com');
|
|
console.log(aRecords);
|
|
// Output: [{ name: 'example.com', type: 'A', dnsSecEnabled: false, value: '93.184.215.14' }]
|
|
```
|
|
|
|
#### Fetching AAAA Records
|
|
|
|
For resolving a domain to IPv6 addresses:
|
|
|
|
```typescript
|
|
const aaaaRecords = await dnsClient.getRecordsAAAA('example.com');
|
|
console.log(aaaaRecords);
|
|
// Output: [{ name: 'example.com', type: 'AAAA', dnsSecEnabled: false, value: '2606:2800:21f:cb07:6820:80da:af6b:8b2c' }]
|
|
```
|
|
|
|
#### Fetching TXT Records
|
|
|
|
TXT records store text data, commonly used for domain verification, SPF records, and other metadata:
|
|
|
|
```typescript
|
|
const txtRecords = await dnsClient.getRecordsTxt('example.com');
|
|
console.log(txtRecords);
|
|
// Output: [{ name: 'example.com', type: 'TXT', dnsSecEnabled: false, value: 'v=spf1 -all' }]
|
|
```
|
|
|
|
#### Other Record Types
|
|
|
|
The client supports various other DNS record types:
|
|
|
|
```typescript
|
|
// MX records for mail servers
|
|
const mxRecords = await dnsClient.getRecords('example.com', 'MX');
|
|
|
|
// NS records for nameservers
|
|
const nsRecords = await dnsClient.getNameServers('example.com');
|
|
|
|
// Generic query method with retry support
|
|
const records = await dnsClient.getRecords('example.com', 'CNAME', { retryCount: 3 });
|
|
```
|
|
|
|
### Advanced DNS Features
|
|
|
|
#### Checking DNS Propagation
|
|
|
|
The client provides a powerful method to verify DNS propagation globally, essential when making DNS changes:
|
|
|
|
```typescript
|
|
// Check if a specific DNS record has propagated
|
|
const recordType = 'TXT';
|
|
const expectedValue = 'verification=abc123';
|
|
|
|
const isAvailable = await dnsClient.checkUntilAvailable(
|
|
'example.com',
|
|
recordType,
|
|
expectedValue,
|
|
50, // Number of check cycles (default: 50)
|
|
500 // Interval between checks in ms (default: 500)
|
|
);
|
|
|
|
if (isAvailable) {
|
|
console.log('DNS record has propagated successfully!');
|
|
} else {
|
|
console.log('DNS propagation timeout - record not found.');
|
|
}
|
|
```
|
|
|
|
#### Configuring System DNS Provider
|
|
|
|
You can configure Node.js to use a specific DNS provider for all DNS queries:
|
|
|
|
```typescript
|
|
// Import the standalone function
|
|
import { makeNodeProcessUseDnsProvider } from '@push.rocks/smartdns/client';
|
|
|
|
// Use Cloudflare DNS for all Node.js DNS operations
|
|
makeNodeProcessUseDnsProvider('cloudflare');
|
|
|
|
// Or use Google DNS
|
|
makeNodeProcessUseDnsProvider('google');
|
|
```
|
|
|
|
### Real-World Use Cases
|
|
|
|
#### DNS-Based Feature Flagging
|
|
|
|
Use TXT records for dynamic feature toggles without redeployment:
|
|
|
|
```typescript
|
|
const txtRecords = await dnsClient.getRecordsTxt('features.example.com');
|
|
const featureFlags = {};
|
|
|
|
txtRecords.forEach(record => {
|
|
// Parse TXT records like "feature-dark-mode=true"
|
|
const [feature, enabled] = record.value.split('=');
|
|
featureFlags[feature] = enabled === 'true';
|
|
});
|
|
|
|
if (featureFlags['feature-dark-mode']) {
|
|
console.log('Dark mode is enabled!');
|
|
}
|
|
```
|
|
|
|
#### Service Discovery
|
|
|
|
Use DNS for service endpoint discovery:
|
|
|
|
```typescript
|
|
// Discover API endpoints via TXT records
|
|
const serviceRecords = await dnsClient.getRecordsTxt('_services.example.com');
|
|
|
|
// Discover mail servers
|
|
const mxRecords = await dnsClient.getRecords('example.com', 'MX');
|
|
const primaryMailServer = mxRecords
|
|
.sort((a, b) => a.priority - b.priority)[0]?.exchange;
|
|
```
|
|
|
|
### DNS Server Implementation
|
|
|
|
The `DnsServer` class provides a full-featured DNS server with support for UDP, DNS-over-HTTPS (DoH), DNSSEC, and automatic SSL certificate management via Let's Encrypt.
|
|
|
|
#### Basic DNS Server Setup
|
|
|
|
Create a simple DNS server that responds to queries:
|
|
|
|
```typescript
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
|
|
const dnsServer = new DnsServer({
|
|
udpPort: 5333, // UDP port for DNS queries
|
|
httpsPort: 8443, // HTTPS port for DNS-over-HTTPS
|
|
httpsKey: 'path/to/key.pem', // Required for HTTPS
|
|
httpsCert: 'path/to/cert.pem', // Required for HTTPS
|
|
dnssecZone: 'example.com' // Optional: enable DNSSEC for this zone
|
|
});
|
|
|
|
// Register a handler for all subdomains of example.com
|
|
dnsServer.registerHandler('*.example.com', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '192.168.1.100',
|
|
}));
|
|
|
|
// Register a handler for TXT records
|
|
dnsServer.registerHandler('example.com', ['TXT'], (question) => ({
|
|
name: question.name,
|
|
type: 'TXT',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: 'v=spf1 include:_spf.example.com ~all',
|
|
}));
|
|
|
|
// Start the server
|
|
await dnsServer.start();
|
|
console.log('DNS Server started!');
|
|
```
|
|
|
|
### DNSSEC Support
|
|
|
|
The DNS server includes comprehensive DNSSEC support with automatic key generation and record signing:
|
|
|
|
```typescript
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
|
|
const dnsServer = new DnsServer({
|
|
udpPort: 53,
|
|
httpsPort: 443,
|
|
dnssecZone: 'secure.example.com', // Enable DNSSEC for this zone
|
|
});
|
|
|
|
// The server automatically:
|
|
// 1. Generates DNSKEY records with ECDSA (algorithm 13)
|
|
// 2. Creates DS records for parent zone delegation
|
|
// 3. Signs all responses with RRSIG records
|
|
// 4. Provides NSEC records for authenticated denial of existence
|
|
|
|
// Register your handlers as normal - DNSSEC signing is automatic
|
|
dnsServer.registerHandler('secure.example.com', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '192.168.1.1',
|
|
}));
|
|
|
|
await dnsServer.start();
|
|
|
|
// Query for DNSSEC records
|
|
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
const client = new Smartdns({});
|
|
const dnskeyRecords = await client.getRecords('secure.example.com', 'DNSKEY');
|
|
const dsRecords = await client.getRecords('secure.example.com', 'DS');
|
|
```
|
|
|
|
#### Supported DNSSEC Algorithms
|
|
|
|
The server supports multiple DNSSEC algorithms:
|
|
- **ECDSAP256SHA256** (Algorithm 13) - Default, using P-256 curve
|
|
- **ED25519** (Algorithm 15) - Modern elliptic curve algorithm
|
|
- **RSASHA256** (Algorithm 8) - RSA-based signatures
|
|
|
|
### Let's Encrypt Integration
|
|
|
|
The DNS server includes built-in Let's Encrypt support for automatic SSL certificate management:
|
|
|
|
```typescript
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
|
|
const dnsServer = new DnsServer({
|
|
udpPort: 53,
|
|
httpsPort: 443,
|
|
httpsKey: '/path/to/letsencrypt/key.pem', // Will be auto-generated
|
|
httpsCert: '/path/to/letsencrypt/cert.pem', // Will be auto-generated
|
|
});
|
|
|
|
// Retrieve Let's Encrypt certificate for your domain
|
|
const result = await dnsServer.retrieveSslCertificate(
|
|
['secure.example.com', 'www.secure.example.com'],
|
|
{
|
|
email: 'admin@example.com',
|
|
staging: false, // Use production Let's Encrypt
|
|
certDir: './certs'
|
|
}
|
|
);
|
|
|
|
if (result.success) {
|
|
console.log('Certificate retrieved successfully!');
|
|
}
|
|
|
|
// The server automatically:
|
|
// 1. Handles ACME DNS-01 challenges
|
|
// 2. Creates temporary TXT records for domain validation
|
|
// 3. Retrieves and installs the certificate
|
|
// 4. Restarts the HTTPS server with the new certificate
|
|
|
|
await dnsServer.start();
|
|
console.log('DNS Server with Let\'s Encrypt SSL started!');
|
|
```
|
|
|
|
### Handling Different Protocols
|
|
|
|
#### UDP DNS Server
|
|
|
|
Traditional DNS queries over UDP (port 53):
|
|
|
|
```typescript
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
import * as plugins from '@push.rocks/smartdns/server/plugins';
|
|
|
|
const dnsServer = new DnsServer({
|
|
udpPort: 5353, // Using alternate port for testing
|
|
httpsPort: 8443,
|
|
httpsKey: fs.readFileSync('/path/to/key.pem', 'utf8'),
|
|
httpsCert: fs.readFileSync('/path/to/cert.pem', 'utf8'),
|
|
dnssecZone: 'test.local' // Optional
|
|
});
|
|
|
|
// The UDP server automatically handles DNS packet parsing and encoding
|
|
dnsServer.registerHandler('test.local', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 60,
|
|
data: '127.0.0.1',
|
|
}));
|
|
|
|
await dnsServer.start();
|
|
|
|
// Test with dig or nslookup:
|
|
// dig @localhost -p 5353 test.local
|
|
```
|
|
|
|
#### DNS-over-HTTPS (DoH) Server
|
|
|
|
Provide encrypted DNS queries over HTTPS:
|
|
|
|
```typescript
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
import * as fs from 'fs';
|
|
|
|
const dnsServer = new DnsServer({
|
|
httpsPort: 8443,
|
|
httpsKey: fs.readFileSync('/path/to/key.pem', 'utf8'),
|
|
httpsCert: fs.readFileSync('/path/to/cert.pem', 'utf8'),
|
|
});
|
|
|
|
// The HTTPS server automatically handles:
|
|
// - DNS wire format in POST body
|
|
// - Proper Content-Type headers (application/dns-message)
|
|
// - Base64url encoding for GET requests
|
|
|
|
dnsServer.registerHandler('secure.local', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '10.0.0.1',
|
|
}));
|
|
|
|
await dnsServer.start();
|
|
|
|
// Test with curl:
|
|
// curl -H "Content-Type: application/dns-message" \
|
|
// --data-binary @query.bin \
|
|
// https://localhost:8443/dns-query
|
|
```
|
|
|
|
### Advanced Handler Patterns
|
|
|
|
#### Pattern-Based Routing
|
|
|
|
Use glob patterns for flexible domain matching:
|
|
|
|
```typescript
|
|
// Match all subdomains
|
|
dnsServer.registerHandler('*.example.com', ['A'], (question) => {
|
|
// Extract subdomain
|
|
const subdomain = question.name.replace('.example.com', '');
|
|
|
|
// Dynamic response based on subdomain
|
|
return {
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: subdomain === 'api' ? '10.0.0.10' : '10.0.0.1',
|
|
};
|
|
});
|
|
|
|
// Match specific patterns
|
|
dnsServer.registerHandler('db-*.service.local', ['A'], (question) => {
|
|
const instanceId = question.name.match(/db-(\d+)/)?.[1];
|
|
return {
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 60,
|
|
data: `10.0.1.${instanceId}`,
|
|
};
|
|
});
|
|
|
|
// Catch-all handler
|
|
dnsServer.registerHandler('*', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '127.0.0.1',
|
|
}));
|
|
```
|
|
|
|
### Testing
|
|
|
|
The library uses `@git.zone/tstest` for testing. Here's an example of comprehensive tests:
|
|
|
|
```typescript
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
|
|
// Test DNS Client
|
|
tap.test('DNS Client - Query Records', async () => {
|
|
const dnsClient = new Smartdns({});
|
|
|
|
// Test A record query
|
|
const aRecords = await dnsClient.getRecordsA('google.com');
|
|
expect(aRecords).toBeArray();
|
|
expect(aRecords[0]).toHaveProperty('type', 'A');
|
|
expect(aRecords[0].data).toMatch(/^\d+\.\d+\.\d+\.\d+$/);
|
|
|
|
// Test TXT record query
|
|
const txtRecords = await dnsClient.getRecordsTxt('google.com');
|
|
expect(txtRecords).toBeArray();
|
|
expect(txtRecords[0]).toHaveProperty('type', 'TXT');
|
|
});
|
|
|
|
// Test DNS Server
|
|
let dnsServer: DnsServer;
|
|
|
|
tap.test('DNS Server - Setup and Start', async () => {
|
|
dnsServer = new DnsServer({
|
|
udpPort: 5353,
|
|
httpsPort: 8443,
|
|
httpsKey: 'test-key', // Use test certificates
|
|
httpsCert: 'test-cert',
|
|
dnssecZone: 'test.local'
|
|
});
|
|
|
|
expect(dnsServer).toBeInstanceOf(DnsServer);
|
|
await dnsServer.start();
|
|
});
|
|
|
|
tap.test('DNS Server - Register Handlers', async () => {
|
|
// Register multiple handlers
|
|
dnsServer.registerHandler('test.local', ['A'], () => ({
|
|
name: 'test.local',
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 300,
|
|
data: '127.0.0.1',
|
|
}));
|
|
|
|
dnsServer.registerHandler('*.test.local', ['A'], (question) => ({
|
|
name: question.name,
|
|
type: 'A',
|
|
class: 'IN',
|
|
ttl: 60,
|
|
data: '127.0.0.2',
|
|
}));
|
|
});
|
|
|
|
tap.test('DNS Server - Query via UDP', async (tools) => {
|
|
const dnsPacket = (await import('dns-packet')).default;
|
|
const dgram = await import('dgram');
|
|
|
|
const query = dnsPacket.encode({
|
|
type: 'query',
|
|
id: 1234,
|
|
questions: [{
|
|
type: 'A',
|
|
class: 'IN',
|
|
name: 'test.local',
|
|
}],
|
|
});
|
|
|
|
const client = dgram.createSocket('udp4');
|
|
const done = tools.defer();
|
|
|
|
client.on('message', (msg) => {
|
|
const response = dnsPacket.decode(msg);
|
|
expect(response.answers[0].data).toEqual('127.0.0.1');
|
|
client.close();
|
|
done.resolve();
|
|
});
|
|
|
|
client.send(query, 5353, 'localhost'); // Use the port specified during server creation
|
|
await done.promise;
|
|
});
|
|
|
|
tap.test('DNS Server - Cleanup', async () => {
|
|
await dnsServer.stop();
|
|
});
|
|
|
|
// Run tests
|
|
await tap.start();
|
|
```
|
|
|
|
### Best Practices
|
|
|
|
1. **Port Selection**: Use non-privileged ports (>1024) during development
|
|
2. **Handler Organization**: Group related handlers together
|
|
3. **Error Handling**: Always handle DNS query errors gracefully
|
|
4. **DNSSEC**: Enable DNSSEC for production deployments
|
|
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
|
|
|
|
### Performance Considerations
|
|
|
|
- The DNS client uses HTTP keep-alive for connection reuse
|
|
- 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
|
|
|
|
### Security Considerations
|
|
|
|
- Always use DNSSEC for authenticated responses
|
|
- Enable DoH for encrypted DNS queries
|
|
- Validate and sanitize all DNS inputs
|
|
- Implement access controls for DNS server handlers
|
|
- Use Let's Encrypt for automatic SSL certificate management
|
|
- Never expose internal network information through DNS
|
|
|
|
This comprehensive library provides everything needed for both DNS client operations and running production-grade DNS servers with modern security features in TypeScript.
|
|
|
|
## License and Legal Information
|
|
|
|
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license.md) file within this repository.
|
|
|
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
|
|
### Trademarks
|
|
|
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
|
|
|
### Company Information
|
|
|
|
Task Venture Capital GmbH
|
|
Registered at District court Bremen HRB 35230 HB, Germany
|
|
|
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
|
|
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. |