@cryptotaxi247 / netdata / commits / 8be0c4deb

Network Viewer (Connections on Windows) (#22585)

thiagoftsm committed Jun 2, 2026 at 17:16 UTC 8be0c4deb2156d721a3c2ad2743618a829b44cf7
4 files changed +1228 -491
CMakeLists.txt
+1 -1
@@ -3144,7 +3144,7 @@ if(ENABLE_PLUGIN_NETWORK_VIEWER)
3144 endif()
3145 if(OS_WINDOWS)
3146 list(APPEND NETWORK_VIEWER_FILES
3147 - src/collectors/network-viewer.plugin/perflib-tcp_udp.c
3147 + src/collectors/network-viewer.plugin/network-viewer-windows.c
3148 )
3149 endif()
3150
src/collectors/network-viewer.plugin/metadata.yaml
+21 -1
@@ -168,8 +168,28 @@ modules:
168 scopes: []
169 functions:
170 description: |
171 - This collector exposes a real-time function for viewing Windows TCP and UDP stack statistics.
171 + This collector exposes real-time functions for viewing Windows network connections and TCP/UDP stack statistics.
172 list:
173 + - id: network-connections
174 + name: Network Connections
175 + description: |
176 + Shows active network connections on Windows with protocol details, states, addresses,
177 + ports, and process information.
178 +
179 + Each row represents one TCP connection or UDP endpoint and includes the socket direction
180 + (listen/inbound/outbound), protocol (tcp4/tcp6/udp4/udp6), TCP state, owning process
181 + (PID and name), username, server port name, local and remote addresses and ports,
182 + address space classification (loopback/private/public/multicast/zero), and server port number.
183 +
184 + Data is collected using the Windows IP Helper API (GetExtendedTcpTable,
185 + GetExtendedUdpTable) with process and user resolution via standard Windows APIs.
186 + parameters: []
187 + returns:
188 + description: ""
189 + columns: []
190 + performance: ""
191 + security: ""
192 + availability: ""
193 - id: network-protocols
194 name: Network Protocols
195 description: |
src/collectors/network-viewer.plugin/network-viewer-windows.c new
+1206
@@ -0,0 +1,1206 @@
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 +}
src/collectors/network-viewer.plugin/perflib-tcp_udp.c deleted
-489
@@ -1,489 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -#include "libnetdata/libnetdata.h"
4 -#include "libnetdata/os/windows-perflib/perflib.h"
5 -
6 -#define PLUGIN_NETWORK_VIEWER_NAME "network-viewer.plugin"
7 -#define NV_WIN_FUNCTION_PROTO "network-protocols"
8 -#define NV_WIN_FUNCTION_PROTO_HELP "Windows TCP and UDP statistics by transport and IP family"
9 -#define NV_WIN_FUNCTION_UPDATE_EVERY 5
10 -#define NV_WIN_FUNCTION_PRIORITY 100
11 -
12 -netdata_mutex_t stdout_mutex;
13 -static bool plugin_should_exit = false;
14 -
15 -// ============================================================
16 -// Shared helpers
17 -// ============================================================
18 -
19 -// Resolve a perflib object by name; returns true and sets both out-pointers on success.
20 -static bool perflib_get_object(const char *object_name,
21 - PERF_DATA_BLOCK **pDataBlock_out,
22 - PERF_OBJECT_TYPE **pObjectType_out)
23 -{
24 - DWORD id = RegistryFindIDByName(object_name);
25 - if (id == PERFLIB_REGISTRY_NAME_NOT_FOUND)
26 - return false;
27 -
28 - *pDataBlock_out = perflibGetPerformanceData(id);
29 - if (!*pDataBlock_out)
30 - return false;
31 -
32 - *pObjectType_out = perflibFindObjectTypeByName(*pDataBlock_out, object_name);
33 - return *pObjectType_out != NULL;
34 -}
35 -
36 -// Write the common JSON table response header fields into an already-created buffer.
37 -static void nv_table_begin(BUFFER *wb, const char *help)
38 -{
39 - buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
40 - buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
41 - buffer_json_member_add_string(wb, "type", "table");
42 - buffer_json_member_add_time_t(wb, "update_every", NV_WIN_FUNCTION_UPDATE_EVERY);
43 - buffer_json_member_add_boolean(wb, "has_history", false);
44 - buffer_json_member_add_string(wb, "help", help);
45 -}
46 -
47 -// Finalize the JSON, then send it to pluginsd under the stdout mutex.
48 -static void nv_send_result(const char *transaction, BUFFER *wb, time_t now_s)
49 -{
50 - buffer_json_member_add_time_t(wb, "expires", now_s + NV_WIN_FUNCTION_UPDATE_EVERY);
51 - buffer_json_finalize(wb);
52 - netdata_mutex_lock(&stdout_mutex);
53 - wb->response_code = HTTP_RESP_OK;
54 - wb->content_type = CT_APPLICATION_JSON;
55 - wb->expires = now_s + NV_WIN_FUNCTION_UPDATE_EVERY;
56 - pluginsd_function_result_to_stdout(transaction, wb);
57 - netdata_mutex_unlock(&stdout_mutex);
58 -}
59 -
60 -// Serialize pluginsd JSON errors the same way as success responses.
61 -static void nv_send_error(const char *transaction, int code, const char *message)
62 -{
63 - netdata_mutex_lock(&stdout_mutex);
64 - pluginsd_function_json_error_to_stdout(transaction, code, message);
65 - netdata_mutex_unlock(&stdout_mutex);
66 -}
67 -
68 -// Add a sticky string key column (Transport, Family, etc.).
69 -static void nv_add_key_field(BUFFER *wb, size_t *field_id, const char *id, const char *label)
70 -{
71 - buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
72 - RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
73 - 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
74 - RRDF_FIELD_FILTER_MULTISELECT,
75 - RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
76 -}
77 -
78 -// Add a standard integer counter column (all counter columns share the same display/filter flags).
79 -static void nv_add_int_field(BUFFER *wb, size_t *field_id,
80 - const char *id, const char *label, const char *unit)
81 -{
82 - buffer_rrdf_table_add_field(wb, (*field_id)++, id, label,
83 - RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER,
84 - 0, unit, NAN, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
85 - RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_VISIBLE, NULL);
86 -}
87 -
88 -// Add one entry to the default_charts array: [chart_key, groupby_column].
89 -static void nv_add_default_chart(BUFFER *wb, const char *chart_key, const char *groupby)
90 -{
91 - buffer_json_add_array_item_array(wb);
92 - buffer_json_add_array_item_string(wb, chart_key);
93 - buffer_json_add_array_item_string(wb, groupby);
94 - buffer_json_array_close(wb);
95 -}
96 -
97 -// Add one entry to the group_by object: a single-column grouping keyed by name.
98 -static void nv_add_group_by(BUFFER *wb, const char *name)
99 -{
100 - buffer_json_member_add_object(wb, name);
101 - {
102 - buffer_json_member_add_string(wb, "name", name);
103 - buffer_json_member_add_array(wb, "columns");
104 - buffer_json_add_array_item_string(wb, name);
105 - buffer_json_array_close(wb);
106 - }
107 - buffer_json_object_close(wb);
108 -}
109 -
110 -// Convert Perflib rate counters to per-second values and keep raw gauges unchanged.
111 -static uint64_t nv_perflib_value(const COUNTER_DATA *cd)
112 -{
113 - if(unlikely(!cd->updated))
114 - return 0;
115 -
116 - switch(cd->current.CounterType) {
117 - case PERF_COUNTER_COUNTER:
118 - case PERF_SAMPLE_COUNTER:
119 - case PERF_COUNTER_BULK_COUNT: {
120 - if(unlikely(!cd->previous.Time || !cd->current.Frequency))
121 - return 0;
122 -
123 - ULONGLONG data1 = cd->current.Data;
124 - ULONGLONG data0 = cd->previous.Data;
125 - LONGLONG time1 = cd->current.Time;
126 - LONGLONG time0 = cd->previous.Time;
127 - LONGLONG dt = time1 - time0;
128 -
129 - if(unlikely(dt <= 0 || data1 < data0))
130 - return 0;
131 -
132 - return (uint64_t)(((double)(data1 - data0) * (double)cd->current.Frequency) / (double)dt);
133 - }
134 -
135 - default:
136 - return (uint64_t)cd->current.Data;
137 - }
138 -}
139 -
140 -// ============================================================
141 -// TCP
142 -// ============================================================
143 -
144 -typedef struct {
145 - const char *af;
146 - const char *object_name;
147 -
148 - COUNTER_DATA connection_failures;
149 - COUNTER_DATA connections_active;
150 - COUNTER_DATA connections_established;
151 - COUNTER_DATA connections_passive;
152 - COUNTER_DATA connections_reset;
153 - COUNTER_DATA segments_total;
154 - COUNTER_DATA segments_received;
155 - COUNTER_DATA segments_retransmitted;
156 - COUNTER_DATA segments_sent;
157 -} TCP_FAMILY;
158 -
159 -static TCP_FAMILY tcp_ipv4 = {
160 - .af = "IPv4",
161 - .object_name = "TCPv4",
162 -};
163 -
164 -static TCP_FAMILY tcp_ipv6 = {
165 - .af = "IPv6",
166 - .object_name = "TCPv6",
167 -};
168 -
169 -static void initialize_tcp_keys(TCP_FAMILY *tcp)
170 -{
171 - tcp->connection_failures.key = "Connection Failures";
172 - tcp->connections_active.key = "Connections Active";
173 - tcp->connections_established.key = "Connections Established";
174 - tcp->connections_passive.key = "Connections Passive";
175 - tcp->connections_reset.key = "Connections Reset";
176 - tcp->segments_total.key = "Segments/sec";
177 - tcp->segments_received.key = "Segments Received/sec";
178 - tcp->segments_retransmitted.key = "Segments Retransmitted/sec";
179 - tcp->segments_sent.key = "Segments Sent/sec";
180 -}
181 -
182 -
183 -static bool tcp_collect_family(TCP_FAMILY *tcp)
184 -{
185 - PERF_DATA_BLOCK *pDataBlock;
186 - PERF_OBJECT_TYPE *pObjectType;
187 - if (!perflib_get_object(tcp->object_name, &pDataBlock, &pObjectType))
188 - return false;
189 -
190 - bool have_any = false;
191 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connection_failures);
192 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_active);
193 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_established);
194 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_passive);
195 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->connections_reset);
196 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_total);
197 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_received);
198 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_retransmitted);
199 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &tcp->segments_sent);
200 -
201 - return have_any;
202 -}
203 -
204 -// ============================================================
205 -// UDP
206 -// ============================================================
207 -
208 -typedef struct {
209 - const char *af;
210 - const char *object_name;
211 -
212 - COUNTER_DATA datagrams_no_port;
213 - COUNTER_DATA datagrams_received_errors;
214 - COUNTER_DATA datagrams_received;
215 - COUNTER_DATA datagrams_sent;
216 -} UDP_FAMILY;
217 -
218 -static UDP_FAMILY udp_ipv4 = {
219 - .af = "IPv4",
220 - .object_name = "UDPv4",
221 -};
222 -
223 -static UDP_FAMILY udp_ipv6 = {
224 - .af = "IPv6",
225 - .object_name = "UDPv6",
226 -};
227 -
228 -static netdata_mutex_t nv_collect_mutex;
229 -
230 -static void initialize_udp_keys(UDP_FAMILY *udp)
231 -{
232 - udp->datagrams_no_port.key = "Datagrams No Port/sec";
233 - udp->datagrams_received_errors.key = "Datagrams Received Errors";
234 - udp->datagrams_received.key = "Datagrams Received/sec";
235 - udp->datagrams_sent.key = "Datagrams Sent/sec";
236 -}
237 -
238 -
239 -static bool udp_collect_family(UDP_FAMILY *udp)
240 -{
241 - PERF_DATA_BLOCK *pDataBlock;
242 - PERF_OBJECT_TYPE *pObjectType;
243 - if (!perflib_get_object(udp->object_name, &pDataBlock, &pObjectType))
244 - return false;
245 -
246 - bool have_any = false;
247 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_no_port);
248 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received_errors);
249 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_received);
250 - have_any |= perflibGetObjectCounter(pDataBlock, pObjectType, &udp->datagrams_sent);
251 -
252 - return have_any;
253 -}
254 -
255 -// ============================================================
256 -// Network Protocols (combined TCP + UDP function)
257 -// ============================================================
258 -
259 -// Column order for all rows: Transport, Family, Received, Sent, Errors,
260 -// ConnActive, ConnEstablished, ConnPassive, ConnReset, SegsTotal, SegsRetransmitted,
261 -// DatagramsNoPort.
262 -
263 -static void proto_emit_tcp_row(BUFFER *wb, const TCP_FAMILY *tcp)
264 -{
265 - buffer_json_add_array_item_array(wb);
266 - {
267 - buffer_json_add_array_item_string(wb, "TCP");
268 - buffer_json_add_array_item_string(wb, tcp->af);
269 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_received));
270 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_sent));
271 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connection_failures));
272 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_active));
273 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_established));
274 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_passive));
275 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->connections_reset));
276 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_total));
277 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&tcp->segments_retransmitted));
278 - buffer_json_add_array_item_uint64(wb, 0); // DatagramsNoPort — UDP only
279 - }
280 - buffer_json_array_close(wb);
281 -}
282 -
283 -static void proto_emit_udp_row(BUFFER *wb, const UDP_FAMILY *udp)
284 -{
285 - buffer_json_add_array_item_array(wb);
286 - {
287 - buffer_json_add_array_item_string(wb, "UDP");
288 - buffer_json_add_array_item_string(wb, udp->af);
289 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received));
290 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_sent));
291 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_received_errors));
292 - buffer_json_add_array_item_uint64(wb, 0); // ConnActive — TCP only
293 - buffer_json_add_array_item_uint64(wb, 0); // ConnEstablished — TCP only
294 - buffer_json_add_array_item_uint64(wb, 0); // ConnPassive — TCP only
295 - buffer_json_add_array_item_uint64(wb, 0); // ConnReset — TCP only
296 - buffer_json_add_array_item_uint64(wb, 0); // SegsTotal — TCP only
297 - buffer_json_add_array_item_uint64(wb, 0); // SegsRetransmitted — TCP only
298 - buffer_json_add_array_item_uint64(wb, nv_perflib_value(&udp->datagrams_no_port));
299 - }
300 - buffer_json_array_close(wb);
301 -}
302 -
303 -void function_network_protocols(
304 - const char *transaction, char *function __maybe_unused,
305 - usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled,
306 - BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
307 - const char *source __maybe_unused, void *data __maybe_unused)
308 -{
309 - bool have_tcp_ipv4 = false;
310 - bool have_tcp_ipv6 = false;
311 - bool have_udp_ipv4 = false;
312 - bool have_udp_ipv6 = false;
313 -
314 - if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
315 - nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
316 - goto cleanup;
317 - }
318 -
319 - // Serialize access to the shared COUNTER_DATA state so previous/current
320 - // deltas are consistent regardless of which worker thread handles the request.
321 - // Hold the mutex across collection AND data-row emission: proto_emit_*_row
322 - // reads previous/current fields from the same shared structs that another
323 - // worker would overwrite during its own collection pass.
324 - netdata_mutex_lock(&nv_collect_mutex);
325 - have_tcp_ipv4 = tcp_collect_family(&tcp_ipv4);
326 - have_tcp_ipv6 = tcp_collect_family(&tcp_ipv6);
327 - have_udp_ipv4 = udp_collect_family(&udp_ipv4);
328 - have_udp_ipv6 = udp_collect_family(&udp_ipv6);
329 -
330 - if(unlikely(cancelled && __atomic_load_n(cancelled, __ATOMIC_RELAXED))) {
331 - netdata_mutex_unlock(&nv_collect_mutex);
332 - nv_send_error(transaction, HTTP_RESP_CLIENT_CLOSED_REQUEST, "Request cancelled.");
333 - goto cleanup;
334 - }
335 -
336 - if(unlikely(!have_tcp_ipv4 && !have_tcp_ipv6 && !have_udp_ipv4 && !have_udp_ipv6)) {
337 - netdata_mutex_unlock(&nv_collect_mutex);
338 - nv_send_error(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
339 - "failed to collect Windows TCP/UDP stack statistics");
340 - goto cleanup;
341 - }
342 -
343 - time_t now_s = now_realtime_sec();
344 - CLEAN_BUFFER *wb = buffer_create(0, NULL);
345 - nv_table_begin(wb, NV_WIN_FUNCTION_PROTO_HELP);
346 -
347 - buffer_json_member_add_array(wb, "data");
348 - {
349 - if(have_tcp_ipv4)
350 - proto_emit_tcp_row(wb, &tcp_ipv4);
351 - if(have_tcp_ipv6)
352 - proto_emit_tcp_row(wb, &tcp_ipv6);
353 - if(have_udp_ipv4)
354 - proto_emit_udp_row(wb, &udp_ipv4);
355 - if(have_udp_ipv6)
356 - proto_emit_udp_row(wb, &udp_ipv6);
357 - }
358 - buffer_json_array_close(wb); // data
359 - netdata_mutex_unlock(&nv_collect_mutex);
360 -
361 - size_t field_id = 0;
362 - buffer_json_member_add_object(wb, "columns");
363 - {
364 - nv_add_key_field(wb, &field_id, "Transport", "Transport Protocol");
365 - nv_add_key_field(wb, &field_id, "Family", "IP Protocol Family");
366 -
367 - // Normalized columns — TCP: segments, UDP: datagrams
368 - nv_add_int_field(wb, &field_id, "Received", "Received (Segments/Datagrams)", "segments/datagrams/s");
369 - nv_add_int_field(wb, &field_id, "Sent", "Sent (Segments/Datagrams)", "segments/datagrams/s");
370 - nv_add_int_field(wb, &field_id, "Errors", "Errors (Failures/Rx Errors)", "errors");
371 -
372 - // TCP-only columns (UDP rows carry 0)
373 - // ConnActive/ConnPassive/ConnReset are PERF_COUNTER_RAWCOUNT cumulative totals,
374 - // not per-second rates, so units have no /s suffix.
375 - nv_add_int_field(wb, &field_id, "ConnActive", "Active Connections Opened", "opens");
376 - nv_add_int_field(wb, &field_id, "ConnEstablished", "Currently Established Connections", "connections");
377 - nv_add_int_field(wb, &field_id, "ConnPassive", "Passive Connections Opened", "opens");
378 - nv_add_int_field(wb, &field_id, "ConnReset", "Reset Connections", "resets");
379 - nv_add_int_field(wb, &field_id, "SegsTotal", "Total Segments", "segments/s");
380 - nv_add_int_field(wb, &field_id, "SegsRetransmitted", "Retransmitted Segments", "segments/s");
381 -
382 - // UDP-only column (TCP rows carry 0)
383 - nv_add_int_field(wb, &field_id, "DatagramsNoPort", "Datagrams with No Port", "datagrams/s");
384 - }
385 - buffer_json_object_close(wb); // columns
386 - buffer_json_member_add_string(wb, "default_sort_column", "Received");
387 -
388 - // charts.columns = metric columns for the Y axis (NOT the groupby column)
389 - buffer_json_member_add_object(wb, "charts");
390 - {
391 - buffer_json_member_add_object(wb, "Traffic");
392 - {
393 - buffer_json_member_add_string(wb, "name", "Traffic");
394 - buffer_json_member_add_string(wb, "type", "stacked-bar");
395 - buffer_json_member_add_array(wb, "columns");
396 - {
397 - buffer_json_add_array_item_string(wb, "Received");
398 - buffer_json_add_array_item_string(wb, "Sent");
399 - }
400 - buffer_json_array_close(wb);
401 - }
402 - buffer_json_object_close(wb);
403 - }
404 - buffer_json_object_close(wb); // charts
405 -
406 - // default_charts: [chart_key, groupby_column] — same chart, two grouping axes
407 - buffer_json_member_add_array(wb, "default_charts");
408 - {
409 - nv_add_default_chart(wb, "Traffic", "Transport");
410 - nv_add_default_chart(wb, "Traffic", "Family");
411 - }
412 - buffer_json_array_close(wb); // default_charts
413 -
414 - buffer_json_member_add_object(wb, "group_by");
415 - {
416 - nv_add_group_by(wb, "Transport");
417 - nv_add_group_by(wb, "Family");
418 - }
419 - buffer_json_object_close(wb); // group_by
420 -
421 - nv_send_result(transaction, wb, now_s);
422 -
423 -cleanup:
424 - // Release the thread-local perflib buffer once per request to avoid retaining
425 - // the largest query size for the lifetime of the worker thread.
426 - perflibFreePerformanceData();
427 -}
428 -
429 -// ============================================================
430 -// main
431 -// ============================================================
432 -
433 -int main(int argc, char **argv)
434 -{
435 - netdata_mutex_init(&stdout_mutex);
436 - nd_log_initialize_for_external_plugins("network-viewer.plugin");
437 - netdata_threads_init_for_external_plugins(0);
438 -
439 - PerflibNamesRegistryInitialize();
440 - netdata_mutex_init(&nv_collect_mutex);
441 -
442 - // Prime each family's COUNTER_DATA so the first real request has a valid
443 - // previous baseline and rate counters return non-zero values immediately.
444 - initialize_tcp_keys(&tcp_ipv4);
445 - initialize_tcp_keys(&tcp_ipv6);
446 - initialize_udp_keys(&udp_ipv4);
447 - initialize_udp_keys(&udp_ipv6);
448 - tcp_collect_family(&tcp_ipv4);
449 - tcp_collect_family(&tcp_ipv6);
450 - udp_collect_family(&udp_ipv4);
451 - udp_collect_family(&udp_ipv6);
452 - perflibFreePerformanceData();
453 -
454 - fprintf(stdout,
455 - PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" " HTTP_ACCESS_FORMAT " %d\n",
456 - NV_WIN_FUNCTION_PROTO, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NV_WIN_FUNCTION_PROTO_HELP,
457 - (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE),
458 - NV_WIN_FUNCTION_PRIORITY);
459 - fflush(stdout);
460 -
461 - struct functions_evloop_globals *wg =
462 - functions_evloop_init(5, "NV-WIN", &stdout_mutex, &plugin_should_exit, NULL);
463 -
464 - functions_evloop_add_function(wg, NV_WIN_FUNCTION_PROTO, function_network_protocols,
465 - PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL);
466 -
467 - usec_t send_newline_ut = 0;
468 - const bool tty = isatty(fileno(stdout)) == 1;
469 -
470 - heartbeat_t hb;
471 - heartbeat_init(&hb, USEC_PER_SEC);
472 -
473 - while (!__atomic_load_n(&plugin_should_exit, __ATOMIC_ACQUIRE)) {
474 - usec_t dt_ut = heartbeat_next(&hb);
475 - send_newline_ut += dt_ut;
476 -
477 - if (!tty && send_newline_ut > USEC_PER_SEC) {
478 - send_newline_and_flush(&stdout_mutex);
479 - send_newline_ut = 0;
480 - }
481 -
482 - PerflibNamesRegistryUpdate();
483 - }
484 -
485 - functions_evloop_cancel_threads(wg);
486 - PerflibNamesRegistryCleanup();
487 -
488 - return 0;
489 -}