@push.rocks/smartdns 7.12.0 → 8.0.0
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/dist_rust/rustdns-client_linux_amd64 +0 -0
- package/dist_rust/rustdns-client_linux_arm64 +0 -0
- package/dist_rust/rustdns_linux_amd64 +0 -0
- package/dist_rust/rustdns_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts_client/classes.dnsclient.d.ts +8 -1
- package/dist_ts_client/classes.dnsclient.js +10 -3
- package/dist_ts_server/classes.dnsserver.d.ts +140 -4
- package/dist_ts_server/classes.dnsserver.js +302 -46
- package/dist_ts_server/classes.rustdnsbridge.d.ts +19 -4
- package/dist_ts_server/classes.rustdnsbridge.js +9 -5
- package/package.json +1 -1
- package/readme.hints.md +120 -0
- package/readme.md +25 -7
- package/ts/00_commitinfo_data.ts +1 -1
|
@@ -67,11 +67,12 @@ export class RustDnsBridge extends plugins.events.EventEmitter {
|
|
|
67
67
|
/**
|
|
68
68
|
* Send a DNS query result back to Rust.
|
|
69
69
|
*/
|
|
70
|
-
async sendQueryResult(correlationId, answers,
|
|
70
|
+
async sendQueryResult(correlationId, answers, responseKind, authorityZone) {
|
|
71
71
|
await this.bridge.sendCommand('dnsQueryResult', {
|
|
72
72
|
correlationId,
|
|
73
73
|
answers,
|
|
74
|
-
|
|
74
|
+
responseKind,
|
|
75
|
+
authorityZone,
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -82,11 +83,14 @@ export class RustDnsBridge extends plugins.events.EventEmitter {
|
|
|
82
83
|
}
|
|
83
84
|
/**
|
|
84
85
|
* Process a raw DNS packet via IPC (for manual/passthrough mode).
|
|
85
|
-
* Returns the DNS response as a Buffer.
|
|
86
|
+
* Returns the DNS response as a Buffer. Pass `transport: 'udp'` when the
|
|
87
|
+
* response will be sent as a UDP datagram, so it is truncated (TC bit) to
|
|
88
|
+
* the client's advertised payload size instead of exceeding it.
|
|
86
89
|
*/
|
|
87
|
-
async processPacket(packet) {
|
|
90
|
+
async processPacket(packet, transport) {
|
|
88
91
|
const result = await this.bridge.sendCommand('processPacket', {
|
|
89
92
|
packet: packet.toString('base64'),
|
|
93
|
+
transport,
|
|
90
94
|
});
|
|
91
95
|
return Buffer.from(result.packet, 'base64');
|
|
92
96
|
}
|
|
@@ -133,4 +137,4 @@ function getPlatformSuffix() {
|
|
|
133
137
|
}
|
|
134
138
|
return null;
|
|
135
139
|
}
|
|
136
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
140
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0ZG5zYnJpZGdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfc2VydmVyL2NsYXNzZXMucnVzdGRuc2JyaWRnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQTBGeEM7O0dBRUc7QUFDSCxNQUFNLE9BQU8sYUFBYyxTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQUc1RDtRQUNFLEtBQUssRUFBRSxDQUFDO1FBRVIsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQ3JDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQ3ZELElBQUksQ0FDTCxDQUFDO1FBRUYsMkVBQTJFO1FBQzNFLE1BQU0sY0FBYyxHQUFHLGlCQUFpQixFQUFFLENBQUM7UUFDM0MsTUFBTSxVQUFVLEdBQWEsRUFBRSxDQUFDO1FBRWhDLDBFQUEwRTtRQUMxRSxJQUFJLGNBQWMsRUFBRSxDQUFDO1lBQ25CLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFdBQVcsRUFBRSxXQUFXLGNBQWMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUMzRixDQUFDO1FBQ0QsMkNBQTJDO1FBQzNDLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLFdBQVcsRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBQ3ZFLHdCQUF3QjtRQUN4QixVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBQ3ZGLFVBQVUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFFckYsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFlO1lBQzNELFVBQVUsRUFBRSxTQUFTO1lBQ3JCLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQztZQUN6QixnQkFBZ0IsRUFBRSxNQUFNO1lBQ3hCLGNBQWMsRUFBRSxNQUFNO1lBQ3RCLFVBQVU7WUFDVixnQkFBZ0IsRUFBRSxLQUFLO1NBQ3hCLENBQUMsQ0FBQztRQUVILG1DQUFtQztRQUNuQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLElBQW9CLEVBQUUsRUFBRTtZQUM3RCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUM5QixDQUFDLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLG9CQUFvQixFQUFFLEdBQUcsRUFBRTtZQUN4QyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3ZCLENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsb0JBQW9CLEVBQUUsR0FBRyxFQUFFO1lBQ3hDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDdkIsQ0FBQyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLElBQXlCLEVBQUUsRUFBRTtZQUMvRCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztRQUM5QyxDQUFDLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLElBQVksRUFBRSxFQUFFO1lBQ3hDLDRDQUE0QztZQUM1QyxJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO2dCQUNoQixPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNuQyxDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsS0FBSztRQUNoQixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDN0IsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFdBQVcsQ0FBQyxNQUFzQjtRQUM3QyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDckQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGVBQWUsQ0FDMUIsYUFBcUIsRUFDckIsT0FBd0IsRUFDeEIsWUFBbUMsRUFDbkMsYUFBc0I7UUFFdEIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxnQkFBZ0IsRUFBRTtZQUM5QyxhQUFhO1lBQ2IsT0FBTztZQUNQLFlBQVk7WUFDWixhQUFhO1NBQ2QsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFdBQVcsQ0FBQyxRQUFnQixFQUFFLFNBQWlCO1FBQzFELE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsYUFBYSxFQUFFLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDeEUsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksS0FBSyxDQUFDLGFBQWEsQ0FBQyxNQUFjLEVBQUUsU0FBeUI7UUFDbEUsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxlQUFlLEVBQUU7WUFDNUQsTUFBTSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQ2pDLFNBQVM7U0FDVixDQUFDLENBQUM7UUFDSCxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQztJQUM5QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsSUFBSTtRQUNmLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3pELE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQztJQUNyQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxJQUFJO1FBQ1QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNyQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFXLE9BQU87UUFDaEIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUM3QixDQUFDO0NBQ0Y7QUFFRDs7O0dBR0c7QUFDSCxTQUFTLGlCQUFpQjtJQUN4QixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDO0lBQ2xDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUM7SUFFMUIsTUFBTSxXQUFXLEdBQTJCO1FBQzFDLE9BQU8sRUFBRSxPQUFPO1FBQ2hCLFFBQVEsRUFBRSxPQUFPO1FBQ2pCLE9BQU8sRUFBRSxTQUFTO0tBQ25CLENBQUM7SUFFRixNQUFNLE9BQU8sR0FBMkI7UUFDdEMsS0FBSyxFQUFFLE9BQU87UUFDZCxPQUFPLEVBQUUsT0FBTztLQUNqQixDQUFDO0lBRUYsTUFBTSxDQUFDLEdBQUcsV0FBVyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUV4QixJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUNYLE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVELE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQyJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartdns",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"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.",
|
|
6
6
|
"exports": {
|
package/readme.hints.md
CHANGED
|
@@ -143,9 +143,129 @@ The test suite demonstrates:
|
|
|
143
143
|
- Properly serializes both components for DNSSEC signature generation
|
|
144
144
|
- Enables mail exchange records to be authenticated with DNSSEC
|
|
145
145
|
|
|
146
|
+
## Authority Model (server)
|
|
147
|
+
|
|
148
|
+
Authority comes from **configuration**, never from the presence of a handler.
|
|
149
|
+
|
|
150
|
+
- `authoritativeZones` (falling back to `[dnssecZone]`) is the single source of
|
|
151
|
+
truth for the `aa` bit. An answer only carries `aa` when the queried name
|
|
152
|
+
falls inside one of those zones.
|
|
153
|
+
- `registerHandler(pattern, types, handler, options?)` takes
|
|
154
|
+
`options.authority`:
|
|
155
|
+
- `'authoritative'` (default) — the handler only contributes for names inside
|
|
156
|
+
a configured zone. A match outside every zone is **suppressed**, not
|
|
157
|
+
answered, and reported on the `refused` event. A registration that cannot
|
|
158
|
+
match any configured zone also logs a warning once, at registration time.
|
|
159
|
+
- `'non-authoritative'` — the handler may answer any matching name, but the
|
|
160
|
+
response never carries `aa` and is never DNSSEC-signed. This is the mode
|
|
161
|
+
for split-horizon / private-route overlays.
|
|
162
|
+
- `aa` is a per-message flag, so a single non-authoritative contribution
|
|
163
|
+
downgrades the whole response.
|
|
164
|
+
|
|
165
|
+
Why this matters: before v8, registering a handler *was* the public claim of
|
|
166
|
+
authority. A private-route overlay registered for a domain delegated elsewhere
|
|
167
|
+
made the public nameserver answer with `aa` and an RFC1918 address, while
|
|
168
|
+
refusing every other query type for the same name. Both halves came from the
|
|
169
|
+
same defect — two disagreeing sources of truth.
|
|
170
|
+
|
|
171
|
+
### Response outcomes
|
|
172
|
+
|
|
173
|
+
`TDnsQueryResponseKind` maps one-to-one onto the wire:
|
|
174
|
+
|
|
175
|
+
| kind | rcode | `aa` | authority section |
|
|
176
|
+
| --- | --- | --- | --- |
|
|
177
|
+
| `answer` | 0 | yes | — |
|
|
178
|
+
| `nonAuthoritativeAnswer` | 0 | no | — |
|
|
179
|
+
| `authoritativeNegative` (NODATA) | 0 | yes | zone SOA |
|
|
180
|
+
| `authoritativeNameError` (NXDOMAIN) | 3 | yes | zone SOA |
|
|
181
|
+
| `refused` | 5 | no | — |
|
|
182
|
+
| `servfail` | 2 | no | — |
|
|
183
|
+
|
|
184
|
+
NXDOMAIN is only emitted when the name provably does not exist: no handler
|
|
185
|
+
pattern matches it for any type, it is not the apex of a configured zone, no
|
|
186
|
+
configured zone sits below it, and no handler pattern is anchored strictly
|
|
187
|
+
below it (literal `.name` suffix, or a wildcard that matches a probe label —
|
|
188
|
+
mid-label wildcards such as `x.*.example.com` are not fully inverted by this
|
|
189
|
+
check and can still leave an ancestor at NXDOMAIN). The apex always exists
|
|
190
|
+
(it answers its own SOA), and a name with covered descendants is an empty
|
|
191
|
+
non-terminal — both get NODATA, because NXDOMAIN denies the entire subtree
|
|
192
|
+
(RFC 8020) and a wrong one lets aggressive negative caching darken the zone.
|
|
193
|
+
A catch-all pattern such as `'*'` therefore suppresses NXDOMAIN entirely —
|
|
194
|
+
that is deliberate.
|
|
195
|
+
|
|
196
|
+
The zone apex answers its own SOA even with no registered SOA handler; an
|
|
197
|
+
authoritative server that cannot answer SOA for its own zone breaks negative
|
|
198
|
+
caching and resolver heuristics.
|
|
199
|
+
|
|
200
|
+
One deliberate exception to the table: RFC 6761 localhost handling (when
|
|
201
|
+
`enableLocalhostHandling` is on) answers `localhost` A/AAAA with `aa` set even
|
|
202
|
+
though `localhost` is outside every configured zone — special-use names are
|
|
203
|
+
authoritative by definition, not by delegation.
|
|
204
|
+
|
|
205
|
+
## Handler Lifecycle
|
|
206
|
+
|
|
207
|
+
- `registerHandler` returns an `IDnsHandlerRegistration` with a unique `id` and
|
|
208
|
+
an `unregister()` that removes **only** that registration.
|
|
209
|
+
- `unregisterHandlerById(id)` and `unregisterHandlersByOwner(owner)` are the
|
|
210
|
+
precise teardown primitives; `listHandlers()` exposes the current set for
|
|
211
|
+
leak checks.
|
|
212
|
+
- `unregisterHandler(pattern, types)` is retained and unchanged: it still
|
|
213
|
+
removes *every* handler matching that pattern/type pair. It cannot distinguish
|
|
214
|
+
two consumers sharing a key — prefer the handle.
|
|
215
|
+
- ACME authorization follows the same rule: `filterAuthorizedDomains` /
|
|
216
|
+
`retrieveSslCertificate` admit exactly the domains inside a configured
|
|
217
|
+
authoritative zone. Handler registrations play no part — a DNS-01 challenge
|
|
218
|
+
for an out-of-zone domain could never validate, because its challenge TXT
|
|
219
|
+
handler would be suppressed.
|
|
220
|
+
|
|
221
|
+
## Transport And Protocol Notes
|
|
222
|
+
|
|
223
|
+
- **EDNS0**: a request carrying an OPT record always gets an OPT back
|
|
224
|
+
(RFC 6891 6.1.1), advertising `DNS_ADVERTISED_UDP_PAYLOAD` (1232). Requests
|
|
225
|
+
without OPT get no OPT. Client-advertised payload sizes are clamped up to 512
|
|
226
|
+
so a hostile OPT cannot force pathological truncation.
|
|
227
|
+
- **Truncation**: UDP responses are truncated to the client's advertised payload
|
|
228
|
+
size with the TC bit set, so resolvers retry over TCP. Without this, oversized
|
|
229
|
+
datagrams were emitted and silently dropped by real networks. Truncation is
|
|
230
|
+
size arithmetic, never a re-encode per dropped record — the encoder does not
|
|
231
|
+
compress names, so record sizes are additive (`DnsPacket::encoded_len`).
|
|
232
|
+
`processRawDnsPacketAsync(packet, 'udp')` declares the same UDP bound for the
|
|
233
|
+
manual path (`handleUdpMessage` passes it automatically); without the hint no
|
|
234
|
+
size bound is applied, which is correct for TCP and DoH framing. Note the
|
|
235
|
+
manual-path deadlock under Known Limitations before relying on this.
|
|
236
|
+
- **RA** is cleared on every path — this server never recurses.
|
|
237
|
+
- **RDATA validation** fails closed: a handler returning an unencodable value
|
|
238
|
+
(for example a non-IPv4 string for an A record) produces SERVFAIL and an error
|
|
239
|
+
log, rather than a malformed record on the wire.
|
|
240
|
+
- **SOA serial** is fixed for the lifetime of the server process and passed to
|
|
241
|
+
Rust via `soaSerial`, so apex answers and negative authorities agree.
|
|
242
|
+
|
|
243
|
+
## Observability
|
|
244
|
+
|
|
245
|
+
- `query` events carry `responseKind`, `authorityZone` and `suppressedHandlers`.
|
|
246
|
+
- `refused` events fire for every refusal and every suppressed handler match,
|
|
247
|
+
carrying the handler `id`, `domainPattern` and `owner`. Wire this up: the
|
|
248
|
+
original incident was invisible for weeks because nothing surfaced it.
|
|
249
|
+
|
|
146
250
|
## Known Limitations
|
|
147
251
|
|
|
148
252
|
1. **Handler Deduplication**: If the same handler is registered multiple times, it will contribute duplicate records (this may be desired behavior for some use cases)
|
|
253
|
+
2. **NXDOMAIN under catch-all patterns**: see the authority model above.
|
|
254
|
+
3. **SOA serial does not track zone changes** — it is a process-lifetime
|
|
255
|
+
constant, not a change counter. Secondaries driven by AXFR/IXFR are not
|
|
256
|
+
supported.
|
|
257
|
+
4. **Manual-path (`processPacket`) queries deadlock on handler resolution**
|
|
258
|
+
— pre-existing, not introduced by the v8 hardening. The IPC select loop in
|
|
259
|
+
`rust/crates/rustdns/src/management.rs` awaits `handle_request` inline, so
|
|
260
|
+
while `handle_process_packet` waits for the TypeScript `dnsQuery` callback
|
|
261
|
+
the loop can neither emit the query event nor read its result; every
|
|
262
|
+
handler-backed query through `processRawDnsPacketAsync` /
|
|
263
|
+
`handleUdpMessage` times out to SERVFAIL after 10 s. The built-in UDP, TCP
|
|
264
|
+
and DoH listeners are unaffected (they resolve outside the IPC request
|
|
265
|
+
loop). The `transport: 'udp'` truncation contract on this path is wired
|
|
266
|
+
and unit-tested but cannot be observed end-to-end until the loop spawns
|
|
267
|
+
`processPacket` handling instead of awaiting it inline. No test exercises
|
|
268
|
+
`processRawDnsPacketAsync`; fixing this needs its own release.
|
|
149
269
|
## Static Rust Binaries
|
|
150
270
|
|
|
151
271
|
- The Rust binaries in `dist_rust/` are statically linked (static-pie) via `"static": true` in the `@git.zone/tsrust` block of `.smartconfig.json` (tsrust >= 1.4.1). They run on both glibc (Debian/Ubuntu) and musl (Alpine) systems.
|
package/readme.md
CHANGED
|
@@ -263,6 +263,7 @@ interface IDnsServerOptions {
|
|
|
263
263
|
httpsKey: string; // PEM private key (path or content)
|
|
264
264
|
httpsCert: string; // PEM certificate (path or content)
|
|
265
265
|
dnssecZone: string; // Zone for DNSSEC signing
|
|
266
|
+
authoritativeZones?: string[]; // Zones this server is authoritative for (default: [dnssecZone])
|
|
266
267
|
primaryNameserver?: string; // SOA mname field (default: 'ns1.{dnssecZone}')
|
|
267
268
|
udpBindInterface?: string; // IP to bind UDP (default: '0.0.0.0')
|
|
268
269
|
tcpBindInterface?: string; // IP to bind TCP (defaults to udpBindInterface)
|
|
@@ -312,6 +313,8 @@ await server.start();
|
|
|
312
313
|
|
|
313
314
|
Handlers use **glob patterns** (via `minimatch`) to match incoming query names. Multiple handlers can contribute records to the same response.
|
|
314
315
|
|
|
316
|
+
Registering a handler does **not** make the server authoritative for the matched names. Authority comes solely from `authoritativeZones` (falling back to `dnssecZone`): list every zone you serve there, or queries for it are withheld. A default-authority handler whose match falls outside every configured zone is suppressed and reported on the `refused` event; register with `{ authority: 'non-authoritative' }` to deliberately serve out-of-zone names without the `aa` bit or DNSSEC signatures.
|
|
317
|
+
|
|
315
318
|
```typescript
|
|
316
319
|
// Exact domain
|
|
317
320
|
server.registerHandler('example.com', ['A'], handler);
|
|
@@ -331,14 +334,15 @@ server.registerHandler('db-*.internal.example.com', ['A'], (question) => {
|
|
|
331
334
|
};
|
|
332
335
|
});
|
|
333
336
|
|
|
334
|
-
// Catch-all
|
|
337
|
+
// Catch-all — '*' is anchored in no configured zone, so it must be
|
|
338
|
+
// registered non-authoritative or its out-of-zone answers are suppressed
|
|
335
339
|
server.registerHandler('*', ['A'], (question) => ({
|
|
336
340
|
name: question.name,
|
|
337
341
|
type: 'A',
|
|
338
342
|
class: 'IN',
|
|
339
343
|
ttl: 300,
|
|
340
344
|
data: '127.0.0.1',
|
|
341
|
-
}));
|
|
345
|
+
}), { authority: 'non-authoritative' });
|
|
342
346
|
|
|
343
347
|
// Multiple record types
|
|
344
348
|
server.registerHandler('example.com', ['MX'], (question) => ({
|
|
@@ -349,11 +353,21 @@ server.registerHandler('example.com', ['MX'], (question) => ({
|
|
|
349
353
|
data: { preference: 10, exchange: 'mail.example.com' },
|
|
350
354
|
}));
|
|
351
355
|
|
|
352
|
-
// Unregister a handler
|
|
356
|
+
// Unregister a handler: registerHandler returns a handle identifying
|
|
357
|
+
// exactly that registration
|
|
358
|
+
const registration = server.registerHandler('example.com', ['A'], handler);
|
|
359
|
+
registration.unregister();
|
|
360
|
+
|
|
361
|
+
// Precise teardown also works by id or owner label, and listHandlers()
|
|
362
|
+
// exposes the current set
|
|
363
|
+
server.registerHandler('example.com', ['TXT'], handler, { owner: 'acme-module' });
|
|
364
|
+
server.unregisterHandlersByOwner('acme-module');
|
|
365
|
+
|
|
366
|
+
// The coarse form removes EVERY handler on the pattern/type pair
|
|
353
367
|
server.unregisterHandler('example.com', ['A']);
|
|
354
368
|
```
|
|
355
369
|
|
|
356
|
-
|
|
370
|
+
Negative answers are authoritative only inside configured zones: a name inside an authoritative zone that provably does not exist yields **NXDOMAIN** with the zone SOA in the AUTHORITY section; a name that exists — a covered name queried for a type no handler serves, the zone apex, or an empty non-terminal with covered names beneath it — yields **NODATA** with the SOA in AUTHORITY; names outside every configured zone are answered **REFUSED**.
|
|
357
371
|
|
|
358
372
|
### DNSSEC ✅
|
|
359
373
|
|
|
@@ -389,7 +403,7 @@ Supported algorithms: **ECDSAP256SHA256** (13), **ED25519** (15), **RSASHA256**
|
|
|
389
403
|
|
|
390
404
|
### SOA Records
|
|
391
405
|
|
|
392
|
-
The server
|
|
406
|
+
The server carries the zone SOA in the AUTHORITY section of negative answers (NXDOMAIN and NODATA), and the zone apex answers its own SOA even without a registered SOA handler. Customize the primary nameserver:
|
|
393
407
|
|
|
394
408
|
```typescript
|
|
395
409
|
const server = new DnsServer({
|
|
@@ -401,13 +415,13 @@ const server = new DnsServer({
|
|
|
401
415
|
// Generated SOA includes:
|
|
402
416
|
// mname: ns1.example.com
|
|
403
417
|
// rname: hostmaster.example.com
|
|
404
|
-
// serial:
|
|
418
|
+
// serial: captured once at server construction, stable for the process lifetime
|
|
405
419
|
// refresh: 3600, retry: 600, expire: 604800, minimum: 86400
|
|
406
420
|
```
|
|
407
421
|
|
|
408
422
|
### Let's Encrypt Integration 🔒
|
|
409
423
|
|
|
410
|
-
Built-in ACME DNS-01 challenge support for automatic SSL certificates:
|
|
424
|
+
Built-in ACME DNS-01 challenge support for automatic SSL certificates. Challenge TXT handlers are registered at default authority, so DNS-01 validation only completes for domains inside `authoritativeZones` (or `dnssecZone`):
|
|
411
425
|
|
|
412
426
|
```typescript
|
|
413
427
|
const server = new DnsServer({
|
|
@@ -504,6 +518,10 @@ Process individual DNS packets directly:
|
|
|
504
518
|
```typescript
|
|
505
519
|
// Asynchronous (via Rust bridge — includes DNSSEC signing)
|
|
506
520
|
const response = await server.processRawDnsPacketAsync(packetBuffer);
|
|
521
|
+
|
|
522
|
+
// When the response goes out as a UDP datagram, pass 'udp' so it is
|
|
523
|
+
// truncated (TC bit) to the client's advertised EDNS0 payload size
|
|
524
|
+
const udpResponse = await server.processRawDnsPacketAsync(packetBuffer, 'udp');
|
|
507
525
|
```
|
|
508
526
|
|
|
509
527
|
Manual TCP callers must handle DNS-over-TCP's two-byte length prefix themselves and pass only the raw DNS packet payload to `processRawDnsPacketAsync()`.
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartdns',
|
|
6
|
-
version: '
|
|
6
|
+
version: '8.0.0',
|
|
7
7
|
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.'
|
|
8
8
|
}
|