@serve.zone/dcrouter 18.7.1 → 18.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +2528 -2206
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +15 -27
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +26 -69
- package/dist_ts/dns/classes.dns-server-runtime.js +136 -96
- package/dist_ts/dns/domain-ownership.d.ts +4 -5
- package/dist_ts/dns/domain-ownership.js +1 -1
- package/dist_ts/dns/manager.dns.d.ts +11 -13
- package/dist_ts/dns/manager.dns.js +155 -73
- package/dist_ts/email/classes.mail-dns-sync.d.ts +0 -1
- package/dist_ts/email/classes.mail-dns-sync.js +10 -38
- package/dist_ts_migrations/index.js +32 -10
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +25 -24
- package/readme.hints.md +98 -84
- package/readme.md +8 -6
- package/third-party-notices.md +13 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +18 -34
- package/ts/dns/classes.dns-server-runtime.ts +154 -105
- package/ts/dns/domain-ownership.ts +4 -5
- package/ts/dns/manager.dns.ts +180 -73
- package/ts/email/classes.mail-dns-sync.ts +9 -35
- package/ts_web/00_commitinfo_data.ts +1 -1
package/ts/dns/manager.dns.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
DnsRecordDoc,
|
|
7
7
|
EmailDomainDoc,
|
|
8
8
|
} from '../db/documents/index.js';
|
|
9
|
+
import { DcRouterDb } from '../db/classes.dcrouter-db.js';
|
|
9
10
|
import type { IDcRouterOptions } from '../classes.dcrouter.js';
|
|
10
11
|
import type { IDnsProviderClient, IProviderRecord } from './providers/interfaces.js';
|
|
11
12
|
import { createDnsProvider } from './providers/factory.js';
|
|
@@ -42,6 +43,10 @@ export interface IDnsRuntimeRegistration {
|
|
|
42
43
|
origin: TDnsRuntimeRegistrationOrigin;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
interface IDnsRuntimeRegistrationState extends IDnsRuntimeRegistration {
|
|
47
|
+
registrations: plugins.smartdns.dnsServerMod.IDnsHandlerRegistration[];
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
export interface IDnsRecordDeleteResult {
|
|
46
51
|
id: string;
|
|
47
52
|
success: boolean;
|
|
@@ -80,21 +85,18 @@ export class DnsManager {
|
|
|
80
85
|
* keyed by DomainDoc.id then by `name|type`.
|
|
81
86
|
*
|
|
82
87
|
* Teardown must remove exactly what registration added — no more, no less.
|
|
83
|
-
*
|
|
84
|
-
* pattern
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* inputs (authority changed, ownership changed, a record row vanished) makes the
|
|
88
|
-
* reconstruction tear down another owner's handlers or miss its own. Recording
|
|
89
|
-
* the actual registrations, with their origin, removes that class of bug.
|
|
88
|
+
* Every key therefore retains all SmartDNS registration handles for the RRset;
|
|
89
|
+
* the coarse `unregisterHandler(pattern, types)` API is never used. Recording
|
|
90
|
+
* the actual registrations, with their origin, prevents deletion or authority
|
|
91
|
+
* reconciliation from removing another owner's identical handler.
|
|
90
92
|
*/
|
|
91
|
-
private runtimeRegistrations = new Map<string, Map<string,
|
|
93
|
+
private runtimeRegistrations = new Map<string, Map<string, IDnsRuntimeRegistrationState>>();
|
|
92
94
|
|
|
93
95
|
/** Serializes managed-mail reconciliation with destructive domain/provider deletion. */
|
|
94
96
|
private managedMailDnsMutationChain: Promise<void> = Promise.resolve();
|
|
95
97
|
private acceptsManagedMailDnsMutations = true;
|
|
96
98
|
|
|
97
|
-
/** Supplies the
|
|
99
|
+
/** Supplies the delegation-verified authority set. */
|
|
98
100
|
private authorityZonesResolver?: () => string[];
|
|
99
101
|
|
|
100
102
|
constructor(private options: IDcRouterOptions) {}
|
|
@@ -122,31 +124,60 @@ export class DnsManager {
|
|
|
122
124
|
name: string,
|
|
123
125
|
type: TDnsRecordType,
|
|
124
126
|
origin: TDnsRuntimeRegistrationOrigin,
|
|
127
|
+
registration: plugins.smartdns.dnsServerMod.IDnsHandlerRegistration,
|
|
125
128
|
): void {
|
|
126
129
|
let owned = this.runtimeRegistrations.get(domainId);
|
|
127
130
|
if (!owned) {
|
|
128
|
-
owned = new Map<string,
|
|
131
|
+
owned = new Map<string, IDnsRuntimeRegistrationState>();
|
|
129
132
|
this.runtimeRegistrations.set(domainId, owned);
|
|
130
133
|
}
|
|
131
|
-
|
|
134
|
+
const key = this.rrsetKey(name, type);
|
|
135
|
+
const existing = owned.get(key);
|
|
136
|
+
if (existing) {
|
|
137
|
+
if (existing.origin !== origin) {
|
|
138
|
+
registration.unregister();
|
|
139
|
+
throw new Error(
|
|
140
|
+
`DnsManager: cannot mix ${existing.origin} and ${origin} runtime registrations for ${name} ${type}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
existing.registrations.push(registration);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
owned.set(key, {
|
|
147
|
+
name: name.toLowerCase(),
|
|
148
|
+
type,
|
|
149
|
+
origin,
|
|
150
|
+
registrations: [registration],
|
|
151
|
+
});
|
|
132
152
|
}
|
|
133
153
|
|
|
134
|
-
private
|
|
154
|
+
private unregisterRuntimeRegistration(
|
|
135
155
|
domainId: string,
|
|
136
156
|
name: string,
|
|
137
157
|
type: TDnsRecordType,
|
|
138
|
-
):
|
|
158
|
+
): number {
|
|
139
159
|
const owned = this.runtimeRegistrations.get(domainId);
|
|
140
|
-
if (!owned) return;
|
|
160
|
+
if (!owned) return 0;
|
|
161
|
+
const registration = owned.get(this.rrsetKey(name, type));
|
|
162
|
+
if (!registration) return 0;
|
|
141
163
|
owned.delete(this.rrsetKey(name, type));
|
|
142
164
|
if (owned.size === 0) {
|
|
143
165
|
this.runtimeRegistrations.delete(domainId);
|
|
144
166
|
}
|
|
167
|
+
let removed = 0;
|
|
168
|
+
for (const handlerRegistration of registration.registrations) {
|
|
169
|
+
if (handlerRegistration.unregister()) removed++;
|
|
170
|
+
}
|
|
171
|
+
return removed;
|
|
145
172
|
}
|
|
146
173
|
|
|
147
174
|
/** Registrations currently owned by a domain. Exposed for teardown assertions. */
|
|
148
175
|
public listRuntimeRegistrations(domainId: string): IDnsRuntimeRegistration[] {
|
|
149
|
-
return [...(this.runtimeRegistrations.get(domainId)?.values() || [])]
|
|
176
|
+
return [...(this.runtimeRegistrations.get(domainId)?.values() || [])].map((registration) => ({
|
|
177
|
+
name: registration.name,
|
|
178
|
+
type: registration.type,
|
|
179
|
+
origin: registration.origin,
|
|
180
|
+
}));
|
|
150
181
|
}
|
|
151
182
|
|
|
152
183
|
/**
|
|
@@ -163,19 +194,22 @@ export class DnsManager {
|
|
|
163
194
|
private tearDownDomainRuntimeRegistrations(domainId: string): void {
|
|
164
195
|
const owned = this.runtimeRegistrations.get(domainId);
|
|
165
196
|
this.runtimeRegistrations.delete(domainId);
|
|
166
|
-
if (!owned
|
|
197
|
+
if (!owned) {
|
|
167
198
|
return;
|
|
168
199
|
}
|
|
169
200
|
const generatedZones: string[] = [];
|
|
201
|
+
let removedHandlers = 0;
|
|
170
202
|
for (const registration of owned.values()) {
|
|
171
|
-
|
|
203
|
+
for (const handlerRegistration of registration.registrations) {
|
|
204
|
+
if (handlerRegistration.unregister()) removedHandlers++;
|
|
205
|
+
}
|
|
172
206
|
if (registration.origin === 'generated-default') {
|
|
173
207
|
generatedZones.push(`${registration.name} ${registration.type}`);
|
|
174
208
|
}
|
|
175
209
|
}
|
|
176
210
|
logger.log(
|
|
177
211
|
'info',
|
|
178
|
-
`DnsManager: unregistered ${
|
|
212
|
+
`DnsManager: unregistered ${removedHandlers} runtime DNS handler(s) across ${owned.size} key(s) for domain ${domainId}`
|
|
179
213
|
+ (generatedZones.length ? `, including generated default(s) ${generatedZones.join(', ')}` : ''),
|
|
180
214
|
{ zone: 'dns', domainId },
|
|
181
215
|
);
|
|
@@ -194,8 +228,7 @@ export class DnsManager {
|
|
|
194
228
|
this.acceptsManagedMailDnsMutations = false;
|
|
195
229
|
await this.managedMailDnsMutationChain;
|
|
196
230
|
this.providerClients.clear();
|
|
197
|
-
this.
|
|
198
|
-
this.dnsServer = undefined;
|
|
231
|
+
this.detachDnsServer();
|
|
199
232
|
}
|
|
200
233
|
|
|
201
234
|
/**
|
|
@@ -204,8 +237,26 @@ export class DnsManager {
|
|
|
204
237
|
* dcrouter-hosted domains loaded from the DB are registered with the server.
|
|
205
238
|
*/
|
|
206
239
|
public async attachDnsServer(dnsServer: plugins.smartdns.dnsServerMod.DnsServer): Promise<void> {
|
|
240
|
+
if (this.dnsServer === dnsServer) return;
|
|
241
|
+
this.detachDnsServer();
|
|
207
242
|
this.dnsServer = dnsServer;
|
|
208
|
-
|
|
243
|
+
try {
|
|
244
|
+
await this.applyDcrouterDomainsToDnsServer();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
this.detachDnsServer(dnsServer);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Remove manager-owned handlers from the current server before replacement or shutdown. */
|
|
252
|
+
public detachDnsServer(expectedServerArg?: plugins.smartdns.dnsServerMod.DnsServer): boolean {
|
|
253
|
+
if (expectedServerArg && this.dnsServer !== expectedServerArg) return false;
|
|
254
|
+
for (const domainId of [...this.runtimeRegistrations.keys()]) {
|
|
255
|
+
this.tearDownDomainRuntimeRegistrations(domainId);
|
|
256
|
+
}
|
|
257
|
+
this.runtimeRegistrations.clear();
|
|
258
|
+
this.dnsServer = undefined;
|
|
259
|
+
return true;
|
|
209
260
|
}
|
|
210
261
|
|
|
211
262
|
// ==========================================================================
|
|
@@ -254,10 +305,9 @@ export class DnsManager {
|
|
|
254
305
|
* bootstrap `dnsScopes` zones, which could neither appear for a zone verified
|
|
255
306
|
* after startup nor disappear for one whose authority was revoked.
|
|
256
307
|
*
|
|
257
|
-
* Ownership is verified first.
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* publicly claim authority over somebody else's domain.
|
|
308
|
+
* Ownership is verified first. SmartDNS suppresses default-authority handlers
|
|
309
|
+
* outside the live authority set, and a zone only enters that set after its
|
|
310
|
+
* delegation is proven, so registration and authority use the same evidence.
|
|
261
311
|
*/
|
|
262
312
|
private async registerAuthoritativeZoneDefaults(
|
|
263
313
|
domain: DomainDoc,
|
|
@@ -288,7 +338,7 @@ export class DnsManager {
|
|
|
288
338
|
logger.log(
|
|
289
339
|
'error',
|
|
290
340
|
`DnsManager: refusing to serve authoritative apex NS records for ${zoneName} — ${ownership.detail}. `
|
|
291
|
-
+ '
|
|
341
|
+
+ 'SmartDNS suppresses authoritative handlers outside the verified authority set, so this zone stays unserved (REFUSED) until ownership is verified.',
|
|
292
342
|
{ zone: 'dns', domainId: domain.id, ownershipReason: ownership.reason },
|
|
293
343
|
);
|
|
294
344
|
return;
|
|
@@ -302,7 +352,7 @@ export class DnsManager {
|
|
|
302
352
|
return;
|
|
303
353
|
}
|
|
304
354
|
for (const nsDomain of nsDomains) {
|
|
305
|
-
this.dnsServer.registerHandler(zoneName, ['NS'], (question) => {
|
|
355
|
+
const registration = this.dnsServer.registerHandler(zoneName, ['NS'], (question) => {
|
|
306
356
|
if (question.name.toLowerCase() === zoneName && question.type.toUpperCase() === 'NS') {
|
|
307
357
|
return {
|
|
308
358
|
name: question.name,
|
|
@@ -313,9 +363,15 @@ export class DnsManager {
|
|
|
313
363
|
};
|
|
314
364
|
}
|
|
315
365
|
return null;
|
|
316
|
-
});
|
|
366
|
+
}, { owner: `dcrouter:dns-manager:${domain.id}` });
|
|
367
|
+
this.trackRuntimeRegistration(
|
|
368
|
+
domain.id,
|
|
369
|
+
zoneName,
|
|
370
|
+
'NS',
|
|
371
|
+
'generated-default',
|
|
372
|
+
registration,
|
|
373
|
+
);
|
|
317
374
|
}
|
|
318
|
-
this.trackRuntimeRegistration(domain.id, zoneName, 'NS', 'generated-default');
|
|
319
375
|
logger.log(
|
|
320
376
|
'info',
|
|
321
377
|
`DnsManager: serving default apex NS records for authoritative zone ${zoneName} `
|
|
@@ -389,8 +445,7 @@ export class DnsManager {
|
|
|
389
445
|
|
|
390
446
|
if (!ownership.verified && hasGenerated) {
|
|
391
447
|
// Authority lost: stop answering for it immediately.
|
|
392
|
-
this.
|
|
393
|
-
this.dnsServer.unregisterHandler(zoneName, ['NS']);
|
|
448
|
+
this.unregisterRuntimeRegistration(domain.id, zoneName, 'NS');
|
|
394
449
|
unregistered.push(zoneName);
|
|
395
450
|
continue;
|
|
396
451
|
}
|
|
@@ -480,7 +535,7 @@ export class DnsManager {
|
|
|
480
535
|
*/
|
|
481
536
|
private registerRecordWithDnsServer(rec: DnsRecordDoc): void {
|
|
482
537
|
if (!this.dnsServer) return;
|
|
483
|
-
this.dnsServer.registerHandler(rec.name, [rec.type], (question) => {
|
|
538
|
+
const registration = this.dnsServer.registerHandler(rec.name, [rec.type], (question) => {
|
|
484
539
|
if (question.name.toLowerCase() === rec.name.toLowerCase() && question.type.toUpperCase() === rec.type) {
|
|
485
540
|
return {
|
|
486
541
|
name: question.name,
|
|
@@ -491,10 +546,9 @@ export class DnsManager {
|
|
|
491
546
|
};
|
|
492
547
|
}
|
|
493
548
|
return null;
|
|
494
|
-
});
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
this.trackRuntimeRegistration(rec.domainId, rec.name, rec.type, 'persisted');
|
|
549
|
+
}, { owner: `dcrouter:dns-manager:${rec.domainId}` });
|
|
550
|
+
// Track beside registration so every call site retains the exact handle.
|
|
551
|
+
this.trackRuntimeRegistration(rec.domainId, rec.name, rec.type, 'persisted', registration);
|
|
498
552
|
}
|
|
499
553
|
|
|
500
554
|
private rrsetKey(name: string, type: TDnsRecordType): string {
|
|
@@ -514,8 +568,7 @@ export class DnsManager {
|
|
|
514
568
|
type: TDnsRecordType,
|
|
515
569
|
): Promise<void> {
|
|
516
570
|
if (!this.dnsServer) return;
|
|
517
|
-
this.
|
|
518
|
-
this.untrackRuntimeRegistration(domainId, name, type);
|
|
571
|
+
this.unregisterRuntimeRegistration(domainId, name, type);
|
|
519
572
|
const records = await DnsRecordDoc.findByDomainId(domainId);
|
|
520
573
|
for (const record of records) {
|
|
521
574
|
if (record.name.toLowerCase() === name.toLowerCase() && record.type === type) {
|
|
@@ -1199,8 +1252,8 @@ export class DnsManager {
|
|
|
1199
1252
|
return { success: false, message: 'Domain is already in the target configuration' };
|
|
1200
1253
|
}
|
|
1201
1254
|
|
|
1202
|
-
const records = await DnsRecordDoc.findByDomainId(domain.id);
|
|
1203
1255
|
if (args.targetSource === 'provider') {
|
|
1256
|
+
const records = await DnsRecordDoc.findByDomainId(domain.id);
|
|
1204
1257
|
return await this.migrateToDnsProvider(
|
|
1205
1258
|
domain,
|
|
1206
1259
|
records,
|
|
@@ -1208,7 +1261,7 @@ export class DnsManager {
|
|
|
1208
1261
|
args.deleteExistingProviderRecords ?? false,
|
|
1209
1262
|
);
|
|
1210
1263
|
}
|
|
1211
|
-
return await this.migrateToDcrouter(domain
|
|
1264
|
+
return await this.migrateToDcrouter(domain.id);
|
|
1212
1265
|
});
|
|
1213
1266
|
}
|
|
1214
1267
|
|
|
@@ -1298,36 +1351,76 @@ export class DnsManager {
|
|
|
1298
1351
|
* Migrate domain from provider-managed to dcrouter-hosted (authoritative).
|
|
1299
1352
|
*/
|
|
1300
1353
|
private async migrateToDcrouter(
|
|
1301
|
-
|
|
1302
|
-
records: DnsRecordDoc[],
|
|
1354
|
+
domainId: string,
|
|
1303
1355
|
): Promise<{ success: boolean; recordsMigrated?: number; message?: string }> {
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1356
|
+
const db = DcRouterDb.getInstance().getDb();
|
|
1357
|
+
const session = db.startSession();
|
|
1358
|
+
let migration: {
|
|
1359
|
+
domain: DomainDoc;
|
|
1360
|
+
records: DnsRecordDoc[];
|
|
1361
|
+
ownership: TDomainOwnership;
|
|
1362
|
+
} | undefined;
|
|
1363
|
+
try {
|
|
1364
|
+
migration = await session.withTransaction(async () => {
|
|
1365
|
+
const domain = await DomainDoc.getInstance({ id: domainId }, { session });
|
|
1366
|
+
if (!domain) {
|
|
1367
|
+
throw new Error(`Domain ${domainId} disappeared during provider-to-dcrouter migration`);
|
|
1368
|
+
}
|
|
1369
|
+
const records = await DnsRecordDoc.getInstances({ domainId }, { session });
|
|
1370
|
+
const ownership = await this.resolveOwnershipForPendingDcrouterZone(domain.name);
|
|
1371
|
+
const now = Date.now();
|
|
1372
|
+
|
|
1373
|
+
for (const rec of records) {
|
|
1374
|
+
rec.source = 'local' as TDnsRecordSource;
|
|
1375
|
+
rec.providerRecordId = undefined;
|
|
1376
|
+
rec.updatedAt = now;
|
|
1377
|
+
await rec.save({ session });
|
|
1378
|
+
await db.mongoDb.collection('DnsRecordDoc').updateOne(
|
|
1379
|
+
{ id: rec.id },
|
|
1380
|
+
{ $unset: { providerRecordId: '' } },
|
|
1381
|
+
{ session },
|
|
1382
|
+
);
|
|
1383
|
+
delete (rec as any).providerRecordId;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Losing the provider link means the credentialed zone listing no
|
|
1387
|
+
// longer proves ownership, so authority is re-derived rather than
|
|
1388
|
+
// assumed.
|
|
1389
|
+
domain.source = 'dcrouter';
|
|
1390
|
+
domain.providerId = undefined;
|
|
1391
|
+
domain.externalZoneId = undefined;
|
|
1392
|
+
domain.nameservers = undefined;
|
|
1393
|
+
domain.lastSyncedAt = undefined;
|
|
1394
|
+
domain.updatedAt = now;
|
|
1395
|
+
domain.authoritative = ownership.verified;
|
|
1396
|
+
await domain.save({ session });
|
|
1397
|
+
await db.mongoDb.collection('DomainDoc').updateOne(
|
|
1398
|
+
{ id: domain.id },
|
|
1399
|
+
{
|
|
1400
|
+
$unset: {
|
|
1401
|
+
providerId: '',
|
|
1402
|
+
externalZoneId: '',
|
|
1403
|
+
nameservers: '',
|
|
1404
|
+
lastSyncedAt: '',
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
{ session },
|
|
1408
|
+
);
|
|
1409
|
+
delete (domain as any).providerId;
|
|
1410
|
+
delete (domain as any).externalZoneId;
|
|
1411
|
+
delete (domain as any).nameservers;
|
|
1412
|
+
delete (domain as any).lastSyncedAt;
|
|
1413
|
+
|
|
1414
|
+
return { domain, records, ownership };
|
|
1415
|
+
});
|
|
1416
|
+
} finally {
|
|
1417
|
+
await session.endSession();
|
|
1317
1418
|
}
|
|
1318
1419
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
domain
|
|
1323
|
-
domain.providerId = undefined;
|
|
1324
|
-
domain.externalZoneId = undefined;
|
|
1325
|
-
domain.nameservers = undefined;
|
|
1326
|
-
domain.lastSyncedAt = undefined;
|
|
1327
|
-
domain.updatedAt = Date.now();
|
|
1328
|
-
const ownership = await this.resolveOwnershipForPendingDcrouterZone(domain.name);
|
|
1329
|
-
domain.authoritative = ownership.verified;
|
|
1330
|
-
await domain.save();
|
|
1420
|
+
if (!migration) {
|
|
1421
|
+
throw new Error(`Provider-to-dcrouter migration for ${domainId} committed without a result`);
|
|
1422
|
+
}
|
|
1423
|
+
const { domain, records, ownership } = migration;
|
|
1331
1424
|
if (!ownership.verified) {
|
|
1332
1425
|
logger.log(
|
|
1333
1426
|
'error',
|
|
@@ -1335,10 +1428,26 @@ export class DnsManager {
|
|
|
1335
1428
|
{ zone: 'dns', domainId: domain.id, ownershipReason: ownership.reason },
|
|
1336
1429
|
);
|
|
1337
1430
|
}
|
|
1338
|
-
await this.registerAuthoritativeZoneDefaults(domain);
|
|
1339
1431
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1432
|
+
// Runtime state is rebuilt only after every durable row has committed. A
|
|
1433
|
+
// failed transaction therefore leaves the provider-backed runtime untouched.
|
|
1434
|
+
this.tearDownDomainRuntimeRegistrations(domain.id);
|
|
1435
|
+
try {
|
|
1436
|
+
const rrsets = new Set(records.map((recordArg) => (
|
|
1437
|
+
this.rrsetKey(recordArg.name, recordArg.type)
|
|
1438
|
+
)));
|
|
1439
|
+
for (const rrset of rrsets) {
|
|
1440
|
+
const [name, type] = rrset.split('|') as [string, TDnsRecordType];
|
|
1441
|
+
await this.refreshLocalRrset(domain.id, name, type);
|
|
1442
|
+
}
|
|
1443
|
+
await this.registerAuthoritativeZoneDefaults(domain);
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
this.tearDownDomainRuntimeRegistrations(domain.id);
|
|
1446
|
+
throw error;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
logger.log('info', `Domain ${domain.name} migrated to dcrouter (${records.length} records)`);
|
|
1450
|
+
return { success: true, recordsMigrated: records.length };
|
|
1342
1451
|
}
|
|
1343
1452
|
|
|
1344
1453
|
// ==========================================================================
|
|
@@ -1622,9 +1731,7 @@ export class DnsManager {
|
|
|
1622
1731
|
* Unregister a record's handler from the embedded DnsServer.
|
|
1623
1732
|
*/
|
|
1624
1733
|
public unregisterRecordFromDnsServer(rec: DnsRecordDoc): void {
|
|
1625
|
-
this.
|
|
1626
|
-
if (!this.dnsServer) return;
|
|
1627
|
-
this.dnsServer.unregisterHandler(rec.name, [rec.type]);
|
|
1734
|
+
this.unregisterRuntimeRegistration(rec.domainId, rec.name, rec.type);
|
|
1628
1735
|
}
|
|
1629
1736
|
|
|
1630
1737
|
// ==========================================================================
|
|
@@ -144,40 +144,6 @@ export function evaluateMailMxValues(
|
|
|
144
144
|
return { valid: true, outcome: 'valid' };
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
function expandIpv6(address: string): string[] | undefined {
|
|
148
|
-
let normalized = address.toLowerCase();
|
|
149
|
-
if (normalized.includes('.')) {
|
|
150
|
-
const lastColon = normalized.lastIndexOf(':');
|
|
151
|
-
const ipv4 = normalized.slice(lastColon + 1).split('.').map(Number);
|
|
152
|
-
if (ipv4.length !== 4 || ipv4.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
normalized = `${normalized.slice(0, lastColon)}:${((ipv4[0] << 8) | ipv4[1]).toString(16)}:${((ipv4[2] << 8) | ipv4[3]).toString(16)}`;
|
|
156
|
-
}
|
|
157
|
-
const halves = normalized.split('::');
|
|
158
|
-
if (halves.length > 2) return undefined;
|
|
159
|
-
const left = halves[0] ? halves[0].split(':') : [];
|
|
160
|
-
const right = halves[1] ? halves[1].split(':') : [];
|
|
161
|
-
const missing = 8 - left.length - right.length;
|
|
162
|
-
if ((halves.length === 1 && missing !== 0) || missing < 0) return undefined;
|
|
163
|
-
const groups = [...left, ...Array.from({ length: missing }, () => '0'), ...right];
|
|
164
|
-
if (groups.length !== 8 || groups.some((group) => !/^[0-9a-f]{1,4}$/.test(group))) return undefined;
|
|
165
|
-
return groups.map((group) => group.padStart(4, '0'));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function toReverseDnsName(address: string): string {
|
|
169
|
-
const family = plugins.smartnetwork.getIpVersion(address);
|
|
170
|
-
if (family === 4) {
|
|
171
|
-
return `${address.split('.').reverse().join('.')}.in-addr.arpa`;
|
|
172
|
-
}
|
|
173
|
-
if (family === 6) {
|
|
174
|
-
const groups = expandIpv6(address);
|
|
175
|
-
if (!groups) throw new Error(`Invalid IPv6 address: ${address}`);
|
|
176
|
-
return `${groups.join('').split('').reverse().join('.')}.ip6.arpa`;
|
|
177
|
-
}
|
|
178
|
-
throw new Error(`Invalid IP address: ${address}`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
147
|
export class SmartDnsMailVerifier implements IMailDnsVerifier {
|
|
182
148
|
private client?: plugins.smartdns.dnsClientMod.Smartdns;
|
|
183
149
|
|
|
@@ -316,7 +282,15 @@ export class SmartDnsMailVerifier implements IMailDnsVerifier {
|
|
|
316
282
|
if (identity.ipv6) checks.push({ family: 'AAAA', address: identity.ipv6 });
|
|
317
283
|
try {
|
|
318
284
|
for (const check of checks) {
|
|
319
|
-
const
|
|
285
|
+
const reverseDnsName = plugins.smartnetwork.toReverseDnsName(check.address);
|
|
286
|
+
if (!reverseDnsName) {
|
|
287
|
+
return {
|
|
288
|
+
valid: false,
|
|
289
|
+
outcome: 'invalid',
|
|
290
|
+
reason: `Invalid source-bound IP address: ${check.address}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const ptrQuery = await this.queryValues(reverseDnsName, 'PTR');
|
|
320
294
|
if (ptrQuery.status === 'error') {
|
|
321
295
|
return { valid: false, outcome: 'lookup-error', reason: `PTR lookup failed: ${ptrQuery.reason}` };
|
|
322
296
|
}
|