Skip to content

Commit

Permalink
report: add fallback for uv_getnameinfo() failures
Browse files Browse the repository at this point in the history
Attempt to report the host and port in the case that uv_getnameinfo()
fails.
  • Loading branch information
richardlau committed Feb 19, 2019
1 parent 8375c70 commit b49fc72
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 59 deletions.
60 changes: 33 additions & 27 deletions src/node_report_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,42 @@ using node::MallocedBuffer;

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

// Utility function to format socket information.
static bool ReportEndpoint(uv_handle_t* h,
struct sockaddr* addr,
const char* name,
JSONWriter* writer) {
uv_getnameinfo_t endpoint;
if (uv_getnameinfo(h->loop, &endpoint, nullptr, addr, NI_NUMERICSERV) == 0) {
writer->json_objectstart(name);
writer->json_keyvalue("host", endpoint.host);
writer->json_keyvalue("port", endpoint.service);
writer->json_objectend();
return true;
} else {
char host[INET6_ADDRSTRLEN];
int family = addr->sa_family;
if (uv_inet_ntop(family, addr, host, sizeof(host)) == 0) {
std::string port = std::to_string(ntohs(family == AF_INET ?
reinterpret_cast<sockaddr_in*>(addr)->sin_port :
reinterpret_cast<sockaddr_in6*>(addr)->sin6_port));
writer->json_objectstart(name);
writer->json_keyvalue("host", host);
writer->json_keyvalue("port", port);
writer->json_objectend();
return true;
}
return false;
}
}

// 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 +54,17 @@ 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 (rc != 0 || !ReportEndpoint(h, addr, "localEndpoint", writer)) {
writer->json_keyvalue("localEndpoint", null);
}
if (!wrote_local_endpoint) writer->json_keyvalue("localEndpoint", null);

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;
}
if (rc != 0 || !ReportEndpoint(h, addr, "remoteEndpoint", writer)) {
writer->json_keyvalue("remoteEndpoint", null);
}
}
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.toString();
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.toString());
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

0 comments on commit b49fc72

Please sign in to comment.