diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index 1ad6e6574c6ebc..8d2c401b07f0d2 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -117,6 +117,21 @@ The callback has arguments `(err, domains)`. On error, `err` is an `Error` object, where `err.code` is one of the error codes listed below. +## dns.getServers() + +Returns an array of IP addresses as strings that are currently being used for +resolution + +## dns.setServers(servers) + +Given an array of IP addresses as strings, set them as the servers to use for +resolving + +If you specify a port with the address it will be stripped, as the underlying +library doesn't support that. + +This will throw if you pass invalid input. + ## Error codes Each DNS query can return one of the following error codes: diff --git a/lib/dns.js b/lib/dns.js index 10214790de7d08..e9611a3a71ab55 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -192,6 +192,55 @@ exports.resolve = function(domain, type_, callback_) { }; +exports.getServers = function() { + return cares.getServers(); +}; + + +exports.setServers = function(servers) { + // cache the original servers because in the event of an error setting the + // servers cares won't have any servers available for resolution + var orig = cares.getServers(); + + var newSet = []; + + servers.forEach(function(serv) { + var ver = isIp(serv); + + if (ver) + return newSet.push([ver, serv]); + + var match = serv.match(/\[(.*)\](:\d+)?/); + + // we have an IPv6 in brackets + if (match) { + ver = isIp(match[1]); + if (ver) + return newSet.push([ver, match[1]]); + } + + var s = serv.split(/:\d+$/)[0]; + ver = isIp(s); + + if (ver) + return newSet.push([ver, s]); + + throw new Error('IP address is not properly formatted: ' + serv); + }); + + var r = cares.setServers(newSet); + + if (r) { + // reset the servers to the old servers, because ares probably unset them + cares.setServers(orig.join(',')); + + var err = cares.strerror(r); + throw new Error('c-ares failed to set servers: "' + err + + '" [' + servers + ']'); + } +}; + + // ERROR CODES exports.NODATA = 'ENODATA'; exports.FORMERR = 'EFORMERR'; diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index d9b4e18849d912..8cca4214bd71e3 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -939,6 +939,111 @@ static Handle GetAddrInfo(const Arguments& args) { } +static Handle GetServers(const Arguments& args) { + HandleScope scope(node_isolate); + + Local server_array = Array::New(); + + ares_addr_node* servers; + + int r = ares_get_servers(ares_channel, &servers); + assert(r == ARES_SUCCESS); + + ares_addr_node* cur = servers; + + for (int i = 0; cur != NULL; ++i, cur = cur->next) { + char ip[INET6_ADDRSTRLEN]; + + const void* caddr = static_cast(&cur->addr); + uv_err_t err = uv_inet_ntop(cur->family, caddr, ip, sizeof(ip)); + assert(err.code == UV_OK); + + Local addr = String::New(ip); + server_array->Set(i, addr); + } + + ares_free_data(servers); + + return scope.Close(server_array); +} + + +static Handle SetServers(const Arguments& args) { + HandleScope scope(node_isolate); + + assert(args[0]->IsArray()); + + Local arr = Local::Cast(args[0]); + + uint32_t len = arr->Length(); + + if (len == 0) { + int rv = ares_set_servers(ares_channel, NULL); + return scope.Close(Integer::New(rv)); + } + + ares_addr_node* servers = new ares_addr_node[len]; + ares_addr_node* last = NULL; + + uv_err_t uv_ret; + + for (uint32_t i = 0; i < len; i++) { + assert(arr->Get(i)->IsArray()); + + Local elm = Local::Cast(arr->Get(i)); + + assert(elm->Get(0)->Int32Value()); + assert(elm->Get(1)->IsString()); + + int fam = elm->Get(0)->Int32Value(); + String::Utf8Value ip(elm->Get(1)); + + ares_addr_node* cur = &servers[i]; + + switch (fam) { + case 4: + cur->family = AF_INET; + uv_ret = uv_inet_pton(AF_INET, *ip, &cur->addr); + break; + case 6: + cur->family = AF_INET6; + uv_ret = uv_inet_pton(AF_INET6, *ip, &cur->addr); + break; + } + + if (uv_ret.code != UV_OK) + break; + + cur->next = NULL; + + if (last != NULL) + last->next = cur; + + last = cur; + } + + int r; + + if (uv_ret.code == UV_OK) + r = ares_set_servers(ares_channel, &servers[0]); + else + r = ARES_EBADSTR; + + delete[] servers; + + return scope.Close(Integer::New(r)); +} + + +static Handle StrError(const Arguments& args) { + HandleScope scope; + + int r = args[0]->Int32Value(); + + return scope.Close(String::New(ares_strerror(r))); +} + + static void Initialize(Handle target) { HandleScope scope(node_isolate); int r; @@ -976,6 +1081,10 @@ static void Initialize(Handle target) { NODE_SET_METHOD(target, "getaddrinfo", GetAddrInfo); NODE_SET_METHOD(target, "isIP", IsIP); + NODE_SET_METHOD(target, "strerror", StrError); + NODE_SET_METHOD(target, "getServers", GetServers); + NODE_SET_METHOD(target, "setServers", SetServers); + target->Set(String::NewSymbol("AF_INET"), Integer::New(AF_INET, node_isolate)); target->Set(String::NewSymbol("AF_INET6"), diff --git a/test/simple/test-dns.js b/test/simple/test-dns.js new file mode 100644 index 00000000000000..9283bab7b595ec --- /dev/null +++ b/test/simple/test-dns.js @@ -0,0 +1,62 @@ +// Copyright Joyent, Inc. and other Node contributors. + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: + +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); + +var dns = require('dns'); + +var existing = dns.getServers(); +assert(existing.length); + +var goog = [ + '8.8.8.8', + '8.8.4.4', +]; +assert.doesNotThrow(function () { dns.setServers(goog) }); +assert.deepEqual(dns.getServers(), goog); +assert.throws(function () { dns.setServers(['foobar']) }); +assert.deepEqual(dns.getServers(), goog); + +var goog6 = [ + '2001:4860:4860::8888', + '2001:4860:4860::8844', +]; +assert.doesNotThrow(function () { dns.setServers(goog6) }); +assert.deepEqual(dns.getServers(), goog6); + +goog6.push('4.4.4.4'); +dns.setServers(goog6); +assert.deepEqual(dns.getServers(), goog6); + +var ports = [ + '4.4.4.4:53', + '[2001:4860:4860::8888]:53', +]; +var portsExpected = [ + '4.4.4.4', + '2001:4860:4860::8888', +]; +dns.setServers(ports); +assert.deepEqual(dns.getServers(), portsExpected); + +assert.doesNotThrow(function () { dns.setServers([]); }); +assert.deepEqual(dns.getServers(), []);