diff --git a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/320-gluon-client-bridge-wireless b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/320-gluon-client-bridge-wireless
index b65a7d57280d6e3660fe92fceaaa4bdeac6e0b18..972b9b612f747b2c3cec8b6229cb2deaf8219bff 100755
--- a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/320-gluon-client-bridge-wireless
+++ b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/320-gluon-client-bridge-wireless
@@ -1,7 +1,7 @@
 #!/usr/bin/lua
 
-local util = require 'gluon.util'
 local platform = require 'gluon.platform'
+local wireless = require 'gluon.wireless'
 
 local uci = require('simple-uci').cursor()
 
@@ -23,7 +23,7 @@ local function configure_ap(radio, index, config, radio_name)
 
 	uci:delete('wireless', name)
 
-	local macaddr = util.get_wlan_mac(uci, radio, index, 1)
+	local macaddr = wireless.get_wlan_mac(uci, radio, index, 1)
 
 	if not ap.ssid() or not macaddr then
 		return
@@ -56,7 +56,7 @@ local function configure_owe(radio, index, config, radio_name)
 		return
 	end
 
-	local macaddr = util.get_wlan_mac(uci, radio, index, 3)
+	local macaddr = wireless.get_wlan_mac(uci, radio, index, 3)
 
 	if not ap.owe_ssid() or not macaddr then
 		return
@@ -109,7 +109,7 @@ local function configure_owe_transition_mode(config, radio_name)
 	uci:set('wireless', name_owe, 'hidden', '1')
 end
 
-util.foreach_radio(uci, function(radio, index, config)
+wireless.foreach_radio(uci, function(radio, index, config)
 	local radio_name = radio['.name']
 
 	configure_ap(radio, index, config, radio_name)
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
index 709cc909265f04676658bb4c7eeabe35f821a3e6..74449a38424a5c4f53cb511e2d74c5ac3199bd68 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
@@ -1,6 +1,7 @@
 #!/usr/bin/lua
 
 local util = require 'gluon.util'
+local wireless = require 'gluon.wireless'
 local site = require 'gluon.site'
 local sysconfig = require 'gluon.sysconfig'
 local iwinfo = require 'iwinfo'
@@ -16,8 +17,8 @@ if not sysconfig.gluon_version then
 	-- capable of operating in the 2.4 GHz and 5 GHz band need
 	-- to be distributed evenly.
 	local radio_band_count = {band24=0, band5=0}
-	util.foreach_radio(uci, function(radio)
-		local hwmodes = iwinfo.nl80211.hwmodelist(util.find_phy(radio))
+	wireless.foreach_radio(uci, function(radio)
+		local hwmodes = iwinfo.nl80211.hwmodelist(wireless.find_phy(radio))
 		if hwmodes.g and not (hwmodes.a or hwmodes.ac) then
 			-- 2.4 GHz
 			radio_band_count["band24"] = radio_band_count["band24"] + 1
@@ -29,9 +30,9 @@ if not sysconfig.gluon_version then
 
 	-- Use the number of all fixed 2.4G GHz and 5 GHz radios to
 	-- distribute dualband radios in this step.
-	util.foreach_radio(uci, function(radio)
+	wireless.foreach_radio(uci, function(radio)
 		local radio_name = radio['.name']
-		local hwmodes = iwinfo.nl80211.hwmodelist(util.find_phy(radio))
+		local hwmodes = iwinfo.nl80211.hwmodelist(wireless.find_phy(radio))
 		if (hwmodes.a or hwmodes.ac) and hwmodes.g then
 			-- Dualband radio
 			if radio_band_count["band24"] <= radio_band_count["band5"] then
@@ -72,7 +73,7 @@ local function get_htmode(radio)
 		end
 	end
 
-	local phy = util.find_phy(radio)
+	local phy = wireless.find_phy(radio)
 	if iwinfo.nl80211.hwmodelist(phy).ac then
 		return 'VHT20'
 	end
@@ -121,7 +122,7 @@ local function configure_mesh(config, radio, index, suffix, disabled)
 		return
 	end
 
-	local macaddr = util.get_wlan_mac(uci, radio, index, 2)
+	local macaddr = wireless.get_wlan_mac(uci, radio, index, 2)
 	if not macaddr then
 		return
 	end
@@ -153,7 +154,7 @@ local function fixup_wan(radio, index)
 		return
 	end
 
-	local macaddr = util.get_wlan_mac(uci, radio, index, 4)
+	local macaddr = wireless.get_wlan_mac(uci, radio, index, 4)
 	if not macaddr then
 		return
 	end
@@ -173,7 +174,7 @@ local function configure_mesh_wireless(radio, index, config)
 	)
 end
 
-util.foreach_radio(uci, function(radio, index, config)
+wireless.foreach_radio(uci, function(radio, index, config)
 	local radio_name = radio['.name']
 
 	delete_ibss(radio_name)
diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
index 85f1de76ef8da778a2a3f756a81beccaff0d3eee..b4fc708b419b280293a57bb773f856da6aa43c0e 100644
--- a/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
+++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/platform.lua
@@ -1,5 +1,6 @@
 local platform_info = require 'platform_info'
 local util = require 'gluon.util'
+local wireless = require 'gluon.wireless'
 local unistd = require 'posix.unistd'
 
 
@@ -80,7 +81,7 @@ function M.device_supports_mfp(uci)
 	end
 
 	uci:foreach('wireless', 'wifi-device', function(radio)
-		local phy = util.find_phy(radio)
+		local phy = wireless.find_phy(radio)
 		local phypath = '/sys/kernel/debug/ieee80211/' .. phy .. '/'
 
 		if not util.file_contains_line(phypath .. 'hwflags', 'MFP_CAPABLE') then
diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua
index bd51fcc46c8cb420beaf0a1449872b267f5b3dcc..a3c5456f3c68aed8827602f6cd51c1e2e7a954a5 100644
--- a/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua
+++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/util.lua
@@ -136,45 +136,6 @@ function M.glob(pattern)
 	return posix_glob.glob(pattern) or {}
 end
 
-local function find_phy_by_path(path)
-	local phy = M.glob('/sys/devices/' .. path .. '/ieee80211/phy*')[1]
-		or M.glob('/sys/devices/platform/' .. path .. '/ieee80211/phy*')[1]
-
-	if phy then
-		return phy:match('([^/]+)$')
-	end
-end
-
-local function find_phy_by_macaddr(macaddr)
-	local addr = macaddr:lower()
-	for _, file in ipairs(M.glob('/sys/class/ieee80211/*/macaddress')) do
-		if M.trim(M.readfile(file)) == addr then
-			return file:match('([^/]+)/macaddress$')
-		end
-	end
-end
-
-function M.find_phy(config)
-	if not config or config.type ~= 'mac80211' then
-		return nil
-	elseif config.path then
-		return find_phy_by_path(config.path)
-	elseif config.macaddr then
-		return find_phy_by_macaddr(config.macaddr)
-	else
-		return nil
-	end
-end
-
-local function get_addresses(radio)
-	local phy = M.find_phy(radio)
-	if not phy then
-		return function() end
-	end
-
-	return io.lines('/sys/class/ieee80211/' .. phy .. '/addresses')
-end
-
 -- Generates a (hopefully) unique MAC address
 -- The parameter defines the ID to add to the MAC address
 --
@@ -209,58 +170,6 @@ function M.generate_mac(i)
 	return string.format('%02x:%s:%s:%s:%s:%02x', m1, m2, m3, m4, m5, m6)
 end
 
-local function get_wlan_mac_from_driver(radio, vif)
-	local primary = sysconfig.primary_mac:lower()
-
-	local addresses = {}
-	for address in get_addresses(radio) do
-		if address:lower() ~= primary then
-			table.insert(addresses, address)
-		end
-	end
-
-	-- Make sure we have at least 4 addresses
-	if #addresses < 4 then
-		return nil
-	end
-
-	for i, addr in ipairs(addresses) do
-		if i == vif then
-			return addr
-		end
-	end
-end
-
-function M.get_wlan_mac(_, radio, index, vif)
-	local addr = get_wlan_mac_from_driver(radio, vif)
-	if addr then
-		return addr
-	end
-
-	return M.generate_mac(4*(index-1) + (vif-1))
-end
-
--- Iterate over all radios defined in UCI calling
--- f(radio, index, site.wifiX) for each radio found while passing
---  site.wifi24 for 2.4 GHz devices and site.wifi5 for 5 GHz ones.
-function M.foreach_radio(uci, f)
-	local radios = {}
-
-	uci:foreach('wireless', 'wifi-device', function(radio)
-		table.insert(radios, radio)
-	end)
-
-	for index, radio in ipairs(radios) do
-		local hwmode = radio.hwmode
-
-		if hwmode == '11g' or hwmode == '11ng' then
-			f(radio, index, site.wifi24)
-		elseif hwmode == '11a' or hwmode == '11na' then
-			f(radio, index, site.wifi5)
-		end
-	end
-end
-
 function M.get_uptime()
 	local uptime_file = M.readfile("/proc/uptime")
 	if uptime_file == nil then
diff --git a/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua b/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua
new file mode 100644
index 0000000000000000000000000000000000000000..8a2f4ed8809e7707426793a6b3ad19cc3f8b764f
--- /dev/null
+++ b/package/gluon-core/luasrc/usr/lib/lua/gluon/wireless.lua
@@ -0,0 +1,99 @@
+local sysconfig = require 'gluon.sysconfig'
+local site = require 'gluon.site'
+local util = require 'gluon.util'
+
+
+local M = {}
+
+local function find_phy_by_path(path)
+	local phy = util.glob('/sys/devices/' .. path .. '/ieee80211/phy*')[1]
+		or util.glob('/sys/devices/platform/' .. path .. '/ieee80211/phy*')[1]
+
+	if phy then
+		return phy:match('([^/]+)$')
+	end
+end
+
+local function find_phy_by_macaddr(macaddr)
+	local addr = macaddr:lower()
+	for _, file in ipairs(util.glob('/sys/class/ieee80211/*/macaddress')) do
+		if util.trim(util.readfile(file)) == addr then
+			return file:match('([^/]+)/macaddress$')
+		end
+	end
+end
+
+function M.find_phy(config)
+	if not config or config.type ~= 'mac80211' then
+		return nil
+	elseif config.path then
+		return find_phy_by_path(config.path)
+	elseif config.macaddr then
+		return find_phy_by_macaddr(config.macaddr)
+	else
+		return nil
+	end
+end
+
+local function get_addresses(radio)
+	local phy = M.find_phy(radio)
+	if not phy then
+		return function() end
+	end
+
+	return io.lines('/sys/class/ieee80211/' .. phy .. '/addresses')
+end
+
+local function get_wlan_mac_from_driver(radio, vif)
+	local primary = sysconfig.primary_mac:lower()
+
+	local addresses = {}
+	for address in get_addresses(radio) do
+		if address:lower() ~= primary then
+			table.insert(addresses, address)
+		end
+	end
+
+	-- Make sure we have at least 4 addresses
+	if #addresses < 4 then
+		return nil
+	end
+
+	for i, addr in ipairs(addresses) do
+		if i == vif then
+			return addr
+		end
+	end
+end
+
+function M.get_wlan_mac(_, radio, index, vif)
+	local addr = get_wlan_mac_from_driver(radio, vif)
+	if addr then
+		return addr
+	end
+
+	return util.generate_mac(4*(index-1) + (vif-1))
+end
+
+-- Iterate over all radios defined in UCI calling
+-- f(radio, index, site.wifiX) for each radio found while passing
+--  site.wifi24 for 2.4 GHz devices and site.wifi5 for 5 GHz ones.
+function M.foreach_radio(uci, f)
+	local radios = {}
+
+	uci:foreach('wireless', 'wifi-device', function(radio)
+		table.insert(radios, radio)
+	end)
+
+	for index, radio in ipairs(radios) do
+		local hwmode = radio.hwmode
+
+		if hwmode == '11g' or hwmode == '11ng' then
+			f(radio, index, site.wifi24)
+		elseif hwmode == '11a' or hwmode == '11na' then
+			f(radio, index, site.wifi5)
+		end
+	end
+end
+
+return M
diff --git a/package/gluon-mesh-wireless-sae/luasrc/lib/gluon/upgrade/205-wireless-mesh-sae b/package/gluon-mesh-wireless-sae/luasrc/lib/gluon/upgrade/205-wireless-mesh-sae
index ee1fae0732b42adcbcab992cc8d71413d2a39c6e..c51da9cc9e09a43de6490d9f6e72e0242d4932cf 100755
--- a/package/gluon-mesh-wireless-sae/luasrc/lib/gluon/upgrade/205-wireless-mesh-sae
+++ b/package/gluon-mesh-wireless-sae/luasrc/lib/gluon/upgrade/205-wireless-mesh-sae
@@ -1,7 +1,7 @@
 #!/usr/bin/lua
 
-local util = require 'gluon.util'
 local site = require 'gluon.site'
+local wireless = require 'gluon.wireless'
 local hash = require 'hash'
 local uci = require('simple-uci').cursor()
 
@@ -11,7 +11,7 @@ local function configure_sae(vif)
 	uci:set('wireless', vif, 'key', site.wifi.mesh.sae_passphrase() or hash.md5(site.prefix6()))
 end
 
-util.foreach_radio(uci, function(radio, _, _)
+wireless.foreach_radio(uci, function(radio, _, _)
 	local radio_name = radio['.name']
 	local vif = 'mesh_' .. radio_name
 	local enable = site.wifi.mesh.sae(false)
diff --git a/package/gluon-web-private-wifi/luasrc/lib/gluon/config-mode/model/admin/privatewifi.lua b/package/gluon-web-private-wifi/luasrc/lib/gluon/config-mode/model/admin/privatewifi.lua
index 30ba4df31eeba92391c2340dc38249610324c6bd..b3d98ae8ed1862723eb9aae94f1737862fe587ed 100644
--- a/package/gluon-web-private-wifi/luasrc/lib/gluon/config-mode/model/admin/privatewifi.lua
+++ b/package/gluon-web-private-wifi/luasrc/lib/gluon/config-mode/model/admin/privatewifi.lua
@@ -1,6 +1,6 @@
 local uci = require("simple-uci").cursor()
-local util = require 'gluon.util'
 local platform = require 'gluon.platform'
+local wireless = require 'gluon.wireless'
 
 -- where to read the configuration from
 local primary_iface = 'wan_radio0'
@@ -47,12 +47,12 @@ mfp.default = uci:get('wireless', primary_iface, 'ieee80211w') or "0"
 
 
 function f:write()
-	util.foreach_radio(uci, function(radio, index)
+	wireless.foreach_radio(uci, function(radio, index)
 		local radio_name = radio['.name']
 		local name   = "wan_" .. radio_name
 
 		if enabled.data then
-			local macaddr = util.get_wlan_mac(uci, radio, index, 4)
+			local macaddr = wireless.get_wlan_mac(uci, radio, index, 4)
 
 			uci:section('wireless', 'wifi-iface', name, {
 				device     = radio_name,
diff --git a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
index 581455270afe1bc717347262d5b39dd2b332a26a..32cdc249032146bf6db091415da28b2b971226c5 100644
--- a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
+++ b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
@@ -1,6 +1,6 @@
 local iwinfo = require 'iwinfo'
 local uci = require("simple-uci").cursor()
-local util = require 'gluon.util'
+local wireless = require 'gluon.wireless'
 
 
 local function txpower_list(phy)
@@ -101,7 +101,7 @@ uci:foreach('wireless', 'wifi-device', function(config)
 	vif_option('client', {'client', 'owe'}, translate('Enable client network (access point)'))
 	vif_option('mesh', {'mesh'}, translate("Enable mesh network (802.11s)"))
 
-	local phy = util.find_phy(config)
+	local phy = wireless.find_phy(config)
 	if not phy then
 		return
 	end
@@ -155,7 +155,7 @@ if has_5ghz_radio() then
 			return
 		end
 
-		local phy = util.find_phy(uci:get_all('wireless', radio))
+		local phy = wireless.find_phy(uci:get_all('wireless', radio))
 
 		local ht = r:option(ListValue, 'outdoor_htmode', translate('HT Mode') .. ' (' .. radio .. ')')
 		ht:depends(outdoor, true)