util.lua 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. -- Writes all lines from the file input to the file output except those starting with prefix
  2. -- Doesn't close the output file, but returns the file object
  3. local function do_filter_prefix(input, output, prefix)
  4. local f = io.open(output, 'w+')
  5. local l = prefix:len()
  6. for line in io.lines(input) do
  7. if line:sub(1, l) ~= prefix then
  8. f:write(line, '\n')
  9. end
  10. end
  11. return f
  12. end
  13. local function escape_args(ret, arg0, ...)
  14. if not arg0 then
  15. return ret
  16. end
  17. return escape_args(ret .. "'" .. string.gsub(arg0, "'", "'\\''") .. "' ", ...)
  18. end
  19. local io = io
  20. local os = os
  21. local string = string
  22. local tonumber = tonumber
  23. local ipairs = ipairs
  24. local pairs = pairs
  25. local table = table
  26. local nixio = require 'nixio'
  27. local hash = require 'hash'
  28. local sysconfig = require 'gluon.sysconfig'
  29. local site = require 'gluon.site_config'
  30. local uci = require('simple-uci').cursor()
  31. local fs = require 'nixio.fs'
  32. module 'gluon.util'
  33. function trim(str)
  34. return str:gsub("^%s*(.-)%s*$", "%1")
  35. end
  36. function contains(table, value)
  37. for k, v in pairs(table) do
  38. if value == v then
  39. return k
  40. end
  41. end
  42. return false
  43. end
  44. function add_to_set(t, itm)
  45. for _,v in ipairs(t) do
  46. if v == itm then return false end
  47. end
  48. table.insert(t, itm)
  49. return true
  50. end
  51. function remove_from_set(t, itm)
  52. local i = 1
  53. local changed = false
  54. while i <= #t do
  55. if t[i] == itm then
  56. table.remove(t, i)
  57. changed = true
  58. else
  59. i = i + 1
  60. end
  61. end
  62. return changed
  63. end
  64. function exec(...)
  65. return os.execute(escape_args('', 'exec', ...))
  66. end
  67. -- Removes all lines starting with a prefix from a file, optionally adding a new one
  68. function replace_prefix(file, prefix, add)
  69. local tmp = file .. '.tmp'
  70. local f = do_filter_prefix(file, tmp, prefix)
  71. if add then
  72. f:write(add)
  73. end
  74. f:close()
  75. os.rename(tmp, file)
  76. end
  77. function readline(fd)
  78. local line = fd:read('*l')
  79. fd:close()
  80. return line
  81. end
  82. function lock(file)
  83. exec('lock', file)
  84. end
  85. function unlock(file)
  86. exec('lock', '-u', file)
  87. end
  88. function node_id()
  89. return string.gsub(sysconfig.primary_mac, ':', '')
  90. end
  91. function get_mesh_devices(uconn)
  92. local dump = uconn:call("network.interface", "dump", {})
  93. local devices = {}
  94. for _, interface in ipairs(dump.interface) do
  95. if ( (interface.proto == "gluon_mesh") and interface.up ) then
  96. table.insert(devices, interface.device)
  97. end
  98. end
  99. return devices
  100. end
  101. local function find_phy_by_path(path)
  102. for phy in fs.glob('/sys/devices/' .. path .. '/ieee80211/phy*') do
  103. return phy:match('([^/]+)$')
  104. end
  105. end
  106. local function find_phy_by_macaddr(macaddr)
  107. local addr = macaddr:lower()
  108. for file in fs.glob('/sys/class/ieee80211/*/macaddress') do
  109. if trim(fs.readfile(file)) == addr then
  110. return file:match('([^/]+)/macaddress$')
  111. end
  112. end
  113. end
  114. local function find_phy(radio)
  115. local config = uci:get_all('wireless', radio)
  116. if not config or config.type ~= 'mac80211' then
  117. return nil
  118. elseif config.path then
  119. return find_phy_by_path(config.path)
  120. elseif config.macaddr then
  121. return find_phy_by_macaddr(config.macaddr)
  122. else
  123. return nil
  124. end
  125. end
  126. local function get_addresses(radio)
  127. local phy = find_phy(radio)
  128. if not phy then
  129. return function() end
  130. end
  131. return io.lines('/sys/class/ieee80211/' .. phy .. '/addresses')
  132. end
  133. -- Generates a (hopefully) unique MAC address
  134. -- The parameter defines the ID to add to the MAC address
  135. --
  136. -- IDs defined so far:
  137. -- 0: client0; WAN
  138. -- 1: mesh0
  139. -- 2: ibss0
  140. -- 3: wan_radio0 (private WLAN); batman-adv primary address
  141. -- 4: client1; LAN
  142. -- 5: mesh1
  143. -- 6: ibss1
  144. -- 7: wan_radio1 (private WLAN); mesh VPN
  145. function generate_mac(i)
  146. if i > 7 or i < 0 then return nil end -- max allowed id (0b111)
  147. local hashed = string.sub(hash.md5(sysconfig.primary_mac), 0, 12)
  148. local m1, m2, m3, m4, m5, m6 = string.match(hashed, '(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)')
  149. m1 = tonumber(m1, 16)
  150. m6 = tonumber(m6, 16)
  151. m1 = nixio.bit.bor(m1, 0x02) -- set locally administered bit
  152. m1 = nixio.bit.band(m1, 0xFE) -- unset the multicast bit
  153. -- It's necessary that the first 45 bits of the MAC address don't
  154. -- vary on a single hardware interface, since some chips are using
  155. -- a hardware MAC filter. (e.g 'rt305x')
  156. m6 = nixio.bit.band(m6, 0xF8) -- zero the last three bits (space needed for counting)
  157. m6 = m6 + i -- add virtual interface id
  158. return string.format('%02x:%s:%s:%s:%s:%02x', m1, m2, m3, m4, m5, m6)
  159. end
  160. local function get_wlan_mac_from_driver(radio, vif)
  161. local primary = sysconfig.primary_mac:lower()
  162. local i = 1
  163. for addr in get_addresses(radio) do
  164. if addr:lower() ~= primary then
  165. if i == vif then
  166. return addr
  167. end
  168. i = i + 1
  169. end
  170. end
  171. end
  172. function get_wlan_mac(radio, index, vif)
  173. local addr = get_wlan_mac_from_driver(radio, vif)
  174. if addr then
  175. return addr
  176. end
  177. return generate_mac(4*(index-1) + (vif-1))
  178. end
  179. -- Iterate over all radios defined in UCI calling
  180. -- f(radio, index, site.wifiX) for each radio found while passing
  181. -- site.wifi24 for 2.4 GHz devices and site.wifi5 for 5 GHz ones.
  182. function iterate_radios(f)
  183. local radios = {}
  184. uci:foreach('wireless', 'wifi-device',
  185. function(s)
  186. table.insert(radios, s['.name'])
  187. end
  188. )
  189. for index, radio in ipairs(radios) do
  190. local hwmode = uci:get('wireless', radio, 'hwmode')
  191. if hwmode == '11g' or hwmode == '11ng' then
  192. f(radio, index, site.wifi24)
  193. elseif hwmode == '11a' or hwmode == '11na' then
  194. f(radio, index, site.wifi5)
  195. end
  196. end
  197. end