flak rss random

checking the wifi

As I move around, I roam between wifi networks, but sometimes lose the connection. Then I click a link and watch in vain as it fails to load. So I’d like an easy way to check which, if any, wifi network I’m connected to, such as by putting it in my dwm status bar. I could run ifconfig and parse the output, but that’s excessively wasteful. I need to get the info myself.

Here are my notes on getting network status via a luajit script. There’s some holes in the documentation, so this required studying the source for ifconfig and copying the needed parts.

The first thing we need to know is how to get network info. Where does it come from? The official documentation is netintro.4 but, to be honest, I’m not sure how you are supposed to discover it. I found it via grep in /usr/share/man. This describes some operations we can perform.

In UNIX, everything is a file, and in networking, everything is a socket, so what we need is a generic UDP socket, and then we can poke it with ioctl. I think this is kinda weird, but it’s obviously not going to change.

local s = ffi.C.socket(net.AF_INET, net.SOCK_DGRAM, 0)

We’re interested in learning whether the network is connected, so we’re looking at SIOCGIFMEDIA. This fills in a struct ifmediareq.

local medreq = ffi.new("struct ifmediareq")
while true do
    local link = "[none]"
    ffi.fill(medreq, ffi.sizeof("struct ifmediareq"))
    ffi.copy(medreq.ifm_name, ifname, 4)
    nx.ioctl(s, net.SIOCGIFMEDIA, medreq)
    local stat = tonumber(medreq.ifm_status)
    if stat == 3 then -- network is active

How do we know 3 is our lucky number? netintro tells us to read ifmedia.4 but that doesn’t tell us anything useful. Our first roadblock. ifconfig uses some macros to turn this into a string, but anyway, I know I’m currently connected, so whatever the current status is, that’s the good number.

Little side note here about my definition of SIOCGIFMEDIA in lua. I already had this code from previous projects to define IOCTL codes.

local function _IOC(inout, group, num, len)
    group = string.byte(group)
    return bit.bor(inout, bit.lshift(len, 16), bit.lshift(group, 8), num)
end
local function _IOR(group, num, len)
    return _IOC(0x40000000, group, num, len)
end

SIOCGIFMEDIA is a read write request (we have to provide the interface name in the struct, then we get an answer). It requires one new helper function.

local function _IOWR(group, num, len)
    return _IOC(0x40000000, group, num, len) + 0x80000000
end
local SIOCGIFMEDIA = nx._IOWR('i', 56, ffi.sizeof("struct ifmediareq"))

This does not use 0xC0000000 because the bit library in luajit is 32-bit signed. Attempting to set the high bit results in a negative number, which gets sign extended and everything goes wildly wrong. This took a little while to figure out, but it can be worked around by adding the high bit after the bit library is done.

Back on track, the next thing we want to know is the name of the wifi network. This comes to us via the SIOCG80211NWID ioctl. There is no documentation for this. The closest you might get is internal kernel documentation for ieee80211_ioctl.9 but it’s of no use.

Anyway, inspired by some code in ifconfig, we can figure out how to make the request. Like many of these network interface requests, it uses a struct ifreq which contains a pointer to a request specific struct.

local ifreq = ffi.new("struct ifreq")
local nwid = ffi.new("struct ieee80211_nwid")
        ffi.fill(ifreq, ffi.sizeof("struct ifreq"))
        ffi.copy(ifreq.ifr_name, ifname, 4)
        ffi.fill(nwid, ffi.sizeof("struct ieee80211_nwid"))
        ifreq.ifr_data = nwid
        nx.ioctl(s, net.SIOCG80211NWID, ifreq)
        link = ffi.string(nwid.i_nwid)

Okay, now we’ve got it. After this, I dig around for some battery stats, etc., and print the result.

A few misc. notes. At some point in the distant past, luajit had funny rules about allocating pointers to C types. You had to create a one element array instead. It would decay to a pointer when calling C code, but still needed indexing in Lua.

local info = ffi.new("struct apm_power_info[1]")
nx.ioctl(fd, APM_IOC_GETPOWER, info)
local ac = info[0].ac_state == 1 and "ac" or "bat"

Awkward, no? Well, at some point in the more recent past, this limitation was fixed. I haven’t been paying attention, and only discovered this by accident, but happy to say we can allocate and access structs more directly now.

It’s been a few years since I decided to rewrite everything in go but that didn’t extend to little system scripts for which I still use luajit. I don’t need a 50MB status updater. cgo makes it a little easier to use system headers without redeclarations, but that wasn’t really the hard part. (The syscall package is also not useful here. It defines SIOCGIFMEDIA but not SIOCG80211NWID, and the ifreq struct is only defined on linux.)

Posted 30 Apr 2025 08:00 by tedu Updated: 30 Apr 2025 08:00
Tagged: openbsd programming