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}