From b49fc7249fcaa9a021f6913445fd8c0f5de63775 Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Fri, 15 Feb 2019 14:26:08 -0500 Subject: [PATCH] report: add fallback for uv_getnameinfo() failures Attempt to report the host and port in the case that uv_getnameinfo() fails. --- src/node_report_utils.cc | 60 +++++++++-------- test/node-report/test-api-uvhandles.js | 89 +++++++++++++++++--------- 2 files changed, 90 insertions(+), 59 deletions(-) diff --git a/src/node_report_utils.cc b/src/node_report_utils.cc index db7290118723e0..845d82b63b8647 100644 --- a/src/node_report_utils.cc +++ b/src/node_report_utils.cc @@ -7,6 +7,35 @@ 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(addr)->sin_port : + reinterpret_cast(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; @@ -14,8 +43,6 @@ static void ReportEndpoints(uv_handle_t* h, JSONWriter* writer) { uv_any_handle* handle = reinterpret_cast(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: @@ -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. diff --git a/test/node-report/test-api-uvhandles.js b/test/node-report/test-api-uvhandles.js index 570fa42aa2f265..408eff0c6cd9c1 100644 --- a/test/node-report/test-api-uvhandles.js +++ b/test/node-report/test-api-uvhandles.js @@ -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 }); }); @@ -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 = ''; @@ -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);