@cryptotaxi247 / netdata / commits / 487e1a3d3

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