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
-}