├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── TODO.md
├── docs
└── index.md
├── examples
├── app.js
├── cluster.js
├── noanswer.js
└── reflect.js
├── lib
├── errors.js
├── index.js
├── protocol.js
├── query.js
├── records
│ ├── a.js
│ ├── aaaa.js
│ ├── cname.js
│ ├── mx.js
│ ├── ns.js
│ ├── soa.js
│ ├── srv.js
│ └── txt.js
├── server.js
└── validators.js
├── package.json
└── test
├── dig.js
├── dnsbuffer.js
├── helper.js
├── named.test.js
├── protocol.test.js
├── query.test.js
├── records.test.js
└── validator.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.swmp
3 | node_modules
4 | npm-debug.log
5 | tmp
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Trevor Orsztynowicz
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | NODEUNIT := ./node_modules/.bin/nodeunit
2 | BUNYAN := ./node_modules/.bin/bunyan
3 | NPM := $(shell which npm)
4 |
5 | .PHONY: setup
6 | setup: $(NPM)
7 | $(NPM) install
8 |
9 | .PHONY: test
10 | test: $(NODEUNIT)
11 | $(NODEUNIT) test/*.test.js $(BUNYAN)
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-named - DNS Server in Node.js
2 |
3 | Node-named is a lightweight DNS server written in pure javascript. It has
4 | limited support for the DNS spec, but aims to implement all of the *common*
5 | functionality that is in use today.
6 |
7 | ** This project is not actively maintained **
8 | I've received a lot of great PRs for this project, but I don't have the capacity to actively maintain this library at the moment. I feel strongly about maintaining backwards compatibility for people who rely on it, so any PRs would also need to adhere to keeping the API sane, or contribute to some improvement in performance.
9 |
10 |
11 |
12 | ## Creating a DNS Server
13 | ```javascript
14 | var named = require('./lib/index');
15 | var server = named.createServer();
16 | var ttl = 300;
17 |
18 | server.listen(9999, '127.0.0.1', function() {
19 | console.log('DNS server started on port 9999');
20 | });
21 |
22 | server.on('query', function(query) {
23 | var domain = query.name();
24 | console.log('DNS Query: %s', domain)
25 | var target = new named.SOARecord(domain, {serial: 12345});
26 | query.addAnswer(domain, target, ttl);
27 | server.send(query);
28 | });
29 | ```
30 | ## Creating DNS Records
31 |
32 | node-named provides helper functions for creating DNS records.
33 | The records are available under 'named.record.NAME' where NAME is one
34 | of ['A', 'AAAA', 'CNAME', 'SOA', 'MX', 'NS', 'TXT, 'SRV']. It is important to
35 | remember that these DNS records are not permanently added to the server.
36 | They only exist for the length of the particular request. After that, they are
37 | destroyed. This means you have to create your own lookup mechanism.
38 | ```javascript
39 | var named = require('node-named');
40 |
41 | var soaRecord = new named.SOARecord('example.com', {serial: 201205150000});
42 | console.log(soaRecord);
43 | ```
44 | ### Supported Record Types
45 |
46 | The following record types are supported
47 |
48 | * A (ipv4)
49 | * AAAA (ipv6)
50 | * CNAME (aliases)
51 | * SOA (start of authority)
52 | * MX (mail server records)
53 | * NS (nameserver entries)
54 | * TXT (arbitrary text entries)
55 | * SRV (service discovery)
56 |
57 | ## Logging
58 |
59 | node-named uses [http://github.com/trentm/node-bunyan](bunyan) for logging.
60 | It's a lot nicer to use if you npm install bunyan and put the bunyan tool in
61 | your path. Otherwise, you will end up with JSON formatted log output by default.
62 |
63 | ### Replacing the default logger
64 |
65 | You can pass in an alternate logger if you wish. If you do not, then it will use
66 | bunyan by default. Your logger must expose the functions 'info', 'debug',
67 | 'warn', 'trace', 'error', and 'notice'.
68 |
69 | ### TODO
70 |
71 | * Better record validation
72 | * Create DNS client for query recursor
73 | * Add support for PTR records
74 | * Add support for TCP AXFR requests
75 |
76 | ## Tell me even more...
77 |
78 | When DNS was designed it was designed prior
79 | to the web existing, so many of the features in the RFC are either never used,
80 | or were never implemented. This server aims to be RFC compliant, but does not
81 | implement any other protocol other than INET (the one we're all used to), and
82 | only supports a handful of record types (the ones that are in use on a regular
83 | basis).
84 |
85 | ## Looking up Records
86 |
87 | There are a few handy ways to lookup DNS records in node.
88 | https://github.com/LCMApps/dns-lookup-cache
89 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | * Add better message compression using 'reference pointers' instead of
4 | additional nsName entries.
5 | * Add / build Query recursor (most record types possible using c-ares)
6 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: API | node-named
3 | ---
4 |
5 | # node-named
6 |
7 | node-named is a lightweight DNS server written in javascript. It implements
8 | commonly used functionality from a variety of DNS RFC specifications. Unlike
9 | many other DNS servers node-named does not attempt to manage DNS records for
10 | you. You simply get a request, build your response based on whatever criteria
11 | you desire, and then send that response back to the client.
12 |
13 | ## Seriously?
14 |
15 | Actually this is quite useful. Both BIND and PowerDNS assume that records never
16 | change, and are not designed to be manipulated using an API or have elegant
17 | pluggable storage mechanisms. This DNS server is good for creating services
18 | where your records may change frequently, or you would like to access records
19 | stored in a central system using a mechanism of your choosing.
20 |
21 |
22 | # Installation
23 |
24 | $ npm install named
25 |
26 | # Server API
27 |
28 | var named = require('./lib/index');
29 | var server = named.createServer();
30 |
31 | server.listen(9999, '127.0.0.1', function() {
32 | console.log('DNS server started on port 9999');
33 | });
34 |
35 | server.on('query', function(query) {
36 | var domain = query.name();
37 | var target = new named.SOARecord(domain, {serial: 12345});
38 | // 300 is the ttl for this record
39 | query.addAnswer(domain, target, 300);
40 | server.send(query);
41 | });
42 |
43 | Hit this DNS server with `dig` to see some results. Because we are only
44 | handling DNS responses for one record type (SOA or 'Start of Authority'), that
45 | is the response we will see, regardless of the type we make a request for. Dig
46 | is nice about this.
47 |
48 | $ dig @localhost -p9999 example.com SOA
49 |
50 | ; <<>> DiG 9.7.3-P3 <<>> @localhost -p9999 example.com SOA
51 | ; (3 servers found)
52 | ;; global options: +cmd
53 | ;; Got answer:
54 | ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32739
55 | ;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
56 | ;; WARNING: recursion requested but not available
57 |
58 | ;; QUESTION SECTION:
59 | ;example.com. IN SOA
60 |
61 | ;; ANSWER SECTION:
62 | example.com. 5 IN SOA example.com. hostmaster.example.com. 12345 10 10 10 10
63 |
64 | ;; Query time: 10 msec
65 | ;; SERVER: ::1#9999(::1)
66 | ;; WHEN: Wed May 23 19:24:09 2012
67 | ;; MSG SIZE rcvd: 109
68 |
69 |
70 | ## Named API
71 |
72 | ### named.createServer([options])
73 |
74 | Create a new named server.
75 |
76 | options is an object which may specify:
77 |
78 | - log: an optional bunyan logger
79 | - name: an optional name used to identify the server
80 |
81 | Here is an example a named server listening on port 53
82 |
83 | var named = require('named');
84 |
85 | var server = named.createServer({
86 | name: 'named0'
87 | });
88 |
89 | server.listen(53);
90 |
91 | ## Class: named.Server
92 |
93 | ### server.listen(port, [host], [onListen])
94 |
95 | Start accepting connections on the specified `port` and `host`.
96 | If the host is ommited then it will listen on all IPv4 and IPv6 interfaces.
97 |
98 | This function is asyncronous. Whenever the server is litening a `listen` event
99 | will be emitted. The last parameter `onListen` will be executed but not attached
100 | to the listen event, when the server is listening.
101 |
102 | ### server.send(queryResponse)
103 |
104 | Sends a `queryResponse` message. The queryResponse includes information about
105 | the client in the object itself. The `send` function will encode the message
106 | and send the response to the appropriate client. Unsolicited DNS messages are
107 | not permitted. This function should only be used within the `query` event.
108 |
109 | **Note** If you do not add any answers to your query, then the `send()` method
110 | will send a 'null-response' DNS message. This is the equivalent of an HTTP 404.
111 |
112 | ### server.close(onClose)
113 |
114 | Stops listening and closes the socket. `onClose` is an optional callback that
115 | will be attached to the underlying socket `close` event.
116 |
117 | ### Event: 'listening'
118 |
119 | `function() { }`
120 |
121 | Emitted once, when the server starts listening on the specified `port` and
122 | `host`
123 |
124 | ### Event: 'query'
125 |
126 | `function (query) { }`
127 |
128 | Emitted each time there is valid request. `query` is an instance of
129 | `named.Query`
130 |
131 | ### Event: 'clientError'
132 |
133 | `function (error) { }`
134 |
135 | Emitted when there is an invalid DNS request. This may be caused by a bad UDP
136 | datagram, or some other malformed DNS request. Parser errors are not included
137 | here.
138 |
139 | `error` is an instance of `named.DnsError`
140 |
141 | ### Event: 'uncaughtException'
142 |
143 | `function (error) { }`
144 |
145 | Emitted when there is an uncaught exception somewhere in the protocol stack.
146 |
147 | `error` is an instance of `named.DnsError`
148 |
149 | ### Event: 'after'
150 |
151 | `function (query, bytes)`
152 |
153 | Emitted after a `query` is sent to a client. This can be used for logging
154 | purposes.
155 |
156 | `query` is an instance of `named.Query`
157 | `bytes` is the number of bytes sent over the wire to the client
158 |
159 | ## Class: named.Query
160 |
161 | A query message is emitted by the `query` event. Query messages include all of
162 | the information about the query from the Client, including the client details.
163 | Because DNS queries are UDP based, the entire query itself is echoed back onto
164 | the wire, with the answer appended to its appropriate 'answer' fields. Several
165 | headers are changed, but the query is the same.
166 |
167 | For this reason, you 'reflect' the modified query back to the client. Prior to
168 | doing this you can check the 'Question' and 'Type' of question and perform an
169 | appropriate lookup & generate an appropriate response.
170 |
171 | ### query.name()
172 |
173 | Returns a string containing the query question name. This may be a hostname, but
174 | depends on the type
175 |
176 | ### query.type()
177 |
178 | Returns a string containing the type code for the query question.
179 |
180 | ### query.answers()
181 |
182 | Returns an array of answers that have been added to the query
183 |
184 | ### query.addAnswer(name, record, ttl)
185 |
186 | Add an instances of `named.Record` to the query.
187 | Name is the name you want to respond with (in 99.99% of cases, the
188 | query.name()), record is the record instance, and type is the type of record you
189 | are responding with. In most cases this will be what the query.type() returns,
190 | but for instances like an 'A' or 'AAAA' request you may elect to respond with a
191 | CNAME record.
192 |
193 | ### query.operation()
194 |
195 | Returns the type of operation that the client is requesting. In almost all cases
196 | this will be 'query'. Valid operations are 'query', 'status', 'notify', and
197 | 'update'.
198 |
199 | ### query.encode()
200 |
201 | Encodes the query and stores the results as a `buffer` in the query itself.
202 | This function should never need to be invoked, as the `server.send` function
203 | will automatically encode a query prior to being sent to the client.
204 |
205 |
206 | ## Records
207 |
208 | A DNS query is a question posed to a server about a record for a specific
209 | domain. The questions are for specific 'types' of records. Of the types listed
210 | in all of the DNS RFCs only some are still in use, and even fewer are
211 | frequently used. Each type of request has an appropriate response, each of
212 | which have different formats. These response formats are known as
213 | "Resource Records" or for the sake of named, just 'Records'.
214 |
215 | All records in named are created using the `new` keyword.
216 |
217 | ### named.SOARecord(domain, [options])
218 |
219 | Create a DNS 'Start of Authority' record
220 |
221 | Options:
222 |
223 | - `admin`: The DNS name formatted email address of the administrator for this
224 | domain. Defaults to 'hostmaster.[domain]'
225 | - `serial`: The serial number of this domain. Defaults to 0
226 | - `refresh`: The refresh interval for this domain. Defaults to 10
227 | - `retry`: The retry interval for this domain. Defaults to 10
228 | - `expire`: The expire interval for this domain. Defaults to 10
229 | - `ttl`: The default time-to-live for records in this domain. Defaults to 10
230 |
231 | ### named.ARecord(ipv4Addr)
232 |
233 | Create an IPv4 resource record
234 | `ipv4Addr` must be a valid IPv4 address (string).
235 |
236 | ### named.AAAARecord(ipv6Addr)
237 |
238 | Create an IPv6 resource record.
239 | `ipv6Addr` must be a valid IPv6 address (string).
240 |
241 | ### named.CNAMERecord(target)
242 |
243 | Create an Alias record. When these records are sent to the client, the client
244 | will often make an additional request for the alias itself.
245 |
246 | ### named.MXRecord(exchange, options)
247 |
248 | Create a Mail Server record. A client making this request will often make an
249 | additional request for the entries in these records.
250 | `exchange` is the name of the mailserver that handles mail for this domain.
251 |
252 | Options:
253 | - `ttl`: The time-to-live for this particular mail server record
254 | - `priority`: The priority of this mailserver over other mailservers. You may
255 | have multiple mail servers. Lowest priority server is selected by client.
256 |
257 | ### named.SRVRecord(target, port, options)
258 |
259 | Create a Server Resource record.
260 | `target` is the name of the server that handles this particular resource name
261 | `port` is the tcp/udp port where the service may be reached
262 |
263 | Options:
264 | - `weight`: Used by the client for selecting between multiple results. Higher
265 | weight wins. Default is 10
266 | - `priority`: Used by the client for selecting between mutiple results. Higher
267 | priortiy wins. Default is 10
268 |
269 | ### named.TXTRecord(target)
270 |
271 | Create a text resource record.
272 | `target` can be any text up to 500 bytes in length
273 |
274 | ## Class: named.Record
275 |
276 | ### record.valid()
277 |
278 | This function will ensure that the data you used to create the record is in fact
279 | valid.
280 |
281 | ## DnsError
282 |
283 | DnsErrors rae objects that consist of:
284 | - `code`: A unique error number
285 | - `name`: the name of the error
286 |
287 | DnsErrors are:
288 |
289 | - `NoError`
290 | - `ProtocolError`
291 | - `CannotProcessError`
292 | - `NoNameError`
293 | - `NotImplementedError`
294 | - `RefusedError`
295 | - `ExceptionError`
296 |
297 | ## Class: named.DnsError
298 |
299 | ### error.message()
300 |
301 | Returns the message that was passed in to the error. The message is a string,
302 | and can be used for logging purposes
303 |
304 | ## Server Properties
305 |
306 |
307 |
--------------------------------------------------------------------------------
/examples/app.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 | var server = named.createServer();
3 |
4 | server.listen(9999, '127.0.0.1', function() {
5 | console.log('DNS server started on port 9999');
6 | });
7 |
8 | console.log(named.SoaRecord);
9 |
10 | server.on('query', function(query) {
11 | var domain = query.name();
12 | var record = new named.SOARecord(domain, {serial: 12345, ttl: 300});
13 | query.addAnswer(domain, record, 300);
14 | server.send(query);
15 | });
16 |
--------------------------------------------------------------------------------
/examples/cluster.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib/index');
2 | var cluster = require('cluster');
3 |
4 | /**
5 | *
6 | *
7 | * Example that uses cluster (http://nodejs.org/api/cluster.html) to
8 | * spin up multiple workers to handle requests.
9 | *
10 | *
11 | *
12 | * You can test it like this: dig @localhost -p 9999 goodtimes.com
13 | *
14 | *
15 | *
16 | * Or using dnsperf:
17 | *
18 | *
19 | *
20 | * $ echo "goodtimes.com A" > /tmp/f
21 | * $ dnsperf -s localhost -p 9999 -d /tmp/f -l 300
22 | *
23 | *
24 | *
25 | * Unfortunately the surprise is that more workers (4) run slower (3711 qps),
26 | * than a single worker (4084 qps).
27 | *
28 | *
29 | * @author Brian Hammond
30 | *
31 | */
32 | var ClusterDns = function() {
33 |
34 | /* lame config */
35 | this.PORT = 9999;
36 | this.LISTEN = '127.0.0.1';
37 | this.SCALING = 1;
38 |
39 | this.master = function() {
40 | var numCPUs = require('os').cpus().length;
41 | var workers = numCPUs * this.SCALING;
42 | workers = 1;
43 |
44 | console.log( 'there are numCPUs:' + numCPUs + ', starting ' + workers + ' workers' );
45 |
46 | for (var i = 0; i < workers ; i++) {
47 | cluster.fork();
48 | }
49 |
50 | cluster.on('exit', function(worker, code, signal) {
51 | console.log('worker ' + worker.process.pid + ' died');
52 | });
53 | }
54 |
55 | this.randumb = function() {
56 | var r = function() { return Math.floor( Math.random() * 252 + 1 ) };
57 | return r() + '.' + r() + '.' + r() + '.' + r();
58 | };
59 |
60 | this.friendo = function() {
61 | var thiz = this;
62 | var server = named.createServer();
63 |
64 | var port = this.PORT;
65 | var listen = this.LISTEN;
66 |
67 | server.listen( port, listen, function() {
68 | console.log( 'DNS worker started on ' + listen + ':' + port + ', pid:' + cluster.worker.process.pid );
69 | });
70 |
71 | server.on('query', function(query) {
72 | var domain = query.name();
73 | var ttl = 0;
74 | query.addAnswer( domain, new named.SOARecord(domain, {serial: 12345}, ttl ) );
75 | query.addAnswer( domain, new named.ARecord( thiz.randumb(), ttl ) );
76 | server.send(query);
77 | });
78 | };
79 |
80 | this.run = function() {
81 | if ( cluster.isMaster ) {
82 | this.master();
83 | } else {
84 | this.friendo();
85 | }
86 | };
87 | };
88 |
89 | new ClusterDns().run();
90 |
--------------------------------------------------------------------------------
/examples/noanswer.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 | var server = named.createServer();
3 |
4 | server.listen(9999, '127.0.0.1', function() {
5 | console.log('DNS server started on port 9999');
6 | });
7 |
8 | server.on('query', function(query) {
9 | var domain = query.name()
10 | var type = query.type();
11 | console.log('DNS Query: (%s) %s', type, domain);
12 | // If we do not add any answers to the query then the
13 | // result will be a 'null-answer' message. This is how
14 | // you send a "404" to a DNS client
15 | server.send(query);
16 | });
17 |
18 | server.on('clientError', function(error) {
19 | console.log("there was a clientError: %s", error);
20 | });
21 |
22 | server.on('uncaughtException', function(error) {
23 | console.log("there was an excepton: %s", error.message());
24 | });
25 |
--------------------------------------------------------------------------------
/examples/reflect.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 | var server = named.createServer();
3 |
4 | server.listen(9999, '127.0.0.1', function() {
5 | console.log('DNS server started on port 9999');
6 | });
7 |
8 | server.on('query', function(query) {
9 | var domain = query.name()
10 | var type = query.type();
11 | console.log('DNS Query: (%s) %s', type, domain);
12 | switch (type) {
13 | case 'A':
14 | var record = new named.ARecord('127.0.0.1');
15 | query.addAnswer(domain, record, 300);
16 | break;
17 | case 'AAAA':
18 | var record = new named.AAAARecord('::1');
19 | query.addAnswer(domain, record, 300);
20 | break;
21 | case 'CNAME':
22 | var record = new named.CNAMERecord('cname.example.com');
23 | query.addAnswer(domain, record, 300);
24 | break;
25 | case 'MX':
26 | var record = new named.MXRecord('smtp.example.com');
27 | query.addAnswer(domain, record, 300);
28 | break;
29 | case 'SOA':
30 | var record = new named.SOARecord('example.com');
31 | query.addAnswer(domain, record, 300);
32 | break;
33 | case 'SRV':
34 | var record = new named.SRVRecord('sip.example.com', 5060);
35 | query.addAnswer(domain, record, 300);
36 | break;
37 | case 'TXT':
38 | var record = new named.TXTRecord('hello world');
39 | query.addAnswer(domain, record, 300);
40 | break;
41 | }
42 | server.send(query);
43 | });
44 |
45 | server.on('clientError', function(error) {
46 | console.log("there was a clientError: %s", error);
47 | });
48 |
49 | server.on('uncaughtException', function(error) {
50 | console.log("there was an excepton: %s", error.message());
51 | });
52 |
--------------------------------------------------------------------------------
/lib/errors.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This excellent error creator concept was borrowed from Mark Cavage
4 | https://github.com/mcavage/node-ldapjs/blob/master/lib/errors/index.js
5 |
6 | */
7 |
8 |
9 | var util = require('util');
10 |
11 | var CODES = {
12 | DNS_NO_ERROR: 0,
13 | DNS_PROTOCOL_ERROR: 1,
14 | DNS_CANNOT_PROCESS: 2,
15 | DNS_NO_NAME: 3,
16 | DNS_NOT_IMPLEMENTED: 4,
17 | DNS_REFUSED: 5,
18 | DNS_EXCEPTION: 6
19 | }
20 |
21 | var ERRORS = [];
22 |
23 | function DnsError(name, code, msg, caller) {
24 | if (Error.captureStackTrace)
25 | Error.captureStackTrace(this, caller || DnsError);
26 |
27 | this.code = code;
28 | this.name = name;
29 |
30 | this.message = function() {
31 | return msg || name;
32 | }
33 | }
34 |
35 | util.inherits(DnsError, Error);
36 |
37 |
38 | module.exports = {};
39 | module.exports.DnsError = DnsError;
40 |
41 | Object.keys(CODES).forEach(function (code) {
42 | module.exports[code] = CODES[code];
43 |
44 | if (CODES[code] === 0)
45 | return;
46 |
47 | var err = '', msg = '';
48 | var pieces = code.split('_').slice(1);
49 | for (var i in pieces) {
50 | var lc = pieces[i].toLowerCase();
51 | var key = lc.charAt(0).toUpperCase() + lc.slice(1);
52 | err += key;
53 | msg += key + (( i + 1 ) < pieces.length ? ' ' : '');
54 | }
55 |
56 | if (!/\w+Error$/.test(err))
57 | err += 'Error';
58 |
59 | module.exports[err] = function(message, caller) {
60 | DnsError.call(this,
61 | err,
62 | CODES[code],
63 | message || msg,
64 | caller || module.exports[err]);
65 |
66 | };
67 | module.exports[err].constructor = module.exports[err];
68 | util.inherits(module.exports[err], DnsError);
69 |
70 | ERRORS[CODES[code]] = {
71 | err: err,
72 | message: msg
73 | }
74 |
75 | });
76 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 |
4 | var bunyan = require('bunyan');
5 |
6 | var Server = require('./server');
7 | var Query = require('./query');
8 | var Protocol = require('./protocol');
9 |
10 |
11 |
12 | ////--- Globals
13 |
14 | var BUNYAN_SERIALIZERS = {
15 | err: bunyan.stdSerializers.err,
16 | query: function serializeQuery(q) {
17 | var out = {
18 | domain: q.name(),
19 | operation: q.operation(),
20 | type: q.type()
21 | };
22 | return (out);
23 | }
24 | };
25 |
26 |
27 |
28 | ///--- Exports
29 | module.exports = {
30 |
31 | createServer: function createServer(options) {
32 | options = options || {};
33 | if (typeof (options) !== 'object')
34 | throw new TypeError('options (object) required');
35 |
36 |
37 | var opts = {
38 | name: options.name || 'named',
39 | log: options.log || bunyan.createLogger({
40 | name: 'named',
41 | level: 'warn',
42 | serializers: BUNYAN_SERIALIZERS
43 | })
44 | };
45 | return (new Server(opts));
46 | },
47 |
48 | Query: Query,
49 |
50 | Protocol: Protocol,
51 |
52 | bunyan: { serializers: BUNYAN_SERIALIZERS }
53 |
54 | };
55 |
56 | // Export all the record types at the top-level
57 | var subdir = path.join(__dirname, 'records');
58 | fs.readdirSync(subdir).forEach(function (f) {
59 | var name = path.basename(f);
60 | if (/\w+\.js/.test(name)) {
61 | var k = name.split('.').shift().toUpperCase() + 'Record';
62 | module.exports[k] = require(path.join(subdir, f));
63 | }
64 | });
65 | // [
66 | // 'A',
67 | // 'MX',
68 | // 'SOA',
69 | // 'SRV',
70 | // 'TXT',
71 | // 'AAAA',
72 | // 'CNAME'
73 | // ].forEach(function (r) {
74 | // var lcr = r.toLowerCase();
75 | // var k = lcr.charAt(0).toUpperCase() + lcr.slice(1) + 'Record';
76 | // module.exports[k] = require(path.join(__dirname, 'records/' + lcr))[r];
77 | // });
78 |
--------------------------------------------------------------------------------
/lib/protocol.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | # Protocol
4 |
5 | Stores protocol definitions and their primitives as well as any other
6 | associated protocol constants
7 |
8 | ## References
9 |
10 | http://tools.ietf.org/html/rfc1035
11 | http://tools.ietf.org/html/rfc4408
12 | http://tools.ietf.org/html/rfc2782
13 | http://tools.ietf.org/html/rfc3596
14 |
15 | ## Notes
16 |
17 | * Even though RFC1035 says that questions should support multiple queries, the
18 | reality is *nobody* does this. MS DNS doesn't support it and apparently BIND
19 | doesn't support it as well. That implies no client side tools do either - so
20 | we will not worry about that complication.
21 |
22 | * DNS Extensions have been proposed, but another case of chicken-and-egg.
23 | These extensions make it _possible_ to have DNS queries over 512 bytes in
24 | length, but because it is not universally supported, nobody does it.
25 |
26 | */
27 |
28 |
29 | var ipaddr = require('ipaddr.js');
30 |
31 | QCLASS_IN = 0x01, // the internet
32 | QCLASS_CS = 0x02, // obsolete
33 | QCLASS_CH = 0x03, // chaos class. yes this actually exists
34 | QCLASS_HS = 0x04, // Hesiod
35 | DNS_ENOERR = 0x00, // No error
36 | DNS_EFORMAT = 0x01, // Formatting Error
37 | DNS_ESERVER = 0x02, // server it unable to process
38 | DNS_ENONAME = 0x03, // name does not exist
39 | DNS_ENOTIMP = 0x04, // feature not implemented on this server
40 | DNS_EREFUSE = 0x05, // refused for policy reasons
41 |
42 | Formats = {};
43 |
44 | Formats.answer = {
45 | name: { type: '_nsName' },
46 | rtype: { type: 'UInt16BE' },
47 | rclass: { type: 'UInt16BE' },
48 | rttl: { type: 'UInt32BE' },
49 | rdata: { type: '_nsData' }, // rdlength is prepended to this field
50 | };
51 |
52 | Formats.question = {
53 | name: { type: '_nsName' },
54 | type: { type: 'UInt16BE' },
55 | qclass: { type: 'UInt16BE' }
56 | };
57 |
58 | Formats.header = {
59 | id: { type: 'UInt16BE' },
60 | flags: { type: '_nsFlags' },
61 | qdCount: { type: 'UInt16BE' },
62 | anCount: { type: 'UInt16BE' },
63 | nsCount: { type: 'UInt16BE' },
64 | srCount: { type: 'UInt16BE' },
65 | };
66 |
67 | Formats.soa = {
68 | host: { type: '_nsName' },
69 | admin: { type: '_nsName' },
70 | serial: { type: 'UInt32BE' },
71 | refresh: { type: 'UInt32BE' },
72 | retry: { type: 'UInt32BE' },
73 | expire: { type: 'UInt32BE' },
74 | ttl: { type: 'UInt32BE' }
75 | };
76 |
77 | Formats.mx = {
78 | priority: { type: 'UInt16BE' },
79 | exchange: { type: '_nsName' }
80 | };
81 |
82 | Formats.txt = {
83 | text: { type: '_nsText' }
84 | };
85 |
86 | Formats.srv = {
87 | priority: { type: 'UInt16BE' },
88 | weight: { type: 'UInt16BE' },
89 | port: { type: 'UInt16BE' },
90 | target: { type: '_nsName' }
91 | };
92 |
93 | Formats.queryMessage = {
94 | header: { type: { format: 'header' } },
95 | question: { type: { format: 'question' } }
96 | };
97 |
98 | Formats.answerMessage = {
99 | header: { type: { format: 'header' } },
100 | question: { type: { format: 'question' } },
101 | answers: { type: '_nsAnswers' }
102 | };
103 |
104 |
105 | // turns a dotted-decimal address into a UInt32
106 | function parseIPv4(addr) {
107 | if (typeof(addr) !== 'string')
108 | throw new TypeError('addr (string) is required');
109 |
110 | var octets = addr.split(/\./).map(function (octet) {
111 | return (parseInt(octet, 10));
112 | });
113 | if (octets.length !== 4)
114 | throw new TypeError('valid IP address required');
115 |
116 | var uint32 = ((octets[0] * Math.pow(256, 3)) +
117 | (octets[1] * Math.pow(256, 2)) +
118 | (octets[2] * 256) + octets[3]);
119 | return (uint32);
120 | }
121 |
122 |
123 | function parseIPv6(addr) {
124 | if (typeof(addr) !== 'string')
125 | throw new TypeError('addr (string) is required');
126 |
127 | var addr;
128 | try {
129 | addr = ipaddr.parse(addr);
130 | } catch (e) {
131 | return false;
132 | }
133 |
134 | return addr.parts;
135 |
136 | }
137 |
138 |
139 | // each of these serializers are functions which accept a value to serialize
140 | // and must returns the serialized value as a buffer
141 | var serializers = {
142 | 'UInt32BE': {
143 | encoder: function(v) {
144 | var b = new Buffer(4);
145 | b.writeUInt32BE(v, 0);
146 | return b;
147 | },
148 | decoder: function(v, p) {
149 | return v.readUInt32BE(v, p);
150 | }
151 | },
152 | 'UInt16BE': {
153 | encoder: function(v) {
154 | var b = new Buffer(2);
155 | b.writeUInt16BE(v, 0);
156 | return b;
157 | },
158 | decoder: function(v, p) {
159 | var res = v.readUInt16BE(p);
160 | return { val: res, len: 2 };
161 | }
162 | },
163 | '_nsAnswers': {
164 | encoder: function(v) {
165 | var s = 0, p = 0, answers = [];
166 | for (i in v) {
167 | var r = encode(v[i], 'answer');
168 | answers.push(r);
169 | s = s + r.length;
170 | }
171 | b = new Buffer(s);
172 | for (n in answers) {
173 | answers[n].copy(b, p);
174 | p = p + answers[n].length;
175 | }
176 | return b;
177 | }
178 | },
179 | '_nsFlags': {
180 | encoder: function(v) {
181 | if (typeof(v) !== 'object') {
182 | throw new TypeError("flag must be an object");
183 | }
184 | var b = new Buffer(2);
185 | var f = 0x0000;
186 | f = f | (v.qr << 15);
187 | f = f | (v.opcode << 11);
188 | f = f | (v.aa << 10);
189 | f = f | (v.tc << 9);
190 | f = f | (v.rd << 8);
191 | f = f | (v.ra << 7);
192 | f = f | (v.z << 6);
193 | f = f | (v.ad << 5);
194 | f = f | (v.cd << 4);
195 | f = f | v.rcode;
196 | b.writeUInt16BE(f, 0);
197 | return b;
198 | },
199 | decoder: function(v, p) {
200 | var flags, f;
201 | flags = v.readUInt16BE(p);
202 | f = {
203 | qr: (( flags & 0x8000 )) ? true : false,
204 | opcode: (( flags & 0x7800 )),
205 | aa: (( flags & 0x0400 )) ? true : false,
206 | tc: (( flags & 0x0200 )) ? true : false,
207 | rd: (( flags & 0x0100 )) ? true : false,
208 | ra: (( flags & 0x0080 )) ? true : false,
209 | z: (( flags & 0x0040 )) ? true : false,
210 | ad: (( flags & 0x0020 )) ? true : false,
211 | cd: (( flags & 0x0010 )) ? true : false,
212 | rcode: (( flags & 0x000F ))
213 | };
214 | return { val: f, len: 2 };
215 | }
216 | },
217 | '_nsIP4': {
218 | encoder: function(v) {
219 | var a, b;
220 | a = parseIPv4(v);
221 | b = new Buffer(4);
222 | b.writeUInt32BE(a, 0);
223 | return b;
224 | }
225 | },
226 | '_nsIP6': {
227 | encoder: function(v) {
228 | var a, b, i = 0;
229 | a = parseIPv6(v);
230 | b = new Buffer(16);
231 | for (var i=0; i<8; i++) {
232 | b.writeUInt16BE(a[i], i * 2);
233 | }
234 | return b;
235 | }
236 | },
237 | '_nsName': {
238 | encoder: function(v) {
239 | if (typeof(v) !== 'string')
240 | throw new TypeError('name (string) is required')
241 | var n = v.split(/\./);
242 |
243 | var b = new Buffer(n.toString().length + 2);
244 | var o = 0; //offset
245 |
246 | for (var i = 0; i < n.length; i++) {
247 | var l = n[i].length;
248 | b[o] = l;
249 | b.write(n[i], ++o, l, 'utf8');
250 | o += l;
251 | }
252 | b[o] = 0x00;
253 |
254 | return b;
255 | },
256 | decoder: function(v, p) {
257 | var rle, start = p, name = [];
258 |
259 | rlen = v.readUInt8(p);
260 | while (rlen != 0x00) {
261 | p++;
262 | var t = v.slice(p, p + rlen);
263 | name.push(t.toString());
264 | p = p + rlen;
265 | rlen = v.readUInt8(p);
266 | }
267 |
268 | return { val: name.join('.'), len: (p - start + 1) };
269 | }
270 | },
271 | '_nsText': {
272 | encoder: function(v) {
273 | var b;
274 | b = new Buffer(v.length + 1);
275 | b.writeUInt8(v.length, 0);
276 | b.write(v, 1);
277 | return b;
278 | }
279 | },
280 | '_nsData': {
281 | encoder: function(v, t) {
282 | var r, b, l;
283 | // TODO with the new queryTypes layout this could probably be mostly
284 | // eliminated
285 |
286 | switch(t) {
287 | case queryTypes['A']:
288 | r = serializers['_nsIP4'].encoder(v.target);
289 | break;
290 | case queryTypes['CNAME']:
291 | r = serializers['_nsName'].encoder(v.target);
292 | break;
293 | case queryTypes['NS']:
294 | r = serializers['_nsName'].encoder(v.target);
295 | break;
296 | case queryTypes['SOA']:
297 | r = encode(v, 'soa');
298 | break;
299 | case queryTypes['MX']:
300 | r = encode(v, 'mx');
301 | break;
302 | case queryTypes['TXT']:
303 | r = serializers['_nsText'].encoder(v.target);
304 | break;
305 | case queryTypes['AAAA']:
306 | r = serializers['_nsIP6'].encoder(v.target);
307 | break;
308 | case queryTypes['SRV']:
309 | r = encode(v, 'srv');
310 | break;
311 | default:
312 | throw new Error('unrecognized nsdata type');
313 | break;
314 | }
315 |
316 | l = r.length;
317 | b = new Buffer(l + 2);
318 | b.writeUInt16BE(l, 0);
319 | r.copy(b, 2);
320 | return b;
321 | }
322 | },
323 | };
324 |
325 | function encode(obj, format) {
326 | var size = 0, pos = 0, fmt, field, type, result, encoder, results = [];
327 |
328 | fmt = Formats[format];
329 |
330 | for (f in fmt) {
331 | var type, decoder, res;
332 | type = fmt[f].type;
333 |
334 | if (typeof(type) === 'string') {
335 | //XXX I dont like this
336 | if (type == '_nsData') {
337 | res = serializers['_nsData'].encoder(obj[f], obj['rtype']);
338 | }
339 | else {
340 | res = serializers[type].encoder(obj[f]);
341 | }
342 | }
343 | else if (typeof(type) === 'object') {
344 | reftype = type.format;
345 | res = encode(obj[f], reftype);
346 | }
347 | else {
348 | throw new TypeError('invalid type');
349 | }
350 |
351 | results.push(res);
352 | size = size + res.length;
353 |
354 | }
355 |
356 | result = new Buffer(size);
357 |
358 | for (i in results) {
359 | var buf = results[i];
360 | buf.copy(result, pos);
361 | pos = pos + buf.length;
362 | }
363 |
364 | return result;
365 | }
366 |
367 | function decode(raw, format, pos) {
368 | var size = 0, fmt, field, type, decoder, result = {}
369 |
370 | if (!pos) pos = 0;
371 | fmt = Formats[format];
372 |
373 | for (var f in fmt) {
374 | var type, decoder, res;
375 | type = fmt[f].type;
376 |
377 | // if the type is a string its a reference to a serializer
378 | // if the type is an object its a nested format and we call decode again
379 | // with the appropriate offset
380 |
381 | if (typeof(type) === 'string') {
382 | res = serializers[type].decoder(raw, pos);
383 | }
384 | else if (typeof(type) === 'object') {
385 | reftype = type.format;
386 | res = decode(raw, reftype, pos);
387 | }
388 | else {
389 | throw new TypeError('invalid type');
390 | }
391 |
392 | pos += res.len;
393 | result[f] = res.val;
394 |
395 | }
396 |
397 | return {val: result, len: pos};
398 | }
399 |
400 |
401 | var queryTypes = {
402 | A : 0x01, // ipv4 address
403 | NS : 0x02, // nameserver
404 | MD : 0x03, // obsolete
405 | MF : 0x04, // obsolete
406 | CNAME : 0x05, // alias
407 | SOA : 0x06, // start of authority
408 | MB : 0x07, // experimental
409 | MG : 0x08, // experimental
410 | MR : 0x09, // experimental
411 | NULL : 0x0A, // experimental null RR
412 | WKS : 0x0B, // service description
413 | PTR : 0x0C, // reverse entry (inaddr.arpa)
414 | HINFO : 0x0D, // host information
415 | MINFO : 0x0E, // mailbox or mail list information
416 | MX : 0x0F, // mail exchange
417 | TXT : 0x10, // text strings
418 | AAAA : 0x1C, // ipv6 address
419 | SRV : 0x21, // srv records
420 | AXFR : 0xFC, // request to transfer entire zone
421 | MAILA : 0xFE, // request for mailbox related records
422 | MAILB : 0xFD, // request for mail agent RRs
423 | ANY : 0xFF, // any class
424 | 0x01 : 'A' , // ipv4 address
425 | 0x02 : 'NS', // nameserver
426 | 0x03 : 'MD', // obsolete
427 | 0x04 : 'MF', // obsolete
428 | 0x05 : 'CNAME',// alias
429 | 0x06 : 'SOA', // start of authority
430 | 0x07 : 'MB', // experimental
431 | 0x08 : 'MG', // experimental
432 | 0x09 : 'MR', // experimental
433 | 0x0A : 'NULL', // experimental null RR
434 | 0x0B : 'WKS', // service description
435 | 0x0C : 'PTR', // reverse entry (inaddr.arpa)
436 | 0x0D : 'HINFO',// host information
437 | 0x0E : 'MINFO',// mailbox or mail list information
438 | 0x0F : 'MX', // mail exchange
439 | 0x10 : 'TXT', // text strings
440 | 0x1C : 'AAAA', // ipv6 address
441 | 0x21 : 'SRV', // srv records
442 | 0xFC : 'AXFR', // request to transfer entire zone
443 | 0xFE : 'MAILA',// request for mailbox related records
444 | 0xFD : 'MAILB',// request for mail agent RRs
445 | 0xFF : 'ANY', // any class
446 | }
447 |
448 | module.exports = {
449 | DNS_ENOERR : 0x00, // No error
450 | DNS_EFORMAT : 0x01, // Formatting Error
451 | DNS_ESERVER : 0x02, // server it unable to process
452 | DNS_ENONAME : 0x03, // name does not exist
453 | DNS_ENOTIMP : 0x04, // feature not implemented on this server
454 | DNS_EREFUSE : 0x05, // refused for policy reasons
455 | encode: encode,
456 | decode: decode,
457 | queryTypes: queryTypes
458 | }
459 |
--------------------------------------------------------------------------------
/lib/query.js:
--------------------------------------------------------------------------------
1 | var protocol = require('./protocol');
2 | var ipaddr = require('ipaddr.js');
3 | var protocol = require('./protocol');
4 | var queryTypes = protocol.queryTypes
5 |
6 |
7 | function Query(arg) {
8 | if (typeof(arg) !== 'object')
9 | throw new TypeError('arg (object) is missing');
10 |
11 | var self = this;
12 |
13 | this.id = arg.id;
14 | this._truncated = false;
15 | this._authoritative = false; // set on response
16 | this._recursionAvailable = false; // set on response
17 | this._responseCode = 0;
18 | this._qdCount = arg.qdCount;
19 | this._anCount = arg.anCount || 0;
20 | this._nsCount = arg.nsCount || 0;
21 | this._srCount = arg.srCount || 0;
22 | this._flags = arg.flags;
23 | this._question = arg.question;
24 | this._answers = [];
25 |
26 | this._raw = null;
27 | this._client = null;
28 | }
29 |
30 |
31 | Query.prototype.answers = function answers() {
32 | return this._answers.map(function(r) {
33 | return {
34 | name: r.name,
35 | type: queryTypes[r.rtype],
36 | record: r.rdata,
37 | ttl: r.rttl
38 | };
39 | });
40 | }
41 |
42 | Query.prototype.name = function name() {
43 | return this._question.name;
44 | }
45 |
46 | Query.prototype.type = function type() {
47 | return queryTypes[this._question.type];
48 | }
49 |
50 | Query.prototype.operation = function operation() {
51 | switch (this._flags.opcode) {
52 | case 0:
53 | return 'query';
54 | case 2:
55 | return 'status';
56 | case 4:
57 | return 'notify';
58 | case 5:
59 | return 'update';
60 | default:
61 | throw new Error('invalid operation %d', this._flags.opcode);
62 | }
63 | };
64 |
65 | Query.prototype.encode = function encode() {
66 | var header, question, answer, rSize, rBuffer;
67 |
68 | // TODO get rid of this intermediate format (or justify it)
69 | var toPack = {
70 | header: {
71 | id: this.id,
72 | flags: this._flags,
73 | qdCount: this._qdCount,
74 | anCount: this._anCount,
75 | nsCount: this._nsCount,
76 | srCount: this._srCount
77 | },
78 | question: this._question,
79 | answers: this._answers,
80 | authority: this._authority,
81 | additional: this._additional
82 | }
83 |
84 | var encoded = protocol.encode(toPack, 'answerMessage');
85 |
86 | this._raw = {
87 | buf: encoded,
88 | len: encoded.length
89 | };
90 | };
91 |
92 | Query.prototype.addAnswer = function(name, record, ttl) {
93 | if (typeof (name) !== 'string')
94 | throw new TypeError('name (string) required');
95 | if (typeof (record) !== 'object')
96 | throw new TypeError('record (Record) required');
97 | if (ttl !== undefined && typeof (ttl) !== 'number')
98 | throw new TypeError('ttl (number) required');
99 |
100 | if (!queryTypes.hasOwnProperty(record._type))
101 | throw new Error('unknown queryType: ' + record._type);
102 |
103 | var answer = {
104 | name: name,
105 | rtype: queryTypes[record._type],
106 | rclass: 1, // INET
107 | rttl: ttl || 5,
108 | rdata: record
109 | };
110 |
111 | // Note:
112 | //
113 | // You can only have multiple answers in certain circumstances in no
114 | // circumstance can you mix different answer types other than 'A' with
115 | // 'AAAA' unless they are in the 'additional' section.
116 | //
117 | // There are also restrictions on what you can answer with depending on
118 | // the question.
119 | //
120 | // We will not attempt to enforce that here at the moment.
121 | //
122 |
123 | this._answers.push(answer);
124 | this._anCount++;
125 | };
126 |
127 | function parseQuery(raw, src) {
128 | var dobj, b = raw.buf;
129 |
130 | dobj = protocol.decode(b, 'queryMessage');
131 |
132 | if (!dobj.val)
133 | return null;
134 |
135 | // TODO get rid of this intermediate format (or justify it)
136 | var d = dobj.val;
137 | var res = {
138 | id: d.header.id,
139 | flags: d.header.flags,
140 | qdCount: d.header.qdCount,
141 | anCount: d.header.anCount,
142 | nsCount: d.header.nsCount,
143 | srCount: d.header.srCount,
144 | question: d.question, //XXX
145 | src: src,
146 | raw: raw
147 | }
148 |
149 | return (res);
150 | }
151 |
152 | function createQuery(req) {
153 | var query = new Query(req);
154 | query._raw = req.raw;
155 | query._client = req.src;
156 | return (query);
157 | }
158 |
159 | module.exports = {
160 | createQuery: createQuery,
161 | parse: parseQuery,
162 | }
163 |
--------------------------------------------------------------------------------
/lib/records/a.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 | function A(target) {
4 | if (typeof (target) !== 'string')
5 | throw new TypeError('IPv4Addr (string) is required');
6 |
7 | this.target = target;
8 | this._type = 'A';
9 | }
10 | module.exports = A;
11 |
12 |
13 | A.prototype.valid = function valid() {
14 | var self = this, model = {};
15 | model = {
16 | target: validators.IPv4
17 | };
18 | return validators.validate(self, model);
19 | }
20 |
--------------------------------------------------------------------------------
/lib/records/aaaa.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 | function AAAA(target) {
4 | if (typeof (target) !== 'string')
5 | throw new TypeError('IPv6Addr (string) is required');
6 |
7 | this.target = target;
8 | this._type = 'AAAA';
9 | }
10 | module.exports = AAAA;
11 |
12 | AAAA.prototype.valid = function valid() {
13 | var self = this, model = {};
14 | model = {
15 | target: validators.IPv6
16 | };
17 | return validators.validate(self, model);
18 | };
19 |
--------------------------------------------------------------------------------
/lib/records/cname.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 |
4 |
5 | function CNAME(target) {
6 | if (typeof(target) !== 'string')
7 | throw new TypeError('target (string) is required');
8 |
9 | this.target = target;
10 | this._type = 'CNAME';
11 | }
12 | module.exports = CNAME;
13 |
14 |
15 | CNAME.prototype.valid = function valid() {
16 | var self = this, model = {};
17 | model = {
18 | target: validators.nsName
19 | };
20 | return validators.validate(self, model);
21 | };
22 |
--------------------------------------------------------------------------------
/lib/records/mx.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 |
4 | function MX(exchange, opts) {
5 | if (typeof (exchange) !== 'string')
6 | throw new TypeError('exchange (string) is required');
7 |
8 | if (!opts)
9 | opts = {};
10 |
11 | var defaults = {
12 | priority: 0,
13 | ttl: 600,
14 | };
15 |
16 | for (key in defaults) {
17 | if (key in opts) continue;
18 | opts[key] = defaults[key];
19 | }
20 |
21 | this.exchange = exchange;
22 | this.ttl = opts.ttl;
23 | this.priority = opts.priority;
24 | this._type = 'MX';
25 | }
26 | module.exports = MX;
27 |
28 |
29 | MX.prototype.valid = function valid() {
30 | var self = this, model = {};
31 | model = {
32 | exchange: validators.nsName,
33 | ttl: validators.UInt32BE,
34 | priority: validators.UInt16BE
35 | };
36 | return validators.validate(self, model);
37 | };
38 |
--------------------------------------------------------------------------------
/lib/records/ns.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 |
4 |
5 | function NS(target) {
6 | if (typeof(target) !== 'string')
7 | throw new TypeError('target (string) is required');
8 |
9 | this.target = target;
10 | this._type = 'NS';
11 | }
12 | module.exports = NS;
13 |
14 |
15 | NS.prototype.valid = function valid() {
16 | var self = this, model = {};
17 | model = {
18 | target: validators.nsName
19 | };
20 | return validators.validate(self, model);
21 | };
22 |
--------------------------------------------------------------------------------
/lib/records/soa.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 | function SOA(host, opts) {
4 | if (typeof(host) !== 'string')
5 | throw new TypeError('host (string) is required');
6 |
7 | if (!opts)
8 | opts = {};
9 |
10 | var defaults = {
11 | admin: 'hostmaster.' + host,
12 | serial: 0,
13 | refresh: 10,
14 | retry: 10,
15 | expire: 10,
16 | ttl: 10
17 | };
18 |
19 | for (key in defaults) {
20 | if (key in opts) continue;
21 | opts[key] = defaults[key];
22 | }
23 |
24 | this.host = host;
25 | this.admin = opts.admin;
26 | this.serial = opts.serial;
27 | this.refresh = opts.refresh;
28 | this.retry = opts.retry;
29 | this.expire = opts.expire;
30 | this.ttl = opts.ttl;
31 | this._type = 'SOA';
32 | }
33 | module.exports = SOA;
34 |
35 |
36 | SOA.prototype.valid = function SOA() {
37 | var self = this, model = {};
38 |
39 | model = {
40 | host: validators.nsName,
41 | admin: validators.nsName,
42 | serial: validators.UInt32BE,
43 | refresh: validators.UInt32BE,
44 | retry: validators.UInt32BE,
45 | expire: validators.UInt32BE,
46 | ttl: validators.UInt32BE
47 | };
48 |
49 | return validators.validate(self, model);
50 | };
51 |
--------------------------------------------------------------------------------
/lib/records/srv.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 | function SRV(target, port, opts) {
4 | if (typeof(target) !== 'string')
5 | throw new TypeError('host (string) is required');
6 | if (!port)
7 | throw new TypeError('port (integer) is required'); //XXX
8 |
9 | if (!opts)
10 | opts = {};
11 |
12 | var defaults = {
13 | priority: 0,
14 | weight: 10,
15 | };
16 |
17 | for (key in defaults) {
18 | if (key in opts) continue;
19 | opts[key] = defaults[key];
20 | }
21 |
22 | this.target = target;
23 | this.port = port;
24 | this.weight = opts.weight;
25 | this.priority = opts.priority;
26 | this._type = 'SRV';
27 | }
28 | module.exports = SRV;
29 |
30 |
31 | SRV.prototype.valid = function SRV() {
32 | var self = this, model = {};
33 | model = {
34 | target: validators.nsText, // XXX
35 | port: validators.UInt16BE,
36 | weight: validators.UInt16BE,
37 | priority: validators.UInt16BE
38 | };
39 | return validators.validate(self, model);
40 | };
41 |
--------------------------------------------------------------------------------
/lib/records/txt.js:
--------------------------------------------------------------------------------
1 | var validators = require('../validators');
2 |
3 | function TXT(target) {
4 | if (typeof(target) !== 'string')
5 | throw new TypeError('target (string) is required');
6 |
7 | this.target = target;
8 | this._type = 'TXT';
9 | }
10 | module.exports = TXT;
11 |
12 |
13 | TXT.prototype.valid = function TXT() {
14 | var self = this, model = {};
15 | model = {
16 | target: validators.nsText
17 | };
18 | return validators.validate(self, model);
19 | };
20 |
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var dgram = require('dgram');
3 | var EventEmitter = require('events').EventEmitter;
4 | var util = require('util');
5 |
6 | var Query = require('./query');
7 | var DnsError = require('./errors');
8 |
9 |
10 |
11 | ///--- Globals
12 |
13 | var sprintf = util.format;
14 |
15 | var ExceptionError = DnsError.ExceptionError;
16 | var ProtocolError = DnsError.ProtocolError;
17 |
18 |
19 |
20 | ///--- API
21 |
22 | function Server(options) {
23 | if (typeof(options) !== 'object')
24 | throw new TypeError('options (object) is required');
25 |
26 | this._log = options.log.child({component: 'agent'}, true);
27 | this._name = options.name || "named";
28 | this._socket = null;
29 |
30 | }
31 | util.inherits(Server, EventEmitter);
32 |
33 |
34 | Server.prototype.close = function close(callback) {
35 | if (typeof (callback) === 'function')
36 | this._socket.once('close', callback);
37 |
38 | this._socket.close();
39 | };
40 |
41 |
42 | Server.prototype.listen = function listen(port, address, callback) {
43 | if (!port)
44 | throw new TypeError('port (number) is required');
45 |
46 | if (typeof (address) === 'function' || !address) {
47 | callback = address;
48 | address = '0.0.0.0';
49 | }
50 |
51 | var self = this;
52 |
53 | this._socket = dgram.createSocket('udp6');
54 | this._socket.once('listening', function () {
55 | self.emit('listening');
56 | if (typeof (callback) === 'function')
57 | process.nextTick(callback);
58 | });
59 | this._socket.on('close', function onSocketClose() {
60 | self.emit('close');
61 | });
62 | this._socket.on('error', function onSocketError(err) {
63 | self.emit('error', err);
64 | });
65 | this._socket.on('message', function (buffer, rinfo) {
66 | var decoded;
67 | var query;
68 | var raw = {
69 | buf: buffer,
70 | len: rinfo.size
71 | };
72 |
73 | var src = {
74 | family: 'udp6',
75 | address: rinfo.address,
76 | port: rinfo.port
77 | };
78 |
79 | try {
80 | decoded = Query.parse(raw, src);
81 | query = Query.createQuery(decoded);
82 | } catch (e) {
83 | self.emit('clientError',
84 | new ProtocolError('invalid DNS datagram'));
85 | }
86 |
87 | if (query === undefined || query === null) {
88 | return;
89 | }
90 |
91 | query.respond = function respond() {
92 | self.send(query);
93 | };
94 |
95 | try {
96 | self.emit('query', query);
97 | } catch (e) {
98 | self._log.warn({
99 | err: e
100 | }, 'query handler threw an uncaughtException');
101 | self.emit('uncaughtException', e);
102 | }
103 | });
104 | this._socket.bind(port, address);
105 | };
106 |
107 |
108 | Server.prototype.send = function send(res) {
109 | assert.ok(res);
110 |
111 | try {
112 | res._flags.qr = 1; // replace with function
113 | res.encode();
114 | } catch (e) {
115 | this._log.trace({err: e}, 'send: uncaughtException');
116 | var err = new ExceptionError('unable to encode response');
117 | this.emit('uncaughtException', err);
118 | return false;
119 | }
120 |
121 | var addr = res._client.address;
122 | var buf = res._raw.buf;
123 | var len = res._raw.len;
124 | var port = res._client.port;
125 | var self = this;
126 |
127 | this._log.trace({
128 | adddress: addr,
129 | port: port,
130 | len: len
131 | }, 'send: writing DNS message to socket');
132 |
133 | this._socket.send(buf, 0, len, port, addr, function (err, bytes) {
134 | if (err) {
135 | self._log.warn({
136 | adddress: addr,
137 | port: port,
138 | err: err
139 | }, 'send: unable to send response');
140 | self.emit('error', new ExceptionError(err.message));
141 | } else {
142 | self._log.trace({
143 | adddress: addr,
144 | port: port
145 | }, 'send: DNS response sent');
146 | self.emit('after', res, bytes);
147 | }
148 | });
149 | };
150 |
151 |
152 | Server.prototype.toString = function toString() {
153 | var str = '[object named.Server <';
154 | str += sprintf('name=%s, ', this._name);
155 | str += sprintf('socket=%j', this._socket ? this._socket.address() : {});
156 | str += '>]';
157 | return (str);
158 | };
159 |
160 |
161 |
162 | ///--- Exports
163 |
164 | module.exports = Server;
165 |
--------------------------------------------------------------------------------
/lib/validators.js:
--------------------------------------------------------------------------------
1 | var ipaddr = require('ipaddr.js');
2 | var net = require('net');
3 |
4 | module.exports = {
5 | nsName: function(v) {
6 | // hostname regex per RFC1123
7 | var reg =/^([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])(\.([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9]))*$/i;
8 | if (typeof(v) !== 'string')
9 | return false;
10 | if (v.length > 255)
11 | return false;
12 |
13 | if (reg.test(v)) {
14 | return true;
15 | }
16 | else {
17 | return false;
18 | }
19 | },
20 | UInt32BE: function(v) {
21 | if (typeof(v) === 'number') {
22 | var n = parseInt(v);
23 | if (n !== NaN && n < 4294967295) {
24 | return true;
25 | }
26 | else {
27 | return false;
28 | }
29 | }
30 | else {
31 | return false;
32 | }
33 | },
34 | UInt16BE: function(v) {
35 | if (typeof(v) === 'number') {
36 | var n = parseInt(v);
37 | if (n !== NaN && n < 65535) {
38 | return true;
39 | }
40 | else {
41 | return false;
42 | }
43 | }
44 | else {
45 | return false;
46 | }
47 | },
48 | nsText: function(v) {
49 | if (typeof(v) === 'string') {
50 | if (v.length < 256)
51 | return true;
52 | }
53 | else {
54 | return false;
55 | }
56 | },
57 | IPv4: function(v) {
58 | return net.isIPv4(v);
59 | },
60 | IPv6: function(v) {
61 | return net.isIPv6(v);
62 | },
63 | validate: function(obj, model) {
64 | var result = true;
65 | for (v in model) {
66 | valid = model[v](obj[v]);
67 | if (!valid) {
68 | valid = false;
69 | break;
70 | }
71 | }
72 | return result;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "named",
3 | "description": "DNS server library for node.js",
4 | "version": "0.0.1",
5 | "author": "trevoro ",
6 | "contributors": [
7 | "Mark Cavage"
8 | ],
9 | "repository": {
10 | "type": "git",
11 | "url": "git://github.com/trevoro/node-named.git"
12 | },
13 | "main": "lib/index.js",
14 | "engines": {
15 | "node": ">=0.6"
16 | },
17 | "dependencies": {
18 | "bunyan": "0.7.0",
19 | "ipaddr.js": "0.1.1"
20 | },
21 | "devDependencies": {
22 | "nodeunit": "0.9.0"
23 | },
24 | "license": "MIT"
25 | }
26 |
--------------------------------------------------------------------------------
/test/dig.js:
--------------------------------------------------------------------------------
1 | // Quick and dirty 'dig' wrapper
2 |
3 | var assert = require('assert');
4 | var exec = require('child_process').exec;
5 | var sprintf = require('util').format;
6 |
7 |
8 |
9 | ///--- Globals
10 |
11 | var DIG = 'dig';
12 |
13 |
14 |
15 | ///--- Helpers
16 |
17 | function parseAnswer(tokens) {
18 | var t = tokens.filter(function (v) {
19 | return (v !== '' ? v : undefined);
20 | });
21 |
22 | var r = {
23 | name: t[0],
24 | ttl: parseInt(t[1], 10),
25 | type: t[3],
26 | target: t[4]
27 | }
28 |
29 | return (r);
30 | }
31 |
32 |
33 | function parseDig(output) {
34 | var lines = output.split(/\n/);
35 | var section = 'header';
36 |
37 | var results = {
38 | question: null,
39 | answers: [],
40 | additional: [],
41 | authority: []
42 | };
43 |
44 | lines.forEach(function (l) {
45 | if (l === '') {
46 | section = undefined;
47 | } else if (/^;; QUESTION SECTION:/.test(l)) {
48 | section = 'question';
49 | } else if (/^;; ANSWER SECTION:/.test(l)) {
50 | section = 'answer';
51 | } else if (/^;; ADDITIONAL SECTION:/.test(l)) {
52 | section = 'additional';
53 | } else if (/^;; AUTHORITY SECTION:/.test(l)) {
54 | section = 'authority';
55 | }
56 |
57 | if (section === 'question') {
58 | if (/^;([A-Za-z0-9])*\./.test(l)) {
59 | results.question =
60 | l.match(/([A-Za-z0-9_\-\.])+/)[0];
61 | }
62 | }
63 |
64 | if (section === 'answer') {
65 | if (/^([_A-Za-z0-9])+/.test(l)) {
66 | var tokens = l.match(/(.*)/)[0].split(/\t/);
67 | var answer = parseAnswer(tokens);
68 | if (answer)
69 | results.answers.push(answer);
70 | }
71 | }
72 | });
73 |
74 | return (results);
75 | }
76 |
77 |
78 |
79 | ///--- API
80 |
81 | function dig(name, type, options, callback) {
82 | if (typeof (name) !== 'string')
83 | throw new TypeError('name (string) is required');
84 | if (typeof (type) !== 'string')
85 | throw new TypeError('type (string) is required');
86 | if (typeof (options) === 'function') {
87 | callback = options;
88 | options = {};
89 | }
90 |
91 | type = type.toUpperCase();
92 |
93 | var opts = ''
94 | if (options.server)
95 | opts += ' @' + options.server;
96 | if (options.port)
97 | opts += ' -p ' + options.port;
98 |
99 | var cmd = sprintf('dig %s -t %s %s +time=1 +retry=0', opts, type, name);
100 | exec(cmd, function (err, stdout, stderr) {
101 | if (err)
102 | return (callback(err));
103 |
104 |
105 | return (callback(null, parseDig(stdout)));
106 | });
107 | }
108 |
109 |
110 |
111 | ///--- Exports
112 |
113 | module.exports = dig;
114 |
--------------------------------------------------------------------------------
/test/dnsbuffer.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This file contains a bunch of sample DNS queries generated against namedjs
4 | using the 'dig' utility, as its the only tool that supports alternative ports
5 | when doing a query.
6 |
7 | When this module is loaded it will return an object containing an array of
8 | samples that you can use to test serializers, protocol generators, create a
9 | raw DNS client, etc.
10 |
11 | Each sample is an object with an 'id', 'description', 'raw', and 'data'.
12 | The ID is used so that adding and removing samples out of order will not affect
13 | external references to them in tests.
14 |
15 | The data is put through an encoder that will turn this string into a raw
16 | buffer. That way, samples may be loaded from file that can be read by a (mortal)
17 | human being.
18 |
19 | When the sample is encoded it places a "raw" value in the object. If you have one
20 | there it will be over-written.
21 |
22 | */
23 |
24 | var samples = [
25 | {
26 | id: 0,
27 | description: 'query ns1.joyent.dev (A)',
28 | data: "0f 34 01 00 00 01 00 00 00 00 00 00 03 6e 73 31 06 6a 6f 79 65 " +
29 | "6e 74 03 64 65 76 00 00 01 00 01",
30 | type: 'queryMessage'
31 | },
32 | {
33 | id: 1,
34 | description: 'query ns1.joyent.dev (AAAA)',
35 | data: "b9 dd 01 00 00 01 00 00 00 00 00 00 03 6e 73 31 06 6a 6f 79 65 " +
36 | "6e 74 03 64 65 76 00 00 1c 00 01",
37 | type: 'queryMessage'
38 | }
39 | ];
40 |
41 | function encode(data) {
42 | var tokens, buffer, pos = 0;
43 |
44 | if (typeof(data) !== 'string')
45 | throw new TypeError('data (string) is required');
46 |
47 | tokens = data.split(/\s/);
48 | buffer = new Buffer(tokens.length);
49 |
50 | for (i in tokens) {
51 | var t = '0x' + tokens[i];
52 | var v = parseInt(t);
53 | buffer.writeInt8(v, pos++, true);
54 | }
55 | return buffer;
56 | }
57 |
58 | function encodeSamples(samples) {
59 | var sample, results = [];
60 | for (i in samples) {
61 | sample = samples[i];
62 | sample.raw = encode(sample.data);
63 | results.push(sample);
64 | }
65 | return results;
66 | }
67 |
68 | function equalBuffers(b1, b2) {
69 | if (b1.length !== b2.length) {
70 | return false;
71 | }
72 |
73 | var l = b1.length;
74 | while (l--) {
75 | var one = b1.readUInt8(l);
76 | var two = b2.readUInt8(l);
77 | if (one !== two) {
78 | return false;
79 | }
80 | }
81 | return true;
82 | }
83 |
84 | module.exports = {
85 | samples: encodeSamples(samples),
86 | equalBuffers: equalBuffers
87 | }
88 |
--------------------------------------------------------------------------------
/test/helper.js:
--------------------------------------------------------------------------------
1 | // Copyright 2012 Mark Cavage. All rights reserved.
2 | //
3 | // Just a simple wrapper over nodeunit's exports syntax. Also exposes
4 | // a common logger for all tests.
5 | //
6 |
7 | var bunyan = require('bunyan');
8 | var named = require('../lib');
9 |
10 |
11 | ///--- Exports
12 |
13 | module.exports = {
14 |
15 | after: function after(teardown) {
16 | module.parent.exports.tearDown = teardown;
17 | },
18 |
19 | before: function before(setup) {
20 | module.parent.exports.setUp = setup;
21 | },
22 |
23 | test: function test(name, tester) {
24 | module.parent.exports[name] = function _(t) {
25 | var _done = false;
26 | t.end = function end() {
27 | if (!_done) {
28 | _done = true;
29 | t.done();
30 | }
31 | };
32 | t.notOk = function notOk(ok, message) {
33 | return (t.ok(!ok, message));
34 | };
35 | return (tester(t));
36 | };
37 | },
38 |
39 |
40 | getLog: function (name, stream) {
41 | return (bunyan.createLogger({
42 | level: (process.env.LOG_LEVEL || 'info'),
43 | name: name || process.argv[1],
44 | serializers: named.bunyan.serializers,
45 | stream: stream || process.stdout,
46 | src: true
47 | }));
48 | },
49 |
50 | };
51 |
--------------------------------------------------------------------------------
/test/named.test.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 |
3 | var dig = require('./dig');
4 |
5 | if (require.cache[__dirname + '/helper.js'])
6 | delete require.cache[__dirname + '/helper.js']
7 | var helper = require('./helper');
8 |
9 |
10 | ///--- Globals
11 |
12 | var test = helper.test;
13 | var before = helper.before;
14 | var after = helper.after;
15 |
16 | var options = {port: 9999, server: '::1'};
17 |
18 |
19 |
20 | ///--- Tests
21 |
22 | before(function (callback) {
23 | this.server = named.createServer({
24 | log: helper.getLog('server')
25 | });
26 |
27 | this.server.on('query', function (query) {
28 | var domain = query.name()
29 | var type = query.type();
30 |
31 | switch (type) {
32 | case 'A':
33 | var record = new named.ARecord('127.0.0.1');
34 | query.addAnswer(domain, record);
35 | break;
36 | case 'AAAA':
37 | var record = new named.AAAARecord('::1');
38 | query.addAnswer(domain, record);
39 | break;
40 | case 'CNAME':
41 | var record = new named.CNAMERecord('cname.example.com');
42 | query.addAnswer(domain, record);
43 | break;
44 | case 'NS':
45 | var record = new named.NSRecord('ns.example.com');
46 | query.addAnswer(domain, record);
47 | break;
48 | case 'MX':
49 | var record = new named.MXRecord('smtp.example.com');
50 | query.addAnswer(domain, record);
51 | break;
52 | case 'SOA':
53 | var record = new named.SOARecord('example.com');
54 | query.addAnswer(domain, record);
55 | break;
56 | case 'SRV':
57 | var record = new named.SRVRecord('sip.example.com', 5060);
58 | query.addAnswer(domain, record);
59 | break;
60 | case 'TXT':
61 | var record = new named.TXTRecord('hello world');
62 | query.addAnswer(domain, record);
63 | break;
64 | }
65 | query.respond();
66 | });
67 |
68 | this.server.listen(options.port, options.server, function() {
69 | process.nextTick(callback);
70 | });
71 | });
72 |
73 |
74 | after(function (callback) {
75 | this.server.close(callback);
76 | });
77 |
78 |
79 | test('listen and close (port only)', function (t) {
80 | // don't conflict with the server made in 'before'
81 | var server = named.createServer();
82 | server.listen(1153, function () {
83 | process.nextTick(function () {
84 | server.close(function () {
85 | t.end();
86 | })
87 | });
88 | });
89 | });
90 |
91 |
92 | test('listen and close (port and ::1)', function(t) {
93 | var server = named.createServer();
94 | server.listen(String(1153), '::1', function() {
95 | process.nextTick(function () {
96 | server.close(function () {
97 | t.end();
98 | })
99 | });
100 | });
101 | });
102 |
103 |
104 | test('answer query: example.com (A)', function (t) {
105 | dig('example.com', 'A', options, function (err, results) {
106 | t.ifError(err);
107 | t.deepEqual(results.answers, [{
108 | name: 'example.com.',
109 | ttl: 5, type: 'A',
110 | target: '127.0.0.1'
111 | }]);
112 | t.end();
113 | });
114 | });
115 |
116 |
117 | test('answer query: example.com (AAAA)', function (t) {
118 | dig('example.com', 'AAAA', options, function (err, results) {
119 | t.ifError(err);
120 | t.deepEqual(results.answers, [{
121 | name: 'example.com.',
122 | ttl: 5, type: 'AAAA',
123 | target: '::1'
124 | }]);
125 | t.end();
126 | });
127 | });
128 |
129 |
130 | test('answer query: example.com (CNAME)', function (t) {
131 | dig('www.example.com', 'CNAME', options, function (err, results) {
132 | t.ifError(err);
133 | t.deepEqual(results.answers, [{
134 | name: 'www.example.com.',
135 | ttl: 5,
136 | type: 'CNAME',
137 | target: 'cname.example.com.'
138 | }]);
139 | t.end();
140 | });
141 | });
142 |
143 | test('answer query: example.com (NS)', function (t) {
144 | dig('example.com', 'NS', options, function (err, results) {
145 | t.ifError(err);
146 | t.deepEqual(results.answers, [{
147 | name: 'example.com.',
148 | ttl: 5,
149 | type: 'NS',
150 | target: 'ns.example.com.'
151 | }]);
152 | t.end();
153 | });
154 | });
155 |
156 |
157 | test('answer query: example.com (MX)', function (t) {
158 | dig('example.com', 'MX', options, function (err, results) {
159 | t.ifError(err);
160 | t.deepEqual(results.answers, [{
161 | name: 'example.com.',
162 | ttl: 5,
163 | type: 'MX',
164 | target: '0 smtp.example.com.'
165 | }]);
166 | t.end();
167 | });
168 | });
169 |
170 |
171 | test('answer query: example.com (SOA)', function (t) {
172 | dig('example.com', 'SOA', options, function (err, results) {
173 | t.ifError(err);
174 | t.deepEqual(results.answers, [{
175 | name: 'example.com.',
176 | ttl: 5,
177 | type: 'SOA',
178 | target: 'example.com. hostmaster.example.com. 0 10 10 10 10'
179 | }]);
180 | t.end();
181 | });
182 | });
183 |
184 |
185 | test('answer query: example.com (SRV)', function (t) {
186 | dig('_sip._tcp.example.com', 'SRV', options, function (err, results) {
187 | t.ifError(err);
188 | t.deepEqual(results.answers, [{
189 | name: '_sip._tcp.example.com.',
190 | ttl: 5,
191 | type: 'SRV',
192 | target: '0 10 5060 sip.example.com.'
193 | }]);
194 | t.end();
195 | });
196 | });
197 |
198 |
199 | test('answer query: example.com (TXT)', function (t) {
200 | dig('example.com', 'TXT', options, function (err, results) {
201 | t.ifError(err);
202 | t.deepEqual(results.answers, [{
203 | name: 'example.com.',
204 | ttl: 5,
205 | type: 'TXT',
206 | target: '"hello world"'
207 | }]);
208 | t.end();
209 | });
210 | });
211 |
--------------------------------------------------------------------------------
/test/protocol.test.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 |
3 | if (require.cache[__dirname + '/helper.js'])
4 | delete require.cache[__dirname + '/helper.js']
5 | var helper = require('./helper');
6 |
7 |
8 | var dnsBuffer = require('./dnsbuffer');
9 |
10 | var test = helper.test;
11 | var protocol = named.Protocol;
12 |
13 | for (var i in dnsBuffer.samples) {
14 | var sample = dnsBuffer.samples[i];
15 | test('protocol decode/encode: ' + sample.description, function(t) {
16 | decoded = protocol.decode(sample.raw, sample.type);
17 | encoded = protocol.encode(decoded.val, sample.type);
18 | if (dnsBuffer.equalBuffers(encoded, sample.raw)) {
19 | t.ok(true, 'encoder cycle passed');
20 | }
21 | else {
22 | t.ok(false, 'encoder cycle failed');
23 | }
24 | t.end();
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/test/query.test.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 | var dnsBuffer = require('./dnsbuffer');
3 |
4 | if (require.cache[__dirname + '/helper.js'])
5 | delete require.cache[__dirname + '/helper.js'];
6 | var helper = require('./helper');
7 |
8 | var test = helper.test;
9 | var before = helper.before;
10 | var after = helper.after;
11 |
12 | var raw, src;
13 |
14 | before(function(callback) {
15 | try {
16 | raw = {
17 | buf: dnsBuffer.samples[0].raw,
18 | len: dnsBuffer.samples[0].length
19 | }
20 | src = {
21 | family: 'udp6',
22 | address: '127.0.0.1',
23 | port: 23456
24 | }
25 |
26 | process.nextTick(callback);
27 | }
28 | catch (e) {
29 | console.error(e.stack);
30 | process.exit(1);
31 | }
32 | });
33 |
34 |
35 | test('decode a query datagram', function(t) {
36 | var query = named.Query.parse(raw, src);
37 | t.end();
38 | });
39 |
40 | test('create a new query object', function(t) {
41 | var decoded = named.Query.parse(raw, src);
42 | var query = named.Query.createQuery(decoded);
43 | t.end();
44 | });
45 |
46 | test('encode an null-response query object', function(t) {
47 | var decoded = named.Query.parse(raw, src);
48 | var query = named.Query.createQuery(decoded);
49 | query.encode();
50 | var ok = dnsBuffer.samples[0].raw;
51 | t.deepEqual(query._raw.buf, ok);
52 | t.end();
53 | });
54 |
55 | // TODO test adding a record
56 | // TODO test name response
57 | // TODO test answers response
58 |
--------------------------------------------------------------------------------
/test/records.test.js:
--------------------------------------------------------------------------------
1 | var named = require('../lib');
2 |
3 | if (require.cache[__dirname + '/helper.js'])
4 | delete require.cache[__dirname + '/helper.js']
5 | var helper = require('./helper');
6 |
7 | var test = helper.test;
8 |
9 | var testRecord = function(record, t) {
10 | if (!record)
11 | t.ok(false, 'record could not be created');
12 |
13 | if (record && record.valid()) {
14 | t.ok(true, 'valid record created');
15 | }
16 | else {
17 | t.ok(false, 'record was not valid');
18 | }
19 |
20 | t.end()
21 | }
22 |
23 | test('create a valid record (A)', function(t) {
24 | var record = new named.ARecord('127.0.0.1');
25 | testRecord(record, t);
26 | });
27 |
28 | test('create a valid record (AAAA)', function(t) {
29 | var record = new named.AAAARecord('::1');
30 | testRecord(record, t);
31 | });
32 |
33 | test('create a valid record (CNAME)', function(t) {
34 | var record = new named.CNAMERecord('alias.example.com');
35 | testRecord(record, t);
36 | });
37 |
38 | test('create a valid record (NS)', function(t) {
39 | var record = new named.NSRecord('ns.example.com');
40 | testRecord(record, t);
41 | });
42 |
43 | test('create a valid record (MX)', function(t) {
44 | var record = new named.MXRecord('smtp.example.com');
45 | testRecord(record, t);
46 | });
47 |
48 | test('create a valid record (SOA)', function(t) {
49 | var record = new named.SOARecord('example.com');
50 | testRecord(record, t);
51 | });
52 |
53 | test('create a valid record (SRV)', function(t) {
54 | var record = new named.SRVRecord('_sip._udp.example.com', 5060);
55 | testRecord(record, t);
56 | });
57 |
58 | test('create a valid record (TXT)', function(t) {
59 | var record = new named.TXTRecord('hello world');
60 | testRecord(record, t);
61 | });
62 |
--------------------------------------------------------------------------------
/test/validator.test.js:
--------------------------------------------------------------------------------
1 | var validators = require('../lib/validators');
2 |
3 | if (require.cache[__dirname + '/helper.js'])
4 | delete require.cache[__dirname + '/helper.js']
5 | var helper = require('./helper');
6 |
7 | var test = helper.test;
8 |
9 | var toTest = {
10 | nsName: [
11 | [ 'example.com', true ],
12 | [ '0example.com', true ],
13 | [ '_example.com', false ],
14 | [ '0_example.com', false ],
15 | [ '-example.com', false ],
16 | [ '0-example.com', true ],
17 | [ 'example-one.com', true ],
18 | [ 'example-111.com', true ],
19 | [ 'Example-111.com', true ],
20 | [ 'a name with spaces', false ],
21 | ],
22 | UInt32BE: [
23 | [ 'hello', false ],
24 | [ '12345', true ],
25 | [ 4294967296, false ],
26 | [ 10, true ]
27 | ],
28 | UInt16BE: [
29 | [ 'hello', false ],
30 | [ '12345', true ],
31 | [ 65536, false ],
32 | [ 10, true ]
33 | ],
34 | nsText: [
35 | [ 'hello world', true ],
36 | ]
37 | };
38 |
39 | test('testing validator (nsName)', function(t) {
40 | var k = 'nsName';
41 | for (var i in toTest.k) {
42 | var s = toTest.k[i][0];
43 | var ok = toTest.k[i][1];
44 | var result = validators.k(s);
45 | t.equal(result, ok);
46 | }
47 | t.end();
48 | });
49 |
50 | test('testing validator (UInt32BE)', function(t) {
51 | var k = 'UInt32BE';
52 | for (var i in toTest.k) {
53 | var s = toTest.k[i][0];
54 | var ok = toTest.k[i][1];
55 | var result = validators.k(s);
56 | t.equal(result, ok);
57 | }
58 | t.end();
59 | });
60 |
61 | test('testing validator (UInt16BE)', function(t) {
62 | var k = 'UInt16BE';
63 | for (var i in toTest.k) {
64 | var s = toTest.k[i][0];
65 | var ok = toTest.k[i][1];
66 | var result = validators.k(s);
67 | t.equal(result, ok);
68 | }
69 | t.end();
70 | });
71 |
72 | test('testing validator (nsText)', function(t) {
73 | var k = 'nsText';
74 | for (var i in toTest.k) {
75 | var s = toTest.k[i][0];
76 | var ok = toTest.k[i][1];
77 | var result = validators.k(s);
78 | t.equal(result, ok);
79 | }
80 | t.end();
81 | });
82 |
--------------------------------------------------------------------------------