| 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 */ |