util.lua 5.9 KB

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