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

report: add fallback for uv_getnameinfo() failures #26140

Closed
wants to merge 3 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 41 additions & 29 deletions src/node_report_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,52 @@ using node::MallocedBuffer;

static constexpr auto null = JSONWriter::Null{};

// Utility function to format socket information.
static void ReportEndpoint(uv_handle_t* h,
struct sockaddr* addr,
const char* name,
JSONWriter* writer) {
if (addr == nullptr) {
writer->json_keyvalue(name, null);
return;
}

uv_getnameinfo_t endpoint;
char* host = nullptr;
char hostbuf[INET6_ADDRSTRLEN];
const int family = addr->sa_family;
const int port = ntohs(family == AF_INET ?
reinterpret_cast<sockaddr_in*>(addr)->sin_port :
reinterpret_cast<sockaddr_in6*>(addr)->sin6_port);

if (uv_getnameinfo(h->loop, &endpoint, nullptr, addr, NI_NUMERICSERV) == 0) {
host = endpoint.host;
DCHECK_EQ(port, std::stoi(endpoint.service));
} else {
const void* src = family == AF_INET ?
static_cast<void*>(
&(reinterpret_cast<sockaddr_in*>(addr)->sin_addr)) :
static_cast<void*>(
&(reinterpret_cast<sockaddr_in6*>(addr)->sin6_addr));
if (uv_inet_ntop(family, src, hostbuf, sizeof(hostbuf)) == 0) {
host = hostbuf;
}
}
writer->json_objectstart(name);
if (host != nullptr) {
writer->json_keyvalue("host", host);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's extremely unlikely for host to be nullptr (both uv_getnameinfo() and uv_inet_ntop() would have to fail) here but in the case that it is I've opted to omit the host key. Let me know if you'd prefer to emit host: null instead (i.e. the host key is always written), or something else (host: '<unable to determine>'?).

}
writer->json_keyvalue("port", port);
writer->json_objectend();
}

// Utility function to format libuv socket information.
static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) {
struct sockaddr_storage addr_storage;
struct sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_storage);
uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(h);
int addr_size = sizeof(addr_storage);
int rc = -1;
bool wrote_local_endpoint = false;
bool wrote_remote_endpoint = false;

switch (h->type) {
case UV_UDP:
Expand All @@ -27,38 +64,13 @@ static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) {
default:
break;
}
if (rc == 0) {
// uv_getnameinfo will format host and port and handle IPv4/IPv6.
uv_getnameinfo_t local;
rc = uv_getnameinfo(h->loop, &local, nullptr, addr, NI_NUMERICSERV);

if (rc == 0) {
writer->json_objectstart("localEndpoint");
writer->json_keyvalue("host", local.host);
writer->json_keyvalue("port", local.service);
writer->json_objectend();
wrote_local_endpoint = true;
}
}
if (!wrote_local_endpoint) writer->json_keyvalue("localEndpoint", null);
ReportEndpoint(h, rc == 0 ? addr : nullptr, "localEndpoint", writer);

if (h->type == UV_TCP) {
// Get the remote end of the connection.
rc = uv_tcp_getpeername(&handle->tcp, addr, &addr_size);
if (rc == 0) {
uv_getnameinfo_t remote;
rc = uv_getnameinfo(h->loop, &remote, nullptr, addr, NI_NUMERICSERV);

if (rc == 0) {
writer->json_objectstart("remoteEndpoint");
writer->json_keyvalue("host", remote.host);
writer->json_keyvalue("port", remote.service);
writer->json_objectend();
wrote_local_endpoint = true;
}
}
ReportEndpoint(h, rc == 0 ? addr : nullptr, "remoteEndpoint", writer);
}
if (!wrote_remote_endpoint) writer->json_keyvalue("remoteEndpoint", null);
}

// Utility function to format libuv path information.
Expand Down
89 changes: 57 additions & 32 deletions test/node-report/test-api-uvhandles.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ if (process.argv[2] === 'child') {
const data = { pid: child_process.pid,
tcp_address: server.address(),
udp_address: udp_socket.address(),
skip_fs_watch: (watcher === undefined ?
'fs.watch() unavailable' :
false) };
skip_fs_watch: (watcher === undefined) };
process.send(data);
http.get({ port: server.address().port });
});
Expand All @@ -74,6 +72,8 @@ if (process.argv[2] === 'child') {
tmpdir.refresh();
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
const child = fork('--experimental-report', [__filename, 'child'], options);
let child_data;
child.on('message', (data) => { child_data = data; });
let stderr = '';
child.stderr.on('data', (chunk) => { stderr += chunk; });
let stdout = '';
Expand All @@ -94,36 +94,61 @@ if (process.argv[2] === 'child') {
assert.deepStrictEqual(reports, [], report_msg, reports);

const report = JSON.parse(stdout);
let fs = 0;
let poll = 0;
let process = 0;
let timer = 0;
let pipe = 0;
let tcp = 0;
let udp = 0;
const fs_msg = 'fs_event not found';
const poll_msg = 'poll_event not found';
const process_msg = 'process event not found';
const timer_msg = 'timer event not found';
const pipe_msg = 'pipe event not found';
const tcp_msg = 'tcp event not found';
const udp_msg = 'udp event not found';
for (const entry in report.libuv) {
if (report.libuv[entry].type === 'fs_event') fs = 1;
else if (report.libuv[entry].type === 'fs_poll') poll = 1;
else if (report.libuv[entry].type === 'process') process = 1;
else if (report.libuv[entry].type === 'timer') timer = 1;
else if (report.libuv[entry].type === 'pipe') pipe = 1;
else if (report.libuv[entry].type === 'tcp') tcp = 1;
else if (report.libuv[entry].type === 'udp') udp = 1;
const prefix = common.isWindows ? '\\\\?\\' : '';
const expected_filename = `${prefix}${__filename}`;
const found_tcp = [];
// Functions are named to aid debugging when they are not called.
const validators = {
fs_event: common.mustCall(function fs_event_validator(handle) {
if (!child_data.skip_fs_watch) {
assert.strictEqual(handle.filename, expected_filename);
assert(handle.is_referenced);
}
}),
fs_poll: common.mustCall(function fs_poll_validator(handle) {
assert.strictEqual(handle.filename, expected_filename);
assert(handle.is_referenced);
}),
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
assert(handle.is_referenced);
}),
process: common.mustCall(function process_validator(handle) {
assert.strictEqual(handle.pid, child_data.pid);
assert(handle.is_referenced);
}),
tcp: common.mustCall(function tcp_validator(handle) {
// TCP handles. The report should contain three sockets:
// 1. The server's listening socket.
// 2. The inbound socket making the request.
// 3. The outbound socket sending the response.
const port = child_data.tcp_address.port;
if (handle.localEndpoint.port === port) {
if (handle.remoteEndpoint === null) {
found_tcp.push('listening');
} else {
found_tcp.push('inbound');
}
} else if (handle.remoteEndpoint.port === port) {
found_tcp.push('outbound');
}
assert(handle.is_referenced);
}, 3),
timer: common.mustCall(function timer_validator(handle) {
assert(!handle.is_referenced);
assert.strictEqual(handle.repeat, 0);
}),
udp: common.mustCall(function udp_validator(handle) {
assert.strictEqual(handle.localEndpoint.port,
child_data.udp_address.port);
assert(handle.is_referenced);
}),
};
for (const entry of report.libuv) {
if (validators[entry.type]) validators[entry.type](entry);
}
for (const socket of ['listening', 'inbound', 'outbound']) {
assert(found_tcp.includes(socket), `${socket} TCP socket was not found`);
}
assert.deepStrictEqual(fs, 1, fs_msg);
assert.deepStrictEqual(poll, 1, poll_msg);
assert.deepStrictEqual(process, 1, process_msg);
assert.deepStrictEqual(timer, 1, timer_msg);
assert.deepStrictEqual(pipe, 1, pipe_msg);
assert.deepStrictEqual(tcp, 1, tcp_msg);
assert.deepStrictEqual(udp, 1, udp_msg);

// Common report tests.
helper.validateContent(stdout);
Expand Down