ohmu
·
2026-10-15
1#include "common/printing.h"
2#include "common/jsonconfig.h"
3#include "common/strutil.h"
4#include "detection/localip/localip.h"
5#include "modules/localip/localip.h"
6
7#pragma GCC diagnostic ignored "-Wsign-conversion"
8
9static int sortIps(const FFLocalIpResult* left, const FFLocalIpResult* right) {
10 return ffStrbufComp(&left->name, &right->name);
11}
12
13static void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint32_t index, FFstrbuf* key) {
14 if (options->moduleArgs.key.length == 0) {
15 if (!ip->name.length) {
16 ffStrbufSetF(&ip->name, "unknown %u", (unsigned) index);
17 }
18
19 ffStrbufSetF(key, "%s (%s)", FF_MODULE_GET_DISPLAY_NAME(LocalIP), ip->name.chars);
20 } else {
21 ffStrbufClear(key);
22 FF_PARSE_FORMAT_STRING_CHECKED(key, &options->moduleArgs.key, ((FFformatarg[]) {
23 FF_ARG(index, "index"),
24 FF_ARG(ip->name, "ifname"),
25 FF_ARG(ip->mac, "mac"),
26 FF_ARG(options->moduleArgs.keyIcon, "icon"),
27 FF_ARG(FF_MODULE_GET_DISPLAY_NAME(LocalIP), "module-name"),
28 }));
29 }
30}
31
32static void appendSpeed(FFLocalIpResult* ip, FFstrbuf* strbuf) {
33 if (ip->speed >= 1000000) {
34 ffStrbufAppendDouble(strbuf, ip->speed / 1e6, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);
35 ffStrbufAppendS(strbuf, " Tbps");
36 } else if (ip->speed >= 1000) {
37 ffStrbufAppendDouble(strbuf, ip->speed / 1e3, instance.config.display.fractionNdigits, instance.config.display.fractionTrailingZeros == FF_FRACTION_TRAILING_ZEROS_TYPE_ALWAYS);
38 ffStrbufAppendS(strbuf, " Gbps");
39 } else {
40 ffStrbufAppendF(strbuf, "%u Mbps", (unsigned) ip->speed);
41 }
42}
43
44static void printIp(FFLocalIpResult* ip, bool markDefaultRoute, FFstrbuf* buffer) {
45 if (ip->ipv4.length) {
46 ffStrbufAppend(buffer, &ip->ipv4);
47 }
48 if (ip->ipv6.length) {
49 if (buffer->length) {
50 ffStrbufAppendC(buffer, ' ');
51 }
52 ffStrbufAppend(buffer, &ip->ipv6);
53 }
54 if (ip->mac.length) {
55 if (buffer->length) {
56 ffStrbufAppendF(buffer, " (%s)", ip->mac.chars);
57 } else {
58 ffStrbufAppend(buffer, &ip->mac);
59 }
60 }
61 if (ip->mtu > 0 || ip->speed > 0) {
62 bool flag = buffer->length > 0;
63 if (flag) {
64 ffStrbufAppendS(buffer, " [");
65 }
66 if (ip->speed > 0) {
67 if (ip->mtu > 0) {
68 ffStrbufAppendS(buffer, "Speed ");
69 }
70 appendSpeed(ip, buffer);
71 if (ip->mtu > 0) {
72 ffStrbufAppendS(buffer, " / MTU ");
73 }
74 }
75 if (ip->mtu > 0) {
76 ffStrbufAppendF(buffer, "%u", (unsigned) ip->mtu);
77 }
78 if (flag) {
79 ffStrbufAppendC(buffer, ']');
80 }
81 }
82 if (ip->flags.length) {
83 bool flag = buffer->length > 0;
84 if (flag) {
85 ffStrbufAppendS(buffer, " <");
86 }
87 ffStrbufAppend(buffer, &ip->flags);
88 if (flag) {
89 ffStrbufAppendC(buffer, '>');
90 }
91 }
92 if (markDefaultRoute && ip->defaultRoute) {
93 ffStrbufAppendS(buffer, " *");
94 }
95}
96
97bool ffPrintLocalIp(FFLocalIpOptions* options) {
98 FF_LIST_AUTO_DESTROY results = ffListCreate();
99
100 const char* error = ffDetectLocalIps(options, &results);
101
102 if (error) {
103 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
104 return false;
105 }
106
107 if (results.length == 0) {
108 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Failed to detect any IPs");
109 return false;
110 }
111
112 ffListSort(&results, sizeof(FFLocalIpResult), (const void*) sortIps);
113
114 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
115 // `{is-default-route}` reports the address families the interface carries the default route for
116 // ("ipv4", "ipv6" or "ipv4,ipv6") instead of exposing the internal bit field as a number.
117 FF_STRBUF_AUTO_DESTROY defaultRoute = ffStrbufCreate();
118
119 if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT) {
120 ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
121
122 FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
123 if (buffer.length) {
124 ffStrbufAppendS(&buffer, " - ");
125 }
126 printIp(ip, false, &buffer);
127 }
128 ffStrbufPutTo(&buffer, stdout);
129 ffStrbufClear(&buffer);
130 } else {
131 FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
132 uint32_t index = 0;
133
134 FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
135 formatKey(options, ip, results.length == 1 ? 0 : index + 1, &key);
136 if (options->moduleArgs.outputFormat.length == 0) {
137 ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY);
138 printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT), &buffer);
139 ffStrbufPutTo(&buffer, stdout);
140 } else {
141 if (ip->speed > 0) {
142 appendSpeed(ip, &buffer);
143 }
144 ffStrbufClear(&defaultRoute);
145 if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT) {
146 ffStrbufAppendS(&defaultRoute, "ipv4");
147 }
148 if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT) {
149 if (defaultRoute.length > 0) {
150 ffStrbufAppendC(&defaultRoute, ',');
151 }
152 ffStrbufAppendS(&defaultRoute, "ipv6");
153 }
154 FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
155 FF_ARG(ip->ipv4, "ipv4"),
156 FF_ARG(ip->ipv6, "ipv6"),
157 FF_ARG(ip->mac, "mac"),
158 FF_ARG(ip->name, "ifname"),
159 FF_ARG(defaultRoute, "is-default-route"),
160 FF_ARG(ip->mtu, "mtu"),
161 FF_ARG(buffer, "speed"),
162 FF_ARG(ip->flags, "flags"),
163 }));
164 }
165 ++index;
166 ffStrbufClear(&buffer);
167 ffStrbufClear(&defaultRoute);
168 }
169 }
170
171 FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
172 ffStrbufDestroy(&ip->name);
173 ffStrbufDestroy(&ip->ipv4);
174 ffStrbufDestroy(&ip->ipv6);
175 ffStrbufDestroy(&ip->mac);
176 ffStrbufDestroy(&ip->flags);
177 }
178
179 return true;
180}
181
182void ffParseLocalIpJsonObject(FFLocalIpOptions* options, yyjson_val* module) {
183 yyjson_val *key, *val;
184 size_t idx, max;
185 yyjson_obj_foreach (module, idx, max, key, val) {
186 if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
187 continue;
188 }
189
190 if (unsafe_yyjson_equals_str(key, "showIpv4")) {
191 if (yyjson_get_bool(val)) {
192 options->showType |= FF_LOCALIP_TYPE_IPV4_BIT;
193 } else {
194 options->showType &= ~FF_LOCALIP_TYPE_IPV4_BIT;
195 }
196 continue;
197 }
198
199 if (unsafe_yyjson_equals_str(key, "showIpv6")) {
200 if (yyjson_is_bool(val)) {
201 options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;
202 if (unsafe_yyjson_get_bool(val)) {
203 options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;
204 } else {
205 options->showType &= ~FF_LOCALIP_TYPE_IPV6_BIT;
206 }
207 } else if (yyjson_is_str(val)) {
208 int value;
209 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
210 { "auto", FF_LOCALIP_IPV6_TYPE_AUTO },
211 { "gua", FF_LOCALIP_IPV6_TYPE_GUA_BIT },
212 { "ula", FF_LOCALIP_IPV6_TYPE_ULA_BIT },
213 { "lla", FF_LOCALIP_IPV6_TYPE_LLA_BIT },
214 { "unknown", FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT },
215 {},
216 });
217 if (error) {
218 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
219 } else {
220 options->showType |= FF_LOCALIP_TYPE_IPV6_BIT;
221 options->ipv6Type = (FFLocalIpIpv6Type) value;
222 }
223 }
224 continue;
225 }
226
227 if (unsafe_yyjson_equals_str(key, "showMac")) {
228 if (yyjson_get_bool(val)) {
229 options->showType |= FF_LOCALIP_TYPE_MAC_BIT;
230 } else {
231 options->showType &= ~FF_LOCALIP_TYPE_MAC_BIT;
232 }
233 continue;
234 }
235
236 if (unsafe_yyjson_equals_str(key, "showLoop")) {
237 if (yyjson_get_bool(val)) {
238 options->showType |= FF_LOCALIP_TYPE_LOOP_BIT;
239 } else {
240 options->showType &= ~FF_LOCALIP_TYPE_LOOP_BIT;
241 }
242 continue;
243 }
244
245 if (unsafe_yyjson_equals_str(key, "showPrefixLen")) {
246 if (yyjson_get_bool(val)) {
247 options->showType |= FF_LOCALIP_TYPE_PREFIX_LEN_BIT;
248 } else {
249 options->showType &= ~FF_LOCALIP_TYPE_PREFIX_LEN_BIT;
250 }
251 continue;
252 }
253
254 if (unsafe_yyjson_equals_str(key, "showMtu")) {
255 if (yyjson_get_bool(val)) {
256 options->showType |= FF_LOCALIP_TYPE_MTU_BIT;
257 } else {
258 options->showType &= ~FF_LOCALIP_TYPE_MTU_BIT;
259 }
260 continue;
261 }
262
263 if (unsafe_yyjson_equals_str(key, "showSpeed")) {
264 if (yyjson_get_bool(val)) {
265 options->showType |= FF_LOCALIP_TYPE_SPEED_BIT;
266 } else {
267 options->showType &= ~FF_LOCALIP_TYPE_SPEED_BIT;
268 }
269 continue;
270 }
271
272 if (unsafe_yyjson_equals_str(key, "showFlags")) {
273 if (yyjson_get_bool(val)) {
274 options->showType |= FF_LOCALIP_TYPE_FLAGS_BIT;
275 } else {
276 options->showType &= ~FF_LOCALIP_TYPE_FLAGS_BIT;
277 }
278 continue;
279 }
280
281 if (unsafe_yyjson_equals_str(key, "compact")) {
282 if (yyjson_get_bool(val)) {
283 options->showType |= FF_LOCALIP_TYPE_COMPACT_BIT;
284 } else {
285 options->showType &= ~FF_LOCALIP_TYPE_COMPACT_BIT;
286 }
287 continue;
288 }
289
290 if (unsafe_yyjson_equals_str(key, "defaultRouteOnly")) {
291 if (yyjson_get_bool(val)) {
292 options->showType |= FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;
293 } else {
294 options->showType &= ~FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT;
295 }
296 continue;
297 }
298
299 if (unsafe_yyjson_equals_str(key, "showAllIps")) {
300 if (yyjson_get_bool(val)) {
301 options->showType |= FF_LOCALIP_TYPE_ALL_IPS_BIT;
302 } else {
303 options->showType &= ~FF_LOCALIP_TYPE_ALL_IPS_BIT;
304 }
305 continue;
306 }
307
308 if (unsafe_yyjson_equals_str(key, "namePrefix")) {
309 ffStrbufSetJsonVal(&options->namePrefix, val);
310 continue;
311 }
312
313 ffPrintError(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
314 }
315}
316
317[[gnu::cold]]
318void ffGenerateLocalIpJsonConfig(FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
319 ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
320
321 yyjson_mut_obj_add_bool(doc, module, "showIpv4", !!(options->showType & FF_LOCALIP_TYPE_IPV4_BIT));
322
323 if (options->ipv6Type == FF_LOCALIP_IPV6_TYPE_AUTO) {
324 yyjson_mut_obj_add_bool(doc, module, "showIpv6", !!(options->showType & FF_LOCALIP_TYPE_IPV6_BIT));
325 } else {
326 const char* str = nullptr;
327 switch (options->ipv6Type) {
328 case FF_LOCALIP_IPV6_TYPE_GUA_BIT:
329 str = "gua";
330 break;
331 case FF_LOCALIP_IPV6_TYPE_ULA_BIT:
332 str = "ula";
333 break;
334 case FF_LOCALIP_IPV6_TYPE_LLA_BIT:
335 str = "lla";
336 break;
337 case FF_LOCALIP_IPV6_TYPE_UNKNOWN_BIT:
338 str = "unknown";
339 break;
340 default:
341 str = "auto";
342 break;
343 }
344 yyjson_mut_obj_add_str(doc, module, "showIpv6", str);
345 }
346
347 yyjson_mut_obj_add_bool(doc, module, "showMac", !!(options->showType & FF_LOCALIP_TYPE_MAC_BIT));
348
349 yyjson_mut_obj_add_bool(doc, module, "showLoop", !!(options->showType & FF_LOCALIP_TYPE_LOOP_BIT));
350
351 yyjson_mut_obj_add_bool(doc, module, "showPrefixLen", !!(options->showType & FF_LOCALIP_TYPE_PREFIX_LEN_BIT));
352
353 yyjson_mut_obj_add_bool(doc, module, "showMtu", !!(options->showType & FF_LOCALIP_TYPE_MTU_BIT));
354
355 yyjson_mut_obj_add_bool(doc, module, "showSpeed", !!(options->showType & FF_LOCALIP_TYPE_SPEED_BIT));
356
357 yyjson_mut_obj_add_bool(doc, module, "showFlags", !!(options->showType & FF_LOCALIP_TYPE_FLAGS_BIT));
358
359 yyjson_mut_obj_add_bool(doc, module, "compact", !!(options->showType & FF_LOCALIP_TYPE_COMPACT_BIT));
360
361 yyjson_mut_obj_add_bool(doc, module, "defaultRouteOnly", !!(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT));
362
363 yyjson_mut_obj_add_bool(doc, module, "showAllIps", !!(options->showType & FF_LOCALIP_TYPE_ALL_IPS_BIT));
364
365 yyjson_mut_obj_add_strbuf(doc, module, "namePrefix", &options->namePrefix);
366}
367
368bool ffGenerateLocalIpJsonResult([[maybe_unused]] FFLocalIpOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
369 FF_LIST_AUTO_DESTROY results = ffListCreate();
370
371 const char* error = ffDetectLocalIps(options, &results);
372
373 if (error) {
374 yyjson_mut_obj_add_str(doc, module, "error", error);
375 return false;
376 }
377
378 yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
379 FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
380 yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
381 yyjson_mut_obj_add_strbuf(doc, obj, "name", &ip->name);
382 if (options->showType & (FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_IPV6_BIT)) {
383 yyjson_mut_val* defaultRoute = yyjson_mut_obj_add_obj(doc, obj, "defaultRoute");
384 if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) {
385 yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv4", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT));
386 }
387 if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) {
388 yyjson_mut_obj_add_bool(doc, defaultRoute, "ipv6", !!(ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT));
389 }
390 }
391 if (options->showType & FF_LOCALIP_TYPE_IPV4_BIT) {
392 yyjson_mut_obj_add_strbuf(doc, obj, "ipv4", &ip->ipv4);
393 }
394 if (options->showType & FF_LOCALIP_TYPE_IPV6_BIT) {
395 yyjson_mut_obj_add_strbuf(doc, obj, "ipv6", &ip->ipv6);
396 }
397 if (options->showType & FF_LOCALIP_TYPE_MAC_BIT) {
398 yyjson_mut_obj_add_strbuf(doc, obj, "mac", &ip->mac);
399 }
400 if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) {
401 yyjson_mut_obj_add_int(doc, obj, "mtu", ip->mtu);
402 }
403 if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) {
404 yyjson_mut_obj_add_int(doc, obj, "speed", ip->speed);
405 }
406 if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) {
407 yyjson_mut_obj_add_strbuf(doc, obj, "flags", &ip->flags);
408 }
409 }
410
411 FF_LIST_FOR_EACH (FFLocalIpResult, ip, results) {
412 ffStrbufDestroy(&ip->name);
413 ffStrbufDestroy(&ip->ipv4);
414 ffStrbufDestroy(&ip->ipv6);
415 ffStrbufDestroy(&ip->mac);
416 ffStrbufDestroy(&ip->flags);
417 }
418
419 return true;
420}
421
422void ffInitLocalIpOptions(FFLocalIpOptions* options) {
423 ffOptionInitModuleArg(&options->moduleArgs, "");
424
425 options->showType = FF_LOCALIP_TYPE_IPV4_BIT | FF_LOCALIP_TYPE_PREFIX_LEN_BIT
426#if !__ANDROID__ /*Permission denied*/
427 | FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT
428#endif
429 ;
430 options->ipv6Type = FF_LOCALIP_IPV6_TYPE_AUTO;
431 ffStrbufInit(&options->namePrefix);
432}
433
434void ffDestroyLocalIpOptions(FFLocalIpOptions* options) {
435 ffOptionDestroyModuleArg(&options->moduleArgs);
436 ffStrbufDestroy(&options->namePrefix);
437}
438
439FFModuleBaseInfo ffLocalIPModuleInfo = {
440 .name = "LocalIp",
441 .description = "List local IP addresses (IPv4 or IPv6), MAC addresses, etc",
442 .displayName = {
443 .en = "Local IP",
444 .ar = "عنوان IP المحلي",
445 .cs = "Místní IP",
446 .de = "Lokale IP",
447 .es = "IP local",
448 .fi = "Paikallinen IP",
449 .fr = "IP locale",
450 .gl = "IP local",
451 .he = "IP מקומי",
452 .id = "IP Lokal",
453 .it = "IP locale",
454 .ja = "ローカル IP",
455 .ko = "로컬 IP",
456 .pl = "Lokalny adres IP",
457 .pt = "IP local",
458 .ru = "Локальный IP",
459 .tr = "Yerel IP",
460 .uk = "Локальна IP",
461 .vi = "IP cục bộ",
462 .zh_CN = "本地 IP",
463 .zh_TW = "區域 IP",
464 },
465 .initOptions = (void*) ffInitLocalIpOptions,
466 .destroyOptions = (void*) ffDestroyLocalIpOptions,
467 .parseJsonObject = (void*) ffParseLocalIpJsonObject,
468 .printModule = (void*) ffPrintLocalIp,
469 .generateJsonResult = (void*) ffGenerateLocalIpJsonResult,
470 .generateJsonConfig = (void*) ffGenerateLocalIpJsonConfig,
471 .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
472 { "IPv4 address", "ipv4" },
473 { "IPv6 address", "ipv6" },
474 { "MAC address *", "mac" },
475 { "Interface name *", "ifname" },
476 { "Is default route", "is-default-route" },
477 { "MTU size in bytes", "mtu" },
478 { "Link speed (formatted)", "speed" },
479 { "Interface flags", "flags" },
480 })),
481 .defaultOrder = 50,
482};