From ec26665ecbb97c817a5d2cba6d218b55e3967e85 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 30 Jun 2025 16:38:13 -0300 Subject: [PATCH 01/18] feat: makefile for odhcpd leases package --- packages/shared-state-odhcpd_leases/Makefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/shared-state-odhcpd_leases/Makefile diff --git a/packages/shared-state-odhcpd_leases/Makefile b/packages/shared-state-odhcpd_leases/Makefile new file mode 100644 index 000000000..6dc98f790 --- /dev/null +++ b/packages/shared-state-odhcpd_leases/Makefile @@ -0,0 +1,17 @@ +include ../../libremesh.mk + +define Package/$(PKG_NAME) + SECTION:=lime + CATEGORY:=LibreMesh + TITLE:=odhcpd leases module for shared-state + DEPENDS:=+lua +luci-lib-jsonc +shared-state-async +odhcpd + PKGARCH:=all +endef + +define Package/$(PKG_NAME)/description +Synchronize external DHCP leases between LibreMesh nodes by +watching odhcpd’s lease file, publishing updates over shared-state-async +and injecting remote leases locally via “ubus call dhcp add_lease”. +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) From e055f966f86f73ef6bba2f4a4dbdf0073dd6f65b Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 30 Jun 2025 16:50:35 -0300 Subject: [PATCH 02/18] feat: uci defaults --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share new file mode 100644 index 000000000..16ce0dc5d --- /dev/null +++ b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share @@ -0,0 +1,16 @@ +#!/bin/sh +CRDT='odhcpd-leases' +LEASEFILE='/tmp/ethers.mesh' + +shared-state-async register $CRDT community 120 1200 2>/dev/null + +uci -q set dhcp.odhcpd.leasetrigger='/usr/bin/shared-state-publish_odhcpd_leases' +uci -q set dhcp.odhcpd.maindhcp='1' +uci commit dhcp + +[ -e /etc/ethers ] || ln -s "$LEASEFILE" /etc/ethers +grep -q 'sync odhcpd-leases' /etc/crontabs/root || \ + echo '*/5 * * * * shared-state-async sync odhcpd-leases >/dev/null 2>&1' >> /etc/crontabs/root + +/etc/init.d/cron restart +/etc/init.d/odhcpd reload From 8d64b58d4d89221d26fccd34cd028230e6b83675 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 30 Jun 2025 16:59:25 -0300 Subject: [PATCH 03/18] feat: added the hook that generates the /tmp/ethers.mesh for the data received via shared state --- .../odhcpd-leases/generate_odhcpd_leases | 1 + .../bin/shared-state-generate_odhcpd_leases | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases create mode 100644 packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases new file mode 100644 index 000000000..49512cfec --- /dev/null +++ b/packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases @@ -0,0 +1 @@ +../../../../usr/bin/shared-state-generate_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases new file mode 100644 index 000000000..31e3023e6 --- /dev/null +++ b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases @@ -0,0 +1,25 @@ +#!/usr/bin/lua + +local JSON = require('luci.jsonc') + +local final_path = '/tmp/ethers.mesh' +local temp_path = final_path .. '.new' + +local leases = JSON.parse(io.stdin:read('*a')) or {} + +local myself_handle = io.open('/proc/sys/kernel/hostname') +local myself = myself_handle:read('*l') +myself_handle:close() + +local f = io.open(temp_path, 'w') + +for ip, data in pairs(leases) do + + if data and data.mData and data.mData.mac and data.mAuthor ~= myself then + f:write(string.format('%s %s\n', data.mData.mac, ip)) -- Format: MAC IP + end +end +f:close() +os.execute('mv "' .. temp_path .. '" "' .. final_path .. '"') + +os.execute('/etc/init.d/odhcpd reload') From 7e0ec94bf02e4b885dcec28ca960e2aa1171e573 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 30 Jun 2025 17:06:15 -0300 Subject: [PATCH 04/18] feat: created publisher that acts as the leasertrigger for odhcpd --- .../shared-state-publish_odhcpd_leases | 1 + .../bin/shared-state-publish_odhcpd_leases | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases create mode 100644 packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases new file mode 100644 index 000000000..074ef00ea --- /dev/null +++ b/packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases @@ -0,0 +1 @@ +../../../usr/bin/shared-state-publish_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases new file mode 100644 index 000000000..e4cb5ae07 --- /dev/null +++ b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases @@ -0,0 +1,47 @@ +#!/usr/bin/lua + +local JSON = require("luci.jsonc") +local CRDT = "odhcpd-leases" + + +local handle = io.popen("ubus call dhcp ipv4leases '{}' 2>/dev/null") +local ubus_output = handle:read("*a") +handle:close() + + +local ubus_data = JSON.parse(ubus_output or "{}") + +local output_table = {} + + +if ubus_data and ubus_data.device then + + for device_name, device_data in pairs(ubus_data.device) do + if device_data and device_data.leases then + + for _, lease in ipairs(device_data.leases) do + + if lease.address and lease.mac then + + output_table[lease.address] = { + hostname = lease.hostname or "", + mac = lease.mac + } + end + end + end + end +end + + +local final_json_string = JSON.stringify(output_table) + + +local pipe = io.popen("shared-state-async insert " .. CRDT, "w") +if pipe then + pipe:write(final_json_string) + pipe:close() +end + + +os.execute("/usr/sbin/odhcpd-update >/dev/null 2>&1") From 5abe425f271007cfc3cee8b70d6e46c8413167c7 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 30 Jun 2025 17:11:57 -0300 Subject: [PATCH 05/18] chore: minimal correction in package's description --- packages/shared-state-odhcpd_leases/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-state-odhcpd_leases/Makefile b/packages/shared-state-odhcpd_leases/Makefile index 6dc98f790..3c3da5389 100644 --- a/packages/shared-state-odhcpd_leases/Makefile +++ b/packages/shared-state-odhcpd_leases/Makefile @@ -11,7 +11,7 @@ endef define Package/$(PKG_NAME)/description Synchronize external DHCP leases between LibreMesh nodes by watching odhcpd’s lease file, publishing updates over shared-state-async -and injecting remote leases locally via “ubus call dhcp add_lease”. +and injecting remote leases locally. endef $(eval $(call BuildPackage,$(PKG_NAME))) From 6ced3992338f4a8b5e3dcce930dc2e1f6d7b2a74 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 7 Jul 2025 10:14:06 -0300 Subject: [PATCH 06/18] fix: structure changed for new shared state async --- .../bin/shared-state-generate_odhcpd_leases | 25 ---------- .../bin/shared-state-publish_odhcpd_leases | 47 ------------------- .../shared-state-generate_odhcpd_leases} | 0 .../shared-state-publish_odhcpd_leases | 0 4 files changed, 72 deletions(-) delete mode 100644 packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases delete mode 100644 packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases rename packages/shared-state-odhcpd_leases/files/{etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases => usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases} (100%) rename packages/shared-state-odhcpd_leases/files/{etc => usr/share}/shared-state/publishers/shared-state-publish_odhcpd_leases (100%) diff --git a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases deleted file mode 100644 index 31e3023e6..000000000 --- a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-generate_odhcpd_leases +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/lua - -local JSON = require('luci.jsonc') - -local final_path = '/tmp/ethers.mesh' -local temp_path = final_path .. '.new' - -local leases = JSON.parse(io.stdin:read('*a')) or {} - -local myself_handle = io.open('/proc/sys/kernel/hostname') -local myself = myself_handle:read('*l') -myself_handle:close() - -local f = io.open(temp_path, 'w') - -for ip, data in pairs(leases) do - - if data and data.mData and data.mData.mac and data.mAuthor ~= myself then - f:write(string.format('%s %s\n', data.mData.mac, ip)) -- Format: MAC IP - end -end -f:close() -os.execute('mv "' .. temp_path .. '" "' .. final_path .. '"') - -os.execute('/etc/init.d/odhcpd reload') diff --git a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases deleted file mode 100644 index e4cb5ae07..000000000 --- a/packages/shared-state-odhcpd_leases/files/usr/bin/shared-state-publish_odhcpd_leases +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/lua - -local JSON = require("luci.jsonc") -local CRDT = "odhcpd-leases" - - -local handle = io.popen("ubus call dhcp ipv4leases '{}' 2>/dev/null") -local ubus_output = handle:read("*a") -handle:close() - - -local ubus_data = JSON.parse(ubus_output or "{}") - -local output_table = {} - - -if ubus_data and ubus_data.device then - - for device_name, device_data in pairs(ubus_data.device) do - if device_data and device_data.leases then - - for _, lease in ipairs(device_data.leases) do - - if lease.address and lease.mac then - - output_table[lease.address] = { - hostname = lease.hostname or "", - mac = lease.mac - } - end - end - end - end -end - - -local final_json_string = JSON.stringify(output_table) - - -local pipe = io.popen("shared-state-async insert " .. CRDT, "w") -if pipe then - pipe:write(final_json_string) - pipe:close() -end - - -os.execute("/usr/sbin/odhcpd-update >/dev/null 2>&1") diff --git a/packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases similarity index 100% rename from packages/shared-state-odhcpd_leases/files/etc/shared-state/hooks/odhcpd-leases/generate_odhcpd_leases rename to packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases similarity index 100% rename from packages/shared-state-odhcpd_leases/files/etc/shared-state/publishers/shared-state-publish_odhcpd_leases rename to packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases From 2aa01ac84bf866c8e936a6b339a844276efad836 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 7 Jul 2025 10:16:21 -0300 Subject: [PATCH 07/18] fix: updated names inside hook --- .../shared-state-generate_odhcpd_leases | 26 +++++++++- .../shared-state-publish_odhcpd_leases | 48 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases index 49512cfec..3e5110736 100644 --- a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases +++ b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases @@ -1 +1,25 @@ -../../../../usr/bin/shared-state-generate_odhcpd_leases +#!/usr/bin/lua + +local JSON = require('luci.jsonc') + +local OUTPUT_FILE = '/tmp/ethers.mesh' +local TMP_FILE = OUTPUT_FILE .. '.new' + +local leases = JSON.parse(io.stdin:read('*a')) or {} + +local hostname_file = io.open('/proc/sys/kernel/hostname') +local node_hostname = hostname_file:read('*l') +hostname_file:close() + +local out_handle = io.open(TMP_FILE, 'w') + +for ip, data in pairs(leases) do + + if data and data.mData and data.mData.mac and data.mAuthor ~= node_hostname then + out_handle:write(string.format('%s %s\n', data.mData.mac, ip)) -- Format: MAC IP + end +end +out_handle:close() +os.execute('mv "' .. TMP_FILE .. '" "' .. OUTPUT_FILE .. '"') + +os.execute('/etc/init.d/odhcpd reload') diff --git a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases index 074ef00ea..e4cb5ae07 100644 --- a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases +++ b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases @@ -1 +1,47 @@ -../../../usr/bin/shared-state-publish_odhcpd_leases +#!/usr/bin/lua + +local JSON = require("luci.jsonc") +local CRDT = "odhcpd-leases" + + +local handle = io.popen("ubus call dhcp ipv4leases '{}' 2>/dev/null") +local ubus_output = handle:read("*a") +handle:close() + + +local ubus_data = JSON.parse(ubus_output or "{}") + +local output_table = {} + + +if ubus_data and ubus_data.device then + + for device_name, device_data in pairs(ubus_data.device) do + if device_data and device_data.leases then + + for _, lease in ipairs(device_data.leases) do + + if lease.address and lease.mac then + + output_table[lease.address] = { + hostname = lease.hostname or "", + mac = lease.mac + } + end + end + end + end +end + + +local final_json_string = JSON.stringify(output_table) + + +local pipe = io.popen("shared-state-async insert " .. CRDT, "w") +if pipe then + pipe:write(final_json_string) + pipe:close() +end + + +os.execute("/usr/sbin/odhcpd-update >/dev/null 2>&1") From c2b8d7c08ebd13a28797446fb4d1d83c997e3227 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Mon, 7 Jul 2025 10:29:54 -0300 Subject: [PATCH 08/18] chore: added uci-defaults registration --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share index 16ce0dc5d..525b0f33d 100644 --- a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share +++ b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share @@ -1,10 +1,17 @@ #!/bin/sh CRDT='odhcpd-leases' LEASEFILE='/tmp/ethers.mesh' +TRIGGERFILE='usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases' +mSc='odhcpd_leases' -shared-state-async register $CRDT community 120 1200 2>/dev/null +uci -q set shared-state.$mSc=dataType +uci -q set shared-state.$mSc.name="$CRDT" +uci -q set shared-state.$mSc.scope='community' +uci -q set shared-state.$mSc.update_interval='120' +uci -q set shared-state.$mSc.ttl='1200' +uci commit shared-state -uci -q set dhcp.odhcpd.leasetrigger='/usr/bin/shared-state-publish_odhcpd_leases' +uci -q set dhcp.odhcpd.leasetrigger="$TRIGGERFILE" uci -q set dhcp.odhcpd.maindhcp='1' uci commit dhcp From 91608bbed67cb0323b6313386375a15aaee8a0eb Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Tue, 8 Jul 2025 10:47:27 -0300 Subject: [PATCH 09/18] chore: granted execution permissions --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 0 .../hooks/odhcpd-leases/shared-state-generate_odhcpd_leases | 0 .../shared-state/publishers/shared-state-publish_odhcpd_leases | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share mode change 100644 => 100755 packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases mode change 100644 => 100755 packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share old mode 100644 new mode 100755 diff --git a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/hooks/odhcpd-leases/shared-state-generate_odhcpd_leases old mode 100644 new mode 100755 diff --git a/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases b/packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases old mode 100644 new mode 100755 From b02280173b79c64df9cd929696c6a7d103c71bcd Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Tue, 8 Jul 2025 21:04:37 -0300 Subject: [PATCH 10/18] chore: deleted redundant cron block --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share index 525b0f33d..2f43f948a 100755 --- a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share +++ b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share @@ -16,8 +16,5 @@ uci -q set dhcp.odhcpd.maindhcp='1' uci commit dhcp [ -e /etc/ethers ] || ln -s "$LEASEFILE" /etc/ethers -grep -q 'sync odhcpd-leases' /etc/crontabs/root || \ - echo '*/5 * * * * shared-state-async sync odhcpd-leases >/dev/null 2>&1' >> /etc/crontabs/root -/etc/init.d/cron restart /etc/init.d/odhcpd reload From 7abf03c7cc28bbd4431e702693a5a77727b93bb2 Mon Sep 17 00:00:00 2001 From: Mateo Rodriguez Date: Wed, 9 Jul 2025 16:08:57 -0300 Subject: [PATCH 11/18] test: add odhcpd-leases publisher unit tests --- luacov.report.out | 9603 +++++++++++++++++ .../tests/test_publish_odhcpd_leases.lua | 101 + 2 files changed, 9704 insertions(+) create mode 100644 luacov.report.out create mode 100644 packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua diff --git a/luacov.report.out b/luacov.report.out new file mode 100644 index 000000000..945144693 --- /dev/null +++ b/luacov.report.out @@ -0,0 +1,9603 @@ +============================================================================== +./packages/pirania/tests/pirania_test_utils.lua +============================================================================== + + + 33 local utils = {} + + 33 function utils.fake_for_tests() + 319 local hooks = require('voucher.hooks') + 319 local config = require('voucher.config') + + 319 config.db_path = '/tmp/pirania_vouchers' + 319 os.execute("mkdir -p " .. config.db_path) + 319 config.prune_expired_for_days = '30' + + 627 hooks.run = function(action) end + end + + 33 return utils + + +============================================================================== +./tests/utils.lua +============================================================================== + 484 local limeutils = require 'lime.utils' + 484 local config = require 'lime.config' + 484 local libuci = require 'uci' + 484 local stub = require 'luassert.stub' + + 484 local utils = {} + + 484 utils.assert = assert + + 484 UCI_CONFIG_FILES = { + 484 "6relayd", "babeld", "batman-adv", "check-date", "dhcp", "dropbear", "fstab", "firewall", + 484 "libremap", "lime", "lime-app", "location", + 484 "luci", "network", "pirania", "rpcd", "shared-state", "system", "ucitrack", + 484 "uhttpd", "wireless", "deferrable-reboot", config.UCI_AUTOGEN_NAME, config.UCI_NODE_NAME, + 484 config.UCI_COMMUNITY_NAME, config.UCI_DEFAULTS_NAME + 484 } + + 484 function utils.disable_asserts() + 6 _G['assert'] = function(expresion, message) return expresion end + end + + 484 function utils.enable_asserts() + 6 _G['assert'] = utils.assert + end + + 484 function utils.lua_path_from_pkgname(pkgname) + 34 return 'packages/' .. pkgname .. '/files/usr/lib/lua/?.lua;' + end + + 484 function utils.enable_package(pkgname) + 17 path = utils.lua_path_from_pkgname(pkgname) + 17 if string.find(package.path, path) == nil then + 17 package.path = path .. package.path + end + end + + 484 function utils.disable_package(pkgname, modulename) + -- remove pkg from LUA search path + 17 path = utils.lua_path_from_pkgname(pkgname) + 17 package.path = string.gsub(package.path, limeutils.literalize(path), '') + -- remove module from preload table + 17 package.preload[modulename] = nil + 17 package.loaded[modulename] = nil + 17 _G[modulename] = nil + end + + -- Creates a custom empty uci environment to be used in unittesting. + -- Should be called in a before_each block and must be followed by a call to + -- teardown_test_uci in an after_each block. + 484 function utils.setup_test_uci() + 2311 local uci = libuci:cursor() + 2311 config.set_uci_cursor(uci) + 2311 local tmpdir = io.popen("mktemp -d"):read('*l') + 1981 uci:set_confdir(tmpdir) + -- If the uci files does not exists then doing uci add fails + -- so here we create empty config files + 53487 for _, cfgname in ipairs(UCI_CONFIG_FILES) do + 51506 local f = io.open(tmpdir .. '/' .. cfgname, "w"):close() + end + 1981 return uci + end + + 484 function utils.teardown_test_uci(uci) + 2311 local confdir = uci:get_confdir() + 1981 if(string.find(confdir, '^/tmp') ~= nil) then + 1981 local out = io.popen("rm -rf " .. confdir .. " " .. uci:get_savedir()) + 1981 out:read('*all') -- this allows waiting for popen completion + 1981 out:close() + 1981 io.popen("rm -rf " .. confdir .. " " .. uci:get_savedir()) + end + 1981 uci:close() + end + + -- Create a temporal empty directory and return its path with a trailin '/' + -- eg: '/tmp/tmp.occigb/' + -- utils.teardown_test_dir() must be called for cleanup + 484 function utils.setup_test_dir() + 545 utils._tmpdir = io.popen("mktemp -d"):read('*l') .. "/" + 545 return utils._tmpdir + end + + 484 function utils.teardown_test_dir() + 776 if(utils._tmpdir ~= nil and string.find(utils._tmpdir, '^/tmp') ~= nil) then + 534 local out = io.popen("rm -rf " .. utils._tmpdir) + 534 out:read('*all') -- this allows waiting for popen completion + 534 out:close() + 534 utils._tmpdir = nil + end + end + + 484 function utils.get_board(name) + 11 local board_path = 'tests/devices/' .. name .. '/board.json' + 11 return limeutils.getBoardAsTable(board_path) + end + + 484 function utils.write_uci_file(uci, filename, content) + 534 local confdir = uci:get_confdir() + 534 local f = io.open(confdir .. '/' .. filename, "w") + 534 f:write(content) + 534 f:close() + end + + 484 function utils.read_uci_file(uci, filename) + 6 local confdir = uci:get_confdir() + 6 local f = io.open(confdir .. '/' .. filename, "r") + 6 local content = nil + 6 if f ~= nil then + 6 content = f:read('*all') + 6 f:close() + end + 6 return content + end + + 484 function utils.load_lua_file_as_function(filename) + 176 local f = io.open(filename) + + 176 local content = "" + + -- removes the shebang if it is the first line + 176 local first_line = f:read("*l") + 176 if not first_line:find("^#!") then +*****0 content = first_line .. content + end + + 176 content = content .. f:read("*a") + 176 f:close() + + -- mimic lua executable arguments handling injecting arg variable from varargs + 176 content = 'local arg = {...}\n' .. content + 176 return loadstring(content, filename) + end + + -- Use this function to test libexec/rpcd "json API" calls + 484 function utils.rpcd_call(f, script_args, call_args) + local response = nil + + 1152 stub(limeutils, "printJson", function (x) response = x end) + 1167 stub(limeutils, "rpcd_readline", function () return call_args end) + + 614 f(unpack(script_args)) + + -- revert the stub + 574 limeutils.printJson:revert() + 574 limeutils.rpcd_readline:revert() + 574 return response + end + + 484 return utils + +============================================================================== +packages/check-internet/files/usr/libexec/rpcd/check-internet +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2021 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2021 Santiago Piccinini + ]]-- + + 33 local ubus = require "ubus" + 33 local json = require "luci.jsonc" + 33 local utils = require "lime.utils" + + 33 local conn = ubus.connect() + 33 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function is_connected() + 22 local exit_status = os.execute('check-internet') + 22 local connected = false + 22 if exit_status == 0 then + 11 connected = true + end + 22 return utils.printJson({status = 'ok', connected = connected}) + end + + 33 local methods = { + 33 is_connected = { no_params = 0 } + } + + 33 if arg[1] == 'list' then + 11 utils.printJson(methods) + end + + 33 if arg[1] == 'call' then + 22 local msg = utils.rpcd_readline() + 22 msg = json.parse(msg) + 22 if arg[2] == 'is_connected' then is_connected(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/deferrable-reboot/files/usr/lib/lua/deferrable_reboot.lua +============================================================================== + 11 local utils = require "lime.utils" + 11 local config = require "lime.config" + + 11 local defreboot = {} + + 11 defreboot.DEFAULT_REBOOT_UPTIME = 60*60*27 + 11 defreboot.SLEEP_BEFORE_REBOOT_S = 30 + + 11 defreboot.POSTPONE_FILE_PATH = '/tmp/deferrable-reboot.defer' + + 11 function defreboot.config(min_uptime) + 121 if min_uptime == nil then + 44 local uci = config.get_uci_cursor() + 44 local lime_min_uptime = config.get("system", "deferrable_reboot_uptime_s", false) + 44 local general_min_uptime = uci:get("deferrable-reboot", "options", "deferrable_reboot_uptime_s") + 44 min_uptime = tonumber(lime_min_uptime or general_min_uptime or defreboot.DEFAULT_REBOOT_UPTIME) + end + 121 assert(type(min_uptime) == "number", "min_uptime must be a number") + 121 defreboot.min_uptime = min_uptime + end + + 11 function defreboot.should_reboot() + 77 local uptime_s = utils.uptime_s() + 77 local postpone_until_s = defreboot.read_postpone_file() + 77 local min_uptime = defreboot.min_uptime + + 77 if (postpone_until_s ~= nil) and (postpone_until_s > min_uptime) then + 33 min_uptime = postpone_until_s + end + 77 return uptime_s > min_uptime + end + + 11 function defreboot.postpone_util_s(uptime) + 33 assert(type(uptime) == "number", "uptime must be a number") + 33 local f = io.open(defreboot.POSTPONE_FILE_PATH, 'w') + 33 f:write(tostring(uptime)) + 33 f:close() + end + + + --! use this function to postpone the reboot, also the following command can be used + --! replacing SECONDS: # awk '{print $1 + SECONDS}' /proc/uptime > /tmp/deferrable-reboot.defer + 11 function defreboot.read_postpone_file() + 77 local f = io.open(defreboot.POSTPONE_FILE_PATH) + 77 if f ~= nil then + 33 return tonumber(f:read("*l")) + end + 44 return nil + end + + 11 function defreboot.reboot() + --! give time to sysupgrade to kill us +*****0 nixio.nanosleep(defreboot.SLEEP_BEFORE_REBOOT_S) +*****0 os.execute("reboot") + end + + 11 return defreboot + +============================================================================== +packages/eupgrade/files/usr/lib/lua/eupgrade.lua +============================================================================== + 11 local utils = require "lime.utils" + 11 local json = require "luci.jsonc" + 11 local libuci = require "uci" + 11 local fs = require("nixio.fs") + + 11 local eup = {} + + 11 eup.STATUS_DEFAULT = 'not-initiated' + 11 eup.STATUS_DOWNLOADING = 'downloading' + 11 eup.STATUS_DOWNLOADED = 'downloaded' + 11 eup.STATUS_DOWNLOAD_FAILED = 'download-failed' + + + 11 local uci = libuci.cursor() + + 11 function eup.set_workdir(workdir) + 99 if not utils.file_exists(workdir) then + 2 os.execute('mkdir -p ' .. workdir) + end + 99 if fs.stat(workdir, "type") ~= "dir" then +*****0 error("Can't configure workdir " .. workdir) + end + 99 eup.WORKDIR = workdir + 99 eup.DOWNLOAD_INFO_CACHE_FILE = eup.WORKDIR .. '/download_status' + 99 eup.FIRMWARE_LATEST_JSON = eup.WORKDIR .. "/firmware_latest.json" + 99 eup.FIRMWARE_LATEST_JSON_SIGNATURE = eup.FIRMWARE_LATEST_JSON .. '.sig' + end + + 11 eup.set_workdir("/tmp/eupgrades") + + 11 function eup.is_enabled() +*****0 return uci:get('eupgrade', 'main', 'enabled') == '1' + end + + 11 function eup.get_upgrade_api_url() + 66 return uci:get('eupgrade', 'main', 'api_url') or '' + end + + 11 function eup._check_signature(file_path, signature_path) +*****0 local cmd = string.format("usign -q -V -P /etc/opkg/keys -x %s -m %s", +*****0 signature_path, file_path) +*****0 local exit_status = os.execute(cmd) +*****0 return exit_status == 0 + end + + 11 function eup._get_board_name() +*****0 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") + end + + 11 function eup._get_current_fw_version() +*****0 return utils.release_info()["DISTRIB_DESCRIPTION"] + end + + 11 function eup._file_sha256(path) +*****0 return utils.unsafe_shell(string.format("sha256sum %s", path)):match("^([^%s]+)") + end + + + --! check if a new firmware is available for download, returning the information of the version + --! when cached_only is true it will not hit the network (only checking the local cache) + 11 function eup.is_new_version_available(cached_only) + --! if 'latest' files are present is because there is a new version + 44 if utils.file_exists(eup.FIRMWARE_LATEST_JSON) and utils.file_exists(eup.FIRMWARE_LATEST_JSON_SIGNATURE) then +*****0 if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then +*****0 return json.parse(utils.read_file(eup.FIRMWARE_LATEST_JSON)) + end + end + 44 if cached_only then +*****0 return false + end + local message + 44 local board_name = eup._get_board_name() + 44 local current_firmware_version = eup._get_current_fw_version() + 44 local url = string.format("%slatest/%s.json", eup.get_upgrade_api_url(), utils.slugify(board_name)) + 44 local latest_json = utils.http_client_get(url, 10) + 44 if not latest_json then + 11 message = "Can't download latest info from: " .. url + else + 33 local latest_data = json.parse(latest_json) + 33 local version = latest_data['version'] + + 33 if version and current_firmware_version ~= version then + 22 utils.write_file(eup.FIRMWARE_LATEST_JSON, latest_json) + 22 local sig_url = url .. ".sig" + 22 if not utils.http_client_get(sig_url, 10, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then +*****0 message = "Can't download signature " .. sig_url +*****0 utils.log(message) + end + + 22 if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then + 11 utils.log("Good signature of firmware_latest.json") + 11 return latest_data + else + 11 message = "Bad signature of firmware_latest.json" + 11 utils.log(message) + end + end + end + --! remove the 'latest' files. + 33 utils.unsafe_shell(string.format('rm -f %s %s', eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE)) + 33 return false, message + end + + 11 function eup.get_latest_info() +*****0 if utils.file_exists(eup.FIRMWARE_LATEST_JSON) then +*****0 return json.parse(utils.read_file(eup.FIRMWARE_LATEST_JSON)) + end + end + + 11 function eup.get_downloaded_info() +*****0 local latest_data = eup.get_latest_info() +*****0 if latest_data then +*****0 for _, image in pairs(latest_data['images']) do +*****0 local fw_type = image['type'] +*****0 local firmware_path = eup.WORKDIR .. "/" .. image['name'] +*****0 if utils.file_exists(firmware_path) then +*****0 return firmware_path, fw_type + end + end + end + end + + 11 function eup.set_download_status(status) + 66 return utils.write_obj_store_var(eup.DOWNLOAD_INFO_CACHE_FILE, 'status', status) + end + + 11 function eup.get_download_status() + 88 local data = utils.read_obj_store(eup.DOWNLOAD_INFO_CACHE_FILE) + 88 if data.status == nil then + 33 return eup.STATUS_DEFAULT + else + 55 return data.status + end + end + + 11 function eup.download_firmware(latest_data) + 33 if eup.get_download_status() == eup.STATUS_DOWNLOADING then + 11 return nil, "Already downloading" + end + + local image, message + + -- Select the image type, discarding unknown types. Prefer image installer over sysupgrade + 22 for _, im in pairs(latest_data['images']) do + 22 if im['type'] == 'installer' then + 22 image = im + 22 break +*****0 elseif im['type'] == 'sysupgrade' then +*****0 image = im + end + end + + 22 if image then + 22 eup.set_download_status(eup.STATUS_DOWNLOADING) + 33 for _, url in pairs(image['download-urls']) do + 22 if not string.match(url, "://") then + 22 url = eup.get_upgrade_api_url() .. url + end + 22 utils.log("Downloading the firmware from " .. url) + + 22 local firmware_path = eup.WORKDIR .. "/" .. image['name'] + 22 local download_status = utils.http_client_get(url, 10, firmware_path) + 22 if download_status then + 22 if image['sha256'] ~= eup._file_sha256(firmware_path) then + 11 message = "Error: the sha256 does not match" + 11 utils.log(message) + 11 utils.unsafe_shell('rm -f ' .. firmware_path) + else + 11 utils.log("Firmware downloaded ok") + 11 eup.set_download_status(eup.STATUS_DOWNLOADED) + 11 return image + end + end + end + end + + 11 eup.set_download_status(eup.STATUS_DOWNLOAD_FAILED) + 11 return nil, message + end + + 11 return eup + +============================================================================== +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard.lua +============================================================================== + #!/usr/bin/lua + + -- FIRSTBOOTWIZARD + -- get_all_networks: Perform scan and fetch configurations + -- apply_file_config: Set lime-community and apply configurations + -- apply_user_configs: Set a new mesh network + -- check_scan_file: Return /tmp/scanning status + -- is_configured: returns true if FBW has already configured the node + -- mark_as_configured: save the status of FBW as configured (is_configured will return true) + -- read_configs: Return scan results + + 14 local json = require 'luci.jsonc' + 14 local ft = require('firstbootwizard.functools') + 14 local utils = require('firstbootwizard.utils') + 14 local iwinfo = require("iwinfo") + 14 local wireless = require("lime.wireless") + 14 local fs = require("nixio.fs") + 14 local config = require("lime.config") + 14 local lutils = require("lime.utils") + 14 local nixio = require "nixio" + 14 local uci = require "uci" + + 14 local fbw = {} + + 14 fbw.WORKDIR = '/tmp/fbw/' + 14 fbw.COMMUNITY_HOST_CONFIG_PREFIX = 'lime-community__host__' + 14 fbw.COMMUNITY_ASSETS_TMPL = 'lime-community_assets__host__%s.tar.gz' + 14 fbw.SCAN_RESULTS_FILE = 'lime-scan-results.json' + + 14 fbw.FETCH_CONFIG_STATUS = { + 14 downloaded_config = { + 14 retval = true, code = "downloaded_config" + 14 }, + 14 downloading_config = { + 14 retval = true, code = "downloading_config" + 14 }, + 14 error_download_lime_community = { + 14 retval = false, code = "error_download_lime_community" + 14 }, + 14 error_not_configured = { + 14 retval = false, code = "error_not_configured" + 14 }, + 14 error_download_lime_assets = { + 14 retval = false, code = "error_download_lime_assets" + 14 }, + 14 } + + + 14 utils.execute('mkdir -p ' .. fbw.WORKDIR) + + 14 function fbw.log(text) + 132 nixio.syslog('info', '[FBW] ' .. text) + end + + 14 function fbw.lime_community_assets_name(hostname) + 33 return fbw.WORKDIR .. string.format(fbw.COMMUNITY_ASSETS_TMPL, hostname) + end + + 14 function fbw.get_lime_communty_fname(hostname, bssid) + 44 return fbw.WORKDIR .. fbw.COMMUNITY_HOST_CONFIG_PREFIX .. hostname .. "__" .. bssid + end + + -- Write lock file at begin + 14 function fbw.start_scan_file() + 44 local file = io.open("/tmp/scanning", "w") + 44 file:write("true") + 44 file:close() + end + + -- Remove old results + 14 function fbw.clean_tmp() +*****0 utils.execute('rm -f ' .. fbw.WORKDIR .. '*') + end + + -- Save working copy of wireless + 14 function fbw.backup_wifi_config() +*****0 utils.execute("cp /etc/config/wireless /tmp/wireless-temp") + end + + -- Get networks in 5ghz radios + 14 function fbw.get_networks() + -- Get all radios + 11 local radios = ft.map(utils.extract_prop(".name"), wireless.scandevices()) + -- Get only 5ghz radios + 11 local radios_5ghz = ft.filter(wireless.is5Ghz, radios) + -- Convert radios to phys (get a list of phys from radio devices) + 11 local phys = ft.map(utils.extract_phys_from_radios, radios_5ghz) + -- Scan networks in phys and format result + 22 local networks = ft.map( + function(phy) +*****0 local nets = iwinfo.nl80211.scanlist(phy) +*****0 return ft.map(utils.add_prop("phy_idx", utils.phy_to_idx(phy)), nets) + 11 end, phys) + -- Merge results + 11 networks = ft.reduce(ft.flatTable, networks, {}) + -- Filter only remote mesh and ad-hoc networks + 11 networks = ft.filter(utils.filter_mesh, networks) + -- Sort by signal + 11 networks = ft.sortBy('signal', true)(networks) + -- Remove dupicated results in multiradios devices + 11 networks = utils.only_best(networks) + 11 return networks + end + + -- Get macs from 5ghz radios + 14 function fbw.get_own_macs() +*****0 local radios = ft.map(utils.extract_prop(".name"), wireless.scandevices()) +*****0 local radios_5ghz = ft.filter(wireless.is5Ghz, radios) +*****0 local phys = ft.map(utils.extract_phys_from_radios, radios_5ghz) +*****0 return ft.map(function(phy) return table.concat(wireless.get_phy_mac(phy),":") end, phys) + end + + -- Calc link local address and download lime-community + 14 function fbw.get_config(results, mesh_network) +*****0 fbw.log('Calc link local address and download lime-community - '.. json.stringify(mesh_network)) +*****0 local mode = mesh_network.mode == "Mesh Point" and 'mesh' or 'adhoc' +*****0 local dev_id = 'wlan'..mesh_network['phy_idx']..'-'..mode +*****0 local stations = {} +*****0 local linksLocalIpv6 = {} + -- Setup wireless interface +*****0 fbw.setup_wireless(mesh_network) + -- Check if connected if not sleep some more until connected or ignore if 10s passed +*****0 utils.is_connected(dev_id) + -- Get associated stations +*****0 stations = utils.get_stations_macs(dev_id) + -- Remove own wifi networks +*****0 local own_macs = fbw.get_own_macs() +*****0 stations = ft.filter(utils.not_own_network(own_macs), stations) + -- Calc ipv6 +*****0 local linksLocalIpv6 = ft.map(utils.eui64, stations) +*****0 local hosts = ft.map(utils.append_network(dev_id), linksLocalIpv6) + -- Add aditional info +*****0 local data = ft.map(function(host) +*****0 return { host = host, signal = mesh_network.signal, ssid = mesh_network.ssid, bssid = mesh_network.bssid } +*****0 end, hosts) +*****0 data = utils.filter_alredy_scanned(data, results) + -- Try to fetch remote config file +*****0 local configs = ft.map(fbw.fetch_config, data) + -- Return only valid configs +*****0 for _, config in pairs(configs) do +*****0 results[config.host] = config + end +*****0 return results + end + + -- Setup wireless + 14 function fbw.setup_wireless(mesh_network) +*****0 local phy_idx = mesh_network["phy_idx"] +*****0 local mode = mesh_network.mode == "Mesh Point" and 'mesh' or 'adhoc' +*****0 local device_name = "lm_wlan"..phy_idx.."_"..mode.."_radio"..phy_idx +*****0 local uci_cursor = config.get_uci_cursor() + -- Get acutal settings +*****0 local current_channel = uci_cursor:get("wireless", 'radio'..phy_idx, "channel") +*****0 local current_mode = uci_cursor:get("wireless", device_name, "mode") + -- Avoid unnecessary configuration changes +*****0 if ( tonumber(current_channel) == tonumber(mesh_network.channel) and current_mode == mode ) then +*****0 return + end + -- Remove current wireless setup +*****0 uci_cursor:foreach("wireless", "wifi-iface", function(entry) +*****0 if entry['.name'] == device_name then +*****0 uci_cursor:delete("wireless", entry['.name']) + end + end) + -- Set new wireless configuration +*****0 uci_cursor:set("wireless", 'radio'..phy_idx, "channel", mesh_network.channel) +*****0 uci_cursor:set("wireless", device_name, "wifi-iface") +*****0 uci_cursor:set("wireless", device_name, "device", 'radio'..phy_idx) +*****0 uci_cursor:set("wireless", device_name, "ifname", 'wlan'..phy_idx..'-'..mode) +*****0 uci_cursor:set("wireless", device_name, "network", 'lm_net_wlan'..phy_idx..'_'..mode) +*****0 uci_cursor:set("wireless", device_name, "distance", '10000') +*****0 uci_cursor:set("wireless", device_name, "mode", mode) +*****0 uci_cursor:set("wireless", device_name, "mesh_id", 'LiMe') +*****0 uci_cursor:set("wireless", device_name, "ssid", 'LiMe') +*****0 uci_cursor:set("wireless", device_name, "mesh_fwding", '0') +*****0 uci_cursor:set("wireless", device_name, "bssid", 'ca:fe:00:c0:ff:ee') +*****0 uci_cursor:set("wireless", device_name, "mcast_rate", '6000') +*****0 uci_cursor:commit("wireless") +*****0 utils.execute("wifi down radio"..phy_idx.."; wifi up radio"..phy_idx) +*****0 os.execute("sleep 10s") + end + + 14 function fbw.fetch_lime_community(host, lime_community_fname) + 11 local res = lutils.http_client_get("http://[" .. host .. "]/cgi-bin/lime/lime-community", 10, lime_community_fname) + 11 if res == nil or utils.file_not_exists_or_empty(lime_community_fname) then + 11 res = lutils.http_client_get("http://[" .. host .. "]/lime-community", 10, lime_community_fname) + end + 11 return res + end + + -- Return true if download success, false otherwise + 14 function fbw.fetch_lime_community_assets(host, fname) + 33 local res = lutils.http_client_get("http://[" .. host .. "]/cgi-bin/lime/lime-community-assets", 10, lime_community_fname) + 33 return res + end + + -- Fetch remote configuration and save result + 14 function fbw.fetch_config(data) + 44 fbw.log('Fetch config from '.. json.stringify(data)) + 44 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.downloading_config) + 44 local success = true + 44 local host = data.host + + 44 local hostname = utils.execute("wget --no-check-certificate http://["..data.host.."]/cgi-bin/hostname -qO - "):gsub("\n", "") + 44 fbw.log('Hostname found: '.. hostname) + 44 if (hostname == '') then hostname = host end + + 44 local lime_community_fname = fbw.get_lime_communty_fname(hostname, data.bssid) + + 44 local res = fbw.fetch_lime_community(data.host, lime_community_fname) + + -- Remove lime-community files that are not yet configured. + -- For this we asume that no ap_ssid options equals not configured. + 44 if res == true and not utils.file_not_exists_or_empty(lime_community_fname) then + 33 local f = io.open(lime_community_fname) + 33 local content = f:read("*a") + 33 f:close() + 33 if not content:match("ap_ssid") then + 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_not_configured) + 11 utils.execute("rm " .. lime_community_fname) + 11 success = false + else + 22 local fname = fbw.lime_community_assets_name(hostname) + 22 success = fbw.fetch_lime_community_assets(data.host, fname) + 22 if success == nil then + -- Error downloading lime community assets + 11 success = false + 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_download_lime_assets) + end + end + else + -- Error downloading lime community + 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_download_lime_community) + 11 success = false + end + + 44 if success then + 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.downloaded_config) + end + + 44 return { host = host, filename = lime_community_fname, success = success} + end + + -- Restore previus wireless configuration + 14 function fbw.restore_wifi_config() + 11 utils.execute("cp /tmp/wireless-temp /etc/config/wireless") + 11 local allRadios = wireless.scandevices() + 11 for _, radio in pairs (allRadios) do +*****0 if wireless.is5Ghz(radio[".name"]) then +*****0 local phyIndex = radio[".name"].sub(radio[".name"], -1) +*****0 utils.execute("wifi down radio"..phyIndex.."; wifi up radio"..phyIndex) + end + end + end + + -- Apply configuration permanenty + -- TODO: check if config is valid + -- TODO: use safe-reboot + 14 function fbw.apply_file_config(file, hostname) +*****0 fbw.log('apply_file_config(file=' .. file .. ', hostname=' .. hostname .. ')') +*****0 local uci_cursor = config.get_uci_cursor() + --Check if lime-community exist +*****0 local filePath = fbw.WORKDIR .. file +*****0 utils.file_exists(filePath) + -- Format hostname +*****0 hostname = hostname or config.get("system", "hostname") + -- Clean previus lime configuration and replace lime-community +*****0 config.reset_node_config() +*****0 utils.execute("cp " .. filePath .. " /etc/config/" .. config.UCI_COMMUNITY_NAME) + + -- Setup the shared lime-assets +*****0 local remote_hostname = string.sub(file, #fbw.COMMUNITY_HOST_CONFIG_PREFIX + 1) +*****0 local lime_community_assets_fname = fbw.lime_community_assets_name(remote_hostname) +*****0 if utils.file_exists(lime_community_assets_fname) then +*****0 utils.execute(string.format("tar xfz %s -C /etc/lime-assets/", lime_community_assets_fname)) + end + + -- Run lime-config as first boot and setup new hostname +*****0 uci_cursor:set(config.UCI_NODE_NAME, "system", "hostname", hostname) +*****0 fbw.end_config() + end + + -- Remove scan lock file + 14 function fbw.end_scan() + 33 local file = io.open("/tmp/scanning", "w") + 33 file:write("false") + 33 file:close() + end + + -- Read scan status + 14 function fbw.check_scan_file() + 99 local file = io.open("/tmp/scanning", "r") + 99 if(file == nil) then + 22 return nil + end + 77 return assert(file:read("*a"), nil) + end + + 14 function fbw.is_configured() + 55 return config.get_bool('system', 'firstbootwizard_configured', false) + end + + 14 function fbw.mark_as_configured() + 11 local uci_cursor = config.get_uci_cursor() + 11 uci_cursor:set(config.UCI_NODE_NAME, 'system', 'firstbootwizard_configured', 'true') + end + + 14 function fbw.is_dismissed() + 55 return config.get_bool('system', 'firstbootwizard_dismissed', false) + end + + 14 function fbw.dismiss() + 11 local uci_cursor = config.get_uci_cursor() + 11 uci_cursor:set(config.UCI_NODE_NAME, 'system', 'firstbootwizard_dismissed', 'true') + 11 uci_cursor:commit(config.UCI_NODE_NAME) + 11 config.uci_autogen() + end + + -- Get config from lime-default file + local function getConfig(path) + 33 local uci_cursor = uci.cursor(fbw.WORKDIR) + 33 local config = uci_cursor:get_all(path) + + 33 if config ~= nil then + 33 return config + end +*****0 return {} + end + + -- List downloaded lime-community + 14 function fbw.read_configs() + 77 local tempFiles = fs.dir(fbw.WORKDIR) + 77 local result = {} + 143 for file in tempFiles do + 66 if (file ~= nil and file:match("^" .. lutils.literalize(fbw.COMMUNITY_HOST_CONFIG_PREFIX))) then + 33 local config = getConfig(file) + 33 local trimedConfig = {} + 33 trimedConfig.wifi = config['wifi'] + 66 table.insert(result, { + 33 config = trimedConfig, + 33 file = file + }) + end + end + + 77 return result + end + + -- Apply configuration for a new network ( used in ubus daemon) + 14 function fbw.create_network(ssid, hostname, password, country) + 11 fbw.log('apply_file_config(ssid=' .. ssid .. ', hostname=' .. hostname .. ')') + 11 local uci_cursor = config.get_uci_cursor() + + -- Save changes in lime-community + 11 if password ~= nil and password ~= '' then + 11 lutils.set_shared_root_password(password) + end + 11 if country then +*****0 uci_cursor:set("lime-community", 'wifi', 'country', country) + end + 11 uci_cursor:set("lime-community", 'wifi', 'ap_ssid', ssid) + 11 uci_cursor:set("lime-community", 'wifi', 'apname_ssid', ssid..'/%H') + 11 uci_cursor:commit("lime-community") + + -- Apply new configuration and setup hostname + 11 config.reset_node_config() + 11 uci_cursor:set("lime-node", 'system', 'hostname', hostname or config.get("system", "hostname")) + 11 fbw.end_config() + end + + 14 function fbw.end_config() +*****0 local uci_cursor = config.get_uci_cursor() +*****0 fbw.mark_as_configured() +*****0 fbw.log('commiting lime-node') +*****0 uci_cursor:commit(config.UCI_NODE_NAME) + -- Apply new configuration + +*****0 os.execute("/usr/bin/lime-config") + +*****0 os.execute("reboot") + end + + 14 function fbw.save_scan_results(networks) + 187 return lutils.write_obj_store(fbw.WORKDIR .. fbw.SCAN_RESULTS_FILE, networks) + end + + 14 function fbw.read_scan_results( ) + 198 return lutils.read_obj_store(fbw.WORKDIR .. fbw.SCAN_RESULTS_FILE) + end + + -- Used to add "status" to an entry on the scanresults file + 14 function fbw.set_status_to_scanned_bbsid(destBssid, status) + -- Open scan_results.json + 110 local results = fbw.read_scan_results() + -- Search ssid + 132 for k, v in pairs(results) do + 132 if(v['bssid'] == destBssid) then + -- Add status message + 110 v["status"] = status + 110 break + end + end + -- Store it again + 110 fbw.save_scan_results(results) + end + + -- Apply file config for specific file, hostname and stop scanning if running + 14 function fbw.set_network(file, hostname) +*****0 fbw.stop_search_networks() -- Stop firstbootwizard service if running +*****0 fbw.apply_file_config(file, hostname) + end + + -- Scan for networks and fetch configurations files + 14 function fbw.get_all_networks() +*****0 local networks = {} +*****0 local configs = {} +*****0 fbw.log("Starting search networks") + +*****0 fbw.log('Add lock file') +*****0 fbw.start_scan_file() +*****0 fbw.log('Clear previus scans') +*****0 fbw.clean_tmp() +*****0 fbw.log('Set wireless backup') +*****0 fbw.backup_wifi_config() +*****0 fbw.log('Get mesh networks') +*****0 networks = fbw.get_networks() +*****0 fbw.log('Saving mesh scan results') +*****0 fbw.save_scan_results(networks) +*****0 fbw.log('Get configs files') +*****0 configs = ft.reduce(fbw.get_config, networks, {}) +*****0 fbw.log('Restore previous wireless configuration') +*****0 fbw.restore_wifi_config() +*****0 fbw.log('Remove lock file') +*****0 fbw.end_scan() +*****0 fbw.log('Return configs files names') +*****0 return configs + end + + -- Run daemonized /bin/firstbootwizard execution that start get_all_networks + -- Return false if already runing + 14 function fbw.start_search_networks() + 11 local scan_file = fbw.check_scan_file() + 11 if(scan_file == nil) or (scan_file == "false") then + 11 os.execute("rm -f /tmp/scanning") + 11 lutils.execute_daemonized("/bin/firstbootwizard") + 11 return true + end +*****0 return false + end + + -- Return object with status, read_configs() and read_scan_results() + 14 function fbw.status_search_networks() + 33 local scan_file = fbw.check_scan_file() + local status + 33 if (scan_file == nil) then + 11 status = 'idle' + 22 elseif(scan_file == "true") then + 22 status = 'scanning' + else +*****0 status = 'scanned' + end + 33 lock = not fbw.is_configured() and not fbw.is_dismissed() + 33 return { lock = lock, status = status, networks = fbw.read_configs(), scanned = fbw.read_scan_results()} + end + + -- todo(kon): check this work properly + 14 function fbw.kill_fbw() + 11 os.execute("killall firstbootwizard") + end + + -- Function that stop get_all_networks function if running + 14 function fbw.stop_search_networks() + 11 local scan_file = fbw.check_scan_file() + 11 if (scan_file == "true") then + 11 fbw.log('Stopping firstbootwizard service') + 11 fbw.kill_fbw() + 11 fbw.log('Restore previus wireless configuration') + 11 fbw.restore_wifi_config() + 11 fbw.log('Remove lock file') + 11 fbw.end_scan() + 11 return true + else +*****0 return true + end +*****0 return false + end + + -- Return false if can't perform the restart + 14 function fbw.restart_search_networks() +*****0 if fbw.stop_search_networks() then +*****0 return fbw.start_search_networks() + end +*****0 return false + end + + 14 return fbw + +============================================================================== +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/functools.lua +============================================================================== + 14 local functools = {} + + -- Lua implementation of the curry function + -- Developed by tinylittlelife.org + -- released under the WTFPL (http://sam.zoy.org/wtfpl/) + + -- curry(func, num_args) : take a function requiring a tuple for num_args arguments + -- and turn it into a series of 1-argument functions + -- e.g.: you have a function dosomething(a, b, c) + -- curried_dosomething = curry(dosomething, 3) -- we want to curry 3 arguments + -- curried_dosomething (a1) (b1) (c1) -- returns the result of dosomething(a1, b1, c1) + -- partial_dosomething1 = curried_dosomething (a_value) -- returns a function + -- partial_dosomething2 = partial_dosomething1 (b_value) -- returns a function + -- partial_dosomething2 (c_value) -- returns the result of dosomething(a_value, b_value, c_value) + 14 function functools.curry(func, num_args) + + -- currying 2-argument functions seems to be the most popular application +*****0 num_args = num_args or 2 + + -- helper + local function curry_h(argtrace, n) +*****0 if 0 == n then + -- reverse argument list and call function +*****0 return func(functools.reverse(argtrace())) + else + -- "push" argument (by building a wrapper function) and decrement n + return function (onearg) +*****0 return curry_h(function () return onearg, argtrace() end, n - 1) + end + end + end + + -- no sense currying for 1 arg or less +*****0 if num_args > 1 then +*****0 return curry_h(function () return end, num_args) + else +*****0 return func + end + end + + -- reverse(...) : take some tuple and return a tuple of elements in reverse order + -- + -- e.g. "reverse(1,2,3)" returns 3,2,1 + 14 function functools.reverse(...) + + --reverse args by building a function to do it, similar to the unpack() example + local function reverse_h(acc, v, ...) +*****0 if 0 == select('#', ...) then +*****0 return v, acc() + else +*****0 return reverse_h(function () return v, acc() end, ...) + end + end + + -- initial acc is the end of the list +*****0 return reverse_h(function () return end, ...) + end + + + 14 function functools.map(func, tbl) + 33 local newtbl = {} + 33 for i,v in pairs(tbl) do +*****0 newtbl[i] = func(v) + end + 33 return newtbl + end + + 14 function functools.filter(func, tbl) + 22 local newtbl= {} + 22 local index=1; + 22 for i,v in pairs(tbl) do +*****0 if func(v, i) then +*****0 newtbl[index]=v +*****0 index = index + 1 + end + end + 22 return newtbl + end + + 14 function functools.search(func, tbl) +*****0 for i,v in pairs(tbl) do +*****0 if func(v, i) then +*****0 return i + end + end + +*****0 return 0 + end + + 14 function functools.print_r ( t ) +*****0 local print_r_cache={} + local function sub_print_r(t,indent) +*****0 if (print_r_cache[tostring(t)]) then +*****0 print(indent.."*"..tostring(t)) + else +*****0 print_r_cache[tostring(t)]=true +*****0 if (type(t)=="table") then +*****0 for pos,val in pairs(t) do +*****0 if (type(val)=="table") then +*****0 print(indent.."["..pos.."] => "..tostring(t).." {") +*****0 sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) +*****0 print(indent..string.rep(" ",string.len(pos)+6).."}") +*****0 elseif (type(val)=="string") then +*****0 print(indent.."["..pos..'] => "'..val..'"') + else +*****0 print(indent.."["..pos.."] => "..tostring(val)) + end + end + else +*****0 print(indent..tostring(t)) + end + end + end +*****0 if (type(t)=="table") then +*****0 print(tostring(t).." {") +*****0 sub_print_r(t," ") +*****0 print("}") + else +*****0 sub_print_r(t," ") + end +*****0 print() + end + + 14 function functools.reduce(cb, tab, default) + 22 local result = default + 22 for k, act in pairs(tab) do +*****0 result = cb(result,act) + end + 22 return result + end + + 14 function functools.splitBy(option) + return function(tab) +*****0 local result = {} +*****0 for k, obj in pairs(tab) do +*****0 if result[obj[option]] == nil then +*****0 result[obj[option]] = {} + end +*****0 table.insert(result[obj.mode], obj) + end +*****0 return result + end + end + + 14 function functools.sortBy(option, reverse) + return function(tab) + 22 table.sort(tab, function (left, right) +*****0 order = left[option] < right[option] +*****0 return (not reverse and order) or not order + end) + 11 return tab + end + end + + 14 function functools.flatTable(prev, act) +*****0 for i=1,#act do +*****0 prev[#prev+1] = act[i] + end +*****0 return prev + end + + 14 return functools + +============================================================================== +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/utils.lua +============================================================================== + 14 local utils = {} + + 14 local ft = require('firstbootwizard.functools') + 14 local fs = require("nixio.fs") + 14 local iwinfo = require("iwinfo") + 14 local json = require("luci.jsonc") + 14 local limeutils = require("lime.utils") + + 14 function execute(cmd) + 498 local f = assert(io.popen(cmd, 'r')) + 498 local s = assert(f:read('*a')) + 498 f:close() + 498 return s + end + + 14 function utils.execute (cmd) + 498 return execute(cmd) + end + + 14 function utils.eui64(mac) + local cmd = [[ + function eui64 { + mac="$(echo "$1" | tr -d : | tr A-Z a-z)" + mac="$(echo "$mac" | head -c 6)fffe$(echo "$mac" | tail -c +7)" + let "b = 0x$(echo "$mac" | head -c 2)" + let "b ^= 2" + printf "%02x" "$b" + echo "$mac" | tail -c +3 | head -c 2 + echo -n : + echo "$mac" | tail -c +5 | head -c 4 + echo -n : + echo "$mac" | tail -c +9 | head -c 4 + echo -n : + echo "$mac" | tail -c +13 + } +*****0 echo -n `eui64 ]]..mac..'`' +*****0 return 'fe80::'..execute(cmd) + end + + 14 function utils.file_exists(filename) +*****0 return fs.stat(filename, "type") == "reg" + end + + 14 function utils.file_not_exists_or_empty(filename) + 33 local f=io.open(filename,"r") + 33 if not f then return true end + 33 local size = f:seek("end") + 33 if f~=nil then io.close(f) end + 33 if size == 0 then return true else return false end + end + + 14 function split(str, sep) +*****0 local sep, fields = sep or ":", {} +*****0 local pattern = string.format("([^%s]+)", sep) +*****0 str:gsub(pattern, function(c) fields[#fields+1] = c end) +*****0 return fields + end + + 14 function utils.split(str, sep) +*****0 return split(str, sep) + end + + -- splits a multiline string in a list of strings, one per line + 14 function lsplit(mlstring) +*****0 return split(mlstring, "\n") + end + + 14 function utils.lsplit(mlstring) +*****0 return lsplit(mlstring) + end + + 14 function utils.phy_to_idx(phy) +*****0 local substr = string.gsub(phy, "phy", "") +*****0 return tonumber(substr) + end + + 14 function utils.extract_phys_from_radios(radio) +*****0 return "phy"..radio.sub(radio, -1) + end + + 14 function utils.not_own_network(own_macs) + return function(remote_mac) +*****0 return limeutils.has_value(own_macs, remote_mac) ~= true + end + end + + 14 function utils.add_prop(option, value) + return function(tab) +*****0 tab[option] = value +*****0 return tab + end + end + + 14 function utils.extract_prop(prop) + return function(tab) +*****0 return tab[prop] + end + end + + 14 function utils.read_file(file) +*****0 local lines = utils.lines_from("/tmp/"..file) +*****0 return lines + end + + + 14 function tableEmpty(self) +*****0 for _, _ in pairs(self) do +*****0 return false + end +*****0 return true + end + + 14 function utils.tableEmpty(self) +*****0 return tableEmpty(self) + end + + 14 function utils.hash_file(file) +*****0 return execute("md5sum "..file.." | awk '{print $1}'") + end + + 14 function utils.are_files_different(file1, file2) +*****0 return hash_file(file1) ~= hash_file(file2) + end + + 14 function utils.unpack_table(t) +*****0 local unpacked = {} +*****0 for k,v in ipairs(t) do +*****0 for sk, sv in ipairs(v) do +*****0 unpacked[#unpacked+1] = sv + end + end +*****0 return unpacked + end + + 14 function utils.filter_mesh(n) +*****0 return n.mode == "Ad-Hoc" or n.mode == "Mesh Point" + end + + local same_network = function(net_a, net_b) +*****0 return net_a.channel == net_b.channel and net_a.ssid == net_b.ssid + end + + local find_network = function(network, data) +*****0 return ft.filter(function(net) return same_network(net,network) end, data) + end + + 14 function utils.only_best(networks) + 22 return ft.reduce(function(acc, network) +*****0 local existent = find_network(network, acc) +*****0 if #existent > 0 then +*****0 acc = ft.map(function(net) +*****0 if same_network(net, network) and net.signal < network.signal then +*****0 return network + end +*****0 return net +*****0 end,acc) +*****0 return acc + end +*****0 table.insert(acc, network) +*****0 return acc + 11 end, networks, {}) + end + + 14 function utils.is_connected(dev_id) +*****0 local isAssociated = {} +*****0 local i = 0 +*****0 while (tableEmpty(isAssociated)) and i < 5 do +*****0 isAssociated = iwinfo.nl80211.assoclist(dev_id) +*****0 if tableEmpty(isAssociated) == false then break end +*****0 i = i + 1 +*****0 os.execute("sleep 2s") + end + end + + 14 function utils.get_stations_macs(network) +*****0 return lsplit(execute('iw dev '..network..' station dump | grep ^Station | cut -d\\ -f 2')) + end + + 14 function utils.append_network(dev) + return function (ipv6) +*****0 return ipv6..'%'..dev + end + end + + 14 function utils.filter_alredy_scanned(hosts, results) +*****0 local fe80scanned = ft.reduce(function(scanned, host) +*****0 local mac = split(host.host, "%%")[1] +*****0 scanned[mac] = mac +*****0 return scanned +*****0 end, results, {}) + +*****0 return ft.filter(function(host) +*****0 local mac = split(host.host, "%%")[1] +*****0 return fe80scanned[mac] == nil +*****0 end, hosts) + end + + 14 return utils + +============================================================================== +packages/lime-eth-config/files/usr/lib/lua/lime-eth-config.lua +============================================================================== + -- ! LibreMesh + -- ! Generic hook to be called as a symbolic link for each ref type + -- ! Copyright (C) 2025 Javier Jorge + -- ! Copyright (C) 2025 Instituto Nacional de Tecnología Industrial (INTI) + -- ! Copyright (C) 2025 Asociación Civil Altermundi + -- ! SPDX-License-Identifier: AGPL-3.0- + 11 local JSON = require("luci.jsonc") + 11 local utils = require("lime.utils") + 11 local config = require("lime.config") + 11 local libuci = require("uci") + 11 local node_status = require 'lime.node_status' + + 11 local luci_config = libuci:cursor() + 11 local eht_config = {} + + 11 function eht_config.get_eth_config() + 33 limenode_interfaces = {} + --get configurations from lime-node + 33 local uci = config.get_uci_cursor() + 66 uci:foreach("lime-autogen", "net", function(entry) +*****0 if entry.eth_role ~= nil then +*****0 local interface = {} +*****0 interface.name = entry.linux_name +*****0 interface.eth_role = entry.eth_role +*****0 table.insert(limenode_interfaces, interface) + end + end) + --get default settings + 33 local switch_status = node_status.switch_status() + 33 local interfaces = {} + 33 if switch_status ~= nil then + 165 for _, status in ipairs(switch_status) do + 132 local interface = { + 132 device = status.device, + 132 num = status.num, + 132 role = status.role, + 132 link = status.link, + 132 eth_role = "default" -- default value + } + 132 for _, limenode_interface in ipairs(limenode_interfaces) do +*****0 if limenode_interface.name == status.device then +*****0 interface.eth_role = limenode_interface.eth_role + break + end + end + 132 table.insert(interfaces, interface) + end + end + 33 return interfaces + end + + 11 function eht_config.delete_eth_config(tag_value) + 11 local uci = config.get_uci_cursor() + 11 config.uci:delete("lime-node", tag_value) + 11 config.uci:save("lime-node") + 11 uci:commit("lime-node") + end + + 11 function eht_config.set_eth_config(device, role) + 33 local tag_value = "lime_app_eth_cfg_" .. device:gsub("%.", "_") + 33 local uci = config.get_uci_cursor() + 33 local eth_role = uci:get("lime-node", tag_value, "eth_role") + -- verify previous config + 33 if eth_role ~= nil then +*****0 if eth_role == role then + -- No changes needed, the role is already set +*****0 return true + else +*****0 eht_config.delete_eth_config(tag_value) + end + end + + -- handle lm_hwd_openwrt_wan + 33 if role ~= "wan" then + -- if the port was automatically configured as a wan port, we need to remove the + -- wan protocol. If we returtn to default we need to enable auto detection. + 22 local switch_status = node_status.switch_status() + 22 if switch_status ~= nil then + 110 for _, status in ipairs(switch_status) do + 88 if status.device == device and status.role == "wan" then +*****0 if role == "lan" or role == "mesh" then +*****0 uci:set("lime-node", "lm_hwd_openwrt_wan", "net") +*****0 uci:set("lime-node", "lm_hwd_openwrt_wan", "autogenerated", "false") + +*****0 elseif role == "default" then +*****0 uci:delete("lime-node", "lm_hwd_openwrt_wan") + end + break + end + end + end + end + + 33 if role == "default" then + 11 eht_config.delete_eth_config(tag_value) + else + 22 uci:set("lime-node", tag_value, "net") + 22 uci:set("lime-node", tag_value, "eth_role", role) + 22 uci:set("lime-node", tag_value, "linux_name", device) + + 22 if role == "wan" then + 11 uci:set("lime-node", tag_value, "protocols", {"wan", "dynamic"}) + 11 elseif role == "lan" then +*****0 uci:set("lime-node", tag_value, "protocols", {"lan"}) + 11 elseif role == "mesh" then + 11 uci:set("lime-node", tag_value, "protocols", {"batadv:%N1", "babeld:17"}) + else +*****0 return false + end + end + 33 uci:commit("lime-node") + 33 config.uci:save("lime-node") + 33 os.execute("lime-config && /etc/init.d/network restart") + 33 return true + end + + 11 return eht_config + +============================================================================== +packages/lime-eth-config/files/usr/libexec/rpcd/lime-eth-config +============================================================================== + #!/usr/bin/env lua + + --! LibreMesh + --! Generic hook to be called as a symbolic link for each ref type + --! Copyright (C) 2025 Javier Jorge + --! Copyright (C) 2025 Instituto Nacional de Tecnología Industrial (INTI) + --! Copyright (C) 2025 Asociación Civil Altermundi + --! SPDX-License-Identifier: AGPL-3.0- + + + 66 local json = require 'luci.jsonc' + 66 local utils = require 'lime.utils' + 66 local eth_config = require("lime-eth-config") + + + local function get_eth_config(msg) + + 33 interfaces = eth_config.get_eth_config() + + 33 result = {} + 33 if interfaces and #interfaces > 0 then + 33 result.status = "ok" + 33 result.interfaces = interfaces + else +*****0 result.status = "error" +*****0 result.interfaces = {} +*****0 result.error = "No interface configuration found" + end + 33 utils.printJson(result) + end + + 66 function set_eth_config(msg) + 33 local device, role = msg.device, msg.role + + 33 if not device or not role then +*****0 return utils.printJson({ status = "error", error = "Missing parameters" }) + end + + 33 local success = eth_config.set_eth_config(device, role) + 33 if success then + 33 utils.printJson({ status = "ok" }) + else +*****0 utils.printJson({ status = "error", error = "Failed to set eth ".. msg.device .. " as " .. msg.role }) + end + + end + + 66 local methods = { + 66 get_eth_config = { no_params = 0 }, + 66 set_eth_config = { device = 'value', role = 'value'} + } + + + 66 if arg[1] == 'list' then +*****0 utils.printJson(methods) + end + + 66 if arg[1] == 'call' then + 66 local msg = utils.rpcd_readline() or '{}' + 66 msg = json.parse(msg) + 66 if arg[2] == 'get_eth_config' then get_eth_config(msg) + 33 elseif arg[2] == 'set_eth_config' then set_eth_config(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/lime-hwd-ground-routing/files/usr/lib/lua/lime/hwd/ground_routing.lua +============================================================================== + #!/usr/bin/lua + + 6 local hardware_detection = require("lime.hardware_detection") + 6 local config = require("lime.config") + 6 local utils = require("lime.utils") + + 6 local ground_routing = {} + + 6 ground_routing.sectionNamePrefix = hardware_detection.sectionNamePrefix.."ground_routing_" + + 6 function ground_routing.delete_all_switch_vlan_sections() +*****0 local uci = config.get_uci_cursor() +*****0 uci:foreach("network", "switch_vlan", function(s) uci:delete("network", s[".name"]) end ) +*****0 uci:save("network") + end + + 6 function ground_routing.clean() + 6 local uci = config.get_uci_cursor() + + 6 function cleanGrSection(section) + 24 if utils.stringStarts(section[".name"], ground_routing.sectionNamePrefix) then +*****0 uci:delete("network", section[".name"]) + end + end + + 6 uci:foreach("network", "switch_vlan", cleanGrSection) + 6 uci:foreach("network", "interface", cleanGrSection) + 6 uci:save("network") + end + + 6 function ground_routing.detect_hardware() + 6 function parse_gr(section) +*****0 local link_name = section[".name"] +*****0 local net_dev = section["net_dev"] +*****0 local vlan = section["vlan"] +*****0 local uci = config.get_uci_cursor() + +*****0 function create_8021q_dev(vlan_p) +*****0 local dev_secname = ground_routing.sectionNamePrefix..link_name.."_"..net_dev.."_"..vlan_p +*****0 uci:set("network", dev_secname, "device") +*****0 uci:set("network", dev_secname, "name", net_dev.."."..vlan_p) +*****0 uci:set("network", dev_secname, "ifname", net_dev) +*****0 uci:set("network", dev_secname, "type", "8021q") +*****0 uci:set("network", dev_secname, "vid", vlan_p) + end + +*****0 local switch_dev = section["switch_dev"] +*****0 if switch_dev then +*****0 local switch_cpu_port = section["switch_cpu_port"] +*****0 function tag_cpu_port(section) +*****0 if (section["device"] ~= switch_dev) then return end + +*****0 local patterns = { "^"..switch_cpu_port.." ", " "..switch_cpu_port.."$", " "..switch_cpu_port.." " } +*****0 local substits = { switch_cpu_port.."t ", " "..switch_cpu_port.."t", " "..switch_cpu_port.."t " } +*****0 local matchCount = 0 +*****0 local m = 0 +*****0 for i,p in pairs(patterns) do +*****0 section["ports"], m = section["ports"]:gsub(p, substits[i]) +*****0 matchCount = matchCount + m + end + +*****0 if (matchCount > 0) then +*****0 create_8021q_dev(section["vlan"]) +*****0 uci:set("network", section[".name"], "ports", section["ports"]) + end + end + +*****0 uci:foreach("network", "switch_vlan", tag_cpu_port) + + +*****0 local sw_secname = ground_routing.sectionNamePrefix..link_name.."_sw_"..switch_dev.."_"..vlan +*****0 local ports = switch_cpu_port.."t" +*****0 for _,p in pairs(section["switch_ports"]) do +*****0 ports = ports.." "..p + end + +*****0 uci:set("network", sw_secname, "switch_vlan") +*****0 uci:set("network", sw_secname, "device", switch_dev) +*****0 uci:set("network", sw_secname, "vlan", vlan) +*****0 uci:set("network", sw_secname, "ports", ports) + end + +*****0 create_8021q_dev(vlan) + +*****0 uci:save("network") + end + + local clean_needed -- if there are no hwd_gr sections defined, don't clean all switch_vlan sections + 6 config.foreach("hwd_gr", function(s) clean_needed = true end) + 6 if clean_needed then ground_routing.delete_all_switch_vlan_sections() end + + 6 config.foreach("hwd_gr", parse_gr) + end + + 6 return ground_routing + +============================================================================== +packages/lime-hwd-openwrt-wan/files/usr/lib/lua/lime/hwd/openwrt_wan.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2014-2023 Gioacchino Mazzurco + --! Copyright (C) 2023 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + + 20 local hardware_detection = require("lime.hardware_detection") + 20 local config = require("lime.config") + 20 local utils = require("lime.utils") + + 20 local openwrt_wan = {} + + 20 openwrt_wan.sectionName = hardware_detection.sectionNamePrefix.."openwrt_wan" + + 20 function openwrt_wan.clean() + 6 if config.autogenerable(openwrt_wan.sectionName) then + 6 config.delete(openwrt_wan.sectionName) + end + end + + 20 function openwrt_wan.detect_hardware() + 39 if config.autogenerable(openwrt_wan.sectionName) then + local ifname + 39 local board = utils.getBoardAsTable() + 39 local networkTable = board['network'] + 39 if networkTable then + 28 local wanTable = networkTable['wan'] + 28 if wanTable then + 17 ifname = wanTable['device'] + end + end + 39 if ifname and ifname ~= "" then + 17 local protos = {} + 17 local net = require("lime.network") + 17 local utils = require("lime.utils") + 88 for _, pArgs in pairs(config.get("network", "protocols")) do + 71 local pArr = utils.split(pArgs, net.protoParamsSeparator) + 71 if ( pArr[1] == "bmx6" or pArr[1] == "bmx7") then + 12 pArr[2] = 0 + 12 pArgs = table.concat(pArr, net.protoParamsSeparator) + 12 table.insert(protos, pArgs) + 59 elseif ( pArr[1]~="lan" and pArr[1]~="wan" ) then + 42 table.insert(protos, pArgs) + end + end + 17 table.insert(protos, "wan") + + 17 config.init_batch() + 17 config.set(openwrt_wan.sectionName, "net") + 17 config.set(openwrt_wan.sectionName, "autogenerated", "true") + 17 config.set(openwrt_wan.sectionName, "protocols", protos) + 17 config.set(openwrt_wan.sectionName, "linux_name", ifname) + 17 config.end_batch() + + 17 utils.dbg("WAN interface:", ifname) + else + 22 utils.dbg("WAN interface not found") + end + end + end + + 20 return openwrt_wan + +============================================================================== +packages/lime-hwd-usbradio/files/usr/lib/lua/lime/hwd/usbradio.lua +============================================================================== + #!/usr/bin/lua + + 6 local utils = require("lime.utils") + 6 local config = require("lime.config") + 6 local hardware_detection = require("lime.hardware_detection") + + 6 local libuci = require("uci") + 6 local fs = require("nixio.fs") + + 6 usbradio = {} + + 6 usbradio.sectionNamePrefix = hardware_detection.sectionNamePrefix.."usbradio_" + + --! Remove configuration about no more plugged usb radio + 12 function usbradio.clean() + 6 local uci = libuci:cursor() + + --! This function control if a usb radio configuration section is valid otherwise delete it + local function test_and_clean_device(s) + --! Check if passed section is an usb radio +*****0 local sectionName = s[".name"] +*****0 if utils.stringStarts(sectionName, usbradio.sectionNamePrefix) then + --! Check if the section is autogenerated otherwise do not touch it + --! We check it to avoid delete usb radio sections manually configured +*****0 if config.get_bool(sectionName, "autogenerated") then +*****0 local phyIndex = sectionName:match("%d+$") + --! Check if the usb radio exist + --! If the usb radio does not exist anymore (numberOfMatches < 1) delete it +*****0 local _, numberOfMatches = fs.glob("/sys/devices/"..s["path"].."/ieee80211/phy"..phyIndex) +*****0 if numberOfMatches < 1 then +*****0 local radioName = s["radio_name"] +*****0 uci:delete("wireless", radioName) +*****0 config.delete(sectionName) + end + end + end + end + + --! For each wifi-device section call test_and_clean_device function + 6 uci:foreach("wireless", "wifi-device", test_and_clean_device) + 6 uci:save("wireless") + end + + + --! Detect the usb radio and configurate it + 12 function usbradio.detect_hardware() + 6 local stdOutput = io.popen("find /sys/devices | grep usb | grep ieee80211 | grep 'phy[0-9]*$'") + --! Repeat for each usb radio found + 6 for path in stdOutput:lines() do + --! Define useful variables +*****0 local endBasePath, phyEnd = string.find(path, "/ieee80211/phy") +*****0 local phyPath = string.sub(path, 14, endBasePath-1) +*****0 local phyIndex = string.sub(path, phyEnd+1) +*****0 local radioName = "radio"..phyIndex +*****0 local sectionName = usbradio.sectionNamePrefix..radioName + + --! If sectionName exist and it is autogenerated configure it + --! If sectionName does not exist it is created and configured + --! Check if a sectionName is autogenerated avoid delete usb radio introduced by the user +*****0 if config.autogenerable(sectionName) then +*****0 local uci = libuci:cursor() + + --! Delete the usb radio +*****0 uci:delete("wireless", radioName) + --! Create and configure the usb radio directly in the OpenWRT system (wireless) +*****0 uci:set("wireless", radioName, "wifi-device") +*****0 uci:set("wireless", radioName, "type", "mac80211") +*****0 uci:set("wireless", radioName, "channel", "11") --TODO: working on all 802.11bgn devices; find a general way for working in different devices +*****0 uci:set("wireless", radioName, "band", "2g") --TODO: working on all 802.11gn devices; find a general way for working in different devices +*****0 uci:set("wireless", radioName, "path", phyPath) +*****0 uci:set("wireless", radioName, "htmode", "HT20") +*****0 uci:set("wireless", radioName, "disabled", "0") + +*****0 uci:save("wireless") + + --! Write just once on the disk all the config.set +*****0 config.init_batch() +*****0 config.set(sectionName, "wifi") +*****0 config.set(sectionName, "autogenerated", "true") +*****0 config.set(sectionName, "radio_name", radioName) + + --! Configuration of an usb radio using general option in LiMe +*****0 for option_name, value in pairs(config.get_all("wifi")) do + --! Options that start with point are hidden option, we exclude them as they can cause problems +*****0 if (option_name:sub(1,1) ~= ".") then + --! Needed a table or a string for config.set +*****0 if ( type(value) ~= "table" ) then value = tostring(value) end +*****0 config.set(sectionName, option_name, value) + end + end + +*****0 local modes = {} +*****0 for _, mode in pairs(config.get("wifi", "modes")) do +*****0 if mode ~= "adhoc" then table.insert(modes, mode) end + end +*****0 config.set(sectionName, "modes", modes) + +*****0 config.end_batch() + end + end + end + + + 6 return usbradio + +============================================================================== +packages/lime-hwd-watchcat/files/usr/lib/lua/lime/hwd/watchcat.lua +============================================================================== + #!/usr/bin/lua + + 6 local hardware_detection = require("lime.hardware_detection") + 6 local config = require("lime.config") + 6 local utils = require("lime.utils") + + 6 local watchcat = {} + + 6 watchcat.sectionNamePrefix = hardware_detection.sectionNamePrefix.."watchcat_" + + local function reload_watchcat() +*****0 os.execute("/etc/init.d/watchcat reload") + end + + 6 function watchcat.clean() + 6 local uci = config.get_uci_cursor() + 6 local modified = false + + local function clear_watchcat_section(section) +*****0 local is_ours = utils.stringStarts(section[".name"], watchcat.sectionNamePrefix) + +*****0 local is_anon = section[".anonymous"] + +*****0 if is_ours or is_anon then +*****0 uci:delete("watchcat", section[".name"]) +*****0 modified = true + end + end + + 6 uci:foreach("watchcat", "watchcat", clear_watchcat_section) + 6 if modified then +*****0 uci:save("watchcat") +*****0 reload_watchcat() + end + end + + 6 function watchcat.detect_hardware() + 6 local uci = config.get_uci_cursor() + 6 local user_defined = false + + 12 config.foreach("hwd_watchcat", function(user_section) +*****0 user_defined = true +*****0 local identifier = user_section.id or "default" +*****0 local section_name = watchcat.sectionNamePrefix .. identifier + +*****0 uci:set("watchcat", section_name, "watchcat") + +*****0 for option_key, option_value in pairs(user_section) do + -- discards .name, .type keys and id name sections +*****0 if option_key:sub(1,1) ~= "." and option_key ~= "id" then +*****0 uci:set("watchcat", section_name, option_key, option_value) + end + end + end) + -- only saved if we actually aplied any user section + 6 if user_defined then +*****0 uci:save("watchcat") +*****0 reload_watchcat() + end + end + + 6 return watchcat + +============================================================================== +packages/lime-proto-babeld/files/usr/lib/lua/lime/proto/babeld.lua +============================================================================== + #!/usr/bin/lua + + --! LiMe Proto Babeld + --! Copyright (C) 2018 Gioacchino Mazzurco + --! + --! This program is free software: you can redistribute it and/or modify + --! it under the terms of the GNU Affero General Public License as + --! published by the Free Software Foundation, either version 3 of the + --! License, or (at your option) any later version. + --! + --! This program is distributed in the hope that it will be useful, + --! but WITHOUT ANY WARRANTY; without even the implied warranty of + --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + --! GNU Affero General Public License for more details. + --! + --! You should have received a copy of the GNU Affero General Public License + --! along with this program. If not, see . + + 6 local network = require("lime.network") + 6 local config = require("lime.config") + 6 local fs = require("nixio.fs") + + 6 babeld = {} + + 6 babeld.configured = false + + 12 function babeld.configure(args) + 72 if babeld.configured then return end + 6 babeld.configured = true + + 6 utils.log("lime.proto.babeld.configure(...)") + + 6 fs.writefile("/etc/config/babeld", "") + + 6 local uci = config.get_uci_cursor() + + 6 if config.get("network", "babeld_over_librenet6", false) then +*****0 uci:set("babeld", "librenet6", "interface") +*****0 uci:set("babeld", "librenet6", "ifname", "librenet6") +*****0 uci:set("babeld", "librenet6", "type", "tunnel") + end + + 6 uci:set("babeld", "general", "general") + 6 uci:set("babeld", "general", "local_port", "30003") + 6 uci:set("babeld", "general", "ubus_bindings", "true") + + 6 uci:set("babeld", "ula6", "filter") + 6 uci:set("babeld", "ula6", "type", "redistribute") + 6 uci:set("babeld", "ula6", "ip", "fc00::/7") + 6 uci:set("babeld", "ula6", "action", "allow") + + 6 uci:set("babeld", "public6", "filter") + 6 uci:set("babeld", "public6", "type", "redistribute") + 6 uci:set("babeld", "public6", "ip", "2000::0/3") + 6 uci:set("babeld", "public6", "action", "allow") + + 6 uci:set("babeld", "default6", "filter") + 6 uci:set("babeld", "default6", "type", "redistribute") + 6 uci:set("babeld", "default6", "ip", "0::0/0") + 6 uci:set("babeld", "default6", "le", "0") + 6 uci:set("babeld", "default6", "action", "allow") + + 6 uci:set("babeld", "mesh4", "filter") + 6 uci:set("babeld", "mesh4", "type", "redistribute") + 6 uci:set("babeld", "mesh4", "ip", "10.0.0.0/8") + 6 uci:set("babeld", "mesh4", "action", "allow") + + 6 uci:set("babeld", "mptp4", "filter") + 6 uci:set("babeld", "mptp4", "type", "redistribute") + 6 uci:set("babeld", "mptp4", "ip", "172.16.0.0/12") + 6 uci:set("babeld", "mptp4", "action", "allow") + + 6 uci:set("babeld", "default4", "filter") + 6 uci:set("babeld", "default4", "type", "redistribute") + 6 uci:set("babeld", "default4", "ip", "0.0.0.0/0") + 6 uci:set("babeld", "default4", "le", "0") + 6 uci:set("babeld", "default4", "action", "allow") + + -- Avoid redistributing extra local addesses + 6 uci:set("babeld", "localdeny", "filter") + 6 uci:set("babeld", "localdeny", "type", "redistribute") + 6 uci:set("babeld", "localdeny", "local", "true") + 6 uci:set("babeld", "localdeny", "action", "deny") + + -- Avoid redistributing enything else + 6 uci:set("babeld", "denyany", "filter") + 6 uci:set("babeld", "denyany", "type", "redistribute") + 6 uci:set("babeld", "denyany", "action", "deny") + + 6 uci:save("babeld") + + end + + 12 function babeld.setup_interface(ifname, args) + 66 if not args["specific"] and ifname:match("^wlan%d+.ap") then + 36 utils.log("lime.proto.babeld.setup_interface(%s, ...) ignored", ifname) + 36 return + end + + 30 utils.log("lime.proto.babeld.setup_interface(%s, ...)", ifname) + + 30 local vlanId = args[2] or 17 + 30 local vlanProto = args[3] or "8021ad" + 30 local nameSuffix = args[4] or "_babeld" + + local owrtInterfaceName, linuxVlanIfName, owrtDeviceName = + 30 network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + + 30 local ipv4, _ = network.primary_address() + + 30 local uci = config.get_uci_cursor() + + 30 if(vlanId ~= 0 and (ifname:match("^eth") or ifname:match("^lan"))) then + 6 uci:set("network", owrtDeviceName, "mtu", tostring(network.MTU_ETH_WITH_VLAN)) + end + + 30 uci:set("network", owrtInterfaceName, "proto", "static") + 30 uci:set("network", owrtInterfaceName, "ipaddr", ipv4:host():string()) + 30 uci:set("network", owrtInterfaceName, "netmask", "255.255.255.255") + 30 uci:save("network") + + 30 uci:set("babeld", owrtInterfaceName, "interface") + 30 uci:set("babeld", owrtInterfaceName, "ifname", linuxVlanIfName) + --! It is quite common to have dummy radio device attached via ethernet so + --! disable wired optimization always as it would consider the link down at + --! first packet lost + 30 uci:set("babeld", owrtInterfaceName, "type", "wireless") + + 30 uci:save("babeld") + end + + 12 function babeld.runOnDevice(linuxDev, args) +*****0 utils.log("lime.proto.babeld.runOnDevice(%s, ...)", linuxDev) + +*****0 local vlanId = args[2] or 17 +*****0 local vlanProto = args[3] or "8021ad" + +*****0 local vlanDev = network.createVlan(linuxDev, vlanId, vlanProto) +*****0 network.createStatic(vlanDev) + +*****0 local libubus = require("ubus") +*****0 local ubus = libubus.connect() +*****0 ubus:call('babeld', 'add_interface', { ifname = vlanDev }) + end + + 6 return babeld + +============================================================================== +packages/lime-proto-batadv/files/usr/lib/lua/lime/proto/batadv.lua +============================================================================== + #!/usr/bin/lua + + 17 local fs = require("nixio.fs") + 17 local lan = require("lime.proto.lan") + 17 local utils = require("lime.utils") + 17 local network = require("lime.network") + 17 local config = require("lime.config") + + 17 batadv = {} + + 17 batadv.configured = false + + 34 function batadv.configure(args) + 94 if batadv.configured then return end + 28 batadv.configured = true + + 28 local uci = config.get_uci_cursor() + + 28 uci:set("network", "bat0", "interface") + 28 uci:set("network", "bat0", "proto", "batadv") + -- BATMAN_V uses throughput rather than packet loss (as in BATMAN_IV) for evaluating + -- the quality of a link. Still, by default we continue selecting BATMAN_IV + 28 local routing_algo = config.get("network", "batadv_routing_algo", "BATMAN_IV") + 28 uci:set("network", "bat0", "routing_algo", routing_algo) + 28 uci:set("network", "bat0", "bridge_loop_avoidance", "1") + 28 uci:set("network", "bat0", "multicast_mode", "0") + -- by default, BATMAN-adv sends out one Originator Message (OGM) every second (orig_interval=1000) + -- in a network with static nodes, a larger interval between OGM packets can be used (e.g. 2000) + -- see https://github.com/libremesh/lime-packages/issues/1010 + 28 local orig_interval = config.get("network", "batadv_orig_interval", "2000") + 28 uci:set("network", "bat0", "orig_interval", orig_interval) + + -- if anygw enabled disable DAT that doesn't play well with it + -- and set gw_mode=client everywhere. Since there's no gw_mode=server, this makes bat0 never forward requests + -- so a rogue DHCP server doesn't affect whole network (DHCP requests are always answered locally) + 121 for _,proto in pairs(config.get("network", "protocols")) do + 93 if proto == "anygw" then + 17 uci:set("network", "bat0", "distributed_arp_table", "0") + 17 uci:set("network", "bat0", "gw_mode", "client") + end + end + 28 uci:save("network") + 28 lan.setup_interface("bat0", nil) + + -- enable alfred on bat0 if installed + 28 if utils.is_installed("alfred") then +*****0 uci:set("alfred", "alfred", "batmanif", "bat0") +*****0 uci:save("alfred") + end + end + + 34 function batadv.setup_interface(ifname, args) + 66 if not args["specific"] then + 60 if ifname:match("^wlan%d+.ap") then + 72 utils.log( "lime.proto.batadv.setup_interface(%s, ...) ignored", + 36 ifname ) + 36 return + end + end + + 30 utils.log("lime.proto.batadv.setup_interface(%s, ...)", ifname) + + 30 local vlanId = args[2] or "%N1" + 30 local vlanProto = args[3] or "8021ad" + 30 local nameSuffix = args[4] or "_batadv" + 30 local mtu = 1532 + + --! Unless a specific integer is passed, parse network_id (%N1) template + --! and use that number to get a vlanId between 29 and 284 for batadv + --! (to avoid overlapping with other protocols, + --! complex definition is for keeping retrocompatibility) + 30 if not tonumber(vlanId) then vlanId = 29 + (utils.applyNetTemplate10(vlanId) - 13) % 256 end + + 30 local owrtInterfaceName, _, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + + 30 local uci = config.get_uci_cursor() + 30 uci:set("network", owrtInterfaceName, "proto", "batadv_hardif") + 30 uci:set("network", owrtInterfaceName, "master", "bat0") + + 30 if ifname:match("^eth") then + --! TODO: Use DSA to check if ethernet device is capable of bigger MTU + --! reducing it +*****0 mtu = network.MTU_ETH_WITH_VLAN + end + + --! Avoid dmesg flooding caused by BLA with messages like "br-lan: + --! received packet on bat0 with own address as source address". + --! Tweak MAC address for each of the interfaces used by Batman-adv + --! 00 + Locally administered unicast .. 2 bytes from interface name + --! .. 3 bytes from main interface + 30 local id = utils.get_id(ifname) + 30 local vMacaddr = network.primary_mac(); + 30 vMacaddr[1] = "02" + 30 vMacaddr[2] = id[2] + 30 vMacaddr[3] = id[3] + 30 uci:set("network", owrtDeviceName, "macaddr", table.concat(vMacaddr, ":")) + + 30 uci:set("network", owrtDeviceName, "mtu", mtu) + 30 uci:save("network") + end + + 34 function batadv.runOnDevice(linuxDev, args) +*****0 args = args or {} +*****0 local vlanId = args[2] or "%N1" +*****0 local vlanProto = args[3] or "8021ad" + +*****0 utils.log("lime.proto.batadv.runOnDevice(%s, ...)", linuxDev) + + +*****0 local mtu = 1532 + +*****0 if not tonumber(vlanId) then +*****0 vlanId = 29 + (utils.applyNetTemplate10(vlanId) - 13) % 256 + end + +*****0 local devName = network.createVlan(linuxDev, vlanId, vlanProto) +*****0 local ifName = network.limeIfNamePrefix..linuxDev .. "_batadv" + +*****0 local ifaceConf = { + name = ifName, + proto = "batadv_hardif", + auto = "1", + device = devName, +*****0 master = "bat0" + } + +*****0 local libubus = require("ubus"); +*****0 local ubus = libubus.connect() +*****0 ubus:call('network', 'add_dynamic', ifaceConf) +*****0 ubus:call('network.interface.'..ifName, 'up', {}) + + + --! TODO: as of today ubus silently fails to properly setting up a linux network + --! device for batman ADV usage dinamycally work around it by using + --! shell commands instead +*****0 network.createStatic(devName) +*****0 utils.unsafe_shell("batctl if add "..devName) + end + + 17 return batadv + +============================================================================== +packages/lime-proto-bmx7/files/usr/lib/lua/lime/proto/bmx7.lua +============================================================================== + #!/usr/bin/lua + + 6 local network = require("lime.network") + 6 local config = require("lime.config") + 6 local fs = require("nixio.fs") + 6 local libuci = require("uci") + 6 local wireless = require("lime.wireless") + 6 local utils = require("lime.utils") + + 6 bmx7 = {} + + 6 bmx7.configured = false + 6 bmx7.f = "bmx7" + + 12 function bmx7.configure(args) + 72 if bmx7.configured then return end + 6 bmx7.configured = true + + 6 local uci = libuci:cursor() + 6 local ipv4, ipv6 = network.primary_address() + + 6 fs.writefile("/etc/config/"..bmx7.f, "") + + 6 uci:set(bmx7.f, "general", "bmx7") + 6 uci:set(bmx7.f, "general", "dbgMuteTimeout", "1000000") + 6 uci:set(bmx7.f, "general", "tunOutTimeout", "100000") + 6 uci:set(bmx7.f, "general", "configSync", "0") + 6 uci:set(bmx7.f, "general", "syslog", "0") + + 6 uci:set(bmx7.f, "main", "tunDev") + 6 uci:set(bmx7.f, "main", "tunDev", "main") + 6 uci:set(bmx7.f, "main", "tun4Address", ipv4:string()) + 6 uci:set(bmx7.f, "main", "tun6Address", ipv6:string()) + + -- If publish own IP enabled, configure tunIn + 6 local pub_own_ip = config.get_bool("network", "bmx7_publish_ownip", false) + 6 if (pub_own_ip) then +*****0 uci:set(bmx7.f, "myIP4", "tunIn") +*****0 uci:set(bmx7.f, "myIP4", "tunIn", "myIP4") +*****0 uci:set(bmx7.f, "myIP4", "network", ipv4:host():string()..'/32') +*****0 uci:set(bmx7.f, "myIP6", "tunIn") +*****0 uci:set(bmx7.f, "myIP6", "tunIn", "myIP6") +*****0 uci:set(bmx7.f, "myIP6", "network", ipv6:host():string()..'/128') + end + + -- Enable bmx7 uci config plugin + 6 uci:set(bmx7.f, "config", "plugin") + 6 uci:set(bmx7.f, "config", "plugin", "bmx7_config.so") + + -- Enable JSON plugin to get bmx7 information in json format + 6 if utils.is_installed("bmx7-json") then +*****0 uci:set(bmx7.f, "json", "plugin") +*****0 uci:set(bmx7.f, "json", "plugin", "bmx7_json.so") + end + + -- Enable topology plugin to get netjson file + 6 if utils.is_installed("bmx7-topology") then +*****0 uci:set(bmx7.f, "topology", "plugin") +*****0 uci:set(bmx7.f, "topology", "plugin", "bmx7_topology.so") + end + + -- Enable iwinfo plugin to get better link bandwidth estimation + 6 if utils.is_installed("bmx7-iwinfo") then +*****0 uci:set(bmx7.f, "iwinfo", "plugin") +*****0 uci:set(bmx7.f, "iwinfo", "plugin", "bmx7_iwinfo.so") + end + + -- Enable SMS plugin to enable sharing of small files + 6 if utils.is_installed("bmx7-sms") then +*****0 uci:set(bmx7.f, "sms", "plugin") +*****0 uci:set(bmx7.f, "sms", "plugin", "bmx7_sms.so") + end + + -- Enable tun plugin, DISCLAIMER: this must be positioned before table plugin if used. + 6 uci:set(bmx7.f, "ptun", "plugin") + 6 uci:set(bmx7.f, "ptun", "plugin", "bmx7_tun.so") + + -- Disable ThrowRules because they are broken in IPv6 with current Linux Kernel + 6 uci:set(bmx7.f, "ipVersion", "ipVersion") + 6 uci:set(bmx7.f, "ipVersion", "ipVersion", "6") + + -- Search for networks in 172.16.0.0/12 + 6 uci:set(bmx7.f, "nodes", "tunOut") + 6 uci:set(bmx7.f, "nodes", "tunOut", "nodes") + 6 uci:set(bmx7.f, "nodes", "network", "172.16.0.0/12") + + -- Search for networks in 10.0.0.0/8 + 6 uci:set(bmx7.f, "clouds", "tunOut") + 6 uci:set(bmx7.f, "clouds", "tunOut", "clouds") + 6 uci:set(bmx7.f, "clouds", "network", "10.0.0.0/8") + + -- Search for internet in the mesh cloud + 6 uci:set(bmx7.f, "inet4", "tunOut") + 6 uci:set(bmx7.f, "inet4", "tunOut", "inet4") + 6 uci:set(bmx7.f, "inet4", "network", "0.0.0.0/0") + 6 uci:set(bmx7.f, "inet4", "maxPrefixLen", "0") + + -- Search for internet IPv6 gateways in the mesh cloud + 6 uci:set(bmx7.f, "inet6", "tunOut") + 6 uci:set(bmx7.f, "inet6", "tunOut", "inet6") + 6 uci:set(bmx7.f, "inet6", "network", "::/0") + 6 uci:set(bmx7.f, "inet6", "maxPrefixLen", "0") + + -- Search for other mesh cloud announcements that have public ipv6 + 6 uci:set(bmx7.f, "publicv6", "tunOut") + 6 uci:set(bmx7.f, "publicv6", "tunOut", "publicv6") + 6 uci:set(bmx7.f, "publicv6", "network", "2000::/3") + 6 uci:set(bmx7.f, "publicv6", "maxPrefixLen", "64") + + -- Set prefered GW if defined + 6 local pref_gw = config.get("network", "bmx7_pref_gw") + 6 if (pref_gw ~= "none") then +*****0 uci:set(bmx7.f, "inet4p", "tunOut") +*****0 uci:set(bmx7.f, "inet4p", "tunOut", "inet4p") +*****0 uci:set(bmx7.f, "inet4p", "network", "0.0.0.0/0") +*****0 uci:set(bmx7.f, "inet4p", "maxPrefixLen", "0") +*****0 uci:set(bmx7.f, "inet4p", "gwName", pref_gw) +*****0 uci:set(bmx7.f, "inet4p", "rating", "1000") + +*****0 uci:set(bmx7.f, "inet6p", "tunOut") +*****0 uci:set(bmx7.f, "inet6p", "tunOut", "inet6p") +*****0 uci:set(bmx7.f, "inet6p", "network", "::/0") +*****0 uci:set(bmx7.f, "inet6p", "maxPrefixLen", "0") +*****0 uci:set(bmx7.f, "inet6p", "gwName", pref_gw) +*****0 uci:set(bmx7.f, "inet6p", "rating", "1000") + else + 6 uci:delete(bmx7.f, "inet4p", "tunOut") + 6 uci:delete(bmx7.f, "inet6p", "tunOut") + end + + 6 local hasBatadv = false + 6 local bmxOverBatdv = config.get_bool("network", "bmx7_over_batman") + 6 local hasLan = false + 66 for _,protoArgs in pairs(config.get("network", "protocols")) do + 60 local proto = utils.split(protoArgs, network.protoParamsSeparator)[1] + 60 if(proto == "lan") then hasLan = true + 54 elseif(proto == "batadv") then hasBatadv = true end + end + + 6 if config.get("network", "bmx7_over_librenet6", false) then +*****0 uci:set(bmx7.f, "librenet6", "dev") +*****0 uci:set(bmx7.f, "librenet6", "dev", "librenet6") + end + + 6 local enablePKI = config.get_bool("network", "bmx7_enable_pki") + 6 if (enablePKI) then +*****0 uci:set(bmx7.f, "general", "trustedNodesDir", "/etc/bmx7/trustedNodes") + end + + 6 if(hasLan) then + 6 uci:set(bmx7.f, "lm_net_br_lan", "dev") + 6 uci:set(bmx7.f, "lm_net_br_lan", "dev", "br-lan") + end + + 6 if(hasLan and hasBatadv and not bmxOverBatdv) then + --! Let firewall4 append a table of family 'bridge' with a chain + --! that hooks into postrouting and prevents bmx7 over batadv. + 6 local includeDir = "/usr/share/nftables.d/ruleset-post/" + 6 local fileName = "lime-proto-bmx7_bmx7-not-over-bat0.nft" + 6 fs.mkdirr(includeDir) + 12 fs.symlink( + 6 "/usr/share/lime/"..fileName, + 6 includeDir..fileName + 6 ) + else +*****0 fs.unlink( +*****0 "/usr/share/nftables.d/ruleset-post/".. + "lime-proto-bmx7_bmx7-not-over-bat0.nft" + ) + end + + 6 uci:save(bmx7.f) + + 6 uci:delete("firewall", "bmxtun") + + 6 uci:set("firewall", "bmxtun", "zone") + 6 uci:set("firewall", "bmxtun", "name", "bmx7tun") + 6 uci:set("firewall", "bmxtun", "input", "ACCEPT") + 6 uci:set("firewall", "bmxtun", "output", "ACCEPT") + 6 uci:set("firewall", "bmxtun", "forward", "ACCEPT") + 6 uci:set("firewall", "bmxtun", "mtu_fix", "1") + 6 uci:set("firewall", "bmxtun", "conntrack", "1") + 6 uci:set("firewall", "bmxtun", "device", "X7+") + 6 uci:set("firewall", "bmxtun", "family", "ipv4") + + 6 uci:save("firewall") + end + + 12 function bmx7.setup_interface(ifname, args) + 66 if not args["specific"] and + 60 ( ifname:match("^wlan%d+.ap") or ifname:match("^eth%d+") ) + 36 then return end + + 30 vlanId = tonumber(args[2]) or 18 + 30 vlanProto = args[3] or "8021ad" + 30 nameSuffix = args[4] or "_bmx7" + + 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + + 30 local uci = libuci:cursor() + 30 local mtu = config.get("network", "bmx7_mtu", "1500") + 30 uci:set("network", owrtDeviceName, "mtu", mtu) + + -- BEGIN [Workaround issue 38] + 30 if ifname:match("^wlan%d+") then + 18 local macAddr = wireless.get_phy_mac("phy"..ifname:match("%d+")) + 18 local vlanIp = { 169, 254, tonumber(macAddr[5], 16), tonumber(macAddr[6], 16) } + 18 uci:set("network", owrtInterfaceName, "proto", "static") + 18 uci:set("network", owrtInterfaceName, "ipaddr", table.concat(vlanIp, ".")) + 18 uci:set("network", owrtInterfaceName, "netmask", "255.255.255.255") + end + --- END [Workaround issue 38] + + 30 uci:save("network") + + 30 uci:set(bmx7.f, owrtInterfaceName, "dev") + 30 uci:set(bmx7.f, owrtInterfaceName, "dev", linux802adIfName) + + 30 if ifname:match("^wlan%d+") then + 18 local rateMax = config.get("network", "bmx7_wifi_rate_max", "auto") + 18 if rateMax ~= "auto" then +*****0 uci:set(bmx7.f, owrtInterfaceName, "rateMax", rateMax) + end + end + + 30 uci:save(bmx7.f) + end + + 12 function bmx7.apply() +*****0 utils.unsafe_shell("killall bmx7 ; sleep 2 ; killall -9 bmx7") +*****0 utils.unsafe_shell("bmx7") + end + + 12 function bmx7.bgp_conf(templateVarsIPv4, templateVarsIPv6) +*****0 local uci = libuci:cursor() + + -- Enable Routing Table Redistribution plugin +*****0 uci:set(bmx7.f, "table", "plugin") +*****0 uci:set(bmx7.f, "table", "plugin", "bmx7_table.so") + + -- Redistribute proto bird routes +*****0 uci:set(bmx7.f, "fromBird", "redistTable") +*****0 uci:set(bmx7.f, "fromBird", "redistTable", "fromBird") +*****0 uci:set(bmx7.f, "fromBird", "table", "254") +*****0 uci:set(bmx7.f, "fromBird", "bandwidth", "100") +*****0 uci:set(bmx7.f, "fromBird", "proto", "12") + + -- Avoid aggregation as it use lot of CPU with huge number of routes +*****0 uci:set(bmx7.f, "fromBird", "aggregatePrefixLen", "128") + + -- Disable proactive tunnels announcement as it use lot of CPU with + -- huge number of routes +*****0 uci:set(bmx7.f, "general", "proactiveTunRoutes", "0") + + -- BMX7 security features are at moment not used by LiMe, disable hop + -- by hop links signature as it consume a lot of CPU expecially in + -- setups with multiples interfaces and lot of routes like LiMe +*****0 uci:set(bmx7.f, "general", "linkSignatureLen", "0") + +*****0 uci:save(bmx7.f) + + local base_bgp_conf = [[ + protocol direct { + interface "X7*"; + } +*****0 ]] + +*****0 return base_bgp_conf + end + + 6 return bmx7 + +============================================================================== +packages/lime-proto-olsr/files/usr/lib/lua/lime/proto/olsr.lua +============================================================================== + #!/usr/bin/lua + + 6 local network = require("lime.network") + 6 local config = require("lime.config") + 6 local fs = require("nixio.fs") + 6 local libuci = require("uci") + 6 local wireless = require("lime.wireless") + 6 local utils = require("lime.utils") + 6 local ip = require("luci.ip") + + 6 local olsr = {} + + 6 olsr.configured = false + + 6 function olsr.configure(args) + 72 if olsr.configured then return end + 6 olsr.configured = true + + 6 local uci = libuci:cursor() + 6 local ipv4 = network.primary_address() + + 6 fs.writefile("/etc/config/olsrd", "") + + 6 uci:set("olsrd", "lime", "olsrd") + 6 uci:set("olsrd", "lime", "LinkQualityAlgorithm", "etx_ff") + 6 uci:set("olsrd", "lime", "IpVersion", "4") + + 6 uci:set("olsrd", "limejson", "LoadPlugin") + 6 uci:set("olsrd", "limejson", "library", "olsrd_jsoninfo.so.0.0") + 6 uci:set("olsrd", "limejson", "accept", "127.0.0.1") + + 6 uci:set("olsrd", "limehna", "Hna4") + 6 uci:set("olsrd", "limehna", "netaddr", ipv4:network():string()) + 6 uci:set("olsrd", "limehna", "netmask", ipv4:mask():string()) + + 6 uci:save("olsrd") + end + + 6 function olsr.setup_interface(ifname, args) + 66 if not args["specific"] then + 60 if ifname:match("^wlan%d+.ap") then return end + end + + 30 local vlanId = tonumber(args[2]) or 14 + 30 local vlanProto = args[3] or "8021ad" + 30 local nameSuffix = args[4] or "_olsr" + 30 local ipPrefixTemplate = args[5] or "169.254.%M5.%M6/16" + 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + 30 local macAddr = network.get_mac(utils.split(ifname, ".")[1]) + 30 local ipAddr = ip.IPv4(utils.applyMacTemplate10(ipPrefixTemplate, macAddr)) + + 30 local uci = libuci:cursor() + 30 uci:set("network", owrtInterfaceName, "proto", "static") + 30 uci:set("network", owrtInterfaceName, "ipaddr", ipAddr:host():string()) + 30 uci:set("network", owrtInterfaceName, "netmask", ipAddr:mask():string()) + 30 uci:save("network") + + 30 uci:set("olsrd", owrtInterfaceName, "Interface") + 30 uci:set("olsrd", owrtInterfaceName, "interface", owrtInterfaceName) + 30 uci:save("olsrd") + end + + + 6 return olsr + +============================================================================== +packages/lime-proto-olsr2/files/usr/lib/lua/lime/proto/olsr2.lua +============================================================================== + #!/usr/bin/lua + + 6 local network = require("lime.network") + 6 local config = require("lime.config") + 6 local fs = require("nixio.fs") + 6 local libuci = require("uci") + 6 local wireless = require("lime.wireless") + 6 local utils = require("lime.utils") + 6 local ip = require("luci.ip") + 6 olsr2 = {} + + 6 olsr2.configured = false + + 12 function olsr2.configure(args) + 72 if olsr2.configured then return end + 6 olsr2.configured = true + + 6 local uci = libuci:cursor() + 6 local ipv4, ipv6 = network.primary_address() + 6 local origInterfaceName = network.limeIfNamePrefix.."olsr_originator_lo" + + 6 fs.writefile("/etc/config/olsrd2", "") + 6 uci:set("olsrd2", "lime", "global") + 6 uci:set("olsrd2", "lime", "failfast", "no") + 6 uci:set("olsrd2", "lime", "pidfile", "/var/run/olsrd2.pid") + 6 uci:set("olsrd2", "lime", "lockfile", "/var/lock/olsrd2") + 6 uci:set("olsrd2", "lime", "olsrv2") + 6 uci:set("olsrd2", "lime", "lan", {ipv4:string(), ipv6:string()}) + 6 uci:set("olsrd2", "limelog", "log") + 6 uci:set("olsrd2", "limejson", "syslog", "true") + 6 uci:set("olsrd2", "limejson", "info", "all") + 6 uci:set("olsrd2", "limetelnet", "telnet") + 6 uci:set("olsrd2", "limetelnet", "port", "2009") + 6 uci:set("olsrd2", origInterfaceName, "interface") + 6 uci:set("olsrd2", origInterfaceName, "ifname", "loopback") + 6 uci:save("olsrd2") + + 6 uci:set("network", origInterfaceName, "interface") + 6 uci:set("network", origInterfaceName, "ifname", "@loopback") + 6 uci:set("network", origInterfaceName, "proto", "static") + 6 uci:set("network", origInterfaceName, "ipaddr", ipv4:host():string()) + 6 uci:set("network", origInterfaceName, "netmask", "255.255.255.255") + 6 uci:set("network", origInterfaceName, "ip6addr", ipv6:host():string().."/128") + 6 uci:save("network") + end + + 12 function olsr2.setup_interface(ifname, args) + 66 if not args["specific"] then + 60 if ifname:match("^wlan%d+.ap") then return end + end + 30 local vlanId = tonumber(args[2]) or 16 + 30 local vlanProto = args[3] or "8021ad" + 30 local nameSuffix = args[4] or "_olsr" + 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + 30 local uci = libuci:cursor() + + 30 uci:set("olsrd2", owrtInterfaceName, "interface") + 30 uci:set("olsrd2", owrtInterfaceName, "ifname", owrtInterfaceName) + 30 uci:save("olsrd2") + + end + + + 6 return olsr2 + +============================================================================== +packages/lime-proto-olsr6/files/usr/lib/lua/lime/proto/olsr6.lua +============================================================================== + #!/usr/bin/lua + + 6 local network = require("lime.network") + 6 local config = require("lime.config") + 6 local fs = require("nixio.fs") + 6 local libuci = require("uci") + 6 local wireless = require("lime.wireless") + 6 local utils = require("lime.utils") + 6 local ip = require("luci.ip") + + 6 local olsr = {} + + 6 olsr.configured = false + + 6 function olsr.configure(args) + 72 if olsr.configured then return end + 6 olsr.configured = true + + 6 local uci = libuci:cursor() + 6 local _, ipv6 = network.primary_address() + + 6 fs.writefile("/etc/config/olsrd6", "") + + 6 uci:set("olsrd6", "lime", "olsrd") + 6 uci:set("olsrd6", "lime", "LinkQualityAlgorithm", "etx_ff") + 6 uci:set("olsrd6", "lime", "IpVersion", "6") + + 6 uci:set("olsrd6", "limejson", "LoadPlugin") + 6 uci:set("olsrd6", "limejson", "library", "olsrd_jsoninfo.so.0.0") + 6 uci:set("olsrd6", "limejson", "accept", "::1") + + 6 uci:set("olsrd6", "limehna", "Hna6") + 6 uci:set("olsrd6", "limehna", "netaddr", ipv6:network():string()) + 6 uci:set("olsrd6", "limehna", "prefix", ipv6:prefix()) + + 6 uci:save("olsrd6") + end + + 6 function olsr.setup_interface(ifname, args) + 66 if not args["specific"] then + 60 if ifname:match("^wlan%d+.ap") then return end + end + + 30 vlanId = tonumber(args[2]) or 15 + 30 vlanProto = args[3] or "8021ad" + 30 nameSuffix = args[4] or "_olsr6" + 30 local ipPrefixTemplate = args[5] or "fc00::%M1%M2:%M3%M4:%M5%M6/64" + + 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) + 30 local macAddr = network.get_mac(utils.split(ifname, ".")[1]) + 30 local ipAddr = ip.IPv6(utils.applyMacTemplate16(ipPrefixTemplate, macAddr)) + + 30 local uci = libuci:cursor() + 30 uci:set("network", owrtInterfaceName, "proto", "static") + 30 uci:set("network", owrtInterfaceName, "ip6addr", ipv6:string()) +*****0 uci:save("network") + +*****0 uci:set("olsrd6", owrtInterfaceName, "Interface") +*****0 uci:set("olsrd6", owrtInterfaceName, "interface", owrtInterfaceName) +*****0 uci:save("olsrd6") + end + + 6 return olsr + +============================================================================== +packages/lime-proto-wan/files/usr/lib/lua/lime/proto/wan.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2014-2023 Gioacchino Mazzurco + --! Copyright (C) 2023 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + 6 local libuci = require("uci") + + 6 wan = {} + + 6 wan.configured = false + + 12 function wan.configure(args) + 6 if wan.configured then return end + 6 wan.configured = true + + 6 local uci = libuci:cursor() + 6 uci:set("network", "wan", "interface") + 6 uci:set("network", "wan", "proto", "dhcp") + 6 uci:save("network") + end + + 12 function wan.setup_interface(ifname, args) + 6 local uci = libuci:cursor() + 6 uci:set("network", "wan", "device", ifname) + 6 uci:save("network") + + --! Accepting link local traffic also on WAN should not cause hazards. + --! It is very helpful in cases where the devices have problem to the other + --! ports, to have at least an addictional way to enter for rescue operation + 6 local ALLOW_WAN_LL_SECT = "lime_allow_wan_all_link_local" + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "rule") + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "name", ALLOW_WAN_LL_SECT) + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "src", "wan") + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "family", "ipv6") + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "src_ip", "fe80::/10") + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "dest_ip", "fe80::/10") + 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "target", "ACCEPT") + 6 uci:save("firewall") + end + + 6 return wan + +============================================================================== +packages/lime-system/files/usr/bin/migrate-wifi-bands-cfg +============================================================================== + #!/usr/bin/lua + + 33 local config = require 'lime.config' + 33 local wireless = require 'lime.wireless' + 33 local uci = config.get_uci_cursor() + + 33 local uci_files = { config.UCI_COMMUNITY_NAME, config.UCI_NODE_NAME } + + local function move_modes_to_specific_band(file_name) + 66 local modes = uci:get(file_name, 'wifi', 'modes') + 66 if modes == nil then + 33 return + end + 33 local modes_2ghz = {} + 33 local modes_5ghz = {} + 121 for _, mode in pairs(modes) do + 88 local mode_name = utils.split(mode, '_')[1] + 88 local mode_band = utils.split(mode, '_')[2] + 88 if mode_band == nil or mode_band == '2ghz' then + 77 table.insert(modes_2ghz, mode_name) + end + 88 if mode_band == nil or mode_band == '5ghz' then + 22 table.insert(modes_5ghz, mode_name) + end + end + 33 modes_2ghz = utils.tableLength(modes_2ghz) > 0 and modes_2ghz or {'manual'} + 33 modes_5ghz = utils.tableLength(modes_5ghz) > 0 and modes_5ghz or {'manual'} + 33 uci:set(file_name, '2ghz', 'lime-wifi-band') + 33 uci:set(file_name, '2ghz', 'modes', modes_2ghz) + 33 uci:set(file_name, '5ghz', 'lime-wifi-band') + 33 uci:set(file_name, '5ghz', 'modes', modes_5ghz) + 33 uci:delete(file_name, 'wifi', 'modes') + end + + + local function move_restof_to_specific_band(file_name) + 66 local options = uci:get_all(file_name, 'wifi') + 66 if options == nil then +*****0 return + end + 297 for key, value in pairs(options) do + 231 local band_name = utils.split(key, '_')[2] + 231 if band_name == '2ghz' or band_name == '5ghz' then + 33 uci:set(file_name, band_name, 'lime-wifi-band') + 33 local derived_key = utils.split(key, '_')[1] + 33 uci:set(file_name, band_name, derived_key, value) + 33 uci:delete(file_name, 'wifi', key) + end + end + end + + local function migrate_file(file_name) + 66 move_modes_to_specific_band(file_name) + 66 move_restof_to_specific_band(file_name) + 66 local changes = uci:changes() + 66 if utils.tableLength(changes) > 0 then + 66 uci:commit(file_name) + end + end + + 99 for _, file_name in pairs(uci_files) do + 66 migrate_file(file_name) + end + + 33 print('migrate-wifi-modes: migration finished, now run lime-config') + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/config.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh is modular but this doesn't mean parallel, modules are executed + --! sequencially, so we don't need to worry about transactionality and all other + --! stuff that affects parrallels database, at moment we don't need parallelism + --! as this is just some configuration stuff and is not performance critical. + + 616 local fs = require("nixio.fs") + 616 local libuci = require("uci") + 616 local nixio = require "nixio" + + 616 config = {} + + 1232 function config.log(text) + 33 nixio.syslog('info', '[config] ' .. text) + end + + 616 config.uci = nil + 616 config.hooksDir = "/etc/hotplug.d/lime-config" + + 1232 function config.get_uci_cursor() + 3567 if config.uci == nil then + 616 config.uci = libuci:cursor() + end + 3567 return config.uci + end + + 1232 function config.set_uci_cursor(cursor) + 2333 config.uci = cursor + end + + 616 config.uci = config.get_uci_cursor() + + 616 config.UCI_AUTOGEN_NAME = 'lime-autogen' + 616 config.UCI_NODE_NAME = 'lime-node' + 616 config.UCI_MAC_NAME = 'lime-000000000000' + 616 config.UCI_COMMUNITY_NAME = 'lime-community' + 616 config.UCI_DEFAULTS_NAME = 'lime-defaults' + 616 config.UCI_CONFIG_NAME = config.UCI_AUTOGEN_NAME + + 1232 function config.get_config_path() + 133 return config.uci:get_confdir() .. '/' .. config.UCI_CONFIG_NAME + end + + 1232 function config.reset_node_config() + 11 config.initialize_config_file(config.UCI_NODE_NAME) + end + + 1232 function config.initialize_config_file(config_name) + 11 local lime_path = config.uci:get_confdir() .. "/" .. config_name + 11 os.execute(string.format('cp /usr/share/lime/configs/%s %s', config_name, lime_path)) + end + + 1232 function config.get(sectionname, option, fallback) + 1418 local limeconf = config.uci:get(config.UCI_CONFIG_NAME, sectionname, option) + 1418 if limeconf then return limeconf end + + 205 if ( fallback ~= nil ) then + 194 config.log("Use fallback value for "..sectionname.."."..option..": "..tostring(fallback)) + 194 return fallback + else + 11 config.log("WARNING: Attempt to access undeclared default for: "..sectionname.."."..option) + 11 config.log(debug.traceback()) + 11 return nil + end + end + + --! Execute +callback+ for each config of type +configtype+ found in + --! +/etc/config/lime-autogen+. + 1232 function config.foreach(configtype, callback) + 219 return config.uci:foreach(config.UCI_CONFIG_NAME, configtype, callback) + end + + 1232 function config.get_all(sectionname) + 379 return config.uci:get_all(config.UCI_CONFIG_NAME, sectionname) + end + + 1232 function config.get_bool(sectionname, option, default) + 233 local val = config.get(sectionname, option, default) + 233 return (val and ((val == '1') or (val == 'on') or (val == 'true') or (val == 'enabled') or (val == 1) or (val == true))) + end + + 616 config.batched = false + + 1232 function config.init_batch() + 17 config.batched = true + end + + 1232 function config.set(...) + 981 local aty = type(arg[3]) + 981 if (aty ~= "nil" and aty ~= "string" and aty ~= "table") then + 44 arg[3] = tostring(arg[3]) + end + 981 config.uci:set(config.UCI_CONFIG_NAME, unpack(arg)) + 981 if(not config.batched) then config.uci:save(config.UCI_CONFIG_NAME) end + end + + 1232 function config.delete(...) + 28 config.uci:delete(config.UCI_CONFIG_NAME, unpack(arg)) + 28 if(not config.batched) then config.uci:save(config.UCI_CONFIG_NAME) end + end + + 1232 function config.end_batch() + 17 if(config.batched) then + 17 config.uci:save(config.UCI_CONFIG_NAME) + 17 config.batched = false + end + end + + 1232 function config.autogenerable(section_name) + 45 return ( (not config.get_all(section_name)) or config.get_bool(section_name, "autogenerated") ) + end + + + --! Merge two uci files. If an option exists in both files the value of high_prio is selected + 1232 function config.uci_merge_files(high_prio, low_prio, output) + 392 local uci = config.get_uci_cursor() + + 392 local high_pt = uci:get_all(high_prio) + 392 local low_pt = uci:get_all(low_prio) + + --! populate high_prio with low_prio values that are not in high_prio + 779 for section_name, section in pairs(low_pt) do + 387 local high_section = high_pt[section_name] + 387 if high_section ~= nil then + --! copy only some attributes + 1601 for option_name, option in pairs(section) do + --! if the options starts with a dot it is not a real options it is an attribute + --! like .name, .type, .anonymous and .index + 1361 if option_name[1] ~= '.' and high_section[option_name] == nil then + 186 high_section[option_name] = option + end + end + else + 147 high_pt[section_name] = section + end + end + + --! populate output from high_prio using uci + 1001 for section_name, section in pairs(high_pt) do + 609 local section_type = section['.type'] + 609 uci:set(output, section_name, section_type) + 4824 for option_name, option in pairs(section) do + --! if the options starts with a dot it is not a real options it is an attribute + --! like .name, .type, .anonymous and .index + 4215 if option_name[1] ~= '.' then + 4215 local otype = type(option) + 4215 if (otype ~= "nil" and otype ~= "string" and otype ~= "table") then + 1218 option = tostring(option) + end + 4215 uci:set(output, section_name, option_name, option) + end + end + end + 392 uci:commit(output) + end + + 1232 function config.uci_autogen() + --! start clearing the config + 127 local f = io.open(config.get_config_path(), "w") + 127 f:write('') + 127 f:close() + + --! clean uci cache + 127 local uci = config.get_uci_cursor() + 127 uci:load(config.UCI_AUTOGEN_NAME) + + 635 for _, cfg_name in pairs({config.UCI_DEFAULTS_NAME, config.UCI_COMMUNITY_NAME, config.UCI_MAC_NAME, config.UCI_NODE_NAME}) do + 508 local cfg = io.open(config.uci:get_confdir() .. '/' .. cfg_name) + 508 if cfg ~= nil then + 381 config.uci_merge_files(cfg_name, config.UCI_AUTOGEN_NAME, config.UCI_AUTOGEN_NAME) + end + end + 127 uci:load(config.UCI_AUTOGEN_NAME) + end + + --! commit all uci changes not yet commited + 1232 function config.uci_commit_all() + 50 local uci = config.get_uci_cursor() + 83 for k, _ in pairs(uci:changes()) do + 33 assert(uci:commit(k)) + end + end + + 1232 function config.main() + --! Get mac address and set mac-based configuration file name + 6 local network = require("lime.network") + 6 local utils = require("lime.utils") + + 6 config.UCI_MAC_NAME = "lime-" .. table.concat(network.primary_mac(),"") + 6 print("Mac-config: " .. config.UCI_MAC_NAME) + + --! Populate the default template configs if lime-node and lime-community + --! are not found in /etc/config + 18 for _, cfg_name in pairs({config.UCI_COMMUNITY_NAME, config.UCI_NODE_NAME}) do + 12 local lime_path = config.uci:get_confdir() .. "/" .. cfg_name + 12 local cf = io.open(lime_path) + 12 if not cf then +*****0 config.initialize_config_file(cfg_name) + else + 12 cf:close() + end + end + 6 config.uci_autogen() + + 6 local modules_name = { "hardware_detection", "wireless", "network", "firewall", "system", + 6 "generic_config" } + + 6 if utils.isModuleAvailable("lime.wifi_unstuck_wa") then + 6 table.insert(modules_name, "wifi_unstuck_wa") + end + + 6 local modules = {} + + 48 for i, name in pairs(modules_name) do modules[i] = require("lime."..name) end + 48 for _,module in pairs(modules) do + 42 xpcall(module.clean, function(errmsg) print(errmsg) ; print(debug.traceback()) end) + end + + 48 for _,module in pairs(modules) do + 42 xpcall(module.configure, function(errmsg) print(errmsg) ; print(debug.traceback()) end) + end + + 6 for hook in fs.dir(config.hooksDir) do +*****0 local hookCmd = config.hooksDir.."/"..hook.." after" +*****0 print("executed hook:", hookCmd, os.execute(hookCmd)) + end + + 6 local cfgpath = config.get_config_path() + --! flush all config changes + 6 local uci = config.get_uci_cursor() + 6 uci:commit(config.UCI_CONFIG_NAME) + 6 local autogen_content = utils.read_file(cfgpath) or '' + 6 notice_message = ('# DO NOT EDIT THIS FILE!. This is autogenerated by lime-config!\n' .. + 6 '# Instead please edit /etc/config/lime-node and/or /etc/config/lime-community files\n' .. + 6 '# and then regenerate this file executing lime-config\n\n') + 6 utils.write_file(cfgpath, notice_message .. autogen_content) + end + + 616 return config + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/firewall.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2020 Asociación Civil Altermundi + --! Copyright (C) 2020 Gioacchino Mazzurco + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + + 6 local fs = require("nixio.fs") + 6 local utils = require("lime.utils") + 6 local config = require("lime.config") + + 6 firewall = {} + + 12 function firewall.clean() + --! There could be things to cleanup here, but we don't do it as it would + --! interfere with rules generated by network protocols, deleting them too. + --! So better we do nothing here. + end + + 12 function firewall.configure() + 6 local uci = config:get_uci_cursor() + 6 local lanIfs = {} + 12 uci:foreach("firewall", "defaults", + function(section) +*****0 uci:set("firewall", section[".name"], "input", "ACCEPT") +*****0 uci:set("firewall", section[".name"], "output", "ACCEPT") +*****0 uci:set("firewall", section[".name"], "forward", "ACCEPT") + end + ) + + 12 uci:foreach("network", "interface", + function(section) + 198 if "lan" == section[".name"] or + 192 "lm_" == string.sub(section[".name"], 1, 3) and + 168 "_if" == string.sub(section[".name"], -3) then + 156 table.insert(lanIfs, section[".name"]) + end + end + ) + + 12 uci:foreach("firewall", "zone", + function(section) +*****0 if uci:get("firewall", section[".name"], "name") == "lan" then +*****0 uci:set("firewall", section[".name"], "input", "ACCEPT") +*****0 uci:set("firewall", section[".name"], "output", "ACCEPT") +*****0 uci:set("firewall", section[".name"], "forward", "ACCEPT") +*****0 uci:set("firewall", section[".name"], "mtu_fix", "1") +*****0 uci:set("firewall", section[".name"], "network", lanIfs) + end + end + ) + end + + 6 return firewall + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/generic_config.lua +============================================================================== + #!/usr/bin/lua + + 53 local config = require("lime.config") + 53 local utils = require("lime.utils") + + 53 gen_cfg = {} + + 53 gen_cfg.ASSET_BASE_DIR = '/etc/lime-assets/' + 53 gen_cfg.NODE_ASSET_DIRNAME = 'node/' + 53 gen_cfg.COMMUNITY_ASSET_DIRNAME = 'community/' + 53 gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE = '/etc/.cfg_first_boot_already_run' + 53 gen_cfg.RUN_ASSET_AT_FIRSTBOOT = 'ATFIRSTBOOT' + 53 gen_cfg.RUN_ASSET_AT_CONFIG = 'ATCONFIG' + + 106 function gen_cfg.clean() + -- nothing to clean, but needs to be declared to comply with the API + end + + 106 function gen_cfg.configure() + 6 gen_cfg.do_generic_uci_configs() + 6 gen_cfg.do_copy_assets() + 6 if not utils.file_exists(gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE) then + 6 gen_cfg.do_run_assets(gen_cfg.RUN_ASSET_AT_FIRSTBOOT) + 6 utils.write_file(gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE, '') + end + 6 gen_cfg.do_run_assets(gen_cfg.RUN_ASSET_AT_CONFIG) + end + + + --! Generic UCI configuration from libremesh. Eg usage: + --! config generic_uci_config libremap + --! list uci_set "libremap.settings=libremap" + --! list uci_set "libremap.settings.community=our.libre.org" + --! list uci_set "libremap.settings.community_lat=-200.123" + --! list uci_set "libremap.settings.community_lon=500.9" + 106 function gen_cfg.do_generic_uci_configs() + 39 local uci = config.get_uci_cursor() + 39 local ok = true + 39 utils.log("Applying generic configs:") + 78 config.foreach("generic_uci_config", function(gen_uci_cfg) + 39 utils.log(" " .. gen_uci_cfg[".name"]) + 133 for _, v in pairs(gen_uci_cfg["uci_set"]) do + 94 if uci:set(v) ~= true then + 22 utils.log(" Error on generic config uci_set: %s", v) + 22 ok = false + end + end + end) + 39 config.uci_commit_all() + 39 utils.log("Done applying generic configs.") + 39 return ok + end + + 106 function gen_cfg.get_asset(asset) + 55 if (utils.stringStarts(asset, gen_cfg.NODE_ASSET_DIRNAME) or + 33 utils.stringStarts(asset, gen_cfg.COMMUNITY_ASSET_DIRNAME)) then + 44 local asset = gen_cfg.ASSET_BASE_DIR .. asset + 44 if utils.file_exists(asset) then + 33 return asset + end + end + end + + --! copy_asset copy an file from the assets directory into a specified path. + --! The node asset directories are /etc/lime-assets/node and /etc/lime-assets/community. + --! The community directory should contain the same files in all the community nodes. + --! + --! config copy_asset collectd + --! option asset 'community/collectd.conf' # or 'node/collectd.conf' or 'community/mynode_collectd.conf' + --! option dst '/etc/collectd.conf' + --! + + 106 function gen_cfg.do_copy_assets() + 28 local uci = config.get_uci_cursor() + 28 local ok = true + 28 utils.log("Copying assets:") + 56 config.foreach("copy_asset", function(copy_asset) + 22 local asset = copy_asset["asset"] + 22 utils.log(" %s (%s)", copy_asset[".name"], asset) + 22 local dst = copy_asset["dst"] + 22 local src = gen_cfg.get_asset(asset) + 22 if src ~= nil then + 11 local dst_dirname = dst:match("(.*/)") + 11 if not utils.file_exists(dst_dirname) then +*****0 os.execute("mkdir -p " .. utils.shell_quote(dst_dirname)) + end + + 11 src = utils.shell_quote(src) + 11 dst = utils.shell_quote(dst) + 11 os.execute('cp -dpf ' .. src .. ' ' .. dst) + else + 11 utils.log(" Error copying asset '%s': file not found.", asset) + 11 ok = false + end + end) + 28 utils.log("Done copying assets.") + 28 return ok + end + + --! Executes a file from the assets directory scheme explained in copy_asset. + --! + --! config run_asset dropbear + --! option asset 'community/dropbear.sh' + --! option when 'ATFIRSTBOOT' # ATFIRSTBOOT, ATCONFIG + --! + 106 function gen_cfg.do_run_assets(when) + 45 local uci = config.get_uci_cursor() + 45 local ok = true + 45 utils.log("Running assets on " .. when .. " :") + 90 config.foreach("run_asset", function(run_asset) + 33 local asset = run_asset["asset"] + 33 if run_asset["when"] == when then + 33 utils.log(" %s (%s)", run_asset[".name"], asset) + 33 local src = gen_cfg.get_asset(asset) + 33 if src ~= nil then + 22 local retval = os.execute("chmod +x " .. src .. "; " .. src) + 22 if retval ~= 0 then + 11 utils.log(" Warning: the asset '%s': returnen non zero status.", src) + 11 ok = false + end + else + 11 utils.log(" Error running asset '%s': file not found .", asset) + 11 ok = false + end + end + end) + 45 utils.log("Done running assets.") + 45 return ok + end + + + 53 return gen_cfg + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/hardware_detection.lua +============================================================================== + #!/usr/bin/lua + + 28 local fs = require("nixio.fs") + 28 local utils = require("lime.utils") + + + 28 local hardware_detection = {} + + 28 hardware_detection.sectionNamePrefix = "lm_hwd_" + 28 hardware_detection.search_paths = {"/usr/lib/lua/lime/hwd/*.lua"} + + --! Hardware detection module clean() + --! Call clean() from all installed submodules + 28 function hardware_detection.clean() + 18 for _,search_path in ipairs(hardware_detection.search_paths) do + 36 for hwd_module_path in fs.glob(search_path) do + 24 local module_name = "lime.hwd." .. fs.basename(hwd_module_path):sub(1,-5) + 24 if utils.isModuleAvailable(module_name) then + 24 require(module_name).clean() + end + end + end + end + + --! Hardware detection module configure() + --! Call detect_hardware() from all installed submodules + 28 function hardware_detection.configure() + 18 for _,search_path in ipairs(hardware_detection.search_paths) do + 36 for hwd_module_path in fs.glob(search_path) do + 24 local module_name = "lime.hwd." .. fs.basename(hwd_module_path):sub(1,-5) + 24 if utils.isModuleAvailable(module_name) then + 24 require(module_name).detect_hardware() + end + end + end + end + + + 28 return hardware_detection + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/mode/ap.lua +============================================================================== + #!/usr/bin/lua + + 6 local ap = {} + + 6 ap.wifi_mode="ap" + + 6 function ap.setup_radio(radio, args) + --! checks("table", "?table") + + 18 args["network"] = "lan" + 18 return wireless.createBaseWirelessIface(radio, ap.wifi_mode, nil, args) + end + + 6 return ap + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/mode/apname.lua +============================================================================== + #!/usr/bin/lua + + 6 local apname = {} + + 6 apname.wifi_mode="ap" + + 6 function apname.setup_radio(radio, args) + --! checks("table", "?table") + + 18 args["network"] = "lan" + 18 return wireless.createBaseWirelessIface(radio, apname.wifi_mode, "name", args) + end + + 6 return apname + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/mode/apup.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2024 Gioacchino Mazzurco + --! Copyright (C) 2024 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + 31 local wireless = require("lime.wireless") + + 31 local apup = {} + + 31 function apup.WIFI_MODE() +*****0 return "ap" + end + + 31 function apup.WIFI_MODE_SUFFIX() +*****0 return "up" + end + + 31 function apup.PEER_SUFFIX() + 331 return "peer" + end + + 31 function apup.setup_radio(radio, args) + --! checks("table", "?table") + +*****0 args["network"] = "lan" +*****0 args["apup"] = "1" +*****0 args["apup_peer_ifname_prefix"] = +*****0 wireless.calcIfname(radio[".name"], apup.PEER_SUFFIX(), "") + +*****0 return wireless.createBaseWirelessIface( +*****0 radio, apup.WIFI_MODE(), apup.WIFI_MODE_SUFFIX(), args ) + end + + --! TODO: port all modes to .WIFI_MODE() + 31 apup.wifi_mode="ap" + + 31 return apup + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/mode/client.lua +============================================================================== + #!/usr/bin/lua + + 11 local client = {} + + 11 client.wifi_mode="sta" + + 11 function client.setup_radio(radio, args) + --! checks("table", "?table") + 11 return wireless.createBaseWirelessIface(radio, client.wifi_mode, nil, args) + end + + 11 return client + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/mode/ieee80211s.lua +============================================================================== + #!/usr/bin/lua + + 17 local ieee80211s = {} + + 17 ieee80211s.wifi_mode="mesh" + + 17 function ieee80211s.setup_radio(radio, args) + --! checks("table", "?table") + + 73 return wireless.createBaseWirelessIface(radio, ieee80211s.wifi_mode, nil, args) + end + + 17 return ieee80211s + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/network.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2013-2024 Gioacchino Mazzurco + --! Copyright (C) 2023-2024 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + 236 network = {} + + 236 local ip = require("luci.ip") + 236 local fs = require("nixio.fs") + + 236 local config = require("lime.config") + 236 local utils = require("lime.utils") + + + 236 function network.PROTO_PARAM_SEPARATOR() return ":" end + 236 function network.PROTO_VLAN_SEPARATOR() return "_" end + 236 function network.LIME_UCI_IFNAME_PREFIX() return "lm_net_" end + + + 236 network.MTU_ETH = 1500 + 236 network.MTU_ETH_WITH_VLAN = network.MTU_ETH - 4 + + -- Deprecated use corresponding functions instead + 236 network.protoParamsSeparator=":" + 236 network.protoVlanSeparator="_" + 236 network.limeIfNamePrefix="lm_net_" + + + 472 function network.get_mac(ifname) + 11 local _, macaddr = next(network.get_own_macs(ifname)) + 11 return utils.split(macaddr, ":") + end + + --! Return a table of macs based on the interface globing filter + 472 function network.get_own_macs(interface_filter) + 110 if interface_filter == nil then + 11 interface_filter = '*' + end + + 110 local macs = {} + 110 local search_path = "/sys/class/net/" .. interface_filter .. "/address" + 678 for address_path in fs.glob(search_path) do + 568 mac = io.open(address_path):read("*l") + 568 macs[mac] = 1 + end + + 110 local result = {} + 678 for mac, _ in pairs(macs) do + 568 table.insert(result, mac) + end + 110 return result + end + + + 472 function network.assert_interface_exists(ifname) +*****0 assert( ifname ~= nil and ifname ~= "", +*****0 "network.primary_interface() could not determine ifname!" ) + +*****0 assert( fs.lstat("/sys/class/net/"..ifname), +*****0 "network.primary_interface() "..ifname.." doesn't exists!" ) + end + + 472 function network.primary_interface() + 218 local ifname = config.get("network", "primary_interface", "eth0") + 218 if ifname == "auto" then + 11 local board = utils.getBoardAsTable() + 11 ifname = board['network']['lan']['device'] + end + 218 network.assert_interface_exists(ifname) + 218 return ifname + end + + 472 function network.primary_mac() + 196 return network.get_mac(network.primary_interface()) + end + + 472 function network.generate_host(ipprefix, hexsuffix) + 164 local num = 0 + -- If it's a network prefix calculate offset to add + 164 if ipprefix:equal(ipprefix:network()) then + 164 local addr_len = ipprefix:is4() and 32 or ipprefix:is6() and 128 + 164 num = tonumber(hexsuffix,16) % 2^(addr_len - ipprefix:prefix()) + end + + 164 return ipprefix:add(num) + end + + 472 function network.primary_address(offset) + 82 local offset = offset or 0 + 82 local pm = network.primary_mac() + 82 local ipv4_template = config.get("network", "main_ipv4_address") + 82 local ipv6_template = config.get("network", "main_ipv6_address") + + 82 local ipv4_maskbits = ipv4_template:match("[^/]+/(%d+)") + 82 ipv4_template = ipv4_template:gsub("/%d-/","/") + 82 local ipv6_maskbits = ipv6_template:match("[^/]+/(%d+)") + 82 ipv6_template = ipv6_template:gsub("/%d-/","/") + + 82 ipv4_template = utils.applyMacTemplate10(ipv4_template, pm) + 82 ipv6_template = utils.applyMacTemplate16(ipv6_template, pm) + + 82 ipv4_template = utils.applyNetTemplate10(ipv4_template) + 82 ipv6_template = utils.applyNetTemplate16(ipv6_template) + + 82 local m4, m5, m6 = tonumber(pm[4], 16), tonumber(pm[5], 16), tonumber(pm[6], 16) + 82 local hexsuffix = utils.hex((m4 * 256*256 + m5 * 256 + m6) + offset) + 82 ipv4_template = network.generate_host(ip.IPv4(ipv4_template), hexsuffix) + 82 ipv6_template = network.generate_host(ip.IPv6(ipv6_template), hexsuffix) + + 82 ipv4_template:prefix(tonumber(ipv4_maskbits)) + 82 local mc = ipv4_template + --! Generated address is network address like 192.0.2.0/24 ? + 82 local invalid = ipv4_template:equal(mc:network()) and "NETWORK" + --! If anygw enabled, generated address is the one reserved for anygw like 192.0.2.1/24 ? + 82 if utils.isModuleAvailable("lime.proto.anygw") then + 22 local generalProtocols = config.get("network", "protocols") + 44 for _,protocol in pairs(generalProtocols) do + 22 if protocol == 'anygw' then +*****0 invalid = invalid or ipv4_template:equal(mc:minhost()) and "ANYGW" + break + end + end + end + --! Generated address is the broadcast address like 192.0.2.255/24 ? + 82 invalid = invalid or ipv4_template:equal(mc:broadcast()) and "BROADCAST" + 82 if invalid then + 22 ipv4_template = mc:maxhost() + 22 ipv4_template:prefix(tonumber(ipv4_maskbits)) + 44 utils.log("INVALID main_ipv4_address " ..tostring(mc).. " IDENTICAL TO RESERVED " + 22 ..invalid.. " ADDRESS. USING " ..tostring(ipv4_template)) + end + + 82 ipv6_template:prefix(tonumber(ipv6_maskbits)) + + 82 return ipv4_template, ipv6_template + end + + 472 function network.setup_rp_filter() + 17 local sysctl_file_path = "/etc/sysctl.conf"; + 17 local sysctl_options = ""; + 17 local sysctl_file = io.open(sysctl_file_path, "r"); + 34 while sysctl_file:read(0) do + 17 local sysctl_line = sysctl_file:read(); + 17 if not string.find(sysctl_line, ".rp_filter") then sysctl_options = sysctl_options .. sysctl_line .. "\n" end + end + 17 sysctl_file:close() + + 17 sysctl_options = sysctl_options .. "net.ipv4.conf.default.rp_filter=2\nnet.ipv4.conf.all.rp_filter=2\n"; + 17 sysctl_file = io.open(sysctl_file_path, "w"); + 17 if sysctl_file ~= nil then +*****0 sysctl_file:write(sysctl_options); +*****0 sysctl_file:close(); + end + end + + 472 function network.setup_dns() + 17 local cloudDomain = config.get("system", "domain") + 17 local resolvers = config.get("network", "resolvers") + + 17 local uci = config.get_uci_cursor() + 34 uci:foreach("dhcp", "dnsmasq", + function(s) +*****0 uci:set("dhcp", s[".name"], "domain", cloudDomain) +*****0 uci:set("dhcp", s[".name"], "local", "/"..cloudDomain.."/") +*****0 uci:set("dhcp", s[".name"], "expandhosts", "1") +*****0 uci:set("dhcp", s[".name"], "domainneeded", "1") + --! allow queries from non-local ips (i.e. from other clouds) +*****0 uci:set("dhcp", s[".name"], "localservice", "0") +*****0 uci:set("dhcp", s[".name"], "server", resolvers) +*****0 uci:set("dhcp", s[".name"], "confdir", "/etc/dnsmasq.d") + end + ) + 17 uci:save("dhcp") + + 17 fs.mkdir("/etc/dnsmasq.d") + end + + 472 function network.clean() + 6 utils.log("Clearing network config...") + + 6 local uci = config.get_uci_cursor() + + 6 uci:delete("network", "globals", "ula_prefix") + 6 uci:set("network", "wan", "proto", "none") + 6 uci:set("network", "wan6", "proto", "none") + + --! Delete sections generated by LiMe + local function delete_lime_section(s) + 30 if utils.stringStarts(s[".name"], network.limeIfNamePrefix) then +*****0 uci:delete("network", s[".name"]) + end + end + 6 uci:foreach("network", "interface", delete_lime_section) + 6 uci:foreach("network", "device", delete_lime_section) + 6 uci:foreach("network", "rule", delete_lime_section) + 6 uci:foreach("network", "route", delete_lime_section) + 6 uci:foreach("network", "rule6", delete_lime_section) + 6 uci:foreach("network", "route6", delete_lime_section) + + 6 uci:save("network") + + 6 if config.get_bool("network", "use_odhcpd", false) then +*****0 utils.log("Use odhcpd as dhcp server") +*****0 uci:set("dhcp", "odchpd", "maindhcp", 1) +*****0 os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd enable") + else + 6 utils.log("Disabling odhcpd") + 6 uci:set("dhcp", "odchpd", "maindhcp", 0) + 6 os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd disable") + end + + 6 utils.log("Cleaning dnsmasq") + 6 uci:foreach("dhcp", "dnsmasq", function(s) uci:delete("dhcp", s[".name"], "server") end) + 6 uci:save("dhcp") + + 6 utils.log("Disabling 6relayd...") + 6 fs.writefile("/etc/config/6relayd", "") + end + + 472 function network._get_lower(dev) +*****0 local lower_if_path = utils.unsafe_shell("ls /sys/class/net/" .. dev .. "/ | grep ^lower") +*****0 local lower_if_table = utils.split(lower_if_path, "_") +*****0 local lower_if = lower_if_table[#lower_if_table] +*****0 return lower_if and lower_if:gsub("\n", "") + end + + 472 function network._is_dsa_conduit(dev) + 111 return "reg" == fs.stat("/sys/class/net/" .. dev .. "/dsa/tagging", "type") + end + + + 472 function network.scandevices(specificIfaces) + 6 local devices = {} + 6 local wireless = require("lime.wireless") + 6 local cpu_ports = {} + 6 local board = utils.getBoardAsTable() + + 6 function dev_parser(dev) + 111 if dev == nil then +*****0 utils.log("network.scandevices.dev_parser got nil device") +*****0 return + end + + --! Avoid configuration on DSA conduit interfaces. + --! See also: + --! https://www.kernel.org/doc/html/latest/networking/dsa/dsa.html#common-pitfalls-using-dsa-setups + 111 if network._is_dsa_conduit(dev) then +*****0 utils.log( "network.scandevices.dev_parser ignored DSA conduit " .. +*****0 "device %s", dev ) +*****0 return + end + + --! Filter out ethernet ports connected to switch in a swconfig device. + 111 for cpu_port,_ in pairs(cpu_ports) do +*****0 if cpu_port == dev then +*****0 utils.log( "network.scandevices.dev_parser ignored ethernet " .. +*****0 "device %s connected to internal switch", dev ) +*****0 return + end + end + + 111 if dev:match("^eth%d+$") then + --! We only get here with devices not listed in board.json, e.g + --! pluggable ethernet dongles. +*****0 utils.log( "network.scandevices.dev_parser found plain Ethernet " .. +*****0 "device %s", dev ) + 111 elseif dev:match("^wlan%d+"..wireless.WIFI_MODE_SEPARATOR().."%w+$") then + 108 utils.log( "network.scandevices.dev_parser found WiFi device %s", + 108 dev ) + 57 elseif specificIfaces[dev] then +*****0 utils.log( "network.scandevices.dev_parser found device %s that " .. + "matches the config net section %s", dev, +*****0 specificIfaces[dev][".name"]) + else + 57 return + end + + 54 local is_dsa = utils.is_dsa(dev) + 54 devices[dev] = devices[dev] or {} + 54 devices[dev]["dsa"] = is_dsa + end + + 6 function owrt_ifname_parser(section) + 54 local ifn = section["ifname"] + 54 if ( type(ifn) == "string" ) then + 108 utils.log( "network.scandevices.owrt_ifname_parser found ifname %s", + 54 ifn ) + 54 dev_parser(ifn) + end + end + + 6 function board_port_parser(dev) + 12 local is_dsa = utils.is_dsa(dev) + 12 devices[dev] = devices[dev] or {} + 12 devices[dev]["dsa"] = is_dsa + 12 if is_dsa then +*****0 utils.log( "network.scandevices found DSA-port %s in board.json", +*****0 dev ) + else + 12 utils.log( "network.scandevices found device %s in board.json", dev ) + end + end + + --! Collect switch facing ethernet ports for swconfig devices from board.json + 6 for switch, switch_table in pairs(board["switch"] or {}) do +*****0 for _,port_table in pairs(switch_table["ports"] or {}) do +*****0 local dev = port_table["device"] +*****0 if dev then +*****0 cpu_ports[dev] = true + end + end + end + + --! Collect dsa ports and usable ethernet and vlan devices from board.json + 18 for role, role_table in pairs(board["network"] or {}) do + --! "ports" and "device" fields may be specified at the same time. + --! In this case, "ports" must be used. + 12 local ports = role_table["ports"] + 12 if ports == nil then + 6 ports = { role_table["device"] } + end + 12 local protocol = role_table["protocol"] + --! Protocol can be dhcp, static, pppoe, ncm, qmi, mbim. + --! Ethernet interfaces usually have protocol "dhcp" or "static", + --! depending on their role. + 12 if protocol == "dhcp" or protocol == "static" then + 24 for _,port in pairs(ports) do + 12 board_port_parser(port) + end + end + end + + --! Scrape from uci wireless + 6 local uci = config.get_uci_cursor() + 6 uci:foreach("wireless", "wifi-iface", owrt_ifname_parser) + + --! Scrape from /sys/class/net/ + 6 local stdOut = io.popen("ls -1 /sys/class/net/") + 63 for dev in stdOut:lines() do dev_parser(dev) end + 6 stdOut:close() + + 6 return devices + end + + 472 function network.configure() + 17 local specificIfaces = {} + + 34 config.foreach("net", function(iface) + 6 if iface["linux_name"] then + 6 specificIfaces[iface["linux_name"]] = iface + end + end) + + 17 local fisDevs = network.scandevices(specificIfaces) + + 17 network.setup_rp_filter() + + 17 network.setup_dns() + + 17 local generalProtocols = config.get("network", "protocols") + 88 for _,protocol in pairs(generalProtocols) do + 71 local protoModule = "lime.proto."..utils.split(protocol,":")[1] + 71 if utils.isModuleAvailable(protoModule) then + 59 local proto = require(protoModule) + 177 xpcall(function() proto.configure(utils.split(protocol, network.protoParamsSeparator)) end, + 118 function(errmsg) print(errmsg) ; print(debug.traceback()) end) + end + end + + --! For each scanned fisical device, if there is a specific config apply that one otherwise apply general config + 94 for device,flags in pairs(fisDevs) do + 77 local owrtIf = specificIfaces[device] + 77 local deviceProtos = generalProtocols + 77 if owrtIf then + 6 deviceProtos = owrtIf["protocols"] or {"manual"} + 6 flags["specific"] = true + 6 flags["_specific_section"] = owrtIf + end + + 748 for _,protoParams in pairs(deviceProtos) do + 671 local args = utils.split(protoParams, network.protoParamsSeparator) + 671 local protoName = args[1] + 671 if protoName == "manual" then break end -- If manual is specified do not configure interface + 671 local protoModule = "lime.proto."..protoName + 671 local needsConfig = utils.isModuleAvailable(protoModule) + 671 if protoName ~= 'lan' and not flags["specific"] then + --! Work around issue 1121. Do not configure any other + --! protocols than lime.proto.lan on dsa devices unless there + --! is a config net section for the device. + 540 needsConfig = needsConfig and not utils.is_dsa(device) + end + 671 if needsConfig then + 1163 for k,v in pairs(flags) do args[k] = v end + 539 local proto = require(protoModule) + 1617 xpcall(function() proto.configure(args) ; proto.setup_interface(device, args) end, + 1108 function(errmsg) print(errmsg) ; print(debug.traceback()) end) + end + end + end + end + + 472 function network.sanitizeIfaceName(ifName) + 528 return network.limeIfNamePrefix..ifName:gsub("[^%w_]", "_") + end + + -- Creates a network Interface with static protocol + -- ipAddr can be IPv4 or IPv6 + -- the function can be called twice to set both IPv4 and IPv6 + 472 function network.createStaticIface(linuxBaseIfname, openwrtNameSuffix, ipAddr, gwAddr) +*****0 local openwrtNameSuffix = openwrtNameSuffix or "" +*****0 local owrtInterfaceName = network.sanitizeIfaceName(linuxBaseIfname) .. openwrtNameSuffix +*****0 local uci = config.get_uci_cursor() + +*****0 uci:set("network", owrtInterfaceName, "interface") +*****0 uci:set("network", owrtInterfaceName, "proto", "static") +*****0 uci:set("network", owrtInterfaceName, "auto", "1") +*****0 uci:set("network", owrtInterfaceName, "ifname", linuxBaseIfname) + +*****0 local addr = luci.ip.new(ipAddr) +*****0 local host = addr:host():string() + +*****0 if addr:is4() then +*****0 local mask = addr:mask():string() +*****0 uci:set("network", owrtInterfaceName, "ipaddr", host) +*****0 uci:set("network", owrtInterfaceName, "netmask", mask) +*****0 if gwAddr then +*****0 uci:set("network", owrtInterfaceName, "gateway", gwAddr) + end +*****0 elseif addr:is6() then +*****0 uci:set("network", owrtInterfaceName, "ip6addr", addr:string()) +*****0 if gwAddr then +*****0 uci:set("network", owrtInterfaceName, "ip6gw", gwAddr) + end + else +*****0 uci:delete("network", owrtInterfaceName, "interface") + end + +*****0 uci:save("network") + end + + 472 function network.createVlanIface(linuxBaseIfname, vid, openwrtNameSuffix, vlanProtocol) + 213 vlanProtocol = vlanProtocol or "8021ad" + 213 openwrtNameSuffix = openwrtNameSuffix or "" + 213 vid = tonumber(vid) + + --! sanitize passed linuxBaseIfName for constructing uci section name + --! because only alphanumeric and underscores are allowed + 213 local owrtInterfaceName = network.sanitizeIfaceName(linuxBaseIfname) + 213 local owrtDeviceName = owrtInterfaceName + 213 local linux802adIfName = linuxBaseIfname + + 213 local uci = config.get_uci_cursor() + + 213 owrtInterfaceName = owrtInterfaceName..openwrtNameSuffix.."_if" + + 213 if vid ~= 0 then + 196 local vlanId = tostring(vid) + --! sanitize passed linuxBaseIfName for constructing uci section name + --! because only alphanumeric and underscores are allowed + 196 owrtDeviceName = network.sanitizeIfaceName(linuxBaseIfname)..openwrtNameSuffix.."_dev" + + 196 if linuxBaseIfname:match("^wlan") then + 119 linuxBaseIfname = "@"..network.sanitizeIfaceName(linuxBaseIfname) + end + + --! Do not use . as separator as this will make netifd create an 802.1q interface anyway + --! and sanitize linuxBaseIfName because it can contain dots as well (i.e. switch ports) + 196 linux802adIfName = linux802adIfName:gsub("[^%w-]", "-")..network.protoVlanSeparator..vlanId + + 196 uci:set("network", owrtDeviceName, "device") + 196 uci:set("network", owrtDeviceName, "type", vlanProtocol) + 196 uci:set("network", owrtDeviceName, "name", linux802adIfName) + --! This is ifname also on current OpenWrt + 196 uci:set("network", owrtDeviceName, "ifname", linuxBaseIfname) + 196 uci:set("network", owrtDeviceName, "vid", vlanId) + end + + 213 uci:set("network", owrtInterfaceName, "interface") + 213 local proto = "none" + 213 if vid == 0 then + 17 proto = "static" + end + 213 uci:set("network", owrtInterfaceName, "proto", proto) + 213 uci:set("network", owrtInterfaceName, "auto", "1") + + --! In case of wifi interface not using vlan (vid == 0) avoid to set + --! ifname in network because it is already set in wireless, because + --! setting ifname on both places cause a netifd race condition + 213 if vid ~= 0 or not linux802adIfName:match("^wlan") then + 213 uci:set("network", owrtInterfaceName, "device", linux802adIfName) + end + + 213 uci:save("network") + + 213 return owrtInterfaceName, linux802adIfName, owrtDeviceName + end + + 472 function network.createMacvlanIface(baseIfname, linuxName, argsDev, argsIf) + --! baseIfname can be a linux interface name like eth0 or an openwrt + --! interface name like @lan of the base interface; + --! linuxName is the linux name of the new interface; + --! argsDev optional additional arguments for device like + --! { macaddr="aa:aa:aa:aa:aa:aa", mode="vepa" }; + --! argsIf optional additional arguments for ifname like + --! { proto="static", ip6addr="2001:db8::1/64" } + --! + --! Although this function is defined here lime-system may not depend + --! on macvlan if it doesn't use this function directly. Instead a + --! lime.proto which want to use macvlan so this function should depend + --! on its own on kmod-macvlan as needed. + +*****0 argsDev = argsDev or {} +*****0 argsIf = argsIf or {} + +*****0 local owrtDeviceName = network.limeIfNamePrefix..baseIfname.."_"..linuxName.."_dev" +*****0 local owrtInterfaceName = network.limeIfNamePrefix..baseIfname.."_"..linuxName.."_if" + --! sanitize uci sections name +*****0 owrtDeviceName = owrtDeviceName:gsub("[^%w_]", "_") +*****0 owrtInterfaceName = owrtInterfaceName:gsub("[^%w_]", "_") + +*****0 local uci = config.get_uci_cursor() + +*****0 uci:set("network", owrtDeviceName, "device") +*****0 uci:set("network", owrtDeviceName, "type", "macvlan") +*****0 uci:set("network", owrtDeviceName, "name", linuxName) + --! This is ifname also on current OpenWrt +*****0 uci:set("network", owrtDeviceName, "ifname", baseIfname) +*****0 for k,v in pairs(argsDev) do +*****0 uci:set("network", owrtDeviceName, k, v) + end + +*****0 uci:set("network", owrtInterfaceName, "interface") +*****0 uci:set("network", owrtInterfaceName, "proto", "none") +*****0 uci:set("network", owrtInterfaceName, "device", linuxName) +*****0 uci:set("network", owrtInterfaceName, "auto", "1") +*****0 for k,v in pairs(argsIf) do +*****0 uci:set("network", owrtInterfaceName, k, v) + end + +*****0 uci:save("network") + +*****0 return owrtInterfaceName, linuxName, owrtDeviceName + end + + --! Create a static interface at runtime via ubus + 472 function network.createStatic(linuxBaseIfname) +*****0 local ipv4, ipv6 = network.primary_address() +*****0 local ubusIfaceName = network.sanitizeIfaceName( +*****0 network.LIME_UCI_IFNAME_PREFIX()..linuxBaseIfname.."_static") +*****0 local ifaceConf = { + name = ubusIfaceName, + proto = "static", + auto = "1", + ifname = linuxBaseIfname, + ipaddr = ipv4:host():string(), +*****0 netmask = "255.255.255.255" + } + +*****0 local libubus = require("ubus") +*****0 local ubus = libubus.connect() +*****0 ubus:call('network', 'add_dynamic', ifaceConf) +*****0 ubus:call('network.interface.'..ifaceConf.name, 'up', {}) + + --! TODO: As of today ubus silently fails to properly setup the interface, + --! subsequent status query return NO_DEVICE error + --! ubus -v call network.interface.lm_net_lm_net_wlan0_peer1_static status + --! { + --! "up": false, + --! "pending": false, + --! "available": false, + --! "autostart": true, + --! "dynamic": true, + --! "proto": "static", + --! "data": { + --! + --! }, + --! "errors": [ + --! { + --! "subsystem": "interface", + --! "code": "NO_DEVICE" + --! } + --! ] + --! } + --! + --! ATM work around the problem configuring IP addresses via ip command + +*****0 utils.unsafe_shell("ip link set up dev "..ifaceConf.ifname) +*****0 utils.unsafe_shell("ip address add "..ifaceConf.ipaddr.."/32 dev "..ifaceConf.ifname) + +*****0 return ifaceConf.name + end + + --! Create a vlan at runtime via ubus + 472 function network.createVlan(linuxBaseIfname, vid, vlanProtocol) +*****0 local vlanConf = { + name = linuxBaseIfname .. network.PROTO_VLAN_SEPARATOR() .. vid, + type = vlanProtocol or "8021ad", + ifname = linuxBaseIfname, +*****0 vid = vid + } + +*****0 utils.log("lime.network.createVlan(%s, ...)", linuxBaseIfname) +*****0 utils.dumptable(vlanConf) + +*****0 local libubus = require("ubus") +*****0 local ubus = libubus.connect() +*****0 ubus:call('network', 'add_dynamic_device', vlanConf) + + --! TODO: as of today ubus silently fails to properly creating a device + --! dinamycally work around it by using ip command instead +*****0 utils.unsafe_shell("ip link add name "..vlanConf.name.." link "..vlanConf.ifname.." type vlan proto 802.1ad id "..vlanConf.vid) + +*****0 return vlanConf.name + end + + --! Run protocols at runtime on top of linux network devices + -- TODO: probably some code between here and configure might be deduplicaded + 472 function network.runProtocols(linuxBaseIfname) +*****0 utils.log("lime.network.runProtocols(%s, ...)", linuxBaseIfname) +*****0 local protoConfs = config.get("network", "protocols") +*****0 for _,protoConf in pairs(protoConfs) do +*****0 local args = utils.split(protoConf, network.PROTO_PARAM_SEPARATOR()) +*****0 local protoModule = "lime.proto."..args[1] +*****0 if utils.isModuleAvailable(protoModule) then +*****0 local proto = require(protoModule) +*****0 xpcall(function() proto.runOnDevice(linuxBaseIfname, args) end, +*****0 function(errmsg) print(errmsg) ; print(debug.traceback()) end) + end + end + end + + 236 return network + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/proto/ieee80211s.lua +============================================================================== + #!/usr/bin/lua + + 6 local config = require("lime.config") + 6 local ieee80211s_mode = require("lime.mode.ieee80211s") + + 6 local ieee80211s = {} + + 6 function ieee80211s.configure(args) + end + + 6 function ieee80211s.setup_interface(ifname, args) + 66 if ifname:match("^wlan%d+."..ieee80211s_mode.wifi_mode) then + 18 local uci = config.get_uci_cursor() + + --! sanitize passed ifname for constructing uci section name + --! because only alphanumeric and underscores are allowed + 18 local networkInterfaceName = network.limeIfNamePrefix..ifname:gsub("[^%w_]", "_") + + 18 uci:set("network", networkInterfaceName, "interface") + 18 uci:set("network", networkInterfaceName, "proto", "none") + 18 uci:set("network", networkInterfaceName, "mtu", "1536") + 18 uci:set("network", networkInterfaceName, "auto", "1") + + 18 uci:save("network") + end + end + + 6 function ieee80211s.runOnDevice(linuxDev, args) end + + 6 return ieee80211s + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/proto/lan.lua +============================================================================== + #!/usr/bin/lua + + 28 lan = {} + + 28 local network = require("lime.network") + 28 local config = require("lime.config") + 28 local utils = require("lime.utils") + + 28 lan.configured = false + + --! Find a device section in network with + --! option name 'br-lan' + --! option type 'bridge' + local function find_br_lan(uci) + local br_lan_section = nil + 124 uci:foreach("network", "device", + function(s) + 40 if br_lan_section then return end + 40 local dev_type = uci:get("network", s[".name"], "type") + 40 local dev_name = uci:get("network", s[".name"], "name") + 40 if not (dev_type == 'bridge') then return end + 40 if not (dev_name == 'br-lan') then return end + 40 br_lan_section = s[".name"] + end + ) + 62 return br_lan_section + end + + 56 function lan.configure(args) + 88 if lan.configured then return end + 17 lan.configured = true + + 17 local ipv4, ipv6 = network.primary_address() + 17 local uci = config.get_uci_cursor() + 17 uci:set("network", "lan", "interface") + 17 uci:set("network", "lan", "ip6addr", ipv6:string()) + 17 uci:set("network", "lan", "ipaddr", ipv4:host():string()) + 17 uci:set("network", "lan", "netmask", ipv4:mask():string()) + 17 uci:set("network", "lan", "proto", "static") + 17 uci:set("network", "lan", "mtu", "1500") + 17 local br_lan_section = find_br_lan(uci) + 17 if br_lan_section then uci:delete("network", br_lan_section, "ports") end + 17 uci:save("network") + + -- disable bat0 on alfred if batadv not enabled + 17 if utils.is_installed("alfred") then +*****0 local is_batadv_enabled = false +*****0 local generalProtocols = config.get("network", "protocols") +*****0 for _,protocol in pairs(generalProtocols) do +*****0 local protoModule = "lime.proto."..utils.split(protocol,":")[1] +*****0 if protoModule == "lime.proto.batadv" then +*****0 is_batadv_enabled = true + break + end + end +*****0 if not is_batadv_enabled then +*****0 uci:set("alfred", "alfred", "batmanif", "none") +*****0 uci:save("alfred") + end + end + end + + 56 function lan.setup_interface(ifname, args) + 99 if ifname:match("^wlan") then return end + 45 if ifname:match(network.protoVlanSeparator.."%d+$") then return end + + 45 local uci = config.get_uci_cursor() + 45 local bridgedIfs = {} + 45 local br_lan_section = find_br_lan(uci) + 45 if not br_lan_section then return end + 23 local oldIfs = uci:get("network", br_lan_section, "ports") or {} + -- it should be a table, it was a string in old OpenWrt releases + 23 if type(oldIfs) == "string" then oldIfs = utils.split(oldIfs, " ") end + 29 for _,iface in pairs(oldIfs) do + 6 if iface ~= ifname then + 6 table.insert(bridgedIfs, iface) + end + end + 23 table.insert(bridgedIfs, ifname) + 23 uci:set("network", br_lan_section, "ports", bridgedIfs) + 23 uci:save("network") + end + + 56 function lan.bgp_conf(templateVarsIPv4, templateVarsIPv6) + local base_conf = [[ + protocol direct { + interface "br-lan"; + } +*****0 ]] +*****0 return base_conf + end + + 28 function lan.runOnDevice(linuxDev, args) end + + 28 return lan + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/system.lua +============================================================================== + #!/usr/bin/lua + + 103 local fs = require("nixio.fs") + + 103 local config = require("lime.config") + 103 local network = require("lime.network") + 103 local utils = require("lime.utils") + + + 103 system = {} + + 206 function system.get_hostname() + 42 local system_hostname = utils.applyMacTemplate16(config.get("system", "hostname"), network.primary_mac()) + 42 return utils.sanitize_hostname(system_hostname) + end + + 206 function system.set_hostname() + 6 local hostname = system.get_hostname() + 6 local uci = config.get_uci_cursor() + 6 uci:foreach("system", "system", function(s) uci:set("system", s[".name"], "hostname", hostname) end) + 6 uci:save("system") + end + + 206 function system.setup_root_password() + + 39 local policy = config.get("system", "root_password_policy") + + 39 if policy == "DO_NOTHING" then + --! nothing... + 22 elseif policy == "SET_SECRET" then + 11 local secret = config.get("system", "root_password_secret") + 11 local current_secret = utils.get_root_secret() + 11 if current_secret == nil then +*****0 error("Can't get root password") + end + 11 if current_secret ~= secret then + 11 utils.set_root_secret(secret) + end + 11 elseif policy == "RANDOM" then + --! Not having a password can be specified by the secret being empty + --! or also being '*' or '!'. So we asume there is no password set + --! in both cases. + 11 if #utils.get_root_secret() <= 1 then + 11 utils.set_password('root', utils.random_string(30)) + end + else +*****0 error('Invalid root_password_policy: ' .. policy) + end + end + + 206 function system.clean() + -- nothing to clean + end + + 206 function system.configure() + 6 utils.log("Configuring system...") + 6 system.set_hostname() + + 6 system.setup_root_password() + + 6 utils.log("Let uhttpd listen on IPv4/IPv6") + 6 local uci = config.get_uci_cursor() + 6 uci:set("uhttpd", "main", "listen_http", "80") + 6 uci:set("uhttpd", "main", "listen_https", "443") + 6 uci:set("uhttpd", "main", "max_requests", "6") + 6 uci:set("uhttpd", "main", "script_timeout", "15") + 6 uci:save("uhttpd") + end + + 206 function system.apply() + -- apply hostname + local hostname +*****0 local uci = config.get_uci_cursor() +*****0 uci:foreach("system", "system", function(s) +*****0 hostname = uci:get("system", s[".name"], "hostname") -- FIXME Doesn't we already have hostaname in s["hostname"] without executing the get ? + end) +*****0 fs.writefile("/proc/sys/kernel/hostname", hostname) + + -- apply uhttpd settings +*****0 os.execute("/etc/init.d/uhttpd reload") + end + + 103 return system + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/utils.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh community mesh networks meta-firmware + --! + --! Copyright (C) 2014-2023 Gioacchino Mazzurco + --! Copyright (C) 2023 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + 607 utils = {} + + 607 local config = require("lime.config") + 607 local json = require("luci.jsonc") + 607 local fs = require("nixio.fs") + 607 local nixio = require("nixio") + + 607 utils.BOARD_JSON_PATH = "/etc/board.json" + 607 utils.SHADOW_FILENAME = "/etc/shadow" + 607 utils.KEEP_ON_UPGRADE_FILES_BASE_PATH = '/lib/upgrade/keep.d/' + + 1214 function utils.dbg(...) + 39 local ofd = io.stderr + + 78 ofd:write( debug.getinfo(2, 'S').source, ":", + 39 debug.getinfo(2, 'l').currentline, " ", + 39 debug.getinfo(2, 'n').name ) + + 95 for n=1, select('#', ...) do + --! Assigantion needed to take only the Nth element discarding the rest + 56 local nE = select(n, ...) + 56 ofd:write(" ", nE) + end + + 39 ofd:write("\n") + end + + 1214 function utils.log(...) + 972 if DISABLE_LOGGING ~= nil then return end + 972 if os.getenv("LUA_DISABLE_LOGGING") ~= nil and os.getenv("LUA_ENABLE_LOGGING") == nil then return end + 388 print(string.format(...)) + end + + 1214 function utils.disable_logging() +*****0 DISABLE_LOGGING = 1 + end + + 1214 function utils.enable_logging() +*****0 DISABLE_LOGGING = nil + end + + 1214 function utils.split(string, sep) + 2910 local ret = {} + 7628 for token in string.gmatch(string, "[^"..sep.."]+") do table.insert(ret, token) end + 2910 return ret + end + + 1214 function utils.stringStarts(string, start) + 236 return (string.sub(string, 1, string.len(start)) == start) + end + + 1214 function utils.stringEnds(string, _end) + 139 return ( _end == '' or string.sub( string, -string.len(_end) ) == _end) + end + + 1214 function utils.hex(x) + 82 return string.format("%02x", x) + end + + 1214 function utils.printf(fmt, ...) +*****0 print(string.format(fmt, ...)) + end + + --! escape the magic characters: ( ) . % + - * ? [ ] ^ $ + --! useful to use with gsub / match when finding exactly a string + 1214 function utils.literalize(str) + 1327 local ret, _ = str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end) + 600 return ret + end + + 1214 function utils.isModuleAvailable(name) + 944 if package.loaded[name] then + 568 return true + else + 1811 for _, searcher in ipairs(package.searchers or package.loaders) do + 1574 local loader = searcher(name) + 1574 if type(loader) == 'function' then + 139 package.preload[name] = loader + 139 return true + end + end + 237 return false + end + end + + 1214 function utils.applyMacTemplate16(template, mac) + 1827 for i=1,6,1 do template = template:gsub("%%M"..i, mac[i]) end + 261 local macid = utils.get_id(mac) + 1827 for i=1,6,1 do template = template:gsub("%%m"..i, macid[i]) end + 261 return template + end + + 1214 function utils.applyMacTemplate10(template, mac) + 784 for i=1,6,1 do template = template:gsub("%%M"..i, tonumber(mac[i], 16)) end + 112 local macid = utils.get_id(mac) + 784 for i=1,6,1 do template = template:gsub("%%m"..i, tonumber(macid[i], 16)) end + 112 return template + end + + 1214 function utils.applyHostnameTemplate(template) + 107 local system = require("lime.system") + 107 return template:gsub("%%H", system.get_hostname()) + end + + 1214 function utils.get_id(input) + 849 if type(input) == "table" then + 373 input = table.concat(input, "") + end + 849 local id = {} + 849 local fd = io.popen('echo "' .. input .. '" | md5sum') + 849 if fd then + 849 local md5 = fd:read("*a") + 849 local j = 1 + 14433 for i=1,16,1 do + 13584 id[i] = string.sub(md5, j, j + 1) + 13584 j = j + 2 + end + 849 fd:close() + end + 849 return id + end + + 1214 function utils.network_id() + 194 local network_essid = config.get("wifi", "ap_ssid") + 194 return utils.get_id(network_essid) + end + + 1214 function utils.applyNetTemplate16(template) + 82 local netid = utils.network_id() + 574 for i=1,6,1 do template = template:gsub("%%N"..i, netid[i]) end + 82 return template + end + + 1214 function utils.applyNetTemplate10(template) + 112 local netid = utils.network_id() + 784 for i=1,6,1 do template = template:gsub("%%N"..i, tonumber(netid[i], 16)) end + 112 return template + end + + + --! This function is inspired to http://lua-users.org/wiki/VarExpand + --! version: 0.0.1 + --! code: Ketmar // Avalon Group + --! licence: public domain + --! expand $var and ${var} in string + --! ${var} can call Lua functions: ${string.rep(' ', 10)} + --! `$' can be screened with `\' + --! `...': args for $ + --! if `...' is just a one table -- take it as args + 1214 function utils.expandVars(s, ...) +*****0 local args = {...} + args = #args == 1 and type(args[1]) == "table" and args[1] or args; + + --! return true if there was an expansion + local function DoExpand(iscode) +*****0 local was = false +*****0 local mask = iscode and "()%$(%b{})" or "()%$([%a%d_]*)" +*****0 local drepl = iscode and "\\$" or "\\\\$" + s = s:gsub(mask, + function(pos, code) +*****0 if s:sub(pos-1, pos-1) == "\\" then +*****0 return "$"..code + else +*****0 was = true + local v, err +*****0 if iscode then +*****0 code = code:sub(2, -2) + else +*****0 local n = tonumber(code) +*****0 if n then +*****0 v = args[n] + else +*****0 v = args[code] + end + end +*****0 if not v then +*****0 v, err = loadstring("return "..code) +*****0 if not v then error(err) end +*****0 v = v() + end +*****0 if v == nil then v = "" end +*****0 v = tostring(v):gsub("%$", drepl) +*****0 return v + end + end) +*****0 if not (iscode or was) then s = s:gsub("\\%$", "$") end +*****0 return was + end +*****0 repeat DoExpand(true); until not DoExpand(false) +*****0 return s + end + + --! return a string that can be a part of an url + 1214 function utils.slugify(s) + 110 s = s:gsub('[^-a-zA-Z0-9]', '-') + 110 return s + end + + 1214 function utils.hostname() + 624 return io.input("/proc/sys/kernel/hostname"):read("*line") + end + + 1214 function utils.sanitize_hostname(hostname) + 42 hostname = hostname:gsub(' ', '-') + 42 hostname = hostname:gsub('[^-a-zA-Z0-9]', '') + 42 hostname = hostname:gsub('^-*', '') + 42 hostname = hostname:gsub('-*$', '') + 42 hostname = hostname:sub(1, 32) + 42 return hostname + end + + --! validate that a hostname is also DNS valid + 1214 function utils.is_valid_hostname(hostname) + 155 if hostname and (#hostname < 64) and + 155 hostname:match("^[a-zA-Z0-9][a-zA-Z0-9%-]*[a-zA-Z0-9]$") + then + 39 return true + end + 116 return false + end + + 1214 function utils.file_exists(name) + 561 local f=io.open(name,"r") + 561 if f~=nil then io.close(f) return true else return false end + end + + 1214 function utils.read_file(name) + 184 local f = io.open(name,"r") + 184 local ret = nil + 184 if f ~= nil then + 156 ret = f:read("*all") + 156 f:close() + end + 184 return ret + end + + 1214 function utils.write_file(name, content) + 342 local f = io.open(name, "w") + 342 local ret = false + 342 if f ~= nil then + 336 f:write(content) + 336 f:close() + 336 ret = true + end + 342 return ret + end + + 1214 function utils.is_installed(pkg) + 97 return utils.file_exists('/usr/lib/opkg/info/'..pkg..'.control') + end + + 1214 function utils.has_value(tab, val) + 249 for index, value in ipairs(tab) do + 202 if value == val then + 125 return true + end + end + 47 return false + end + + --! contact array a2 to the end of array a1 + 1214 function utils.arrayConcat(a1,a2) +*****0 for _,i in ipairs(a2) do +*****0 table.insert(a1,i) + end +*****0 return a1 + end + + --! melt table t1 into t2, if keys exists in both tables use value of t2 + 1214 function utils.tableMelt(t1, t2) + 1438 for key, value in pairs(t2) do + 1108 t1[key] = value + end + 330 return t1 + end + + 1214 function utils.tableLength(t) + 349 local count = 0 + 761 for _ in pairs(t) do count = count + 1 end + 339 return count + end + + 1214 function utils.indexFromName(name) + 336 return tonumber(name:match("%d+")) + end + + 1214 function utils.getBoardAsTable(board_path) + 11 if board_path == nil then +*****0 board_path = utils.BOARD_JSON_PATH + end + 11 return json.parse(fs.readfile(board_path)) + end + + 1214 function utils.current_board() + 11 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") + end + + 1214 function utils.printJson(obj) + 80 print(json.stringify(obj)) + end + + --! use rpcd_readline() in libexec/rpcd/ scripts to access the arguments that + --! are passed through stdin. The use of this functions allows testing. + 1214 function utils.rpcd_readline() +*****0 return io.read() + end + + --! for testing only + 607 utils._uptime_line = nil + + 1214 function utils.uptime_s() + 66 local uptime_line = utils._uptime_line or io.open("/proc/uptime"):read("*l") + 66 return tonumber(string.match(uptime_line, "^%S+")) + end + + --! Escape strings for safe shell usage. + 1214 function utils.shell_quote(s) + --! Based on Python's shlex.quote() + 88 return "'" .. string.gsub(s, "'", "'\"'\"'") .. "'" + end + + --! Excutes a shell command, waits for completion and returns stdout. + --! Warning! Use this function carefully as it could be exploited if used with + --! untrusted input. Always use function utils.shell_quote() to escape untrusted + --! input. + 1214 function utils.unsafe_shell(command) + 235 local handle = io.popen(command) + 235 local result = handle:read("*a") + 230 handle:close() + 230 return result + end + + --! based on luci.sys.setpassword + 1214 function utils.set_password(username, password) + 11 local user = utils.shell_quote(username) + 11 local pass = utils.shell_quote(password) + 22 return os.execute(string.format("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1", + 11 pass, pass, user)) + end + + 1214 function utils.get_root_secret() + 39 local f = io.open(utils.SHADOW_FILENAME, "r") + 39 if f ~= nil then + 33 local root_line = f:read("*l") --! root user is always in the first line + 33 local secret = root_line:match("root:(.-):") + 33 return secret + end + end + + 1214 function utils.set_root_secret(secret) + 22 local f = io.open(utils.SHADOW_FILENAME, "r") + 22 if f ~= nil then + --! perform a backup of the shadow + 22 local f_bkp = io.open(utils.SHADOW_FILENAME .. "-", "w") + 22 f_bkp:write(f:read("*a")) + 22 f:seek("set") + 22 f_bkp:close() + + 22 local root_line = f:read("*l") --! root user is always in the first line + 22 local starts, ends = string.find(root_line, "root:.-:") + 22 local content = "root:" .. secret .. root_line:sub(ends) .. "\n" + 22 content = content .. f:read("*a") + 22 f:close() + 22 f = io.open(utils.SHADOW_FILENAME, "w") + 22 f:write(content) + 22 f:close() + end + end + + 1214 function utils.set_shared_root_password(password) + 28 local uci = config.get_uci_cursor() + 28 utils.set_password('root', password) -- this takes 1 second, it may be replaced with nixio.crypt(password, '$1$vv44cu1H') + 28 uci:set("lime-community", 'system', 'root_password_policy', 'SET_SECRET') + 28 uci:set("lime-community", 'system', 'root_password_secret', utils.get_root_secret()) + end + + --! returns a random string. filter is an optional function to reduce the possible characters. + --! by default the filter allows all the alphanumeric characters + 1214 function utils.random_string(length, filter) + 737 if filter == nil then + --! all alphanumeric characters + 13256 filter = function (c) return string.match(c, "%w") ~= nil end + end + 737 local urandom = io.open("/dev/urandom", "rb") + 737 local out = "" + 30257 while length > #out do + 29520 local c = urandom:read(1) + 29520 if filter(c) then + 4642 out = out .. c + end + end + 737 return out + end + + --! Return a list of files to keep when upgrading. Existence of the files is not guaranteed. + 1214 function utils.keep_on_upgrade_files() + 11 local file_lists = config.get("system", "keep_on_upgrade", "") + 11 local files = {} + 44 for _, list in pairs(utils.split(file_lists, " ")) do + --! convert non absolute paths to absolute + 33 if not utils.stringStarts(list, "/") then + 22 list = utils.KEEP_ON_UPGRADE_FILES_BASE_PATH .. list + end + 33 if utils.file_exists(list) then + 77 for file_name in io.lines(list) do + --! exclude comments, blank lines, etc + 55 if utils.stringStarts(file_name, "/") then + 33 table.insert(files, file_name) + end + end + end + end + 11 return files + end + + --! Run a command in a shell and daemonized directly in pid 1 (similar to diswon/nohup) + --! If out_file optional arg is passed then stdout and stderr will go to that file. + 1214 function utils.execute_daemonized(cmd, out_file, stdin) + 23 if out_file == nil then + 11 out_file = "/dev/null" + end + 23 if not stdin then + 11 stdin = "0<&-" -- closing standard input + else + 12 stdin = "0<" .. stdin + end + + 23 return os.execute("(( " .. cmd .. " " .. stdin .. " &> " .. out_file .. " &) &)") + end + + 1214 function utils.bitwise_xor(a, b) + 66 local r = 0 + 2178 for i = 0, 31 do + 2112 local x = a / 2 + b / 2 + 2112 if x ~= math.floor(x) then + 209 r = r + 2^i + end + 2112 a = math.floor(a / 2) + 2112 b = math.floor(b / 2) + end + 66 return r + end + + 1214 function utils.mac2ipv6linklocal(text) + 66 function f(mac0, mac1, mac2, mac3, mac4, mac5, mac6) + 66 local mac0 = string.format("%x", utils.bitwise_xor(tonumber(mac0, 16), 0x02)) + --! going from and to string to remove leading zeroes and convert to lowercase + 66 local gr1 = string.format("%x", tonumber(mac0 .. mac1, 16)) + 66 local gr2 = string.format("%x", tonumber(mac2 .. 'ff', 16)) + 66 local gr3 = string.format("%x", tonumber('fe' .. mac3, 16)) + 66 local gr4 = string.format("%x", tonumber(mac4 .. mac5, 16)) + 66 return 'fe80::' .. gr1 .. ":" .. gr2 .. ":" .. gr3 .. ":" .. gr4 + end + 66 local ret, _ = string.gsub(text, "(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)", f) + 66 return ret + end + + --! do a HTTP GET returning the body or nil. If out_file is provided then the body is saved + --! to this file and true is returned instead + 1214 function utils.http_client_get(url, timeout_s, out_file) + 44 local remove_file = false + 44 if not out_file then + 22 remove_file = true + 22 out_file = os.tmpname() + end + 88 local cmd = string.format("uclient-fetch -q -O %s --timeout=%d %s 2> /dev/null", out_file, + 44 timeout_s, url) + 44 local exit_value = os.execute(cmd) + 44 if exit_value == 0 then +*****0 if remove_file then +*****0 local data = utils.read_file(out_file) +*****0 os.execute("rm -f " .. out_file) +*****0 return data + else +*****0 return true + end + else + 44 return nil + end + end + + 1214 function utils.release_info() + 34 local result = {} + 34 local release_data = utils.read_file("/etc/openwrt_release") + 195 for key, value in release_data:gmatch("(.-)='(%C-)'\n") do + 161 result[key] = value + end + 23 return result + end + + 1214 function utils.open_with_lock(fname, max_wait_s) + 710 max_wait_s = max_wait_s or 1 + 710 local locked = false + + 710 local fd = nixio.open(fname, nixio.open_flags("rdwr", "creat") ) + 710 if not fd then +*****0 return nil, "Can't create file" + end + + 710 for i=0,max_wait_s do + 710 if not fd:lock("tlock") then +*****0 nixio.nanosleep(1) + else + 710 locked = true + 710 break + end + end + + 710 if not locked then +*****0 if fd then fd:close() end +*****0 return nil, "Failed acquiring lock" + end + + 710 return fd + end + + --! Object store database. Uses json as on disk format. Uses posix file locks to prevent corruption, + --! be aware that this mechanism does only works between processes and not beteen threads or same thread. + 1214 function utils.read_obj_store(datafile) + 349 local fd = utils.open_with_lock(datafile) + local store + 349 if fd then + 349 store = json.parse(nixio.fs.readfile(datafile)) or {} + 349 fd:close() + end + 349 return store + end + + 1214 function utils.write_obj_store_var(datafile, name, data) + 123 local fd = utils.open_with_lock(datafile) + local store + 123 if fd then + 123 store = json.parse(nixio.fs.readfile(datafile)) or {} + 123 fd:seek(0) + 123 store[name] = data + 123 fd:write(json.stringify(store)) + 123 fd:close() + end + 123 return store + end + + 1214 function utils.write_obj_store(datafile, data) + 216 local fd = utils.open_with_lock(datafile) + 216 if fd then + 216 fd:write(json.stringify(data)) + 216 fd:close() + 216 return true + end + end + + 1214 function utils.get_ifnames() +*****0 local ifnames = {} +*****0 for ifname in fs.dir("/sys/class/net/") do +*****0 table.insert(ifnames, ifname) + end +*****0 return ifnames + end + + 1214 function utils.is_valid_mac(string) + 33 local string = string:match("%w%w:%w%w:%w%w:%w%w:%w%w:%w%w") + 33 if string then + 33 return true + else +*****0 return false + end + end + + --! TODO: Better having a C strcmp/memcmp like behavior so the output can be + --! used for sorting beyond determining equality + 1214 function utils.deepcompare(t1,t2) + 15513 if t1 == t2 then return true end + 3417 local ty1 = type(t1) + 3417 local ty2 = type(t2) + 3417 if ty1 ~= ty2 then return false end + 3417 if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end + 11036 for k1, v1 in pairs(t1) do + 7733 local v2 = t2[k1] + 7733 if v2 == nil or not utils.deepcompare(v1, v2) then return false end + end + 10946 for k2, v2 in pairs(t2) do + 7643 local v1 = t1[k2] + 7643 if v1 == nil or not utils.deepcompare(v1, v2) then return false end + end + 3303 return true + end + + 1214 function utils.is_dsa(port) + --! Code adapted from Jow https://forum.openwrt.org/t/how-to-detect-dsa/111868/4 + 553 port = port or "*" + 553 return 0 == os.execute("grep -sq DEVTYPE=dsa /sys/class/net/"..port.."/uevent") + end + + 1214 function utils.dumptable(table, nesting) +*****0 local nesting = nesting or 1 +*****0 if type(table) ~= "table" then +*****0 print("dumptable: first argument is expected to be a table but you passed a", type(table), table) + else +*****0 if next(table) == nil then +*****0 print(table, "empty") + else +*****0 for k,v in pairs(table) do +*****0 print(string.rep('\t', nesting), k, ' = ', v) +*****0 if type(v) == 'table' then dumptable(v, nesting+1) end + end + end + end + end + + 607 return utils + +============================================================================== +packages/lime-system/files/usr/lib/lua/lime/wireless.lua +============================================================================== + #!/usr/bin/lua + + 194 local config = require("lime.config") + 194 local network = require("lime.network") + 194 local utils = require("lime.utils") + 194 local fs = require("nixio.fs") + 194 local iwinfo = require("iwinfo") + + 194 wireless = {} + + 194 wireless.limeIfNamePrefix="lm_" + + 388 function wireless.WIFI_MODE_SEPARATOR() + 231 return "-" + end + + 388 function wireless.get_phy_mac(phy) +*****0 local path = "/sys/class/ieee80211/"..phy.."/macaddress" +*****0 local mac = assert(fs.readfile(path), "wireless.get_phy_mac(..) failed reading: "..path):gsub("\n","") +*****0 return utils.split(mac, ":") + end + + 388 function wireless.clean() + 6 utils.log("Clearing wireless config...") + 6 local uci = config.get_uci_cursor() + 24 uci:foreach("wireless", "wifi-iface", function(s) uci:delete("wireless", s[".name"]) end) + 6 uci:save("wireless") + end + + 388 function wireless.scandevices() + 138 local devices = {} + 138 local uci = config.get_uci_cursor() + 310 uci:foreach("wireless", "wifi-device", function(dev) devices[dev[".name"]] = dev end) + + 138 local sorted_devices = {} + 310 for _, dev in pairs(devices) do + 172 table.insert(sorted_devices, utils.indexFromName(dev[".name"])+1, dev) + end + + 138 local band_2ghz_index = 0 + 138 local band_5ghz_index = 0 + + 310 for _, dev in pairs(sorted_devices) do + 172 if wireless.is5Ghz(dev[".name"]) then + 155 dev.per_band_index = band_5ghz_index + 155 band_5ghz_index = band_5ghz_index + 1 + else + 17 dev.per_band_index = band_2ghz_index + 17 band_2ghz_index = band_2ghz_index + 1 + end + end + 138 return devices + end + + 388 function wireless.is5Ghz(radio) + 311 local config = require("lime.config") + 311 local uci = config.get_uci_cursor() + 311 wifi_band = uci:get('wireless', radio, 'band') + 311 if wifi_band then return wifi_band=='5g' end + 55 wifi_channel = uci:get('wireless', radio, 'channel') + 55 if tonumber(wifi_channel) then +*****0 wifi_channel = tonumber(wifi_channel) +*****0 return 32<=wifi_channel and wifi_channel<178 + end + 55 local backend = iwinfo.type(radio) + 55 local devModes = backend and iwinfo[backend].hwmodelist(radio) + 55 return devModes and (devModes.a or devModes.ac) + end + + 194 wireless.availableModes = { adhoc=true, ap=true, apname=true, apbb=true, ieee80211s=true } + 388 function wireless.isMode(m) + 519 return wireless.availableModes[m] + end + + 388 function wireless.is_mesh(iface) + 52 return iface.mode == 'mesh' or iface.mode == 'adhoc' + end + + 388 function wireless.mesh_ifaces() + 43 local uci = config.get_uci_cursor() + 43 local ifaces = {} + + 86 uci:foreach("wireless", "wifi-iface", function(entry) + 56 if entry.disabled ~= '1' and wireless.is_mesh(entry) then + 34 table.insert(ifaces, entry.ifname) + end + end) + --add apup interfaces + --this are not listed in uci + 43 local shell_output = utils.unsafe_shell("ls /sys/class/net/ -R") + 43 if shell_output ~= nil then + 374 for line in shell_output:gmatch("[^\n]+") do + -- Check if the line contains the pattern 'wlanX-peerY' + 331 local apup = require("lime.mode.apup") + 331 local iface = line:match("wlan(%d+)-"..apup.PEER_SUFFIX().."(%d+)$") + 331 if iface then + -- Add the matched interface to the table +*****0 table.insert(ifaces, line) + end + end + end + 43 return ifaces + end + + 388 function wireless.get_radio_ifaces(radio) + 42 local uci = config.get_uci_cursor() + 42 local ifaces = {} + + 84 uci:foreach("wireless", "wifi-iface", function(entry) + 18 if entry.disabled ~= '1' and entry.device == radio then + 18 table.insert(ifaces, entry) + end + end) + 42 return ifaces + end + + 388 function wireless.calcIfname(radioName, mode, nameSuffix) + 120 local phyIndex = tostring(utils.indexFromName(radioName)) + 120 return "wlan"..phyIndex..wireless.WIFI_MODE_SEPARATOR()..mode..nameSuffix + end + + 388 function wireless.createBaseWirelessIface(radio, mode, nameSuffix, extras) + --! checks("table", "string", "?string", "?table") + --! checks(...) come from http://lua-users.org/wiki/LuaTypeChecking -> https://github.com/fab13n/checks + 120 nameSuffix = nameSuffix or "" + 120 local radioName = radio[".name"] + 120 local ifname = wireless.calcIfname(radioName, mode, nameSuffix) + --! sanitize generated ifname for constructing uci section name + --! because only alphanumeric and underscores are allowed + 120 local wirelessInterfaceName = wireless.limeIfNamePrefix..ifname:gsub("[^%w_]", "_").."_"..radioName + 120 local networkInterfaceName = network.limeIfNamePrefix..ifname:gsub("[^%w_]", "_") + + 120 local uci = config.get_uci_cursor() + + 120 uci:set("wireless", wirelessInterfaceName, "wifi-iface") + 120 uci:set("wireless", wirelessInterfaceName, "mode", mode) + 120 uci:set("wireless", wirelessInterfaceName, "device", radioName) + 120 uci:set("wireless", wirelessInterfaceName, "ifname", ifname) + 120 uci:set("wireless", wirelessInterfaceName, "network", networkInterfaceName) + + 120 if extras then + 333 for key, value in pairs(extras) do + 213 uci:set("wireless", wirelessInterfaceName, key, value) + end + end + + 120 uci:save("wireless") + + 120 return uci:get_all("wireless", wirelessInterfaceName) + end + + 388 function wireless.resolve_ssid(ssid) + 107 local result = utils.applyHostnameTemplate(ssid) + 107 result = utils.applyMacTemplate16(result, network.primary_mac()) + 107 result = string.sub(result, 1, 32) + 107 return result + end + + 388 function wireless.configure() + 61 local specificRadios = {} + 122 config.foreach("wifi", function(radio) + 22 specificRadios[radio[".name"]] = radio + end) + + 61 local allRadios = wireless.scandevices() + 145 for _,radio in pairs(allRadios) do + 84 local radioName = radio[".name"] + 84 local radioBand = wireless.is5Ghz(radioName) and '5ghz' or '2ghz' + 84 local radioOptions = specificRadios[radioName] or {} + 84 local bandOptions = config.get_all(radioBand) or {} + 84 local options = config.get_all("wifi") + + 84 options = utils.tableMelt(options, bandOptions) + 84 options = utils.tableMelt(options, radioOptions) + + --! If manual mode is used toghether with other modes it results in an + --! unpredictable behaviour + 84 if options["modes"][1] ~= "manual" then + --! fallback to "auto" in client mode + local channel + 84 if options["modes"][1] ~= "client" then + 73 channel = options["channel"] + 73 if type(channel) == "table" then + 34 channel = channel[1 + radio.per_band_index % #channel] + end + else + 11 channel = options["channel"] or "auto" + end + + 84 local uci = config.get_uci_cursor() + 84 uci:set("wireless", radioName, "disabled", 0) + 84 uci:set("wireless", radioName, "distance", options["distance"]) + 84 uci:set("wireless", radioName, "noscan", 1) + 84 uci:set("wireless", radioName, "channel", channel) + 84 if options["country"] then uci:set("wireless", radioName, "country", options["country"]) end + 84 if options["legacy_rates"] and not wireless.is5Ghz(radioName) then uci:set("wireless", radioName, "legacy_rates", options["legacy_rates"]) end + 84 if options["txpower"] then uci:set("wireless", radioName, "txpower", options["txpower"]) end + 84 if options["htmode"] then uci:set("wireless", radioName, "htmode", options["htmode"]) end + 84 uci:save("wireless") + + 204 for _,modeName in pairs(options["modes"]) do + 120 local args = {} + 120 local mode = require("lime.mode."..modeName) + + -- gather mode specific configs (eg ieee80211s_mcast_rate_5ghz) + 1576 for key,value in pairs(options) do + 1456 local keyPrefix = utils.split(key, "_")[1] + 1456 local isGoodOption = ( (key ~= "modes") + 1336 and (not key:match("^%.")) + 1336 and (not key:match("channel")) + 954 and (not key:match("country")) + 845 and (not key:match("legacy_rates")) + 823 and (not key:match("txpower")) + 823 and (not key:match("htmode")) + 801 and (not key:match("distance")) + 747 and (not key:match("unstuck_interval")) + 627 and (not key:match("unstuck_timeout")) + 1456 and (not (wireless.isMode(keyPrefix) and keyPrefix ~= modeName))) + 1456 if isGoodOption then + 177 local nk = key:gsub("^"..modeName.."_", "") + 177 if nk == "ssid" then + 47 value = wireless.resolve_ssid(value) + end + 177 args[nk] = value + end + end + + 120 mode.setup_radio(radio, args) + end + end + end + end + + 388 function wireless.get_band_config(band) + 72 local general_cfg = config.get_all("wifi") or {} + 72 local band_cfg = config.get_all(band) or {} + 72 local result = general_cfg + 72 utils.tableMelt(result, band_cfg) + 72 return result + end + + 388 function wireless.get_community_band_config(band) + 30 local uci = config.get_uci_cursor() + 30 local default_general_cfg = uci:get_all(config.UCI_DEFAULTS_NAME, "wifi") or {} + 30 local default_band_cfg = uci:get_all(config.UCI_DEFAULTS_NAME, band) or {} + 30 local community_general_cfg = uci:get_all(config.UCI_COMMUNITY_NAME, "wifi") or {} + 30 local community_band_cfg = uci:get_all(config.UCI_COMMUNITY_NAME, band) or {} + 30 local result = default_general_cfg + 30 utils.tableMelt(result, default_band_cfg) + 30 utils.tableMelt(result, community_general_cfg) + 30 utils.tableMelt(result, community_band_cfg) + 30 return result + end + + 388 function wireless.add_band_mode(band, mode_name) + 6 local uci = config.get_uci_cursor() + 6 local cfg = wireless.get_band_config(band) + 6 if not utils.has_value(cfg.modes, mode_name) then + 6 local modes = uci:get(config.UCI_NODE_NAME, band, 'modes') + 6 if not modes or modes[1] == 'manual' then + 6 modes = { mode_name } + else +*****0 table.insert(modes, mode_name) + end + 6 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') + 6 uci:set(config.UCI_NODE_NAME, band, 'modes', modes) + 6 uci:commit(config.UCI_NODE_NAME) + 6 utils.unsafe_shell('lime-config') + end + end + + 388 function wireless.remove_band_mode(band, mode_name) + 6 local uci = config.get_uci_cursor() + 6 local cfg = wireless.get_band_config(band) + 6 if utils.has_value(cfg.modes, mode_name) then + 6 local new_modes = {} + 18 for _, mode in pairs(cfg.modes) do + 12 if mode ~= mode_name then + 6 table.insert(new_modes, mode) + end + end + 6 if utils.tableLength(new_modes) == 0 then +*****0 new_modes = {'manual'} + end + 6 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') + 6 uci:set(config.UCI_NODE_NAME, band, 'modes', new_modes) + 6 uci:commit(config.UCI_NODE_NAME) + 6 utils.unsafe_shell('lime-config') + end + end + + 388 function wireless.set_band_config(band, cfg) + 12 local uci = config.get_uci_cursor() + 12 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') + 30 for key, value in pairs(cfg) do + 18 uci:set(config.UCI_NODE_NAME, band, key, value) + end + 12 uci:commit(config.UCI_NODE_NAME) + 12 utils.unsafe_shell('lime-config') + end + + 194 return wireless + +============================================================================== +packages/pirania/files/usr/lib/lua/portal/portal.lua +============================================================================== + 55 local utils = require('lime.utils') + 55 local config = require('lime.config') + 55 local shared_state = require("shared-state") + 55 local read_for_access = require("read_for_access.read_for_access") + + 55 local portal = {} + + 55 portal.PAGE_CONTENT_OBJ_PATH = '/etc/pirania/portal.json' + + 55 function portal.get_config() + 110 local uci = config.get_uci_cursor() + 110 local activated = uci:get("pirania", "base_config", "enabled") == '1' + 110 local with_vouchers = uci:get("pirania", "base_config", "with_vouchers") == '1' + 110 return {activated = activated, with_vouchers = with_vouchers} + end + + 55 function portal.set_config(activated, with_vouchers) + 33 local uci = config.get_uci_cursor() + + 66 uci:set("pirania", "base_config", "with_vouchers", + 33 with_vouchers and "1" or "0") + 33 if activated then + 11 uci:set("pirania", "base_config", "enabled", "1") + 11 uci:commit("pirania") + 11 utils.unsafe_shell("captive-portal start") + else + 22 uci:set("pirania", "base_config", "enabled", "0") + 22 uci:commit("pirania") + 22 utils.unsafe_shell("captive-portal stop") + end + 33 return true + end + + 55 function portal.get_page_content() + 22 local db = shared_state.SharedStateMultiWriter:new('pirania_persistent'):get() + 22 if db.portal then + 11 return db.portal.data + else + 11 return utils.read_obj_store(portal.PAGE_CONTENT_OBJ_PATH) + end + end + + + 55 function portal.set_page_content(title, main_text, logo, link_title, link_url, background_color) + 11 local data = {title=title, main_text=main_text, logo=logo, link_title=link_title, link_url=link_url, background_color=background_color} + 11 local db = shared_state.SharedStateMultiWriter:new('pirania_persistent') + 11 return db:insert({portal=data}) + end + + 55 function portal.get_authorized_macs() +*****0 local auth_macs = {} +*****0 local with_vouchers = portal.get_config().with_vouchers +*****0 if with_vouchers then +*****0 local vouchera = require("voucher.vouchera") +*****0 vouchera.init() +*****0 auth_macs = vouchera.get_authorized_macs() + else +*****0 auth_macs = read_for_access.get_authorized_macs() + end +*****0 return auth_macs + end + + 55 function portal.update_captive_portal(daemonized) +*****0 if daemonized then +*****0 utils.execute_daemonized('captive-portal update') + else +*****0 os.execute('captive-portal update') + end + end + + 55 return portal + +============================================================================== +packages/pirania/files/usr/lib/lua/read_for_access/cgi_handlers.lua +============================================================================== + 11 local utils = require('voucher.utils') + 11 local read_for_access = require('read_for_access.read_for_access') + 11 local portal = require('portal.portal') + 11 local config = require('lime.config') + + 11 local handlers = {} + + 11 function handlers.authorize_mac() + 44 local uci = config.get_uci_cursor() + 44 local with_vouchers = portal.get_config().with_vouchers + 44 if with_vouchers then + 11 return uci:get("pirania", "base_config", "url_auth") + end + 33 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) + 33 read_for_access.authorize_mac(client_data.mac) + 33 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) + 33 local url_prev = utils.urldecode(params['prev']) + 33 local url_authenticated = uci:get("pirania", "base_config", "url_authenticated") + 33 return url_prev or url_authenticated + end + + 11 return handlers + +============================================================================== +packages/pirania/files/usr/lib/lua/read_for_access/read_for_access.lua +============================================================================== + 66 local fs = require('nixio.fs') + 66 local utils = require('lime.utils') + 66 local config = require('lime.config') + + 66 local read_for_access = {} + + 66 function uptime_s() + 77 return math.floor(utils.uptime_s()) + end + + 66 function read_for_access.set_workdir(workdir) + 99 if not utils.file_exists(workdir) then + 2 os.execute('mkdir -p ' .. workdir) + end + 99 if fs.stat(workdir, "type") ~= "dir" then +*****0 error("Can't configure workdir " .. workdir) + end + 99 read_for_access.AUTH_MACS_FILE = workdir .. '/auth_macs' + end + + 66 read_for_access.set_workdir('/tmp/pirania/read_for_access') + + 66 function read_for_access.authorize_mac(mac) + 44 local uci = config.get_uci_cursor() + 44 local found = false + 44 if utils.file_exists(read_for_access.AUTH_MACS_FILE) then + 11 for line in io.lines(read_for_access.AUTH_MACS_FILE) do + 11 if line:match(mac) then + 11 found = true + 11 break + end + end + end + 44 local duration = uci:get("pirania", "read_for_access", "duration_m") + 44 local timestamp = uptime_s() + tonumber(duration) * 60 + 44 if not found then + 33 local ofile = io.open(read_for_access.AUTH_MACS_FILE, 'a') + 33 ofile:write(mac .. ' ' .. timestamp .. '\n') + 33 ofile:close() + else + 11 local content = utils.read_file(read_for_access.AUTH_MACS_FILE) + 11 content = content:gsub("(" .. mac .. ") %d+", "%1 " .. timestamp) + 11 utils.write_file(read_for_access.AUTH_MACS_FILE, content) + end + 44 os.execute('/usr/bin/captive-portal update') + end + + 66 function read_for_access.get_authorized_macs() + 33 local result = {} + 33 local current_time = uptime_s() + 33 if not utils.file_exists(read_for_access.AUTH_MACS_FILE) then +*****0 return result + end + 66 for line in io.lines(read_for_access.AUTH_MACS_FILE) do + 33 local words = {} + 99 for w in line:gmatch("%S+") do table.insert(words, w) end + 33 if tonumber(words[2]) > current_time then + 22 table.insert(result, words[1]) + end + end + 33 return result + end + + 66 return read_for_access + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/cgi_handlers.lua +============================================================================== + 11 local vouchera = require('voucher.vouchera') + 11 local utils = require('voucher.utils') + + 11 handlers = {} + + 11 local TESTING_URL_INFO = '/info' + 11 local TESTING_URL_FAIL = '/fail' + 11 local TESTING_URL_AUTHENTICATED = '/authenticated' + + + 22 function handlers.preactivate_voucher() + 77 local uci_cursor = require('uci').cursor() + + --! using defaults for testing as uci environment is not available + 77 local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated") or TESTING_URL_AUTHENTICATED + 77 local url_fail = uci_cursor:get("pirania", "base_config", "url_fail") or TESTING_URL_FAIL + 77 local url_info = uci_cursor:get("pirania", "base_config", "url_info") or TESTING_URL_INFO + + 77 vouchera.init() + + local url + + 77 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) + 77 local client_is_authorized = vouchera.is_mac_authorized(client_data.mac) + + 77 if client_is_authorized then + 11 url = url_authenticated + else + local output + 66 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) + 66 local code = params['voucher'] + 66 local prevUrl = params['prev'] + --! if client does not have javascript then activate right away without going to the INFO portal + 66 if params['nojs'] == 'true' then +*****0 if vouchera.activate(code, client_data.mac) then +*****0 url = url_authenticated + else +*****0 url = url_fail + end + else + 66 local setParams = prevUrl and '?voucher=' .. code .. '&prev=' .. prevUrl or '?voucher=' .. code + --! redirect to the INFO portal for some seconds with the url params already set to activate + --! the voucher after this time + 66 if vouchera.is_activable(code) then + 22 url = url_info .. setParams + else + 44 url = url_fail .. (prevUrl and '?prev=' .. prevUrl or '') + end + end + end + 77 return url + end + + 22 function handlers.activate_voucher() + 33 local uci_cursor = require('uci').cursor() + + 33 local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated") or TESTING_URL_AUTHENTICATED + 33 local url_fail = uci_cursor:get("pirania", "base_config", "url_fail") or TESTING_URL_FAIL + + 33 vouchera.init() + 33 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) + 33 local client_is_authorized = vouchera.is_mac_authorized(client_data.mac) + + 33 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) + 33 local code = params['voucher'] + 33 local prevUrl = params['prev'] + + 33 if client_is_authorized then + 11 return url_authenticated + end + + 22 local url = url_fail .. (prevUrl and '?prev=' .. prevUrl or '') + + 22 if code and client_data.mac then + 11 if vouchera.activate(code, client_data.mac) then + 11 if prevUrl ~= nil then + 11 url = prevUrl + else +*****0 url = url_authenticated + end + end + end + 22 return url + end + + 11 return handlers + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/config.lua +============================================================================== + 33 local uci = require("uci") + 33 local pirania_config = 'pirania' + + 33 local ucicursor = uci.cursor() + + 33 local config = { + 33 db_path = ucicursor:get(pirania_config, 'base_config', 'db_path'), + 33 hooksDir = ucicursor:get(pirania_config, 'base_config', 'hooks_path'), + 33 prune_expired_for_days = ucicursor:get(pirania_config, 'base_config', 'prune_expired_for_days') + } + + 33 return config + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/hooks.lua +============================================================================== + #!/usr/bin/lua + + 33 local config = require('voucher.config') + 33 local fs = require("nixio.fs") + + 33 local hooks = {} + + + 33 function hooks.run(action) +*****0 local hookPath = config.hooksDir..action..'/' +*****0 local files = fs.dir(hookPath) +*****0 if files then +*****0 for file in files do +*****0 os.execute("(( sh "..hookPath..file.." 0<&- &>/dev/null &) &)") + end + end + end + + 33 if debug.getinfo(2).name == nil then +*****0 local arguments = { ... } +*****0 if (arguments ~= nil and arguments[1] ~= nil) then +*****0 hooks.run(arguments[1]) + end + end + + 33 return hooks + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/store.lua +============================================================================== + #!/bin/lua + + 33 local fs = require("nixio.fs") + 33 local json = require("luci.jsonc") + 33 local hooks = require('voucher.hooks') + 33 local utils = require("voucher.utils") + + 33 local store = {} + + 33 function store.load_db(db_path, voucher_init) + 671 local vouchers = {} + + 671 local f = io.open(db_path, "r") + 671 if f ~= nil then + 528 io.close(f) + else + 143 os.execute("mkdir -p " .. db_path) + end + + 891 for fname in fs.glob(db_path .. '/*.json') do + 220 local f = io.open(fname, "r") + 220 if f ~= nil then + 220 local json_obj = json.parse(f:read("*all")) + 220 f:close() + local voucher, err + 220 if json_obj then + 209 voucher, err = voucher_init(json_obj) + else + 11 err = "invalid json" + end + + 220 if voucher ~= nil then + 209 if vouchers[voucher.id] ~= nil then +*****0 utils.log('warning', "vouchers: multiple vouchers with the same id " .. voucher.id) + end + 209 vouchers[voucher.id] = voucher + else + 11 utils.log('warning', "vouchers: Error loading voucher file " .. fname .. ", " .. err) + end + end + end + 671 return vouchers + end + + 33 function store.add_voucher(db_path, voucher, voucher_init) + 825 local fname = db_path .. "/" .. voucher.id .. ".json" + --! check if it already exists and if it is equal do not rewrite it + 825 local f = io.open(fname, "r") + 825 if f ~= nil then + 275 local json_obj = json.parse(f:read("*all")) + 275 f:close() + 275 local local_voucher = voucher_init(json_obj) + 275 if local_voucher == voucher then +*****0 return false + end + end + 825 f = io.open(fname, "w") + 825 f:write(json.stringify(voucher)) + 825 f:close() + 825 return true + end + + 33 function store.remove_voucher(db_path, voucher) + 22 local fname = db_path .. "/" .. voucher.id .. ".json" + 22 local removed_db = io.open(db_path .. "/removed.txt", "a") + 22 if removed_db then + 22 removed_db:write(voucher.id .. ",") + 22 removed_db:close() + end + 22 local removed = os.execute("rm " .. fname) == 0 + 22 return removed + end + + 33 return store + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/utils.lua +============================================================================== + #!/bin/lua + 44 local nixio = require('nixio') + 44 local lhttp = require('lucihttp') + + 44 local utils = {} + + 44 function utils.log(...) + 11 nixio.syslog(...) + end + + local function checkIfIpv4(ip) + 77 if ip == nil or type(ip) ~= "string" then +*****0 return 0 + end + -- check for format 1.11.111.111 for ipv4 + 77 local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")} + 77 if (#chunks == 4) then + 385 for _,v in pairs(chunks) do + 308 if (tonumber(v) < 0 or tonumber(v) > 255) then +*****0 return 0 + end + end + 77 return true + else +*****0 return false + end + end + + --! get ipv4 and MAC from a ip_address that could be ipv4 or ipv6 + 44 function utils.getIpv4AndMac(ip_address) + 77 local isIpv4 = checkIfIpv4(ip_address) + 77 if (isIpv4) then + 77 local ipv4macCommand = "cat /proc/net/arp | grep "..ip_address.." | awk -F ' ' '{print $4}' | head -n 1" + 77 fd = io.popen(ipv4macCommand, 'r') + 77 ipv4mac = fd:read('*l') + 77 fd:close() + 77 local res = {} + 77 res.ip = ip_address + 77 res.mac = ipv4mac + 77 return res + else +*****0 local ipv6macCommand = "ip neigh | grep "..ip_address.." | awk -F ' ' '{print $5}' | head -n 1" +*****0 fd6 = io.popen(ipv6macCommand, 'r') +*****0 ipv6mac = fd6:read('*l') +*****0 fd6:close() +*****0 local ipv4cCommand = "cat /proc/net/arp | grep "..ipv6mac.." | awk -F ' ' '{print $1}' | head -n 1" +*****0 fd4 = io.popen(ipv4cCommand, 'r') +*****0 ipv4 = fd4:read('*l') +*****0 fd4:close() +*****0 local res = {} +*****0 res.ip = ipv4 +*****0 res.mac = ipv6mac +*****0 return res + end + end + + --! from given url or string. Returns a table with urldecoded values. + --! Simple parameters are stored as string values associated with the parameter + --! name within the table. Parameters with multiple values are stored as array + --! containing the corresponding values. + 44 function utils.urldecode_params(url, tbl) + local parser, name + 132 local params = tbl or { } + + 264 parser = lhttp.urlencoded_parser(function (what, buffer, length) + 484 if what == parser.TUPLE then + 176 name, value = nil, nil + 308 elseif what == parser.NAME then + 154 name = lhttp.urldecode(buffer) + 154 elseif what == parser.VALUE and name then + 154 params[name] = lhttp.urldecode(buffer) or "" + end + + 484 return true + end) + + 132 if parser then + 132 parser:parse((url or ""):match("[^?]*$")) + 132 parser:parse(nil) + end + + 132 return params + end + + 44 function utils.urlencode(value) + 77 if value ~= nil then + 77 local str = tostring(value) + 77 return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL) or str + end +*****0 return nil + end + + 44 function utils.urldecode(value) + 33 if value ~= nil then + 11 local str = tostring(value) + 11 return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED) or str + end + 22 return nil + end + + 44 return utils + +============================================================================== +packages/pirania/files/usr/lib/lua/voucher/vouchera.lua +============================================================================== + #!/bin/lua + + 33 local store = require('voucher.store') + 33 local config = require('voucher.config') + 33 local utils = require('lime.utils') + 33 local portal = require('portal.portal') + 33 local hooks = require('voucher.hooks') + 33 local vouchera = {} + + 33 vouchera.ID_SIZE = 6 + 33 vouchera.CODE_SIZE = 6 + + --! Simplify the comparison of vouchers using a metatable for the == operator + 33 local voucher_metatable = { + __eq = function(self, value) + 374 return self.tostring() == value.tostring() + end + } + + --! obj attrs id, name, code, mac, duration_m, mod_counter, creation_date, activation_date + 33 function voucher_init(obj) + 1166 local voucher = {} + + 1166 if not obj.id then + 429 obj.id = utils.random_string(vouchera.ID_SIZE) + end + + 1166 voucher.id = obj.id + 1166 if type(obj.id) ~= "string" then +*****0 return nil, "id must be a string" + end + + 1166 if type(obj.name) ~= "string" then +*****0 return nil, "name must be a string" + end + 1166 voucher.name = obj.name + + 1166 if type(obj.code) ~= "string" then +*****0 return nil, "code must be a string" + end + 1166 voucher.code = obj.code + + 1166 if type(obj.mac) == "string" and #obj.mac ~= 17 then +*****0 return nil, "invalid mac" + end + 1166 voucher.mac = obj.mac + + + 1166 if not (type(obj.duration_m) == "nil" or type(obj.duration_m) == "number") then +*****0 return nil, "invalid duration_m type" + end + 1166 voucher.duration_m = obj.duration_m -- use nil to create a permanent voucher + + 1166 if not obj.creation_date then +*****0 return nil, "creation_date can't be nil" + end + + 1166 voucher.author_node = obj.author_node + + 1166 voucher.creation_date = obj.creation_date + + 1166 voucher.activation_date = obj.activation_date + + 1166 if not (type(obj.activation_deadline) == "nil" or type(obj.activation_deadline) == "number") then +*****0 return nil, "invalid activation_deadline type", type(obj.activation_deadline) + end + 1166 voucher.activation_deadline = obj.activation_deadline + + 1166 voucher.invalidation_date = obj.invalidation_date + + 1166 voucher.mod_counter = obj.mod_counter or 1 + + --! tostring must reflect all the state of a voucher (so vouchers can be compared reliably using tostring) + voucher.tostring = function() + 748 local v = voucher + 748 local creation = os.date("%c", v.creation_date) + 748 local expiration = ' - ' + 748 if v.expiration_date() then + 209 expiration = os.date("%c", v.expiration_date()) + end + 1496 return(string.format('%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s', v.id, v.name, v.code, v.mac or 'xx:xx:xx:xx:xx:xx', + 1496 creation, v.duration_m or 'perm', expiration, v.mod_counter)) + end + + voucher.expiration_date = function() + local ret = nil + 2123 if voucher.duration_m and voucher.mac and voucher.activation_date then + 847 ret = voucher.activation_date + voucher.duration_m * 60 + end + 2123 return ret + end + + voucher.is_active = function() + 451 if voucher.is_invalidated() or voucher.mac == nil then + 308 return false + else + 143 if voucher.expiration_date() and voucher.expiration_date() <= os.time() then + 22 return false + end + end + 121 return true + end + + voucher.is_invalidated = function() + 847 return voucher.invalidation_date ~= nil + end + + voucher.is_expired = function() + 385 local curr_time = os.time() + 385 return (voucher.expiration_date() ~= nil and voucher.expiration_date() < curr_time) or + 385 (voucher.activation_deadline ~= nil and voucher.activation_deadline < curr_time) + end + + voucher.is_activable = function() + 242 return voucher.mac == nil and not voucher.is_invalidated() and not voucher.is_expired() + end + + voucher.status = function() + 165 local status = 'available' + 165 if voucher.is_invalidated() then + 11 status = 'invalidated' + 154 elseif voucher.is_expired() then + 11 status = 'expired' + 143 elseif voucher.is_active() then + 11 status = 'active' + end + 165 return status + end + + 1166 setmetatable(voucher, voucher_metatable) + 1166 return voucher + end + + 33 function vouchera.init(cfg) + 671 if cfg ~= nil then +*****0 config = cfg + end + 671 vouchera.config = config + 671 vouchera.PRUNE_OLDER_THAN_S = tonumber(config.prune_expired_for_days) * 60 * 60 * 24 + 671 vouchera.vouchers = store.load_db(config.db_path, voucher_init) + + --! Automatic voucher pruning + 880 for _, voucher in pairs(vouchera.vouchers) do + 209 if vouchera.should_be_pruned(voucher) then + 11 vouchera.remove_locally(voucher.id) + end + end + end + + 33 function vouchera.add(obj) + 550 if not obj.creation_date then + 528 obj.creation_date = os.time() + end + 550 obj.author_node = utils.hostname() + 550 local voucher, errmsg = voucher_init(obj) + 550 if vouchera.vouchers[obj.id] ~= nil then +*****0 return nil, "voucher with same id already exists" + end + 550 if voucher and store.add_voucher(config.db_path, voucher, voucher_init) then + 550 vouchera.vouchers[obj.id] = voucher + 550 return voucher + end +*****0 return nil, "can't create voucher: " .. tostring(errmsg) + end + + 33 function vouchera.get_by_id(id) + 132 return vouchera.vouchers[id] + end + + 33 function vouchera.create(basename, qty, duration_m, activation_deadline) + 77 local vouchers = {} + 330 for n=1, qty do + local name + 253 if qty == 1 then + 11 name = basename + else + 242 name = basename .. "-" .. tostring(n) + end + 253 local v = {name=name, code=vouchera.gen_code(), duration_m=duration_m, + 253 activation_deadline=activation_deadline} + 253 local voucher, msg = vouchera.add(v) + 253 if voucher == nil then +*****0 return nil, msg + end + 253 table.insert(vouchers, n, {id=voucher.id, code=voucher.code}) + end + 77 portal.update_captive_portal(true) + 77 hooks.run('db_change') + 77 return vouchers + end + + --! Remove a voucher from the local db. This won't trigger a remove in the shared db. + 33 function vouchera.remove_locally(id) + 33 if vouchera.vouchers[id] ~= nil then + 22 if store.remove_voucher(config.db_path, vouchera.vouchers[id]) then + 22 vouchera.vouchers[id] = nil + 22 return true + else +*****0 return nil, "can't remove voucher" + end + end + 11 return nil, "can't find voucher to remove" + end + + local function modify_voucher_with_func(id, func) + 297 local voucher = vouchera.vouchers[id] + 297 if voucher then + 275 func(voucher) + 275 voucher.mod_counter = voucher.mod_counter + 1 + 275 return store.add_voucher(config.db_path, voucher, voucher_init) + end + 22 return voucher + end + + --! Remove a voucher from the shared db. + --! This will eventualy prune the voucher in all the dbs after PRUNE_OLDER_THAN_S seconds. + --! It is important to maintain the "removed" (invalidated) voucher in the shared db for some time + --! so that all nodes (even nodes that are offline when this is executed) have time to update locally + --! and eventualy prune the voucher. + 33 function vouchera.invalidate(id) + 99 local voucher = vouchera.vouchers[id] + 99 local is_active = voucher ~= nil and voucher.is_active() + local function _update(v) + 77 v.invalidation_date = os.time() + end + 99 voucher = modify_voucher_with_func(id, _update) + 99 if is_active then + 44 portal.update_captive_portal(true) + end + 99 hooks.run('db_change') + 99 return voucher + end + + --! Activate a voucher returning true or false depending on the status of the operation. + 33 function vouchera.activate(code, mac) + 198 local voucher = vouchera.is_activable(code) + 198 if voucher then + 165 function _update(v) + 165 v.mac = mac + 165 v.activation_date = os.time() + end + 165 modify_voucher_with_func(voucher.id, _update) + 165 portal.update_captive_portal(false) + 165 hooks.run('db_change') + end + 198 return voucher + end + + 33 function vouchera.deactivate(id) + local function _update(v) + 11 v.mac = nil + end + 11 return modify_voucher_with_func(id, _update) + end + + --! updates the database with the new voucher information + 33 function vouchera.update_with(voucher) +*****0 vouchera.vouchers[voucher.id] = voucher +*****0 return store.add_voucher(config.db_path, voucher, voucher_init) + end + + --! Return true if there is an activated voucher that grants a access to the specified MAC + 33 function vouchera.is_mac_authorized(mac) + 165 if mac ~= nil then + 121 for k, v in pairs(vouchera.vouchers) do + 77 if v.mac == mac and v.is_active() then + 44 return true + end + end + end + 121 return false + end + + --! Check if a code would be good to be activated but without activating it right away. + 33 function vouchera.is_activable(code) + 347 for _, v in pairs(vouchera.vouchers) do + 270 if v.code == code then + 242 if v.is_activable() then + 198 return v + else + 44 return false + end + end + end + 77 return false + end + + 33 function vouchera.should_be_pruned(voucher) + 297 local current_time = os.time() + 297 return (voucher.expiration_date() ~= nil and ( + 77 voucher.expiration_date() <= (current_time - vouchera.PRUNE_OLDER_THAN_S))) or + 297 ((voucher.invalidation_date or false) and (voucher.invalidation_date <= (current_time - vouchera.PRUNE_OLDER_THAN_S))) + end + + 33 function vouchera.rename(id, new_name) + local function _update(v) + 22 v.name = new_name + end + 22 return modify_voucher_with_func(id, _update) + end + + 33 function vouchera.gen_code() + 15078 return utils.random_string(vouchera.CODE_SIZE, function (c) return c:match('%u') ~= nil end) + end + + 33 function vouchera.list() + 22 local vouchers = {} + 132 for k, v in pairs(vouchera.vouchers) do + 220 table.insert(vouchers, { + 110 id=v.id, + 110 name=v.name, + 110 code=v.code, + 110 mac=v.mac, + 110 duration_m=v.duration_m, + 110 creation_date=v.creation_date, + 110 activation_date=v.activation_date, + 110 expiration_date=v.expiration_date(), + 110 is_active=v.is_active(), + 110 permanent=not v.duration_m, + 110 activation_deadline=v.activation_deadline, + 110 author_node=v.author_node, + 110 status=v.status(), + }) + end + 22 return vouchers + end + + 33 function vouchera.get_authorized_macs() +*****0 local auth_macs = {} +*****0 for _, voucher in pairs(vouchera.vouchers) do +*****0 if voucher.is_active() then +*****0 table.insert(auth_macs, voucher.mac) + end + end +*****0 return auth_macs + end + + 33 vouchera.voucher = voucher_init + + 33 return vouchera + +============================================================================== +packages/pirania/files/usr/libexec/rpcd/pirania +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright 2018 Marcos Gutierrez + Copyright 2021 Santiago Piccinini + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-3.0 + ]]-- + 121 local ubus = require "ubus" + 121 local json = require 'luci.jsonc' + 121 local uci = require 'uci' + 121 local vouchera = require('voucher.vouchera') + 121 local utils = require('lime.utils') + 121 local config = require('lime.config') + 121 local portal = require('portal.portal') + + 121 vouchera.init() + + 121 local uci_cursor = config.get_uci_cursor() + + 121 local conn = ubus.connect() + 121 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function get_portal_config() + 11 local portal_config = portal.get_config() + 11 portal_config.status = 'ok' + 11 return utils.printJson(portal_config) + end + + local function set_portal_config(msg) + 22 local status, error_msg = portal.set_config(msg.activated, msg.with_vouchers) + 22 if not status then + 11 utils.printJson({ status = "error", message = error_msg }) + else + 11 utils.printJson({ status = "ok" }) + end + end + + local function get_portal_page_content(msg) +*****0 utils.printJson(portal.get_page_content()) + end + + local function set_portal_page_content(msg) + 22 portal.set_page_content( + 11 msg.title, + 11 msg.main_text, + 11 msg.logo, + 11 msg.link_title, + 11 msg.link_url, + msg.background_color + 11 ) + 11 utils.printJson({ status = 'ok'}) + end + + local function show_url(msg) +*****0 utils.printJson({ status = 'ok', url = uci_cursor:get("pirania", "base_config", "portal_url") }); + end + + local function change_url(msg) +*****0 local url = msg.url +*****0 uci_cursor:set("pirania", "base_config", "portal_url", url) +*****0 uci_cursor:commit("pirania") +*****0 utils.printJson({status = 'ok', url = url}); + end + + + local function add_vouchers(msg) + 44 local vouchers, errmsg = vouchera.create(msg.name, msg.qty, msg.duration_m, + 22 msg.activation_deadline, msg.permanent) + 22 if vouchers then + 22 return utils.printJson({ status = 'ok', vouchers = vouchers}) + else +*****0 return utils.printJson({ status = 'error', message = errmsg}) + end + end + + local function rename(msg) + 11 local voucher = vouchera.rename(msg.id, msg.name) + 11 return utils.printJson({ status = voucher and 'ok' or 'error' }) + end + + local function invalidate(msg) + 22 local voucher = vouchera.invalidate(msg.id) + 22 return utils.printJson({ status = voucher and 'ok' or 'error' }) + end + + + local function list_vouchers(msg) + 11 local vouchers = vouchera.list() + 11 return utils.printJson({ status = vouchers and 'ok' or 'error', vouchers = vouchers }) + end + + 121 local methods = { + 121 get_portal_config = { no_params = 0 }, + 121 set_portal_config = { activated = 'bool', with_vouchers = 'bool' }, + 121 disable = { no_params = 0 }, + 121 show_url = { no_params = 0 }, + 121 change_url = { url = 'value' }, + 121 add_vouchers = { name='str', qty='int', duration_m='int', activation_deadline='timestamp', permanent='bool'}, + 121 list_vouchers = { no_params = 0 }, + 121 rename = { id = 'str', name = 'str' }, + 121 invalidate = { id = 'str' }, + 121 get_portal_page_content = { no_params = 0 }, + 121 set_portal_page_content = { + 121 title = 'value', + 121 main_text = 'value', + 121 logo = 'value', + 121 link_title = 'value', + 121 link_url = 'value', + 121 background_color = 'value' + 121 }, + } + + 121 if arg[1] == 'list' then + 11 utils.printJson(methods) + end + + 121 if arg[1] == 'call' then + 110 local msg = utils.rpcd_readline() + 110 msg = json.parse(msg) + 110 if arg[2] == 'get_portal_config' then get_portal_config(msg) + 99 elseif arg[2] == 'set_portal_config' then set_portal_config(msg) + 77 elseif arg[2] == 'disable' then disable(msg) + 77 elseif arg[2] == 'show_url' then show_url(msg) + 77 elseif arg[2] == 'change_url' then change_url(msg) + 77 elseif arg[2] == 'list_vouchers' then list_vouchers(msg) + 66 elseif arg[2] == 'add_vouchers' then add_vouchers(msg) + 44 elseif arg[2] == 'invalidate' then invalidate(msg) + 22 elseif arg[2] == 'rename' then rename(msg) + 11 elseif arg[2] == 'get_portal_page_content' then get_portal_page_content(msg) + 11 elseif arg[2] == 'set_portal_page_content' then set_portal_page_content(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/pirania/files/www/pirania-redirect/redirect +============================================================================== + #!/usr/bin/lua + 22 local utils = require('voucher.utils') + 22 local portal = require('portal.portal') + 22 local config = require('lime.config') + + + 22 local uci_cursor = config.get_uci_cursor() + + 22 function handle_request (env) + 22 local method = 'http://' + 22 local origin_url = utils.urlencode(method .. env.HTTP_HOST .. env.REQUEST_URI) + 22 local portal_domain = uci_cursor:get("pirania", "base_config", "portal_domain") + 22 local with_vouchers = portal.get_config().with_vouchers + 22 local redirect_path = '' + 22 if with_vouchers then + 11 redirect_path = uci_cursor:get("pirania", "base_config", "url_auth") + else + 11 redirect_path = uci_cursor:get("pirania", "read_for_access", "url_portal") + end + 22 local redirect_url = method .. portal_domain .. redirect_path .. "?prev=" .. origin_url + + 22 uhttpd.send("Status: 302 \r\n") + 22 uhttpd.send("Location: " .. redirect_url .. "\r\n") + 22 uhttpd.send("\r\n") --! indicate uhttpd to send the response + end + +============================================================================== +packages/safe-upgrade/files/usr/sbin/safe-upgrade +============================================================================== + #!/usr/bin/lua + --[[ + Copyright (C) 2019-2020 Santiago Piccinini + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + ]]-- + + 14 local io = require "io" + 14 local json = require "luci.jsonc" + 14 local utils = require "lime.utils" + + 14 local su = {} + + 14 su.version = '1.0' + 14 local firmware_size_bytes = 7936*1024 + -- Keep the fw addresses as strings beacause of https://gitlab.com/librerouter/librerouteros/-/issues/61 + 14 local fw1_addr = '0x9f050000' + 14 local fw2_addr = '0x9f810000' -- fw1_addr + firmware_size_bytes + + 14 su._supported_devices = {'librerouter-v1'} + 14 su._mtd_partitions_desc = '/proc/mtd' + + -- safe upgrade script, generated with bootscript.py, DO NOT edit here! + 14 local bootcmd = 'run preboot; boot_part=${stable_part}; if test ${testing_part} -ne 0; then echo Testing part ${testing_part}; boot_part=${testing_part}; set testing_part 0; saveenv; fi; if test ${boot_part} -eq 2; then fw_addr=${fw2_addr}; run boot_2; else fw_addr=${fw1_addr}; run boot_1; fi; run boot_1; bootm ${fw1_addr};' + + 14 su.STABLE_PARTITION_NAME = 'stable_part' + 14 su.TESTING_PARTITION_NAME = 'testing_part' + + 14 su.EXIT_ERROR_OK = 0 + 14 su.EXIT_STATUS_INVALID_FIRMWARE = 111 + 14 su.EXIT_STATUS_ALREADY_CONFIRMED = 113 + 14 su.EXIT_STATUS_FW_ENV_NOT_FOUND = 115 + 14 su.EXIT_STATUS_BOARD_NOT_SUPPORTED = 198 + 14 su.EXIT_STATUS_NOT_INSTALLED = 199 + 14 su.EXIT_STATUS_INSTALL_FROM_PART2 = 120 + 14 su.EXIT_STATUS_ALREADY_INSTALLED = 121 + 14 su.REBOOT_FILE_CONFIG_TIMEOUT_S = "/etc/safe_upgrade_auto_reboot_confirm_timeout_s" + + local safe_upgrade_auto_reboot_script = [[#!/bin/sh /etc/rc.common + + REBOOT_FILE_CONFIG_TIMEOUT_S="/etc/safe_upgrade_auto_reboot_confirm_timeout_s" + MINIMUM_REBOOT_TIMEOUT_S=60 + PIDFILE="/var/run/safe_upgrade_auto_reboot_script.pid" + CMD_FORCE_REBOOT="echo b > /proc/sysrq-trigger" # Immediately reboot the system without syncing or unmounting disks. + + START=11 + + start() { + if [ -s "$REBOOT_FILE_CONFIG_TIMEOUT_S" ]; then + read reboot_at_uptime_s < "$REBOOT_FILE_CONFIG_TIMEOUT_S" + else + exit 0 + fi + + # check that the reboot time is above the minimum to prevent infinite reboots + if [ "$reboot_at_uptime_s" -lt "$MINIMUM_REBOOT_TIMEOUT_S" ]; then + echo "safe-upgrade reboot: Less than minimum timeout! aborting" + exit 0 + fi + + (sleep "$reboot_at_uptime_s" && \ + if [ -s "$REBOOT_FILE_CONFIG_TIMEOUT_S" ]; then + reboot ; sleep 10 ; eval "$CMD_FORCE_REBOOT" + fi + ) & + + echo $! > "$PIDFILE" + } + + stop() { + rm "$REBOOT_FILE_CONFIG_TIMEOUT_S" + sync + kill -9 `cat "$PIDFILE"` + } + 14 ]] + + 14 function su.get_uboot_env(key) +*****0 local value = utils.unsafe_shell("fw_printenv -n " .. key .. " 2>&1") + +*****0 if value:find('## Error:') == nil then + -- remove EOL +*****0 local value = value:sub(1, -2) +*****0 return value + else +*****0 return nil + end + end + + local function set_uboot_env(key, value) +*****0 print("DEBUG: setting key:" .. key) +*****0 print("DEBUG: value:" .. value) +*****0 utils.unsafe_shell("fw_setenv " .. key .. " '" .. value .. "'") + end + + local function fw_env_configured() +*****0 return utils.file_exists('/etc/fw_env.config') + end + + local function assert_fw_env_configured() +*****0 if not fw_env_configured() then +*****0 print('/etc/fw_env.confg does not exist, aborting') +*****0 os.exit(su.EXIT_STATUS_FW_ENV_NOT_FOUND) + end + end + + local function get_current_cmdline() +*****0 return utils.read_file('/proc/cmdline') + end + + 14 function su.get_current_partition() + 22 local data = utils.read_file(su._mtd_partitions_desc) + 22 if data:find("fw2") == nil then + 11 return 2 + else + 11 return 1 + end + end + + 14 function su.get_partitions() + 22 local p = {} + 22 p.current = su.get_current_partition() + 22 if p.current == 1 then + 11 p.other = 2 + else + 11 p.other = 1 + end + 22 p.stable = tonumber(su.get_uboot_env(su.STABLE_PARTITION_NAME)) + 22 p.testing = tonumber(su.get_uboot_env(su.TESTING_PARTITION_NAME)) + + 22 return p + end + + local function get_su_version() +*****0 return su.get_uboot_env('su_version') + end + + local function is_su_installed() +*****0 return get_su_version() ~= nil + end + + local function set_testing_partition(partition) +*****0 set_uboot_env(su.TESTING_PARTITION_NAME, tostring(partition)) + end + + local function set_stable_partition(partition) +*****0 set_uboot_env(su.STABLE_PARTITION_NAME, tostring(partition)) + end + + local function assert_su_installed() +*****0 if not is_su_installed() then +*****0 print('safe-upgrade is not installed, aborting') +*****0 os.exit(su.EXIT_STATUS_NOT_INSTALLED) + end + end + + local function read_fw_metadata(path) +*****0 local handle = io.popen("fwtool -i - " .. path) +*****0 local metadata = json.parse(handle:read("*a")) +*****0 handle:close() +*****0 return metadata + end + + 14 function su.get_current_device() +*****0 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") + end + + 14 function su.get_supported_devices() + 88 return su._supported_devices + end + + 14 function su.is_current_board_supported() + 44 local current_device = su.get_current_device() + 99 for _, supported_device in pairs(su.get_supported_devices()) do + 77 if string.find(current_device, utils.literalize(supported_device)) then + 22 return true + end + end + 22 return false + end + + 14 function su.is_firmware_valid(metadata) + 132 local current_device = su.get_current_device() + 132 if metadata ~= nil then + 110 local fw_devices = metadata['supported_devices'] + 110 if fw_devices ~= nil then + 154 for _, fw_device in pairs(fw_devices) do + 154 for _, supported_device in pairs(su.get_supported_devices()) do + --! Check that the firmware is supported by safe upgrade + --! and that the current board is among the firmware devices + 88 if string.find(fw_device, utils.literalize(supported_device)) and + 44 string.find(fw_device, utils.literalize(current_device)) then + 22 return true + end + end + end + end + end + 110 return false + end + + 14 function su.preserve_files_to_new_partition(args) + 33 os.execute("rm -rf /tmp/_to_sysupgradetgz/") + 33 os.execute("mkdir -p /tmp/_to_sysupgradetgz/etc/init.d/") + 33 os.execute("mkdir -p /tmp/_to_sysupgradetgz/etc/rc.d/") + 33 local f = io.open("/tmp/_to_sysupgradetgz/etc/init.d/safe_upgrade_auto_reboot", "w") + 33 f:write(safe_upgrade_auto_reboot_script) + 33 f:close() + + 33 os.execute("chmod +x /tmp/_to_sysupgradetgz/etc/init.d/safe_upgrade_auto_reboot") + 33 os.execute("ln -s ../init.d/safe_upgrade_auto_reboot /tmp/_to_sysupgradetgz/etc/rc.d/S11safe_upgrade_auto_reboot") + + 33 if not args.disable_reboot_safety then + 33 local f = io.open("/tmp/_to_sysupgradetgz/etc/safe_upgrade_auto_reboot_confirm_timeout_s", "w") + 33 f:write(args.reboot_safety_timeout) + 33 f:close() + end + + 33 if args.do_not_preserve_config then + 11 utils.log('Not preserving config.') + 22 elseif args.preserve_archive then + 11 utils.log('Preserving from archive') + 11 os.execute("tar xfz " .. args.preserve_archive .. " -C /tmp/_to_sysupgradetgz/") + else + 11 utils.log('Preserving libremesh minimal config.') + 11 local files = "" + 33 for _, file_name in pairs(utils.keep_on_upgrade_files()) do + 22 if utils.file_exists(file_name) then + 11 files = files .. " " .. file_name + end + end + 11 if files ~= '' then + --! using an intermediate tar file for simplicity + 11 os.execute("tar cf /tmp/_safe_upgrade_intermadiate.tar " .. files .. " 2> /dev/null") + 11 os.execute("tar xf /tmp/_safe_upgrade_intermadiate.tar -C /tmp/_to_sysupgradetgz/") + end + end + + 33 os.execute('find /tmp/_to_sysupgradetgz/ -type f -o -type l | sed "s|^\/tmp\/_to_sysupgradetgz\/||" | sort -u > /tmp/_to_persist') + 33 os.execute("tar cfz /tmp/sysupgrade.tgz -C /tmp/_to_sysupgradetgz/ -T /tmp/_to_persist 2>/dev/null") + + 33 utils.log('List of files that are being preserved:') + 33 local file_list = {} + 33 local out = io.popen("tar tfz /tmp/sysupgrade.tgz") + 154 for line in out:lines() do + --! skip directories + 121 if not utils.stringEnds(line, '/') then + 121 table.insert(file_list, line) + 121 utils.log('\t' .. line) + end + end + 33 out:close() + 33 return file_list + end + + local function bootstrap(args) +*****0 if is_su_installed() then +*****0 if args.force then +*****0 print("Forcing the bootstrap.") + else +*****0 print(string.format("safe-upgrade version '%s' is already installed, aborting", +*****0 get_su_version())) +*****0 os.exit(su.EXIT_STATUS_ALREADY_INSTALLED) + end + end + + +*****0 if su.get_current_partition() ~= 1 then +*****0 print("installing safe-upgrade from partition 2 is not supported yet") +*****0 os.exit(su.EXIT_STATUS_INSTALL_FROM_PART2) + end + +*****0 set_stable_partition(1) +*****0 set_testing_partition(0) +*****0 set_uboot_env('fw1_addr', fw1_addr) +*****0 set_uboot_env('fw2_addr', fw2_addr) + + -- configure cmdline using the current cmdline config to not force + -- us to know here the correct cmdline bootargs of the running kernel +*****0 local boot_1 = 'set bootargs ' .. get_current_cmdline() .. '; echo booting part 1; bootm ${fw_addr};' +*****0 set_uboot_env('boot_1', boot_1) +*****0 set_uboot_env('su_version', su.version) + + -- installing the script. Everything must be installed before this! +*****0 set_uboot_env('bootcmd', bootcmd) +*****0 print('succesfully bootstraped safe-upgrade') + end + + local function _verify(firmware) +*****0 local fw_metadata = read_fw_metadata(firmware) +*****0 local fw_valid = fw_metadata ~= nil and su.is_firmware_valid(fw_metadata) +*****0 return fw_valid + end + + local function verify(args) +*****0 if _verify(args.firmware) then +*****0 os.exit(su.EXIT_STATUS_OK) + else +*****0 print("Invalid firmware!") +*****0 os.exit(su.EXIT_STATUS_INVALID_FIRMWARE) + end + end + + local function upgrade(args) +*****0 assert_su_installed() +*****0 local partitions = su.get_partitions() + +*****0 if not _verify(args.firmware) then +*****0 print("Invalid firmware!") +*****0 if args.force then +*****0 print("Forcing upgrade to continue as requested.") + else +*****0 os.exit(su.EXIT_STATUS_INVALID_FIRMWARE) + end + end + +*****0 su.preserve_files_to_new_partition(args) + + -- postpone 10m defarable-reboot +*****0 os.execute("awk '{print $1 + 600}' /proc/uptime > /tmp/deferrable-reboot.defer") + +*****0 print(string.format("erasing partition %d", partitions.other)) +*****0 os.execute(string.format("mtd erase fw%d", partitions.other)) + + -- It is important that the mtd -j option to preserve a file is used + -- with the file /tmp/sysupgrade.tgz because there are hooks in place + -- to unpack this tar and install the files at boot +*****0 print(string.format("writing partition %d", partitions.other)) +*****0 os.execute(string.format("mtd -j /tmp/sysupgrade.tgz write '%s' fw%d", +*****0 args.firmware, partitions.other)) + + -- TODO: load bootargs from acompaning image, here is hardcoded!! +*****0 local fw_mtd_str = '' +*****0 if partitions.other == 2 then +*****0 fw_mtd_str = '7936k(fw1),7936k(firmware)' + else +*****0 fw_mtd_str = '7936k(firmware),7936k(fw2)' + end +*****0 local boot_script_tpl = 'set bootargs console=ttyS0,115200 board=LIBREROUTERV1 mtdparts=spi0.0:256k(u-boot),64k(u-boot-env),%s,128k(res),64k(art); echo booting part %d; bootm ${fw_addr};' +*****0 local boot_script = string.format(boot_script_tpl, fw_mtd_str, partitions.other) +*****0 set_uboot_env(string.format('boot_%d', partitions.other), boot_script) +*****0 set_testing_partition(partitions.other) + +*****0 if not args.no_reboot then +*****0 print("Rebooting into the new firmware. Do the confirm step if everything is ok.") +*****0 os.execute("reboot") + end + end + + local function confirm(args) +*****0 assert_su_installed() +*****0 local partitions = su.get_partitions() +*****0 if partitions.current == partitions.stable then +*****0 print(string.format('the current partition: %d is already the stable partition, aborting', partitions.current)) +*****0 os.exit(su.EXIT_STATUS_ALREADY_CONFIRMED) + end + +*****0 print("Canceling and disabling automatic reboot") +*****0 os.execute("/etc/init.d/safe_upgrade_auto_reboot stop") +*****0 os.execute("/etc/init.d/safe_upgrade_auto_reboot disable") + +*****0 set_stable_partition(partitions.current) +*****0 print(string.format('Confirmed partition %d as stable partition', partitions.current)) + end + + local function test_other_partition(args) +*****0 assert_su_installed() +*****0 local partitions = su.get_partitions() +*****0 set_testing_partition(partitions.other) +*****0 print(string.format('Next boot will run partition: %d. You may confirm it if you like after reboot.', partitions.other)) + end + + + local function parse_args() + local function validate_file_exists(filename) +*****0 if utils.file_exists(filename) then +*****0 return filename + else +*****0 return nil, string.format("file %q does not exists", filename) + end + end + + local function validate_safety_timeout(value) +*****0 local timeout = tonumber(value) +*****0 if timeout == nil then +*****0 return nil, string.format("invalid --reboot-safety-timeout value: %q", value) + end +*****0 if timeout < 60 then +*****0 return nil, string.format("--reboot-safety-timeout must be greater than 60 but was %q", timeout) + end +*****0 return timeout + end + +*****0 local argparse = require 'argparse' +*****0 local parser = argparse('safe-upgrade', 'Safe upgrade mechanism for dual-boot systems') +*****0 parser:command_target('command') + +*****0 parser:command('show', 'Show the status of the system partitions.') + +*****0 local verify = parser:command('verify', 'Verify that the firmware is valid and can be installed.') +*****0 verify:argument("firmware", "firmware image (xxx-sysupgrade.bin)"):convert(validate_file_exists) + +*****0 local upgrade = parser:command('upgrade', 'Upgrade firmware in a non permanent way.') +*****0 upgrade:argument("firmware", "firmware image (xxx-sysupgrade.bin)"):convert(validate_file_exists) +*****0 upgrade:flag("-n --do-not-preserve-config", "Do not save configuration to the new partition") +*****0 upgrade:flag("--disable-reboot-safety", "Disable the automatic reboot safety mechanism") +*****0 upgrade:flag("--force", "Upgrade even if firmware is not valid") +*****0 upgrade:flag("--no-reboot", "Do not reboot automatically after flashing") +*****0 upgrade:option("--preserve-archive", [[Specify the files to be preserved as a tar.gz (Like the sysupgrade -f). +*****0 To preserve a full config you may use sysupgrade --create-backup and use this .tar.gz.]]) +*****0 :convert(validate_file_exists) +*****0 upgrade:option("--reboot-safety-timeout", +*****0 "Set the timeout (in seconds) of the automatic reboot safety mechanism") +*****0 :default('600'):convert(validate_safety_timeout) + +*****0 parser:command('confirm', ('Confirm the current partition. Use when after an upgrade ' .. +*****0 'or after running "test-other-partition".')) + +*****0 local bootstrap = parser:command('bootstrap', 'Install the safe-upgrade mechanism') +*****0 bootstrap:flag("--force", "Install even it is already installed.") +*****0 parser:command('test-other-partition', 'Mark the other partition as testing partition.') + +*****0 parser:command('board-supported', 'Exits with 0 if board is supported') +*****0 parser:command('confirm-remaining', ('Return the remaining seconds to confirm until the ' .. +*****0 'automatic reboot is done or -1 if not in a confirmable state.')) + +*****0 local args = parser:parse() + + +*****0 return args + end + + -- detect if this module is run as a library or as a script + 14 if pcall(debug.getlocal, 4, 1) then + -- Library mode + 14 return su + else + -- Main script mode + +*****0 local args = parse_args() +*****0 assert_fw_env_configured() +*****0 if args.bootstrap then +*****0 bootstrap(args) +*****0 elseif args.upgrade then +*****0 upgrade(args) +*****0 elseif args.confirm then +*****0 confirm(args) +*****0 elseif args.verify then +*****0 verify(args) +*****0 elseif args['test-other-partition'] then +*****0 test_other_partition(args) +*****0 elseif args['board-supported'] then +*****0 if su.is_current_board_supported() then +*****0 os.exit(su.EXIT_STATUS_OK) + else +*****0 print("This board is not supported") +*****0 os.exit(su.EXIT_STATUS_BOARD_NOT_SUPPORTED) + end +*****0 elseif args['confirm-remaining'] then +*****0 local out = "-1" +*****0 local f = io.open(su.REBOOT_FILE_CONFIG_TIMEOUT_S) +*****0 if f then +*****0 local total_time_s = tonumber(f:read('*l')) +*****0 if total_time_s then +*****0 out = string.format("%d", math.floor(total_time_s - utils.uptime_s())) + end +*****0 f:close() + end +*****0 io.write(out) +*****0 elseif args.show then +*****0 assert_su_installed() +*****0 print('safe-upgrade version: ' .. get_su_version()) +*****0 local partitions = su.get_partitions() + --TODO show labels of partitions (maybe store them when flashing from a metadata file) +*****0 print(string.format('current partition: %d', partitions.current)) +*****0 print(string.format('stable partition: %d', partitions.stable)) +*****0 print(string.format('testing partition: %d', partitions.testing)) + end + end + +============================================================================== +packages/shared-state-babel_links_info/files/usr/share/shared-state/publishers/shared-state-publish_babel_links_info +============================================================================== + #!/usr/bin/lua + + -- ! LibreMesh + -- ! Copyright (c) 2023 Javier Jorge + -- ! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial + -- ! Copyright (C) 2023 Asociación Civil Altermundi + -- ! + -- ! SPDX-License-Identifier: AGPL-3.0-only + + 11 local utils = require('lime.utils') + 11 local ubus = require "ubus" + 11 local shared_state_links_info = require ("shared_state_links_info") + + 11 local ifaceip = {} + + 11 function get_interface_ip(ifname) + 242 if ifaceip[ifname] == nil then + 198 ifaceip[ifname] = string.gsub(utils.unsafe_shell("ip -6 address show " + 66 .. ifname .. + 66 " | awk '{if ($1 == \"inet6\") print $2}' | grep fe80 | awk -F/ '{print $1}'"), + 132 "\n", "") + end + 242 return ifaceip[ifname] + end + + 11 function get_babel_links_info() + 22 local conn = ubus.connect() + 22 local links = {} + 22 babelneigt_obj = ubus.call(conn, "babeld", "get_neighbours", {}) + 22 if babelneigt_obj ~= nil then + 110 for key, value in pairs(babelneigt_obj.IPv6) do + 88 local key_table = {(string.gsub(get_interface_ip(value.dev),":","")),(string.gsub(key,":",""))} + 88 table.sort(key_table) + 88 links[table.concat(key_table)]= { + 88 src_ip = get_interface_ip(value.dev), + 88 dst_ip = key, + 88 iface = value.dev + 88 } + end + end + 22 return links + end + + 11 shared_state_links_info.insert_in_ss_with_location(get_babel_links_info(),"babel_links_info") + +============================================================================== +packages/shared-state-bat_hosts/files/usr/lib/lua/bat-hosts.lua +============================================================================== + 28 local JSON = require("luci.jsonc") + 28 local utils = require("lime.utils") + + 28 local bat_hosts = {} + + 28 function bat_hosts.bathost_deserialize(hostname_plus_iface) + 55 local partial_hostname = hostname_plus_iface + local iface + 330 for _, ifname in ipairs(utils.get_ifnames()) do + 275 local serialized_ifname = string.gsub(ifname, "%W", "_") + 275 serialized_ifname = utils.literalize(serialized_ifname) + 275 local replaced_hostname = hostname_plus_iface:gsub("_" .. serialized_ifname, "") + -- hostname don't have underscores see utils.is_valid_hostname + 275 replaced_hostname = replaced_hostname:gsub("_", "-") + 275 if #replaced_hostname < #partial_hostname then + 66 partial_hostname = replaced_hostname + 66 iface = ifname + end + end + 55 return partial_hostname, iface + end + + 28 function bat_hosts.get_bat_hosts_from_shared_state() +*****0 return JSON.parse( +*****0 io.popen("shared-state-async get bat-hosts 2> /dev/null", "r"):read("*all")) + end + + 28 function bat_hosts.get_bathost(mac, outgoing_iface) + 77 local bathosts = bat_hosts.get_bat_hosts_from_shared_state() + 77 local bathost = bathosts[mac:lower()] + 77 if bathost == nil then return end + 55 local hostname, iface = bat_hosts.bathost_deserialize(bathost) + 55 return { hostname = hostname, iface = iface } + end + + 28 return bat_hosts + +============================================================================== +packages/shared-state-bat_hosts/files/usr/libexec/rpcd/bat-hosts +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2013-2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2020 German Ferrero + ]]-- + + 44 local ubus = require "ubus" + 44 local json = require 'luci.jsonc' + 44 local utils = require 'lime.utils' + 44 local bat_hosts = require 'bat-hosts' + + 44 local conn = ubus.connect() + 44 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function get_bathost(msg) + 44 if not msg.mac or not utils.is_valid_mac(msg.mac) then + 11 utils.printJson({ status = "error", message = "invalid mac" }) + 11 return + end + + 33 if msg.outgoing_iface and not utils.has_value(utils.get_ifnames(), msg.outgoing_iface) then + 11 utils.printJson({ status = "error", message = "invalid outgoing interface" }) + 11 return + end + 22 local bathost = bat_hosts.get_bathost(msg.mac, msg.outgoing_iface) + 22 local result = {} + 22 if bathost.hostname ~= nil then + 22 result.status = "ok" + 22 result.bathost = bathost + else +*****0 result.status = "error" +*****0 result.error = "Couldn't retrieve hostname" + end + 22 utils.printJson(result) + end + + 44 local methods = { + 44 get_bathost = { mac = 'value', outgoing_iface = 'value'} + } + + 44 if arg[1] == 'list' then +*****0 utils.printJson(methods) + end + + 44 if arg[1] == 'call' then + 44 local msg = utils.rpcd_readline() or '{}' + 44 msg = json.parse(msg) + 44 if arg[2] == 'get_bathost' then get_bathost(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/shared-state-bat_links_info/files/usr/share/shared-state/publishers/shared-state-publish_bat_links_info +============================================================================== + #!/usr/bin/lua + + --! LibreMesh + --! Copyright (C) 2023 Javier Jorge + --! Copyright (C) 2023 Asociación Civil Altermundi + --! + --! SPDX-License-Identifier: AGPL-3.0-only + + 14 local JSON = require("luci.jsonc") + 14 local utils = require('lime.utils') + 14 local network = require ("lime.network") + 14 local shared_state_links_info = require ("shared_state_links_info") + + 14 function get_bat_links_info() + 28 local bat_neighbors_obj={} + 28 local bat_originators_obj={} + 28 local bat_originators = utils.unsafe_shell("batctl oj") + 28 bat_originators_obj = JSON.parse(bat_originators) + + + 28 local bat_neighbors = utils.unsafe_shell("batctl nj") + 28 bat_neighbors = string.gsub(bat_neighbors,"neigh_address","dst_mac") + 28 bat_neighbors = string.gsub(bat_neighbors,"hard_ifname","iface") + 28 bat_neighbors_obj = JSON.parse(bat_neighbors) + 28 local kv_fromlinks = {} + 28 if bat_neighbors_obj then + 140 for key,neight_value in pairs (bat_neighbors_obj) do + 112 local macparts = network.get_mac(neight_value.iface) + 112 local src_macaddr = table.concat(macparts,":") + 112 neight_value.hard_ifindex=nil + 112 neight_value.src_mac=string.lower(src_macaddr) + 112 neight_value.dst_mac=string.lower(neight_value.dst_mac) + 1904 for key,originator_value in pairs (bat_originators_obj) do + 1792 if originator_value.hard_ifname == neight_value.iface and + 896 originator_value.neigh_address== originator_value.orig_address and + 224 originator_value.neigh_address== neight_value.dst_mac then + -- Batman "transmit link quality" (tq) is a byte that describes + -- the probability of a successful transmission towards a + -- neighbor node + 112 neight_value.tq = originator_value.tq + end + end + 112 local key_table = {(string.gsub(neight_value.src_mac,":","")),(string.gsub(neight_value.dst_mac,":",""))} + 112 table.sort(key_table) + 112 kv_fromlinks[table.concat(key_table)]=neight_value + end + end + 28 return kv_fromlinks + end + + 14 shared_state_links_info.insert_in_ss_with_location(get_bat_links_info(),"bat_links_info") + +============================================================================== +packages/shared-state-network_nodes/files/usr/lib/lua/network-nodes.lua +============================================================================== + 11 local shared_state = require("shared-state") + 11 local utils = require("lime.utils") + 11 local config = require("lime.config") + + 11 local network_nodes = {} + + 11 function network_nodes._node(hostname, member, fw_version, board, ipv4, ipv6) + 286 return {hostname=hostname, member=member, fw_version=fw_version, board=board, ipv4=ipv4, ipv6=ipv6} + end + + 11 function network_nodes._serialize_for_network_nodes(node) + 110 return {hostname=node.hostname, member=node.member, fw_version=node.fw_version, board=node.board, + 110 ipv4=node.ipv4, ipv6=node.ipv6} + end + + 11 function network_nodes._deserialize_from_network_nodes(data) + 187 return network_nodes._node(data.hostname, data.member, data.fw_version, data.board, data.ipv4, data.ipv6) + end + + 11 function network_nodes._nodes_from_db(db) + 66 local nodes = {} + 242 for hostname, value in pairs(db:get()) do + 176 nodes[hostname] = network_nodes._deserialize_from_network_nodes(value.data) + end + 66 return nodes + end + + 11 function network_nodes._create_node() + 22 local uci = config.get_uci_cursor() + + 22 local hostname = utils.hostname() + 22 local fw_version = utils.release_info()['DISTRIB_RELEASE'] + 22 local board = utils.current_board() + 22 local member = true + 22 local ipv4 = uci:get("network", "lan", "ipaddr") + 22 local ipv6 = uci:get("network", "lan", "ip6addr") + 22 if ipv6 then ipv6 = ipv6:gsub("/.*$", "") end -- remove the netmask info + 22 local node = network_nodes._node(hostname, member, fw_version, board, ipv4, ipv6) + 22 node.status = "recently_reachable" + + 22 return node + end + + --! Public API + + 11 function network_nodes.get_nodes() + 44 local network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") + 44 local node_and_links_db = shared_state.SharedState:new("nodes_and_links") + + 44 local nodes = {} + -- augment the node information from the network_nodes and the 'nodes_and_links' dbs + 154 for hostname, node in pairs(network_nodes._nodes_from_db(network_nodes_db)) do + 110 if node_and_links_db:get()[hostname] then + 22 node.status = "recently_reachable" + 88 elseif node.member then + 66 node.status = "unreachable" + else + 22 node.status = "gone" + end + 110 nodes[hostname] = node + end + 44 return nodes + end + + 11 function network_nodes.as_human_readable_table() + 11 local nodes = network_nodes.get_nodes() + 11 local tmpl = "%-26s %-16s %-30s %-20s %-30s %-40s\n" + 11 local out = string.format(tmpl, "hostname", "ipv4", "ipv6", "status", "board", "fw_version") + 44 for _, node in pairs(nodes) do + 33 if node.member then + 66 out = out .. string.format(tmpl, node.hostname, node.ipv4, node.ipv6, node.status, + 66 node.board, node.fw_version) + end + end + 11 return out + end + + 11 function network_nodes.publish() + 11 local node = network_nodes._create_node() + 11 local data = { + 11 [node.hostname] = network_nodes._serialize_for_network_nodes(node) + } + 11 network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") + 11 network_nodes_db:insert(data) + end + + 11 function network_nodes.mark_nodes_as_gone(hostnames) + 11 local network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") + 11 local nodes = network_nodes._nodes_from_db(network_nodes_db) + 11 local data = {} + 33 for _, hostname in pairs(hostnames or {}) do + 22 local node = nodes[hostname] + 22 if node then + 22 node.member = false + 22 data[hostname] = network_nodes._serialize_for_network_nodes(node) + end + end + 11 network_nodes_db:insert(data) + end + + 11 return network_nodes + +============================================================================== +packages/shared-state-node_info/files/usr/share/shared-state/publishers/shared-state-publish_node_info +============================================================================== + #!/usr/bin/lua + + --! LibreMesh + --! Copyright (C) 2023 Javier Jorge + --! Copyright (C) 2023 Asociación Civil Altermundi + --! + --! This program is free software: you can redistribute it and/or modify + --! it under the terms of the GNU Affero General Public License as + --! published by the Free Software Foundation, either version 3 of the + --! License, or (at your option) any later version. + --! + --! This program is distributed in the hope that it will be useful, + --! but WITHOUT ANY WARRANTY; without even the implied warranty of + --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + --! GNU Affero General Public License for more details. + --! + --! You should have received a copy of the GNU Affero General Public License + --! along with this program. If not, see . + + 11 local JSON = require("luci.jsonc") + 11 local network = require ("lime.network") + 11 local location = require("lime.location") + 11 local utils = require('lime.utils') + 11 local config = require("lime.config") + + 11 local hostname = utils.hostname() + 11 function get_node_info() + 22 local uci = config.get_uci_cursor() + 22 local coords = location.get_node() or location.get_community() or + 22 {lat="FIXME", long="FIXME"} + 22 local fw_version = "no version" + 44 pcall(function () fw_version = utils.release_info()['DISTRIB_RELEASE'] end) + 22 local board = "no board" + 44 pcall(function () board = utils.current_board() end) + 22 local ipv4 = uci:get("network", "lan", "ipaddr") + 22 local ipv6 = uci:get("network", "lan", "ip6addr") + 22 if ipv6 then ipv6 = ipv6:gsub("/.*$", "") end + 22 local uptime = utils.uptime_s() + 22 local macs = network.get_own_macs("*") + 22 return {hostname=hostname, firmware_version=fw_version, board=board, + 22 ipv4=ipv4, ipv6=ipv6,coordinates=coords,macs= macs,uptime = uptime, + 22 device = "Router" } + end + + 11 local result = { [hostname] = get_node_info() } + 11 io.popen("shared-state-async insert node_info", "w"):write(JSON.stringify(result)) + +============================================================================== +packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases +============================================================================== + #!/usr/bin/lua + + 33 local JSON = require("luci.jsonc") + 33 local CRDT = "odhcpd-leases" + + + 33 local handle = io.popen("ubus call dhcp ipv4leases '{}' 2>/dev/null") + 33 local ubus_output = handle:read("*a") + 33 handle:close() + + + 33 local ubus_data = JSON.parse(ubus_output or "{}") + + 33 local output_table = {} + + + 33 if ubus_data and ubus_data.device then + + 8 for device_name, device_data in pairs(ubus_data.device) do + 4 if device_data and device_data.leases then + + 12 for _, lease in ipairs(device_data.leases) do + + 8 if lease.address and lease.mac then + + 8 output_table[lease.address] = { + 8 hostname = lease.hostname or "", + 8 mac = lease.mac + 8 } + end + end + end + end + end + + + 33 local final_json_string = JSON.stringify(output_table) + + + 33 local pipe = io.popen("shared-state-async insert " .. CRDT, "w") + 33 if pipe then + 33 pipe:write(final_json_string) + 33 pipe:close() + end + + + 33 os.execute("/usr/sbin/odhcpd-update >/dev/null 2>&1") + +============================================================================== +packages/shared-state-ref_state_commons/files/usr/lib/lua/shared_state_links_info.lua +============================================================================== + #!/usr/bin/lua + + --! LibreMesh + --! Copyright (c) 2024 Javier Jorge + --! Copyright (c) 2024 Instituto Nacional de Tecnología Industrial + --! Copyright (C) 2024 Asociación Civil Altermundi + --! SPDX-License-Identifier: AGPL-3.0-only + + 39 local JSON = require("luci.jsonc") + 39 local location = require 'lime.location' + + 39 local shared_state_links_info = {} + + 39 function shared_state_links_info.add_dst_loc(links_info, shared_state_sample, hostname) + 57 if shared_state_sample ~= nil then + 54 for link, l_data in pairs(links_info.links) do + 108 for node, data in pairs(shared_state_sample) do + 72 if node ~= hostname and data.links ~= nil then + 18 local link_data = data.links[link] + 18 if link_data ~= nil and data.src_loc~= nil then + 9 l_data.dst_loc = {} + 9 l_data.dst_loc.lat = data.src_loc.lat + 9 l_data.dst_loc.long = data.src_loc.long + end + end + end + end + end + end + + 39 function shared_state_links_info.add_own_location_to_links(links) + 76 return { + 76 links = links, + -- we are not interested in the community location. + 76 src_loc = location.get_node() or { + 76 lat = "FIXME", + 76 long = "FIXME" + 76 } + 76 } + end + + 39 function shared_state_links_info.insert_in_ss_with_location(links,data_type_name) + 39 local hostname = io.input("/proc/sys/kernel/hostname"):read("*line") + 39 local links_info = shared_state_links_info.add_own_location_to_links(links) + 39 local shared_state_sample = JSON.parse(io.popen("shared-state-async get "..data_type_name, "r"):read('*all')) + 39 shared_state_links_info.add_dst_loc(links_info, shared_state_sample, hostname) + 39 local result = {[hostname] = links_info} + 39 io.popen("shared-state-async insert "..data_type_name, "w"):write(JSON.stringify(result)) + end + + 39 return shared_state_links_info + +============================================================================== +packages/shared-state-wifi_links_info/files/usr/share/shared-state/publishers/shared-state-publish_wifi_links_info +============================================================================== + #!/usr/bin/lua + + --! LibreMesh + --! Copyright (C) 2019 Gioacchino Mazzurco + --! Copyright (c) 2023 Javier Jorge + --! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial + --! Copyright (C) 2023 Asociación Civil Altermundi + --! SPDX-License-Identifier: AGPL-3.0-only + + 14 local node_status = require("lime.node_status") + 14 local network = require("lime.network") + 14 local iwinfo = require ("iwinfo") + 14 local shared_state_links_info = require ("shared_state_links_info") + + 14 local data_type_name = "wifi_links_info" + + 14 function get_wifi_links_info() + 51 local stations = node_status.get_stations() + 51 local links = {} + 125 for _, station in ipairs(stations) do + 74 macparts = network.get_mac(station.iface) + 74 src_macaddr = string.lower(table.concat(macparts, ":")) + 74 local station_stats = node_status.get_station_stats(station) + 74 local freq = iwinfo.nl80211.frequency(station.iface) + 74 local chanenel = iwinfo.nl80211.channel(station.iface) + 74 local key_table = {string.lower(string.gsub(src_macaddr, ":", "")), + 74 string.lower(string.gsub(station.station_mac, ":", ""))} + 74 table.sort(key_table) + 74 links[table.concat(key_table)] = { + 74 src_mac = src_macaddr, + 74 dst_mac = string.lower(station.station_mac), + 74 signal = station_stats.signal, + 74 chains = station_stats.chains, + 74 iface = station.iface, + 74 rx_rate = station_stats.rx_rate, + 74 tx_rate = station_stats.tx_rate, + 74 freq = freq, + 74 channel = chanenel, + --dst_loc = {lat="FIXME",long="FIXME"} --if no location is found later no dst location will be informed + 74 } + end + 51 return links + end + + 14 shared_state_links_info.insert_in_ss_with_location(get_wifi_links_info(),data_type_name) + +============================================================================== +packages/shared-state/files/usr/lib/lua/shared-state.lua +============================================================================== + #!/usr/bin/lua + + --! Minimalistic CRDT-like shared state structure suitable for mesh networks + --! + --! Copyright (C) 2019-2020 Gioacchino Mazzurco + --! + --! This program is free software: you can redistribute it and/or modify + --! it under the terms of the GNU Affero General Public License version 3 as + --! published by the Free Software Foundation. + --! + --! This program is distributed in the hope that it will be useful, + --! but WITHOUT ANY WARRANTY; without even the implied warranty of + --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + --! GNU Affero General Public License for more details. + --! + --! You should have received a copy of the GNU Affero General Public License + --! along with this program. If not, see . + + 110 local fs = require("nixio.fs") + 110 local JSON = require("luci.jsonc") + 110 local nixio = require("nixio") + 110 local uci = require("uci") + 110 local utils = require("lime.utils") + + 110 local shared_state = {} + 110 shared_state.DATA_DIR = '/var/shared-state/data/' + 110 shared_state.PERSISTENT_DATA_DIR = '/etc/shared-state/persistent-data/' + 110 shared_state.ERROR_LOCK_FAILED = 165 + 110 shared_state.CANDIDATE_NEIGHBORS_BIN = '/usr/bin/shared-state-get_candidates_neigh' + + 110 local SharedStateBase = {} + + 110 function SharedStateBase:load(mergeWithCurrentState) + 1004 local onDiskData = JSON.parse(self.storageFD:readall()) or {} + 1004 if mergeWithCurrentState then +*****0 self:_merge(onDiskData) + else + 1944 for key, value in pairs(onDiskData) do + 940 self.storage[key] = value + end + end + end + + 110 function SharedStateBase:lock(maxwait) + 1026 if self.locked then return end + 1026 maxwait = maxwait or 10 + 1026 fs.mkdirr(fs.dirname(self.dataFile)) + 2052 self.storageFD = nixio.open( + 2052 self.dataFile, nixio.open_flags("rdwr", "creat") ) + + 1026 for i=1,maxwait do + 1026 if not self.storageFD:lock("tlock") then +*****0 nixio.nanosleep(1) + else + 1026 self.locked = true + 1026 break + end + end + + 1026 if not self.locked then +*****0 self.log( "err", self.dataFile, "Failed acquiring lock on data!" ) +*****0 os.exit(shared_state.ERROR_LOCK_FAILED) + end + end + + 110 function SharedStateBase:merge(stateSlice) + 55 self:lock() + 55 self:load() + 55 self:_merge(stateSlice) + 55 self:save() + 55 self:unlock() + 55 self:notifyHooks() + end + + 110 function SharedStateBase:notifyHooks() + 454 if self.changed then + 454 local jsonString = self:toJsonString() + 454 if not fs.dir(self.hooksDir) then return end +*****0 for hook in fs.dir(self.hooksDir) do +*****0 local cStdin = io.popen(self.hooksDir.."/"..hook, "w") +*****0 cStdin:write(jsonString) +*****0 cStdin:close() + end + end + end + + 110 function SharedStateBase:save() + 487 if self.changed then + 487 local outFd = io.open(self.dataFile, "w") + 487 outFd:write(self:toJsonString()) + 487 outFd:close() + 487 outFd = nil + end + end + + 110 function SharedStateBase:httpRequest(url, body) +*****0 local tmpfname = os.tmpname() + +*****0 local tmpfd = io.open(tmpfname, "w") +*****0 tmpfd:write(body) +*****0 tmpfd:close() +*****0 tmpfd = nil + +*****0 local cmd = "uclient-fetch --no-check-certificate -q -O- --timeout=3 " +*****0 cmd = cmd.."--post-file='"..tmpfname.."' '"..url.."' ; " +*****0 cmd = cmd.."rm -f '"..tmpfname.."'" +*****0 local fd = io.popen(cmd) + +*****0 local value = fd:read("*a") +*****0 fd:close() + +*****0 return value + end + + 110 function SharedStateBase:_sync(urls) +*****0 urls = urls or {} + +*****0 if #urls < 1 then +*****0 local uci_cursor = uci:cursor() + local fixed_candidates = +*****0 uci_cursor:get("shared-state", "options","candidates") or {} +*****0 for _, line in pairs(fixed_candidates) do +*****0 table.insert( + urls, +*****0 line.."/"..self.dataType ) + end + +*****0 io.input(io.popen(shared_state.CANDIDATE_NEIGHBORS_BIN)) +*****0 for line in io.lines() do +*****0 table.insert( + urls, +*****0 self:getSyncUrl(line, self.dataType)) + end + end + +*****0 for _,url in ipairs(urls) do +*****0 local body = self:toJsonString() + +*****0 local response = self:httpRequest(url, body) + +*****0 if type(response) == "string" and response:len() > 1 then +*****0 local parsedJson = JSON.parse(response) +*****0 if parsedJson then self:_merge(parsedJson) end + else +*****0 self.log( "debug", "error requesting "..url ) + end + end + end + + 110 function SharedStateBase:sync(urls) +*****0 self:lock() +*****0 self:load() +*****0 self:unlock() +*****0 self:_sync(urls) +*****0 self:lock() +*****0 self:load(true) -- Take in account changes happened during sync +*****0 self:save() +*****0 self:unlock() +*****0 self:notifyHooks() + end + + 110 function SharedStateBase:toJsonString() + 941 return JSON.stringify(self.storage) + end + + 110 function SharedStateBase:get() + 517 self:lock() + 517 self:load() + 517 self:unlock() + 517 return self.storage + end + + 110 function SharedStateBase:unlock() + 1004 if not self.locked then return end + 1004 self.storageFD:lock("ulock") + 1004 self.storageFD:close() + 1004 self.storageFD = nil + 1004 self.locked = false + end + + 110 function createSharedStateBase(dataType, logger, dataFile) + 590 local logger = (type(logger) == "function") and logger or function() end + 546 local newInstance = { + 546 dataType = dataType, + 546 log = logger, + --! Map + --! bleachTTL is the count of how much bleaching should occur before the + --! entry expires + --! author is the name of the host who generated that entry + --! data is the value of the entry + 546 storage={}, + --! true if self_storage has changed after loading + 546 changed=false, + -- File descriptor of the persistent file storage + 546 storageFD=nil, + --! true when persistent storage file is locked by this instance + 546 locked=false, + 546 dataFile = dataFile, + 546 hooksDir = "/etc/shared-state/hooks/"..dataType.."/" + } + 546 return newInstance + end + + 110 local SharedState = {} + 110 setmetatable(SharedState, {__index = SharedStateBase}) + + 110 function SharedState:new(dataType, logger) + 204 local dataFile = shared_state.DATA_DIR..dataType..".json" + 204 local newInstance = createSharedStateBase(dataType, logger, dataFile) + 204 setmetatable(newInstance, {__index = SharedState}) + 204 return newInstance + end + + 110 function SharedState:_bleach() + 44 local substancialChange = false + 110 for k,v in pairs(self.storage) do + 66 if(v.bleachTTL < 2) then + 11 self.storage[k] = nil + 11 substancialChange = true + else + 55 v.bleachTTL = v.bleachTTL-1 + end + 66 self.changed = true + end + 44 return substancialChange + end + + 110 function SharedState:bleach() + 44 self:lock() + 44 self:load() + 44 local shouldNotify = self:_bleach() + 44 self:save() + 44 self:unlock() + --! Avoid hooks being called if data hasn't substantially changed + 44 if(shouldNotify) then self:notifyHooks() end + end + + 110 function SharedState:_insert(key, data, bleachTTL) + 209 bleachTTL = bleachTTL or 30 + 209 self.storage[key] = { + 209 bleachTTL=bleachTTL, + 209 author=io.input("/proc/sys/kernel/hostname"):read("*line"), + 209 data=data + 209 } + 209 self.changed = true + end + + 110 function SharedState:insert(data, bleachTTL) + 132 self:lock() + 132 self:load() + 330 for key, lv in pairs(data) do self:_insert(key, lv, bleachTTL) end + 132 self:save() + 132 self:unlock() + 132 self:notifyHooks() + end + + 110 function SharedState:_merge(stateSlice) + 11 local stateSlice = stateSlice or {} + 44 for key,rv in pairs(stateSlice) do + 33 if rv.bleachTTL <= 0 then +*****0 self.log( "debug", "sharedState:merge got expired entry" ) +*****0 self.changed = true + else + 33 local lv = self.storage[key] + 33 if( lv == nil or lv.bleachTTL < rv.bleachTTL ) then + 44 self.log( "debug", "Updating entry for: "..key.." older: ".. + 22 (lv and lv.bleachTTL or 'no entry').." newer: "..rv.bleachTTL ) + 22 self.storage[key] = rv + 22 self.changed = true + end + end + end + end + + 110 function SharedState:_remove(key) + 11 if(self.storage[key] ~= nil and self.storage[key].data ~= nil) + 11 then self:_insert(key, nil) end + end + + 110 function SharedState:remove(keys) + 11 self:lock() + 11 self:load() + 22 for _,key in ipairs(keys) do self:_remove(key) end + 11 self:save() + 11 self:unlock() + 11 self:notifyHooks() + end + + 110 function SharedState:getSyncUrl(host) +*****0 return "http://["..host.."]/cgi-bin/shared-state/"..self.dataType + end + + + 110 local SharedStateMultiWriter = {} + 110 setmetatable(SharedStateMultiWriter, {__index = SharedStateBase}) + + 110 function SharedStateMultiWriter:new(dataType, logger) + 342 local dataFile = shared_state.PERSISTENT_DATA_DIR..dataType..".json" + 342 local newInstance = createSharedStateBase(dataType, logger, dataFile) + 342 setmetatable(newInstance, {__index = SharedStateMultiWriter}) + 342 return newInstance + end + + + 110 function SharedStateMultiWriter:_merge(stateSlice) + --! Make merge based on an incremental counter (changes) and a random number (fortune) + 44 local stateSlice = stateSlice or {} + 88 for key,rv in pairs(stateSlice) do + 44 local lv = self.storage[key] + 44 if ( lv == nil or lv.changes < rv.changes or + 33 ( lv.changes == rv.changes and lv.fortune < rv.fortune )) then + 44 self.log( "debug", "Updating entry for: "..key.." older: ".. + 22 (lv and lv.changes or 'no entry') .." newer: "..rv.changes ) + 22 self.storage[key] = rv + 22 self.changed = true + end + end + end + + 110 function SharedStateMultiWriter:insert(data) + 245 self:lock() + 245 self:load() + 747 for key, lv in pairs(data) do self:_insert(key, lv) end + 245 self:save() + 245 self:unlock() + 245 self:notifyHooks() + end + + 110 function shared_state._getFortune() + 269 return math.random(1, 100000) + end + + 110 function SharedStateMultiWriter:_insert(key, data) + 502 local lv = self.storage[key] + 502 if (lv == nil or not utils.deepcompare(lv.data, data)) then + 478 local changes = lv and lv.changes + 1 or 0 + 478 self.storage[key] = { + 478 lastModified=os.time(), + 478 changes=changes, + 478 fortune=shared_state._getFortune(), + 478 author=io.input("/proc/sys/kernel/hostname"):read("*line"), + 478 data=data + 478 } + 478 self.changed = true + end + end + + 110 function SharedStateMultiWriter:getSyncUrl(host) +*****0 return "http://["..host.."]/cgi-bin/shared-state-multiwriter/"..self.dataType + end + + 110 shared_state.SharedState = SharedState + 110 shared_state.SharedStateMultiWriter = SharedStateMultiWriter + 110 return shared_state + +============================================================================== +packages/shared-state/files/usr/libexec/rpcd/shared-state +============================================================================== + #!/usr/bin/env lua + + --! Shared State + --! Copyright (c) 2023 Javier Jorge + --! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial + --! Copyright (C) 2023 Asociación Civil Altermundi + --! SPDX-License-Identifier: AGPL-3.0-only + + 125 local ubus = require "ubus" + 125 local utils = require('lime.utils') + 125 local shared_state = require("shared-state") + 125 local json = require 'luci.jsonc' + + 125 require("nixio.util") + + 125 local response_template = {data = {}, error = 500} + + local function showData(sharedState) + 48 local resultTable = sharedState:get() + 48 if next(resultTable) == nill then + 6 response_template.error = 404 + else + 156 for k, v in pairs(resultTable) do + 114 response_template.data[k] = v.data + end + 42 response_template.error = 0 + end + 48 utils.printJson(response_template) + end + + local function getFromSharedState(msg) + 17 local sharedState = shared_state.SharedState:new(msg.data_type, + 6 nixio.syslog) + 6 showData(sharedState) + end + + local function getFromSharedStateMultiWriter(msg) + 99 local sharedState = shared_state.SharedStateMultiWriter:new(msg.data_type, + 42 nixio.syslog) + 42 showData(sharedState) + end + + local function insertIntoSharedStateMultiWriter(msg) + 82 local sharedState = shared_state.SharedStateMultiWriter:new(msg.data_type, + 36 nixio.syslog) + 36 local inputTable = msg.json or {} + 36 sharedState:insert(inputTable) + end + + 125 local methods = { + 125 getFromSharedState = { + 125 data_type = 'value' + 125 }, + 125 getFromSharedStateMultiWriter = { + 125 data_type = 'value' + 125 }, + 125 insertIntoSharedStateMultiWriter = { + 125 data_type = 'value', + 125 json = 'value' + 125 } + } + + 125 if arg[1] == 'list' then + 11 utils.printJson(methods) + end + + 125 if arg[1] == 'call' then + 114 local msg = utils.rpcd_readline() + 114 msg = json.parse(msg) + 114 if arg[2] == 'getFromSharedState' then + 11 getFromSharedState(msg) + 103 elseif arg[2] == 'getFromSharedStateMultiWriter' then + 57 getFromSharedStateMultiWriter(msg) + 46 elseif arg[2] == 'insertIntoSharedStateMultiWriter' then + 46 insertIntoSharedStateMultiWriter(msg) + else +*****0 utils.printJson({ +*****0 error = "Method not found" + }) + end + end + +============================================================================== +packages/ubus-lime-location/files/usr/lib/lua/lime/location.lua +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright 2017 Marcos Gutierrez + Copyright 2020 Santiago Piccinini + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-3.0 + ]]-- + + 61 local iwinfo = require "iwinfo" + 61 local json = require "luci.jsonc" + 61 local config = require "lime.config" + 61 local wireless = require "lime.wireless" + 61 local system = require "lime.system" + + 61 local location = {} + + 61 function location.is_valid_coordinate(value) + 287 return type(tonumber(value)) == "number" + end + + 61 function location.get_node() + 179 local uci = config.get_uci_cursor() + 179 local lat = uci:get("location", "settings", "node_latitude") + 179 local long = uci:get("location", "settings", "node_longitude") + + 179 if location.is_valid_coordinate(lat) and location.is_valid_coordinate(long) then + 23 return {lat=lat, long=long} + end + end + + 61 function location.get_community() + 68 local uci = config.get_uci_cursor() + 68 local lat = uci:get("location", "settings", "community_latitude") + 68 local long = uci:get("location", "settings", "community_longitude") + + 68 if location.is_valid_coordinate(lat) and location.is_valid_coordinate(long) then + 17 return {lat=lat, long=long} + end + end + + 61 function location.set(lat, long) + 12 local uci = config.get_uci_cursor() + 12 uci:set("location", "settings", "node_latitude", lat) + 12 uci:set("location", "settings", "node_longitude", long) + 12 uci:commit("location") + 12 local hostname = system.get_hostname() + 12 local data = {} + 12 data[hostname] = location.nodes_and_links() + 12 io.popen("shared-state insert nodes_and_links", "w"):write(json.stringify(data)) + end + + 61 function location.nodes_and_links() + 30 local hostname = io.input("/proc/sys/kernel/hostname"):read("*line") + 30 local macs = network.get_own_macs("wlan*") + + 30 local coords = location.get_node() or location.get_community() or {lat="FIXME", long="FIXME"} + local iface, currneigh, _, n + + 30 local interfaces = wireless.mesh_ifaces() + 30 local links = {} + 42 for _, iface in pairs(interfaces) do + 12 currneigh = iwinfo.nl80211.assoclist(iface) + 18 for mac, station in pairs(currneigh) do + 6 table.insert(links, string.lower(mac)) + end + end + 30 return {hostname=hostname, macs=macs, coordinates={lat=coords.lat, lon=coords.long}, links=links} + end + + 61 return location + +============================================================================== +packages/ubus-lime-location/files/usr/libexec/rpcd/lime-location +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright 2017 Marcos Gutierrez + Copyright 2020 Santiago Piccinini + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-3.0 + ]]-- + + + 61 local ubus = require "ubus" + 61 local iwinfo = require "iwinfo" + 61 local json = require "luci.jsonc" + 61 local config = require("lime.config") + 61 local wireless = require "lime.wireless" + 61 local network = require 'lime.network' + 61 local utils = require 'lime.utils' + 61 local location = require 'lime.location' + + + 61 local conn = ubus.connect() + 61 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function _get_location(msg) + 33 local result = {location = {}, default = false} + + 33 local coords = location.get_node() + 33 if not coords then + 22 result.default = true + 22 coords = location.get_community() or {lat="FIXME", long="FIXME"} + end + + 33 result.location.lat = coords.lat + 33 result.location.lon = coords.long + 33 result.status = "ok" + 33 return result + end + + local function get_location(msg) + 33 utils.printJson(_get_location(msg)) + end + + local function set_location(msg) + 11 location.set(msg.lat, msg.lon) + 6 utils.printJson({ lat = msg.lat, lon = msg.lon, status = 'ok' }); + end + + local function nodes_and_links() + 6 local hostname = utils.hostname() + 6 local result = {} + 6 result[hostname] = location.nodes_and_links() + 6 utils.printJson(result) + end + + 61 function all_nodes_and_links(msg) +*****0 print('{"result":'..utils.unsafe_shell("shared-state get nodes_and_links")..'}') + end + + 61 local methods = { + 61 all_nodes_and_links = { no_params = 0}, + 61 nodes_and_links = { no_params = 0}, + 61 get = { no_params = 0 }, + 61 set = { lat = 'value', lon = 'value' } + } + + 61 if arg[1] == 'list' then + 11 utils.printJson(methods) + end + + 61 if arg[1] == 'call' then + 50 local msg = utils.rpcd_readline() + 50 msg = json.parse(msg) + 50 if arg[2] == 'get' then get_location(msg) + 17 elseif arg[2] == 'set' then set_location(msg) + 6 elseif arg[2] == 'nodes_and_links' then nodes_and_links(msg) +*****0 elseif arg[2] == 'all_nodes_and_links' then all_nodes_and_links(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics.lua +============================================================================== + #!/usr/bin/lua + + 14 local utils = require('lime-metrics.utils') + 14 local lutils = require("lime.utils") + 14 local json = require 'luci.jsonc' + + 14 local metrics = {} + + 14 function metrics.get_last_internet_path_filename() + 14 return "/etc/last_internet_path" + end + + 14 function metrics.get_metrics(target) + 28 local result = {} + 28 local node = target + 28 local loss = nil + 28 local shell_output = "" + + 28 if lutils.is_installed("lime-proto-bmx6") then +*****0 loss = utils.get_loss(node..".mesh") +*****0 shell_output = lutils.unsafe_shell("netperf -6 -l 10 -H "..node..".mesh| tail -n1| awk '{ print $5 }'") + 28 elseif lutils.is_installed("lime-proto-babeld") then + 14 loss = utils.get_loss(node) + 14 shell_output = lutils.unsafe_shell("netperf -l 10 -H "..node.."| tail -n1| awk '{ print $5 }'") + else + 14 return {status="error", error={msg="No lime-proto-bmx6 or lime-proto-babeld found", code="1"}} + end + 14 local bw = 0 + 14 if shell_output ~= "" and shell_output ~= nil then + 5 bw = shell_output:match("[%d.]+") + end + 14 result.loss = loss + 14 result.bandwidth = bw + 14 result.status = "ok" + 14 return result + end + + 14 function metrics.get_loss(target) +*****0 local result = {} +*****0 local node = target + local loss = nil + +*****0 loss = utils.get_loss(node) +*****0 result.loss = loss +*****0 result.status = "ok" +*****0 return result + end + + + 14 function metrics.get_gateway() + 28 local result = {} + 28 local gw = nil + + 28 local internet_path_file = io.open(metrics.get_last_internet_path_filename(), "r") + 28 if internet_path_file then + 28 local path_content = assert(internet_path_file:read("*a"), nil) + 28 internet_path_file:close() + 28 path = json.parse(path_content) or nil + 28 if lutils.tableLength(path) > 0 then + 9 return { status="ok", gateway=path[lutils.tableLength(path)] } + end + end + + 9 return {status="error", error={msg="Not found. No gateway available.", code="1"}} + end + + 14 function metrics.get_last_internet_path(msg) + 28 local internet_path_file = io.open(metrics.get_last_internet_path_filename(), "r") + 28 if internet_path_file then + 14 path_content = assert(internet_path_file:read("*a"), nil) + 14 internet_path_file:close() + 14 path = json.parse(path_content) or nil + 14 local result = {} + 14 if path ~= nil then + 9 result.path = path + 9 result.status = "ok" + 9 return result + end + else + 14 return {status="error", error={msg="Not found. No known Internet path.", code="1"}} + end + end + + 14 function metrics.get_internet_status( ) + 14 local result = {} + 14 local lossV4 = utils.get_loss("4.2.2.2") + 14 if lossV4 ~= "100" then + 14 result.IPv4 = { working=true } + else +*****0 result.IPv4 = { working=false } + end + + 14 local lossV6 = utils.get_loss("2600::") + 14 if lossV6 ~= "100" then + 14 result.IPv6 = { working=true } + else +*****0 result.IPv6 = { working=false } + end + 14 local lookup_output = utils.is_nslookup_working() + 14 if lookup_output ~= "" then + 14 result.DNS = { working=true } + else +*****0 result.DNS = { working=false } + end + 14 result.status = "ok" + 14 return result + end + + 14 function metrics.get_station_traffic(msg) + 28 local iface = msg.iface + 18 local mac = msg.station_mac + 18 local result = {} + 18 local traffic = lutils.unsafe_shell("iw "..iface.." station get "..mac.." | grep bytes | awk '{ print $3}'") + 18 if traffic == "" or traffic == nil then + 9 return {status="error", error={msg="No interface found.", code="1"}} + end + 9 words = {} + 27 for w in traffic:gmatch("[^\n]+") do table.insert(words, w) end + 9 rx = words[1] + 9 tx = words[2] + 9 result.station = mac + 9 result.rx_bytes = tonumber(rx, 10) + 9 result.tx_bytes = tonumber(tx, 10) + 9 result.status = "ok" + 9 return result + end + + + 14 return metrics + +============================================================================== +packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics/utils.lua +============================================================================== + 14 local lutils = require("lime.utils") + + 14 local utils = {} + + 14 function utils.is_nslookup_working() +*****0 local shell_output = lutils.unsafe_shell("nslookup google.com | grep Name -A2 | grep Address") +*****0 return shell_output + end + + 14 function utils.get_loss(host) + 14 local shell_output = lutils.unsafe_shell("ping -q -i 0.1 -c4 -w2 "..host) + 14 local loss = "100" + 14 if shell_output ~= "" and shell_output ~= nil then + 5 loss = shell_output:match("(%d*)%% packet loss") + end + 14 return loss + end + + 14 return utils + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime-utils-admin.lua +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2020 Santiago Piccinini + ]]-- + + + 6 local utils = require 'lime.utils' + 6 local config = require 'lime.config' + 6 local upgrade = require 'lime.upgrade' + 6 local hotspot_wwan = require "lime.hotspot_wwan" + + + 6 local UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" + + 6 local limeutilsadmin = {} + + 6 function limeutilsadmin.set_root_password(msg) + local result = nil + 6 if type(msg.password) ~= "string" then +*****0 result = {status = 'error', msg = 'Password must be a string'} + else + 6 utils.set_shared_root_password(msg.password or '') + 6 result = {status = 'ok'} + end + 6 return result + end + + 6 function limeutilsadmin.set_hostname(msg) + 18 if msg.hostname ~= nil and utils.is_valid_hostname(msg.hostname) then + 6 local uci = config.get_uci_cursor() + 6 uci:set(config.UCI_NODE_NAME, 'system', 'hostname', msg.hostname) + 6 uci:commit(config.UCI_NODE_NAME) + 6 utils.unsafe_shell("lime-config") + 6 return { status = 'ok'} + else + local err + 12 if msg.hostname then + 6 err = 'Invalid hostname' + else + 6 err = 'Hostname not provided' + end + 12 return { status = 'error', msg = err } + end + end + + 6 function limeutilsadmin.is_upgrade_confirm_supported() + 12 local supported = upgrade.is_upgrade_confirm_supported() + 12 return {status = 'ok', supported = supported} + end + + + 6 function limeutilsadmin.firmware_upgrade(msg) + 12 local status, ret = upgrade.firmware_upgrade(msg.fw_path, msg.preserve_config, msg.metadata, msg.fw_type) + 12 if status then + 12 return {status = 'ok', metadata = ret} + else +*****0 return {status = 'error', message = ret} + end + end + + 6 function limeutilsadmin.last_upgrade_metadata() + local metadata + 12 if utils.file_exists(UPGRADE_METADATA_FILE) then + 6 metadata = utils.read_obj_store(UPGRADE_METADATA_FILE) + 6 return {status = 'ok', metadata = metadata} + else + 6 return {status = 'error', message = 'No metadata available'} + end + end + + 6 function limeutilsadmin.firmware_confirm() + 6 local exit_code = os.execute("safe-upgrade confirm > /dev/null 2>&1") + 6 local status = 'error' + 6 if exit_code == 0 then + 6 status = 'ok' + end + 6 return {status = status, exit_code = exit_code} + end + + --! Creates a client connection to a wifi hotspot + 6 function limeutilsadmin.hotspot_wwan_enable(msg) + 18 local msg = msg or {} + 18 local status, errmsg = hotspot_wwan.safe_enable(msg.ssid, msg.password, msg.encryption, msg.radio) + 18 if status then + 18 return {status = 'ok'} + else +*****0 return {status = 'error', message = errmsg} + end + end + + + 6 function limeutilsadmin.hotspot_wwan_disable(msg) + 6 local msg = msg or {} + 6 local status, errmsg = hotspot_wwan.disable(msg.radio) + 6 if status then + 6 return {status = 'ok'} + else +*****0 return {status = 'error', message = errmsg} + end + end + + + 6 function limeutilsadmin.safe_reboot(msg) +*****0 local result = {} + local function getStatus() +*****0 local f = io.open('/overlay/upper/.etc.last-good.tgz', "rb") +*****0 if f then f:close() end +*****0 return f ~= nil + end + + -- Get safe-reboot status +*****0 if msg.action == nil then return {error = true} end +*****0 if msg.action == 'status' then result.status = getStatus() end + + -- Start safe-reboot +*****0 if msg.action == 'start' then +*****0 local args = '' +*****0 if msg.value ~= nil then +*****0 if msg.value.wait ~= nil then +*****0 args = args .. ' -w ' .. msg.value.wait + end +*****0 if msg.value.fallback ~= nil then +*****0 args = args .. ' -f ' .. msg.value.fallback + end + end +*****0 local sr = assert(io.popen('safe-reboot ' .. args)) +*****0 sr:close() +*****0 result.status = getStatus() +*****0 if result.status == true then result.started = true end + end + + -- Rreboot now and wait for fallback timeout +*****0 if msg.action == 'now' then +*****0 local sr = assert(io.popen('safe-reboot now')) +*****0 result.status = getStatus() +*****0 result.now = result.status + end + + -- Keep changes and stop safe-reboot +*****0 if msg.action == 'cancel' then +*****0 result.status = true +*****0 result.canceled = false +*****0 local sr = assert(io.popen('safe-reboot cancel')) +*****0 sr:close() +*****0 if getStatus() == false then +*****0 result.status = false +*****0 result.canceled = true + end + end + + -- Discard changes - Restore previous state and reboot +*****0 if msg.action == 'discard' then +*****0 local sr = assert(io.popen('safe-reboot discard')) +*****0 sr:close() +*****0 result.status = getStatus() +*****0 if result.status == true then result.started = true end + end + +*****0 return result + end + + 6 return limeutilsadmin + + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime-utils.lua +============================================================================== + #!/usr/bin/env lua + + -- Used on lime-utils ubus script + 11 local limewireless = require 'lime.wireless' + 11 local utils = require 'lime.utils' + 11 local upgrade = require 'lime.upgrade' + 11 local node_status = require 'lime.node_status' + 11 local hotspot_wwan = require "lime.hotspot_wwan" + 11 local ubus = require "ubus" + + 11 local conn = ubus.connect() + 11 if not conn then error("Failed to connect to ubus") end + + 11 local limeutils = {} + + 11 function limeutils.get_cloud_nodes() + 12 local nodes = utils.unsafe_shell( + 6 "cat /tmp/bat-hosts | grep bat0 | cut -d' ' -f2 | sed 's/_bat0//' | sed 's/_/-/g' | sort | uniq") + 6 local result = {} + 6 result.nodes = {} + 36 for line in nodes:gmatch("[^\n]*") do + 30 if line ~= "" then table.insert(result.nodes, line) end + end + 6 result.status = "ok" + 6 return result + end + + + 11 function limeutils.get_mesh_ifaces() +*****0 local result = {} +*****0 result.ifaces = limewireless.mesh_ifaces() +*****0 return result + end + + 11 function limeutils.get_node_status() + 12 local result = {} + 12 result.hostname = utils.hostname() + 12 result.ips = node_status.get_ips() + 12 result.most_active = node_status.get_most_active() + 12 result.switch_status = node_status.switch_status() + 12 result.uptime = tostring(utils.uptime_s()) + 12 result.status = "ok" + 12 return result + end + + 11 function limeutils.get_notes() + 18 local result = {} + 18 result.notes = utils.read_file('/etc/banner.notes') or '' + 18 result.status = "ok" + 18 return result + end + + 11 function limeutils.set_notes(msg) + 6 local banner = utils.write_file('/etc/banner.notes', msg.text) + 6 return limeutils.get_notes() + end + + 11 function limeutils.get_community_settings() +*****0 local config = conn:call("uci", "get", {config = "lime-app"}).values +*****0 if config ~= nil then +*****0 for name, value in pairs(config) do + -- TODO: Find a best way to remove uci keys +*****0 function table.removekey(table, key) +*****0 local element = table[key] +*****0 table[key] = nil +*****0 return element + end +*****0 table.removekey(value, ".name") +*****0 table.removekey(value, ".index") +*****0 table.removekey(value, ".anonymous") +*****0 table.removekey(value, ".type") +*****0 return value + end + else +*****0 return {error = "config not found"} + end + end + + -- todo(kon): move to utility class?? + 11 function limeutils.get_channels() +*****0 local devices = limewireless.scandevices() +*****0 local phys = {} +*****0 for k, radio in pairs(devices) do +*****0 local phyIndex = radio[".name"].sub(radio[".name"], -1) +*****0 phys[k] = {phy = "phy" .. phyIndex} +*****0 if limewireless.is5Ghz(radio[".name"]) then +*****0 phys[k].freq = '5ghz' + else +*****0 phys[k].freq = '2.4ghz' + end + end +*****0 local frequencies = {} +*****0 for _, phy in pairs(phys) do +*****0 local info = utils.unsafe_shell("iw " .. phy.phy .. +*****0 " info | sed -n '/Frequencies:/,/valid/p' | sed '1d;$d' | grep -v radar | grep -v disabled | sed -e 's/.*\\[\\(.*\\)\\].*/\\1/'") +*****0 frequencies[phy.freq] = utils.split(info, '\n') + end +*****0 return frequencies + end + + 11 function limeutils.get_config() +*****0 local result = conn:call("uci", "get", +*****0 {config = "lime-autogen", section = "wifi"}) +*****0 result.channels = limeutils.get_channels() +*****0 return result + end + + 11 function limeutils.get_upgrade_info() + 6 local result = upgrade.get_upgrade_info() + 6 if not result then return {status = "error"} end + 6 result.status = 'ok' + 6 return result + end + + + 11 function limeutils.hotspot_wwan_get_status(msg) + 18 local msg = msg or {} + 18 local status, errmsg = hotspot_wwan.status(msg.radio) + 18 if status then + 18 return { + 18 status = 'ok', + 18 enabled = status.enabled, + 18 connected = status.connected, + 18 signal = status.signal + 18 } + else +*****0 return {status = 'error', message = errmsg} + end + end + + 11 return limeutils + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime/hotspot_wwan.lua +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2021 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2021 Santiago Piccinini + ]]-- + + 33 local utils = require 'lime.utils' + 33 local config = require 'lime.config' + 33 local iwinfo = require "iwinfo" + 33 local wireless = require "lime.wireless" + + 33 local pkg = {} + + -- checkout ap_match_encryption for supported encryptions + 33 pkg.DEFAULT_ENCRYPTION = 'psk2' + 33 pkg.DEFAULT_SSID = 'internet' + 33 pkg.DEFAULT_PASSWORD = 'internet' + 33 pkg.DEFAULT_RADIO = 'radio0' + 33 pkg.GENERIC_SECTION_NAME = 'hotspot_wwan' + 33 pkg.IFACE_SECTION_NAME = 'lm_client_wwan' + 33 pkg.IFACE_NAME = 'client-wwan' + + 33 local gen_cfg = require 'lime.generic_config' + + 33 function pkg._apply_change() +*****0 utils.execute_daemonized("lime-config && wifi reload") + end + + --! Create a client connection to a wifi hotspot + 33 function pkg.enable(ssid, password, encryption, radio) + 12 local uci = config.get_uci_cursor() + 12 local encryption = encryption or pkg.DEFAULT_ENCRYPTION + 12 local ssid = ssid or pkg.DEFAULT_SSID + 12 local password = password or pkg.DEFAULT_PASSWORD + 12 local radio = radio or pkg.DEFAULT_RADIO + + 12 uci:set(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME, "generic_uci_config") + 24 uci:set(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME, "uci_set", { + 12 "wireless." .. radio .. ".disabled=0", + 12 "wireless." .. radio .. ".channel=auto", + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. "=wifi-iface", + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".device=" .. radio, + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".network=" .. pkg.IFACE_SECTION_NAME, + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".mode=sta", + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".ifname=" .. pkg.IFACE_NAME, + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".ssid=" .. ssid, + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".encryption=" .. encryption, + 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".key=" .. password, + 12 "network." .. pkg.IFACE_SECTION_NAME .. "=interface", + 12 "network." .. pkg.IFACE_SECTION_NAME .. ".proto=dhcp", + 12 } + ) + 12 uci:commit(config.UCI_NODE_NAME) + 12 pkg._apply_change() + 12 return true + end + + 33 function ap_match_encryption(ap, encryption) + 12 if encryption == 'psk2' then + 12 return ap.encryption.enabled and ap.encryption.wpa == 2 + end +*****0 return false + end + + 33 function pkg._is_safe(ssid, encryption, radio) + 42 local ifaces = wireless.get_radio_ifaces(radio) + 42 if utils.tableLength(ifaces) == 0 then + 24 return true + end + 30 for _, iface in pairs(ifaces) do + 18 if wireless.is_mesh(iface) then + 6 return false, 'radio has mesh ifaces' + end + end + 12 ifname = ifaces[1].ifname + 12 iface_type = iwinfo.type(ifname) + 12 if iface_type ~= nil then + 12 scanlist = iwinfo[iface_type].scanlist(ifname) + 18 for _, ap in pairs(scanlist) do + 12 if (ap.ssid == ssid and ap_match_encryption(ap, encryption)) then + 6 return true + end + end + end + 6 return false, 'hotspot ap not found' + end + + 33 function pkg.safe_enable(ssid, password, encryption, radio) + -- Enables the hotpost client only if the hotpost is already available + -- in order to avoid clients from ap interfaces to be kicked out. + 12 local encryption = encryption or pkg.DEFAULT_ENCRYPTION + 12 local ssid = ssid or pkg.DEFAULT_SSID + 12 local radio = radio or pkg.DEFAULT_RADIO + + 12 local is_safe, reason = pkg._is_safe(ssid, encryption, radio) + 12 if is_safe then + 12 return pkg.enable(ssid, password, encryption, radio) + else +*****0 return false, reason + end + end + + 33 function pkg.disable(radio) + 12 local uci = config.get_uci_cursor() + 12 local radio = radio or pkg.DEFAULT_RADIO + + 12 uci:delete(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME) + 12 uci:commit(config.UCI_NODE_NAME) + 12 uci:delete('system', 'hotspot_watchping') + 12 uci:commit('system') + 12 pkg._apply_change() + 12 return true + end + + 33 function pkg.status(radio) + 30 local uci = config.get_uci_cursor() + 30 local radio = radio or pkg.DEFAULT_RADIO + 30 local connected = false + local signal + + 30 local enabled = false + + 30 if uci:get(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME) then + 6 enabled = true + end + + 36 for mac, station in pairs(iwinfo.nl80211.assoclist(pkg.IFACE_NAME)) do + 6 connected = true + 6 signal = station['signal'] + end + + 30 return {connected = connected, signal = signal, enabled = enabled} + end + + + 33 return pkg + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime/node_status.lua +============================================================================== + 36 local limewireless = require 'lime.wireless' + 36 local iwinfo = require 'iwinfo' + 36 local utils = require 'lime.utils' + 36 local json = require("luci.jsonc") + + + -- Functions used by get_node_status + 36 local node_status = {} + + 36 function node_status.get_ips() + 12 local res = {} + 24 local ips = utils.unsafe_shell( + 12 "ip a s br-lan | grep inet | awk '{ print $1, $2 }'") + 12 for line in ips:gmatch("[^\n]+") do +*****0 local words = {} +*****0 for w in line:gmatch("%S+") do +*****0 if w ~= "" then table.insert(words, w) end + end +*****0 local version = words[1] +*****0 local address = words[2] +*****0 if version == "inet6" then +*****0 table.insert(res, {version = "6", address = address}) + else +*****0 table.insert(res, {version = "4", address = address}) + end + end + 12 return res + end + + 36 function node_status.get_stations() + 26 local res = {} + 26 local ifaces = limewireless.mesh_ifaces() + 26 for _, iface in ipairs(ifaces) do +*****0 local iface_type = iwinfo.type(iface) +*****0 local iface_stations = iface_type and iwinfo[iface_type].assoclist(iface) +*****0 if iface_stations then +*****0 for mac, station in pairs(iface_stations) do +*****0 station['iface'] = iface +*****0 station.station_mac = mac +*****0 table.insert(res, station) + end + end + end + 26 return res + end + + 36 function node_status.get_station_stats(station) + 86 local iface = station.iface + 86 local mac = station.station_mac + 172 local iw_result = utils.unsafe_shell( + 86 "iw " .. iface .. " station get " .. mac) + 172 station.rx_bytes = tonumber( + 172 string.match(iw_result, "rx bytes:%s+(.-)\n"), 10) + 172 station.tx_bytes = tonumber( + 172 string.match(iw_result, "tx bytes:%s+(.-)\n"), 10) + 86 local signal_str = string.match(iw_result, "signal:%s+(.-)\n") + 86 local signal, chain = string.match(signal_str, "(%-?%d+)%s+%[(.-)%]") + 86 station.signal = tonumber(signal) + 86 station.chains = {} + --[[ + succesive calls to this function will lead to an error + /usr/bin/lua: /usr/lib/lua/lime/node_status.lua:60: bad argument #1 to 'gmatch' (string expected, got nil) + stack traceback: + [C]: in function 'gmatch' + /usr/lib/lua/lime/node_status.lua:60: in function 'get_station_stats' + ...tate/publishers/shared-state-publish_wifi_links_info:32: in function 'get_wifi_links_info' + ...tate/publishers/shared-state-publish_wifi_links_info:45: in main chunk + [C]: ? + ]]-- + 86 if chain ~= nil then + 258 for num in string.gmatch(chain, "%-?%d+") do + 172 table.insert(station.chains, tonumber(num)) + end + end + 86 return station + end + + 36 function node_status.get_most_active() + 18 local res = {} + 18 local stations = node_status.get_stations() + 18 if next(stations) ~= nil then + 6 local most_active = {} + 6 most_active.rx_bytes = 0 + 18 for _, station in ipairs(stations) do + 12 local station_stats = node_status.get_station_stats(station) + 12 if station_stats.rx_bytes > most_active.rx_bytes then + 6 most_active = station + end + end + 6 res = most_active + end + 18 return res + end + + 36 function node_status.switch_status() + 67 local response_ports = node_status.boardjson_get_ports() + 67 if #response_ports ~= 0 then + 67 if utils.is_dsa() then +*****0 node_status.dsa_get_link_status(response_ports) + else + 67 node_status.swconfig_get_link_status(response_ports) + end + end + 67 return response_ports + end + + 36 function node_status.boardjson_get_ports() + 67 local response_ports = {} + 67 local board = utils.getBoardAsTable() + 67 if board['switch'] ~= nil and board['switch']['switch0'] ~= nil then -- legacy swconfig devices support + 201 for _, role in ipairs(board['switch']['switch0']['roles']) do + 438 for port_number in string.gmatch(role['ports'], "%S+") do + 304 if not tonumber(port_number) then + 134 local n = tonumber(string.match(port_number, "^%d+")) + 134 table.insert(response_ports, { num = n, role = "cpu", device = role['device']}) + else + 170 table.insert(response_ports, { num = tonumber(port_number), role = role['role'], device = role['device']}) + end + end + end +*****0 elseif board['network'] ~= nil then -- DSA devices support +*****0 for switch_name, switch in pairs(board['network']) do +*****0 if switch['ports'] ~= nil then +*****0 for _, port in ipairs(switch.ports) do +*****0 table.insert(response_ports, { num = port, role = switch_name, device = switch_name}) + end + else +*****0 table.insert(response_ports, { num = switch_name, role = switch_name, device = switch['device'] }) + end + end + end + 67 return response_ports + end + + 36 function node_status.dsa_get_link_status(ports) +*****0 for _, port in ipairs(ports) do +*****0 local dsa = utils.unsafe_shell("ip link show " .. port['num']) + -- Match ifindex, ifname, link (optional), and operstate +*****0 local ifindex, ifname, link, operstate = dsa:match("^(%d+): ([^:@]+)@?([^:]*):.-state (%S+)") +*****0 if ifindex and ifname and operstate then +*****0 port['device'] = port['num'] +*****0 port['num'] = tonumber(ifindex) +*****0 port['role'] = link ~= "" and link or nil -- Handle optional link field +*****0 if port['role'] == nil then +*****0 port['role'] = ifname + end +*****0 port['link'] = operstate +*****0 if operstate == "LOWERLAYERDOWN" then +*****0 port['link'] = "DOWN" + end + end + end +*****0 return ports + end + + + 36 function node_status.swconfig_get_link_status(ports) + local function add_link_status(port_number, status) + 336 for x, obj in pairs(ports) do + 294 if obj.num == port_number then + 42 obj["link"] = status + end + end + end + + 67 local swconfig = utils.unsafe_shell("swconfig dev switch0 show") + 67 local lines = {} + 535 for line in swconfig:gmatch("[^\r\n]+") do + 468 table.insert(lines, line) + end + + local port_number + 535 for i, line in ipairs(lines) do + 468 if line:match("Port %d:") then + 108 port_number = tonumber(line:match("Port (%d):")) + end + 468 if string.find(line, "link:up") then + 24 add_link_status(port_number, "up") + 444 elseif string.find(line, "link:down") then + 18 add_link_status(port_number, "down") + end + end + 67 return ports + end + + 36 return node_status + + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime/upgrade.lua +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2020 Santiago Piccinini + ]]-- + + 33 local json = require 'luci.jsonc' + 33 local utils = require 'lime.utils' + + 33 local pkg = {} + + 33 pkg.UPGRADE_INFO_CACHE_FILE = '/tmp/upgrade_info_cache' + + 33 pkg.UPGRADE_STATUS_DEFAULT = 'NOT_STARTED' + 33 pkg.UPGRADE_STATUS_UPGRADING = 'UPGRADING' + 33 pkg.UPGRADE_STATUS_FAILED = 'FAILED' + 33 pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR = "/tmp/lime-sysupgrade/preserve" + 33 pkg.UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" + + 33 function pkg.safe_upgrade_confirm_remaining_s() +*****0 local remaining_s = tonumber(utils.unsafe_shell("safe-upgrade confirm-remaining")) +*****0 if not remaining_s then +*****0 remaining_s = -1 + end +*****0 return remaining_s + end + + 33 function pkg.is_upgrade_confirm_supported() + 36 local exit_value = os.execute("safe-upgrade board-supported > /dev/null 2>&1") + 36 return exit_value == 0 + end + + 33 function pkg.get_upgrade_status() + 12 local info = utils.read_obj_store(pkg.UPGRADE_INFO_CACHE_FILE) + 12 if info.status == nil then +*****0 return pkg.UPGRADE_STATUS_DEFAULT + else + 12 return info.status + end + end + + 33 function pkg.set_upgrade_status(status) + 24 return utils.write_obj_store_var(pkg.UPGRADE_INFO_CACHE_FILE, 'status', status) + end + + 33 function pkg.get_upgrade_info() + 18 local result = utils.read_obj_store(pkg.UPGRADE_INFO_CACHE_FILE) + 18 if result.is_upgrade_confirm_supported == nil then + 18 result.is_upgrade_confirm_supported = pkg.is_upgrade_confirm_supported() + end + 18 if not result.is_upgrade_confirm_supported then + 12 result.safe_upgrade_confirm_remaining_s = -1 + else + 6 result.safe_upgrade_confirm_remaining_s = pkg.safe_upgrade_confirm_remaining_s() + end + 18 utils.write_obj_store(pkg.UPGRADE_INFO_CACHE_FILE, result) + 18 return result + end + + 33 function pkg.firmware_verify(fw_path) + local command + 12 if pkg.is_upgrade_confirm_supported() then + 12 command = "safe-upgrade verify " + else +*****0 command = "sysupgrade --test " + end + 12 command = command .. fw_path .. " > /dev/null 2>&1" + 12 local exit_value = os.execute(command) + 12 return exit_value == 0 + end + + + 33 function pkg.firmware_upgrade(fw_path, preserve_config, metadata, fw_type) + 12 if not fw_path then +*****0 return nil, "Firmware file needed" + end + 12 if not utils.file_exists(fw_path) then +*****0 return nil, "Firmware file not found" + end + 12 if pkg.get_upgrade_status() == pkg.UPGRADE_STATUS_UPGRADING then +*****0 return nil, "There is an upgrade in progress" + end + + 12 metadata = metadata or {} + + 12 if not fw_type then + 12 if utils.stringEnds(fw_path, ".bin") then + 12 fw_type = 'sysupgrade' +*****0 elseif utils.stringEnds(fw_path, ".sh") then +*****0 fw_type = 'installer' + else +*****0 return nil, "Unsupported firmware type" + end + end + + 12 if fw_type == 'sysupgrade' then + 12 if not pkg.firmware_verify(fw_path) then +*****0 return nil, "Invalid firmware" + end + end + + 12 local backup = "" + 12 if preserve_config == nil then + 6 preserve_config = true + end + 12 if not preserve_config then +*****0 backup = "DO_NOT_BACKUP=1" + end + + 12 metadata['config_preserved'] = preserve_config or false + + -- store info of the current firmware + 12 local current_fw_description = utils.release_info()["DISTRIB_DESCRIPTION"] + 12 if current_fw_description then + 12 metadata['old_release_description'] = current_fw_description + end + + 12 metadata['local_timestamp'] = os.time() + + --! Use the BACKUP_EXTRA_DIR function of lime-sysupgrade to store the medatada file + 12 utils.unsafe_shell("mkdir -p " .. pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR .. "/etc") + 12 local meta_file_path = pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR .. pkg.UPGRADE_METADATA_FILE + 12 if not utils.write_file(meta_file_path, json.stringify(metadata)) then +*****0 return nil, "Can't write " .. meta_file_path + end + + 12 pkg.set_upgrade_status(pkg.UPGRADE_STATUS_UPGRADING) + 12 if fw_type == 'sysupgrade' then + --! Give some time so the response can be returned to the client + 12 local cmd = "sleep 3; FORCE=1 " .. backup .. " lime-sysupgrade " .. fw_path + --! stdin must be /dev/null because of a tar bug when using gzip that tries to read from stin and fails + --! if it is closed + 12 utils.execute_daemonized(cmd, "/tmp/lime-sysupgrade.log", "/dev/null") +*****0 elseif fw_type == 'installer' then +*****0 utils.unsafe_shell("chmod +x " .. fw_path) +*****0 utils.execute_daemonized(fw_path, "/tmp/upgrade-installer.log", "/dev/null") + --! give the installer some time and try to collect the status up to that moment +*****0 utils.unsafe_shell("sleep 10s") +*****0 local progress_status = utils.read_file("/tmp/upgrade-installer-status") or 'unknown' +*****0 if progress_status == 'failed' then +*****0 pkg.set_upgrade_status(pkg.UPGRADE_STATUS_FAILED) +*****0 return nil, utils.read_file("/tmp/upgrade-installer-error-mesage") or 'Installer failed without error message' + end + end + 12 return true, metadata + end + + 33 return pkg + +============================================================================== +packages/ubus-lime-utils/files/usr/lib/lua/lime/wireless_service.lua +============================================================================== + 22 local utils = require('lime.utils') + 22 local config = require('lime.config') + 22 local wireless = require('lime.wireless') + + 22 local wireless_service = {} + 22 wireless_service.AP_BAND = '2ghz' -- TODO: grab from uci config + + + local function get_node_ap_data(is_admin) + 30 local result = {} + 30 local cfg = wireless.get_band_config(wireless_service.AP_BAND) + 30 result.enabled = utils.has_value(cfg.modes, 'apname') + 30 result.has_password = (cfg.apname_encryption and + 30 cfg.apname_encryption ~= "none") + 30 result.password = is_admin and cfg.apname_key or nil + 30 result.ssid = wireless.resolve_ssid(cfg.apname_ssid) + 30 return result + end + + local function get_community_ap_data() + 30 local result = {} + 30 local cfg = wireless.get_band_config(wireless_service.AP_BAND) + 30 local community_cfg = wireless.get_community_band_config(wireless_service.AP_BAND) + 30 result.enabled = utils.has_value(cfg.modes, 'ap') + 30 result.ssid = wireless.resolve_ssid(cfg.ap_ssid) + 30 result.community = {} + 30 result.community.enabled = utils.has_value(community_cfg.modes, 'ap') + 30 return result + end + + 22 function wireless_service.get_access_points_data(is_admin) + 30 local result = {} + 30 result.node_ap = get_node_ap_data(is_admin) + 30 result.community_ap = get_community_ap_data() + 30 return result + end + + 22 function wireless_service.set_node_ap(has_password, password) + 12 local config = {} + 12 config.apname_encryption = has_password and 'psk2' or 'none' + 12 config.apname_key = password or nil + 12 wireless.set_band_config(wireless_service.AP_BAND, config) + end + + 22 function wireless_service.set_community_ap(enabled) + 12 if enabled then + 6 wireless.add_band_mode(wireless_service.AP_BAND, 'ap') + else + 6 wireless.remove_band_mode(wireless_service.AP_BAND, 'ap') + end + end + + 22 return wireless_service + +============================================================================== +packages/ubus-lime-utils/files/usr/libexec/rpcd/lime-utils-admin +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2020 Santiago Piccinini + ]]-- + + 96 local ubus = require "ubus" + 96 local json = require 'luci.jsonc' + 96 local utils = require 'lime.utils' + + 96 local limeutilsadmin = require 'lime-utils-admin' + + 96 local conn = ubus.connect() + 96 if not conn then +*****0 error("Failed to connect to ubus") + end + + 96 local UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" + + + local function set_root_password(msg) + 6 local result = limeutilsadmin.set_root_password(msg) + 6 return utils.printJson(result) + end + + local function set_hostname(msg) + 18 local result = limeutilsadmin.set_hostname(msg) + 18 return utils.printJson(result) + end + + local function is_upgrade_confirm_supported() + 12 local result = limeutilsadmin.is_upgrade_confirm_supported() + 12 return utils.printJson(result) + end + + local function firmware_upgrade(msg) + 12 local result = limeutilsadmin.firmware_upgrade(msg) + 12 return utils.printJson(result) + end + + local function last_upgrade_metadata() + 12 local result = limeutilsadmin.last_upgrade_metadata() + 12 return utils.printJson(result) + end + + local function firmware_confirm() + 6 local result = limeutilsadmin.firmware_confirm() + 6 return utils.printJson(result) + end + + --! Creates a client connection to a wifi hotspot + local function hotspot_wwan_enable(msg) + 18 local result = limeutilsadmin.hotspot_wwan_enable(msg) + 18 return utils.printJson(result) + end + + + local function hotspot_wwan_disable(msg) + 6 local result = limeutilsadmin.hotspot_wwan_disable(msg) + 6 return utils.printJson(result) + end + + local function safe_reboot(msg) +*****0 local result = limeutilsadmin.safe_reboot(msg) +*****0 utils.printJson(result) + end + + 96 local methods = { + 96 set_root_password = { password = 'value'}, + 96 set_hostname = { hostname = 'value'}, + 96 is_upgrade_confirm_supported = { no_params = 0 }, + 96 firmware_upgrade = { fw_path = 'value', preserve_config = 'value', metadata = 'value', fw_type = 'value'}, + 96 last_upgrade_metadata = { no_params = 0 }, + 96 firmware_confirm = { no_params = 0 }, + 96 hotspot_wwan_enable = { radio = 'value', ssid = 'value', password = 'value', encryption = 'value'}, + 96 hotspot_wwan_disable = { radio = 'value' }, + 96 safe_reboot = {action = 'value', value = 'value'}, + } + + 96 if arg[1] == 'list' then + 6 utils.printJson(methods) + end + + 96 if arg[1] == 'call' then + 90 local msg = utils.rpcd_readline() + 90 msg = json.parse(msg) + 90 if arg[2] == 'set_root_password' then set_root_password(msg) + 84 elseif arg[2] == 'set_hostname' then set_hostname(msg) + 66 elseif arg[2] == 'is_upgrade_confirm_supported' then is_upgrade_confirm_supported(msg) + 54 elseif arg[2] == 'firmware_upgrade' then firmware_upgrade(msg) + 42 elseif arg[2] == 'last_upgrade_metadata' then last_upgrade_metadata(msg) + 30 elseif arg[2] == 'firmware_confirm' then firmware_confirm(msg) + 24 elseif arg[2] == 'hotspot_wwan_enable' then hotspot_wwan_enable(msg) + 6 elseif arg[2] == 'hotspot_wwan_disable' then hotspot_wwan_disable(msg) +*****0 elseif arg[2] == 'safe_reboot' then safe_reboot(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2021 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2021 German Ferrero + ]]-- + + 6 local ubus = require "ubus" + 6 local json = require 'luci.jsonc' + 6 local utils = require 'lime.utils' + 6 local wireless = require 'lime.wireless_service' + + 6 local conn = ubus.connect() + 6 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function get_access_points_data() + 6 local data = wireless.get_access_points_data() + 6 data.status = "ok" + 6 return utils.printJson(data) + end + + 6 local methods = { + 6 get_access_points_data = { no_params = 0 }, + } + + 6 if arg[1] == 'list' then +*****0 utils.printJson(methods) + end + + 6 if arg[1] == 'call' then + 6 local msg = utils.rpcd_readline() + 6 msg = json.parse(msg) + 6 if arg[2] == 'get_access_points_data' then get_access_points_data(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service-admin +============================================================================== + #!/usr/bin/env lua + --[[ + Copyright (C) 2021 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + Copyright 2021 German Ferrero + ]]-- + + 18 local ubus = require "ubus" + 18 local json = require 'luci.jsonc' + 18 local utils = require 'lime.utils' + 18 local wireless = require 'lime.wireless_service' + + 18 local conn = ubus.connect() + 18 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function get_access_points_data() + 6 local data = wireless.get_access_points_data(true) + 6 data.status = "ok" + 6 return utils.printJson(data) + end + + local function set_node_ap(msg) + 6 wireless.set_node_ap(msg.has_password, msg.password) + 6 return utils.printJson({status = 'ok'}) + end + + local function set_community_ap(msg) + 6 wireless.set_community_ap(msg.enabled) + 6 return utils.printJson({status = 'ok'}) + end + + 18 local methods = { + 18 get_access_points_data = { no_params = 0 }, + 18 set_node_ap = { has_password = 'value', password = 'value' }, + 18 set_community_ap = { enabled = 'value' } + } + + 18 if arg[1] == 'list' then +*****0 utils.printJson(methods) + end + + 18 if arg[1] == 'call' then + 18 local msg = utils.rpcd_readline() + 18 msg = json.parse(msg) + 18 if arg[2] == 'get_access_points_data' then get_access_points_data(msg) + 12 elseif arg[2] == 'set_node_ap' then set_node_ap(msg) + 6 elseif arg[2] == 'set_community_ap' then set_community_ap(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + +============================================================================== +packages/ubus-tmate/files/usr/lib/lua/tmate.lua +============================================================================== + #!/usr/bin/env lua + --[[ Copyright (C) 2013-2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + ]]-- + + 22 local utils = require 'lime.utils' + + + 22 local TMATE_SOCK = "/tmp/tmate.sock" + 22 local TMATE_CONFIG = "/etc/tmate/tmate.conf" + + 22 local tmate = {} + + 22 function tmate.cmd_as_str(cmd) +*****0 final_cmd = "tmate -f "..TMATE_CONFIG.." -S "..TMATE_SOCK.." "..cmd +*****0 return utils.unsafe_shell(final_cmd) + end + + local function unix_socket_listening(name) + 11 return "" ~= utils.unsafe_shell("netstat -xl | grep "..TMATE_SOCK.." 2>/dev/null") + end + + 22 function tmate.session_running() + 11 return unix_socket_listening(TMATE_SOCK) + end + + 22 function tmate.get_rw_session() + 33 return tmate.cmd_as_str("display -p '#{tmate_ssh}'"):sub(1, -2) + end + + 22 function tmate.get_ro_session() + 33 return tmate.cmd_as_str("display -p '#{tmate_ssh_ro}'"):sub(1, -2) + end + + 22 function tmate.get_connected_clients() + 22 return tmate.cmd_as_str("display -p '#{tmate_num_clients}'"):sub(1, -2) + end + + 22 function tmate.open_session() + 33 tmate.cmd_as_str("new-session -d") + 33 tmate.cmd_as_str("send-keys C-c") + end + + 22 function tmate.wait_session_ready() + 33 tmate.cmd_as_str("wait tmate-ready") + end + + 22 function tmate.close_session() + 33 tmate.cmd_as_str("kill-session -t 0") + end + + 22 return tmate + +============================================================================== +packages/ubus-tmate/files/usr/libexec/rpcd/tmate +============================================================================== + #!/usr/bin/env lua + --[[ Copyright (C) 2013-2020 LibreMesh.org + This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + + ]]-- + + 44 local ubus = require "ubus" + 44 local json = require 'luci.jsonc' + 44 local utils = require 'lime.utils' + 44 local tmate = require 'tmate' + + 44 local conn = ubus.connect() + 44 if not conn then +*****0 error("Failed to connect to ubus") + end + + local function get_session(msg) + 22 local session = "no session" + + 22 if tmate.session_running() then + 11 local rw_ssh = tmate.get_rw_session() + 11 local ro_ssh = tmate.get_ro_session() + 11 local clients = tmate.get_connected_clients() + + 11 session = { rw_ssh = rw_ssh, ro_ssh = ro_ssh, clients = clients } + end + + 17 utils.printJson({ status = "ok", session = session }) + end + + local function open_session(msg) + 11 tmate.open_session() + 11 tmate.wait_session_ready() + + 11 utils.printJson({status = "ok"}) + end + + local function close_session(params) +*****0 if tmate.session_running() then +*****0 tmate.close_session() + end + +*****0 utils.printJson({ status = "ok" }) + end + + 44 local methods = { + 44 get_session = { no_params = 0 }, + 44 open_session = { no_params = 0 }, + 44 close_session = { no_params = 0 }, + } + + 44 if arg[1] == 'list' then + 11 utils.printJson(methods) + end + + 44 if arg[1] == 'call' then + 33 local msg = utils.rpcd_readline() + 33 msg = json.parse(msg) + 33 if arg[2] == 'get_session' then get_session(msg) + 11 elseif arg[2] == 'open_session' then open_session(msg) +*****0 elseif arg[2] == 'close_session' then close_session(msg) +*****0 else utils.printJson({ error = "Method not found" }) + end + end + + +============================================================================== +packages/wifi-unstuck-wa/files/usr/lib/lua/lime/wifi_unstuck_wa.lua +============================================================================== + 17 local config = require "lime.config" + 17 local utils = require "lime.utils" + 17 local iwinfo = require "iwinfo" + 17 local nixio = require 'nixio' + 17 local wu = {} + + -- so we always scan in at least one different frequency + 17 wu.FREQ_2GHZ_LIST = "2412 2462" + 17 wu.FREQ_5GHZ_LIST = "5180 5240" + + -- if iw runs for 5 min, it is likely hanging + 17 wu.TIMEOUT = tonumber( config.get("wifi", "unstuck_timeout", 300 )) + + 17 function wu.get_stickable_ifaces() + 18 local uci = config.get_uci_cursor() + 18 local ifaces = {} + 18 local devices = {} + + 36 uci:foreach("wireless", "wifi-iface", function(entry) + 90 if (entry.mode == 'mesh' or entry.mode == 'adhoc' or + 36 entry.mode == 'sta' or entry.mode == 'ap') then + 90 local device_path = uci:get("wireless", entry.device, "path") + 90 local device_disabled = uci:get("wireless", entry.device, "disabled") + --! get only one interface per radio and check that the radio is not disabled + 90 if device_path and device_disabled == '0' and devices[device_path] == nil then + 54 table.insert(ifaces, entry.ifname) + 54 devices[device_path] = 1 + end + end + end) + 18 return ifaces + end + + 17 function wu.wait_and_kill_on_timeout(pid_time_started) + 12 local pid_done = {} + + 24 for pid,time_started in pairs(pid_time_started) do + 12 pid_done[pid]=false + end + + repeat + -- wait for 100ms + 1806 nixio.nanosleep(0,100e6) + + -- see if something changed + while true do + 1827 pid,state,code = nixio.waitpid(nil,"nohang") + 1827 if not pid then break end + 21 if pid == 0 then break end + 21 pid_done[pid] = true + end + + -- see if time is up + 1806 now = os.time() + 5406 for pid,time_started in pairs(pid_time_started) do + 3600 time_is_up = now - time_started > wu.TIMEOUT + 3600 if not pid_done[pid] and time_is_up then + -- time is up. send SIGTERM + 6 nixio.kill(pid,15) + -- we don't care any longer about processes we signaled + 6 pid_done[pid] = true + end + end + + -- see if there are remaining processes + 1806 all_done = true + 5421 for pid,done in pairs(pid_done) do + 3615 all_done = all_done and done + end + 1806 until all_done + + end + + 17 function wu.do_workaround() + 12 local ifaces = wu.get_stickable_ifaces() + 12 local pid_time_started = {} + + 48 for _, iface in pairs(ifaces) do + 36 local cmd = "iw dev " .. iface .. " scan freq " + 36 local freq = iwinfo.nl80211.frequency(iface) + 36 if freq ~= nil then + 24 if freq < 3000 then + 12 cmd = cmd .. wu.FREQ_2GHZ_LIST + else + 12 cmd = cmd .. wu.FREQ_5GHZ_LIST + end + 24 utils.log(cmd) + + -- we can not use os.popen here, because it does not give us the + -- pid + 24 pid = nixio.fork() + 24 if pid == 0 then + 12 nixio.exec('/bin/sh','-c',cmd..' >/dev/null') + 12 os.exit(1) + else + 12 pid_time_started[pid] = os.time() + end + + 24 nixio.nanosleep(1) + end + end + + 12 wu.wait_and_kill_on_timeout(pid_time_started) + end + + 17 function wu.configure() + 6 interval = tonumber( config.get("wifi", "unstuck_interval", -1) ) + + 6 if interval and interval > 0 then + --! use sed to replace interval in /etc/crontabs/root + 12 io.popen("sed -i 's/\\*\\/\\d\\+ \\* \\* \\* \\* ((wifi-unstuck &> \\/dev\\/".. + 6 "null)&)/*\\/"..interval.." * * * * ((wifi-unstuck \\&> \\/dev\\/null)\\&)/g".. + 6 "' /etc/crontabs/root") + end + end + + 17 function wu.clean() + -- nothing to clean, but needs to be declared to comply with the API + end + + 17 return wu + +============================================================================== +tests/fakes/iwinfo.lua +============================================================================== + 219 local iwinfo = {} + 219 iwinfo.nl80211 = {} + 219 iwinfo.fake = {} + 219 iwinfo.mocks = {} + + 219 iwinfo.mocks.iw_station_get_result_wlan1 = [[ + Station c0:4a:00:be:7b:0a (on wlan1-mesh) + inactive time: 50 ms + rx bytes: 503044 + rx packets: 3976 + tx bytes: 545116 + tx packets: 1237 + tx retries: 9 + tx failed: 0 + rx drop misc: 3 + signal: -14 [-17, -16] dBm + signal avg: -12 [-14, -15] dBm + Toffset: 46408315 us + tx bitrate: 300.0 MBit/s MCS 15 40MHz short GI + rx bitrate: 300.0 MBit/s MCS 15 40MHz short GI + rx duration: 0 us + expected throughput: 58.43Mbps + mesh llid: 5944 + mesh plid: 1241 + mesh plink: ESTAB + mesh local PS mode: ACTIVE + mesh peer PS mode: ACTIVE + mesh non-peer PS mode: ACTIVE + authorized: yes + authenticated: yes + associated: yes + preamble: long + WMM/WME: yes + MFP: no + TDLS peer: no + DTIM period: 2 + beacon interval:100 + short slot time:yes + connected time: 139 seconds + 219 ]] + + + 219 iwinfo.mocks.iw_station_get_result_wlan0 = [[ + Station c0:4a:00:be:7b:09 (on wlan0-mesh) + inactive time: 140 ms + rx bytes: 3116498 + rx packets: 31613 + tx bytes: 1166333 + tx packets: 4462 + tx retries: 2448 + tx failed: 15 + rx drop misc: 938 + signal: -14 [-17, -18] dBm + signal avg: -14 [-17, -18] dBm + Toffset: 18446744073577465064 us + tx bitrate: 6.5 MBit/s MCS 0 + rx bitrate: 39.0 MBit/s MCS 10 + rx duration: 0 us + expected throughput: 2.197Mbps + mesh llid: 63041 + mesh plid: 61249 + mesh plink: ESTAB + mesh local PS mode: ACTIVE + mesh peer PS mode: ACTIVE + mesh non-peer PS mode: ACTIVE + authorized: yes + authenticated: yes + associated: yes + preamble: long + WMM/WME: yes + MFP: no + TDLS peer: no + DTIM period: 2 + beacon interval:100 + short slot time:yes + connected time: 5070 seconds + 219 ]] + + + 219 iwinfo.mocks.get_stations = { + 219 [1] = { + 219 ["rx_short_gi"] = false, + 219 ["station_mac"] = "C0:4A:00:BE:7B:09", + 219 ["rx_vht"] = false, + 219 ["rx_mhz"] = 20, + 219 ["rx_40mhz"] = false, + 219 ["tx_packets"] = 1574, + 219 ["tx_mhz"] = 20, + 219 ["rx_packets"] = 16879, + 219 ["rx_ht"] = true, + 219 ["tx_mcs"] = 9, + 219 ["noise"] = -95, + 219 ["rx_mcs"] = 1, + 219 ["tx_ht"] = true, + 219 ["iface"] = "wlan0-mesh", + 219 ["tx_rate"] = 26000, + 219 ["inactive"] = 1390, + 219 ["tx_short_gi"] = false, + 219 ["tx_40mhz"] = false, + 219 ["expected_throughput"] = 11437, + 219 ["tx_vht"] = false, + 219 ["rx_rate"] = 13000, + 219 ["signal"] = 13 + 219 }, + 219 [2] = { + 219 ["rx_short_gi"] = true, + 219 ["station_mac"] = "C0:4A:00:BE:7B:0A", + 219 ["rx_vht"] = false, + 219 ["rx_mhz"] = 40, + 219 ["rx_40mhz"] = true, + 219 ["tx_packets"] = 7078, + 219 ["tx_mhz"] = 40, + 219 ["rx_packets"] = 54294, + 219 ["rx_ht"] = true, + 219 ["tx_mcs"] = 15, + 219 ["noise"] = -91, + 219 ["rx_mcs"] = 15, + 219 ["tx_ht"] = true, + 219 ["iface"] = "wlan1-mesh", + 219 ["tx_rate"] = 300000, + 219 ["inactive"] = 70, + 219 ["tx_short_gi"] = true, + 219 ["tx_40mhz"] = true, + 219 ["expected_throughput"] = 59437, + 219 ["tx_vht"] = false, + 219 ["rx_rate"] = 300000, + 219 ["signal"] = -13 + 219 } + 219 } + + 219 iwinfo.mocks.wlan1_mesh_mac = {'C0', '00', '00', '01', '01', '01'} + 219 iwinfo.mocks.wlan0_mesh_mac = {'C0', '00', '00', '00', '00', '00'} + + 219 OP_MODES = { + 219 "Unknown", "Master", "Ad-Hoc", "Client", "Monitor", "Master (VLAN)", + 219 "WDS", "Mesh Point", "P2P Client", "P2P Go" + 219 } + + 219 HT_MODES = {"HT20", "HT40", "VHT20", "VHT40", "VHT80", "VHT80+80", "VHT160"} + + 219 iwinfo.fake._scanlists = {} + 219 iwinfo.fake._channels = {} + 219 iwinfo.fake._assoclists = {} + 219 iwinfo.fake._hwmodelists = {} + + 438 function iwinfo.fake.set_scanlist(phy_id, scanlist) + 101 iwinfo.fake._scanlists[phy_id] = scanlist + end + + 438 function iwinfo.fake.scanlist_gen_station(ssid, channel, signal, mac, mode, quality) + 6 local utils = require("lime.utils") + 6 assert(utils.has_value(OP_MODES, mode)) + + 6 local station = { + 6 ["encryption"] = { + 6 ["enabled"] = false, + 6 ["auth_algs"] = { } , + 6 ["description"] = None, + 6 ["wep"] = false, + 6 ["auth_suites"] = { } , + 6 ["wpa"] = 0, + 6 ["pair_ciphers"] = { } , + 6 ["group_ciphers"] = { } , + 6 } , + 6 ["quality_max"] = 70, + 6 ["ssid"] = ssid, + 6 ["channel"] = channel, + 6 ["signal"] = signal, + 6 ["bssid"] = bssid, + 6 ["mode"] = mode, + 6 ["quality"] = quality, + } + 6 return station + end + + + 438 function iwinfo.nl80211.scanlist(phy_id) + 107 return iwinfo.fake._scanlists[phy_id] or {} + end + + 438 function iwinfo.fake.set_channel(phy_id, channel) + 179 iwinfo.fake._channels[phy_id] = channel + end + + 438 function iwinfo.nl80211.channel(phy_id) + 98 return iwinfo.fake._channels[phy_id] + end + + 438 function iwinfo.fake.set_assoclist(radio, assoclist) + 24 iwinfo.fake._assoclists[radio] = assoclist + end + + 438 function iwinfo.nl80211.assoclist(radio) + 60 return iwinfo.fake._assoclists[radio] or {} + end + + 219 function iwinfo.type(phy_id) + 73 return 'nl80211' + end + + 438 function iwinfo.fake.gen_assoc_station(rx_ht_mode, tx_ht_mode, signal, quality, inactive_ms, + tx_packets, rx_packets) + 18 local utils = require("lime.utils") + + -- VHT modes not yet supported + 18 assert(utils.has_value({"HT20", "HT40"}, rx_ht_mode)) + 18 assert(utils.has_value({"HT20", "HT40"}, tx_ht_mode)) + + 18 local rx_vht = false + 18 local tx_vht = false + 18 local rx_ht = false + 18 local tx_ht = false + 18 local rx_mhz = 20 + 18 local tx_mhz = 20 + 18 local tx_40mhz = false + + 18 if rx_ht_mode == "HT40" then +*****0 rx_ht = true +*****0 rx_mhz = 40 + end + + 18 if tx_ht_mode == "HT40" then + 18 tx_ht = true + 18 tx_40mhz = true + 18 tx_mhz = 40 + end + + 18 local r = { + 18 ["rx_ht"] = rx_ht, + 18 ["rx_vht"] = rx_vht, + 18 ["rx_mhz"] = rx_mhz, + 18 ["rx_rate"] = rx_rate, + + 18 ["tx_ht"] = tx_ht, + 18 ["tx_vht"] = tx_vht, + 18 ["tx_40mhz"] = tx_40mhz, + 18 ["tx_mhz"] = tx_mhz, + 18 ["tx_mcs"] = tx_mcs, + 18 ["tx_rate"] = tx_rate, + 18 ["tx_short_gi"] = true, + + 18 ["tx_packets"] = tx_packets, + 18 ["rx_packets"] = rx_packets, + 18 ["noise"] = noise, + 18 ["inactive"] = inactive_ms, + 18 ["expected_throughput"] = throughtput, + 18 ["signal"] = signal + } + 18 return r + end + + 219 iwinfo.fake.HWMODE = { + 219 ["HW_2GHZ_N"] = { ["a"] = false, ["b"] = true, ["ac"] = false, ["g"] = true, ["n"] = true}, + 219 ["HW_5GHZ_N"] = { ["a"] = true, ["b"] = false, ["ac"] = false, ["g"] = false, ["n"] = true} + 219 } + + 438 function iwinfo.fake.set_hwmodelist(radio_or_phy, hwmodelist) + 246 iwinfo.fake._hwmodelists[radio_or_phy] = hwmodelist + end + + 438 function iwinfo.nl80211.hwmodelist(radio_or_phy) + 85 return iwinfo.fake._hwmodelists[radio_or_phy] + end + + 438 function iwinfo.fake.load_from_uci(uci_cursor) + 106 function create_device(dev) + local hwmode + 167 if dev.band == '5g' then + 150 hwmode = iwinfo.fake.HWMODE.HW_5GHZ_N + 17 elseif dev.band == '2g' then + 17 hwmode = iwinfo.fake.HWMODE.HW_2GHZ_N + else +*****0 assert(0, 'posibility not supported yet, please add support!') + end + 167 iwinfo.fake.set_hwmodelist(dev[".name"], hwmode) + 167 iwinfo.fake.set_channel(dev[".name"], dev.channel) + end + 273 uci_cursor:foreach("wireless", "wifi-device", function(dev) create_device(dev) end) + end + + 219 return iwinfo + + +============================================================================== +tests/fakes/ubus.lua +============================================================================== + 100 local ubus = {} + + 100 function ubus.connect() + 456 local conn = {} + + 456 function conn.call() +*****0 return {} + end + + 456 return conn + end + + 100 return ubus + + +============================================================================== +Summary +============================================================================== + +File Hits Missed Coverage +----------------------------------------------------------------------------------------------------------------------------------------- +./packages/pirania/tests/pirania_test_utils.lua 9 0 100.00% +./tests/utils.lua 87 1 98.86% +packages/check-internet/files/usr/libexec/rpcd/check-internet 18 2 90.00% +packages/deferrable-reboot/files/usr/lib/lua/deferrable_reboot.lua 33 2 94.29% +packages/eupgrade/files/usr/lib/lua/eupgrade.lua 83 25 76.85% +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard.lua 191 108 63.88% +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/functools.lua 24 51 32.00% +packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/utils.lua 45 61 42.45% +packages/lime-eth-config/files/usr/lib/lua/lime-eth-config.lua 54 17 76.06% +packages/lime-eth-config/files/usr/libexec/rpcd/lime-eth-config 24 7 77.42% +packages/lime-hwd-ground-routing/files/usr/lib/lua/lime/hwd/ground_routing.lua 19 41 31.67% +packages/lime-hwd-openwrt-wan/files/usr/lib/lua/lime/hwd/openwrt_wan.lua 38 0 100.00% +packages/lime-hwd-usbradio/files/usr/lib/lua/lime/hwd/usbradio.lua 15 38 28.30% +packages/lime-hwd-watchcat/files/usr/lib/lua/lime/hwd/watchcat.lua 16 17 48.48% +packages/lime-proto-babeld/files/usr/lib/lua/lime/proto/babeld.lua 72 11 86.75% +packages/lime-proto-batadv/files/usr/lib/lua/lime/proto/batadv.lua 53 20 72.60% +packages/lime-proto-bmx7/files/usr/lib/lua/lime/proto/bmx7.lua 118 48 71.08% +packages/lime-proto-olsr/files/usr/lib/lua/lime/proto/olsr.lua 44 0 100.00% +packages/lime-proto-olsr2/files/usr/lib/lua/lime/proto/olsr2.lua 49 0 100.00% +packages/lime-proto-olsr6/files/usr/lib/lua/lime/proto/olsr6.lua 39 4 90.70% +packages/lime-proto-wan/files/usr/lib/lua/lime/proto/wan.lua 24 0 100.00% +packages/lime-system/files/usr/bin/migrate-wifi-bands-cfg 40 1 97.56% +packages/lime-system/files/usr/lib/lua/lime/config.lua 129 3 97.73% +packages/lime-system/files/usr/lib/lua/lime/firewall.lua 16 9 64.00% +packages/lime-system/files/usr/lib/lua/lime/generic_config.lua 74 1 98.67% +packages/lime-system/files/usr/lib/lua/lime/hardware_detection.lua 18 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/mode/ap.lua 6 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/mode/apname.lua 6 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/mode/apup.lua 9 8 52.94% +packages/lime-system/files/usr/lib/lua/lime/mode/client.lua 5 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/mode/ieee80211s.lua 5 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/network.lua 238 113 67.81% +packages/lime-system/files/usr/lib/lua/lime/proto/ieee80211s.lua 15 0 100.00% +packages/lime-system/files/usr/lib/lua/lime/proto/lan.lua 46 11 80.70% +packages/lime-system/files/usr/lib/lua/lime/system.lua 39 7 84.78% +packages/lime-system/files/usr/lib/lua/lime/utils.lua 305 54 84.96% +packages/lime-system/files/usr/lib/lua/lime/wireless.lua 191 8 95.98% +packages/pirania/files/usr/lib/lua/portal/portal.lua 35 11 76.09% +packages/pirania/files/usr/lib/lua/read_for_access/cgi_handlers.lua 17 0 100.00% +packages/pirania/files/usr/lib/lua/read_for_access/read_for_access.lua 41 2 95.35% +packages/pirania/files/usr/lib/lua/voucher/cgi_handlers.lua 44 4 91.67% +packages/pirania/files/usr/lib/lua/voucher/config.lua 8 0 100.00% +packages/pirania/files/usr/lib/lua/voucher/hooks.lua 6 8 42.86% +packages/pirania/files/usr/lib/lua/voucher/store.lua 45 2 95.74% +packages/pirania/files/usr/lib/lua/voucher/utils.lua 46 16 74.19% +packages/pirania/files/usr/lib/lua/voucher/vouchera.lua 174 19 90.16% +packages/pirania/files/usr/libexec/rpcd/pirania 71 9 88.75% +packages/pirania/files/www/pirania-redirect/redirect 17 0 100.00% +packages/safe-upgrade/files/usr/sbin/safe-upgrade 97 159 37.89% +packages/shared-state-babel_links_info/files/usr/share/shared-state/publishers/shared-state-publish_babel_links_info 26 0 100.00% +packages/shared-state-bat_hosts/files/usr/lib/lua/bat-hosts.lua 22 2 91.67% +packages/shared-state-bat_hosts/files/usr/libexec/rpcd/bat-hosts 25 5 83.33% +packages/shared-state-bat_links_info/files/usr/share/shared-state/publishers/shared-state-publish_bat_links_info 31 0 100.00% +packages/shared-state-network_nodes/files/usr/lib/lua/network-nodes.lua 66 0 100.00% +packages/shared-state-node_info/files/usr/share/shared-state/publishers/shared-state-publish_node_info 24 0 100.00% +packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases 22 0 100.00% +packages/shared-state-ref_state_commons/files/usr/lib/lua/shared_state_links_info.lua 29 0 100.00% +packages/shared-state-wifi_links_info/files/usr/share/shared-state/publishers/shared-state-publish_wifi_links_info 30 0 100.00% +packages/shared-state/files/usr/lib/lua/shared-state.lua 173 51 77.23% +packages/shared-state/files/usr/libexec/rpcd/shared-state 45 2 95.74% +packages/ubus-lime-location/files/usr/lib/lua/lime/location.lua 41 0 100.00% +packages/ubus-lime-location/files/usr/libexec/rpcd/lime-location 40 4 90.91% +packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics.lua 77 11 87.50% +packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics/utils.lua 10 2 83.33% +packages/ubus-lime-utils/files/usr/lib/lua/lime-utils-admin.lua 52 39 57.14% +packages/ubus-lime-utils/files/usr/lib/lua/lime-utils.lua 55 35 61.11% +packages/ubus-lime-utils/files/usr/lib/lua/lime/hotspot_wwan.lua 84 3 96.55% +packages/ubus-lime-utils/files/usr/lib/lua/lime/node_status.lua 80 35 69.57% +packages/ubus-lime-utils/files/usr/lib/lua/lime/upgrade.lua 62 23 72.94% +packages/ubus-lime-utils/files/usr/lib/lua/lime/wireless_service.lua 36 0 100.00% +packages/ubus-lime-utils/files/usr/libexec/rpcd/lime-utils-admin 46 5 90.20% +packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service 16 3 84.21% +packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service-admin 24 3 88.89% +packages/ubus-tmate/files/usr/lib/lua/tmate.lua 22 2 91.67% +packages/ubus-tmate/files/usr/libexec/rpcd/tmate 27 6 81.82% +packages/wifi-unstuck-wa/files/usr/lib/lua/lime/wifi_unstuck_wa.lua 66 0 100.00% +tests/fakes/iwinfo.lua 158 3 98.14% +tests/fakes/ubus.lua 6 1 85.71% +----------------------------------------------------------------------------------------------------------------------------------------- +Total 4193 1133 78.73% diff --git a/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua b/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua new file mode 100644 index 000000000..715275066 --- /dev/null +++ b/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua @@ -0,0 +1,101 @@ +local testUtils = require "tests.utils" +local stub = require "luassert.stub" + +local publisher_file = + "packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/" .. + "shared-state-publish_odhcpd_leases" +local run_publisher = testUtils.load_lua_file_as_function(publisher_file) + +local captured_json +local ubus_reply + +local popen_stub, execute_stub + +local function stub_system_calls() + popen_stub = stub(io, "popen", function(cmd, _) + if cmd:match("^ubus call dhcp ipv4leases") then + return { read = function() return "" end, close = function() end } + + elseif cmd:match("^shared%-state%-async insert") then + return { + write = function(_, s) captured_json = s end, + close = function() end + } + + else + return { read = function() return "" end, close = function() end } + end + end) + + execute_stub = stub(os, "execute", function() return true end) +end + +local function revert_system_stubs() + if popen_stub then popen_stub:revert() end + if execute_stub then execute_stub:revert() end +end + +describe("shared-state-odhcpd_leases publisher #odhcpd-leases", function() + + before_each(function() + captured_json = nil + ubus_reply = nil + + package.loaded["luci.jsonc"] = nil + package.preload["luci.jsonc"] = function() + return { + parse = function() return ubus_reply end, + stringify = function(tbl) + if next(tbl) == nil then return "[]" end + local parts = {} + for ip, info in pairs(tbl) do + parts[#parts + 1] = string.format( + '"%s":{"mac":"%s","hostname":"%s"}', + ip, info.mac or "", info.hostname or "") + end + return "{" .. table.concat(parts, ",") .. "}" + end + } + end + + stub_system_calls() + end) + + after_each(function() + revert_system_stubs() + package.preload["luci.jsonc"] = nil + end) + + it("#happy_path publica todas las leases", function() + ubus_reply = { + device = { + eth0 = { + leases = { + { address = "10.0.0.5", mac = "aa:bb", hostname = "h1" }, + { address = "10.0.0.6", mac = "cc:dd", hostname = "h2" } + } + } + } + } + + run_publisher() + + assert.is_string(captured_json, "Se esperaba JSON") + assert.matches('"10%.0%.0%.5"%s*:%s*{[^}]-"mac"%s*:%s*"aa:bb"', captured_json) + assert.matches('"10%.0%.0%.6"%s*:%s*{[^}]-"mac"%s*:%s*"cc:dd"', captured_json) + assert.matches('"hostname"%s*:%s*"h1"', captured_json) + assert.matches('"hostname"%s*:%s*"h2"', captured_json) + end) + + it("#empty ante cero leases publica '[]'", function() + ubus_reply = {} + run_publisher() + assert.equals("[]", captured_json) + end) + + it("#malformed ante parse nil publica '[]'", function() + ubus_reply = nil + run_publisher() + assert.equals("[]", captured_json) + end) +end) From 50b59d3f524f26831ed1a932b48dec05a26de9e1 Mon Sep 17 00:00:00 2001 From: Mateo Rodriguez Date: Wed, 9 Jul 2025 16:11:31 -0300 Subject: [PATCH 12/18] test: add odhcpd-leases publisher unit tests --- .../tests/test_publish_odhcpd_leases.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua b/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua index 715275066..ddb0c5e01 100644 --- a/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua +++ b/packages/shared-state-odhcpd_leases/tests/test_publish_odhcpd_leases.lua @@ -63,10 +63,10 @@ describe("shared-state-odhcpd_leases publisher #odhcpd-leases", function() after_each(function() revert_system_stubs() - package.preload["luci.jsonc"] = nil + package.preload["luci.jsonc"] = nil end) - it("#happy_path publica todas las leases", function() + it("#happy_path publishes every lease", function() ubus_reply = { device = { eth0 = { @@ -80,20 +80,20 @@ describe("shared-state-odhcpd_leases publisher #odhcpd-leases", function() run_publisher() - assert.is_string(captured_json, "Se esperaba JSON") + assert.is_string(captured_json, "Expected JSON") assert.matches('"10%.0%.0%.5"%s*:%s*{[^}]-"mac"%s*:%s*"aa:bb"', captured_json) assert.matches('"10%.0%.0%.6"%s*:%s*{[^}]-"mac"%s*:%s*"cc:dd"', captured_json) assert.matches('"hostname"%s*:%s*"h1"', captured_json) assert.matches('"hostname"%s*:%s*"h2"', captured_json) end) - it("#empty ante cero leases publica '[]'", function() + it("#empty with zero leases publishes '[]'", function() ubus_reply = {} run_publisher() assert.equals("[]", captured_json) end) - it("#malformed ante parse nil publica '[]'", function() + it("#malformed when parse returns nil publishes '[]'", function() ubus_reply = nil run_publisher() assert.equals("[]", captured_json) From d3114d7167163ea61347e0d5197c2f012533e10d Mon Sep 17 00:00:00 2001 From: Mateo Rodriguez Date: Wed, 9 Jul 2025 16:18:13 -0300 Subject: [PATCH 13/18] Test: deleted binary files --- luacov.report.out | 9603 --------------------------------------------- 1 file changed, 9603 deletions(-) delete mode 100644 luacov.report.out diff --git a/luacov.report.out b/luacov.report.out deleted file mode 100644 index 945144693..000000000 --- a/luacov.report.out +++ /dev/null @@ -1,9603 +0,0 @@ -============================================================================== -./packages/pirania/tests/pirania_test_utils.lua -============================================================================== - - - 33 local utils = {} - - 33 function utils.fake_for_tests() - 319 local hooks = require('voucher.hooks') - 319 local config = require('voucher.config') - - 319 config.db_path = '/tmp/pirania_vouchers' - 319 os.execute("mkdir -p " .. config.db_path) - 319 config.prune_expired_for_days = '30' - - 627 hooks.run = function(action) end - end - - 33 return utils - - -============================================================================== -./tests/utils.lua -============================================================================== - 484 local limeutils = require 'lime.utils' - 484 local config = require 'lime.config' - 484 local libuci = require 'uci' - 484 local stub = require 'luassert.stub' - - 484 local utils = {} - - 484 utils.assert = assert - - 484 UCI_CONFIG_FILES = { - 484 "6relayd", "babeld", "batman-adv", "check-date", "dhcp", "dropbear", "fstab", "firewall", - 484 "libremap", "lime", "lime-app", "location", - 484 "luci", "network", "pirania", "rpcd", "shared-state", "system", "ucitrack", - 484 "uhttpd", "wireless", "deferrable-reboot", config.UCI_AUTOGEN_NAME, config.UCI_NODE_NAME, - 484 config.UCI_COMMUNITY_NAME, config.UCI_DEFAULTS_NAME - 484 } - - 484 function utils.disable_asserts() - 6 _G['assert'] = function(expresion, message) return expresion end - end - - 484 function utils.enable_asserts() - 6 _G['assert'] = utils.assert - end - - 484 function utils.lua_path_from_pkgname(pkgname) - 34 return 'packages/' .. pkgname .. '/files/usr/lib/lua/?.lua;' - end - - 484 function utils.enable_package(pkgname) - 17 path = utils.lua_path_from_pkgname(pkgname) - 17 if string.find(package.path, path) == nil then - 17 package.path = path .. package.path - end - end - - 484 function utils.disable_package(pkgname, modulename) - -- remove pkg from LUA search path - 17 path = utils.lua_path_from_pkgname(pkgname) - 17 package.path = string.gsub(package.path, limeutils.literalize(path), '') - -- remove module from preload table - 17 package.preload[modulename] = nil - 17 package.loaded[modulename] = nil - 17 _G[modulename] = nil - end - - -- Creates a custom empty uci environment to be used in unittesting. - -- Should be called in a before_each block and must be followed by a call to - -- teardown_test_uci in an after_each block. - 484 function utils.setup_test_uci() - 2311 local uci = libuci:cursor() - 2311 config.set_uci_cursor(uci) - 2311 local tmpdir = io.popen("mktemp -d"):read('*l') - 1981 uci:set_confdir(tmpdir) - -- If the uci files does not exists then doing uci add fails - -- so here we create empty config files - 53487 for _, cfgname in ipairs(UCI_CONFIG_FILES) do - 51506 local f = io.open(tmpdir .. '/' .. cfgname, "w"):close() - end - 1981 return uci - end - - 484 function utils.teardown_test_uci(uci) - 2311 local confdir = uci:get_confdir() - 1981 if(string.find(confdir, '^/tmp') ~= nil) then - 1981 local out = io.popen("rm -rf " .. confdir .. " " .. uci:get_savedir()) - 1981 out:read('*all') -- this allows waiting for popen completion - 1981 out:close() - 1981 io.popen("rm -rf " .. confdir .. " " .. uci:get_savedir()) - end - 1981 uci:close() - end - - -- Create a temporal empty directory and return its path with a trailin '/' - -- eg: '/tmp/tmp.occigb/' - -- utils.teardown_test_dir() must be called for cleanup - 484 function utils.setup_test_dir() - 545 utils._tmpdir = io.popen("mktemp -d"):read('*l') .. "/" - 545 return utils._tmpdir - end - - 484 function utils.teardown_test_dir() - 776 if(utils._tmpdir ~= nil and string.find(utils._tmpdir, '^/tmp') ~= nil) then - 534 local out = io.popen("rm -rf " .. utils._tmpdir) - 534 out:read('*all') -- this allows waiting for popen completion - 534 out:close() - 534 utils._tmpdir = nil - end - end - - 484 function utils.get_board(name) - 11 local board_path = 'tests/devices/' .. name .. '/board.json' - 11 return limeutils.getBoardAsTable(board_path) - end - - 484 function utils.write_uci_file(uci, filename, content) - 534 local confdir = uci:get_confdir() - 534 local f = io.open(confdir .. '/' .. filename, "w") - 534 f:write(content) - 534 f:close() - end - - 484 function utils.read_uci_file(uci, filename) - 6 local confdir = uci:get_confdir() - 6 local f = io.open(confdir .. '/' .. filename, "r") - 6 local content = nil - 6 if f ~= nil then - 6 content = f:read('*all') - 6 f:close() - end - 6 return content - end - - 484 function utils.load_lua_file_as_function(filename) - 176 local f = io.open(filename) - - 176 local content = "" - - -- removes the shebang if it is the first line - 176 local first_line = f:read("*l") - 176 if not first_line:find("^#!") then -*****0 content = first_line .. content - end - - 176 content = content .. f:read("*a") - 176 f:close() - - -- mimic lua executable arguments handling injecting arg variable from varargs - 176 content = 'local arg = {...}\n' .. content - 176 return loadstring(content, filename) - end - - -- Use this function to test libexec/rpcd "json API" calls - 484 function utils.rpcd_call(f, script_args, call_args) - local response = nil - - 1152 stub(limeutils, "printJson", function (x) response = x end) - 1167 stub(limeutils, "rpcd_readline", function () return call_args end) - - 614 f(unpack(script_args)) - - -- revert the stub - 574 limeutils.printJson:revert() - 574 limeutils.rpcd_readline:revert() - 574 return response - end - - 484 return utils - -============================================================================== -packages/check-internet/files/usr/libexec/rpcd/check-internet -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2021 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2021 Santiago Piccinini - ]]-- - - 33 local ubus = require "ubus" - 33 local json = require "luci.jsonc" - 33 local utils = require "lime.utils" - - 33 local conn = ubus.connect() - 33 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function is_connected() - 22 local exit_status = os.execute('check-internet') - 22 local connected = false - 22 if exit_status == 0 then - 11 connected = true - end - 22 return utils.printJson({status = 'ok', connected = connected}) - end - - 33 local methods = { - 33 is_connected = { no_params = 0 } - } - - 33 if arg[1] == 'list' then - 11 utils.printJson(methods) - end - - 33 if arg[1] == 'call' then - 22 local msg = utils.rpcd_readline() - 22 msg = json.parse(msg) - 22 if arg[2] == 'is_connected' then is_connected(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/deferrable-reboot/files/usr/lib/lua/deferrable_reboot.lua -============================================================================== - 11 local utils = require "lime.utils" - 11 local config = require "lime.config" - - 11 local defreboot = {} - - 11 defreboot.DEFAULT_REBOOT_UPTIME = 60*60*27 - 11 defreboot.SLEEP_BEFORE_REBOOT_S = 30 - - 11 defreboot.POSTPONE_FILE_PATH = '/tmp/deferrable-reboot.defer' - - 11 function defreboot.config(min_uptime) - 121 if min_uptime == nil then - 44 local uci = config.get_uci_cursor() - 44 local lime_min_uptime = config.get("system", "deferrable_reboot_uptime_s", false) - 44 local general_min_uptime = uci:get("deferrable-reboot", "options", "deferrable_reboot_uptime_s") - 44 min_uptime = tonumber(lime_min_uptime or general_min_uptime or defreboot.DEFAULT_REBOOT_UPTIME) - end - 121 assert(type(min_uptime) == "number", "min_uptime must be a number") - 121 defreboot.min_uptime = min_uptime - end - - 11 function defreboot.should_reboot() - 77 local uptime_s = utils.uptime_s() - 77 local postpone_until_s = defreboot.read_postpone_file() - 77 local min_uptime = defreboot.min_uptime - - 77 if (postpone_until_s ~= nil) and (postpone_until_s > min_uptime) then - 33 min_uptime = postpone_until_s - end - 77 return uptime_s > min_uptime - end - - 11 function defreboot.postpone_util_s(uptime) - 33 assert(type(uptime) == "number", "uptime must be a number") - 33 local f = io.open(defreboot.POSTPONE_FILE_PATH, 'w') - 33 f:write(tostring(uptime)) - 33 f:close() - end - - - --! use this function to postpone the reboot, also the following command can be used - --! replacing SECONDS: # awk '{print $1 + SECONDS}' /proc/uptime > /tmp/deferrable-reboot.defer - 11 function defreboot.read_postpone_file() - 77 local f = io.open(defreboot.POSTPONE_FILE_PATH) - 77 if f ~= nil then - 33 return tonumber(f:read("*l")) - end - 44 return nil - end - - 11 function defreboot.reboot() - --! give time to sysupgrade to kill us -*****0 nixio.nanosleep(defreboot.SLEEP_BEFORE_REBOOT_S) -*****0 os.execute("reboot") - end - - 11 return defreboot - -============================================================================== -packages/eupgrade/files/usr/lib/lua/eupgrade.lua -============================================================================== - 11 local utils = require "lime.utils" - 11 local json = require "luci.jsonc" - 11 local libuci = require "uci" - 11 local fs = require("nixio.fs") - - 11 local eup = {} - - 11 eup.STATUS_DEFAULT = 'not-initiated' - 11 eup.STATUS_DOWNLOADING = 'downloading' - 11 eup.STATUS_DOWNLOADED = 'downloaded' - 11 eup.STATUS_DOWNLOAD_FAILED = 'download-failed' - - - 11 local uci = libuci.cursor() - - 11 function eup.set_workdir(workdir) - 99 if not utils.file_exists(workdir) then - 2 os.execute('mkdir -p ' .. workdir) - end - 99 if fs.stat(workdir, "type") ~= "dir" then -*****0 error("Can't configure workdir " .. workdir) - end - 99 eup.WORKDIR = workdir - 99 eup.DOWNLOAD_INFO_CACHE_FILE = eup.WORKDIR .. '/download_status' - 99 eup.FIRMWARE_LATEST_JSON = eup.WORKDIR .. "/firmware_latest.json" - 99 eup.FIRMWARE_LATEST_JSON_SIGNATURE = eup.FIRMWARE_LATEST_JSON .. '.sig' - end - - 11 eup.set_workdir("/tmp/eupgrades") - - 11 function eup.is_enabled() -*****0 return uci:get('eupgrade', 'main', 'enabled') == '1' - end - - 11 function eup.get_upgrade_api_url() - 66 return uci:get('eupgrade', 'main', 'api_url') or '' - end - - 11 function eup._check_signature(file_path, signature_path) -*****0 local cmd = string.format("usign -q -V -P /etc/opkg/keys -x %s -m %s", -*****0 signature_path, file_path) -*****0 local exit_status = os.execute(cmd) -*****0 return exit_status == 0 - end - - 11 function eup._get_board_name() -*****0 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") - end - - 11 function eup._get_current_fw_version() -*****0 return utils.release_info()["DISTRIB_DESCRIPTION"] - end - - 11 function eup._file_sha256(path) -*****0 return utils.unsafe_shell(string.format("sha256sum %s", path)):match("^([^%s]+)") - end - - - --! check if a new firmware is available for download, returning the information of the version - --! when cached_only is true it will not hit the network (only checking the local cache) - 11 function eup.is_new_version_available(cached_only) - --! if 'latest' files are present is because there is a new version - 44 if utils.file_exists(eup.FIRMWARE_LATEST_JSON) and utils.file_exists(eup.FIRMWARE_LATEST_JSON_SIGNATURE) then -*****0 if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then -*****0 return json.parse(utils.read_file(eup.FIRMWARE_LATEST_JSON)) - end - end - 44 if cached_only then -*****0 return false - end - local message - 44 local board_name = eup._get_board_name() - 44 local current_firmware_version = eup._get_current_fw_version() - 44 local url = string.format("%slatest/%s.json", eup.get_upgrade_api_url(), utils.slugify(board_name)) - 44 local latest_json = utils.http_client_get(url, 10) - 44 if not latest_json then - 11 message = "Can't download latest info from: " .. url - else - 33 local latest_data = json.parse(latest_json) - 33 local version = latest_data['version'] - - 33 if version and current_firmware_version ~= version then - 22 utils.write_file(eup.FIRMWARE_LATEST_JSON, latest_json) - 22 local sig_url = url .. ".sig" - 22 if not utils.http_client_get(sig_url, 10, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then -*****0 message = "Can't download signature " .. sig_url -*****0 utils.log(message) - end - - 22 if eup._check_signature(eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE) then - 11 utils.log("Good signature of firmware_latest.json") - 11 return latest_data - else - 11 message = "Bad signature of firmware_latest.json" - 11 utils.log(message) - end - end - end - --! remove the 'latest' files. - 33 utils.unsafe_shell(string.format('rm -f %s %s', eup.FIRMWARE_LATEST_JSON, eup.FIRMWARE_LATEST_JSON_SIGNATURE)) - 33 return false, message - end - - 11 function eup.get_latest_info() -*****0 if utils.file_exists(eup.FIRMWARE_LATEST_JSON) then -*****0 return json.parse(utils.read_file(eup.FIRMWARE_LATEST_JSON)) - end - end - - 11 function eup.get_downloaded_info() -*****0 local latest_data = eup.get_latest_info() -*****0 if latest_data then -*****0 for _, image in pairs(latest_data['images']) do -*****0 local fw_type = image['type'] -*****0 local firmware_path = eup.WORKDIR .. "/" .. image['name'] -*****0 if utils.file_exists(firmware_path) then -*****0 return firmware_path, fw_type - end - end - end - end - - 11 function eup.set_download_status(status) - 66 return utils.write_obj_store_var(eup.DOWNLOAD_INFO_CACHE_FILE, 'status', status) - end - - 11 function eup.get_download_status() - 88 local data = utils.read_obj_store(eup.DOWNLOAD_INFO_CACHE_FILE) - 88 if data.status == nil then - 33 return eup.STATUS_DEFAULT - else - 55 return data.status - end - end - - 11 function eup.download_firmware(latest_data) - 33 if eup.get_download_status() == eup.STATUS_DOWNLOADING then - 11 return nil, "Already downloading" - end - - local image, message - - -- Select the image type, discarding unknown types. Prefer image installer over sysupgrade - 22 for _, im in pairs(latest_data['images']) do - 22 if im['type'] == 'installer' then - 22 image = im - 22 break -*****0 elseif im['type'] == 'sysupgrade' then -*****0 image = im - end - end - - 22 if image then - 22 eup.set_download_status(eup.STATUS_DOWNLOADING) - 33 for _, url in pairs(image['download-urls']) do - 22 if not string.match(url, "://") then - 22 url = eup.get_upgrade_api_url() .. url - end - 22 utils.log("Downloading the firmware from " .. url) - - 22 local firmware_path = eup.WORKDIR .. "/" .. image['name'] - 22 local download_status = utils.http_client_get(url, 10, firmware_path) - 22 if download_status then - 22 if image['sha256'] ~= eup._file_sha256(firmware_path) then - 11 message = "Error: the sha256 does not match" - 11 utils.log(message) - 11 utils.unsafe_shell('rm -f ' .. firmware_path) - else - 11 utils.log("Firmware downloaded ok") - 11 eup.set_download_status(eup.STATUS_DOWNLOADED) - 11 return image - end - end - end - end - - 11 eup.set_download_status(eup.STATUS_DOWNLOAD_FAILED) - 11 return nil, message - end - - 11 return eup - -============================================================================== -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard.lua -============================================================================== - #!/usr/bin/lua - - -- FIRSTBOOTWIZARD - -- get_all_networks: Perform scan and fetch configurations - -- apply_file_config: Set lime-community and apply configurations - -- apply_user_configs: Set a new mesh network - -- check_scan_file: Return /tmp/scanning status - -- is_configured: returns true if FBW has already configured the node - -- mark_as_configured: save the status of FBW as configured (is_configured will return true) - -- read_configs: Return scan results - - 14 local json = require 'luci.jsonc' - 14 local ft = require('firstbootwizard.functools') - 14 local utils = require('firstbootwizard.utils') - 14 local iwinfo = require("iwinfo") - 14 local wireless = require("lime.wireless") - 14 local fs = require("nixio.fs") - 14 local config = require("lime.config") - 14 local lutils = require("lime.utils") - 14 local nixio = require "nixio" - 14 local uci = require "uci" - - 14 local fbw = {} - - 14 fbw.WORKDIR = '/tmp/fbw/' - 14 fbw.COMMUNITY_HOST_CONFIG_PREFIX = 'lime-community__host__' - 14 fbw.COMMUNITY_ASSETS_TMPL = 'lime-community_assets__host__%s.tar.gz' - 14 fbw.SCAN_RESULTS_FILE = 'lime-scan-results.json' - - 14 fbw.FETCH_CONFIG_STATUS = { - 14 downloaded_config = { - 14 retval = true, code = "downloaded_config" - 14 }, - 14 downloading_config = { - 14 retval = true, code = "downloading_config" - 14 }, - 14 error_download_lime_community = { - 14 retval = false, code = "error_download_lime_community" - 14 }, - 14 error_not_configured = { - 14 retval = false, code = "error_not_configured" - 14 }, - 14 error_download_lime_assets = { - 14 retval = false, code = "error_download_lime_assets" - 14 }, - 14 } - - - 14 utils.execute('mkdir -p ' .. fbw.WORKDIR) - - 14 function fbw.log(text) - 132 nixio.syslog('info', '[FBW] ' .. text) - end - - 14 function fbw.lime_community_assets_name(hostname) - 33 return fbw.WORKDIR .. string.format(fbw.COMMUNITY_ASSETS_TMPL, hostname) - end - - 14 function fbw.get_lime_communty_fname(hostname, bssid) - 44 return fbw.WORKDIR .. fbw.COMMUNITY_HOST_CONFIG_PREFIX .. hostname .. "__" .. bssid - end - - -- Write lock file at begin - 14 function fbw.start_scan_file() - 44 local file = io.open("/tmp/scanning", "w") - 44 file:write("true") - 44 file:close() - end - - -- Remove old results - 14 function fbw.clean_tmp() -*****0 utils.execute('rm -f ' .. fbw.WORKDIR .. '*') - end - - -- Save working copy of wireless - 14 function fbw.backup_wifi_config() -*****0 utils.execute("cp /etc/config/wireless /tmp/wireless-temp") - end - - -- Get networks in 5ghz radios - 14 function fbw.get_networks() - -- Get all radios - 11 local radios = ft.map(utils.extract_prop(".name"), wireless.scandevices()) - -- Get only 5ghz radios - 11 local radios_5ghz = ft.filter(wireless.is5Ghz, radios) - -- Convert radios to phys (get a list of phys from radio devices) - 11 local phys = ft.map(utils.extract_phys_from_radios, radios_5ghz) - -- Scan networks in phys and format result - 22 local networks = ft.map( - function(phy) -*****0 local nets = iwinfo.nl80211.scanlist(phy) -*****0 return ft.map(utils.add_prop("phy_idx", utils.phy_to_idx(phy)), nets) - 11 end, phys) - -- Merge results - 11 networks = ft.reduce(ft.flatTable, networks, {}) - -- Filter only remote mesh and ad-hoc networks - 11 networks = ft.filter(utils.filter_mesh, networks) - -- Sort by signal - 11 networks = ft.sortBy('signal', true)(networks) - -- Remove dupicated results in multiradios devices - 11 networks = utils.only_best(networks) - 11 return networks - end - - -- Get macs from 5ghz radios - 14 function fbw.get_own_macs() -*****0 local radios = ft.map(utils.extract_prop(".name"), wireless.scandevices()) -*****0 local radios_5ghz = ft.filter(wireless.is5Ghz, radios) -*****0 local phys = ft.map(utils.extract_phys_from_radios, radios_5ghz) -*****0 return ft.map(function(phy) return table.concat(wireless.get_phy_mac(phy),":") end, phys) - end - - -- Calc link local address and download lime-community - 14 function fbw.get_config(results, mesh_network) -*****0 fbw.log('Calc link local address and download lime-community - '.. json.stringify(mesh_network)) -*****0 local mode = mesh_network.mode == "Mesh Point" and 'mesh' or 'adhoc' -*****0 local dev_id = 'wlan'..mesh_network['phy_idx']..'-'..mode -*****0 local stations = {} -*****0 local linksLocalIpv6 = {} - -- Setup wireless interface -*****0 fbw.setup_wireless(mesh_network) - -- Check if connected if not sleep some more until connected or ignore if 10s passed -*****0 utils.is_connected(dev_id) - -- Get associated stations -*****0 stations = utils.get_stations_macs(dev_id) - -- Remove own wifi networks -*****0 local own_macs = fbw.get_own_macs() -*****0 stations = ft.filter(utils.not_own_network(own_macs), stations) - -- Calc ipv6 -*****0 local linksLocalIpv6 = ft.map(utils.eui64, stations) -*****0 local hosts = ft.map(utils.append_network(dev_id), linksLocalIpv6) - -- Add aditional info -*****0 local data = ft.map(function(host) -*****0 return { host = host, signal = mesh_network.signal, ssid = mesh_network.ssid, bssid = mesh_network.bssid } -*****0 end, hosts) -*****0 data = utils.filter_alredy_scanned(data, results) - -- Try to fetch remote config file -*****0 local configs = ft.map(fbw.fetch_config, data) - -- Return only valid configs -*****0 for _, config in pairs(configs) do -*****0 results[config.host] = config - end -*****0 return results - end - - -- Setup wireless - 14 function fbw.setup_wireless(mesh_network) -*****0 local phy_idx = mesh_network["phy_idx"] -*****0 local mode = mesh_network.mode == "Mesh Point" and 'mesh' or 'adhoc' -*****0 local device_name = "lm_wlan"..phy_idx.."_"..mode.."_radio"..phy_idx -*****0 local uci_cursor = config.get_uci_cursor() - -- Get acutal settings -*****0 local current_channel = uci_cursor:get("wireless", 'radio'..phy_idx, "channel") -*****0 local current_mode = uci_cursor:get("wireless", device_name, "mode") - -- Avoid unnecessary configuration changes -*****0 if ( tonumber(current_channel) == tonumber(mesh_network.channel) and current_mode == mode ) then -*****0 return - end - -- Remove current wireless setup -*****0 uci_cursor:foreach("wireless", "wifi-iface", function(entry) -*****0 if entry['.name'] == device_name then -*****0 uci_cursor:delete("wireless", entry['.name']) - end - end) - -- Set new wireless configuration -*****0 uci_cursor:set("wireless", 'radio'..phy_idx, "channel", mesh_network.channel) -*****0 uci_cursor:set("wireless", device_name, "wifi-iface") -*****0 uci_cursor:set("wireless", device_name, "device", 'radio'..phy_idx) -*****0 uci_cursor:set("wireless", device_name, "ifname", 'wlan'..phy_idx..'-'..mode) -*****0 uci_cursor:set("wireless", device_name, "network", 'lm_net_wlan'..phy_idx..'_'..mode) -*****0 uci_cursor:set("wireless", device_name, "distance", '10000') -*****0 uci_cursor:set("wireless", device_name, "mode", mode) -*****0 uci_cursor:set("wireless", device_name, "mesh_id", 'LiMe') -*****0 uci_cursor:set("wireless", device_name, "ssid", 'LiMe') -*****0 uci_cursor:set("wireless", device_name, "mesh_fwding", '0') -*****0 uci_cursor:set("wireless", device_name, "bssid", 'ca:fe:00:c0:ff:ee') -*****0 uci_cursor:set("wireless", device_name, "mcast_rate", '6000') -*****0 uci_cursor:commit("wireless") -*****0 utils.execute("wifi down radio"..phy_idx.."; wifi up radio"..phy_idx) -*****0 os.execute("sleep 10s") - end - - 14 function fbw.fetch_lime_community(host, lime_community_fname) - 11 local res = lutils.http_client_get("http://[" .. host .. "]/cgi-bin/lime/lime-community", 10, lime_community_fname) - 11 if res == nil or utils.file_not_exists_or_empty(lime_community_fname) then - 11 res = lutils.http_client_get("http://[" .. host .. "]/lime-community", 10, lime_community_fname) - end - 11 return res - end - - -- Return true if download success, false otherwise - 14 function fbw.fetch_lime_community_assets(host, fname) - 33 local res = lutils.http_client_get("http://[" .. host .. "]/cgi-bin/lime/lime-community-assets", 10, lime_community_fname) - 33 return res - end - - -- Fetch remote configuration and save result - 14 function fbw.fetch_config(data) - 44 fbw.log('Fetch config from '.. json.stringify(data)) - 44 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.downloading_config) - 44 local success = true - 44 local host = data.host - - 44 local hostname = utils.execute("wget --no-check-certificate http://["..data.host.."]/cgi-bin/hostname -qO - "):gsub("\n", "") - 44 fbw.log('Hostname found: '.. hostname) - 44 if (hostname == '') then hostname = host end - - 44 local lime_community_fname = fbw.get_lime_communty_fname(hostname, data.bssid) - - 44 local res = fbw.fetch_lime_community(data.host, lime_community_fname) - - -- Remove lime-community files that are not yet configured. - -- For this we asume that no ap_ssid options equals not configured. - 44 if res == true and not utils.file_not_exists_or_empty(lime_community_fname) then - 33 local f = io.open(lime_community_fname) - 33 local content = f:read("*a") - 33 f:close() - 33 if not content:match("ap_ssid") then - 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_not_configured) - 11 utils.execute("rm " .. lime_community_fname) - 11 success = false - else - 22 local fname = fbw.lime_community_assets_name(hostname) - 22 success = fbw.fetch_lime_community_assets(data.host, fname) - 22 if success == nil then - -- Error downloading lime community assets - 11 success = false - 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_download_lime_assets) - end - end - else - -- Error downloading lime community - 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.error_download_lime_community) - 11 success = false - end - - 44 if success then - 11 fbw.set_status_to_scanned_bbsid(data.bssid, fbw.FETCH_CONFIG_STATUS.downloaded_config) - end - - 44 return { host = host, filename = lime_community_fname, success = success} - end - - -- Restore previus wireless configuration - 14 function fbw.restore_wifi_config() - 11 utils.execute("cp /tmp/wireless-temp /etc/config/wireless") - 11 local allRadios = wireless.scandevices() - 11 for _, radio in pairs (allRadios) do -*****0 if wireless.is5Ghz(radio[".name"]) then -*****0 local phyIndex = radio[".name"].sub(radio[".name"], -1) -*****0 utils.execute("wifi down radio"..phyIndex.."; wifi up radio"..phyIndex) - end - end - end - - -- Apply configuration permanenty - -- TODO: check if config is valid - -- TODO: use safe-reboot - 14 function fbw.apply_file_config(file, hostname) -*****0 fbw.log('apply_file_config(file=' .. file .. ', hostname=' .. hostname .. ')') -*****0 local uci_cursor = config.get_uci_cursor() - --Check if lime-community exist -*****0 local filePath = fbw.WORKDIR .. file -*****0 utils.file_exists(filePath) - -- Format hostname -*****0 hostname = hostname or config.get("system", "hostname") - -- Clean previus lime configuration and replace lime-community -*****0 config.reset_node_config() -*****0 utils.execute("cp " .. filePath .. " /etc/config/" .. config.UCI_COMMUNITY_NAME) - - -- Setup the shared lime-assets -*****0 local remote_hostname = string.sub(file, #fbw.COMMUNITY_HOST_CONFIG_PREFIX + 1) -*****0 local lime_community_assets_fname = fbw.lime_community_assets_name(remote_hostname) -*****0 if utils.file_exists(lime_community_assets_fname) then -*****0 utils.execute(string.format("tar xfz %s -C /etc/lime-assets/", lime_community_assets_fname)) - end - - -- Run lime-config as first boot and setup new hostname -*****0 uci_cursor:set(config.UCI_NODE_NAME, "system", "hostname", hostname) -*****0 fbw.end_config() - end - - -- Remove scan lock file - 14 function fbw.end_scan() - 33 local file = io.open("/tmp/scanning", "w") - 33 file:write("false") - 33 file:close() - end - - -- Read scan status - 14 function fbw.check_scan_file() - 99 local file = io.open("/tmp/scanning", "r") - 99 if(file == nil) then - 22 return nil - end - 77 return assert(file:read("*a"), nil) - end - - 14 function fbw.is_configured() - 55 return config.get_bool('system', 'firstbootwizard_configured', false) - end - - 14 function fbw.mark_as_configured() - 11 local uci_cursor = config.get_uci_cursor() - 11 uci_cursor:set(config.UCI_NODE_NAME, 'system', 'firstbootwizard_configured', 'true') - end - - 14 function fbw.is_dismissed() - 55 return config.get_bool('system', 'firstbootwizard_dismissed', false) - end - - 14 function fbw.dismiss() - 11 local uci_cursor = config.get_uci_cursor() - 11 uci_cursor:set(config.UCI_NODE_NAME, 'system', 'firstbootwizard_dismissed', 'true') - 11 uci_cursor:commit(config.UCI_NODE_NAME) - 11 config.uci_autogen() - end - - -- Get config from lime-default file - local function getConfig(path) - 33 local uci_cursor = uci.cursor(fbw.WORKDIR) - 33 local config = uci_cursor:get_all(path) - - 33 if config ~= nil then - 33 return config - end -*****0 return {} - end - - -- List downloaded lime-community - 14 function fbw.read_configs() - 77 local tempFiles = fs.dir(fbw.WORKDIR) - 77 local result = {} - 143 for file in tempFiles do - 66 if (file ~= nil and file:match("^" .. lutils.literalize(fbw.COMMUNITY_HOST_CONFIG_PREFIX))) then - 33 local config = getConfig(file) - 33 local trimedConfig = {} - 33 trimedConfig.wifi = config['wifi'] - 66 table.insert(result, { - 33 config = trimedConfig, - 33 file = file - }) - end - end - - 77 return result - end - - -- Apply configuration for a new network ( used in ubus daemon) - 14 function fbw.create_network(ssid, hostname, password, country) - 11 fbw.log('apply_file_config(ssid=' .. ssid .. ', hostname=' .. hostname .. ')') - 11 local uci_cursor = config.get_uci_cursor() - - -- Save changes in lime-community - 11 if password ~= nil and password ~= '' then - 11 lutils.set_shared_root_password(password) - end - 11 if country then -*****0 uci_cursor:set("lime-community", 'wifi', 'country', country) - end - 11 uci_cursor:set("lime-community", 'wifi', 'ap_ssid', ssid) - 11 uci_cursor:set("lime-community", 'wifi', 'apname_ssid', ssid..'/%H') - 11 uci_cursor:commit("lime-community") - - -- Apply new configuration and setup hostname - 11 config.reset_node_config() - 11 uci_cursor:set("lime-node", 'system', 'hostname', hostname or config.get("system", "hostname")) - 11 fbw.end_config() - end - - 14 function fbw.end_config() -*****0 local uci_cursor = config.get_uci_cursor() -*****0 fbw.mark_as_configured() -*****0 fbw.log('commiting lime-node') -*****0 uci_cursor:commit(config.UCI_NODE_NAME) - -- Apply new configuration - -*****0 os.execute("/usr/bin/lime-config") - -*****0 os.execute("reboot") - end - - 14 function fbw.save_scan_results(networks) - 187 return lutils.write_obj_store(fbw.WORKDIR .. fbw.SCAN_RESULTS_FILE, networks) - end - - 14 function fbw.read_scan_results( ) - 198 return lutils.read_obj_store(fbw.WORKDIR .. fbw.SCAN_RESULTS_FILE) - end - - -- Used to add "status" to an entry on the scanresults file - 14 function fbw.set_status_to_scanned_bbsid(destBssid, status) - -- Open scan_results.json - 110 local results = fbw.read_scan_results() - -- Search ssid - 132 for k, v in pairs(results) do - 132 if(v['bssid'] == destBssid) then - -- Add status message - 110 v["status"] = status - 110 break - end - end - -- Store it again - 110 fbw.save_scan_results(results) - end - - -- Apply file config for specific file, hostname and stop scanning if running - 14 function fbw.set_network(file, hostname) -*****0 fbw.stop_search_networks() -- Stop firstbootwizard service if running -*****0 fbw.apply_file_config(file, hostname) - end - - -- Scan for networks and fetch configurations files - 14 function fbw.get_all_networks() -*****0 local networks = {} -*****0 local configs = {} -*****0 fbw.log("Starting search networks") - -*****0 fbw.log('Add lock file') -*****0 fbw.start_scan_file() -*****0 fbw.log('Clear previus scans') -*****0 fbw.clean_tmp() -*****0 fbw.log('Set wireless backup') -*****0 fbw.backup_wifi_config() -*****0 fbw.log('Get mesh networks') -*****0 networks = fbw.get_networks() -*****0 fbw.log('Saving mesh scan results') -*****0 fbw.save_scan_results(networks) -*****0 fbw.log('Get configs files') -*****0 configs = ft.reduce(fbw.get_config, networks, {}) -*****0 fbw.log('Restore previous wireless configuration') -*****0 fbw.restore_wifi_config() -*****0 fbw.log('Remove lock file') -*****0 fbw.end_scan() -*****0 fbw.log('Return configs files names') -*****0 return configs - end - - -- Run daemonized /bin/firstbootwizard execution that start get_all_networks - -- Return false if already runing - 14 function fbw.start_search_networks() - 11 local scan_file = fbw.check_scan_file() - 11 if(scan_file == nil) or (scan_file == "false") then - 11 os.execute("rm -f /tmp/scanning") - 11 lutils.execute_daemonized("/bin/firstbootwizard") - 11 return true - end -*****0 return false - end - - -- Return object with status, read_configs() and read_scan_results() - 14 function fbw.status_search_networks() - 33 local scan_file = fbw.check_scan_file() - local status - 33 if (scan_file == nil) then - 11 status = 'idle' - 22 elseif(scan_file == "true") then - 22 status = 'scanning' - else -*****0 status = 'scanned' - end - 33 lock = not fbw.is_configured() and not fbw.is_dismissed() - 33 return { lock = lock, status = status, networks = fbw.read_configs(), scanned = fbw.read_scan_results()} - end - - -- todo(kon): check this work properly - 14 function fbw.kill_fbw() - 11 os.execute("killall firstbootwizard") - end - - -- Function that stop get_all_networks function if running - 14 function fbw.stop_search_networks() - 11 local scan_file = fbw.check_scan_file() - 11 if (scan_file == "true") then - 11 fbw.log('Stopping firstbootwizard service') - 11 fbw.kill_fbw() - 11 fbw.log('Restore previus wireless configuration') - 11 fbw.restore_wifi_config() - 11 fbw.log('Remove lock file') - 11 fbw.end_scan() - 11 return true - else -*****0 return true - end -*****0 return false - end - - -- Return false if can't perform the restart - 14 function fbw.restart_search_networks() -*****0 if fbw.stop_search_networks() then -*****0 return fbw.start_search_networks() - end -*****0 return false - end - - 14 return fbw - -============================================================================== -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/functools.lua -============================================================================== - 14 local functools = {} - - -- Lua implementation of the curry function - -- Developed by tinylittlelife.org - -- released under the WTFPL (http://sam.zoy.org/wtfpl/) - - -- curry(func, num_args) : take a function requiring a tuple for num_args arguments - -- and turn it into a series of 1-argument functions - -- e.g.: you have a function dosomething(a, b, c) - -- curried_dosomething = curry(dosomething, 3) -- we want to curry 3 arguments - -- curried_dosomething (a1) (b1) (c1) -- returns the result of dosomething(a1, b1, c1) - -- partial_dosomething1 = curried_dosomething (a_value) -- returns a function - -- partial_dosomething2 = partial_dosomething1 (b_value) -- returns a function - -- partial_dosomething2 (c_value) -- returns the result of dosomething(a_value, b_value, c_value) - 14 function functools.curry(func, num_args) - - -- currying 2-argument functions seems to be the most popular application -*****0 num_args = num_args or 2 - - -- helper - local function curry_h(argtrace, n) -*****0 if 0 == n then - -- reverse argument list and call function -*****0 return func(functools.reverse(argtrace())) - else - -- "push" argument (by building a wrapper function) and decrement n - return function (onearg) -*****0 return curry_h(function () return onearg, argtrace() end, n - 1) - end - end - end - - -- no sense currying for 1 arg or less -*****0 if num_args > 1 then -*****0 return curry_h(function () return end, num_args) - else -*****0 return func - end - end - - -- reverse(...) : take some tuple and return a tuple of elements in reverse order - -- - -- e.g. "reverse(1,2,3)" returns 3,2,1 - 14 function functools.reverse(...) - - --reverse args by building a function to do it, similar to the unpack() example - local function reverse_h(acc, v, ...) -*****0 if 0 == select('#', ...) then -*****0 return v, acc() - else -*****0 return reverse_h(function () return v, acc() end, ...) - end - end - - -- initial acc is the end of the list -*****0 return reverse_h(function () return end, ...) - end - - - 14 function functools.map(func, tbl) - 33 local newtbl = {} - 33 for i,v in pairs(tbl) do -*****0 newtbl[i] = func(v) - end - 33 return newtbl - end - - 14 function functools.filter(func, tbl) - 22 local newtbl= {} - 22 local index=1; - 22 for i,v in pairs(tbl) do -*****0 if func(v, i) then -*****0 newtbl[index]=v -*****0 index = index + 1 - end - end - 22 return newtbl - end - - 14 function functools.search(func, tbl) -*****0 for i,v in pairs(tbl) do -*****0 if func(v, i) then -*****0 return i - end - end - -*****0 return 0 - end - - 14 function functools.print_r ( t ) -*****0 local print_r_cache={} - local function sub_print_r(t,indent) -*****0 if (print_r_cache[tostring(t)]) then -*****0 print(indent.."*"..tostring(t)) - else -*****0 print_r_cache[tostring(t)]=true -*****0 if (type(t)=="table") then -*****0 for pos,val in pairs(t) do -*****0 if (type(val)=="table") then -*****0 print(indent.."["..pos.."] => "..tostring(t).." {") -*****0 sub_print_r(val,indent..string.rep(" ",string.len(pos)+8)) -*****0 print(indent..string.rep(" ",string.len(pos)+6).."}") -*****0 elseif (type(val)=="string") then -*****0 print(indent.."["..pos..'] => "'..val..'"') - else -*****0 print(indent.."["..pos.."] => "..tostring(val)) - end - end - else -*****0 print(indent..tostring(t)) - end - end - end -*****0 if (type(t)=="table") then -*****0 print(tostring(t).." {") -*****0 sub_print_r(t," ") -*****0 print("}") - else -*****0 sub_print_r(t," ") - end -*****0 print() - end - - 14 function functools.reduce(cb, tab, default) - 22 local result = default - 22 for k, act in pairs(tab) do -*****0 result = cb(result,act) - end - 22 return result - end - - 14 function functools.splitBy(option) - return function(tab) -*****0 local result = {} -*****0 for k, obj in pairs(tab) do -*****0 if result[obj[option]] == nil then -*****0 result[obj[option]] = {} - end -*****0 table.insert(result[obj.mode], obj) - end -*****0 return result - end - end - - 14 function functools.sortBy(option, reverse) - return function(tab) - 22 table.sort(tab, function (left, right) -*****0 order = left[option] < right[option] -*****0 return (not reverse and order) or not order - end) - 11 return tab - end - end - - 14 function functools.flatTable(prev, act) -*****0 for i=1,#act do -*****0 prev[#prev+1] = act[i] - end -*****0 return prev - end - - 14 return functools - -============================================================================== -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/utils.lua -============================================================================== - 14 local utils = {} - - 14 local ft = require('firstbootwizard.functools') - 14 local fs = require("nixio.fs") - 14 local iwinfo = require("iwinfo") - 14 local json = require("luci.jsonc") - 14 local limeutils = require("lime.utils") - - 14 function execute(cmd) - 498 local f = assert(io.popen(cmd, 'r')) - 498 local s = assert(f:read('*a')) - 498 f:close() - 498 return s - end - - 14 function utils.execute (cmd) - 498 return execute(cmd) - end - - 14 function utils.eui64(mac) - local cmd = [[ - function eui64 { - mac="$(echo "$1" | tr -d : | tr A-Z a-z)" - mac="$(echo "$mac" | head -c 6)fffe$(echo "$mac" | tail -c +7)" - let "b = 0x$(echo "$mac" | head -c 2)" - let "b ^= 2" - printf "%02x" "$b" - echo "$mac" | tail -c +3 | head -c 2 - echo -n : - echo "$mac" | tail -c +5 | head -c 4 - echo -n : - echo "$mac" | tail -c +9 | head -c 4 - echo -n : - echo "$mac" | tail -c +13 - } -*****0 echo -n `eui64 ]]..mac..'`' -*****0 return 'fe80::'..execute(cmd) - end - - 14 function utils.file_exists(filename) -*****0 return fs.stat(filename, "type") == "reg" - end - - 14 function utils.file_not_exists_or_empty(filename) - 33 local f=io.open(filename,"r") - 33 if not f then return true end - 33 local size = f:seek("end") - 33 if f~=nil then io.close(f) end - 33 if size == 0 then return true else return false end - end - - 14 function split(str, sep) -*****0 local sep, fields = sep or ":", {} -*****0 local pattern = string.format("([^%s]+)", sep) -*****0 str:gsub(pattern, function(c) fields[#fields+1] = c end) -*****0 return fields - end - - 14 function utils.split(str, sep) -*****0 return split(str, sep) - end - - -- splits a multiline string in a list of strings, one per line - 14 function lsplit(mlstring) -*****0 return split(mlstring, "\n") - end - - 14 function utils.lsplit(mlstring) -*****0 return lsplit(mlstring) - end - - 14 function utils.phy_to_idx(phy) -*****0 local substr = string.gsub(phy, "phy", "") -*****0 return tonumber(substr) - end - - 14 function utils.extract_phys_from_radios(radio) -*****0 return "phy"..radio.sub(radio, -1) - end - - 14 function utils.not_own_network(own_macs) - return function(remote_mac) -*****0 return limeutils.has_value(own_macs, remote_mac) ~= true - end - end - - 14 function utils.add_prop(option, value) - return function(tab) -*****0 tab[option] = value -*****0 return tab - end - end - - 14 function utils.extract_prop(prop) - return function(tab) -*****0 return tab[prop] - end - end - - 14 function utils.read_file(file) -*****0 local lines = utils.lines_from("/tmp/"..file) -*****0 return lines - end - - - 14 function tableEmpty(self) -*****0 for _, _ in pairs(self) do -*****0 return false - end -*****0 return true - end - - 14 function utils.tableEmpty(self) -*****0 return tableEmpty(self) - end - - 14 function utils.hash_file(file) -*****0 return execute("md5sum "..file.." | awk '{print $1}'") - end - - 14 function utils.are_files_different(file1, file2) -*****0 return hash_file(file1) ~= hash_file(file2) - end - - 14 function utils.unpack_table(t) -*****0 local unpacked = {} -*****0 for k,v in ipairs(t) do -*****0 for sk, sv in ipairs(v) do -*****0 unpacked[#unpacked+1] = sv - end - end -*****0 return unpacked - end - - 14 function utils.filter_mesh(n) -*****0 return n.mode == "Ad-Hoc" or n.mode == "Mesh Point" - end - - local same_network = function(net_a, net_b) -*****0 return net_a.channel == net_b.channel and net_a.ssid == net_b.ssid - end - - local find_network = function(network, data) -*****0 return ft.filter(function(net) return same_network(net,network) end, data) - end - - 14 function utils.only_best(networks) - 22 return ft.reduce(function(acc, network) -*****0 local existent = find_network(network, acc) -*****0 if #existent > 0 then -*****0 acc = ft.map(function(net) -*****0 if same_network(net, network) and net.signal < network.signal then -*****0 return network - end -*****0 return net -*****0 end,acc) -*****0 return acc - end -*****0 table.insert(acc, network) -*****0 return acc - 11 end, networks, {}) - end - - 14 function utils.is_connected(dev_id) -*****0 local isAssociated = {} -*****0 local i = 0 -*****0 while (tableEmpty(isAssociated)) and i < 5 do -*****0 isAssociated = iwinfo.nl80211.assoclist(dev_id) -*****0 if tableEmpty(isAssociated) == false then break end -*****0 i = i + 1 -*****0 os.execute("sleep 2s") - end - end - - 14 function utils.get_stations_macs(network) -*****0 return lsplit(execute('iw dev '..network..' station dump | grep ^Station | cut -d\\ -f 2')) - end - - 14 function utils.append_network(dev) - return function (ipv6) -*****0 return ipv6..'%'..dev - end - end - - 14 function utils.filter_alredy_scanned(hosts, results) -*****0 local fe80scanned = ft.reduce(function(scanned, host) -*****0 local mac = split(host.host, "%%")[1] -*****0 scanned[mac] = mac -*****0 return scanned -*****0 end, results, {}) - -*****0 return ft.filter(function(host) -*****0 local mac = split(host.host, "%%")[1] -*****0 return fe80scanned[mac] == nil -*****0 end, hosts) - end - - 14 return utils - -============================================================================== -packages/lime-eth-config/files/usr/lib/lua/lime-eth-config.lua -============================================================================== - -- ! LibreMesh - -- ! Generic hook to be called as a symbolic link for each ref type - -- ! Copyright (C) 2025 Javier Jorge - -- ! Copyright (C) 2025 Instituto Nacional de Tecnología Industrial (INTI) - -- ! Copyright (C) 2025 Asociación Civil Altermundi - -- ! SPDX-License-Identifier: AGPL-3.0- - 11 local JSON = require("luci.jsonc") - 11 local utils = require("lime.utils") - 11 local config = require("lime.config") - 11 local libuci = require("uci") - 11 local node_status = require 'lime.node_status' - - 11 local luci_config = libuci:cursor() - 11 local eht_config = {} - - 11 function eht_config.get_eth_config() - 33 limenode_interfaces = {} - --get configurations from lime-node - 33 local uci = config.get_uci_cursor() - 66 uci:foreach("lime-autogen", "net", function(entry) -*****0 if entry.eth_role ~= nil then -*****0 local interface = {} -*****0 interface.name = entry.linux_name -*****0 interface.eth_role = entry.eth_role -*****0 table.insert(limenode_interfaces, interface) - end - end) - --get default settings - 33 local switch_status = node_status.switch_status() - 33 local interfaces = {} - 33 if switch_status ~= nil then - 165 for _, status in ipairs(switch_status) do - 132 local interface = { - 132 device = status.device, - 132 num = status.num, - 132 role = status.role, - 132 link = status.link, - 132 eth_role = "default" -- default value - } - 132 for _, limenode_interface in ipairs(limenode_interfaces) do -*****0 if limenode_interface.name == status.device then -*****0 interface.eth_role = limenode_interface.eth_role - break - end - end - 132 table.insert(interfaces, interface) - end - end - 33 return interfaces - end - - 11 function eht_config.delete_eth_config(tag_value) - 11 local uci = config.get_uci_cursor() - 11 config.uci:delete("lime-node", tag_value) - 11 config.uci:save("lime-node") - 11 uci:commit("lime-node") - end - - 11 function eht_config.set_eth_config(device, role) - 33 local tag_value = "lime_app_eth_cfg_" .. device:gsub("%.", "_") - 33 local uci = config.get_uci_cursor() - 33 local eth_role = uci:get("lime-node", tag_value, "eth_role") - -- verify previous config - 33 if eth_role ~= nil then -*****0 if eth_role == role then - -- No changes needed, the role is already set -*****0 return true - else -*****0 eht_config.delete_eth_config(tag_value) - end - end - - -- handle lm_hwd_openwrt_wan - 33 if role ~= "wan" then - -- if the port was automatically configured as a wan port, we need to remove the - -- wan protocol. If we returtn to default we need to enable auto detection. - 22 local switch_status = node_status.switch_status() - 22 if switch_status ~= nil then - 110 for _, status in ipairs(switch_status) do - 88 if status.device == device and status.role == "wan" then -*****0 if role == "lan" or role == "mesh" then -*****0 uci:set("lime-node", "lm_hwd_openwrt_wan", "net") -*****0 uci:set("lime-node", "lm_hwd_openwrt_wan", "autogenerated", "false") - -*****0 elseif role == "default" then -*****0 uci:delete("lime-node", "lm_hwd_openwrt_wan") - end - break - end - end - end - end - - 33 if role == "default" then - 11 eht_config.delete_eth_config(tag_value) - else - 22 uci:set("lime-node", tag_value, "net") - 22 uci:set("lime-node", tag_value, "eth_role", role) - 22 uci:set("lime-node", tag_value, "linux_name", device) - - 22 if role == "wan" then - 11 uci:set("lime-node", tag_value, "protocols", {"wan", "dynamic"}) - 11 elseif role == "lan" then -*****0 uci:set("lime-node", tag_value, "protocols", {"lan"}) - 11 elseif role == "mesh" then - 11 uci:set("lime-node", tag_value, "protocols", {"batadv:%N1", "babeld:17"}) - else -*****0 return false - end - end - 33 uci:commit("lime-node") - 33 config.uci:save("lime-node") - 33 os.execute("lime-config && /etc/init.d/network restart") - 33 return true - end - - 11 return eht_config - -============================================================================== -packages/lime-eth-config/files/usr/libexec/rpcd/lime-eth-config -============================================================================== - #!/usr/bin/env lua - - --! LibreMesh - --! Generic hook to be called as a symbolic link for each ref type - --! Copyright (C) 2025 Javier Jorge - --! Copyright (C) 2025 Instituto Nacional de Tecnología Industrial (INTI) - --! Copyright (C) 2025 Asociación Civil Altermundi - --! SPDX-License-Identifier: AGPL-3.0- - - - 66 local json = require 'luci.jsonc' - 66 local utils = require 'lime.utils' - 66 local eth_config = require("lime-eth-config") - - - local function get_eth_config(msg) - - 33 interfaces = eth_config.get_eth_config() - - 33 result = {} - 33 if interfaces and #interfaces > 0 then - 33 result.status = "ok" - 33 result.interfaces = interfaces - else -*****0 result.status = "error" -*****0 result.interfaces = {} -*****0 result.error = "No interface configuration found" - end - 33 utils.printJson(result) - end - - 66 function set_eth_config(msg) - 33 local device, role = msg.device, msg.role - - 33 if not device or not role then -*****0 return utils.printJson({ status = "error", error = "Missing parameters" }) - end - - 33 local success = eth_config.set_eth_config(device, role) - 33 if success then - 33 utils.printJson({ status = "ok" }) - else -*****0 utils.printJson({ status = "error", error = "Failed to set eth ".. msg.device .. " as " .. msg.role }) - end - - end - - 66 local methods = { - 66 get_eth_config = { no_params = 0 }, - 66 set_eth_config = { device = 'value', role = 'value'} - } - - - 66 if arg[1] == 'list' then -*****0 utils.printJson(methods) - end - - 66 if arg[1] == 'call' then - 66 local msg = utils.rpcd_readline() or '{}' - 66 msg = json.parse(msg) - 66 if arg[2] == 'get_eth_config' then get_eth_config(msg) - 33 elseif arg[2] == 'set_eth_config' then set_eth_config(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/lime-hwd-ground-routing/files/usr/lib/lua/lime/hwd/ground_routing.lua -============================================================================== - #!/usr/bin/lua - - 6 local hardware_detection = require("lime.hardware_detection") - 6 local config = require("lime.config") - 6 local utils = require("lime.utils") - - 6 local ground_routing = {} - - 6 ground_routing.sectionNamePrefix = hardware_detection.sectionNamePrefix.."ground_routing_" - - 6 function ground_routing.delete_all_switch_vlan_sections() -*****0 local uci = config.get_uci_cursor() -*****0 uci:foreach("network", "switch_vlan", function(s) uci:delete("network", s[".name"]) end ) -*****0 uci:save("network") - end - - 6 function ground_routing.clean() - 6 local uci = config.get_uci_cursor() - - 6 function cleanGrSection(section) - 24 if utils.stringStarts(section[".name"], ground_routing.sectionNamePrefix) then -*****0 uci:delete("network", section[".name"]) - end - end - - 6 uci:foreach("network", "switch_vlan", cleanGrSection) - 6 uci:foreach("network", "interface", cleanGrSection) - 6 uci:save("network") - end - - 6 function ground_routing.detect_hardware() - 6 function parse_gr(section) -*****0 local link_name = section[".name"] -*****0 local net_dev = section["net_dev"] -*****0 local vlan = section["vlan"] -*****0 local uci = config.get_uci_cursor() - -*****0 function create_8021q_dev(vlan_p) -*****0 local dev_secname = ground_routing.sectionNamePrefix..link_name.."_"..net_dev.."_"..vlan_p -*****0 uci:set("network", dev_secname, "device") -*****0 uci:set("network", dev_secname, "name", net_dev.."."..vlan_p) -*****0 uci:set("network", dev_secname, "ifname", net_dev) -*****0 uci:set("network", dev_secname, "type", "8021q") -*****0 uci:set("network", dev_secname, "vid", vlan_p) - end - -*****0 local switch_dev = section["switch_dev"] -*****0 if switch_dev then -*****0 local switch_cpu_port = section["switch_cpu_port"] -*****0 function tag_cpu_port(section) -*****0 if (section["device"] ~= switch_dev) then return end - -*****0 local patterns = { "^"..switch_cpu_port.." ", " "..switch_cpu_port.."$", " "..switch_cpu_port.." " } -*****0 local substits = { switch_cpu_port.."t ", " "..switch_cpu_port.."t", " "..switch_cpu_port.."t " } -*****0 local matchCount = 0 -*****0 local m = 0 -*****0 for i,p in pairs(patterns) do -*****0 section["ports"], m = section["ports"]:gsub(p, substits[i]) -*****0 matchCount = matchCount + m - end - -*****0 if (matchCount > 0) then -*****0 create_8021q_dev(section["vlan"]) -*****0 uci:set("network", section[".name"], "ports", section["ports"]) - end - end - -*****0 uci:foreach("network", "switch_vlan", tag_cpu_port) - - -*****0 local sw_secname = ground_routing.sectionNamePrefix..link_name.."_sw_"..switch_dev.."_"..vlan -*****0 local ports = switch_cpu_port.."t" -*****0 for _,p in pairs(section["switch_ports"]) do -*****0 ports = ports.." "..p - end - -*****0 uci:set("network", sw_secname, "switch_vlan") -*****0 uci:set("network", sw_secname, "device", switch_dev) -*****0 uci:set("network", sw_secname, "vlan", vlan) -*****0 uci:set("network", sw_secname, "ports", ports) - end - -*****0 create_8021q_dev(vlan) - -*****0 uci:save("network") - end - - local clean_needed -- if there are no hwd_gr sections defined, don't clean all switch_vlan sections - 6 config.foreach("hwd_gr", function(s) clean_needed = true end) - 6 if clean_needed then ground_routing.delete_all_switch_vlan_sections() end - - 6 config.foreach("hwd_gr", parse_gr) - end - - 6 return ground_routing - -============================================================================== -packages/lime-hwd-openwrt-wan/files/usr/lib/lua/lime/hwd/openwrt_wan.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2014-2023 Gioacchino Mazzurco - --! Copyright (C) 2023 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - - 20 local hardware_detection = require("lime.hardware_detection") - 20 local config = require("lime.config") - 20 local utils = require("lime.utils") - - 20 local openwrt_wan = {} - - 20 openwrt_wan.sectionName = hardware_detection.sectionNamePrefix.."openwrt_wan" - - 20 function openwrt_wan.clean() - 6 if config.autogenerable(openwrt_wan.sectionName) then - 6 config.delete(openwrt_wan.sectionName) - end - end - - 20 function openwrt_wan.detect_hardware() - 39 if config.autogenerable(openwrt_wan.sectionName) then - local ifname - 39 local board = utils.getBoardAsTable() - 39 local networkTable = board['network'] - 39 if networkTable then - 28 local wanTable = networkTable['wan'] - 28 if wanTable then - 17 ifname = wanTable['device'] - end - end - 39 if ifname and ifname ~= "" then - 17 local protos = {} - 17 local net = require("lime.network") - 17 local utils = require("lime.utils") - 88 for _, pArgs in pairs(config.get("network", "protocols")) do - 71 local pArr = utils.split(pArgs, net.protoParamsSeparator) - 71 if ( pArr[1] == "bmx6" or pArr[1] == "bmx7") then - 12 pArr[2] = 0 - 12 pArgs = table.concat(pArr, net.protoParamsSeparator) - 12 table.insert(protos, pArgs) - 59 elseif ( pArr[1]~="lan" and pArr[1]~="wan" ) then - 42 table.insert(protos, pArgs) - end - end - 17 table.insert(protos, "wan") - - 17 config.init_batch() - 17 config.set(openwrt_wan.sectionName, "net") - 17 config.set(openwrt_wan.sectionName, "autogenerated", "true") - 17 config.set(openwrt_wan.sectionName, "protocols", protos) - 17 config.set(openwrt_wan.sectionName, "linux_name", ifname) - 17 config.end_batch() - - 17 utils.dbg("WAN interface:", ifname) - else - 22 utils.dbg("WAN interface not found") - end - end - end - - 20 return openwrt_wan - -============================================================================== -packages/lime-hwd-usbradio/files/usr/lib/lua/lime/hwd/usbradio.lua -============================================================================== - #!/usr/bin/lua - - 6 local utils = require("lime.utils") - 6 local config = require("lime.config") - 6 local hardware_detection = require("lime.hardware_detection") - - 6 local libuci = require("uci") - 6 local fs = require("nixio.fs") - - 6 usbradio = {} - - 6 usbradio.sectionNamePrefix = hardware_detection.sectionNamePrefix.."usbradio_" - - --! Remove configuration about no more plugged usb radio - 12 function usbradio.clean() - 6 local uci = libuci:cursor() - - --! This function control if a usb radio configuration section is valid otherwise delete it - local function test_and_clean_device(s) - --! Check if passed section is an usb radio -*****0 local sectionName = s[".name"] -*****0 if utils.stringStarts(sectionName, usbradio.sectionNamePrefix) then - --! Check if the section is autogenerated otherwise do not touch it - --! We check it to avoid delete usb radio sections manually configured -*****0 if config.get_bool(sectionName, "autogenerated") then -*****0 local phyIndex = sectionName:match("%d+$") - --! Check if the usb radio exist - --! If the usb radio does not exist anymore (numberOfMatches < 1) delete it -*****0 local _, numberOfMatches = fs.glob("/sys/devices/"..s["path"].."/ieee80211/phy"..phyIndex) -*****0 if numberOfMatches < 1 then -*****0 local radioName = s["radio_name"] -*****0 uci:delete("wireless", radioName) -*****0 config.delete(sectionName) - end - end - end - end - - --! For each wifi-device section call test_and_clean_device function - 6 uci:foreach("wireless", "wifi-device", test_and_clean_device) - 6 uci:save("wireless") - end - - - --! Detect the usb radio and configurate it - 12 function usbradio.detect_hardware() - 6 local stdOutput = io.popen("find /sys/devices | grep usb | grep ieee80211 | grep 'phy[0-9]*$'") - --! Repeat for each usb radio found - 6 for path in stdOutput:lines() do - --! Define useful variables -*****0 local endBasePath, phyEnd = string.find(path, "/ieee80211/phy") -*****0 local phyPath = string.sub(path, 14, endBasePath-1) -*****0 local phyIndex = string.sub(path, phyEnd+1) -*****0 local radioName = "radio"..phyIndex -*****0 local sectionName = usbradio.sectionNamePrefix..radioName - - --! If sectionName exist and it is autogenerated configure it - --! If sectionName does not exist it is created and configured - --! Check if a sectionName is autogenerated avoid delete usb radio introduced by the user -*****0 if config.autogenerable(sectionName) then -*****0 local uci = libuci:cursor() - - --! Delete the usb radio -*****0 uci:delete("wireless", radioName) - --! Create and configure the usb radio directly in the OpenWRT system (wireless) -*****0 uci:set("wireless", radioName, "wifi-device") -*****0 uci:set("wireless", radioName, "type", "mac80211") -*****0 uci:set("wireless", radioName, "channel", "11") --TODO: working on all 802.11bgn devices; find a general way for working in different devices -*****0 uci:set("wireless", radioName, "band", "2g") --TODO: working on all 802.11gn devices; find a general way for working in different devices -*****0 uci:set("wireless", radioName, "path", phyPath) -*****0 uci:set("wireless", radioName, "htmode", "HT20") -*****0 uci:set("wireless", radioName, "disabled", "0") - -*****0 uci:save("wireless") - - --! Write just once on the disk all the config.set -*****0 config.init_batch() -*****0 config.set(sectionName, "wifi") -*****0 config.set(sectionName, "autogenerated", "true") -*****0 config.set(sectionName, "radio_name", radioName) - - --! Configuration of an usb radio using general option in LiMe -*****0 for option_name, value in pairs(config.get_all("wifi")) do - --! Options that start with point are hidden option, we exclude them as they can cause problems -*****0 if (option_name:sub(1,1) ~= ".") then - --! Needed a table or a string for config.set -*****0 if ( type(value) ~= "table" ) then value = tostring(value) end -*****0 config.set(sectionName, option_name, value) - end - end - -*****0 local modes = {} -*****0 for _, mode in pairs(config.get("wifi", "modes")) do -*****0 if mode ~= "adhoc" then table.insert(modes, mode) end - end -*****0 config.set(sectionName, "modes", modes) - -*****0 config.end_batch() - end - end - end - - - 6 return usbradio - -============================================================================== -packages/lime-hwd-watchcat/files/usr/lib/lua/lime/hwd/watchcat.lua -============================================================================== - #!/usr/bin/lua - - 6 local hardware_detection = require("lime.hardware_detection") - 6 local config = require("lime.config") - 6 local utils = require("lime.utils") - - 6 local watchcat = {} - - 6 watchcat.sectionNamePrefix = hardware_detection.sectionNamePrefix.."watchcat_" - - local function reload_watchcat() -*****0 os.execute("/etc/init.d/watchcat reload") - end - - 6 function watchcat.clean() - 6 local uci = config.get_uci_cursor() - 6 local modified = false - - local function clear_watchcat_section(section) -*****0 local is_ours = utils.stringStarts(section[".name"], watchcat.sectionNamePrefix) - -*****0 local is_anon = section[".anonymous"] - -*****0 if is_ours or is_anon then -*****0 uci:delete("watchcat", section[".name"]) -*****0 modified = true - end - end - - 6 uci:foreach("watchcat", "watchcat", clear_watchcat_section) - 6 if modified then -*****0 uci:save("watchcat") -*****0 reload_watchcat() - end - end - - 6 function watchcat.detect_hardware() - 6 local uci = config.get_uci_cursor() - 6 local user_defined = false - - 12 config.foreach("hwd_watchcat", function(user_section) -*****0 user_defined = true -*****0 local identifier = user_section.id or "default" -*****0 local section_name = watchcat.sectionNamePrefix .. identifier - -*****0 uci:set("watchcat", section_name, "watchcat") - -*****0 for option_key, option_value in pairs(user_section) do - -- discards .name, .type keys and id name sections -*****0 if option_key:sub(1,1) ~= "." and option_key ~= "id" then -*****0 uci:set("watchcat", section_name, option_key, option_value) - end - end - end) - -- only saved if we actually aplied any user section - 6 if user_defined then -*****0 uci:save("watchcat") -*****0 reload_watchcat() - end - end - - 6 return watchcat - -============================================================================== -packages/lime-proto-babeld/files/usr/lib/lua/lime/proto/babeld.lua -============================================================================== - #!/usr/bin/lua - - --! LiMe Proto Babeld - --! Copyright (C) 2018 Gioacchino Mazzurco - --! - --! This program is free software: you can redistribute it and/or modify - --! it under the terms of the GNU Affero General Public License as - --! published by the Free Software Foundation, either version 3 of the - --! License, or (at your option) any later version. - --! - --! This program is distributed in the hope that it will be useful, - --! but WITHOUT ANY WARRANTY; without even the implied warranty of - --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - --! GNU Affero General Public License for more details. - --! - --! You should have received a copy of the GNU Affero General Public License - --! along with this program. If not, see . - - 6 local network = require("lime.network") - 6 local config = require("lime.config") - 6 local fs = require("nixio.fs") - - 6 babeld = {} - - 6 babeld.configured = false - - 12 function babeld.configure(args) - 72 if babeld.configured then return end - 6 babeld.configured = true - - 6 utils.log("lime.proto.babeld.configure(...)") - - 6 fs.writefile("/etc/config/babeld", "") - - 6 local uci = config.get_uci_cursor() - - 6 if config.get("network", "babeld_over_librenet6", false) then -*****0 uci:set("babeld", "librenet6", "interface") -*****0 uci:set("babeld", "librenet6", "ifname", "librenet6") -*****0 uci:set("babeld", "librenet6", "type", "tunnel") - end - - 6 uci:set("babeld", "general", "general") - 6 uci:set("babeld", "general", "local_port", "30003") - 6 uci:set("babeld", "general", "ubus_bindings", "true") - - 6 uci:set("babeld", "ula6", "filter") - 6 uci:set("babeld", "ula6", "type", "redistribute") - 6 uci:set("babeld", "ula6", "ip", "fc00::/7") - 6 uci:set("babeld", "ula6", "action", "allow") - - 6 uci:set("babeld", "public6", "filter") - 6 uci:set("babeld", "public6", "type", "redistribute") - 6 uci:set("babeld", "public6", "ip", "2000::0/3") - 6 uci:set("babeld", "public6", "action", "allow") - - 6 uci:set("babeld", "default6", "filter") - 6 uci:set("babeld", "default6", "type", "redistribute") - 6 uci:set("babeld", "default6", "ip", "0::0/0") - 6 uci:set("babeld", "default6", "le", "0") - 6 uci:set("babeld", "default6", "action", "allow") - - 6 uci:set("babeld", "mesh4", "filter") - 6 uci:set("babeld", "mesh4", "type", "redistribute") - 6 uci:set("babeld", "mesh4", "ip", "10.0.0.0/8") - 6 uci:set("babeld", "mesh4", "action", "allow") - - 6 uci:set("babeld", "mptp4", "filter") - 6 uci:set("babeld", "mptp4", "type", "redistribute") - 6 uci:set("babeld", "mptp4", "ip", "172.16.0.0/12") - 6 uci:set("babeld", "mptp4", "action", "allow") - - 6 uci:set("babeld", "default4", "filter") - 6 uci:set("babeld", "default4", "type", "redistribute") - 6 uci:set("babeld", "default4", "ip", "0.0.0.0/0") - 6 uci:set("babeld", "default4", "le", "0") - 6 uci:set("babeld", "default4", "action", "allow") - - -- Avoid redistributing extra local addesses - 6 uci:set("babeld", "localdeny", "filter") - 6 uci:set("babeld", "localdeny", "type", "redistribute") - 6 uci:set("babeld", "localdeny", "local", "true") - 6 uci:set("babeld", "localdeny", "action", "deny") - - -- Avoid redistributing enything else - 6 uci:set("babeld", "denyany", "filter") - 6 uci:set("babeld", "denyany", "type", "redistribute") - 6 uci:set("babeld", "denyany", "action", "deny") - - 6 uci:save("babeld") - - end - - 12 function babeld.setup_interface(ifname, args) - 66 if not args["specific"] and ifname:match("^wlan%d+.ap") then - 36 utils.log("lime.proto.babeld.setup_interface(%s, ...) ignored", ifname) - 36 return - end - - 30 utils.log("lime.proto.babeld.setup_interface(%s, ...)", ifname) - - 30 local vlanId = args[2] or 17 - 30 local vlanProto = args[3] or "8021ad" - 30 local nameSuffix = args[4] or "_babeld" - - local owrtInterfaceName, linuxVlanIfName, owrtDeviceName = - 30 network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - - 30 local ipv4, _ = network.primary_address() - - 30 local uci = config.get_uci_cursor() - - 30 if(vlanId ~= 0 and (ifname:match("^eth") or ifname:match("^lan"))) then - 6 uci:set("network", owrtDeviceName, "mtu", tostring(network.MTU_ETH_WITH_VLAN)) - end - - 30 uci:set("network", owrtInterfaceName, "proto", "static") - 30 uci:set("network", owrtInterfaceName, "ipaddr", ipv4:host():string()) - 30 uci:set("network", owrtInterfaceName, "netmask", "255.255.255.255") - 30 uci:save("network") - - 30 uci:set("babeld", owrtInterfaceName, "interface") - 30 uci:set("babeld", owrtInterfaceName, "ifname", linuxVlanIfName) - --! It is quite common to have dummy radio device attached via ethernet so - --! disable wired optimization always as it would consider the link down at - --! first packet lost - 30 uci:set("babeld", owrtInterfaceName, "type", "wireless") - - 30 uci:save("babeld") - end - - 12 function babeld.runOnDevice(linuxDev, args) -*****0 utils.log("lime.proto.babeld.runOnDevice(%s, ...)", linuxDev) - -*****0 local vlanId = args[2] or 17 -*****0 local vlanProto = args[3] or "8021ad" - -*****0 local vlanDev = network.createVlan(linuxDev, vlanId, vlanProto) -*****0 network.createStatic(vlanDev) - -*****0 local libubus = require("ubus") -*****0 local ubus = libubus.connect() -*****0 ubus:call('babeld', 'add_interface', { ifname = vlanDev }) - end - - 6 return babeld - -============================================================================== -packages/lime-proto-batadv/files/usr/lib/lua/lime/proto/batadv.lua -============================================================================== - #!/usr/bin/lua - - 17 local fs = require("nixio.fs") - 17 local lan = require("lime.proto.lan") - 17 local utils = require("lime.utils") - 17 local network = require("lime.network") - 17 local config = require("lime.config") - - 17 batadv = {} - - 17 batadv.configured = false - - 34 function batadv.configure(args) - 94 if batadv.configured then return end - 28 batadv.configured = true - - 28 local uci = config.get_uci_cursor() - - 28 uci:set("network", "bat0", "interface") - 28 uci:set("network", "bat0", "proto", "batadv") - -- BATMAN_V uses throughput rather than packet loss (as in BATMAN_IV) for evaluating - -- the quality of a link. Still, by default we continue selecting BATMAN_IV - 28 local routing_algo = config.get("network", "batadv_routing_algo", "BATMAN_IV") - 28 uci:set("network", "bat0", "routing_algo", routing_algo) - 28 uci:set("network", "bat0", "bridge_loop_avoidance", "1") - 28 uci:set("network", "bat0", "multicast_mode", "0") - -- by default, BATMAN-adv sends out one Originator Message (OGM) every second (orig_interval=1000) - -- in a network with static nodes, a larger interval between OGM packets can be used (e.g. 2000) - -- see https://github.com/libremesh/lime-packages/issues/1010 - 28 local orig_interval = config.get("network", "batadv_orig_interval", "2000") - 28 uci:set("network", "bat0", "orig_interval", orig_interval) - - -- if anygw enabled disable DAT that doesn't play well with it - -- and set gw_mode=client everywhere. Since there's no gw_mode=server, this makes bat0 never forward requests - -- so a rogue DHCP server doesn't affect whole network (DHCP requests are always answered locally) - 121 for _,proto in pairs(config.get("network", "protocols")) do - 93 if proto == "anygw" then - 17 uci:set("network", "bat0", "distributed_arp_table", "0") - 17 uci:set("network", "bat0", "gw_mode", "client") - end - end - 28 uci:save("network") - 28 lan.setup_interface("bat0", nil) - - -- enable alfred on bat0 if installed - 28 if utils.is_installed("alfred") then -*****0 uci:set("alfred", "alfred", "batmanif", "bat0") -*****0 uci:save("alfred") - end - end - - 34 function batadv.setup_interface(ifname, args) - 66 if not args["specific"] then - 60 if ifname:match("^wlan%d+.ap") then - 72 utils.log( "lime.proto.batadv.setup_interface(%s, ...) ignored", - 36 ifname ) - 36 return - end - end - - 30 utils.log("lime.proto.batadv.setup_interface(%s, ...)", ifname) - - 30 local vlanId = args[2] or "%N1" - 30 local vlanProto = args[3] or "8021ad" - 30 local nameSuffix = args[4] or "_batadv" - 30 local mtu = 1532 - - --! Unless a specific integer is passed, parse network_id (%N1) template - --! and use that number to get a vlanId between 29 and 284 for batadv - --! (to avoid overlapping with other protocols, - --! complex definition is for keeping retrocompatibility) - 30 if not tonumber(vlanId) then vlanId = 29 + (utils.applyNetTemplate10(vlanId) - 13) % 256 end - - 30 local owrtInterfaceName, _, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - - 30 local uci = config.get_uci_cursor() - 30 uci:set("network", owrtInterfaceName, "proto", "batadv_hardif") - 30 uci:set("network", owrtInterfaceName, "master", "bat0") - - 30 if ifname:match("^eth") then - --! TODO: Use DSA to check if ethernet device is capable of bigger MTU - --! reducing it -*****0 mtu = network.MTU_ETH_WITH_VLAN - end - - --! Avoid dmesg flooding caused by BLA with messages like "br-lan: - --! received packet on bat0 with own address as source address". - --! Tweak MAC address for each of the interfaces used by Batman-adv - --! 00 + Locally administered unicast .. 2 bytes from interface name - --! .. 3 bytes from main interface - 30 local id = utils.get_id(ifname) - 30 local vMacaddr = network.primary_mac(); - 30 vMacaddr[1] = "02" - 30 vMacaddr[2] = id[2] - 30 vMacaddr[3] = id[3] - 30 uci:set("network", owrtDeviceName, "macaddr", table.concat(vMacaddr, ":")) - - 30 uci:set("network", owrtDeviceName, "mtu", mtu) - 30 uci:save("network") - end - - 34 function batadv.runOnDevice(linuxDev, args) -*****0 args = args or {} -*****0 local vlanId = args[2] or "%N1" -*****0 local vlanProto = args[3] or "8021ad" - -*****0 utils.log("lime.proto.batadv.runOnDevice(%s, ...)", linuxDev) - - -*****0 local mtu = 1532 - -*****0 if not tonumber(vlanId) then -*****0 vlanId = 29 + (utils.applyNetTemplate10(vlanId) - 13) % 256 - end - -*****0 local devName = network.createVlan(linuxDev, vlanId, vlanProto) -*****0 local ifName = network.limeIfNamePrefix..linuxDev .. "_batadv" - -*****0 local ifaceConf = { - name = ifName, - proto = "batadv_hardif", - auto = "1", - device = devName, -*****0 master = "bat0" - } - -*****0 local libubus = require("ubus"); -*****0 local ubus = libubus.connect() -*****0 ubus:call('network', 'add_dynamic', ifaceConf) -*****0 ubus:call('network.interface.'..ifName, 'up', {}) - - - --! TODO: as of today ubus silently fails to properly setting up a linux network - --! device for batman ADV usage dinamycally work around it by using - --! shell commands instead -*****0 network.createStatic(devName) -*****0 utils.unsafe_shell("batctl if add "..devName) - end - - 17 return batadv - -============================================================================== -packages/lime-proto-bmx7/files/usr/lib/lua/lime/proto/bmx7.lua -============================================================================== - #!/usr/bin/lua - - 6 local network = require("lime.network") - 6 local config = require("lime.config") - 6 local fs = require("nixio.fs") - 6 local libuci = require("uci") - 6 local wireless = require("lime.wireless") - 6 local utils = require("lime.utils") - - 6 bmx7 = {} - - 6 bmx7.configured = false - 6 bmx7.f = "bmx7" - - 12 function bmx7.configure(args) - 72 if bmx7.configured then return end - 6 bmx7.configured = true - - 6 local uci = libuci:cursor() - 6 local ipv4, ipv6 = network.primary_address() - - 6 fs.writefile("/etc/config/"..bmx7.f, "") - - 6 uci:set(bmx7.f, "general", "bmx7") - 6 uci:set(bmx7.f, "general", "dbgMuteTimeout", "1000000") - 6 uci:set(bmx7.f, "general", "tunOutTimeout", "100000") - 6 uci:set(bmx7.f, "general", "configSync", "0") - 6 uci:set(bmx7.f, "general", "syslog", "0") - - 6 uci:set(bmx7.f, "main", "tunDev") - 6 uci:set(bmx7.f, "main", "tunDev", "main") - 6 uci:set(bmx7.f, "main", "tun4Address", ipv4:string()) - 6 uci:set(bmx7.f, "main", "tun6Address", ipv6:string()) - - -- If publish own IP enabled, configure tunIn - 6 local pub_own_ip = config.get_bool("network", "bmx7_publish_ownip", false) - 6 if (pub_own_ip) then -*****0 uci:set(bmx7.f, "myIP4", "tunIn") -*****0 uci:set(bmx7.f, "myIP4", "tunIn", "myIP4") -*****0 uci:set(bmx7.f, "myIP4", "network", ipv4:host():string()..'/32') -*****0 uci:set(bmx7.f, "myIP6", "tunIn") -*****0 uci:set(bmx7.f, "myIP6", "tunIn", "myIP6") -*****0 uci:set(bmx7.f, "myIP6", "network", ipv6:host():string()..'/128') - end - - -- Enable bmx7 uci config plugin - 6 uci:set(bmx7.f, "config", "plugin") - 6 uci:set(bmx7.f, "config", "plugin", "bmx7_config.so") - - -- Enable JSON plugin to get bmx7 information in json format - 6 if utils.is_installed("bmx7-json") then -*****0 uci:set(bmx7.f, "json", "plugin") -*****0 uci:set(bmx7.f, "json", "plugin", "bmx7_json.so") - end - - -- Enable topology plugin to get netjson file - 6 if utils.is_installed("bmx7-topology") then -*****0 uci:set(bmx7.f, "topology", "plugin") -*****0 uci:set(bmx7.f, "topology", "plugin", "bmx7_topology.so") - end - - -- Enable iwinfo plugin to get better link bandwidth estimation - 6 if utils.is_installed("bmx7-iwinfo") then -*****0 uci:set(bmx7.f, "iwinfo", "plugin") -*****0 uci:set(bmx7.f, "iwinfo", "plugin", "bmx7_iwinfo.so") - end - - -- Enable SMS plugin to enable sharing of small files - 6 if utils.is_installed("bmx7-sms") then -*****0 uci:set(bmx7.f, "sms", "plugin") -*****0 uci:set(bmx7.f, "sms", "plugin", "bmx7_sms.so") - end - - -- Enable tun plugin, DISCLAIMER: this must be positioned before table plugin if used. - 6 uci:set(bmx7.f, "ptun", "plugin") - 6 uci:set(bmx7.f, "ptun", "plugin", "bmx7_tun.so") - - -- Disable ThrowRules because they are broken in IPv6 with current Linux Kernel - 6 uci:set(bmx7.f, "ipVersion", "ipVersion") - 6 uci:set(bmx7.f, "ipVersion", "ipVersion", "6") - - -- Search for networks in 172.16.0.0/12 - 6 uci:set(bmx7.f, "nodes", "tunOut") - 6 uci:set(bmx7.f, "nodes", "tunOut", "nodes") - 6 uci:set(bmx7.f, "nodes", "network", "172.16.0.0/12") - - -- Search for networks in 10.0.0.0/8 - 6 uci:set(bmx7.f, "clouds", "tunOut") - 6 uci:set(bmx7.f, "clouds", "tunOut", "clouds") - 6 uci:set(bmx7.f, "clouds", "network", "10.0.0.0/8") - - -- Search for internet in the mesh cloud - 6 uci:set(bmx7.f, "inet4", "tunOut") - 6 uci:set(bmx7.f, "inet4", "tunOut", "inet4") - 6 uci:set(bmx7.f, "inet4", "network", "0.0.0.0/0") - 6 uci:set(bmx7.f, "inet4", "maxPrefixLen", "0") - - -- Search for internet IPv6 gateways in the mesh cloud - 6 uci:set(bmx7.f, "inet6", "tunOut") - 6 uci:set(bmx7.f, "inet6", "tunOut", "inet6") - 6 uci:set(bmx7.f, "inet6", "network", "::/0") - 6 uci:set(bmx7.f, "inet6", "maxPrefixLen", "0") - - -- Search for other mesh cloud announcements that have public ipv6 - 6 uci:set(bmx7.f, "publicv6", "tunOut") - 6 uci:set(bmx7.f, "publicv6", "tunOut", "publicv6") - 6 uci:set(bmx7.f, "publicv6", "network", "2000::/3") - 6 uci:set(bmx7.f, "publicv6", "maxPrefixLen", "64") - - -- Set prefered GW if defined - 6 local pref_gw = config.get("network", "bmx7_pref_gw") - 6 if (pref_gw ~= "none") then -*****0 uci:set(bmx7.f, "inet4p", "tunOut") -*****0 uci:set(bmx7.f, "inet4p", "tunOut", "inet4p") -*****0 uci:set(bmx7.f, "inet4p", "network", "0.0.0.0/0") -*****0 uci:set(bmx7.f, "inet4p", "maxPrefixLen", "0") -*****0 uci:set(bmx7.f, "inet4p", "gwName", pref_gw) -*****0 uci:set(bmx7.f, "inet4p", "rating", "1000") - -*****0 uci:set(bmx7.f, "inet6p", "tunOut") -*****0 uci:set(bmx7.f, "inet6p", "tunOut", "inet6p") -*****0 uci:set(bmx7.f, "inet6p", "network", "::/0") -*****0 uci:set(bmx7.f, "inet6p", "maxPrefixLen", "0") -*****0 uci:set(bmx7.f, "inet6p", "gwName", pref_gw) -*****0 uci:set(bmx7.f, "inet6p", "rating", "1000") - else - 6 uci:delete(bmx7.f, "inet4p", "tunOut") - 6 uci:delete(bmx7.f, "inet6p", "tunOut") - end - - 6 local hasBatadv = false - 6 local bmxOverBatdv = config.get_bool("network", "bmx7_over_batman") - 6 local hasLan = false - 66 for _,protoArgs in pairs(config.get("network", "protocols")) do - 60 local proto = utils.split(protoArgs, network.protoParamsSeparator)[1] - 60 if(proto == "lan") then hasLan = true - 54 elseif(proto == "batadv") then hasBatadv = true end - end - - 6 if config.get("network", "bmx7_over_librenet6", false) then -*****0 uci:set(bmx7.f, "librenet6", "dev") -*****0 uci:set(bmx7.f, "librenet6", "dev", "librenet6") - end - - 6 local enablePKI = config.get_bool("network", "bmx7_enable_pki") - 6 if (enablePKI) then -*****0 uci:set(bmx7.f, "general", "trustedNodesDir", "/etc/bmx7/trustedNodes") - end - - 6 if(hasLan) then - 6 uci:set(bmx7.f, "lm_net_br_lan", "dev") - 6 uci:set(bmx7.f, "lm_net_br_lan", "dev", "br-lan") - end - - 6 if(hasLan and hasBatadv and not bmxOverBatdv) then - --! Let firewall4 append a table of family 'bridge' with a chain - --! that hooks into postrouting and prevents bmx7 over batadv. - 6 local includeDir = "/usr/share/nftables.d/ruleset-post/" - 6 local fileName = "lime-proto-bmx7_bmx7-not-over-bat0.nft" - 6 fs.mkdirr(includeDir) - 12 fs.symlink( - 6 "/usr/share/lime/"..fileName, - 6 includeDir..fileName - 6 ) - else -*****0 fs.unlink( -*****0 "/usr/share/nftables.d/ruleset-post/".. - "lime-proto-bmx7_bmx7-not-over-bat0.nft" - ) - end - - 6 uci:save(bmx7.f) - - 6 uci:delete("firewall", "bmxtun") - - 6 uci:set("firewall", "bmxtun", "zone") - 6 uci:set("firewall", "bmxtun", "name", "bmx7tun") - 6 uci:set("firewall", "bmxtun", "input", "ACCEPT") - 6 uci:set("firewall", "bmxtun", "output", "ACCEPT") - 6 uci:set("firewall", "bmxtun", "forward", "ACCEPT") - 6 uci:set("firewall", "bmxtun", "mtu_fix", "1") - 6 uci:set("firewall", "bmxtun", "conntrack", "1") - 6 uci:set("firewall", "bmxtun", "device", "X7+") - 6 uci:set("firewall", "bmxtun", "family", "ipv4") - - 6 uci:save("firewall") - end - - 12 function bmx7.setup_interface(ifname, args) - 66 if not args["specific"] and - 60 ( ifname:match("^wlan%d+.ap") or ifname:match("^eth%d+") ) - 36 then return end - - 30 vlanId = tonumber(args[2]) or 18 - 30 vlanProto = args[3] or "8021ad" - 30 nameSuffix = args[4] or "_bmx7" - - 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - - 30 local uci = libuci:cursor() - 30 local mtu = config.get("network", "bmx7_mtu", "1500") - 30 uci:set("network", owrtDeviceName, "mtu", mtu) - - -- BEGIN [Workaround issue 38] - 30 if ifname:match("^wlan%d+") then - 18 local macAddr = wireless.get_phy_mac("phy"..ifname:match("%d+")) - 18 local vlanIp = { 169, 254, tonumber(macAddr[5], 16), tonumber(macAddr[6], 16) } - 18 uci:set("network", owrtInterfaceName, "proto", "static") - 18 uci:set("network", owrtInterfaceName, "ipaddr", table.concat(vlanIp, ".")) - 18 uci:set("network", owrtInterfaceName, "netmask", "255.255.255.255") - end - --- END [Workaround issue 38] - - 30 uci:save("network") - - 30 uci:set(bmx7.f, owrtInterfaceName, "dev") - 30 uci:set(bmx7.f, owrtInterfaceName, "dev", linux802adIfName) - - 30 if ifname:match("^wlan%d+") then - 18 local rateMax = config.get("network", "bmx7_wifi_rate_max", "auto") - 18 if rateMax ~= "auto" then -*****0 uci:set(bmx7.f, owrtInterfaceName, "rateMax", rateMax) - end - end - - 30 uci:save(bmx7.f) - end - - 12 function bmx7.apply() -*****0 utils.unsafe_shell("killall bmx7 ; sleep 2 ; killall -9 bmx7") -*****0 utils.unsafe_shell("bmx7") - end - - 12 function bmx7.bgp_conf(templateVarsIPv4, templateVarsIPv6) -*****0 local uci = libuci:cursor() - - -- Enable Routing Table Redistribution plugin -*****0 uci:set(bmx7.f, "table", "plugin") -*****0 uci:set(bmx7.f, "table", "plugin", "bmx7_table.so") - - -- Redistribute proto bird routes -*****0 uci:set(bmx7.f, "fromBird", "redistTable") -*****0 uci:set(bmx7.f, "fromBird", "redistTable", "fromBird") -*****0 uci:set(bmx7.f, "fromBird", "table", "254") -*****0 uci:set(bmx7.f, "fromBird", "bandwidth", "100") -*****0 uci:set(bmx7.f, "fromBird", "proto", "12") - - -- Avoid aggregation as it use lot of CPU with huge number of routes -*****0 uci:set(bmx7.f, "fromBird", "aggregatePrefixLen", "128") - - -- Disable proactive tunnels announcement as it use lot of CPU with - -- huge number of routes -*****0 uci:set(bmx7.f, "general", "proactiveTunRoutes", "0") - - -- BMX7 security features are at moment not used by LiMe, disable hop - -- by hop links signature as it consume a lot of CPU expecially in - -- setups with multiples interfaces and lot of routes like LiMe -*****0 uci:set(bmx7.f, "general", "linkSignatureLen", "0") - -*****0 uci:save(bmx7.f) - - local base_bgp_conf = [[ - protocol direct { - interface "X7*"; - } -*****0 ]] - -*****0 return base_bgp_conf - end - - 6 return bmx7 - -============================================================================== -packages/lime-proto-olsr/files/usr/lib/lua/lime/proto/olsr.lua -============================================================================== - #!/usr/bin/lua - - 6 local network = require("lime.network") - 6 local config = require("lime.config") - 6 local fs = require("nixio.fs") - 6 local libuci = require("uci") - 6 local wireless = require("lime.wireless") - 6 local utils = require("lime.utils") - 6 local ip = require("luci.ip") - - 6 local olsr = {} - - 6 olsr.configured = false - - 6 function olsr.configure(args) - 72 if olsr.configured then return end - 6 olsr.configured = true - - 6 local uci = libuci:cursor() - 6 local ipv4 = network.primary_address() - - 6 fs.writefile("/etc/config/olsrd", "") - - 6 uci:set("olsrd", "lime", "olsrd") - 6 uci:set("olsrd", "lime", "LinkQualityAlgorithm", "etx_ff") - 6 uci:set("olsrd", "lime", "IpVersion", "4") - - 6 uci:set("olsrd", "limejson", "LoadPlugin") - 6 uci:set("olsrd", "limejson", "library", "olsrd_jsoninfo.so.0.0") - 6 uci:set("olsrd", "limejson", "accept", "127.0.0.1") - - 6 uci:set("olsrd", "limehna", "Hna4") - 6 uci:set("olsrd", "limehna", "netaddr", ipv4:network():string()) - 6 uci:set("olsrd", "limehna", "netmask", ipv4:mask():string()) - - 6 uci:save("olsrd") - end - - 6 function olsr.setup_interface(ifname, args) - 66 if not args["specific"] then - 60 if ifname:match("^wlan%d+.ap") then return end - end - - 30 local vlanId = tonumber(args[2]) or 14 - 30 local vlanProto = args[3] or "8021ad" - 30 local nameSuffix = args[4] or "_olsr" - 30 local ipPrefixTemplate = args[5] or "169.254.%M5.%M6/16" - 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - 30 local macAddr = network.get_mac(utils.split(ifname, ".")[1]) - 30 local ipAddr = ip.IPv4(utils.applyMacTemplate10(ipPrefixTemplate, macAddr)) - - 30 local uci = libuci:cursor() - 30 uci:set("network", owrtInterfaceName, "proto", "static") - 30 uci:set("network", owrtInterfaceName, "ipaddr", ipAddr:host():string()) - 30 uci:set("network", owrtInterfaceName, "netmask", ipAddr:mask():string()) - 30 uci:save("network") - - 30 uci:set("olsrd", owrtInterfaceName, "Interface") - 30 uci:set("olsrd", owrtInterfaceName, "interface", owrtInterfaceName) - 30 uci:save("olsrd") - end - - - 6 return olsr - -============================================================================== -packages/lime-proto-olsr2/files/usr/lib/lua/lime/proto/olsr2.lua -============================================================================== - #!/usr/bin/lua - - 6 local network = require("lime.network") - 6 local config = require("lime.config") - 6 local fs = require("nixio.fs") - 6 local libuci = require("uci") - 6 local wireless = require("lime.wireless") - 6 local utils = require("lime.utils") - 6 local ip = require("luci.ip") - 6 olsr2 = {} - - 6 olsr2.configured = false - - 12 function olsr2.configure(args) - 72 if olsr2.configured then return end - 6 olsr2.configured = true - - 6 local uci = libuci:cursor() - 6 local ipv4, ipv6 = network.primary_address() - 6 local origInterfaceName = network.limeIfNamePrefix.."olsr_originator_lo" - - 6 fs.writefile("/etc/config/olsrd2", "") - 6 uci:set("olsrd2", "lime", "global") - 6 uci:set("olsrd2", "lime", "failfast", "no") - 6 uci:set("olsrd2", "lime", "pidfile", "/var/run/olsrd2.pid") - 6 uci:set("olsrd2", "lime", "lockfile", "/var/lock/olsrd2") - 6 uci:set("olsrd2", "lime", "olsrv2") - 6 uci:set("olsrd2", "lime", "lan", {ipv4:string(), ipv6:string()}) - 6 uci:set("olsrd2", "limelog", "log") - 6 uci:set("olsrd2", "limejson", "syslog", "true") - 6 uci:set("olsrd2", "limejson", "info", "all") - 6 uci:set("olsrd2", "limetelnet", "telnet") - 6 uci:set("olsrd2", "limetelnet", "port", "2009") - 6 uci:set("olsrd2", origInterfaceName, "interface") - 6 uci:set("olsrd2", origInterfaceName, "ifname", "loopback") - 6 uci:save("olsrd2") - - 6 uci:set("network", origInterfaceName, "interface") - 6 uci:set("network", origInterfaceName, "ifname", "@loopback") - 6 uci:set("network", origInterfaceName, "proto", "static") - 6 uci:set("network", origInterfaceName, "ipaddr", ipv4:host():string()) - 6 uci:set("network", origInterfaceName, "netmask", "255.255.255.255") - 6 uci:set("network", origInterfaceName, "ip6addr", ipv6:host():string().."/128") - 6 uci:save("network") - end - - 12 function olsr2.setup_interface(ifname, args) - 66 if not args["specific"] then - 60 if ifname:match("^wlan%d+.ap") then return end - end - 30 local vlanId = tonumber(args[2]) or 16 - 30 local vlanProto = args[3] or "8021ad" - 30 local nameSuffix = args[4] or "_olsr" - 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - 30 local uci = libuci:cursor() - - 30 uci:set("olsrd2", owrtInterfaceName, "interface") - 30 uci:set("olsrd2", owrtInterfaceName, "ifname", owrtInterfaceName) - 30 uci:save("olsrd2") - - end - - - 6 return olsr2 - -============================================================================== -packages/lime-proto-olsr6/files/usr/lib/lua/lime/proto/olsr6.lua -============================================================================== - #!/usr/bin/lua - - 6 local network = require("lime.network") - 6 local config = require("lime.config") - 6 local fs = require("nixio.fs") - 6 local libuci = require("uci") - 6 local wireless = require("lime.wireless") - 6 local utils = require("lime.utils") - 6 local ip = require("luci.ip") - - 6 local olsr = {} - - 6 olsr.configured = false - - 6 function olsr.configure(args) - 72 if olsr.configured then return end - 6 olsr.configured = true - - 6 local uci = libuci:cursor() - 6 local _, ipv6 = network.primary_address() - - 6 fs.writefile("/etc/config/olsrd6", "") - - 6 uci:set("olsrd6", "lime", "olsrd") - 6 uci:set("olsrd6", "lime", "LinkQualityAlgorithm", "etx_ff") - 6 uci:set("olsrd6", "lime", "IpVersion", "6") - - 6 uci:set("olsrd6", "limejson", "LoadPlugin") - 6 uci:set("olsrd6", "limejson", "library", "olsrd_jsoninfo.so.0.0") - 6 uci:set("olsrd6", "limejson", "accept", "::1") - - 6 uci:set("olsrd6", "limehna", "Hna6") - 6 uci:set("olsrd6", "limehna", "netaddr", ipv6:network():string()) - 6 uci:set("olsrd6", "limehna", "prefix", ipv6:prefix()) - - 6 uci:save("olsrd6") - end - - 6 function olsr.setup_interface(ifname, args) - 66 if not args["specific"] then - 60 if ifname:match("^wlan%d+.ap") then return end - end - - 30 vlanId = tonumber(args[2]) or 15 - 30 vlanProto = args[3] or "8021ad" - 30 nameSuffix = args[4] or "_olsr6" - 30 local ipPrefixTemplate = args[5] or "fc00::%M1%M2:%M3%M4:%M5%M6/64" - - 30 local owrtInterfaceName, linux802adIfName, owrtDeviceName = network.createVlanIface(ifname, vlanId, nameSuffix, vlanProto) - 30 local macAddr = network.get_mac(utils.split(ifname, ".")[1]) - 30 local ipAddr = ip.IPv6(utils.applyMacTemplate16(ipPrefixTemplate, macAddr)) - - 30 local uci = libuci:cursor() - 30 uci:set("network", owrtInterfaceName, "proto", "static") - 30 uci:set("network", owrtInterfaceName, "ip6addr", ipv6:string()) -*****0 uci:save("network") - -*****0 uci:set("olsrd6", owrtInterfaceName, "Interface") -*****0 uci:set("olsrd6", owrtInterfaceName, "interface", owrtInterfaceName) -*****0 uci:save("olsrd6") - end - - 6 return olsr - -============================================================================== -packages/lime-proto-wan/files/usr/lib/lua/lime/proto/wan.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2014-2023 Gioacchino Mazzurco - --! Copyright (C) 2023 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - 6 local libuci = require("uci") - - 6 wan = {} - - 6 wan.configured = false - - 12 function wan.configure(args) - 6 if wan.configured then return end - 6 wan.configured = true - - 6 local uci = libuci:cursor() - 6 uci:set("network", "wan", "interface") - 6 uci:set("network", "wan", "proto", "dhcp") - 6 uci:save("network") - end - - 12 function wan.setup_interface(ifname, args) - 6 local uci = libuci:cursor() - 6 uci:set("network", "wan", "device", ifname) - 6 uci:save("network") - - --! Accepting link local traffic also on WAN should not cause hazards. - --! It is very helpful in cases where the devices have problem to the other - --! ports, to have at least an addictional way to enter for rescue operation - 6 local ALLOW_WAN_LL_SECT = "lime_allow_wan_all_link_local" - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "rule") - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "name", ALLOW_WAN_LL_SECT) - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "src", "wan") - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "family", "ipv6") - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "src_ip", "fe80::/10") - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "dest_ip", "fe80::/10") - 6 uci:set("firewall", ALLOW_WAN_LL_SECT, "target", "ACCEPT") - 6 uci:save("firewall") - end - - 6 return wan - -============================================================================== -packages/lime-system/files/usr/bin/migrate-wifi-bands-cfg -============================================================================== - #!/usr/bin/lua - - 33 local config = require 'lime.config' - 33 local wireless = require 'lime.wireless' - 33 local uci = config.get_uci_cursor() - - 33 local uci_files = { config.UCI_COMMUNITY_NAME, config.UCI_NODE_NAME } - - local function move_modes_to_specific_band(file_name) - 66 local modes = uci:get(file_name, 'wifi', 'modes') - 66 if modes == nil then - 33 return - end - 33 local modes_2ghz = {} - 33 local modes_5ghz = {} - 121 for _, mode in pairs(modes) do - 88 local mode_name = utils.split(mode, '_')[1] - 88 local mode_band = utils.split(mode, '_')[2] - 88 if mode_band == nil or mode_band == '2ghz' then - 77 table.insert(modes_2ghz, mode_name) - end - 88 if mode_band == nil or mode_band == '5ghz' then - 22 table.insert(modes_5ghz, mode_name) - end - end - 33 modes_2ghz = utils.tableLength(modes_2ghz) > 0 and modes_2ghz or {'manual'} - 33 modes_5ghz = utils.tableLength(modes_5ghz) > 0 and modes_5ghz or {'manual'} - 33 uci:set(file_name, '2ghz', 'lime-wifi-band') - 33 uci:set(file_name, '2ghz', 'modes', modes_2ghz) - 33 uci:set(file_name, '5ghz', 'lime-wifi-band') - 33 uci:set(file_name, '5ghz', 'modes', modes_5ghz) - 33 uci:delete(file_name, 'wifi', 'modes') - end - - - local function move_restof_to_specific_band(file_name) - 66 local options = uci:get_all(file_name, 'wifi') - 66 if options == nil then -*****0 return - end - 297 for key, value in pairs(options) do - 231 local band_name = utils.split(key, '_')[2] - 231 if band_name == '2ghz' or band_name == '5ghz' then - 33 uci:set(file_name, band_name, 'lime-wifi-band') - 33 local derived_key = utils.split(key, '_')[1] - 33 uci:set(file_name, band_name, derived_key, value) - 33 uci:delete(file_name, 'wifi', key) - end - end - end - - local function migrate_file(file_name) - 66 move_modes_to_specific_band(file_name) - 66 move_restof_to_specific_band(file_name) - 66 local changes = uci:changes() - 66 if utils.tableLength(changes) > 0 then - 66 uci:commit(file_name) - end - end - - 99 for _, file_name in pairs(uci_files) do - 66 migrate_file(file_name) - end - - 33 print('migrate-wifi-modes: migration finished, now run lime-config') - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/config.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh is modular but this doesn't mean parallel, modules are executed - --! sequencially, so we don't need to worry about transactionality and all other - --! stuff that affects parrallels database, at moment we don't need parallelism - --! as this is just some configuration stuff and is not performance critical. - - 616 local fs = require("nixio.fs") - 616 local libuci = require("uci") - 616 local nixio = require "nixio" - - 616 config = {} - - 1232 function config.log(text) - 33 nixio.syslog('info', '[config] ' .. text) - end - - 616 config.uci = nil - 616 config.hooksDir = "/etc/hotplug.d/lime-config" - - 1232 function config.get_uci_cursor() - 3567 if config.uci == nil then - 616 config.uci = libuci:cursor() - end - 3567 return config.uci - end - - 1232 function config.set_uci_cursor(cursor) - 2333 config.uci = cursor - end - - 616 config.uci = config.get_uci_cursor() - - 616 config.UCI_AUTOGEN_NAME = 'lime-autogen' - 616 config.UCI_NODE_NAME = 'lime-node' - 616 config.UCI_MAC_NAME = 'lime-000000000000' - 616 config.UCI_COMMUNITY_NAME = 'lime-community' - 616 config.UCI_DEFAULTS_NAME = 'lime-defaults' - 616 config.UCI_CONFIG_NAME = config.UCI_AUTOGEN_NAME - - 1232 function config.get_config_path() - 133 return config.uci:get_confdir() .. '/' .. config.UCI_CONFIG_NAME - end - - 1232 function config.reset_node_config() - 11 config.initialize_config_file(config.UCI_NODE_NAME) - end - - 1232 function config.initialize_config_file(config_name) - 11 local lime_path = config.uci:get_confdir() .. "/" .. config_name - 11 os.execute(string.format('cp /usr/share/lime/configs/%s %s', config_name, lime_path)) - end - - 1232 function config.get(sectionname, option, fallback) - 1418 local limeconf = config.uci:get(config.UCI_CONFIG_NAME, sectionname, option) - 1418 if limeconf then return limeconf end - - 205 if ( fallback ~= nil ) then - 194 config.log("Use fallback value for "..sectionname.."."..option..": "..tostring(fallback)) - 194 return fallback - else - 11 config.log("WARNING: Attempt to access undeclared default for: "..sectionname.."."..option) - 11 config.log(debug.traceback()) - 11 return nil - end - end - - --! Execute +callback+ for each config of type +configtype+ found in - --! +/etc/config/lime-autogen+. - 1232 function config.foreach(configtype, callback) - 219 return config.uci:foreach(config.UCI_CONFIG_NAME, configtype, callback) - end - - 1232 function config.get_all(sectionname) - 379 return config.uci:get_all(config.UCI_CONFIG_NAME, sectionname) - end - - 1232 function config.get_bool(sectionname, option, default) - 233 local val = config.get(sectionname, option, default) - 233 return (val and ((val == '1') or (val == 'on') or (val == 'true') or (val == 'enabled') or (val == 1) or (val == true))) - end - - 616 config.batched = false - - 1232 function config.init_batch() - 17 config.batched = true - end - - 1232 function config.set(...) - 981 local aty = type(arg[3]) - 981 if (aty ~= "nil" and aty ~= "string" and aty ~= "table") then - 44 arg[3] = tostring(arg[3]) - end - 981 config.uci:set(config.UCI_CONFIG_NAME, unpack(arg)) - 981 if(not config.batched) then config.uci:save(config.UCI_CONFIG_NAME) end - end - - 1232 function config.delete(...) - 28 config.uci:delete(config.UCI_CONFIG_NAME, unpack(arg)) - 28 if(not config.batched) then config.uci:save(config.UCI_CONFIG_NAME) end - end - - 1232 function config.end_batch() - 17 if(config.batched) then - 17 config.uci:save(config.UCI_CONFIG_NAME) - 17 config.batched = false - end - end - - 1232 function config.autogenerable(section_name) - 45 return ( (not config.get_all(section_name)) or config.get_bool(section_name, "autogenerated") ) - end - - - --! Merge two uci files. If an option exists in both files the value of high_prio is selected - 1232 function config.uci_merge_files(high_prio, low_prio, output) - 392 local uci = config.get_uci_cursor() - - 392 local high_pt = uci:get_all(high_prio) - 392 local low_pt = uci:get_all(low_prio) - - --! populate high_prio with low_prio values that are not in high_prio - 779 for section_name, section in pairs(low_pt) do - 387 local high_section = high_pt[section_name] - 387 if high_section ~= nil then - --! copy only some attributes - 1601 for option_name, option in pairs(section) do - --! if the options starts with a dot it is not a real options it is an attribute - --! like .name, .type, .anonymous and .index - 1361 if option_name[1] ~= '.' and high_section[option_name] == nil then - 186 high_section[option_name] = option - end - end - else - 147 high_pt[section_name] = section - end - end - - --! populate output from high_prio using uci - 1001 for section_name, section in pairs(high_pt) do - 609 local section_type = section['.type'] - 609 uci:set(output, section_name, section_type) - 4824 for option_name, option in pairs(section) do - --! if the options starts with a dot it is not a real options it is an attribute - --! like .name, .type, .anonymous and .index - 4215 if option_name[1] ~= '.' then - 4215 local otype = type(option) - 4215 if (otype ~= "nil" and otype ~= "string" and otype ~= "table") then - 1218 option = tostring(option) - end - 4215 uci:set(output, section_name, option_name, option) - end - end - end - 392 uci:commit(output) - end - - 1232 function config.uci_autogen() - --! start clearing the config - 127 local f = io.open(config.get_config_path(), "w") - 127 f:write('') - 127 f:close() - - --! clean uci cache - 127 local uci = config.get_uci_cursor() - 127 uci:load(config.UCI_AUTOGEN_NAME) - - 635 for _, cfg_name in pairs({config.UCI_DEFAULTS_NAME, config.UCI_COMMUNITY_NAME, config.UCI_MAC_NAME, config.UCI_NODE_NAME}) do - 508 local cfg = io.open(config.uci:get_confdir() .. '/' .. cfg_name) - 508 if cfg ~= nil then - 381 config.uci_merge_files(cfg_name, config.UCI_AUTOGEN_NAME, config.UCI_AUTOGEN_NAME) - end - end - 127 uci:load(config.UCI_AUTOGEN_NAME) - end - - --! commit all uci changes not yet commited - 1232 function config.uci_commit_all() - 50 local uci = config.get_uci_cursor() - 83 for k, _ in pairs(uci:changes()) do - 33 assert(uci:commit(k)) - end - end - - 1232 function config.main() - --! Get mac address and set mac-based configuration file name - 6 local network = require("lime.network") - 6 local utils = require("lime.utils") - - 6 config.UCI_MAC_NAME = "lime-" .. table.concat(network.primary_mac(),"") - 6 print("Mac-config: " .. config.UCI_MAC_NAME) - - --! Populate the default template configs if lime-node and lime-community - --! are not found in /etc/config - 18 for _, cfg_name in pairs({config.UCI_COMMUNITY_NAME, config.UCI_NODE_NAME}) do - 12 local lime_path = config.uci:get_confdir() .. "/" .. cfg_name - 12 local cf = io.open(lime_path) - 12 if not cf then -*****0 config.initialize_config_file(cfg_name) - else - 12 cf:close() - end - end - 6 config.uci_autogen() - - 6 local modules_name = { "hardware_detection", "wireless", "network", "firewall", "system", - 6 "generic_config" } - - 6 if utils.isModuleAvailable("lime.wifi_unstuck_wa") then - 6 table.insert(modules_name, "wifi_unstuck_wa") - end - - 6 local modules = {} - - 48 for i, name in pairs(modules_name) do modules[i] = require("lime."..name) end - 48 for _,module in pairs(modules) do - 42 xpcall(module.clean, function(errmsg) print(errmsg) ; print(debug.traceback()) end) - end - - 48 for _,module in pairs(modules) do - 42 xpcall(module.configure, function(errmsg) print(errmsg) ; print(debug.traceback()) end) - end - - 6 for hook in fs.dir(config.hooksDir) do -*****0 local hookCmd = config.hooksDir.."/"..hook.." after" -*****0 print("executed hook:", hookCmd, os.execute(hookCmd)) - end - - 6 local cfgpath = config.get_config_path() - --! flush all config changes - 6 local uci = config.get_uci_cursor() - 6 uci:commit(config.UCI_CONFIG_NAME) - 6 local autogen_content = utils.read_file(cfgpath) or '' - 6 notice_message = ('# DO NOT EDIT THIS FILE!. This is autogenerated by lime-config!\n' .. - 6 '# Instead please edit /etc/config/lime-node and/or /etc/config/lime-community files\n' .. - 6 '# and then regenerate this file executing lime-config\n\n') - 6 utils.write_file(cfgpath, notice_message .. autogen_content) - end - - 616 return config - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/firewall.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2020 Asociación Civil Altermundi - --! Copyright (C) 2020 Gioacchino Mazzurco - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - - 6 local fs = require("nixio.fs") - 6 local utils = require("lime.utils") - 6 local config = require("lime.config") - - 6 firewall = {} - - 12 function firewall.clean() - --! There could be things to cleanup here, but we don't do it as it would - --! interfere with rules generated by network protocols, deleting them too. - --! So better we do nothing here. - end - - 12 function firewall.configure() - 6 local uci = config:get_uci_cursor() - 6 local lanIfs = {} - 12 uci:foreach("firewall", "defaults", - function(section) -*****0 uci:set("firewall", section[".name"], "input", "ACCEPT") -*****0 uci:set("firewall", section[".name"], "output", "ACCEPT") -*****0 uci:set("firewall", section[".name"], "forward", "ACCEPT") - end - ) - - 12 uci:foreach("network", "interface", - function(section) - 198 if "lan" == section[".name"] or - 192 "lm_" == string.sub(section[".name"], 1, 3) and - 168 "_if" == string.sub(section[".name"], -3) then - 156 table.insert(lanIfs, section[".name"]) - end - end - ) - - 12 uci:foreach("firewall", "zone", - function(section) -*****0 if uci:get("firewall", section[".name"], "name") == "lan" then -*****0 uci:set("firewall", section[".name"], "input", "ACCEPT") -*****0 uci:set("firewall", section[".name"], "output", "ACCEPT") -*****0 uci:set("firewall", section[".name"], "forward", "ACCEPT") -*****0 uci:set("firewall", section[".name"], "mtu_fix", "1") -*****0 uci:set("firewall", section[".name"], "network", lanIfs) - end - end - ) - end - - 6 return firewall - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/generic_config.lua -============================================================================== - #!/usr/bin/lua - - 53 local config = require("lime.config") - 53 local utils = require("lime.utils") - - 53 gen_cfg = {} - - 53 gen_cfg.ASSET_BASE_DIR = '/etc/lime-assets/' - 53 gen_cfg.NODE_ASSET_DIRNAME = 'node/' - 53 gen_cfg.COMMUNITY_ASSET_DIRNAME = 'community/' - 53 gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE = '/etc/.cfg_first_boot_already_run' - 53 gen_cfg.RUN_ASSET_AT_FIRSTBOOT = 'ATFIRSTBOOT' - 53 gen_cfg.RUN_ASSET_AT_CONFIG = 'ATCONFIG' - - 106 function gen_cfg.clean() - -- nothing to clean, but needs to be declared to comply with the API - end - - 106 function gen_cfg.configure() - 6 gen_cfg.do_generic_uci_configs() - 6 gen_cfg.do_copy_assets() - 6 if not utils.file_exists(gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE) then - 6 gen_cfg.do_run_assets(gen_cfg.RUN_ASSET_AT_FIRSTBOOT) - 6 utils.write_file(gen_cfg.CONFIG_FIRST_BOOT_SIGNAL_FILE, '') - end - 6 gen_cfg.do_run_assets(gen_cfg.RUN_ASSET_AT_CONFIG) - end - - - --! Generic UCI configuration from libremesh. Eg usage: - --! config generic_uci_config libremap - --! list uci_set "libremap.settings=libremap" - --! list uci_set "libremap.settings.community=our.libre.org" - --! list uci_set "libremap.settings.community_lat=-200.123" - --! list uci_set "libremap.settings.community_lon=500.9" - 106 function gen_cfg.do_generic_uci_configs() - 39 local uci = config.get_uci_cursor() - 39 local ok = true - 39 utils.log("Applying generic configs:") - 78 config.foreach("generic_uci_config", function(gen_uci_cfg) - 39 utils.log(" " .. gen_uci_cfg[".name"]) - 133 for _, v in pairs(gen_uci_cfg["uci_set"]) do - 94 if uci:set(v) ~= true then - 22 utils.log(" Error on generic config uci_set: %s", v) - 22 ok = false - end - end - end) - 39 config.uci_commit_all() - 39 utils.log("Done applying generic configs.") - 39 return ok - end - - 106 function gen_cfg.get_asset(asset) - 55 if (utils.stringStarts(asset, gen_cfg.NODE_ASSET_DIRNAME) or - 33 utils.stringStarts(asset, gen_cfg.COMMUNITY_ASSET_DIRNAME)) then - 44 local asset = gen_cfg.ASSET_BASE_DIR .. asset - 44 if utils.file_exists(asset) then - 33 return asset - end - end - end - - --! copy_asset copy an file from the assets directory into a specified path. - --! The node asset directories are /etc/lime-assets/node and /etc/lime-assets/community. - --! The community directory should contain the same files in all the community nodes. - --! - --! config copy_asset collectd - --! option asset 'community/collectd.conf' # or 'node/collectd.conf' or 'community/mynode_collectd.conf' - --! option dst '/etc/collectd.conf' - --! - - 106 function gen_cfg.do_copy_assets() - 28 local uci = config.get_uci_cursor() - 28 local ok = true - 28 utils.log("Copying assets:") - 56 config.foreach("copy_asset", function(copy_asset) - 22 local asset = copy_asset["asset"] - 22 utils.log(" %s (%s)", copy_asset[".name"], asset) - 22 local dst = copy_asset["dst"] - 22 local src = gen_cfg.get_asset(asset) - 22 if src ~= nil then - 11 local dst_dirname = dst:match("(.*/)") - 11 if not utils.file_exists(dst_dirname) then -*****0 os.execute("mkdir -p " .. utils.shell_quote(dst_dirname)) - end - - 11 src = utils.shell_quote(src) - 11 dst = utils.shell_quote(dst) - 11 os.execute('cp -dpf ' .. src .. ' ' .. dst) - else - 11 utils.log(" Error copying asset '%s': file not found.", asset) - 11 ok = false - end - end) - 28 utils.log("Done copying assets.") - 28 return ok - end - - --! Executes a file from the assets directory scheme explained in copy_asset. - --! - --! config run_asset dropbear - --! option asset 'community/dropbear.sh' - --! option when 'ATFIRSTBOOT' # ATFIRSTBOOT, ATCONFIG - --! - 106 function gen_cfg.do_run_assets(when) - 45 local uci = config.get_uci_cursor() - 45 local ok = true - 45 utils.log("Running assets on " .. when .. " :") - 90 config.foreach("run_asset", function(run_asset) - 33 local asset = run_asset["asset"] - 33 if run_asset["when"] == when then - 33 utils.log(" %s (%s)", run_asset[".name"], asset) - 33 local src = gen_cfg.get_asset(asset) - 33 if src ~= nil then - 22 local retval = os.execute("chmod +x " .. src .. "; " .. src) - 22 if retval ~= 0 then - 11 utils.log(" Warning: the asset '%s': returnen non zero status.", src) - 11 ok = false - end - else - 11 utils.log(" Error running asset '%s': file not found .", asset) - 11 ok = false - end - end - end) - 45 utils.log("Done running assets.") - 45 return ok - end - - - 53 return gen_cfg - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/hardware_detection.lua -============================================================================== - #!/usr/bin/lua - - 28 local fs = require("nixio.fs") - 28 local utils = require("lime.utils") - - - 28 local hardware_detection = {} - - 28 hardware_detection.sectionNamePrefix = "lm_hwd_" - 28 hardware_detection.search_paths = {"/usr/lib/lua/lime/hwd/*.lua"} - - --! Hardware detection module clean() - --! Call clean() from all installed submodules - 28 function hardware_detection.clean() - 18 for _,search_path in ipairs(hardware_detection.search_paths) do - 36 for hwd_module_path in fs.glob(search_path) do - 24 local module_name = "lime.hwd." .. fs.basename(hwd_module_path):sub(1,-5) - 24 if utils.isModuleAvailable(module_name) then - 24 require(module_name).clean() - end - end - end - end - - --! Hardware detection module configure() - --! Call detect_hardware() from all installed submodules - 28 function hardware_detection.configure() - 18 for _,search_path in ipairs(hardware_detection.search_paths) do - 36 for hwd_module_path in fs.glob(search_path) do - 24 local module_name = "lime.hwd." .. fs.basename(hwd_module_path):sub(1,-5) - 24 if utils.isModuleAvailable(module_name) then - 24 require(module_name).detect_hardware() - end - end - end - end - - - 28 return hardware_detection - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/mode/ap.lua -============================================================================== - #!/usr/bin/lua - - 6 local ap = {} - - 6 ap.wifi_mode="ap" - - 6 function ap.setup_radio(radio, args) - --! checks("table", "?table") - - 18 args["network"] = "lan" - 18 return wireless.createBaseWirelessIface(radio, ap.wifi_mode, nil, args) - end - - 6 return ap - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/mode/apname.lua -============================================================================== - #!/usr/bin/lua - - 6 local apname = {} - - 6 apname.wifi_mode="ap" - - 6 function apname.setup_radio(radio, args) - --! checks("table", "?table") - - 18 args["network"] = "lan" - 18 return wireless.createBaseWirelessIface(radio, apname.wifi_mode, "name", args) - end - - 6 return apname - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/mode/apup.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2024 Gioacchino Mazzurco - --! Copyright (C) 2024 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - 31 local wireless = require("lime.wireless") - - 31 local apup = {} - - 31 function apup.WIFI_MODE() -*****0 return "ap" - end - - 31 function apup.WIFI_MODE_SUFFIX() -*****0 return "up" - end - - 31 function apup.PEER_SUFFIX() - 331 return "peer" - end - - 31 function apup.setup_radio(radio, args) - --! checks("table", "?table") - -*****0 args["network"] = "lan" -*****0 args["apup"] = "1" -*****0 args["apup_peer_ifname_prefix"] = -*****0 wireless.calcIfname(radio[".name"], apup.PEER_SUFFIX(), "") - -*****0 return wireless.createBaseWirelessIface( -*****0 radio, apup.WIFI_MODE(), apup.WIFI_MODE_SUFFIX(), args ) - end - - --! TODO: port all modes to .WIFI_MODE() - 31 apup.wifi_mode="ap" - - 31 return apup - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/mode/client.lua -============================================================================== - #!/usr/bin/lua - - 11 local client = {} - - 11 client.wifi_mode="sta" - - 11 function client.setup_radio(radio, args) - --! checks("table", "?table") - 11 return wireless.createBaseWirelessIface(radio, client.wifi_mode, nil, args) - end - - 11 return client - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/mode/ieee80211s.lua -============================================================================== - #!/usr/bin/lua - - 17 local ieee80211s = {} - - 17 ieee80211s.wifi_mode="mesh" - - 17 function ieee80211s.setup_radio(radio, args) - --! checks("table", "?table") - - 73 return wireless.createBaseWirelessIface(radio, ieee80211s.wifi_mode, nil, args) - end - - 17 return ieee80211s - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/network.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2013-2024 Gioacchino Mazzurco - --! Copyright (C) 2023-2024 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - 236 network = {} - - 236 local ip = require("luci.ip") - 236 local fs = require("nixio.fs") - - 236 local config = require("lime.config") - 236 local utils = require("lime.utils") - - - 236 function network.PROTO_PARAM_SEPARATOR() return ":" end - 236 function network.PROTO_VLAN_SEPARATOR() return "_" end - 236 function network.LIME_UCI_IFNAME_PREFIX() return "lm_net_" end - - - 236 network.MTU_ETH = 1500 - 236 network.MTU_ETH_WITH_VLAN = network.MTU_ETH - 4 - - -- Deprecated use corresponding functions instead - 236 network.protoParamsSeparator=":" - 236 network.protoVlanSeparator="_" - 236 network.limeIfNamePrefix="lm_net_" - - - 472 function network.get_mac(ifname) - 11 local _, macaddr = next(network.get_own_macs(ifname)) - 11 return utils.split(macaddr, ":") - end - - --! Return a table of macs based on the interface globing filter - 472 function network.get_own_macs(interface_filter) - 110 if interface_filter == nil then - 11 interface_filter = '*' - end - - 110 local macs = {} - 110 local search_path = "/sys/class/net/" .. interface_filter .. "/address" - 678 for address_path in fs.glob(search_path) do - 568 mac = io.open(address_path):read("*l") - 568 macs[mac] = 1 - end - - 110 local result = {} - 678 for mac, _ in pairs(macs) do - 568 table.insert(result, mac) - end - 110 return result - end - - - 472 function network.assert_interface_exists(ifname) -*****0 assert( ifname ~= nil and ifname ~= "", -*****0 "network.primary_interface() could not determine ifname!" ) - -*****0 assert( fs.lstat("/sys/class/net/"..ifname), -*****0 "network.primary_interface() "..ifname.." doesn't exists!" ) - end - - 472 function network.primary_interface() - 218 local ifname = config.get("network", "primary_interface", "eth0") - 218 if ifname == "auto" then - 11 local board = utils.getBoardAsTable() - 11 ifname = board['network']['lan']['device'] - end - 218 network.assert_interface_exists(ifname) - 218 return ifname - end - - 472 function network.primary_mac() - 196 return network.get_mac(network.primary_interface()) - end - - 472 function network.generate_host(ipprefix, hexsuffix) - 164 local num = 0 - -- If it's a network prefix calculate offset to add - 164 if ipprefix:equal(ipprefix:network()) then - 164 local addr_len = ipprefix:is4() and 32 or ipprefix:is6() and 128 - 164 num = tonumber(hexsuffix,16) % 2^(addr_len - ipprefix:prefix()) - end - - 164 return ipprefix:add(num) - end - - 472 function network.primary_address(offset) - 82 local offset = offset or 0 - 82 local pm = network.primary_mac() - 82 local ipv4_template = config.get("network", "main_ipv4_address") - 82 local ipv6_template = config.get("network", "main_ipv6_address") - - 82 local ipv4_maskbits = ipv4_template:match("[^/]+/(%d+)") - 82 ipv4_template = ipv4_template:gsub("/%d-/","/") - 82 local ipv6_maskbits = ipv6_template:match("[^/]+/(%d+)") - 82 ipv6_template = ipv6_template:gsub("/%d-/","/") - - 82 ipv4_template = utils.applyMacTemplate10(ipv4_template, pm) - 82 ipv6_template = utils.applyMacTemplate16(ipv6_template, pm) - - 82 ipv4_template = utils.applyNetTemplate10(ipv4_template) - 82 ipv6_template = utils.applyNetTemplate16(ipv6_template) - - 82 local m4, m5, m6 = tonumber(pm[4], 16), tonumber(pm[5], 16), tonumber(pm[6], 16) - 82 local hexsuffix = utils.hex((m4 * 256*256 + m5 * 256 + m6) + offset) - 82 ipv4_template = network.generate_host(ip.IPv4(ipv4_template), hexsuffix) - 82 ipv6_template = network.generate_host(ip.IPv6(ipv6_template), hexsuffix) - - 82 ipv4_template:prefix(tonumber(ipv4_maskbits)) - 82 local mc = ipv4_template - --! Generated address is network address like 192.0.2.0/24 ? - 82 local invalid = ipv4_template:equal(mc:network()) and "NETWORK" - --! If anygw enabled, generated address is the one reserved for anygw like 192.0.2.1/24 ? - 82 if utils.isModuleAvailable("lime.proto.anygw") then - 22 local generalProtocols = config.get("network", "protocols") - 44 for _,protocol in pairs(generalProtocols) do - 22 if protocol == 'anygw' then -*****0 invalid = invalid or ipv4_template:equal(mc:minhost()) and "ANYGW" - break - end - end - end - --! Generated address is the broadcast address like 192.0.2.255/24 ? - 82 invalid = invalid or ipv4_template:equal(mc:broadcast()) and "BROADCAST" - 82 if invalid then - 22 ipv4_template = mc:maxhost() - 22 ipv4_template:prefix(tonumber(ipv4_maskbits)) - 44 utils.log("INVALID main_ipv4_address " ..tostring(mc).. " IDENTICAL TO RESERVED " - 22 ..invalid.. " ADDRESS. USING " ..tostring(ipv4_template)) - end - - 82 ipv6_template:prefix(tonumber(ipv6_maskbits)) - - 82 return ipv4_template, ipv6_template - end - - 472 function network.setup_rp_filter() - 17 local sysctl_file_path = "/etc/sysctl.conf"; - 17 local sysctl_options = ""; - 17 local sysctl_file = io.open(sysctl_file_path, "r"); - 34 while sysctl_file:read(0) do - 17 local sysctl_line = sysctl_file:read(); - 17 if not string.find(sysctl_line, ".rp_filter") then sysctl_options = sysctl_options .. sysctl_line .. "\n" end - end - 17 sysctl_file:close() - - 17 sysctl_options = sysctl_options .. "net.ipv4.conf.default.rp_filter=2\nnet.ipv4.conf.all.rp_filter=2\n"; - 17 sysctl_file = io.open(sysctl_file_path, "w"); - 17 if sysctl_file ~= nil then -*****0 sysctl_file:write(sysctl_options); -*****0 sysctl_file:close(); - end - end - - 472 function network.setup_dns() - 17 local cloudDomain = config.get("system", "domain") - 17 local resolvers = config.get("network", "resolvers") - - 17 local uci = config.get_uci_cursor() - 34 uci:foreach("dhcp", "dnsmasq", - function(s) -*****0 uci:set("dhcp", s[".name"], "domain", cloudDomain) -*****0 uci:set("dhcp", s[".name"], "local", "/"..cloudDomain.."/") -*****0 uci:set("dhcp", s[".name"], "expandhosts", "1") -*****0 uci:set("dhcp", s[".name"], "domainneeded", "1") - --! allow queries from non-local ips (i.e. from other clouds) -*****0 uci:set("dhcp", s[".name"], "localservice", "0") -*****0 uci:set("dhcp", s[".name"], "server", resolvers) -*****0 uci:set("dhcp", s[".name"], "confdir", "/etc/dnsmasq.d") - end - ) - 17 uci:save("dhcp") - - 17 fs.mkdir("/etc/dnsmasq.d") - end - - 472 function network.clean() - 6 utils.log("Clearing network config...") - - 6 local uci = config.get_uci_cursor() - - 6 uci:delete("network", "globals", "ula_prefix") - 6 uci:set("network", "wan", "proto", "none") - 6 uci:set("network", "wan6", "proto", "none") - - --! Delete sections generated by LiMe - local function delete_lime_section(s) - 30 if utils.stringStarts(s[".name"], network.limeIfNamePrefix) then -*****0 uci:delete("network", s[".name"]) - end - end - 6 uci:foreach("network", "interface", delete_lime_section) - 6 uci:foreach("network", "device", delete_lime_section) - 6 uci:foreach("network", "rule", delete_lime_section) - 6 uci:foreach("network", "route", delete_lime_section) - 6 uci:foreach("network", "rule6", delete_lime_section) - 6 uci:foreach("network", "route6", delete_lime_section) - - 6 uci:save("network") - - 6 if config.get_bool("network", "use_odhcpd", false) then -*****0 utils.log("Use odhcpd as dhcp server") -*****0 uci:set("dhcp", "odchpd", "maindhcp", 1) -*****0 os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd enable") - else - 6 utils.log("Disabling odhcpd") - 6 uci:set("dhcp", "odchpd", "maindhcp", 0) - 6 os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd disable") - end - - 6 utils.log("Cleaning dnsmasq") - 6 uci:foreach("dhcp", "dnsmasq", function(s) uci:delete("dhcp", s[".name"], "server") end) - 6 uci:save("dhcp") - - 6 utils.log("Disabling 6relayd...") - 6 fs.writefile("/etc/config/6relayd", "") - end - - 472 function network._get_lower(dev) -*****0 local lower_if_path = utils.unsafe_shell("ls /sys/class/net/" .. dev .. "/ | grep ^lower") -*****0 local lower_if_table = utils.split(lower_if_path, "_") -*****0 local lower_if = lower_if_table[#lower_if_table] -*****0 return lower_if and lower_if:gsub("\n", "") - end - - 472 function network._is_dsa_conduit(dev) - 111 return "reg" == fs.stat("/sys/class/net/" .. dev .. "/dsa/tagging", "type") - end - - - 472 function network.scandevices(specificIfaces) - 6 local devices = {} - 6 local wireless = require("lime.wireless") - 6 local cpu_ports = {} - 6 local board = utils.getBoardAsTable() - - 6 function dev_parser(dev) - 111 if dev == nil then -*****0 utils.log("network.scandevices.dev_parser got nil device") -*****0 return - end - - --! Avoid configuration on DSA conduit interfaces. - --! See also: - --! https://www.kernel.org/doc/html/latest/networking/dsa/dsa.html#common-pitfalls-using-dsa-setups - 111 if network._is_dsa_conduit(dev) then -*****0 utils.log( "network.scandevices.dev_parser ignored DSA conduit " .. -*****0 "device %s", dev ) -*****0 return - end - - --! Filter out ethernet ports connected to switch in a swconfig device. - 111 for cpu_port,_ in pairs(cpu_ports) do -*****0 if cpu_port == dev then -*****0 utils.log( "network.scandevices.dev_parser ignored ethernet " .. -*****0 "device %s connected to internal switch", dev ) -*****0 return - end - end - - 111 if dev:match("^eth%d+$") then - --! We only get here with devices not listed in board.json, e.g - --! pluggable ethernet dongles. -*****0 utils.log( "network.scandevices.dev_parser found plain Ethernet " .. -*****0 "device %s", dev ) - 111 elseif dev:match("^wlan%d+"..wireless.WIFI_MODE_SEPARATOR().."%w+$") then - 108 utils.log( "network.scandevices.dev_parser found WiFi device %s", - 108 dev ) - 57 elseif specificIfaces[dev] then -*****0 utils.log( "network.scandevices.dev_parser found device %s that " .. - "matches the config net section %s", dev, -*****0 specificIfaces[dev][".name"]) - else - 57 return - end - - 54 local is_dsa = utils.is_dsa(dev) - 54 devices[dev] = devices[dev] or {} - 54 devices[dev]["dsa"] = is_dsa - end - - 6 function owrt_ifname_parser(section) - 54 local ifn = section["ifname"] - 54 if ( type(ifn) == "string" ) then - 108 utils.log( "network.scandevices.owrt_ifname_parser found ifname %s", - 54 ifn ) - 54 dev_parser(ifn) - end - end - - 6 function board_port_parser(dev) - 12 local is_dsa = utils.is_dsa(dev) - 12 devices[dev] = devices[dev] or {} - 12 devices[dev]["dsa"] = is_dsa - 12 if is_dsa then -*****0 utils.log( "network.scandevices found DSA-port %s in board.json", -*****0 dev ) - else - 12 utils.log( "network.scandevices found device %s in board.json", dev ) - end - end - - --! Collect switch facing ethernet ports for swconfig devices from board.json - 6 for switch, switch_table in pairs(board["switch"] or {}) do -*****0 for _,port_table in pairs(switch_table["ports"] or {}) do -*****0 local dev = port_table["device"] -*****0 if dev then -*****0 cpu_ports[dev] = true - end - end - end - - --! Collect dsa ports and usable ethernet and vlan devices from board.json - 18 for role, role_table in pairs(board["network"] or {}) do - --! "ports" and "device" fields may be specified at the same time. - --! In this case, "ports" must be used. - 12 local ports = role_table["ports"] - 12 if ports == nil then - 6 ports = { role_table["device"] } - end - 12 local protocol = role_table["protocol"] - --! Protocol can be dhcp, static, pppoe, ncm, qmi, mbim. - --! Ethernet interfaces usually have protocol "dhcp" or "static", - --! depending on their role. - 12 if protocol == "dhcp" or protocol == "static" then - 24 for _,port in pairs(ports) do - 12 board_port_parser(port) - end - end - end - - --! Scrape from uci wireless - 6 local uci = config.get_uci_cursor() - 6 uci:foreach("wireless", "wifi-iface", owrt_ifname_parser) - - --! Scrape from /sys/class/net/ - 6 local stdOut = io.popen("ls -1 /sys/class/net/") - 63 for dev in stdOut:lines() do dev_parser(dev) end - 6 stdOut:close() - - 6 return devices - end - - 472 function network.configure() - 17 local specificIfaces = {} - - 34 config.foreach("net", function(iface) - 6 if iface["linux_name"] then - 6 specificIfaces[iface["linux_name"]] = iface - end - end) - - 17 local fisDevs = network.scandevices(specificIfaces) - - 17 network.setup_rp_filter() - - 17 network.setup_dns() - - 17 local generalProtocols = config.get("network", "protocols") - 88 for _,protocol in pairs(generalProtocols) do - 71 local protoModule = "lime.proto."..utils.split(protocol,":")[1] - 71 if utils.isModuleAvailable(protoModule) then - 59 local proto = require(protoModule) - 177 xpcall(function() proto.configure(utils.split(protocol, network.protoParamsSeparator)) end, - 118 function(errmsg) print(errmsg) ; print(debug.traceback()) end) - end - end - - --! For each scanned fisical device, if there is a specific config apply that one otherwise apply general config - 94 for device,flags in pairs(fisDevs) do - 77 local owrtIf = specificIfaces[device] - 77 local deviceProtos = generalProtocols - 77 if owrtIf then - 6 deviceProtos = owrtIf["protocols"] or {"manual"} - 6 flags["specific"] = true - 6 flags["_specific_section"] = owrtIf - end - - 748 for _,protoParams in pairs(deviceProtos) do - 671 local args = utils.split(protoParams, network.protoParamsSeparator) - 671 local protoName = args[1] - 671 if protoName == "manual" then break end -- If manual is specified do not configure interface - 671 local protoModule = "lime.proto."..protoName - 671 local needsConfig = utils.isModuleAvailable(protoModule) - 671 if protoName ~= 'lan' and not flags["specific"] then - --! Work around issue 1121. Do not configure any other - --! protocols than lime.proto.lan on dsa devices unless there - --! is a config net section for the device. - 540 needsConfig = needsConfig and not utils.is_dsa(device) - end - 671 if needsConfig then - 1163 for k,v in pairs(flags) do args[k] = v end - 539 local proto = require(protoModule) - 1617 xpcall(function() proto.configure(args) ; proto.setup_interface(device, args) end, - 1108 function(errmsg) print(errmsg) ; print(debug.traceback()) end) - end - end - end - end - - 472 function network.sanitizeIfaceName(ifName) - 528 return network.limeIfNamePrefix..ifName:gsub("[^%w_]", "_") - end - - -- Creates a network Interface with static protocol - -- ipAddr can be IPv4 or IPv6 - -- the function can be called twice to set both IPv4 and IPv6 - 472 function network.createStaticIface(linuxBaseIfname, openwrtNameSuffix, ipAddr, gwAddr) -*****0 local openwrtNameSuffix = openwrtNameSuffix or "" -*****0 local owrtInterfaceName = network.sanitizeIfaceName(linuxBaseIfname) .. openwrtNameSuffix -*****0 local uci = config.get_uci_cursor() - -*****0 uci:set("network", owrtInterfaceName, "interface") -*****0 uci:set("network", owrtInterfaceName, "proto", "static") -*****0 uci:set("network", owrtInterfaceName, "auto", "1") -*****0 uci:set("network", owrtInterfaceName, "ifname", linuxBaseIfname) - -*****0 local addr = luci.ip.new(ipAddr) -*****0 local host = addr:host():string() - -*****0 if addr:is4() then -*****0 local mask = addr:mask():string() -*****0 uci:set("network", owrtInterfaceName, "ipaddr", host) -*****0 uci:set("network", owrtInterfaceName, "netmask", mask) -*****0 if gwAddr then -*****0 uci:set("network", owrtInterfaceName, "gateway", gwAddr) - end -*****0 elseif addr:is6() then -*****0 uci:set("network", owrtInterfaceName, "ip6addr", addr:string()) -*****0 if gwAddr then -*****0 uci:set("network", owrtInterfaceName, "ip6gw", gwAddr) - end - else -*****0 uci:delete("network", owrtInterfaceName, "interface") - end - -*****0 uci:save("network") - end - - 472 function network.createVlanIface(linuxBaseIfname, vid, openwrtNameSuffix, vlanProtocol) - 213 vlanProtocol = vlanProtocol or "8021ad" - 213 openwrtNameSuffix = openwrtNameSuffix or "" - 213 vid = tonumber(vid) - - --! sanitize passed linuxBaseIfName for constructing uci section name - --! because only alphanumeric and underscores are allowed - 213 local owrtInterfaceName = network.sanitizeIfaceName(linuxBaseIfname) - 213 local owrtDeviceName = owrtInterfaceName - 213 local linux802adIfName = linuxBaseIfname - - 213 local uci = config.get_uci_cursor() - - 213 owrtInterfaceName = owrtInterfaceName..openwrtNameSuffix.."_if" - - 213 if vid ~= 0 then - 196 local vlanId = tostring(vid) - --! sanitize passed linuxBaseIfName for constructing uci section name - --! because only alphanumeric and underscores are allowed - 196 owrtDeviceName = network.sanitizeIfaceName(linuxBaseIfname)..openwrtNameSuffix.."_dev" - - 196 if linuxBaseIfname:match("^wlan") then - 119 linuxBaseIfname = "@"..network.sanitizeIfaceName(linuxBaseIfname) - end - - --! Do not use . as separator as this will make netifd create an 802.1q interface anyway - --! and sanitize linuxBaseIfName because it can contain dots as well (i.e. switch ports) - 196 linux802adIfName = linux802adIfName:gsub("[^%w-]", "-")..network.protoVlanSeparator..vlanId - - 196 uci:set("network", owrtDeviceName, "device") - 196 uci:set("network", owrtDeviceName, "type", vlanProtocol) - 196 uci:set("network", owrtDeviceName, "name", linux802adIfName) - --! This is ifname also on current OpenWrt - 196 uci:set("network", owrtDeviceName, "ifname", linuxBaseIfname) - 196 uci:set("network", owrtDeviceName, "vid", vlanId) - end - - 213 uci:set("network", owrtInterfaceName, "interface") - 213 local proto = "none" - 213 if vid == 0 then - 17 proto = "static" - end - 213 uci:set("network", owrtInterfaceName, "proto", proto) - 213 uci:set("network", owrtInterfaceName, "auto", "1") - - --! In case of wifi interface not using vlan (vid == 0) avoid to set - --! ifname in network because it is already set in wireless, because - --! setting ifname on both places cause a netifd race condition - 213 if vid ~= 0 or not linux802adIfName:match("^wlan") then - 213 uci:set("network", owrtInterfaceName, "device", linux802adIfName) - end - - 213 uci:save("network") - - 213 return owrtInterfaceName, linux802adIfName, owrtDeviceName - end - - 472 function network.createMacvlanIface(baseIfname, linuxName, argsDev, argsIf) - --! baseIfname can be a linux interface name like eth0 or an openwrt - --! interface name like @lan of the base interface; - --! linuxName is the linux name of the new interface; - --! argsDev optional additional arguments for device like - --! { macaddr="aa:aa:aa:aa:aa:aa", mode="vepa" }; - --! argsIf optional additional arguments for ifname like - --! { proto="static", ip6addr="2001:db8::1/64" } - --! - --! Although this function is defined here lime-system may not depend - --! on macvlan if it doesn't use this function directly. Instead a - --! lime.proto which want to use macvlan so this function should depend - --! on its own on kmod-macvlan as needed. - -*****0 argsDev = argsDev or {} -*****0 argsIf = argsIf or {} - -*****0 local owrtDeviceName = network.limeIfNamePrefix..baseIfname.."_"..linuxName.."_dev" -*****0 local owrtInterfaceName = network.limeIfNamePrefix..baseIfname.."_"..linuxName.."_if" - --! sanitize uci sections name -*****0 owrtDeviceName = owrtDeviceName:gsub("[^%w_]", "_") -*****0 owrtInterfaceName = owrtInterfaceName:gsub("[^%w_]", "_") - -*****0 local uci = config.get_uci_cursor() - -*****0 uci:set("network", owrtDeviceName, "device") -*****0 uci:set("network", owrtDeviceName, "type", "macvlan") -*****0 uci:set("network", owrtDeviceName, "name", linuxName) - --! This is ifname also on current OpenWrt -*****0 uci:set("network", owrtDeviceName, "ifname", baseIfname) -*****0 for k,v in pairs(argsDev) do -*****0 uci:set("network", owrtDeviceName, k, v) - end - -*****0 uci:set("network", owrtInterfaceName, "interface") -*****0 uci:set("network", owrtInterfaceName, "proto", "none") -*****0 uci:set("network", owrtInterfaceName, "device", linuxName) -*****0 uci:set("network", owrtInterfaceName, "auto", "1") -*****0 for k,v in pairs(argsIf) do -*****0 uci:set("network", owrtInterfaceName, k, v) - end - -*****0 uci:save("network") - -*****0 return owrtInterfaceName, linuxName, owrtDeviceName - end - - --! Create a static interface at runtime via ubus - 472 function network.createStatic(linuxBaseIfname) -*****0 local ipv4, ipv6 = network.primary_address() -*****0 local ubusIfaceName = network.sanitizeIfaceName( -*****0 network.LIME_UCI_IFNAME_PREFIX()..linuxBaseIfname.."_static") -*****0 local ifaceConf = { - name = ubusIfaceName, - proto = "static", - auto = "1", - ifname = linuxBaseIfname, - ipaddr = ipv4:host():string(), -*****0 netmask = "255.255.255.255" - } - -*****0 local libubus = require("ubus") -*****0 local ubus = libubus.connect() -*****0 ubus:call('network', 'add_dynamic', ifaceConf) -*****0 ubus:call('network.interface.'..ifaceConf.name, 'up', {}) - - --! TODO: As of today ubus silently fails to properly setup the interface, - --! subsequent status query return NO_DEVICE error - --! ubus -v call network.interface.lm_net_lm_net_wlan0_peer1_static status - --! { - --! "up": false, - --! "pending": false, - --! "available": false, - --! "autostart": true, - --! "dynamic": true, - --! "proto": "static", - --! "data": { - --! - --! }, - --! "errors": [ - --! { - --! "subsystem": "interface", - --! "code": "NO_DEVICE" - --! } - --! ] - --! } - --! - --! ATM work around the problem configuring IP addresses via ip command - -*****0 utils.unsafe_shell("ip link set up dev "..ifaceConf.ifname) -*****0 utils.unsafe_shell("ip address add "..ifaceConf.ipaddr.."/32 dev "..ifaceConf.ifname) - -*****0 return ifaceConf.name - end - - --! Create a vlan at runtime via ubus - 472 function network.createVlan(linuxBaseIfname, vid, vlanProtocol) -*****0 local vlanConf = { - name = linuxBaseIfname .. network.PROTO_VLAN_SEPARATOR() .. vid, - type = vlanProtocol or "8021ad", - ifname = linuxBaseIfname, -*****0 vid = vid - } - -*****0 utils.log("lime.network.createVlan(%s, ...)", linuxBaseIfname) -*****0 utils.dumptable(vlanConf) - -*****0 local libubus = require("ubus") -*****0 local ubus = libubus.connect() -*****0 ubus:call('network', 'add_dynamic_device', vlanConf) - - --! TODO: as of today ubus silently fails to properly creating a device - --! dinamycally work around it by using ip command instead -*****0 utils.unsafe_shell("ip link add name "..vlanConf.name.." link "..vlanConf.ifname.." type vlan proto 802.1ad id "..vlanConf.vid) - -*****0 return vlanConf.name - end - - --! Run protocols at runtime on top of linux network devices - -- TODO: probably some code between here and configure might be deduplicaded - 472 function network.runProtocols(linuxBaseIfname) -*****0 utils.log("lime.network.runProtocols(%s, ...)", linuxBaseIfname) -*****0 local protoConfs = config.get("network", "protocols") -*****0 for _,protoConf in pairs(protoConfs) do -*****0 local args = utils.split(protoConf, network.PROTO_PARAM_SEPARATOR()) -*****0 local protoModule = "lime.proto."..args[1] -*****0 if utils.isModuleAvailable(protoModule) then -*****0 local proto = require(protoModule) -*****0 xpcall(function() proto.runOnDevice(linuxBaseIfname, args) end, -*****0 function(errmsg) print(errmsg) ; print(debug.traceback()) end) - end - end - end - - 236 return network - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/proto/ieee80211s.lua -============================================================================== - #!/usr/bin/lua - - 6 local config = require("lime.config") - 6 local ieee80211s_mode = require("lime.mode.ieee80211s") - - 6 local ieee80211s = {} - - 6 function ieee80211s.configure(args) - end - - 6 function ieee80211s.setup_interface(ifname, args) - 66 if ifname:match("^wlan%d+."..ieee80211s_mode.wifi_mode) then - 18 local uci = config.get_uci_cursor() - - --! sanitize passed ifname for constructing uci section name - --! because only alphanumeric and underscores are allowed - 18 local networkInterfaceName = network.limeIfNamePrefix..ifname:gsub("[^%w_]", "_") - - 18 uci:set("network", networkInterfaceName, "interface") - 18 uci:set("network", networkInterfaceName, "proto", "none") - 18 uci:set("network", networkInterfaceName, "mtu", "1536") - 18 uci:set("network", networkInterfaceName, "auto", "1") - - 18 uci:save("network") - end - end - - 6 function ieee80211s.runOnDevice(linuxDev, args) end - - 6 return ieee80211s - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/proto/lan.lua -============================================================================== - #!/usr/bin/lua - - 28 lan = {} - - 28 local network = require("lime.network") - 28 local config = require("lime.config") - 28 local utils = require("lime.utils") - - 28 lan.configured = false - - --! Find a device section in network with - --! option name 'br-lan' - --! option type 'bridge' - local function find_br_lan(uci) - local br_lan_section = nil - 124 uci:foreach("network", "device", - function(s) - 40 if br_lan_section then return end - 40 local dev_type = uci:get("network", s[".name"], "type") - 40 local dev_name = uci:get("network", s[".name"], "name") - 40 if not (dev_type == 'bridge') then return end - 40 if not (dev_name == 'br-lan') then return end - 40 br_lan_section = s[".name"] - end - ) - 62 return br_lan_section - end - - 56 function lan.configure(args) - 88 if lan.configured then return end - 17 lan.configured = true - - 17 local ipv4, ipv6 = network.primary_address() - 17 local uci = config.get_uci_cursor() - 17 uci:set("network", "lan", "interface") - 17 uci:set("network", "lan", "ip6addr", ipv6:string()) - 17 uci:set("network", "lan", "ipaddr", ipv4:host():string()) - 17 uci:set("network", "lan", "netmask", ipv4:mask():string()) - 17 uci:set("network", "lan", "proto", "static") - 17 uci:set("network", "lan", "mtu", "1500") - 17 local br_lan_section = find_br_lan(uci) - 17 if br_lan_section then uci:delete("network", br_lan_section, "ports") end - 17 uci:save("network") - - -- disable bat0 on alfred if batadv not enabled - 17 if utils.is_installed("alfred") then -*****0 local is_batadv_enabled = false -*****0 local generalProtocols = config.get("network", "protocols") -*****0 for _,protocol in pairs(generalProtocols) do -*****0 local protoModule = "lime.proto."..utils.split(protocol,":")[1] -*****0 if protoModule == "lime.proto.batadv" then -*****0 is_batadv_enabled = true - break - end - end -*****0 if not is_batadv_enabled then -*****0 uci:set("alfred", "alfred", "batmanif", "none") -*****0 uci:save("alfred") - end - end - end - - 56 function lan.setup_interface(ifname, args) - 99 if ifname:match("^wlan") then return end - 45 if ifname:match(network.protoVlanSeparator.."%d+$") then return end - - 45 local uci = config.get_uci_cursor() - 45 local bridgedIfs = {} - 45 local br_lan_section = find_br_lan(uci) - 45 if not br_lan_section then return end - 23 local oldIfs = uci:get("network", br_lan_section, "ports") or {} - -- it should be a table, it was a string in old OpenWrt releases - 23 if type(oldIfs) == "string" then oldIfs = utils.split(oldIfs, " ") end - 29 for _,iface in pairs(oldIfs) do - 6 if iface ~= ifname then - 6 table.insert(bridgedIfs, iface) - end - end - 23 table.insert(bridgedIfs, ifname) - 23 uci:set("network", br_lan_section, "ports", bridgedIfs) - 23 uci:save("network") - end - - 56 function lan.bgp_conf(templateVarsIPv4, templateVarsIPv6) - local base_conf = [[ - protocol direct { - interface "br-lan"; - } -*****0 ]] -*****0 return base_conf - end - - 28 function lan.runOnDevice(linuxDev, args) end - - 28 return lan - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/system.lua -============================================================================== - #!/usr/bin/lua - - 103 local fs = require("nixio.fs") - - 103 local config = require("lime.config") - 103 local network = require("lime.network") - 103 local utils = require("lime.utils") - - - 103 system = {} - - 206 function system.get_hostname() - 42 local system_hostname = utils.applyMacTemplate16(config.get("system", "hostname"), network.primary_mac()) - 42 return utils.sanitize_hostname(system_hostname) - end - - 206 function system.set_hostname() - 6 local hostname = system.get_hostname() - 6 local uci = config.get_uci_cursor() - 6 uci:foreach("system", "system", function(s) uci:set("system", s[".name"], "hostname", hostname) end) - 6 uci:save("system") - end - - 206 function system.setup_root_password() - - 39 local policy = config.get("system", "root_password_policy") - - 39 if policy == "DO_NOTHING" then - --! nothing... - 22 elseif policy == "SET_SECRET" then - 11 local secret = config.get("system", "root_password_secret") - 11 local current_secret = utils.get_root_secret() - 11 if current_secret == nil then -*****0 error("Can't get root password") - end - 11 if current_secret ~= secret then - 11 utils.set_root_secret(secret) - end - 11 elseif policy == "RANDOM" then - --! Not having a password can be specified by the secret being empty - --! or also being '*' or '!'. So we asume there is no password set - --! in both cases. - 11 if #utils.get_root_secret() <= 1 then - 11 utils.set_password('root', utils.random_string(30)) - end - else -*****0 error('Invalid root_password_policy: ' .. policy) - end - end - - 206 function system.clean() - -- nothing to clean - end - - 206 function system.configure() - 6 utils.log("Configuring system...") - 6 system.set_hostname() - - 6 system.setup_root_password() - - 6 utils.log("Let uhttpd listen on IPv4/IPv6") - 6 local uci = config.get_uci_cursor() - 6 uci:set("uhttpd", "main", "listen_http", "80") - 6 uci:set("uhttpd", "main", "listen_https", "443") - 6 uci:set("uhttpd", "main", "max_requests", "6") - 6 uci:set("uhttpd", "main", "script_timeout", "15") - 6 uci:save("uhttpd") - end - - 206 function system.apply() - -- apply hostname - local hostname -*****0 local uci = config.get_uci_cursor() -*****0 uci:foreach("system", "system", function(s) -*****0 hostname = uci:get("system", s[".name"], "hostname") -- FIXME Doesn't we already have hostaname in s["hostname"] without executing the get ? - end) -*****0 fs.writefile("/proc/sys/kernel/hostname", hostname) - - -- apply uhttpd settings -*****0 os.execute("/etc/init.d/uhttpd reload") - end - - 103 return system - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/utils.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh community mesh networks meta-firmware - --! - --! Copyright (C) 2014-2023 Gioacchino Mazzurco - --! Copyright (C) 2023 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - 607 utils = {} - - 607 local config = require("lime.config") - 607 local json = require("luci.jsonc") - 607 local fs = require("nixio.fs") - 607 local nixio = require("nixio") - - 607 utils.BOARD_JSON_PATH = "/etc/board.json" - 607 utils.SHADOW_FILENAME = "/etc/shadow" - 607 utils.KEEP_ON_UPGRADE_FILES_BASE_PATH = '/lib/upgrade/keep.d/' - - 1214 function utils.dbg(...) - 39 local ofd = io.stderr - - 78 ofd:write( debug.getinfo(2, 'S').source, ":", - 39 debug.getinfo(2, 'l').currentline, " ", - 39 debug.getinfo(2, 'n').name ) - - 95 for n=1, select('#', ...) do - --! Assigantion needed to take only the Nth element discarding the rest - 56 local nE = select(n, ...) - 56 ofd:write(" ", nE) - end - - 39 ofd:write("\n") - end - - 1214 function utils.log(...) - 972 if DISABLE_LOGGING ~= nil then return end - 972 if os.getenv("LUA_DISABLE_LOGGING") ~= nil and os.getenv("LUA_ENABLE_LOGGING") == nil then return end - 388 print(string.format(...)) - end - - 1214 function utils.disable_logging() -*****0 DISABLE_LOGGING = 1 - end - - 1214 function utils.enable_logging() -*****0 DISABLE_LOGGING = nil - end - - 1214 function utils.split(string, sep) - 2910 local ret = {} - 7628 for token in string.gmatch(string, "[^"..sep.."]+") do table.insert(ret, token) end - 2910 return ret - end - - 1214 function utils.stringStarts(string, start) - 236 return (string.sub(string, 1, string.len(start)) == start) - end - - 1214 function utils.stringEnds(string, _end) - 139 return ( _end == '' or string.sub( string, -string.len(_end) ) == _end) - end - - 1214 function utils.hex(x) - 82 return string.format("%02x", x) - end - - 1214 function utils.printf(fmt, ...) -*****0 print(string.format(fmt, ...)) - end - - --! escape the magic characters: ( ) . % + - * ? [ ] ^ $ - --! useful to use with gsub / match when finding exactly a string - 1214 function utils.literalize(str) - 1327 local ret, _ = str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", function(c) return "%" .. c end) - 600 return ret - end - - 1214 function utils.isModuleAvailable(name) - 944 if package.loaded[name] then - 568 return true - else - 1811 for _, searcher in ipairs(package.searchers or package.loaders) do - 1574 local loader = searcher(name) - 1574 if type(loader) == 'function' then - 139 package.preload[name] = loader - 139 return true - end - end - 237 return false - end - end - - 1214 function utils.applyMacTemplate16(template, mac) - 1827 for i=1,6,1 do template = template:gsub("%%M"..i, mac[i]) end - 261 local macid = utils.get_id(mac) - 1827 for i=1,6,1 do template = template:gsub("%%m"..i, macid[i]) end - 261 return template - end - - 1214 function utils.applyMacTemplate10(template, mac) - 784 for i=1,6,1 do template = template:gsub("%%M"..i, tonumber(mac[i], 16)) end - 112 local macid = utils.get_id(mac) - 784 for i=1,6,1 do template = template:gsub("%%m"..i, tonumber(macid[i], 16)) end - 112 return template - end - - 1214 function utils.applyHostnameTemplate(template) - 107 local system = require("lime.system") - 107 return template:gsub("%%H", system.get_hostname()) - end - - 1214 function utils.get_id(input) - 849 if type(input) == "table" then - 373 input = table.concat(input, "") - end - 849 local id = {} - 849 local fd = io.popen('echo "' .. input .. '" | md5sum') - 849 if fd then - 849 local md5 = fd:read("*a") - 849 local j = 1 - 14433 for i=1,16,1 do - 13584 id[i] = string.sub(md5, j, j + 1) - 13584 j = j + 2 - end - 849 fd:close() - end - 849 return id - end - - 1214 function utils.network_id() - 194 local network_essid = config.get("wifi", "ap_ssid") - 194 return utils.get_id(network_essid) - end - - 1214 function utils.applyNetTemplate16(template) - 82 local netid = utils.network_id() - 574 for i=1,6,1 do template = template:gsub("%%N"..i, netid[i]) end - 82 return template - end - - 1214 function utils.applyNetTemplate10(template) - 112 local netid = utils.network_id() - 784 for i=1,6,1 do template = template:gsub("%%N"..i, tonumber(netid[i], 16)) end - 112 return template - end - - - --! This function is inspired to http://lua-users.org/wiki/VarExpand - --! version: 0.0.1 - --! code: Ketmar // Avalon Group - --! licence: public domain - --! expand $var and ${var} in string - --! ${var} can call Lua functions: ${string.rep(' ', 10)} - --! `$' can be screened with `\' - --! `...': args for $ - --! if `...' is just a one table -- take it as args - 1214 function utils.expandVars(s, ...) -*****0 local args = {...} - args = #args == 1 and type(args[1]) == "table" and args[1] or args; - - --! return true if there was an expansion - local function DoExpand(iscode) -*****0 local was = false -*****0 local mask = iscode and "()%$(%b{})" or "()%$([%a%d_]*)" -*****0 local drepl = iscode and "\\$" or "\\\\$" - s = s:gsub(mask, - function(pos, code) -*****0 if s:sub(pos-1, pos-1) == "\\" then -*****0 return "$"..code - else -*****0 was = true - local v, err -*****0 if iscode then -*****0 code = code:sub(2, -2) - else -*****0 local n = tonumber(code) -*****0 if n then -*****0 v = args[n] - else -*****0 v = args[code] - end - end -*****0 if not v then -*****0 v, err = loadstring("return "..code) -*****0 if not v then error(err) end -*****0 v = v() - end -*****0 if v == nil then v = "" end -*****0 v = tostring(v):gsub("%$", drepl) -*****0 return v - end - end) -*****0 if not (iscode or was) then s = s:gsub("\\%$", "$") end -*****0 return was - end -*****0 repeat DoExpand(true); until not DoExpand(false) -*****0 return s - end - - --! return a string that can be a part of an url - 1214 function utils.slugify(s) - 110 s = s:gsub('[^-a-zA-Z0-9]', '-') - 110 return s - end - - 1214 function utils.hostname() - 624 return io.input("/proc/sys/kernel/hostname"):read("*line") - end - - 1214 function utils.sanitize_hostname(hostname) - 42 hostname = hostname:gsub(' ', '-') - 42 hostname = hostname:gsub('[^-a-zA-Z0-9]', '') - 42 hostname = hostname:gsub('^-*', '') - 42 hostname = hostname:gsub('-*$', '') - 42 hostname = hostname:sub(1, 32) - 42 return hostname - end - - --! validate that a hostname is also DNS valid - 1214 function utils.is_valid_hostname(hostname) - 155 if hostname and (#hostname < 64) and - 155 hostname:match("^[a-zA-Z0-9][a-zA-Z0-9%-]*[a-zA-Z0-9]$") - then - 39 return true - end - 116 return false - end - - 1214 function utils.file_exists(name) - 561 local f=io.open(name,"r") - 561 if f~=nil then io.close(f) return true else return false end - end - - 1214 function utils.read_file(name) - 184 local f = io.open(name,"r") - 184 local ret = nil - 184 if f ~= nil then - 156 ret = f:read("*all") - 156 f:close() - end - 184 return ret - end - - 1214 function utils.write_file(name, content) - 342 local f = io.open(name, "w") - 342 local ret = false - 342 if f ~= nil then - 336 f:write(content) - 336 f:close() - 336 ret = true - end - 342 return ret - end - - 1214 function utils.is_installed(pkg) - 97 return utils.file_exists('/usr/lib/opkg/info/'..pkg..'.control') - end - - 1214 function utils.has_value(tab, val) - 249 for index, value in ipairs(tab) do - 202 if value == val then - 125 return true - end - end - 47 return false - end - - --! contact array a2 to the end of array a1 - 1214 function utils.arrayConcat(a1,a2) -*****0 for _,i in ipairs(a2) do -*****0 table.insert(a1,i) - end -*****0 return a1 - end - - --! melt table t1 into t2, if keys exists in both tables use value of t2 - 1214 function utils.tableMelt(t1, t2) - 1438 for key, value in pairs(t2) do - 1108 t1[key] = value - end - 330 return t1 - end - - 1214 function utils.tableLength(t) - 349 local count = 0 - 761 for _ in pairs(t) do count = count + 1 end - 339 return count - end - - 1214 function utils.indexFromName(name) - 336 return tonumber(name:match("%d+")) - end - - 1214 function utils.getBoardAsTable(board_path) - 11 if board_path == nil then -*****0 board_path = utils.BOARD_JSON_PATH - end - 11 return json.parse(fs.readfile(board_path)) - end - - 1214 function utils.current_board() - 11 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") - end - - 1214 function utils.printJson(obj) - 80 print(json.stringify(obj)) - end - - --! use rpcd_readline() in libexec/rpcd/ scripts to access the arguments that - --! are passed through stdin. The use of this functions allows testing. - 1214 function utils.rpcd_readline() -*****0 return io.read() - end - - --! for testing only - 607 utils._uptime_line = nil - - 1214 function utils.uptime_s() - 66 local uptime_line = utils._uptime_line or io.open("/proc/uptime"):read("*l") - 66 return tonumber(string.match(uptime_line, "^%S+")) - end - - --! Escape strings for safe shell usage. - 1214 function utils.shell_quote(s) - --! Based on Python's shlex.quote() - 88 return "'" .. string.gsub(s, "'", "'\"'\"'") .. "'" - end - - --! Excutes a shell command, waits for completion and returns stdout. - --! Warning! Use this function carefully as it could be exploited if used with - --! untrusted input. Always use function utils.shell_quote() to escape untrusted - --! input. - 1214 function utils.unsafe_shell(command) - 235 local handle = io.popen(command) - 235 local result = handle:read("*a") - 230 handle:close() - 230 return result - end - - --! based on luci.sys.setpassword - 1214 function utils.set_password(username, password) - 11 local user = utils.shell_quote(username) - 11 local pass = utils.shell_quote(password) - 22 return os.execute(string.format("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1", - 11 pass, pass, user)) - end - - 1214 function utils.get_root_secret() - 39 local f = io.open(utils.SHADOW_FILENAME, "r") - 39 if f ~= nil then - 33 local root_line = f:read("*l") --! root user is always in the first line - 33 local secret = root_line:match("root:(.-):") - 33 return secret - end - end - - 1214 function utils.set_root_secret(secret) - 22 local f = io.open(utils.SHADOW_FILENAME, "r") - 22 if f ~= nil then - --! perform a backup of the shadow - 22 local f_bkp = io.open(utils.SHADOW_FILENAME .. "-", "w") - 22 f_bkp:write(f:read("*a")) - 22 f:seek("set") - 22 f_bkp:close() - - 22 local root_line = f:read("*l") --! root user is always in the first line - 22 local starts, ends = string.find(root_line, "root:.-:") - 22 local content = "root:" .. secret .. root_line:sub(ends) .. "\n" - 22 content = content .. f:read("*a") - 22 f:close() - 22 f = io.open(utils.SHADOW_FILENAME, "w") - 22 f:write(content) - 22 f:close() - end - end - - 1214 function utils.set_shared_root_password(password) - 28 local uci = config.get_uci_cursor() - 28 utils.set_password('root', password) -- this takes 1 second, it may be replaced with nixio.crypt(password, '$1$vv44cu1H') - 28 uci:set("lime-community", 'system', 'root_password_policy', 'SET_SECRET') - 28 uci:set("lime-community", 'system', 'root_password_secret', utils.get_root_secret()) - end - - --! returns a random string. filter is an optional function to reduce the possible characters. - --! by default the filter allows all the alphanumeric characters - 1214 function utils.random_string(length, filter) - 737 if filter == nil then - --! all alphanumeric characters - 13256 filter = function (c) return string.match(c, "%w") ~= nil end - end - 737 local urandom = io.open("/dev/urandom", "rb") - 737 local out = "" - 30257 while length > #out do - 29520 local c = urandom:read(1) - 29520 if filter(c) then - 4642 out = out .. c - end - end - 737 return out - end - - --! Return a list of files to keep when upgrading. Existence of the files is not guaranteed. - 1214 function utils.keep_on_upgrade_files() - 11 local file_lists = config.get("system", "keep_on_upgrade", "") - 11 local files = {} - 44 for _, list in pairs(utils.split(file_lists, " ")) do - --! convert non absolute paths to absolute - 33 if not utils.stringStarts(list, "/") then - 22 list = utils.KEEP_ON_UPGRADE_FILES_BASE_PATH .. list - end - 33 if utils.file_exists(list) then - 77 for file_name in io.lines(list) do - --! exclude comments, blank lines, etc - 55 if utils.stringStarts(file_name, "/") then - 33 table.insert(files, file_name) - end - end - end - end - 11 return files - end - - --! Run a command in a shell and daemonized directly in pid 1 (similar to diswon/nohup) - --! If out_file optional arg is passed then stdout and stderr will go to that file. - 1214 function utils.execute_daemonized(cmd, out_file, stdin) - 23 if out_file == nil then - 11 out_file = "/dev/null" - end - 23 if not stdin then - 11 stdin = "0<&-" -- closing standard input - else - 12 stdin = "0<" .. stdin - end - - 23 return os.execute("(( " .. cmd .. " " .. stdin .. " &> " .. out_file .. " &) &)") - end - - 1214 function utils.bitwise_xor(a, b) - 66 local r = 0 - 2178 for i = 0, 31 do - 2112 local x = a / 2 + b / 2 - 2112 if x ~= math.floor(x) then - 209 r = r + 2^i - end - 2112 a = math.floor(a / 2) - 2112 b = math.floor(b / 2) - end - 66 return r - end - - 1214 function utils.mac2ipv6linklocal(text) - 66 function f(mac0, mac1, mac2, mac3, mac4, mac5, mac6) - 66 local mac0 = string.format("%x", utils.bitwise_xor(tonumber(mac0, 16), 0x02)) - --! going from and to string to remove leading zeroes and convert to lowercase - 66 local gr1 = string.format("%x", tonumber(mac0 .. mac1, 16)) - 66 local gr2 = string.format("%x", tonumber(mac2 .. 'ff', 16)) - 66 local gr3 = string.format("%x", tonumber('fe' .. mac3, 16)) - 66 local gr4 = string.format("%x", tonumber(mac4 .. mac5, 16)) - 66 return 'fe80::' .. gr1 .. ":" .. gr2 .. ":" .. gr3 .. ":" .. gr4 - end - 66 local ret, _ = string.gsub(text, "(%x%x):(%x%x):(%x%x):(%x%x):(%x%x):(%x%x)", f) - 66 return ret - end - - --! do a HTTP GET returning the body or nil. If out_file is provided then the body is saved - --! to this file and true is returned instead - 1214 function utils.http_client_get(url, timeout_s, out_file) - 44 local remove_file = false - 44 if not out_file then - 22 remove_file = true - 22 out_file = os.tmpname() - end - 88 local cmd = string.format("uclient-fetch -q -O %s --timeout=%d %s 2> /dev/null", out_file, - 44 timeout_s, url) - 44 local exit_value = os.execute(cmd) - 44 if exit_value == 0 then -*****0 if remove_file then -*****0 local data = utils.read_file(out_file) -*****0 os.execute("rm -f " .. out_file) -*****0 return data - else -*****0 return true - end - else - 44 return nil - end - end - - 1214 function utils.release_info() - 34 local result = {} - 34 local release_data = utils.read_file("/etc/openwrt_release") - 195 for key, value in release_data:gmatch("(.-)='(%C-)'\n") do - 161 result[key] = value - end - 23 return result - end - - 1214 function utils.open_with_lock(fname, max_wait_s) - 710 max_wait_s = max_wait_s or 1 - 710 local locked = false - - 710 local fd = nixio.open(fname, nixio.open_flags("rdwr", "creat") ) - 710 if not fd then -*****0 return nil, "Can't create file" - end - - 710 for i=0,max_wait_s do - 710 if not fd:lock("tlock") then -*****0 nixio.nanosleep(1) - else - 710 locked = true - 710 break - end - end - - 710 if not locked then -*****0 if fd then fd:close() end -*****0 return nil, "Failed acquiring lock" - end - - 710 return fd - end - - --! Object store database. Uses json as on disk format. Uses posix file locks to prevent corruption, - --! be aware that this mechanism does only works between processes and not beteen threads or same thread. - 1214 function utils.read_obj_store(datafile) - 349 local fd = utils.open_with_lock(datafile) - local store - 349 if fd then - 349 store = json.parse(nixio.fs.readfile(datafile)) or {} - 349 fd:close() - end - 349 return store - end - - 1214 function utils.write_obj_store_var(datafile, name, data) - 123 local fd = utils.open_with_lock(datafile) - local store - 123 if fd then - 123 store = json.parse(nixio.fs.readfile(datafile)) or {} - 123 fd:seek(0) - 123 store[name] = data - 123 fd:write(json.stringify(store)) - 123 fd:close() - end - 123 return store - end - - 1214 function utils.write_obj_store(datafile, data) - 216 local fd = utils.open_with_lock(datafile) - 216 if fd then - 216 fd:write(json.stringify(data)) - 216 fd:close() - 216 return true - end - end - - 1214 function utils.get_ifnames() -*****0 local ifnames = {} -*****0 for ifname in fs.dir("/sys/class/net/") do -*****0 table.insert(ifnames, ifname) - end -*****0 return ifnames - end - - 1214 function utils.is_valid_mac(string) - 33 local string = string:match("%w%w:%w%w:%w%w:%w%w:%w%w:%w%w") - 33 if string then - 33 return true - else -*****0 return false - end - end - - --! TODO: Better having a C strcmp/memcmp like behavior so the output can be - --! used for sorting beyond determining equality - 1214 function utils.deepcompare(t1,t2) - 15513 if t1 == t2 then return true end - 3417 local ty1 = type(t1) - 3417 local ty2 = type(t2) - 3417 if ty1 ~= ty2 then return false end - 3417 if ty1 ~= 'table' and ty2 ~= 'table' then return t1 == t2 end - 11036 for k1, v1 in pairs(t1) do - 7733 local v2 = t2[k1] - 7733 if v2 == nil or not utils.deepcompare(v1, v2) then return false end - end - 10946 for k2, v2 in pairs(t2) do - 7643 local v1 = t1[k2] - 7643 if v1 == nil or not utils.deepcompare(v1, v2) then return false end - end - 3303 return true - end - - 1214 function utils.is_dsa(port) - --! Code adapted from Jow https://forum.openwrt.org/t/how-to-detect-dsa/111868/4 - 553 port = port or "*" - 553 return 0 == os.execute("grep -sq DEVTYPE=dsa /sys/class/net/"..port.."/uevent") - end - - 1214 function utils.dumptable(table, nesting) -*****0 local nesting = nesting or 1 -*****0 if type(table) ~= "table" then -*****0 print("dumptable: first argument is expected to be a table but you passed a", type(table), table) - else -*****0 if next(table) == nil then -*****0 print(table, "empty") - else -*****0 for k,v in pairs(table) do -*****0 print(string.rep('\t', nesting), k, ' = ', v) -*****0 if type(v) == 'table' then dumptable(v, nesting+1) end - end - end - end - end - - 607 return utils - -============================================================================== -packages/lime-system/files/usr/lib/lua/lime/wireless.lua -============================================================================== - #!/usr/bin/lua - - 194 local config = require("lime.config") - 194 local network = require("lime.network") - 194 local utils = require("lime.utils") - 194 local fs = require("nixio.fs") - 194 local iwinfo = require("iwinfo") - - 194 wireless = {} - - 194 wireless.limeIfNamePrefix="lm_" - - 388 function wireless.WIFI_MODE_SEPARATOR() - 231 return "-" - end - - 388 function wireless.get_phy_mac(phy) -*****0 local path = "/sys/class/ieee80211/"..phy.."/macaddress" -*****0 local mac = assert(fs.readfile(path), "wireless.get_phy_mac(..) failed reading: "..path):gsub("\n","") -*****0 return utils.split(mac, ":") - end - - 388 function wireless.clean() - 6 utils.log("Clearing wireless config...") - 6 local uci = config.get_uci_cursor() - 24 uci:foreach("wireless", "wifi-iface", function(s) uci:delete("wireless", s[".name"]) end) - 6 uci:save("wireless") - end - - 388 function wireless.scandevices() - 138 local devices = {} - 138 local uci = config.get_uci_cursor() - 310 uci:foreach("wireless", "wifi-device", function(dev) devices[dev[".name"]] = dev end) - - 138 local sorted_devices = {} - 310 for _, dev in pairs(devices) do - 172 table.insert(sorted_devices, utils.indexFromName(dev[".name"])+1, dev) - end - - 138 local band_2ghz_index = 0 - 138 local band_5ghz_index = 0 - - 310 for _, dev in pairs(sorted_devices) do - 172 if wireless.is5Ghz(dev[".name"]) then - 155 dev.per_band_index = band_5ghz_index - 155 band_5ghz_index = band_5ghz_index + 1 - else - 17 dev.per_band_index = band_2ghz_index - 17 band_2ghz_index = band_2ghz_index + 1 - end - end - 138 return devices - end - - 388 function wireless.is5Ghz(radio) - 311 local config = require("lime.config") - 311 local uci = config.get_uci_cursor() - 311 wifi_band = uci:get('wireless', radio, 'band') - 311 if wifi_band then return wifi_band=='5g' end - 55 wifi_channel = uci:get('wireless', radio, 'channel') - 55 if tonumber(wifi_channel) then -*****0 wifi_channel = tonumber(wifi_channel) -*****0 return 32<=wifi_channel and wifi_channel<178 - end - 55 local backend = iwinfo.type(radio) - 55 local devModes = backend and iwinfo[backend].hwmodelist(radio) - 55 return devModes and (devModes.a or devModes.ac) - end - - 194 wireless.availableModes = { adhoc=true, ap=true, apname=true, apbb=true, ieee80211s=true } - 388 function wireless.isMode(m) - 519 return wireless.availableModes[m] - end - - 388 function wireless.is_mesh(iface) - 52 return iface.mode == 'mesh' or iface.mode == 'adhoc' - end - - 388 function wireless.mesh_ifaces() - 43 local uci = config.get_uci_cursor() - 43 local ifaces = {} - - 86 uci:foreach("wireless", "wifi-iface", function(entry) - 56 if entry.disabled ~= '1' and wireless.is_mesh(entry) then - 34 table.insert(ifaces, entry.ifname) - end - end) - --add apup interfaces - --this are not listed in uci - 43 local shell_output = utils.unsafe_shell("ls /sys/class/net/ -R") - 43 if shell_output ~= nil then - 374 for line in shell_output:gmatch("[^\n]+") do - -- Check if the line contains the pattern 'wlanX-peerY' - 331 local apup = require("lime.mode.apup") - 331 local iface = line:match("wlan(%d+)-"..apup.PEER_SUFFIX().."(%d+)$") - 331 if iface then - -- Add the matched interface to the table -*****0 table.insert(ifaces, line) - end - end - end - 43 return ifaces - end - - 388 function wireless.get_radio_ifaces(radio) - 42 local uci = config.get_uci_cursor() - 42 local ifaces = {} - - 84 uci:foreach("wireless", "wifi-iface", function(entry) - 18 if entry.disabled ~= '1' and entry.device == radio then - 18 table.insert(ifaces, entry) - end - end) - 42 return ifaces - end - - 388 function wireless.calcIfname(radioName, mode, nameSuffix) - 120 local phyIndex = tostring(utils.indexFromName(radioName)) - 120 return "wlan"..phyIndex..wireless.WIFI_MODE_SEPARATOR()..mode..nameSuffix - end - - 388 function wireless.createBaseWirelessIface(radio, mode, nameSuffix, extras) - --! checks("table", "string", "?string", "?table") - --! checks(...) come from http://lua-users.org/wiki/LuaTypeChecking -> https://github.com/fab13n/checks - 120 nameSuffix = nameSuffix or "" - 120 local radioName = radio[".name"] - 120 local ifname = wireless.calcIfname(radioName, mode, nameSuffix) - --! sanitize generated ifname for constructing uci section name - --! because only alphanumeric and underscores are allowed - 120 local wirelessInterfaceName = wireless.limeIfNamePrefix..ifname:gsub("[^%w_]", "_").."_"..radioName - 120 local networkInterfaceName = network.limeIfNamePrefix..ifname:gsub("[^%w_]", "_") - - 120 local uci = config.get_uci_cursor() - - 120 uci:set("wireless", wirelessInterfaceName, "wifi-iface") - 120 uci:set("wireless", wirelessInterfaceName, "mode", mode) - 120 uci:set("wireless", wirelessInterfaceName, "device", radioName) - 120 uci:set("wireless", wirelessInterfaceName, "ifname", ifname) - 120 uci:set("wireless", wirelessInterfaceName, "network", networkInterfaceName) - - 120 if extras then - 333 for key, value in pairs(extras) do - 213 uci:set("wireless", wirelessInterfaceName, key, value) - end - end - - 120 uci:save("wireless") - - 120 return uci:get_all("wireless", wirelessInterfaceName) - end - - 388 function wireless.resolve_ssid(ssid) - 107 local result = utils.applyHostnameTemplate(ssid) - 107 result = utils.applyMacTemplate16(result, network.primary_mac()) - 107 result = string.sub(result, 1, 32) - 107 return result - end - - 388 function wireless.configure() - 61 local specificRadios = {} - 122 config.foreach("wifi", function(radio) - 22 specificRadios[radio[".name"]] = radio - end) - - 61 local allRadios = wireless.scandevices() - 145 for _,radio in pairs(allRadios) do - 84 local radioName = radio[".name"] - 84 local radioBand = wireless.is5Ghz(radioName) and '5ghz' or '2ghz' - 84 local radioOptions = specificRadios[radioName] or {} - 84 local bandOptions = config.get_all(radioBand) or {} - 84 local options = config.get_all("wifi") - - 84 options = utils.tableMelt(options, bandOptions) - 84 options = utils.tableMelt(options, radioOptions) - - --! If manual mode is used toghether with other modes it results in an - --! unpredictable behaviour - 84 if options["modes"][1] ~= "manual" then - --! fallback to "auto" in client mode - local channel - 84 if options["modes"][1] ~= "client" then - 73 channel = options["channel"] - 73 if type(channel) == "table" then - 34 channel = channel[1 + radio.per_band_index % #channel] - end - else - 11 channel = options["channel"] or "auto" - end - - 84 local uci = config.get_uci_cursor() - 84 uci:set("wireless", radioName, "disabled", 0) - 84 uci:set("wireless", radioName, "distance", options["distance"]) - 84 uci:set("wireless", radioName, "noscan", 1) - 84 uci:set("wireless", radioName, "channel", channel) - 84 if options["country"] then uci:set("wireless", radioName, "country", options["country"]) end - 84 if options["legacy_rates"] and not wireless.is5Ghz(radioName) then uci:set("wireless", radioName, "legacy_rates", options["legacy_rates"]) end - 84 if options["txpower"] then uci:set("wireless", radioName, "txpower", options["txpower"]) end - 84 if options["htmode"] then uci:set("wireless", radioName, "htmode", options["htmode"]) end - 84 uci:save("wireless") - - 204 for _,modeName in pairs(options["modes"]) do - 120 local args = {} - 120 local mode = require("lime.mode."..modeName) - - -- gather mode specific configs (eg ieee80211s_mcast_rate_5ghz) - 1576 for key,value in pairs(options) do - 1456 local keyPrefix = utils.split(key, "_")[1] - 1456 local isGoodOption = ( (key ~= "modes") - 1336 and (not key:match("^%.")) - 1336 and (not key:match("channel")) - 954 and (not key:match("country")) - 845 and (not key:match("legacy_rates")) - 823 and (not key:match("txpower")) - 823 and (not key:match("htmode")) - 801 and (not key:match("distance")) - 747 and (not key:match("unstuck_interval")) - 627 and (not key:match("unstuck_timeout")) - 1456 and (not (wireless.isMode(keyPrefix) and keyPrefix ~= modeName))) - 1456 if isGoodOption then - 177 local nk = key:gsub("^"..modeName.."_", "") - 177 if nk == "ssid" then - 47 value = wireless.resolve_ssid(value) - end - 177 args[nk] = value - end - end - - 120 mode.setup_radio(radio, args) - end - end - end - end - - 388 function wireless.get_band_config(band) - 72 local general_cfg = config.get_all("wifi") or {} - 72 local band_cfg = config.get_all(band) or {} - 72 local result = general_cfg - 72 utils.tableMelt(result, band_cfg) - 72 return result - end - - 388 function wireless.get_community_band_config(band) - 30 local uci = config.get_uci_cursor() - 30 local default_general_cfg = uci:get_all(config.UCI_DEFAULTS_NAME, "wifi") or {} - 30 local default_band_cfg = uci:get_all(config.UCI_DEFAULTS_NAME, band) or {} - 30 local community_general_cfg = uci:get_all(config.UCI_COMMUNITY_NAME, "wifi") or {} - 30 local community_band_cfg = uci:get_all(config.UCI_COMMUNITY_NAME, band) or {} - 30 local result = default_general_cfg - 30 utils.tableMelt(result, default_band_cfg) - 30 utils.tableMelt(result, community_general_cfg) - 30 utils.tableMelt(result, community_band_cfg) - 30 return result - end - - 388 function wireless.add_band_mode(band, mode_name) - 6 local uci = config.get_uci_cursor() - 6 local cfg = wireless.get_band_config(band) - 6 if not utils.has_value(cfg.modes, mode_name) then - 6 local modes = uci:get(config.UCI_NODE_NAME, band, 'modes') - 6 if not modes or modes[1] == 'manual' then - 6 modes = { mode_name } - else -*****0 table.insert(modes, mode_name) - end - 6 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') - 6 uci:set(config.UCI_NODE_NAME, band, 'modes', modes) - 6 uci:commit(config.UCI_NODE_NAME) - 6 utils.unsafe_shell('lime-config') - end - end - - 388 function wireless.remove_band_mode(band, mode_name) - 6 local uci = config.get_uci_cursor() - 6 local cfg = wireless.get_band_config(band) - 6 if utils.has_value(cfg.modes, mode_name) then - 6 local new_modes = {} - 18 for _, mode in pairs(cfg.modes) do - 12 if mode ~= mode_name then - 6 table.insert(new_modes, mode) - end - end - 6 if utils.tableLength(new_modes) == 0 then -*****0 new_modes = {'manual'} - end - 6 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') - 6 uci:set(config.UCI_NODE_NAME, band, 'modes', new_modes) - 6 uci:commit(config.UCI_NODE_NAME) - 6 utils.unsafe_shell('lime-config') - end - end - - 388 function wireless.set_band_config(band, cfg) - 12 local uci = config.get_uci_cursor() - 12 uci:set(config.UCI_NODE_NAME, band, 'lime-wifi-band') - 30 for key, value in pairs(cfg) do - 18 uci:set(config.UCI_NODE_NAME, band, key, value) - end - 12 uci:commit(config.UCI_NODE_NAME) - 12 utils.unsafe_shell('lime-config') - end - - 194 return wireless - -============================================================================== -packages/pirania/files/usr/lib/lua/portal/portal.lua -============================================================================== - 55 local utils = require('lime.utils') - 55 local config = require('lime.config') - 55 local shared_state = require("shared-state") - 55 local read_for_access = require("read_for_access.read_for_access") - - 55 local portal = {} - - 55 portal.PAGE_CONTENT_OBJ_PATH = '/etc/pirania/portal.json' - - 55 function portal.get_config() - 110 local uci = config.get_uci_cursor() - 110 local activated = uci:get("pirania", "base_config", "enabled") == '1' - 110 local with_vouchers = uci:get("pirania", "base_config", "with_vouchers") == '1' - 110 return {activated = activated, with_vouchers = with_vouchers} - end - - 55 function portal.set_config(activated, with_vouchers) - 33 local uci = config.get_uci_cursor() - - 66 uci:set("pirania", "base_config", "with_vouchers", - 33 with_vouchers and "1" or "0") - 33 if activated then - 11 uci:set("pirania", "base_config", "enabled", "1") - 11 uci:commit("pirania") - 11 utils.unsafe_shell("captive-portal start") - else - 22 uci:set("pirania", "base_config", "enabled", "0") - 22 uci:commit("pirania") - 22 utils.unsafe_shell("captive-portal stop") - end - 33 return true - end - - 55 function portal.get_page_content() - 22 local db = shared_state.SharedStateMultiWriter:new('pirania_persistent'):get() - 22 if db.portal then - 11 return db.portal.data - else - 11 return utils.read_obj_store(portal.PAGE_CONTENT_OBJ_PATH) - end - end - - - 55 function portal.set_page_content(title, main_text, logo, link_title, link_url, background_color) - 11 local data = {title=title, main_text=main_text, logo=logo, link_title=link_title, link_url=link_url, background_color=background_color} - 11 local db = shared_state.SharedStateMultiWriter:new('pirania_persistent') - 11 return db:insert({portal=data}) - end - - 55 function portal.get_authorized_macs() -*****0 local auth_macs = {} -*****0 local with_vouchers = portal.get_config().with_vouchers -*****0 if with_vouchers then -*****0 local vouchera = require("voucher.vouchera") -*****0 vouchera.init() -*****0 auth_macs = vouchera.get_authorized_macs() - else -*****0 auth_macs = read_for_access.get_authorized_macs() - end -*****0 return auth_macs - end - - 55 function portal.update_captive_portal(daemonized) -*****0 if daemonized then -*****0 utils.execute_daemonized('captive-portal update') - else -*****0 os.execute('captive-portal update') - end - end - - 55 return portal - -============================================================================== -packages/pirania/files/usr/lib/lua/read_for_access/cgi_handlers.lua -============================================================================== - 11 local utils = require('voucher.utils') - 11 local read_for_access = require('read_for_access.read_for_access') - 11 local portal = require('portal.portal') - 11 local config = require('lime.config') - - 11 local handlers = {} - - 11 function handlers.authorize_mac() - 44 local uci = config.get_uci_cursor() - 44 local with_vouchers = portal.get_config().with_vouchers - 44 if with_vouchers then - 11 return uci:get("pirania", "base_config", "url_auth") - end - 33 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) - 33 read_for_access.authorize_mac(client_data.mac) - 33 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) - 33 local url_prev = utils.urldecode(params['prev']) - 33 local url_authenticated = uci:get("pirania", "base_config", "url_authenticated") - 33 return url_prev or url_authenticated - end - - 11 return handlers - -============================================================================== -packages/pirania/files/usr/lib/lua/read_for_access/read_for_access.lua -============================================================================== - 66 local fs = require('nixio.fs') - 66 local utils = require('lime.utils') - 66 local config = require('lime.config') - - 66 local read_for_access = {} - - 66 function uptime_s() - 77 return math.floor(utils.uptime_s()) - end - - 66 function read_for_access.set_workdir(workdir) - 99 if not utils.file_exists(workdir) then - 2 os.execute('mkdir -p ' .. workdir) - end - 99 if fs.stat(workdir, "type") ~= "dir" then -*****0 error("Can't configure workdir " .. workdir) - end - 99 read_for_access.AUTH_MACS_FILE = workdir .. '/auth_macs' - end - - 66 read_for_access.set_workdir('/tmp/pirania/read_for_access') - - 66 function read_for_access.authorize_mac(mac) - 44 local uci = config.get_uci_cursor() - 44 local found = false - 44 if utils.file_exists(read_for_access.AUTH_MACS_FILE) then - 11 for line in io.lines(read_for_access.AUTH_MACS_FILE) do - 11 if line:match(mac) then - 11 found = true - 11 break - end - end - end - 44 local duration = uci:get("pirania", "read_for_access", "duration_m") - 44 local timestamp = uptime_s() + tonumber(duration) * 60 - 44 if not found then - 33 local ofile = io.open(read_for_access.AUTH_MACS_FILE, 'a') - 33 ofile:write(mac .. ' ' .. timestamp .. '\n') - 33 ofile:close() - else - 11 local content = utils.read_file(read_for_access.AUTH_MACS_FILE) - 11 content = content:gsub("(" .. mac .. ") %d+", "%1 " .. timestamp) - 11 utils.write_file(read_for_access.AUTH_MACS_FILE, content) - end - 44 os.execute('/usr/bin/captive-portal update') - end - - 66 function read_for_access.get_authorized_macs() - 33 local result = {} - 33 local current_time = uptime_s() - 33 if not utils.file_exists(read_for_access.AUTH_MACS_FILE) then -*****0 return result - end - 66 for line in io.lines(read_for_access.AUTH_MACS_FILE) do - 33 local words = {} - 99 for w in line:gmatch("%S+") do table.insert(words, w) end - 33 if tonumber(words[2]) > current_time then - 22 table.insert(result, words[1]) - end - end - 33 return result - end - - 66 return read_for_access - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/cgi_handlers.lua -============================================================================== - 11 local vouchera = require('voucher.vouchera') - 11 local utils = require('voucher.utils') - - 11 handlers = {} - - 11 local TESTING_URL_INFO = '/info' - 11 local TESTING_URL_FAIL = '/fail' - 11 local TESTING_URL_AUTHENTICATED = '/authenticated' - - - 22 function handlers.preactivate_voucher() - 77 local uci_cursor = require('uci').cursor() - - --! using defaults for testing as uci environment is not available - 77 local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated") or TESTING_URL_AUTHENTICATED - 77 local url_fail = uci_cursor:get("pirania", "base_config", "url_fail") or TESTING_URL_FAIL - 77 local url_info = uci_cursor:get("pirania", "base_config", "url_info") or TESTING_URL_INFO - - 77 vouchera.init() - - local url - - 77 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) - 77 local client_is_authorized = vouchera.is_mac_authorized(client_data.mac) - - 77 if client_is_authorized then - 11 url = url_authenticated - else - local output - 66 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) - 66 local code = params['voucher'] - 66 local prevUrl = params['prev'] - --! if client does not have javascript then activate right away without going to the INFO portal - 66 if params['nojs'] == 'true' then -*****0 if vouchera.activate(code, client_data.mac) then -*****0 url = url_authenticated - else -*****0 url = url_fail - end - else - 66 local setParams = prevUrl and '?voucher=' .. code .. '&prev=' .. prevUrl or '?voucher=' .. code - --! redirect to the INFO portal for some seconds with the url params already set to activate - --! the voucher after this time - 66 if vouchera.is_activable(code) then - 22 url = url_info .. setParams - else - 44 url = url_fail .. (prevUrl and '?prev=' .. prevUrl or '') - end - end - end - 77 return url - end - - 22 function handlers.activate_voucher() - 33 local uci_cursor = require('uci').cursor() - - 33 local url_authenticated = uci_cursor:get("pirania", "base_config", "url_authenticated") or TESTING_URL_AUTHENTICATED - 33 local url_fail = uci_cursor:get("pirania", "base_config", "url_fail") or TESTING_URL_FAIL - - 33 vouchera.init() - 33 local client_data = utils.getIpv4AndMac(os.getenv('REMOTE_ADDR')) - 33 local client_is_authorized = vouchera.is_mac_authorized(client_data.mac) - - 33 local params = utils.urldecode_params(os.getenv("QUERY_STRING")) - 33 local code = params['voucher'] - 33 local prevUrl = params['prev'] - - 33 if client_is_authorized then - 11 return url_authenticated - end - - 22 local url = url_fail .. (prevUrl and '?prev=' .. prevUrl or '') - - 22 if code and client_data.mac then - 11 if vouchera.activate(code, client_data.mac) then - 11 if prevUrl ~= nil then - 11 url = prevUrl - else -*****0 url = url_authenticated - end - end - end - 22 return url - end - - 11 return handlers - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/config.lua -============================================================================== - 33 local uci = require("uci") - 33 local pirania_config = 'pirania' - - 33 local ucicursor = uci.cursor() - - 33 local config = { - 33 db_path = ucicursor:get(pirania_config, 'base_config', 'db_path'), - 33 hooksDir = ucicursor:get(pirania_config, 'base_config', 'hooks_path'), - 33 prune_expired_for_days = ucicursor:get(pirania_config, 'base_config', 'prune_expired_for_days') - } - - 33 return config - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/hooks.lua -============================================================================== - #!/usr/bin/lua - - 33 local config = require('voucher.config') - 33 local fs = require("nixio.fs") - - 33 local hooks = {} - - - 33 function hooks.run(action) -*****0 local hookPath = config.hooksDir..action..'/' -*****0 local files = fs.dir(hookPath) -*****0 if files then -*****0 for file in files do -*****0 os.execute("(( sh "..hookPath..file.." 0<&- &>/dev/null &) &)") - end - end - end - - 33 if debug.getinfo(2).name == nil then -*****0 local arguments = { ... } -*****0 if (arguments ~= nil and arguments[1] ~= nil) then -*****0 hooks.run(arguments[1]) - end - end - - 33 return hooks - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/store.lua -============================================================================== - #!/bin/lua - - 33 local fs = require("nixio.fs") - 33 local json = require("luci.jsonc") - 33 local hooks = require('voucher.hooks') - 33 local utils = require("voucher.utils") - - 33 local store = {} - - 33 function store.load_db(db_path, voucher_init) - 671 local vouchers = {} - - 671 local f = io.open(db_path, "r") - 671 if f ~= nil then - 528 io.close(f) - else - 143 os.execute("mkdir -p " .. db_path) - end - - 891 for fname in fs.glob(db_path .. '/*.json') do - 220 local f = io.open(fname, "r") - 220 if f ~= nil then - 220 local json_obj = json.parse(f:read("*all")) - 220 f:close() - local voucher, err - 220 if json_obj then - 209 voucher, err = voucher_init(json_obj) - else - 11 err = "invalid json" - end - - 220 if voucher ~= nil then - 209 if vouchers[voucher.id] ~= nil then -*****0 utils.log('warning', "vouchers: multiple vouchers with the same id " .. voucher.id) - end - 209 vouchers[voucher.id] = voucher - else - 11 utils.log('warning', "vouchers: Error loading voucher file " .. fname .. ", " .. err) - end - end - end - 671 return vouchers - end - - 33 function store.add_voucher(db_path, voucher, voucher_init) - 825 local fname = db_path .. "/" .. voucher.id .. ".json" - --! check if it already exists and if it is equal do not rewrite it - 825 local f = io.open(fname, "r") - 825 if f ~= nil then - 275 local json_obj = json.parse(f:read("*all")) - 275 f:close() - 275 local local_voucher = voucher_init(json_obj) - 275 if local_voucher == voucher then -*****0 return false - end - end - 825 f = io.open(fname, "w") - 825 f:write(json.stringify(voucher)) - 825 f:close() - 825 return true - end - - 33 function store.remove_voucher(db_path, voucher) - 22 local fname = db_path .. "/" .. voucher.id .. ".json" - 22 local removed_db = io.open(db_path .. "/removed.txt", "a") - 22 if removed_db then - 22 removed_db:write(voucher.id .. ",") - 22 removed_db:close() - end - 22 local removed = os.execute("rm " .. fname) == 0 - 22 return removed - end - - 33 return store - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/utils.lua -============================================================================== - #!/bin/lua - 44 local nixio = require('nixio') - 44 local lhttp = require('lucihttp') - - 44 local utils = {} - - 44 function utils.log(...) - 11 nixio.syslog(...) - end - - local function checkIfIpv4(ip) - 77 if ip == nil or type(ip) ~= "string" then -*****0 return 0 - end - -- check for format 1.11.111.111 for ipv4 - 77 local chunks = {ip:match("(%d+)%.(%d+)%.(%d+)%.(%d+)")} - 77 if (#chunks == 4) then - 385 for _,v in pairs(chunks) do - 308 if (tonumber(v) < 0 or tonumber(v) > 255) then -*****0 return 0 - end - end - 77 return true - else -*****0 return false - end - end - - --! get ipv4 and MAC from a ip_address that could be ipv4 or ipv6 - 44 function utils.getIpv4AndMac(ip_address) - 77 local isIpv4 = checkIfIpv4(ip_address) - 77 if (isIpv4) then - 77 local ipv4macCommand = "cat /proc/net/arp | grep "..ip_address.." | awk -F ' ' '{print $4}' | head -n 1" - 77 fd = io.popen(ipv4macCommand, 'r') - 77 ipv4mac = fd:read('*l') - 77 fd:close() - 77 local res = {} - 77 res.ip = ip_address - 77 res.mac = ipv4mac - 77 return res - else -*****0 local ipv6macCommand = "ip neigh | grep "..ip_address.." | awk -F ' ' '{print $5}' | head -n 1" -*****0 fd6 = io.popen(ipv6macCommand, 'r') -*****0 ipv6mac = fd6:read('*l') -*****0 fd6:close() -*****0 local ipv4cCommand = "cat /proc/net/arp | grep "..ipv6mac.." | awk -F ' ' '{print $1}' | head -n 1" -*****0 fd4 = io.popen(ipv4cCommand, 'r') -*****0 ipv4 = fd4:read('*l') -*****0 fd4:close() -*****0 local res = {} -*****0 res.ip = ipv4 -*****0 res.mac = ipv6mac -*****0 return res - end - end - - --! from given url or string. Returns a table with urldecoded values. - --! Simple parameters are stored as string values associated with the parameter - --! name within the table. Parameters with multiple values are stored as array - --! containing the corresponding values. - 44 function utils.urldecode_params(url, tbl) - local parser, name - 132 local params = tbl or { } - - 264 parser = lhttp.urlencoded_parser(function (what, buffer, length) - 484 if what == parser.TUPLE then - 176 name, value = nil, nil - 308 elseif what == parser.NAME then - 154 name = lhttp.urldecode(buffer) - 154 elseif what == parser.VALUE and name then - 154 params[name] = lhttp.urldecode(buffer) or "" - end - - 484 return true - end) - - 132 if parser then - 132 parser:parse((url or ""):match("[^?]*$")) - 132 parser:parse(nil) - end - - 132 return params - end - - 44 function utils.urlencode(value) - 77 if value ~= nil then - 77 local str = tostring(value) - 77 return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL) or str - end -*****0 return nil - end - - 44 function utils.urldecode(value) - 33 if value ~= nil then - 11 local str = tostring(value) - 11 return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED) or str - end - 22 return nil - end - - 44 return utils - -============================================================================== -packages/pirania/files/usr/lib/lua/voucher/vouchera.lua -============================================================================== - #!/bin/lua - - 33 local store = require('voucher.store') - 33 local config = require('voucher.config') - 33 local utils = require('lime.utils') - 33 local portal = require('portal.portal') - 33 local hooks = require('voucher.hooks') - 33 local vouchera = {} - - 33 vouchera.ID_SIZE = 6 - 33 vouchera.CODE_SIZE = 6 - - --! Simplify the comparison of vouchers using a metatable for the == operator - 33 local voucher_metatable = { - __eq = function(self, value) - 374 return self.tostring() == value.tostring() - end - } - - --! obj attrs id, name, code, mac, duration_m, mod_counter, creation_date, activation_date - 33 function voucher_init(obj) - 1166 local voucher = {} - - 1166 if not obj.id then - 429 obj.id = utils.random_string(vouchera.ID_SIZE) - end - - 1166 voucher.id = obj.id - 1166 if type(obj.id) ~= "string" then -*****0 return nil, "id must be a string" - end - - 1166 if type(obj.name) ~= "string" then -*****0 return nil, "name must be a string" - end - 1166 voucher.name = obj.name - - 1166 if type(obj.code) ~= "string" then -*****0 return nil, "code must be a string" - end - 1166 voucher.code = obj.code - - 1166 if type(obj.mac) == "string" and #obj.mac ~= 17 then -*****0 return nil, "invalid mac" - end - 1166 voucher.mac = obj.mac - - - 1166 if not (type(obj.duration_m) == "nil" or type(obj.duration_m) == "number") then -*****0 return nil, "invalid duration_m type" - end - 1166 voucher.duration_m = obj.duration_m -- use nil to create a permanent voucher - - 1166 if not obj.creation_date then -*****0 return nil, "creation_date can't be nil" - end - - 1166 voucher.author_node = obj.author_node - - 1166 voucher.creation_date = obj.creation_date - - 1166 voucher.activation_date = obj.activation_date - - 1166 if not (type(obj.activation_deadline) == "nil" or type(obj.activation_deadline) == "number") then -*****0 return nil, "invalid activation_deadline type", type(obj.activation_deadline) - end - 1166 voucher.activation_deadline = obj.activation_deadline - - 1166 voucher.invalidation_date = obj.invalidation_date - - 1166 voucher.mod_counter = obj.mod_counter or 1 - - --! tostring must reflect all the state of a voucher (so vouchers can be compared reliably using tostring) - voucher.tostring = function() - 748 local v = voucher - 748 local creation = os.date("%c", v.creation_date) - 748 local expiration = ' - ' - 748 if v.expiration_date() then - 209 expiration = os.date("%c", v.expiration_date()) - end - 1496 return(string.format('%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s', v.id, v.name, v.code, v.mac or 'xx:xx:xx:xx:xx:xx', - 1496 creation, v.duration_m or 'perm', expiration, v.mod_counter)) - end - - voucher.expiration_date = function() - local ret = nil - 2123 if voucher.duration_m and voucher.mac and voucher.activation_date then - 847 ret = voucher.activation_date + voucher.duration_m * 60 - end - 2123 return ret - end - - voucher.is_active = function() - 451 if voucher.is_invalidated() or voucher.mac == nil then - 308 return false - else - 143 if voucher.expiration_date() and voucher.expiration_date() <= os.time() then - 22 return false - end - end - 121 return true - end - - voucher.is_invalidated = function() - 847 return voucher.invalidation_date ~= nil - end - - voucher.is_expired = function() - 385 local curr_time = os.time() - 385 return (voucher.expiration_date() ~= nil and voucher.expiration_date() < curr_time) or - 385 (voucher.activation_deadline ~= nil and voucher.activation_deadline < curr_time) - end - - voucher.is_activable = function() - 242 return voucher.mac == nil and not voucher.is_invalidated() and not voucher.is_expired() - end - - voucher.status = function() - 165 local status = 'available' - 165 if voucher.is_invalidated() then - 11 status = 'invalidated' - 154 elseif voucher.is_expired() then - 11 status = 'expired' - 143 elseif voucher.is_active() then - 11 status = 'active' - end - 165 return status - end - - 1166 setmetatable(voucher, voucher_metatable) - 1166 return voucher - end - - 33 function vouchera.init(cfg) - 671 if cfg ~= nil then -*****0 config = cfg - end - 671 vouchera.config = config - 671 vouchera.PRUNE_OLDER_THAN_S = tonumber(config.prune_expired_for_days) * 60 * 60 * 24 - 671 vouchera.vouchers = store.load_db(config.db_path, voucher_init) - - --! Automatic voucher pruning - 880 for _, voucher in pairs(vouchera.vouchers) do - 209 if vouchera.should_be_pruned(voucher) then - 11 vouchera.remove_locally(voucher.id) - end - end - end - - 33 function vouchera.add(obj) - 550 if not obj.creation_date then - 528 obj.creation_date = os.time() - end - 550 obj.author_node = utils.hostname() - 550 local voucher, errmsg = voucher_init(obj) - 550 if vouchera.vouchers[obj.id] ~= nil then -*****0 return nil, "voucher with same id already exists" - end - 550 if voucher and store.add_voucher(config.db_path, voucher, voucher_init) then - 550 vouchera.vouchers[obj.id] = voucher - 550 return voucher - end -*****0 return nil, "can't create voucher: " .. tostring(errmsg) - end - - 33 function vouchera.get_by_id(id) - 132 return vouchera.vouchers[id] - end - - 33 function vouchera.create(basename, qty, duration_m, activation_deadline) - 77 local vouchers = {} - 330 for n=1, qty do - local name - 253 if qty == 1 then - 11 name = basename - else - 242 name = basename .. "-" .. tostring(n) - end - 253 local v = {name=name, code=vouchera.gen_code(), duration_m=duration_m, - 253 activation_deadline=activation_deadline} - 253 local voucher, msg = vouchera.add(v) - 253 if voucher == nil then -*****0 return nil, msg - end - 253 table.insert(vouchers, n, {id=voucher.id, code=voucher.code}) - end - 77 portal.update_captive_portal(true) - 77 hooks.run('db_change') - 77 return vouchers - end - - --! Remove a voucher from the local db. This won't trigger a remove in the shared db. - 33 function vouchera.remove_locally(id) - 33 if vouchera.vouchers[id] ~= nil then - 22 if store.remove_voucher(config.db_path, vouchera.vouchers[id]) then - 22 vouchera.vouchers[id] = nil - 22 return true - else -*****0 return nil, "can't remove voucher" - end - end - 11 return nil, "can't find voucher to remove" - end - - local function modify_voucher_with_func(id, func) - 297 local voucher = vouchera.vouchers[id] - 297 if voucher then - 275 func(voucher) - 275 voucher.mod_counter = voucher.mod_counter + 1 - 275 return store.add_voucher(config.db_path, voucher, voucher_init) - end - 22 return voucher - end - - --! Remove a voucher from the shared db. - --! This will eventualy prune the voucher in all the dbs after PRUNE_OLDER_THAN_S seconds. - --! It is important to maintain the "removed" (invalidated) voucher in the shared db for some time - --! so that all nodes (even nodes that are offline when this is executed) have time to update locally - --! and eventualy prune the voucher. - 33 function vouchera.invalidate(id) - 99 local voucher = vouchera.vouchers[id] - 99 local is_active = voucher ~= nil and voucher.is_active() - local function _update(v) - 77 v.invalidation_date = os.time() - end - 99 voucher = modify_voucher_with_func(id, _update) - 99 if is_active then - 44 portal.update_captive_portal(true) - end - 99 hooks.run('db_change') - 99 return voucher - end - - --! Activate a voucher returning true or false depending on the status of the operation. - 33 function vouchera.activate(code, mac) - 198 local voucher = vouchera.is_activable(code) - 198 if voucher then - 165 function _update(v) - 165 v.mac = mac - 165 v.activation_date = os.time() - end - 165 modify_voucher_with_func(voucher.id, _update) - 165 portal.update_captive_portal(false) - 165 hooks.run('db_change') - end - 198 return voucher - end - - 33 function vouchera.deactivate(id) - local function _update(v) - 11 v.mac = nil - end - 11 return modify_voucher_with_func(id, _update) - end - - --! updates the database with the new voucher information - 33 function vouchera.update_with(voucher) -*****0 vouchera.vouchers[voucher.id] = voucher -*****0 return store.add_voucher(config.db_path, voucher, voucher_init) - end - - --! Return true if there is an activated voucher that grants a access to the specified MAC - 33 function vouchera.is_mac_authorized(mac) - 165 if mac ~= nil then - 121 for k, v in pairs(vouchera.vouchers) do - 77 if v.mac == mac and v.is_active() then - 44 return true - end - end - end - 121 return false - end - - --! Check if a code would be good to be activated but without activating it right away. - 33 function vouchera.is_activable(code) - 347 for _, v in pairs(vouchera.vouchers) do - 270 if v.code == code then - 242 if v.is_activable() then - 198 return v - else - 44 return false - end - end - end - 77 return false - end - - 33 function vouchera.should_be_pruned(voucher) - 297 local current_time = os.time() - 297 return (voucher.expiration_date() ~= nil and ( - 77 voucher.expiration_date() <= (current_time - vouchera.PRUNE_OLDER_THAN_S))) or - 297 ((voucher.invalidation_date or false) and (voucher.invalidation_date <= (current_time - vouchera.PRUNE_OLDER_THAN_S))) - end - - 33 function vouchera.rename(id, new_name) - local function _update(v) - 22 v.name = new_name - end - 22 return modify_voucher_with_func(id, _update) - end - - 33 function vouchera.gen_code() - 15078 return utils.random_string(vouchera.CODE_SIZE, function (c) return c:match('%u') ~= nil end) - end - - 33 function vouchera.list() - 22 local vouchers = {} - 132 for k, v in pairs(vouchera.vouchers) do - 220 table.insert(vouchers, { - 110 id=v.id, - 110 name=v.name, - 110 code=v.code, - 110 mac=v.mac, - 110 duration_m=v.duration_m, - 110 creation_date=v.creation_date, - 110 activation_date=v.activation_date, - 110 expiration_date=v.expiration_date(), - 110 is_active=v.is_active(), - 110 permanent=not v.duration_m, - 110 activation_deadline=v.activation_deadline, - 110 author_node=v.author_node, - 110 status=v.status(), - }) - end - 22 return vouchers - end - - 33 function vouchera.get_authorized_macs() -*****0 local auth_macs = {} -*****0 for _, voucher in pairs(vouchera.vouchers) do -*****0 if voucher.is_active() then -*****0 table.insert(auth_macs, voucher.mac) - end - end -*****0 return auth_macs - end - - 33 vouchera.voucher = voucher_init - - 33 return vouchera - -============================================================================== -packages/pirania/files/usr/libexec/rpcd/pirania -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright 2018 Marcos Gutierrez - Copyright 2021 Santiago Piccinini - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-3.0 - ]]-- - 121 local ubus = require "ubus" - 121 local json = require 'luci.jsonc' - 121 local uci = require 'uci' - 121 local vouchera = require('voucher.vouchera') - 121 local utils = require('lime.utils') - 121 local config = require('lime.config') - 121 local portal = require('portal.portal') - - 121 vouchera.init() - - 121 local uci_cursor = config.get_uci_cursor() - - 121 local conn = ubus.connect() - 121 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function get_portal_config() - 11 local portal_config = portal.get_config() - 11 portal_config.status = 'ok' - 11 return utils.printJson(portal_config) - end - - local function set_portal_config(msg) - 22 local status, error_msg = portal.set_config(msg.activated, msg.with_vouchers) - 22 if not status then - 11 utils.printJson({ status = "error", message = error_msg }) - else - 11 utils.printJson({ status = "ok" }) - end - end - - local function get_portal_page_content(msg) -*****0 utils.printJson(portal.get_page_content()) - end - - local function set_portal_page_content(msg) - 22 portal.set_page_content( - 11 msg.title, - 11 msg.main_text, - 11 msg.logo, - 11 msg.link_title, - 11 msg.link_url, - msg.background_color - 11 ) - 11 utils.printJson({ status = 'ok'}) - end - - local function show_url(msg) -*****0 utils.printJson({ status = 'ok', url = uci_cursor:get("pirania", "base_config", "portal_url") }); - end - - local function change_url(msg) -*****0 local url = msg.url -*****0 uci_cursor:set("pirania", "base_config", "portal_url", url) -*****0 uci_cursor:commit("pirania") -*****0 utils.printJson({status = 'ok', url = url}); - end - - - local function add_vouchers(msg) - 44 local vouchers, errmsg = vouchera.create(msg.name, msg.qty, msg.duration_m, - 22 msg.activation_deadline, msg.permanent) - 22 if vouchers then - 22 return utils.printJson({ status = 'ok', vouchers = vouchers}) - else -*****0 return utils.printJson({ status = 'error', message = errmsg}) - end - end - - local function rename(msg) - 11 local voucher = vouchera.rename(msg.id, msg.name) - 11 return utils.printJson({ status = voucher and 'ok' or 'error' }) - end - - local function invalidate(msg) - 22 local voucher = vouchera.invalidate(msg.id) - 22 return utils.printJson({ status = voucher and 'ok' or 'error' }) - end - - - local function list_vouchers(msg) - 11 local vouchers = vouchera.list() - 11 return utils.printJson({ status = vouchers and 'ok' or 'error', vouchers = vouchers }) - end - - 121 local methods = { - 121 get_portal_config = { no_params = 0 }, - 121 set_portal_config = { activated = 'bool', with_vouchers = 'bool' }, - 121 disable = { no_params = 0 }, - 121 show_url = { no_params = 0 }, - 121 change_url = { url = 'value' }, - 121 add_vouchers = { name='str', qty='int', duration_m='int', activation_deadline='timestamp', permanent='bool'}, - 121 list_vouchers = { no_params = 0 }, - 121 rename = { id = 'str', name = 'str' }, - 121 invalidate = { id = 'str' }, - 121 get_portal_page_content = { no_params = 0 }, - 121 set_portal_page_content = { - 121 title = 'value', - 121 main_text = 'value', - 121 logo = 'value', - 121 link_title = 'value', - 121 link_url = 'value', - 121 background_color = 'value' - 121 }, - } - - 121 if arg[1] == 'list' then - 11 utils.printJson(methods) - end - - 121 if arg[1] == 'call' then - 110 local msg = utils.rpcd_readline() - 110 msg = json.parse(msg) - 110 if arg[2] == 'get_portal_config' then get_portal_config(msg) - 99 elseif arg[2] == 'set_portal_config' then set_portal_config(msg) - 77 elseif arg[2] == 'disable' then disable(msg) - 77 elseif arg[2] == 'show_url' then show_url(msg) - 77 elseif arg[2] == 'change_url' then change_url(msg) - 77 elseif arg[2] == 'list_vouchers' then list_vouchers(msg) - 66 elseif arg[2] == 'add_vouchers' then add_vouchers(msg) - 44 elseif arg[2] == 'invalidate' then invalidate(msg) - 22 elseif arg[2] == 'rename' then rename(msg) - 11 elseif arg[2] == 'get_portal_page_content' then get_portal_page_content(msg) - 11 elseif arg[2] == 'set_portal_page_content' then set_portal_page_content(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/pirania/files/www/pirania-redirect/redirect -============================================================================== - #!/usr/bin/lua - 22 local utils = require('voucher.utils') - 22 local portal = require('portal.portal') - 22 local config = require('lime.config') - - - 22 local uci_cursor = config.get_uci_cursor() - - 22 function handle_request (env) - 22 local method = 'http://' - 22 local origin_url = utils.urlencode(method .. env.HTTP_HOST .. env.REQUEST_URI) - 22 local portal_domain = uci_cursor:get("pirania", "base_config", "portal_domain") - 22 local with_vouchers = portal.get_config().with_vouchers - 22 local redirect_path = '' - 22 if with_vouchers then - 11 redirect_path = uci_cursor:get("pirania", "base_config", "url_auth") - else - 11 redirect_path = uci_cursor:get("pirania", "read_for_access", "url_portal") - end - 22 local redirect_url = method .. portal_domain .. redirect_path .. "?prev=" .. origin_url - - 22 uhttpd.send("Status: 302 \r\n") - 22 uhttpd.send("Location: " .. redirect_url .. "\r\n") - 22 uhttpd.send("\r\n") --! indicate uhttpd to send the response - end - -============================================================================== -packages/safe-upgrade/files/usr/sbin/safe-upgrade -============================================================================== - #!/usr/bin/lua - --[[ - Copyright (C) 2019-2020 Santiago Piccinini - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - ]]-- - - 14 local io = require "io" - 14 local json = require "luci.jsonc" - 14 local utils = require "lime.utils" - - 14 local su = {} - - 14 su.version = '1.0' - 14 local firmware_size_bytes = 7936*1024 - -- Keep the fw addresses as strings beacause of https://gitlab.com/librerouter/librerouteros/-/issues/61 - 14 local fw1_addr = '0x9f050000' - 14 local fw2_addr = '0x9f810000' -- fw1_addr + firmware_size_bytes - - 14 su._supported_devices = {'librerouter-v1'} - 14 su._mtd_partitions_desc = '/proc/mtd' - - -- safe upgrade script, generated with bootscript.py, DO NOT edit here! - 14 local bootcmd = 'run preboot; boot_part=${stable_part}; if test ${testing_part} -ne 0; then echo Testing part ${testing_part}; boot_part=${testing_part}; set testing_part 0; saveenv; fi; if test ${boot_part} -eq 2; then fw_addr=${fw2_addr}; run boot_2; else fw_addr=${fw1_addr}; run boot_1; fi; run boot_1; bootm ${fw1_addr};' - - 14 su.STABLE_PARTITION_NAME = 'stable_part' - 14 su.TESTING_PARTITION_NAME = 'testing_part' - - 14 su.EXIT_ERROR_OK = 0 - 14 su.EXIT_STATUS_INVALID_FIRMWARE = 111 - 14 su.EXIT_STATUS_ALREADY_CONFIRMED = 113 - 14 su.EXIT_STATUS_FW_ENV_NOT_FOUND = 115 - 14 su.EXIT_STATUS_BOARD_NOT_SUPPORTED = 198 - 14 su.EXIT_STATUS_NOT_INSTALLED = 199 - 14 su.EXIT_STATUS_INSTALL_FROM_PART2 = 120 - 14 su.EXIT_STATUS_ALREADY_INSTALLED = 121 - 14 su.REBOOT_FILE_CONFIG_TIMEOUT_S = "/etc/safe_upgrade_auto_reboot_confirm_timeout_s" - - local safe_upgrade_auto_reboot_script = [[#!/bin/sh /etc/rc.common - - REBOOT_FILE_CONFIG_TIMEOUT_S="/etc/safe_upgrade_auto_reboot_confirm_timeout_s" - MINIMUM_REBOOT_TIMEOUT_S=60 - PIDFILE="/var/run/safe_upgrade_auto_reboot_script.pid" - CMD_FORCE_REBOOT="echo b > /proc/sysrq-trigger" # Immediately reboot the system without syncing or unmounting disks. - - START=11 - - start() { - if [ -s "$REBOOT_FILE_CONFIG_TIMEOUT_S" ]; then - read reboot_at_uptime_s < "$REBOOT_FILE_CONFIG_TIMEOUT_S" - else - exit 0 - fi - - # check that the reboot time is above the minimum to prevent infinite reboots - if [ "$reboot_at_uptime_s" -lt "$MINIMUM_REBOOT_TIMEOUT_S" ]; then - echo "safe-upgrade reboot: Less than minimum timeout! aborting" - exit 0 - fi - - (sleep "$reboot_at_uptime_s" && \ - if [ -s "$REBOOT_FILE_CONFIG_TIMEOUT_S" ]; then - reboot ; sleep 10 ; eval "$CMD_FORCE_REBOOT" - fi - ) & - - echo $! > "$PIDFILE" - } - - stop() { - rm "$REBOOT_FILE_CONFIG_TIMEOUT_S" - sync - kill -9 `cat "$PIDFILE"` - } - 14 ]] - - 14 function su.get_uboot_env(key) -*****0 local value = utils.unsafe_shell("fw_printenv -n " .. key .. " 2>&1") - -*****0 if value:find('## Error:') == nil then - -- remove EOL -*****0 local value = value:sub(1, -2) -*****0 return value - else -*****0 return nil - end - end - - local function set_uboot_env(key, value) -*****0 print("DEBUG: setting key:" .. key) -*****0 print("DEBUG: value:" .. value) -*****0 utils.unsafe_shell("fw_setenv " .. key .. " '" .. value .. "'") - end - - local function fw_env_configured() -*****0 return utils.file_exists('/etc/fw_env.config') - end - - local function assert_fw_env_configured() -*****0 if not fw_env_configured() then -*****0 print('/etc/fw_env.confg does not exist, aborting') -*****0 os.exit(su.EXIT_STATUS_FW_ENV_NOT_FOUND) - end - end - - local function get_current_cmdline() -*****0 return utils.read_file('/proc/cmdline') - end - - 14 function su.get_current_partition() - 22 local data = utils.read_file(su._mtd_partitions_desc) - 22 if data:find("fw2") == nil then - 11 return 2 - else - 11 return 1 - end - end - - 14 function su.get_partitions() - 22 local p = {} - 22 p.current = su.get_current_partition() - 22 if p.current == 1 then - 11 p.other = 2 - else - 11 p.other = 1 - end - 22 p.stable = tonumber(su.get_uboot_env(su.STABLE_PARTITION_NAME)) - 22 p.testing = tonumber(su.get_uboot_env(su.TESTING_PARTITION_NAME)) - - 22 return p - end - - local function get_su_version() -*****0 return su.get_uboot_env('su_version') - end - - local function is_su_installed() -*****0 return get_su_version() ~= nil - end - - local function set_testing_partition(partition) -*****0 set_uboot_env(su.TESTING_PARTITION_NAME, tostring(partition)) - end - - local function set_stable_partition(partition) -*****0 set_uboot_env(su.STABLE_PARTITION_NAME, tostring(partition)) - end - - local function assert_su_installed() -*****0 if not is_su_installed() then -*****0 print('safe-upgrade is not installed, aborting') -*****0 os.exit(su.EXIT_STATUS_NOT_INSTALLED) - end - end - - local function read_fw_metadata(path) -*****0 local handle = io.popen("fwtool -i - " .. path) -*****0 local metadata = json.parse(handle:read("*a")) -*****0 handle:close() -*****0 return metadata - end - - 14 function su.get_current_device() -*****0 return utils.read_file("/tmp/sysinfo/board_name"):gsub("\n","") - end - - 14 function su.get_supported_devices() - 88 return su._supported_devices - end - - 14 function su.is_current_board_supported() - 44 local current_device = su.get_current_device() - 99 for _, supported_device in pairs(su.get_supported_devices()) do - 77 if string.find(current_device, utils.literalize(supported_device)) then - 22 return true - end - end - 22 return false - end - - 14 function su.is_firmware_valid(metadata) - 132 local current_device = su.get_current_device() - 132 if metadata ~= nil then - 110 local fw_devices = metadata['supported_devices'] - 110 if fw_devices ~= nil then - 154 for _, fw_device in pairs(fw_devices) do - 154 for _, supported_device in pairs(su.get_supported_devices()) do - --! Check that the firmware is supported by safe upgrade - --! and that the current board is among the firmware devices - 88 if string.find(fw_device, utils.literalize(supported_device)) and - 44 string.find(fw_device, utils.literalize(current_device)) then - 22 return true - end - end - end - end - end - 110 return false - end - - 14 function su.preserve_files_to_new_partition(args) - 33 os.execute("rm -rf /tmp/_to_sysupgradetgz/") - 33 os.execute("mkdir -p /tmp/_to_sysupgradetgz/etc/init.d/") - 33 os.execute("mkdir -p /tmp/_to_sysupgradetgz/etc/rc.d/") - 33 local f = io.open("/tmp/_to_sysupgradetgz/etc/init.d/safe_upgrade_auto_reboot", "w") - 33 f:write(safe_upgrade_auto_reboot_script) - 33 f:close() - - 33 os.execute("chmod +x /tmp/_to_sysupgradetgz/etc/init.d/safe_upgrade_auto_reboot") - 33 os.execute("ln -s ../init.d/safe_upgrade_auto_reboot /tmp/_to_sysupgradetgz/etc/rc.d/S11safe_upgrade_auto_reboot") - - 33 if not args.disable_reboot_safety then - 33 local f = io.open("/tmp/_to_sysupgradetgz/etc/safe_upgrade_auto_reboot_confirm_timeout_s", "w") - 33 f:write(args.reboot_safety_timeout) - 33 f:close() - end - - 33 if args.do_not_preserve_config then - 11 utils.log('Not preserving config.') - 22 elseif args.preserve_archive then - 11 utils.log('Preserving from archive') - 11 os.execute("tar xfz " .. args.preserve_archive .. " -C /tmp/_to_sysupgradetgz/") - else - 11 utils.log('Preserving libremesh minimal config.') - 11 local files = "" - 33 for _, file_name in pairs(utils.keep_on_upgrade_files()) do - 22 if utils.file_exists(file_name) then - 11 files = files .. " " .. file_name - end - end - 11 if files ~= '' then - --! using an intermediate tar file for simplicity - 11 os.execute("tar cf /tmp/_safe_upgrade_intermadiate.tar " .. files .. " 2> /dev/null") - 11 os.execute("tar xf /tmp/_safe_upgrade_intermadiate.tar -C /tmp/_to_sysupgradetgz/") - end - end - - 33 os.execute('find /tmp/_to_sysupgradetgz/ -type f -o -type l | sed "s|^\/tmp\/_to_sysupgradetgz\/||" | sort -u > /tmp/_to_persist') - 33 os.execute("tar cfz /tmp/sysupgrade.tgz -C /tmp/_to_sysupgradetgz/ -T /tmp/_to_persist 2>/dev/null") - - 33 utils.log('List of files that are being preserved:') - 33 local file_list = {} - 33 local out = io.popen("tar tfz /tmp/sysupgrade.tgz") - 154 for line in out:lines() do - --! skip directories - 121 if not utils.stringEnds(line, '/') then - 121 table.insert(file_list, line) - 121 utils.log('\t' .. line) - end - end - 33 out:close() - 33 return file_list - end - - local function bootstrap(args) -*****0 if is_su_installed() then -*****0 if args.force then -*****0 print("Forcing the bootstrap.") - else -*****0 print(string.format("safe-upgrade version '%s' is already installed, aborting", -*****0 get_su_version())) -*****0 os.exit(su.EXIT_STATUS_ALREADY_INSTALLED) - end - end - - -*****0 if su.get_current_partition() ~= 1 then -*****0 print("installing safe-upgrade from partition 2 is not supported yet") -*****0 os.exit(su.EXIT_STATUS_INSTALL_FROM_PART2) - end - -*****0 set_stable_partition(1) -*****0 set_testing_partition(0) -*****0 set_uboot_env('fw1_addr', fw1_addr) -*****0 set_uboot_env('fw2_addr', fw2_addr) - - -- configure cmdline using the current cmdline config to not force - -- us to know here the correct cmdline bootargs of the running kernel -*****0 local boot_1 = 'set bootargs ' .. get_current_cmdline() .. '; echo booting part 1; bootm ${fw_addr};' -*****0 set_uboot_env('boot_1', boot_1) -*****0 set_uboot_env('su_version', su.version) - - -- installing the script. Everything must be installed before this! -*****0 set_uboot_env('bootcmd', bootcmd) -*****0 print('succesfully bootstraped safe-upgrade') - end - - local function _verify(firmware) -*****0 local fw_metadata = read_fw_metadata(firmware) -*****0 local fw_valid = fw_metadata ~= nil and su.is_firmware_valid(fw_metadata) -*****0 return fw_valid - end - - local function verify(args) -*****0 if _verify(args.firmware) then -*****0 os.exit(su.EXIT_STATUS_OK) - else -*****0 print("Invalid firmware!") -*****0 os.exit(su.EXIT_STATUS_INVALID_FIRMWARE) - end - end - - local function upgrade(args) -*****0 assert_su_installed() -*****0 local partitions = su.get_partitions() - -*****0 if not _verify(args.firmware) then -*****0 print("Invalid firmware!") -*****0 if args.force then -*****0 print("Forcing upgrade to continue as requested.") - else -*****0 os.exit(su.EXIT_STATUS_INVALID_FIRMWARE) - end - end - -*****0 su.preserve_files_to_new_partition(args) - - -- postpone 10m defarable-reboot -*****0 os.execute("awk '{print $1 + 600}' /proc/uptime > /tmp/deferrable-reboot.defer") - -*****0 print(string.format("erasing partition %d", partitions.other)) -*****0 os.execute(string.format("mtd erase fw%d", partitions.other)) - - -- It is important that the mtd -j option to preserve a file is used - -- with the file /tmp/sysupgrade.tgz because there are hooks in place - -- to unpack this tar and install the files at boot -*****0 print(string.format("writing partition %d", partitions.other)) -*****0 os.execute(string.format("mtd -j /tmp/sysupgrade.tgz write '%s' fw%d", -*****0 args.firmware, partitions.other)) - - -- TODO: load bootargs from acompaning image, here is hardcoded!! -*****0 local fw_mtd_str = '' -*****0 if partitions.other == 2 then -*****0 fw_mtd_str = '7936k(fw1),7936k(firmware)' - else -*****0 fw_mtd_str = '7936k(firmware),7936k(fw2)' - end -*****0 local boot_script_tpl = 'set bootargs console=ttyS0,115200 board=LIBREROUTERV1 mtdparts=spi0.0:256k(u-boot),64k(u-boot-env),%s,128k(res),64k(art); echo booting part %d; bootm ${fw_addr};' -*****0 local boot_script = string.format(boot_script_tpl, fw_mtd_str, partitions.other) -*****0 set_uboot_env(string.format('boot_%d', partitions.other), boot_script) -*****0 set_testing_partition(partitions.other) - -*****0 if not args.no_reboot then -*****0 print("Rebooting into the new firmware. Do the confirm step if everything is ok.") -*****0 os.execute("reboot") - end - end - - local function confirm(args) -*****0 assert_su_installed() -*****0 local partitions = su.get_partitions() -*****0 if partitions.current == partitions.stable then -*****0 print(string.format('the current partition: %d is already the stable partition, aborting', partitions.current)) -*****0 os.exit(su.EXIT_STATUS_ALREADY_CONFIRMED) - end - -*****0 print("Canceling and disabling automatic reboot") -*****0 os.execute("/etc/init.d/safe_upgrade_auto_reboot stop") -*****0 os.execute("/etc/init.d/safe_upgrade_auto_reboot disable") - -*****0 set_stable_partition(partitions.current) -*****0 print(string.format('Confirmed partition %d as stable partition', partitions.current)) - end - - local function test_other_partition(args) -*****0 assert_su_installed() -*****0 local partitions = su.get_partitions() -*****0 set_testing_partition(partitions.other) -*****0 print(string.format('Next boot will run partition: %d. You may confirm it if you like after reboot.', partitions.other)) - end - - - local function parse_args() - local function validate_file_exists(filename) -*****0 if utils.file_exists(filename) then -*****0 return filename - else -*****0 return nil, string.format("file %q does not exists", filename) - end - end - - local function validate_safety_timeout(value) -*****0 local timeout = tonumber(value) -*****0 if timeout == nil then -*****0 return nil, string.format("invalid --reboot-safety-timeout value: %q", value) - end -*****0 if timeout < 60 then -*****0 return nil, string.format("--reboot-safety-timeout must be greater than 60 but was %q", timeout) - end -*****0 return timeout - end - -*****0 local argparse = require 'argparse' -*****0 local parser = argparse('safe-upgrade', 'Safe upgrade mechanism for dual-boot systems') -*****0 parser:command_target('command') - -*****0 parser:command('show', 'Show the status of the system partitions.') - -*****0 local verify = parser:command('verify', 'Verify that the firmware is valid and can be installed.') -*****0 verify:argument("firmware", "firmware image (xxx-sysupgrade.bin)"):convert(validate_file_exists) - -*****0 local upgrade = parser:command('upgrade', 'Upgrade firmware in a non permanent way.') -*****0 upgrade:argument("firmware", "firmware image (xxx-sysupgrade.bin)"):convert(validate_file_exists) -*****0 upgrade:flag("-n --do-not-preserve-config", "Do not save configuration to the new partition") -*****0 upgrade:flag("--disable-reboot-safety", "Disable the automatic reboot safety mechanism") -*****0 upgrade:flag("--force", "Upgrade even if firmware is not valid") -*****0 upgrade:flag("--no-reboot", "Do not reboot automatically after flashing") -*****0 upgrade:option("--preserve-archive", [[Specify the files to be preserved as a tar.gz (Like the sysupgrade -f). -*****0 To preserve a full config you may use sysupgrade --create-backup and use this .tar.gz.]]) -*****0 :convert(validate_file_exists) -*****0 upgrade:option("--reboot-safety-timeout", -*****0 "Set the timeout (in seconds) of the automatic reboot safety mechanism") -*****0 :default('600'):convert(validate_safety_timeout) - -*****0 parser:command('confirm', ('Confirm the current partition. Use when after an upgrade ' .. -*****0 'or after running "test-other-partition".')) - -*****0 local bootstrap = parser:command('bootstrap', 'Install the safe-upgrade mechanism') -*****0 bootstrap:flag("--force", "Install even it is already installed.") -*****0 parser:command('test-other-partition', 'Mark the other partition as testing partition.') - -*****0 parser:command('board-supported', 'Exits with 0 if board is supported') -*****0 parser:command('confirm-remaining', ('Return the remaining seconds to confirm until the ' .. -*****0 'automatic reboot is done or -1 if not in a confirmable state.')) - -*****0 local args = parser:parse() - - -*****0 return args - end - - -- detect if this module is run as a library or as a script - 14 if pcall(debug.getlocal, 4, 1) then - -- Library mode - 14 return su - else - -- Main script mode - -*****0 local args = parse_args() -*****0 assert_fw_env_configured() -*****0 if args.bootstrap then -*****0 bootstrap(args) -*****0 elseif args.upgrade then -*****0 upgrade(args) -*****0 elseif args.confirm then -*****0 confirm(args) -*****0 elseif args.verify then -*****0 verify(args) -*****0 elseif args['test-other-partition'] then -*****0 test_other_partition(args) -*****0 elseif args['board-supported'] then -*****0 if su.is_current_board_supported() then -*****0 os.exit(su.EXIT_STATUS_OK) - else -*****0 print("This board is not supported") -*****0 os.exit(su.EXIT_STATUS_BOARD_NOT_SUPPORTED) - end -*****0 elseif args['confirm-remaining'] then -*****0 local out = "-1" -*****0 local f = io.open(su.REBOOT_FILE_CONFIG_TIMEOUT_S) -*****0 if f then -*****0 local total_time_s = tonumber(f:read('*l')) -*****0 if total_time_s then -*****0 out = string.format("%d", math.floor(total_time_s - utils.uptime_s())) - end -*****0 f:close() - end -*****0 io.write(out) -*****0 elseif args.show then -*****0 assert_su_installed() -*****0 print('safe-upgrade version: ' .. get_su_version()) -*****0 local partitions = su.get_partitions() - --TODO show labels of partitions (maybe store them when flashing from a metadata file) -*****0 print(string.format('current partition: %d', partitions.current)) -*****0 print(string.format('stable partition: %d', partitions.stable)) -*****0 print(string.format('testing partition: %d', partitions.testing)) - end - end - -============================================================================== -packages/shared-state-babel_links_info/files/usr/share/shared-state/publishers/shared-state-publish_babel_links_info -============================================================================== - #!/usr/bin/lua - - -- ! LibreMesh - -- ! Copyright (c) 2023 Javier Jorge - -- ! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial - -- ! Copyright (C) 2023 Asociación Civil Altermundi - -- ! - -- ! SPDX-License-Identifier: AGPL-3.0-only - - 11 local utils = require('lime.utils') - 11 local ubus = require "ubus" - 11 local shared_state_links_info = require ("shared_state_links_info") - - 11 local ifaceip = {} - - 11 function get_interface_ip(ifname) - 242 if ifaceip[ifname] == nil then - 198 ifaceip[ifname] = string.gsub(utils.unsafe_shell("ip -6 address show " - 66 .. ifname .. - 66 " | awk '{if ($1 == \"inet6\") print $2}' | grep fe80 | awk -F/ '{print $1}'"), - 132 "\n", "") - end - 242 return ifaceip[ifname] - end - - 11 function get_babel_links_info() - 22 local conn = ubus.connect() - 22 local links = {} - 22 babelneigt_obj = ubus.call(conn, "babeld", "get_neighbours", {}) - 22 if babelneigt_obj ~= nil then - 110 for key, value in pairs(babelneigt_obj.IPv6) do - 88 local key_table = {(string.gsub(get_interface_ip(value.dev),":","")),(string.gsub(key,":",""))} - 88 table.sort(key_table) - 88 links[table.concat(key_table)]= { - 88 src_ip = get_interface_ip(value.dev), - 88 dst_ip = key, - 88 iface = value.dev - 88 } - end - end - 22 return links - end - - 11 shared_state_links_info.insert_in_ss_with_location(get_babel_links_info(),"babel_links_info") - -============================================================================== -packages/shared-state-bat_hosts/files/usr/lib/lua/bat-hosts.lua -============================================================================== - 28 local JSON = require("luci.jsonc") - 28 local utils = require("lime.utils") - - 28 local bat_hosts = {} - - 28 function bat_hosts.bathost_deserialize(hostname_plus_iface) - 55 local partial_hostname = hostname_plus_iface - local iface - 330 for _, ifname in ipairs(utils.get_ifnames()) do - 275 local serialized_ifname = string.gsub(ifname, "%W", "_") - 275 serialized_ifname = utils.literalize(serialized_ifname) - 275 local replaced_hostname = hostname_plus_iface:gsub("_" .. serialized_ifname, "") - -- hostname don't have underscores see utils.is_valid_hostname - 275 replaced_hostname = replaced_hostname:gsub("_", "-") - 275 if #replaced_hostname < #partial_hostname then - 66 partial_hostname = replaced_hostname - 66 iface = ifname - end - end - 55 return partial_hostname, iface - end - - 28 function bat_hosts.get_bat_hosts_from_shared_state() -*****0 return JSON.parse( -*****0 io.popen("shared-state-async get bat-hosts 2> /dev/null", "r"):read("*all")) - end - - 28 function bat_hosts.get_bathost(mac, outgoing_iface) - 77 local bathosts = bat_hosts.get_bat_hosts_from_shared_state() - 77 local bathost = bathosts[mac:lower()] - 77 if bathost == nil then return end - 55 local hostname, iface = bat_hosts.bathost_deserialize(bathost) - 55 return { hostname = hostname, iface = iface } - end - - 28 return bat_hosts - -============================================================================== -packages/shared-state-bat_hosts/files/usr/libexec/rpcd/bat-hosts -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2013-2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2020 German Ferrero - ]]-- - - 44 local ubus = require "ubus" - 44 local json = require 'luci.jsonc' - 44 local utils = require 'lime.utils' - 44 local bat_hosts = require 'bat-hosts' - - 44 local conn = ubus.connect() - 44 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function get_bathost(msg) - 44 if not msg.mac or not utils.is_valid_mac(msg.mac) then - 11 utils.printJson({ status = "error", message = "invalid mac" }) - 11 return - end - - 33 if msg.outgoing_iface and not utils.has_value(utils.get_ifnames(), msg.outgoing_iface) then - 11 utils.printJson({ status = "error", message = "invalid outgoing interface" }) - 11 return - end - 22 local bathost = bat_hosts.get_bathost(msg.mac, msg.outgoing_iface) - 22 local result = {} - 22 if bathost.hostname ~= nil then - 22 result.status = "ok" - 22 result.bathost = bathost - else -*****0 result.status = "error" -*****0 result.error = "Couldn't retrieve hostname" - end - 22 utils.printJson(result) - end - - 44 local methods = { - 44 get_bathost = { mac = 'value', outgoing_iface = 'value'} - } - - 44 if arg[1] == 'list' then -*****0 utils.printJson(methods) - end - - 44 if arg[1] == 'call' then - 44 local msg = utils.rpcd_readline() or '{}' - 44 msg = json.parse(msg) - 44 if arg[2] == 'get_bathost' then get_bathost(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/shared-state-bat_links_info/files/usr/share/shared-state/publishers/shared-state-publish_bat_links_info -============================================================================== - #!/usr/bin/lua - - --! LibreMesh - --! Copyright (C) 2023 Javier Jorge - --! Copyright (C) 2023 Asociación Civil Altermundi - --! - --! SPDX-License-Identifier: AGPL-3.0-only - - 14 local JSON = require("luci.jsonc") - 14 local utils = require('lime.utils') - 14 local network = require ("lime.network") - 14 local shared_state_links_info = require ("shared_state_links_info") - - 14 function get_bat_links_info() - 28 local bat_neighbors_obj={} - 28 local bat_originators_obj={} - 28 local bat_originators = utils.unsafe_shell("batctl oj") - 28 bat_originators_obj = JSON.parse(bat_originators) - - - 28 local bat_neighbors = utils.unsafe_shell("batctl nj") - 28 bat_neighbors = string.gsub(bat_neighbors,"neigh_address","dst_mac") - 28 bat_neighbors = string.gsub(bat_neighbors,"hard_ifname","iface") - 28 bat_neighbors_obj = JSON.parse(bat_neighbors) - 28 local kv_fromlinks = {} - 28 if bat_neighbors_obj then - 140 for key,neight_value in pairs (bat_neighbors_obj) do - 112 local macparts = network.get_mac(neight_value.iface) - 112 local src_macaddr = table.concat(macparts,":") - 112 neight_value.hard_ifindex=nil - 112 neight_value.src_mac=string.lower(src_macaddr) - 112 neight_value.dst_mac=string.lower(neight_value.dst_mac) - 1904 for key,originator_value in pairs (bat_originators_obj) do - 1792 if originator_value.hard_ifname == neight_value.iface and - 896 originator_value.neigh_address== originator_value.orig_address and - 224 originator_value.neigh_address== neight_value.dst_mac then - -- Batman "transmit link quality" (tq) is a byte that describes - -- the probability of a successful transmission towards a - -- neighbor node - 112 neight_value.tq = originator_value.tq - end - end - 112 local key_table = {(string.gsub(neight_value.src_mac,":","")),(string.gsub(neight_value.dst_mac,":",""))} - 112 table.sort(key_table) - 112 kv_fromlinks[table.concat(key_table)]=neight_value - end - end - 28 return kv_fromlinks - end - - 14 shared_state_links_info.insert_in_ss_with_location(get_bat_links_info(),"bat_links_info") - -============================================================================== -packages/shared-state-network_nodes/files/usr/lib/lua/network-nodes.lua -============================================================================== - 11 local shared_state = require("shared-state") - 11 local utils = require("lime.utils") - 11 local config = require("lime.config") - - 11 local network_nodes = {} - - 11 function network_nodes._node(hostname, member, fw_version, board, ipv4, ipv6) - 286 return {hostname=hostname, member=member, fw_version=fw_version, board=board, ipv4=ipv4, ipv6=ipv6} - end - - 11 function network_nodes._serialize_for_network_nodes(node) - 110 return {hostname=node.hostname, member=node.member, fw_version=node.fw_version, board=node.board, - 110 ipv4=node.ipv4, ipv6=node.ipv6} - end - - 11 function network_nodes._deserialize_from_network_nodes(data) - 187 return network_nodes._node(data.hostname, data.member, data.fw_version, data.board, data.ipv4, data.ipv6) - end - - 11 function network_nodes._nodes_from_db(db) - 66 local nodes = {} - 242 for hostname, value in pairs(db:get()) do - 176 nodes[hostname] = network_nodes._deserialize_from_network_nodes(value.data) - end - 66 return nodes - end - - 11 function network_nodes._create_node() - 22 local uci = config.get_uci_cursor() - - 22 local hostname = utils.hostname() - 22 local fw_version = utils.release_info()['DISTRIB_RELEASE'] - 22 local board = utils.current_board() - 22 local member = true - 22 local ipv4 = uci:get("network", "lan", "ipaddr") - 22 local ipv6 = uci:get("network", "lan", "ip6addr") - 22 if ipv6 then ipv6 = ipv6:gsub("/.*$", "") end -- remove the netmask info - 22 local node = network_nodes._node(hostname, member, fw_version, board, ipv4, ipv6) - 22 node.status = "recently_reachable" - - 22 return node - end - - --! Public API - - 11 function network_nodes.get_nodes() - 44 local network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") - 44 local node_and_links_db = shared_state.SharedState:new("nodes_and_links") - - 44 local nodes = {} - -- augment the node information from the network_nodes and the 'nodes_and_links' dbs - 154 for hostname, node in pairs(network_nodes._nodes_from_db(network_nodes_db)) do - 110 if node_and_links_db:get()[hostname] then - 22 node.status = "recently_reachable" - 88 elseif node.member then - 66 node.status = "unreachable" - else - 22 node.status = "gone" - end - 110 nodes[hostname] = node - end - 44 return nodes - end - - 11 function network_nodes.as_human_readable_table() - 11 local nodes = network_nodes.get_nodes() - 11 local tmpl = "%-26s %-16s %-30s %-20s %-30s %-40s\n" - 11 local out = string.format(tmpl, "hostname", "ipv4", "ipv6", "status", "board", "fw_version") - 44 for _, node in pairs(nodes) do - 33 if node.member then - 66 out = out .. string.format(tmpl, node.hostname, node.ipv4, node.ipv6, node.status, - 66 node.board, node.fw_version) - end - end - 11 return out - end - - 11 function network_nodes.publish() - 11 local node = network_nodes._create_node() - 11 local data = { - 11 [node.hostname] = network_nodes._serialize_for_network_nodes(node) - } - 11 network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") - 11 network_nodes_db:insert(data) - end - - 11 function network_nodes.mark_nodes_as_gone(hostnames) - 11 local network_nodes_db = shared_state.SharedStateMultiWriter:new("network_nodes") - 11 local nodes = network_nodes._nodes_from_db(network_nodes_db) - 11 local data = {} - 33 for _, hostname in pairs(hostnames or {}) do - 22 local node = nodes[hostname] - 22 if node then - 22 node.member = false - 22 data[hostname] = network_nodes._serialize_for_network_nodes(node) - end - end - 11 network_nodes_db:insert(data) - end - - 11 return network_nodes - -============================================================================== -packages/shared-state-node_info/files/usr/share/shared-state/publishers/shared-state-publish_node_info -============================================================================== - #!/usr/bin/lua - - --! LibreMesh - --! Copyright (C) 2023 Javier Jorge - --! Copyright (C) 2023 Asociación Civil Altermundi - --! - --! This program is free software: you can redistribute it and/or modify - --! it under the terms of the GNU Affero General Public License as - --! published by the Free Software Foundation, either version 3 of the - --! License, or (at your option) any later version. - --! - --! This program is distributed in the hope that it will be useful, - --! but WITHOUT ANY WARRANTY; without even the implied warranty of - --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - --! GNU Affero General Public License for more details. - --! - --! You should have received a copy of the GNU Affero General Public License - --! along with this program. If not, see . - - 11 local JSON = require("luci.jsonc") - 11 local network = require ("lime.network") - 11 local location = require("lime.location") - 11 local utils = require('lime.utils') - 11 local config = require("lime.config") - - 11 local hostname = utils.hostname() - 11 function get_node_info() - 22 local uci = config.get_uci_cursor() - 22 local coords = location.get_node() or location.get_community() or - 22 {lat="FIXME", long="FIXME"} - 22 local fw_version = "no version" - 44 pcall(function () fw_version = utils.release_info()['DISTRIB_RELEASE'] end) - 22 local board = "no board" - 44 pcall(function () board = utils.current_board() end) - 22 local ipv4 = uci:get("network", "lan", "ipaddr") - 22 local ipv6 = uci:get("network", "lan", "ip6addr") - 22 if ipv6 then ipv6 = ipv6:gsub("/.*$", "") end - 22 local uptime = utils.uptime_s() - 22 local macs = network.get_own_macs("*") - 22 return {hostname=hostname, firmware_version=fw_version, board=board, - 22 ipv4=ipv4, ipv6=ipv6,coordinates=coords,macs= macs,uptime = uptime, - 22 device = "Router" } - end - - 11 local result = { [hostname] = get_node_info() } - 11 io.popen("shared-state-async insert node_info", "w"):write(JSON.stringify(result)) - -============================================================================== -packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases -============================================================================== - #!/usr/bin/lua - - 33 local JSON = require("luci.jsonc") - 33 local CRDT = "odhcpd-leases" - - - 33 local handle = io.popen("ubus call dhcp ipv4leases '{}' 2>/dev/null") - 33 local ubus_output = handle:read("*a") - 33 handle:close() - - - 33 local ubus_data = JSON.parse(ubus_output or "{}") - - 33 local output_table = {} - - - 33 if ubus_data and ubus_data.device then - - 8 for device_name, device_data in pairs(ubus_data.device) do - 4 if device_data and device_data.leases then - - 12 for _, lease in ipairs(device_data.leases) do - - 8 if lease.address and lease.mac then - - 8 output_table[lease.address] = { - 8 hostname = lease.hostname or "", - 8 mac = lease.mac - 8 } - end - end - end - end - end - - - 33 local final_json_string = JSON.stringify(output_table) - - - 33 local pipe = io.popen("shared-state-async insert " .. CRDT, "w") - 33 if pipe then - 33 pipe:write(final_json_string) - 33 pipe:close() - end - - - 33 os.execute("/usr/sbin/odhcpd-update >/dev/null 2>&1") - -============================================================================== -packages/shared-state-ref_state_commons/files/usr/lib/lua/shared_state_links_info.lua -============================================================================== - #!/usr/bin/lua - - --! LibreMesh - --! Copyright (c) 2024 Javier Jorge - --! Copyright (c) 2024 Instituto Nacional de Tecnología Industrial - --! Copyright (C) 2024 Asociación Civil Altermundi - --! SPDX-License-Identifier: AGPL-3.0-only - - 39 local JSON = require("luci.jsonc") - 39 local location = require 'lime.location' - - 39 local shared_state_links_info = {} - - 39 function shared_state_links_info.add_dst_loc(links_info, shared_state_sample, hostname) - 57 if shared_state_sample ~= nil then - 54 for link, l_data in pairs(links_info.links) do - 108 for node, data in pairs(shared_state_sample) do - 72 if node ~= hostname and data.links ~= nil then - 18 local link_data = data.links[link] - 18 if link_data ~= nil and data.src_loc~= nil then - 9 l_data.dst_loc = {} - 9 l_data.dst_loc.lat = data.src_loc.lat - 9 l_data.dst_loc.long = data.src_loc.long - end - end - end - end - end - end - - 39 function shared_state_links_info.add_own_location_to_links(links) - 76 return { - 76 links = links, - -- we are not interested in the community location. - 76 src_loc = location.get_node() or { - 76 lat = "FIXME", - 76 long = "FIXME" - 76 } - 76 } - end - - 39 function shared_state_links_info.insert_in_ss_with_location(links,data_type_name) - 39 local hostname = io.input("/proc/sys/kernel/hostname"):read("*line") - 39 local links_info = shared_state_links_info.add_own_location_to_links(links) - 39 local shared_state_sample = JSON.parse(io.popen("shared-state-async get "..data_type_name, "r"):read('*all')) - 39 shared_state_links_info.add_dst_loc(links_info, shared_state_sample, hostname) - 39 local result = {[hostname] = links_info} - 39 io.popen("shared-state-async insert "..data_type_name, "w"):write(JSON.stringify(result)) - end - - 39 return shared_state_links_info - -============================================================================== -packages/shared-state-wifi_links_info/files/usr/share/shared-state/publishers/shared-state-publish_wifi_links_info -============================================================================== - #!/usr/bin/lua - - --! LibreMesh - --! Copyright (C) 2019 Gioacchino Mazzurco - --! Copyright (c) 2023 Javier Jorge - --! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial - --! Copyright (C) 2023 Asociación Civil Altermundi - --! SPDX-License-Identifier: AGPL-3.0-only - - 14 local node_status = require("lime.node_status") - 14 local network = require("lime.network") - 14 local iwinfo = require ("iwinfo") - 14 local shared_state_links_info = require ("shared_state_links_info") - - 14 local data_type_name = "wifi_links_info" - - 14 function get_wifi_links_info() - 51 local stations = node_status.get_stations() - 51 local links = {} - 125 for _, station in ipairs(stations) do - 74 macparts = network.get_mac(station.iface) - 74 src_macaddr = string.lower(table.concat(macparts, ":")) - 74 local station_stats = node_status.get_station_stats(station) - 74 local freq = iwinfo.nl80211.frequency(station.iface) - 74 local chanenel = iwinfo.nl80211.channel(station.iface) - 74 local key_table = {string.lower(string.gsub(src_macaddr, ":", "")), - 74 string.lower(string.gsub(station.station_mac, ":", ""))} - 74 table.sort(key_table) - 74 links[table.concat(key_table)] = { - 74 src_mac = src_macaddr, - 74 dst_mac = string.lower(station.station_mac), - 74 signal = station_stats.signal, - 74 chains = station_stats.chains, - 74 iface = station.iface, - 74 rx_rate = station_stats.rx_rate, - 74 tx_rate = station_stats.tx_rate, - 74 freq = freq, - 74 channel = chanenel, - --dst_loc = {lat="FIXME",long="FIXME"} --if no location is found later no dst location will be informed - 74 } - end - 51 return links - end - - 14 shared_state_links_info.insert_in_ss_with_location(get_wifi_links_info(),data_type_name) - -============================================================================== -packages/shared-state/files/usr/lib/lua/shared-state.lua -============================================================================== - #!/usr/bin/lua - - --! Minimalistic CRDT-like shared state structure suitable for mesh networks - --! - --! Copyright (C) 2019-2020 Gioacchino Mazzurco - --! - --! This program is free software: you can redistribute it and/or modify - --! it under the terms of the GNU Affero General Public License version 3 as - --! published by the Free Software Foundation. - --! - --! This program is distributed in the hope that it will be useful, - --! but WITHOUT ANY WARRANTY; without even the implied warranty of - --! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - --! GNU Affero General Public License for more details. - --! - --! You should have received a copy of the GNU Affero General Public License - --! along with this program. If not, see . - - 110 local fs = require("nixio.fs") - 110 local JSON = require("luci.jsonc") - 110 local nixio = require("nixio") - 110 local uci = require("uci") - 110 local utils = require("lime.utils") - - 110 local shared_state = {} - 110 shared_state.DATA_DIR = '/var/shared-state/data/' - 110 shared_state.PERSISTENT_DATA_DIR = '/etc/shared-state/persistent-data/' - 110 shared_state.ERROR_LOCK_FAILED = 165 - 110 shared_state.CANDIDATE_NEIGHBORS_BIN = '/usr/bin/shared-state-get_candidates_neigh' - - 110 local SharedStateBase = {} - - 110 function SharedStateBase:load(mergeWithCurrentState) - 1004 local onDiskData = JSON.parse(self.storageFD:readall()) or {} - 1004 if mergeWithCurrentState then -*****0 self:_merge(onDiskData) - else - 1944 for key, value in pairs(onDiskData) do - 940 self.storage[key] = value - end - end - end - - 110 function SharedStateBase:lock(maxwait) - 1026 if self.locked then return end - 1026 maxwait = maxwait or 10 - 1026 fs.mkdirr(fs.dirname(self.dataFile)) - 2052 self.storageFD = nixio.open( - 2052 self.dataFile, nixio.open_flags("rdwr", "creat") ) - - 1026 for i=1,maxwait do - 1026 if not self.storageFD:lock("tlock") then -*****0 nixio.nanosleep(1) - else - 1026 self.locked = true - 1026 break - end - end - - 1026 if not self.locked then -*****0 self.log( "err", self.dataFile, "Failed acquiring lock on data!" ) -*****0 os.exit(shared_state.ERROR_LOCK_FAILED) - end - end - - 110 function SharedStateBase:merge(stateSlice) - 55 self:lock() - 55 self:load() - 55 self:_merge(stateSlice) - 55 self:save() - 55 self:unlock() - 55 self:notifyHooks() - end - - 110 function SharedStateBase:notifyHooks() - 454 if self.changed then - 454 local jsonString = self:toJsonString() - 454 if not fs.dir(self.hooksDir) then return end -*****0 for hook in fs.dir(self.hooksDir) do -*****0 local cStdin = io.popen(self.hooksDir.."/"..hook, "w") -*****0 cStdin:write(jsonString) -*****0 cStdin:close() - end - end - end - - 110 function SharedStateBase:save() - 487 if self.changed then - 487 local outFd = io.open(self.dataFile, "w") - 487 outFd:write(self:toJsonString()) - 487 outFd:close() - 487 outFd = nil - end - end - - 110 function SharedStateBase:httpRequest(url, body) -*****0 local tmpfname = os.tmpname() - -*****0 local tmpfd = io.open(tmpfname, "w") -*****0 tmpfd:write(body) -*****0 tmpfd:close() -*****0 tmpfd = nil - -*****0 local cmd = "uclient-fetch --no-check-certificate -q -O- --timeout=3 " -*****0 cmd = cmd.."--post-file='"..tmpfname.."' '"..url.."' ; " -*****0 cmd = cmd.."rm -f '"..tmpfname.."'" -*****0 local fd = io.popen(cmd) - -*****0 local value = fd:read("*a") -*****0 fd:close() - -*****0 return value - end - - 110 function SharedStateBase:_sync(urls) -*****0 urls = urls or {} - -*****0 if #urls < 1 then -*****0 local uci_cursor = uci:cursor() - local fixed_candidates = -*****0 uci_cursor:get("shared-state", "options","candidates") or {} -*****0 for _, line in pairs(fixed_candidates) do -*****0 table.insert( - urls, -*****0 line.."/"..self.dataType ) - end - -*****0 io.input(io.popen(shared_state.CANDIDATE_NEIGHBORS_BIN)) -*****0 for line in io.lines() do -*****0 table.insert( - urls, -*****0 self:getSyncUrl(line, self.dataType)) - end - end - -*****0 for _,url in ipairs(urls) do -*****0 local body = self:toJsonString() - -*****0 local response = self:httpRequest(url, body) - -*****0 if type(response) == "string" and response:len() > 1 then -*****0 local parsedJson = JSON.parse(response) -*****0 if parsedJson then self:_merge(parsedJson) end - else -*****0 self.log( "debug", "error requesting "..url ) - end - end - end - - 110 function SharedStateBase:sync(urls) -*****0 self:lock() -*****0 self:load() -*****0 self:unlock() -*****0 self:_sync(urls) -*****0 self:lock() -*****0 self:load(true) -- Take in account changes happened during sync -*****0 self:save() -*****0 self:unlock() -*****0 self:notifyHooks() - end - - 110 function SharedStateBase:toJsonString() - 941 return JSON.stringify(self.storage) - end - - 110 function SharedStateBase:get() - 517 self:lock() - 517 self:load() - 517 self:unlock() - 517 return self.storage - end - - 110 function SharedStateBase:unlock() - 1004 if not self.locked then return end - 1004 self.storageFD:lock("ulock") - 1004 self.storageFD:close() - 1004 self.storageFD = nil - 1004 self.locked = false - end - - 110 function createSharedStateBase(dataType, logger, dataFile) - 590 local logger = (type(logger) == "function") and logger or function() end - 546 local newInstance = { - 546 dataType = dataType, - 546 log = logger, - --! Map - --! bleachTTL is the count of how much bleaching should occur before the - --! entry expires - --! author is the name of the host who generated that entry - --! data is the value of the entry - 546 storage={}, - --! true if self_storage has changed after loading - 546 changed=false, - -- File descriptor of the persistent file storage - 546 storageFD=nil, - --! true when persistent storage file is locked by this instance - 546 locked=false, - 546 dataFile = dataFile, - 546 hooksDir = "/etc/shared-state/hooks/"..dataType.."/" - } - 546 return newInstance - end - - 110 local SharedState = {} - 110 setmetatable(SharedState, {__index = SharedStateBase}) - - 110 function SharedState:new(dataType, logger) - 204 local dataFile = shared_state.DATA_DIR..dataType..".json" - 204 local newInstance = createSharedStateBase(dataType, logger, dataFile) - 204 setmetatable(newInstance, {__index = SharedState}) - 204 return newInstance - end - - 110 function SharedState:_bleach() - 44 local substancialChange = false - 110 for k,v in pairs(self.storage) do - 66 if(v.bleachTTL < 2) then - 11 self.storage[k] = nil - 11 substancialChange = true - else - 55 v.bleachTTL = v.bleachTTL-1 - end - 66 self.changed = true - end - 44 return substancialChange - end - - 110 function SharedState:bleach() - 44 self:lock() - 44 self:load() - 44 local shouldNotify = self:_bleach() - 44 self:save() - 44 self:unlock() - --! Avoid hooks being called if data hasn't substantially changed - 44 if(shouldNotify) then self:notifyHooks() end - end - - 110 function SharedState:_insert(key, data, bleachTTL) - 209 bleachTTL = bleachTTL or 30 - 209 self.storage[key] = { - 209 bleachTTL=bleachTTL, - 209 author=io.input("/proc/sys/kernel/hostname"):read("*line"), - 209 data=data - 209 } - 209 self.changed = true - end - - 110 function SharedState:insert(data, bleachTTL) - 132 self:lock() - 132 self:load() - 330 for key, lv in pairs(data) do self:_insert(key, lv, bleachTTL) end - 132 self:save() - 132 self:unlock() - 132 self:notifyHooks() - end - - 110 function SharedState:_merge(stateSlice) - 11 local stateSlice = stateSlice or {} - 44 for key,rv in pairs(stateSlice) do - 33 if rv.bleachTTL <= 0 then -*****0 self.log( "debug", "sharedState:merge got expired entry" ) -*****0 self.changed = true - else - 33 local lv = self.storage[key] - 33 if( lv == nil or lv.bleachTTL < rv.bleachTTL ) then - 44 self.log( "debug", "Updating entry for: "..key.." older: ".. - 22 (lv and lv.bleachTTL or 'no entry').." newer: "..rv.bleachTTL ) - 22 self.storage[key] = rv - 22 self.changed = true - end - end - end - end - - 110 function SharedState:_remove(key) - 11 if(self.storage[key] ~= nil and self.storage[key].data ~= nil) - 11 then self:_insert(key, nil) end - end - - 110 function SharedState:remove(keys) - 11 self:lock() - 11 self:load() - 22 for _,key in ipairs(keys) do self:_remove(key) end - 11 self:save() - 11 self:unlock() - 11 self:notifyHooks() - end - - 110 function SharedState:getSyncUrl(host) -*****0 return "http://["..host.."]/cgi-bin/shared-state/"..self.dataType - end - - - 110 local SharedStateMultiWriter = {} - 110 setmetatable(SharedStateMultiWriter, {__index = SharedStateBase}) - - 110 function SharedStateMultiWriter:new(dataType, logger) - 342 local dataFile = shared_state.PERSISTENT_DATA_DIR..dataType..".json" - 342 local newInstance = createSharedStateBase(dataType, logger, dataFile) - 342 setmetatable(newInstance, {__index = SharedStateMultiWriter}) - 342 return newInstance - end - - - 110 function SharedStateMultiWriter:_merge(stateSlice) - --! Make merge based on an incremental counter (changes) and a random number (fortune) - 44 local stateSlice = stateSlice or {} - 88 for key,rv in pairs(stateSlice) do - 44 local lv = self.storage[key] - 44 if ( lv == nil or lv.changes < rv.changes or - 33 ( lv.changes == rv.changes and lv.fortune < rv.fortune )) then - 44 self.log( "debug", "Updating entry for: "..key.." older: ".. - 22 (lv and lv.changes or 'no entry') .." newer: "..rv.changes ) - 22 self.storage[key] = rv - 22 self.changed = true - end - end - end - - 110 function SharedStateMultiWriter:insert(data) - 245 self:lock() - 245 self:load() - 747 for key, lv in pairs(data) do self:_insert(key, lv) end - 245 self:save() - 245 self:unlock() - 245 self:notifyHooks() - end - - 110 function shared_state._getFortune() - 269 return math.random(1, 100000) - end - - 110 function SharedStateMultiWriter:_insert(key, data) - 502 local lv = self.storage[key] - 502 if (lv == nil or not utils.deepcompare(lv.data, data)) then - 478 local changes = lv and lv.changes + 1 or 0 - 478 self.storage[key] = { - 478 lastModified=os.time(), - 478 changes=changes, - 478 fortune=shared_state._getFortune(), - 478 author=io.input("/proc/sys/kernel/hostname"):read("*line"), - 478 data=data - 478 } - 478 self.changed = true - end - end - - 110 function SharedStateMultiWriter:getSyncUrl(host) -*****0 return "http://["..host.."]/cgi-bin/shared-state-multiwriter/"..self.dataType - end - - 110 shared_state.SharedState = SharedState - 110 shared_state.SharedStateMultiWriter = SharedStateMultiWriter - 110 return shared_state - -============================================================================== -packages/shared-state/files/usr/libexec/rpcd/shared-state -============================================================================== - #!/usr/bin/env lua - - --! Shared State - --! Copyright (c) 2023 Javier Jorge - --! Copyright (c) 2023 Instituto Nacional de Tecnología Industrial - --! Copyright (C) 2023 Asociación Civil Altermundi - --! SPDX-License-Identifier: AGPL-3.0-only - - 125 local ubus = require "ubus" - 125 local utils = require('lime.utils') - 125 local shared_state = require("shared-state") - 125 local json = require 'luci.jsonc' - - 125 require("nixio.util") - - 125 local response_template = {data = {}, error = 500} - - local function showData(sharedState) - 48 local resultTable = sharedState:get() - 48 if next(resultTable) == nill then - 6 response_template.error = 404 - else - 156 for k, v in pairs(resultTable) do - 114 response_template.data[k] = v.data - end - 42 response_template.error = 0 - end - 48 utils.printJson(response_template) - end - - local function getFromSharedState(msg) - 17 local sharedState = shared_state.SharedState:new(msg.data_type, - 6 nixio.syslog) - 6 showData(sharedState) - end - - local function getFromSharedStateMultiWriter(msg) - 99 local sharedState = shared_state.SharedStateMultiWriter:new(msg.data_type, - 42 nixio.syslog) - 42 showData(sharedState) - end - - local function insertIntoSharedStateMultiWriter(msg) - 82 local sharedState = shared_state.SharedStateMultiWriter:new(msg.data_type, - 36 nixio.syslog) - 36 local inputTable = msg.json or {} - 36 sharedState:insert(inputTable) - end - - 125 local methods = { - 125 getFromSharedState = { - 125 data_type = 'value' - 125 }, - 125 getFromSharedStateMultiWriter = { - 125 data_type = 'value' - 125 }, - 125 insertIntoSharedStateMultiWriter = { - 125 data_type = 'value', - 125 json = 'value' - 125 } - } - - 125 if arg[1] == 'list' then - 11 utils.printJson(methods) - end - - 125 if arg[1] == 'call' then - 114 local msg = utils.rpcd_readline() - 114 msg = json.parse(msg) - 114 if arg[2] == 'getFromSharedState' then - 11 getFromSharedState(msg) - 103 elseif arg[2] == 'getFromSharedStateMultiWriter' then - 57 getFromSharedStateMultiWriter(msg) - 46 elseif arg[2] == 'insertIntoSharedStateMultiWriter' then - 46 insertIntoSharedStateMultiWriter(msg) - else -*****0 utils.printJson({ -*****0 error = "Method not found" - }) - end - end - -============================================================================== -packages/ubus-lime-location/files/usr/lib/lua/lime/location.lua -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright 2017 Marcos Gutierrez - Copyright 2020 Santiago Piccinini - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-3.0 - ]]-- - - 61 local iwinfo = require "iwinfo" - 61 local json = require "luci.jsonc" - 61 local config = require "lime.config" - 61 local wireless = require "lime.wireless" - 61 local system = require "lime.system" - - 61 local location = {} - - 61 function location.is_valid_coordinate(value) - 287 return type(tonumber(value)) == "number" - end - - 61 function location.get_node() - 179 local uci = config.get_uci_cursor() - 179 local lat = uci:get("location", "settings", "node_latitude") - 179 local long = uci:get("location", "settings", "node_longitude") - - 179 if location.is_valid_coordinate(lat) and location.is_valid_coordinate(long) then - 23 return {lat=lat, long=long} - end - end - - 61 function location.get_community() - 68 local uci = config.get_uci_cursor() - 68 local lat = uci:get("location", "settings", "community_latitude") - 68 local long = uci:get("location", "settings", "community_longitude") - - 68 if location.is_valid_coordinate(lat) and location.is_valid_coordinate(long) then - 17 return {lat=lat, long=long} - end - end - - 61 function location.set(lat, long) - 12 local uci = config.get_uci_cursor() - 12 uci:set("location", "settings", "node_latitude", lat) - 12 uci:set("location", "settings", "node_longitude", long) - 12 uci:commit("location") - 12 local hostname = system.get_hostname() - 12 local data = {} - 12 data[hostname] = location.nodes_and_links() - 12 io.popen("shared-state insert nodes_and_links", "w"):write(json.stringify(data)) - end - - 61 function location.nodes_and_links() - 30 local hostname = io.input("/proc/sys/kernel/hostname"):read("*line") - 30 local macs = network.get_own_macs("wlan*") - - 30 local coords = location.get_node() or location.get_community() or {lat="FIXME", long="FIXME"} - local iface, currneigh, _, n - - 30 local interfaces = wireless.mesh_ifaces() - 30 local links = {} - 42 for _, iface in pairs(interfaces) do - 12 currneigh = iwinfo.nl80211.assoclist(iface) - 18 for mac, station in pairs(currneigh) do - 6 table.insert(links, string.lower(mac)) - end - end - 30 return {hostname=hostname, macs=macs, coordinates={lat=coords.lat, lon=coords.long}, links=links} - end - - 61 return location - -============================================================================== -packages/ubus-lime-location/files/usr/libexec/rpcd/lime-location -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright 2017 Marcos Gutierrez - Copyright 2020 Santiago Piccinini - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-3.0 - ]]-- - - - 61 local ubus = require "ubus" - 61 local iwinfo = require "iwinfo" - 61 local json = require "luci.jsonc" - 61 local config = require("lime.config") - 61 local wireless = require "lime.wireless" - 61 local network = require 'lime.network' - 61 local utils = require 'lime.utils' - 61 local location = require 'lime.location' - - - 61 local conn = ubus.connect() - 61 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function _get_location(msg) - 33 local result = {location = {}, default = false} - - 33 local coords = location.get_node() - 33 if not coords then - 22 result.default = true - 22 coords = location.get_community() or {lat="FIXME", long="FIXME"} - end - - 33 result.location.lat = coords.lat - 33 result.location.lon = coords.long - 33 result.status = "ok" - 33 return result - end - - local function get_location(msg) - 33 utils.printJson(_get_location(msg)) - end - - local function set_location(msg) - 11 location.set(msg.lat, msg.lon) - 6 utils.printJson({ lat = msg.lat, lon = msg.lon, status = 'ok' }); - end - - local function nodes_and_links() - 6 local hostname = utils.hostname() - 6 local result = {} - 6 result[hostname] = location.nodes_and_links() - 6 utils.printJson(result) - end - - 61 function all_nodes_and_links(msg) -*****0 print('{"result":'..utils.unsafe_shell("shared-state get nodes_and_links")..'}') - end - - 61 local methods = { - 61 all_nodes_and_links = { no_params = 0}, - 61 nodes_and_links = { no_params = 0}, - 61 get = { no_params = 0 }, - 61 set = { lat = 'value', lon = 'value' } - } - - 61 if arg[1] == 'list' then - 11 utils.printJson(methods) - end - - 61 if arg[1] == 'call' then - 50 local msg = utils.rpcd_readline() - 50 msg = json.parse(msg) - 50 if arg[2] == 'get' then get_location(msg) - 17 elseif arg[2] == 'set' then set_location(msg) - 6 elseif arg[2] == 'nodes_and_links' then nodes_and_links(msg) -*****0 elseif arg[2] == 'all_nodes_and_links' then all_nodes_and_links(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics.lua -============================================================================== - #!/usr/bin/lua - - 14 local utils = require('lime-metrics.utils') - 14 local lutils = require("lime.utils") - 14 local json = require 'luci.jsonc' - - 14 local metrics = {} - - 14 function metrics.get_last_internet_path_filename() - 14 return "/etc/last_internet_path" - end - - 14 function metrics.get_metrics(target) - 28 local result = {} - 28 local node = target - 28 local loss = nil - 28 local shell_output = "" - - 28 if lutils.is_installed("lime-proto-bmx6") then -*****0 loss = utils.get_loss(node..".mesh") -*****0 shell_output = lutils.unsafe_shell("netperf -6 -l 10 -H "..node..".mesh| tail -n1| awk '{ print $5 }'") - 28 elseif lutils.is_installed("lime-proto-babeld") then - 14 loss = utils.get_loss(node) - 14 shell_output = lutils.unsafe_shell("netperf -l 10 -H "..node.."| tail -n1| awk '{ print $5 }'") - else - 14 return {status="error", error={msg="No lime-proto-bmx6 or lime-proto-babeld found", code="1"}} - end - 14 local bw = 0 - 14 if shell_output ~= "" and shell_output ~= nil then - 5 bw = shell_output:match("[%d.]+") - end - 14 result.loss = loss - 14 result.bandwidth = bw - 14 result.status = "ok" - 14 return result - end - - 14 function metrics.get_loss(target) -*****0 local result = {} -*****0 local node = target - local loss = nil - -*****0 loss = utils.get_loss(node) -*****0 result.loss = loss -*****0 result.status = "ok" -*****0 return result - end - - - 14 function metrics.get_gateway() - 28 local result = {} - 28 local gw = nil - - 28 local internet_path_file = io.open(metrics.get_last_internet_path_filename(), "r") - 28 if internet_path_file then - 28 local path_content = assert(internet_path_file:read("*a"), nil) - 28 internet_path_file:close() - 28 path = json.parse(path_content) or nil - 28 if lutils.tableLength(path) > 0 then - 9 return { status="ok", gateway=path[lutils.tableLength(path)] } - end - end - - 9 return {status="error", error={msg="Not found. No gateway available.", code="1"}} - end - - 14 function metrics.get_last_internet_path(msg) - 28 local internet_path_file = io.open(metrics.get_last_internet_path_filename(), "r") - 28 if internet_path_file then - 14 path_content = assert(internet_path_file:read("*a"), nil) - 14 internet_path_file:close() - 14 path = json.parse(path_content) or nil - 14 local result = {} - 14 if path ~= nil then - 9 result.path = path - 9 result.status = "ok" - 9 return result - end - else - 14 return {status="error", error={msg="Not found. No known Internet path.", code="1"}} - end - end - - 14 function metrics.get_internet_status( ) - 14 local result = {} - 14 local lossV4 = utils.get_loss("4.2.2.2") - 14 if lossV4 ~= "100" then - 14 result.IPv4 = { working=true } - else -*****0 result.IPv4 = { working=false } - end - - 14 local lossV6 = utils.get_loss("2600::") - 14 if lossV6 ~= "100" then - 14 result.IPv6 = { working=true } - else -*****0 result.IPv6 = { working=false } - end - 14 local lookup_output = utils.is_nslookup_working() - 14 if lookup_output ~= "" then - 14 result.DNS = { working=true } - else -*****0 result.DNS = { working=false } - end - 14 result.status = "ok" - 14 return result - end - - 14 function metrics.get_station_traffic(msg) - 28 local iface = msg.iface - 18 local mac = msg.station_mac - 18 local result = {} - 18 local traffic = lutils.unsafe_shell("iw "..iface.." station get "..mac.." | grep bytes | awk '{ print $3}'") - 18 if traffic == "" or traffic == nil then - 9 return {status="error", error={msg="No interface found.", code="1"}} - end - 9 words = {} - 27 for w in traffic:gmatch("[^\n]+") do table.insert(words, w) end - 9 rx = words[1] - 9 tx = words[2] - 9 result.station = mac - 9 result.rx_bytes = tonumber(rx, 10) - 9 result.tx_bytes = tonumber(tx, 10) - 9 result.status = "ok" - 9 return result - end - - - 14 return metrics - -============================================================================== -packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics/utils.lua -============================================================================== - 14 local lutils = require("lime.utils") - - 14 local utils = {} - - 14 function utils.is_nslookup_working() -*****0 local shell_output = lutils.unsafe_shell("nslookup google.com | grep Name -A2 | grep Address") -*****0 return shell_output - end - - 14 function utils.get_loss(host) - 14 local shell_output = lutils.unsafe_shell("ping -q -i 0.1 -c4 -w2 "..host) - 14 local loss = "100" - 14 if shell_output ~= "" and shell_output ~= nil then - 5 loss = shell_output:match("(%d*)%% packet loss") - end - 14 return loss - end - - 14 return utils - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime-utils-admin.lua -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2020 Santiago Piccinini - ]]-- - - - 6 local utils = require 'lime.utils' - 6 local config = require 'lime.config' - 6 local upgrade = require 'lime.upgrade' - 6 local hotspot_wwan = require "lime.hotspot_wwan" - - - 6 local UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" - - 6 local limeutilsadmin = {} - - 6 function limeutilsadmin.set_root_password(msg) - local result = nil - 6 if type(msg.password) ~= "string" then -*****0 result = {status = 'error', msg = 'Password must be a string'} - else - 6 utils.set_shared_root_password(msg.password or '') - 6 result = {status = 'ok'} - end - 6 return result - end - - 6 function limeutilsadmin.set_hostname(msg) - 18 if msg.hostname ~= nil and utils.is_valid_hostname(msg.hostname) then - 6 local uci = config.get_uci_cursor() - 6 uci:set(config.UCI_NODE_NAME, 'system', 'hostname', msg.hostname) - 6 uci:commit(config.UCI_NODE_NAME) - 6 utils.unsafe_shell("lime-config") - 6 return { status = 'ok'} - else - local err - 12 if msg.hostname then - 6 err = 'Invalid hostname' - else - 6 err = 'Hostname not provided' - end - 12 return { status = 'error', msg = err } - end - end - - 6 function limeutilsadmin.is_upgrade_confirm_supported() - 12 local supported = upgrade.is_upgrade_confirm_supported() - 12 return {status = 'ok', supported = supported} - end - - - 6 function limeutilsadmin.firmware_upgrade(msg) - 12 local status, ret = upgrade.firmware_upgrade(msg.fw_path, msg.preserve_config, msg.metadata, msg.fw_type) - 12 if status then - 12 return {status = 'ok', metadata = ret} - else -*****0 return {status = 'error', message = ret} - end - end - - 6 function limeutilsadmin.last_upgrade_metadata() - local metadata - 12 if utils.file_exists(UPGRADE_METADATA_FILE) then - 6 metadata = utils.read_obj_store(UPGRADE_METADATA_FILE) - 6 return {status = 'ok', metadata = metadata} - else - 6 return {status = 'error', message = 'No metadata available'} - end - end - - 6 function limeutilsadmin.firmware_confirm() - 6 local exit_code = os.execute("safe-upgrade confirm > /dev/null 2>&1") - 6 local status = 'error' - 6 if exit_code == 0 then - 6 status = 'ok' - end - 6 return {status = status, exit_code = exit_code} - end - - --! Creates a client connection to a wifi hotspot - 6 function limeutilsadmin.hotspot_wwan_enable(msg) - 18 local msg = msg or {} - 18 local status, errmsg = hotspot_wwan.safe_enable(msg.ssid, msg.password, msg.encryption, msg.radio) - 18 if status then - 18 return {status = 'ok'} - else -*****0 return {status = 'error', message = errmsg} - end - end - - - 6 function limeutilsadmin.hotspot_wwan_disable(msg) - 6 local msg = msg or {} - 6 local status, errmsg = hotspot_wwan.disable(msg.radio) - 6 if status then - 6 return {status = 'ok'} - else -*****0 return {status = 'error', message = errmsg} - end - end - - - 6 function limeutilsadmin.safe_reboot(msg) -*****0 local result = {} - local function getStatus() -*****0 local f = io.open('/overlay/upper/.etc.last-good.tgz', "rb") -*****0 if f then f:close() end -*****0 return f ~= nil - end - - -- Get safe-reboot status -*****0 if msg.action == nil then return {error = true} end -*****0 if msg.action == 'status' then result.status = getStatus() end - - -- Start safe-reboot -*****0 if msg.action == 'start' then -*****0 local args = '' -*****0 if msg.value ~= nil then -*****0 if msg.value.wait ~= nil then -*****0 args = args .. ' -w ' .. msg.value.wait - end -*****0 if msg.value.fallback ~= nil then -*****0 args = args .. ' -f ' .. msg.value.fallback - end - end -*****0 local sr = assert(io.popen('safe-reboot ' .. args)) -*****0 sr:close() -*****0 result.status = getStatus() -*****0 if result.status == true then result.started = true end - end - - -- Rreboot now and wait for fallback timeout -*****0 if msg.action == 'now' then -*****0 local sr = assert(io.popen('safe-reboot now')) -*****0 result.status = getStatus() -*****0 result.now = result.status - end - - -- Keep changes and stop safe-reboot -*****0 if msg.action == 'cancel' then -*****0 result.status = true -*****0 result.canceled = false -*****0 local sr = assert(io.popen('safe-reboot cancel')) -*****0 sr:close() -*****0 if getStatus() == false then -*****0 result.status = false -*****0 result.canceled = true - end - end - - -- Discard changes - Restore previous state and reboot -*****0 if msg.action == 'discard' then -*****0 local sr = assert(io.popen('safe-reboot discard')) -*****0 sr:close() -*****0 result.status = getStatus() -*****0 if result.status == true then result.started = true end - end - -*****0 return result - end - - 6 return limeutilsadmin - - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime-utils.lua -============================================================================== - #!/usr/bin/env lua - - -- Used on lime-utils ubus script - 11 local limewireless = require 'lime.wireless' - 11 local utils = require 'lime.utils' - 11 local upgrade = require 'lime.upgrade' - 11 local node_status = require 'lime.node_status' - 11 local hotspot_wwan = require "lime.hotspot_wwan" - 11 local ubus = require "ubus" - - 11 local conn = ubus.connect() - 11 if not conn then error("Failed to connect to ubus") end - - 11 local limeutils = {} - - 11 function limeutils.get_cloud_nodes() - 12 local nodes = utils.unsafe_shell( - 6 "cat /tmp/bat-hosts | grep bat0 | cut -d' ' -f2 | sed 's/_bat0//' | sed 's/_/-/g' | sort | uniq") - 6 local result = {} - 6 result.nodes = {} - 36 for line in nodes:gmatch("[^\n]*") do - 30 if line ~= "" then table.insert(result.nodes, line) end - end - 6 result.status = "ok" - 6 return result - end - - - 11 function limeutils.get_mesh_ifaces() -*****0 local result = {} -*****0 result.ifaces = limewireless.mesh_ifaces() -*****0 return result - end - - 11 function limeutils.get_node_status() - 12 local result = {} - 12 result.hostname = utils.hostname() - 12 result.ips = node_status.get_ips() - 12 result.most_active = node_status.get_most_active() - 12 result.switch_status = node_status.switch_status() - 12 result.uptime = tostring(utils.uptime_s()) - 12 result.status = "ok" - 12 return result - end - - 11 function limeutils.get_notes() - 18 local result = {} - 18 result.notes = utils.read_file('/etc/banner.notes') or '' - 18 result.status = "ok" - 18 return result - end - - 11 function limeutils.set_notes(msg) - 6 local banner = utils.write_file('/etc/banner.notes', msg.text) - 6 return limeutils.get_notes() - end - - 11 function limeutils.get_community_settings() -*****0 local config = conn:call("uci", "get", {config = "lime-app"}).values -*****0 if config ~= nil then -*****0 for name, value in pairs(config) do - -- TODO: Find a best way to remove uci keys -*****0 function table.removekey(table, key) -*****0 local element = table[key] -*****0 table[key] = nil -*****0 return element - end -*****0 table.removekey(value, ".name") -*****0 table.removekey(value, ".index") -*****0 table.removekey(value, ".anonymous") -*****0 table.removekey(value, ".type") -*****0 return value - end - else -*****0 return {error = "config not found"} - end - end - - -- todo(kon): move to utility class?? - 11 function limeutils.get_channels() -*****0 local devices = limewireless.scandevices() -*****0 local phys = {} -*****0 for k, radio in pairs(devices) do -*****0 local phyIndex = radio[".name"].sub(radio[".name"], -1) -*****0 phys[k] = {phy = "phy" .. phyIndex} -*****0 if limewireless.is5Ghz(radio[".name"]) then -*****0 phys[k].freq = '5ghz' - else -*****0 phys[k].freq = '2.4ghz' - end - end -*****0 local frequencies = {} -*****0 for _, phy in pairs(phys) do -*****0 local info = utils.unsafe_shell("iw " .. phy.phy .. -*****0 " info | sed -n '/Frequencies:/,/valid/p' | sed '1d;$d' | grep -v radar | grep -v disabled | sed -e 's/.*\\[\\(.*\\)\\].*/\\1/'") -*****0 frequencies[phy.freq] = utils.split(info, '\n') - end -*****0 return frequencies - end - - 11 function limeutils.get_config() -*****0 local result = conn:call("uci", "get", -*****0 {config = "lime-autogen", section = "wifi"}) -*****0 result.channels = limeutils.get_channels() -*****0 return result - end - - 11 function limeutils.get_upgrade_info() - 6 local result = upgrade.get_upgrade_info() - 6 if not result then return {status = "error"} end - 6 result.status = 'ok' - 6 return result - end - - - 11 function limeutils.hotspot_wwan_get_status(msg) - 18 local msg = msg or {} - 18 local status, errmsg = hotspot_wwan.status(msg.radio) - 18 if status then - 18 return { - 18 status = 'ok', - 18 enabled = status.enabled, - 18 connected = status.connected, - 18 signal = status.signal - 18 } - else -*****0 return {status = 'error', message = errmsg} - end - end - - 11 return limeutils - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime/hotspot_wwan.lua -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2021 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2021 Santiago Piccinini - ]]-- - - 33 local utils = require 'lime.utils' - 33 local config = require 'lime.config' - 33 local iwinfo = require "iwinfo" - 33 local wireless = require "lime.wireless" - - 33 local pkg = {} - - -- checkout ap_match_encryption for supported encryptions - 33 pkg.DEFAULT_ENCRYPTION = 'psk2' - 33 pkg.DEFAULT_SSID = 'internet' - 33 pkg.DEFAULT_PASSWORD = 'internet' - 33 pkg.DEFAULT_RADIO = 'radio0' - 33 pkg.GENERIC_SECTION_NAME = 'hotspot_wwan' - 33 pkg.IFACE_SECTION_NAME = 'lm_client_wwan' - 33 pkg.IFACE_NAME = 'client-wwan' - - 33 local gen_cfg = require 'lime.generic_config' - - 33 function pkg._apply_change() -*****0 utils.execute_daemonized("lime-config && wifi reload") - end - - --! Create a client connection to a wifi hotspot - 33 function pkg.enable(ssid, password, encryption, radio) - 12 local uci = config.get_uci_cursor() - 12 local encryption = encryption or pkg.DEFAULT_ENCRYPTION - 12 local ssid = ssid or pkg.DEFAULT_SSID - 12 local password = password or pkg.DEFAULT_PASSWORD - 12 local radio = radio or pkg.DEFAULT_RADIO - - 12 uci:set(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME, "generic_uci_config") - 24 uci:set(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME, "uci_set", { - 12 "wireless." .. radio .. ".disabled=0", - 12 "wireless." .. radio .. ".channel=auto", - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. "=wifi-iface", - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".device=" .. radio, - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".network=" .. pkg.IFACE_SECTION_NAME, - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".mode=sta", - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".ifname=" .. pkg.IFACE_NAME, - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".ssid=" .. ssid, - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".encryption=" .. encryption, - 12 "wireless." .. pkg.IFACE_SECTION_NAME .. ".key=" .. password, - 12 "network." .. pkg.IFACE_SECTION_NAME .. "=interface", - 12 "network." .. pkg.IFACE_SECTION_NAME .. ".proto=dhcp", - 12 } - ) - 12 uci:commit(config.UCI_NODE_NAME) - 12 pkg._apply_change() - 12 return true - end - - 33 function ap_match_encryption(ap, encryption) - 12 if encryption == 'psk2' then - 12 return ap.encryption.enabled and ap.encryption.wpa == 2 - end -*****0 return false - end - - 33 function pkg._is_safe(ssid, encryption, radio) - 42 local ifaces = wireless.get_radio_ifaces(radio) - 42 if utils.tableLength(ifaces) == 0 then - 24 return true - end - 30 for _, iface in pairs(ifaces) do - 18 if wireless.is_mesh(iface) then - 6 return false, 'radio has mesh ifaces' - end - end - 12 ifname = ifaces[1].ifname - 12 iface_type = iwinfo.type(ifname) - 12 if iface_type ~= nil then - 12 scanlist = iwinfo[iface_type].scanlist(ifname) - 18 for _, ap in pairs(scanlist) do - 12 if (ap.ssid == ssid and ap_match_encryption(ap, encryption)) then - 6 return true - end - end - end - 6 return false, 'hotspot ap not found' - end - - 33 function pkg.safe_enable(ssid, password, encryption, radio) - -- Enables the hotpost client only if the hotpost is already available - -- in order to avoid clients from ap interfaces to be kicked out. - 12 local encryption = encryption or pkg.DEFAULT_ENCRYPTION - 12 local ssid = ssid or pkg.DEFAULT_SSID - 12 local radio = radio or pkg.DEFAULT_RADIO - - 12 local is_safe, reason = pkg._is_safe(ssid, encryption, radio) - 12 if is_safe then - 12 return pkg.enable(ssid, password, encryption, radio) - else -*****0 return false, reason - end - end - - 33 function pkg.disable(radio) - 12 local uci = config.get_uci_cursor() - 12 local radio = radio or pkg.DEFAULT_RADIO - - 12 uci:delete(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME) - 12 uci:commit(config.UCI_NODE_NAME) - 12 uci:delete('system', 'hotspot_watchping') - 12 uci:commit('system') - 12 pkg._apply_change() - 12 return true - end - - 33 function pkg.status(radio) - 30 local uci = config.get_uci_cursor() - 30 local radio = radio or pkg.DEFAULT_RADIO - 30 local connected = false - local signal - - 30 local enabled = false - - 30 if uci:get(config.UCI_NODE_NAME, pkg.GENERIC_SECTION_NAME) then - 6 enabled = true - end - - 36 for mac, station in pairs(iwinfo.nl80211.assoclist(pkg.IFACE_NAME)) do - 6 connected = true - 6 signal = station['signal'] - end - - 30 return {connected = connected, signal = signal, enabled = enabled} - end - - - 33 return pkg - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime/node_status.lua -============================================================================== - 36 local limewireless = require 'lime.wireless' - 36 local iwinfo = require 'iwinfo' - 36 local utils = require 'lime.utils' - 36 local json = require("luci.jsonc") - - - -- Functions used by get_node_status - 36 local node_status = {} - - 36 function node_status.get_ips() - 12 local res = {} - 24 local ips = utils.unsafe_shell( - 12 "ip a s br-lan | grep inet | awk '{ print $1, $2 }'") - 12 for line in ips:gmatch("[^\n]+") do -*****0 local words = {} -*****0 for w in line:gmatch("%S+") do -*****0 if w ~= "" then table.insert(words, w) end - end -*****0 local version = words[1] -*****0 local address = words[2] -*****0 if version == "inet6" then -*****0 table.insert(res, {version = "6", address = address}) - else -*****0 table.insert(res, {version = "4", address = address}) - end - end - 12 return res - end - - 36 function node_status.get_stations() - 26 local res = {} - 26 local ifaces = limewireless.mesh_ifaces() - 26 for _, iface in ipairs(ifaces) do -*****0 local iface_type = iwinfo.type(iface) -*****0 local iface_stations = iface_type and iwinfo[iface_type].assoclist(iface) -*****0 if iface_stations then -*****0 for mac, station in pairs(iface_stations) do -*****0 station['iface'] = iface -*****0 station.station_mac = mac -*****0 table.insert(res, station) - end - end - end - 26 return res - end - - 36 function node_status.get_station_stats(station) - 86 local iface = station.iface - 86 local mac = station.station_mac - 172 local iw_result = utils.unsafe_shell( - 86 "iw " .. iface .. " station get " .. mac) - 172 station.rx_bytes = tonumber( - 172 string.match(iw_result, "rx bytes:%s+(.-)\n"), 10) - 172 station.tx_bytes = tonumber( - 172 string.match(iw_result, "tx bytes:%s+(.-)\n"), 10) - 86 local signal_str = string.match(iw_result, "signal:%s+(.-)\n") - 86 local signal, chain = string.match(signal_str, "(%-?%d+)%s+%[(.-)%]") - 86 station.signal = tonumber(signal) - 86 station.chains = {} - --[[ - succesive calls to this function will lead to an error - /usr/bin/lua: /usr/lib/lua/lime/node_status.lua:60: bad argument #1 to 'gmatch' (string expected, got nil) - stack traceback: - [C]: in function 'gmatch' - /usr/lib/lua/lime/node_status.lua:60: in function 'get_station_stats' - ...tate/publishers/shared-state-publish_wifi_links_info:32: in function 'get_wifi_links_info' - ...tate/publishers/shared-state-publish_wifi_links_info:45: in main chunk - [C]: ? - ]]-- - 86 if chain ~= nil then - 258 for num in string.gmatch(chain, "%-?%d+") do - 172 table.insert(station.chains, tonumber(num)) - end - end - 86 return station - end - - 36 function node_status.get_most_active() - 18 local res = {} - 18 local stations = node_status.get_stations() - 18 if next(stations) ~= nil then - 6 local most_active = {} - 6 most_active.rx_bytes = 0 - 18 for _, station in ipairs(stations) do - 12 local station_stats = node_status.get_station_stats(station) - 12 if station_stats.rx_bytes > most_active.rx_bytes then - 6 most_active = station - end - end - 6 res = most_active - end - 18 return res - end - - 36 function node_status.switch_status() - 67 local response_ports = node_status.boardjson_get_ports() - 67 if #response_ports ~= 0 then - 67 if utils.is_dsa() then -*****0 node_status.dsa_get_link_status(response_ports) - else - 67 node_status.swconfig_get_link_status(response_ports) - end - end - 67 return response_ports - end - - 36 function node_status.boardjson_get_ports() - 67 local response_ports = {} - 67 local board = utils.getBoardAsTable() - 67 if board['switch'] ~= nil and board['switch']['switch0'] ~= nil then -- legacy swconfig devices support - 201 for _, role in ipairs(board['switch']['switch0']['roles']) do - 438 for port_number in string.gmatch(role['ports'], "%S+") do - 304 if not tonumber(port_number) then - 134 local n = tonumber(string.match(port_number, "^%d+")) - 134 table.insert(response_ports, { num = n, role = "cpu", device = role['device']}) - else - 170 table.insert(response_ports, { num = tonumber(port_number), role = role['role'], device = role['device']}) - end - end - end -*****0 elseif board['network'] ~= nil then -- DSA devices support -*****0 for switch_name, switch in pairs(board['network']) do -*****0 if switch['ports'] ~= nil then -*****0 for _, port in ipairs(switch.ports) do -*****0 table.insert(response_ports, { num = port, role = switch_name, device = switch_name}) - end - else -*****0 table.insert(response_ports, { num = switch_name, role = switch_name, device = switch['device'] }) - end - end - end - 67 return response_ports - end - - 36 function node_status.dsa_get_link_status(ports) -*****0 for _, port in ipairs(ports) do -*****0 local dsa = utils.unsafe_shell("ip link show " .. port['num']) - -- Match ifindex, ifname, link (optional), and operstate -*****0 local ifindex, ifname, link, operstate = dsa:match("^(%d+): ([^:@]+)@?([^:]*):.-state (%S+)") -*****0 if ifindex and ifname and operstate then -*****0 port['device'] = port['num'] -*****0 port['num'] = tonumber(ifindex) -*****0 port['role'] = link ~= "" and link or nil -- Handle optional link field -*****0 if port['role'] == nil then -*****0 port['role'] = ifname - end -*****0 port['link'] = operstate -*****0 if operstate == "LOWERLAYERDOWN" then -*****0 port['link'] = "DOWN" - end - end - end -*****0 return ports - end - - - 36 function node_status.swconfig_get_link_status(ports) - local function add_link_status(port_number, status) - 336 for x, obj in pairs(ports) do - 294 if obj.num == port_number then - 42 obj["link"] = status - end - end - end - - 67 local swconfig = utils.unsafe_shell("swconfig dev switch0 show") - 67 local lines = {} - 535 for line in swconfig:gmatch("[^\r\n]+") do - 468 table.insert(lines, line) - end - - local port_number - 535 for i, line in ipairs(lines) do - 468 if line:match("Port %d:") then - 108 port_number = tonumber(line:match("Port (%d):")) - end - 468 if string.find(line, "link:up") then - 24 add_link_status(port_number, "up") - 444 elseif string.find(line, "link:down") then - 18 add_link_status(port_number, "down") - end - end - 67 return ports - end - - 36 return node_status - - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime/upgrade.lua -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2020 Santiago Piccinini - ]]-- - - 33 local json = require 'luci.jsonc' - 33 local utils = require 'lime.utils' - - 33 local pkg = {} - - 33 pkg.UPGRADE_INFO_CACHE_FILE = '/tmp/upgrade_info_cache' - - 33 pkg.UPGRADE_STATUS_DEFAULT = 'NOT_STARTED' - 33 pkg.UPGRADE_STATUS_UPGRADING = 'UPGRADING' - 33 pkg.UPGRADE_STATUS_FAILED = 'FAILED' - 33 pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR = "/tmp/lime-sysupgrade/preserve" - 33 pkg.UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" - - 33 function pkg.safe_upgrade_confirm_remaining_s() -*****0 local remaining_s = tonumber(utils.unsafe_shell("safe-upgrade confirm-remaining")) -*****0 if not remaining_s then -*****0 remaining_s = -1 - end -*****0 return remaining_s - end - - 33 function pkg.is_upgrade_confirm_supported() - 36 local exit_value = os.execute("safe-upgrade board-supported > /dev/null 2>&1") - 36 return exit_value == 0 - end - - 33 function pkg.get_upgrade_status() - 12 local info = utils.read_obj_store(pkg.UPGRADE_INFO_CACHE_FILE) - 12 if info.status == nil then -*****0 return pkg.UPGRADE_STATUS_DEFAULT - else - 12 return info.status - end - end - - 33 function pkg.set_upgrade_status(status) - 24 return utils.write_obj_store_var(pkg.UPGRADE_INFO_CACHE_FILE, 'status', status) - end - - 33 function pkg.get_upgrade_info() - 18 local result = utils.read_obj_store(pkg.UPGRADE_INFO_CACHE_FILE) - 18 if result.is_upgrade_confirm_supported == nil then - 18 result.is_upgrade_confirm_supported = pkg.is_upgrade_confirm_supported() - end - 18 if not result.is_upgrade_confirm_supported then - 12 result.safe_upgrade_confirm_remaining_s = -1 - else - 6 result.safe_upgrade_confirm_remaining_s = pkg.safe_upgrade_confirm_remaining_s() - end - 18 utils.write_obj_store(pkg.UPGRADE_INFO_CACHE_FILE, result) - 18 return result - end - - 33 function pkg.firmware_verify(fw_path) - local command - 12 if pkg.is_upgrade_confirm_supported() then - 12 command = "safe-upgrade verify " - else -*****0 command = "sysupgrade --test " - end - 12 command = command .. fw_path .. " > /dev/null 2>&1" - 12 local exit_value = os.execute(command) - 12 return exit_value == 0 - end - - - 33 function pkg.firmware_upgrade(fw_path, preserve_config, metadata, fw_type) - 12 if not fw_path then -*****0 return nil, "Firmware file needed" - end - 12 if not utils.file_exists(fw_path) then -*****0 return nil, "Firmware file not found" - end - 12 if pkg.get_upgrade_status() == pkg.UPGRADE_STATUS_UPGRADING then -*****0 return nil, "There is an upgrade in progress" - end - - 12 metadata = metadata or {} - - 12 if not fw_type then - 12 if utils.stringEnds(fw_path, ".bin") then - 12 fw_type = 'sysupgrade' -*****0 elseif utils.stringEnds(fw_path, ".sh") then -*****0 fw_type = 'installer' - else -*****0 return nil, "Unsupported firmware type" - end - end - - 12 if fw_type == 'sysupgrade' then - 12 if not pkg.firmware_verify(fw_path) then -*****0 return nil, "Invalid firmware" - end - end - - 12 local backup = "" - 12 if preserve_config == nil then - 6 preserve_config = true - end - 12 if not preserve_config then -*****0 backup = "DO_NOT_BACKUP=1" - end - - 12 metadata['config_preserved'] = preserve_config or false - - -- store info of the current firmware - 12 local current_fw_description = utils.release_info()["DISTRIB_DESCRIPTION"] - 12 if current_fw_description then - 12 metadata['old_release_description'] = current_fw_description - end - - 12 metadata['local_timestamp'] = os.time() - - --! Use the BACKUP_EXTRA_DIR function of lime-sysupgrade to store the medatada file - 12 utils.unsafe_shell("mkdir -p " .. pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR .. "/etc") - 12 local meta_file_path = pkg.LIME_SYSUPGRADE_BACKUP_EXTRA_DIR .. pkg.UPGRADE_METADATA_FILE - 12 if not utils.write_file(meta_file_path, json.stringify(metadata)) then -*****0 return nil, "Can't write " .. meta_file_path - end - - 12 pkg.set_upgrade_status(pkg.UPGRADE_STATUS_UPGRADING) - 12 if fw_type == 'sysupgrade' then - --! Give some time so the response can be returned to the client - 12 local cmd = "sleep 3; FORCE=1 " .. backup .. " lime-sysupgrade " .. fw_path - --! stdin must be /dev/null because of a tar bug when using gzip that tries to read from stin and fails - --! if it is closed - 12 utils.execute_daemonized(cmd, "/tmp/lime-sysupgrade.log", "/dev/null") -*****0 elseif fw_type == 'installer' then -*****0 utils.unsafe_shell("chmod +x " .. fw_path) -*****0 utils.execute_daemonized(fw_path, "/tmp/upgrade-installer.log", "/dev/null") - --! give the installer some time and try to collect the status up to that moment -*****0 utils.unsafe_shell("sleep 10s") -*****0 local progress_status = utils.read_file("/tmp/upgrade-installer-status") or 'unknown' -*****0 if progress_status == 'failed' then -*****0 pkg.set_upgrade_status(pkg.UPGRADE_STATUS_FAILED) -*****0 return nil, utils.read_file("/tmp/upgrade-installer-error-mesage") or 'Installer failed without error message' - end - end - 12 return true, metadata - end - - 33 return pkg - -============================================================================== -packages/ubus-lime-utils/files/usr/lib/lua/lime/wireless_service.lua -============================================================================== - 22 local utils = require('lime.utils') - 22 local config = require('lime.config') - 22 local wireless = require('lime.wireless') - - 22 local wireless_service = {} - 22 wireless_service.AP_BAND = '2ghz' -- TODO: grab from uci config - - - local function get_node_ap_data(is_admin) - 30 local result = {} - 30 local cfg = wireless.get_band_config(wireless_service.AP_BAND) - 30 result.enabled = utils.has_value(cfg.modes, 'apname') - 30 result.has_password = (cfg.apname_encryption and - 30 cfg.apname_encryption ~= "none") - 30 result.password = is_admin and cfg.apname_key or nil - 30 result.ssid = wireless.resolve_ssid(cfg.apname_ssid) - 30 return result - end - - local function get_community_ap_data() - 30 local result = {} - 30 local cfg = wireless.get_band_config(wireless_service.AP_BAND) - 30 local community_cfg = wireless.get_community_band_config(wireless_service.AP_BAND) - 30 result.enabled = utils.has_value(cfg.modes, 'ap') - 30 result.ssid = wireless.resolve_ssid(cfg.ap_ssid) - 30 result.community = {} - 30 result.community.enabled = utils.has_value(community_cfg.modes, 'ap') - 30 return result - end - - 22 function wireless_service.get_access_points_data(is_admin) - 30 local result = {} - 30 result.node_ap = get_node_ap_data(is_admin) - 30 result.community_ap = get_community_ap_data() - 30 return result - end - - 22 function wireless_service.set_node_ap(has_password, password) - 12 local config = {} - 12 config.apname_encryption = has_password and 'psk2' or 'none' - 12 config.apname_key = password or nil - 12 wireless.set_band_config(wireless_service.AP_BAND, config) - end - - 22 function wireless_service.set_community_ap(enabled) - 12 if enabled then - 6 wireless.add_band_mode(wireless_service.AP_BAND, 'ap') - else - 6 wireless.remove_band_mode(wireless_service.AP_BAND, 'ap') - end - end - - 22 return wireless_service - -============================================================================== -packages/ubus-lime-utils/files/usr/libexec/rpcd/lime-utils-admin -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2020 Santiago Piccinini - ]]-- - - 96 local ubus = require "ubus" - 96 local json = require 'luci.jsonc' - 96 local utils = require 'lime.utils' - - 96 local limeutilsadmin = require 'lime-utils-admin' - - 96 local conn = ubus.connect() - 96 if not conn then -*****0 error("Failed to connect to ubus") - end - - 96 local UPGRADE_METADATA_FILE = "/etc/upgrade_metadata" - - - local function set_root_password(msg) - 6 local result = limeutilsadmin.set_root_password(msg) - 6 return utils.printJson(result) - end - - local function set_hostname(msg) - 18 local result = limeutilsadmin.set_hostname(msg) - 18 return utils.printJson(result) - end - - local function is_upgrade_confirm_supported() - 12 local result = limeutilsadmin.is_upgrade_confirm_supported() - 12 return utils.printJson(result) - end - - local function firmware_upgrade(msg) - 12 local result = limeutilsadmin.firmware_upgrade(msg) - 12 return utils.printJson(result) - end - - local function last_upgrade_metadata() - 12 local result = limeutilsadmin.last_upgrade_metadata() - 12 return utils.printJson(result) - end - - local function firmware_confirm() - 6 local result = limeutilsadmin.firmware_confirm() - 6 return utils.printJson(result) - end - - --! Creates a client connection to a wifi hotspot - local function hotspot_wwan_enable(msg) - 18 local result = limeutilsadmin.hotspot_wwan_enable(msg) - 18 return utils.printJson(result) - end - - - local function hotspot_wwan_disable(msg) - 6 local result = limeutilsadmin.hotspot_wwan_disable(msg) - 6 return utils.printJson(result) - end - - local function safe_reboot(msg) -*****0 local result = limeutilsadmin.safe_reboot(msg) -*****0 utils.printJson(result) - end - - 96 local methods = { - 96 set_root_password = { password = 'value'}, - 96 set_hostname = { hostname = 'value'}, - 96 is_upgrade_confirm_supported = { no_params = 0 }, - 96 firmware_upgrade = { fw_path = 'value', preserve_config = 'value', metadata = 'value', fw_type = 'value'}, - 96 last_upgrade_metadata = { no_params = 0 }, - 96 firmware_confirm = { no_params = 0 }, - 96 hotspot_wwan_enable = { radio = 'value', ssid = 'value', password = 'value', encryption = 'value'}, - 96 hotspot_wwan_disable = { radio = 'value' }, - 96 safe_reboot = {action = 'value', value = 'value'}, - } - - 96 if arg[1] == 'list' then - 6 utils.printJson(methods) - end - - 96 if arg[1] == 'call' then - 90 local msg = utils.rpcd_readline() - 90 msg = json.parse(msg) - 90 if arg[2] == 'set_root_password' then set_root_password(msg) - 84 elseif arg[2] == 'set_hostname' then set_hostname(msg) - 66 elseif arg[2] == 'is_upgrade_confirm_supported' then is_upgrade_confirm_supported(msg) - 54 elseif arg[2] == 'firmware_upgrade' then firmware_upgrade(msg) - 42 elseif arg[2] == 'last_upgrade_metadata' then last_upgrade_metadata(msg) - 30 elseif arg[2] == 'firmware_confirm' then firmware_confirm(msg) - 24 elseif arg[2] == 'hotspot_wwan_enable' then hotspot_wwan_enable(msg) - 6 elseif arg[2] == 'hotspot_wwan_disable' then hotspot_wwan_disable(msg) -*****0 elseif arg[2] == 'safe_reboot' then safe_reboot(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2021 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2021 German Ferrero - ]]-- - - 6 local ubus = require "ubus" - 6 local json = require 'luci.jsonc' - 6 local utils = require 'lime.utils' - 6 local wireless = require 'lime.wireless_service' - - 6 local conn = ubus.connect() - 6 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function get_access_points_data() - 6 local data = wireless.get_access_points_data() - 6 data.status = "ok" - 6 return utils.printJson(data) - end - - 6 local methods = { - 6 get_access_points_data = { no_params = 0 }, - } - - 6 if arg[1] == 'list' then -*****0 utils.printJson(methods) - end - - 6 if arg[1] == 'call' then - 6 local msg = utils.rpcd_readline() - 6 msg = json.parse(msg) - 6 if arg[2] == 'get_access_points_data' then get_access_points_data(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service-admin -============================================================================== - #!/usr/bin/env lua - --[[ - Copyright (C) 2021 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - Copyright 2021 German Ferrero - ]]-- - - 18 local ubus = require "ubus" - 18 local json = require 'luci.jsonc' - 18 local utils = require 'lime.utils' - 18 local wireless = require 'lime.wireless_service' - - 18 local conn = ubus.connect() - 18 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function get_access_points_data() - 6 local data = wireless.get_access_points_data(true) - 6 data.status = "ok" - 6 return utils.printJson(data) - end - - local function set_node_ap(msg) - 6 wireless.set_node_ap(msg.has_password, msg.password) - 6 return utils.printJson({status = 'ok'}) - end - - local function set_community_ap(msg) - 6 wireless.set_community_ap(msg.enabled) - 6 return utils.printJson({status = 'ok'}) - end - - 18 local methods = { - 18 get_access_points_data = { no_params = 0 }, - 18 set_node_ap = { has_password = 'value', password = 'value' }, - 18 set_community_ap = { enabled = 'value' } - } - - 18 if arg[1] == 'list' then -*****0 utils.printJson(methods) - end - - 18 if arg[1] == 'call' then - 18 local msg = utils.rpcd_readline() - 18 msg = json.parse(msg) - 18 if arg[2] == 'get_access_points_data' then get_access_points_data(msg) - 12 elseif arg[2] == 'set_node_ap' then set_node_ap(msg) - 6 elseif arg[2] == 'set_community_ap' then set_community_ap(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - -============================================================================== -packages/ubus-tmate/files/usr/lib/lua/tmate.lua -============================================================================== - #!/usr/bin/env lua - --[[ Copyright (C) 2013-2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - ]]-- - - 22 local utils = require 'lime.utils' - - - 22 local TMATE_SOCK = "/tmp/tmate.sock" - 22 local TMATE_CONFIG = "/etc/tmate/tmate.conf" - - 22 local tmate = {} - - 22 function tmate.cmd_as_str(cmd) -*****0 final_cmd = "tmate -f "..TMATE_CONFIG.." -S "..TMATE_SOCK.." "..cmd -*****0 return utils.unsafe_shell(final_cmd) - end - - local function unix_socket_listening(name) - 11 return "" ~= utils.unsafe_shell("netstat -xl | grep "..TMATE_SOCK.." 2>/dev/null") - end - - 22 function tmate.session_running() - 11 return unix_socket_listening(TMATE_SOCK) - end - - 22 function tmate.get_rw_session() - 33 return tmate.cmd_as_str("display -p '#{tmate_ssh}'"):sub(1, -2) - end - - 22 function tmate.get_ro_session() - 33 return tmate.cmd_as_str("display -p '#{tmate_ssh_ro}'"):sub(1, -2) - end - - 22 function tmate.get_connected_clients() - 22 return tmate.cmd_as_str("display -p '#{tmate_num_clients}'"):sub(1, -2) - end - - 22 function tmate.open_session() - 33 tmate.cmd_as_str("new-session -d") - 33 tmate.cmd_as_str("send-keys C-c") - end - - 22 function tmate.wait_session_ready() - 33 tmate.cmd_as_str("wait tmate-ready") - end - - 22 function tmate.close_session() - 33 tmate.cmd_as_str("kill-session -t 0") - end - - 22 return tmate - -============================================================================== -packages/ubus-tmate/files/usr/libexec/rpcd/tmate -============================================================================== - #!/usr/bin/env lua - --[[ Copyright (C) 2013-2020 LibreMesh.org - This is free software, licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 - - ]]-- - - 44 local ubus = require "ubus" - 44 local json = require 'luci.jsonc' - 44 local utils = require 'lime.utils' - 44 local tmate = require 'tmate' - - 44 local conn = ubus.connect() - 44 if not conn then -*****0 error("Failed to connect to ubus") - end - - local function get_session(msg) - 22 local session = "no session" - - 22 if tmate.session_running() then - 11 local rw_ssh = tmate.get_rw_session() - 11 local ro_ssh = tmate.get_ro_session() - 11 local clients = tmate.get_connected_clients() - - 11 session = { rw_ssh = rw_ssh, ro_ssh = ro_ssh, clients = clients } - end - - 17 utils.printJson({ status = "ok", session = session }) - end - - local function open_session(msg) - 11 tmate.open_session() - 11 tmate.wait_session_ready() - - 11 utils.printJson({status = "ok"}) - end - - local function close_session(params) -*****0 if tmate.session_running() then -*****0 tmate.close_session() - end - -*****0 utils.printJson({ status = "ok" }) - end - - 44 local methods = { - 44 get_session = { no_params = 0 }, - 44 open_session = { no_params = 0 }, - 44 close_session = { no_params = 0 }, - } - - 44 if arg[1] == 'list' then - 11 utils.printJson(methods) - end - - 44 if arg[1] == 'call' then - 33 local msg = utils.rpcd_readline() - 33 msg = json.parse(msg) - 33 if arg[2] == 'get_session' then get_session(msg) - 11 elseif arg[2] == 'open_session' then open_session(msg) -*****0 elseif arg[2] == 'close_session' then close_session(msg) -*****0 else utils.printJson({ error = "Method not found" }) - end - end - - -============================================================================== -packages/wifi-unstuck-wa/files/usr/lib/lua/lime/wifi_unstuck_wa.lua -============================================================================== - 17 local config = require "lime.config" - 17 local utils = require "lime.utils" - 17 local iwinfo = require "iwinfo" - 17 local nixio = require 'nixio' - 17 local wu = {} - - -- so we always scan in at least one different frequency - 17 wu.FREQ_2GHZ_LIST = "2412 2462" - 17 wu.FREQ_5GHZ_LIST = "5180 5240" - - -- if iw runs for 5 min, it is likely hanging - 17 wu.TIMEOUT = tonumber( config.get("wifi", "unstuck_timeout", 300 )) - - 17 function wu.get_stickable_ifaces() - 18 local uci = config.get_uci_cursor() - 18 local ifaces = {} - 18 local devices = {} - - 36 uci:foreach("wireless", "wifi-iface", function(entry) - 90 if (entry.mode == 'mesh' or entry.mode == 'adhoc' or - 36 entry.mode == 'sta' or entry.mode == 'ap') then - 90 local device_path = uci:get("wireless", entry.device, "path") - 90 local device_disabled = uci:get("wireless", entry.device, "disabled") - --! get only one interface per radio and check that the radio is not disabled - 90 if device_path and device_disabled == '0' and devices[device_path] == nil then - 54 table.insert(ifaces, entry.ifname) - 54 devices[device_path] = 1 - end - end - end) - 18 return ifaces - end - - 17 function wu.wait_and_kill_on_timeout(pid_time_started) - 12 local pid_done = {} - - 24 for pid,time_started in pairs(pid_time_started) do - 12 pid_done[pid]=false - end - - repeat - -- wait for 100ms - 1806 nixio.nanosleep(0,100e6) - - -- see if something changed - while true do - 1827 pid,state,code = nixio.waitpid(nil,"nohang") - 1827 if not pid then break end - 21 if pid == 0 then break end - 21 pid_done[pid] = true - end - - -- see if time is up - 1806 now = os.time() - 5406 for pid,time_started in pairs(pid_time_started) do - 3600 time_is_up = now - time_started > wu.TIMEOUT - 3600 if not pid_done[pid] and time_is_up then - -- time is up. send SIGTERM - 6 nixio.kill(pid,15) - -- we don't care any longer about processes we signaled - 6 pid_done[pid] = true - end - end - - -- see if there are remaining processes - 1806 all_done = true - 5421 for pid,done in pairs(pid_done) do - 3615 all_done = all_done and done - end - 1806 until all_done - - end - - 17 function wu.do_workaround() - 12 local ifaces = wu.get_stickable_ifaces() - 12 local pid_time_started = {} - - 48 for _, iface in pairs(ifaces) do - 36 local cmd = "iw dev " .. iface .. " scan freq " - 36 local freq = iwinfo.nl80211.frequency(iface) - 36 if freq ~= nil then - 24 if freq < 3000 then - 12 cmd = cmd .. wu.FREQ_2GHZ_LIST - else - 12 cmd = cmd .. wu.FREQ_5GHZ_LIST - end - 24 utils.log(cmd) - - -- we can not use os.popen here, because it does not give us the - -- pid - 24 pid = nixio.fork() - 24 if pid == 0 then - 12 nixio.exec('/bin/sh','-c',cmd..' >/dev/null') - 12 os.exit(1) - else - 12 pid_time_started[pid] = os.time() - end - - 24 nixio.nanosleep(1) - end - end - - 12 wu.wait_and_kill_on_timeout(pid_time_started) - end - - 17 function wu.configure() - 6 interval = tonumber( config.get("wifi", "unstuck_interval", -1) ) - - 6 if interval and interval > 0 then - --! use sed to replace interval in /etc/crontabs/root - 12 io.popen("sed -i 's/\\*\\/\\d\\+ \\* \\* \\* \\* ((wifi-unstuck &> \\/dev\\/".. - 6 "null)&)/*\\/"..interval.." * * * * ((wifi-unstuck \\&> \\/dev\\/null)\\&)/g".. - 6 "' /etc/crontabs/root") - end - end - - 17 function wu.clean() - -- nothing to clean, but needs to be declared to comply with the API - end - - 17 return wu - -============================================================================== -tests/fakes/iwinfo.lua -============================================================================== - 219 local iwinfo = {} - 219 iwinfo.nl80211 = {} - 219 iwinfo.fake = {} - 219 iwinfo.mocks = {} - - 219 iwinfo.mocks.iw_station_get_result_wlan1 = [[ - Station c0:4a:00:be:7b:0a (on wlan1-mesh) - inactive time: 50 ms - rx bytes: 503044 - rx packets: 3976 - tx bytes: 545116 - tx packets: 1237 - tx retries: 9 - tx failed: 0 - rx drop misc: 3 - signal: -14 [-17, -16] dBm - signal avg: -12 [-14, -15] dBm - Toffset: 46408315 us - tx bitrate: 300.0 MBit/s MCS 15 40MHz short GI - rx bitrate: 300.0 MBit/s MCS 15 40MHz short GI - rx duration: 0 us - expected throughput: 58.43Mbps - mesh llid: 5944 - mesh plid: 1241 - mesh plink: ESTAB - mesh local PS mode: ACTIVE - mesh peer PS mode: ACTIVE - mesh non-peer PS mode: ACTIVE - authorized: yes - authenticated: yes - associated: yes - preamble: long - WMM/WME: yes - MFP: no - TDLS peer: no - DTIM period: 2 - beacon interval:100 - short slot time:yes - connected time: 139 seconds - 219 ]] - - - 219 iwinfo.mocks.iw_station_get_result_wlan0 = [[ - Station c0:4a:00:be:7b:09 (on wlan0-mesh) - inactive time: 140 ms - rx bytes: 3116498 - rx packets: 31613 - tx bytes: 1166333 - tx packets: 4462 - tx retries: 2448 - tx failed: 15 - rx drop misc: 938 - signal: -14 [-17, -18] dBm - signal avg: -14 [-17, -18] dBm - Toffset: 18446744073577465064 us - tx bitrate: 6.5 MBit/s MCS 0 - rx bitrate: 39.0 MBit/s MCS 10 - rx duration: 0 us - expected throughput: 2.197Mbps - mesh llid: 63041 - mesh plid: 61249 - mesh plink: ESTAB - mesh local PS mode: ACTIVE - mesh peer PS mode: ACTIVE - mesh non-peer PS mode: ACTIVE - authorized: yes - authenticated: yes - associated: yes - preamble: long - WMM/WME: yes - MFP: no - TDLS peer: no - DTIM period: 2 - beacon interval:100 - short slot time:yes - connected time: 5070 seconds - 219 ]] - - - 219 iwinfo.mocks.get_stations = { - 219 [1] = { - 219 ["rx_short_gi"] = false, - 219 ["station_mac"] = "C0:4A:00:BE:7B:09", - 219 ["rx_vht"] = false, - 219 ["rx_mhz"] = 20, - 219 ["rx_40mhz"] = false, - 219 ["tx_packets"] = 1574, - 219 ["tx_mhz"] = 20, - 219 ["rx_packets"] = 16879, - 219 ["rx_ht"] = true, - 219 ["tx_mcs"] = 9, - 219 ["noise"] = -95, - 219 ["rx_mcs"] = 1, - 219 ["tx_ht"] = true, - 219 ["iface"] = "wlan0-mesh", - 219 ["tx_rate"] = 26000, - 219 ["inactive"] = 1390, - 219 ["tx_short_gi"] = false, - 219 ["tx_40mhz"] = false, - 219 ["expected_throughput"] = 11437, - 219 ["tx_vht"] = false, - 219 ["rx_rate"] = 13000, - 219 ["signal"] = 13 - 219 }, - 219 [2] = { - 219 ["rx_short_gi"] = true, - 219 ["station_mac"] = "C0:4A:00:BE:7B:0A", - 219 ["rx_vht"] = false, - 219 ["rx_mhz"] = 40, - 219 ["rx_40mhz"] = true, - 219 ["tx_packets"] = 7078, - 219 ["tx_mhz"] = 40, - 219 ["rx_packets"] = 54294, - 219 ["rx_ht"] = true, - 219 ["tx_mcs"] = 15, - 219 ["noise"] = -91, - 219 ["rx_mcs"] = 15, - 219 ["tx_ht"] = true, - 219 ["iface"] = "wlan1-mesh", - 219 ["tx_rate"] = 300000, - 219 ["inactive"] = 70, - 219 ["tx_short_gi"] = true, - 219 ["tx_40mhz"] = true, - 219 ["expected_throughput"] = 59437, - 219 ["tx_vht"] = false, - 219 ["rx_rate"] = 300000, - 219 ["signal"] = -13 - 219 } - 219 } - - 219 iwinfo.mocks.wlan1_mesh_mac = {'C0', '00', '00', '01', '01', '01'} - 219 iwinfo.mocks.wlan0_mesh_mac = {'C0', '00', '00', '00', '00', '00'} - - 219 OP_MODES = { - 219 "Unknown", "Master", "Ad-Hoc", "Client", "Monitor", "Master (VLAN)", - 219 "WDS", "Mesh Point", "P2P Client", "P2P Go" - 219 } - - 219 HT_MODES = {"HT20", "HT40", "VHT20", "VHT40", "VHT80", "VHT80+80", "VHT160"} - - 219 iwinfo.fake._scanlists = {} - 219 iwinfo.fake._channels = {} - 219 iwinfo.fake._assoclists = {} - 219 iwinfo.fake._hwmodelists = {} - - 438 function iwinfo.fake.set_scanlist(phy_id, scanlist) - 101 iwinfo.fake._scanlists[phy_id] = scanlist - end - - 438 function iwinfo.fake.scanlist_gen_station(ssid, channel, signal, mac, mode, quality) - 6 local utils = require("lime.utils") - 6 assert(utils.has_value(OP_MODES, mode)) - - 6 local station = { - 6 ["encryption"] = { - 6 ["enabled"] = false, - 6 ["auth_algs"] = { } , - 6 ["description"] = None, - 6 ["wep"] = false, - 6 ["auth_suites"] = { } , - 6 ["wpa"] = 0, - 6 ["pair_ciphers"] = { } , - 6 ["group_ciphers"] = { } , - 6 } , - 6 ["quality_max"] = 70, - 6 ["ssid"] = ssid, - 6 ["channel"] = channel, - 6 ["signal"] = signal, - 6 ["bssid"] = bssid, - 6 ["mode"] = mode, - 6 ["quality"] = quality, - } - 6 return station - end - - - 438 function iwinfo.nl80211.scanlist(phy_id) - 107 return iwinfo.fake._scanlists[phy_id] or {} - end - - 438 function iwinfo.fake.set_channel(phy_id, channel) - 179 iwinfo.fake._channels[phy_id] = channel - end - - 438 function iwinfo.nl80211.channel(phy_id) - 98 return iwinfo.fake._channels[phy_id] - end - - 438 function iwinfo.fake.set_assoclist(radio, assoclist) - 24 iwinfo.fake._assoclists[radio] = assoclist - end - - 438 function iwinfo.nl80211.assoclist(radio) - 60 return iwinfo.fake._assoclists[radio] or {} - end - - 219 function iwinfo.type(phy_id) - 73 return 'nl80211' - end - - 438 function iwinfo.fake.gen_assoc_station(rx_ht_mode, tx_ht_mode, signal, quality, inactive_ms, - tx_packets, rx_packets) - 18 local utils = require("lime.utils") - - -- VHT modes not yet supported - 18 assert(utils.has_value({"HT20", "HT40"}, rx_ht_mode)) - 18 assert(utils.has_value({"HT20", "HT40"}, tx_ht_mode)) - - 18 local rx_vht = false - 18 local tx_vht = false - 18 local rx_ht = false - 18 local tx_ht = false - 18 local rx_mhz = 20 - 18 local tx_mhz = 20 - 18 local tx_40mhz = false - - 18 if rx_ht_mode == "HT40" then -*****0 rx_ht = true -*****0 rx_mhz = 40 - end - - 18 if tx_ht_mode == "HT40" then - 18 tx_ht = true - 18 tx_40mhz = true - 18 tx_mhz = 40 - end - - 18 local r = { - 18 ["rx_ht"] = rx_ht, - 18 ["rx_vht"] = rx_vht, - 18 ["rx_mhz"] = rx_mhz, - 18 ["rx_rate"] = rx_rate, - - 18 ["tx_ht"] = tx_ht, - 18 ["tx_vht"] = tx_vht, - 18 ["tx_40mhz"] = tx_40mhz, - 18 ["tx_mhz"] = tx_mhz, - 18 ["tx_mcs"] = tx_mcs, - 18 ["tx_rate"] = tx_rate, - 18 ["tx_short_gi"] = true, - - 18 ["tx_packets"] = tx_packets, - 18 ["rx_packets"] = rx_packets, - 18 ["noise"] = noise, - 18 ["inactive"] = inactive_ms, - 18 ["expected_throughput"] = throughtput, - 18 ["signal"] = signal - } - 18 return r - end - - 219 iwinfo.fake.HWMODE = { - 219 ["HW_2GHZ_N"] = { ["a"] = false, ["b"] = true, ["ac"] = false, ["g"] = true, ["n"] = true}, - 219 ["HW_5GHZ_N"] = { ["a"] = true, ["b"] = false, ["ac"] = false, ["g"] = false, ["n"] = true} - 219 } - - 438 function iwinfo.fake.set_hwmodelist(radio_or_phy, hwmodelist) - 246 iwinfo.fake._hwmodelists[radio_or_phy] = hwmodelist - end - - 438 function iwinfo.nl80211.hwmodelist(radio_or_phy) - 85 return iwinfo.fake._hwmodelists[radio_or_phy] - end - - 438 function iwinfo.fake.load_from_uci(uci_cursor) - 106 function create_device(dev) - local hwmode - 167 if dev.band == '5g' then - 150 hwmode = iwinfo.fake.HWMODE.HW_5GHZ_N - 17 elseif dev.band == '2g' then - 17 hwmode = iwinfo.fake.HWMODE.HW_2GHZ_N - else -*****0 assert(0, 'posibility not supported yet, please add support!') - end - 167 iwinfo.fake.set_hwmodelist(dev[".name"], hwmode) - 167 iwinfo.fake.set_channel(dev[".name"], dev.channel) - end - 273 uci_cursor:foreach("wireless", "wifi-device", function(dev) create_device(dev) end) - end - - 219 return iwinfo - - -============================================================================== -tests/fakes/ubus.lua -============================================================================== - 100 local ubus = {} - - 100 function ubus.connect() - 456 local conn = {} - - 456 function conn.call() -*****0 return {} - end - - 456 return conn - end - - 100 return ubus - - -============================================================================== -Summary -============================================================================== - -File Hits Missed Coverage ------------------------------------------------------------------------------------------------------------------------------------------ -./packages/pirania/tests/pirania_test_utils.lua 9 0 100.00% -./tests/utils.lua 87 1 98.86% -packages/check-internet/files/usr/libexec/rpcd/check-internet 18 2 90.00% -packages/deferrable-reboot/files/usr/lib/lua/deferrable_reboot.lua 33 2 94.29% -packages/eupgrade/files/usr/lib/lua/eupgrade.lua 83 25 76.85% -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard.lua 191 108 63.88% -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/functools.lua 24 51 32.00% -packages/first-boot-wizard/files/usr/lib/lua/firstbootwizard/utils.lua 45 61 42.45% -packages/lime-eth-config/files/usr/lib/lua/lime-eth-config.lua 54 17 76.06% -packages/lime-eth-config/files/usr/libexec/rpcd/lime-eth-config 24 7 77.42% -packages/lime-hwd-ground-routing/files/usr/lib/lua/lime/hwd/ground_routing.lua 19 41 31.67% -packages/lime-hwd-openwrt-wan/files/usr/lib/lua/lime/hwd/openwrt_wan.lua 38 0 100.00% -packages/lime-hwd-usbradio/files/usr/lib/lua/lime/hwd/usbradio.lua 15 38 28.30% -packages/lime-hwd-watchcat/files/usr/lib/lua/lime/hwd/watchcat.lua 16 17 48.48% -packages/lime-proto-babeld/files/usr/lib/lua/lime/proto/babeld.lua 72 11 86.75% -packages/lime-proto-batadv/files/usr/lib/lua/lime/proto/batadv.lua 53 20 72.60% -packages/lime-proto-bmx7/files/usr/lib/lua/lime/proto/bmx7.lua 118 48 71.08% -packages/lime-proto-olsr/files/usr/lib/lua/lime/proto/olsr.lua 44 0 100.00% -packages/lime-proto-olsr2/files/usr/lib/lua/lime/proto/olsr2.lua 49 0 100.00% -packages/lime-proto-olsr6/files/usr/lib/lua/lime/proto/olsr6.lua 39 4 90.70% -packages/lime-proto-wan/files/usr/lib/lua/lime/proto/wan.lua 24 0 100.00% -packages/lime-system/files/usr/bin/migrate-wifi-bands-cfg 40 1 97.56% -packages/lime-system/files/usr/lib/lua/lime/config.lua 129 3 97.73% -packages/lime-system/files/usr/lib/lua/lime/firewall.lua 16 9 64.00% -packages/lime-system/files/usr/lib/lua/lime/generic_config.lua 74 1 98.67% -packages/lime-system/files/usr/lib/lua/lime/hardware_detection.lua 18 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/mode/ap.lua 6 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/mode/apname.lua 6 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/mode/apup.lua 9 8 52.94% -packages/lime-system/files/usr/lib/lua/lime/mode/client.lua 5 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/mode/ieee80211s.lua 5 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/network.lua 238 113 67.81% -packages/lime-system/files/usr/lib/lua/lime/proto/ieee80211s.lua 15 0 100.00% -packages/lime-system/files/usr/lib/lua/lime/proto/lan.lua 46 11 80.70% -packages/lime-system/files/usr/lib/lua/lime/system.lua 39 7 84.78% -packages/lime-system/files/usr/lib/lua/lime/utils.lua 305 54 84.96% -packages/lime-system/files/usr/lib/lua/lime/wireless.lua 191 8 95.98% -packages/pirania/files/usr/lib/lua/portal/portal.lua 35 11 76.09% -packages/pirania/files/usr/lib/lua/read_for_access/cgi_handlers.lua 17 0 100.00% -packages/pirania/files/usr/lib/lua/read_for_access/read_for_access.lua 41 2 95.35% -packages/pirania/files/usr/lib/lua/voucher/cgi_handlers.lua 44 4 91.67% -packages/pirania/files/usr/lib/lua/voucher/config.lua 8 0 100.00% -packages/pirania/files/usr/lib/lua/voucher/hooks.lua 6 8 42.86% -packages/pirania/files/usr/lib/lua/voucher/store.lua 45 2 95.74% -packages/pirania/files/usr/lib/lua/voucher/utils.lua 46 16 74.19% -packages/pirania/files/usr/lib/lua/voucher/vouchera.lua 174 19 90.16% -packages/pirania/files/usr/libexec/rpcd/pirania 71 9 88.75% -packages/pirania/files/www/pirania-redirect/redirect 17 0 100.00% -packages/safe-upgrade/files/usr/sbin/safe-upgrade 97 159 37.89% -packages/shared-state-babel_links_info/files/usr/share/shared-state/publishers/shared-state-publish_babel_links_info 26 0 100.00% -packages/shared-state-bat_hosts/files/usr/lib/lua/bat-hosts.lua 22 2 91.67% -packages/shared-state-bat_hosts/files/usr/libexec/rpcd/bat-hosts 25 5 83.33% -packages/shared-state-bat_links_info/files/usr/share/shared-state/publishers/shared-state-publish_bat_links_info 31 0 100.00% -packages/shared-state-network_nodes/files/usr/lib/lua/network-nodes.lua 66 0 100.00% -packages/shared-state-node_info/files/usr/share/shared-state/publishers/shared-state-publish_node_info 24 0 100.00% -packages/shared-state-odhcpd_leases/files/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases 22 0 100.00% -packages/shared-state-ref_state_commons/files/usr/lib/lua/shared_state_links_info.lua 29 0 100.00% -packages/shared-state-wifi_links_info/files/usr/share/shared-state/publishers/shared-state-publish_wifi_links_info 30 0 100.00% -packages/shared-state/files/usr/lib/lua/shared-state.lua 173 51 77.23% -packages/shared-state/files/usr/libexec/rpcd/shared-state 45 2 95.74% -packages/ubus-lime-location/files/usr/lib/lua/lime/location.lua 41 0 100.00% -packages/ubus-lime-location/files/usr/libexec/rpcd/lime-location 40 4 90.91% -packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics.lua 77 11 87.50% -packages/ubus-lime-metrics/files/usr/lib/lua/lime-metrics/utils.lua 10 2 83.33% -packages/ubus-lime-utils/files/usr/lib/lua/lime-utils-admin.lua 52 39 57.14% -packages/ubus-lime-utils/files/usr/lib/lua/lime-utils.lua 55 35 61.11% -packages/ubus-lime-utils/files/usr/lib/lua/lime/hotspot_wwan.lua 84 3 96.55% -packages/ubus-lime-utils/files/usr/lib/lua/lime/node_status.lua 80 35 69.57% -packages/ubus-lime-utils/files/usr/lib/lua/lime/upgrade.lua 62 23 72.94% -packages/ubus-lime-utils/files/usr/lib/lua/lime/wireless_service.lua 36 0 100.00% -packages/ubus-lime-utils/files/usr/libexec/rpcd/lime-utils-admin 46 5 90.20% -packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service 16 3 84.21% -packages/ubus-lime-utils/files/usr/libexec/rpcd/wireless-service-admin 24 3 88.89% -packages/ubus-tmate/files/usr/lib/lua/tmate.lua 22 2 91.67% -packages/ubus-tmate/files/usr/libexec/rpcd/tmate 27 6 81.82% -packages/wifi-unstuck-wa/files/usr/lib/lua/lime/wifi_unstuck_wa.lua 66 0 100.00% -tests/fakes/iwinfo.lua 158 3 98.14% -tests/fakes/ubus.lua 6 1 85.71% ------------------------------------------------------------------------------------------------------------------------------------------ -Total 4193 1133 78.73% From 40c6825b810935f30632aa8dc0e39482ab57b815 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Wed, 16 Jul 2025 11:47:34 -0300 Subject: [PATCH 14/18] docs: added README for guidance of testing this package --- packages/shared-state-odhcpd_leases/README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/shared-state-odhcpd_leases/README.md diff --git a/packages/shared-state-odhcpd_leases/README.md b/packages/shared-state-odhcpd_leases/README.md new file mode 100644 index 000000000..a31268842 --- /dev/null +++ b/packages/shared-state-odhcpd_leases/README.md @@ -0,0 +1,37 @@ + +# shared-state-odhcpd\_leases + +`shared-state-odhcpd_leases` replaces **dnsmasq** for DHCP in LibreMesh-based firmware. Each node’s **odhcpd** publishes its IPv4 lease table to the Shared‑State CRDT `odhcpd-leases`; peers pull that map, convert remote entries into `/tmp/ethers.mesh`, reload odhcpd and thereby reserve the same addresses mesh‑wide. + +## Prerequisites + +LibreMesh / OpenWrt with `odhcpd`, `shared-state-async`, `lua`, `luci-lib-jsonc` installed. + +Once the package is installed, it will: + +1. register the datatype `odhcpd-leases` in Shared‑State; +2. set `option leasetrigger '/usr/bin/odhcpd-lease-share.sh'` and `option leasefile_static '/tmp/ethers.mesh'` in `/etc/config/dhcp`; +3. symlink `/etc/ethers` → `/tmp/ethers.mesh` when `leasefile_static` is not supported; +4. restarts **odhcpd** and **cron**. + +No manual edits are required. + +## 1 Quick test (single node) + +```sh +# connect a client +ubus call dhcp ipv4leases +shared-state-async dump odhcpd-leases # lease visible +cat /tmp/ethers.mesh # should see "MAC IP" +``` + +## 2 Mesh test (two nodes) + +1. Connect client to **Node A** and ensure its lease appears in `dump` on **Node B**. +2. Roam client to **Node B**; `ubus call dhcp ipv4leases '{}'` should show the **same IP**. +3. Connect a second client; confirm addresses never collide. + +## Contributing + +Open issues or pull requests in the LibreMesh *lime‑packages* repository, including router model, OpenWrt release, logs (`ubus`, `dump`, `/tmp/ethers.mesh`) and reproduction steps. + From daca428e1442f48c662969002ce4815fad9f487b Mon Sep 17 00:00:00 2001 From: Agustin Trachta <142672522+AguTrachta@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:49:55 -0300 Subject: [PATCH 15/18] docs: update README --- packages/shared-state-odhcpd_leases/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-state-odhcpd_leases/README.md b/packages/shared-state-odhcpd_leases/README.md index a31268842..2b907a21d 100644 --- a/packages/shared-state-odhcpd_leases/README.md +++ b/packages/shared-state-odhcpd_leases/README.md @@ -28,7 +28,7 @@ cat /tmp/ethers.mesh # should see "MAC IP" ## 2 Mesh test (two nodes) 1. Connect client to **Node A** and ensure its lease appears in `dump` on **Node B**. -2. Roam client to **Node B**; `ubus call dhcp ipv4leases '{}'` should show the **same IP**. +2. Roam client to **Node B**; `shared-state-async dump odhcpd-leases` and `cat /tmp/ethers.mesh` should show the **same IP**. 3. Connect a second client; confirm addresses never collide. ## Contributing From 33ee0f4bc6bd86c1cb6b71a85792eccf77057ace Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Wed, 23 Jul 2025 21:12:15 -0300 Subject: [PATCH 16/18] fix: set leasertrigger to absolute path --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share index 2f43f948a..b6bc46dff 100755 --- a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share +++ b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share @@ -1,7 +1,7 @@ #!/bin/sh CRDT='odhcpd-leases' LEASEFILE='/tmp/ethers.mesh' -TRIGGERFILE='usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases' +TRIGGERFILE='/usr/share/shared-state/publishers/shared-state-publish_odhcpd_leases' mSc='odhcpd_leases' uci -q set shared-state.$mSc=dataType From 81b684227efd0aa1da4a73e9ae567fb8a6b0c632 Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Wed, 23 Jul 2025 21:16:43 -0300 Subject: [PATCH 17/18] fix: replace existing /etc/ethers with symlink -L verifies if file exists and if it is already a symlink and now routers that already have a regular file get it force-replaced by the mesh lease symlink. --- .../files/etc/uci-defaults/90_odhcpd-lease-share | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share index b6bc46dff..b390472c5 100755 --- a/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share +++ b/packages/shared-state-odhcpd_leases/files/etc/uci-defaults/90_odhcpd-lease-share @@ -15,6 +15,6 @@ uci -q set dhcp.odhcpd.leasetrigger="$TRIGGERFILE" uci -q set dhcp.odhcpd.maindhcp='1' uci commit dhcp -[ -e /etc/ethers ] || ln -s "$LEASEFILE" /etc/ethers +[ -L /etc/ethers ] || ln -sf "$LEASEFILE" /etc/ethers /etc/init.d/odhcpd reload From 7428c1b1e1611d8e5c9d609602c914817c0c3f2b Mon Sep 17 00:00:00 2001 From: AguTrachta Date: Wed, 30 Jul 2025 17:12:30 -0300 Subject: [PATCH 18/18] fix: typo error corrected from 'odchpd' to 'odhcpd' --- packages/lime-system/files/usr/lib/lua/lime/network.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lime-system/files/usr/lib/lua/lime/network.lua b/packages/lime-system/files/usr/lib/lua/lime/network.lua index 2c5078fd8..5367b6767 100644 --- a/packages/lime-system/files/usr/lib/lua/lime/network.lua +++ b/packages/lime-system/files/usr/lib/lua/lime/network.lua @@ -205,11 +205,11 @@ function network.clean() if config.get_bool("network", "use_odhcpd", false) then utils.log("Use odhcpd as dhcp server") - uci:set("dhcp", "odchpd", "maindhcp", 1) + uci:set("dhcp", "odhcpd", "maindhcp", 1) os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd enable") else utils.log("Disabling odhcpd") - uci:set("dhcp", "odchpd", "maindhcp", 0) + uci:set("dhcp", "odhcpd", "maindhcp", 0) os.execute("[ -e /etc/init.d/odhcpd ] && /etc/init.d/odhcpd disable") end