Skip to content

Commit 548f91a

Browse files
daguejtargos
authored andcommittedNov 3, 2020
dns: add setLocalAddress to Resolver
Fixes: #34818 PR-URL: #34824 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io>
1 parent cfe61b8 commit 548f91a

File tree

5 files changed

+137
-0
lines changed

5 files changed

+137
-0
lines changed
 

‎doc/api/dns.md

+21
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,27 @@ added: v8.3.0
117117
Cancel all outstanding DNS queries made by this resolver. The corresponding
118118
callbacks will be called with an error with code `ECANCELLED`.
119119

120+
### `resolver.setLocalAddress([ipv4][, ipv6])`
121+
<!-- YAML
122+
added: REPLACEME
123+
-->
124+
125+
* `ipv4` {string} A string representation of an IPv4 address.
126+
**Default:** `'0.0.0.0'`
127+
* `ipv6` {string} A string representation of an IPv6 address.
128+
**Default:** `'::0'`
129+
130+
The resolver instance will send its requests from the specified IP address.
131+
This allows programs to specify outbound interfaces when used on multi-homed
132+
systems.
133+
134+
If a v4 or v6 address is not specified, it is set to the default, and the
135+
operating system will choose a local address automatically.
136+
137+
The resolver will use the v4 local address when making requests to IPv4 DNS
138+
servers, and the v6 local address when making requests to IPv6 DNS servers.
139+
The `rrtype` of resolution requests has no impact on the local address used.
140+
120141
## `dns.getServers()`
121142
<!-- YAML
122143
added: v0.11.3

‎lib/internal/dns/promises.js

+1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ class Resolver {
217217

218218
Resolver.prototype.getServers = CallbackResolver.prototype.getServers;
219219
Resolver.prototype.setServers = CallbackResolver.prototype.setServers;
220+
Resolver.prototype.setLocalAddress = CallbackResolver.prototype.setLocalAddress;
220221
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
221222
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
222223
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');

‎lib/internal/dns/utils.js

+12
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ class Resolver {
114114
throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
115115
}
116116
}
117+
118+
setLocalAddress(ipv4, ipv6) {
119+
if (typeof ipv4 !== 'string') {
120+
throw new ERR_INVALID_ARG_TYPE('ipv4', 'String', ipv4);
121+
}
122+
123+
if (typeof ipv6 !== 'string' && ipv6 !== undefined) {
124+
throw new ERR_INVALID_ARG_TYPE('ipv6', ['String', 'undefined'], ipv6);
125+
}
126+
127+
this._handle.setLocalAddress(ipv4, ipv6);
128+
}
117129
}
118130

119131
let defaultResolver = new Resolver();

‎src/cares_wrap.cc

+66
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "req_wrap-inl.h"
3030
#include "util-inl.h"
3131
#include "uv.h"
32+
#include "node_errors.h"
3233

3334
#include <cerrno>
3435
#include <cstring>
@@ -2227,6 +2228,70 @@ void SetServers(const FunctionCallbackInfo<Value>& args) {
22272228
args.GetReturnValue().Set(err);
22282229
}
22292230

2231+
void SetLocalAddress(const FunctionCallbackInfo<Value>& args) {
2232+
Environment* env = Environment::GetCurrent(args);
2233+
ChannelWrap* channel;
2234+
ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());
2235+
2236+
CHECK_EQ(args.Length(), 2);
2237+
CHECK(args[0]->IsString());
2238+
2239+
Isolate* isolate = args.GetIsolate();
2240+
node::Utf8Value ip0(isolate, args[0]);
2241+
2242+
unsigned char addr0[sizeof(struct in6_addr)];
2243+
unsigned char addr1[sizeof(struct in6_addr)];
2244+
int type0 = 0;
2245+
2246+
// This function accepts 2 arguments. The first may be either an IPv4
2247+
// address or an IPv6 address. If present, the second argument must be the
2248+
// other type of address. Otherwise, the unspecified type of IP is set
2249+
// to 0 (any).
2250+
2251+
if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) {
2252+
ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr0));
2253+
type0 = 4;
2254+
} else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) {
2255+
ares_set_local_ip6(channel->cares_channel(), addr0);
2256+
type0 = 6;
2257+
} else {
2258+
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
2259+
return;
2260+
}
2261+
2262+
if (!args[1]->IsUndefined()) {
2263+
CHECK(args[1]->IsString());
2264+
node::Utf8Value ip1(isolate, args[1]);
2265+
2266+
if (uv_inet_pton(AF_INET, *ip1, &addr1) == 0) {
2267+
if (type0 == 4) {
2268+
THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses.");
2269+
return;
2270+
} else {
2271+
ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr1));
2272+
}
2273+
} else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) {
2274+
if (type0 == 6) {
2275+
THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses.");
2276+
return;
2277+
} else {
2278+
ares_set_local_ip6(channel->cares_channel(), addr1);
2279+
}
2280+
} else {
2281+
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
2282+
return;
2283+
}
2284+
} else {
2285+
// No second arg specifed
2286+
if (type0 == 4) {
2287+
memset(&addr1, 0, sizeof(addr1));
2288+
ares_set_local_ip6(channel->cares_channel(), addr1);
2289+
} else {
2290+
ares_set_local_ip4(channel->cares_channel(), 0);
2291+
}
2292+
}
2293+
}
2294+
22302295
void Cancel(const FunctionCallbackInfo<Value>& args) {
22312296
ChannelWrap* channel;
22322297
ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());
@@ -2329,6 +2394,7 @@ void Initialize(Local<Object> target,
23292394

23302395
env->SetProtoMethodNoSideEffect(channel_wrap, "getServers", GetServers);
23312396
env->SetProtoMethod(channel_wrap, "setServers", SetServers);
2397+
env->SetProtoMethod(channel_wrap, "setLocalAddress", SetLocalAddress);
23322398
env->SetProtoMethod(channel_wrap, "cancel", Cancel);
23332399

23342400
Local<String> channelWrapString =
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
5+
const dns = require('dns');
6+
const resolver = new dns.Resolver();
7+
const promiseResolver = new dns.promises.Resolver();
8+
9+
// Verifies that setLocalAddress succeeds with IPv4 and IPv6 addresses
10+
{
11+
resolver.setLocalAddress('127.0.0.1');
12+
resolver.setLocalAddress('::1');
13+
resolver.setLocalAddress('127.0.0.1', '::1');
14+
promiseResolver.setLocalAddress('127.0.0.1', '::1');
15+
}
16+
17+
// Verify that setLocalAddress throws if called with an invalid address
18+
{
19+
assert.throws(() => {
20+
resolver.setLocalAddress('127.0.0.1', '127.0.0.1');
21+
}, Error);
22+
assert.throws(() => {
23+
resolver.setLocalAddress('::1', '::1');
24+
}, Error);
25+
assert.throws(() => {
26+
resolver.setLocalAddress('bad');
27+
}, Error);
28+
assert.throws(() => {
29+
resolver.setLocalAddress(123);
30+
}, Error);
31+
assert.throws(() => {
32+
resolver.setLocalAddress();
33+
}, Error);
34+
assert.throws(() => {
35+
promiseResolver.setLocalAddress();
36+
}, Error);
37+
}

0 commit comments

Comments
 (0)