Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions benchmark/net/net-socketaddress-parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const common = require('../common.js');
const { SocketAddress } = require('net');

const inputs = {
Comment thread
araujogui marked this conversation as resolved.
'ipv4': [
'127.0.0.1',
'10.168.209.250',
'255.255.255.255',
],
'ipv4-port': [
'127.0.0.1:80',
'10.168.209.250:8080',
'255.255.255.255:65535',
],
'ipv6': [
'[::1]',
'[2001:db8::1]',
'[fe80::1ff:fe23:4567:890a]',
],
'ipv6-port': [
'[::1]:80',
'[2001:db8::1]:8080',
'[::ffff:127.0.0.1]:65535',
],
'invalid': [
'localhost',
'0x7f.1',
'127.0.0.1:99999',
'[::1',
],
};

const bench = common.createBenchmark(main, {
n: [1e6],
input: Object.keys(inputs),
});

function main({ n, input }) {
const values = inputs[input];
const length = values.length;

bench.start();
for (let i = 0; i < n; i++) {
SocketAddress.parse(values[i % length]);
}
bench.end(n);
}
27 changes: 27 additions & 0 deletions doc/api/net.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,40 @@ added:
added:
- v23.4.0
- v22.13.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64832
description: Input is now parsed strictly. URL syntax, IPv6 zone ids and
legacy IPv4 formats such as octal, hexadecimal and shorthand
notation are no longer accepted.
-->

* `input` {string} An input string containing an IP address and optional port,
e.g. `123.1.2.3:1234` or `[1::1]:1234`.
* Returns: {net.SocketAddress} Returns a `SocketAddress` if parsing was successful.
Otherwise returns `undefined`.

The entire input must match one of the following forms:

```text
socket-address = ipv4-socket-address / ipv6-socket-address
ipv4-socket-address = ipv4-address [ ":" port ]
ipv6-socket-address = "[" ipv6-address "]" [ ":" port ]

ipv4-address = octet 3( "." octet )
octet = 1*3DIGIT ; no leading zeros; value <= 255
ipv6-address = RFC 4291 textual form: groups of 1*4HEXDIG (leading
zeros allowed, case-insensitive), "::" compression, and
an optional trailing embedded ipv4-address
port = 1*DIGIT ; leading zeros allowed; value <= 65535
```

Anything else returns `undefined`, including URL components such as userinfo,
paths, queries and fragments, surrounding whitespace, host names, non-ASCII
digits, and the legacy IPv4 notations that permit octal (`0177.0.0.1`),
hexadecimal (`0x7f.0.0.1`), integer (`2130706433`) and shorthand (`127.1`)
addresses. IPv6 zone ids such as `%2` or `%eth0` are not accepted.

## Class: `net.Server`

<!-- YAML
Expand Down
54 changes: 18 additions & 36 deletions lib/internal/socketaddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {

const {
SocketAddress: _SocketAddress,
parseSocketAddress,
AF_INET,
AF_INET6,
} = internalBinding('block_list');
Expand Down Expand Up @@ -37,11 +38,23 @@ const {
kDeserialize,
} = require('internal/worker/js_transferable');

const { URLParse } = require('internal/url');

const kHandle = Symbol('kHandle');
const kDetail = Symbol('kDetail');

class InternalSocketAddress {
constructor(handle) {
markTransferMode(this, true, false);

this[kHandle] = handle;
this[kDetail] = this[kHandle]?.detail({
address: undefined,
port: undefined,
family: undefined,
flowlabel: undefined,
});
}
}

class SocketAddress {
static isSocketAddress(value) {
return value?.[kHandle] !== undefined;
Expand Down Expand Up @@ -149,40 +162,9 @@ class SocketAddress {
*/
static parse(input) {
validateString(input, 'input');
// While URL.parse is not expected to throw, there are several
// other pieces here that do... the destucturing, the SocketAddress
// constructor, etc. So we wrap this in a try/catch to be safe.
try {
const {
hostname: address,
port,
} = URLParse(`http://${input}`);
if (address[0] === '[' && address[address.length - 1] === ']') {
return new SocketAddress({
address: address.slice(1, -1),
port: port | 0,
family: 'ipv6',
});
}
return new SocketAddress({ address, port: port | 0 });
} catch {
// Ignore errors here. Return undefined if the input cannot
// be successfully parsed or is not a proper socket address.
}
}
}

class InternalSocketAddress {
constructor(handle) {
markTransferMode(this, true, false);

this[kHandle] = handle;
this[kDetail] = this[kHandle]?.detail({
address: undefined,
port: undefined,
family: undefined,
flowlabel: undefined,
});
const handle = parseSocketAddress(input);
if (handle === undefined) return undefined;
return new InternalSocketAddress(handle);
}
}

Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
'src/node_shadow_realm.cc',
'src/node_snapshotable.cc',
'src/node_sockaddr.cc',
'src/node_sockaddr_parser.cc',
'src/node_stat_watcher.cc',
'src/node_symbols.cc',
'src/node_task_queue.cc',
Expand Down Expand Up @@ -297,6 +298,7 @@
'src/node_snapshot_builder.h',
'src/node_sockaddr.h',
'src/node_sockaddr-inl.h',
'src/node_sockaddr_parser.h',
'src/node_stat_watcher.h',
'src/node_union_bytes.h',
'src/node_url.h',
Expand Down
32 changes: 32 additions & 0 deletions src/node_sockaddr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
#include "node_errors.h"
#include "node_hash.h"
#include "node_sockaddr-inl.h" // NOLINT(build/include_inline)
#include "node_sockaddr_parser.h"
#include "uv.h"

#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace node {
Expand Down Expand Up @@ -69,6 +72,19 @@ bool SocketAddress::New(int32_t family,
family, host, port, reinterpret_cast<sockaddr_storage*>(addr->storage()));
}

bool SocketAddress::Parse(std::string_view input, SocketAddress* addr) {
std::optional<sockaddr_parser::parse_result> parsed =
sockaddr_parser::ParseSocketAddress(input);
if (!parsed.has_value()) return false;

CHECK_LE(parsed->host.size(), sockaddr_parser::kMaxHostLength);

char host[sockaddr_parser::kMaxHostLength + 1];
host[parsed->host.copy(host, parsed->host.size())] = '\0';

return New(parsed->is_ipv6 ? AF_INET6 : AF_INET, host, parsed->port, addr);
}

size_t SocketAddress::Hash::operator()(const SocketAddress& addr) const {
// Hash only the meaningful bytes (family + port + address), not the
// full 128-byte sockaddr_storage.
Expand Down Expand Up @@ -1126,6 +1142,8 @@ void SocketAddressBase::Initialize(Environment* env, Local<Object> target) {
"SocketAddress",
GetConstructorTemplate(env),
SetConstructorFunctionFlag::NONE);

SetMethod(env->context(), target, "parseSocketAddress", Parse);
}

BaseObjectPtr<SocketAddressBase> SocketAddressBase::Create(
Expand Down Expand Up @@ -1164,6 +1182,20 @@ void SocketAddressBase::New(const FunctionCallbackInfo<Value>& args) {
new SocketAddressBase(env, args.This(), std::move(addr));
}

void SocketAddressBase::Parse(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString()); // input

Utf8Value input(env->isolate(), args[0]);

auto addr = std::make_shared<SocketAddress>();
if (!SocketAddress::Parse(input.ToStringView(), addr.get())) return;

BaseObjectPtr<SocketAddressBase> base =
SocketAddressBase::Create(env, std::move(addr));
if (base) args.GetReturnValue().Set(base->object());
}

void SocketAddressBase::Detail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject());
Expand Down
5 changes: 5 additions & 0 deletions src/node_sockaddr.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <list>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>

namespace node {
Expand Down Expand Up @@ -59,6 +60,9 @@ class SocketAddress : public MemoryRetainer {

static bool New(const char* host, uint32_t port, SocketAddress* addr);

// Returns true if parsing input as an "ip[:port]" socket address succeeded.
static bool Parse(std::string_view input, SocketAddress* addr);

// Returns the port for an IPv4 or IPv6 address.
inline static int GetPort(const sockaddr* addr);
inline static int GetPort(const sockaddr_storage* addr);
Expand Down Expand Up @@ -157,6 +161,7 @@ class SocketAddressBase : public BaseObject {
Environment* env, std::shared_ptr<SocketAddress> address);

static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Parse(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);
Expand Down
Loading
Loading