diff --git a/base b/base index e23fd2715..a5e198ec5 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit e23fd271508970ef4bb79d2de69a9b9525f65195 +Subproject commit a5e198ec5ac6f7b0cee11b82909394064bb7fcad diff --git a/frontend/device/cervantes/device.lua b/frontend/device/cervantes/device.lua index 8e3a08951..106a3ee2c 100644 --- a/frontend/device/cervantes/device.lua +++ b/frontend/device/cervantes/device.lua @@ -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 diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index 78b6e0945..b6f1a6373 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -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() diff --git a/frontend/device/kobo/device.lua b/frontend/device/kobo/device.lua index 3dd74eb12..eef87de75 100644 --- a/frontend/device/kobo/device.lua +++ b/frontend/device/kobo/device.lua @@ -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() diff --git a/frontend/device/remarkable/device.lua b/frontend/device/remarkable/device.lua index 43d0b3178..e2db4d8d4 100644 --- a/frontend/device/remarkable/device.lua +++ b/frontend/device/remarkable/device.lua @@ -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) diff --git a/frontend/device/sony-prstux/device.lua b/frontend/device/sony-prstux/device.lua index 6f55e7fa3..64c40b094 100644 --- a/frontend/device/sony-prstux/device.lua +++ b/frontend/device/sony-prstux/device.lua @@ -178,7 +178,7 @@ function SonyPRSTUX:initNetworkManager(NetworkMgr) end --]] NetworkMgr.isWifiOn = NetworkMgr.sysfsWifiOn - NetworkMgr.isConnected = NetworkMgr.sysfsCarrierConnected + NetworkMgr.isConnected = NetworkMgr.ifHasAnAddress end diff --git a/frontend/ui/network/manager.lua b/frontend/ui/network/manager.lua index 2dc492522..5432c4569 100644 --- a/frontend/ui/network/manager.lua +++ b/frontend/ui/network/manager.lua @@ -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 ) + -- 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 ) + -- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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()