Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dns: add setLocalAddress to Resolver #34824

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/api/dns.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/internal/dns/promises.js
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
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
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 @@ -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());
Expand Down Expand Up @@ -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 =
Expand Down
37 changes: 37 additions & 0 deletions 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();
daguej marked this conversation as resolved.
Show resolved Hide resolved
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);
}