modules/admin-full: rework hwmode/htmode/channel selection
[project/luci.git] / modules / admin-full / luasrc / model / cbi / admin_network / wifi.lua
index 2ad3d55..f61fda3 100644 (file)
@@ -14,17 +14,20 @@ $Id$
 
 local wa = require "luci.tools.webadmin"
 local nw = require "luci.model.network"
+local ut = require "luci.util"
+local nt = require "luci.sys".net
 local fs = require "nixio.fs"
 
 arg[1] = arg[1] or ""
 
 m = Map("wireless", "",
        translate("The <em>Device Configuration</em> section covers physical settings of the radio " ..
-               "hardware such as channel, transmit power or antenna selection which is shared among all " ..
+               "hardware such as channel, transmit power or antenna selection which are shared among all " ..
                "defined wireless networks (if the radio hardware is multi-SSID capable). Per network settings " ..
                "like encryption or operation mode are grouped in the <em>Interface Configuration</em>."))
 
 m:chain("network")
+m:chain("firewall")
 
 local ifsection
 
@@ -58,7 +61,7 @@ function m.parse(map)
                wdev:set("disabled", nil)
 
                nw:commit("wireless")
-               luci.sys.call("(env -i /sbin/wifi down; env -i /sbin/wifi up) >/dev/null 2>/dev/null")
+               luci.sys.call("(env -i /bin/ubus call network reload) >/dev/null 2>/dev/null")
 
                luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless", arg[1]))
                return
@@ -69,13 +72,45 @@ end
 m.title = luci.util.pcdata(wnet:get_i18n())
 
 
+local function txpower_list(iw)
+       local list = iw.txpwrlist or { }
+       local off  = tonumber(iw.txpower_offset) or 0
+       local new  = { }
+       local prev = -1
+       local _, val
+       for _, val in ipairs(list) do
+               local dbm = val.dbm + off
+               local mw  = math.floor(10 ^ (dbm / 10))
+               if mw ~= prev then
+                       prev = mw
+                       new[#new+1] = {
+                               display_dbm = dbm,
+                               display_mw  = mw,
+                               driver_dbm  = val.dbm,
+                               driver_mw   = val.mw
+                       }
+               end
+       end
+       return new
+end
+
+local function txpower_current(pwr, list)
+       pwr = tonumber(pwr)
+       if pwr ~= nil then
+               local _, item
+               for _, item in ipairs(list) do
+                       if item.driver_dbm >= pwr then
+                               return item.driver_dbm
+                       end
+               end
+       end
+       return (list[#list] and list[#list].driver_dbm) or pwr or 0
+end
+
 local iw = luci.sys.wifi.getiwinfo(arg[1])
-local hw_modes  = iw.hwmodelist or { }
-local tx_powers = iw.txpwrlist  or { }
-local tx_power  = tostring(
-       (iw.txpower and iw.txpower > 0 and iw.txpower) or
-       (#tx_powers > 0 and tx_powers[#tx_powers].dbm)
-)
+local hw_modes      = iw.hwmodelist or { }
+local tx_power_list = txpower_list(iw)
+local tx_power_cur  = txpower_current(wdev:get("txpower"), tx_power_list)
 
 s = m:section(NamedSection, wdev:name(), "wifi-device", translate("Device Configuration"))
 s.addremove = false
@@ -108,53 +143,76 @@ end
 
 
 local hwtype = wdev:get("type")
-local htcaps = wdev:get("ht_capab") and true or false
 
 -- NanoFoo
 local nsantenna = wdev:get("antenna")
 
-ch = s:taboption("general", Value, "channel", translate("Channel"))
-ch:value("auto", translate("auto"))
-for _, f in ipairs(iw and iw.freqlist or luci.sys.wifi.channels()) do
-       if not f.restricted then
-               ch:value(f.channel, "%i (%.3f GHz)" %{ f.channel, f.mhz / 1000 })
+-- Check whether there are client interfaces on the same radio,
+-- if yes, lock the channel choice as these stations will dicatate the freq
+local found_sta = nil
+local _, net
+if wnet:mode() ~= "sta" then
+       for _, net in ipairs(wdev:get_wifinets()) do
+               if net:mode() == "sta" then
+                       if not found_sta then
+                               found_sta = {}
+                               found_sta.channel = net:channel()
+                               found_sta.names = {}
+                       end
+                       found_sta.names[#found_sta.names+1] = net:shortname()
+               end
        end
 end
 
+if found_sta then
+       ch = s:taboption("general", DummyValue, "choice", translate("Channel"))
+       ch.value = translatef("Locked to channel %d used by: %s",
+               found_sta.channel, table.concat(found_sta.names, ", "))
+else
+       ch = s:taboption("general", Value, "_mode_freq", '<br />'..translate("Operating frequency"))
+       ch.hwmodes = iw.hwmodelist
+       ch.freqlist = iw.freqlist
+       ch.template = "cbi/wireless_modefreq"
+
+       function ch.cfgvalue(self, section)
+               return {
+                       m:get(section, "hwmode") or "",
+                       m:get(section, "channel") or "auto",
+                       m:get(section, "htmode") or ""
+               }
+       end
+
+       function ch.formvalue(self, section)
+               return {
+                       m:formvalue(self:cbid(section) .. ".band") or (iw.hwmodelist.g and "11g" or "11a"),
+                       m:formvalue(self:cbid(section) .. ".channel") or "auto",
+                       m:formvalue(self:cbid(section) .. ".htmode") or ""
+               }
+       end
+
+       function ch.write(self, section, value)
+               m:set(section, "hwmode", value[1])
+               m:set(section, "channel", value[2])
+               m:set(section, "htmode", value[3])
+       end
+end
 
 ------------------- MAC80211 Device ------------------
 
 if hwtype == "mac80211" then
-       tp = s:taboption("general",
-               (tx_powers and #tx_powers > 0) and ListValue or Value,
-               "txpower", translate("Transmit Power"), "dBm")
-
-       tp.rmempty = true
-       tp.default = tx_power
-       for _, p in ipairs(tx_powers or {}) do
-               tp:value(p.dbm, "%i dBm (%i mW)" %{ p.dbm, p.mw })
-       end
+       if #tx_power_list > 1 then
+               tp = s:taboption("general", ListValue,
+                       "txpower", translate("Transmit Power"), "dBm")
+               tp.rmempty = true
+               tp.default = tx_power_cur
+               function tp.cfgvalue(...)
+                       return txpower_current(Value.cfgvalue(...), tx_power_list)
+               end
 
-       mode = s:taboption("advanced", ListValue, "hwmode", translate("Mode"))
-       mode:value("", translate("auto"))
-       if hw_modes.b then mode:value("11b", "802.11b") end
-       if hw_modes.g then mode:value("11g", "802.11g") end
-       if hw_modes.a then mode:value("11a", "802.11a") end
-
-       if htcaps then
-               if hw_modes.g and hw_modes.n then mode:value("11ng", "802.11g+n") end
-               if hw_modes.a and hw_modes.n then mode:value("11na", "802.11a+n") end
-
-               htmode = s:taboption("advanced", ListValue, "htmode", translate("HT mode"))
-               htmode:depends("hwmode", "11na")
-               htmode:depends("hwmode", "11ng")
-               htmode:value("HT20", "20MHz")
-               htmode:value("HT40-", translate("40MHz 2nd channel below"))
-               htmode:value("HT40+", translate("40MHz 2nd channel above"))
-
-               --htcapab = s:taboption("advanced", DynamicList, "ht_capab", translate("HT capabilities"))
-               --htcapab:depends("hwmode", "11na")
-               --htcapab:depends("hwmode", "11ng")
+               for _, p in ipairs(tx_power_list) do
+                       tp:value(p.driver_dbm, "%i dBm (%i mW)"
+                               %{ p.display_dbm, p.display_mw })
+               end
        end
 
        local cl = iw and iw.countrylist
@@ -170,6 +228,21 @@ if hwtype == "mac80211" then
 
        s:taboption("advanced", Value, "distance", translate("Distance Optimization"),
                translate("Distance to farthest network member in meters."))
+
+       -- external antenna profiles
+       local eal = iw and iw.extant
+       if eal and #eal > 0 then
+               ea = s:taboption("advanced", ListValue, "extant", translate("Antenna Configuration"))
+               for _, eap in ipairs(eal) do
+                       ea:value(eap.id, "%s (%s)" %{ eap.name, eap.description })
+                       if eap.selected then
+                               ea.default = eap.id
+                       end
+               end
+       end
+
+       s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
+       s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold"))
 end
 
 
@@ -177,24 +250,20 @@ end
 
 if hwtype == "atheros" then
        tp = s:taboption("general",
-               (#tx_powers > 0) and ListValue or Value,
+               (#tx_power_list > 0) and ListValue or Value,
                "txpower", translate("Transmit Power"), "dBm")
 
        tp.rmempty = true
-       tp.default = tx_power
-       for _, p in ipairs(tx_powers or {}) do
-               tp:value(p.dbm, "%i dBm (%i mW)" %{ p.dbm, p.mw })
+       tp.default = tx_power_cur
+
+       function tp.cfgvalue(...)
+               return txpower_current(Value.cfgvalue(...), tx_power_list)
        end
 
-       mode = s:taboption("advanced", ListValue, "hwmode", translate("Mode"))
-       mode:value("", translate("auto"))
-       if hw_modes.b then mode:value("11b", "802.11b") end
-       if hw_modes.g then mode:value("11g", "802.11g") end
-       if hw_modes.a then mode:value("11a", "802.11a") end
-       if hw_modes.g then mode:value("11bg", "802.11b+g") end
-       if hw_modes.g then mode:value("11gst", "802.11g + Turbo") end
-       if hw_modes.a then mode:value("11ast", "802.11a + Turbo") end
-       mode:value("fh", translate("Frequency Hopping"))
+       for _, p in ipairs(tx_power_list) do
+               tp:value(p.driver_dbm, "%i dBm (%i mW)"
+                       %{ p.display_dbm, p.display_mw })
+       end
 
        s:taboption("advanced", Flag, "diversity", translate("Diversity")).rmempty = false
 
@@ -238,20 +307,20 @@ end
 
 if hwtype == "broadcom" then
        tp = s:taboption("general",
-               (#tx_powers > 0) and ListValue or Value,
+               (#tx_power_list > 0) and ListValue or Value,
                "txpower", translate("Transmit Power"), "dBm")
 
        tp.rmempty = true
-       tp.default = tx_power
-       for _, p in ipairs(tx_powers or {}) do
-               tp:value(p.dbm, "%i dBm (%i mW)" %{ p.dbm, p.mw })
+       tp.default = tx_power_cur
+
+       function tp.cfgvalue(...)
+               return txpower_current(Value.cfgvalue(...), tx_power_list)
        end
 
-       mode = s:taboption("advanced", ListValue, "hwmode", translate("Mode"))
-       mode:value("11bg", "802.11b+g")
-       mode:value("11b", "802.11b")
-       mode:value("11g", "802.11g")
-       mode:value("11gst", "802.11g + Turbo")
+       for _, p in ipairs(tx_power_list) do
+               tp:value(p.driver_dbm, "%i dBm (%i mW)"
+                       %{ p.display_dbm, p.display_mw })
+       end
 
        ant1 = s:taboption("advanced", ListValue, "txantenna", translate("Transmitter Antenna"))
        ant1.widget = "radio"
@@ -313,13 +382,13 @@ mode:value("adhoc", translate("Ad-Hoc"))
 bssid = s:taboption("general", Value, "bssid", translate("<abbr title=\"Basic Service Set Identifier\">BSSID</abbr>"))
 
 network = s:taboption("general", Value, "network", translate("Network"),
-       translate("Choose the network you want to attach to this wireless interface. " ..
-               "Select <em>unspecified</em> to not attach any network or fill out the " ..
-               "<em>create</em> field to define a new network."))
+       translate("Choose the network(s) you want to attach to this wireless interface or " ..
+               "fill out the <em>create</em> field to define a new network."))
 
 network.rmempty = true
 network.template = "cbi/network_netlist"
-network.widget = "radio"
+network.widget = "checkbox"
+network.novirtual = true
 
 function network.write(self, section, value)
        local i = nw:get_interface(section)
@@ -334,10 +403,18 @@ function network.write(self, section, value)
                                if n then n:del_interface(i) end
                        end
                else
-                       local n = nw:get_network(value)
-                       if n then
-                               n:set("type", "bridge")
-                               n:add_interface(i)
+                       local v
+                       for _, v in ipairs(i:get_networks()) do
+                               v:del_interface(i)
+                       end
+                       for v in ut.imatch(value) do
+                               local n = nw:get_network(v)
+                               if n then
+                                       if not n:is_empty() then
+                                               n:set("type", "bridge")
+                                       end
+                                       n:add_interface(i)
+                               end
                        end
                end
        end
@@ -353,9 +430,8 @@ if hwtype == "mac80211" then
        mode:value("ahdemo", translate("Pseudo Ad-Hoc (ahdemo)"))
        mode:value("monitor", translate("Monitor"))
        bssid:depends({mode="adhoc"})
-
-       s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
-       s:taboption("advanced", Value, "rts", translate("RTS/CTS Threshold"))
+       bssid:depends({mode="sta"})
+       bssid:depends({mode="sta-wds"})
 
        mp = s:taboption("macfilter", ListValue, "macfilter", translate("MAC-Address Filter"))
        mp:depends({mode="ap"})
@@ -368,6 +444,7 @@ if hwtype == "mac80211" then
        ml.datatype = "macaddr"
        ml:depends({macfilter="allow"})
        ml:depends({macfilter="deny"})
+       nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
 
        mode:value("ap-wds", "%s (%s)" % {translate("Access Point"), translate("WDS")})
        mode:value("sta-wds", "%s (%s)" % {translate("Client"), translate("WDS")})
@@ -401,6 +478,11 @@ if hwtype == "mac80211" then
        hidden = s:taboption("general", Flag, "hidden", translate("Hide <abbr title=\"Extended Service Set Identifier\">ESSID</abbr>"))
        hidden:depends({mode="ap"})
        hidden:depends({mode="ap-wds"})
+
+       wmm = s:taboption("general", Flag, "wmm", translate("WMM Mode"))
+       wmm:depends({mode="ap"})
+       wmm:depends({mode="ap-wds"})
+       wmm.default = wmm.enabled
 end
 
 
@@ -467,6 +549,7 @@ if hwtype == "atheros" then
        ml.datatype = "macaddr"
        ml:depends({macpolicy="allow"})
        ml:depends({macpolicy="deny"})
+       nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
 
        s:taboption("advanced", Value, "rate", translate("Transmission Rate"))
        s:taboption("advanced", Value, "mcast_rate", translate("Multicast Rate"))
@@ -540,6 +623,7 @@ if hwtype == "prism2" then
        ml = s:taboption("macfilter", DynamicList, "maclist", translate("MAC-List"))
        ml:depends({macpolicy="allow"})
        ml:depends({macpolicy="deny"})
+       nt.mac_hints(function(mac, name) ml:value(mac, "%s (%s)" %{ mac, name }) end)
 
        s:taboption("advanced", Value, "rate", translate("Transmission Rate"))
        s:taboption("advanced", Value, "frag", translate("Fragmentation Threshold"))
@@ -560,34 +644,86 @@ encr:depends({mode="ap-wds"})
 encr:depends({mode="sta-wds"})
 encr:depends({mode="mesh"})
 
+cipher = s:taboption("encryption", ListValue, "cipher", translate("Cipher"))
+cipher:depends({encryption="wpa"})
+cipher:depends({encryption="wpa2"})
+cipher:depends({encryption="psk"})
+cipher:depends({encryption="psk2"})
+cipher:depends({encryption="wpa-mixed"})
+cipher:depends({encryption="psk-mixed"})
+cipher:value("auto", translate("auto"))
+cipher:value("ccmp", translate("Force CCMP (AES)"))
+cipher:value("tkip", translate("Force TKIP"))
+cipher:value("tkip+ccmp", translate("Force TKIP and CCMP (AES)"))
+
+function encr.cfgvalue(self, section)
+       local v = tostring(ListValue.cfgvalue(self, section))
+       if v == "wep" then
+               return "wep-open"
+       elseif v and v:match("%+") then
+               return (v:gsub("%+.+$", ""))
+       end
+       return v
+end
+
 function encr.write(self, section, value)
+       local e = tostring(encr:formvalue(section))
+       local c = tostring(cipher:formvalue(section))
        if value == "wpa" or value == "wpa2"  then
                self.map.uci:delete("wireless", section, "key")
        end
-       self.map.uci:set("wireless", section, "encryption", value)
+       if e and (c == "tkip" or c == "ccmp" or c == "tkip+ccmp") then
+               e = e .. "+" .. c
+       end
+       self.map:set(section, "encryption", e)
+end
+
+function cipher.cfgvalue(self, section)
+       local v = tostring(ListValue.cfgvalue(encr, section))
+       if v and v:match("%+") then
+               v = v:gsub("^[^%+]+%+", "")
+               if v == "aes" then v = "ccmp"
+               elseif v == "tkip+aes" then v = "tkip+ccmp"
+               elseif v == "aes+tkip" then v = "tkip+ccmp"
+               elseif v == "ccmp+tkip" then v = "tkip+ccmp"
+               end
+       end
+       return v
+end
+
+function cipher.write(self, section)
+       return encr:write(section)
 end
 
 
 encr:value("none", "No Encryption")
-encr:value("wep-open",   translate("WEP Open System"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
-encr:value("wep-shared", translate("WEP Shared Key"),  {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+encr:value("wep-open",   translate("WEP Open System"), {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"})
+encr:value("wep-shared", translate("WEP Shared Key"),  {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"}, {mode="adhoc"}, {mode="ahdemo"}, {mode="wds"})
 
 if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
        local supplicant = fs.access("/usr/sbin/wpa_supplicant")
        local hostapd = fs.access("/usr/sbin/hostapd")
 
+       -- Probe EAP support
+       local has_ap_eap  = (os.execute("hostapd -veap >/dev/null 2>/dev/null") == 0)
+       local has_sta_eap = (os.execute("wpa_supplicant -veap >/dev/null 2>/dev/null") == 0)
+
        if hostapd and supplicant then
-               encr:value("psk", "WPA-PSK")
-               encr:value("psk2", "WPA2-PSK")
-               encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode")
-               encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
-               encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+               encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+               encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+               encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+               if has_ap_eap and has_sta_eap then
+                       encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+                       encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="sta"}, {mode="ap-wds"}, {mode="sta-wds"})
+               end
        elseif hostapd and not supplicant then
-               encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="ap-wds"}, {mode="adhoc"}, {mode="ahdemo"})
-               encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="ap-wds"}, {mode="adhoc"}, {mode="ahdemo"})
-               encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="ap-wds"}, {mode="adhoc"}, {mode="ahdemo"})
-               encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="ap-wds"})
-               encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="ap-wds"})
+               encr:value("psk", "WPA-PSK", {mode="ap"}, {mode="ap-wds"})
+               encr:value("psk2", "WPA2-PSK", {mode="ap"}, {mode="ap-wds"})
+               encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="ap"}, {mode="ap-wds"})
+               if has_ap_eap then
+                       encr:value("wpa", "WPA-EAP", {mode="ap"}, {mode="ap-wds"})
+                       encr:value("wpa2", "WPA2-EAP", {mode="ap"}, {mode="ap-wds"})
+               end
                encr.description = translate(
                        "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " ..
                        "and ad-hoc mode) to be installed."
@@ -596,8 +732,10 @@ if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
                encr:value("psk", "WPA-PSK", {mode="sta"}, {mode="sta-wds"})
                encr:value("psk2", "WPA2-PSK", {mode="sta"}, {mode="sta-wds"})
                encr:value("psk-mixed", "WPA-PSK/WPA2-PSK Mixed Mode", {mode="sta"}, {mode="sta-wds"})
-               encr:value("wpa", "WPA-EAP", {mode="sta"}, {mode="sta-wds"})
-               encr:value("wpa2", "WPA2-EAP", {mode="sta"}, {mode="sta-wds"})
+               if has_sta_eap then
+                       encr:value("wpa", "WPA-EAP", {mode="sta"}, {mode="sta-wds"})
+                       encr:value("wpa2", "WPA2-EAP", {mode="sta"}, {mode="sta-wds"})
+               end
                encr.description = translate(
                        "WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP " ..
                        "and ad-hoc mode) to be installed."
@@ -746,6 +884,12 @@ if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
        cacert:depends({mode="sta-wds", encryption="wpa"})
        cacert:depends({mode="sta-wds", encryption="wpa2"})
 
+       clientcert = s:taboption("encryption", FileUpload, "client_cert", translate("Path to Client-Certificate"))
+       clientcert:depends({mode="sta", encryption="wpa"})
+       clientcert:depends({mode="sta", encryption="wpa2"})
+       clientcert:depends({mode="sta-wds", encryption="wpa"})
+       clientcert:depends({mode="sta-wds", encryption="wpa2"})
+
        privkey = s:taboption("encryption", FileUpload, "priv_key", translate("Path to Private Key"))
        privkey:depends({mode="sta", eap_type="tls", encryption="wpa2"})
        privkey:depends({mode="sta", eap_type="tls", encryption="wpa"})
@@ -795,4 +939,18 @@ if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
        password:depends({mode="sta-wds", eap_type="ttls", encryption="wpa"})
 end
 
+if hwtype == "atheros" or hwtype == "mac80211" or hwtype == "prism2" then
+       local wpasupplicant = fs.access("/usr/sbin/wpa_supplicant")
+       local hostcli = fs.access("/usr/sbin/hostapd_cli")
+       if hostcli and wpasupplicant then
+               wps = s:taboption("encryption", Flag, "wps_pushbutton", translate("Enable WPS pushbutton, requires WPA(2)-PSK"))
+               wps.enabled = "1"
+               wps.disabled = "0"
+               wps.rmempty = false
+               wps:depends("encryption", "psk")
+               wps:depends("encryption", "psk2")
+               wps:depends("encryption", "psk-mixed")
+       end
+end
+
 return m