diff options
author | Josh Dague <jd@josh3736.net> | 2020-08-18 09:23:25 +0300 |
---|---|---|
committer | Antoine du Hamel <duhamelantoine1995@gmail.com> | 2020-10-25 13:18:39 +0300 |
commit | 3e10ce30b471214cc5020a92d628357e9ed4fb91 (patch) | |
tree | 4c9092895509dab0a0092e42cc53da4890643e83 | |
parent | 70834250e83fa89e92314be37a9592978ee8c6bd (diff) |
dns: add setLocalAddress to Resolver
Fixes: https://github.com/nodejs/node/issues/34818
PR-URL: https://github.com/nodejs/node/pull/34824
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
-rw-r--r-- | doc/api/dns.md | 21 | ||||
-rw-r--r-- | lib/internal/dns/promises.js | 1 | ||||
-rw-r--r-- | lib/internal/dns/utils.js | 12 | ||||
-rw-r--r-- | src/cares_wrap.cc | 66 | ||||
-rw-r--r-- | test/parallel/test-dns-setlocaladdress.js | 37 |
5 files changed, 137 insertions, 0 deletions
diff --git a/doc/api/dns.md b/doc/api/dns.md index a1f4240981f..638321f222a 100644 --- a/doc/api/dns.md +++ b/doc/api/dns.md @@ -117,6 +117,27 @@ added: v8.3.0 Cancel all outstanding DNS queries made by this resolver. The corresponding callbacks will be called with an error with code `ECANCELLED`. +### `resolver.setLocalAddress([ipv4][, ipv6])` +<!-- YAML +added: REPLACEME +--> + +* `ipv4` {string} A string representation of an IPv4 address. + **Default:** `'0.0.0.0'` +* `ipv6` {string} A string representation of an IPv6 address. + **Default:** `'::0'` + +The resolver instance will send its requests from the specified IP address. +This allows programs to specify outbound interfaces when used on multi-homed +systems. + +If a v4 or v6 address is not specified, it is set to the default, and the +operating system will choose a local address automatically. + +The resolver will use the v4 local address when making requests to IPv4 DNS +servers, and the v6 local address when making requests to IPv6 DNS servers. +The `rrtype` of resolution requests has no impact on the local address used. + ## `dns.getServers()` <!-- YAML added: v0.11.3 diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js index 99693445e82..e8505a95d12 100644 --- a/lib/internal/dns/promises.js +++ b/lib/internal/dns/promises.js @@ -217,6 +217,7 @@ class Resolver { Resolver.prototype.getServers = CallbackResolver.prototype.getServers; Resolver.prototype.setServers = CallbackResolver.prototype.setServers; +Resolver.prototype.setLocalAddress = CallbackResolver.prototype.setLocalAddress; Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny'); Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA'); Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa'); diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js index 459a67be3d8..1c50a089d1d 100644 --- a/lib/internal/dns/utils.js +++ b/lib/internal/dns/utils.js @@ -114,6 +114,18 @@ class Resolver { throw new ERR_DNS_SET_SERVERS_FAILED(err, servers); } } + + setLocalAddress(ipv4, ipv6) { + if (typeof ipv4 !== 'string') { + throw new ERR_INVALID_ARG_TYPE('ipv4', 'String', ipv4); + } + + if (typeof ipv6 !== 'string' && ipv6 !== undefined) { + throw new ERR_INVALID_ARG_TYPE('ipv6', ['String', 'undefined'], ipv6); + } + + this._handle.setLocalAddress(ipv4, ipv6); + } } let defaultResolver = new Resolver(); diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index fcffc2bec67..6d7a8e66a9d 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -29,6 +29,7 @@ #include "req_wrap-inl.h" #include "util-inl.h" #include "uv.h" +#include "node_errors.h" #include <cerrno> #include <cstring> @@ -2227,6 +2228,70 @@ void SetServers(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(err); } +void SetLocalAddress(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + ChannelWrap* channel; + ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder()); + + CHECK_EQ(args.Length(), 2); + CHECK(args[0]->IsString()); + + Isolate* isolate = args.GetIsolate(); + node::Utf8Value ip0(isolate, args[0]); + + unsigned char addr0[sizeof(struct in6_addr)]; + unsigned char addr1[sizeof(struct in6_addr)]; + int type0 = 0; + + // This function accepts 2 arguments. The first may be either an IPv4 + // address or an IPv6 address. If present, the second argument must be the + // other type of address. Otherwise, the unspecified type of IP is set + // to 0 (any). + + if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) { + ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr0)); + type0 = 4; + } else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) { + ares_set_local_ip6(channel->cares_channel(), addr0); + type0 = 6; + } else { + THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); + return; + } + + if (!args[1]->IsUndefined()) { + CHECK(args[1]->IsString()); + node::Utf8Value ip1(isolate, args[1]); + + if (uv_inet_pton(AF_INET, *ip1, &addr1) == 0) { + if (type0 == 4) { + THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses."); + return; + } else { + ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr1)); + } + } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) { + if (type0 == 6) { + THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses."); + return; + } else { + ares_set_local_ip6(channel->cares_channel(), addr1); + } + } else { + THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address."); + return; + } + } else { + // No second arg specifed + if (type0 == 4) { + memset(&addr1, 0, sizeof(addr1)); + ares_set_local_ip6(channel->cares_channel(), addr1); + } else { + ares_set_local_ip4(channel->cares_channel(), 0); + } + } +} + void Cancel(const FunctionCallbackInfo<Value>& args) { ChannelWrap* channel; ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder()); @@ -2329,6 +2394,7 @@ void Initialize(Local<Object> target, env->SetProtoMethodNoSideEffect(channel_wrap, "getServers", GetServers); env->SetProtoMethod(channel_wrap, "setServers", SetServers); + env->SetProtoMethod(channel_wrap, "setLocalAddress", SetLocalAddress); env->SetProtoMethod(channel_wrap, "cancel", Cancel); Local<String> channelWrapString = diff --git a/test/parallel/test-dns-setlocaladdress.js b/test/parallel/test-dns-setlocaladdress.js new file mode 100644 index 00000000000..2e39f3bf910 --- /dev/null +++ b/test/parallel/test-dns-setlocaladdress.js @@ -0,0 +1,37 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +const dns = require('dns'); +const resolver = new dns.Resolver(); +const promiseResolver = new dns.promises.Resolver(); + +// Verifies that setLocalAddress succeeds with IPv4 and IPv6 addresses +{ + resolver.setLocalAddress('127.0.0.1'); + resolver.setLocalAddress('::1'); + resolver.setLocalAddress('127.0.0.1', '::1'); + promiseResolver.setLocalAddress('127.0.0.1', '::1'); +} + +// Verify that setLocalAddress throws if called with an invalid address +{ + assert.throws(() => { + resolver.setLocalAddress('127.0.0.1', '127.0.0.1'); + }, Error); + assert.throws(() => { + resolver.setLocalAddress('::1', '::1'); + }, Error); + assert.throws(() => { + resolver.setLocalAddress('bad'); + }, Error); + assert.throws(() => { + resolver.setLocalAddress(123); + }, Error); + assert.throws(() => { + resolver.setLocalAddress(); + }, Error); + assert.throws(() => { + promiseResolver.setLocalAddress(); + }, Error); +} |