net: add SocketAddress class

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/37917
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2021-03-25 12:19:30 -07:00 committed by Myles Borins
parent a4169ce519
commit daa8a7bbcf
No known key found for this signature in database
GPG Key ID: 933B01F40B5CA946
10 changed files with 516 additions and 0 deletions

View File

@ -135,6 +135,51 @@ added: v15.0.0
The list of rules added to the blocklist.
## Class: `net.SocketAddress`
<!-- YAML
added: REPLACEME
-->
### `new net.SocketAddress([options])`
<!-- YAML
added: REPLACEME
-->
* `options` {Object}
* `address` {string} The network address as either an IPv4 or IPv6 string.
**Default**: `'127.0.0.1'` if `family` is `'ipv4'`; `'::'` if `family` is
`'ipv6'`.
* `family` {string} One of either `'ipv4'` or 'ipv6'`. **Default**: `'ipv4'`.
* `flowlabel` {number} An IPv6 flow-label used only if `family` is `'ipv6'`.
* `port` {number} An IP port.
### `socketaddress.address`
<!-- YAML
added: REPLACEME
-->
* Type {string}
### `socketaddress.family`
<!-- YAML
added: REPLACEME
-->
* Type {string} Either `'ipv4'` or `'ipv6'`.
### `socketaddress.flowlabel`
<!-- YAML
added: REPLACEME
-->
* Type {number}
### `socketaddress.port`
<!-- YAML
added: REPLACEME
-->
* Type {number}
## Class: `net.Server`
<!-- YAML
added: v0.1.90

View File

@ -573,6 +573,7 @@ In particular, the significant differences to `JSON` are:
* {KeyObject}s,
* {MessagePort}s,
* {net.BlockList}s,
* {net.SocketAddress}es,
* {X509Certificate}s.
```js

View File

@ -0,0 +1,153 @@
'use strict';
const {
ObjectSetPrototypeOf,
Symbol,
} = primordials;
const {
SocketAddress: _SocketAddress,
AF_INET,
AF_INET6,
} = internalBinding('block_list');
const {
validateObject,
validateString,
validatePort,
validateUint32,
} = require('internal/validators');
const {
codes: {
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
customInspectSymbol: kInspect,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const {
JSTransferable,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const kHandle = Symbol('kHandle');
const kDetail = Symbol('kDetail');
class SocketAddress extends JSTransferable {
static isSocketAddress(value) {
return value?.[kHandle] !== undefined;
}
constructor(options = {}) {
super();
validateObject(options, 'options');
const {
family = 'ipv4',
address = (family === 'ipv4' ? '127.0.0.1' : '::'),
port = 0,
flowlabel = 0,
} = options;
let type;
switch (family) {
case 'ipv4':
type = AF_INET;
break;
case 'ipv6':
type = AF_INET6;
break;
default:
throw new ERR_INVALID_ARG_VALUE('options.family', family);
}
validateString(address, 'options.address');
validatePort(port, 'options.port');
validateUint32(flowlabel, 'options.flowlabel', false);
this[kHandle] = new _SocketAddress(address, port, type, flowlabel);
this[kDetail] = this[kHandle].detail({
address: undefined,
port: undefined,
family: undefined,
flowlabel: undefined,
});
}
get address() {
return this[kDetail].address;
}
get port() {
return this[kDetail].port;
}
get family() {
return this[kDetail].family === AF_INET ? 'ipv4' : 'ipv6';
}
get flowlabel() {
// The flow label can be changed internally.
return this[kHandle].flowlabel();
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1
};
return `SocketAddress ${inspect(this.toJSON(), opts)}`;
}
[kClone]() {
const handle = this[kHandle];
return {
data: { handle },
deserializeInfo: 'internal/socketaddress:InternalSocketAddress',
};
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
this[kDetail] = handle.detail({
address: undefined,
port: undefined,
family: undefined,
flowlabel: undefined,
});
}
toJSON() {
return {
address: this.address,
port: this.port,
family: this.family,
flowlabel: this.flowlabel,
};
}
}
class InternalSocketAddress extends JSTransferable {
constructor(handle) {
super();
this[kHandle] = handle;
}
}
InternalSocketAddress.prototype.constructor =
SocketAddress.prototype.construtor;
ObjectSetPrototypeOf(InternalSocketAddress.prototype, SocketAddress.prototype);
module.exports = {
SocketAddress,
InternalSocketAddress,
};

View File

@ -122,6 +122,7 @@ const {
let cluster;
let dns;
let BlockList;
let SocketAddress;
const { clearTimeout } = require('timers');
const { kTimeout } = require('internal/timers');
@ -1751,6 +1752,10 @@ module.exports = {
BlockList ??= require('internal/blocklist').BlockList;
return BlockList;
},
get SocketAddress() {
SocketAddress ??= require('internal/socketaddress').SocketAddress;
return SocketAddress;
},
connect,
createConnection: connect,
createServer,

View File

@ -212,6 +212,7 @@
'lib/internal/repl/await.js',
'lib/internal/repl/history.js',
'lib/internal/repl/utils.js',
'lib/internal/socketaddress.js',
'lib/internal/socket_list.js',
'lib/internal/source_map/prepare_stack_trace.js',
'lib/internal/source_map/source_map.js',

View File

@ -258,6 +258,7 @@ constexpr size_t kFsStatsBufferLength =
V(fingerprint256_string, "fingerprint256") \
V(fingerprint_string, "fingerprint") \
V(flags_string, "flags") \
V(flowlabel_string, "flowlabel") \
V(fragment_string, "fragment") \
V(function_string, "function") \
V(get_data_clone_error_string, "_getDataCloneError") \
@ -465,6 +466,7 @@ constexpr size_t kFsStatsBufferLength =
V(script_context_constructor_template, v8::FunctionTemplate) \
V(secure_context_constructor_template, v8::FunctionTemplate) \
V(shutdown_wrap_template, v8::ObjectTemplate) \
V(socketaddress_constructor_template, v8::FunctionTemplate) \
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
V(qlogoutputstream_constructor_template, v8::ObjectTemplate) \
V(tcp_constructor_template, v8::FunctionTemplate) \

View File

@ -56,6 +56,7 @@ void OnFatalError(const char* location, const char* message);
V(ERR_CRYPTO_JOB_INIT_FAILED, Error) \
V(ERR_DLOPEN_FAILED, Error) \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
V(ERR_INVALID_ADDRESS, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
V(ERR_INVALID_ARG_TYPE, TypeError) \
@ -143,6 +144,7 @@ ERRORS_WITH_CODE(V)
V(ERR_DLOPEN_FAILED, "DLOpen failed") \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, \
"Context not associated with Node.js environment") \
V(ERR_INVALID_ADDRESS, "Invalid socket address") \
V(ERR_INVALID_MODULE, "No such module") \
V(ERR_INVALID_THIS, "Value of \"this\" is the wrong type") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \

View File

@ -3,6 +3,7 @@
#include "base64-inl.h"
#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "uv.h"
#include <memory>
@ -15,9 +16,11 @@ using v8::Array;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Int32;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace {
@ -752,6 +755,8 @@ void SocketAddressBlockListWrap::Initialize(
GetConstructorTemplate(env),
Environment::SetConstructorFunctionFlag::NONE);
SocketAddressBase::Initialize(env, target);
NODE_DEFINE_CONSTANT(target, AF_INET);
NODE_DEFINE_CONSTANT(target, AF_INET6);
}
@ -768,6 +773,144 @@ void SocketAddressBlockListWrap::TransferData::MemoryInfo(
blocklist_->MemoryInfo(tracker);
}
bool SocketAddressBase::HasInstance(Environment* env, Local<Value> value) {
return GetConstructorTemplate(env)->HasInstance(value);
}
Local<FunctionTemplate> SocketAddressBase::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl = env->socketaddress_constructor_template();
if (tmpl.IsEmpty()) {
tmpl = env->NewFunctionTemplate(New);
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "SocketAddress"));
tmpl->InstanceTemplate()->SetInternalFieldCount(
SocketAddressBase::kInternalFieldCount);
tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(tmpl, "detail", Detail);
env->SetProtoMethod(tmpl, "legacyDetail", LegacyDetail);
env->SetProtoMethodNoSideEffect(tmpl, "flowlabel", GetFlowLabel);
env->set_socketaddress_constructor_template(tmpl);
}
return tmpl;
}
void SocketAddressBase::Initialize(Environment* env, Local<Object> target) {
env->SetConstructorFunction(
target,
"SocketAddress",
GetConstructorTemplate(env),
Environment::SetConstructorFunctionFlag::NONE);
}
BaseObjectPtr<SocketAddressBase> SocketAddressBase::Create(
Environment* env,
std::shared_ptr<SocketAddress> address) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context()).ToLocal(&obj)) {
return BaseObjectPtr<SocketAddressBase>();
}
return MakeBaseObject<SocketAddressBase>(env, obj, std::move(address));
}
void SocketAddressBase::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
CHECK(args[0]->IsString()); // address
CHECK(args[1]->IsInt32()); // port
CHECK(args[2]->IsInt32()); // family
CHECK(args[3]->IsUint32()); // flow label
Utf8Value address(env->isolate(), args[0]);
int32_t port = args[1].As<Int32>()->Value();
int32_t family = args[2].As<Int32>()->Value();
uint32_t flow_label = args[3].As<Uint32>()->Value();
std::shared_ptr<SocketAddress> addr = std::make_shared<SocketAddress>();
if (!SocketAddress::New(family, *address, port, addr.get()))
return THROW_ERR_INVALID_ADDRESS(env);
addr->set_flow_label(flow_label);
new SocketAddressBase(env, args.This(), std::move(addr));
}
void SocketAddressBase::Detail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject());
Local<Object> detail = args[0].As<Object>();
SocketAddressBase* base;
ASSIGN_OR_RETURN_UNWRAP(&base, args.Holder());
Local<Value> address;
if (!ToV8Value(env->context(), base->address_->address()).ToLocal(&address))
return;
if (detail->Set(env->context(), env->address_string(), address).IsJust() &&
detail->Set(
env->context(),
env->port_string(),
Int32::New(env->isolate(), base->address_->port())).IsJust() &&
detail->Set(
env->context(),
env->family_string(),
Int32::New(env->isolate(), base->address_->family())).IsJust() &&
detail->Set(
env->context(),
env->flowlabel_string(),
Uint32::New(env->isolate(), base->address_->flow_label()))
.IsJust()) {
args.GetReturnValue().Set(detail);
}
}
void SocketAddressBase::GetFlowLabel(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
SocketAddressBase* base;
ASSIGN_OR_RETURN_UNWRAP(&base, args.Holder());
args.GetReturnValue().Set(base->address_->flow_label());
}
void SocketAddressBase::LegacyDetail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
SocketAddressBase* base;
ASSIGN_OR_RETURN_UNWRAP(&base, args.Holder());
args.GetReturnValue().Set(base->address_->ToJS(env));
}
SocketAddressBase::SocketAddressBase(
Environment* env,
Local<Object> wrap,
std::shared_ptr<SocketAddress> address)
: BaseObject(env, wrap),
address_(std::move(address)) {
MakeWeak();
}
void SocketAddressBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("address", address_);
}
std::unique_ptr<worker::TransferData>
SocketAddressBase::CloneForMessaging() const {
return std::make_unique<TransferData>(this);
}
void SocketAddressBase::TransferData::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("address", address_);
}
BaseObjectPtr<BaseObject> SocketAddressBase::TransferData::Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) {
return SocketAddressBase::Create(env, std::move(address_));
}
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(

View File

@ -148,6 +148,60 @@ class SocketAddress : public MemoryRetainer {
sockaddr_storage address_;
};
class SocketAddressBase : public BaseObject {
public:
static bool HasInstance(Environment* env, v8::Local<v8::Value> value);
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static BaseObjectPtr<SocketAddressBase> Create(
Environment* env,
std::shared_ptr<SocketAddress> address);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Detail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void LegacyDetail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetFlowLabel(const v8::FunctionCallbackInfo<v8::Value>& args);
SocketAddressBase(
Environment* env,
v8::Local<v8::Object> wrap,
std::shared_ptr<SocketAddress> address);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBase);
SET_SELF_SIZE(SocketAddressBase);
TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
class TransferData : public worker::TransferData {
public:
inline explicit TransferData(const SocketAddressBase* wrap)
: address_(wrap->address_) {}
inline explicit TransferData(std::shared_ptr<SocketAddress> address)
: address_(std::move(address)) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(SocketAddressBase::TransferData)
SET_SELF_SIZE(TransferData)
private:
std::shared_ptr<SocketAddress> address_;
};
private:
std::shared_ptr<SocketAddress> address_;
};
template <typename T>
class SocketAddressLRU : public MemoryRetainer {
public:

View File

@ -0,0 +1,110 @@
'use strict';
const common = require('../common');
const {
ok,
strictEqual,
throws,
} = require('assert');
const {
SocketAddress,
} = require('net');
{
const sa = new SocketAddress();
strictEqual(sa.address, '127.0.0.1');
strictEqual(sa.port, 0);
strictEqual(sa.family, 'ipv4');
strictEqual(sa.flowlabel, 0);
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => {
ok(SocketAddress.isSocketAddress(data));
strictEqual(data.address, '127.0.0.1');
strictEqual(data.port, 0);
strictEqual(data.family, 'ipv4');
strictEqual(data.flowlabel, 0);
mc.port1.close();
});
mc.port2.postMessage(sa);
}
{
const sa = new SocketAddress({});
strictEqual(sa.address, '127.0.0.1');
strictEqual(sa.port, 0);
strictEqual(sa.family, 'ipv4');
strictEqual(sa.flowlabel, 0);
}
{
const sa = new SocketAddress({
address: '123.123.123.123',
});
strictEqual(sa.address, '123.123.123.123');
strictEqual(sa.port, 0);
strictEqual(sa.family, 'ipv4');
strictEqual(sa.flowlabel, 0);
}
{
const sa = new SocketAddress({
address: '123.123.123.123',
port: 80
});
strictEqual(sa.address, '123.123.123.123');
strictEqual(sa.port, 80);
strictEqual(sa.family, 'ipv4');
strictEqual(sa.flowlabel, 0);
}
{
const sa = new SocketAddress({
family: 'ipv6'
});
strictEqual(sa.address, '::');
strictEqual(sa.port, 0);
strictEqual(sa.family, 'ipv6');
strictEqual(sa.flowlabel, 0);
}
{
const sa = new SocketAddress({
family: 'ipv6',
flowlabel: 1,
});
strictEqual(sa.address, '::');
strictEqual(sa.port, 0);
strictEqual(sa.family, 'ipv6');
strictEqual(sa.flowlabel, 1);
}
[1, false, 'hello'].forEach((i) => {
throws(() => new SocketAddress(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
[1, false, {}, [], 'test'].forEach((family) => {
throws(() => new SocketAddress({ family }), {
code: 'ERR_INVALID_ARG_VALUE'
});
});
[1, false, {}, []].forEach((address) => {
throws(() => new SocketAddress({ address }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
[-1, false, {}, []].forEach((port) => {
throws(() => new SocketAddress({ port }), {
code: 'ERR_SOCKET_BAD_PORT'
});
});
throws(() => new SocketAddress({ flowlabel: -1 }), {
code: 'ERR_OUT_OF_RANGE'
});