Skip to content

Commit

Permalink
dns: add setLocalAddress to Resolver
Browse files Browse the repository at this point in the history
Fixes: #34818

PR-URL: #34824
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Roman Reiss <[email protected]>
  • Loading branch information
daguej authored and MylesBorins committed Apr 6, 2021
1 parent f41c28c commit ed79c98
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 0 deletions.
21 changes: 21 additions & 0 deletions doc/api/dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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
Expand Down
1 change: 1 addition & 0 deletions lib/internal/dns/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
12 changes: 12 additions & 0 deletions lib/internal/dns/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
66 changes: 66 additions & 0 deletions src/cares_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "req_wrap-inl.h"
#include "util-inl.h"
#include "uv.h"
#include "node_errors.h"

#include <cerrno>
#include <cstring>
Expand Down Expand Up @@ -2149,6 +2150,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());
Expand Down Expand Up @@ -2250,6 +2315,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 =
Expand Down
37 changes: 37 additions & 0 deletions test/parallel/test-dns-setlocaladdress.js
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit ed79c98

Please sign in to comment.