NetworkMgr: Refine isConnected check (#10098)

Much easier to deal with thanks to the cleanup work done in #10062 ;).

* `carrier` is set to 1 as soon as the device is *administratively* up (in practice, as soon as we run `ifconfig up`). This is perfectly fine for `isWifiOn`, but absolutely not for `isConnected`, because we are not, actually, connected to *anything*, no attempt at associating has even been made at that point. Besides being semantically wrong, in practice, this will horribly break the connectivity check, because it expects that `isConnected` means we can talk to at least the LAN.
* Delving into the Linux docs reveals that `operstate` looks like a better candidate, as it reflects *operational status*; for Wi-Fi, that means associated and successfully authenticated. That's... closer, but still not it, because we still don't have an IP, so we technically can't talk to anything other than the AP.
* So, I've brought out the big guns (`getifaddrs`), and replicated a bit of code that I already use in the USBNetwork hack on Kindle, to detect whether we actually have an IP assigned. (Other approaches, like `/proc/net/route`, may not be entirely fool-proof, and/or get complicated when IPv6 enters the fray (which it does, on Kobo, Mk. 8+ devices are IPv6-enabled)).

TL;DR: Bunch of C via ffi, and `isConnected` now returns true only when the device is operationally up *and* we have an IP assigned.

Pulls in https://github.com/koreader/koreader-base/pull/1579 & https://github.com/koreader/lj-wpaclient/pull/10
reviewable/pr10109/r1
NiLuJe 1 year ago committed by GitHub
parent 03454a89d8
commit 96850c23a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1 +1 @@
Subproject commit e23fd271508970ef4bb79d2de69a9b9525f65195
Subproject commit a5e198ec5ac6f7b0cee11b82909394064bb7fcad

@ -177,7 +177,7 @@ function Cervantes:initNetworkManager(NetworkMgr)
os.execute("./restore-wifi-async.sh")
end
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
end
-- power functions: suspend, resume, reboot, poweroff

@ -187,7 +187,7 @@ function Kindle:initNetworkManager(NetworkMgr)
end
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
end
function Kindle:supportsScreensaver()

@ -829,10 +829,7 @@ function Kobo:initNetworkManager(NetworkMgr)
self:reconnectOrShowNetworkMenu(complete_callback)
end
local net_if = os.getenv("INTERFACE")
if not net_if then
net_if = "eth0"
end
local net_if = os.getenv("INTERFACE") or "eth0"
function NetworkMgr:getNetworkInterfaceName()
return net_if
end
@ -852,7 +849,16 @@ function Kobo:initNetworkManager(NetworkMgr)
end
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
-- Usually handled in NetworkMgr:init, but we'll need it *now*
NetworkMgr.interface = net_if
-- Kill Wi-Fi if NetworkMgr:isWifiOn() and NOT NetworkMgr:isConnected()
-- (i.e., if the launcher left the Wi-Fi in an inconsistent state: modules loaded, but no route to gateway).
if NetworkMgr:isWifiOn() and not NetworkMgr:isConnected() then
logger.info("Kobo Wi-Fi: Left in an inconsistent state by launcher!")
NetworkMgr:turnOffWifi()
end
end
function Kobo:setTouchEventHandler()

@ -195,7 +195,7 @@ function Remarkable:initNetworkManager(NetworkMgr)
NetworkMgr:setWirelessBackend("wpa_supplicant", {ctrl_interface = "/var/run/wpa_supplicant/wlan0"})
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
end
function Remarkable:setDateTime(year, month, day, hour, min, sec)

@ -178,7 +178,7 @@ function SonyPRSTUX:initNetworkManager(NetworkMgr)
end
--]]
NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn
NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected
NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress
end

@ -7,15 +7,21 @@ local InfoMessage = require("ui/widget/infomessage")
local LuaSettings = require("luasettings")
local MultiConfirmBox = require("ui/widget/multiconfirmbox")
local UIManager = require("ui/uimanager")
local ffi = require("ffi")
local ffiutil = require("ffi/util")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
local C = ffi.C
local T = ffiutil.template
-- We'll need a bunch of stuff for getifaddrs in NetworkMgr:ifHasAnAddress
require("ffi/posix_h")
local NetworkMgr = {
is_wifi_on = false,
is_connected = false,
interface = nil,
}
function NetworkMgr:readNWSettings()
@ -77,12 +83,8 @@ function NetworkMgr:scheduleConnectivityCheck(callback, widget)
end
function NetworkMgr:init()
-- On Kobo, kill Wi-Fi if NetworkMgr:isWifiOn() and NOT NetworkMgr:isConnected()
-- (i.e., if the launcher left the Wi-Fi in an inconsistent state: modules loaded, but no route to gateway).
if Device:isKobo() and self:isWifiOn() and not self:isConnected() then
logger.info("Kobo Wi-Fi: Left in an inconsistent state by launcher!")
self:turnOffWifi()
end
Device:initNetworkManager(self)
self.interface = self:getNetworkInterfaceName()
self:queryNetworkState()
self.wifi_was_on = G_reader_settings:isTrue("wifi_was_on")
@ -99,6 +101,8 @@ function NetworkMgr:init()
UIManager:scheduleIn(2, UIManager.broadcastEvent, UIManager, Event:new("NetworkConnected"))
end
end
return self
end
-- Following methods are Device specific which need to be initialized in
@ -120,23 +124,23 @@ function NetworkMgr:releaseIP() end
function NetworkMgr:restoreWifiAsync() end
-- End of device specific methods
--Helper fuctions for devices that use the sysfs entry to check connectivity.
-- Helper functions for devices that use sysfs entries to check connectivity.
function NetworkMgr:sysfsWifiOn()
-- Network interface directory exists while the Wi-Fi module is loaded.
local net_if = self:getNetworkInterfaceName()
return util.pathExists("/sys/class/net/".. net_if)
-- Network interface directory only exists as long as the Wi-Fi module is loaded
return util.pathExists("/sys/class/net/".. self.interface)
end
function NetworkMgr:sysfsCarrierConnected()
-- Read carrier state from sysfs.
-- NOTE: We can afford to use CLOEXEC, as devices too old for it don't support Wi-Fi anyway ;)
local out
local net_if = self:getNetworkInterfaceName()
local file = io.open("/sys/class/net/" .. net_if .. "/carrier", "re")
local file = io.open("/sys/class/net/" .. self.interface .. "/carrier", "re")
-- File only exists while Wi-Fi module is loaded.
-- File only exists while the Wi-Fi module is loaded, but may fail to read until the interface is brought up.
if file then
-- 0 means not connected, 1 connected
-- 0 means the interface is down, 1 that it's up
-- (technically, it reflects the state of the physical link (e.g., plugged in or not for Ethernet))
-- This does *NOT* represent network association state for Wi-Fi (it'll return 1 as soon as ifup)!
out = file:read("*number")
file:close()
end
@ -144,6 +148,108 @@ function NetworkMgr:sysfsCarrierConnected()
return out == 1
end
function NetworkMgr:sysfsInterfaceOperational()
-- Reads the interface's RFC2863 operational state from sysfs, and wait for it to be up
-- (For Wi-Fi, that means associated & successfully authenticated)
local out
local file = io.open("/sys/class/net/" .. self.interface .. "/operstate", "re")
-- Possible values: "unknown", "notpresent", "down", "lowerlayerdown", "testing", "dormant", "up"
-- (c.f., Linux's <Documentation/ABI/testing/sysfs-class-net>)
-- We're *assuming* all the drivers we care about implement this properly, so we can just rely on checking for "up".
-- On unsupported drivers, this would be stuck on "unknown" (c.f., Linux's <Documentation/networking/operstates.rst>)
-- NOTE: This does *NOT* mean the interface has been assigned an IP!
if file then
out = file:read("*l")
file:close()
end
return out == "up"
end
-- This relies on the BSD API instead of the Linux ioctls (netdevice(7)), because handling IPv6 is slightly less painful this way...
function NetworkMgr:ifHasAnAddress()
-- If the interface isn't operationally up, no need to go any further
if not self:sysfsInterfaceOperational() then
logger.dbg("NetworkMgr: interface is not operational yet")
return false
end
-- It's up, do the getifaddrs dance to see if it was assigned an IP yet...
-- c.f., getifaddrs(3)
local ifaddr = ffi.new("struct ifaddrs *[1]")
if C.getifaddrs(ifaddr) == -1 then
local errno = ffi.errno()
logger.err("NetworkMgr: getifaddrs:", ffi.string(C.strerror(errno)))
return false
end
local ok
local ifa = ifaddr[0]
while ifa ~= nil do
if ifa.ifa_addr ~= nil and C.strcmp(ifa.ifa_name, self.interface) == 0 then
local family = ifa.ifa_addr.sa_family
if family == C.AF_INET or family == C.AF_INET6 then
local host = ffi.new("char[?]", C.NI_MAXHOST)
local s = C.getnameinfo(ifa.ifa_addr,
family == C.AF_INET and ffi.sizeof("struct sockaddr_in") or ffi.sizeof("struct sockaddr_in6"),
host, C.NI_MAXHOST,
nil, 0,
C.NI_NUMERICHOST)
if s ~= 0 then
logger.err("NetworkMgr: getnameinfo:", ffi.string(C.gai_strerror(s)))
ok = false
else
logger.dbg("NetworkMgr: interface", self.interface, "is up @", ffi.string(host))
ok = true
end
-- Regardless of failure, we only check a single if, so we're done
break
end
end
ifa = ifa.ifa_next
end
C.freeifaddrs(ifaddr[0])
return ok
end
--[[
-- This would be the aforementioned Linux ioctl approach
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
// Querying IPv6 would require a different in6_ifreq struct and more hoop-jumping...
struct ifreq ifr;
struct sockaddr_in sai;
strncpy(ifr.ifr_name, *++argv, IFNAMSIZ);
int fd = socket(PF_INET, SOCK_DGRAM, 0);
ioctl(fd, SIOCGIFADDR, &ifr);
close(fd);
/*
// inet_ntoa is deprecated
memcpy(&sai, &ifr.ifr_addr, sizeof(sai));
printf("ifr.ifr_addr: %s\n", inet_ntoa(sai.sin_addr));
*/
char host[NI_MAXHOST];
int s = getnameinfo(&ifr.ifr_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
printf("ifr.ifr_addr: %s\n", host);
return EXIT_SUCCESS;
}
--]]
function NetworkMgr:toggleWifiOn(complete_callback, long_press)
local toggle_im = InfoMessage:new{
text = _("Turning on Wi-Fi…"),
@ -740,8 +846,4 @@ if G_defaults:readSetting("NETWORK_PROXY") then
NetworkMgr:setHTTPProxy(G_defaults:readSetting("NETWORK_PROXY"))
end
Device:initNetworkManager(NetworkMgr)
NetworkMgr:init()
return NetworkMgr
return NetworkMgr:init()

Loading…
Cancel
Save