master ohmu/fastfetch-openbsd / src / detection / wifi / wifi_nbsd.c
ohmu  ·  2026-10-15
  1#include "wifi.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/strutil.h"
  5
  6#include <errno.h>
  7#include <string.h>
  8
  9#define COMPAT_FREEBSD_NET80211 1
 10#include <sys/ioctl.h>
 11#include <sys/socket.h>
 12#include <net/if.h>
 13#include <net/if_media.h>
 14#include <net80211/ieee80211.h>
 15#include <net80211/ieee80211_ioctl.h>
 16
 17// ieee80211 header of NetBSD is full of mess. Add compatibility macros from FreeBSD
 18#undef IEEE80211_IS_CHAN_ANYG
 19#define IEEE80211_IS_CHAN_ANYG(x) (IEEE80211_IS_CHAN_PUREG(x) || IEEE80211_IS_CHAN_G(x))
 20#undef IEEE80211_IS_CHAN_HT
 21#define IEEE80211_IS_CHAN_HT(x) (((x)->ic_flags & IEEE80211_CHAN_HT) != 0)
 22#undef IEEE80211_IS_CHAN_VHT
 23#define IEEE80211_IS_CHAN_VHT(x) (((x)->ic_flags & IEEE80211_CHAN_VHT) != 0)
 24
 25const char* ffDetectWifi(FFlist* result) {
 26    struct if_nameindex* infs = if_nameindex();
 27    if (!infs) {
 28        FF_DEBUG("if_nameindex() failed: %s", strerror(errno));
 29        return "if_nameindex() failed";
 30    }
 31
 32    FF_AUTO_CLOSE_FD int sock = socket(AF_INET, SOCK_DGRAM, 0);
 33    if (sock < 0) {
 34        FF_DEBUG("socket(AF_INET, SOCK_DGRAM) failed: %s", strerror(errno));
 35        if_freenameindex(infs);
 36        return "socket() failed";
 37    }
 38
 39    for (struct if_nameindex* i = infs; !(i->if_index == 0 && i->if_name == nullptr); ++i) {
 40        if (!ffStrStartsWith(i->if_name, "iwm")) {
 41            continue;
 42        }
 43
 44        FFWifiResult* item = FF_LIST_ADD(FFWifiResult, *result);
 45        ffStrbufInitS(&item->inf.description, i->if_name);
 46        ffStrbufInit(&item->inf.status);
 47        ffStrbufInit(&item->conn.status);
 48        ffStrbufInit(&item->conn.ssid);
 49        ffStrbufInit(&item->conn.bssid);
 50        ffStrbufInit(&item->conn.protocol);
 51        ffStrbufInit(&item->conn.security);
 52        item->conn.signalQuality = -DBL_MAX;
 53        item->conn.rxRate = -DBL_MAX;
 54        item->conn.txRate = -DBL_MAX;
 55        item->conn.channel = 0;
 56        item->conn.channelWidth = 0;
 57        item->conn.frequency = 0;
 58
 59        char ssid[IEEE80211_NWID_LEN + 1] = {};
 60        struct ieee80211req ireq = {};
 61        strlcpy(ireq.i_name, i->if_name, sizeof(ireq.i_name));
 62        ireq.i_type = IEEE80211_IOC_SSID;
 63        ireq.i_data = ssid;
 64        ireq.i_len = sizeof(ssid) - 1;
 65
 66        if (ioctl(sock, SIOCG80211, &ireq) < 0 || ireq.i_len == 0) {
 67            struct ifreq ifr;
 68            strlcpy(ifr.ifr_name, i->if_name, sizeof(ifr.ifr_name));
 69            if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
 70                ffStrbufSetStatic(&item->inf.status, "Unknown");
 71            } else {
 72                ffStrbufSetStatic(&item->inf.status, ifr.ifr_flags & IFF_UP ? "Up" : "Down");
 73            }
 74            ffStrbufAppendS(&item->conn.status, "Not associated");
 75            continue;
 76        }
 77
 78        ffStrbufSetStatic(&item->inf.status, "Up");
 79        ffStrbufSetStatic(&item->conn.status, "Associated");
 80        ffStrbufAppendNS(&item->conn.ssid, ireq.i_len, ssid);
 81
 82        uint8_t bssid[IEEE80211_ADDR_LEN] = {};
 83        ireq.i_type = IEEE80211_IOC_BSSID;
 84        ireq.i_data = bssid;
 85        ireq.i_len = sizeof(bssid);
 86
 87        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {
 88            ffStrbufSetF(&item->conn.bssid, "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
 89        }
 90
 91        struct ieee80211_channel curchan = {};
 92        ireq.i_type = IEEE80211_IOC_CHANNEL;
 93        ireq.i_data = &curchan;
 94        ireq.i_len = sizeof(curchan);
 95
 96        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {
 97            // item->conn.channel = curchan.ic_ieee; // No ic_ieee in NetBSD
 98            item->conn.channel = ffWifiFreqToChannel(curchan.ic_freq);
 99            item->conn.frequency = curchan.ic_freq;
100
101            // Determine channel width from channel flags
102            if (curchan.ic_flags & IEEE80211_CHAN_VHT160)
103                item->conn.channelWidth = 160;
104            else if (curchan.ic_flags & IEEE80211_CHAN_VHT80)
105                item->conn.channelWidth = 80;
106            else if (curchan.ic_flags & IEEE80211_CHAN_HT40)
107                item->conn.channelWidth = 40;
108            else
109                item->conn.channelWidth = 20;
110
111#ifdef IEEE80211_IS_CHAN_HE // for future use
112            if (IEEE80211_IS_CHAN_HE(&curchan)) {
113                ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)");
114            } else
115#endif
116                if (IEEE80211_IS_CHAN_VHT(&curchan))
117                ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)");
118            else if (IEEE80211_IS_CHAN_HT(&curchan)) {
119                ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)");
120            } else if (IEEE80211_IS_CHAN_ANYG(&curchan)) {
121                ffStrbufSetStatic(&item->conn.protocol, "802.11g");
122            } else if (IEEE80211_IS_CHAN_B(&curchan)) {
123                ffStrbufSetStatic(&item->conn.protocol, "802.11b");
124            } else if (IEEE80211_IS_CHAN_A(&curchan)) {
125                ffStrbufSetStatic(&item->conn.protocol, "802.11a");
126            } else if (IEEE80211_IS_CHAN_FHSS(&curchan)) {
127                ffStrbufSetStatic(&item->conn.protocol, "802.11 (FHSS)");
128            }
129        }
130
131        union {
132            struct ieee80211req_sta_req req;
133            uint8_t buf[1024];
134        } stareq = {};
135        memcpy(stareq.req.is_u.macaddr, bssid, sizeof(bssid));
136        ireq.i_type = IEEE80211_IOC_STA_INFO;
137        ireq.i_data = &stareq;
138        ireq.i_len = sizeof(stareq);
139
140        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {
141            struct ieee80211req_sta_info* sta = stareq.req.info;
142            if (sta->isi_len != 0) {
143                int8_t rssi = (int8_t) sta->isi_rssi; // Strangely, `sta->isi_rssi` is unsigned
144                item->conn.signalQuality = (rssi >= -50 ? 100 : rssi <= -100 ? 0
145                                                                             : (rssi + 100) * 2);
146
147                if (sta->isi_txrate) {
148                    item->conn.txRate = (double) sta->isi_txrate / 2.0;
149                }
150            }
151        }
152
153        ireq.i_type = IEEE80211_IOC_AUTHMODE;
154        ireq.i_data = nullptr;
155        ireq.i_len = 0;
156        if (ioctl(sock, SIOCG80211, &ireq) >= 0) {
157            switch (ireq.i_val) {
158                case IEEE80211_AUTH_NONE:
159                    ffStrbufSetStatic(&item->conn.security, "Insecure");
160                    break;
161                case IEEE80211_AUTH_OPEN:
162                    ffStrbufSetStatic(&item->conn.security, "Open");
163                    break;
164                case IEEE80211_AUTH_SHARED:
165                    ffStrbufSetStatic(&item->conn.security, "Shared");
166                    break;
167                case IEEE80211_AUTH_8021X:
168                    ffStrbufSetStatic(&item->conn.security, "8021X");
169                    break;
170                case IEEE80211_AUTH_AUTO:
171                    ffStrbufSetStatic(&item->conn.security, "Auto");
172                    break;
173                case IEEE80211_AUTH_WPA:
174                    ffStrbufSetStatic(&item->conn.security, "WPA");
175                    break;
176                default:
177                    ffStrbufSetF(&item->conn.security, "Unknown (%d)", ireq.i_val);
178                    break;
179            }
180        }
181    }
182
183    if_freenameindex(infs);
184    return nullptr;
185}