master
c 1,206 lines 47.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "libnetdata/libnetdata.h"
4 #include "libnetdata/os/windows-perflib/perflib.h"
5 #include "libnetdata/os/system-maps/system-services.h"
6 #include "libnetdata/os/system-maps/cached-sid-username.h"
7
8 // Minimal IP Helper API forward declarations.
9 // <winsock2.h> and <ws2tcpip.h> cannot be included here: libnetdata.h already
10 // pulls in POSIX socket headers (via uv.h), and the Windows headers redefine
11 // hostent, sockaddr, pollfd etc. causing compile errors on Cygwin/MSYS2.
12 // Base types (DWORD, ULONG, UCHAR, BOOL, PVOID, PDWORD) come from <windows.h>
13 // which is included for OS_WINDOWS by libnetdata/common.h.
14 // inet_ntop / struct in_addr / AF_INET* / INET6_ADDRSTRLEN come from the
15 // POSIX headers already included by libnetdata.h.
16
17 #define MIB_TCP_STATE_CLOSED 1
18 #define MIB_TCP_STATE_LISTEN 2
19 #define MIB_TCP_STATE_SYN_SENT 3
20 #define MIB_TCP_STATE_SYN_RCVD 4
21 #define MIB_TCP_STATE_ESTAB 5
22 #define MIB_TCP_STATE_FIN_WAIT1 6
23 #define MIB_TCP_STATE_FIN_WAIT2 7
24 #define MIB_TCP_STATE_CLOSE_WAIT 8
25 #define MIB_TCP_STATE_CLOSING 9
26 #define MIB_TCP_STATE_LAST_ACK 10
27 #define MIB_TCP_STATE_TIME_WAIT 11
28 #define MIB_TCP_STATE_DELETE_TCB 12
29
30 typedef enum { TCP_TABLE_OWNER_PID_ALL = 5 } TCP_TABLE_CLASS;
31 typedef enum { UDP_TABLE_OWNER_PID = 1 } UDP_TABLE_CLASS;
32
33 typedef struct {
34 DWORD dwState;
35 DWORD dwLocalAddr;
36 DWORD dwLocalPort;
37 DWORD dwRemoteAddr;
38 DWORD dwRemotePort;
39 DWORD dwOwningPid;
40 } MIB_TCPROW_OWNER_PID;
41
42 typedef struct {
43 UCHAR ucLocalAddr[16];
44 DWORD dwLocalScopeId;
45 DWORD dwLocalPort;
46 UCHAR ucRemoteAddr[16];
47 DWORD dwRemoteScopeId;
48 DWORD dwRemotePort;
49 DWORD dwState;
50 DWORD dwOwningPid;
51 } MIB_TCP6ROW_OWNER_PID;
52
53 typedef struct {
54 DWORD dwNumEntries;
55 MIB_TCPROW_OWNER_PID table[];
56 } MIB_TCPTABLE_OWNER_PID;
57
58 typedef struct {
59 DWORD dwNumEntries;
60 MIB_TCP6ROW_OWNER_PID table[];
61 } MIB_TCP6TABLE_OWNER_PID;
62
63 typedef struct {
64 DWORD dwLocalAddr;
65 DWORD dwLocalPort;
66 DWORD dwOwningPid;
67 } MIB_UDPROW_OWNER_PID;
68
69 typedef struct {
70 UCHAR ucLocalAddr[16];
71 DWORD dwLocalScopeId;
72 DWORD dwLocalPort;
73 DWORD dwOwningPid;
74 } MIB_UDP6ROW_OWNER_PID;
75
76 typedef struct {
77 DWORD dwNumEntries;
78 MIB_UDPROW_OWNER_PID table[];
79 } MIB_UDPTABLE_OWNER_PID;
80
81 typedef struct {
82 DWORD dwNumEntries;
83 MIB_UDP6ROW_OWNER_PID table[];
84 } MIB_UDP6TABLE_OWNER_PID;
85
86 DWORD WINAPI GetExtendedTcpTable(PVOID pTcpTable, PDWORD pdwSize, BOOL bOrder,
87 ULONG ulAf, TCP_TABLE_CLASS TableClass, ULONG Reserved);
88 DWORD WINAPI GetExtendedUdpTable(PVOID pUdpTable, PDWORD pdwSize, BOOL bOrder,
89 ULONG ulAf, UDP_TABLE_CLASS TableClass, ULONG Reserved);
90
91 // Windows-native AF_ values for IP Helper API calls.
92 // Cygwin POSIX headers define AF_INET6=10; Windows APIs expect 23.
93 // AF_INET=2 happens to be the same on both.
94 #define NV_WIN_AF_INET 2
95 #define NV_WIN_AF_INET6 23
96
97 #define PLUGIN_NETWORK_VIEWER_NAME "network-viewer.plugin"
98 #define NV_WIN_FUNCTION_PROTO "network-protocols"
99 #define NV_WIN_FUNCTION_PROTO_HELP "Windows TCP and UDP statistics by transport and IP family"
100 #define NV_WIN_FUNCTION_UPDATE_EVERY 5
101 #define NV_WIN_FUNCTION_PRIORITY 100
102 #define NV_WIN_FUNCTION_CONN "network-connections"
103 #define NV_WIN_FUNCTION_CONN_HELP "Shows active network connections with protocol details, states, addresses, ports, and process information."
104
105 netdata_mutex_t stdout_mutex;
106 static bool plugin_should_exit = false;
107
108 // ============================================================
109 // Shared helpers
110 // ============================================================
111
112 // Resolve a perflib object by name; returns true and sets both out-pointers on success.
113 static bool perflib_get_object(const char *object_name,
114 PERF_DATA_BLOCK **pDataBlock_out,
115 PERF_OBJECT_TYPE **pObjectType_out)
116 {
117 DWORD id = RegistryFindIDByName(object_name);
118 if (id == PERFLIB_REGISTRY_NAME_NOT_FOUND)
119 return false;
120
121 *pDataBlock_out = perflibGetPerformanceData(id);
122 if (!*pDataBlock_out)
123 return false;
124
125 *pObjectType_out = perflibFindObjectTypeByName(*pDataBlock_out, object_name);
126 return *pObjectType_out != NULL;
127 }
128
129 // Write the common JSON table response header fields into an already-created buffer.
130 static void nv_table_begin(BUFFER *wb, const char *help)
131 {
132 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
133 buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
134 buffer_json_member_add_string(wb, "type", "table");
135 buffer_json_member_add_time_t(wb, "update_every", NV_WIN_FUNCTION_UPDATE_EVERY);
136 buffer_json_member_add_boolean(wb, "has_history", false);
137 buffer_json_member_add_string(wb, "help", help);
138 }
139
140 // Finalize the JSON, then send it to pluginsd under the stdout mutex.
141 static void nv_send_result(const char *transaction, BUFFER *wb, time_t now_s)
142 {
143 buffer_json_member_add_time_t(wb, "expires", now_s + NV_WIN_FUNCTION_UPDATE_EVERY);
144 buffer_json_finalize(wb);
145 netdata_mutex_lock(&stdout_mutex);
146 wb->response_code = HTTP_RESP_OK;
147 wb->content_type = CT_APPLICATION_JSON;
148 wb->expires = now_s + NV_WIN_FUNCTION_UPDATE_EVERY;
149 pluginsd_function_result_to_stdout(transaction, wb);
150 netdata_mutex_unlock(&stdout_mutex);
151 }
152
153 // Serialize pluginsd JSON errors the same way as success responses.
154 static void nv_send_error(const char *transaction, int code, const char *message)
155 {
156 netdata_mutex_lock(&stdout_mutex);
157 pluginsd_function_json_error_to_stdout(transaction, code, message);
158 netdata_mutex_unlock(&stdout_mutex);
159 }
160
161 // Add a sticky string key column (Transport, Family, etc.).
162 static void nv_add_key_field(BUFFER *wb, size_t *field_id, const char *id, const char *label)
163 {
164 buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
165 RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
166 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
167 RRDF_FIELD_FILTER_MULTISELECT,
168 RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
169 }
170
171 // Add a standard integer counter column (all counter columns share the same display/filter flags).
172 static void nv_add_int_field(BUFFER *wb, size_t *field_id,
173 const char *id, const char *label, const char *unit)
174 {
175 buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
176 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
177 0, unit, NAN, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
178 RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_VISIBLE, NULL);
179 }
180
181 // Add one entry to the default_charts array: [chart_key, groupby_column].
182 static void nv_add_default_chart(BUFFER *wb, const char *chart_key, const char *groupby)
183 {
184 buffer_json_add_array_item_array(wb);
185 buffer_json_add_array_item_string(wb, chart_key);
186 buffer_json_add_array_item_string(wb, groupby);
187 buffer_json_array_close(wb);
188 }
189
190 // Add one entry to the group_by object: a single-column grouping keyed by name.
191 static void nv_add_group_by(BUFFER *wb, const char *name)
192 {
193 buffer_json_member_add_object(wb, name);
194 {
195 buffer_json_member_add_string(wb, "name", name);
196 buffer_json_member_add_array(wb, "columns");
197 buffer_json_add_array_item_string(wb, name);
198 buffer_json_array_close(wb);
199 }
200 buffer_json_object_close(wb);
201 }
202
203 // Helpers for network-connections column definitions (all use SORT_ASCENDING, SUMMARY_COUNT).
204 static void nv_conn_str_field(BUFFER *wb, size_t *field_id,
205 const char *id, const char *label,
206 RRDF_FIELD_FILTER filter, RRDF_FIELD_OPTIONS opts)
207 {
208 buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
209 RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
210 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
211 RRDF_FIELD_SUMMARY_COUNT, filter, opts, NULL);
212 }
213
214 static void nv_conn_int_field(BUFFER *wb, size_t *field_id,
215 const char *id, const char *label,
216 RRDF_FIELD_FILTER filter, RRDF_FIELD_OPTIONS opts)
217 {
218 buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
219 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
220 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL,
221 RRDF_FIELD_SUMMARY_COUNT, filter, opts, NULL);
222 }
223
224 // Convert Perflib rate counters to per-second values and keep raw gauges unchanged.
225 static uint64_t nv_perflib_value(const COUNTER_DATA *cd)
226 {
227 if(unlikely(!cd->updated))
228 return 0;
229
230 switch(cd->current.CounterType) {
231 case PERF_COUNTER_COUNTER:
232 case PERF_SAMPLE_COUNTER:
233 case PERF_COUNTER_BULK_COUNT: {
234 if(unlikely(!cd->previous.Time || !cd->current.Frequency))
235 return 0;
236
237 ULONGLONG data1 = cd->current.Data;
238 ULONGLONG data0 = cd->previous.Data;
239 LONGLONG time1 = cd->current.Time;
240 LONGLONG time0 = cd->previous.Time;
241 LONGLONG dt = time1 - time0;
242
243 if(unlikely(dt <= 0 || data1 < data0))
244 return 0;
245
246 return (uint64_t)(((double)(data1 - data0) * (double)cd->current.Frequency) / (double)dt);
247 }
248
249 default:
250 return (uint64_t)cd->current.Data;
251 }
252 }
253
254 // ============================================================
255 // TCP
256 // ============================================================
257
258 typedef struct {
259 const char *af;
260 const char *object_name;
261
262 COUNTER_DATA connection_failures;
263 COUNTER_DATA connections_active;
264 COUNTER_DATA connections_established;
265 COUNTER_DATA connections_passive;
266 COUNTER_DATA connections_reset;
267 COUNTER_DATA segments_total;
268 COUNTER_DATA segments_received;
269 COUNTER_DATA segments_retransmitted;
270 COUNTER_DATA segments_sent;
271 } TCP_FAMILY;
272
273 static TCP_FAMILY tcp_ipv4 = {
274 .af = "IPv4",
275 .object_name = "TCPv4",
276 };
277
278 static TCP_FAMILY tcp_ipv6 = {
279 .af = "IPv6",
280 .object_name = "TCPv6",
281 };
282
283 static void initialize_tcp_keys(TCP_FAMILY *tcp)
284 {
285 tcp->connection_failures.key = "Connection Failures";
286 tcp->connections_active.key = "Connections Active";
287 tcp->connections_established.key = "Connections Established";
288 tcp->connections_passive.key = "Connections Passive";
289 tcp->connections_reset.key = "Connections Reset";
290 tcp->segments_total.key = "Segments/sec";
291 tcp->segments_received.key = "Segments Received/sec";
292 tcp->segments_retransmitted.key = "Segments Retransmitted/sec";
293 tcp->segments_sent.key = "Segments Sent/sec";
294 }
295
296
297 static bool tcp_collect_family(TCP_FAMILY *tcp)
298 {
299 PERF_DATA_BLOCK *pDataBlock;
300 PERF_OBJECT_TYPE *pObjectType;
301 if (!perflib_get_object(tcp->object_name, &pDataBlock, &pObjectType))
302 return false;
303
304 bool have_any = false;
305 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connection_failures);
306 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_active);
307 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_established);
308 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_passive);
309 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_reset);
310 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_total);
311 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_received);
312 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_retransmitted);
313 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_sent);
314
315 return have_any;
316 }
317
318 // ============================================================
319 // UDP
320 // ============================================================
321
322 typedef struct {
323 const char *af;
324 const char *object_name;
325
326 COUNTER_DATA datagrams_no_port;
327 COUNTER_DATA datagrams_received_errors;
328 COUNTER_DATA datagrams_received;
329 COUNTER_DATA datagrams_sent;
330 } UDP_FAMILY;
331
332 static UDP_FAMILY udp_ipv4 = {
333 .af = "IPv4",
334 .object_name = "UDPv4",
335 };
336
337 static UDP_FAMILY udp_ipv6 = {
338 .af = "IPv6",
339 .object_name = "UDPv6",
340 };
341
342 static netdata_mutex_t nv_collect_mutex;
343 static SERVICENAMES_CACHE *sc = NULL;
344
345 static void initialize_udp_keys(UDP_FAMILY *udp)
346 {
347 udp->datagrams_no_port.key = "Datagrams No Port/sec";
348 udp->datagrams_received_errors.key = "Datagrams Received Errors";
349 udp->datagrams_received.key = "Datagrams Received/sec";
350 udp->datagrams_sent.key = "Datagrams Sent/sec";
351 }
352
353
354 static bool udp_collect_family(UDP_FAMILY *udp)
355 {
356 PERF_DATA_BLOCK *pDataBlock;
357 PERF_OBJECT_TYPE *pObjectType;
358 if (!perflib_get_object(udp->object_name, &pDataBlock, &pObjectType))
359 return false;
360
361 bool have_any = false;
362 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_no_port);
363 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received_errors);
364 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received);
365 have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_sent);
366
367 return have_any;
368 }
369
370 // ============================================================
371 // Network Protocols (combined TCP + UDP function)
372 // ============================================================
373
374 // Column order for all rows: Transport, Family, Received, Sent, Errors,
375 // ConnActive, ConnEstablished, ConnPassive, ConnReset, SegsTotal, SegsRetransmitted,
376 // DatagramsNoPort.
377
378 static void proto_emit_tcp_row(BUFFER *wb, const TCP_FAMILY *tcp)
379 {
380 buffer_json_add_array_item_array(wb);
381 {
382 buffer_json_add_array_item_string(wb, "TCP");
383 buffer_json_add_array_item_string(wb, tcp->af);
384 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_received));
385 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_sent));
386 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connection_failures));
387 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_active));
388 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_established));
389 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_passive));
390 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_reset));
391 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_total));
392 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_retransmitted));
393 buffer_json_add_array_item_uint64(wb, 0); // DatagramsNoPort — UDP only
394 }
395 buffer_json_array_close(wb);
396 }
397
398 static void proto_emit_udp_row(BUFFER *wb, const UDP_FAMILY *udp)
399 {
400 buffer_json_add_array_item_array(wb);
401 {
402 buffer_json_add_array_item_string(wb, "UDP");
403 buffer_json_add_array_item_string(wb, udp->af);
404 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received));
405 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_sent));
406 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received_errors));
407 buffer_json_add_array_item_uint64(wb, 0); // ConnActive — TCP only
408 buffer_json_add_array_item_uint64(wb, 0); // ConnEstablished — TCP only
409 buffer_json_add_array_item_uint64(wb, 0); // ConnPassive — TCP only
410 buffer_json_add_array_item_uint64(wb, 0); // ConnReset — TCP only
411 buffer_json_add_array_item_uint64(wb, 0); // SegsTotal — TCP only
412 buffer_json_add_array_item_uint64(wb, 0); // SegsRetransmitted — TCP only
413 buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_no_port));
414 }
415 buffer_json_array_close(wb);
416 }
417
418 void function_network_protocols(
419 const char *transaction, char *function __maybe_unused,
420 usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled,
421 BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
422 const char *source __maybe_unused, void *data __maybe_unused)
423 {
424 bool have_tcp_ipv4 = false;
425 bool have_tcp_ipv6 = false;
426 bool have_udp_ipv4 = false;
427 bool have_udp_ipv6 = false;
428
429 if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
430 nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
431 goto cleanup;
432 }
433
434 // Serialize access to the shared COUNTER_DATA state so previous/current
435 // deltas are consistent regardless of which worker thread handles the request.
436 // Hold the mutex across collection AND data-row emission: proto_emit_*_row
437 // reads previous/current fields from the same shared structs that another
438 // worker would overwrite during its own collection pass.
439 netdata_mutex_lock(&nv_collect_mutex);
440 have_tcp_ipv4 = tcp_collect_family(&tcp_ipv4);
441 have_tcp_ipv6 = tcp_collect_family(&tcp_ipv6);
442 have_udp_ipv4 = udp_collect_family(&udp_ipv4);
443 have_udp_ipv6 = udp_collect_family(&udp_ipv6);
444
445 if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
446 netdata_mutex_unlock(&nv_collect_mutex);
447 nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
448 goto cleanup;
449 }
450
451 if(unlikely(!have_tcp_ipv4 && !have_tcp_ipv6 && !have_udp_ipv4 && !have_udp_ipv6)) {
452 netdata_mutex_unlock(&nv_collect_mutex);
453 nv_send_error(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
454 "failed to collect Windows TCP/UDP stack statistics");
455 goto cleanup;
456 }
457
458 time_t now_s = now_realtime_sec();
459 CLEAN_BUFFER *wb = buffer_create(0, NULL);
460 nv_table_begin(wb, NV_WIN_FUNCTION_PROTO_HELP);
461
462 buffer_json_member_add_array(wb, "data");
463 {
464 if(have_tcp_ipv4)
465 proto_emit_tcp_row(wb, &tcp_ipv4);
466 if(have_tcp_ipv6)
467 proto_emit_tcp_row(wb, &tcp_ipv6);
468 if(have_udp_ipv4)
469 proto_emit_udp_row(wb, &udp_ipv4);
470 if(have_udp_ipv6)
471 proto_emit_udp_row(wb, &udp_ipv6);
472 }
473 buffer_json_array_close(wb); // data
474 netdata_mutex_unlock(&nv_collect_mutex);
475
476 size_t field_id = 0;
477 buffer_json_member_add_object(wb, "columns");
478 {
479 nv_add_key_field(wb, &field_id, "Transport", "Transport Protocol");
480 nv_add_key_field(wb, &field_id, "Family", "IP Protocol Family");
481
482 // Normalized columns — TCP: segments, UDP: datagrams
483 nv_add_int_field(wb, &field_id, "Received", "Received (Segments/Datagrams)", "segments/datagrams/s");
484 nv_add_int_field(wb, &field_id, "Sent", "Sent (Segments/Datagrams)", "segments/datagrams/s");
485 nv_add_int_field(wb, &field_id, "Errors", "Errors (Failures/Rx Errors)", "errors");
486
487 // TCP-only columns (UDP rows carry 0)
488 // ConnActive/ConnPassive/ConnReset are PERF_COUNTER_RAWCOUNT cumulative totals,
489 // not per-second rates, so units have no /s suffix.
490 nv_add_int_field(wb, &field_id, "ConnActive", "Active Connections Opened", "opens");
491 nv_add_int_field(wb, &field_id, "ConnEstablished", "Currently Established Connections", "connections");
492 nv_add_int_field(wb, &field_id, "ConnPassive", "Passive Connections Opened", "opens");
493 nv_add_int_field(wb, &field_id, "ConnReset", "Reset Connections", "resets");
494 nv_add_int_field(wb, &field_id, "SegsTotal", "Total Segments", "segments/s");
495 nv_add_int_field(wb, &field_id, "SegsRetransmitted", "Retransmitted Segments", "segments/s");
496
497 // UDP-only column (TCP rows carry 0)
498 nv_add_int_field(wb, &field_id, "DatagramsNoPort", "Datagrams with No Port", "datagrams/s");
499 }
500 buffer_json_object_close(wb); // columns
501 buffer_json_member_add_string(wb, "default_sort_column", "Received");
502
503 // charts.columns = metric columns for the Y axis (NOT the groupby column)
504 buffer_json_member_add_object(wb, "charts");
505 {
506 buffer_json_member_add_object(wb, "Traffic");
507 {
508 buffer_json_member_add_string(wb, "name", "Traffic");
509 buffer_json_member_add_string(wb, "type", "stacked-bar");
510 buffer_json_member_add_array(wb, "columns");
511 {
512 buffer_json_add_array_item_string(wb, "Received");
513 buffer_json_add_array_item_string(wb, "Sent");
514 }
515 buffer_json_array_close(wb);
516 }
517 buffer_json_object_close(wb);
518 }
519 buffer_json_object_close(wb); // charts
520
521 // default_charts: [chart_key, groupby_column] — same chart, two grouping axes
522 buffer_json_member_add_array(wb, "default_charts");
523 {
524 nv_add_default_chart(wb, "Traffic", "Transport");
525 nv_add_default_chart(wb, "Traffic", "Family");
526 }
527 buffer_json_array_close(wb); // default_charts
528
529 buffer_json_member_add_object(wb, "group_by");
530 {
531 nv_add_group_by(wb, "Transport");
532 nv_add_group_by(wb, "Family");
533 }
534 buffer_json_object_close(wb); // group_by
535
536 nv_send_result(transaction, wb, now_s);
537
538 cleanup:
539 // Release the thread-local perflib buffer once per request to avoid retaining
540 // the largest query size for the lifetime of the worker thread.
541 perflibFreePerformanceData();
542 }
543
544 // ============================================================
545 // Network Connections — per-socket view via IP Helper API
546 // ============================================================
547
548 // --- Address space classification -----------------------------------------------
549
550 static const uint8_t nv_ipv6_zero_addr[16] = {0};
551 static const uint8_t nv_ipv6_loopback_addr[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
552
553 static const char *nv_ipv4_address_space(DWORD ip_nbo)
554 {
555 uint32_t ip = ntohl(ip_nbo);
556 if (!ip) return "zero";
557 if ((ip >> 24) == 127) return "loopback";
558 if ((ip >> 28) == 0xEU) return "multicast"; // 224.0.0.0/4
559 uint32_t o1 = ip >> 24;
560 uint32_t o2 = (ip >> 16) & 0xFF;
561 if (o1 == 10 ||
562 (o1 == 172 && o2 >= 16 && o2 <= 31) ||
563 (o1 == 192 && o2 == 168) ||
564 (o1 == 169 && o2 == 254))
565 return "private";
566 return "public";
567 }
568
569 static const char *nv_ipv6_address_space(const UCHAR *b)
570 {
571 static const uint8_t v4map[12] = {0,0,0,0,0,0,0,0,0,0,0xFF,0xFF};
572
573 if (!memcmp(b, nv_ipv6_zero_addr, 16)) return "zero";
574 if (!memcmp(b, nv_ipv6_loopback_addr, 16)) return "loopback";
575 if (!memcmp(b, v4map, 12)) {
576 DWORD v4;
577 memcpy(&v4, b + 12, 4);
578 return nv_ipv4_address_space(v4);
579 }
580 if (b[0] == 0xFF) return "multicast";
581 if ((b[0] & 0xFE) == 0xFC) return "private"; // ULA fc00::/7
582 if (b[0] == 0xFE && (b[1] & 0xC0) == 0x80) return "private"; // link-local fe80::/10
583 return "public";
584 }
585
586 // --- Loopback checks for direction detection ------------------------------------
587
588 static bool nv_is_ipv4_loopback(DWORD ip_nbo)
589 {
590 return (ntohl(ip_nbo) >> 24) == 127;
591 }
592
593 static bool nv_is_ipv6_loopback(const UCHAR *b)
594 {
595 return !memcmp(b, nv_ipv6_loopback_addr, 16);
596 }
597
598 // --- TCP state number → string --------------------------------------------------
599
600 static const char *nv_tcp_state_str(DWORD state)
601 {
602 switch (state) {
603 case MIB_TCP_STATE_CLOSED: return "close";
604 case MIB_TCP_STATE_LISTEN: return "listen";
605 case MIB_TCP_STATE_SYN_SENT: return "syn-sent";
606 case MIB_TCP_STATE_SYN_RCVD: return "syn-received";
607 case MIB_TCP_STATE_ESTAB: return "established";
608 case MIB_TCP_STATE_FIN_WAIT1: return "fin-wait1";
609 case MIB_TCP_STATE_FIN_WAIT2: return "fin-wait2";
610 case MIB_TCP_STATE_CLOSE_WAIT: return "close-wait";
611 case MIB_TCP_STATE_CLOSING: return "closing";
612 case MIB_TCP_STATE_LAST_ACK: return "last-ack";
613 case MIB_TCP_STATE_TIME_WAIT: return "time-wait";
614 case MIB_TCP_STATE_DELETE_TCB: return "delete";
615 default: return "unknown";
616 }
617 }
618
619 // --- Process info ---------------------------------------------------------------
620
621 static void nv_get_comm(DWORD pid, char *comm, size_t comm_size)
622 {
623 comm[0] = '\0';
624 HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
625 if (!h) return;
626
627 char path[MAX_PATH];
628 DWORD sz = (DWORD)sizeof(path);
629 if (QueryFullProcessImageNameA(h, 0, path, &sz)) {
630 const char *base = strrchr(path, '\\');
631 base = base ? base + 1 : path;
632 strncpyz(comm, base, comm_size - 1);
633 }
634 CloseHandle(h);
635 }
636
637 static STRING *nv_get_username(DWORD pid)
638 {
639 HANDLE hp = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
640 if (!hp) return NULL;
641
642 STRING *result = NULL;
643 HANDLE ht = NULL;
644 if (OpenProcessToken(hp, TOKEN_QUERY, &ht)) {
645 DWORD sz = 0;
646 GetTokenInformation(ht, TokenUser, NULL, 0, &sz);
647 if (sz) {
648 TOKEN_USER *tu = mallocz(sz);
649 if (GetTokenInformation(ht, TokenUser, tu, sz, &sz))
650 result = cached_sid_fullname_or_sid_str(tu->User.Sid);
651 freez(tu);
652 }
653 CloseHandle(ht);
654 }
655 CloseHandle(hp);
656 return result;
657 }
658
659 // --- Per-request PID cache ------------------------------------------------------
660 // Resolving process name and username requires OpenProcess + kernel work.
661 // Many sockets share the same PID (e.g. svchost.exe), so caching per request
662 // eliminates redundant syscalls for every repeated PID.
663
664 typedef struct {
665 DWORD pid;
666 char comm[256];
667 STRING *username; // ref-counted; freed by nv_pid_cache_free
668 } NV_PID_CACHE_ENTRY;
669
670 typedef struct {
671 NV_PID_CACHE_ENTRY *entries;
672 size_t used;
673 size_t capacity;
674 } NV_PID_CACHE;
675
676 static void nv_pid_cache_init(NV_PID_CACHE *c)
677 {
678 c->entries = NULL;
679 c->used = 0;
680 c->capacity = 0;
681 }
682
683 static void nv_pid_cache_lookup(NV_PID_CACHE *c, DWORD pid,
684 const char **comm_out, const char **username_out)
685 {
686 for (size_t i = 0; i < c->used; i++) {
687 if (c->entries[i].pid == pid) {
688 *comm_out = c->entries[i].comm;
689 *username_out = string2str(c->entries[i].username);
690 return;
691 }
692 }
693
694 if (c->used >= c->capacity) {
695 c->capacity = c->capacity ? c->capacity * 2 : 32;
696 c->entries = reallocz(c->entries, c->capacity * sizeof(*c->entries));
697 }
698
699 NV_PID_CACHE_ENTRY *e = &c->entries[c->used++];
700 e->pid = pid;
701 e->comm[0] = '\0';
702 e->username = NULL;
703
704 nv_get_comm(pid, e->comm, sizeof(e->comm));
705 e->username = nv_get_username(pid);
706
707 *comm_out = e->comm;
708 *username_out = string2str(e->username);
709 }
710
711 static void nv_pid_cache_free(NV_PID_CACHE *c)
712 {
713 for (size_t i = 0; i < c->used; i++)
714 string_freez(c->entries[i].username);
715 freez(c->entries);
716 c->entries = NULL;
717 c->used = 0;
718 c->capacity = 0;
719 }
720
721 // --- Listening-port set for direction classification ----------------------------
722 // Key is {family, port} so that a TCP6 listener on port N does not cause TCP4
723 // connections whose ephemeral port happens to be N to be misclassified as inbound.
724 // Mirrors the Linux approach (local-sockets.h:772-774) where family and protocol
725 // are both part of the listening-port key.
726
727 typedef struct {
728 ULONG family; // NV_WIN_AF_INET or NV_WIN_AF_INET6
729 DWORD port; // host byte order
730 } NV_LISTEN_PORT;
731
732 typedef struct {
733 NV_LISTEN_PORT *ports;
734 size_t used;
735 size_t capacity;
736 } NV_LISTEN_SET;
737
738 static void nv_listen_set_init(NV_LISTEN_SET *s)
739 {
740 s->ports = NULL;
741 s->used = 0;
742 s->capacity = 0;
743 }
744
745 static void nv_listen_set_add(NV_LISTEN_SET *s, DWORD port_hbo, ULONG family)
746 {
747 if (s->used >= s->capacity) {
748 s->capacity = s->capacity ? s->capacity * 2 : 64;
749 s->ports = reallocz(s->ports, s->capacity * sizeof(NV_LISTEN_PORT));
750 }
751 s->ports[s->used].port = port_hbo;
752 s->ports[s->used].family = family;
753 s->used++;
754 }
755
756 static int nv_listen_port_compar(const void *a, const void *b)
757 {
758 const NV_LISTEN_PORT *pa = a, *pb = b;
759 if (pa->family != pb->family)
760 return (pa->family > pb->family) - (pa->family < pb->family);
761 return (pa->port > pb->port) - (pa->port < pb->port);
762 }
763
764 static void nv_listen_set_sort(NV_LISTEN_SET *s)
765 {
766 if (s->used > 1)
767 qsort(s->ports, s->used, sizeof(NV_LISTEN_PORT), nv_listen_port_compar);
768 }
769
770 static bool nv_listen_set_contains(const NV_LISTEN_SET *s, DWORD port_hbo, ULONG family)
771 {
772 if (!s->used) return false;
773 NV_LISTEN_PORT key = { .family = family, .port = port_hbo };
774 return bsearch(&key, s->ports, s->used, sizeof(NV_LISTEN_PORT), nv_listen_port_compar) != NULL;
775 }
776
777 static void nv_listen_set_free(NV_LISTEN_SET *s)
778 {
779 freez(s->ports);
780 s->ports = NULL;
781 s->used = 0;
782 s->capacity = 0;
783 }
784
785 // --- Table fetch helpers --------------------------------------------------------
786
787 // Common Windows API signature for GetExtended{Tcp,Udp}Table.
788 // Both functions have identical prototypes except for the TableClass enum type;
789 // since all Windows enum types are int-sized, a single typedef covers both.
790 typedef DWORD (WINAPI *NV_GET_TABLE_FN)(PVOID, PDWORD, BOOL, ULONG, int, ULONG);
791
792 static void *nv_fetch_ip_table(NV_GET_TABLE_FN get_fn, ULONG af, int table_class)
793 {
794 DWORD size = 0;
795 get_fn(NULL, &size, FALSE, af, table_class, 0);
796 if (!size) return NULL;
797
798 // Add headroom to tolerate connections added between size-probe and actual fetch.
799 size += 4096;
800 void *buf = mallocz(size);
801 DWORD ret = get_fn(buf, &size, FALSE, af, table_class, 0);
802 if (ret == ERROR_INSUFFICIENT_BUFFER) {
803 buf = reallocz(buf, size);
804 ret = get_fn(buf, &size, FALSE, af, table_class, 0);
805 }
806 if (ret != NO_ERROR) {
807 freez(buf);
808 return NULL;
809 }
810 return buf;
811 }
812
813 static void *nv_fetch_tcp_table(ULONG af)
814 {
815 return nv_fetch_ip_table((NV_GET_TABLE_FN)GetExtendedTcpTable, af, TCP_TABLE_OWNER_PID_ALL);
816 }
817
818 static void *nv_fetch_udp_table(ULONG af)
819 {
820 return nv_fetch_ip_table((NV_GET_TABLE_FN)GetExtendedUdpTable, af, UDP_TABLE_OWNER_PID);
821 }
822
823 // --- Row emitter ----------------------------------------------------------------
824
825 // Column order (matches the columns declared in function_network_connections):
826 // Direction, Protocol, State, PID, Process, User, Portname,
827 // LocalIP, LocalPort, LocalAddressSpace,
828 // RemoteIP, RemotePort, RemoteAddressSpace,
829 // ServerPort, Count
830
831 static void nv_emit_row(BUFFER *wb,
832 NV_PID_CACHE *pid_cache,
833 const char *direction,
834 const char *protocol,
835 const char *state,
836 DWORD pid,
837 const char *local_ip, DWORD local_port_hbo, const char *local_as,
838 const char *remote_ip, DWORD remote_port_hbo, const char *remote_as)
839 {
840 DWORD server_port = (strcmp(direction, "outbound") == 0) ? remote_port_hbo : local_port_hbo;
841
842 uint16_t ipproto = (strncmp(protocol, "tcp", 3) == 0) ? IPPROTO_TCP : IPPROTO_UDP;
843 STRING *portname = system_servicenames_cache_lookup(sc, (uint16_t)server_port, (uint16_t)ipproto);
844
845 const char *comm, *username;
846 nv_pid_cache_lookup(pid_cache, pid, &comm, &username);
847
848 buffer_json_add_array_item_array(wb);
849 {
850 buffer_json_add_array_item_string(wb, direction);
851 buffer_json_add_array_item_string(wb, protocol);
852 buffer_json_add_array_item_string(wb, state);
853 buffer_json_add_array_item_uint64(wb, pid);
854 buffer_json_add_array_item_string(wb, comm[0] ? comm : "[unknown]");
855 buffer_json_add_array_item_string(wb, (username && username[0]) ? username : "[unknown]");
856 buffer_json_add_array_item_string(wb, string2str(portname));
857 buffer_json_add_array_item_string(wb, local_ip);
858 buffer_json_add_array_item_uint64(wb, local_port_hbo);
859 buffer_json_add_array_item_string(wb, local_as);
860 buffer_json_add_array_item_string(wb, remote_ip);
861 buffer_json_add_array_item_uint64(wb, remote_port_hbo);
862 buffer_json_add_array_item_string(wb, remote_as);
863 buffer_json_add_array_item_uint64(wb, server_port);
864 buffer_json_add_array_item_uint64(wb, 1); // Count — always 1 (detailed view)
865 }
866 buffer_json_array_close(wb);
867
868 string_freez(portname);
869 // comm and username are owned by pid_cache; not freed here
870 }
871
872 // --- Main function handler -------------------------------------------------------
873
874 void function_network_connections(
875 const char *transaction, char *function __maybe_unused,
876 usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled,
877 BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
878 const char *source __maybe_unused, void *data __maybe_unused)
879 {
880 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
881 nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
882 return;
883 }
884
885 // Fetch all four tables upfront to minimise the enumeration window.
886 MIB_TCPTABLE_OWNER_PID *tcp4 = nv_fetch_tcp_table(NV_WIN_AF_INET);
887 MIB_TCP6TABLE_OWNER_PID *tcp6 = nv_fetch_tcp_table(NV_WIN_AF_INET6);
888 MIB_UDPTABLE_OWNER_PID *udp4 = nv_fetch_udp_table(NV_WIN_AF_INET);
889 MIB_UDP6TABLE_OWNER_PID *udp6 = nv_fetch_udp_table(NV_WIN_AF_INET6);
890
891 // A NULL return from nv_fetch_*_table means the API call failed entirely
892 // (not merely an empty table — zero-entry tables return a non-NULL buffer).
893 // If every fetch failed we cannot distinguish "no sockets" from a broken
894 // driver, so report an error instead of silently returning an empty table.
895 if (unlikely(!tcp4 && !tcp6 && !udp4 && !udp6)) {
896 nv_send_error(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
897 "failed to collect Windows network connections");
898 return;
899 }
900
901 NV_PID_CACHE pid_cache;
902 nv_pid_cache_init(&pid_cache);
903
904 // --- First pass: collect all TCP LISTEN ports for direction classification ---
905 NV_LISTEN_SET listen_set;
906 nv_listen_set_init(&listen_set);
907
908 // Collect all LISTEN ports from a TCP table into the listen set.
909 // Works for both MIB_TCPTABLE_OWNER_PID and MIB_TCP6TABLE_OWNER_PID because
910 // the macro expands with the concrete type at each call site.
911 #define NV_COLLECT_TCP_LISTEN(tbl, family, set) \
912 do { \
913 for (DWORD _i = 0; _i < (tbl)->dwNumEntries; _i++) { \
914 if ((tbl)->table[_i].dwState == MIB_TCP_STATE_LISTEN) \
915 nv_listen_set_add((set), ntohs((uint16_t)(tbl)->table[_i].dwLocalPort), \
916 (family)); \
917 } \
918 } while (0)
919
920 if (tcp4) NV_COLLECT_TCP_LISTEN(tcp4, NV_WIN_AF_INET, &listen_set);
921 if (tcp6) NV_COLLECT_TCP_LISTEN(tcp6, NV_WIN_AF_INET6, &listen_set);
922 nv_listen_set_sort(&listen_set);
923
924 // --- Second pass: emit rows --------------------------------------------------
925 time_t now_s = now_realtime_sec();
926 CLEAN_BUFFER *wb = buffer_create(0, NULL);
927 nv_table_begin(wb, NV_WIN_FUNCTION_CONN_HELP);
928
929 buffer_json_member_add_array(wb, "data");
930 {
931 char local_ip [INET6_ADDRSTRLEN];
932 char remote_ip[INET6_ADDRSTRLEN];
933
934 // TCP IPv4
935 if (tcp4) {
936 for (DWORD i = 0; i < tcp4->dwNumEntries; i++) {
937 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
938 goto emit_done;
939 MIB_TCPROW_OWNER_PID *r = &tcp4->table[i];
940
941 struct in_addr la = { .s_addr = r->dwLocalAddr };
942 if (!inet_ntop(AF_INET, &la, local_ip, sizeof(local_ip)))
943 continue;
944
945 DWORD local_port = ntohs((uint16_t)r->dwLocalPort);
946 const char *local_as = nv_ipv4_address_space(r->dwLocalAddr);
947
948 const char *direction;
949 if (r->dwState == MIB_TCP_STATE_LISTEN) {
950 direction = "listen";
951 } else if (nv_is_ipv4_loopback(r->dwLocalAddr) || nv_is_ipv4_loopback(r->dwRemoteAddr)) {
952 direction = nv_listen_set_contains(&listen_set, local_port, NV_WIN_AF_INET) ? "inbound" : "outbound";
953 } else if (nv_listen_set_contains(&listen_set, local_port, NV_WIN_AF_INET)) {
954 direction = "inbound";
955 } else {
956 direction = "outbound";
957 }
958
959 // LISTEN rows have no remote endpoint; defaults cover inet_ntop failure too.
960 const char *remote_ip_s = "";
961 const char *remote_as = "";
962 DWORD remote_port_emit = 0;
963 if (r->dwState != MIB_TCP_STATE_LISTEN && r->dwRemoteAddr) {
964 struct in_addr ra = { .s_addr = r->dwRemoteAddr };
965 if (inet_ntop(AF_INET, &ra, remote_ip, sizeof(remote_ip))) {
966 remote_ip_s = remote_ip;
967 remote_as = nv_ipv4_address_space(r->dwRemoteAddr);
968 remote_port_emit = ntohs((uint16_t)r->dwRemotePort);
969 }
970 }
971
972 nv_emit_row(wb, &pid_cache, direction, "tcp4", nv_tcp_state_str(r->dwState), r->dwOwningPid,
973 local_ip, local_port, local_as,
974 remote_ip_s, remote_port_emit, remote_as);
975 }
976 }
977
978 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
979 goto emit_done;
980
981 // TCP IPv6
982 if (tcp6) {
983 for (DWORD i = 0; i < tcp6->dwNumEntries; i++) {
984 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
985 goto emit_done;
986 MIB_TCP6ROW_OWNER_PID *r = &tcp6->table[i];
987
988 if (!inet_ntop(AF_INET6, r->ucLocalAddr, local_ip, sizeof(local_ip)))
989 continue;
990 DWORD local_port = ntohs((uint16_t)r->dwLocalPort);
991 const char *local_as = nv_ipv6_address_space(r->ucLocalAddr);
992
993 const char *direction;
994 if (r->dwState == MIB_TCP_STATE_LISTEN) {
995 direction = "listen";
996 } else if (nv_is_ipv6_loopback(r->ucLocalAddr) || nv_is_ipv6_loopback(r->ucRemoteAddr)) {
997 direction = nv_listen_set_contains(&listen_set, local_port, NV_WIN_AF_INET6) ? "inbound" : "outbound";
998 } else if (nv_listen_set_contains(&listen_set, local_port, NV_WIN_AF_INET6)) {
999 direction = "inbound";
1000 } else {
1001 direction = "outbound";
1002 }
1003
1004 bool remote_zero = !memcmp(r->ucRemoteAddr, nv_ipv6_zero_addr, 16) && !r->dwRemotePort;
1005
1006 const char *remote_ip_s = "";
1007 const char *remote_as = "";
1008 DWORD remote_port_emit = 0;
1009 if (r->dwState != MIB_TCP_STATE_LISTEN && !remote_zero) {
1010 if (inet_ntop(AF_INET6, r->ucRemoteAddr, remote_ip, sizeof(remote_ip))) {
1011 remote_ip_s = remote_ip;
1012 remote_as = nv_ipv6_address_space(r->ucRemoteAddr);
1013 remote_port_emit = ntohs((uint16_t)r->dwRemotePort);
1014 }
1015 }
1016
1017 nv_emit_row(wb, &pid_cache, direction, "tcp6", nv_tcp_state_str(r->dwState), r->dwOwningPid,
1018 local_ip, local_port, local_as,
1019 remote_ip_s, remote_port_emit, remote_as);
1020 }
1021 }
1022
1023 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
1024 goto emit_done;
1025
1026 // UDP IPv4 — endpoints have no remote address; direction is always "listen".
1027 if (udp4) {
1028 for (DWORD i = 0; i < udp4->dwNumEntries; i++) {
1029 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
1030 goto emit_done;
1031 MIB_UDPROW_OWNER_PID *r = &udp4->table[i];
1032
1033 struct in_addr la = { .s_addr = r->dwLocalAddr };
1034 if (!inet_ntop(AF_INET, &la, local_ip, sizeof(local_ip)))
1035 continue;
1036 DWORD local_port = ntohs((uint16_t)r->dwLocalPort);
1037 const char *local_as = nv_ipv4_address_space(r->dwLocalAddr);
1038
1039 nv_emit_row(wb, &pid_cache, "listen", "udp4", "stateless", r->dwOwningPid,
1040 local_ip, local_port, local_as,
1041 "", 0, "");
1042 }
1043 }
1044
1045 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
1046 goto emit_done;
1047
1048 // UDP IPv6
1049 if (udp6) {
1050 for (DWORD i = 0; i < udp6->dwNumEntries; i++) {
1051 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED)))
1052 goto emit_done;
1053 MIB_UDP6ROW_OWNER_PID *r = &udp6->table[i];
1054
1055 if (!inet_ntop(AF_INET6, r->ucLocalAddr, local_ip, sizeof(local_ip)))
1056 continue;
1057 DWORD local_port = ntohs((uint16_t)r->dwLocalPort);
1058 const char *local_as = nv_ipv6_address_space(r->ucLocalAddr);
1059
1060 nv_emit_row(wb, &pid_cache, "listen", "udp6", "stateless", r->dwOwningPid,
1061 local_ip, local_port, local_as,
1062 "", 0, "");
1063 }
1064 }
1065
1066 emit_done:;
1067 }
1068 buffer_json_array_close(wb); // data
1069
1070 nv_listen_set_free(&listen_set);
1071 nv_pid_cache_free(&pid_cache);
1072 freez(tcp4);
1073 freez(tcp6);
1074 freez(udp4);
1075 freez(udp6);
1076
1077 if (unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
1078 nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
1079 return;
1080 }
1081
1082 // --- Column definitions ---
1083 size_t field_id = 0;
1084 buffer_json_member_add_object(wb, "columns");
1085 {
1086 nv_conn_str_field(wb, &field_id, "Direction", "Socket Direction", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY);
1087 nv_conn_str_field(wb, &field_id, "Protocol", "Socket Protocol", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE);
1088 nv_conn_str_field(wb, &field_id, "State", "Socket State", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE);
1089 nv_conn_int_field(wb, &field_id, "PID", "Process ID", RRDF_FIELD_FILTER_NONE, RRDF_FIELD_OPTS_VISIBLE);
1090 nv_conn_str_field(wb, &field_id, "Process", "Process Name", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_FULL_WIDTH);
1091 nv_conn_str_field(wb, &field_id, "User", "Username", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE);
1092 nv_conn_str_field(wb, &field_id, "Portname", "Server Port Name", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_VISIBLE);
1093 nv_conn_str_field(wb, &field_id, "LocalIP", "Local IP Address", RRDF_FIELD_FILTER_NONE, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_FULL_WIDTH);
1094 nv_conn_int_field(wb, &field_id, "LocalPort", "Local Port", RRDF_FIELD_FILTER_NONE, RRDF_FIELD_OPTS_VISIBLE);
1095 nv_conn_str_field(wb, &field_id, "LocalAddressSpace", "Local IP Address Space", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_NONE);
1096 nv_conn_str_field(wb, &field_id, "RemoteIP", "Remote IP Address", RRDF_FIELD_FILTER_NONE, RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_FULL_WIDTH);
1097 nv_conn_int_field(wb, &field_id, "RemotePort", "Remote Port", RRDF_FIELD_FILTER_NONE, RRDF_FIELD_OPTS_VISIBLE);
1098 nv_conn_str_field(wb, &field_id, "RemoteAddressSpace", "Remote IP Address Space", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_NONE);
1099 nv_conn_int_field(wb, &field_id, "ServerPort", "Server Port", RRDF_FIELD_FILTER_MULTISELECT, RRDF_FIELD_OPTS_NONE);
1100 buffer_rrdf_table_add_field(wb, field_id++, "Count", "Number of sockets",
1101 RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
1102 0, "sockets", NAN, RRDF_FIELD_SORT_DESCENDING, NULL,
1103 RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_NONE,
1104 RRDF_FIELD_OPTS_NONE, NULL);
1105 }
1106 buffer_json_object_close(wb); // columns
1107
1108 buffer_json_member_add_string(wb, "default_sort_column", "Direction");
1109
1110 buffer_json_member_add_object(wb, "custom_charts");
1111 {
1112 buffer_json_member_add_object(wb, "Network Map");
1113 {
1114 buffer_json_member_add_string(wb, "type", "network-viewer");
1115 }
1116 buffer_json_object_close(wb);
1117 }
1118 buffer_json_object_close(wb); // custom_charts
1119
1120 buffer_json_member_add_object(wb, "group_by");
1121 {
1122 nv_add_group_by(wb, "Direction");
1123 nv_add_group_by(wb, "Protocol");
1124 nv_add_group_by(wb, "State");
1125 nv_add_group_by(wb, "Process");
1126 }
1127 buffer_json_object_close(wb); // group_by
1128
1129 nv_send_result(transaction, wb, now_s);
1130 }
1131
1132 // ============================================================
1133 // main
1134 // ============================================================
1135
1136 int main(int argc, char **argv)
1137 {
1138 netdata_mutex_init(&stdout_mutex);
1139 nd_log_initialize_for_external_plugins("network-viewer.plugin");
1140 netdata_threads_init_for_external_plugins(0);
1141
1142 PerflibNamesRegistryInitialize();
1143 netdata_mutex_init(&nv_collect_mutex);
1144
1145 // Prime each family's COUNTER_DATA so the first real request has a valid
1146 // previous baseline and rate counters return non-zero values immediately.
1147 initialize_tcp_keys(&tcp_ipv4);
1148 initialize_tcp_keys(&tcp_ipv6);
1149 initialize_udp_keys(&udp_ipv4);
1150 initialize_udp_keys(&udp_ipv6);
1151 tcp_collect_family(&tcp_ipv4);
1152 tcp_collect_family(&tcp_ipv6);
1153 udp_collect_family(&udp_ipv4);
1154 udp_collect_family(&udp_ipv6);
1155 perflibFreePerformanceData();
1156
1157 cached_sid_username_init();
1158 sc = system_servicenames_cache_init();
1159
1160 fprintf(stdout,
1161 PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" " HTTP_ACCESS_FORMAT " %d\n",
1162 NV_WIN_FUNCTION_PROTO, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NV_WIN_FUNCTION_PROTO_HELP,
1163 (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE),
1164 NV_WIN_FUNCTION_PRIORITY);
1165
1166 fprintf(stdout,
1167 PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" " HTTP_ACCESS_FORMAT " %d\n",
1168 NV_WIN_FUNCTION_CONN, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NV_WIN_FUNCTION_CONN_HELP,
1169 (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
1170 NV_WIN_FUNCTION_PRIORITY);
1171
1172 fflush(stdout);
1173
1174 struct functions_evloop_globals *wg =
1175 functions_evloop_init(5, "NV-WIN", &stdout_mutex, &plugin_should_exit, NULL);
1176
1177 functions_evloop_add_function(wg, NV_WIN_FUNCTION_PROTO, function_network_protocols,
1178 PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL);
1179
1180 functions_evloop_add_function(wg, NV_WIN_FUNCTION_CONN, function_network_connections,
1181 PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL);
1182
1183 usec_t send_newline_ut = 0;
1184 const bool tty = isatty(fileno(stdout)) == 1;
1185
1186 heartbeat_t hb;
1187 heartbeat_init(&hb, USEC_PER_SEC);
1188
1189 while (!__atomic_load_n(&plugin_should_exit, __ATOMIC_ACQUIRE)) {
1190 usec_t dt_ut = heartbeat_next(&hb);
1191 send_newline_ut += dt_ut;
1192
1193 if (!tty && send_newline_ut > USEC_PER_SEC) {
1194 send_newline_and_flush(&stdout_mutex);
1195 send_newline_ut = 0;
1196 }
1197
1198 PerflibNamesRegistryUpdate();
1199 }
1200
1201 functions_evloop_cancel_threads(wg);
1202 PerflibNamesRegistryCleanup();
1203 system_servicenames_cache_destroy(sc);
1204
1205 return 0;
1206 }