master ohmu/fastfetch-openbsd / src / common / impl / netif_linux.c
ohmu  ·  2026-10-15
  1#include "common/netif.h"
  2#include "common/io.h"
  3#include "common/mallocHelper.h"
  4#include "common/debug.h"
  5
  6#include <arpa/inet.h>
  7#include <linux/rtnetlink.h>
  8#include <net/if.h>
  9
 10static uint32_t ffNetifGetNetlinkPortId(int sock_fd) {
 11    struct sockaddr_nl addr = {};
 12    socklen_t addrLen = sizeof(addr);
 13    if (getsockname(sock_fd, (struct sockaddr*) &addr, &addrLen) < 0) {
 14        FF_DEBUG("Failed to query netlink socket address (use PID instead): %s", strerror(errno));
 15        return instance.state.platform.pid;
 16    } else {
 17        FF_DEBUG("Netlink port ID: %u", addr.nl_pid);
 18        return addr.nl_pid;
 19    }
 20}
 21
 22bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) {
 23    FF_DEBUG("Starting IPv4 default route detection");
 24
 25    FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
 26    if (sock_fd < 0) {
 27        FF_DEBUG("Failed to create netlink socket: %s", strerror(errno));
 28        return false;
 29    }
 30    FF_DEBUG("Created netlink socket: fd=%d", sock_fd);
 31
 32    // Bind socket
 33    struct sockaddr_nl addr = {
 34        .nl_family = AF_NETLINK,
 35        .nl_pid = 0,    // Let kernel choose PID
 36        .nl_groups = 0, // No multicast groups
 37    };
 38
 39    if (bind(sock_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
 40        FF_DEBUG("Failed to bind socket: %s", strerror(errno));
 41        return false;
 42    }
 43    FF_DEBUG("Successfully bound socket");
 44
 45    uint32_t pid = ffNetifGetNetlinkPortId(sock_fd);
 46
 47    struct [[gnu::packed]] {
 48        struct nlmsghdr nlh;
 49        struct rtmsg rtm;
 50        struct rtattr rta;
 51        uint32_t table;
 52    } req = {
 53        // Netlink message header
 54        .nlh = {
 55            .nlmsg_len = sizeof(req),
 56            .nlmsg_type = RTM_GETROUTE,
 57            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
 58            .nlmsg_seq = 1,
 59            .nlmsg_pid = pid,
 60        },
 61        // Route message
 62        .rtm = {
 63            .rtm_family = AF_INET,
 64            .rtm_dst_len = 0, // Match all destinations
 65            .rtm_src_len = 0, // Match all sources
 66            .rtm_tos = 0,
 67            .rtm_table = RT_TABLE_UNSPEC,
 68            .rtm_protocol = RTPROT_UNSPEC,
 69            .rtm_scope = RT_SCOPE_UNIVERSE,
 70            .rtm_type = RTN_UNSPEC,
 71            .rtm_flags = 0,
 72        },
 73        // Route attribute for main table
 74        .rta = {
 75            .rta_len = RTA_LENGTH(sizeof(uint32_t)),
 76            .rta_type = RTA_TABLE,
 77        },
 78        .table = RT_TABLE_MAIN,
 79    };
 80
 81    struct sockaddr_nl dest_addr = {
 82        .nl_family = AF_NETLINK,
 83        .nl_pid = 0,    // Kernel
 84        .nl_groups = 0, // No multicast groups
 85    };
 86
 87    ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0, (struct sockaddr*) &dest_addr, sizeof(dest_addr));
 88
 89    if (sent != sizeof(req)) {
 90        FF_DEBUG("Failed to send netlink request: sent=%zd, expected=%zu", sent, sizeof(req));
 91        return false;
 92    }
 93    FF_DEBUG("Sent netlink request: %zd bytes", sent);
 94
 95    struct sockaddr_nl src_addr = {};
 96    socklen_t src_addr_len = sizeof(src_addr);
 97
 98    uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient
 99    uint32_t minMetric = UINT32_MAX;
100    [[maybe_unused]] int routeCount = 0;
101
102    while (true) {
103        ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &src_addr, &src_addr_len);
104
105        if (received < 0) {
106            FF_DEBUG("Failed to receive netlink response: %s", strerror(errno));
107            return false;
108        }
109
110        if (received >= (ssize_t) sizeof(buffer)) {
111            FF_DEBUG("Received truncated message: received %zd, bufsize %zu", received, sizeof(buffer));
112            return false;
113        }
114        FF_DEBUG("Received netlink response: %zd bytes", received);
115        if (received == 0) {
116            FF_DEBUG("Received zero-length netlink response, ending processing");
117            break;
118        }
119
120        struct {
121            uint32_t metric;
122            uint32_t ifindex;
123            uint32_t prefsrc;
124        } entry;
125
126        for (const struct nlmsghdr* nlh = (struct nlmsghdr*) buffer;
127            NLMSG_OK(nlh, received);
128            nlh = NLMSG_NEXT(nlh, received)) {
129            if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid) {
130                continue;
131            }
132            if (nlh->nlmsg_type == NLMSG_DONE) {
133                FF_DEBUG("Received NLMSG_DONE, processed %d routes", routeCount);
134                goto exit;
135            }
136
137            if (nlh->nlmsg_type == NLMSG_ERROR) {
138                FF_DEBUG("Netlink reports error: %s", strerror(-((struct nlmsgerr*) NLMSG_DATA(nlh))->error));
139                continue;
140            }
141
142            if (nlh->nlmsg_type != RTM_NEWROUTE) {
143                FF_DEBUG("Skipping non-route message: type=%d", nlh->nlmsg_type);
144                continue;
145            }
146
147            routeCount++;
148            struct rtmsg* rtm = (struct rtmsg*) NLMSG_DATA(nlh);
149            if (rtm->rtm_family != AF_INET) {
150                FF_DEBUG("Skipping non-IPv4 route #%d (family=%d)", routeCount, rtm->rtm_family);
151                continue;
152            }
153
154            if (rtm->rtm_dst_len != 0) {
155                FF_DEBUG("Skipping non-default route #%d (dst_len=%d)", routeCount, rtm->rtm_dst_len);
156                continue;
157            }
158
159            // Skip local/loopback routes
160            if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) {
161                FF_DEBUG("Skipping local route #%d (scope=%d, type=%d)", routeCount, rtm->rtm_scope, rtm->rtm_type);
162                continue;
163            }
164
165            FF_DEBUG("Processing IPv4 default route candidate #%d", routeCount);
166            entry = (typeof(entry)) {}; // Default to zero metric (no RTA_PRIORITY found)
167
168            // Parse route attributes
169            size_t rtm_len = RTM_PAYLOAD(nlh);
170            for (struct rtattr* rta = RTM_RTA(rtm);
171                RTA_OK(rta, rtm_len);
172                rta = RTA_NEXT(rta, rtm_len)) {
173                if (RTA_PAYLOAD(rta) < sizeof(uint32_t)) {
174                    continue; // Skip invalid attributes
175                }
176
177                uint32_t rta_data = *(uint32_t*) RTA_DATA(rta);
178                switch (rta->rta_type) {
179                    case RTA_DST:
180                        FF_DEBUG("Unexpected RTA_DST: %s (len=%u)", inet_ntoa((struct in_addr) { .s_addr = rta_data }), rtm->rtm_dst_len);
181                        goto next;
182                    case RTA_OIF:
183                        entry.ifindex = rta_data;
184                        FF_DEBUG("Found interface index: %u", entry.ifindex);
185                        break;
186                    case RTA_GATEWAY:
187                        FF_DEBUG("Found gateway: %s", inet_ntoa(*(struct in_addr*) &rta_data));
188                        if (rta_data == 0) {
189                            goto next;
190                        }
191                        break;
192                    case RTA_PRIORITY:
193                        FF_DEBUG("Found metric: %u", rta_data);
194                        if (rta_data >= minMetric) {
195                            goto next;
196                        }
197                        entry.metric = rta_data;
198                        break;
199                    case RTA_PREFSRC:
200                        entry.prefsrc = rta_data;
201                        FF_DEBUG("Found preferred source: %s", inet_ntoa(*(struct in_addr*) &rta_data));
202                        break;
203                }
204            }
205
206            if (entry.ifindex == 0 || entry.metric >= minMetric) {
207            next:
208                FF_DEBUG("Skipping route: ifindex=%u, metric=%u", entry.ifindex, entry.metric);
209                continue;
210            }
211            minMetric = entry.metric;
212            result->ifIndex = entry.ifindex;
213            FF_DEBUG("Updated best route: ifindex=%u, metric=%u, prefsrc=%x", entry.ifindex, entry.metric, entry.prefsrc);
214            result->preferredSourceAddrV4 = entry.prefsrc;
215            if (minMetric == 0) {
216                FF_DEBUG("Found zero metric route, stopping further processing");
217                break; // Stop processing if we found a zero metric route
218            }
219        }
220    }
221
222exit:
223    if (minMetric < UINT32_MAX) {
224        if_indextoname(result->ifIndex, result->ifName);
225        FF_DEBUG("Found default IPv4 route: interface=%s, index=%u, metric=%u", result->ifName, result->ifIndex, minMetric);
226        return true;
227    }
228    FF_DEBUG("No IPv4 default route found");
229    return false;
230}
231
232bool ffNetifGetDefaultRouteImplV6(FFNetifDefaultRouteResult* result) {
233    FF_DEBUG("Starting IPv6 default route detection");
234
235    FF_AUTO_CLOSE_FD int sock_fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
236    if (sock_fd < 0) {
237        FF_DEBUG("Failed to create netlink socket: %s", strerror(errno));
238        return false;
239    }
240    FF_DEBUG("Created netlink socket: fd=%d", sock_fd);
241
242    // Bind socket
243    struct sockaddr_nl addr = {
244        .nl_family = AF_NETLINK,
245        .nl_pid = 0,    // Let kernel choose PID
246        .nl_groups = 0, // No multicast groups
247    };
248
249    if (bind(sock_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
250        FF_DEBUG("Failed to bind socket: %s", strerror(errno));
251        return false;
252    }
253    FF_DEBUG("Successfully bound socket");
254
255    uint32_t pid = ffNetifGetNetlinkPortId(sock_fd);
256
257    struct [[gnu::packed]] {
258        struct nlmsghdr nlh;
259        struct rtmsg rtm;
260        struct rtattr rta;
261        uint32_t table;
262    } req = {
263        // Netlink message header
264        .nlh = {
265            .nlmsg_len = sizeof(req),
266            .nlmsg_type = RTM_GETROUTE,
267            .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
268            .nlmsg_seq = 1,
269            .nlmsg_pid = pid,
270        },
271        // Route message
272        .rtm = {
273            .rtm_family = AF_INET6, // IPv6 instead of IPv4
274            .rtm_dst_len = 0,       // Match all destinations
275            .rtm_src_len = 0,       // Match all sources
276            .rtm_tos = 0,
277            .rtm_table = RT_TABLE_UNSPEC,
278            .rtm_protocol = RTPROT_UNSPEC,
279            .rtm_scope = RT_SCOPE_UNIVERSE,
280            .rtm_type = RTN_UNSPEC,
281            .rtm_flags = 0,
282        },
283        // Route attribute for main table
284        .rta = {
285            .rta_len = RTA_LENGTH(sizeof(uint32_t)),
286            .rta_type = RTA_TABLE,
287        },
288        .table = RT_TABLE_MAIN,
289    };
290
291    struct sockaddr_nl dest_addr = {
292        .nl_family = AF_NETLINK,
293        .nl_pid = 0,    // Kernel
294        .nl_groups = 0, // No multicast groups
295    };
296
297    ssize_t sent = sendto(sock_fd, &req, sizeof(req), 0, (struct sockaddr*) &dest_addr, sizeof(dest_addr));
298
299    if (sent != sizeof(req)) {
300        FF_DEBUG("Failed to send netlink request: sent=%zd, expected=%zu", sent, sizeof(req));
301        return false;
302    }
303    FF_DEBUG("Sent netlink request: %zd bytes", sent);
304
305    struct sockaddr_nl src_addr = {};
306    socklen_t src_addr_len = sizeof(src_addr);
307
308    uint8_t buffer[1024 * 16]; // 16 KB buffer should be sufficient
309    uint32_t minMetric = UINT32_MAX;
310    [[maybe_unused]] int routeCount = 0;
311
312    while (true) {
313        ssize_t received = recvfrom(sock_fd, buffer, sizeof(buffer), 0, (struct sockaddr*) &src_addr, &src_addr_len);
314
315        if (received < 0) {
316            FF_DEBUG("Failed to receive netlink response: %s", strerror(errno));
317            return false;
318        }
319
320        if (received >= (ssize_t) sizeof(buffer)) {
321            FF_DEBUG("Received truncated message: received %zd, bufsize %zu", received, sizeof(buffer));
322            return false;
323        }
324        FF_DEBUG("Received netlink response: %zd bytes", received);
325        if (received == 0) {
326            FF_DEBUG("Received zero-length netlink response, ending processing");
327            break;
328        }
329
330        struct {
331            uint32_t metric;
332            uint32_t ifindex;
333        } entry;
334
335        for (const struct nlmsghdr* nlh = (struct nlmsghdr*) buffer;
336            NLMSG_OK(nlh, received);
337            nlh = NLMSG_NEXT(nlh, received)) {
338            if (nlh->nlmsg_seq != 1 || nlh->nlmsg_pid != pid) {
339                continue;
340            }
341            if (nlh->nlmsg_type == NLMSG_DONE) {
342                FF_DEBUG("Received NLMSG_DONE, processed %d routes", routeCount);
343                goto exit;
344            }
345
346            if (nlh->nlmsg_type == NLMSG_ERROR) {
347                FF_DEBUG("Netlink reports error: %s", strerror(-((struct nlmsgerr*) NLMSG_DATA(nlh))->error));
348                continue;
349            }
350
351            if (nlh->nlmsg_type != RTM_NEWROUTE) {
352                FF_DEBUG("Skipping non-route message: type=%d", nlh->nlmsg_type);
353                continue;
354            }
355
356            routeCount++;
357            struct rtmsg* rtm = (struct rtmsg*) NLMSG_DATA(nlh);
358            if (rtm->rtm_family != AF_INET6) {
359                FF_DEBUG("Skipping non-IPv6 route #%d (family=%d)", routeCount, rtm->rtm_family);
360                continue;
361            }
362
363            if (rtm->rtm_dst_len != 0) {
364                FF_DEBUG("Skipping non-default route #%d (dst_len=%d)", routeCount, rtm->rtm_dst_len);
365                continue;
366            }
367
368            // Skip local/loopback routes
369            if (rtm->rtm_scope == RT_SCOPE_HOST || rtm->rtm_type == RTN_LOCAL) {
370                FF_DEBUG("Skipping local route #%d (scope=%d, type=%d)", routeCount, rtm->rtm_scope, rtm->rtm_type);
371                continue;
372            }
373
374            FF_DEBUG("Processing IPv6 default route candidate #%d", routeCount);
375            entry = (typeof(entry)) {}; // Default to zero metric (no RTA_PRIORITY found)
376
377            // Parse route attributes
378            size_t rtm_len = RTM_PAYLOAD(nlh);
379            for (struct rtattr* rta = RTM_RTA(rtm);
380                RTA_OK(rta, rtm_len);
381                rta = RTA_NEXT(rta, rtm_len)) {
382                switch (rta->rta_type) {
383                    case RTA_DST:
384                        if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) {
385                            [[maybe_unused]] char str[INET6_ADDRSTRLEN];
386                            FF_DEBUG("Unexpected RTA_DST: %s", inet_ntop(AF_INET6, RTA_DATA(rta), str, sizeof(str)));
387                            goto next;
388                        }
389                        break;
390                    case RTA_OIF:
391                        if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) {
392                            entry.ifindex = *(uint32_t*) RTA_DATA(rta);
393                            FF_DEBUG("Found interface index: %u", entry.ifindex);
394                        }
395                        break;
396                    case RTA_GATEWAY:
397                        if (RTA_PAYLOAD(rta) >= sizeof(struct in6_addr)) {
398                            struct in6_addr* gw = (struct in6_addr*) RTA_DATA(rta);
399                            if (IN6_IS_ADDR_UNSPECIFIED(gw)) {
400                                goto next;
401                            }
402                            [[maybe_unused]] char str[INET6_ADDRSTRLEN];
403                            FF_DEBUG("Found gateway: %s", inet_ntop(AF_INET6, gw, str, sizeof(str)));
404                        }
405                        break;
406                    case RTA_PRIORITY:
407                        if (RTA_PAYLOAD(rta) >= sizeof(uint32_t)) {
408                            uint32_t metric = *(uint32_t*) RTA_DATA(rta);
409                            FF_DEBUG("Found metric: %u", metric);
410                            if (metric >= minMetric) {
411                                goto next;
412                            }
413                            entry.metric = metric;
414                        }
415                        break;
416                }
417            }
418
419            if (entry.ifindex == 0 || entry.metric >= minMetric) {
420            next:
421                FF_DEBUG("Skipping route: ifindex=%u, metric=%u", entry.ifindex, entry.metric);
422                continue;
423            }
424            minMetric = entry.metric;
425            result->ifIndex = entry.ifindex;
426            FF_DEBUG("Updated best route: ifindex=%u, metric=%u", entry.ifindex, entry.metric);
427
428            if (minMetric == 0) {
429                FF_DEBUG("Found zero metric route, stopping further processing");
430                break; // Stop processing if we found a zero metric route
431            }
432        }
433    }
434
435exit:
436    if (minMetric < UINT32_MAX) {
437        if_indextoname(result->ifIndex, result->ifName);
438        FF_DEBUG("Found default IPv6 route: interface=%s, index=%u, metric=%u", result->ifName, result->ifIndex, minMetric);
439        return true;
440    }
441    FF_DEBUG("No IPv6 default route found");
442    return false;
443}