Browse Source

Import new Autoupdater from joka

  If we change i.e. the mesh protocol or something else which
  breakes connectivity from mesh only nodes to their neighbours,
  they could get disconnected from the rest of the network.
  Therfore we try to update via the mesh network and if this fails,
  we try to find any open network to get our new firmware.

  This is also discussed in https://github.com/freifunk-gluon/gluon/issues/427
Michael Schwarz 8 years ago
parent
commit
8d1781e3a8

+ 31 - 0
ffho/ffho-autoupdater-wifi-fallback/Makefile

@@ -0,0 +1,31 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=ffho-autoupdater-wifi-fallback
+PKG_VERSION:=1
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(GLUONDIR)/include/package.mk
+
+define Package/ffho-autoupdater-wifi-fallback
+  SECTION:=admin
+  CATEGORY:=Administration
+  DEPENDS:=+wireless-tools +autoupdater
+  TITLE:=Implements switching to fallback mode if we are cut off from the mesh
+endef
+
+define Build/Prepare
+	mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/ffho-autoupdater-wifi-fallback/install
+	$(CP) ./files/* $(1)/
+endef
+
+$(eval $(call BuildPackage,ffho-autoupdater-wifi-fallback))

+ 3 - 0
ffho/ffho-autoupdater-wifi-fallback/files/etc/config/autoupdater-wifi-fallback

@@ -0,0 +1,3 @@
+config autoupdater-wifi-fallback settings
+	option enabled 1
+	option min_uptime_secs 3600

+ 1 - 0
ffho/ffho-autoupdater-wifi-fallback/files/lib/gluon/cron/autoupdater-wifi-fallback

@@ -0,0 +1 @@
+*/29 * * * * /usr/sbin/autoupdater-wifi-fallback

+ 52 - 0
ffho/ffho-autoupdater-wifi-fallback/files/lib/gluon/upgrade/510-autoupdater-wifi-fallback

@@ -0,0 +1,52 @@
+#!/usr/bin/lua
+
+local uci = require 'luci.model.uci'.cursor()
+local site = require 'gluon.site_config'
+local util = require 'gluon.util'
+local fs = require('nixio.fs')
+local sysctl = require 'gluon.sysctl'
+
+local radios = {}
+
+uci:foreach('wireless', 'wifi-device',
+  function(s)
+    table.insert(radios, s['.name'])
+  end
+)
+
+for _, radio in ipairs(radios) do
+  uci:delete('wireless', 'fallback')
+  uci:section('wireless', 'wifi-iface', 'fallback',
+  {
+    device = radio,
+    network = 'fallback',
+    mode = 'sta',
+    ssid = site.wifi24.ssid,
+    disabled = 1,
+    macaddr = util.generate_mac(3, 10),
+    ifname = 'fallback',
+    encryption = 'none',
+  }
+)
+end
+uci:delete('network','fallback')
+uci:section('network', 'interface', 'fallback',
+  {
+    ifname = 'fallback',
+  }
+)
+
+uci:save('wireless')
+uci:save('network')
+uci:commit('wireless')
+uci:commit('network')
+
+-- if there is no wpa_supplicant, just fake it
+if not fs.stat("/usr/sbin/wpa_supplicant", 'type') == 'reg' then
+  local f = io.open("/usr/sbin/wpa_supplicant", "w")
+  f:write("iw dev fallback connect -w " .. site.wifi24.ssid)
+  f:close()
+  fs.chmod("/usr/sbin/wpa_supplicant", "775")
+end
+
+sysctl.set('net.ipv6.conf.fallback.accept_ra=2', 0)

+ 76 - 0
ffho/ffho-autoupdater-wifi-fallback/files/usr/lib/lua/autoupdater-wifi-fallback/util.lua

@@ -0,0 +1,76 @@
+#!/usr/bin/lua
+
+local uci = require('luci.model.uci').cursor()
+local site = require 'gluon.site_config'
+local json = require 'luci.json'
+local util = require("luci.util")
+
+
+function is_site_ssid_available()
+  local found = false
+  local interfaces = util.split(util.trim(util.exec("iw dev | grep Interface | cut -d' ' -f2")))
+  for _, ifname in ipairs(interfaces) do
+    for line in io.popen(string.format("iw dev %s scan 2>/dev/null", ifname)):lines() do
+        if line:match("SSID: " .. site.wifi24.ssid) then
+          found = true
+        end
+    end
+  end
+
+  return found
+end
+
+function get_site_macs()
+  local macs = {}
+  local interfaces = util.split(util.trim(util.exec(string.format("iw dev mesh0 scan | grep -B10 %s | grep BSS | cut -f2 -d' ' | cut -f1 -d'('", site.wifi24.ssid))))
+  for _, mac in ipairs(interfaces) do
+    table.insert(macs, mac)
+  end
+
+  return macs
+end
+
+function is_in_fallback_mode()
+  if uci:get('wireless', 'fallback', 'disabled') == '0' then
+    return true
+  end
+  return false
+end
+
+function neighbours(iface)
+  local stations = {}
+  for k, v in pairs(iface.iw.assoclist(iface.ifname)) do
+    table.insert(stations,k:lower())
+  end
+
+  return stations
+end
+
+function interfaces()
+  local interfaces = {}
+  for _, line in ipairs(util.split(util.exec('batctl if'))) do
+    local ifname = line:match('^(.-): active')
+    if ifname ~= nil then
+      pcall(function()
+        local address = util.trim(fs.readfile('/sys/class/net/' .. ifname .. '/address'))
+        local wifitype = iwinfo.type(ifname)
+        if wifitype ~= nil then
+          interfaces[address] = { ifname = ifname, iw = iwinfo[wifitype] }
+        end
+      end)
+    end
+  end
+
+  return interfaces
+end
+
+function get_wifi_neighbours()
+  local wifi = {}
+  for address, iface in pairs(interfaces()) do
+    wifi[address] = { neighbours = neighbours(iface) }
+  end
+  
+  if next(wifi) then
+    return wifi
+  end
+end

+ 160 - 0
ffho/ffho-autoupdater-wifi-fallback/files/usr/sbin/autoupdater-wifi-fallback

@@ -0,0 +1,160 @@
+#!/usr/bin/lua
+local fs = require('nixio.fs')
+local uci = require('luci.model.uci').cursor()
+local site = require 'gluon.site_config'
+local iwinfo = require 'iwinfo'
+local configname = 'autoupdater-wifi-fallback'
+local util = require 'luci.util'
+local settings = uci:get_all(configname, 'settings')
+local ut = require('autoupdater-wifi-fallback.util')
+local debug = true
+
+-- preflight checks
+
+if settings.enabled ~= '1' or uci:get('autoupdater','settings','enabled') ~= '1' then
+  if debug then io.stderr:write('connectivity checks or autoupdater are disabled.\n') end
+  os.exit(0)
+end
+
+if fs.stat("/tmp/run/fastd.mesh_vpn.socket", "type") == "socket" then
+  if debug then io.stderr:write('we have a valid socket for fastd. no fallback required.\n') end
+  os.exit(0)
+end
+
+if tonumber(fs.readfile('/proc/uptime'):match('^([^ ]+) ')) < tonumber(settings.min_uptime_secs) then
+  if debug then io.stderr:write('we just booted. check skipped.\n') end
+  os.exit(0)
+end
+
+local function check_connectivity()
+  local f = io.open("/sys/kernel/debug/batman_adv/bat0/gateways","r")
+  if f ~= nil then
+    f:close()
+    local wifi = get_wifi_neighbours()
+    if wifi ~= nil then
+      for key, interface in ipairs(wifi) do                                          
+        if os.execute(string.format("batctl ping -t5 -c1 %s > /dev/null 2>&1", interface)) == 0 then
+              return true
+        end
+      end
+    end 
+    
+    local list = io.lines("/sys/kernel/debug/batman_adv/bat0/gateways")
+    for line in list do
+      local gateway_mac = line:match("^=?>? +([0-9a-f:]+)")
+      if gateway_mac ~= nil then
+        if os.execute(string.format("batctl ping -t5 -c1 %s > /dev/null 2>&1", gateway_mac)) == 0 then
+          return true
+        end
+      end
+    end
+  end
+  
+  -- ipv6 connectivity check against google dns
+  if os.execute("ping6 -w2 -c1 2001:4860:4860::8888 > /dev/null 2>&1") == 0 then
+    return true
+  end
+  return false
+end
+
+local function run_autoupdater()
+  -- TODO:should be called with -f !
+  os.execute("/usr/sbin/autoupdater")
+end
+
+local function switch_to_fallback_mode()
+  local disabled_radios = {'fallback'}
+  uci:foreach('wireless', 'wifi-iface',
+        function(s)
+          if s.disabled == '1' and s['.name'] ~= 'fallback' then
+            table.insert(disabled_radios, s['.name'])
+          end
+          uci:set('wireless', s['.name'], 'disabled', '1')
+        end
+  )
+  
+  if disabled_radios ~= nil then
+    uci:set_list(configname, 'settings', 'disabled_radios', disabled_radios)
+  end
+  
+  uci:set('wireless', 'fallback', 'disabled', 0)
+  uci:save('wireless')
+  uci:commit('wireless')
+  
+  local maclist = get_site_macs()
+  local mac = table.remove(maclist, math.random(#maclist))
+  os.remove("/usr/sbin/wpa_supplicant")  
+  local f = io.open("/usr/sbin/wpa_supplicant", "w")
+  local freq = 2412 + (site.wifi24.channel -1) * 5
+  f:write("iw dev fallback connect -w " .. site.wifi24.ssid .. " " .. freq .. " " .. mac)
+  f:close()
+  fs.chmod("/usr/sbin/wpa_supplicant", "775")
+  
+  os.execute("/etc/init.d/wireless restart")
+  -- wait for dhcp
+  os.execute("sleep 30")
+  
+  run_autoupdater()
+end
+
+local function revert_to_standard_mode(restart_network)
+  local disabled_radios = uci:get_list(configname, 'settings', 'disabled_radios')
+  uci:foreach('wireless', 'wifi-iface',
+    function(s)
+      local keep_disabled = false
+      if s.disabled == '1' then
+        for _, radio in ipairs(disabled_radios) do
+          if s['.name'] == radio then
+            keep_disabled = true
+          end
+        end
+      end
+      if not keep_disabled then
+        uci:set('wireless', s['.name'], 'disabled', '0')
+      end
+    end
+  )
+  uci:set('wireless', 'fallback', 'disabled', '1')
+  uci:delete(configname,'settings','disabled_radios')
+  uci:save('wireless')
+  uci:save(configname)
+  uci:commit('wireless')
+  uci:commit(configname)
+  if restart_network then
+    os.execute("/etc/init.d/network restart")
+    os.execute("sleep 30")
+  end
+end
+
+if is_in_fallback_mode() then
+    revert_to_standard_mode(false)
+    run_autoupdater()
+    -- if there really is a firmware update, we should not reach this point
+    os.execute("/etc/init.d/network restart")
+    os.exit(0)
+end
+
+if not check_connectivity() then
+  local offset = 2 * 3600
+  if fs.stat("/tmp/run/fastd.mesh_vpn.socket", "type") == "socket" then offset = 4 * 3600 end
+  local unreachable_since = settings.unreachable_since
+  if unreachable_since == nil then 
+    uci:set(configname, 'settings', 'unreachable_since', os.time())
+    unreachable_since = os.time()
+  else
+    uci:set(configname, 'settings', 'last_run', os.time())
+  end
+  uci:save(configname)
+  uci:commit(configname)
+  
+  if tonumber(unreachable_since) + offset < os.time() then
+    if is_site_ssid_available() then
+      switch_to_fallback_mode()
+    end
+  end
+else
+  uci:delete(configname, 'settings','unreachable_since')
+  uci:delete(configname, 'settings','last_run')
+  uci:save(configname)
+  uci:commit(configname)
+end