Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Dague <jd@josh3736.net>2020-08-18 09:23:25 +0300
committerAntoine du Hamel <duhamelantoine1995@gmail.com>2020-10-25 13:18:39 +0300
commit3e10ce30b471214cc5020a92d628357e9ed4fb91 (patch)
tree4c9092895509dab0a0092e42cc53da4890643e83
parent70834250e83fa89e92314be37a9592978ee8c6bd (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.md21
-rw-r--r--lib/internal/dns/promises.js1
-rw-r--r--lib/internal/dns/utils.js12
-rw-r--r--src/cares_wrap.cc66
-rw-r--r--test/parallel/test-dns-setlocaladdress.js37
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);
+}