matchd 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop-https---relaxed-ruby-style-rubocop-yml +166 -0
- data/.rubocop.yml +20 -0
- data/.travis.yml +18 -0
- data/DEVELOPMENT.md +5 -0
- data/Gemfile +8 -0
- data/License.txt +373 -0
- data/README.md +473 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/matchd +4 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/examples/registry.yml +30 -0
- data/exe/matchd +6 -0
- data/lib/matchd.rb +31 -0
- data/lib/matchd/cli.rb +10 -0
- data/lib/matchd/cli/config.rb +36 -0
- data/lib/matchd/cli/config_file_option.rb +23 -0
- data/lib/matchd/cli/main.rb +45 -0
- data/lib/matchd/config.rb +39 -0
- data/lib/matchd/control.rb +56 -0
- data/lib/matchd/glue.rb +6 -0
- data/lib/matchd/glue/async_endpoint.rb +93 -0
- data/lib/matchd/helpers.rb +14 -0
- data/lib/matchd/registry.rb +66 -0
- data/lib/matchd/response.rb +72 -0
- data/lib/matchd/response/a.rb +12 -0
- data/lib/matchd/response/aaaa.rb +5 -0
- data/lib/matchd/response/cname.rb +12 -0
- data/lib/matchd/response/mx.rb +16 -0
- data/lib/matchd/response/ns.rb +12 -0
- data/lib/matchd/response/ptr.rb +12 -0
- data/lib/matchd/response/soa.rb +28 -0
- data/lib/matchd/response/srv.rb +20 -0
- data/lib/matchd/response/txt.rb +12 -0
- data/lib/matchd/rule.rb +116 -0
- data/lib/matchd/rule/append.rb +23 -0
- data/lib/matchd/rule/fail.rb +18 -0
- data/lib/matchd/rule/invalid.rb +24 -0
- data/lib/matchd/rule/passthrough.rb +50 -0
- data/lib/matchd/rule/respond.rb +16 -0
- data/lib/matchd/server.rb +44 -0
- data/lib/matchd/version.rb +3 -0
- data/matchd.gemspec +31 -0
- metadata +191 -0
data/README.md
ADDED
@@ -0,0 +1,473 @@
|
|
1
|
+
# Matchd
|
2
|
+
|
3
|
+
Matchd is a DNS server which can be configured via YAML files.
|
4
|
+
|
5
|
+
Like it's example [`RubyDNS`](https://github.com/socketry/rubydns), it uses a pattern matching rule model but does not require you to write any ruby.
|
6
|
+
|
7
|
+
**This is work in progress and not yet released. I will break things all the time.**
|
8
|
+
|
9
|
+
[![Build Status](https://travis-ci.org/fnordfish/matchd.svg?branch=master)](https://travis-ci.org/fnordfish/matchd)
|
10
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/9791617021f321155cb0/maintainability)](https://codeclimate.com/github/fnordfish/matchd/maintainability)
|
11
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/9791617021f321155cb0/test_coverage)](https://codeclimate.com/github/fnordfish/matchd/test_coverage)
|
12
|
+
[![Inline docs](https://inch-ci.org/github/fnordfish/matchd.svg?branch=master)](http://inch-ci.org/github/fnordfish/matchd)
|
13
|
+
|
14
|
+
## Table of Contents
|
15
|
+
|
16
|
+
- [Matchd](#matchd)
|
17
|
+
- [Table of Contents](#table-of-contents)
|
18
|
+
- [Installation](#installation)
|
19
|
+
- [Intended use](#intended-use)
|
20
|
+
- [Usage](#usage)
|
21
|
+
- [Configure the Server/Daemon (`config.yml`)](#configure-the-serverdaemon-configyml)
|
22
|
+
- [Configure the Rules registry (`registry.yml`)](#configure-the-rules-registry-registryyml)
|
23
|
+
- [Matching the hostname](#matching-the-hostname)
|
24
|
+
- [Matching the resource_class](#matching-the-resourceclass)
|
25
|
+
- [respond](#respond)
|
26
|
+
- [`A`](#a)
|
27
|
+
- [`AAAA`](#aaaa)
|
28
|
+
- [`CNAME`](#cname)
|
29
|
+
- [`MX`](#mx)
|
30
|
+
- [`NS`](#ns)
|
31
|
+
- [`PTR`](#ptr)
|
32
|
+
- [`SOA`](#soa)
|
33
|
+
- [`SRV`](#srv)
|
34
|
+
- [`TXT`](#txt)
|
35
|
+
- [append](#append)
|
36
|
+
- [passthrough](#passthrough)
|
37
|
+
- [fail](#fail)
|
38
|
+
- [Using as a local Dev DNS](#using-as-a-local-dev-dns)
|
39
|
+
- [macOS / resolver](#macos--resolver)
|
40
|
+
- [Using as a library](#using-as-a-library)
|
41
|
+
- [Development](#development)
|
42
|
+
- [Contributing](#contributing)
|
43
|
+
- [License](#license)
|
44
|
+
- [Previous Work](#previous-work)
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
$ gem install matchd
|
49
|
+
|
50
|
+
## Intended use
|
51
|
+
|
52
|
+
Although it's probably possible to use as a public/private DNS, Matchd is intended to be used in a development environment where using "localhost" or "editing /etc/hosts" isn't feasible anymore. Such as:
|
53
|
+
|
54
|
+
- large list of sub-domains (use regular expressions to match them all)
|
55
|
+
- DNS record types like TXT, MX, SRV etc. for integration testing or development
|
56
|
+
|
57
|
+
The idea is to use a globally non-route-able top level domain like `.test` and configure your local system in a way, that it will use Matchd as a resolver for such domains.
|
58
|
+
|
59
|
+
You can use Matchd to hook into global top level domain such as `.com` if you wish, and Matchd will forward everything it can't match to an upstream resolver. But keep in mind that doing so can have unexpected or even catastrophic results when expediently matching a domain you actually need for work.
|
60
|
+
|
61
|
+
Please refer to the section "[Using as a local Dev DNS](#using-as-a-local-dev-dns)" on how to make your system use Matchd as a resolver.
|
62
|
+
|
63
|
+
## Usage
|
64
|
+
|
65
|
+
Print available commands and sub-commands:
|
66
|
+
|
67
|
+
$ matchd help
|
68
|
+
$ matchd help start # to display details for a specific command
|
69
|
+
$ matchd help config # ... or sub-command
|
70
|
+
$ matchd config help setup # prints details for a specific sub-commands command
|
71
|
+
|
72
|
+
|
73
|
+
Create the default configuration directory within your `$HOME`
|
74
|
+
|
75
|
+
$ matchd config setup
|
76
|
+
|
77
|
+
- `.matchd`: This is also the home for log and pid files
|
78
|
+
- `.matchd/config.yml`: Base configurations of the server
|
79
|
+
- `.matchd/registry.yml`: The registry for all of your rules
|
80
|
+
|
81
|
+
Please also see [`examples`](../examples/) for more.
|
82
|
+
|
83
|
+
## Configure the Server/Daemon (`config.yml`)
|
84
|
+
|
85
|
+
- `dot_dir`: Path to the daemons base directory. This is where log and pid files are written to.
|
86
|
+
- `listen`: Collection of interface where the server will listen on.
|
87
|
+
This setting supports multiple formats:
|
88
|
+
- Array/Collection form:
|
89
|
+
```yaml
|
90
|
+
listen:
|
91
|
+
- ["udp", "127.0.0.1", 15353]
|
92
|
+
```
|
93
|
+
- Hash/Structure form:
|
94
|
+
```yaml
|
95
|
+
listen:
|
96
|
+
- protocol: udp
|
97
|
+
ip: 127.0.0.1
|
98
|
+
port: 15353
|
99
|
+
```
|
100
|
+
- URL form:
|
101
|
+
```yaml
|
102
|
+
listen:
|
103
|
+
- "udp://127.0.0.1:15353"
|
104
|
+
```
|
105
|
+
- `resolver`: Collection of upstream resolvers for "passthrough" requests. (If a query could net been matched, we'll forward that request to this server)
|
106
|
+
Supports the same formats as `listen`:
|
107
|
+
- Array/Collection form:
|
108
|
+
```yaml
|
109
|
+
resolver:
|
110
|
+
- ["udp", "1.1.1.1", 53]
|
111
|
+
```
|
112
|
+
- Hash/Structure form:
|
113
|
+
```yaml
|
114
|
+
resolver:
|
115
|
+
- protocol: udp
|
116
|
+
ip: 1.1.1.1
|
117
|
+
port: 53
|
118
|
+
```
|
119
|
+
- URL form:
|
120
|
+
```yaml
|
121
|
+
resolver:
|
122
|
+
- "udp://1.1.1.1:53"
|
123
|
+
```
|
124
|
+
You can also default to your system settings (NOTE that this is a single value):
|
125
|
+
```yaml
|
126
|
+
resolver: system
|
127
|
+
```
|
128
|
+
|
129
|
+
## Configure the Rules registry (`registry.yml`)
|
130
|
+
|
131
|
+
This file uses a single key `rules` which lists all the rules. Each rule is a structure with three keys.
|
132
|
+
|
133
|
+
```yaml
|
134
|
+
- rules:
|
135
|
+
- match: ... # String or Regexp
|
136
|
+
resource_class: ... # A IN::* resource record name
|
137
|
+
_action_: ... # How to answer this match
|
138
|
+
```
|
139
|
+
|
140
|
+
The first two options, `match` and `resource_class` describe which query to match. The third describes how to respond and should be one of: `append_question`, `respond`, `passthrough` or `fail`.
|
141
|
+
|
142
|
+
Rules will get evaluated top-to-bottom. Once a rule matches, it will get executed and processing stops.
|
143
|
+
A match always considers the name _and_ resource class of the query.
|
144
|
+
|
145
|
+
The query name can be matched against a specific string or regular expression.
|
146
|
+
The query resource class can be matched against a single or a list of targets. When a list is used, any of the targets in the list will trigger.
|
147
|
+
|
148
|
+
When no rule matches, the query will be forwarded to the configured resolvers (see `config.yml` and it's `resolver` key).
|
149
|
+
|
150
|
+
### Matching the hostname
|
151
|
+
|
152
|
+
To match against a specific name use the string notation.
|
153
|
+
|
154
|
+
```yaml
|
155
|
+
# This will only match where the requested domain is exactly `mydomain.test`, sub-domains like `sub.mydomain.test` will not match.
|
156
|
+
match: mydomain.test
|
157
|
+
```
|
158
|
+
|
159
|
+
To match all sub-domains, use a regular expression.
|
160
|
+
|
161
|
+
```yaml
|
162
|
+
# This will only match sub-domain queries like `sub.mydomain.test` or `bus.mydomain.test`, but not `mydomain.de`
|
163
|
+
match: /^\w+\.mydomain\.test$/
|
164
|
+
# This will only match sub-domain queries like `sub.mydomain.test` or `bus.mydomain.test`, and also `mydomain.de`
|
165
|
+
match: /(^\w+\.)?mydomain\.test$/
|
166
|
+
# This will also match any depth of sub-domain queries like `bus.sub.mydomain.test`:
|
167
|
+
match: /(^\w+\.)*mydomain\.test$/
|
168
|
+
```
|
169
|
+
|
170
|
+
Under the hood, Matchd uses [ruby regular expressions](https://ruby-doc.org/core-2.5.1/Regexp.html).
|
171
|
+
For the YAML configuration format only the `/.../` syntax is supported, optionally using the `m`, `x` and `i` modifiers.
|
172
|
+
|
173
|
+
When using regular expressions, it's recommended to use `^` and `$` anchors to have better control over what you want to match. Also keep in mind, that you'll need to escape dots (`.` -> `\.`).
|
174
|
+
|
175
|
+
### Matching the resource_class
|
176
|
+
|
177
|
+
A single or a list of ARPA Internet specific resource record (RRs) names:
|
178
|
+
`A`, `AAAA`, `ANY`, `CNAME`, `HINFO`, `LOC`, `MINFO`, `MX`, `NS`, `PTR`, `SOA`, `SRV`, `TXT`, `WKS`
|
179
|
+
|
180
|
+
To give some most common samples:
|
181
|
+
|
182
|
+
```yaml
|
183
|
+
# IPv4 record
|
184
|
+
resource_class: A
|
185
|
+
# IPv6 record
|
186
|
+
resource_class: AAAA
|
187
|
+
# I'm funny:
|
188
|
+
resource_class:
|
189
|
+
- TXT
|
190
|
+
- CNAME
|
191
|
+
```
|
192
|
+
|
193
|
+
### respond
|
194
|
+
|
195
|
+
To let a rule respond, include the `respond` key.
|
196
|
+
|
197
|
+
Supported response types are: `A`, `AAAA`, `CNAME`, `MX`, `NS`, `PTR`, `SOA`, `SRV`, `TXT`
|
198
|
+
|
199
|
+
You can configure multiple responses per rule. Each rule has it's own configuration keys (see below) but they all support a common set of options:
|
200
|
+
|
201
|
+
|
202
|
+
```yaml
|
203
|
+
ttl: 86400 # The Time-To-Live of the record (default: 86400 seconds == 24h)
|
204
|
+
name: "other.sample.test." # The absolute DNS name (needs to end with a dot). Default is the question name.
|
205
|
+
section: answer # The answer section. One of "answer", "additional", "authority" (default: "answer")
|
206
|
+
```
|
207
|
+
|
208
|
+
To avoid clutter, there are some shortcuts when defining a rule:
|
209
|
+
|
210
|
+
1. Omit the List for single responses
|
211
|
+
2. Omit the Collection for responses that take only one argument
|
212
|
+
3. Omit the responds `resource_class` when it's the same as the queries `resource_class`
|
213
|
+
|
214
|
+
Example:
|
215
|
+
|
216
|
+
One single value response:
|
217
|
+
|
218
|
+
```yaml
|
219
|
+
- match: sample.test
|
220
|
+
resource_class: A
|
221
|
+
respond: 10.0.0.1
|
222
|
+
|
223
|
+
# is the same as:
|
224
|
+
- match: sample.test
|
225
|
+
resource_class: A
|
226
|
+
respond:
|
227
|
+
- resource_class: A
|
228
|
+
ip: 10.0.0.1
|
229
|
+
```
|
230
|
+
|
231
|
+
Multiple single value responses:
|
232
|
+
|
233
|
+
```yaml
|
234
|
+
- match: sample.test
|
235
|
+
resource_class: NS
|
236
|
+
respond:
|
237
|
+
- 'ns1.sample.test.'
|
238
|
+
- 'ns2.sample.test.'
|
239
|
+
|
240
|
+
# is the same as:
|
241
|
+
- match: sample.test
|
242
|
+
resource_class: NS
|
243
|
+
respond:
|
244
|
+
- resource_class: NS
|
245
|
+
host: 'ns1.sample.test.'
|
246
|
+
- resource_class: NS
|
247
|
+
host: 'ns2.sample.test.'
|
248
|
+
```
|
249
|
+
|
250
|
+
#### `A`
|
251
|
+
|
252
|
+
```yaml
|
253
|
+
resource_class: A
|
254
|
+
ip: "127.0.0.1"
|
255
|
+
```
|
256
|
+
|
257
|
+
#### `AAAA`
|
258
|
+
|
259
|
+
```yaml
|
260
|
+
resource_class: AAAA
|
261
|
+
ip: "::1"
|
262
|
+
```
|
263
|
+
|
264
|
+
#### `CNAME`
|
265
|
+
|
266
|
+
```yaml
|
267
|
+
resource_class: CNAME
|
268
|
+
alias: "sample.test"
|
269
|
+
```
|
270
|
+
|
271
|
+
#### `MX`
|
272
|
+
|
273
|
+
```yaml
|
274
|
+
resource_class: MX
|
275
|
+
preference: 10
|
276
|
+
host: "mail.sample.test"
|
277
|
+
```
|
278
|
+
|
279
|
+
#### `NS`
|
280
|
+
|
281
|
+
```yaml
|
282
|
+
resource_class: NS
|
283
|
+
host: "ns1.sample.test"
|
284
|
+
```
|
285
|
+
|
286
|
+
#### `PTR`
|
287
|
+
|
288
|
+
```yaml
|
289
|
+
resource_class: PTR
|
290
|
+
host: "host1.sample.test"
|
291
|
+
```
|
292
|
+
|
293
|
+
#### `SOA`
|
294
|
+
|
295
|
+
```yaml
|
296
|
+
resource_class: SOA
|
297
|
+
mname: "ns1.sample.test." # master zone name
|
298
|
+
rname: "admin.sample.test." # Responsible Name
|
299
|
+
serial: "1533038712" # Serial Number
|
300
|
+
refresh: 1200 # Refresh Time
|
301
|
+
retry: 900 # Retry Time
|
302
|
+
expire: 3600000 # Maximum TTL / Expiry Time
|
303
|
+
minimum_ttl: 172800 # Minimum TTL
|
304
|
+
```
|
305
|
+
|
306
|
+
#### `SRV`
|
307
|
+
|
308
|
+
```yaml
|
309
|
+
resource_class: SRV
|
310
|
+
target: jabber
|
311
|
+
priority: 10
|
312
|
+
weight: 0
|
313
|
+
port: 5269
|
314
|
+
# To make this meaningful, we need to provide a name:
|
315
|
+
name: "_xmpp-server._tcp.sample.test"
|
316
|
+
```
|
317
|
+
|
318
|
+
#### `TXT`
|
319
|
+
|
320
|
+
```yaml
|
321
|
+
resource_class: TXT
|
322
|
+
txt: "some-token=value"
|
323
|
+
```
|
324
|
+
|
325
|
+
### append
|
326
|
+
|
327
|
+
Append the results of another query to the response.
|
328
|
+
|
329
|
+
```yaml
|
330
|
+
append_question: A
|
331
|
+
# is the same as:
|
332
|
+
append_question:
|
333
|
+
- A
|
334
|
+
```
|
335
|
+
|
336
|
+
Append rules accept the same common options as Respond rules. The Response rules options will take precedence if configured.
|
337
|
+
|
338
|
+
```yaml
|
339
|
+
append_question:
|
340
|
+
ttl: 86400 # The Time-To-Live of the record (default: 86400 seconds == 24h)
|
341
|
+
name: "other.sample.test." # The absolute DNS name (needs to end with a dot). Default is the question name.
|
342
|
+
section: answer # The answer section. One of "answer", "additional", "authority" (default: "answer")
|
343
|
+
resource_class:
|
344
|
+
- A
|
345
|
+
- AAAA
|
346
|
+
```
|
347
|
+
|
348
|
+
Example:
|
349
|
+
|
350
|
+
```yaml
|
351
|
+
- match: dev.sample.test
|
352
|
+
resource_class: ANY
|
353
|
+
append_question:
|
354
|
+
ttl: 86400
|
355
|
+
resource_class:
|
356
|
+
- A
|
357
|
+
- CNAME
|
358
|
+
- MX
|
359
|
+
- NS
|
360
|
+
```
|
361
|
+
|
362
|
+
### passthrough
|
363
|
+
|
364
|
+
Query an upstream resolver and use its response.
|
365
|
+
|
366
|
+
Takes the same parameters as the server config's `resolver`:
|
367
|
+
|
368
|
+
```yaml
|
369
|
+
# Mixing IPv4 and IPv6 in different formats:
|
370
|
+
passthrough:
|
371
|
+
- ["udp", "1.1.1.1", 53]
|
372
|
+
- protocol: udp
|
373
|
+
ip: 1.0.0.1
|
374
|
+
port: 53
|
375
|
+
- "tcp://[2606:4700:4700::1111]:53"
|
376
|
+
- system
|
377
|
+
|
378
|
+
# Shortcut, if you need only one:
|
379
|
+
passthrough: system # or any other version above
|
380
|
+
|
381
|
+
# Long format with added options
|
382
|
+
passthrough:
|
383
|
+
force: true
|
384
|
+
resolver:
|
385
|
+
- ["udp", "1.1.1.1", 53]
|
386
|
+
- protocol: udp
|
387
|
+
ip: 1.0.0.1
|
388
|
+
port: 53
|
389
|
+
- "tcp://[2606:4700:4700::1111]:53"
|
390
|
+
- system
|
391
|
+
```
|
392
|
+
|
393
|
+
|
394
|
+
|
395
|
+
### fail
|
396
|
+
|
397
|
+
Indicate an error processing the query.
|
398
|
+
|
399
|
+
To cite `Async::DNS` list of list of the most commonly used errors:
|
400
|
+
|
401
|
+
- `NoError`: No error occurred.
|
402
|
+
- `FormErr`: The incoming data was not formatted correctly.
|
403
|
+
- `ServFail`: The operation caused a server failure (internal error, etc).
|
404
|
+
- `NXDomain`: Non-eXistant Domain (domain record does not exist).
|
405
|
+
- `NotImp`: The operation requested is not implemented.
|
406
|
+
- `Refused`: The operation was refused by the server.
|
407
|
+
- `NotAuth`: The server is not authoritive for the zone.
|
408
|
+
|
409
|
+
Note that the above values are all case __sensitive__!
|
410
|
+
|
411
|
+
Example:
|
412
|
+
|
413
|
+
```yaml
|
414
|
+
- match: not-there.sample.test
|
415
|
+
resource_class: A
|
416
|
+
fail: NXDomain
|
417
|
+
|
418
|
+
# a catchall rule:
|
419
|
+
- match: /^(\w+\.)?sample.test$/
|
420
|
+
resource_class: A
|
421
|
+
respond: "127.0.0.1"
|
422
|
+
```
|
423
|
+
|
424
|
+
## Using as a local Dev DNS
|
425
|
+
|
426
|
+
When using Matchd as a local dns server for development purposes, it's recommended to:
|
427
|
+
|
428
|
+
1. use non-route-able top level domains, such as `.test`
|
429
|
+
2. use a non-privileged port (usually < 1024), such as 15353 (the default)
|
430
|
+
|
431
|
+
### macOS / resolver
|
432
|
+
|
433
|
+
Create a file under `/etc/resolver/` which is named like the top level domain you'd like to specify a different resolver (Matchd) for.
|
434
|
+
|
435
|
+
```bash
|
436
|
+
cat <<EOF | sudo tee /etc/resolver/test
|
437
|
+
nameserver 127.0.0.1
|
438
|
+
nameserver ::1
|
439
|
+
port 15353
|
440
|
+
EOF
|
441
|
+
```
|
442
|
+
|
443
|
+
|
444
|
+
|
445
|
+
## Using as a library
|
446
|
+
|
447
|
+
While Matchd is intended to be used as a demonizing CLI tool, you can use it as a library.
|
448
|
+
|
449
|
+
TODO: Docs
|
450
|
+
|
451
|
+
## Development
|
452
|
+
|
453
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
454
|
+
|
455
|
+
To install this gem onto your local machine, run `bin/rake install`. To release a new version, update the version number in `version.rb`, and then run `bin/rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
456
|
+
|
457
|
+
|
458
|
+
## Contributing
|
459
|
+
|
460
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fnordfish/matchd
|
461
|
+
|
462
|
+
## License
|
463
|
+
|
464
|
+
This software is distributed under the Mozilla Public License Version 2.0
|
465
|
+
|
466
|
+
A copy of the license text can be found in this repository under `License.txt` or at http://mozilla.org/MPL/2.0/
|
467
|
+
|
468
|
+
### Previous Work
|
469
|
+
|
470
|
+
This software is based on the ideas of:
|
471
|
+
|
472
|
+
- [vagrant-dns](https://github.com/BerlinVagrant/vagrant-dns), created by Florian Gilcher and released under the MIT License
|
473
|
+
- [rubydns](https://github.com/socketry/rubydns), created by Samuel Williams and released under the MIT License.
|