Networ Viewer (FreeBSD) (#22632)
thiagoftsm committed
Jun 9, 2026 at 16:54 UTC
487e1a3d377fff990f62e935ab9ac37af9b92710
6 files changed
+902
-8
CMakeLists.txt
+7
-1
@@ -201,7 +201,7 @@ cmake_dependent_option(ENABLE_PLUGIN_EBPF "Enable Linux eBPF metric collection"
201
cmake_dependent_option(ENABLE_LEGACY_EBPF_PROGRAMS "Enable eBPF programs for kernels without BTF support" True "ENABLE_PLUGIN_EBPF" False)
202
mark_as_advanced(ENABLE_LEGACY_EBPF_PROGRAMS)
203
cmake_dependent_option(ENABLE_PLUGIN_LOCAL_LISTENERS "Enable local listening socket tracking (including service auto-discovery support)" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
204
-cmake_dependent_option(ENABLE_PLUGIN_NETWORK_VIEWER "Enable network viewer functionality" ${DEFAULT_FEATURE_STATE} "OS_LINUX OR OS_WINDOWS" False)
204
+cmake_dependent_option(ENABLE_PLUGIN_NETWORK_VIEWER "Enable network viewer functionality" ${DEFAULT_FEATURE_STATE} "OS_LINUX OR OS_WINDOWS OR OS_FREEBSD" False)
205
cmake_dependent_option(ENABLE_PLUGIN_NFACCT "Enable Linux NFACCT metric collection" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
206
cmake_dependent_option(ENABLE_PLUGIN_PERF "Enable Linux performance counter monitoring" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
207
cmake_dependent_option(ENABLE_PLUGIN_SLABINFO "Enable Linux kernel SLAB allocator monitoring" ${DEFAULT_FEATURE_STATE} "OS_LINUX" False)
@@ -3191,6 +3191,12 @@ if(ENABLE_PLUGIN_NETWORK_VIEWER)
3191
src/collectors/network-viewer.plugin/network-viewer-windows.c
3192
)
3193
endif()
3194
+ if(OS_FREEBSD)
3195
+ list(APPEND NETWORK_VIEWER_FILES
3196
+ src/libnetdata/local-sockets/local-sockets-freebsd.h
3197
+ src/collectors/network-viewer.plugin/network-viewer.c
3198
+ )
3199
+ endif()
3200
3201
add_executable(network-viewer.plugin ${NETWORK_VIEWER_FILES})
3202
if(USE_LTO AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
packaging/installer/functions.sh
+3
-1
@@ -381,12 +381,14 @@ prepare_cmake_options() {
381
382
IS_LINUX=0
383
[ "$(uname -s)" = "Linux" ] && IS_LINUX=1
384
+ IS_LINUX_OR_FREEBSD="${IS_LINUX}"
385
+ [ "$(uname -s)" = "FreeBSD" ] && IS_LINUX_OR_FREEBSD=1
386
enable_feature PLUGIN_DEBUGFS "${IS_LINUX}"
387
enable_feature PLUGIN_PERF "${IS_LINUX}"
388
enable_feature PLUGIN_SLABINFO "${IS_LINUX}"
389
enable_feature PLUGIN_CGROUP_NETWORK "${IS_LINUX}"
390
enable_feature PLUGIN_LOCAL_LISTENERS "${IS_LINUX}"
389
- enable_feature PLUGIN_NETWORK_VIEWER "${IS_LINUX}"
391
+ enable_feature PLUGIN_NETWORK_VIEWER "${IS_LINUX_OR_FREEBSD}"
392
enable_feature PLUGIN_EBPF "${ENABLE_EBPF:-0}"
393
394
enable_feature BUNDLED_JSONC "${NETDATA_BUILD_JSON_C:-0}"
src/collectors/network-viewer.plugin/metadata.yaml
+30
-2
@@ -18,6 +18,7 @@ modules:
18
- tcp
19
- udp
20
- ports
21
+ - freebsd
22
related_resources:
23
integrations:
24
list: []
@@ -29,9 +30,14 @@ modules:
30
method_description: |
31
This plugin reads the system's socket tables to enumerate all active network connections,
32
including TCP and UDP sockets in all states, for both IPv4 and IPv6.
33
+
34
+ On Linux it consumes `NETLINK_INET_DIAG` and the `/proc` socket tables;
35
+ on FreeBSD it walks `KERN_PROC_FILEDESC` and consults `net.inet.tcp.pcblist`
36
+ to attribute sockets to processes.
37
supported_platforms:
38
include:
39
- Linux
40
+ - FreeBSD
41
exclude: []
42
multi_instance: false
43
additional_permissions:
@@ -74,7 +80,8 @@ modules:
80
scopes: []
81
functions:
82
description: |
77
- This plugin exposes a real-time function for viewing active network connections.
83
+ This plugin exposes real-time functions for viewing active network connections and,
84
+ on FreeBSD, system-wide TCP and UDP stack statistics.
85
list:
86
- id: network-connections
87
name: Network Connections
@@ -85,7 +92,8 @@ modules:
92
including connection direction (listen, inbound, outbound, local), process information,
93
and TCP performance metrics (RTT, retransmissions).
94
88
- Connections are classified as system or container based on network namespace.
95
+ On Linux, connections are classified as system or container based on network namespace.
96
+ On FreeBSD, namespaces are not exposed to userspace and the classification is omitted.
97
parameters: []
98
returns:
99
description: ""
@@ -93,6 +101,26 @@ modules:
101
performance: ""
102
security: ""
103
availability: ""
104
+ - id: network-protocols
105
+ name: Network Protocols
106
+ description: |
107
+ Shows FreeBSD TCP and UDP stack statistics grouped by transport protocol.
108
+
109
+ Each row covers one transport (TCP or UDP) and reports IPv4+IPv6 combined
110
+ traffic counters (received, sent, errors), TCP-specific connection counters
111
+ (active opens, currently established, passive opens, resets, total segments,
112
+ retransmitted segments), and the UDP-specific datagrams-with-no-port counter.
113
+
114
+ Values are per-second deltas sampled from `net.inet.tcp.stats`,
115
+ `net.inet.tcp.states`, and `net.inet.udp.stats`. The first call after the
116
+ plugin starts returns zeros because there is no previous sample to diff against.
117
+ parameters: []
118
+ returns:
119
+ description: ""
120
+ columns: []
121
+ performance: ""
122
+ security: ""
123
+ availability: "FreeBSD only."
124
- meta:
125
plugin_name: network-viewer.plugin
126
module_name: PerflibNetworkProtocols
src/collectors/network-viewer.plugin/network-viewer.c
+341
@@ -30,6 +30,8 @@ static SPAWN_SERVER *spawn_srv = NULL;
30
#define NETWORK_TOPOLOGY_VIEWER_FUNCTION "topology:network-connections"
31
#define NETWORK_TOPOLOGY_VIEWER_HELP "Shows live network-connections topology with self/process/endpoint actors and ownership/socket links."
32
#define NETWORK_VIEWER_RESPONSE_UPDATE_EVERY 5
33
+#define NETWORK_PROTOCOLS_FUNCTION "network-protocols"
34
+#define NETWORK_PROTOCOLS_FUNCTION_HELP "FreeBSD TCP and UDP statistics (IPv4 and IPv6 combined)"
35
// Keep in sync with the topology schema contract used across topology producers.
36
#define NETWORK_TOPOLOGY_SCHEMA_VERSION "netdata.topology.v1"
37
#define NETWORK_TOPOLOGY_SOURCE "network-connections"
@@ -157,9 +159,15 @@ typedef struct {
159
#include "libnetdata/simple_hashtable/simple_hashtable.h"
160
161
netdata_mutex_t stdout_mutex;
162
+#if defined(OS_FREEBSD)
163
+static netdata_mutex_t nv_proto_mutex;
164
+#endif
165
166
static void __attribute__((constructor)) init_mutex(void) {
167
netdata_mutex_init(&stdout_mutex);
168
+#if defined(OS_FREEBSD)
169
+ netdata_mutex_init(&nv_proto_mutex);
170
+#endif
171
}
172
173
static void __attribute__((destructor)) destroy_mutex(void) {
@@ -472,6 +480,7 @@ static inline void topology_pid_lookup_key(char *dst, size_t dst_size, uint64_t
480
snprintf(dst, dst_size, "pid=%llu", (unsigned long long)pid);
481
}
482
483
+#if defined(OS_LINUX)
484
static bool topology_read_proc_ppid(uint64_t pid, uint64_t *ppid) {
485
if(!pid || !ppid)
486
return false;
@@ -503,6 +512,31 @@ static bool topology_read_proc_ppid(uint64_t pid, uint64_t *ppid) {
512
*ppid = parent;
513
return true;
514
}
515
+#elif defined(OS_FREEBSD)
516
+#include <sys/types.h>
517
+#include <sys/sysctl.h>
518
+#include <sys/user.h>
519
+#include <netinet/udp.h>
520
+#include <netinet/udp_var.h>
521
+static bool topology_read_proc_ppid(uint64_t pid, uint64_t *ppid) {
522
+ if(!pid || !ppid)
523
+ return false;
524
+
525
+ struct kinfo_proc kp;
526
+ size_t size = sizeof(kp);
527
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid };
528
+
529
+ if(sysctl(mib, 4, &kp, &size, NULL, 0) != 0 || size == 0)
530
+ return false;
531
+
532
+ uint64_t parent = (uint64_t)kp.ki_ppid;
533
+ if(parent == pid)
534
+ parent = 0;
535
+
536
+ *ppid = parent;
537
+ return true;
538
+}
539
+#endif
540
541
static uint64_t topology_ppid_cache_get_or_load(DICTIONARY *ppid_cache, uint64_t pid) {
542
if(!ppid_cache || !pid)
@@ -923,14 +957,22 @@ static void local_socket_to_json_array(struct sockets_stats *st, const LOCAL_SOC
957
st->max.tcpi_rtt = n->info.tcp.tcpi_rtt;
958
959
// Receiver RTT
960
+#if defined(OS_LINUX)
961
buffer_json_add_array_item_double(wb, (double)n->info.tcp.tcpi_rcv_rtt / (double)USEC_PER_MS);
962
if(st->max.tcpi_rcv_rtt < n->info.tcp.tcpi_rcv_rtt)
963
st->max.tcpi_rcv_rtt = n->info.tcp.tcpi_rcv_rtt;
964
+#else
965
+ buffer_json_add_array_item_double(wb, 0.0);
966
+#endif
967
968
// Retransmissions
969
+#if defined(OS_LINUX)
970
buffer_json_add_array_item_uint64(wb, n->info.tcp.tcpi_total_retrans);
971
if(st->max.tcpi_total_retrans < n->info.tcp.tcpi_total_retrans)
972
st->max.tcpi_total_retrans = n->info.tcp.tcpi_total_retrans;
973
+#else
974
+ buffer_json_add_array_item_uint64(wb, 0);
975
+#endif
976
977
// count
978
buffer_json_add_array_item_uint64(wb, n->network_viewer.count);
@@ -997,19 +1039,29 @@ static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const L
1039
KEEP_THE_BIGGER(t->wqueue, n->wqueue);
1040
1041
// The current number of consecutive retransmissions that have occurred for the most recently transmitted segment.
1042
+#if defined(OS_LINUX)
1043
SUM_THEM_ALL(t->info.tcp.tcpi_retransmits, n->info.tcp.tcpi_retransmits);
1044
+#endif
1045
1046
// The total number of retransmissions that have occurred for the entire connection since it was established.
1047
+#if defined(OS_LINUX)
1048
SUM_THEM_ALL(t->info.tcp.tcpi_total_retrans, n->info.tcp.tcpi_total_retrans);
1049
+#endif
1050
1051
// The total number of segments that have been retransmitted since the connection was established.
1052
+#if defined(OS_LINUX)
1053
SUM_THEM_ALL(t->info.tcp.tcpi_retrans, n->info.tcp.tcpi_retrans);
1054
+#endif
1055
1056
// The number of keepalive probes sent
1057
+#if defined(OS_LINUX)
1058
SUM_THEM_ALL(t->info.tcp.tcpi_probes, n->info.tcp.tcpi_probes);
1059
+#endif
1060
1061
// The number of times the retransmission timeout has been backed off.
1062
+#if defined(OS_LINUX)
1063
SUM_THEM_ALL(t->info.tcp.tcpi_backoff, n->info.tcp.tcpi_backoff);
1064
+#endif
1065
1066
// A bitmask representing the TCP options currently enabled for the connection, such as SACK and Timestamps.
1067
OR_THEM_ALL(t->info.tcp.tcpi_options, n->info.tcp.tcpi_options);
@@ -1024,7 +1076,9 @@ static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const L
1076
KEEP_THE_SMALLER(t->info.tcp.tcpi_rto, n->info.tcp.tcpi_rto);
1077
1078
// The delayed acknowledgement timeout in milliseconds.
1079
+#if defined(OS_LINUX)
1080
KEEP_THE_SMALLER(t->info.tcp.tcpi_ato, n->info.tcp.tcpi_ato);
1081
+#endif
1082
1083
// The maximum segment size for sending.
1084
KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_mss, n->info.tcp.tcpi_snd_mss);
@@ -1033,34 +1087,54 @@ static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const L
1087
KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_mss, n->info.tcp.tcpi_rcv_mss);
1088
1089
// The number of unacknowledged segments
1090
+#if defined(OS_LINUX)
1091
SUM_THEM_ALL(t->info.tcp.tcpi_unacked, n->info.tcp.tcpi_unacked);
1092
+#endif
1093
1094
// The number of segments that have been selectively acknowledged
1095
+#if defined(OS_LINUX)
1096
SUM_THEM_ALL(t->info.tcp.tcpi_sacked, n->info.tcp.tcpi_sacked);
1097
+#endif
1098
1099
// The number of lost segments.
1100
+#if defined(OS_LINUX)
1101
SUM_THEM_ALL(t->info.tcp.tcpi_lost, n->info.tcp.tcpi_lost);
1102
+#endif
1103
1104
// The number of forward acknowledgment segments.
1105
+#if defined(OS_LINUX)
1106
SUM_THEM_ALL(t->info.tcp.tcpi_fackets, n->info.tcp.tcpi_fackets);
1107
+#endif
1108
1109
// The time in milliseconds since the last data was sent.
1110
+#if defined(OS_LINUX)
1111
KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_sent, n->info.tcp.tcpi_last_data_sent);
1112
+#endif
1113
1114
// The time in milliseconds since the last acknowledgment was sent (not tracked in Linux, hence often zero).
1115
+#if defined(OS_LINUX)
1116
KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_sent, n->info.tcp.tcpi_last_ack_sent);
1117
+#endif
1118
1119
// The time in milliseconds since the last data was received.
1120
+#if defined(OS_LINUX)
1121
KEEP_THE_SMALLER(t->info.tcp.tcpi_last_data_recv, n->info.tcp.tcpi_last_data_recv);
1122
+#endif
1123
1124
// The time in milliseconds since the last acknowledgment was received.
1125
+#if defined(OS_LINUX)
1126
KEEP_THE_SMALLER(t->info.tcp.tcpi_last_ack_recv, n->info.tcp.tcpi_last_ack_recv);
1127
+#endif
1128
1129
// The path MTU for this connection
1130
+#if defined(OS_LINUX)
1131
KEEP_THE_SMALLER(t->info.tcp.tcpi_pmtu, n->info.tcp.tcpi_pmtu);
1132
+#endif
1133
1134
// The slow start threshold for receiving
1135
+#if defined(OS_LINUX)
1136
KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_ssthresh, n->info.tcp.tcpi_rcv_ssthresh);
1137
+#endif
1138
1139
// The slow start threshold for sending
1140
KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_ssthresh, n->info.tcp.tcpi_snd_ssthresh);
@@ -1075,13 +1149,19 @@ static void local_sockets_cb_to_aggregation(LS_STATE *ls __maybe_unused, const L
1149
KEEP_THE_SMALLER(t->info.tcp.tcpi_snd_cwnd, n->info.tcp.tcpi_snd_cwnd);
1150
1151
// The maximum segment size that could be advertised.
1152
+#if defined(OS_LINUX)
1153
KEEP_THE_BIGGER(t->info.tcp.tcpi_advmss, n->info.tcp.tcpi_advmss);
1154
+#endif
1155
1156
// The reordering metric
1157
+#if defined(OS_LINUX)
1158
KEEP_THE_SMALLER(t->info.tcp.tcpi_reordering, n->info.tcp.tcpi_reordering);
1159
+#endif
1160
1161
// The receive round trip time in milliseconds.
1162
+#if defined(OS_LINUX)
1163
KEEP_THE_BIGGER(t->info.tcp.tcpi_rcv_rtt, n->info.tcp.tcpi_rcv_rtt);
1164
+#endif
1165
1166
// The available space in the receive buffer.
1167
KEEP_THE_SMALLER(t->info.tcp.tcpi_rcv_space, n->info.tcp.tcpi_rcv_space);
@@ -1322,11 +1402,15 @@ static void local_sockets_cb_to_topology(LS_STATE *ls, const LOCAL_SOCKET *n, vo
1402
}
1403
1404
link->sockets++;
1405
+#if defined(OS_LINUX)
1406
link->retransmissions += n->info.tcp.tcpi_total_retrans;
1407
+#endif
1408
if(link->max_rtt_usec < n->info.tcp.tcpi_rtt)
1409
link->max_rtt_usec = n->info.tcp.tcpi_rtt;
1410
+#if defined(OS_LINUX)
1411
if(link->max_rcv_rtt_usec < n->info.tcp.tcpi_rcv_rtt)
1412
link->max_rcv_rtt_usec = n->info.tcp.tcpi_rcv_rtt;
1413
+#endif
1414
}
1415
1416
static void topology_context_destroy(NV_TOPOLOGY_CONTEXT *ctx) {
@@ -4521,6 +4605,244 @@ close_and_send:
4605
netdata_mutex_unlock(&stdout_mutex);
4606
}
4607
4608
+// ----------------------------------------------------------------------------------------------------------------
4609
+// FreeBSD: network-protocols function
4610
+
4611
+#if defined(OS_FREEBSD)
4612
+
4613
+typedef struct {
4614
+ struct tcpstat tcp;
4615
+ struct udpstat udp;
4616
+ usec_t last_ut;
4617
+ bool initialized;
4618
+} NV_PROTO_STATE;
4619
+
4620
+static NV_PROTO_STATE nv_proto_prev = { .initialized = false };
4621
+
4622
+static uint64_t nv_proto_delta(uint64_t cur, uint64_t prev, double elapsed_s) {
4623
+ if (elapsed_s <= 0.0 || cur < prev)
4624
+ return 0;
4625
+ return (uint64_t)((double)(cur - prev) / elapsed_s + 0.5);
4626
+}
4627
+
4628
+void function_network_protocols(
4629
+ const char *transaction, char *function __maybe_unused,
4630
+ usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled __maybe_unused,
4631
+ BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
4632
+ const char *source __maybe_unused, void *data __maybe_unused)
4633
+{
4634
+ // Sampling and delta computation must be atomic: acquiring the mutex first
4635
+ // prevents two concurrent requests from each sampling stale counters and
4636
+ // then computing deltas against the same nv_proto_prev with a near-zero
4637
+ // elapsed time, which would produce wildly inflated per-second rates.
4638
+ netdata_mutex_lock(&nv_proto_mutex);
4639
+
4640
+ struct tcpstat tcp_cur = { 0 };
4641
+ struct udpstat udp_cur = { 0 };
4642
+ uint64_t established = 0;
4643
+ size_t len;
4644
+
4645
+ len = sizeof(tcp_cur);
4646
+ if (sysctlbyname("net.inet.tcp.stats", &tcp_cur, &len, NULL, 0) < 0) {
4647
+ netdata_mutex_unlock(&nv_proto_mutex);
4648
+ netdata_mutex_lock(&stdout_mutex);
4649
+ pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
4650
+ "failed to read net.inet.tcp.stats");
4651
+ netdata_mutex_unlock(&stdout_mutex);
4652
+ return;
4653
+ }
4654
+
4655
+ uint64_t tcp_states[TCP_NSTATES] = { 0 };
4656
+ len = sizeof(tcp_states);
4657
+ if (sysctlbyname("net.inet.tcp.states", tcp_states, &len, NULL, 0) == 0)
4658
+ established = tcp_states[TCPS_ESTABLISHED];
4659
+
4660
+ len = sizeof(udp_cur);
4661
+ if (sysctlbyname("net.inet.udp.stats", &udp_cur, &len, NULL, 0) < 0) {
4662
+ netdata_mutex_unlock(&nv_proto_mutex);
4663
+ netdata_mutex_lock(&stdout_mutex);
4664
+ pluginsd_function_json_error_to_stdout(transaction, HTTP_RESP_INTERNAL_SERVER_ERROR,
4665
+ "failed to read net.inet.udp.stats");
4666
+ netdata_mutex_unlock(&stdout_mutex);
4667
+ return;
4668
+ }
4669
+
4670
+ usec_t now_ut = now_monotonic_usec();
4671
+
4672
+ bool first = !nv_proto_prev.initialized;
4673
+ double elapsed_s = first ? 0.0 : (double)(now_ut - nv_proto_prev.last_ut) / (double)USEC_PER_SEC;
4674
+
4675
+#define TCP_DELTA(f) nv_proto_delta((uint64_t)tcp_cur.f, (uint64_t)nv_proto_prev.tcp.f, elapsed_s)
4676
+#define UDP_DELTA(f) nv_proto_delta((uint64_t)udp_cur.f, (uint64_t)nv_proto_prev.udp.f, elapsed_s)
4677
+
4678
+ uint64_t tcp_received = first ? 0 : TCP_DELTA(tcps_rcvtotal);
4679
+ uint64_t tcp_sent = first ? 0 : TCP_DELTA(tcps_sndtotal);
4680
+ uint64_t tcp_errors = first ? 0 : TCP_DELTA(tcps_conndrops);
4681
+ uint64_t tcp_active = first ? 0 : TCP_DELTA(tcps_connattempt);
4682
+ uint64_t tcp_passive = first ? 0 : TCP_DELTA(tcps_accepts);
4683
+ uint64_t tcp_resets = first ? 0 : TCP_DELTA(tcps_drops);
4684
+ uint64_t tcp_segs_total = first ? 0 : tcp_received + tcp_sent;
4685
+ uint64_t tcp_retrans = first ? 0 : TCP_DELTA(tcps_sndrexmitpack);
4686
+
4687
+ uint64_t udp_received = first ? 0 : UDP_DELTA(udps_ipackets);
4688
+ uint64_t udp_sent = first ? 0 : UDP_DELTA(udps_opackets);
4689
+ uint64_t udp_errors = first ? 0 : (
4690
+ UDP_DELTA(udps_hdrops) +
4691
+ UDP_DELTA(udps_badlen) +
4692
+ UDP_DELTA(udps_badsum) +
4693
+ UDP_DELTA(udps_nosum));
4694
+ uint64_t udp_no_port = first ? 0 : UDP_DELTA(udps_noport);
4695
+
4696
+#undef TCP_DELTA
4697
+#undef UDP_DELTA
4698
+
4699
+ nv_proto_prev.tcp = tcp_cur;
4700
+ nv_proto_prev.udp = udp_cur;
4701
+ nv_proto_prev.last_ut = now_ut;
4702
+ nv_proto_prev.initialized = true;
4703
+
4704
+ netdata_mutex_unlock(&nv_proto_mutex);
4705
+
4706
+ time_t now_s = now_realtime_sec();
4707
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
4708
+ wb->content_type = CT_APPLICATION_JSON;
4709
+ buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
4710
+
4711
+ buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
4712
+ buffer_json_member_add_string(wb, "type", "table");
4713
+ buffer_json_member_add_time_t(wb, "update_every", NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
4714
+ buffer_json_member_add_boolean(wb, "has_history", false);
4715
+ buffer_json_member_add_string(wb, "help", NETWORK_PROTOCOLS_FUNCTION_HELP);
4716
+
4717
+ buffer_json_member_add_array(wb, "data");
4718
+ {
4719
+ // TCP row — FreeBSD stats cover both IPv4 and IPv6 in a single counter set
4720
+ buffer_json_add_array_item_array(wb);
4721
+ {
4722
+ buffer_json_add_array_item_string(wb, "TCP");
4723
+ buffer_json_add_array_item_string(wb, "IPv4+IPv6");
4724
+ buffer_json_add_array_item_uint64(wb, tcp_received);
4725
+ buffer_json_add_array_item_uint64(wb, tcp_sent);
4726
+ buffer_json_add_array_item_uint64(wb, tcp_errors);
4727
+ buffer_json_add_array_item_uint64(wb, tcp_active);
4728
+ buffer_json_add_array_item_uint64(wb, established);
4729
+ buffer_json_add_array_item_uint64(wb, tcp_passive);
4730
+ buffer_json_add_array_item_uint64(wb, tcp_resets);
4731
+ buffer_json_add_array_item_uint64(wb, tcp_segs_total);
4732
+ buffer_json_add_array_item_uint64(wb, tcp_retrans);
4733
+ buffer_json_add_array_item_uint64(wb, 0); // DatagramsNoPort — UDP only
4734
+ }
4735
+ buffer_json_array_close(wb);
4736
+
4737
+ // UDP row
4738
+ buffer_json_add_array_item_array(wb);
4739
+ {
4740
+ buffer_json_add_array_item_string(wb, "UDP");
4741
+ buffer_json_add_array_item_string(wb, "IPv4+IPv6");
4742
+ buffer_json_add_array_item_uint64(wb, udp_received);
4743
+ buffer_json_add_array_item_uint64(wb, udp_sent);
4744
+ buffer_json_add_array_item_uint64(wb, udp_errors);
4745
+ buffer_json_add_array_item_uint64(wb, 0); // ConnActive — TCP only
4746
+ buffer_json_add_array_item_uint64(wb, 0); // ConnEstablished — TCP only
4747
+ buffer_json_add_array_item_uint64(wb, 0); // ConnPassive — TCP only
4748
+ buffer_json_add_array_item_uint64(wb, 0); // ConnReset — TCP only
4749
+ buffer_json_add_array_item_uint64(wb, 0); // SegsTotal — TCP only
4750
+ buffer_json_add_array_item_uint64(wb, 0); // SegsRetransmitted — TCP only
4751
+ buffer_json_add_array_item_uint64(wb, udp_no_port);
4752
+ }
4753
+ buffer_json_array_close(wb);
4754
+ }
4755
+ buffer_json_array_close(wb); // data
4756
+
4757
+ size_t field_id = 0;
4758
+ buffer_json_member_add_object(wb, "columns");
4759
+ {
4760
+ buffer_rrdf_table_add_field(wb, field_id++, "Transport", "Transport Protocol",
4761
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
4762
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
4763
+ RRDF_FIELD_FILTER_MULTISELECT,
4764
+ RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
4765
+
4766
+ buffer_rrdf_table_add_field(wb, field_id++, "Family", "IP Protocol Family",
4767
+ RRDF_FIELD_TYPE_STRING, RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE,
4768
+ 0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
4769
+ RRDF_FIELD_FILTER_MULTISELECT,
4770
+ RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
4771
+
4772
+#define NV_INT_FIELD(id, label, unit) \
4773
+ buffer_rrdf_table_add_field(wb, field_id++, id, label, \
4774
+ RRDF_FIELD_TYPE_INTEGER, RRDF_FIELD_VISUAL_VALUE, \
4775
+ RRDF_FIELD_TRANSFORM_NUMBER, 0, unit, NAN, \
4776
+ RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, \
4777
+ RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_VISIBLE, NULL)
4778
+
4779
+ NV_INT_FIELD("Received", "Received (Segments/Datagrams)", "segments/datagrams/s");
4780
+ NV_INT_FIELD("Sent", "Sent (Segments/Datagrams)", "segments/datagrams/s");
4781
+ NV_INT_FIELD("Errors", "Errors (Failures/Rx Errors)", "errors");
4782
+ NV_INT_FIELD("ConnActive", "Active Connections Opened", "opens");
4783
+ NV_INT_FIELD("ConnEstablished", "Currently Established Connections", "connections");
4784
+ NV_INT_FIELD("ConnPassive", "Passive Connections Opened", "opens");
4785
+ NV_INT_FIELD("ConnReset", "Reset Connections", "resets");
4786
+ NV_INT_FIELD("SegsTotal", "Total Segments", "segments/s");
4787
+ NV_INT_FIELD("SegsRetransmitted", "Retransmitted Segments", "segments/s");
4788
+ NV_INT_FIELD("DatagramsNoPort", "Datagrams with No Port", "datagrams/s");
4789
+#undef NV_INT_FIELD
4790
+ }
4791
+ buffer_json_object_close(wb); // columns
4792
+
4793
+ buffer_json_member_add_string(wb, "default_sort_column", "Received");
4794
+
4795
+ buffer_json_member_add_object(wb, "charts");
4796
+ {
4797
+ buffer_json_member_add_object(wb, "Traffic");
4798
+ {
4799
+ buffer_json_member_add_string(wb, "name", "Traffic");
4800
+ buffer_json_member_add_string(wb, "type", "stacked-bar");
4801
+ buffer_json_member_add_array(wb, "columns");
4802
+ {
4803
+ buffer_json_add_array_item_string(wb, "Received");
4804
+ buffer_json_add_array_item_string(wb, "Sent");
4805
+ }
4806
+ buffer_json_array_close(wb);
4807
+ }
4808
+ buffer_json_object_close(wb);
4809
+ }
4810
+ buffer_json_object_close(wb); // charts
4811
+
4812
+ buffer_json_member_add_array(wb, "default_charts");
4813
+ {
4814
+ buffer_json_add_array_item_array(wb);
4815
+ buffer_json_add_array_item_string(wb, "Traffic");
4816
+ buffer_json_add_array_item_string(wb, "Transport");
4817
+ buffer_json_array_close(wb);
4818
+ }
4819
+ buffer_json_array_close(wb); // default_charts
4820
+
4821
+ buffer_json_member_add_object(wb, "group_by");
4822
+ {
4823
+ buffer_json_member_add_object(wb, "Transport");
4824
+ {
4825
+ buffer_json_member_add_string(wb, "name", "Transport");
4826
+ buffer_json_member_add_array(wb, "columns");
4827
+ buffer_json_add_array_item_string(wb, "Transport");
4828
+ buffer_json_array_close(wb);
4829
+ }
4830
+ buffer_json_object_close(wb);
4831
+ }
4832
+ buffer_json_object_close(wb); // group_by
4833
+
4834
+ buffer_json_member_add_time_t(wb, "expires", now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY);
4835
+ buffer_json_finalize(wb);
4836
+
4837
+ netdata_mutex_lock(&stdout_mutex);
4838
+ wb->response_code = HTTP_RESP_OK;
4839
+ wb->expires = now_s + NETWORK_VIEWER_RESPONSE_UPDATE_EVERY;
4840
+ pluginsd_function_result_to_stdout(transaction, wb);
4841
+ netdata_mutex_unlock(&stdout_mutex);
4842
+}
4843
+
4844
+#endif // OS_FREEBSD
4845
+
4846
// ----------------------------------------------------------------------------------------------------------------
4847
// main
4848
@@ -4564,7 +4886,9 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
4886
NULL, HTTP_ACCESS_ALL, NULL, NULL);
4887
// }
4888
4889
+#if defined(LOCAL_SOCKETS_USE_SETNS)
4890
spawn_server_destroy(spawn_srv);
4891
+#endif
4892
exit(1);
4893
}
4894
@@ -4582,6 +4906,14 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
4906
(HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
4907
RRDFUNCTIONS_PRIORITY_DEFAULT);
4908
4909
+#if defined(OS_FREEBSD)
4910
+ fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
4911
+ NETWORK_PROTOCOLS_FUNCTION, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
4912
+ NETWORK_PROTOCOLS_FUNCTION_HELP,
4913
+ (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE),
4914
+ RRDFUNCTIONS_PRIORITY_DEFAULT);
4915
+#endif
4916
+
4917
// ----------------------------------------------------------------------------------------------------------------
4918
4919
struct functions_evloop_globals *wg =
@@ -4597,6 +4929,13 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
4929
PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
4930
NULL);
4931
4932
+#if defined(OS_FREEBSD)
4933
+ functions_evloop_add_function(wg, NETWORK_PROTOCOLS_FUNCTION,
4934
+ function_network_protocols,
4935
+ PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT,
4936
+ NULL);
4937
+#endif
4938
+
4939
// ----------------------------------------------------------------------------------------------------------------
4940
4941
usec_t send_newline_ut = 0;
@@ -4615,8 +4954,10 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
4954
}
4955
}
4956
4957
+#if defined(LOCAL_SOCKETS_USE_SETNS)
4958
spawn_server_destroy(spawn_srv);
4959
spawn_srv = NULL;
4960
+#endif
4961
4962
return 0;
4963
}
src/libnetdata/local-sockets/local-sockets-freebsd.h
new
+478
@@ -0,0 +1,478 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+// FreeBSD socket collection backend for local-sockets.h.
4
+// Included only when OS_FREEBSD is defined; never compiled standalone.
5
+
6
+#ifndef NETDATA_LOCAL_SOCKETS_FREEBSD_H
7
+#define NETDATA_LOCAL_SOCKETS_FREEBSD_H
8
+
9
+#include <errno.h>
10
+#include <sys/types.h>
11
+#include <sys/socket.h>
12
+#include <sys/sysctl.h>
13
+#include <sys/user.h>
14
+#include <sys/file.h>
15
+// FreeBSD 14+ guards struct inpcb, struct xinpgen, and struct xtcpcb
16
+// behind _KERNEL unless these macros are defined before the headers.
17
+#ifndef _WANT_INPCB
18
+#define _WANT_INPCB
19
+#endif
20
+#ifndef _WANT_TCPCB
21
+#define _WANT_TCPCB
22
+#endif
23
+
24
+#include <netinet/in.h>
25
+#include <netinet/in_pcb.h>
26
+#include <netinet/tcp_var.h>
27
+#include <netinet/tcp_fsm.h>
28
+
29
+// --------------------------------------------------------------------------------------------------------------------
30
+// kinfo_file address accessor: struct layout changed in FreeBSD 12.0.31
31
+
32
+#if __FreeBSD_version >= 1200031
33
+#define KF_SA_LOCAL(kf) ((const struct sockaddr_storage *)&(kf)->kf_un.kf_sock.kf_sa_local)
34
+#define KF_SA_PEER(kf) ((const struct sockaddr_storage *)&(kf)->kf_un.kf_sock.kf_sa_peer)
35
+#else
36
+#define KF_SA_LOCAL(kf) ((const struct sockaddr_storage *)&(kf)->kf_sa_local)
37
+#define KF_SA_PEER(kf) ((const struct sockaddr_storage *)&(kf)->kf_sa_peer)
38
+#endif
39
+
40
+// kf_sock_inpcb was removed when inpcb/tcpcb merged in FreeBSD 14.
41
+#if __FreeBSD_version >= 1400074
42
+#define KF_SOCK_PCB_TCP(kf) ((uint64_t)(uintptr_t)(kf)->kf_un.kf_sock.kf_sock_pcb)
43
+#elif __FreeBSD_version >= 1200031
44
+#define KF_SOCK_PCB_TCP(kf) ((uint64_t)(uintptr_t)(kf)->kf_un.kf_sock.kf_sock_inpcb)
45
+#else
46
+#define KF_SOCK_PCB_TCP(kf) ((uint64_t)(uintptr_t)(kf)->kf_sock_inpcb)
47
+#endif
48
+
49
+#if __FreeBSD_version >= 1200031
50
+#define KF_SOCK_PCB_UDP(kf) ((uint64_t)(uintptr_t)(kf)->kf_un.kf_sock.kf_sock_pcb)
51
+#else
52
+#define KF_SOCK_PCB_UDP(kf) ((uint64_t)(uintptr_t)(kf)->kf_sock_pcb)
53
+#endif
54
+
55
+// --------------------------------------------------------------------------------------------------------------------
56
+// Normalize FreeBSD TCPS_* states to Linux-compatible TCP_* values.
57
+// This keeps the rest of the code (direction detection, TCP_STATE_2str display)
58
+// unchanged, since Linux and FreeBSD use different numbering for the same states.
59
+// Only used on FreeBSD 12/13; on 14+ the pcblist format changed and states are
60
+// not parsed from xinpgen/xtcpcb (those structs are no longer exported).
61
+
62
+#if __FreeBSD_version < 1400000
63
+static inline int local_sockets_freebsd_normalize_tcp_state(int tcps) {
64
+ switch (tcps) {
65
+ case TCPS_ESTABLISHED: return TCP_ESTABLISHED;
66
+ case TCPS_SYN_SENT: return TCP_SYN_SENT;
67
+ case TCPS_SYN_RECEIVED: return TCP_SYN_RECV;
68
+ case TCPS_FIN_WAIT_1: return TCP_FIN_WAIT1;
69
+ case TCPS_FIN_WAIT_2: return TCP_FIN_WAIT2;
70
+ case TCPS_TIME_WAIT: return TCP_TIME_WAIT;
71
+ case TCPS_CLOSED: return TCP_CLOSE;
72
+ case TCPS_CLOSE_WAIT: return TCP_CLOSE_WAIT;
73
+ case TCPS_LAST_ACK: return TCP_LAST_ACK;
74
+ case TCPS_LISTEN: return TCP_LISTEN;
75
+ case TCPS_CLOSING: return TCP_CLOSING;
76
+ default: return 0;
77
+ }
78
+}
79
+#endif
80
+
81
+// --------------------------------------------------------------------------------------------------------------------
82
+// PCB state table entry: keyed by 4-tuple for matching against kinfo_file data.
83
+
84
+struct freebsd_tcp_state {
85
+ uint8_t family;
86
+ uint16_t local_port;
87
+ uint16_t remote_port;
88
+ int tcp_state; // Linux-normalised TCP_* value
89
+ union ipv46 local_ip;
90
+ union ipv46 remote_ip;
91
+};
92
+
93
+// --------------------------------------------------------------------------------------------------------------------
94
+// Fill a socket_endpoint from a sockaddr_storage.
95
+// Returns false if the address family is not AF_INET / AF_INET6 (e.g., AF_UNSPEC for
96
+// an unconnected peer slot).
97
+
98
+static inline bool local_sockets_freebsd_fill_endpoint(
99
+ struct socket_endpoint *ep,
100
+ const struct sockaddr_storage *ss,
101
+ uint16_t protocol)
102
+{
103
+ ep->protocol = protocol;
104
+
105
+ if (ss->ss_family == AF_INET) {
106
+ const struct sockaddr_in *sin = (const struct sockaddr_in *)ss;
107
+ ep->family = AF_INET;
108
+ ep->port = ntohs(sin->sin_port);
109
+ ep->ip.ipv4 = sin->sin_addr.s_addr;
110
+ return true;
111
+ }
112
+ if (ss->ss_family == AF_INET6) {
113
+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)ss;
114
+ ep->family = AF_INET6;
115
+ ep->port = ntohs(sin6->sin6_port);
116
+ ep->ip.ipv6 = sin6->sin6_addr;
117
+ return true;
118
+ }
119
+ return false;
120
+}
121
+
122
+// --------------------------------------------------------------------------------------------------------------------
123
+// Read net.inet.tcp.pcblist to build a table of {4-tuple → TCP state}.
124
+// Returns a malloc'd array; caller must freez() it.
125
+// FreeBSD 14+ changed the inpcb/tcpcb layout; if xt_tp is unavailable we skip
126
+// the state table (direction detection still works via zero-peer-address check).
127
+
128
+static struct freebsd_tcp_state *local_sockets_freebsd_read_tcp_pcblist(size_t *out_count) {
129
+ *out_count = 0;
130
+
131
+#if __FreeBSD_version < 1400000
132
+ // FreeBSD 12/13: net.inet.tcp.pcblist returns xinpgen/xtcpcb records.
133
+ // These structs were removed from the public headers in FreeBSD 14 when
134
+ // inpcb and tcpcb were merged; attempting to use them on 14+ would not
135
+ // compile. On 14+ we return NULL and all TCP sockets are treated as
136
+ // ESTABLISHED (direction detection still works via zero-peer-address).
137
+
138
+ size_t len = 0;
139
+ if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0 || len == 0)
140
+ return NULL;
141
+
142
+ char *buf = NULL;
143
+ // Retry loop: the list may grow between the size query and the read.
144
+ for (int attempt = 0; attempt < 3; attempt++) {
145
+ size_t try_len = len + len / 5; // 20% headroom
146
+ freez(buf);
147
+ buf = mallocz(try_len);
148
+ if (sysctlbyname("net.inet.tcp.pcblist", buf, &try_len, NULL, 0) == 0) {
149
+ len = try_len;
150
+ break;
151
+ }
152
+ // Free and null on any failure so the post-loop guard is meaningful.
153
+ // Without this, ENOMEM exhaustion leaves buf non-NULL but unfilled.
154
+ freez(buf);
155
+ buf = NULL;
156
+ if (errno != ENOMEM)
157
+ return NULL;
158
+ // ENOMEM: retry with larger buffer
159
+ }
160
+ if (!buf) return NULL;
161
+
162
+ const char *p = buf;
163
+ const char *end = buf + len;
164
+
165
+ // Parse header xinpgen to get the entry count.
166
+ if ((size_t)(end - p) < sizeof(struct xinpgen)) {
167
+ freez(buf);
168
+ return NULL;
169
+ }
170
+ const struct xinpgen *hdr = (const struct xinpgen *)p;
171
+ p += hdr->xig_len;
172
+
173
+ size_t capacity = hdr->xig_count + 4;
174
+ struct freebsd_tcp_state *states = mallocz(capacity * sizeof(*states));
175
+ size_t count = 0;
176
+
177
+ while (p + sizeof(struct xtcpcb) <= end) {
178
+ const struct xtcpcb *tp = (const struct xtcpcb *)p;
179
+
180
+ if (tp->xt_len == 0)
181
+ break;
182
+ // The footer is an xinpgen whose xt_len equals sizeof(xinpgen).
183
+ if (tp->xt_len <= sizeof(struct xinpgen))
184
+ break;
185
+
186
+ const struct inpcb *inp = &tp->xt_inp;
187
+
188
+ uint8_t family;
189
+ if (inp->inp_vflag & INP_IPV6)
190
+ family = AF_INET6;
191
+ else if (inp->inp_vflag & INP_IPV4)
192
+ family = AF_INET;
193
+ else {
194
+ p += tp->xt_len;
195
+ continue;
196
+ }
197
+
198
+ if (count >= capacity) {
199
+ capacity *= 2;
200
+ states = reallocz(states, capacity * sizeof(*states));
201
+ }
202
+
203
+ struct freebsd_tcp_state *s = &states[count++];
204
+ s->family = family;
205
+ s->local_port = ntohs(inp->inp_lport);
206
+ s->remote_port = ntohs(inp->inp_fport);
207
+ s->tcp_state = local_sockets_freebsd_normalize_tcp_state(tp->xt_tp.t_state);
208
+
209
+ if (family == AF_INET) {
210
+ s->local_ip.ipv4 = inp->inp_laddr.s_addr;
211
+ s->remote_ip.ipv4 = inp->inp_faddr.s_addr;
212
+ } else {
213
+ s->local_ip.ipv6 = inp->in6p_laddr;
214
+ s->remote_ip.ipv6 = inp->in6p_faddr;
215
+ }
216
+
217
+ p += tp->xt_len;
218
+ }
219
+
220
+ freez(buf);
221
+ *out_count = count;
222
+ return states;
223
+#else
224
+ // FreeBSD 14+: xinpgen/xtcpcb no longer exported to userspace.
225
+ // Direction detection via zero-peer-address still works in enumerate_pids.
226
+ return NULL;
227
+#endif
228
+}
229
+
230
+// --------------------------------------------------------------------------------------------------------------------
231
+// Lookup TCP state by 4-tuple (linear scan; O(n) per socket, n is typically < 5000).
232
+
233
+static inline int local_sockets_freebsd_lookup_tcp_state(
234
+ const struct freebsd_tcp_state *states,
235
+ size_t n_states,
236
+ uint8_t family,
237
+ uint16_t local_port,
238
+ const union ipv46 *local_ip,
239
+ uint16_t remote_port,
240
+ const union ipv46 *remote_ip)
241
+{
242
+ for (size_t i = 0; i < n_states; i++) {
243
+ const struct freebsd_tcp_state *s = &states[i];
244
+
245
+ if (s->family != family || s->local_port != local_port || s->remote_port != remote_port)
246
+ continue;
247
+
248
+ if (family == AF_INET) {
249
+ if (s->local_ip.ipv4 != local_ip->ipv4 || s->remote_ip.ipv4 != remote_ip->ipv4)
250
+ continue;
251
+ } else {
252
+ if (memcmp(&s->local_ip.ipv6, local_ip, sizeof(struct in6_addr)) != 0 ||
253
+ memcmp(&s->remote_ip.ipv6, remote_ip, sizeof(struct in6_addr)) != 0)
254
+ continue;
255
+ }
256
+
257
+ return s->tcp_state;
258
+ }
259
+ // Not found in pcblist: treat as established so the socket still appears.
260
+ return TCP_ESTABLISHED;
261
+}
262
+
263
+// --------------------------------------------------------------------------------------------------------------------
264
+// Walk all processes + their file descriptors via sysctl(KERN_PROC_FILEDESC).
265
+// For each TCP/UDP socket FD, look up TCP state from the pcblist table, build a
266
+// LOCAL_SOCKET, and feed it into local_sockets_add_socket().
267
+
268
+static inline void local_sockets_freebsd_enumerate_pids(
269
+ LS_STATE *ls,
270
+ const struct freebsd_tcp_state *tcp_states,
271
+ size_t n_tcp_states)
272
+{
273
+ // --- Get list of all processes ---
274
+ int mib_all[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
275
+ size_t proc_size = 0;
276
+
277
+ if (sysctl(mib_all, 3, NULL, &proc_size, NULL, 0) < 0) {
278
+ local_sockets_log(ls, "sysctl KERN_PROC_ALL size failed: %s", strerror(errno));
279
+ return;
280
+ }
281
+
282
+ struct kinfo_proc *procs = NULL;
283
+ size_t nprocs = 0;
284
+
285
+ for (int attempt = 0; attempt < 3; attempt++) {
286
+ size_t try_size = proc_size + proc_size / 5;
287
+ freez(procs);
288
+ procs = mallocz(try_size);
289
+ if (sysctl(mib_all, 3, procs, &try_size, NULL, 0) == 0) {
290
+ nprocs = try_size / sizeof(struct kinfo_proc);
291
+ break;
292
+ }
293
+ freez(procs);
294
+ procs = NULL;
295
+ if (errno != ENOMEM) {
296
+ local_sockets_log(ls, "sysctl KERN_PROC_ALL failed: %s", strerror(errno));
297
+ return;
298
+ }
299
+ }
300
+ if (!procs) return;
301
+
302
+ // --- Walk each process's file descriptors ---
303
+ for (size_t i = 0; i < nprocs; i++) {
304
+ pid_t pid = procs[i].ki_pid;
305
+ pid_t ppid = procs[i].ki_ppid;
306
+ uid_t uid = procs[i].ki_uid;
307
+
308
+ if (pid <= 0) continue;
309
+
310
+ int mib_fd[4] = { CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC, (int)pid };
311
+ size_t fd_size = 0;
312
+
313
+ if (sysctl(mib_fd, 4, NULL, &fd_size, NULL, 0) < 0 || fd_size == 0)
314
+ continue;
315
+
316
+ char *fdbuf = NULL;
317
+ for (int attempt = 0; attempt < 3; attempt++) {
318
+ size_t try_size = fd_size + fd_size / 5;
319
+ freez(fdbuf);
320
+ fdbuf = mallocz(try_size);
321
+ if (sysctl(mib_fd, 4, fdbuf, &try_size, NULL, 0) == 0) {
322
+ fd_size = try_size;
323
+ break;
324
+ }
325
+ freez(fdbuf);
326
+ fdbuf = NULL;
327
+ if (errno != ENOMEM)
328
+ break;
329
+ }
330
+ if (!fdbuf) continue;
331
+
332
+ char cmdline[LOCAL_SOCKETS_CMDLINE_MAX];
333
+ bool cmdline_loaded = false;
334
+ cmdline[0] = '\0';
335
+
336
+ char *p = fdbuf;
337
+ char *efdbuf = fdbuf + fd_size;
338
+
339
+ while (p < efdbuf) {
340
+ struct kinfo_file *kf = (struct kinfo_file *)p;
341
+ if (kf->kf_structsize == 0) break;
342
+
343
+ // Only care about internet sockets owned by a FD (fd >= 0).
344
+ if (kf->kf_fd < 0 ||
345
+ kf->kf_type != KF_TYPE_SOCKET ||
346
+ (kf->kf_sock_domain != AF_INET && kf->kf_sock_domain != AF_INET6) ||
347
+ (kf->kf_sock_protocol != IPPROTO_TCP && kf->kf_sock_protocol != IPPROTO_UDP)) {
348
+ p += kf->kf_structsize;
349
+ continue;
350
+ }
351
+
352
+ const struct sockaddr_storage *sa_local = KF_SA_LOCAL(kf);
353
+ const struct sockaddr_storage *sa_peer = KF_SA_PEER(kf);
354
+
355
+ // Build a LOCAL_SOCKET from the kinfo_file data.
356
+ LOCAL_SOCKET n = {
357
+ .direction = SOCKET_DIRECTION_NONE,
358
+ .uid = uid,
359
+ .pid = pid,
360
+ .ppid = ppid,
361
+ .ipv6ony = { .checked = false, .ipv46 = false },
362
+ };
363
+
364
+ if (!local_sockets_freebsd_fill_endpoint(&n.local, sa_local, kf->kf_sock_protocol)) {
365
+ p += kf->kf_structsize;
366
+ continue;
367
+ }
368
+
369
+ // Peer may be unset (listening / unconnected UDP); zero it out in that case.
370
+ if (!local_sockets_freebsd_fill_endpoint(&n.remote, sa_peer, kf->kf_sock_protocol)) {
371
+ n.remote.family = kf->kf_sock_domain;
372
+ n.remote.protocol = kf->kf_sock_protocol;
373
+ n.remote.port = 0;
374
+ memset(&n.remote.ip, 0, sizeof(n.remote.ip));
375
+ }
376
+
377
+ // Use PCB kernel address as the unique per-socket key (inode equivalent).
378
+ if (kf->kf_sock_protocol == IPPROTO_TCP)
379
+ n.inode = KF_SOCK_PCB_TCP(kf);
380
+ else
381
+ n.inode = KF_SOCK_PCB_UDP(kf);
382
+
383
+ if (!n.inode) {
384
+ p += kf->kf_structsize;
385
+ continue;
386
+ }
387
+
388
+ // TCP state from pcblist; UDP is stateless.
389
+ if (kf->kf_sock_protocol == IPPROTO_TCP) {
390
+ n.state = local_sockets_freebsd_lookup_tcp_state(
391
+ tcp_states, n_tcp_states,
392
+ n.local.family,
393
+ n.local.port,
394
+ &n.local.ip,
395
+ n.remote.port,
396
+ &n.remote.ip);
397
+ }
398
+
399
+ // Lazily read cmdline on first socket found for this process.
400
+ if (ls->config.cmdline && !cmdline_loaded) {
401
+ cmdline_loaded = true;
402
+ int mib_args[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ARGS, (int)pid };
403
+ size_t args_size = sizeof(cmdline) - 1;
404
+ if (sysctl(mib_args, 4, cmdline, &args_size, NULL, 0) == 0 && args_size > 0) {
405
+ // cmdline args are NUL-separated; convert to spaces.
406
+ for (size_t j = 0; j + 1 < args_size; j++)
407
+ if (cmdline[j] == '\0') cmdline[j] = ' ';
408
+ cmdline[args_size] = '\0';
409
+ local_sockets_fix_cmdline(cmdline);
410
+ }
411
+ else
412
+ cmdline[0] = '\0';
413
+ }
414
+
415
+ // --- Register PID attribution entry ---
416
+ {
417
+ XXH64_hash_t h = XXH3_64bits(&n.inode, sizeof(n.inode));
418
+ SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl =
419
+ simple_hashtable_get_slot_PID_SOCKET(&ls->pid_sockets_hashtable, h, &n.inode, true);
420
+ struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl);
421
+
422
+ if (!ps || (ps->pid == 1 && pid != 1)) {
423
+ if (!ps) ps = aral_callocz(ls->pid_socket_aral);
424
+
425
+ ps->inode = n.inode;
426
+ ps->pid = pid;
427
+ ps->ppid = ppid;
428
+ ps->uid = uid;
429
+ ps->net_ns_inode = 0; // FreeBSD: no network namespaces
430
+
431
+ if (ls->config.comm)
432
+ strncpyz(ps->comm, procs[i].ki_comm, sizeof(ps->comm) - 1);
433
+
434
+ if (ls->config.cmdline) {
435
+ freez(ps->cmdline);
436
+ const char *t = cmdline[0] ? trim(cmdline) : NULL;
437
+ ps->cmdline = t ? strdupz(t) : NULL;
438
+ }
439
+
440
+ simple_hashtable_set_slot_PID_SOCKET(&ls->pid_sockets_hashtable, sl, h, ps);
441
+ }
442
+ }
443
+
444
+ // Comm goes into the socket via pid_socket lookup inside add_socket;
445
+ // set it here too so it is available even if the pid_socket slot lost a race.
446
+ if (ls->config.comm)
447
+ strncpyz(n.comm, procs[i].ki_comm, sizeof(n.comm) - 1);
448
+
449
+ local_sockets_add_socket(ls, &n);
450
+
451
+ p += kf->kf_structsize;
452
+ }
453
+
454
+ freez(fdbuf);
455
+ }
456
+
457
+ freez(procs);
458
+}
459
+
460
+// --------------------------------------------------------------------------------------------------------------------
461
+// FreeBSD entry point for local_sockets_read_all_system_sockets().
462
+// Called from local_sockets_process() in local-sockets.h.
463
+
464
+static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) {
465
+ // Phase 1: build TCP state table from net.inet.tcp.pcblist.
466
+ size_t n_tcp_states = 0;
467
+ struct freebsd_tcp_state *tcp_states = NULL;
468
+
469
+ if (ls->config.tcp4 || ls->config.tcp6)
470
+ tcp_states = local_sockets_freebsd_read_tcp_pcblist(&n_tcp_states);
471
+
472
+ // Phase 2: enumerate per-process sockets via KERN_PROC_FILEDESC.
473
+ local_sockets_freebsd_enumerate_pids(ls, tcp_states, n_tcp_states);
474
+
475
+ freez(tcp_states);
476
+}
477
+
478
+#endif /* NETDATA_LOCAL_SOCKETS_FREEBSD_H */
src/libnetdata/local-sockets/local-sockets.h
+43
-4
@@ -9,8 +9,11 @@
9
#define _countof(x) (sizeof(x) / sizeof(*(x)))
10
#endif
11
12
+// Network-namespace switching via setns() is Linux-only.
13
+#if defined(OS_LINUX)
14
#define LOCAL_SOCKETS_USE_SETNS
15
#define USE_LIBMNL_AFTER_SETNS
16
+#endif
17
18
#if defined(HAVE_LIBMNL)
19
#include <linux/rtnetlink.h>
@@ -23,6 +26,25 @@
26
27
#define UID_UNSET (uid_t)(UINT32_MAX)
28
29
+// FreeBSD uses TCPS_* values from tcp_fsm.h (different numbering from Linux).
30
+// Define Linux-compatible TCP_* constants here so the rest of the code —
31
+// direction detection (TCP_LISTEN check) and TCP_STATE_2str display — works
32
+// unchanged on FreeBSD. The FreeBSD backend converts TCPS_* → TCP_* before
33
+// storing the state in LOCAL_SOCKET.state.
34
+#if defined(OS_FREEBSD)
35
+#define TCP_ESTABLISHED 1
36
+#define TCP_SYN_SENT 2
37
+#define TCP_SYN_RECV 3
38
+#define TCP_FIN_WAIT1 4
39
+#define TCP_FIN_WAIT2 5
40
+#define TCP_TIME_WAIT 6
41
+#define TCP_CLOSE 7
42
+#define TCP_CLOSE_WAIT 8
43
+#define TCP_LAST_ACK 9
44
+#define TCP_LISTEN 10
45
+#define TCP_CLOSING 11
46
+#endif
47
+
48
// max cmdline bytes read from /proc/<pid>/cmdline — reader-side guard must match
49
#define LOCAL_SOCKETS_CMDLINE_MAX 8192
50
@@ -570,6 +592,8 @@ local_sockets_read_proc_inode_link(LS_STATE *ls, const char *filename, uint64_t
592
}
593
}
594
595
+#if !defined(OS_FREEBSD) // /proc-based PID + FD walking is Linux-only
596
+
597
static inline bool local_sockets_is_path_a_pid(const char *s) {
598
if(!s || !*s) return false;
599
@@ -736,6 +760,8 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
760
return true;
761
}
762
763
+#endif // !OS_FREEBSD
764
+
765
// --------------------------------------------------------------------------------------------------------------------
766
767
static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET *n) {
@@ -980,6 +1006,8 @@ static inline bool local_sockets_libmnl_get_sockets(LS_STATE *ls, uint16_t famil
1006
}
1007
#endif // HAVE_LIBMNL
1008
1009
+#if !defined(OS_FREEBSD) // /proc-based socket tables and pcblist reading is Linux-only
1010
+
1011
static inline bool local_sockets_process_proc_line(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol, size_t line, char **words, size_t num_words) {
1012
// char *sl_txt = get_word(words, num_words, 0);
1013
char *local_ip_txt = get_word(words, num_words, 1);
@@ -1143,7 +1171,10 @@ static inline bool local_sockets_read_proc_net_x_procfile(LS_STATE *ls, const ch
1171
return true;
1172
}
1173
1174
+#endif // !OS_FREEBSD
1175
+
1176
// --------------------------------------------------------------------------------------------------------------------
1177
+// Generic helpers – compiled on all platforms.
1178
1179
static inline void local_sockets_detect_directions(LS_STATE *ls) {
1180
for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable);
@@ -1355,6 +1386,8 @@ static void local_sockets_track_time_by_protocol(LS_STATE *ls, bool mnl, uint16_
1386
}
1387
}
1388
1389
+#if !defined(OS_FREEBSD) // /proc-based socket reading is Linux-only
1390
+
1391
static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) {
1392
#if defined(HAVE_LIBMNL)
1393
if(!ls->config.no_mnl) {
@@ -1410,8 +1443,13 @@ static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) {
1443
}
1444
}
1445
1446
+#elif defined(OS_FREEBSD)
1447
+// FreeBSD: local_sockets_read_all_system_sockets() is provided by the FreeBSD backend.
1448
+#include "local-sockets-freebsd.h"
1449
+#endif // !OS_FREEBSD
1450
+
1451
// --------------------------------------------------------------------------------------------------------------------
1414
-// switch namespaces to read namespace sockets
1452
+// switch namespaces to read namespace sockets (Linux/setns only)
1453
1454
#if defined(LOCAL_SOCKETS_USE_SETNS)
1455
@@ -1726,9 +1764,9 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
1764
#endif // LOCAL_SOCKETS_USE_SETNS
1765
1766
// --------------------------------------------------------------------------------------------------------------------
1729
-// read namespace sockets from the host's /proc
1767
+// read namespace sockets from the host's /proc (Linux without setns only)
1768
1731
-#if !defined(LOCAL_SOCKETS_USE_SETNS)
1769
+#if !defined(LOCAL_SOCKETS_USE_SETNS) && !defined(OS_FREEBSD)
1770
1771
static inline bool local_sockets_namespaces_from_proc_with_pid(LS_STATE *ls, struct pid_socket *ps) {
1772
char filename[1024];
@@ -1836,9 +1874,10 @@ static inline void local_sockets_process(LS_STATE *ls) {
1874
local_sockets_track_time(ls, "switch_namespaces");
1875
#if defined(LOCAL_SOCKETS_USE_SETNS)
1876
local_sockets_namespaces(ls);
1839
-#else
1877
+#elif !defined(OS_FREEBSD)
1878
local_sockets_namespaces_from_proc(ls);
1879
#endif
1880
+ // FreeBSD: jails are a different isolation model; namespace switching not supported.
1881
}
1882
1883
// detect the directions of the sockets