Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

obedient meshing #6

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions ffh-obedient-meshing/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
include $(TOPDIR)/rules.mk

PKG_NAME:=ffh-obedient-meshing
PKG_VERSION:=1
PKG_RELEASE:=1

PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)

include $(TOPDIR)/../package/gluon.mk

define Package/ffh-obedient-meshing
SECTION:=ffh
CATEGORY:=FFH
TITLE:=Automatically switch to the most popular domain in wifi reach, as soon as no mesh neighbours are available.
DEPENDS:=+gluon-core +gluon-state-check +iw +libiwinfo-lua +micrond @GLUON_MULTIDOMAIN
MAINTAINER:=Freifunk Hannover <dev@hannover.freifunk.net>
endef

define Package/ffh-obedient-meshing/description
This package checks periodically, whether the router has mesh-neighbours.
If it doesn't,
it scans for mesh-networks,
filters out the ones that are not part of its site and domainconf,
joins the most popular across all radios, regardless of their band.
It does so by switching its own domain.
endef

$(eval $(call BuildPackageGluon,$(PKG_NAME)))
43 changes: 43 additions & 0 deletions ffh-obedient-meshing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ffh-obedient-meshing

This package allows the router to scan for available mesh-networks in order to join the most prominent one in radio distance.

## Dependencies

This package relies on `gluon-state-online` to tell whether it has mesh partners.
It relies on `micron.d` to do this every minute.
In case it doesn't, it relies on `iw` and `iwinfo` to scan for wireless networks nearby and join one.
Lastly this only makes sense to be deployed in a multi-domain-setup.

##what it does

### when there are mesh neighbors

The device stays in that domain, where it and its neighbors currently are.

### when there aren't

The file `/var/guon/state/has_neighbours` is missing.
It scans for mesh networks, and checks for a network with a `mesh_id` matching one of the known domains meshes.
It does this on all available radios hence all available bands.

In case it finds none, the device will try again a minute later, until it eventually does.

In case it finds more than one, it determines the locally most popular (whichever it encounters most often) and changes its domain in order to match it.

It reconfigures without reboot using `gluon-switch-domain` and should have mesh neighbors a minute later.

### on boot

Domains are configured and stored across (unrelated) reboots.
As the device comes up it is part of the last domain configured, like a regular device.
And only if it does not have mesh neighbors, it does its job.

## drawbacks

Mesh networks do only provide a `mesh_id` which is shared across multiple domains related to one primary.
This package does not know about which pretty domains are near.
It therefore joins the primary one; which leads to devices in e.g. `dom14` instead of `Nordstadt` in Hanover.

Devices do not mark that they're meshing obediently; this could lead to unexpected behavior for mesh-clouds without Internet-access. As they technically have neighbors and therefore do not need to switch domains. This is for now intended behavior and matter to discuss, but not part of this first implementation.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* * * * * sleep 10 && /usr/sbin/ffh-obedient-meshing
152 changes: 152 additions & 0 deletions ffh-obedient-meshing/luasrc/usr/sbin/ffh-obedient-meshing
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/lua
local uci = require("simple-uci").cursor()
local iwinfo = require "iwinfo"
local util = require "gluon.util"
local json = require "jsonc"
local sys_stat = require "posix.sys.stat"
local isreg = require "posix.sys.stat".S_ISREG
local isdir = require "posix.sys.stat".S_ISDIR
local unistd = require "posix.unistd"


local function has_state()
local state_path="/var/gluon/state"
local stat=sys_stat.lstat(state_path)
if nil~=stat then
return 0~=isdir(stat.st_mode)
end
return false
end

local function has_neighbours()
local neighbours_path="/var/gluon/state/has_neighbours"
local stat=sys_stat.stat(neighbours_path)
if nil~=stat then
return 0~=isreg(stat.st_mode)
end
return false
end

local function get_band(channel)
if channel >= 1 and channel <=14 then
return "wifi24"
elseif channel >= 36 and channel <= 165 then
return "wifi5"
end
end

local function get_available_wifi_networks()
local radios = {}
local ssid_counts = {}
ssid_counts["wifi24"]={}
ssid_counts["wifi5"]={}

uci:foreach('wireless', 'wifi-device',
function(s)
radios[s['.name']] = {}
end
)

for radio, _ in pairs(radios) do
local wifitype = iwinfo.type(radio)
local iw = iwinfo[wifitype]
if not iw then
return nil
end
local tmplist = iw.scanlist(radio)
for _, net in ipairs(tmplist) do
if net.mode and net.channel and net.ssid and "Mesh Point"==net.mode then
local band = get_band(net.channel)
if not ssid_counts[band][net.ssid] then
ssid_counts[band][net.ssid]=0
end

ssid_counts[band][net.ssid]=ssid_counts[band][net.ssid]+1
end
end
end
return ssid_counts
end


local function get_domain_list()
local list = {}
for _, domain_path in ipairs(util.glob('/lib/gluon/domains/*.json')) do
local is_primary = 0~=isreg(sys_stat.lstat(domain_path).st_mode)
if is_primary then
local domain_code = domain_path:match('([^/]+)%.json$')
local domain = assert(json.load(domain_path))

table.insert(list, {
domain_code = domain_code,
domain_name = domain.domain_names[domain_code],
wifi24 = domain.wifi24.mesh.id,
wifi5 = domain.wifi5.mesh.id
})
end
end

table.sort(list, function(a, b) return a.domain_name < b.domain_name end)
return list
end


local function to_domain_counts(band_ssid_counts)
local domain_code_counts={}
local domain_list = get_domain_list()
local function get_domaincode(band, ssid)
for _, domain in pairs(domain_list) do
if domain[band]==ssid then
return domain["domain_code"]
end
end
end

for band, countlist in pairs(band_ssid_counts) do
--print(band)
for ssid, count in pairs(countlist) do
--print(ssid .. ": " ..count)
local code = get_domaincode(band, ssid)
if nil~=code then
--print(code .. ": +" ..count)
if nil==domain_code_counts[code] then
domain_code_counts[code]=count
else
domain_code_counts[code]=domain_code_counts[code]+count
end
end
end
end

return domain_code_counts
end

if not has_state() then
print("Device state is not available yet, aborting.")
os.exit()
end

if has_neighbours() then
print("Device still has neighbours, no need to rescan.")
os.exit()
end

local selected_domain = uci:get('gluon', 'core', 'domain')
print("selected domain: " .. selected_domain)
local networks = get_available_wifi_networks()

local dom_counts = to_domain_counts(networks)

local high = 0
local high_dom
for dom, count in pairs(dom_counts) do
if count >= high then
high=count
high_dom=dom
end
end
print(high_dom .. ": " .. high)


local cmd = {[0]="gluon-switch-domain", "--no-reboot", high_dom}
unistd.execp(cmd[0], cmd)