master
h 478 lines 17.4 KB
Raw
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 */