diff --git a/files/www/luci-static/resources/view/network/wireless.js b/files/www/luci-static/resources/view/network/wireless.js new file mode 100644 index 0000000000..bd9af9f384 --- /dev/null +++ b/files/www/luci-static/resources/view/network/wireless.js @@ -0,0 +1,2270 @@ +'use strict'; +'require view'; +'require dom'; +'require poll'; +'require fs'; +'require ui'; +'require rpc'; +'require uci'; +'require form'; +'require network'; +'require firewall'; +'require tools.widgets as widgets'; + +var isReadonlyView = !L.hasViewPermission(); + +function count_changes(section_id) { + var changes = ui.changes.changes, n = 0; + + if (!L.isObject(changes)) + return n; + + if (Array.isArray(changes.wireless)) + for (var i = 0; i < changes.wireless.length; i++) + n += (changes.wireless[i][1] == section_id); + + return n; +} + +function render_radio_badge(radioDev) { + return E('span', { 'class': 'ifacebadge' }, [ + E('img', { 'src': L.resource('icons/wifi%s.png').format(radioDev.isUp() ? '' : '_disabled') }), + ' ', + radioDev.getName() + ]); +} + +function render_signal_badge(signalPercent, signalValue, noiseValue, wrap, mode) { + var icon, title, value; + + if (signalPercent < 0) + icon = L.resource('icons/signal-none.png'); + else if (signalPercent == 0) + icon = L.resource('icons/signal-0.png'); + else if (signalPercent < 25) + icon = L.resource('icons/signal-0-25.png'); + else if (signalPercent < 50) + icon = L.resource('icons/signal-25-50.png'); + else if (signalPercent < 75) + icon = L.resource('icons/signal-50-75.png'); + else + icon = L.resource('icons/signal-75-100.png'); + + if (signalValue != null && signalValue != 0) { + if (noiseValue != null && noiseValue != 0) { + value = '%d/%d\xa0%s'.format(signalValue, noiseValue, _('dBm')); + title = '%s: %d %s / %s: %d %s / %s %d'.format( + _('Signal'), signalValue, _('dBm'), + _('Noise'), noiseValue, _('dBm'), + _('SNR'), signalValue - noiseValue); + } + else { + value = '%d\xa0%s'.format(signalValue, _('dBm')); + title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm')); + } + } + else if (signalPercent > -1) { + switch (mode) { + case 'ap': + title = _('No client associated'); + break; + + case 'sta': + case 'adhoc': + case 'mesh': + title = _('Not associated'); + break; + + default: + title = _('No RX signal'); + } + + if (noiseValue != null && noiseValue != 0) { + value = '---/%d\x0a%s'.format(noiseValue, _('dBm')); + title = '%s / %s: %d %s'.format(title, _('Noise'), noiseValue, _('dBm')); + } + else { + value = '---\xa0%s'.format(_('dBm')); + } + } + else { + value = E('em', {}, E('small', {}, [ _('disabled') ])); + title = _('Interface is disabled'); + } + + return E('div', { + 'class': wrap ? 'center' : 'ifacebadge', + 'title': title, + 'data-signal': signalValue, + 'data-noise': noiseValue + }, [ + E('img', { 'src': icon }), + E('span', {}, [ + wrap ? E('br') : ' ', + value + ]) + ]); +} + +function render_network_badge(radioNet) { + return render_signal_badge( + radioNet.isUp() ? radioNet.getSignalPercent() : -1, + radioNet.getSignal(), radioNet.getNoise(), false, radioNet.getMode()); +} + +function render_radio_status(radioDev, wifiNets) { + var name = radioDev.getI18n().replace(/ Wireless Controller .+$/, ''), + node = E('div', [ E('big', {}, E('strong', {}, name)), E('div') ]), + channel, frequency, bitrate; + + for (var i = 0; i < wifiNets.length; i++) { + channel = channel || wifiNets[i].getChannel(); + frequency = frequency || wifiNets[i].getFrequency(); + bitrate = bitrate || wifiNets[i].getBitRate(); + } + + if (radioDev.isUp()) + L.itemlist(node.lastElementChild, [ + _('Channel'), '%s (%s %s)'.format(channel || '?', frequency || '?', _('GHz')), + _('Bitrate'), '%s %s'.format(bitrate || '?', _('Mbit/s')) + ], ' | '); + else + node.lastElementChild.appendChild(E('em', _('Device is not active'))); + + return node; +} + +function render_network_status(radioNet) { + var mode = radioNet.getActiveMode(), + bssid = radioNet.getActiveBSSID(), + channel = radioNet.getChannel(), + disabled = (radioNet.get('disabled') == '1' || uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == '1'), + is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled), + is_mesh = (radioNet.getMode() == 'mesh'), + changecount = count_changes(radioNet.getName()), + status_text = null; + + if (changecount) + status_text = E('a', { + href: '#', + click: L.bind(ui.changes.displayChanges, ui.changes) + }, _('Interface has %d pending changes').format(changecount)); + else if (!is_assoc) + status_text = E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated')); + + return L.itemlist(E('div'), [ + is_mesh ? _('Mesh ID') : _('SSID'), (is_mesh ? radioNet.getMeshID() : radioNet.getSSID()) || '?', + _('Mode'), mode, + _('BSSID'), (!changecount && is_assoc) ? bssid : null, + _('Encryption'), (!changecount && is_assoc) ? radioNet.getActiveEncryption() || _('None') : null, + null, status_text + ], [ ' | ', E('br') ]); +} + +function render_modal_status(node, radioNet) { + var mode = radioNet.getActiveMode(), + noise = radioNet.getNoise(), + bssid = radioNet.getActiveBSSID(), + channel = radioNet.getChannel(), + disabled = (radioNet.get('disabled') == '1'), + is_assoc = (bssid && bssid != '00:00:00:00:00:00' && channel && mode != 'Unknown' && !disabled); + + if (node == null) + node = E('span', { 'class': 'ifacebadge large', 'data-network': radioNet.getName() }, [ E('small'), E('span') ]); + + dom.content(node.firstElementChild, render_signal_badge( + disabled ? -1 : radioNet.getSignalPercent(), + radioNet.getSignal(), noise, true, radioNet.getMode())); + + L.itemlist(node.lastElementChild, [ + _('Mode'), mode, + _('SSID'), radioNet.getSSID() || '?', + _('BSSID'), is_assoc ? bssid : null, + _('Encryption'), is_assoc ? radioNet.getActiveEncryption() || _('None') : null, + _('Channel'), is_assoc ? '%d (%.3f %s)'.format(radioNet.getChannel(), radioNet.getFrequency() || 0, _('GHz')) : null, + _('Tx-Power'), is_assoc ? '%d %s'.format(radioNet.getTXPower(), _('dBm')) : null, + _('Signal'), is_assoc ? '%d %s'.format(radioNet.getSignal(), _('dBm')) : null, + _('Noise'), (is_assoc && noise != null) ? '%d %s'.format(noise, _('dBm')) : null, + _('Bitrate'), is_assoc ? '%.1f %s'.format(radioNet.getBitRate() || 0, _('Mbit/s')) : null, + _('Country'), is_assoc ? radioNet.getCountryCode() : null + ], [ ' | ', E('br'), E('br'), E('br'), E('br'), E('br'), ' | ', E('br'), ' | ' ]); + + if (!is_assoc) + dom.append(node.lastElementChild, E('em', disabled ? _('Wireless is disabled') : _('Wireless is not associated'))); + + return node; +} + +function format_wifirate(rate) { + var s = '%.1f\xa0%s, %d\xa0%s'.format(rate.rate / 1000, _('Mbit/s'), rate.mhz, _('MHz')), + ht = rate.ht, vht = rate.vht, + mhz = rate.mhz, nss = rate.nss, + mcs = rate.mcs, sgi = rate.short_gi, + he = rate.he, he_gi = rate.he_gi, + he_dcm = rate.he_dcm; + + if (ht || vht) { + if (vht) s += ', VHT-MCS\xa0%d'.format(mcs); + if (nss) s += ', VHT-NSS\xa0%d'.format(nss); + if (ht) s += ', MCS\xa0%s'.format(mcs); + if (sgi) s += ', ' + _('Short GI').replace(/ /g, '\xa0'); + } + + if (he) { + s += ', HE-MCS\xa0%d'.format(mcs); + if (nss) s += ', HE-NSS\xa0%d'.format(nss); + if (he_gi) s += ', HE-GI\xa0%d'.format(he_gi); + if (he_dcm) s += ', HE-DCM\xa0%d'.format(he_dcm); + } + + return s; +} + +function radio_restart(id, ev) { + var row = document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(id)), + dsc = row.querySelector('[data-name="_stat"] > div'), + btn = row.querySelector('.cbi-section-actions button'); + + btn.blur(); + btn.classList.add('spinning'); + btn.disabled = true; + + dsc.setAttribute('restart', ''); + dom.content(dsc, E('em', _('Device is restarting…'))); +} + +function network_updown(id, map, ev) { + var radio = uci.get('wireless', id, 'device'), + disabled = (uci.get('wireless', id, 'disabled') == '1') || + (uci.get('wireless', radio, 'disabled') == '1'); + + if (disabled) { + uci.unset('wireless', id, 'disabled'); + uci.unset('wireless', radio, 'disabled'); + } + else { + uci.set('wireless', id, 'disabled', '1'); + + var all_networks_disabled = true, + wifi_ifaces = uci.sections('wireless', 'wifi-iface'); + + for (var i = 0; i < wifi_ifaces.length; i++) { + if (wifi_ifaces[i].device == radio && wifi_ifaces[i].disabled != '1') { + all_networks_disabled = false; + break; + } + } + + if (all_networks_disabled) + uci.set('wireless', radio, 'disabled', '1'); + } + + return map.save().then(function() { + ui.changes.apply() + }); +} + +function next_free_sid(offset) { + var sid = 'wifinet' + offset; + + while (uci.get('wireless', sid)) + sid = 'wifinet' + (++offset); + + return sid; +} + +function add_dependency_permutations(o, deps) { + var res = null; + + for (var key in deps) { + if (!deps.hasOwnProperty(key) || !Array.isArray(deps[key])) + continue; + + var list = deps[key], + tmp = []; + + for (var j = 0; j < list.length; j++) { + for (var k = 0; k < (res ? res.length : 1); k++) { + var item = (res ? Object.assign({}, res[k]) : {}); + item[key] = list[j]; + tmp.push(item); + } + } + + res = tmp; + } + + for (var i = 0; i < (res ? res.length : 0); i++) + o.depends(res[i]); +} + +var CBIWifiFrequencyValue = form.Value.extend({ + callFrequencyList: rpc.declare({ + object: 'iwinfo', + method: 'freqlist', + params: [ 'device' ], + expect: { results: [] } + }), + + load: function(section_id) { + return Promise.all([ + network.getWifiDevice(section_id), + this.callFrequencyList(section_id) + ]).then(L.bind(function(data) { + this.channels = { + '2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], + '5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], + '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', true ] : [], + '60g': [] + }; + + for (var i = 0; i < data[1].length; i++) { + if (!data[1][i].band) + continue; + + var band = '%dg'.format(data[1][i].band); + + this.channels[band].push( + data[1][i].channel, + '%d (%d Mhz)'.format(data[1][i].channel, data[1][i].mhz), + !data[1][i].restricted + ); + } + + var hwmodelist = L.toArray(data[0] ? data[0].getHWModes() : null) + .reduce(function(o, v) { o[v] = true; return o }, {}); + + this.modes = [ + '', 'Legacy', hwmodelist.a || hwmodelist.b || hwmodelist.g, + 'n', 'N', hwmodelist.n, + 'ac', 'AC', L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac, + 'ax', 'AX', L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax + ]; + + var htmodelist = L.toArray(data[0] ? data[0].getHTModes() : null) + .reduce(function(o, v) { o[v] = true; return o }, {}); + + this.htmodes = { + '': [ '', '-', true ], + 'n': [ + 'HT20', '20 MHz', htmodelist.HT20, + 'HT40', '40 MHz', htmodelist.HT40, + 'VHT20', '20 MHz', htmodelist.VHT20, + 'VHT40', '40 MHz', htmodelist.VHT40, + ], + 'ac': [ + 'VHT20', '20 MHz', htmodelist.VHT20, + 'VHT40', '40 MHz', htmodelist.VHT40, + 'VHT80', '80 MHz', htmodelist.VHT80, + 'VHT160', '160 MHz', htmodelist.VHT160 + ], + 'ax': [ + 'HE20', '20 MHz', htmodelist.HE20, + 'HE40', '40 MHz', htmodelist.HE40, + 'HE80', '80 MHz', htmodelist.HE80, + 'HE160', '160 MHz', htmodelist.HE160 + ] + }; + + this.bands = { + '': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3, + '60g', '60 GHz', this.channels['60g'].length > 0 + ], + 'n': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3 + ], + 'ac': [ + '5g', '5 GHz', true + ], + 'ax': [ + '2g', '2.4 GHz', this.channels['2g'].length > 3, + '5g', '5 GHz', this.channels['5g'].length > 3, + '6g', '6 GHz', this.channels['6g'].length > 3 + ] + }; + }, this)); + }, + + setValues: function(sel, vals) { + if (sel.vals) + sel.vals.selected = sel.selectedIndex; + + while (sel.options[0]) + sel.remove(0); + + for (var i = 0; vals && i < vals.length; i += 3) + if (vals[i+2]) + sel.add(E('option', { value: vals[i+0] }, [ vals[i+1] ])); + + if (vals && !isNaN(vals.selected)) + sel.selectedIndex = vals.selected; + + sel.parentNode.style.display = (sel.options.length <= 1) ? 'none' : ''; + sel.vals = vals; + }, + + toggleWifiMode: function(elem) { + this.toggleWifiHTMode(elem); + this.toggleWifiBand(elem); + }, + + toggleWifiHTMode: function(elem) { + var mode = elem.querySelector('.mode'); + var bwdt = elem.querySelector('.htmode'); + + this.setValues(bwdt, this.htmodes[mode.value]); + }, + + toggleWifiBand: function(elem) { + var mode = elem.querySelector('.mode'); + var band = elem.querySelector('.band'); + + this.setValues(band, this.bands[mode.value]); + this.toggleWifiChannel(elem); + + this.map.checkDepends(); + }, + + toggleWifiChannel: function(elem) { + var band = elem.querySelector('.band'); + var chan = elem.querySelector('.channel'); + + this.setValues(chan, this.channels[band.value]); + }, + + setInitialValues: function(section_id, elem) { + var mode = elem.querySelector('.mode'), + band = elem.querySelector('.band'), + chan = elem.querySelector('.channel'), + bwdt = elem.querySelector('.htmode'), + htval = uci.get('wireless', section_id, 'htmode'), + hwval = uci.get('wireless', section_id, 'hwmode'), + chval = uci.get('wireless', section_id, 'channel'), + bandval = uci.get('wireless', section_id, 'band'); + + this.setValues(mode, this.modes); + + if (/HE20|HE40|HE80|HE160/.test(htval)) + mode.value = 'ax'; + else if (/VHT20|VHT40|VHT80|VHT160/.test(htval)) + mode.value = 'ac'; + else if (/HT20|HT40/.test(htval)) + mode.value = 'n'; + else + mode.value = ''; + + this.toggleWifiMode(elem); + + if (hwval != null) { + this.useBandOption = false; + + if (/a/.test(hwval)) + band.value = '5g'; + else + band.value = '2g'; + } + else { + this.useBandOption = true; + + band.value = bandval; + } + + this.toggleWifiBand(elem); + + bwdt.value = htval; + chan.value = chval || (chan.options[0] ? chan.options[0].value : 'auto'); + + return elem; + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var elem = E('div'); + + dom.content(elem, [ + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Mode'), E('br'), + E('select', { + 'class': 'mode', + 'style': 'width:auto', + 'change': L.bind(this.toggleWifiMode, this, elem), + 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Band'), E('br'), + E('select', { + 'class': 'band', + 'style': 'width:auto', + 'change': L.bind(this.toggleWifiBand, this, elem), + 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Channel'), E('br'), + E('select', { + 'class': 'channel', + 'style': 'width:auto', + 'change': L.bind(this.map.checkDepends, this.map), + 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly + }) + ]), + E('label', { 'style': 'float:left; margin-right:3px' }, [ + _('Width'), E('br'), + E('select', { + 'class': 'htmode', + 'style': 'width:auto', + 'change': L.bind(this.map.checkDepends, this.map), + 'disabled': (this.disabled != null) ? this.disabled : this.map.readonly + }) + ]), + E('br', { 'style': 'clear:left' }) + ]); + + return this.setInitialValues(section_id, elem); + }, + + cfgvalue: function(section_id) { + return [ + uci.get('wireless', section_id, 'htmode'), + uci.get('wireless', section_id, 'hwmode') || uci.get('wireless', section_id, 'band'), + uci.get('wireless', section_id, 'channel') + ]; + }, + + formvalue: function(section_id) { + var node = this.map.findElement('data-field', this.cbid(section_id)); + + return [ + node.querySelector('.htmode').value, + node.querySelector('.band').value, + node.querySelector('.channel').value + ]; + }, + + write: function(section_id, value) { + uci.set('wireless', section_id, 'htmode', value[0] || null); + + if (this.useBandOption) + uci.set('wireless', section_id, 'band', value[1]); + else + uci.set('wireless', section_id, 'hwmode', (value[1] == '2g') ? '11g' : '11a'); + + uci.set('wireless', section_id, 'channel', value[2]); + } +}); + +var CBIWifiTxPowerValue = form.ListValue.extend({ + callTxPowerList: rpc.declare({ + object: 'iwinfo', + method: 'txpowerlist', + params: [ 'device' ], + expect: { results: [] } + }), + + load: function(section_id) { + return this.callTxPowerList(section_id).then(L.bind(function(pwrlist) { + this.powerval = this.wifiNetwork ? this.wifiNetwork.getTXPower() : null; + this.poweroff = this.wifiNetwork ? this.wifiNetwork.getTXPowerOffset() : null; + + this.value('', _('driver default')); + + for (var i = 0; i < pwrlist.length; i++) + this.value(pwrlist[i].dbm, '%d dBm (%d mW)'.format(pwrlist[i].dbm, pwrlist[i].mw)); + + return form.ListValue.prototype.load.apply(this, [section_id]); + }, this)); + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var widget = form.ListValue.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); + widget.firstElementChild.style.width = 'auto'; + + dom.append(widget, E('span', [ + ' - ', _('Current power'), ': ', + E('span', [ this.powerval != null ? '%d dBm'.format(this.powerval) : E('em', _('unknown')) ]), + this.poweroff ? ' + %d dB offset = %s dBm'.format(this.poweroff, this.powerval != null ? this.powerval + this.poweroff : '?') : '' + ])); + + return widget; + } +}); + +var CBIWifiCountryValue = form.Value.extend({ + callCountryList: rpc.declare({ + object: 'iwinfo', + method: 'countrylist', + params: [ 'device' ], + expect: { results: [] } + }), + + load: function(section_id) { + return this.callCountryList(section_id).then(L.bind(function(countrylist) { + if (Array.isArray(countrylist) && countrylist.length > 0) { + this.value('', _('driver default')); + + for (var i = 0; i < countrylist.length; i++) + this.value(countrylist[i].iso3166, '%s - %s'.format(countrylist[i].iso3166, countrylist[i].country)); + } + + return form.Value.prototype.load.apply(this, [section_id]); + }, this)); + }, + + validate: function(section_id, formvalue) { + if (formvalue != null && formvalue != '' && !/^[A-Z0-9][A-Z0-9]$/.test(formvalue)) + return _('Use ISO/IEC 3166 alpha2 country codes.'); + + return true; + }, + + renderWidget: function(section_id, option_index, cfgvalue) { + var typeClass = (this.keylist && this.keylist.length) ? form.ListValue : form.Value; + return typeClass.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]); + } +}); + +return view.extend({ + poll_status: function(map, data) { + var rows = map.querySelectorAll('.cbi-section-table-row[data-sid]'); + + for (var i = 0; i < rows.length; i++) { + var section_id = rows[i].getAttribute('data-sid'), + radioDev = data[1].filter(function(d) { return d.getName() == section_id })[0], + radioNet = data[2].filter(function(n) { return n.getName() == section_id })[0], + badge = rows[i].querySelector('[data-name="_badge"] > div'), + stat = rows[i].querySelector('[data-name="_stat"]'), + btns = rows[i].querySelectorAll('.cbi-section-actions button'), + busy = btns[0].classList.contains('spinning') || btns[1].classList.contains('spinning') || btns[2].classList.contains('spinning'); + + if (radioDev) { + dom.content(badge, render_radio_badge(radioDev)); + dom.content(stat, render_radio_status(radioDev, data[2].filter(function(n) { return n.getWifiDeviceName() == radioDev.getName() }))); + } + else { + dom.content(badge, render_network_badge(radioNet)); + dom.content(stat, render_network_status(radioNet)); + } + + if (stat.hasAttribute('restart')) + dom.content(stat, E('em', _('Device is restarting…'))); + + btns[0].disabled = isReadonlyView || busy; + btns[1].disabled = (isReadonlyView && radioDev) || busy; + btns[2].disabled = isReadonlyView || busy; + } + + var table = document.querySelector('#wifi_assoclist_table'), + hosts = data[0], + trows = []; + + for (var i = 0; i < data[3].length; i++) { + var bss = data[3][i], + name = hosts.getHostnameByMACAddr(bss.mac), + ipv4 = hosts.getIPAddrByMACAddr(bss.mac), + ipv6 = hosts.getIP6AddrByMACAddr(bss.mac); + + var hint; + + if (name && ipv4 && ipv6) + hint = '%s (%s, %s)'.format(name, ipv4, ipv6); + else if (name && (ipv4 || ipv6)) + hint = '%s (%s)'.format(name, ipv4 || ipv6); + else + hint = name || ipv4 || ipv6 || '?'; + + var row = [ + E('span', { + 'class': 'ifacebadge', + 'data-ifname': bss.network.getIfname(), + 'data-ssid': bss.network.getSSID() + }, [ + E('img', { + 'src': L.resource('icons/wifi%s.png').format(bss.network.isUp() ? '' : '_disabled'), + 'title': bss.radio.getI18n() + }), + E('span', [ + ' %s '.format(bss.network.getShortName()), + E('small', '(%s)'.format(bss.network.getIfname())) + ]) + ]), + bss.mac, + hint, + render_signal_badge(Math.min((bss.signal + 110) / 70 * 100, 100), bss.signal, bss.noise), + E('span', {}, [ + E('span', format_wifirate(bss.rx)), + E('br'), + E('span', format_wifirate(bss.tx)) + ]) + ]; + + if (bss.network.isClientDisconnectSupported()) { + if (table.firstElementChild.childNodes.length < 6) + table.firstElementChild.appendChild(E('th', { 'class': 'th cbi-section-actions'})); + + row.push(E('button', { + 'class': 'cbi-button cbi-button-remove', + 'click': L.bind(function(net, mac, ev) { + dom.parent(ev.currentTarget, '.tr').style.opacity = 0.5; + ev.currentTarget.classList.add('spinning'); + ev.currentTarget.disabled = true; + ev.currentTarget.blur(); + + net.disconnectClient(mac, true, 5, 60000); + }, this, bss.network, bss.mac), + 'disabled': isReadonlyView || null + }, [ _('Disconnect') ])); + } + else { + row.push('-'); + } + + trows.push(row); + } + + cbi_update_table(table, trows, E('em', _('No information available'))); + + var stat = document.querySelector('.cbi-modal [data-name="_wifistat_modal"] .ifacebadge.large'); + + if (stat) + render_modal_status(stat, data[2].filter(function(n) { return n.getName() == stat.getAttribute('data-network') })[0]); + + return network.flushCache(); + }, + + load: function() { + return Promise.all([ + uci.changes(), + uci.load('wireless'), + uci.load('system') + ]); + }, + + checkAnonymousSections: function() { + var wifiIfaces = uci.sections('wireless', 'wifi-iface'); + + for (var i = 0; i < wifiIfaces.length; i++) + if (wifiIfaces[i]['.anonymous']) + return true; + + return false; + }, + + callUciRename: rpc.declare({ + object: 'uci', + method: 'rename', + params: [ 'config', 'section', 'name' ] + }), + + render: function() { + if (this.checkAnonymousSections()) + return this.renderMigration(); + else + return this.renderOverview(); + }, + + handleMigration: function(ev) { + var wifiIfaces = uci.sections('wireless', 'wifi-iface'), + id_offset = 0, + tasks = []; + + for (var i = 0; i < wifiIfaces.length; i++) { + if (!wifiIfaces[i]['.anonymous']) + continue; + + var new_name = next_free_sid(id_offset); + + tasks.push(this.callUciRename('wireless', wifiIfaces[i]['.name'], new_name)); + id_offset = +new_name.substring(7) + 1; + } + + return Promise.all(tasks) + .then(L.bind(ui.changes.init, ui.changes)) + .then(L.bind(ui.changes.apply, ui.changes)); + }, + + renderMigration: function() { + ui.showModal(_('Wireless configuration migration'), [ + E('p', _('The existing wireless configuration needs to be changed for LuCI to function properly.')), + E('p', _('Upon pressing "Continue", anonymous "wifi-iface" sections will be assigned with a name in the form wifinet# and the network will be restarted to apply the updated configuration.')), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn cbi-button-action important', + 'click': ui.createHandlerFn(this, 'handleMigration') + }, _('Continue'))) + ]); + }, + + renderOverview: function() { + var m, s, o; + + m = new form.Map('wireless'); + m.chain('network'); + m.chain('firewall'); + + s = m.section(form.GridSection, 'wifi-device', _('Wireless Overview')); + s.anonymous = true; + s.addremove = false; + + s.load = function() { + return network.getWifiDevices().then(L.bind(function(radios) { + this.radios = radios.sort(function(a, b) { + return a.getName() > b.getName(); + }); + + var tasks = []; + + for (var i = 0; i < radios.length; i++) + tasks.push(radios[i].getWifiNetworks()); + + return Promise.all(tasks); + }, this)).then(L.bind(function(data) { + this.wifis = []; + + for (var i = 0; i < data.length; i++) + this.wifis.push.apply(this.wifis, data[i]); + }, this)); + }; + + s.cfgsections = function() { + var rv = []; + + for (var i = 0; i < this.radios.length; i++) { + rv.push(this.radios[i].getName()); + + for (var j = 0; j < this.wifis.length; j++) + if (this.wifis[j].getWifiDeviceName() == this.radios[i].getName()) + rv.push(this.wifis[j].getName()); + } + + return rv; + }; + + s.modaltitle = function(section_id) { + var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id})[0]; + return radioNet ? radioNet.getI18n() : _('Edit wireless network'); + }; + + s.lookupRadioOrNetwork = function(section_id) { + var radioDev = this.radios.filter(function(r) { return r.getName() == section_id })[0]; + if (radioDev) + return radioDev; + + var radioNet = this.wifis.filter(function(w) { return w.getName() == section_id })[0]; + if (radioNet) + return radioNet; + + return null; + }; + + s.renderRowActions = function(section_id) { + var inst = this.lookupRadioOrNetwork(section_id), btns; + + if (inst.getWifiNetworks) { + btns = [ + E('button', { + 'class': 'cbi-button cbi-button-neutral', + 'title': _('Restart radio interface'), + 'click': ui.createHandlerFn(this, radio_restart, section_id) + }, _('Restart')), + E('button', { + 'class': 'cbi-button cbi-button-action important', + 'title': _('Find and join network'), + 'click': ui.createHandlerFn(this, 'handleScan', inst) + }, _('Scan')), + E('button', { + 'class': 'cbi-button cbi-button-add', + 'title': _('Provide new network'), + 'click': ui.createHandlerFn(this, 'handleAdd', inst) + }, _('Add')) + ]; + } + else { + var isDisabled = (inst.get('disabled') == '1' || + uci.get('wireless', inst.getWifiDeviceName(), 'disabled') == '1'); + + btns = [ + E('button', { + 'class': 'cbi-button cbi-button-neutral enable-disable', + 'title': isDisabled ? _('Enable this network') : _('Disable this network'), + 'click': ui.createHandlerFn(this, network_updown, section_id, this.map) + }, isDisabled ? _('Enable') : _('Disable')), + E('button', { + 'class': 'cbi-button cbi-button-action important', + 'title': _('Edit this network'), + 'click': ui.createHandlerFn(this, 'renderMoreOptionsModal', section_id) + }, _('Edit')), + E('button', { + 'class': 'cbi-button cbi-button-negative remove', + 'title': _('Delete this network'), + 'click': ui.createHandlerFn(this, 'handleRemove', section_id) + }, _('Remove')) + ]; + } + + return E('td', { 'class': 'td middle cbi-section-actions' }, E('div', btns)); + }; + + s.addModalOptions = function(s) { + return network.getWifiNetwork(s.section).then(function(radioNet) { + var hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type'); + var o, ss; + + o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getWifiDeviceName(), 'wifi-device', _('Device Configuration')); + o.modalonly = true; + + ss = o.subsection; + ss.tab('general', _('General Setup')); + ss.tab('advanced', _('Advanced Settings')); + + var isDisabled = (radioNet.get('disabled') == '1' || + uci.get('wireless', radioNet.getWifiDeviceName(), 'disabled') == 1); + + o = ss.taboption('general', form.DummyValue, '_wifistat_modal', _('Status')); + o.cfgvalue = L.bind(function(radioNet) { + return render_modal_status(null, radioNet); + }, this, radioNet); + o.write = function() {}; + + o = ss.taboption('general', form.Button, '_toggle', isDisabled ? _('Wireless network is disabled') : _('Wireless network is enabled')); + o.inputstyle = isDisabled ? 'apply' : 'reset'; + o.inputtitle = isDisabled ? _('Enable') : _('Disable'); + o.onclick = ui.createHandlerFn(s, network_updown, s.section, s.map); + + o = ss.taboption('general', CBIWifiFrequencyValue, '_freq', '
' + _('Operating frequency')); + o.ucisection = s.section; + + if (hwtype == 'mac80211') { + o = ss.taboption('general', form.Flag, 'legacy_rates', _('Allow legacy 802.11b rates'), _('Legacy or badly behaving devices may require legacy 802.11b rates to interoperate. Airtime efficiency may be significantly reduced where these are used. It is recommended to not allow 802.11b rates where possible.')); + o.depends({'_freq': '2g', '!contains': true}); + + o = ss.taboption('general', CBIWifiTxPowerValue, 'txpower', _('Maximum transmit power'), _('Specifies the maximum transmit power the wireless radio may use. Depending on regulatory requirements and wireless usage, the actual transmit power may be reduced by the driver.')); + o.wifiNetwork = radioNet; + + o = ss.taboption('advanced', CBIWifiCountryValue, 'country', _('Country Code')); + o.wifiNetwork = radioNet; + + o = ss.taboption('advanced', form.ListValue, 'cell_density', _('Coverage cell density'), _('Configures data rates based on the coverage cell density. Normal configures basic rates to 6, 12, 24 Mbps if legacy 802.11b rates are not used else to 5.5, 11 Mbps. High configures basic rates to 12, 24 Mbps if legacy 802.11b rates are not used else to the 11 Mbps rate. Very High configures 24 Mbps as the basic rate. Supported rates lower than the minimum basic rate are not offered.')); + o.value('0', _('Disabled')); + o.value('1', _('Normal')); + o.value('2', _('High')); + o.value('3', _('Very High')); + + o = ss.taboption('advanced', form.Value, 'distance', _('Distance Optimization'), _('Distance to farthest network member in meters.')); + o.datatype = 'or(range(0,114750),"auto")'; + o.placeholder = 'auto'; + + o = ss.taboption('advanced', form.Value, 'frag', _('Fragmentation Threshold')); + o.datatype = 'min(256)'; + o.placeholder = _('off'); + + o = ss.taboption('advanced', form.Value, 'rts', _('RTS/CTS Threshold')); + o.datatype = 'uinteger'; + o.placeholder = _('off'); + + o = ss.taboption('advanced', form.Flag, 'noscan', _('Force 40MHz mode'), _('Always use 40MHz channels even if the secondary channel overlaps. Using this option does not comply with IEEE 802.11n-2009!')); + o.rmempty = true; + + o = ss.taboption('advanced', form.Value, 'beacon_int', _('Beacon Interval')); + o.datatype = 'range(15,65535)'; + o.placeholder = 100; + o.rmempty = true; + } + + + o = s.option(form.SectionValue, '_device', form.NamedSection, radioNet.getName(), 'wifi-iface', _('Interface Configuration')); + o.modalonly = true; + + ss = o.subsection; + ss.tab('general', _('General Setup')); + ss.tab('encryption', _('Wireless Security')); + ss.tab('macfilter', _('MAC-Filter')); + ss.tab('advanced', _('Advanced Settings')); + ss.tab('roaming', _('WLAN roaming'), _('Settings for assisting wireless clients in roaming between multiple APs: 802.11r, 802.11k and 802.11v')); + + o = ss.taboption('general', form.ListValue, 'mode', _('Mode')); + o.value('ap', _('Access Point')); + o.value('sta', _('Client')); + o.value('adhoc', _('Ad-Hoc')); + + o = ss.taboption('general', form.Value, 'mesh_id', _('Mesh Id')); + o.depends('mode', 'mesh'); + + o = ss.taboption('advanced', form.Flag, 'mesh_fwding', _('Forward mesh peer traffic')); + o.rmempty = false; + o.default = '1'; + o.depends('mode', 'mesh'); + + o = ss.taboption('advanced', form.Value, 'mesh_rssi_threshold', _('RSSI threshold for joining'), _('0 = not using RSSI threshold, 1 = do not change driver default')); + o.rmempty = false; + o.default = '0'; + o.datatype = 'range(-255,1)'; + o.depends('mode', 'mesh'); + + o = ss.taboption('general', form.Value, 'ssid', _('ESSID')); + o.datatype = 'maxlength(32)'; + o.depends('mode', 'ap'); + o.depends('mode', 'sta'); + o.depends('mode', 'adhoc'); + o.depends('mode', 'ahdemo'); + o.depends('mode', 'monitor'); + o.depends('mode', 'ap-wds'); + o.depends('mode', 'sta-wds'); + o.depends('mode', 'wds'); + + o = ss.taboption('general', form.Value, 'bssid', _('BSSID')); + o.datatype = 'macaddr'; + + o = ss.taboption('general', widgets.NetworkSelect, 'network', _('Network'), _('Choose the network(s) you want to attach to this wireless interface or fill out the custom field to define a new network.')); + o.rmempty = true; + o.multiple = true; + o.novirtual = true; + o.write = function(section_id, value) { + return network.getDevice(section_id).then(L.bind(function(dev) { + var old_networks = dev.getNetworks().reduce(function(o, v) { o[v.getName()] = v; return o }, {}), + new_networks = {}, + values = L.toArray(value), + tasks = []; + + for (var i = 0; i < values.length; i++) { + new_networks[values[i]] = true; + + if (old_networks[values[i]]) + continue; + + tasks.push(network.getNetwork(values[i]).then(L.bind(function(name, net) { + return net || network.addNetwork(name, { proto: 'none' }); + }, this, values[i])).then(L.bind(function(dev, net) { + if (net) { + if (!net.isEmpty()) { + var target_dev = net.getDevice(); + + /* Resolve parent interface of vlan */ + while (target_dev && target_dev.getType() == 'vlan') + target_dev = target_dev.getParent(); + + if (!target_dev || target_dev.getType() != 'bridge') + net.set('type', 'bridge'); + } + + net.addDevice(dev); + } + }, this, dev))); + } + + for (var name in old_networks) + if (!new_networks[name]) + tasks.push(network.getNetwork(name).then(L.bind(function(dev, net) { + if (net) + net.deleteDevice(dev); + }, this, dev))); + + return Promise.all(tasks); + }, this)); + }; + + if (hwtype == 'mac80211') { + var mode = ss.children[0], + bssid = ss.children[5], + encr; + + mode.value('mesh', '802.11s'); + mode.value('ahdemo', _('Pseudo Ad-Hoc (ahdemo)')); + mode.value('monitor', _('Monitor')); + + bssid.depends('mode', 'adhoc'); + bssid.depends('mode', 'sta'); + bssid.depends('mode', 'sta-wds'); + + o = ss.taboption('macfilter', form.ListValue, 'macfilter', _('MAC Address Filter')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + o.value('', _('disable')); + o.value('allow', _('Allow listed only')); + o.value('deny', _('Allow all except listed')); + + o = ss.taboption('macfilter', form.DynamicList, 'maclist', _('MAC-List')); + o.datatype = 'macaddr'; + o.retain = true; + o.depends('macfilter', 'allow'); + o.depends('macfilter', 'deny'); + o.load = function(section_id) { + return network.getHostHints().then(L.bind(function(hints) { + hints.getMACHints().map(L.bind(function(hint) { + this.value(hint[0], hint[1] ? '%s (%s)'.format(hint[0], hint[1]) : hint[0]); + }, this)); + + return form.DynamicList.prototype.load.apply(this, [section_id]); + }, this)); + }; + + mode.value('ap-wds', '%s (%s)'.format(_('Access Point'), _('WDS'))); + mode.value('sta-wds', '%s (%s)'.format(_('Client'), _('WDS'))); + + mode.write = function(section_id, value) { + switch (value) { + case 'ap-wds': + uci.set('wireless', section_id, 'mode', 'ap'); + uci.set('wireless', section_id, 'wds', '1'); + break; + + case 'sta-wds': + uci.set('wireless', section_id, 'mode', 'sta'); + uci.set('wireless', section_id, 'wds', '1'); + break; + + default: + uci.set('wireless', section_id, 'mode', value); + uci.unset('wireless', section_id, 'wds'); + break; + } + }; + + mode.cfgvalue = function(section_id) { + var mode = uci.get('wireless', section_id, 'mode'), + wds = uci.get('wireless', section_id, 'wds'); + + if (mode == 'ap' && wds) + return 'ap-wds'; + else if (mode == 'sta' && wds) + return 'sta-wds'; + + return mode; + }; + + o = ss.taboption('general', form.Flag, 'hidden', _('Hide ESSID'), _('Where the ESSID is hidden, clients may fail to roam and airtime efficiency may be significantly reduced.')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + + o = ss.taboption('general', form.Flag, 'wmm', _('WMM Mode'), _('Where Wi-Fi Multimedia (WMM) Mode QoS is disabled, clients may be limited to 802.11a/802.11g rates.')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + o.default = o.enabled; + + /* https://w1.fi/cgit/hostap/commit/?id=34f7c699a6bcb5c45f82ceb6743354ad79296078 */ + /* multicast_to_unicast https://github.com/openwrt/openwrt/commit/7babb978ad9d7fc29acb1ff86afb1eb343af303a */ + o = ss.taboption('advanced', form.Flag, 'multicast_to_unicast', _('Multi To Unicast'), _('ARP, IPv4 and IPv6 (even 802.1Q) with multicast destination MACs are unicast to the STA MAC address. Note: This is not Directed Multicast Service (DMS) in 802.11v. Note: might break receiver STA multicast expectations.')); + o.rmempty = true; + + o = ss.taboption('advanced', form.Flag, 'isolate', _('Isolate Clients'), _('Prevents client-to-client communication')); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + + o = ss.taboption('advanced', form.Value, 'ifname', _('Interface name'), _('Override default interface name')); + o.optional = true; + o.datatype = 'netdevname'; + o.placeholder = radioNet.getIfname(); + if (/^radio\d+\.network/.test(o.placeholder)) + o.placeholder = ''; + + o = ss.taboption('advanced', form.Value, 'macaddr', _('MAC address'), _('Override default MAC address - the range of usable addresses might be limited by the driver')); + o.optional = true; + o.placeholder = radioNet.getActiveBSSID(); + o.datatype = 'macaddr'; + + o = ss.taboption('advanced', form.Flag, 'short_preamble', _('Short Preamble')); + o.default = o.enabled; + + o = ss.taboption('advanced', form.Value, 'dtim_period', _('DTIM Interval'), _('Delivery Traffic Indication Message Interval')); + o.optional = true; + o.placeholder = 2; + o.datatype = 'range(1,255)'; + + o = ss.taboption('advanced', form.Value, 'wpa_group_rekey', _('Time interval for rekeying GTK'), _('sec')); + o.optional = true; + o.placeholder = 600; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Flag , 'skip_inactivity_poll', _('Disable Inactivity Polling')); + o.optional = true; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Value, 'max_inactivity', _('Station inactivity limit'), _('802.11v: BSS Max Idle. Units: seconds.')); + o.optional = true; + o.placeholder = 300; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Value, 'max_listen_interval', _('Maximum allowed Listen Interval')); + o.optional = true; + o.placeholder = 65535; + o.datatype = 'uinteger'; + + o = ss.taboption('advanced', form.Flag, 'disassoc_low_ack', _('Disassociate On Low Acknowledgement'), _('Allow AP mode to disconnect STAs based on low ACK condition')); + o.default = o.enabled; + } + + + encr = o = ss.taboption('encryption', form.ListValue, 'encryption', _('Encryption')); + o.depends('mode', 'ap'); + o.depends('mode', 'sta'); + o.depends('mode', 'adhoc'); + o.depends('mode', 'ahdemo'); + o.depends('mode', 'ap-wds'); + o.depends('mode', 'sta-wds'); + o.depends('mode', 'mesh'); + + o.cfgvalue = function(section_id) { + var v = String(uci.get('wireless', section_id, 'encryption')); + if (v == 'wep') + return 'wep-open'; + else if (v.match(/\+/)) + return v.replace(/\+.+$/, ''); + return v; + }; + + o.write = function(section_id, value) { + var e = this.section.children.filter(function(o) { return o.option == 'encryption' })[0].formvalue(section_id), + co = this.section.children.filter(function(o) { return o.option == 'cipher' })[0], c = co.formvalue(section_id); + + if (value == 'wpa' || value == 'wpa2' || value == 'wpa3' || value == 'wpa3-mixed') + uci.unset('wireless', section_id, 'key'); + + if (co.isActive(section_id) && e && (c == 'tkip' || c == 'ccmp' || c == 'tkip+ccmp')) + e += '+' + c; + + uci.set('wireless', section_id, 'encryption', e); + }; + + o = ss.taboption('encryption', form.ListValue, 'cipher', _('Cipher')); + o.depends('encryption', 'wpa'); + o.depends('encryption', 'wpa2'); + o.depends('encryption', 'wpa3'); + o.depends('encryption', 'wpa3-mixed'); + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'wpa-mixed'); + o.depends('encryption', 'psk-mixed'); + o.value('auto', _('auto')); + o.value('ccmp', _('Force CCMP (AES)')); + o.value('tkip', _('Force TKIP')); + o.value('tkip+ccmp', _('Force TKIP and CCMP (AES)')); + o.write = ss.children.filter(function(o) { return o.option == 'encryption' })[0].write; + + o.cfgvalue = function(section_id) { + var v = String(uci.get('wireless', section_id, 'encryption')); + if (v.match(/\+/)) { + v = v.replace(/^[^+]+\+/, ''); + if (v == 'aes') + v = 'ccmp'; + else if (v == 'tkip+aes' || v == 'aes+tkip' || v == 'ccmp+tkip') + v = 'tkip+ccmp'; + } + return v; + }; + + + var crypto_modes = []; + + if (hwtype == 'mac80211') { + var has_supplicant = L.hasSystemFeature('wpasupplicant'), + has_hostapd = L.hasSystemFeature('hostapd'); + + // Probe EAP support + var has_ap_eap = L.hasSystemFeature('hostapd', 'eap'), + has_sta_eap = L.hasSystemFeature('wpasupplicant', 'eap'); + + // Probe SAE support + var has_ap_sae = L.hasSystemFeature('hostapd', 'sae'), + has_sta_sae = L.hasSystemFeature('wpasupplicant', 'sae'); + + // Probe OWE support + var has_ap_owe = L.hasSystemFeature('hostapd', 'owe'), + has_sta_owe = L.hasSystemFeature('wpasupplicant', 'owe'); + + // Probe Suite-B support + var has_ap_eap192 = L.hasSystemFeature('hostapd', 'suiteb192'), + has_sta_eap192 = L.hasSystemFeature('wpasupplicant', 'suiteb192'); + + // Probe WEP support + var has_ap_wep = L.hasSystemFeature('hostapd', 'wep'), + has_sta_wep = L.hasSystemFeature('wpasupplicant', 'wep'); + + if (has_hostapd || has_supplicant) { + crypto_modes.push(['psk2', 'WPA2-PSK', 35]); + crypto_modes.push(['psk-mixed', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]); + crypto_modes.push(['psk', 'WPA-PSK', 12]); + } + else { + encr.description = _('WPA-Encryption requires wpa_supplicant (for client mode) or hostapd (for AP and ad-hoc mode) to be installed.'); + } + + if (has_ap_sae || has_sta_sae) { + crypto_modes.push(['sae', 'WPA3-SAE', 31]); + crypto_modes.push(['sae-mixed', 'WPA2-PSK/WPA3-SAE Mixed Mode', 30]); + } + + if (has_ap_wep || has_sta_wep) { + crypto_modes.push(['wep-open', _('WEP Open System'), 11]); + crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]); + } + + if (has_ap_eap || has_sta_eap) { + if (has_ap_eap192 || has_sta_eap192) { + crypto_modes.push(['wpa3', 'WPA3-EAP', 33]); + crypto_modes.push(['wpa3-mixed', 'WPA2-EAP/WPA3-EAP Mixed Mode', 32]); + } + + crypto_modes.push(['wpa2', 'WPA2-EAP', 34]); + crypto_modes.push(['wpa', 'WPA-EAP', 20]); + } + + if (has_ap_owe || has_sta_owe) { + crypto_modes.push(['owe', 'OWE', 1]); + } + + encr.crypto_support = { + 'ap': { + 'wep-open': has_ap_wep || _('Requires hostapd with WEP support'), + 'wep-shared': has_ap_wep || _('Requires hostapd with WEP support'), + 'psk': has_hostapd || _('Requires hostapd'), + 'psk2': has_hostapd || _('Requires hostapd'), + 'psk-mixed': has_hostapd || _('Requires hostapd'), + 'sae': has_ap_sae || _('Requires hostapd with SAE support'), + 'sae-mixed': has_ap_sae || _('Requires hostapd with SAE support'), + 'wpa': has_ap_eap || _('Requires hostapd with EAP support'), + 'wpa2': has_ap_eap || _('Requires hostapd with EAP support'), + 'wpa3': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'), + 'wpa3-mixed': has_ap_eap192 || _('Requires hostapd with EAP Suite-B support'), + 'owe': has_ap_owe || _('Requires hostapd with OWE support') + }, + 'sta': { + 'wep-open': has_sta_wep || _('Requires wpa-supplicant with WEP support'), + 'wep-shared': has_sta_wep || _('Requires wpa-supplicant with WEP support'), + 'psk': has_supplicant || _('Requires wpa-supplicant'), + 'psk2': has_supplicant || _('Requires wpa-supplicant'), + 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), + 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support'), + 'sae-mixed': has_sta_sae || _('Requires wpa-supplicant with SAE support'), + 'wpa': has_sta_eap || _('Requires wpa-supplicant with EAP support'), + 'wpa2': has_sta_eap || _('Requires wpa-supplicant with EAP support'), + 'wpa3': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'), + 'wpa3-mixed': has_sta_eap192 || _('Requires wpa-supplicant with EAP Suite-B support'), + 'owe': has_sta_owe || _('Requires wpa-supplicant with OWE support') + }, + 'adhoc': { + 'wep-open': true, + 'wep-shared': true, + 'psk': has_supplicant || _('Requires wpa-supplicant'), + 'psk2': has_supplicant || _('Requires wpa-supplicant'), + 'psk-mixed': has_supplicant || _('Requires wpa-supplicant'), + }, + 'mesh': { + 'sae': has_sta_sae || _('Requires wpa-supplicant with SAE support') + }, + 'ahdemo': { + 'wep-open': true, + 'wep-shared': true + }, + 'wds': { + 'wep-open': true, + 'wep-shared': true + } + }; + + encr.crypto_support['ap-wds'] = encr.crypto_support['ap']; + encr.crypto_support['sta-wds'] = encr.crypto_support['sta']; + + encr.validate = function(section_id, value) { + var modeopt = this.section.children.filter(function(o) { return o.option == 'mode' })[0], + modeval = modeopt.formvalue(section_id), + modetitle = modeopt.vallist[modeopt.keylist.indexOf(modeval)], + enctitle = this.vallist[this.keylist.indexOf(value)]; + + if (value == 'none') + return true; + + if (!L.isObject(this.crypto_support[modeval]) || !this.crypto_support[modeval].hasOwnProperty(value)) + return _('The selected %s mode is incompatible with %s encryption').format(modetitle, enctitle); + + return this.crypto_support[modeval][value]; + }; + } + else if (hwtype == 'broadcom') { + crypto_modes.push(['psk2', 'WPA2-PSK', 33]); + crypto_modes.push(['psk+psk2', 'WPA-PSK/WPA2-PSK Mixed Mode', 22]); + crypto_modes.push(['psk', 'WPA-PSK', 12]); + crypto_modes.push(['wep-open', _('WEP Open System'), 11]); + crypto_modes.push(['wep-shared', _('WEP Shared Key'), 10]); + } + + crypto_modes.push(['none', _('No Encryption'), 0]); + + crypto_modes.sort(function(a, b) { return b[2] - a[2] }); + + for (var i = 0; i < crypto_modes.length; i++) { + var security_level = (crypto_modes[i][2] >= 30) ? _('strong security') + : (crypto_modes[i][2] >= 20) ? _('medium security') + : (crypto_modes[i][2] >= 10) ? _('weak security') : _('open network'); + + encr.value(crypto_modes[i][0], '%s (%s)'.format(crypto_modes[i][1], security_level)); + } + + + o = ss.taboption('encryption', form.Value, 'auth_server', _('RADIUS Authentication Server')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'auth_port', _('RADIUS Authentication Port')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'port'; + o.placeholder = '1812'; + + o = ss.taboption('encryption', form.Value, 'auth_secret', _('RADIUS Authentication Secret')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.password = true; + + o = ss.taboption('encryption', form.Value, 'acct_server', _('RADIUS Accounting Server')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'acct_port', _('RADIUS Accounting Port')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'port'; + o.placeholder = '1813'; + + o = ss.taboption('encryption', form.Value, 'acct_secret', _('RADIUS Accounting Secret')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.password = true; + + /* extra RADIUS settings start */ + o = ss.taboption('encryption', form.ListValue, 'dynamic_vlan', _('RADIUS Dynamic VLAN Assignment'), _('Required: Rejects auth if RADIUS server does not provide appropriate VLAN attributes.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.value('0', _('Disabled')); + o.value('1', _('Optional')); + o.value('2', _('Required')); + o.write = function (section_id, value) { + return this.super('write', [section_id, (value == 0) ? null: value]); + } + + o = ss.taboption('encryption', form.Flag, 'per_sta_vif', _('RADIUS Per STA VLAN'), _('Each STA is assigned its own AP_VLAN interface.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + //hostapd internally defaults to vlan_naming=1 even with dynamic VLAN off + o = ss.taboption('encryption', form.Flag, 'vlan_naming', _('RADIUS VLAN Naming'), _('Off: vlanXXX, e.g., vlan1. On: vlan_tagged_interface.XXX, e.g. eth0.1.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', widgets.DeviceSelect, 'vlan_tagged_interface', _('RADIUS VLAN Tagged Interface'), _('E.g. eth0, eth1')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.size = 1; + o.rmempty = true; + o.multiple = false; + o.noaliases = true; + o.nobridges = true; + o.nocreate = true; + o.noinactive = true; + + o = ss.taboption('encryption', form.Value, 'vlan_bridge', _('RADIUS VLAN Bridge Naming Scheme'), _('E.g. br-vlan or brvlan.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + /* extra RADIUS settings end */ + + o = ss.taboption('encryption', form.Value, 'dae_client', _('DAE-Client'), _('Dynamic Authorization Extension client.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'host(0)'; + + o = ss.taboption('encryption', form.Value, 'dae_port', _('DAE-Port'), _('Dynamic Authorization Extension port.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.datatype = 'port'; + o.placeholder = '3799'; + + o = ss.taboption('encryption', form.Value, 'dae_secret', _('DAE-Secret'), _('Dynamic Authorization Extension secret.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.rmempty = true; + o.password = true; + + //WPA(1) has only WPA IE. Only >= WPA2 has RSN IE Preauth frames. + o = ss.taboption('encryption', form.Flag, 'rsn_preauth', _('RSN Preauth'), _('Robust Security Network (RSN): Allow roaming preauth for WPA2-EAP networks (and advertise it in WLAN beacons). Only works if the specified network interface is a bridge. Shortens the time-critical reassociation process.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa2', 'wpa3', 'wpa3-mixed'] }); + + + o = ss.taboption('encryption', form.Value, '_wpa_key', _('Key')); + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'psk+psk2'); + o.depends('encryption', 'psk-mixed'); + o.depends('encryption', 'sae'); + o.depends('encryption', 'sae-mixed'); + o.datatype = 'wpakey'; + o.rmempty = true; + o.password = true; + + o.cfgvalue = function(section_id) { + var key = uci.get('wireless', section_id, 'key'); + return /^[1234]$/.test(key) ? null : key; + }; + + o.write = function(section_id, value) { + uci.set('wireless', section_id, 'key', value); + uci.unset('wireless', section_id, 'key1'); + uci.unset('wireless', section_id, 'key2'); + uci.unset('wireless', section_id, 'key3'); + uci.unset('wireless', section_id, 'key4'); + }; + + + o = ss.taboption('encryption', form.ListValue, '_wep_key', _('Used Key Slot')); + o.depends('encryption', 'wep-open'); + o.depends('encryption', 'wep-shared'); + o.value('1', _('Key #%d').format(1)); + o.value('2', _('Key #%d').format(2)); + o.value('3', _('Key #%d').format(3)); + o.value('4', _('Key #%d').format(4)); + + o.cfgvalue = function(section_id) { + var slot = +uci.get('wireless', section_id, 'key'); + return (slot >= 1 && slot <= 4) ? String(slot) : ''; + }; + + o.write = function(section_id, value) { + uci.set('wireless', section_id, 'key', value); + }; + + for (var slot = 1; slot <= 4; slot++) { + o = ss.taboption('encryption', form.Value, 'key%d'.format(slot), _('Key #%d').format(slot)); + o.depends('encryption', 'wep-open'); + o.depends('encryption', 'wep-shared'); + o.datatype = 'wepkey'; + o.rmempty = true; + o.password = true; + + o.write = function(section_id, value) { + if (value != null && (value.length == 5 || value.length == 13)) + value = 's:%s'.format(value); + uci.set('wireless', section_id, this.option, value); + }; + } + + + if (hwtype == 'mac80211') { + // Probe 802.11r support (and EAP support as a proxy for Openwrt) + var has_80211r = L.hasSystemFeature('hostapd', '11r') || L.hasSystemFeature('hostapd', 'eap'); + + o = ss.taboption('roaming', form.Flag, 'ieee80211r', _('802.11r Fast Transition'), _('Enables fast roaming among access points that belong to the same Mobility Domain')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + if (has_80211r) + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] }); + o.rmempty = true; + + o = ss.taboption('roaming', form.Value, 'nasid', _('NAS ID'), _('Used for two different purposes: RADIUS NAS ID and 802.11r R0KH-ID. Not needed with normal WPA(2)-PSK.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + + o = ss.taboption('roaming', form.Value, 'mobility_domain', _('Mobility Domain'), _('4-character hexadecimal ID')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '4f57'; + o.datatype = 'and(hexstring,length(4))'; + o.rmempty = true; + + o = ss.taboption('roaming', form.Value, 'reassociation_deadline', _('Reassociation Deadline'), _('time units (TUs / 1.024 ms) [1000-65535]')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '1000'; + o.datatype = 'range(1000,65535)'; + o.rmempty = true; + + o = ss.taboption('roaming', form.ListValue, 'ft_over_ds', _('FT protocol')); + o.depends({ ieee80211r: '1' }); + o.value('0', _('FT over the Air')); + o.value('1', _('FT over DS')); + o.rmempty = true; + + o = ss.taboption('roaming', form.Flag, 'ft_psk_generate_local', _('Generate PMK locally'), _('When using a PSK, the PMK can be automatically generated. When enabled, the R0/R1 key options below are not applied. Disable this to use the R0 and R1 key options.')); + o.depends({ ieee80211r: '1' }); + o.default = o.enabled; + o.rmempty = false; + + o = ss.taboption('roaming', form.Value, 'r0_key_lifetime', _('R0 Key Lifetime'), _('minutes')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '10000'; + o.datatype = 'uinteger'; + o.rmempty = true; + + o = ss.taboption('roaming', form.Value, 'r1_key_holder', _('R1 Key Holder'), _('6-octet identifier as a hex string - no colons')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '00004f577274'; + o.datatype = 'and(hexstring,length(12))'; + o.rmempty = true; + + o = ss.taboption('roaming', form.Flag, 'pmk_r1_push', _('PMK R1 Push')); + o.depends({ ieee80211r: '1' }); + o.placeholder = '0'; + o.rmempty = true; + + o = ss.taboption('roaming', form.DynamicList, 'r0kh', _('External R0 Key Holder List'), _('List of R0KHs in the same Mobility Domain.
Format: MAC-address,NAS-Identifier,128-bit key as hex string.
This list is used to map R0KH-ID (NAS Identifier) to a destination MAC address when requesting PMK-R1 key from the R0KH that the STA used during the Initial Mobility Domain Association.')); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + + o = ss.taboption('roaming', form.DynamicList, 'r1kh', _('External R1 Key Holder List'), _ ('List of R1KHs in the same Mobility Domain.
Format: MAC-address,R1KH-ID as 6 octets with colons,128-bit key as hex string.
This list is used to map R1KH-ID to a destination MAC address when sending PMK-R1 key from the R0KH. This is also the list of authorized R1KHs in the MD that can request PMK-R1 keys.')); + o.depends({ ieee80211r: '1' }); + o.rmempty = true; + // End of 802.11r options + + // Probe 802.11k and 802.11v support via EAP support (full hostapd has EAP) + if (L.hasSystemFeature('hostapd', 'eap')) { + /* 802.11k settings start */ o = + ss.taboption('roaming', form.Flag, 'ieee80211k', _('802.11k RRM'), _('Radio Resource Measurement - Sends beacons to assist roaming. Not all clients support this.')); + // add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk', 'psk2', 'psk-mixed', 'sae', 'sae-mixed'] }); + o.depends('mode', 'ap'); + o.depends('mode', 'ap-wds'); + + o = ss.taboption('roaming', form.Flag, 'rrm_neighbor_report', _('Neighbour Report'), _('802.11k: Enable neighbor report via radio measurements.')); + o.depends({ ieee80211k: '1' }); + o.default = o.enabled; + + o = ss.taboption('roaming', form.Flag, 'rrm_beacon_report', _('Beacon Report'), _('802.11k: Enable beacon report via radio measurements.')); + o.depends({ ieee80211k: '1' }); + o.default = o.enabled; + /* 802.11k settings end */ + + /* 802.11v settings start */ + o = ss.taboption('roaming', form.ListValue, 'time_advertisement', _('Time advertisement'), _('802.11v: Time Advertisement in management frames.')); + o.value('0', _('Disabled')); + o.value('2', _('Enabled')); + o.write = function (section_id, value) { + return this.super('write', [section_id, (value == 2) ? value: null]); + } + + //Pull current System TZ setting + var tz = uci.get('system', '@system[0]', 'timezone'); + o = ss.taboption('roaming', form.Value, 'time_zone', _('Time zone'), _('802.11v: Local Time Zone Advertisement in management frames.')); + o.value(tz); + o.rmempty = true; + + o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode', _('WNM Sleep Mode'), _('802.11v: Wireless Network Management (WNM) Sleep Mode (extended sleep mode for stations).')); + o.rmempty = true; + + /* wnm_sleep_mode_no_keys: https://git.openwrt.org/?p=openwrt/openwrt.git;a=commitdiff;h=bf98faaac8ed24cf7d3d93dd4fcd7304d109363b */ + o = ss.taboption('roaming', form.Flag, 'wnm_sleep_mode_no_keys', _('WNM Sleep Mode Fixes'), _('802.11v: Wireless Network Management (WNM) Sleep Mode Fixes: Prevents reinstallation attacks.')); + o.rmempty = true; + + o = ss.taboption('roaming', form.Flag, 'bss_transition', _('BSS Transition'), _('802.11v: Basic Service Set (BSS) transition management.')); + o.rmempty = true; + + /* in master, but not 21.02.1: proxy_arp */ + o = ss.taboption('roaming', form.Flag, 'proxy_arp', _('ProxyARP'), _('802.11v: Proxy ARP enables non-AP STA to remain in power-save for longer.')); + o.rmempty = true; + + /* TODO: na_mcast_to_ucast is missing: needs adding to hostapd.sh - nice to have */ + } + /* 802.11v settings end */ + } + + if (hwtype == 'mac80211') { + o = ss.taboption('encryption', form.ListValue, 'eap_type', _('EAP-Method')); + o.value('tls', 'TLS'); + o.value('ttls', 'TTLS'); + o.value('peap', 'PEAP'); + o.value('fast', 'FAST'); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', form.Flag, 'ca_cert_usesystem', _('Use system certificates'), _("Validate server certificate using built-in system CA bundle,
requires the \"ca-bundle\" package")); + o.enabled = '1'; + o.disabled = '0'; + o.default = o.disabled; + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + o.validate = function(section_id, value) { + if (value == '1' && !L.hasSystemFeature('cabundle')) { + return _("This option cannot be used because the ca-bundle package is not installed."); + } + return true; + }; + + o = ss.taboption('encryption', form.FileUpload, 'ca_cert', _('Path to CA-Certificate')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], ca_cert_usesystem: ['0'] }); + + o = ss.taboption('encryption', form.Value, 'subject_match', _('Certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', form.DynamicList, 'altsubject_match', _('Certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', form.DynamicList, 'domain_match', _('Certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match', _('Certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o = ss.taboption('encryption', form.FileUpload, 'client_cert', _('Path to Client-Certificate')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); + + o = ss.taboption('encryption', form.FileUpload, 'priv_key', _('Path to Private Key')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); + + o = ss.taboption('encryption', form.Value, 'priv_key_pwd', _('Password of Private Key')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['tls'] }); + o.password = true; + + o = ss.taboption('encryption', form.ListValue, 'auth', _('Authentication')); + o.value('PAP', 'PAP'); + o.value('CHAP', 'CHAP'); + o.value('MSCHAP', 'MSCHAP'); + o.value('MSCHAPV2', 'MSCHAPv2'); + o.value('EAP-GTC', 'EAP-GTC'); + o.value('EAP-MD5', 'EAP-MD5'); + o.value('EAP-MSCHAPV2', 'EAP-MSCHAPv2'); + o.value('EAP-TLS', 'EAP-TLS'); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] }); + + o.validate = function(section_id, value) { + var eo = this.section.children.filter(function(o) { return o.option == 'eap_type' })[0], + ev = eo.formvalue(section_id); + + if (ev != 'ttls' && (value == 'PAP' || value == 'CHAP' || value == 'MSCHAP' || value == 'MSCHAPV2')) + return _('This authentication type is not applicable to the selected EAP method.'); + + return true; + }; + + o = ss.taboption('encryption', form.Flag, 'ca_cert2_usesystem', _('Use system certificates for inner-tunnel'), _("Validate server certificate using built-in system CA bundle,
requires the \"ca-bundle\" package")); + o.enabled = '1'; + o.disabled = '0'; + o.default = o.disabled; + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + o.validate = function(section_id, value) { + if (value == '1' && !L.hasSystemFeature('cabundle')) { + return _("This option cannot be used because the ca-bundle package is not installed."); + } + return true; + }; + + o = ss.taboption('encryption', form.FileUpload, 'ca_cert2', _('Path to inner CA-Certificate')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'], ca_cert2_usesystem: ['0'] }); + + o = ss.taboption('encryption', form.Value, 'subject_match2', _('Inner certificate constraint (Subject)'), _("Certificate constraint substring - e.g. /CN=wifi.mycompany.com
See `logread -f` during handshake for actual values")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.DynamicList, 'altsubject_match2', _('Inner certificate constraint (SAN)'), _("Certificate constraint(s) via Subject Alternate Name values
(supported attributes: EMAIL, DNS, URI) - e.g. DNS:wifi.mycompany.com")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.DynamicList, 'domain_match2', _('Inner certificate constraint (Domain)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (exact match)")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.DynamicList, 'domain_suffix_match2', _('Inner certificate constraint (Wildcard)'), _("Certificate constraint(s) against DNS SAN values (if available)
or Subject CN (suffix match)")); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.FileUpload, 'client_cert2', _('Path to inner Client-Certificate')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.FileUpload, 'priv_key2', _('Path to inner Private Key')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + + o = ss.taboption('encryption', form.Value, 'priv_key2_pwd', _('Password of inner Private Key')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], auth: ['EAP-TLS'] }); + o.password = true; + + o = ss.taboption('encryption', form.Value, 'identity', _('Identity')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] }); + + o = ss.taboption('encryption', form.Value, 'anonymous_identity', _('Anonymous Identity')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'tls', 'ttls'] }); + + o = ss.taboption('encryption', form.Value, 'password', _('Password')); + add_dependency_permutations(o, { mode: ['sta', 'sta-wds'], encryption: ['wpa', 'wpa2', 'wpa3', 'wpa3-mixed'], eap_type: ['fast', 'peap', 'ttls'] }); + o.password = true; + + + if (hwtype == 'mac80211') { + // ieee802.11w options + o = ss.taboption('encryption', form.ListValue, 'ieee80211w', _('802.11w Management Frame Protection'), _("Note: Some wireless drivers do not fully support 802.11w. E.g. mwlwifi may have problems")); + o.value('0', _('Disabled')); + o.value('1', _('Optional')); + o.value('2', _('Required')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds', 'sta', 'sta-wds'], encryption: ['owe', 'psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + o.defaults = { + '2': [{ encryption: 'sae' }, { encryption: 'owe' }, { encryption: 'wpa3' }, { encryption: 'wpa3-mixed' }], + '1': [{ encryption: 'sae-mixed'}], + '0': [] + }; + + o.write = function(section_id, value) { + if (value != this.default) + return form.ListValue.prototype.write.call(this, section_id, value); + else + return form.ListValue.prototype.remove.call(this, section_id); + }; + + o = ss.taboption('encryption', form.Value, 'ieee80211w_max_timeout', _('802.11w maximum timeout'), _('802.11w Association SA Query maximum timeout')); + o.depends('ieee80211w', '1'); + o.depends('ieee80211w', '2'); + o.datatype = 'uinteger'; + o.placeholder = '1000'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Value, 'ieee80211w_retry_timeout', _('802.11w retry timeout'), _('802.11w Association SA Query retry timeout')); + o.depends('ieee80211w', '1'); + o.depends('ieee80211w', '2'); + o.datatype = 'uinteger'; + o.placeholder = '201'; + o.rmempty = true; + + o = ss.taboption('encryption', form.Flag, 'wpa_disable_eapol_key_retries', _('Enable key reinstallation (KRACK) countermeasures'), _('Complicates key reinstallation attacks on the client side by disabling retransmission of EAPOL-Key frames that are used to install keys. This workaround might cause interoperability issues and reduced robustness of key negotiation especially in environments with heavy traffic load.')); + add_dependency_permutations(o, { mode: ['ap', 'ap-wds'], encryption: ['psk2', 'psk-mixed', 'sae', 'sae-mixed', 'wpa2', 'wpa3', 'wpa3-mixed'] }); + + if (L.hasSystemFeature('hostapd', 'wps') && L.hasSystemFeature('wpasupplicant')) { + o = ss.taboption('encryption', form.Flag, 'wps_pushbutton', _('Enable WPS pushbutton, requires WPA(2)-PSK/WPA3-SAE')) + o.enabled = '1'; + o.disabled = '0'; + o.default = o.disabled; + o.depends('encryption', 'psk'); + o.depends('encryption', 'psk2'); + o.depends('encryption', 'psk-mixed'); + o.depends('encryption', 'sae'); + o.depends('encryption', 'sae-mixed'); + } + } + } + }); + }; + + s.handleRemove = function(section_id, ev) { + document.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_id)).style.opacity = 0.5; + return form.TypedSection.prototype.handleRemove.apply(this, [section_id, ev]); + }; + + s.handleScan = function(radioDev, ev) { + var table = E('table', { 'class': 'table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th col-2 middle center' }, _('Signal')), + E('th', { 'class': 'th col-4 middle left' }, _('SSID')), + E('th', { 'class': 'th col-2 middle center hide-xs' }, _('Channel')), + E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')), + E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')), + E('th', { 'class': 'th col-3 middle left' }, _('Encryption')), + E('th', { 'class': 'th cbi-section-actions right' }, ' '), + ]) + ]); + + var stop = E('button', { + 'class': 'btn', + 'click': L.bind(this.handleScanStartStop, this), + 'style': 'display:none', + 'data-state': 'stop' + }, _('Stop refresh')); + + cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); + + var md = ui.showModal(_('Join Network: Wireless Scan'), [ + table, + E('div', { 'class': 'right' }, [ + stop, + ' ', + E('button', { + 'class': 'btn', + 'click': L.bind(this.handleScanAbort, this) + }, _('Dismiss')) + ]) + ]); + + md.style.maxWidth = '90%'; + md.style.maxHeight = 'none'; + + this.pollFn = L.bind(this.handleScanRefresh, this, radioDev, {}, table, stop); + + poll.add(this.pollFn); + poll.start(); + }; + + s.handleScanRefresh = function(radioDev, scanCache, table, stop) { + return radioDev.getScanList().then(L.bind(function(results) { + var rows = []; + + for (var i = 0; i < results.length; i++) + scanCache[results[i].bssid] = results[i]; + + for (var k in scanCache) + if (scanCache[k].stale) + results.push(scanCache[k]); + + results.sort(function(a, b) { + var diff = (b.quality - a.quality) || (a.channel - b.channel); + + if (diff) + return diff; + + if (a.ssid < b.ssid) + return -1; + else if (a.ssid > b.ssid) + return 1; + + if (a.bssid < b.bssid) + return -1; + else if (a.bssid > b.bssid) + return 1; + }); + + for (var i = 0; i < results.length; i++) { + var res = results[i], + qv = res.quality || 0, + qm = res.quality_max || 0, + q = (qv > 0 && qm > 0) ? Math.floor((100 / qm) * qv) : 0, + s = res.stale ? 'opacity:0.5' : ''; + + rows.push([ + E('span', { 'style': s }, render_signal_badge(q, res.signal, res.noise)), + E('span', { 'style': s }, (res.ssid != null) ? '%h'.format(res.ssid) : E('em', _('hidden'))), + E('span', { 'style': s }, '%d'.format(res.channel)), + E('span', { 'style': s }, '%h'.format(res.mode)), + E('span', { 'style': s }, '%h'.format(res.bssid)), + E('span', { 'style': s }, '%h'.format(network.formatWifiEncryption(res.encryption))), + E('div', { 'class': 'right' }, E('button', { + 'class': 'cbi-button cbi-button-action important', + 'click': ui.createHandlerFn(this, 'handleJoin', radioDev, res) + }, _('Join Network'))) + ]); + + res.stale = true; + } + + cbi_update_table(table, rows); + + stop.disabled = false; + stop.style.display = ''; + stop.classList.remove('spinning'); + }, this)); + }; + + s.handleScanStartStop = function(ev) { + var btn = ev.currentTarget; + + if (btn.getAttribute('data-state') == 'stop') { + poll.remove(this.pollFn); + btn.firstChild.data = _('Start refresh'); + btn.setAttribute('data-state', 'start'); + } + else { + poll.add(this.pollFn); + btn.firstChild.data = _('Stop refresh'); + btn.setAttribute('data-state', 'stop'); + btn.classList.add('spinning'); + btn.disabled = true; + } + }; + + s.handleScanAbort = function(ev) { + var md = dom.parent(ev.target, 'div[aria-modal="true"]'); + if (md) { + md.style.maxWidth = ''; + md.style.maxHeight = ''; + } + + ui.hideModal(); + poll.remove(this.pollFn); + + this.pollFn = null; + }; + + s.handleJoinConfirm = function(radioDev, bss, form, ev) { + var nameopt = L.toArray(form.lookupOption('name', '_new_'))[0], + passopt = L.toArray(form.lookupOption('password', '_new_'))[0], + ssidopt = L.toArray(form.lookupOption('ssid', '_new_'))[0], + bssidopt = L.toArray(form.lookupOption('bssid', '_new_'))[0], + zoneopt = L.toArray(form.lookupOption('zone', '_new_'))[0], + replopt = L.toArray(form.lookupOption('replace', '_new_'))[0], + nameval = (nameopt && nameopt.isValid('_new_')) ? nameopt.formvalue('_new_') : null, + passval = (passopt && passopt.isValid('_new_')) ? passopt.formvalue('_new_') : null, + ssidval = (ssidopt && ssidopt.isValid('_new_')) ? ssidopt.formvalue('_new_') : null, + bssidval = (bssidopt && bssidopt.isValid('_new_')) ? bssidopt.formvalue('_new_') : null, + zoneval = zoneopt ? zoneopt.formvalue('_new_') : null, + enc = L.isObject(bss.encryption) ? bss.encryption : null, + is_wep = (enc && Array.isArray(enc.wep)), + is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' }).length > 0), + is_sae = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'sae' }).length > 0); + + if (nameval == null || (passopt && passval == null)) + return; + + var section_id = null; + + return this.map.save(function() { + var wifi_sections = uci.sections('wireless', 'wifi-iface'); + + if (replopt.formvalue('_new_') == '1') { + for (var i = 0; i < wifi_sections.length; i++) + if (wifi_sections[i].device == radioDev.getName()) + uci.remove('wireless', wifi_sections[i]['.name']); + } + + if (uci.get('wireless', radioDev.getName(), 'disabled') == '1') { + for (var i = 0; i < wifi_sections.length; i++) + if (wifi_sections[i].device == radioDev.getName()) + uci.set('wireless', wifi_sections[i]['.name'], 'disabled', '1'); + + uci.unset('wireless', radioDev.getName(), 'disabled'); + } + + section_id = next_free_sid(wifi_sections.length); + + uci.add('wireless', 'wifi-iface', section_id); + uci.set('wireless', section_id, 'device', radioDev.getName()); + uci.set('wireless', section_id, 'mode', (bss.mode == 'Ad-Hoc') ? 'adhoc' : 'sta'); + uci.set('wireless', section_id, 'network', nameval); + + if (bss.ssid != null) { + uci.set('wireless', section_id, 'ssid', bss.ssid); + + if (bssidval == '1') + uci.set('wireless', section_id, 'bssid', bss.bssid); + } + else if (bss.bssid != null) { + uci.set('wireless', section_id, 'bssid', bss.bssid); + } + + if (ssidval != null) + uci.set('wireless', section_id, 'ssid', ssidval); + + if (is_sae) { + uci.set('wireless', section_id, 'encryption', 'sae'); + uci.set('wireless', section_id, 'key', passval); + } + else if (is_psk) { + for (var i = enc.wpa.length - 1; i >= 0; i--) { + if (enc.wpa[i] == 2) { + uci.set('wireless', section_id, 'encryption', 'psk2'); + break; + } + else if (enc.wpa[i] == 1) { + uci.set('wireless', section_id, 'encryption', 'psk'); + break; + } + } + + uci.set('wireless', section_id, 'key', passval); + } + else if (is_wep) { + uci.set('wireless', section_id, 'encryption', 'wep-open'); + uci.set('wireless', section_id, 'key', '1'); + uci.set('wireless', section_id, 'key1', passval); + } + else { + uci.set('wireless', section_id, 'encryption', 'none'); + } + + return network.addNetwork(nameval, { proto: 'dhcp' }).then(function(net) { + firewall.deleteNetwork(net.getName()); + + var zonePromise = zoneval + ? firewall.getZone(zoneval).then(function(zone) { return zone || firewall.addZone(zoneval) }) + : Promise.resolve(); + + return zonePromise.then(function(zone) { + if (zone) + zone.addNetwork(net.getName()); + }); + }); + }).then(L.bind(function() { + ui.showModal(null, E('p', { 'class': 'spinning' }, [ _('Loading data…') ])); + + return this.renderMoreOptionsModal(section_id); + }, this)); + }; + + s.handleJoin = function(radioDev, bss, ev) { + poll.remove(this.pollFn); + + var m2 = new form.Map('wireless'), + s2 = m2.section(form.NamedSection, '_new_'), + enc = L.isObject(bss.encryption) ? bss.encryption : null, + is_wep = (enc && Array.isArray(enc.wep)), + is_psk = (enc && Array.isArray(enc.wpa) && L.toArray(enc.authentication).filter(function(a) { return a == 'psk' || a == 'sae' })), + replace, passphrase, name, bssid, zone; + + var nameUsed = function(name) { + var s = uci.get('network', name); + if (s != null && s['.type'] != 'interface') + return true; + + var net = (s != null) ? network.instantiateNetwork(name) : null; + return (net != null && !net.isEmpty()); + }; + + s2.render = function() { + return Promise.all([ + {}, + this.renderUCISection('_new_') + ]).then(this.renderContents.bind(this)); + }; + + if (bss.ssid == null) { + name = s2.option(form.Value, 'ssid', _('Network SSID'), _('The correct SSID must be manually specified when joining a hidden wireless network')); + name.rmempty = false; + }; + + replace = s2.option(form.Flag, 'replace', _('Replace wireless configuration'), _('Check this option to delete the existing networks from this radio.')); + + name = s2.option(form.Value, 'name', _('Name of the new network'), _('The allowed characters are: A-Z, a-z, 0-9 and _')); + name.datatype = 'uciname'; + name.default = 'wwan'; + name.rmempty = false; + name.validate = function(section_id, value) { + if (nameUsed(value)) + return _('The network name is already used'); + + return true; + }; + + for (var i = 2; nameUsed(name.default); i++) + name.default = 'wwan%d'.format(i); + + if (is_wep || is_psk) { + passphrase = s2.option(form.Value, 'password', is_wep ? _('WEP passphrase') : _('WPA passphrase'), _('Specify the secret encryption key here.')); + passphrase.datatype = is_wep ? 'wepkey' : 'wpakey'; + passphrase.password = true; + passphrase.rmempty = false; + } + + if (bss.ssid != null) { + bssid = s2.option(form.Flag, 'bssid', _('Lock to BSSID'), _('Instead of joining any network with a matching SSID, only connect to the BSSID %h.').format(bss.bssid)); + bssid.default = '0'; + } + + zone = s2.option(widgets.ZoneSelect, 'zone', _('Create / Assign firewall-zone'), _('Choose the firewall zone you want to assign to this interface. Select unspecified to remove the interface from the associated zone or fill out the custom field to define a new zone and attach the interface to it.')); + zone.default = 'wan'; + + return m2.render().then(L.bind(function(nodes) { + ui.showModal(_('Joining Network: %q').replace(/%q/, '"%h"'.format(bss.ssid)), [ + nodes, + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': ui.hideModal + }, _('Cancel')), ' ', + E('button', { + 'class': 'cbi-button cbi-button-positive important', + 'click': ui.createHandlerFn(this, 'handleJoinConfirm', radioDev, bss, m2) + }, _('Submit')) + ]) + ], 'cbi-modal').querySelector('[id="%s"] input[class][type]'.format((passphrase || name).cbid('_new_'))).focus(); + }, this)); + }; + + s.handleAdd = function(radioDev, ev) { + var section_id = next_free_sid(uci.sections('wireless', 'wifi-iface').length); + + uci.unset('wireless', radioDev.getName(), 'disabled'); + + uci.add('wireless', 'wifi-iface', section_id); + uci.set('wireless', section_id, 'device', radioDev.getName()); + uci.set('wireless', section_id, 'mode', 'ap'); + uci.set('wireless', section_id, 'ssid', 'OpenWrt'); + uci.set('wireless', section_id, 'encryption', 'none'); + + this.addedSection = section_id; + return this.renderMoreOptionsModal(section_id); + }; + + o = s.option(form.DummyValue, '_badge'); + o.modalonly = false; + o.textvalue = function(section_id) { + var inst = this.section.lookupRadioOrNetwork(section_id), + node = E('div', { 'class': 'center' }); + + if (inst.getWifiNetworks) + node.appendChild(render_radio_badge(inst)); + else + node.appendChild(render_network_badge(inst)); + + return node; + }; + + o = s.option(form.DummyValue, '_stat'); + o.modalonly = false; + o.textvalue = function(section_id) { + var inst = this.section.lookupRadioOrNetwork(section_id); + + if (inst.getWifiNetworks) + return render_radio_status(inst, this.section.wifis.filter(function(e) { + return (e.getWifiDeviceName() == inst.getName()); + })); + else + return render_network_status(inst); + }; + + return m.render().then(L.bind(function(m, nodes) { + poll.add(L.bind(function() { + var section_ids = m.children[0].cfgsections(), + tasks = [ network.getHostHints(), network.getWifiDevices() ]; + + for (var i = 0; i < section_ids.length; i++) { + var row = nodes.querySelector('.cbi-section-table-row[data-sid="%s"]'.format(section_ids[i])), + dsc = row.querySelector('[data-name="_stat"] > div'), + btns = row.querySelectorAll('.cbi-section-actions button'); + + if (dsc.getAttribute('restart') == '') { + dsc.setAttribute('restart', '1'); + tasks.push(fs.exec('/sbin/wifi', ['up', section_ids[i]]).catch(function(e) { + ui.addNotification(null, E('p', e.message)); + })); + } + else if (dsc.getAttribute('restart') == '1') { + dsc.removeAttribute('restart'); + btns[0].classList.remove('spinning'); + btns[0].disabled = false; + } + } + + return Promise.all(tasks) + .then(L.bind(function(hosts_radios) { + var tasks = []; + + for (var i = 0; i < hosts_radios[1].length; i++) + tasks.push(hosts_radios[1][i].getWifiNetworks()); + + return Promise.all(tasks).then(function(data) { + hosts_radios[2] = []; + + for (var i = 0; i < data.length; i++) + hosts_radios[2].push.apply(hosts_radios[2], data[i]); + + return hosts_radios; + }); + }, network)) + .then(L.bind(function(hosts_radios_wifis) { + var tasks = []; + + for (var i = 0; i < hosts_radios_wifis[2].length; i++) + tasks.push(hosts_radios_wifis[2][i].getAssocList()); + + return Promise.all(tasks).then(function(data) { + hosts_radios_wifis[3] = []; + + for (var i = 0; i < data.length; i++) { + var wifiNetwork = hosts_radios_wifis[2][i], + radioDev = hosts_radios_wifis[1].filter(function(d) { return d.getName() == wifiNetwork.getWifiDeviceName() })[0]; + + for (var j = 0; j < data[i].length; j++) + hosts_radios_wifis[3].push(Object.assign({ radio: radioDev, network: wifiNetwork }, data[i][j])); + } + + return hosts_radios_wifis; + }); + }, network)) + .then(L.bind(this.poll_status, this, nodes)); + }, this), 5); + + var table = E('table', { 'class': 'table assoclist', 'id': 'wifi_assoclist_table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th nowrap' }, _('Network')), + E('th', { 'class': 'th hide-xs' }, _('MAC address')), + E('th', { 'class': 'th' }, _('Host')), + E('th', { 'class': 'th' }, _('Signal / Noise')), + E('th', { 'class': 'th' }, _('RX Rate / TX Rate')) + ]) + ]); + + cbi_update_table(table, [], E('em', { 'class': 'spinning' }, _('Collecting data...'))) + + return E([ nodes, E('h3', _('Associated Stations')), table ]); + }, this, m)); + } +}); diff --git a/package/kernel/ath10k-ct/patches/203-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch b/package/kernel/ath10k-ct/patches/203-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch new file mode 100644 index 0000000000..968086efb6 --- /dev/null +++ b/package/kernel/ath10k-ct/patches/203-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch @@ -0,0 +1,41 @@ +From 7658e786e6de189f218ba7ed5d8db953cc6cf980 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Wed, 14 Jun 2023 09:14:56 +0200 +Subject: [PATCH 3/4] wifi: ath10k: add support for QAM-256 in 2.4GHz 802.11n + +Ath10k supports non-standard QAM-256 in 2.4GHz in 802.11n. Permit for +supported client to benefits from these extra bandwidth by enabling +support for it. + +Signed-off-by: Christian Marangi +--- + ath10k-6.2/mac.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/ath10k-6.2/mac.c b/ath10k-6.2/mac.c +index 9c4bf2fdbc0f..8d362ba351e6 100644 +--- a/ath10k-6.2/mac.c ++++ b/ath10k-6.2k/mac.c +@@ -2749,9 +2749,9 @@ static void ath10k_peer_assoc_h_phymode(struct ath10k *ar, + if (sta->deflink.vht_cap.vht_supported && + !ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +- phymode = MODE_11AC_VHT40; ++ phymode = MODE_11AC_VHT40_2G; + else +- phymode = MODE_11AC_VHT20; ++ phymode = MODE_11AC_VHT20_2G; + } else if (sta->deflink.ht_cap.ht_supported && + !ath10k_peer_assoc_h_ht_masked(ht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +@@ -4998,6 +4998,7 @@ static void ath10k_mac_setup_ht_vht_cap(struct ath10k *ar) + + /* Enable the VHT support at 2.4 GHz */ + band->vht_cap = vht_cap; ++ band->vht_cap.vendor_qam256_supported = true; + } + if (ar->phy_capability & WHAL_WLAN_11A_CAPABILITY) { + band = &ar->mac.sbands[NL80211_BAND_5GHZ]; +-- +2.40.1 + diff --git a/package/kernel/mac80211/patches/ath10k/200-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch b/package/kernel/mac80211/patches/ath10k/200-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch new file mode 100644 index 0000000000..fbb9b68741 --- /dev/null +++ b/package/kernel/mac80211/patches/ath10k/200-wifi-ath10k-add-support-for-QAM-256-in-2.4GHz-802.11.patch @@ -0,0 +1,42 @@ +From 7658e786e6de189f218ba7ed5d8db953cc6cf980 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Wed, 14 Jun 2023 09:14:56 +0200 +Subject: [PATCH 3/4] wifi: ath10k: add support for QAM-256 in 2.4GHz 802.11n + +Ath10k supports non-standard QAM-256 in 2.4GHz in 802.11n. Permit for +supported client to benefits from these extra bandwidth by enabling +support for it. + +Signed-off-by: Christian Marangi +--- + drivers/net/wireless/ath/ath10k/mac.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c +index 9c4bf2fdbc0f..8d362ba351e6 100644 +--- a/drivers/net/wireless/ath/ath10k/mac.c ++++ b/drivers/net/wireless/ath/ath10k/mac.c +@@ -2749,9 +2749,9 @@ static void ath10k_peer_assoc_h_phymode(struct ath10k *ar, + if (sta->deflink.vht_cap.vht_supported && + !ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +- phymode = MODE_11AC_VHT40; ++ phymode = MODE_11AC_VHT40_2G; + else +- phymode = MODE_11AC_VHT20; ++ phymode = MODE_11AC_VHT20_2G; + } else if (sta->deflink.ht_cap.ht_supported && + !ath10k_peer_assoc_h_ht_masked(ht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +@@ -4998,6 +4998,8 @@ static void ath10k_mac_setup_ht_vht_cap(struct ath10k *ar) + if (ar->phy_capability & WHAL_WLAN_11G_CAPABILITY) { + band = &ar->mac.sbands[NL80211_BAND_2GHZ]; + band->ht_cap = ht_cap; ++ band->vht_cap = vht_cap; ++ band->vht_cap.vendor_qam256_supported = true; + } + if (ar->phy_capability & WHAL_WLAN_11A_CAPABILITY) { + band = &ar->mac.sbands[NL80211_BAND_5GHZ]; +-- +2.40.1 + diff --git a/package/kernel/mac80211/patches/ath11k/200-wifi-ath11k-add-support-for-QAM-256-in-2.4GHz-802.11.patch b/package/kernel/mac80211/patches/ath11k/200-wifi-ath11k-add-support-for-QAM-256-in-2.4GHz-802.11.patch new file mode 100644 index 0000000000..451dd82828 --- /dev/null +++ b/package/kernel/mac80211/patches/ath11k/200-wifi-ath11k-add-support-for-QAM-256-in-2.4GHz-802.11.patch @@ -0,0 +1,57 @@ +From 462b0e7ce7cadef5c14bb16134e43573b169c9b5 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Wed, 14 Jun 2023 09:44:29 +0200 +Subject: [PATCH 4/4] wifi: ath11k: add support for QAM-256 in 2.4GHz 802.11n + +Ath11k supports non-standard QAM-256 in 2.4GHz in 802.11n. Permit for +supported client to benefits from these extra bandwidth by enabling +support for it. + +Signed-off-by: Christian Marangi +--- + drivers/net/wireless/ath/ath11k/mac.c | 7 +++++-- + drivers/net/wireless/ath/ath11k/wmi.c | 2 ++ + 2 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c +index c947d1c8d8c1..1cadfdd50afc 100644 +--- a/drivers/net/wireless/ath/ath11k/mac.c ++++ b/drivers/net/wireless/ath/ath11k/mac.c +@@ -2773,9 +2773,9 @@ static void ath11k_peer_assoc_h_phymode(struct ath11k *ar, + } else if (sta->deflink.vht_cap.vht_supported && + !ath11k_peer_assoc_h_vht_masked(vht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +- phymode = MODE_11AC_VHT40; ++ phymode = MODE_11AC_VHT40_2G; + else +- phymode = MODE_11AC_VHT20; ++ phymode = MODE_11AC_VHT20_2G; + } else if (sta->deflink.ht_cap.ht_supported && + !ath11k_peer_assoc_h_ht_masked(ht_mcs_mask)) { + if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_40) +@@ -5613,6 +5613,9 @@ static void ath11k_mac_setup_ht_vht_cap(struct ath11k *ar, + *ht_cap_info = ht_cap; + band->ht_cap = ath11k_create_ht_cap(ar, ht_cap, + rate_cap_rx_chainmask); ++ band->vht_cap = ath11k_create_vht_cap(ar, rate_cap_tx_chainmask, ++ rate_cap_rx_chainmask); ++ band->vht_cap.vendor_qam256_supported = true; + } + + if (cap->supported_bands & WMI_HOST_WLAN_5G_CAP && +diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c +index 443199e85fa2..77054ed687d5 100644 +--- a/drivers/net/wireless/ath/ath11k/wmi.c ++++ b/drivers/net/wireless/ath/ath11k/wmi.c +@@ -406,6 +406,8 @@ ath11k_pull_mac_phy_cap_svc_ready_ext(struct ath11k_pdev_wmi *wmi_handle, + * handled. + */ + if (mac_phy_caps->supported_bands & WMI_HOST_WLAN_2G_CAP) { ++ pdev_cap->vht_cap = mac_phy_caps->vht_cap_info_2g; ++ pdev_cap->vht_mcs = mac_phy_caps->vht_supp_mcs_2g; + pdev_cap->tx_chain_mask = mac_phy_caps->tx_chain_mask_2g; + pdev_cap->rx_chain_mask = mac_phy_caps->rx_chain_mask_2g; + } +-- +2.40.1 + diff --git a/package/kernel/mac80211/patches/subsys/200-mac80211-introduce-support-for-vendor-QAM-256-in-2.4.patch b/package/kernel/mac80211/patches/subsys/200-mac80211-introduce-support-for-vendor-QAM-256-in-2.4.patch new file mode 100644 index 0000000000..4c1b021dd9 --- /dev/null +++ b/package/kernel/mac80211/patches/subsys/200-mac80211-introduce-support-for-vendor-QAM-256-in-2.4.patch @@ -0,0 +1,232 @@ +From 35040d1f349f2346832ca1ebb408924dbefc1cfc Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Wed, 14 Jun 2023 07:15:48 +0200 +Subject: [PATCH 1/4] mac80211: introduce support for vendor QAM-256 in 2.4GHz + 802.11n + +Some vendor supports non-standard QAM-256 in 2.4GHz 802.11n mode. +The implementation works by comunicating vht capabilities to the client +in 2.4GHz 802.11n, the supported client will take this info and +benefits from the additional rates of it. + +Each driver needs to enable support for this by enabling the +vendor_qam256_supported in the 2G sband struct and add the required +capabilities for vht_cap. + +This feature is supported by various vendor with all kind of marketing +name, but all of them have in common the use of vht capabilities in +2.4GHz 802.11n. + +Signed-off-by: Christian Marangi +--- + include/net/cfg80211.h | 2 ++ + net/mac80211/mlme.c | 14 ++++++++---- + net/mac80211/util.c | 10 +++++--- + net/mac80211/vht.c | 52 +++++++++++++++++++++++++++--------------- + 4 files changed, 51 insertions(+), 27 deletions(-) + +diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h +index 1b8619685bf6..e9c9afae9a80 100644 +--- a/include/net/cfg80211.h ++++ b/include/net/cfg80211.h +@@ -338,11 +338,13 @@ struct ieee80211_sta_ht_cap { + * to describe 802.11ac VHT capabilities for an STA. + * + * @vht_supported: is VHT supported by the STA ++ * @qam256_supported: is QAM256 supported by the STA + * @cap: VHT capabilities map as described in 802.11ac spec + * @vht_mcs: Supported VHT MCS rates + */ + struct ieee80211_sta_vht_cap { + bool vht_supported; ++ bool vendor_qam256_supported; + u32 cap; /* use IEEE80211_VHT_CAP_ */ + struct ieee80211_vht_mcs_info vht_mcs; + }; +diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c +index 3827b5dcdb03..9c3b3de28dc8 100644 +--- a/net/mac80211/mlme.c ++++ b/net/mac80211/mlme.c +@@ -4720,7 +4720,7 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, + const struct cfg80211_bss_ies *ies; + int ret; + u32 i; +- bool have_80mhz; ++ bool can_vht; + + rcu_read_lock(); + +@@ -4867,18 +4867,22 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, + } + + /* Allow VHT if at least one channel on the sband supports 80 MHz */ +- have_80mhz = false; ++ can_vht = false; + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_NO_80MHZ)) + continue; + +- have_80mhz = true; ++ can_vht = true; + break; + } + +- if (!have_80mhz) { +- sdata_info(sdata, "80 MHz not supported, disabling VHT\n"); ++ /* Some Vendor supports non-standard QAM-256 on 2.4GHz 802.11n */ ++ if (sband->vht_cap.vendor_qam256_supported) ++ can_vht = true; ++ ++ if (!can_vht) { ++ sdata_info(sdata, "80 MHz or QAM-256 not supported, disabling VHT\n"); + *conn_flags |= IEEE80211_CONN_DISABLE_VHT; + } + +diff --git a/net/mac80211/util.c b/net/mac80211/util.c +index 083ad56d08d9..3fc1e84e4275 100644 +--- a/net/mac80211/util.c ++++ b/net/mac80211/util.c +@@ -1951,7 +1951,7 @@ static int ieee80211_build_preq_ies_band(struct ieee80211_sub_if_data *sdata, + int ext_rates_len; + int shift; + u32 rate_flags; +- bool have_80mhz = false; ++ bool can_vht = false; + + *offset = 0; + +@@ -2089,11 +2089,15 @@ static int ieee80211_build_preq_ies_band(struct ieee80211_sub_if_data *sdata, + IEEE80211_CHAN_NO_80MHZ)) + continue; + +- have_80mhz = true; ++ can_vht = true; + break; + } + +- if (sband->vht_cap.vht_supported && have_80mhz) { ++ /* Some Vendor supports non-standard QAM-256 on 2.4GHz 802.11n */ ++ if (sband->vht_cap.vendor_qam256_supported) ++ can_vht = true; ++ ++ if (sband->vht_cap.vht_supported && can_vht) { + if (end - pos < 2 + sizeof(struct ieee80211_vht_cap)) + goto out_err; + pos = ieee80211_ie_build_vht_cap(pos, &sband->vht_cap, +diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c +index c1250aa47808..37f4673d4194 100644 +--- a/net/mac80211/vht.c ++++ b/net/mac80211/vht.c +@@ -121,7 +121,7 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata, + struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap; + struct ieee80211_sta_vht_cap own_cap; + u32 cap_info, i; +- bool have_80mhz; ++ bool can_vht; + + memset(vht_cap, 0, sizeof(*vht_cap)); + +@@ -132,17 +132,21 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata, + return; + + /* Allow VHT if at least one channel on the sband supports 80 MHz */ +- have_80mhz = false; ++ can_vht = false; + for (i = 0; i < sband->n_channels; i++) { + if (sband->channels[i].flags & (IEEE80211_CHAN_DISABLED | + IEEE80211_CHAN_NO_80MHZ)) + continue; + +- have_80mhz = true; ++ can_vht = true; + break; + } + +- if (!have_80mhz) ++ /* Some Vendor supports non-standard QAM-256 on 2.4GHz 802.11n */ ++ if (sband->vht_cap.vendor_qam256_supported) ++ can_vht = true; ++ ++ if (!can_vht) + return; + + /* +@@ -346,16 +350,16 @@ ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta) + struct ieee80211_sta_vht_cap *vht_cap = &link_sta->pub->vht_cap; + struct ieee80211_sta_he_cap *he_cap = &link_sta->pub->he_cap; + struct ieee80211_sta_eht_cap *eht_cap = &link_sta->pub->eht_cap; ++ struct ieee80211_bss_conf *link_conf; ++ enum ieee80211_sta_rx_bandwidth ret; + u32 cap_width; + ++ rcu_read_lock(); ++ link_conf = rcu_dereference(sdata->vif.link_conf[link_id]); ++ + if (he_cap->has_he) { +- struct ieee80211_bss_conf *link_conf; +- enum ieee80211_sta_rx_bandwidth ret; + u8 info; + +- rcu_read_lock(); +- link_conf = rcu_dereference(sdata->vif.link_conf[link_id]); +- + if (eht_cap->has_eht && + link_conf->chandef.chan->band == NL80211_BAND_6GHZ) { + info = eht_cap->eht_cap_elem.phy_cap_info[0]; +@@ -383,32 +387,42 @@ ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta) + ret = IEEE80211_STA_RX_BW_80; + else + ret = IEEE80211_STA_RX_BW_20; +-out: +- rcu_read_unlock(); + +- return ret; ++ goto out; + } + +- if (!vht_cap->vht_supported) +- return link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ? ++ if (!vht_cap->vht_supported || ++ (link_conf->chandef.chan->band == NL80211_BAND_2GHZ && ++ vht_cap->vendor_qam256_supported)) { ++ ret = link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ? + IEEE80211_STA_RX_BW_40 : + IEEE80211_STA_RX_BW_20; ++ goto out; ++ } + + cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK; + + if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ || +- cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) +- return IEEE80211_STA_RX_BW_160; ++ cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) { ++ ret = IEEE80211_STA_RX_BW_160; ++ goto out; ++ } + + /* + * If this is non-zero, then it does support 160 MHz after all, + * in one form or the other. We don't distinguish here (or even + * above) between 160 and 80+80 yet. + */ +- if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK) +- return IEEE80211_STA_RX_BW_160; ++ if (vht_cap->cap & IEEE80211_VHT_CAP_EXT_NSS_BW_MASK) { ++ ret = IEEE80211_STA_RX_BW_160; ++ goto out; ++ } ++ ++ ret = IEEE80211_STA_RX_BW_80; + +- return IEEE80211_STA_RX_BW_80; ++out: ++ rcu_read_unlock(); ++ return ret; + } + + enum nl80211_chan_width +-- +2.40.1 + diff --git a/package/kernel/mac80211/patches/subsys/201-mac80211-add-logic-to-skip-useless-VHT-cap-check-in-.patch b/package/kernel/mac80211/patches/subsys/201-mac80211-add-logic-to-skip-useless-VHT-cap-check-in-.patch new file mode 100644 index 0000000000..4dbf801b7d --- /dev/null +++ b/package/kernel/mac80211/patches/subsys/201-mac80211-add-logic-to-skip-useless-VHT-cap-check-in-.patch @@ -0,0 +1,37 @@ +From 793bfa410bd16c273beb390818ab44886c680a22 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Wed, 14 Jun 2023 10:20:31 +0200 +Subject: [PATCH 2/4] mac80211: add logic to skip useless VHT cap check in + ieee80211_sta_cap_rx_bw + +Add logic to skip useless VHT cap check in ieee80211_sta_cap_rx_bw in +the case where HT doesn't indicate support for 40MHz. + +If the STA doesn't indicate support for 40MHz then it doesn't make sense +to check additional support for 80MHz, 80+80MHz or 160MHz + +Signed-off-by: Christian Marangi +--- + net/mac80211/vht.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c +index 37f4673d4194..b57391a032c8 100644 +--- a/net/mac80211/vht.c ++++ b/net/mac80211/vht.c +@@ -400,6 +400,12 @@ ieee80211_sta_cap_rx_bw(struct link_sta_info *link_sta) + goto out; + } + ++ /* Skip checking VHT cap and set to 20MHz if HT cap doesn't indicate ++ * support for 40MHz. ++ */ ++ if (!(link_sta->pub->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) ++ return IEEE80211_STA_RX_BW_20; ++ + cap_width = vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK; + + if (cap_width == IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ || +-- +2.40.1 + diff --git a/package/network/utils/iwinfo/patches/0001-nl80211-add-support-for-QAM-256-in-2.4GHz-802.11n.patch b/package/network/utils/iwinfo/patches/0001-nl80211-add-support-for-QAM-256-in-2.4GHz-802.11n.patch new file mode 100644 index 0000000000..4e31ad47a8 --- /dev/null +++ b/package/network/utils/iwinfo/patches/0001-nl80211-add-support-for-QAM-256-in-2.4GHz-802.11n.patch @@ -0,0 +1,76 @@ +From b4fe1f680ba28588c330bcd65156ef7f8423426c Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Thu, 15 Jun 2023 19:44:53 +0200 +Subject: [PATCH] nl80211: add support for QAM-256 in 2.4GHz 802.11n + +Add support for QAM-256 in 2.4GHz 802.11n where VHT rates are set in +2.4GHz 802.11n mode. + +To identify if we are using this non-standard mode, we refer to the +hostapd conf providing vht_capab config. + +Signed-off-by: Christian Marangi +--- + iwinfo_nl80211.c | 18 +++++++++++++++--- + 1 file changed, 15 insertions(+), 3 deletions(-) + +diff --git a/iwinfo_nl80211.c b/iwinfo_nl80211.c +index 50bb8f0..cb3100f 100644 +--- a/iwinfo_nl80211.c ++++ b/iwinfo_nl80211.c +@@ -3179,6 +3179,14 @@ static void nl80211_eval_modelist(struct nl80211_modes *m) + { + m->hw |= IWINFO_80211_B; + m->hw |= IWINFO_80211_G; ++ ++ if (m->nl_vht > 0) ++ { ++ m->ht |= IWINFO_HTMODE_VHT20; ++ ++ if (m->nl_ht & (1 << 1)) ++ m->ht |= IWINFO_HTMODE_VHT40; ++ } + } + + if (m->bands & IWINFO_BAND_5) +@@ -3313,10 +3321,10 @@ static int nl80211_get_htmode_cb(struct nl_msg *msg, void *arg) + + static int nl80211_get_htmode(const char *ifname, int *buf) + { ++ bool he = false, vendor_qam256 = false; + struct chan_info chn = { 0 }; + char *res, b[2] = { 0 }; + int err; +- bool he = false; + + res = nl80211_phy2ifname(ifname); + *buf = 0; +@@ -3332,11 +3340,15 @@ static int nl80211_get_htmode(const char *ifname, int *buf) + else if (nl80211_wpactl_query(res ? res : ifname, "wifi_generation", b, sizeof(b))) + he = b[0] == '6'; + ++ if ((chn.width == NL80211_CHAN_WIDTH_20 || chn.width == NL80211_CHAN_WIDTH_40) && ++ nl80211_hostapd_query(res ? res : ifname, "vht_capab", b, sizeof(b))) ++ vendor_qam256 = true; ++ + switch (chn.width) { + case NL80211_CHAN_WIDTH_20: + if (he) + *buf = IWINFO_HTMODE_HE20; +- else if (chn.mode == -1) ++ else if (chn.mode == -1 || vendor_qam256) + *buf = IWINFO_HTMODE_VHT20; + else + *buf = IWINFO_HTMODE_HT20; +@@ -3344,7 +3356,7 @@ static int nl80211_get_htmode(const char *ifname, int *buf) + case NL80211_CHAN_WIDTH_40: + if (he) + *buf = IWINFO_HTMODE_HE40; +- else if (chn.mode == -1) ++ else if (chn.mode == -1 || vendor_qam256) + *buf = IWINFO_HTMODE_VHT40; + else + *buf = IWINFO_HTMODE_HT40; +-- +2.40.1 +