Skip to content

Commit ed79c98

Browse files
daguejMylesBorins
authored andcommittedApr 6, 2021
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 f41c28c commit ed79c98

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
@@ -116,6 +116,27 @@ added: v8.3.0
116116
Cancel all outstanding DNS queries made by this resolver. The corresponding
117117
callbacks will be called with an error with code `ECANCELLED`.
118118

119+
### `resolver.setLocalAddress([ipv4][, ipv6])`
120+
<!-- YAML
121+
added: REPLACEME
122+
-->
123+
124+
* `ipv4` {string} A string representation of an IPv4 address.
125+
**Default:** `'0.0.0.0'`
126+
* `ipv6` {string} A string representation of an IPv6 address.
127+
**Default:** `'::0'`
128+
129+
The resolver instance will send its requests from the specified IP address.
130+
This allows programs to specify outbound interfaces when used on multi-homed
131+
systems.
132+
133+
If a v4 or v6 address is not specified, it is set to the default, and the
134+
operating system will choose a local address automatically.
135+
136+
The resolver will use the v4 local address when making requests to IPv4 DNS
137+
servers, and the v6 local address when making requests to IPv6 DNS servers.
138+
The `rrtype` of resolution requests has no impact on the local address used.
139+
119140
## `dns.getServers()`
120141
<!-- YAML
121142
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>
@@ -2149,6 +2150,70 @@ void SetServers(const FunctionCallbackInfo<Value>& args) {
21492150
args.GetReturnValue().Set(err);
21502151
}
21512152

2153+
void SetLocalAddress(const FunctionCallbackInfo<Value>& args) {
2154+
Environment* env = Environment::GetCurrent(args);
2155+
ChannelWrap* channel;
2156+
ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());
2157+
2158+
CHECK_EQ(args.Length(), 2);
2159+
CHECK(args[0]->IsString());
2160+
2161+
Isolate* isolate = args.GetIsolate();
2162+
node::Utf8Value ip0(isolate, args[0]);
2163+
2164+
unsigned char addr0[sizeof(struct in6_addr)];
2165+
unsigned char addr1[sizeof(struct in6_addr)];
2166+
int type0 = 0;
2167+
2168+
// This function accepts 2 arguments. The first may be either an IPv4
2169+
// address or an IPv6 address. If present, the second argument must be the
2170+
// other type of address. Otherwise, the unspecified type of IP is set
2171+
// to 0 (any).
2172+
2173+
if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) {
2174+
ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr0));
2175+
type0 = 4;
2176+
} else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) {
2177+
ares_set_local_ip6(channel->cares_channel(), addr0);
2178+
type0 = 6;
2179+
} else {
2180+
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
2181+
return;
2182+
}
2183+
2184+
if (!args[1]->IsUndefined()) {
2185+
CHECK(args[1]->IsString());
2186+
node::Utf8Value ip1(isolate, args[1]);
2187+
2188+
if (uv_inet_pton(AF_INET, *ip1, &addr1) == 0) {
2189+
if (type0 == 4) {
2190+
THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses.");
2191+
return;
2192+
} else {
2193+
ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr1));
2194+
}
2195+
} else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) {
2196+
if (type0 == 6) {
2197+
THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv6 addresses.");
2198+
return;
2199+
} else {
2200+
ares_set_local_ip6(channel->cares_channel(), addr1);
2201+
}
2202+
} else {
2203+
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP address.");
2204+
return;
2205+
}
2206+
} else {
2207+
// No second arg specifed
2208+
if (type0 == 4) {
2209+
memset(&addr1, 0, sizeof(addr1));
2210+
ares_set_local_ip6(channel->cares_channel(), addr1);
2211+
} else {
2212+
ares_set_local_ip4(channel->cares_channel(), 0);
2213+
}
2214+
}
2215+
}
2216+
21522217
void Cancel(const FunctionCallbackInfo<Value>& args) {
21532218
ChannelWrap* channel;
21542219
ASSIGN_OR_RETURN_UNWRAP(&channel, args.Holder());
@@ -2250,6 +2315,7 @@ void Initialize(Local<Object> target,
22502315

22512316
env->SetProtoMethodNoSideEffect(channel_wrap, "getServers", GetServers);
22522317
env->SetProtoMethod(channel_wrap, "setServers", SetServers);
2318+
env->SetProtoMethod(channel_wrap, "setLocalAddress", SetLocalAddress);
22532319
env->SetProtoMethod(channel_wrap, "cancel", Cancel);
22542320

22552321
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)