|
11 | 11 | #include <winsock2.h> |
12 | 12 | #include <ws2tcpip.h> |
13 | 13 | #include <mstcpip.h> |
| 14 | +#include <iphlpapi.h> |
| 15 | +#include <tcpestats.h> |
| 16 | +#include <vector> |
14 | 17 | #else |
15 | 18 | #include <errno.h> |
16 | 19 | #include <limits.h> |
@@ -59,6 +62,177 @@ static napi_value Unavailable(napi_env env, const char* error) { |
59 | 62 | return result; |
60 | 63 | } |
61 | 64 |
|
| 65 | +#if defined(_WIN32) |
| 66 | +static void FormatWindowsError(const char* operation, DWORD error, char* message, size_t message_size) { |
| 67 | + snprintf(message, message_size, "%s failed: %lu", operation, static_cast<unsigned long>(error)); |
| 68 | +} |
| 69 | + |
| 70 | +static bool GetStringArg(napi_env env, napi_value value, char* buffer, size_t buffer_size) { |
| 71 | + size_t length = 0; |
| 72 | + if (napi_get_value_string_utf8(env, value, buffer, buffer_size, &length) != napi_ok) { |
| 73 | + return false; |
| 74 | + } |
| 75 | + |
| 76 | + return length > 0 && length < buffer_size; |
| 77 | +} |
| 78 | + |
| 79 | +static bool GetPortArg(napi_env env, napi_value value, uint16_t* port) { |
| 80 | + uint32_t port_value = 0; |
| 81 | + if (napi_get_value_uint32(env, value, &port_value) != napi_ok) { |
| 82 | + return false; |
| 83 | + } |
| 84 | + |
| 85 | + if (port_value == 0 || port_value > 65535) { |
| 86 | + return false; |
| 87 | + } |
| 88 | + |
| 89 | + *port = static_cast<uint16_t>(port_value); |
| 90 | + return true; |
| 91 | +} |
| 92 | + |
| 93 | +static bool ParseIpv4Address(const char* address, DWORD* parsed_address) { |
| 94 | + const char* normalized_address = address; |
| 95 | + if (strncmp(address, "::ffff:", 7) == 0) { |
| 96 | + normalized_address = address + 7; |
| 97 | + } |
| 98 | + |
| 99 | + IN_ADDR in_address; |
| 100 | + if (InetPtonA(AF_INET, normalized_address, &in_address) != 1) { |
| 101 | + return false; |
| 102 | + } |
| 103 | + |
| 104 | + *parsed_address = in_address.S_un.S_addr; |
| 105 | + return true; |
| 106 | +} |
| 107 | + |
| 108 | +static bool FindTcpRowByEndpoint( |
| 109 | + DWORD local_address, |
| 110 | + uint16_t local_port, |
| 111 | + DWORD remote_address, |
| 112 | + uint16_t remote_port, |
| 113 | + MIB_TCPROW* row, |
| 114 | + char* error, |
| 115 | + size_t error_size |
| 116 | +) { |
| 117 | + DWORD table_size = 0; |
| 118 | + DWORD result = GetTcpTable(nullptr, &table_size, FALSE); |
| 119 | + if (result != ERROR_INSUFFICIENT_BUFFER) { |
| 120 | + FormatWindowsError("GetTcpTable(size)", result, error, error_size); |
| 121 | + return false; |
| 122 | + } |
| 123 | + |
| 124 | + std::vector<unsigned char> buffer(table_size); |
| 125 | + PMIB_TCPTABLE table = reinterpret_cast<PMIB_TCPTABLE>(buffer.data()); |
| 126 | + result = GetTcpTable(table, &table_size, FALSE); |
| 127 | + if (result != NO_ERROR) { |
| 128 | + FormatWindowsError("GetTcpTable", result, error, error_size); |
| 129 | + return false; |
| 130 | + } |
| 131 | + |
| 132 | + const DWORD local_port_network_order = static_cast<DWORD>(htons(local_port)); |
| 133 | + const DWORD remote_port_network_order = static_cast<DWORD>(htons(remote_port)); |
| 134 | + for (DWORD i = 0; i < table->dwNumEntries; i++) { |
| 135 | + const MIB_TCPROW& candidate = table->table[i]; |
| 136 | + if ( |
| 137 | + candidate.dwLocalAddr == local_address && |
| 138 | + candidate.dwLocalPort == local_port_network_order && |
| 139 | + candidate.dwRemoteAddr == remote_address && |
| 140 | + candidate.dwRemotePort == remote_port_network_order |
| 141 | + ) { |
| 142 | + *row = candidate; |
| 143 | + return true; |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + snprintf(error, error_size, "TCP connection not found in IPv4 table"); |
| 148 | + return false; |
| 149 | +} |
| 150 | + |
| 151 | +static napi_value GetTcpRttMicrosByEndpoint(napi_env env, napi_callback_info info) { |
| 152 | + size_t argc = 4; |
| 153 | + napi_value args[4]; |
| 154 | + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); |
| 155 | + if (argc < 4) { |
| 156 | + return Unavailable(env, "local address, local port, remote address, and remote port arguments are required"); |
| 157 | + } |
| 158 | + |
| 159 | + char local_address_string[96]; |
| 160 | + char remote_address_string[96]; |
| 161 | + if (!GetStringArg(env, args[0], local_address_string, sizeof(local_address_string))) { |
| 162 | + return Unavailable(env, "local address must be a non-empty string"); |
| 163 | + } |
| 164 | + |
| 165 | + uint16_t local_port = 0; |
| 166 | + if (!GetPortArg(env, args[1], &local_port)) { |
| 167 | + return Unavailable(env, "local port must be a TCP port from 1 to 65535"); |
| 168 | + } |
| 169 | + |
| 170 | + if (!GetStringArg(env, args[2], remote_address_string, sizeof(remote_address_string))) { |
| 171 | + return Unavailable(env, "remote address must be a non-empty string"); |
| 172 | + } |
| 173 | + |
| 174 | + uint16_t remote_port = 0; |
| 175 | + if (!GetPortArg(env, args[3], &remote_port)) { |
| 176 | + return Unavailable(env, "remote port must be a TCP port from 1 to 65535"); |
| 177 | + } |
| 178 | + |
| 179 | + DWORD local_address = 0; |
| 180 | + if (!ParseIpv4Address(local_address_string, &local_address)) { |
| 181 | + return Unavailable(env, "local address is not an IPv4 address"); |
| 182 | + } |
| 183 | + |
| 184 | + DWORD remote_address = 0; |
| 185 | + if (!ParseIpv4Address(remote_address_string, &remote_address)) { |
| 186 | + return Unavailable(env, "remote address is not an IPv4 address"); |
| 187 | + } |
| 188 | + |
| 189 | + char error[128]; |
| 190 | + MIB_TCPROW row; |
| 191 | + memset(&row, 0, sizeof(row)); |
| 192 | + if (!FindTcpRowByEndpoint(local_address, local_port, remote_address, remote_port, &row, error, sizeof(error))) { |
| 193 | + return Unavailable(env, error); |
| 194 | + } |
| 195 | + |
| 196 | + TCP_ESTATS_FINE_RTT_RW_v0 rw; |
| 197 | + memset(&rw, 0, sizeof(rw)); |
| 198 | + rw.EnableCollection = TRUE; |
| 199 | + DWORD result = SetPerTcpConnectionEStats( |
| 200 | + &row, |
| 201 | + TcpConnectionEstatsFineRtt, |
| 202 | + reinterpret_cast<PUCHAR>(&rw), |
| 203 | + 0, |
| 204 | + sizeof(rw), |
| 205 | + 0 |
| 206 | + ); |
| 207 | + if (result != NO_ERROR) { |
| 208 | + FormatWindowsError("SetPerTcpConnectionEStats(TcpConnectionEstatsFineRtt)", result, error, sizeof(error)); |
| 209 | + return Unavailable(env, error); |
| 210 | + } |
| 211 | + |
| 212 | + TCP_ESTATS_FINE_RTT_ROD_v0 rod; |
| 213 | + memset(&rod, 0, sizeof(rod)); |
| 214 | + result = GetPerTcpConnectionEStats( |
| 215 | + &row, |
| 216 | + TcpConnectionEstatsFineRtt, |
| 217 | + nullptr, |
| 218 | + 0, |
| 219 | + 0, |
| 220 | + nullptr, |
| 221 | + 0, |
| 222 | + 0, |
| 223 | + reinterpret_cast<PUCHAR>(&rod), |
| 224 | + 0, |
| 225 | + sizeof(rod) |
| 226 | + ); |
| 227 | + if (result != NO_ERROR) { |
| 228 | + FormatWindowsError("GetPerTcpConnectionEStats(TcpConnectionEstatsFineRtt)", result, error, sizeof(error)); |
| 229 | + return Unavailable(env, error); |
| 230 | + } |
| 231 | + |
| 232 | + return Available(env, rod.SumRtt); |
| 233 | +} |
| 234 | +#endif |
| 235 | + |
62 | 236 | static napi_value GetTcpRttMicros(napi_env env, napi_callback_info info) { |
63 | 237 | size_t argc = 1; |
64 | 238 | napi_value args[1]; |
@@ -127,6 +301,10 @@ static napi_value Init(napi_env env, napi_value exports) { |
127 | 301 | napi_value fn; |
128 | 302 | napi_create_function(env, nullptr, 0, GetTcpRttMicros, nullptr, &fn); |
129 | 303 | napi_set_named_property(env, exports, "getTcpRttMicros", fn); |
| 304 | +#if defined(_WIN32) |
| 305 | + napi_create_function(env, nullptr, 0, GetTcpRttMicrosByEndpoint, nullptr, &fn); |
| 306 | + napi_set_named_property(env, exports, "getTcpRttMicrosByEndpoint", fn); |
| 307 | +#endif |
130 | 308 | return exports; |
131 | 309 | } |
132 | 310 |
|
|
0 commit comments