@push.rocks/smartdns 7.0.2 → 7.4.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_server/classes.dnsserver.d.ts +45 -0
- package/dist_ts_server/classes.dnsserver.js +201 -41
- package/dist_ts_server/plugins.d.ts +2 -1
- package/dist_ts_server/plugins.js +3 -2
- package/package.json +2 -2
- package/readme.hints.md +97 -1
- package/readme.md +618 -197
- package/readme.plan.md +103 -0
- package/ts/00_commitinfo_data.ts +1 -1
package/readme.md
CHANGED
|
@@ -1,392 +1,813 @@
|
|
|
1
1
|
# @push.rocks/smartdns
|
|
2
2
|
|
|
3
|
-
A TypeScript library for
|
|
3
|
+
A robust TypeScript library providing advanced DNS management and resolution capabilities including support for DNSSEC, custom DNS servers, and integration with various DNS providers.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
To install `@push.rocks/smartdns`, use the following command with
|
|
7
|
+
To install `@push.rocks/smartdns`, use the following command with pnpm:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
pnpm install @push.rocks/smartdns --save
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Or with
|
|
13
|
+
Or with npm:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
npm install @push.rocks/smartdns --save
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Make sure you have a TypeScript environment set up to utilize the library effectively.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
|
-
`@push.rocks/smartdns` is a comprehensive library
|
|
23
|
+
`@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:
|
|
24
|
+
|
|
25
|
+
- **Client Module** (`@push.rocks/smartdns/client`): DNS resolution and record queries
|
|
26
|
+
- **Server Module** (`@push.rocks/smartdns/server`): Full DNS server implementation with DNSSEC
|
|
27
|
+
- **Main Module** (`@push.rocks/smartdns`): Convenience exports for both client and server
|
|
24
28
|
|
|
25
29
|
### Getting Started
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
You can import the modules based on your needs:
|
|
28
32
|
|
|
29
33
|
```typescript
|
|
30
|
-
|
|
34
|
+
// For DNS client operations
|
|
35
|
+
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
36
|
+
|
|
37
|
+
// For DNS server operations
|
|
38
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
39
|
+
|
|
40
|
+
// Or import from the main module (note the different syntax)
|
|
41
|
+
import { dnsClientMod, dnsServerMod } from '@push.rocks/smartdns';
|
|
42
|
+
const dnsClient = new dnsClientMod.Smartdns({});
|
|
43
|
+
const dnsServer = new dnsServerMod.DnsServer({ /* options */ });
|
|
31
44
|
```
|
|
32
45
|
|
|
33
|
-
###
|
|
46
|
+
### DNS Client Operations
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
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.
|
|
36
49
|
|
|
37
50
|
#### Fetching A Records
|
|
38
51
|
|
|
39
|
-
To fetch
|
|
52
|
+
To fetch "A" records (IPv4 addresses) for a domain:
|
|
40
53
|
|
|
41
54
|
```typescript
|
|
42
|
-
import { Smartdns } from '@push.rocks/smartdns';
|
|
55
|
+
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
43
56
|
|
|
44
|
-
const
|
|
45
|
-
const aRecords = await
|
|
57
|
+
const dnsClient = new Smartdns({});
|
|
58
|
+
const aRecords = await dnsClient.getRecordsA('example.com');
|
|
46
59
|
console.log(aRecords);
|
|
60
|
+
// Output: [{ name: 'example.com', type: 'A', dnsSecEnabled: false, value: '93.184.215.14' }]
|
|
47
61
|
```
|
|
48
62
|
|
|
49
|
-
This will return an array of records that include the IPv4 addresses mapped to the domain `example.com`.
|
|
50
|
-
|
|
51
63
|
#### Fetching AAAA Records
|
|
52
64
|
|
|
53
|
-
For resolving a domain to
|
|
65
|
+
For resolving a domain to IPv6 addresses:
|
|
54
66
|
|
|
55
67
|
```typescript
|
|
56
|
-
const aaaaRecords = await
|
|
68
|
+
const aaaaRecords = await dnsClient.getRecordsAAAA('example.com');
|
|
57
69
|
console.log(aaaaRecords);
|
|
70
|
+
// Output: [{ name: 'example.com', type: 'AAAA', dnsSecEnabled: false, value: '2606:2800:21f:cb07:6820:80da:af6b:8b2c' }]
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
These queries are quite common where IPv6 addresses have been prevalent due to the scarcity of IPv4 addresses.
|
|
61
|
-
|
|
62
73
|
#### Fetching TXT Records
|
|
63
74
|
|
|
64
|
-
TXT records store
|
|
75
|
+
TXT records store text data, commonly used for domain verification, SPF records, and other metadata:
|
|
65
76
|
|
|
66
77
|
```typescript
|
|
67
|
-
const txtRecords = await
|
|
78
|
+
const txtRecords = await dnsClient.getRecordsTxt('example.com');
|
|
68
79
|
console.log(txtRecords);
|
|
80
|
+
// Output: [{ name: 'example.com', type: 'TXT', dnsSecEnabled: false, value: 'v=spf1 -all' }]
|
|
69
81
|
```
|
|
70
82
|
|
|
71
|
-
|
|
83
|
+
#### Other Record Types
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
The client supports various other DNS record types:
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
```typescript
|
|
88
|
+
// MX records for mail servers
|
|
89
|
+
const mxRecords = await dnsClient.getRecords('example.com', 'MX');
|
|
90
|
+
|
|
91
|
+
// NS records for nameservers
|
|
92
|
+
const nsRecords = await dnsClient.getNameServers('example.com');
|
|
93
|
+
|
|
94
|
+
// Generic query method with retry support
|
|
95
|
+
const records = await dnsClient.getRecords('example.com', 'CNAME', { retryCount: 3 });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Advanced DNS Features
|
|
76
99
|
|
|
77
100
|
#### Checking DNS Propagation
|
|
78
101
|
|
|
79
|
-
|
|
102
|
+
The client provides a powerful method to verify DNS propagation globally, essential when making DNS changes:
|
|
80
103
|
|
|
81
104
|
```typescript
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
105
|
+
// Check if a specific DNS record has propagated
|
|
106
|
+
const recordType = 'TXT';
|
|
107
|
+
const expectedValue = 'verification=abc123';
|
|
108
|
+
|
|
109
|
+
const isAvailable = await dnsClient.checkUntilAvailable(
|
|
110
|
+
'example.com',
|
|
111
|
+
recordType,
|
|
112
|
+
expectedValue,
|
|
113
|
+
50, // Number of check cycles (default: 50)
|
|
114
|
+
500 // Interval between checks in ms (default: 500)
|
|
115
|
+
);
|
|
85
116
|
|
|
86
117
|
if (isAvailable) {
|
|
87
|
-
console.log('
|
|
118
|
+
console.log('DNS record has propagated successfully!');
|
|
88
119
|
} else {
|
|
89
|
-
console.log('
|
|
120
|
+
console.log('DNS propagation timeout - record not found.');
|
|
90
121
|
}
|
|
91
122
|
```
|
|
92
123
|
|
|
93
|
-
|
|
124
|
+
#### Configuring System DNS Provider
|
|
94
125
|
|
|
95
|
-
|
|
126
|
+
You can configure Node.js to use a specific DNS provider for all DNS queries:
|
|
96
127
|
|
|
97
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
// Import the standalone function
|
|
130
|
+
import { makeNodeProcessUseDnsProvider } from '@push.rocks/smartdns/client';
|
|
131
|
+
|
|
132
|
+
// Use Cloudflare DNS for all Node.js DNS operations
|
|
133
|
+
makeNodeProcessUseDnsProvider('cloudflare');
|
|
98
134
|
|
|
99
|
-
|
|
135
|
+
// Or use Google DNS
|
|
136
|
+
makeNodeProcessUseDnsProvider('google');
|
|
137
|
+
```
|
|
100
138
|
|
|
101
|
-
|
|
139
|
+
### Real-World Use Cases
|
|
140
|
+
|
|
141
|
+
#### DNS-Based Feature Flagging
|
|
142
|
+
|
|
143
|
+
Use TXT records for dynamic feature toggles without redeployment:
|
|
102
144
|
|
|
103
145
|
```typescript
|
|
104
|
-
const txtRecords = await
|
|
105
|
-
const featureFlags =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
const txtRecords = await dnsClient.getRecordsTxt('features.example.com');
|
|
147
|
+
const featureFlags = {};
|
|
148
|
+
|
|
149
|
+
txtRecords.forEach(record => {
|
|
150
|
+
// Parse TXT records like "feature-dark-mode=true"
|
|
151
|
+
const [feature, enabled] = record.value.split('=');
|
|
152
|
+
featureFlags[feature] = enabled === 'true';
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (featureFlags['feature-dark-mode']) {
|
|
156
|
+
console.log('Dark mode is enabled!');
|
|
114
157
|
}
|
|
115
158
|
```
|
|
116
159
|
|
|
117
|
-
|
|
160
|
+
#### Service Discovery
|
|
161
|
+
|
|
162
|
+
Use DNS for service endpoint discovery:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Discover API endpoints via TXT records
|
|
166
|
+
const serviceRecords = await dnsClient.getRecordsTxt('_services.example.com');
|
|
167
|
+
|
|
168
|
+
// Discover mail servers
|
|
169
|
+
const mxRecords = await dnsClient.getRecords('example.com', 'MX');
|
|
170
|
+
const primaryMailServer = mxRecords
|
|
171
|
+
.sort((a, b) => a.priority - b.priority)[0]?.exchange;
|
|
172
|
+
```
|
|
118
173
|
|
|
119
174
|
### DNS Server Implementation
|
|
120
175
|
|
|
121
|
-
|
|
176
|
+
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.
|
|
122
177
|
|
|
123
|
-
#### Basic DNS Server
|
|
178
|
+
#### Basic DNS Server Setup
|
|
124
179
|
|
|
125
|
-
|
|
180
|
+
Create a simple DNS server that responds to queries:
|
|
126
181
|
|
|
127
182
|
```typescript
|
|
128
|
-
import { DnsServer } from '@push.rocks/smartdns';
|
|
183
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
129
184
|
|
|
130
185
|
const dnsServer = new DnsServer({
|
|
186
|
+
udpPort: 5333, // UDP port for DNS queries
|
|
187
|
+
httpsPort: 8443, // HTTPS port for DNS-over-HTTPS
|
|
188
|
+
httpsKey: 'path/to/key.pem', // Required for HTTPS
|
|
189
|
+
httpsCert: 'path/to/cert.pem', // Required for HTTPS
|
|
190
|
+
dnssecZone: 'example.com' // Optional: enable DNSSEC for this zone
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// For enhanced security, bind to specific interfaces
|
|
194
|
+
const secureServer = new DnsServer({
|
|
195
|
+
udpPort: 53,
|
|
196
|
+
httpsPort: 443,
|
|
131
197
|
httpsKey: 'path/to/key.pem',
|
|
132
198
|
httpsCert: 'path/to/cert.pem',
|
|
133
|
-
httpsPort: 443,
|
|
134
|
-
udpPort: 53,
|
|
135
199
|
dnssecZone: 'example.com',
|
|
200
|
+
udpBindInterface: '127.0.0.1', // Bind UDP to localhost only
|
|
201
|
+
httpsBindInterface: '127.0.0.1' // Bind HTTPS to localhost only
|
|
136
202
|
});
|
|
137
203
|
|
|
204
|
+
// Register a handler for all subdomains of example.com
|
|
138
205
|
dnsServer.registerHandler('*.example.com', ['A'], (question) => ({
|
|
139
206
|
name: question.name,
|
|
140
207
|
type: 'A',
|
|
141
208
|
class: 'IN',
|
|
142
209
|
ttl: 300,
|
|
143
|
-
data: '
|
|
210
|
+
data: '192.168.1.100',
|
|
144
211
|
}));
|
|
145
212
|
|
|
146
|
-
|
|
147
|
-
|
|
213
|
+
// Register a handler for TXT records
|
|
214
|
+
dnsServer.registerHandler('example.com', ['TXT'], (question) => ({
|
|
215
|
+
name: question.name,
|
|
216
|
+
type: 'TXT',
|
|
217
|
+
class: 'IN',
|
|
218
|
+
ttl: 300,
|
|
219
|
+
data: 'v=spf1 include:_spf.example.com ~all',
|
|
220
|
+
}));
|
|
148
221
|
|
|
149
|
-
|
|
222
|
+
// Start the server
|
|
223
|
+
await dnsServer.start();
|
|
224
|
+
console.log('DNS Server started!');
|
|
225
|
+
```
|
|
150
226
|
|
|
151
227
|
### DNSSEC Support
|
|
152
228
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
#### DNSSEC Configuration
|
|
156
|
-
|
|
157
|
-
To configure DNSSEC for your DNS server, you’ll need to establish DNSSEC parameters including zone signatures and enabling key management. This setup ensures that DNS records are signed and can be verified for authenticity.
|
|
229
|
+
The DNS server includes comprehensive DNSSEC support with automatic key generation and record signing:
|
|
158
230
|
|
|
159
231
|
```typescript
|
|
160
|
-
import { DnsServer } from '@push.rocks/smartdns';
|
|
232
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
161
233
|
|
|
162
234
|
const dnsServer = new DnsServer({
|
|
163
|
-
httpsKey: 'path/to/key.pem',
|
|
164
|
-
httpsCert: 'path/to/cert.pem',
|
|
165
|
-
httpsPort: 443,
|
|
166
235
|
udpPort: 53,
|
|
167
|
-
|
|
236
|
+
httpsPort: 443,
|
|
237
|
+
dnssecZone: 'secure.example.com', // Enable DNSSEC for this zone
|
|
168
238
|
});
|
|
169
239
|
|
|
170
|
-
|
|
240
|
+
// The server automatically:
|
|
241
|
+
// 1. Generates DNSKEY records with ECDSA (algorithm 13)
|
|
242
|
+
// 2. Creates DS records for parent zone delegation
|
|
243
|
+
// 3. Signs all responses with RRSIG records
|
|
244
|
+
// 4. Provides NSEC records for authenticated denial of existence
|
|
245
|
+
|
|
246
|
+
// Register your handlers as normal - DNSSEC signing is automatic
|
|
247
|
+
dnsServer.registerHandler('secure.example.com', ['A'], (question) => ({
|
|
171
248
|
name: question.name,
|
|
172
249
|
type: 'A',
|
|
173
250
|
class: 'IN',
|
|
174
251
|
ttl: 300,
|
|
175
|
-
data: '
|
|
252
|
+
data: '192.168.1.1',
|
|
176
253
|
}));
|
|
177
254
|
|
|
178
|
-
dnsServer.start()
|
|
255
|
+
await dnsServer.start();
|
|
256
|
+
|
|
257
|
+
// Query for DNSSEC records
|
|
258
|
+
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
259
|
+
const client = new Smartdns({});
|
|
260
|
+
const dnskeyRecords = await client.getRecords('secure.example.com', 'DNSKEY');
|
|
261
|
+
const dsRecords = await client.getRecords('secure.example.com', 'DS');
|
|
179
262
|
```
|
|
180
263
|
|
|
181
|
-
|
|
264
|
+
#### Supported DNSSEC Algorithms
|
|
182
265
|
|
|
183
|
-
The
|
|
266
|
+
The server supports multiple DNSSEC algorithms:
|
|
267
|
+
- **ECDSAP256SHA256** (Algorithm 13) - Default, using P-256 curve
|
|
268
|
+
- **ED25519** (Algorithm 15) - Modern elliptic curve algorithm
|
|
269
|
+
- **RSASHA256** (Algorithm 8) - RSA-based signatures
|
|
184
270
|
|
|
185
|
-
|
|
271
|
+
### Let's Encrypt Integration
|
|
186
272
|
|
|
187
|
-
|
|
273
|
+
The DNS server includes built-in Let's Encrypt support for automatic SSL certificate management:
|
|
188
274
|
|
|
189
275
|
```typescript
|
|
190
|
-
import { DnsServer } from '@push.rocks/smartdns';
|
|
191
|
-
import dgram from 'dgram';
|
|
276
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
192
277
|
|
|
193
278
|
const dnsServer = new DnsServer({
|
|
194
279
|
udpPort: 53,
|
|
195
280
|
httpsPort: 443,
|
|
281
|
+
httpsKey: '/path/to/letsencrypt/key.pem', // Will be auto-generated
|
|
282
|
+
httpsCert: '/path/to/letsencrypt/cert.pem', // Will be auto-generated
|
|
196
283
|
});
|
|
197
284
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
285
|
+
// Retrieve Let's Encrypt certificate for your domain
|
|
286
|
+
const result = await dnsServer.retrieveSslCertificate(
|
|
287
|
+
['secure.example.com', 'www.secure.example.com'],
|
|
288
|
+
{
|
|
289
|
+
email: 'admin@example.com',
|
|
290
|
+
staging: false, // Use production Let's Encrypt
|
|
291
|
+
certDir: './certs'
|
|
292
|
+
}
|
|
293
|
+
);
|
|
205
294
|
|
|
206
|
-
|
|
207
|
-
console.log('
|
|
295
|
+
if (result.success) {
|
|
296
|
+
console.log('Certificate retrieved successfully!');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// The server automatically:
|
|
300
|
+
// 1. Handles ACME DNS-01 challenges
|
|
301
|
+
// 2. Creates temporary TXT records for domain validation
|
|
302
|
+
// 3. Retrieves and installs the certificate
|
|
303
|
+
// 4. Restarts the HTTPS server with the new certificate
|
|
304
|
+
|
|
305
|
+
await dnsServer.start();
|
|
306
|
+
console.log('DNS Server with Let\'s Encrypt SSL started!');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Manual Socket Handling
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
#### Configuration Options
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
export interface IDnsServerOptions {
|
|
317
|
+
// ... standard options ...
|
|
318
|
+
manualUdpMode?: boolean; // Handle UDP sockets manually
|
|
319
|
+
manualHttpsMode?: boolean; // Handle HTTPS sockets manually
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Basic Manual Socket Usage
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
327
|
+
import * as dgram from 'dgram';
|
|
328
|
+
import * as net from 'net';
|
|
329
|
+
|
|
330
|
+
// Create server with manual UDP mode
|
|
331
|
+
const dnsServer = new DnsServer({
|
|
332
|
+
httpsKey: '...',
|
|
333
|
+
httpsCert: '...',
|
|
334
|
+
httpsPort: 853,
|
|
335
|
+
udpPort: 53,
|
|
336
|
+
dnssecZone: 'example.com',
|
|
337
|
+
manualUdpMode: true // UDP manual, HTTPS automatic
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await dnsServer.start(); // HTTPS binds, UDP doesn't
|
|
341
|
+
|
|
342
|
+
// Create your own UDP socket
|
|
343
|
+
const udpSocket = dgram.createSocket('udp4');
|
|
344
|
+
|
|
345
|
+
// Handle incoming UDP messages
|
|
346
|
+
udpSocket.on('message', (msg, rinfo) => {
|
|
347
|
+
dnsServer.handleUdpMessage(msg, rinfo, (response, responseRinfo) => {
|
|
348
|
+
// Send response using your socket
|
|
349
|
+
udpSocket.send(response, responseRinfo.port, responseRinfo.address);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Bind to custom port or multiple interfaces
|
|
354
|
+
udpSocket.bind(5353, '0.0.0.0');
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Manual HTTPS Socket Handling
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
// Create server with manual HTTPS mode
|
|
361
|
+
const dnsServer = new DnsServer({
|
|
362
|
+
httpsKey: '...',
|
|
363
|
+
httpsCert: '...',
|
|
364
|
+
httpsPort: 853,
|
|
365
|
+
udpPort: 53,
|
|
366
|
+
dnssecZone: 'example.com',
|
|
367
|
+
manualHttpsMode: true // HTTPS manual, UDP automatic
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await dnsServer.start(); // UDP binds, HTTPS doesn't
|
|
371
|
+
|
|
372
|
+
// Create your own TCP server
|
|
373
|
+
const tcpServer = net.createServer((socket) => {
|
|
374
|
+
// Pass TCP sockets to DNS server
|
|
375
|
+
dnsServer.handleHttpsSocket(socket);
|
|
208
376
|
});
|
|
209
377
|
|
|
210
|
-
|
|
378
|
+
tcpServer.listen(8853, '0.0.0.0');
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Full Manual Mode
|
|
211
382
|
|
|
212
|
-
|
|
213
|
-
|
|
383
|
+
Control both protocols manually for complete flexibility:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const dnsServer = new DnsServer({
|
|
387
|
+
httpsKey: '...',
|
|
388
|
+
httpsCert: '...',
|
|
389
|
+
httpsPort: 853,
|
|
390
|
+
udpPort: 53,
|
|
391
|
+
dnssecZone: 'example.com',
|
|
392
|
+
manualUdpMode: true,
|
|
393
|
+
manualHttpsMode: true
|
|
214
394
|
});
|
|
215
395
|
|
|
216
|
-
|
|
396
|
+
await dnsServer.start(); // Neither protocol binds
|
|
397
|
+
|
|
398
|
+
// Set up your own socket handling for both protocols
|
|
399
|
+
// Perfect for custom routing, load balancing, or clustering
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
#### Advanced Use Cases
|
|
403
|
+
|
|
404
|
+
##### Load Balancing Across Multiple UDP Sockets
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// Create multiple UDP sockets for different CPU cores
|
|
408
|
+
const sockets = [];
|
|
409
|
+
const numCPUs = require('os').cpus().length;
|
|
410
|
+
|
|
411
|
+
for (let i = 0; i < numCPUs; i++) {
|
|
412
|
+
const socket = dgram.createSocket({
|
|
413
|
+
type: 'udp4',
|
|
414
|
+
reuseAddr: true // Allow multiple sockets on same port
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
socket.on('message', (msg, rinfo) => {
|
|
418
|
+
dnsServer.handleUdpMessage(msg, rinfo, (response, rinfo) => {
|
|
419
|
+
socket.send(response, rinfo.port, rinfo.address);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
socket.bind(53);
|
|
424
|
+
sockets.push(socket);
|
|
425
|
+
}
|
|
217
426
|
```
|
|
218
427
|
|
|
219
|
-
|
|
428
|
+
##### Clustering with Worker Processes
|
|
220
429
|
|
|
221
|
-
|
|
430
|
+
```typescript
|
|
431
|
+
import cluster from 'cluster';
|
|
432
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
433
|
+
|
|
434
|
+
if (cluster.isPrimary) {
|
|
435
|
+
// Master process accepts connections
|
|
436
|
+
const server = net.createServer({ pauseOnConnect: true });
|
|
437
|
+
|
|
438
|
+
// Distribute connections to workers
|
|
439
|
+
server.on('connection', (socket) => {
|
|
440
|
+
const worker = getNextWorker(); // Round-robin or custom logic
|
|
441
|
+
worker.send('socket', socket);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
server.listen(853);
|
|
445
|
+
} else {
|
|
446
|
+
// Worker process handles DNS
|
|
447
|
+
const dnsServer = new DnsServer({
|
|
448
|
+
httpsKey: '...',
|
|
449
|
+
httpsCert: '...',
|
|
450
|
+
httpsPort: 853,
|
|
451
|
+
udpPort: 53,
|
|
452
|
+
dnssecZone: 'example.com',
|
|
453
|
+
manualHttpsMode: true
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
process.on('message', (msg, socket) => {
|
|
457
|
+
if (msg === 'socket') {
|
|
458
|
+
dnsServer.handleHttpsSocket(socket);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
await dnsServer.start();
|
|
463
|
+
}
|
|
464
|
+
```
|
|
222
465
|
|
|
223
|
-
|
|
466
|
+
##### Custom Transport Protocol
|
|
224
467
|
|
|
225
468
|
```typescript
|
|
226
|
-
|
|
227
|
-
import
|
|
228
|
-
import fs from 'fs';
|
|
469
|
+
// Use DNS server with custom transport (e.g., WebSocket)
|
|
470
|
+
import WebSocket from 'ws';
|
|
229
471
|
|
|
472
|
+
const wss = new WebSocket.Server({ port: 8080 });
|
|
230
473
|
const dnsServer = new DnsServer({
|
|
231
|
-
httpsKey:
|
|
232
|
-
httpsCert:
|
|
233
|
-
httpsPort:
|
|
474
|
+
httpsKey: '...',
|
|
475
|
+
httpsCert: '...',
|
|
476
|
+
httpsPort: 853,
|
|
234
477
|
udpPort: 53,
|
|
235
478
|
dnssecZone: 'example.com',
|
|
479
|
+
manualUdpMode: true,
|
|
480
|
+
manualHttpsMode: true
|
|
236
481
|
});
|
|
237
482
|
|
|
238
|
-
dnsServer.
|
|
483
|
+
await dnsServer.start();
|
|
484
|
+
|
|
485
|
+
wss.on('connection', (ws) => {
|
|
486
|
+
ws.on('message', (data) => {
|
|
487
|
+
// Process DNS query from WebSocket
|
|
488
|
+
const response = dnsServer.processRawDnsPacket(Buffer.from(data));
|
|
489
|
+
ws.send(response);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
##### Multi-Interface Binding
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
// Bind to multiple network interfaces manually
|
|
498
|
+
const interfaces = [
|
|
499
|
+
{ address: '192.168.1.100', type: 'udp4' },
|
|
500
|
+
{ address: '10.0.0.50', type: 'udp4' },
|
|
501
|
+
{ address: '::1', type: 'udp6' }
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
interfaces.forEach(({ address, type }) => {
|
|
505
|
+
const socket = dgram.createSocket(type);
|
|
506
|
+
|
|
507
|
+
socket.on('message', (msg, rinfo) => {
|
|
508
|
+
console.log(`Query received on ${address}`);
|
|
509
|
+
dnsServer.handleUdpMessage(msg, rinfo, (response, rinfo) => {
|
|
510
|
+
socket.send(response, rinfo.port, rinfo.address);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
socket.bind(53, address);
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Handling Different Protocols
|
|
519
|
+
|
|
520
|
+
#### UDP DNS Server
|
|
521
|
+
|
|
522
|
+
Traditional DNS queries over UDP (port 53):
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
526
|
+
import * as plugins from '@push.rocks/smartdns/server/plugins';
|
|
527
|
+
|
|
528
|
+
const dnsServer = new DnsServer({
|
|
529
|
+
udpPort: 5353, // Using alternate port for testing
|
|
530
|
+
httpsPort: 8443,
|
|
531
|
+
httpsKey: fs.readFileSync('/path/to/key.pem', 'utf8'),
|
|
532
|
+
httpsCert: fs.readFileSync('/path/to/cert.pem', 'utf8'),
|
|
533
|
+
dnssecZone: 'test.local' // Optional
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// The UDP server automatically handles DNS packet parsing and encoding
|
|
537
|
+
dnsServer.registerHandler('test.local', ['A'], (question) => ({
|
|
239
538
|
name: question.name,
|
|
240
|
-
type: 'A',
|
|
539
|
+
type: 'A',
|
|
241
540
|
class: 'IN',
|
|
242
|
-
ttl:
|
|
541
|
+
ttl: 60,
|
|
243
542
|
data: '127.0.0.1',
|
|
244
543
|
}));
|
|
245
544
|
|
|
246
|
-
dnsServer.start()
|
|
545
|
+
await dnsServer.start();
|
|
247
546
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
});
|
|
260
|
-
});
|
|
547
|
+
// Test with dig or nslookup:
|
|
548
|
+
// dig @localhost -p 5353 test.local
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
#### DNS-over-HTTPS (DoH) Server
|
|
552
|
+
|
|
553
|
+
Provide encrypted DNS queries over HTTPS:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
557
|
+
import * as fs from 'fs';
|
|
261
558
|
|
|
262
|
-
|
|
263
|
-
|
|
559
|
+
const dnsServer = new DnsServer({
|
|
560
|
+
httpsPort: 8443,
|
|
561
|
+
httpsKey: fs.readFileSync('/path/to/key.pem', 'utf8'),
|
|
562
|
+
httpsCert: fs.readFileSync('/path/to/cert.pem', 'utf8'),
|
|
264
563
|
});
|
|
265
564
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
565
|
+
// The HTTPS server automatically handles:
|
|
566
|
+
// - DNS wire format in POST body
|
|
567
|
+
// - Proper Content-Type headers (application/dns-message)
|
|
568
|
+
// - Base64url encoding for GET requests
|
|
569
|
+
|
|
570
|
+
dnsServer.registerHandler('secure.local', ['A'], (question) => ({
|
|
571
|
+
name: question.name,
|
|
572
|
+
type: 'A',
|
|
573
|
+
class: 'IN',
|
|
574
|
+
ttl: 300,
|
|
575
|
+
data: '10.0.0.1',
|
|
576
|
+
}));
|
|
269
577
|
|
|
270
|
-
|
|
578
|
+
await dnsServer.start();
|
|
271
579
|
|
|
272
|
-
|
|
580
|
+
// Test with curl:
|
|
581
|
+
// curl -H "Content-Type: application/dns-message" \
|
|
582
|
+
// --data-binary @query.bin \
|
|
583
|
+
// https://localhost:8443/dns-query
|
|
584
|
+
```
|
|
273
585
|
|
|
274
|
-
|
|
586
|
+
### Interface Binding
|
|
275
587
|
|
|
276
|
-
|
|
588
|
+
For enhanced security and network isolation, you can bind the DNS server to specific network interfaces instead of all available interfaces.
|
|
277
589
|
|
|
278
|
-
|
|
590
|
+
#### Localhost-Only Binding
|
|
279
591
|
|
|
280
|
-
|
|
592
|
+
Bind to localhost for development or local-only DNS services:
|
|
281
593
|
|
|
282
594
|
```typescript
|
|
283
|
-
|
|
595
|
+
const localServer = new DnsServer({
|
|
596
|
+
udpPort: 5353,
|
|
597
|
+
httpsPort: 8443,
|
|
598
|
+
httpsKey: cert.key,
|
|
599
|
+
httpsCert: cert.cert,
|
|
600
|
+
dnssecZone: 'local.test',
|
|
601
|
+
udpBindInterface: '127.0.0.1', // IPv4 localhost
|
|
602
|
+
httpsBindInterface: '127.0.0.1'
|
|
603
|
+
});
|
|
284
604
|
|
|
285
|
-
|
|
605
|
+
// Or use IPv6 localhost
|
|
606
|
+
const ipv6LocalServer = new DnsServer({
|
|
607
|
+
// ... other options
|
|
608
|
+
udpBindInterface: '::1', // IPv6 localhost
|
|
609
|
+
httpsBindInterface: '::1'
|
|
610
|
+
});
|
|
611
|
+
```
|
|
286
612
|
|
|
287
|
-
|
|
613
|
+
#### Specific Interface Binding
|
|
288
614
|
|
|
289
|
-
|
|
290
|
-
dnsServer = new DnsServer({
|
|
291
|
-
httpsKey: 'path/to/key.pem',
|
|
292
|
-
httpsCert: 'path/to/cert.pem',
|
|
293
|
-
httpsPort: 443,
|
|
294
|
-
udpPort: 53,
|
|
295
|
-
dnssecZone: 'example.com',
|
|
296
|
-
});
|
|
297
|
-
expect(dnsServer).toBeInstanceOf(DnsServer);
|
|
298
|
-
});
|
|
615
|
+
Bind to a specific network interface in multi-homed servers:
|
|
299
616
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
617
|
+
```typescript
|
|
618
|
+
const interfaceServer = new DnsServer({
|
|
619
|
+
udpPort: 53,
|
|
620
|
+
httpsPort: 443,
|
|
621
|
+
httpsKey: cert.key,
|
|
622
|
+
httpsCert: cert.cert,
|
|
623
|
+
dnssecZone: 'example.com',
|
|
624
|
+
udpBindInterface: '192.168.1.100', // Specific internal interface
|
|
625
|
+
httpsBindInterface: '10.0.0.50' // Different interface for HTTPS
|
|
303
626
|
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### Security Considerations
|
|
630
|
+
|
|
631
|
+
- **Default Behavior**: If not specified, servers bind to all interfaces (`0.0.0.0`)
|
|
632
|
+
- **Localhost Binding**: Use `127.0.0.1` or `::1` for development and testing
|
|
633
|
+
- **Production**: Consider binding to specific internal interfaces for security
|
|
634
|
+
- **Validation**: Invalid IP addresses will throw an error during server startup
|
|
304
635
|
|
|
305
|
-
|
|
306
|
-
|
|
636
|
+
### Advanced Handler Patterns
|
|
637
|
+
|
|
638
|
+
#### Pattern-Based Routing
|
|
639
|
+
|
|
640
|
+
Use glob patterns for flexible domain matching:
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
// Match all subdomains
|
|
644
|
+
dnsServer.registerHandler('*.example.com', ['A'], (question) => {
|
|
645
|
+
// Extract subdomain
|
|
646
|
+
const subdomain = question.name.replace('.example.com', '');
|
|
647
|
+
|
|
648
|
+
// Dynamic response based on subdomain
|
|
649
|
+
return {
|
|
307
650
|
name: question.name,
|
|
308
651
|
type: 'A',
|
|
309
652
|
class: 'IN',
|
|
310
653
|
ttl: 300,
|
|
311
|
-
data: '
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const response = dnsServer.processDnsRequest({
|
|
315
|
-
type: 'query',
|
|
316
|
-
id: 1,
|
|
317
|
-
flags: 0,
|
|
318
|
-
questions: [
|
|
319
|
-
{
|
|
320
|
-
name: 'test.example.com',
|
|
321
|
-
type: 'A',
|
|
322
|
-
class: 'IN',
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
answers: [],
|
|
326
|
-
});
|
|
654
|
+
data: subdomain === 'api' ? '10.0.0.10' : '10.0.0.1',
|
|
655
|
+
};
|
|
656
|
+
});
|
|
327
657
|
|
|
328
|
-
|
|
329
|
-
|
|
658
|
+
// Match specific patterns
|
|
659
|
+
dnsServer.registerHandler('db-*.service.local', ['A'], (question) => {
|
|
660
|
+
const instanceId = question.name.match(/db-(\d+)/)?.[1];
|
|
661
|
+
return {
|
|
662
|
+
name: question.name,
|
|
330
663
|
type: 'A',
|
|
331
664
|
class: 'IN',
|
|
332
|
-
ttl:
|
|
333
|
-
data:
|
|
334
|
-
}
|
|
665
|
+
ttl: 60,
|
|
666
|
+
data: `10.0.1.${instanceId}`,
|
|
667
|
+
};
|
|
335
668
|
});
|
|
336
669
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
class: 'IN',
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
});
|
|
670
|
+
// Catch-all handler
|
|
671
|
+
dnsServer.registerHandler('*', ['A'], (question) => ({
|
|
672
|
+
name: question.name,
|
|
673
|
+
type: 'A',
|
|
674
|
+
class: 'IN',
|
|
675
|
+
ttl: 300,
|
|
676
|
+
data: '127.0.0.1',
|
|
677
|
+
}));
|
|
678
|
+
```
|
|
350
679
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
680
|
+
### Testing
|
|
681
|
+
|
|
682
|
+
The library uses `@git.zone/tstest` for testing. Here's an example of comprehensive tests:
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
686
|
+
import { Smartdns } from '@push.rocks/smartdns/client';
|
|
687
|
+
import { DnsServer } from '@push.rocks/smartdns/server';
|
|
688
|
+
|
|
689
|
+
// Test DNS Client
|
|
690
|
+
tap.test('DNS Client - Query Records', async () => {
|
|
691
|
+
const dnsClient = new Smartdns({});
|
|
692
|
+
|
|
693
|
+
// Test A record query
|
|
694
|
+
const aRecords = await dnsClient.getRecordsA('google.com');
|
|
695
|
+
expect(aRecords).toBeArray();
|
|
696
|
+
expect(aRecords[0]).toHaveProperty('type', 'A');
|
|
697
|
+
expect(aRecords[0].data).toMatch(/^\d+\.\d+\.\d+\.\d+$/);
|
|
698
|
+
|
|
699
|
+
// Test TXT record query
|
|
700
|
+
const txtRecords = await dnsClient.getRecordsTxt('google.com');
|
|
701
|
+
expect(txtRecords).toBeArray();
|
|
702
|
+
expect(txtRecords[0]).toHaveProperty('type', 'TXT');
|
|
703
|
+
});
|
|
358
704
|
|
|
359
|
-
|
|
705
|
+
// Test DNS Server
|
|
706
|
+
let dnsServer: DnsServer;
|
|
360
707
|
|
|
361
|
-
|
|
362
|
-
|
|
708
|
+
tap.test('DNS Server - Setup and Start', async () => {
|
|
709
|
+
dnsServer = new DnsServer({
|
|
710
|
+
udpPort: 5353,
|
|
711
|
+
httpsPort: 8443,
|
|
712
|
+
httpsKey: 'test-key', // Use test certificates
|
|
713
|
+
httpsCert: 'test-cert',
|
|
714
|
+
dnssecZone: 'test.local'
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(dnsServer).toBeInstanceOf(DnsServer);
|
|
718
|
+
await dnsServer.start();
|
|
719
|
+
});
|
|
363
720
|
|
|
364
|
-
|
|
365
|
-
|
|
721
|
+
tap.test('DNS Server - Register Handlers', async () => {
|
|
722
|
+
// Register multiple handlers
|
|
723
|
+
dnsServer.registerHandler('test.local', ['A'], () => ({
|
|
724
|
+
name: 'test.local',
|
|
366
725
|
type: 'A',
|
|
367
726
|
class: 'IN',
|
|
368
727
|
ttl: 300,
|
|
369
728
|
data: '127.0.0.1',
|
|
729
|
+
}));
|
|
730
|
+
|
|
731
|
+
dnsServer.registerHandler('*.test.local', ['A'], (question) => ({
|
|
732
|
+
name: question.name,
|
|
733
|
+
type: 'A',
|
|
734
|
+
class: 'IN',
|
|
735
|
+
ttl: 60,
|
|
736
|
+
data: '127.0.0.2',
|
|
737
|
+
}));
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
tap.test('DNS Server - Query via UDP', async (tools) => {
|
|
741
|
+
const dnsPacket = (await import('dns-packet')).default;
|
|
742
|
+
const dgram = await import('dgram');
|
|
743
|
+
|
|
744
|
+
const query = dnsPacket.encode({
|
|
745
|
+
type: 'query',
|
|
746
|
+
id: 1234,
|
|
747
|
+
questions: [{
|
|
748
|
+
type: 'A',
|
|
749
|
+
class: 'IN',
|
|
750
|
+
name: 'test.local',
|
|
751
|
+
}],
|
|
370
752
|
});
|
|
753
|
+
|
|
754
|
+
const client = dgram.createSocket('udp4');
|
|
755
|
+
const done = tools.defer();
|
|
756
|
+
|
|
757
|
+
client.on('message', (msg) => {
|
|
758
|
+
const response = dnsPacket.decode(msg);
|
|
759
|
+
expect(response.answers[0].data).toEqual('127.0.0.1');
|
|
760
|
+
client.close();
|
|
761
|
+
done.resolve();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
client.send(query, 5353, 'localhost'); // Use the port specified during server creation
|
|
765
|
+
await done.promise;
|
|
371
766
|
});
|
|
372
767
|
|
|
373
|
-
tap.test('
|
|
768
|
+
tap.test('DNS Server - Cleanup', async () => {
|
|
374
769
|
await dnsServer.stop();
|
|
375
|
-
expect(dnsServer.isRunning()).toBeFalse();
|
|
376
770
|
});
|
|
377
771
|
|
|
772
|
+
// Run tests
|
|
378
773
|
await tap.start();
|
|
379
774
|
```
|
|
380
775
|
|
|
381
|
-
|
|
776
|
+
### Best Practices
|
|
777
|
+
|
|
778
|
+
1. **Port Selection**: Use non-privileged ports (>1024) during development
|
|
779
|
+
2. **Handler Organization**: Group related handlers together
|
|
780
|
+
3. **Error Handling**: Always handle DNS query errors gracefully
|
|
781
|
+
4. **DNSSEC**: Enable DNSSEC for production deployments
|
|
782
|
+
5. **Monitoring**: Log DNS queries for debugging and analytics
|
|
783
|
+
6. **Rate Limiting**: Implement rate limiting for public DNS servers
|
|
784
|
+
7. **Caching**: Respect TTL values and implement proper caching
|
|
785
|
+
8. **Manual Sockets**: Use manual socket handling for clustering and load balancing
|
|
786
|
+
|
|
787
|
+
### Performance Considerations
|
|
788
|
+
|
|
789
|
+
- The DNS client uses HTTP keep-alive for connection reuse
|
|
790
|
+
- The DNS server handles concurrent UDP and HTTPS requests efficiently
|
|
791
|
+
- DNSSEC signatures are generated on-demand to reduce memory usage
|
|
792
|
+
- Pattern matching uses caching for improved performance
|
|
793
|
+
- Manual socket handling enables horizontal scaling across CPU cores
|
|
794
|
+
|
|
795
|
+
### Security Considerations
|
|
382
796
|
|
|
383
|
-
|
|
797
|
+
- Always use DNSSEC for authenticated responses
|
|
798
|
+
- Enable DoH for encrypted DNS queries
|
|
799
|
+
- Validate and sanitize all DNS inputs
|
|
800
|
+
- Implement access controls for DNS server handlers
|
|
801
|
+
- Use Let's Encrypt for automatic SSL certificate management
|
|
802
|
+
- Never expose internal network information through DNS
|
|
803
|
+
- Bind to specific interfaces in production environments
|
|
804
|
+
- Use manual socket handling for custom security layers
|
|
384
805
|
|
|
385
|
-
This comprehensive
|
|
806
|
+
This comprehensive library provides everything needed for both DNS client operations and running production-grade DNS servers with modern security features in TypeScript.
|
|
386
807
|
|
|
387
808
|
## License and Legal Information
|
|
388
809
|
|
|
389
|
-
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) file within this repository.
|
|
810
|
+
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.
|
|
390
811
|
|
|
391
812
|
**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.
|
|
392
813
|
|
|
@@ -401,4 +822,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|
|
401
822
|
|
|
402
823
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
|
403
824
|
|
|
404
|
-
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.
|
|
825
|
+
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.
|