fix(dns): Fixed Soa records

This commit is contained in:
Philipp Kunz 2025-05-30 19:54:48 +00:00
parent 71183b35c0
commit 74692c4aa5
4 changed files with 50 additions and 184 deletions

View File

@ -20,7 +20,7 @@
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^2.3.1",
"@git.zone/tswatch": "^2.0.1",
"@types/node": "^22.15.24",
"@types/node": "^22.15.29",
"node-forge": "^1.3.1"
},
"dependencies": {
@ -32,7 +32,7 @@
"@push.rocks/qenv": "^6.1.0",
"@push.rocks/smartacme": "^8.0.0",
"@push.rocks/smartdata": "^5.15.1",
"@push.rocks/smartdns": "^7.4.0",
"@push.rocks/smartdns": "^7.4.7",
"@push.rocks/smartfile": "^11.2.5",
"@push.rocks/smartlog": "^3.1.8",
"@push.rocks/smartmail": "^2.1.0",

82
pnpm-lock.yaml generated
View File

@ -33,8 +33,8 @@ importers:
specifier: ^5.15.1
version: 5.15.1(@aws-sdk/credential-providers@3.817.0)(socks@2.8.4)
'@push.rocks/smartdns':
specifier: ^7.4.0
version: 7.4.0
specifier: ^7.4.7
version: 7.4.7
'@push.rocks/smartfile':
specifier: ^11.2.5
version: 11.2.5
@ -103,8 +103,8 @@ importers:
specifier: ^2.0.1
version: 2.1.0
'@types/node':
specifier: ^22.15.24
version: 22.15.24
specifier: ^22.15.29
version: 22.15.29
node-forge:
specifier: ^1.3.1
version: 1.3.1
@ -833,8 +833,8 @@ packages:
'@push.rocks/smartdns@6.2.2':
resolution: {integrity: sha512-MhJcHujbyIuwIIFdnXb2OScGtRjNsliLUS8GoAurFsKtcCOaA0ytfP+PNzkukyBufjb1nMiJF3rjhswXdHakAQ==}
'@push.rocks/smartdns@7.4.0':
resolution: {integrity: sha512-dedSy946AUOeT2EzgbcEC1IVeT+3wNDzw/28anQXL3juMdA05T1aP7TkSOee3uU8AUjVx2ZASz/2D/TpdBDWsw==}
'@push.rocks/smartdns@7.4.7':
resolution: {integrity: sha512-sgOUqtGJstRvt8+aoAR13L9g/m3sZNY5ai9qpczhDnxGW9uP7+JnthMtQHrhnMiNbZxxq1cBCuRN4YVz6R4V5Q==}
'@push.rocks/smartenv@5.0.12':
resolution: {integrity: sha512-tDEFwywzq0FNzRYc9qY2dRl2pgQuZG0G2/yml2RLWZWSW+Fn1EHshnKOGHz8o77W7zvu4hTgQQX42r/JY5XHTg==}
@ -1500,11 +1500,11 @@ packages:
'@types/node-forge@1.3.11':
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
'@types/node@18.19.105':
resolution: {integrity: sha512-a+DrwD2VyzqQR2W0EVF8EaCh6Em4ilQAYLEPZnMNkQHXR7ziWW7RUhZMWZAgRpkDDAdUIcJOXSPJT/zBEwz3sA==}
'@types/node@18.19.110':
resolution: {integrity: sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==}
'@types/node@22.15.24':
resolution: {integrity: sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==}
'@types/node@22.15.29':
resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
'@types/ping@0.4.4':
resolution: {integrity: sha512-ifvo6w2f5eJYlXm+HiVx67iJe8WZp87sfa683nlqED5Vnt9Z93onkokNoWqOG21EaE8fMxyKPobE+mkPEyxsdw==}
@ -5131,10 +5131,8 @@ snapshots:
'@push.rocks/taskbuffer': 3.1.7
transitivePeerDependencies:
- '@nuxt/kit'
- bufferutil
- react
- supports-color
- utf-8-validate
- vue
'@hapi/hoek@9.3.0': {}
@ -5571,7 +5569,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@push.rocks/smartdns@7.4.0':
'@push.rocks/smartdns@7.4.7':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartenv': 5.0.12
@ -6651,27 +6649,27 @@ snapshots:
'@types/bn.js@5.1.6':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/buffer-json@2.0.3': {}
'@types/clean-css@4.2.11':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
source-map: 0.6.1
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/cors@2.8.18':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/debug@4.1.12':
dependencies:
@ -6683,7 +6681,7 @@ snapshots:
'@types/dns-packet@5.6.5':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/elliptic@6.4.18':
dependencies:
@ -6691,7 +6689,7 @@ snapshots:
'@types/express-serve-static-core@5.0.6':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@ -6708,30 +6706,30 @@ snapshots:
'@types/from2@2.3.5':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/fs-extra@9.0.13':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/gunzip-maybe@1.4.2':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/hast@3.0.4':
dependencies:
@ -6753,11 +6751,11 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/mailparser@3.4.6':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
iconv-lite: 0.6.3
'@types/mdast@4.0.4':
@ -6776,18 +6774,18 @@ snapshots:
'@types/node-fetch@2.6.12':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
form-data: 4.0.2
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/node@18.19.105':
'@types/node@18.19.110':
dependencies:
undici-types: 5.26.5
'@types/node@22.15.24':
'@types/node@22.15.29':
dependencies:
undici-types: 6.21.0
@ -6803,30 +6801,30 @@ snapshots:
'@types/s3rver@3.7.4':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/semver@7.7.0': {}
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/send': 0.17.4
'@types/symbol-tree@3.2.5': {}
'@types/tar-stream@2.2.3':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/through2@2.0.41':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/triple-beam@1.3.5': {}
@ -6850,18 +6848,18 @@ snapshots:
'@types/whatwg-url@8.2.2':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/webidl-conversions': 7.0.3
'@types/which@3.0.4': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.15.24
'@types/node': 22.15.29
optional: true
'@ungap/structured-clone@1.3.0': {}
@ -7141,7 +7139,7 @@ snapshots:
cloudflare@4.2.0:
dependencies:
'@types/node': 18.19.105
'@types/node': 18.19.110
'@types/node-fetch': 2.6.12
abort-controller: 3.0.0
agentkeepalive: 4.6.0
@ -7412,7 +7410,7 @@ snapshots:
engine.io@6.6.4:
dependencies:
'@types/cors': 2.8.18
'@types/node': 22.15.24
'@types/node': 22.15.29
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2

View File

@ -1,126 +0,0 @@
# SmartDNS DnsServer Issues and Required Changes
## Current Limitation: Cannot Serve Multiple Records of Same Type
### Problem Description
The current implementation of `@push.rocks/smartdns` DnsServer has a critical limitation that prevents it from serving multiple DNS records of the same type for a domain. This is particularly problematic for:
- **NS records** - Most domains need 2+ nameservers for redundancy
- **A/AAAA records** - Round-robin DNS requires multiple IPs
- **MX records** - Multiple mail servers with different priorities
- **TXT records** - Multiple TXT records for SPF, DKIM, domain verification, etc.
### Root Cause
In the `processDnsRequest` method of DnsServer, when processing DNS queries:
```javascript
for (const handlerEntry of this.handlers) {
if (plugins.minimatch.minimatch(question.name, handlerEntry.domainPattern) &&
handlerEntry.recordTypes.includes(question.type)) {
const answer = handlerEntry.handler(question);
if (answer) {
response.answers.push(dnsAnswer);
answered = true;
break; // <-- PROBLEM: Exits after first matching handler!
}
}
}
```
The `break` statement causes the loop to exit after the first matching handler returns an answer, preventing subsequent handlers from being called.
### Impact
- **NS Records**: Only one nameserver is returned instead of multiple
- **Domain Registration Failures**: Registrars like GoDaddy reject domains with only one NS record
- **No Round-Robin**: Cannot implement round-robin DNS with multiple A records
- **Limited TXT Records**: Cannot serve multiple TXT records (SPF + DKIM + others)
### Required Changes
#### Option 1: Continue Processing All Handlers (Recommended)
Remove the `break` statement to allow all matching handlers to contribute answers:
```javascript
for (const handlerEntry of this.handlers) {
if (plugins.minimatch.minimatch(question.name, handlerEntry.domainPattern) &&
handlerEntry.recordTypes.includes(question.type)) {
const answer = handlerEntry.handler(question);
if (answer) {
response.answers.push(dnsAnswer);
if (dnssecRequested) {
const rrsig = this.generateRRSIG(question.type, [dnsAnswer], question.name);
response.answers.push(rrsig);
}
answered = true;
// Don't break - continue checking other handlers
}
}
}
```
#### Option 2: Handler Returns Array
Modify the handler interface to allow returning multiple answers:
```typescript
export interface IDnsHandler {
domainPattern: string;
recordTypes: string[];
handler: (question: dnsPacket.Question) => DnsAnswer | DnsAnswer[] | null;
}
```
Then update processDnsRequest to handle arrays:
```javascript
const answer = handlerEntry.handler(question);
if (answer) {
const answers = Array.isArray(answer) ? answer : [answer];
for (const ans of answers) {
response.answers.push({...ans, ttl: ans.ttl || 300, class: ans.class || 'IN'});
}
answered = true;
break; // Can break here since handler returns all records
}
```
### Workaround (Current Implementation)
Currently working around this by registering separate handlers for each record. This is inefficient but functional:
```typescript
// Instead of one handler for multiple NS records
// We register separate handlers for each NS record
for (const record of records) {
this.dnsServer.registerHandler(record.name, [record.type], (question) => {
if (question.name === record.name && question.type === record.type) {
return { /* single record */ };
}
return null;
});
}
```
### Other Issues Found
#### 1. SOA Record Timeout
SOA queries sometimes timeout or return incorrect data. This may be related to the DNSSEC implementation or SOA record generation.
#### 2. DNSSEC Zone Prefix
The DnsServer automatically prefixes 'ns1.' to the dnssecZone:
```javascript
mname: `ns1.${this.options.dnssecZone}`
```
This can cause mismatches when the actual nameserver doesn't have the 'ns1.' prefix.
### Testing the Fix
After implementing Option 1 or 2, test with:
```bash
# Should return BOTH nameservers
dig @nameserver.example.com domain.com NS
# Should show multiple NS records in answer section
domain.com. 3600 IN NS ns1.example.com.
domain.com. 3600 IN NS ns2.example.com.
```
### Priority
**HIGH** - This issue prevents proper DNS server operation and blocks domain registration at many registrars.

View File

@ -79,7 +79,7 @@ export interface IDcRouterOptions {
/**
* DNS records to register
* Must be within the defined dnsScopes (or receive warning)
* Only need A, CNAME, TXT, MX records (NS and SOA are auto-generated)
* Only need A, CNAME, TXT, MX records (NS records auto-generated, SOA handled by smartdns)
* Can use `useIngressProxy: false` to expose real server IP (defaults to true)
*/
dnsRecords?: Array<{
@ -788,6 +788,7 @@ export class DcRouter {
httpsPort: 443, // Required but won't bind due to manual mode
manualHttpsMode: true, // Enable manual HTTPS socket handling
dnssecZone: primaryNameserver,
primaryNameserver: primaryNameserver, // Automatically generates correct SOA records
// For now, use self-signed cert until we integrate with Let's Encrypt
httpsKey: '',
httpsCert: ''
@ -937,7 +938,8 @@ export class DcRouter {
}
/**
* Generate authoritative DNS records (NS and SOA) for all domains in dnsScopes
* Generate authoritative DNS records (NS only) for all domains in dnsScopes
* SOA records are now automatically generated by smartdns with primaryNameserver setting
*/
private async generateAuthoritativeRecords(): Promise<Array<{name: string; type: string; value: string; ttl?: number}>> {
const records: Array<{name: string; type: string; value: string; ttl?: number}> = [];
@ -946,9 +948,7 @@ export class DcRouter {
return records;
}
const primaryNameserver = this.options.dnsNsDomains[0];
// Generate NS and SOA records for each domain in scopes
// Generate NS records for each domain in scopes
for (const domain of this.options.dnsScopes) {
// Add NS records for all nameservers
for (const nsDomain of this.options.dnsNsDomains) {
@ -960,17 +960,11 @@ export class DcRouter {
});
}
// Add SOA record with first nameserver as primary
const soaValue = `${primaryNameserver} hostmaster.${domain} ${Date.now()} 7200 3600 1209600 3600`;
records.push({
name: domain,
type: 'SOA',
value: soaValue,
ttl: 3600
});
// SOA records are now automatically generated by smartdns DnsServer
// with the primaryNameserver configuration option
}
logger.log('info', `Generated ${records.length} authoritative records for ${this.options.dnsScopes.length} domains`);
logger.log('info', `Generated ${records.length} NS records for ${this.options.dnsScopes.length} domains`);
return records;
}