├── .gitignore ├── .gitmodules ├── .npmignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── TODO.md ├── docs └── index.md ├── examples ├── app.js ├── axfr.js ├── cluster.js ├── noanswer.js └── reflect.js ├── lib ├── balancer.js ├── dns-buffer.js ├── errors.js ├── index.js ├── protocol.js ├── query.js ├── records │ ├── a.js │ ├── aaaa.js │ ├── cname.js │ ├── mx.js │ ├── ns.js │ ├── ptr.js │ ├── soa.js │ ├── srv.js │ └── txt.js ├── server.js ├── tsig.js └── validators.js ├── package.json ├── test ├── dig.js ├── dnsbuffer.js ├── helper.js ├── named.test.js ├── notify.test.js ├── pipeline.test.js ├── protocol.test.js ├── query.test.js ├── records.test.js ├── tsig.test.js └── validator.test.js └── tools ├── bashstyle ├── jsl.node.conf ├── jsstyle.conf └── mk ├── Makefile.defs ├── Makefile.deps ├── Makefile.node.defs ├── Makefile.node.targ ├── Makefile.node_deps.defs ├── Makefile.node_deps.targ ├── Makefile.node_prebuilt.defs ├── Makefile.node_prebuilt.targ └── Makefile.targ /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swmp 3 | node_modules 4 | npm-debug.log 5 | tmp 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/javascriptlint"] 2 | path = deps/javascriptlint 3 | url = https://github.com/davepacheco/javascriptlint.git 4 | [submodule "deps/jsstyle"] 5 | path = deps/jsstyle 6 | url = https://github.com/joyent/jsstyle.git 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitmodules 2 | deps 3 | docs 4 | Makefile 5 | node_modules 6 | test 7 | tools 8 | coverage 9 | man/src 10 | examples 11 | test.* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.4" 4 | - "0.12" 5 | - "0.10" 6 | before_install: 7 | - "make check" 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository uses GitHub pull requests for code review. 4 | 5 | See the [Joyent Engineering 6 | Guidelines](https://github.com/joyent/eng/blob/master/docs/index.md) for general 7 | best practices expected in this repository. 8 | 9 | Contributions should be "make check" clean. The "make check" target will 10 | install all necessary tools as part of its operation. 11 | 12 | If you're changing something non-trivial or user-facing, you may want to submit 13 | an issue first. 14 | -------------------------------------------------------------------------------- /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 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2014, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile: basic Makefile for template API service 13 | # 14 | # This Makefile is a template for new repos. It contains only repo-specific 15 | # logic and uses included makefiles to supply common targets (javascriptlint, 16 | # jsstyle, restdown, etc.), which are used by other repos as well. You may well 17 | # need to rewrite most of this file, but you shouldn't need to touch the 18 | # included makefiles. 19 | # 20 | # If you find yourself adding support for new targets that could be useful for 21 | # other projects too, you should add these to the original versions of the 22 | # included Makefiles (in eng.git) so that other teams can use them too. 23 | # 24 | 25 | # 26 | # Tools 27 | # 28 | NPM := $(shell which npm) 29 | NODEUNIT := ./node_modules/.bin/nodeunit 30 | 31 | # 32 | # Files 33 | # 34 | DOC_FILES = index.md boilerplateapi.md 35 | JS_FILES := $(shell find lib -name '*.js') 36 | JSON_FILES = package.json 37 | JSL_CONF_NODE = tools/jsl.node.conf 38 | JSL_FILES_NODE = $(JS_FILES) 39 | JSSTYLE_FILES = $(JS_FILES) 40 | JSSTYLE_FLAGS = -f tools/jsstyle.conf 41 | 42 | include ./tools/mk/Makefile.defs 43 | 44 | # 45 | # Repo-specific targets 46 | # 47 | .PHONY: all 48 | all: $(SMF_MANIFESTS) | $(NODEUNIT) $(REPO_DEPS) 49 | $(NPM) rebuild 50 | 51 | $(NODEUNIT): | $(NPM) 52 | $(NPM) install 53 | 54 | CLEAN_FILES += $(NODEUNIT) ./node_modules/nodeunit 55 | 56 | .PHONY: test 57 | test: $(NODEUNIT) 58 | $(NODEUNIT) test/*.test.js 59 | 60 | include ./tools/mk/Makefile.deps 61 | include ./tools/mk/Makefile.targ 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-mname - DNS Server in Node.js 2 | 3 | `mname` is a fork of the `node-named` library, which enables the development of 4 | DNS servers in node.js. 5 | 6 | This fork adds the following features: 7 | - Queries over TCP connections 8 | - AXFR, IXFR zone transfers 9 | - PTR records 10 | - Name compression 11 | - EDNS 1.0 12 | 13 | ## Creating a DNS Server 14 | 15 | ```js 16 | var named = require('./lib/index'); 17 | var server = named.createServer(); 18 | var ttl = 300; 19 | 20 | server.listenUdp(9999, '127.0.0.1', function() { 21 | console.log('DNS server started on port 9999'); 22 | }); 23 | 24 | server.on('query', function(query, done) { 25 | var domain = query.name(); 26 | console.log('DNS Query: %s', domain) 27 | var target = new SOARecord(domain, {serial: 12345}); 28 | query.addAnswer(domain, target, ttl); 29 | query.respond(); 30 | done(); 31 | }); 32 | ``` 33 | 34 | ## Creating DNS Records 35 | 36 | node-named provides helper functions for creating DNS records. 37 | The records are available under 'named.record.NAME' where NAME is one 38 | of ['A', 'AAAA', 'CNAME', 'SOA', 'MX', 'TXT, 'SRV']. It is important to 39 | remember that these DNS records are not permanently added to the server. 40 | They only exist for the length of the particular request. After that, they are 41 | destroyed. This means you have to create your own lookup mechanism. 42 | 43 | ```js 44 | var named = require('node-named'); 45 | 46 | var soaRecord = named.SOARecord('example.com', {serial: 201205150000}); 47 | console.log(soaRecord); 48 | ``` 49 | 50 | ### Supported Record Types 51 | 52 | The following record types are supported 53 | 54 | * A (ipv4) 55 | * AAAA (ipv6) 56 | * CNAME (aliases) 57 | * SOA (start of authority) 58 | * MX (mail server records) 59 | * TXT (arbitrary text entries) 60 | * SRV (service discovery) 61 | 62 | ## Logging 63 | 64 | node-named uses [http://github.com/trentm/node-bunyan](bunyan) for logging. 65 | It's a lot nicer to use if you npm install bunyan and put the bunyan tool in 66 | your path. Otherwise, you will end up with JSON formatted log output by default. 67 | 68 | ### Replacing the default logger 69 | 70 | You can pass in an alternate logger if you wish. If you do not, then it will use 71 | bunyan by default. Your logger must expose the functions 'info', 'debug', 72 | 'warn', 'trace', 'error', and 'notice'. 73 | 74 | ## Tell me even more... 75 | 76 | When DNS was designed it was designed prior 77 | to the web existing, so many of the features in the RFC are either never used, 78 | or were never implemented. This server aims to be RFC compliant, but does not 79 | implement any other protocol other than INET (the one we're all used to), and 80 | only supports a handful of record types (the ones that are in use on a regular 81 | basis). 82 | -------------------------------------------------------------------------------- /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 called once all underlying resources have been released. 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/axfr.js: -------------------------------------------------------------------------------- 1 | var named = require('../lib/index'); 2 | var bunyan = require('bunyan'); 3 | var log = bunyan.createLogger({name: 'test'}); 4 | 5 | var server = named.createServer(); 6 | var ttl = 3600; 7 | 8 | server.listenUdp({port: 9953, address: '127.0.0.1'}); 9 | server.listenTcp({port: 9953, address: '127.0.0.1'}); 10 | 11 | server.on('query', function(query) { 12 | var domain = query.name(); 13 | log.info({name: query.name(), type: query.type()}); 14 | 15 | if (query.type() === 'AXFR') { 16 | var soa = new named.SOARecord(domain, {serial: 12345}); 17 | query.addAnswer(domain, soa, ttl); 18 | server.send(query); 19 | 20 | var a = new named.ARecord("1.2.3.4"); 21 | var a2 = new named.ARecord("1.2.3.5"); 22 | query.addAnswer("foo." + domain, a, ttl); 23 | query.addAnswer("bar." + domain, a, ttl); 24 | query.addAnswer("foobar." + domain, a2, ttl); 25 | server.send(query); 26 | 27 | query.addAnswer(domain, soa, ttl); 28 | server.send(query); 29 | 30 | } else if (query.type() === 'IXFR') { 31 | var base = query.ixfrBase(); 32 | var oldSoa = new named.SOARecord(domain, {serial: base}); 33 | var newSoa = new named.SOARecord(domain, {serial: 12345}); 34 | query.addAnswer(domain, newSoa, ttl); 35 | server.send(query); 36 | 37 | var a = new named.ARecord("1.2.3.4"); 38 | var a2 = new named.ARecord("1.2.3.5"); 39 | 40 | /* removed since old serial */ 41 | query.addAnswer(domain, oldSoa, ttl); 42 | server.send(query); 43 | query.addAnswer("tri." + domain, a, ttl); 44 | 45 | /* new additions */ 46 | query.addAnswer(domain, newSoa, ttl); 47 | query.addAnswer("foo." + domain, a, ttl); 48 | query.addAnswer("bar." + domain, a, ttl); 49 | query.addAnswer("foobar." + domain, a2, ttl); 50 | server.send(query); 51 | 52 | query.addAnswer(domain, newSoa, ttl); 53 | server.send(query); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/balancer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, Joyent, Inc 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* 24 | * MNAME-BALANCER INTERFACE 25 | * 26 | * To support vertically scaling an individual DNS server through the use of 27 | * multiple Node processes, a load balancer has been built that is specifically 28 | * tailored for use with this DNS server library. A custom protocol has been 29 | * devised, which represents an interface between this library and the load 30 | * balancer software. The load balancer, including full protocol 31 | * documentation, is available here: 32 | * 33 | * https://github.com/joyent/mname-balancer 34 | * 35 | */ 36 | 37 | var assert = require('assert-plus'); 38 | var util = require('util'); 39 | var stream = require('stream'); 40 | 41 | 42 | var FRAME_TYPE_CLIENT_HELLO = 1; 43 | var FRAME_TYPE_INBOUND_UDP = 2; 44 | var FRAME_TYPE_INBOUND_TCP = 3; 45 | var FRAME_TYPE_CLIENT_HEARTBEAT = 4; 46 | var FRAME_TYPE_SERVER_HELLO = 1001; 47 | var FRAME_TYPE_OUTBOUND_UDP = 1002; 48 | var FRAME_TYPE_INBOUND_TCP_OK = 1003; 49 | var FRAME_TYPE_SERVER_HEARTBEAT = 1004; 50 | 51 | var SIZE_U32 = 4; 52 | 53 | 54 | function BalancerTransform(opts) { 55 | var self = this; 56 | 57 | stream.Transform.call(self); 58 | 59 | self.bal_accum = new Buffer(0); 60 | self.bal_tcp = null; 61 | self.bal_hello = false; 62 | self.bal_log = opts.log; 63 | self.bal_sock = opts.sock; 64 | } 65 | util.inherits(BalancerTransform, stream.Transform); 66 | 67 | BalancerTransform.prototype.discard = function (n) { 68 | var self = this; 69 | 70 | self.bal_accum = self.bal_accum.slice(n); 71 | }; 72 | 73 | BalancerTransform.prototype.avail = function (n) { 74 | var self = this; 75 | 76 | return (self.bal_accum.length >= n); 77 | }; 78 | 79 | /* 80 | * Read a little endian uint32_t from the provided offset (in bytes) into the 81 | * buffer. 82 | */ 83 | BalancerTransform.prototype.readU32 = function (offs) { 84 | var self = this; 85 | 86 | return (self.bal_accum.readUInt32LE(offs)); 87 | }; 88 | 89 | /* 90 | * Read a little endian IPv4 address from the provided offset (in bytes) into 91 | * the buffer. 92 | */ 93 | BalancerTransform.prototype.readIPv4 = function (offs) { 94 | var self = this; 95 | 96 | var ipaddr = [ 97 | self.bal_accum.readUInt8(offs + 3), 98 | self.bal_accum.readUInt8(offs + 2), 99 | self.bal_accum.readUInt8(offs + 1), 100 | self.bal_accum.readUInt8(offs + 0) 101 | ].join('.'); 102 | 103 | return (ipaddr); 104 | }; 105 | 106 | BalancerTransform.prototype._transform = function (o, _, done) { 107 | var self = this; 108 | var sock = self.bal_sock; 109 | var log = self.bal_log; 110 | 111 | if (self.bal_tcp !== null) { 112 | /* 113 | * We are piped to the emulated TCP stream, so just pass on 114 | * buffers as we get them. 115 | */ 116 | self.push(o); 117 | setImmediate(done); 118 | return; 119 | } 120 | 121 | self.bal_accum = Buffer.concat([ self.bal_accum, o ], 122 | self.bal_accum.length + o.length); 123 | 124 | while (self.avail(SIZE_U32)) { 125 | /* 126 | * Read frame type. 127 | */ 128 | var frame_type = self.readU32(0); 129 | var out, ipaddr, port; 130 | 131 | if (frame_type === FRAME_TYPE_CLIENT_HELLO) { 132 | self.discard(SIZE_U32); 133 | self.bal_hello = true; 134 | 135 | /* 136 | * A CLIENT_HELLO frame requires a SERVER_HELLO 137 | * response. 138 | */ 139 | out = new Buffer(SIZE_U32); 140 | out.writeUInt32LE(FRAME_TYPE_SERVER_HELLO, 0); 141 | sock.write(out); 142 | continue; 143 | } 144 | 145 | if (frame_type === FRAME_TYPE_INBOUND_TCP) { 146 | /* 147 | * This frame signals a request to convert this session 148 | * to a TCP proxy session. 149 | */ 150 | if (!self.avail(3 * SIZE_U32)) { 151 | /* 152 | * Wait for the entire frame to arrive. 153 | */ 154 | break; 155 | } 156 | 157 | ipaddr = self.readIPv4(1 * SIZE_U32); 158 | port = self.readU32(2 * SIZE_U32); 159 | 160 | log.info('backend TCP connection: ' + 161 | 'remote peer %s:%d', ipaddr, port); 162 | 163 | /* 164 | * Send acknowledgement to the balancer that this is 165 | * now a proxied TCP stream. 166 | */ 167 | out = new Buffer(SIZE_U32); 168 | out.writeUInt32LE(FRAME_TYPE_INBOUND_TCP_OK, 0); 169 | sock.write(out); 170 | 171 | /* 172 | * As part of the conversion into a proxied TCP stream, 173 | * we must forward on several events from the 174 | * underlying socket to the consumer. 175 | */ 176 | sock.on('timeout', function () { 177 | self.emit('timeout'); 178 | }); 179 | sock.on('error', function (sockErr) { 180 | self.emit('error', sockErr); 181 | }); 182 | 183 | /* 184 | * Dress this Transform up as if it were a regular TCP 185 | * connection from a DNS client. 186 | */ 187 | var tcp = Object.create(self); 188 | 189 | tcp.remoteAddress = ipaddr; 190 | tcp.remotePort = port; 191 | tcp.setTimeout = sock.setTimeout.bind(sock); 192 | tcp.end = sock.end.bind(sock); 193 | tcp.destroy = sock.destroy.bind(sock); 194 | tcp.write = sock.write.bind(sock); 195 | 196 | self.bal_tcp = tcp; 197 | self.emit('inbound_tcp', tcp); 198 | 199 | /* 200 | * Push any remaining data after the frame header into 201 | * the emulated TCP stream. 202 | */ 203 | self.discard(3 * SIZE_U32); 204 | self.push(self.bal_accum); 205 | self.bal_accum = null; 206 | 207 | setImmediate(done); 208 | return; 209 | } 210 | 211 | var err; 212 | if (!self.bal_hello) { 213 | err = new Error('frame type ' + frame_type + 214 | ' before HELLO'); 215 | log.debug(err, 'protocol error on balancer connection'); 216 | done(err); 217 | sock.destroy(); 218 | return; 219 | } 220 | 221 | if (frame_type === FRAME_TYPE_CLIENT_HEARTBEAT) { 222 | self.discard(SIZE_U32); 223 | 224 | /* 225 | * The balancer expects a prompt reply for each 226 | * heartbeat frame. 227 | */ 228 | out = new Buffer(SIZE_U32); 229 | out.writeUInt32LE(FRAME_TYPE_SERVER_HEARTBEAT, 0); 230 | sock.write(out); 231 | continue; 232 | } 233 | 234 | /* 235 | * All other frame types have been processed already. At this 236 | * point, the frame _must_ be an inbound UDP packet. 237 | */ 238 | if (frame_type !== FRAME_TYPE_INBOUND_UDP) { 239 | err = new Error('frame type ' + frame_type + 240 | ' is not valid'); 241 | log.debug(err, 'protocol error on balancer connection'); 242 | done(err); 243 | sock.destroy(); 244 | return; 245 | } 246 | 247 | var headerlen = 4 * SIZE_U32; 248 | if (!self.avail(headerlen)) { 249 | /* 250 | * The complete header has not yet arrived. 251 | */ 252 | break; 253 | } 254 | 255 | ipaddr = self.readIPv4(1 * SIZE_U32); 256 | port = self.readU32(2 * SIZE_U32); 257 | var datalen = self.readU32(3 * SIZE_U32); 258 | 259 | if (!self.avail(headerlen + datalen)) { 260 | /* 261 | * Entire frame has not yet arrived. 262 | */ 263 | break; 264 | } 265 | 266 | log.trace('balancer packet: %s:%d (len %d)', ipaddr, port, 267 | datalen); 268 | 269 | var rinfo = { address: ipaddr, port: port }; 270 | 271 | self.emit('inbound_udp', rinfo, 272 | self.bal_accum.slice(headerlen, headerlen + datalen), 273 | function reply(buf, from, len, sendport, addr, cb) { 274 | assert.equal(from, 0); 275 | assert.equal(len, buf.length); 276 | 277 | var hdr = new Buffer(4 * SIZE_U32); 278 | hdr.writeUInt32LE(FRAME_TYPE_OUTBOUND_UDP, 0); 279 | 280 | var octs = addr.split('.'); 281 | hdr.writeUInt8(octs[3], 1 * SIZE_U32 + 0); 282 | hdr.writeUInt8(octs[2], 1 * SIZE_U32 + 1); 283 | hdr.writeUInt8(octs[1], 1 * SIZE_U32 + 2); 284 | hdr.writeUInt8(octs[0], 1 * SIZE_U32 + 3); 285 | 286 | hdr.writeUInt32LE(sendport, 2 * SIZE_U32); 287 | hdr.writeUInt32LE(len, 3 * SIZE_U32); 288 | 289 | sock.write(hdr); 290 | sock.write(buf); 291 | 292 | setImmediate(cb); 293 | }); 294 | 295 | self.discard(headerlen + datalen); 296 | } 297 | 298 | setImmediate(done); 299 | }; 300 | 301 | 302 | function wrapBalancerConnection(opts) { 303 | var log = opts.log; 304 | var sock = opts.sock; 305 | 306 | var t = new BalancerTransform({ log: log, sock: sock }); 307 | 308 | sock.pipe(t); 309 | 310 | sock.on('end', function () { 311 | log.warn('balancer ended connection'); 312 | }); 313 | sock.on('error', function (err) { 314 | log.warn(err, 'error on balancer connection'); 315 | sock.destroy(); 316 | }); 317 | sock.on('close', function () { 318 | log.trace('balancer socket closed'); 319 | }); 320 | 321 | return (t); 322 | } 323 | 324 | module.exports = { 325 | wrapBalancerConnection: wrapBalancerConnection 326 | }; 327 | -------------------------------------------------------------------------------- /lib/dns-buffer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | module.exports = DNSBuffer; 25 | 26 | var assert = require('assert-plus'); 27 | 28 | function DNSBuffer(opts) { 29 | assert.object(opts, 'options'); 30 | assert.optionalBuffer(opts.buffer, 'options.buffer'); 31 | 32 | this._size = opts.buffer ? opts.buffer.length : 1024; 33 | this._buffer = opts.buffer || (new Buffer(this._size)); 34 | this._offset = 0; 35 | this._ncache = new NameOffsetCache(); 36 | } 37 | 38 | DNSBuffer.prototype.toBuffer = function () { 39 | return (this._buffer.slice(0, this._offset)); 40 | }; 41 | 42 | DNSBuffer.prototype.atEnd = function () { 43 | return (this._offset >= this._size); 44 | }; 45 | 46 | DNSBuffer.prototype.remainder = function () { 47 | return (this._buffer.slice(this._offset, this._size)); 48 | }; 49 | 50 | DNSBuffer.prototype.skip = function (n) { 51 | this._offset += n; 52 | }; 53 | 54 | DNSBuffer.prototype.expand = function () { 55 | this._size *= 2; 56 | var buf = new Buffer(this._size); 57 | this._buffer.copy(buf, 0); 58 | this._buffer = buf; 59 | }; 60 | 61 | DNSBuffer.prototype.read = function (bytes) { 62 | var v = this._buffer.slice(this._offset, this._offset + bytes); 63 | this._offset += bytes; 64 | return (v); 65 | }; 66 | 67 | DNSBuffer.prototype.readUInt32 = function () { 68 | var v = this._buffer.readUInt32BE(this._offset); 69 | this._offset += 4; 70 | return (v); 71 | }; 72 | 73 | DNSBuffer.prototype.readUInt16 = function () { 74 | var v = this._buffer.readUInt16BE(this._offset); 75 | this._offset += 2; 76 | return (v); 77 | }; 78 | 79 | DNSBuffer.prototype.readUInt8 = function () { 80 | var v = this._buffer.readUInt8(this._offset++); 81 | return (v); 82 | }; 83 | 84 | var NAME_META_MASK = 0xC0; 85 | var NAME_STRING = 0x00; 86 | var NAME_PTR = 0xC0; 87 | 88 | DNSBuffer.prototype.readName = function () { 89 | var rlen, name = ''; 90 | 91 | var off = this._offset; 92 | var finalOff; 93 | 94 | rlen = this._buffer.readUInt8(off++); 95 | while (rlen !== 0x00 && name.length < 256) { 96 | var meta = rlen & NAME_META_MASK; 97 | 98 | if (meta == NAME_STRING) { 99 | assert.ok(off + rlen < this._size, 100 | 'invalid name label length'); 101 | var buf = this._buffer.slice(off, 102 | off + rlen); 103 | off += rlen; 104 | name += buf.toString('ascii') + '.'; 105 | 106 | } else if (meta == NAME_PTR) { 107 | var ptr = this._buffer.readUInt8(off++); 108 | ptr = ptr | ((rlen & ~(0xC0)) << 8); 109 | 110 | assert.ok(ptr < this._size, 111 | 'Invalid label pointer (off end of buffer)'); 112 | assert.ok(!(ptr >= this._offset && ptr <= off), 113 | 'Invalid label pointer (causes a loop)'); 114 | 115 | if (finalOff === undefined) 116 | finalOff = off; 117 | off = ptr; 118 | 119 | } else { 120 | throw (new Error('Invalid name segment type: ' + meta)); 121 | } 122 | 123 | rlen = this._buffer.readUInt8(off++); 124 | } 125 | 126 | if (name.length > 255) { 127 | throw (new Error('Invalid name (maximum length exceeded)')); 128 | } 129 | 130 | if (finalOff === undefined) 131 | finalOff = off; 132 | this._offset = finalOff; 133 | 134 | if (name.charAt(name.length - 1) === '.') 135 | name = name.slice(0, name.length - 1); 136 | 137 | return (name); 138 | }; 139 | 140 | DNSBuffer.prototype.writeName = function (name) { 141 | assert.string(name, 'name'); 142 | 143 | if (name === '' || name === '.') { 144 | this.writeUInt8(0); 145 | return; 146 | } 147 | if (name.charAt(name.length - 1) === '.') 148 | name = name.slice(0, name.length - 1); 149 | var maxIdx = name.length; 150 | 151 | var suffix = this._ncache.getSuffix(name); 152 | if (suffix.index !== undefined) { 153 | maxIdx = suffix.index; 154 | } 155 | 156 | var rlen; 157 | var i = -1, j; 158 | while (i < maxIdx) { 159 | var rem = name.slice(i + 1); 160 | j = name.indexOf('.', i + 1); 161 | if (j === -1) 162 | j = name.length; 163 | var part = name.slice(i + 1, j); 164 | i = j; 165 | rlen = part.length; 166 | 167 | if (rlen === 0) 168 | break; 169 | 170 | /* Can only use ptrs to things in the first 0x3fff bytes. */ 171 | if (this._offset <= 0x3fff) 172 | this._ncache.add(rem, this._offset); 173 | 174 | assert.ok(rlen < 64, 'segment "' + part + '" of name "' + 175 | name + '" is too long'); 176 | this.writeUInt8(rlen); 177 | this.write(new Buffer(part, 'ascii')); 178 | } 179 | 180 | if (suffix.offset !== undefined) { 181 | assert.ok(suffix.offset <= 0x3fff); 182 | 183 | var ptr = suffix.offset & 0xff; 184 | rlen = NAME_PTR | ((suffix.offset & 0x3f00) >> 8); 185 | this.writeUInt8(rlen); 186 | this.writeUInt8(ptr); 187 | 188 | } else { 189 | this.writeUInt8(0); 190 | } 191 | }; 192 | 193 | DNSBuffer.prototype.writeNamePlain = function (name) { 194 | assert.string(name, 'name'); 195 | 196 | if (name === '' || name === '.') { 197 | this.writeUInt8(0); 198 | return; 199 | } 200 | var rparts = name.split('.'); 201 | 202 | var rlen; 203 | while (rparts.length > 0) { 204 | var part = rparts.shift(); 205 | rlen = part.length; 206 | assert.ok(rlen < 64, 'segment "' + part + '" of name "' + 207 | name + '" is too long'); 208 | this.writeUInt8(rlen); 209 | this.write(new Buffer(part, 'ascii')); 210 | } 211 | this.writeUInt8(0); 212 | }; 213 | 214 | DNSBuffer.prototype.writeUInt32 = function (v) { 215 | while (this._offset + 4 > this._size) 216 | this.expand(); 217 | this._buffer.writeUInt32BE(v, this._offset); 218 | this._offset += 4; 219 | }; 220 | 221 | DNSBuffer.prototype.writeUInt16 = function (v) { 222 | while (this._offset + 2 > this._size) 223 | this.expand(); 224 | this._buffer.writeUInt16BE(v, this._offset); 225 | this._offset += 2; 226 | }; 227 | 228 | DNSBuffer.prototype.writeUInt8 = function (v) { 229 | while (this._offset + 1 > this._size) 230 | this.expand(); 231 | this._buffer.writeUInt8(v, this._offset++); 232 | }; 233 | 234 | DNSBuffer.prototype.write = function (buf) { 235 | while (this._offset + buf.length > this._size) 236 | this.expand(); 237 | buf.copy(this._buffer, this._offset); 238 | this._offset += buf.length; 239 | }; 240 | 241 | /* node 0.10 and earlier do not have read/writeUIntBE on Buffers */ 242 | if (Buffer.prototype.readUIntBE !== undefined && 243 | Buffer.prototype.writeUIntBE !== undefined) { 244 | 245 | DNSBuffer.prototype.readLengthPrefixed = function (lenBytes, cb) { 246 | var len = this._buffer.readUIntBE(this._offset, lenBytes); 247 | this._offset += lenBytes; 248 | 249 | var child = Object.create(this); 250 | child._size = this._offset + len; 251 | var ret = cb(child); 252 | this._offset += len; 253 | 254 | return (ret); 255 | }; 256 | 257 | DNSBuffer.prototype.writeLengthPrefixed = function (lenBytes, cb) { 258 | var lenOffset = this._offset; 259 | this._offset += lenBytes; 260 | var ret = cb(this); 261 | var len = this._offset - lenOffset - lenBytes; 262 | this._buffer.writeUIntBE(len, lenOffset, lenBytes); 263 | 264 | return (ret); 265 | }; 266 | 267 | } else { 268 | 269 | DNSBuffer.prototype.readLengthPrefixed = function (lenBytes, cb) { 270 | var len; 271 | switch (lenBytes) { 272 | case 1: 273 | len = this._buffer.readUInt8(this._offset); 274 | break; 275 | case 2: 276 | len = this._buffer.readUInt16BE(this._offset); 277 | break; 278 | case 4: 279 | len = this._buffer.readUInt32BE(this._offset); 280 | break; 281 | default: 282 | throw (new Error('Invalid prefix length value')); 283 | } 284 | this._offset += lenBytes; 285 | 286 | var child = Object.create(this); 287 | child._size = this._offset + len; 288 | var ret = cb(child); 289 | this._offset += len; 290 | 291 | return (ret); 292 | }; 293 | 294 | DNSBuffer.prototype.writeLengthPrefixed = function (lenBytes, cb) { 295 | assert.ok(lenBytes === 1 || lenBytes === 2 || lenBytes === 4); 296 | 297 | var lenOffset = this._offset; 298 | this._offset += lenBytes; 299 | var ret = cb(this); 300 | var len = this._offset - lenOffset - lenBytes; 301 | switch (lenBytes) { 302 | case 1: 303 | this._buffer.writeUInt8(len, lenOffset); 304 | break; 305 | case 2: 306 | this._buffer.writeUInt16BE(len, lenOffset); 307 | break; 308 | case 4: 309 | this._buffer.writeUInt32BE(len, lenOffset); 310 | break; 311 | default: 312 | throw (new Error('Invalid prefix length value')); 313 | } 314 | 315 | return (ret); 316 | }; 317 | 318 | } 319 | 320 | /* 321 | * We are expected to run on node v0.10, which lacks Map. Polyfill just the 322 | * bits of it we actually need here. Map is a bit faster than an object for our 323 | * task, but an object will do as a substitute on 0.10. 324 | */ 325 | if (typeof (Map) !== 'function') { 326 | var Map = function () { 327 | this._obj = Object.create(null); 328 | }; 329 | Map.prototype.set = function (k, v) { 330 | this._obj[k] = v; 331 | }; 332 | Map.prototype.get = function (k) { 333 | return (this._obj[k]); 334 | }; 335 | } 336 | 337 | function NameOffsetCache() { 338 | this.root = new Map(); 339 | } 340 | 341 | NameOffsetCache.prototype.add = function (name, offset) { 342 | this.root.set(name, offset); 343 | }; 344 | 345 | NameOffsetCache.prototype.getSuffix = function (name) { 346 | var i = -1; 347 | while (i < name.length) { 348 | var off = this.root.get(name.slice(i + 1)); 349 | if (off !== undefined) { 350 | return ({ 351 | index: i, 352 | offset: off 353 | }); 354 | } 355 | if ((i = name.indexOf('.', i + 1)) === -1) 356 | break; 357 | } 358 | return ({}); 359 | }; 360 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | /* 24 | * This excellent error creator concept was borrowed from Mark Cavage 25 | * https://github.com/mcavage/node-ldapjs/blob/master/lib/errors/index.js 26 | */ 27 | 28 | 29 | var util = require('util'); 30 | 31 | var CODES = { 32 | DNS_NO_ERROR: 0, 33 | DNS_PROTOCOL_ERROR: 1, 34 | DNS_CANNOT_PROCESS: 2, 35 | DNS_NO_NAME: 3, 36 | DNS_NOT_IMPLEMENTED: 4, 37 | DNS_REFUSED: 5, 38 | DNS_EXCEPTION: 6 39 | }; 40 | 41 | var ERRORS = []; 42 | 43 | function DnsError(name, code, msg, caller) { 44 | if (Error.captureStackTrace) 45 | Error.captureStackTrace(this, caller || DnsError); 46 | 47 | this.code = code; 48 | this.name = name; 49 | this.message = msg || name; 50 | } 51 | 52 | util.inherits(DnsError, Error); 53 | 54 | 55 | module.exports = {}; 56 | module.exports.DnsError = DnsError; 57 | 58 | Object.keys(CODES).forEach(function (code) { 59 | module.exports[code] = CODES[code]; 60 | 61 | if (CODES[code] === 0) 62 | return; 63 | 64 | var err = '', msg = ''; 65 | var pieces = code.split('_').slice(1); 66 | for (var i in pieces) { 67 | var lc = pieces[i].toLowerCase(); 68 | var key = lc.charAt(0).toUpperCase() + lc.slice(1); 69 | err += key; 70 | msg += key + ((i + 1) < pieces.length ? ' ' : ''); 71 | } 72 | 73 | if (!/\w+Error$/.test(err)) 74 | err += 'Error'; 75 | 76 | module.exports[err] = function (message, caller) { 77 | DnsError.call(this, 78 | err, 79 | CODES[code], 80 | message || msg, 81 | caller || module.exports[err]); 82 | }; 83 | module.exports[err].constructor = module.exports[err]; 84 | util.inherits(module.exports[err], DnsError); 85 | 86 | ERRORS[CODES[code]] = { 87 | err: err, 88 | message: msg 89 | }; 90 | }); 91 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | var fs = require('fs'); 25 | var path = require('path'); 26 | var assert = require('assert-plus'); 27 | 28 | var bunyan = require('bunyan'); 29 | 30 | var Server = require('./server'); 31 | var Query = require('./query'); 32 | var Protocol = require('./protocol'); 33 | var tsig = require('./tsig'); 34 | 35 | 36 | 37 | ////--- Globals 38 | 39 | var BUNYAN_SERIALIZERS = { 40 | err: bunyan.stdSerializers.err, 41 | query: function serializeQuery(q) { 42 | var out = { 43 | domain: q.name(), 44 | operation: q.operation(), 45 | type: q.type() 46 | }; 47 | return (out); 48 | } 49 | }; 50 | 51 | 52 | 53 | ///--- Exports 54 | module.exports = { 55 | 56 | createServer: function createServer(options) { 57 | options = options || {}; 58 | assert.object(options); 59 | 60 | var opts = { 61 | name: options.name || 'named', 62 | log: options.log || bunyan.createLogger({ 63 | name: 'named', 64 | level: 'warn', 65 | serializers: BUNYAN_SERIALIZERS 66 | }) 67 | }; 68 | return (new Server(opts)); 69 | }, 70 | 71 | Query: Query, 72 | 73 | Protocol: Protocol, 74 | 75 | tsig: tsig, 76 | 77 | bunyan: { serializers: BUNYAN_SERIALIZERS } 78 | 79 | }; 80 | 81 | /* Export all the record types at the top-level */ 82 | var subdir = path.join(__dirname, 'records'); 83 | fs.readdirSync(subdir).forEach(function (f) { 84 | var name = path.basename(f); 85 | if (/\w+\.js/.test(name)) { 86 | var k = name.split('.').shift().toUpperCase() + 'Record'; 87 | module.exports[k] = require(path.join(subdir, f)); 88 | } 89 | }); 90 | -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var protocol = require('./protocol'); 24 | var ipaddr = require('ipaddr.js'); 25 | var queryTypes = protocol.queryTypes; 26 | var nsRecord = require('./records/ns'); 27 | var soaRecord = require('./records/soa'); 28 | var assert = require('assert-plus'); 29 | var mod_tsig = require('./tsig'); 30 | 31 | function Query(opts) { 32 | assert.object(opts, 'options'); 33 | assert.buffer(opts.data, 'options.data'); 34 | assert.string(opts.family, 'options.family'); 35 | 36 | var q = protocol.decode(opts.data, 'message'); 37 | assert.object(q); 38 | 39 | delete (opts.data); 40 | this.src = opts; 41 | this.family = opts.family; 42 | 43 | this.id = q.header.id; 44 | this.query = q; 45 | this.envelopeCount = 1; 46 | this.tsigKey = undefined; 47 | this.lastResponse = undefined; 48 | 49 | this.reset(); 50 | this.response.header.qdCount = q.header.qdCount; 51 | assert.arrayOfObject(q.question); 52 | assert.strictEqual(q.question.length, 1); 53 | this.response.question = q.question; 54 | } 55 | 56 | Query.prototype.reset = function () { 57 | var q = this.query; 58 | this.lastResponse = this.response; 59 | var r = this.response = {}; 60 | ++this.envelopeCount; 61 | 62 | r.header = { 63 | id: q.header.id, 64 | qdCount: 0, 65 | anCount: 0, 66 | nsCount: 0, 67 | arCount: 0 68 | }; 69 | r.question = []; 70 | r.answer = []; 71 | r.authority = []; 72 | r.additional = []; 73 | 74 | /* Inherit the query's flags until we override them */ 75 | r.header.flags = Object.create(q.header.flags); 76 | 77 | /* Respond with NOTIMP unless we set otherwise */ 78 | r.header.flags.rcode = protocol.rCodes.NOTIMP; 79 | r.header.flags.qr = true; 80 | 81 | parseEdns.call(this); 82 | }; 83 | 84 | function parseEdns() { 85 | var q = this.query; 86 | var r = this.response; 87 | 88 | if (this.family === 'udp') 89 | this.maxReplySize = 512; 90 | else 91 | this.maxReplySize = 65530; 92 | 93 | if (typeof (q.additional) === 'object') { 94 | var add = q.additional; 95 | if (!Array.isArray(add)) 96 | add = [add]; 97 | 98 | var edns = add.filter(function (a) { 99 | return (a.rtype === queryTypes['OPT']); 100 | }).pop(); 101 | 102 | if (edns) { 103 | var maxReplySize = this.maxReplySize; 104 | maxReplySize = edns.rclass; 105 | if (maxReplySize > 1470) 106 | maxReplySize = 1470; 107 | r.additional.push({ 108 | name: '.', 109 | rtype: queryTypes['OPT'], 110 | rclass: maxReplySize, 111 | rttl: 0, 112 | rdata: { options: [] } 113 | }); 114 | r.header.arCount++; 115 | if (this.family === 'udp') 116 | this.maxReplySize = maxReplySize; 117 | } 118 | } 119 | } 120 | 121 | Query.prototype.answers = function answers() { 122 | return this.response.answer.map(function (r) { 123 | return { 124 | name: r.name, 125 | type: queryTypes[r.rtype], 126 | record: r.rdata, 127 | ttl: r.rttl 128 | }; 129 | }); 130 | }; 131 | 132 | Query.prototype.name = function () { 133 | return (this.query.question[0].name); 134 | }; 135 | 136 | Query.prototype.type = function type() { 137 | return (queryTypes[this.query.question[0].type]); 138 | }; 139 | 140 | var FLAGS = { 141 | qr: true, aa: true, rd: true, ra: true, ad: true, cd: true, 142 | response: 'qr', authoritative: 'aa', recursionDesired: 'rd', 143 | recursionAvailable: 'ra', authenticated: 'ad', noChecking: 'cd', 144 | checkingDisabled: 'cd' 145 | }; 146 | 147 | Query.prototype.testFlag = function testFlag(flag) { 148 | if (typeof (FLAGS[flag]) === 'string') 149 | flag = FLAGS[flag]; 150 | if (FLAGS[flag] !== true) 151 | throw (new Error('Invalid DNS header flag "' + flag + '"')); 152 | return (this.query.header.flags[flag] === true); 153 | }; 154 | 155 | Query.prototype.setFlag = function setFlag(flag) { 156 | if (typeof (FLAGS[flag]) === 'string') 157 | flag = FLAGS[flag]; 158 | if (FLAGS[flag] !== true) 159 | throw (new Error('Invalid DNS header flag "' + flag + '"')); 160 | this.response.header.flags[flag] = true; 161 | }; 162 | 163 | Query.prototype.clearFlag = function clearFlag(flag) { 164 | if (typeof (FLAGS[flag]) === 'string') 165 | flag = FLAGS[flag]; 166 | if (FLAGS[flag] !== true) 167 | throw (new Error('Invalid DNS header flag "' + flag + '"')); 168 | this.response.header.flags[flag] = false; 169 | }; 170 | 171 | Query.prototype.ixfrBase = function ixfrBase() { 172 | var q = this.query; 173 | assert.strictEqual(q.question[0].type, queryTypes['IXFR']); 174 | assert.arrayOfObject(q.authority); 175 | assert.strictEqual(q.authority[0].rtype, queryTypes['SOA']); 176 | return (q.authority[0].rdata.serial); 177 | }; 178 | 179 | Query.prototype.isSigned = function isSigned() { 180 | var tsig = this.query.additional[this.query.additional.length - 1]; 181 | return (tsig && tsig.rtype === queryTypes.TSIG); 182 | }; 183 | 184 | Query.prototype.verify = function verify(keys) { 185 | var tsig = this.query.additional[this.query.additional.length - 1]; 186 | if (tsig.rtype !== queryTypes.TSIG) 187 | return (false); 188 | try { 189 | var result = mod_tsig.verifyRequest(this.query, keys); 190 | } catch (err) { 191 | var log = this._log || this.log; 192 | if (log) { 193 | log.warn({err: err}, 'error processing TSIG'); 194 | } 195 | return (false); 196 | } 197 | if (result) 198 | this.tsigKey = keys[tsig.name]; 199 | return (result); 200 | }; 201 | 202 | Query.prototype.setError = function setError(name) { 203 | var code = protocol.rCodes[name.toUpperCase()]; 204 | if (code === undefined) { 205 | /* Try stripping off a leading E. */ 206 | code = protocol.rCodes[name.toUpperCase().slice(1)]; 207 | if (code === undefined) 208 | throw new Error('invalid error code %s', name); 209 | } 210 | this.response.header.flags.rcode = code; 211 | }; 212 | 213 | Query.prototype.error = function () { 214 | var code = protocol.rCodes[this.response.header.flags.rcode]; 215 | return code.toLowerCase(); 216 | }; 217 | 218 | Query.prototype.operation = function operation() { 219 | var h = this.query.header; 220 | var op = protocol.opCodes[h.flags.opcode]; 221 | if (op === undefined) 222 | throw new Error('invalid operation %d', h.flags.opcode); 223 | if (op === 'IQUERY') 224 | throw new Error('iquery operations are obselete'); 225 | return (op.toLowerCase()); 226 | }; 227 | 228 | Query.prototype.encode = function encode(recur) { 229 | if (this.tsigKey !== undefined) { 230 | if (this.lastResponse === undefined) { 231 | mod_tsig.signResponse(this.response, this.tsigKey, 232 | this.query); 233 | } else { 234 | mod_tsig.signTcpContinuation(this.response, 235 | this.tsigKey, this.lastResponse); 236 | } 237 | } 238 | 239 | var encoded = protocol.encode(this.response, 'message'); 240 | 241 | if (encoded.length > this.maxReplySize) { 242 | if (recur || this.family !== 'udp') 243 | throw (new Error('Truncated answer message ' + 244 | 'too large to send: ' + encoded.length + ' bytes')); 245 | 246 | var log = this._log || this.log; 247 | if (log) { 248 | log.info({ 249 | len: encoded.length, 250 | max_len: this.maxReplySize 251 | }, 'truncated response sent'); 252 | } 253 | 254 | var r = this.response; 255 | r.header.flags.tc = true; 256 | r.header.anCount = 0; 257 | r.header.nsCount = 0; 258 | r.header.arCount = 0; 259 | r.answer = []; 260 | r.authority = []; 261 | r.additional = []; 262 | return (encode.call(this, true)); 263 | } 264 | 265 | return (encoded); 266 | }; 267 | Query.prototype.addAuthority = function (name, record, ttl) { 268 | assert.string(name, 'name'); 269 | assert.object(record, 'record'); 270 | assert.optionalNumber(ttl, 'ttl'); 271 | 272 | var authority = { 273 | name: name, 274 | rclass: 1, // INET 275 | rttl: ttl || 5, 276 | rdata: record 277 | }; 278 | if (record instanceof nsRecord) 279 | authority.rtype = queryTypes['NS']; 280 | else if (record instanceof soaRecord) 281 | authority.rtype = queryTypes['SOA']; 282 | else 283 | throw (new TypeError('invalid type for authority section ' + 284 | 'record')); 285 | 286 | var r = this.response; 287 | r.authority.push(authority); 288 | r.header.nsCount++; 289 | /* This is not great, but is necessary to be backwards compatible. */ 290 | r.header.flags.aa = true; 291 | }; 292 | Query.prototype.addAdditional = function (name, record, ttl) { 293 | assert.string(name, 'name'); 294 | assert.object(record, 'record'); 295 | assert.optionalNumber(ttl, 'ttl'); 296 | 297 | var add = { 298 | name: name, 299 | rtype: queryTypes[record._type], 300 | rclass: 1, // INET 301 | rttl: ttl || 5, 302 | rdata: record 303 | }; 304 | 305 | var r = this.response; 306 | r.additional.push(add); 307 | r.header.arCount++; 308 | }; 309 | 310 | Query.prototype.addAnswer = function (name, record, ttl) { 311 | assert.string(name, 'name'); 312 | assert.object(record, 'record'); 313 | assert.optionalNumber(ttl, 'ttl'); 314 | 315 | if (!queryTypes.hasOwnProperty(record._type)) 316 | throw (new TypeError('unknown queryType: ' + record._type)); 317 | 318 | var r = this.response; 319 | // return ok, we have an answer 320 | r.header.flags.rcode = protocol.rCodes.NOERROR; 321 | var answer = { 322 | name: name, 323 | rtype: queryTypes[record._type], 324 | rclass: 1, // INET 325 | rttl: ttl || 5, 326 | rdata: record 327 | }; 328 | 329 | r.answer.push(answer); 330 | r.header.anCount++; 331 | }; 332 | 333 | function parseQuery(opts) { 334 | return (new Query(opts)); 335 | } 336 | 337 | module.exports = { 338 | parse: parseQuery 339 | }; 340 | -------------------------------------------------------------------------------- /lib/records/a.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function A(target) { 27 | assert.string(target, 'target IPv4Addr'); 28 | 29 | this.target = target; 30 | this._type = 'A'; 31 | } 32 | module.exports = A; 33 | 34 | 35 | A.prototype.valid = function valid() { 36 | var self = this, model = {}; 37 | model = { 38 | target: validators.IPv4 39 | }; 40 | return validators.validate(self, model); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/records/aaaa.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function AAAA(target) { 27 | assert.string(target, 'target IPv6Addr'); 28 | 29 | this.target = target; 30 | this._type = 'AAAA'; 31 | } 32 | module.exports = AAAA; 33 | 34 | AAAA.prototype.valid = function valid() { 35 | var self = this, model = {}; 36 | model = { 37 | target: validators.IPv6 38 | }; 39 | return validators.validate(self, model); 40 | }; 41 | -------------------------------------------------------------------------------- /lib/records/cname.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function CNAME(target) { 27 | assert.string(target, 'target'); 28 | 29 | this.target = target; 30 | this._type = 'CNAME'; 31 | } 32 | module.exports = CNAME; 33 | 34 | 35 | CNAME.prototype.valid = function valid() { 36 | var self = this, model = {}; 37 | model = { 38 | target: validators.nsName 39 | }; 40 | return validators.validate(self, model); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/records/mx.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function MX(exchange, opts) { 27 | assert.string(exchange, 'exchange'); 28 | 29 | if (!opts) 30 | opts = {}; 31 | 32 | var defaults = { 33 | priority: 0, 34 | ttl: 600 35 | }; 36 | 37 | Object.keys(defaults).forEach(function (key) { 38 | if (opts[key] !== undefined) 39 | return; 40 | opts[key] = defaults[key]; 41 | }); 42 | 43 | this.exchange = exchange; 44 | this.ttl = opts.ttl; 45 | this.priority = opts.priority; 46 | this._type = 'MX'; 47 | } 48 | module.exports = MX; 49 | 50 | 51 | MX.prototype.valid = function valid() { 52 | var self = this, model = {}; 53 | model = { 54 | exchange: validators.nsName, 55 | ttl: validators.UInt32BE, 56 | priority: validators.UInt16BE 57 | }; 58 | return validators.validate(self, model); 59 | }; 60 | -------------------------------------------------------------------------------- /lib/records/ns.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function NS(target) { 27 | assert.string(target, 'target'); 28 | 29 | this.target = target; 30 | this._type = 'NS'; 31 | } 32 | module.exports = NS; 33 | 34 | 35 | NS.prototype.valid = function valid() { 36 | var self = this, model = {}; 37 | model = { 38 | target: validators.nsName 39 | }; 40 | return validators.validate(self, model); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/records/ptr.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function PTR(target) { 27 | assert.string(target, 'target'); 28 | 29 | this.target = target; 30 | this._type = 'PTR'; 31 | } 32 | module.exports = PTR; 33 | 34 | 35 | PTR.prototype.valid = function valid() { 36 | var self = this, model = {}; 37 | model = { 38 | target: validators.nsName 39 | }; 40 | return validators.validate(self, model); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/records/soa.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function SOA(host, opts) { 27 | assert.string(host, 'host'); 28 | assert.optionalObject(opts, 'options'); 29 | 30 | if (!opts) 31 | opts = {}; 32 | 33 | var defaults = { 34 | admin: 'hostmaster.' + host, 35 | serial: 0, 36 | refresh: 86400, 37 | retry: 7200, 38 | expire: 1209600, 39 | ttl: 10800 40 | }; 41 | 42 | Object.keys(defaults).forEach(function (key) { 43 | if (opts[key] !== undefined) 44 | return; 45 | opts[key] = defaults[key]; 46 | }); 47 | 48 | this.host = host; 49 | this.admin = opts.admin; 50 | this.serial = opts.serial; 51 | this.refresh = opts.refresh; 52 | this.retry = opts.retry; 53 | this.expire = opts.expire; 54 | this.ttl = opts.ttl; 55 | this._type = 'SOA'; 56 | } 57 | module.exports = SOA; 58 | 59 | 60 | SOA.prototype.valid = function () { 61 | var self = this, model = {}; 62 | 63 | model = { 64 | host: validators.nsName, 65 | admin: validators.nsName, 66 | serial: validators.UInt32BE, 67 | refresh: validators.UInt32BE, 68 | retry: validators.UInt32BE, 69 | expire: validators.UInt32BE, 70 | ttl: validators.UInt32BE 71 | }; 72 | 73 | return validators.validate(self, model); 74 | }; 75 | -------------------------------------------------------------------------------- /lib/records/srv.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function SRV(target, port, opts) { 27 | assert.string(target, 'target host'); 28 | assert.number(port, 'port'); 29 | assert.optionalObject(opts, 'options'); 30 | 31 | if (!opts) 32 | opts = {}; 33 | 34 | var defaults = { 35 | priority: 0, 36 | weight: 10 37 | }; 38 | 39 | Object.keys(defaults).forEach(function (key) { 40 | if (opts[key] !== undefined) 41 | return; 42 | opts[key] = defaults[key]; 43 | }); 44 | 45 | this.target = target; 46 | this.port = port; 47 | this.weight = opts.weight; 48 | this.priority = opts.priority; 49 | this._type = 'SRV'; 50 | } 51 | module.exports = SRV; 52 | 53 | 54 | SRV.prototype.valid = function () { 55 | var self = this, model = {}; 56 | model = { 57 | target: validators.nsText, // XXX 58 | port: validators.UInt16BE, 59 | weight: validators.UInt16BE, 60 | priority: validators.UInt16BE 61 | }; 62 | return validators.validate(self, model); 63 | }; 64 | -------------------------------------------------------------------------------- /lib/records/txt.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../validators'); 24 | var assert = require('assert-plus'); 25 | 26 | function TXT(target) { 27 | assert.string(target, 'target'); 28 | 29 | this.target = target; 30 | this._type = 'TXT'; 31 | } 32 | module.exports = TXT; 33 | 34 | 35 | TXT.prototype.valid = function () { 36 | var self = this, model = {}; 37 | model = { 38 | target: validators.nsText 39 | }; 40 | return validators.validate(self, model); 41 | }; 42 | -------------------------------------------------------------------------------- /lib/tsig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Joyent, Inc 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* 24 | * Transaction Signatures for DNS (see RFC2845) 25 | */ 26 | 27 | module.exports = { 28 | signRequest: signRequest, 29 | signResponse: signResponse, 30 | signTcpContinuation: signTcpContinuation, 31 | verifyRequest: verifyRequest, 32 | verifyResponse: verifyResponse, 33 | verifyTcpContinuation: verifyTcpContinuation 34 | }; 35 | 36 | var protocol = require('./protocol'); 37 | var assert = require('assert-plus'); 38 | var crypto = require('crypto'); 39 | 40 | var ALGOS = { 41 | 'hmac-md5': 'md5', 42 | 'hmac-md5.sig-alg.reg.int': 'md5', 43 | 'hmac-sha1': 'sha1', 44 | 'hmac-sha256': 'sha256', 45 | 'hmac-sha384': 'sha384', 46 | 'hmac-sha512': 'sha512' 47 | }; 48 | var ALGOREV = {}; 49 | Object.keys(ALGOS).forEach(function (k) { 50 | ALGOREV[ALGOS[k]] = k; 51 | }); 52 | 53 | function assertKey(key) { 54 | assert.string(key.name, 'key.name'); 55 | assert.string(key.algorithm, 'key.algorithm'); 56 | assert.buffer(key.data, 'key.data'); 57 | assert.string(ALGOS[key.algorithm], 'supported algorithm'); 58 | } 59 | 60 | function verifyRequest(msg, keys) { 61 | assert.object(msg, 'message'); 62 | assert.object(msg.header, 'message.header'); 63 | assert.object(keys, 'keys'); 64 | assert.ok(Object.keys(keys).length > 0, 'non-empty keys object'); 65 | return (verify(msg, keys, 'tsigSignDataReq')); 66 | } 67 | 68 | function verifyResponse(msg, keys, reqMsg) { 69 | assert.object(msg, 'message'); 70 | assert.object(msg.header, 'message.header'); 71 | assert.object(keys, 'keys'); 72 | assert.object(reqMsg, 'signedRequestMessage'); 73 | assert.object(reqMsg.header, 'signedRequestMessage.header'); 74 | assert.arrayOfObject(reqMsg.additional, 75 | 'signedRequestMessage.additional'); 76 | var tsigs = reqMsg.additional.filter(function (rr) { 77 | return (rr.rtype === protocol.queryTypes.TSIG); 78 | }); 79 | assert.ok(tsigs.length === 1, 'signedRequestMessage TSIG signature'); 80 | assert.ok(Object.keys(keys).length > 0, 'non-empty keys object'); 81 | return (verify(msg, keys, 'tsigSignDataResp', tsigs[0].rdata.mac)); 82 | } 83 | 84 | function verifyTcpContinuation(msg, keys, lastMsg) { 85 | assert.object(msg, 'message'); 86 | assert.object(msg.header, 'message.header'); 87 | assert.object(keys, 'keys'); 88 | assert.object(lastMsg, 'signedLastMessage'); 89 | assert.object(lastMsg.header, 'signedLastMessage.header'); 90 | assert.arrayOfObject(lastMsg.additional, 91 | 'signedLastMessage.additional'); 92 | var tsigs = lastMsg.additional.filter(function (rr) { 93 | return (rr.rtype === protocol.queryTypes.TSIG); 94 | }); 95 | assert.ok(tsigs.length === 1, 'signedLastMessage TSIG signature'); 96 | assert.ok(Object.keys(keys).length > 0, 'non-empty keys object'); 97 | return (verify(msg, keys, 'tsigSignTcp', tsigs[0].rdata.mac)); 98 | } 99 | 100 | function signRequest(msg, key) { 101 | assert.object(msg, 'message'); 102 | assert.object(msg.header, 'message.header'); 103 | assertKey(key); 104 | return (sign(msg, key, 'tsigSignDataReq')); 105 | } 106 | 107 | function signResponse(msg, key, reqMsg) { 108 | assert.object(msg, 'message'); 109 | assert.object(msg.header, 'message.header'); 110 | assertKey(key); 111 | assert.object(reqMsg.header, 'signedRequestMessage.header'); 112 | assert.arrayOfObject(reqMsg.additional, 113 | 'signedRequestMessage.additional'); 114 | var tsigs = reqMsg.additional.filter(function (rr) { 115 | return (rr.rtype === protocol.queryTypes.TSIG); 116 | }); 117 | assert.ok(tsigs.length === 1, 'signedRequestMessage TSIG signature'); 118 | return (sign(msg, key, 'tsigSignDataResp', tsigs[0].rdata.mac)); 119 | } 120 | 121 | function signTcpContinuation(msg, key, lastMsg) { 122 | assert.object(msg, 'message'); 123 | assert.object(msg.header, 'message.header'); 124 | assertKey(key); 125 | assert.object(lastMsg.header, 'signedLastMessage.header'); 126 | assert.arrayOfObject(lastMsg.additional, 127 | 'signedLastMessage.additional'); 128 | var tsigs = lastMsg.additional.filter(function (rr) { 129 | return (rr.rtype === protocol.queryTypes.TSIG); 130 | }); 131 | assert.ok(tsigs.length === 1, 'signedLastMessage TSIG signature'); 132 | return (sign(msg, key, 'tsigSignTcp', tsigs[0].rdata.mac)); 133 | } 134 | 135 | function verify(msg, keys, format, reqMac) { 136 | var newMsg = Object.create(msg); 137 | newMsg.header = Object.create(msg.header); 138 | newMsg.additional = msg.additional.slice(); 139 | var tsig = newMsg.additional.pop(); 140 | assert.strictEqual(tsig.rtype, protocol.queryTypes.TSIG); 141 | newMsg.header.arCount--; 142 | 143 | var kname = tsig.name; 144 | var key = keys[kname]; 145 | if (key === undefined) 146 | throw (new Error('Unknown TSIG key "' + kname + '"')); 147 | assertKey(key); 148 | 149 | var algo = ALGOS[tsig.rdata.algorithm]; 150 | assert.strictEqual(algo, ALGOS[key.algorithm], 'matching algorithm'); 151 | 152 | var tsign = {}; 153 | tsign.message = protocol.encode(newMsg, 'message'); 154 | tsign.rname = tsig.name; 155 | assert.strictEqual(tsig.rclass, protocol.qClasses.ANY); 156 | tsign.rclass = tsig.rclass; 157 | assert.strictEqual(tsig.rttl, 0); 158 | tsign.rttl = tsig.rttl; 159 | 160 | tsign.algorithm = tsig.rdata.algorithm; 161 | tsign.time = tsig.rdata.time; 162 | tsign.fudge = tsig.rdata.fudge; 163 | tsign.error = tsig.rdata.error; 164 | tsign.other = tsig.rdata.other; 165 | if (reqMac !== undefined) 166 | tsign.rmac = reqMac; 167 | 168 | var now = new Date(); 169 | var delta = now.getTime() - tsign.time.getTime(); 170 | 171 | var blob = protocol.encode(tsign, format); 172 | assert.buffer(blob); 173 | 174 | var hmac = crypto.createHmac(algo, key.data); 175 | hmac.update(blob); 176 | var digest = hmac.digest(); 177 | 178 | var comp1 = crypto.createHmac(algo, key.data). 179 | update(tsig.rdata.mac).digest().toString('base64'); 180 | var comp2 = crypto.createHmac(algo, key.data). 181 | update(digest).digest().toString('base64'); 182 | 183 | return (comp1 === comp2 && delta > 0 && delta < tsign.fudge * 1000); 184 | } 185 | 186 | function sign(msg, key, format, reqMac) { 187 | var algo = ALGOS[key.algorithm]; 188 | 189 | var tsign = {}; 190 | if (reqMac !== undefined) 191 | tsign.rmac = reqMac; 192 | 193 | tsign.message = protocol.encode(msg, 'message'); 194 | tsign.rname = key.name; 195 | tsign.rclass = protocol.qClasses.ANY; 196 | tsign.rttl = 0; 197 | 198 | tsign.algorithm = ALGOREV[algo]; 199 | tsign.time = new Date(); 200 | tsign.fudge = 300; 201 | tsign.error = 0; 202 | tsign.other = new Buffer(0); 203 | 204 | var blob = protocol.encode(tsign, format); 205 | assert.buffer(blob); 206 | 207 | var hmac = crypto.createHmac(algo, key.data); 208 | hmac.update(blob); 209 | var digest = hmac.digest(); 210 | 211 | var tsig = {}; 212 | tsig.name = tsign.rname; 213 | tsig.rtype = protocol.queryTypes.TSIG; 214 | tsig.rclass = tsign.rclass; 215 | tsig.rttl = tsign.rttl; 216 | tsig.rdata = {}; 217 | tsig.rdata.algorithm = tsign.algorithm; 218 | tsig.rdata.time = tsign.time; 219 | tsig.rdata.fudge = tsign.fudge; 220 | tsig.rdata.origid = msg.header.id; 221 | tsig.rdata.error = tsign.error; 222 | tsig.rdata.other = tsign.other; 223 | 224 | tsig.rdata.mac = digest; 225 | 226 | msg.header.arCount++; 227 | msg.additional.push(tsig); 228 | } 229 | -------------------------------------------------------------------------------- /lib/validators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var ipaddr = require('ipaddr.js'); 24 | var net = require('net'); 25 | 26 | module.exports = { 27 | nsName: function (v) { 28 | // hostname regex per RFC1123 29 | /*JSSTYLED*/ 30 | 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; 31 | if (typeof (v) !== 'string') 32 | return false; 33 | if (v.length > 255) 34 | return false; 35 | 36 | if (reg.test(v)) { 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | }, 42 | UInt32BE: function (v) { 43 | if (typeof (v) === 'number') { 44 | var n = parseInt(v, 10); 45 | if (n !== NaN && n < 4294967295) { 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } else { 51 | return false; 52 | } 53 | }, 54 | UInt16BE: function (v) { 55 | if (typeof (v) === 'number') { 56 | var n = parseInt(v, 10); 57 | if (n !== NaN && n < 65535) { 58 | return true; 59 | } else { 60 | return false; 61 | } 62 | } else { 63 | return false; 64 | } 65 | }, 66 | nsText: function (v) { 67 | if (typeof (v) === 'string') { 68 | if (v.length < 256) 69 | return true; 70 | } 71 | return false; 72 | }, 73 | IPv4: function (v) { 74 | return net.isIPv4(v); 75 | }, 76 | IPv6: function (v) { 77 | return net.isIPv6(v); 78 | }, 79 | validate: function (obj, model) { 80 | var result = true; 81 | for (var v in model) { 82 | if (!model[v](obj[v])) { 83 | result = false; 84 | break; 85 | } 86 | } 87 | return result; 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mname", 3 | "description": "DNS server library for node.js", 4 | "version": "1.5.1", 5 | "author": "arekinath ", 6 | "contributors": [ 7 | "Mark Cavage", 8 | "Alex Wilson", 9 | "Lorenz Brun", 10 | "Trevor Orsztynowicz", 11 | "Joshua M. Clulow", 12 | "Robert Bogart" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/arekinath/node-mname.git" 17 | }, 18 | "main": "lib/index.js", 19 | "engines": { 20 | "node": ">=0.10" 21 | }, 22 | "dependencies": { 23 | "assert-plus": "^1.0.0", 24 | "bunyan": "^1.5.1", 25 | "ipaddr.js": "^1.0.0", 26 | "vasync": "^2.2.0" 27 | }, 28 | "devDependencies": { 29 | "nodeunit": "0.9.1" 30 | }, 31 | "license": "MIT" 32 | } 33 | -------------------------------------------------------------------------------- /test/dig.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | // Quick and dirty 'dig' wrapper 25 | 26 | var assert = require('assert'); 27 | var spawn = require('child_process').spawn; 28 | var sprintf = require('util').format; 29 | var path = require('path'); 30 | 31 | 32 | ///--- Globals 33 | 34 | var DIG = 'dig'; 35 | 36 | 37 | 38 | ///--- Helpers 39 | 40 | function parseAnswer(tokens) { 41 | var t = tokens.filter(function (v) { 42 | return (v !== '' ? v : undefined); 43 | }); 44 | 45 | var r = { 46 | name: t[0], 47 | ttl: parseInt(t[1], 10), 48 | type: t[3], 49 | target: t[4] 50 | }; 51 | 52 | return (r); 53 | } 54 | 55 | 56 | function parseDig(output) { 57 | var lines = output.split(/\n/); 58 | var section = 'header'; 59 | 60 | var results = { 61 | id: null, 62 | status: null, 63 | question: null, 64 | tsigFail: false, 65 | answers: [], 66 | additional: [], 67 | authority: [] 68 | }; 69 | 70 | lines.forEach(function (l) { 71 | if (l === '') { 72 | section = undefined; 73 | } else if (/^;; QUESTION SECTION:/.test(l)) { 74 | section = 'question'; 75 | } else if (/^;; ANSWER SECTION:/.test(l)) { 76 | section = 'answer'; 77 | } else if (/^;; ADDITIONAL SECTION:/.test(l)) { 78 | section = 'additional'; 79 | } else if (/^;; AUTHORITY SECTION:/.test(l)) { 80 | section = 'authority'; 81 | } else if (/^; <<>> DiG.* axfr /i.test(l)) { 82 | section = 'answer'; 83 | } 84 | 85 | if (section === 'question') { 86 | if (/^;([A-Za-z0-9])*\./.test(l)) { 87 | results.question = 88 | l.match(/([A-Za-z0-9_\-\.])+/)[0]; 89 | } 90 | } 91 | 92 | if (section === 'answer') { 93 | if (/^([_A-Za-z0-9])+/.test(l)) { 94 | var tokens = l.match(/(.*)/)[0].split(/\t/); 95 | var answer = parseAnswer(tokens); 96 | if (answer) 97 | results.answers.push(answer); 98 | } 99 | } 100 | 101 | if (/^;; ->>HEADER<<-/.test(l)) { 102 | var m = l.match(/status: ([A-Z]+)/) 103 | results.status = m[1].toLowerCase(); 104 | m = l.match(/id: ([0-9]+)/); 105 | results.id = parseInt(m[1], 10); 106 | } 107 | 108 | if (/Some TSIG could not be validated/.test(l) || 109 | /tsig verify failure/.test(l)) { 110 | results.tsigFail = true; 111 | } 112 | 113 | if (/^; Transfer failed/.test(l)) { 114 | results.status = 'failed'; 115 | } 116 | }); 117 | 118 | return (results); 119 | } 120 | 121 | 122 | 123 | ///--- API 124 | 125 | function dig(name, type, options, callback) { 126 | if (typeof (name) !== 'string') 127 | throw new TypeError('name (string) is required'); 128 | if (typeof (type) !== 'string') 129 | throw new TypeError('type (string) is required'); 130 | if (typeof (options) === 'function') { 131 | callback = options; 132 | options = {}; 133 | } 134 | 135 | type = type.toUpperCase(); 136 | 137 | var opts = []; 138 | if (options.server) { 139 | opts.push('@' + options.server); 140 | } 141 | if (options.port) { 142 | opts.push('-p'); 143 | opts.push(options.port); 144 | } 145 | if (options.key) { 146 | opts.push('-y'); 147 | var key = options.key; 148 | opts.push(key.algorithm + ':' + key.name + ':' + 149 | key.data.toString('base64')); 150 | } 151 | opts = opts.concat(['-t', type, name, '+time=1', '+retry=0']); 152 | 153 | var kid = spawn('dig', opts, { 154 | stdio: ['pipe', 'pipe', 'inherit'], 155 | }); 156 | kid.stdin.end(); 157 | var stdout = []; 158 | kid.stdout.on('readable', function () { 159 | var b; 160 | while ((b = kid.stdout.read()) !== null) { 161 | stdout.push(b); 162 | } 163 | }); 164 | kid.on('exit', function (exitStatus) { 165 | if (exitStatus !== 0) { 166 | return (callback( 167 | new Error('dig exited with status ' + exitStatus))); 168 | } 169 | return (callback(null, parseDig( 170 | Buffer.concat(stdout).toString('ascii')))); 171 | }); 172 | } 173 | 174 | 175 | 176 | ///--- Exports 177 | 178 | module.exports = dig; 179 | -------------------------------------------------------------------------------- /test/dnsbuffer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | /* 24 | * using the 'dig' utility, as its the only tool that supports alternative ports 25 | * when doing a query. 26 | * 27 | * When this module is loaded it will return an object containing an array of 28 | * samples that you can use to test serializers, protocol generators, create a 29 | * raw DNS client, etc. 30 | * 31 | * Each sample is an object with an 'id', 'description', 'raw', and 'data'. 32 | * The ID is used so that adding and removing samples out of order will not 33 | * affect external references to them in tests. 34 | * 35 | * The data is put through an encoder that will turn this string into a raw 36 | * buffer. That way, samples may be loaded from file that can be read by a 37 | * (mortal) human being. 38 | * 39 | * When the sample is encoded it places a "raw" value in the object. If you 40 | * have one there it will be over-written. 41 | */ 42 | 43 | var samples = [ 44 | { 45 | id: 0, 46 | description: 'query ns1.joyent.dev (A)', 47 | data: "0f 34 81 00 00 01 00 00 00 00 00 00 03 6e 73 31 06 6a 6f 79 65 " + 48 | "6e 74 03 64 65 76 00 00 01 00 01", 49 | type: 'message', 50 | decoded: { 51 | header: { 52 | id: 3892, 53 | flags: { 54 | qr: true, opcode: 0, aa: false, 55 | tc: false, rd: true, ra: false, 56 | z: false, ad: false, cd: false, 57 | rcode: 0 58 | }, 59 | qdCount: 1, 60 | anCount: 0, 61 | nsCount: 0, 62 | arCount: 0 63 | }, 64 | question: [ 65 | { 66 | name: 'ns1.joyent.dev', 67 | type: 1, qclass: 1 68 | } 69 | ], 70 | answer: [], 71 | authority: [], 72 | additional: [] 73 | } 74 | }, 75 | { 76 | id: 1, 77 | description: 'query ns1.joyent.dev (AAAA)', 78 | data: "b9 dd 01 00 00 01 00 00 00 00 00 00 03 6e 73 31 06 6a 6f 79 65 " + 79 | "6e 74 03 64 65 76 00 00 1c 00 01", 80 | type: 'message' 81 | }, 82 | { 83 | id: 2, 84 | description: 'ptr dns-sd', 85 | data: 'c4 2f 01 00 00 01 00 00 00 00 00 00 02 6c 62 07 ' + 86 | '5f 64 6e 73 2d 73 64 04 5f 75 64 70 01 30 01 34 ' + 87 | '02 32 30 03 31 37 32 07 69 6e 2d 61 64 64 72 04 ' + 88 | '61 72 70 61 00 00 0c 00 01', 89 | type: 'message' 90 | }, 91 | { 92 | id: 3, 93 | description: 'txt response', 94 | data: '4c 91 85 00 00 01 00 02 00 03 00 06 07 63 6f 6f ' + 95 | '70 65 72 69 03 6e 65 74 00 00 10 00 01 c0 0c 00 ' + 96 | '10 00 01 00 00 1c 20 00 3a 39 76 3d 73 70 66 31 ' + 97 | '20 6d 78 20 6d 78 3a 73 6f 72 75 73 2e 63 6f 6f ' + 98 | '70 65 72 69 2e 6e 65 74 20 6d 78 3a 6c 61 6d 69 ' + 99 | '6e 61 2e 63 6f 6f 70 65 72 69 2e 6e 65 74 20 2d ' + 100 | '61 6c 6c c0 0c 00 10 00 01 00 00 1c 20 00 6d 6c ' + 101 | '74 69 6e 66 6f 69 6c 2d 73 69 74 65 2d 76 65 72 ' + 102 | '69 66 69 63 61 74 69 6f 6e 3a 20 32 64 37 63 32 ' + 103 | '61 32 38 34 35 36 64 31 66 66 36 66 32 64 65 39 ' + 104 | '39 61 66 36 32 34 32 39 30 37 65 35 61 61 31 66 ' + 105 | '66 31 63 3d 61 36 34 37 32 61 30 32 32 31 33 31 ' + 106 | '37 33 66 32 35 31 31 66 32 36 39 62 66 63 31 36 ' + 107 | '39 34 61 31 30 39 39 35 63 30 36 38 c0 0c 00 02 ' + 108 | '00 01 00 00 1c 20 00 09 06 6c 61 6d 69 6e 61 c0 ' + 109 | '0c c0 0c 00 02 00 01 00 00 1c 20 00 08 05 78 79 ' + 110 | '6c 65 6d c0 0c c0 0c 00 02 00 01 00 00 1c 20 00 ' + 111 | '08 05 73 74 69 70 65 c0 0c c1 11 00 01 00 01 00 ' + 112 | '00 1c 20 00 04 a8 eb 56 ec c1 11 00 1c 00 01 00 ' + 113 | '00 1c 20 00 10 26 04 01 80 00 03 07 c2 00 00 00 ' + 114 | '00 00 00 e5 6a c0 fd 00 01 00 01 00 00 1c 20 00 ' + 115 | '04 73 92 4c 41 c0 fd 00 1c 00 01 00 00 1c 20 00 ' + 116 | '10 24 02 74 00 50 08 00 01 00 00 00 00 00 00 00 ' + 117 | '04 c0 e8 00 01 00 01 00 00 1c 20 00 04 96 6b 48 ' + 118 | '5a c0 e8 00 1c 00 01 00 00 1c 20 00 10 24 04 94 ' + 119 | '00 00 02 00 00 02 16 3e ff fe f0 33 74', 120 | type: 'message' 121 | }, 122 | { 123 | id: 4, 124 | description: 'mx response', 125 | data: 'dd 08 85 00 00 01 00 02 00 03 00 08 07 63 6f 6f ' + 126 | '70 65 72 69 03 6e 65 74 00 00 0f 00 01 c0 0c 00 ' + 127 | '0f 00 01 00 00 1c 20 00 0b 00 14 06 6c 61 6d 69 ' + 128 | '6e 61 c0 0c c0 0c 00 0f 00 01 00 00 1c 20 00 0a ' + 129 | '00 0a 05 73 6f 72 75 73 c0 0c c0 0c 00 02 00 01 ' + 130 | '00 00 1c 20 00 08 05 73 74 69 70 65 c0 0c c0 0c ' + 131 | '00 02 00 01 00 00 1c 20 00 08 05 78 79 6c 65 6d ' + 132 | 'c0 0c c0 0c 00 02 00 01 00 00 1c 20 00 02 c0 2b ' + 133 | 'c0 42 00 01 00 01 00 00 1c 20 00 04 c0 c6 5e 65 ' + 134 | 'c0 42 00 1c 00 01 00 00 1c 20 00 10 26 07 56 00 ' + 135 | '01 68 00 00 00 00 00 00 00 00 00 0a c0 2b 00 01 ' + 136 | '00 01 00 00 1c 20 00 04 96 6b 48 5a c0 2b 00 1c ' + 137 | '00 01 00 00 1c 20 00 10 24 04 94 00 00 02 00 00 ' + 138 | '02 16 3e ff fe f0 33 74 c0 56 00 01 00 01 00 00 ' + 139 | '1c 20 00 04 a8 eb 56 ec c0 56 00 1c 00 01 00 00 ' + 140 | '1c 20 00 10 26 04 01 80 00 03 07 c2 00 00 00 00 ' + 141 | '00 00 e5 6a c0 6a 00 01 00 01 00 00 1c 20 00 04 ' + 142 | '73 92 4c 41 c0 6a 00 1c 00 01 00 00 1c 20 00 10 ' + 143 | '24 02 74 00 50 08 00 01 00 00 00 00 00 00 00 04', 144 | type: 'message' 145 | }, 146 | { 147 | id: 5, 148 | description: 'srv response with additional', 149 | data: 'cb b4 81 00 00 01 00 04 00 00 00 01 05 5f 6c 64 ' + 150 | '61 70 04 5f 74 63 70 04 75 66 64 73 04 63 6f 61 ' + 151 | '6c 06 6a 6f 79 65 6e 74 02 75 73 00 00 21 00 01 ' + 152 | 'c0 0c 00 21 00 01 00 00 00 05 00 2d 00 00 00 0a ' + 153 | '05 6e 24 32 36 65 38 31 32 34 31 2d 32 31 66 64 ' + 154 | '2d 34 39 66 62 2d 61 64 35 33 2d 38 33 61 31 38 ' + 155 | '35 66 32 61 39 62 38 c0 17 c0 0c 00 21 00 01 00 ' + 156 | '00 00 05 00 08 00 00 00 0a 05 6f c0 42 c0 0c 00 ' + 157 | '21 00 01 00 00 00 05 00 08 00 00 00 0a 05 70 c0 ' + 158 | '42 c0 0c 00 21 00 01 00 00 00 05 00 08 00 00 00 ' + 159 | '0a 05 71 c0 42 c0 42 00 01 00 01 00 00 00 05 00 ' + 160 | '04 0a 63 63 12', 161 | type: 'message' 162 | }, 163 | { 164 | id: 6, 165 | description: 'response with additional count but no ' + 166 | 'additionals (a la binder bug)', 167 | data: '3b b9 81 00 00 01 00 01 00 00 00 01 04 75 66 64 ' + 168 | '73 04 63 6f 61 6c 06 6a 6f 79 65 6e 74 02 75 73 ' + 169 | '00 00 01 00 01 c0 0c 00 01 00 01 00 00 00 05 00 ' + 170 | '04 0a 63 63 12', 171 | encode: false, 172 | decoded: { 173 | header: { 174 | id: 15289, 175 | flags: { 176 | qr: true, opcode: 0, aa: false, 177 | tc: false, rd: true, ra: false, 178 | z: false, ad: false, cd: false, 179 | rcode: 0 180 | }, 181 | qdCount: 1, 182 | anCount: 1, 183 | nsCount: 0, 184 | arCount: 1 185 | }, 186 | question: [ 187 | { 188 | name: 'ufds.coal.joyent.us', 189 | type: 1, qclass: 1 190 | } 191 | ], 192 | answer: [ 193 | { 194 | name: 'ufds.coal.joyent.us', 195 | rtype: 1, rclass: 1, rttl: 5, 196 | rdata: { target: '10.99.99.18' } 197 | } 198 | ], 199 | authority: [], 200 | additional: [] 201 | }, 202 | type: 'message' 203 | } 204 | ]; 205 | 206 | function encode(data) { 207 | var tokens, buffer, pos = 0; 208 | 209 | if (typeof(data) !== 'string') 210 | throw new TypeError('data (string) is required'); 211 | 212 | tokens = data.split(/\s/); 213 | buffer = new Buffer(tokens.length); 214 | 215 | for (var i = 0; i < tokens.length; ++i) { 216 | var v = parseInt(tokens[i], 16); 217 | buffer.writeInt8(v, pos++, true); 218 | } 219 | return buffer; 220 | } 221 | 222 | function encodeSamples(samps) { 223 | var sample, results = []; 224 | for (var i = 0; i < samps.length; ++i) { 225 | sample = samps[i]; 226 | sample.raw = encode(sample.data); 227 | results.push(sample); 228 | } 229 | return results; 230 | } 231 | 232 | function equalBuffers(b1, b2) { 233 | if (b1.length !== b2.length) { 234 | return false; 235 | } 236 | 237 | var l = b1.length; 238 | while (l--) { 239 | var one = b1.readUInt8(l); 240 | var two = b2.readUInt8(l); 241 | if (one !== two) { 242 | return false; 243 | } 244 | } 245 | return true; 246 | } 247 | 248 | module.exports = { 249 | samples: encodeSamples(samples), 250 | equalBuffers: equalBuffers 251 | }; 252 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Mark Cavage. All rights reserved. 3 | * Copyright (c) 2012 Trevor Orsztynowicz 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | var bunyan = require('bunyan'); 25 | var named = require('../lib'); 26 | 27 | 28 | ///--- Exports 29 | 30 | module.exports = { 31 | 32 | after: function after(teardown) { 33 | module.parent.exports.tearDown = teardown; 34 | }, 35 | 36 | before: function before(setup) { 37 | module.parent.exports.setUp = setup; 38 | }, 39 | 40 | test: function test(name, tester) { 41 | module.parent.exports[name] = function _(t) { 42 | var _done = false; 43 | t.end = function end() { 44 | if (!_done) { 45 | _done = true; 46 | t.done(); 47 | } 48 | }; 49 | t.notOk = function notOk(ok, message) { 50 | return (t.ok(!ok, message)); 51 | }; 52 | return (tester(t)); 53 | }; 54 | }, 55 | 56 | 57 | getLog: function (name, stream) { 58 | return (bunyan.createLogger({ 59 | level: (process.env.LOG_LEVEL || 'info'), 60 | name: name || process.argv[1], 61 | serializers: named.bunyan.serializers, 62 | stream: stream || process.stdout, 63 | src: true 64 | })); 65 | } 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /test/named.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var named = require('../lib'); 24 | 25 | var dig = require('./dig'); 26 | 27 | if (require.cache[__dirname + '/helper.js']) 28 | delete require.cache[__dirname + '/helper.js'] 29 | var helper = require('./helper'); 30 | 31 | 32 | ///--- Globals 33 | 34 | var test = helper.test; 35 | var before = helper.before; 36 | var after = helper.after; 37 | 38 | var options = {port: 9999, server: '::1'}; 39 | 40 | 41 | 42 | ///--- Tests 43 | 44 | before(function (callback) { 45 | this.server = named.createServer({ 46 | log: helper.getLog('server') 47 | }); 48 | 49 | this.server.on('query', function (query, cb) { 50 | var domain = query.name() 51 | var type = query.type(); 52 | 53 | switch (type) { 54 | case 'A': 55 | var record = new named.ARecord('127.0.0.1'); 56 | query.addAnswer(domain, record); 57 | break; 58 | case 'AAAA': 59 | var record = new named.AAAARecord('::1'); 60 | query.addAnswer(domain, record); 61 | break; 62 | case 'CNAME': 63 | var record = new named.CNAMERecord('cname.example.com'); 64 | query.addAnswer(domain, record); 65 | break; 66 | case 'NS': 67 | var record = new named.NSRecord('ns.example.com'); 68 | query.addAnswer(domain, record); 69 | break; 70 | case 'MX': 71 | var record = new named.MXRecord('smtp.example.com'); 72 | query.addAnswer(domain, record); 73 | break; 74 | case 'SOA': 75 | var record = new named.SOARecord('example.com'); 76 | query.addAnswer(domain, record); 77 | break; 78 | case 'SRV': 79 | var record = new named.SRVRecord('sip.example.com', 5060); 80 | query.addAnswer(domain, record); 81 | break; 82 | case 'TXT': 83 | var record = new named.TXTRecord('hello world'); 84 | query.addAnswer(domain, record); 85 | break; 86 | } 87 | query.respond(); 88 | cb(); 89 | }); 90 | 91 | this.server.listen(options.port, options.server, function() { 92 | process.nextTick(callback); 93 | }); 94 | }); 95 | 96 | 97 | after(function (callback) { 98 | this.server.close(callback); 99 | }); 100 | 101 | 102 | test('listen and close (port only)', function (t) { 103 | // don't conflict with the server made in 'before' 104 | var server = named.createServer(); 105 | server.listen(1153, function () { 106 | process.nextTick(function () { 107 | server.close(function () { 108 | t.end(); 109 | }) 110 | }); 111 | }); 112 | }); 113 | 114 | 115 | test('listen and close (port and ::1)', function(t) { 116 | var server = named.createServer(); 117 | server.listen(String(1153), '::1', function() { 118 | process.nextTick(function () { 119 | server.close(function () { 120 | t.end(); 121 | }) 122 | }); 123 | }); 124 | }); 125 | 126 | 127 | test('answer query: example.com (A)', function (t) { 128 | dig('example.com', 'A', options, function (err, results) { 129 | t.ifError(err); 130 | t.deepEqual(results.answers, [{ 131 | name: 'example.com.', 132 | ttl: 5, type: 'A', 133 | target: '127.0.0.1' 134 | }]); 135 | t.end(); 136 | }); 137 | }); 138 | 139 | 140 | test('answer query: example.com (AAAA)', function (t) { 141 | dig('example.com', 'AAAA', options, function (err, results) { 142 | t.ifError(err); 143 | t.deepEqual(results.answers, [{ 144 | name: 'example.com.', 145 | ttl: 5, type: 'AAAA', 146 | target: '::1' 147 | }]); 148 | t.end(); 149 | }); 150 | }); 151 | 152 | 153 | test('answer query: example.com (CNAME)', function (t) { 154 | dig('www.example.com', 'CNAME', options, function (err, results) { 155 | t.ifError(err); 156 | t.deepEqual(results.answers, [{ 157 | name: 'www.example.com.', 158 | ttl: 5, 159 | type: 'CNAME', 160 | target: 'cname.example.com.' 161 | }]); 162 | t.end(); 163 | }); 164 | }); 165 | 166 | test('answer query: example.com (NS)', function (t) { 167 | dig('example.com', 'NS', options, function (err, results) { 168 | t.ifError(err); 169 | t.deepEqual(results.answers, [{ 170 | name: 'example.com.', 171 | ttl: 5, 172 | type: 'NS', 173 | target: 'ns.example.com.' 174 | }]); 175 | t.end(); 176 | }); 177 | }); 178 | 179 | 180 | test('answer query: example.com (MX)', function (t) { 181 | dig('example.com', 'MX', options, function (err, results) { 182 | t.ifError(err); 183 | t.deepEqual(results.answers, [{ 184 | name: 'example.com.', 185 | ttl: 5, 186 | type: 'MX', 187 | target: '0 smtp.example.com.' 188 | }]); 189 | t.end(); 190 | }); 191 | }); 192 | 193 | 194 | test('answer query: example.com (SOA)', function (t) { 195 | dig('example.com', 'SOA', options, function (err, results) { 196 | t.ifError(err); 197 | t.deepEqual(results.answers, [{ 198 | name: 'example.com.', 199 | ttl: 5, 200 | type: 'SOA', 201 | target: 'example.com. hostmaster.example.com. 0 86400 7200 1209600 10800' 202 | }]); 203 | t.end(); 204 | }); 205 | }); 206 | 207 | 208 | test('answer query: example.com (SRV)', function (t) { 209 | dig('_sip._tcp.example.com', 'SRV', options, function (err, results) { 210 | t.ifError(err); 211 | t.deepEqual(results.answers, [{ 212 | name: '_sip._tcp.example.com.', 213 | ttl: 5, 214 | type: 'SRV', 215 | target: '0 10 5060 sip.example.com.' 216 | }]); 217 | t.end(); 218 | }); 219 | }); 220 | 221 | 222 | test('answer query: example.com (TXT)', function (t) { 223 | dig('example.com', 'TXT', options, function (err, results) { 224 | t.ifError(err); 225 | t.deepEqual(results.answers, [{ 226 | name: 'example.com.', 227 | ttl: 5, 228 | type: 'TXT', 229 | target: '"hello world"' 230 | }]); 231 | t.end(); 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /test/notify.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Joyent, Inc 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | var named = require('../lib'); 23 | 24 | var dig = require('./dig'); 25 | 26 | if (require.cache[__dirname + '/helper.js']) 27 | delete require.cache[__dirname + '/helper.js'] 28 | var helper = require('./helper'); 29 | 30 | var test = helper.test; 31 | var before = helper.before; 32 | var after = helper.after; 33 | 34 | var server1; 35 | var server2; 36 | 37 | before(function (callback) { 38 | server1 = named.createServer({ 39 | log: helper.getLog('server1') 40 | }); 41 | server2 = named.createServer({ 42 | log: helper.getLog('server2') 43 | }); 44 | 45 | server1.on('query', function (query, cb) { 46 | var op = query.operation(); 47 | if (op === 'notify') { 48 | query.setError('noerror'); 49 | query.send(); 50 | } 51 | cb(); 52 | }); 53 | 54 | server1.listenUdp({ address: '::1', port: 9999 }, function() { 55 | server2.listenUdp({ address: '::1', port: 9991 }, function () { 56 | process.nextTick(callback); 57 | }); 58 | }); 59 | }); 60 | 61 | test('answer notify: foo.com', function (t) { 62 | var n = server2.createNotify({ 63 | address: '::1', 64 | port: 9999, 65 | zone: 'foo.com' 66 | }); 67 | n.once('response', function (q) { 68 | t.strictEqual(q.operation(), 'notify'); 69 | t.strictEqual(q.error(), 'noerror'); 70 | t.strictEqual(q.name(), 'foo.com'); 71 | t.end(); 72 | }); 73 | n.once('error', function (err) { 74 | t.ifError(err); 75 | t.end(); 76 | }) 77 | n.send(); 78 | }); 79 | 80 | after(function (cb) { 81 | server1.close(function () { 82 | server2.close(cb); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/pipeline.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Joyent, Inc 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | var named = require('../lib'); 23 | var protocol = require('../lib/protocol'); 24 | 25 | var dig = require('./dig'); 26 | 27 | if (require.cache[__dirname + '/helper.js']) 28 | delete require.cache[__dirname + '/helper.js'] 29 | var helper = require('./helper'); 30 | var net = require('net'); 31 | 32 | var test = helper.test; 33 | var before = helper.before; 34 | var after = helper.after; 35 | 36 | var server1; 37 | 38 | before(function (callback) { 39 | server1 = named.createServer({ 40 | log: helper.getLog('server1') 41 | }); 42 | 43 | server1.on('query', function (query, cb) { 44 | var domain = query.name(); 45 | var type = query.type(); 46 | 47 | if (type === 'A') { 48 | var rec = new named.ARecord('127.0.0.1'); 49 | query.addAnswer(domain, rec); 50 | query.send(); 51 | cb(); 52 | } else if (type === 'AAAA') { 53 | setTimeout(function () { 54 | var rec = new named.AAAARecord('::1'); 55 | query.addAnswer(domain, rec); 56 | query.send(); 57 | cb(); 58 | }, 50); 59 | } else { 60 | cb(); 61 | } 62 | }); 63 | 64 | server1.listenUdp({ address: '::1', port: 9999 }); 65 | server1.listenTcp({ address: '::1', port: 9999 }, function() { 66 | process.nextTick(callback); 67 | }); 68 | }); 69 | 70 | test('simple pipelining test', function (t) { 71 | var q1 = {}; 72 | q1.header = { 73 | id: 1234, 74 | flags: { 75 | opcode: protocol.opCodes.QUERY, 76 | rcode: protocol.rCodes.NOERROR 77 | }, 78 | qdCount: 1, 79 | anCount: 0, 80 | nsCount: 0, 81 | arCount: 0 82 | }; 83 | q1.question = [{ 84 | name: 'foo.bar', 85 | type: protocol.queryTypes.A, 86 | qclass: protocol.qClasses.IN 87 | }]; 88 | q1.answer = []; 89 | q1.authority = []; 90 | q1.additional = []; 91 | var q1Buf = lengthPrefix(protocol.encode(q1, 'message')); 92 | 93 | var q2 = q1; 94 | q2.header.id = 1235; 95 | q2.question = [{ 96 | name: 'baz.bar', 97 | type: protocol.queryTypes.AAAA, 98 | qclass: protocol.qClasses.IN 99 | }]; 100 | var q2Buf = lengthPrefix(protocol.encode(q2, 'message')); 101 | 102 | var q3 = q1; 103 | q3.header.id = 1236; 104 | q3.question[0].type = protocol.queryTypes.A; 105 | var q3Buf = lengthPrefix(protocol.encode(q3, 'message')); 106 | 107 | var sock = net.connect(9999, '::1'); 108 | 109 | var outstanding = {}; 110 | var replies = {}; 111 | var order = []; 112 | 113 | sock.on('connect', function () { 114 | sock.write(q1Buf); 115 | outstanding[1234] = true; 116 | sock.write(q2Buf); 117 | outstanding[1235] = true; 118 | sock.write(q3Buf); 119 | outstanding[1236] = true; 120 | }); 121 | 122 | var buf = new Buffer(0); 123 | sock.on('readable', function () { 124 | var b; 125 | while ((b = sock.read()) !== null) 126 | buf = Buffer.concat([buf, b]); 127 | while (buf.length > 2) { 128 | var len = buf.readUInt16BE(0); 129 | if (buf.length >= len + 2) { 130 | var pkt = buf.slice(2, len + 2); 131 | buf = buf.slice(len + 2); 132 | var msg = protocol.decode(pkt, 'message'); 133 | onMessage(msg); 134 | } else { 135 | break; 136 | } 137 | } 138 | }); 139 | 140 | function onMessage(msg) { 141 | t.ok(outstanding[msg.header.id]); 142 | delete outstanding[msg.header.id]; 143 | replies[msg.header.id] = msg; 144 | order.push(msg.header.id); 145 | if (Object.keys(outstanding).length < 1) { 146 | sock.end(); 147 | onComplete(); 148 | } 149 | } 150 | 151 | function onComplete() { 152 | t.deepEqual(order, [1234, 1236, 1235]); 153 | t.strictEqual(replies[1234].answer[0].name, 'foo.bar'); 154 | t.strictEqual(replies[1235].answer[0].name, 'baz.bar'); 155 | t.strictEqual(replies[1236].answer[0].name, 'baz.bar'); 156 | t.strictEqual(replies[1236].answer[0].rtype, 157 | protocol.queryTypes.A); 158 | t.done(); 159 | } 160 | }); 161 | 162 | function lengthPrefix(buf) { 163 | var buf2 = new Buffer(buf.length + 2); 164 | buf.copy(buf2, 2); 165 | buf2.writeUInt16BE(buf.length, 0); 166 | return (buf2); 167 | } 168 | 169 | after(function (cb) { 170 | server1.close(cb); 171 | }); 172 | -------------------------------------------------------------------------------- /test/protocol.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var named = require('../lib'); 24 | 25 | if (require.cache[__dirname + '/helper.js']) 26 | delete require.cache[__dirname + '/helper.js'] 27 | var helper = require('./helper'); 28 | 29 | 30 | var dnsBuffer = require('./dnsbuffer'); 31 | 32 | var test = helper.test; 33 | var protocol = named.Protocol; 34 | 35 | Object.keys(dnsBuffer.samples).forEach(function (i) { 36 | var sample = dnsBuffer.samples[i]; 37 | test('protocol decode/encode: ' + sample.description, function(t) { 38 | var decoded = protocol.decode(sample.raw, sample.type); 39 | if (sample.decoded !== undefined) { 40 | t.deepEqual(decoded, sample.decoded); 41 | } 42 | if (sample.encode === false) { 43 | t.end(); 44 | return; 45 | } 46 | encoded = protocol.encode(decoded, sample.type); 47 | if (dnsBuffer.equalBuffers(encoded, sample.raw)) { 48 | t.ok(true, 'encoder cycle passed'); 49 | } 50 | else { 51 | console.log(decoded); 52 | console.log(encoded.toString('base64')); 53 | console.log(sample.raw.toString('base64')); 54 | t.ok(false, 'encoder cycle failed'); 55 | } 56 | t.end(); 57 | }); 58 | }); 59 | 60 | test('mname#12 regression test', function (t) { 61 | var b = new Buffer('GET / HTTP/1.1\r\n\r\n'); 62 | t.throws(function () { 63 | var decoded = protocol.decode(b, 'message'); 64 | }, /label length/i); 65 | t.end(); 66 | }); 67 | 68 | test('mname#19 regression test (easy loop)', function (t) { 69 | var b = new Buffer('3bb981000001000000000000' + 70 | '047566647304636f616c066a6f79656e74027573c00c' + 71 | '00010001', 'hex'); 72 | t.throws(function () { 73 | var decoded = protocol.decode(b, 'message'); 74 | }, /label pointer/i); 75 | t.end(); 76 | }); 77 | 78 | test('mname#19 regression test (harder loop)', function (t) { 79 | var b = new Buffer('3bb981000001000000000005' + 80 | '047566647304636f616c066a6f79656e74027573c00b' + 81 | '00010001', 'hex'); 82 | t.throws(function () { 83 | var decoded = protocol.decode(b, 'message'); 84 | }, /maximum length/i); 85 | t.end(); 86 | }); 87 | -------------------------------------------------------------------------------- /test/query.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var named = require('../lib'); 24 | var dnsBuffer = require('./dnsbuffer'); 25 | 26 | if (require.cache[__dirname + '/helper.js']) 27 | delete require.cache[__dirname + '/helper.js']; 28 | var helper = require('./helper'); 29 | 30 | var test = helper.test; 31 | var before = helper.before; 32 | var after = helper.after; 33 | 34 | var qopts = {}; 35 | 36 | before(function(callback) { 37 | try { 38 | qopts.data = dnsBuffer.samples[0].raw, 39 | qopts.family = 'udp'; 40 | qopts.address = '127.0.0.1'; 41 | qopts.port = 23456; 42 | 43 | process.nextTick(callback); 44 | } 45 | catch (e) { 46 | console.error(e.stack); 47 | process.exit(1); 48 | } 49 | }); 50 | 51 | 52 | test('decode a query datagram', function(t) { 53 | var query = named.Query.parse(qopts); 54 | t.end(); 55 | }); 56 | 57 | test('encode an null-response query object', function(t) { 58 | var query = named.Query.parse(qopts); 59 | query.setError('enoerr'); 60 | var buf = query.encode(); 61 | var ok = dnsBuffer.samples[0].raw; 62 | t.deepEqual(buf, ok); 63 | t.end(); 64 | }); 65 | 66 | // TODO test adding a record 67 | // TODO test name response 68 | // TODO test answers response 69 | -------------------------------------------------------------------------------- /test/records.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var named = require('../lib'); 24 | 25 | if (require.cache[__dirname + '/helper.js']) 26 | delete require.cache[__dirname + '/helper.js'] 27 | var helper = require('./helper'); 28 | 29 | var test = helper.test; 30 | 31 | var testRecord = function(record, t) { 32 | if (!record) 33 | t.ok(false, 'record could not be created'); 34 | 35 | if (record && record.valid()) { 36 | t.ok(true, 'valid record created'); 37 | } 38 | else { 39 | t.ok(false, 'record was not valid'); 40 | } 41 | 42 | t.end() 43 | } 44 | 45 | test('create a valid record (A)', function(t) { 46 | var record = new named.ARecord('127.0.0.1'); 47 | testRecord(record, t); 48 | }); 49 | 50 | test('create a valid record (AAAA)', function(t) { 51 | var record = new named.AAAARecord('::1'); 52 | testRecord(record, t); 53 | }); 54 | 55 | test('create a valid record (CNAME)', function(t) { 56 | var record = new named.CNAMERecord('alias.example.com'); 57 | testRecord(record, t); 58 | }); 59 | 60 | test('create a valid record (NS)', function(t) { 61 | var record = new named.NSRecord('ns.example.com'); 62 | testRecord(record, t); 63 | }); 64 | 65 | test('create a valid record (MX)', function(t) { 66 | var record = new named.MXRecord('smtp.example.com'); 67 | testRecord(record, t); 68 | }); 69 | 70 | test('create a valid record (SOA)', function(t) { 71 | var record = new named.SOARecord('example.com'); 72 | testRecord(record, t); 73 | }); 74 | 75 | test('create a valid record (SRV)', function(t) { 76 | var record = new named.SRVRecord('_sip._udp.example.com', 5060); 77 | testRecord(record, t); 78 | }); 79 | 80 | test('create a valid record (TXT)', function(t) { 81 | var record = new named.TXTRecord('hello world'); 82 | testRecord(record, t); 83 | }); 84 | -------------------------------------------------------------------------------- /test/tsig.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Joyent, Inc 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | var mod_mname = require('../lib'); 23 | var dig = require('./dig'); 24 | var mod_crypto = require('crypto'); 25 | 26 | if (require.cache[__dirname + '/helper.js']) 27 | delete require.cache[__dirname + '/helper.js'] 28 | var helper = require('./helper'); 29 | 30 | var test = helper.test; 31 | var before = helper.before; 32 | var after = helper.after; 33 | 34 | var options = { port: 9999, server: '::1' }; 35 | 36 | var KEY_MD5 = { 37 | name: 'md5test', 38 | algorithm: 'hmac-md5', 39 | data: mod_crypto.randomBytes(8) 40 | }; 41 | 42 | var KEY_SHA1 = { 43 | name: 'shatest', 44 | algorithm: 'hmac-sha1', 45 | data: mod_crypto.randomBytes(12) 46 | }; 47 | 48 | var KEY_SHA1_2 = { 49 | name: 'shatest2', 50 | algorithm: 'hmac-sha1', 51 | data: mod_crypto.randomBytes(12) 52 | }; 53 | 54 | var KEY_SHA1_3 = { 55 | name: 'shatest', 56 | algorithm: 'hmac-sha1', 57 | data: mod_crypto.randomBytes(12) 58 | }; 59 | 60 | var KEYS = { 61 | 'shatest': KEY_SHA1, 62 | 'md5test': KEY_MD5 63 | }; 64 | 65 | before(function (callback) { 66 | this.server = mod_mname.createServer({ 67 | log: helper.getLog('server') 68 | }); 69 | var server = this.server; 70 | 71 | this.server.on('query', function (query, cb) { 72 | if (!query.isSigned() || !query.verify(KEYS)) { 73 | query.setError('notauth'); 74 | query.send(); 75 | cb(); 76 | return; 77 | } 78 | var domain = query.name(); 79 | var record; 80 | if (query.type() === 'AXFR') { 81 | var soa = new mod_mname.SOARecord(domain); 82 | query.addAnswer(domain, soa, 300); 83 | query.send(); 84 | record = new mod_mname.ARecord('127.0.0.1'); 85 | query.addAnswer(domain, record, 300); 86 | query.send(); 87 | query.addAnswer(domain, soa, 300); 88 | query.send(); 89 | cb(); 90 | } else { 91 | record = new mod_mname.ARecord('127.0.0.1'); 92 | query.addAnswer(domain, record, 300); 93 | query.send(); 94 | cb(); 95 | } 96 | }); 97 | 98 | this.server.listenUdp({port: options.port, address: options.server}, 99 | function () { 100 | server.listenTcp({port: options.port, address: options.server}, 101 | function () { 102 | process.nextTick(callback); 103 | }); 104 | }); 105 | }); 106 | 107 | after(function (cb) { 108 | this.server.close(cb); 109 | }); 110 | 111 | process.on('uncaughtException', function(err) { 112 | console.error(err.stack); 113 | }); 114 | 115 | test('tsig required', function (t) { 116 | dig('example.com', 'A', options, function (err, results) { 117 | t.ifError(err); 118 | t.equal(results.status, 'notauth'); 119 | t.end(); 120 | }); 121 | }); 122 | 123 | test('tsig md5', function (t) { 124 | options.key = KEY_MD5; 125 | dig('example.com', 'A', options, function (err, results) { 126 | t.ifError(err); 127 | t.equal(results.status, 'noerror'); 128 | t.ok(!results.tsigFail); 129 | t.deepEqual(results.answers, [{ 130 | name: 'example.com.', 131 | ttl: 300, type: 'A', 132 | target: '127.0.0.1' 133 | }]); 134 | t.end(); 135 | }); 136 | }); 137 | 138 | test('tsig sha1', function (t) { 139 | options.key = KEY_SHA1; 140 | dig('example.com', 'A', options, function (err, results) { 141 | t.ifError(err); 142 | t.equal(results.status, 'noerror'); 143 | t.ok(!results.tsigFail); 144 | t.deepEqual(results.answers, [{ 145 | name: 'example.com.', 146 | ttl: 300, type: 'A', 147 | target: '127.0.0.1' 148 | }]); 149 | t.end(); 150 | }); 151 | }); 152 | 153 | test('tsig sha1 with unknown key', function (t) { 154 | options.key = KEY_SHA1_2; 155 | dig('example.com', 'A', options, function (err, results) { 156 | t.ifError(err); 157 | t.equal(results.status, 'notauth'); 158 | t.end(); 159 | }); 160 | }); 161 | 162 | test('tsig sha1 with wrong key', function (t) { 163 | options.key = KEY_SHA1_3; 164 | dig('example.com', 'A', options, function (err, results) { 165 | t.ifError(err); 166 | t.equal(results.status, 'notauth'); 167 | t.end(); 168 | }); 169 | }); 170 | 171 | test('tsig axfr', function (t) { 172 | options.key = KEY_SHA1; 173 | dig('example.com', 'AXFR', options, function (err, results) { 174 | t.ifError(err); 175 | t.strictEqual(results.status, null); 176 | t.ok(!results.tsigFail); 177 | var noTsig = results.answers.filter(function (rec) { 178 | return (rec.type !== 'TSIG'); 179 | }); 180 | t.deepEqual(noTsig, [{ 181 | name: 'example.com.', 182 | ttl: 300, type: 'SOA', 183 | target: 'example.com. hostmaster.example.com. ' + 184 | '0 86400 7200 1209600 10800' 185 | }, { 186 | name: 'example.com.', 187 | ttl: 300, type: 'A', 188 | target: '127.0.0.1' 189 | }, { 190 | name: 'example.com.', 191 | ttl: 300, type: 'SOA', 192 | target: 'example.com. hostmaster.example.com. ' + 193 | '0 86400 7200 1209600 10800' 194 | }]); 195 | t.end(); 196 | }); 197 | }); 198 | -------------------------------------------------------------------------------- /test/validator.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Trevor Orsztynowicz 3 | * Copyright (c) 2015 Joyent, Inc 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | var validators = require('../lib/validators'); 24 | 25 | if (require.cache[__dirname + '/helper.js']) 26 | delete require.cache[__dirname + '/helper.js'] 27 | var helper = require('./helper'); 28 | 29 | var test = helper.test; 30 | 31 | var toTest = { 32 | nsName: [ 33 | [ 'example.com', true ], 34 | [ '0example.com', true ], 35 | [ '_example.com', false ], 36 | [ '0_example.com', false ], 37 | [ '-example.com', false ], 38 | [ '0-example.com', true ], 39 | [ 'example-one.com', true ], 40 | [ 'example-111.com', true ], 41 | [ 'Example-111.com', true ], 42 | [ 'a name with spaces', false ], 43 | ], 44 | UInt32BE: [ 45 | [ 'hello', false ], 46 | [ '12345', true ], 47 | [ 4294967296, false ], 48 | [ 10, true ] 49 | ], 50 | UInt16BE: [ 51 | [ 'hello', false ], 52 | [ '12345', true ], 53 | [ 65536, false ], 54 | [ 10, true ] 55 | ], 56 | nsText: [ 57 | [ 'hello world', true ], 58 | ] 59 | }; 60 | 61 | test('testing validator (nsName)', function(t) { 62 | var k = 'nsName'; 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 (UInt32BE)', function(t) { 73 | var k = 'UInt32BE'; 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 | 83 | test('testing validator (UInt16BE)', function(t) { 84 | var k = 'UInt16BE'; 85 | for (var i in toTest.k) { 86 | var s = toTest.k[i][0]; 87 | var ok = toTest.k[i][1]; 88 | var result = validators.k(s); 89 | t.equal(result, ok); 90 | } 91 | t.end(); 92 | }); 93 | 94 | test('testing validator (nsText)', function(t) { 95 | var k = 'nsText'; 96 | for (var i in toTest.k) { 97 | var s = toTest.k[i][0]; 98 | var ok = toTest.k[i][1]; 99 | var result = validators.k(s); 100 | t.equal(result, ok); 101 | } 102 | t.end(); 103 | }); 104 | -------------------------------------------------------------------------------- /tools/bashstyle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | /* 9 | * Copyright (c) 2014, Joyent, Inc. 10 | */ 11 | 12 | /* 13 | * bashstyle: check bash scripts for adherence to style guidelines, including: 14 | * 15 | * o no lines longer than 80 characters 16 | * o file does not end with a blank line 17 | * o Do not use 'local' and var initialization *using a subshell* in the 18 | * same statement. See 19 | * 20 | * for why not. Arguably this belongs in a separate 'bashlint'. 21 | * 22 | * Future enhancements could include: 23 | * o indents consistent with respect to tabs, spaces 24 | * o indents consistently sized (all are some multiple of the smallest 25 | * indent, which must be a tab or 4 or 8 spaces) 26 | */ 27 | 28 | var VERSION = '2.0.0'; 29 | 30 | var mod_assert = require('assert'); 31 | var mod_fs = require('fs'); 32 | 33 | var nerrors = 0; 34 | 35 | main(); 36 | process.exit(0); 37 | 38 | function main() 39 | { 40 | var files = process.argv.slice(2); 41 | 42 | if (files.length === 0) { 43 | console.error('usage: %s file1 [...]', 44 | process.argv.slice(0, 2).join(' ')); 45 | process.exit(2); 46 | } 47 | 48 | files.forEach(checkFile); 49 | 50 | if (nerrors != 0) 51 | process.exit(1); 52 | } 53 | 54 | function checkFile(filename) 55 | { 56 | var text = mod_fs.readFileSync(filename, 'utf-8'); 57 | var lines = text.split('\n'); 58 | var i; 59 | var styled = false; 60 | var styleStart; 61 | 62 | mod_assert.ok(lines.length > 0); 63 | 64 | /* 65 | * Expand tabs in each line and check for long lines. 66 | */ 67 | for (i = 1; i <= lines.length; i++) { 68 | var line = expandTabs(lines[i - 1]); 69 | 70 | if (i > 1 && lines[i-2].match(/# BASHSTYLED/)) { 71 | continue; 72 | } 73 | 74 | if (line.match(/# BEGIN BASHSTYLED/)) { 75 | styleStart = i; 76 | styled = true; 77 | } 78 | 79 | if (line.match(/# END BASHSTYLED/)) { 80 | if (styled != true) { 81 | nerrors++; 82 | console.log('%s: %d: END BASHSTYLED w/o corresponding BEGIN', 83 | filename, i); 84 | } 85 | styled = false; 86 | } 87 | 88 | if (!styled && line.match(/^\s*local\s+(\w+)\s*=.*\$\(/)) { 89 | nerrors++; 90 | var m = line.match(/^\s*local\s+(\w+)\s*=/); 91 | console.log('%s: %d: declaring and setting a "local" ' + 92 | 'var in the same statement ' + 93 | 'ignores a subshell return code ' + 94 | ': ' + 95 | 'local %s=...', 96 | filename, i, m[1]); 97 | } 98 | 99 | // Regexplanation: non-[, [, space (contents) space, ], non-] 100 | // groups before and after brackets to ease search/replace. 101 | if (!styled && line.match(/(^|[^\[])\[(\s.+\s)\]([^\]])/)) { 102 | nerrors++; 103 | console.log('%s: %d: prefer [[ to [ for tests.', filename, i); 104 | } 105 | 106 | if (!styled && line.length > 80) { 107 | nerrors++; 108 | console.log('%s: %d: line exceeds 80 columns', 109 | filename, i); 110 | } 111 | } 112 | 113 | if (styled) { 114 | nerrors++; 115 | console.log('%s: %d: BEGIN BASHSTYLED that does not END', 116 | filename, styleStart); 117 | } 118 | 119 | 120 | /* 121 | * No sane editor lets you save a file without a newline at the very end. 122 | */ 123 | if (lines[lines.length - 1].length !== 0) { 124 | nerrors++; 125 | console.log('%s: %d: file does not end with newline', 126 | filename, lines.length); 127 | } 128 | 129 | /* 130 | * Since the file will always end with a newline, the last entry of 131 | * "lines" will actually be blank. 132 | */ 133 | if (lines.length > 1 && lines[lines.length - 2].length === 0) { 134 | nerrors++; 135 | console.log('%s: %d: file ends with a blank line', 136 | filename, lines.length - 1); 137 | } 138 | } 139 | 140 | function expandTabs(text) 141 | { 142 | var out = ''; 143 | var col = 0; 144 | var j, k; 145 | 146 | for (j = 0; j < text.length; j++) { 147 | if (text[j] != '\t') { 148 | out += text[j]; 149 | col++; 150 | continue; 151 | } 152 | 153 | k = 8 - (col % 8); 154 | col += k; 155 | 156 | do { 157 | out += ' '; 158 | } while (--k > 0); 159 | 160 | col += k; 161 | } 162 | 163 | return (out); 164 | } 165 | -------------------------------------------------------------------------------- /tools/jsl.node.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration File for JavaScript Lint 3 | # 4 | # This configuration file can be used to lint a collection of scripts, or to enable 5 | # or disable warnings for scripts that are linted via the command line. 6 | # 7 | 8 | ### Warnings 9 | # Enable or disable warnings based on requirements. 10 | # Use "+WarningName" to display or "-WarningName" to suppress. 11 | # 12 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent 13 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 14 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 15 | +anon_no_return_value # anonymous function does not always return value 16 | +assign_to_function_call # assignment to a function call 17 | -block_without_braces # block statement without curly braces 18 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 19 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 20 | +default_not_at_end # the default case is not at the end of the switch statement 21 | +dup_option_explicit # duplicate "option explicit" control comment 22 | +duplicate_case_in_switch # duplicate case in switch statement 23 | +duplicate_formal # duplicate formal argument {name} 24 | +empty_statement # empty statement or extra semicolon 25 | +identifier_hides_another # identifer {name} hides an identifier in a parent scope 26 | -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 27 | +incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version. 28 | +invalid_fallthru # unexpected "fallthru" control comment 29 | +invalid_pass # unexpected "pass" control comment 30 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 31 | +leading_decimal_point # leading decimal point may indicate a number or an object member 32 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 33 | +meaningless_block # meaningless block; curly braces have no impact 34 | +mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence 35 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 36 | +missing_break # missing break statement 37 | +missing_break_for_last_case # missing break statement for last case in switch 38 | +missing_default_case # missing default case in switch statement 39 | +missing_option_explicit # the "option explicit" control comment is missing 40 | +missing_semicolon # missing semicolon 41 | +missing_semicolon_for_lambda # missing semicolon for lambda assignment 42 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 43 | +nested_comment # nested comment 44 | +no_return_value # function {name} does not always return a value 45 | +octal_number # leading zeros make an octal number 46 | +parseint_missing_radix # parseInt missing radix parameter 47 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 48 | +redeclared_var # redeclaration of {name} 49 | +trailing_comma_in_array # extra comma is not recommended in array initializers 50 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 51 | +undeclared_identifier # undeclared identifier: {name} 52 | +unreachable_code # unreachable code 53 | -unreferenced_argument # argument declared but never referenced: {name} 54 | -unreferenced_function # function is declared but never referenced: {name} 55 | +unreferenced_variable # variable is declared but never referenced: {name} 56 | +unsupported_version # JavaScript {version} is not supported 57 | +use_of_label # use of label 58 | +useless_assign # useless assignment 59 | +useless_comparison # useless comparison; comparing identical expressions 60 | -useless_quotes # the quotation marks are unnecessary 61 | +useless_void # use of the void type may be unnecessary (void is always undefined) 62 | +var_hides_arg # variable {name} hides argument 63 | +want_assign_or_call # expected an assignment or function call 64 | +with_statement # with statement hides undeclared variables; use temporary variable instead 65 | 66 | 67 | ### Output format 68 | # Customize the format of the error message. 69 | # __FILE__ indicates current file path 70 | # __FILENAME__ indicates current file name 71 | # __LINE__ indicates current line 72 | # __COL__ indicates current column 73 | # __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__) 74 | # __ERROR_NAME__ indicates error name (used in configuration file) 75 | # __ERROR_PREFIX__ indicates error prefix 76 | # __ERROR_MSG__ indicates error message 77 | # 78 | # For machine-friendly output, the output format can be prefixed with 79 | # "encode:". If specified, all items will be encoded with C-slashes. 80 | # 81 | # Visual Studio syntax (default): 82 | +output-format __FILE__(__LINE__): __ERROR__ 83 | # Alternative syntax: 84 | #+output-format __FILE__:__LINE__: __ERROR__ 85 | 86 | 87 | ### Context 88 | # Show the in-line position of the error. 89 | # Use "+context" to display or "-context" to suppress. 90 | # 91 | +context 92 | 93 | 94 | ### Control Comments 95 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 96 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 97 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 98 | # although legacy control comments are enabled by default for backward compatibility. 99 | # 100 | -legacy_control_comments 101 | 102 | 103 | ### Defining identifiers 104 | # By default, "option explicit" is enabled on a per-file basis. 105 | # To enable this for all files, use "+always_use_option_explicit" 106 | -always_use_option_explicit 107 | 108 | # Define certain identifiers of which the lint is not aware. 109 | # (Use this in conjunction with the "undeclared identifier" warning.) 110 | # 111 | # Common uses for webpages might be: 112 | +define __dirname 113 | +define clearInterval 114 | +define clearTimeout 115 | +define console 116 | +define exports 117 | +define global 118 | +define module 119 | +define process 120 | +define require 121 | +define setInterval 122 | +define setImmediate 123 | +define setTimeout 124 | +define Buffer 125 | +define JSON 126 | +define Math 127 | +define Map 128 | 129 | ### JavaScript Version 130 | # To change the default JavaScript version: 131 | #+default-type text/javascript;version=1.5 132 | #+default-type text/javascript;e4x=1 133 | 134 | ### Files 135 | # Specify which files to lint 136 | # Use "+recurse" to enable recursion (disabled by default). 137 | # To add a set of files, use "+process FileName", "+process Folder\Path\*.js", 138 | # or "+process Folder\Path\*.htm". 139 | # 140 | 141 | -------------------------------------------------------------------------------- /tools/jsstyle.conf: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2014, Joyent, Inc. 9 | # 10 | 11 | indent=8 12 | doxygen 13 | unparenthesized-return=0 14 | blank-after-start-comment=0 15 | -------------------------------------------------------------------------------- /tools/mk/Makefile.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.defs: common defines. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This makefile defines some useful defines. Include it at the top of 21 | # your Makefile. 22 | # 23 | # Definitions in this Makefile: 24 | # 25 | # TOP The absolute path to the project directory. The top dir. 26 | # BRANCH The current git branch. 27 | # TIMESTAMP The timestamp for the build. This can be set via 28 | # the TIMESTAMP envvar (used by MG-based builds). 29 | # STAMP A build stamp to use in built package names. 30 | # 31 | 32 | TOP := $(shell pwd) 33 | 34 | # 35 | # Mountain Gorilla-spec'd versioning. 36 | # See "Package Versioning" in MG's README.md: 37 | # 38 | # 39 | # Need GNU awk for multi-char arg to "-F". 40 | _AWK := $(shell (which gawk >/dev/null && echo gawk) \ 41 | || (which nawk >/dev/null && echo nawk) \ 42 | || echo awk) 43 | BRANCH := $(shell git symbolic-ref HEAD | $(_AWK) -F/ '{print $$3}') 44 | ifeq ($(TIMESTAMP),) 45 | TIMESTAMP := $(shell date -u "+%Y%m%dT%H%M%SZ") 46 | endif 47 | _GITDESCRIBE := g$(shell git describe --all --long --dirty | $(_AWK) -F'-g' '{print $$NF}') 48 | STAMP := $(BRANCH)-$(TIMESTAMP)-$(_GITDESCRIBE) 49 | 50 | # node-gyp will print build info useful for debugging with V=1 51 | export V=1 52 | -------------------------------------------------------------------------------- /tools/mk/Makefile.deps: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.deps: Makefile for including common tools as dependencies 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This file is separate from Makefile.targ so that teams can choose 21 | # independently whether to use the common targets in Makefile.targ and the 22 | # common tools here. 23 | # 24 | 25 | # 26 | # javascriptlint 27 | # 28 | JSL_EXEC ?= deps/javascriptlint/build/install/jsl 29 | JSL ?= $(JSL_EXEC) 30 | 31 | $(JSL_EXEC): | deps/javascriptlint/.git 32 | cd deps/javascriptlint && make install 33 | 34 | distclean:: 35 | if [[ -f deps/javascriptlint/Makefile ]]; then \ 36 | cd deps/javascriptlint && make clean; \ 37 | fi 38 | 39 | # 40 | # jsstyle 41 | # 42 | JSSTYLE_EXEC ?= deps/jsstyle/jsstyle 43 | JSSTYLE ?= $(JSSTYLE_EXEC) 44 | 45 | $(JSSTYLE_EXEC): | deps/jsstyle/.git 46 | 47 | JSON_EXEC ?= node_modules/.bin/json 48 | JSON ?= $(JSON_EXEC) 49 | 50 | $(JSON_EXEC): $(NPM_EXEC) 51 | $(NPM) install json 52 | 53 | # 54 | # restdown 55 | # 56 | RESTDOWN_EXEC ?= deps/restdown/bin/restdown 57 | RESTDOWN ?= python $(RESTDOWN_EXEC) 58 | $(RESTDOWN_EXEC): | deps/restdown/.git 59 | 60 | EXTRA_DOC_DEPS ?= 61 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node.defs: Makefile for building and bundling your own Node.js. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | 21 | # 22 | # This Makefile facilitates building and bundling your own copy of Node.js in 23 | # your repo. All it does is define variables for node, node-waf, and npm for 24 | # you to use elsewhere in your Makefile and rules to build these tools when 25 | # needed. 26 | # 27 | # To use this facility, include "Makefile.node.defs", use the variables as 28 | # described below to define targets, and then include "Makefile.node.targ". 29 | # 30 | # There are two use cases addressed here: 31 | # 32 | # (1) Invoking node, node-waf, or npm as part of the build process, as in "npm 33 | # install" and "node-waf configure build". To facilitate this, this 34 | # Makefile defines Make variables NODE, NODE_WAF, and NPM that you can use 35 | # to invoke these commands during the build process. You MUST NOT assume 36 | # that these variables just evaluate to the filenames themselves, as they 37 | # may have environment variable definitions and other things that prevent 38 | # you from using them directly as a filename. If you want that, see (2). 39 | # 40 | # Wherever you use one of these variables, you MUST include a dependency on 41 | # the corresponding *_EXEC variable as well, like so: 42 | # 43 | # node_modules/restify: deps/restify $(NPM_EXEC) 44 | # $(NPM) install deps/restify 45 | # 46 | # or better, use an order-only dependency to avoid spurious rebuilds: 47 | # 48 | # node_modules/restify: deps/restify | $(NPM_EXEC) 49 | # $(NPM) install deps/restify 50 | # 51 | # Otherwise, the underlying file will not get built. We don't 52 | # automatically build them as part of "all" because that approach is 53 | # brittle. 54 | # 55 | # (2) Specifying paths for invoking node, node-waf, or npm at RUNTIME, as in 56 | # specifying the path to node used for the start method of your service's 57 | # SMF manifest. For this, this Makefile defines variables NODE_EXEC, 58 | # NODE_WAF_EXEC, and NPM_EXEC, which represent the relative paths of these 59 | # files from the root of the workspace. You MUST NOT use these variables 60 | # to invoke these commands during the build process. See (1) instead. 61 | # 62 | # However, in order to work at runtime, you must build the tool as well. 63 | # That is, if you use NODE_EXEC to specify the path to node, you must 64 | # depend on NODE_EXEC somewhere. This usually happens anyway because you 65 | # usually need them during the build process too, but if you don't then 66 | # you need to explicitly add NODE_EXEC (or whichever) to your "all" 67 | # target. 68 | # 69 | # When including this Makefile, you MAY also specify: 70 | # 71 | # BUILD top-level directory for built binaries 72 | # (default: "build") 73 | # 74 | # NODE_INSTALL where node should install its built items 75 | # (default: "$BUILD/node") 76 | # 77 | # NODE_CONFIG_FLAGS extra flags to pass to Node's "configure" 78 | # (default: "--with-dtrace" on SmartOS; empty 79 | # otherwise.) 80 | # 81 | 82 | TOP ?= $(error You must include Makefile.defs before this makefile) 83 | 84 | BUILD ?= build 85 | NODE_INSTALL ?= $(BUILD)/node 86 | DISTCLEAN_FILES += $(NODE_INSTALL) 87 | 88 | NODE_CONFIG_FLAGS += --prefix=$(TOP)/$(NODE_INSTALL) 89 | 90 | ifeq ($(shell uname -s),SunOS) 91 | NODE_CONFIG_FLAGS += --with-dtrace \ 92 | --openssl-libpath=/opt/local/lib \ 93 | --openssl-includes=/opt/local/include 94 | endif 95 | 96 | NODE_EXEC = $(NODE_INSTALL)/bin/node 97 | NODE_WAF_EXEC = $(NODE_INSTALL)/bin/node-waf 98 | NPM_EXEC = $(NODE_INSTALL)/bin/npm 99 | 100 | # Ensure these use absolute paths to the executables to allow running 101 | # from a dir other than the project top. 102 | NODE := $(TOP)/$(NODE_EXEC) 103 | NODE_WAF := $(TOP)/$(NODE_WAF_EXEC) 104 | NPM := PATH=$(TOP)/$(NODE_INSTALL)/bin:$(PATH) node $(TOP)/$(NPM_EXEC) 105 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node.targ: See Makefile.node.defs. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | 21 | ifneq ($(shell uname -s),SunOS) 22 | NODE_PREBUILT_VERSION ?= $(error You must define NODE_PREBUILT_VERSION to use Makefile.node.targ on non-SunOS) 23 | endif 24 | 25 | ifeq ($(shell uname -s),SunOS) 26 | $(NODE_EXEC) $(NPM_EXEC) $(NODE_WAF_EXEC): | deps/node/.git 27 | (cd deps/node; ./configure $(NODE_CONFIG_FLAGS) && $(MAKE) && $(MAKE) install) 28 | else 29 | $(NODE_EXEC) $(NPM_EXEC) $(NODE_WAF_EXEC): 30 | (mkdir -p $(BUILD) \ 31 | && cd $(BUILD) \ 32 | && [[ -d src-node ]] && (cd src-node && git checkout master && git pull) || git clone https://github.com/joyent/node.git src-node \ 33 | && cd src-node \ 34 | && git checkout $(NODE_PREBUILT_VERSION) \ 35 | && ./configure $(NODE_CONFIG_FLAGS) \ 36 | && $(MAKE) && $(MAKE) install) 37 | endif 38 | 39 | DISTCLEAN_FILES += $(NODE_INSTALL) $(BUILD)/src-node 40 | 41 | distclean:: 42 | -([[ ! -d deps/node ]] || (cd deps/node && $(MAKE) distclean)) 43 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_deps.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node_deps.defs: Makefile for including npm modules whose sources 14 | # reside inside the repo. This should NOT be used for modules in the npm 15 | # public repo or modules that could be specified with git SHAs. 16 | # 17 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 18 | # into other repos as-is without requiring any modifications. If you find 19 | # yourself changing this file, you should instead update the original copy in 20 | # eng.git and then update your repo to use the new version. 21 | # 22 | 23 | # 24 | # This Makefile takes as input the following make variable: 25 | # 26 | # REPO_MODULES List of relative paths to node modules (i.e., npm 27 | # packages) inside this repo. For example: 28 | # src/node-canative, where there's a binary npm package 29 | # in src/node-canative. 30 | # 31 | # Based on the above, this Makefile defines the following new variables: 32 | # 33 | # REPO_DEPS List of relative paths to the installed modules. For 34 | # example: "node_modules/canative". 35 | # 36 | # The accompanying Makefile.node_deps.targ defines a target that will install 37 | # each of REPO_MODULES into REPO_DEPS and remove REPO_DEPS with "make clean". 38 | # The top-level Makefile is responsible for depending on REPO_DEPS where 39 | # appropriate (usually the "deps" or "all" target). 40 | # 41 | 42 | REPO_DEPS = $(REPO_MODULES:src/node-%=node_modules/%) 43 | CLEAN_FILES += $(REPO_DEPS) 44 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_deps.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node_deps.targ: targets for Makefile.node_deps.defs. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | 21 | NPM_EXEC ?= $(error NPM_EXEC must be defined for Makefile.node_deps.targ) 22 | 23 | node_modules/%: src/node-% | $(NPM_EXEC) 24 | $(NPM) install $< 25 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_prebuilt.defs: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node_prebuilt.defs: Makefile for including a prebuilt Node.js build. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | 21 | # 22 | # This Makefile facilitates downloading and bundling a prebuilt node.js 23 | # build (using the 'sdcnode' distro builds). This is an alternative to 24 | # the "Makefile.node.*" makefiles for *building* a node from source. 25 | # 26 | # Usage: 27 | # 28 | # - Define `NODE_PREBUILT_VERSION` in your Makefile to choose a node version. 29 | # E.g.: `NODE_PREBUILT_VERSION=v0.6.19`. See other optional variables 30 | # below. 31 | # - `include tools/mk/Makefile.node_prebuilt.defs` after this in your Makefile. 32 | # - `include tools/mk/Makefile.node_prebuilt.targ` near the end of your 33 | # Makefile. 34 | # - Have at least one of your Makefile targets depend on either `$(NODE_EXEC)` 35 | # or `$(NPM_EXEC)`. E.g.: 36 | # 37 | # node_modules/restify: deps/restify $(NPM_EXEC) 38 | # $(NPM) install deps/restify 39 | # 40 | # or better, use an order-only dependency to avoid spurious rebuilds: 41 | # 42 | # node_modules/restify: deps/restify | $(NPM_EXEC) 43 | # $(NPM) install deps/restify 44 | # 45 | # - Use `$(NPM)` or `$(NODE)` to use your node build. 46 | # - Include the "$(NODE_INSTALL)" tree in your release package. 47 | # 48 | # 49 | # When including this Makefile, you MUST also specify: 50 | # 51 | # NODE_PREBUILT_VERSION The node version in the prebuilt 'sdcnode' 52 | # package to use. Typically this is one of the 53 | # node version tags, e.g. "v0.6.18" but it 54 | # can be any commitish. 55 | # 56 | # When including this Makefile, you MAY also specify: 57 | # 58 | # NODE_PREBUILT_DIR The dir in which to find sdcnode builds. This 59 | # can either be a *local directory* or *a 60 | # URL* dir (with trailing '/') which serves 61 | # Apache/Nginx dir listing HTML. 62 | # (default: sdcnode master build dir on stuff) 63 | # 64 | # NODE_PREBUILT_TAG The 'sdcnode' project supports special 65 | # configuration builds of node, e.g. say a 66 | # build configured `--without-ssl`. These 67 | # special configurations are given a tag, e.g. 68 | # 'gz', that is used in the filename. Optionally 69 | # specify a tag name here. 70 | # (default: empty) 71 | # 72 | # NODE_PREBUILT_BRANCH Specify a particular branch of 'sdcnode' builds 73 | # from which to pull. Generally one should stick 74 | # with the default. 75 | # (default: master) 76 | # 77 | # NODE_PREBUILT_IMAGE If you have a zone image that differs from that 78 | # for an sdcnode build that you want to use (potential compat 79 | # issues be damned), then set this to the UUID of the sdcnode 80 | # build you want. See here for available build image uuids: 81 | # 82 | # 83 | # BUILD top-level directory for built binaries 84 | # (default: "build") 85 | # 86 | # NODE_INSTALL where node should install its built items 87 | # (default: "$BUILD/node") 88 | # 89 | # 90 | # Dev Notes: 91 | # 92 | # This works by getting "NODE_PREBUILT_NAME" from the provided "NODE_PREBUILT_*" 93 | # vars and the image version (via 'mdata-get sdc:image_uuid'). The image uuid is 94 | # included to ensure an exact match with the build machine. This name (e.g. 95 | # "v0.6.18-zone-$uuid") is used to find a matching "sdcnode-$name-*.tgz" build 96 | # in "NODE_PREBUILT_DIR" (either a local directory or a URL). That tarball is 97 | # downloaded and extracted into "NODE_INSTALL". 98 | # 99 | # The "*_EXEC" vars are set to named symlinks, e.g. 100 | # "build/prebuilt-node-v0.6.18-$uuid", so that a change of selected node 101 | # build (say the developer changes NODE_PREBUILT_VERSION) will recreate the 102 | # node install. 103 | # 104 | # See for details on 'sdcnode-*' 105 | # package naming. 106 | # 107 | 108 | TOP ?= $(error You must include Makefile.defs before this makefile) 109 | NODE_PREBUILT_VERSION ?= $(error NODE_PREBUILT_VERSION is not set.) 110 | 111 | 112 | BUILD ?= build 113 | NODE_INSTALL ?= $(BUILD)/node 114 | DISTCLEAN_FILES += $(NODE_INSTALL) \ 115 | $(BUILD)/prebuilt-node-* $(BUILD)/prebuilt-npm-* 116 | 117 | NODE_PREBUILT_BRANCH ?= master 118 | NODE_PREBUILT_IMAGE ?= $(shell pfexec mdata-get sdc:image_uuid) 119 | ifeq ($(NODE_PREBUILT_TAG),) 120 | NODE_PREBUILT_NAME := $(NODE_PREBUILT_VERSION)-$(NODE_PREBUILT_IMAGE) 121 | else 122 | NODE_PREBUILT_NAME := $(NODE_PREBUILT_VERSION)-$(NODE_PREBUILT_TAG)-$(NODE_PREBUILT_IMAGE) 123 | endif 124 | NODE_PREBUILT_PATTERN := sdcnode-$(NODE_PREBUILT_NAME)-$(NODE_PREBUILT_BRANCH)-.*\.tgz 125 | NODE_PREBUILT_DIR ?= https://download.joyent.com/pub/build/sdcnode/$(NODE_PREBUILT_IMAGE)/$(NODE_PREBUILT_BRANCH)-latest/sdcnode/ 126 | ifeq ($(shell echo $(NODE_PREBUILT_DIR) | cut -c 1-4),http) 127 | NODE_PREBUILT_BASE := $(shell curl -ksS --fail --connect-timeout 30 $(NODE_PREBUILT_DIR) | grep 'href=' | cut -d'"' -f2 | grep "^$(NODE_PREBUILT_PATTERN)$$" | sort | tail -1) 128 | ifneq ($(NODE_PREBUILT_BASE),) 129 | NODE_PREBUILT_TARBALL := $(NODE_PREBUILT_DIR)$(NODE_PREBUILT_BASE) 130 | endif 131 | else 132 | NODE_PREBUILT_BASE := $(shell ls -1 $(NODE_PREBUILT_DIR)/ | grep "^$(NODE_PREBUILT_PATTERN)$$" 2>/dev/null | sort | tail -1) 133 | ifneq ($(NODE_PREBUILT_BASE),) 134 | NODE_PREBUILT_TARBALL := $(NODE_PREBUILT_DIR)/$(NODE_PREBUILT_BASE) 135 | endif 136 | endif 137 | ifeq ($(NODE_PREBUILT_TARBALL),) 138 | NODE_PREBUILT_TARBALL = $(error NODE_PREBUILT_TARBALL is empty: no '$(NODE_PREBUILT_DIR)/$(NODE_PREBUILT_PATTERN)' found) 139 | endif 140 | 141 | 142 | # Prebuild-specific paths for the "*_EXEC" vars to ensure that 143 | # a prebuild change (e.g. if master Makefile's NODE_PREBUILT_VERSION 144 | # choice changes) causes a install of the new node. 145 | NODE_EXEC := $(BUILD)/prebuilt-node-$(NODE_PREBUILT_NAME) 146 | NODE_WAF_EXEC := $(BUILD)/prebuilt-node-waf-$(NODE_PREBUILT_NAME) 147 | NPM_EXEC := $(BUILD)/prebuilt-npm-$(NODE_PREBUILT_NAME) 148 | 149 | # Ensure these use absolute paths to the executables to allow running 150 | # from a dir other than the project top. 151 | NODE := $(TOP)/$(NODE_INSTALL)/bin/node 152 | NODE_WAF := $(TOP)/$(NODE_INSTALL)/bin/node-waf 153 | NPM := PATH=$(TOP)/$(NODE_INSTALL)/bin:$(PATH) node $(TOP)/$(NODE_INSTALL)/bin/npm 154 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_prebuilt.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.node_prebuilt.targ: Makefile for including a prebuilt Node.js 14 | # build. 15 | # 16 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 17 | # into other repos as-is without requiring any modifications. If you find 18 | # yourself changing this file, you should instead update the original copy in 19 | # eng.git and then update your repo to use the new version. 20 | 21 | 22 | NODE_PREBUILT_TARBALL ?= $(error NODE_PREBUILT_TARBALL is not set: was Makefile.node_prebuilt.defs included?) 23 | 24 | 25 | # TODO: remove this limitation 26 | # Limitation: currently presuming that the NODE_INSTALL basename is 27 | # 'node' and that sdcnode tarballs have a 'node' top-level dir. 28 | $(NODE_EXEC) $(NPM_EXEC) $(NODE_WAF_EXEC): 29 | [[ $(shell basename $(NODE_INSTALL)) == "node" ]] \ 30 | || (echo "Limitation: 'basename NODE_INSTALL' is not 'node'" && exit 1) 31 | rm -rf $(NODE_INSTALL) \ 32 | $(BUILD)/prebuilt-node-* $(BUILD)/prebuilt-npm-* 33 | mkdir -p $(shell dirname $(NODE_INSTALL)) 34 | if [[ $(shell echo $(NODE_PREBUILT_TARBALL) | cut -c 1-4) == "http" ]]; then \ 35 | echo "Downloading '$(NODE_PREBUILT_BASE)'."; \ 36 | curl -ksS --fail --connect-timeout 30 -o $(shell dirname $(NODE_INSTALL))/$(NODE_PREBUILT_BASE) $(NODE_PREBUILT_TARBALL); \ 37 | (cd $(shell dirname $(NODE_INSTALL)) && $(TAR) xf $(NODE_PREBUILT_BASE)); \ 38 | else \ 39 | (cd $(shell dirname $(NODE_INSTALL)) && $(TAR) xf $(NODE_PREBUILT_TARBALL)); \ 40 | fi 41 | ln -s $(TOP)/$(NODE_INSTALL)/bin/node $(NODE_EXEC) 42 | ln -s $(TOP)/$(NODE_INSTALL)/bin/npm $(NPM_EXEC) 43 | -------------------------------------------------------------------------------- /tools/mk/Makefile.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.targ: common targets. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This Makefile defines several useful targets and rules. You can use it by 21 | # including it from a Makefile that specifies some of the variables below. 22 | # 23 | # Targets defined in this Makefile: 24 | # 25 | # check Checks JavaScript files for lint and style 26 | # Checks bash scripts for syntax 27 | # Checks SMF manifests for validity against the SMF DTD 28 | # 29 | # clean Removes built files 30 | # 31 | # docs Builds restdown documentation in docs/ 32 | # 33 | # prepush Depends on "check" and "test" 34 | # 35 | # test Does nothing (you should override this) 36 | # 37 | # xref Generates cscope (source cross-reference index) 38 | # 39 | # For details on what these targets are supposed to do, see the Joyent 40 | # Engineering Guide. 41 | # 42 | # To make use of these targets, you'll need to set some of these variables. Any 43 | # variables left unset will simply not be used. 44 | # 45 | # BASH_FILES Bash scripts to check for syntax 46 | # (paths relative to top-level Makefile) 47 | # 48 | # CLEAN_FILES Files to remove as part of the "clean" target. Note 49 | # that files generated by targets in this Makefile are 50 | # automatically included in CLEAN_FILES. These include 51 | # restdown-generated HTML and JSON files. 52 | # 53 | # DOC_FILES Restdown (documentation source) files. These are 54 | # assumed to be contained in "docs/", and must NOT 55 | # contain the "docs/" prefix. 56 | # 57 | # JSL_CONF_NODE Specify JavaScriptLint configuration files 58 | # JSL_CONF_WEB (paths relative to top-level Makefile) 59 | # 60 | # Node.js and Web configuration files are separate 61 | # because you'll usually want different global variable 62 | # configurations. If no file is specified, none is given 63 | # to jsl, which causes it to use a default configuration, 64 | # which probably isn't what you want. 65 | # 66 | # JSL_FILES_NODE JavaScript files to check with Node config file. 67 | # JSL_FILES_WEB JavaScript files to check with Web config file. 68 | # 69 | # JSON_FILES JSON files to be validated 70 | # 71 | # JSSTYLE_FILES JavaScript files to be style-checked 72 | # 73 | # You can also override these variables: 74 | # 75 | # BASH Path to bash (default: "bash") 76 | # 77 | # CSCOPE_DIRS Directories to search for source files for the cscope 78 | # index. (default: ".") 79 | # 80 | # JSL Path to JavaScriptLint (default: "jsl") 81 | # 82 | # JSL_FLAGS_NODE Additional flags to pass through to JSL 83 | # JSL_FLAGS_WEB 84 | # JSL_FLAGS 85 | # 86 | # JSON Path to json tool (default: "json") 87 | # 88 | # JSSTYLE Path to jsstyle (default: "jsstyle") 89 | # 90 | # JSSTYLE_FLAGS Additional flags to pass through to jsstyle 91 | # 92 | # RESTDOWN_EXT By default '.md' is required for DOC_FILES (see above). 93 | # If you want to use, say, '.restdown' instead, then set 94 | # 'RESTDOWN_EXT=.restdown' in your Makefile. 95 | # 96 | 97 | # 98 | # Defaults for the various tools we use. 99 | # 100 | BASH ?= bash 101 | BASHSTYLE ?= tools/bashstyle 102 | CP ?= cp 103 | CSCOPE ?= cscope 104 | CSCOPE_DIRS ?= . 105 | JSL ?= jsl 106 | JSON ?= json 107 | JSSTYLE ?= jsstyle 108 | MKDIR ?= mkdir -p 109 | MV ?= mv 110 | RESTDOWN_FLAGS ?= 111 | RESTDOWN_EXT ?= .md 112 | RMTREE ?= rm -rf 113 | JSL_FLAGS ?= --nologo --nosummary 114 | 115 | ifeq ($(shell uname -s),SunOS) 116 | TAR ?= gtar 117 | else 118 | TAR ?= tar 119 | endif 120 | 121 | 122 | # 123 | # Defaults for other fixed values. 124 | # 125 | BUILD = build 126 | DISTCLEAN_FILES += $(BUILD) 127 | DOC_BUILD = $(BUILD)/docs/public 128 | 129 | # 130 | # Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}. 131 | # 132 | ifneq ($(origin JSL_CONF_NODE), undefined) 133 | JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE) 134 | endif 135 | 136 | ifneq ($(origin JSL_CONF_WEB), undefined) 137 | JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB) 138 | endif 139 | 140 | # 141 | # Targets. For descriptions on what these are supposed to do, see the 142 | # Joyent Engineering Guide. 143 | # 144 | 145 | # 146 | # Instruct make to keep around temporary files. We have rules below that 147 | # automatically update git submodules as needed, but they employ a deps/*/.git 148 | # temporary file. Without this directive, make tries to remove these .git 149 | # directories after the build has completed. 150 | # 151 | .SECONDARY: $($(wildcard deps/*):%=%/.git) 152 | 153 | # 154 | # This rule enables other rules that use files from a git submodule to have 155 | # those files depend on deps/module/.git and have "make" automatically check 156 | # out the submodule as needed. 157 | # 158 | deps/%/.git: 159 | git submodule update --init deps/$* 160 | 161 | # 162 | # These recipes make heavy use of dynamically-created phony targets. The parent 163 | # Makefile defines a list of input files like BASH_FILES. We then say that each 164 | # of these files depends on a fake target called filename.bashchk, and then we 165 | # define a pattern rule for those targets that runs bash in check-syntax-only 166 | # mode. This mechanism has the nice properties that if you specify zero files, 167 | # the rule becomes a noop (unlike a single rule to check all bash files, which 168 | # would invoke bash with zero files), and you can check individual files from 169 | # the command line with "make filename.bashchk". 170 | # 171 | .PHONY: check-bash 172 | check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle) 173 | 174 | %.bashchk: % 175 | $(BASH) -n $^ 176 | 177 | %.bashstyle: % 178 | $(BASHSTYLE) $^ 179 | 180 | .PHONY: check-json 181 | check-json: $(JSON_EXEC) $(JSON_FILES:%=%.jsonchk) 182 | 183 | %.jsonchk: % 184 | $(JSON) --validate -f $^ 185 | 186 | # 187 | # The above approach can be slow when there are many files to check because it 188 | # requires that "make" invoke the check tool once for each file, rather than 189 | # passing in several files at once. For the JavaScript check targets, we define 190 | # a variable for the target itself *only if* the list of input files is 191 | # non-empty. This avoids invoking the tool if there are no files to check. 192 | # 193 | JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node) 194 | .PHONY: check-jsl-node 195 | check-jsl-node: $(JSL_EXEC) 196 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE) 197 | 198 | JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web) 199 | .PHONY: check-jsl-web 200 | check-jsl-web: $(JSL_EXEC) 201 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB) 202 | 203 | .PHONY: check-jsl 204 | check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET) 205 | 206 | JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle) 207 | .PHONY: check-jsstyle 208 | check-jsstyle: $(JSSTYLE_EXEC) 209 | $(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES) 210 | 211 | .PHONY: check 212 | check:: check-jsl check-json $(JSSTYLE_TARGET) check-bash 213 | @echo check ok 214 | 215 | .PHONY: clean 216 | clean:: 217 | -$(RMTREE) $(CLEAN_FILES) 218 | 219 | .PHONY: distclean 220 | distclean:: clean 221 | -$(RMTREE) $(DISTCLEAN_FILES) 222 | 223 | CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out 224 | CLEAN_FILES += $(CSCOPE_FILES) 225 | 226 | .PHONY: xref 227 | xref: cscope.files 228 | $(CSCOPE) -bqR 229 | 230 | .PHONY: cscope.files 231 | cscope.files: 232 | find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \ 233 | -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@ 234 | 235 | # 236 | # The "docs" target is complicated because we do several things here: 237 | # 238 | # (1) Use restdown to build HTML and JSON files from each of DOC_FILES. 239 | # 240 | # (2) Copy these files into $(DOC_BUILD) (build/docs/public), which 241 | # functions as a complete copy of the documentation that could be 242 | # mirrored or served over HTTP. 243 | # 244 | # (3) Then copy any directories and media from docs/media into 245 | # $(DOC_BUILD)/media. This allows projects to include their own media, 246 | # including files that will override same-named files provided by 247 | # restdown. 248 | # 249 | # Step (3) is the surprisingly complex part: in order to do this, we need to 250 | # identify the subdirectories in docs/media, recreate them in 251 | # $(DOC_BUILD)/media, then do the same with the files. 252 | # 253 | DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$") 254 | DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%) 255 | DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%) 256 | 257 | DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null) 258 | DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%) 259 | DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%) 260 | 261 | # 262 | # Like the other targets, "docs" just depends on the final files we want to 263 | # create in $(DOC_BUILD), leveraging other targets and recipes to define how 264 | # to get there. 265 | # 266 | .PHONY: docs 267 | docs:: \ 268 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \ 269 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \ 270 | $(DOC_MEDIA_FILES_BUILD) 271 | 272 | # 273 | # We keep the intermediate files so that the next build can see whether the 274 | # files in DOC_BUILD are up to date. 275 | # 276 | .PRECIOUS: \ 277 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 278 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json) 279 | 280 | # 281 | # We do clean those intermediate files, as well as all of DOC_BUILD. 282 | # 283 | CLEAN_FILES += \ 284 | $(DOC_BUILD) \ 285 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 286 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json) 287 | 288 | # 289 | # Before installing the files, we must make sure the directories exist. The | 290 | # syntax tells make that the dependency need only exist, not be up to date. 291 | # Otherwise, it might try to rebuild spuriously because the directory itself 292 | # appears out of date. 293 | # 294 | $(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD) 295 | 296 | $(DOC_BUILD)/%: docs/% | $(DOC_BUILD) 297 | $(CP) $< $@ 298 | 299 | docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC) \ 300 | $(EXTRA_DOC_DEPS) 301 | $(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $< 302 | 303 | $(DOC_BUILD): 304 | $(MKDIR) $@ 305 | 306 | $(DOC_MEDIA_DIRS_BUILD): 307 | $(MKDIR) $@ 308 | 309 | # 310 | # The default "test" target does nothing. This should usually be overridden by 311 | # the parent Makefile. It's included here so we can define "prepush" without 312 | # requiring the repo to define "test". 313 | # 314 | .PHONY: test 315 | test: 316 | 317 | .PHONY: prepush 318 | prepush: check test 319 | --------------------------------------------------------------------------------