diff --git a/README.md b/README.md index 7c967ff..0870e3e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # x1pro -这是一个x1pro 刷机成cudytr3000 \ No newline at end of file +这是一个x1pro 刷机成cudytr3000 + +## v37 (2026-07-25) +`sysupgrade-v37.bin` (50,473,789 B, md5 `353fc69dac266a5b7a5dea4647b24eb6`) — 修 LuCI 信道分析页 3 补丁 (mt_wifi freq.band/扫描 band/tab-active) + 坑#6 根治 (rc.local + 30-mss-clamp 在 br_netfilter 加载后兜底重写 `net.bridge.bridge-nf-call-*=1` 与 `nft mangle_forward set 1360`). 详细如 `记忆/project_v37_build.md` (本地内存索引). diff --git a/immortalwrt-patches/feeds/luci/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/immortalwrt-patches/feeds/luci/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js index 0937491..836aa4c 100644 --- a/immortalwrt-patches/feeds/luci/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js +++ b/immortalwrt-patches/feeds/luci/modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js @@ -1,7 +1,7 @@ 'use strict'; 'require view'; 'require dom'; -require 'request'; +'require request'; var IW_ESSID = {}; 'require poll'; @@ -312,18 +312,20 @@ var CBIWifiFrequencyValue = form.Value.extend({ network.getWifiDevice(section_id), this.callFrequencyList(freqdev) ]).then(L.bind(function(data) { + // v36: mt_wifi has no hostapd — always offer 'auto' channel this.channels = { - '2g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [], - '5g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [], - '6g': L.hasSystemFeature('hostapd', 'acs') ? [ 'auto', 'auto', { available: true } ] : [], + '2g': [ 'auto', 'auto', { available: true } ], + '5g': [ 'auto', 'auto', { available: true } ], + '6g': [ 'auto', 'auto', { available: true } ], '60g': [] }; const wifidevs = data[0]; const freqlist = data[1]; freqlist.forEach(freq => { + // v36: rpcd-iwinfo on mt_wifi lacks freq.band — derive from mhz if (!freq.band) - return; + freq.band = (freq.mhz > 50000) ? 60 : (freq.mhz >= 5900) ? 6 : (freq.mhz >= 5000) ? 5 : 2; const band = '%dg'.format(freq.band); const available = (freq.restricted && freq.no_ir) ? false: true; @@ -342,19 +344,27 @@ var CBIWifiFrequencyValue = form.Value.extend({ const hwmodelist = L.toArray(wifidevs ? wifidevs.getHWModes() : null) .reduce(function(o, v) { o[v] = true; return o; }, {}); + // v36: mt_wifi netifd handler reports no hwmodes — assume a/b/g/n/ac/ax + if (!Object.keys(hwmodelist).length) + Object.assign(hwmodelist, { a: true, b: true, g: true, n: true, ac: true, ax: true }); + // Define supported modes this.modes = [ '', 'Legacy', { available: hwmodelist.a || hwmodelist.b || hwmodelist.g }, 'n', 'N', { available: hwmodelist.n }, - 'ac', 'AC', { available: L.hasSystemFeature('hostapd', '11ac') && hwmodelist.ac }, - 'ax', 'AX', { available: L.hasSystemFeature('hostapd', '11ax') && hwmodelist.ax }, - 'be', 'BE', { available: L.hasSystemFeature('hostapd', '11be') && hwmodelist.be } + 'ac', 'AC', { available: hwmodelist.ac }, + 'ax', 'AX', { available: hwmodelist.ax }, + 'be', 'BE', { available: hwmodelist.be } ]; // Create a list of HT modes based on device capabilities const htmodelist = L.toArray(wifidevs ? wifidevs.getHTModes() : null) .reduce(function(o, v) { o[v] = true; return o; }, {}); + // v36: mt_wifi reports no htmodes — assume HT/VHT/HE up to 160MHz + if (!Object.keys(htmodelist).length) + Object.assign(htmodelist, { HT20: true, HT40: true, VHT20: true, VHT40: true, VHT80: true, VHT160: true, HE20: true, HE40: true, HE80: true, HE160: true }); + this.htmodes = { '': [ '', '-', { available: true } ], 'n': [ @@ -2377,9 +2387,10 @@ return view.extend({ const m = out && out.match(/ESSID: "([^"]*)"/); if (m) IW_ESSID[ifname] = m[1]; }, this)).catch(function(){}); - })); + }); }, this)); - }, this)); + }); + }, this)); const tasks = [ network.getHostHints(), network.getWifiDevices() ]; diff --git a/immortalwrt-patches/feeds/luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/channel_analysis.js b/immortalwrt-patches/feeds/luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/channel_analysis.js new file mode 100644 index 0000000..baa783b --- /dev/null +++ b/immortalwrt-patches/feeds/luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/channel_analysis.js @@ -0,0 +1,531 @@ +'use strict'; +'require view'; +'require poll'; +'require request'; +'require network'; +'require ui'; +'require rpc'; +'require tools.prng as random'; + +return view.extend({ + callFrequencyList : rpc.declare({ + object: 'iwinfo', + method: 'freqlist', + params: [ 'device' ], + expect: { results: [] } + }), + + callInfo : rpc.declare({ + object: 'iwinfo', + method: 'info', + params: [ 'device' ], + expect: { } + }), + + render_signal_badge: function(signalPercent, signalValue) { + var icon, title, value; + + if (signalPercent < 0) + icon = L.resource('icons/signal-none.svg'); + else if (signalPercent == 0) + icon = L.resource('icons/signal-000-000.svg'); + else if (signalPercent < 25) + icon = L.resource('icons/signal-000-025.svg'); + else if (signalPercent < 50) + icon = L.resource('icons/signal-025-050.svg'); + else if (signalPercent < 75) + icon = L.resource('icons/signal-050-075.svg'); + else + icon = L.resource('icons/signal-075-100.svg'); + + value = '%d\xa0%s'.format(signalValue, _('dBm')); + title = '%s: %d %s'.format(_('Signal'), signalValue, _('dBm')); + + return E('div', { + 'class': 'ifacebadge', + 'title': title, + 'data-signal': signalValue + }, [ + E('img', { 'src': icon }), + value + ]); + }, + + add_wifi_to_graph: function(chan_analysis, res, scanCache, channels, channel_width) { + const offset_tbl = chan_analysis.offset_tbl; + const height = chan_analysis.graph.offsetHeight - 2; + const step = chan_analysis.col_width; + const height_diff = (height-(height-(res.signal*-4))); + + if (scanCache[res.bssid].color == null) + scanCache[res.bssid].color = random.derive_color(res.bssid); + + if (scanCache[res.bssid].graph == null || scanCache[res.bssid].graph === undefined) { + const group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + const line = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); + const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + const color = scanCache[res.bssid].color; + + line.setAttribute('style', 'fill:'+color+'4f'+';stroke:'+color+';stroke-width:0.5'); + text.setAttribute('style', 'fill:'+color+';font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + text.appendChild(document.createTextNode(res.ssid || res.bssid)); + + group.appendChild(line); + group.appendChild(text); + + chan_analysis.graph.firstElementChild.appendChild(group); + scanCache[res.bssid].graph = { group : group, line : line, text : text }; + } + + channels.forEach(function(channel) { + if (channel_width > 2) { + if (!("main" in scanCache[res.bssid].graph)) { + const main = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); + main.setAttribute('style', 'fill:url(#GradientVerticalCenteredBlack)'); + scanCache[res.bssid].graph.group.appendChild(main); + chan_analysis.graph.firstElementChild.lastElementChild.appendChild(main); + scanCache[res.bssid].graph["main"] = main; + } + const main_offset = offset_tbl[res.channel]; + const points = [ + (main_offset-(step*(2 )))+','+height, + (main_offset-(step*(2-1)))+','+height_diff, + (main_offset+(step*(2-1)))+','+height_diff, + (main_offset+(step*(2 )))+','+height + ]; + scanCache[res.bssid].graph.main.setAttribute('points', points); + } + + const chan_offset = offset_tbl[channel]; + const points = [ + (chan_offset-(step*(channel_width )))+','+height, + (chan_offset-(step*(channel_width-1)))+','+height_diff, + (chan_offset+(step*(channel_width-1)))+','+height_diff, + (chan_offset+(step*(channel_width )))+','+height + ]; + + scanCache[res.bssid].graph.text.setAttribute('x', offset_tbl[res.channel]-step); + scanCache[res.bssid].graph.text.setAttribute('y', height_diff - 2); + scanCache[res.bssid].graph.line.setAttribute('points', points); + scanCache[res.bssid].graph.group.style.zIndex = res.signal*-1; + scanCache[res.bssid].graph.group.style.opacity = res.stale ? '0.5' : null; + }) + }, + + create_channel_graph: function(chan_analysis, freq_tbl, band) { + /* v37: hidden tabs have offsetWidth=0 — defer graph creation until tab active */ + if (!chan_analysis.tab_listener) { + chan_analysis.tab_listener = true; + chan_analysis.tab.addEventListener('cbi-tab-active', L.bind(function(ev) { + this.active_tab = ev.detail.tab; + if (!chan_analysis.drawn) + this.create_channel_graph(chan_analysis, freq_tbl, band); + if (!this.radios[this.active_tab].loadedOnce) + poll.start(); + }, this)); + } + if (chan_analysis.drawn || chan_analysis.graph.offsetWidth <= 2) + return; + chan_analysis.drawn = true; + + var columns = (band != 2) ? freq_tbl.length * 4 : freq_tbl.length + 3, + chan_graph = chan_analysis.graph, + G = chan_graph.firstElementChild, + step = (chan_graph.offsetWidth - 2) / columns, + curr_offset = step; + + function createGraphHLine(graph, pos, width, dash) { + var elem = document.createElementNS('http://www.w3.org/2000/svg', 'line'); + elem.setAttribute('x1', pos); + elem.setAttribute('y1', 0); + elem.setAttribute('x2', pos); + elem.setAttribute('y2', '100%'); + elem.setAttribute('style', 'stroke:black;stroke-width:'+width+';stroke-dasharray:'+dash); + graph.appendChild(elem); + } + + function createGraphText(graph, pos, text) { + var elem = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + elem.setAttribute('y', 15); + elem.setAttribute('style', 'fill:#eee; font-size:9pt; font-family:sans-serif; text-shadow:1px 1px 1px #000'); + elem.setAttribute('x', pos + 5); + elem.appendChild(document.createTextNode(text)); + graph.appendChild(elem); + } + + chan_analysis.col_width = step; + + createGraphHLine(G,curr_offset, 0.1, 1); + for (var i=0; i< freq_tbl.length;i++) { + var channel = freq_tbl[i] + chan_analysis.offset_tbl[channel] = curr_offset+step; + + if (band != 2) { + createGraphHLine(G,curr_offset+step, 0.1, 3); + if (channel < 100) + createGraphText(G,curr_offset-(step/2), channel); + else + createGraphText(G,curr_offset-step, channel); + } else { + createGraphHLine(G,curr_offset+step, 0.1, 0); + createGraphText(G,curr_offset+step, channel); + } + curr_offset += step; + + if ((band != 2) && freq_tbl[i+1]) { + var next_channel = freq_tbl[i+1]; + /* Check if we are transitioning to another 5/6Ghz band range */ + if ((next_channel - channel) == 4) { + for (var j=1; j < 4; j++) { + chan_analysis.offset_tbl[channel+j] = curr_offset+step; + if (j == 2) + createGraphHLine(G,curr_offset+step, 0.1, 0); + else + createGraphHLine(G,curr_offset+step, 0.1, 1); + curr_offset += step; + } + } else { + chan_analysis.offset_tbl[channel+1] = curr_offset+step; + createGraphHLine(G,curr_offset+step, 0.1, 1); + curr_offset += step; + + chan_analysis.offset_tbl[next_channel-2] = curr_offset+step; + createGraphHLine(G,curr_offset+step, 0.5, 0); + curr_offset += step; + + chan_analysis.offset_tbl[next_channel-1] = curr_offset+step; + createGraphHLine(G,curr_offset+step, 0.1, 1); + curr_offset += step; + } + } + } + createGraphHLine(G,curr_offset+step, 0.1, 1); + + chan_analysis.tab.addEventListener('cbi-tab-active', L.bind(function(ev) { + this.active_tab = ev.detail.tab; + if (!this.radios[this.active_tab].loadedOnce) + poll.start(); + }, this)); + }, + + handleScanRefresh: function() { + if (!this.active_tab) + return; + + var radio = this.radios[this.active_tab]; + + return Promise.all([ + radio.dev.getScanList(), + this.callInfo(radio.dev.getName()) + ]).then(L.bind(function(data) { + var results = data[0], + local_wifi = data[1], + table = radio.table, + chan_analysis = radio.graph, + scanCache = radio.scanCache, + band = radio.band; + + var rows = []; + + for (var i = 0; i < results.length; i++) { + if (scanCache[results[i].bssid] == null) + scanCache[results[i].bssid] = {}; + + scanCache[results[i].bssid].data = results[i]; + scanCache[results[i].bssid].data.stale = false; + } + + if (band + 'g' == radio.dev.get('band')) { + if (scanCache[local_wifi.bssid] == null) + scanCache[local_wifi.bssid] = {}; + + scanCache[local_wifi.bssid].data = local_wifi; + + if (chan_analysis.offset_tbl[local_wifi.channel] != null && local_wifi.center_chan1) { + var center_channels = [local_wifi.center_chan1], + chan_width_text = local_wifi.htmode.replace(/(V)*H[TE]/,''), /* Handle HT VHT HE */ + chan_width = parseInt(chan_width_text)/10; + + if (local_wifi.center_chan2) { + center_channels.push(local_wifi.center_chan2); + chan_width = 8; + } + + local_wifi.signal = -10; + local_wifi.ssid = 'Local Interface'; + + this.add_wifi_to_graph(chan_analysis, local_wifi, scanCache, center_channels, chan_width); + rows.push([ + this.render_signal_badge(q, local_wifi.signal), + [ + E('span', { 'style': 'color:'+scanCache[local_wifi.bssid].color }, '⬤ '), + local_wifi.ssid + ], + '%d'.format(local_wifi.channel), + '%h MHz'.format(chan_width_text), + '%h'.format(local_wifi.mode), + '%h'.format(local_wifi.bssid) + ]); + } + } + + for (var k in scanCache) + if (scanCache[k].data.stale) + results.push(scanCache[k].data); + + results.sort(function(a, b) { + if (a.channel - b.channel) + return 1; + + 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' : '', + center_channels = [res.channel], + chan_width = 2; + + /* Skip WiFi not supported by the current band */ + /* v37: rpcd-iwinfo scan results lack band (mhz is 0/garbage) — derive from channel */ + if (!res.band) + res.band = (res.channel <= 14) ? 2 : 5; + + if (band != res.band) + continue; + if (chan_analysis.offset_tbl[res.channel] == null) + continue; + + res.channel_width = "20 MHz"; + if (res.ht_operation != null) { + /* Detect 40 MHz operation by looking for the presence of + * a secondary channel. */ + if (res.ht_operation.secondary_channel_offset == "below") { + res.channel_width = "40 MHz"; + chan_width = 4; /* 40 MHz Channel Used */ + center_channels[0] -= 2; + } else if (res.ht_operation.secondary_channel_offset == "above") { + res.channel_width = "40 MHz"; + chan_width = 4; /* 40 MHz Channel Used */ + center_channels[0] += 2; + } else { + /* Fallback to 20 MHz due to discovery of other APs on the + * same channel (802.11n coexistence mechanism). */ + if (res.ht_operation.channel_width == 2040) + res.channel_width = "20 MHz (40 MHz Intolerant)"; + } + } + + /* if channel_width <= 40, refer to HT (above) for actual channel width, + * as vht_operation.channel_width == 40 really only means that the used + * bandwidth is <= 40 and could be 20 Mhz as well */ + if (res.vht_operation?.channel_width > 40) { + center_channels[0] = res.vht_operation.center_freq_1; + if (res.vht_operation.channel_width == 80) { + chan_width = 8; + res.channel_width = "80 MHz"; + + /* If needed, adjust based on the 802.11ac Wave 2 interop workaround. */ + if (res.vht_operation.center_freq_2) { + var diff = Math.abs(res.vht_operation.center_freq_2 - + res.vht_operation.center_freq_1); + + if (diff == 8) { + chan_width = 16; + res.channel_width = "160 MHz"; + center_channels.push(res.vht_operation.center_freq_2); + } else if (diff > 8) { + chan_width = 8; + res.channel_width = "80+80 MHz"; + center_channels.push(res.vht_operation.center_freq_2); + } + } + } else if (res.vht_operation.channel_width == 8080) { + res.channel_width = "80+80 MHz"; + chan_width = 8; + center_channels.push(res.vht_operation.center_freq_2); + } else if (res.vht_operation.channel_width == 160) { + res.channel_width = "160 MHz"; + chan_width = 16; + } + } + + if (res.he_operation?.channel_width > 20) { + center_channels[0] = res.he_operation.center_freq_1; + chan_width = res.he_operation.channel_width / 10; + switch (res.he_operation.channel_width) { + case 40: + res.channel_width = "40 MHz"; + break; + case 80: + res.channel_width = "80 MHz"; + break; + case 160: + res.channel_width = "160 MHz"; + center_channels.push(res.he_operation.center_freq_2); + break; + } + } + + if (res.eht_operation?.channel_width == 320) { + chan_width = 32; + res.channel_width = "320 MHz"; + center_channels.push(res.eht_operation.center_freq_2); + } + + this.add_wifi_to_graph(chan_analysis, res, scanCache, center_channels, chan_width); + + rows.push([ + E('span', { 'style': s }, this.render_signal_badge(q, res.signal)), + E('span', { 'style': s }, [ + E('span', { 'style': 'color:'+scanCache[results[i].bssid].color }, '⬤ '), + (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.channel_width)), + E('span', { 'style': s }, '%h'.format(res.mode)), + E('span', { 'style': s }, '%h'.format(res.bssid)) + ]); + + scanCache[results[i].bssid].data.stale = true; + } + + cbi_update_table(table, rows); + + if (!radio.loadedOnce) { + radio.loadedOnce = true; + poll.stop(); + } + }, this)) + }, + + radios : {}, + + loadSVG : function(src) { + return request.get(src).then(function(response) { + if (!response.ok) + throw new Error(response.statusText); + + return E('div', { + 'id': 'channel_graph', + 'style': 'width:100%;height:400px;border:1px solid #000;background:#fff' + }, E(response.text())); + }); + }, + + load: function() { + return Promise.all([ + this.loadSVG(L.resource('svg/channel_analysis.svg')), + network.getWifiDevices().then(L.bind(function(data) { + var tasks = [], ret = []; + + for (var i = 0; i < data.length; i++) { + ret[data[i].getName()] = { dev : data[i] }; + + tasks.push(this.callFrequencyList(data[i].getName()) + .then(L.bind(function(radio, data) { + ret[radio.getName()].freq = data; + }, this, data[i]))); + } + + return Promise.all(tasks).then(function() { return ret; }) + }, this)) + ]); + }, + + render: function(data) { + var svg = data[0], + wifiDevs = data[1]; + + var h2 = E('div', {'class' : 'cbi-title-section'}, [ + E('h2', {'class': 'cbi-title-field'}, [ _('Channel Analysis') ]), + E('div', {'class': 'cbi-title-buttons' }, [ + E('button', { + 'class': 'cbi-button cbi-button-edit', + 'click': ui.createHandlerFn(this, 'handleScanRefresh') + }, [ _('Refresh Channels') ])]) + ]); + + var tabs = E('div', {}, E('div')); + + for (var ifname in wifiDevs) { + var bands = { + [2] : { title: '2.4GHz', channels: [] }, + [5] : { title: '5GHz', channels: [] }, + [6] : { title: '6GHz', channels: [] }, + }; + + /* Split FrequencyList in Bands */ + wifiDevs[ifname].freq.forEach(function(freq) { + /* v37: rpcd-iwinfo on mt_wifi lacks freq.band — derive from mhz */ + if (!freq.band) + freq.band = (freq.mhz > 50000) ? 60 : (freq.mhz >= 5900) ? 6 : (freq.mhz >= 5000) ? 5 : 2; + + if (bands[freq.band]) + bands[freq.band].channels.push(freq.channel); + }); + + for (var band in bands) { + if (bands[band].channels.length == 0) + continue; + + var csvg = svg.cloneNode(true), + 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-3 middle left' }, _('Channel Width')), + E('th', { 'class': 'th col-2 middle left hide-xs' }, _('Mode')), + E('th', { 'class': 'th col-3 middle left hide-xs' }, _('BSSID')) + ]) + ]), + tab = E('div', { 'data-tab': ifname+band, 'data-tab-title': ifname+' ('+bands[band].title+')' }, + [E('br'),csvg,E('br'),table,E('br')]), + graph_data = { + graph: csvg, + offset_tbl: {}, + col_width: 0, + tab: tab, + }; + + this.radios[ifname+band] = { + dev: wifiDevs[ifname].dev, + band: band, + graph: graph_data, + table: table, + scanCache: {}, + loadedOnce: false, + }; + + cbi_update_table(table, [], E('em', { class: 'spinning' }, _('Starting wireless scan...'))); + + tabs.firstElementChild.appendChild(tab) + + requestAnimationFrame(L.bind(this.create_channel_graph, this, graph_data, bands[band].channels, band)); + } + } + + ui.tabs.initTabGroup(tabs.firstElementChild.childNodes); + + this.pollFn = L.bind(this.handleScanRefresh, this); + poll.add(this.pollFn); + + return E('div', {}, [h2, tabs]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/immortalwrt-patches/files/etc/hotplug.d/iface/30-mss-clamp b/immortalwrt-patches/files/etc/hotplug.d/iface/30-mss-clamp index 36353ff..6fba0d9 100644 --- a/immortalwrt-patches/files/etc/hotplug.d/iface/30-mss-clamp +++ b/immortalwrt-patches/files/etc/hotplug.d/iface/30-mss-clamp @@ -1,11 +1,12 @@ #!/bin/sh -# === MSS clamp persistence (坑 #12 part 3) === +# === MSS clamp persistence (坑 #12 part 3 + 坑 #6 2026-07-25 加固) === # fw4 reload (triggered by any ifup) wipes the nft rule added by 20-vxlan hotplug. -# This iface hotplug re-adds the rule after every interface ifup event, -# but only if the rule is missing (idempotent). -# Idempotency: grep for the unique "set 1360" string before adding. +# br_netfilter 晚加载会把 bridge-nf-call-* sysctl 重置回 0 (sysctl.d 开机时键不存在被静默跳过)。 +# This iface hotplug re-asserts both after every interface ifup event (idempotent). [ "$ACTION" = "ifup" ] || exit 0 sleep 2 # wait for fw4 reload to complete -nft list chain inet fw4 mangle_forward 2>/dev/null | grep -q 'set 1360' || \ - nft add rule inet fw4 mangle_forward 'iifname "br-lan" tcp flags syn tcp option maxseg size set 1360' 2>/dev/null +sysctl -w net.bridge.bridge-nf-call-iptables=1 >/dev/null 2>&1 +sysctl -w net.bridge.bridge-nf-call-ip6tables=1 >/dev/null 2>&1 +sysctl -w net.bridge.bridge-nf-call-arptables=1 >/dev/null 2>&1 +nft list chain inet fw4 mangle_forward 2>/dev/null | grep -q 'set 1360' || nft add rule inet fw4 mangle_forward 'iifname "br-lan" tcp flags syn tcp option maxseg size set 1360' 2>/dev/null exit 0 diff --git a/immortalwrt-patches/files/etc/rc.local b/immortalwrt-patches/files/etc/rc.local index be1fb92..12ea6a7 100644 --- a/immortalwrt-patches/files/etc/rc.local +++ b/immortalwrt-patches/files/etc/rc.local @@ -23,4 +23,12 @@ echo 32768 > /proc/sys/net/core/rps_sock_flow_entries 2>/dev/null # Log-only (no uci mutation); see /etc/wifi-band-verify.sh for rationale. ( /etc/wifi-band-verify.sh >/dev/null 2>&1 & ) + +# === bridge-nf re-assert (keng #6 加固 2026-07-25) === +# sysctl.d/99-bridge-nf.conf 开机应用时 br_netfilter 可能未加载, 键不存在被静默跳过; +# 模块晚加载后默认值 0 会让 MSS clamp 对桥接流量失效. rc.local 最后兜底. +sysctl -w net.bridge.bridge-nf-call-iptables=1 2>/dev/null +sysctl -w net.bridge.bridge-nf-call-ip6tables=1 2>/dev/null +sysctl -w net.bridge.bridge-nf-call-arptables=1 2>/dev/null + exit 0 diff --git a/sysupgrade-v37.bin b/sysupgrade-v37.bin new file mode 100644 index 0000000..1edf2e3 Binary files /dev/null and b/sysupgrade-v37.bin differ