|
| 1 | +#!/usr/bin/lua |
| 2 | + |
| 3 | +local uci = require('simple-uci').cursor() |
| 4 | + |
| 5 | +-- Safety check functions |
| 6 | +local function log_debug(...) |
| 7 | + if uci:get('ssid-changer', 'settings', 'debug_log_enabled') == '1' then |
| 8 | + os.execute('logger -t "ffac-ssid-changer" -p debug "' .. table.concat({...}, ' ') .. '"') |
| 9 | + end |
| 10 | +end |
| 11 | + |
| 12 | +local function log(...) |
| 13 | + os.execute('logger -t "ffac-ssid-changer" "' .. table.concat({...}, ' ') .. '"') |
| 14 | +end |
| 15 | + |
| 16 | +local function safety_exit(message) |
| 17 | + log_debug(message .. ", exiting with error code 2") |
| 18 | + os.exit(2) |
| 19 | +end |
| 20 | + |
| 21 | +-- Check if the script is enabled |
| 22 | +if uci:get('ssid-changer', 'settings', 'enabled') == '0' then |
| 23 | + os.exit(0) -- Exit silently if the script is disabled |
| 24 | +end |
| 25 | + |
| 26 | +-- Check for autoupdater running |
| 27 | +local function is_autoupdater_running() |
| 28 | + local handle = io.popen('pgrep -f autoupdater') |
| 29 | + local result = handle:read("*a") |
| 30 | + handle:close() |
| 31 | + return result ~= '' |
| 32 | +end |
| 33 | + |
| 34 | +if is_autoupdater_running() then |
| 35 | + safety_exit('autoupdater running') |
| 36 | +end |
| 37 | + |
| 38 | +-- Read uptime |
| 39 | +local function get_uptime() |
| 40 | + local file = io.open('/proc/uptime', 'r') |
| 41 | + local uptime = file:read("*n") -- Read only the first number (uptime in seconds) |
| 42 | + file:close() |
| 43 | + return uptime |
| 44 | +end |
| 45 | + |
| 46 | +local uptime = get_uptime() |
| 47 | +local uptime_minutes = math.floor(uptime / 60) |
| 48 | +local monitor_duration = tonumber(uci:get('ssid-changer', 'settings', 'switch_timeframe') or 30) |
| 49 | +local is_switch_time = uptime_minutes % monitor_duration |
| 50 | + |
| 51 | +if uptime < 60 then |
| 52 | + safety_exit('uptime less than one minute') |
| 53 | +end |
| 54 | + |
| 55 | +-- Check for hostapd processes |
| 56 | +local function has_hostapd_processes() |
| 57 | + local handle = io.popen('find /var/run -name "hostapd-*.conf" | wc -l') |
| 58 | + local result = handle:read("*a") |
| 59 | + handle:close() |
| 60 | + return tonumber(result) > 0 |
| 61 | +end |
| 62 | + |
| 63 | +if not has_hostapd_processes() then |
| 64 | + safety_exit('no hostapd-*') |
| 65 | +end |
| 66 | + |
| 67 | +-- Generate the offline SSID |
| 68 | +local function calculate_offline_ssid() |
| 69 | + local prefix = uci:get('ssid-changer', 'settings', 'prefix') or 'FF_Offline_' |
| 70 | + local settings_suffix = uci:get('ssid-changer', 'settings', 'suffix') or 'nodename' |
| 71 | + local suffix |
| 72 | + |
| 73 | + if settings_suffix == 'nodename' then |
| 74 | + suffix = io.popen('uname -n'):read("*a"):gsub("%s+", "") |
| 75 | + if #suffix > (30 - #prefix) then |
| 76 | + local max_suffix_length = math.floor((28 - #prefix) / 2) |
| 77 | + local suffix_first_chars = suffix:sub(1, max_suffix_length) |
| 78 | + local suffix_last_chars = suffix:sub(-max_suffix_length) |
| 79 | + suffix = suffix_first_chars .. '...' .. suffix_last_chars |
| 80 | + end |
| 81 | + elseif settings_suffix == 'mac' then |
| 82 | + suffix = io.popen('uci -q get network.bat0.macaddr | sed "s/://g"'):read("*a"):gsub("%s+", "") |
| 83 | + else |
| 84 | + suffix = '' |
| 85 | + end |
| 86 | + |
| 87 | + return prefix .. suffix |
| 88 | +end |
| 89 | + |
| 90 | +local offline_ssid = calculate_offline_ssid() |
| 91 | + |
| 92 | +-- Count offline incidents |
| 93 | +local tmp = '/tmp/ssid-changer-count' |
| 94 | +local tmp_state = '/tmp/ssid-changer-offline' |
| 95 | +local off_count = 0 |
| 96 | +local file = io.open(tmp, 'r') |
| 97 | + |
| 98 | +local is_offline = 0 |
| 99 | +local state_file = io.open(tmp_state, 'r') |
| 100 | + |
| 101 | +if state_file then |
| 102 | + is_offline = tonumber(state_file:read("*a")) or 0 |
| 103 | + state_file:close() |
| 104 | +else |
| 105 | + state_file = io.open(tmp_state, 'w') |
| 106 | + state_file:write("0") |
| 107 | + state_file:close() |
| 108 | +end |
| 109 | + |
| 110 | +if file then |
| 111 | + off_count = tonumber(file:read("*a")) or 0 |
| 112 | + file:close() |
| 113 | +else |
| 114 | + file = io.open(tmp, 'w') |
| 115 | + file:write("0") |
| 116 | + file:close() |
| 117 | +end |
| 118 | + |
| 119 | +local function calculate_tq_limit() |
| 120 | + local tq_limit_max = tonumber(uci:get('ssid-changer', 'settings', 'tq_limit_max') or 45) |
| 121 | + local tq_limit_min = tonumber(uci:get('ssid-changer', 'settings', 'tq_limit_min') or 35) |
| 122 | + local gateway_tq |
| 123 | + |
| 124 | + local handle = io.popen('batctl gwl -H | grep -e "^\\*" | awk -F"[()]" "{print $2}" | tr -d " "') |
| 125 | + gateway_tq = tonumber(handle:read("*a")) |
| 126 | + handle:close() |
| 127 | + |
| 128 | + if not gateway_tq then |
| 129 | + safety_exit('tq_limit can not be calculated without gateway') |
| 130 | + end |
| 131 | + local is_online |
| 132 | + |
| 133 | + if gateway_tq >= tq_limit_max then |
| 134 | + is_online = true |
| 135 | + elseif gateway_tq < tq_limit_min then |
| 136 | + is_online = false |
| 137 | + else |
| 138 | + -- in the middle part we consider us offline if there was at least one offline incidents |
| 139 | + -- or we were offline before the current monitor interval |
| 140 | + is_online = off_count > 0 |
| 141 | + end |
| 142 | + |
| 143 | + if is_online then |
| 144 | + return 'online' |
| 145 | + else |
| 146 | + return 'offline' |
| 147 | + end |
| 148 | +end |
| 149 | + |
| 150 | +local function has_default_gw4() |
| 151 | + local default_gw4 = io.open('/var/gluon/state/has_default_gw4', 'r') |
| 152 | + if default_gw4 then |
| 153 | + default_gw4:close() |
| 154 | + return true |
| 155 | + end |
| 156 | + return false |
| 157 | +end |
| 158 | + |
| 159 | +local status |
| 160 | +if has_default_gw4() then |
| 161 | + local tq_limit_enabled = tonumber(uci:get('ssid-changer', 'settings', 'tq_limit_enabled') or 0) |
| 162 | + |
| 163 | + if tq_limit_enabled == 1 then |
| 164 | + status = calculate_tq_limit() |
| 165 | + else |
| 166 | + status = 'online' |
| 167 | + end |
| 168 | +else |
| 169 | + status = 'offline' |
| 170 | +end |
| 171 | + |
| 172 | +if status == 'online' then |
| 173 | + log_debug("node is online") |
| 174 | + -- only revert and reconf if we were offline in the current monitoring timeframe or before |
| 175 | + -- to reduce impact |
| 176 | + if off_count > 0 then |
| 177 | + log("reverting offline ssid back to default wireless config") |
| 178 | + uci:revert('wireless') |
| 179 | + os.execute('wifi reconf') |
| 180 | + end |
| 181 | +elseif status == 'offline' then |
| 182 | + log_debug("node is considered offline") |
| 183 | + local first = tonumber(uci:get('ssid-changer', 'settings', 'first') or 5) |
| 184 | + -- set SSID offline, only if uptime is less than FIRST or exactly a multiplicative of switch_timeframe |
| 185 | + if uptime_minutes < first or is_switch_time == 0 then |
| 186 | + |
| 187 | + -- check if off_count is more than half of the monitor duration |
| 188 | + if not is_offline and off_count >= math.floor(monitor_duration / 2) then |
| 189 | + -- if has been offline for at least half checks in monitor duration |
| 190 | + -- set the SSID to the offline SSID |
| 191 | + -- and disable owe client radios |
| 192 | + for i = 0, 2 do |
| 193 | + local client_ssid = uci:get('wireless', 'client_radio' .. i, 'ssid') |
| 194 | + if client_ssid then |
| 195 | + uci:set('wireless', 'client_radio' .. i, 'ssid', offline_ssid) |
| 196 | + end |
| 197 | + |
| 198 | + local owe_ssid = uci:get('wireless', 'owe_radio' .. i, 'ssid') |
| 199 | + if owe_ssid then |
| 200 | + uci:set('wireless', 'owe_radio' .. i, 'disabled', 1) |
| 201 | + end |
| 202 | + -- save does not commit |
| 203 | + uci:save('wireless') |
| 204 | + end |
| 205 | + log("reconfiguring wifi to offline ssid") |
| 206 | + os.execute('wifi reconf') |
| 207 | + end |
| 208 | + end |
| 209 | + off_count = off_count + 1 |
| 210 | + file = io.open(tmp, 'w') |
| 211 | + file:write(tostring(off_count)) |
| 212 | + file:close() |
| 213 | +end |
| 214 | + |
| 215 | +if is_switch_time == 0 then |
| 216 | + file = io.open(tmp, 'w') |
| 217 | + state_file = io.open(tmp_state, 'w') |
| 218 | + |
| 219 | + if status == 'offline' then |
| 220 | + file:write("1") |
| 221 | + state_file:write("1") |
| 222 | + else |
| 223 | + file:write("0") |
| 224 | + state_file:write("0") |
| 225 | + end |
| 226 | + file:close() |
| 227 | +end |
0 commit comments