Replace tarball with un-tarred ImmortalWrt patch tree (package/feeds/files overrides)

This commit is contained in:
2026-07-23 19:35:11 +08:00
parent 3442922678
commit cb3dd4ce9f
21 changed files with 5795 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
# v33 完美固件源码包
对应 sysupgrade-v33.binmd5 57d43f1a56c66fafd5c62e8846641232)。
## 包含文件(相对 ImmortalWrt 源码树根)
### package/mtk/ — MTK 闭源驱动层补丁
- `applications/mtwifi-cfg/files/mtwifi-cfg/mtwifi_cfg` — 5G/HE160 双分支写 VHT_Sec80_Channel=155
- `applications/mtwifi-cfg/files/mtwifi.sh` — detect_mtwifi 诚实 UCIidx=1=5g/HE160, idx=2=2g/HE40
- `applications/mtwifi-cfg/files/netifd/mtwifi.sh` — v33 关键补丁:setup 完成后 deferred 后台检测 dat MacAddress 与物理 netdev MAC,重发 wireless_add_vif 上报物理 ifname
- `applications/luci-app-mtk/luasrc/controller/mtkwifi.lua` — 顶部空 index() 禁用 MTK 私有面板
### package/mtk/drivers/wifi-profile/
- `files/mt7981/l1profile.dat` — 主次顺序 b0;b1(出厂值,未改动)
### feeds/luci/
- `modules/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js` — Overview/Modal SSID 读 iwinfo、reload-block 补丁、freqlist/txpowerlist 改传物理 ifname
### files/ — 烤入 squashfs 的配置与脚本
- `etc/uci-defaults/99-custom-config` — 首启 UCI 默认值(诚实标签 + turboacc tcpcca=bbr
- `etc/wifi-band-verify.sh` — log-only 验证脚本
- `etc/rc.local` — 开机兜底(检测 vxlan0,缺失重触发)
- `etc/hotplug.d/iface/30-mss-clamp` — MSS clamp 持久化
- `etc/hotplug.d/iface/40-wg-track` — WG 隧道状态跟踪
- `etc/sysctl.d/99-bridge-nf.conf` — bridge-nf-call-{ip,ip6,ar}tables=1
- `etc/config/{wireless,firewall,dhcp,turboacc,dropbear,network}` — 7 个核心 UCI 配置全部烤入(**network 已脱敏:private_key 与 preshared_key 替换为占位符,使用前必须重新生成 WG 密钥对**)
### 顶层
- `netifd-wireless.sh``/lib/netifd/netifd-wireless.sh` (wifi-scripts 包)
## 刷入流程
1. 克隆 padavanonly fork 的 ImmortalWrt-Actions 源码树到 build server
2. 用本包覆盖对应路径
3. `make -j4``bin/targets/mediatek/filogic/immortalwrt-mediatek-filogic-cudy_tr3000-v1-ubootmod-squashfs-sysupgrade.bin`
4. FIT 镜像雕刻验证:`OFF=$(grep -abo "hsqs" image.bin | head -1 | cut -d: -f1); dd if=image.bin of=root.sqfs bs=1 skip=$OFF; unsquashfs -d sq root.sqfs`
5. U-Boot TFTP/网页刷入(**绝不用 sysupgrade**
+35
View File
@@ -0,0 +1,35 @@
config dnsmasq
option domainneeded '1'
option boguspriv '1'
option filterwin2k '0'
option localise_queries '1'
option rebind_protection '1'
option rebind_localhost '1'
option local '/lan/'
option domain 'lan'
option expandhosts '1'
option nonegcache '0'
option cachesize '1000'
option authoritative '1'
option readethers '1'
option leasefile '/tmp/dhcp.leases'
option resolvfile '/tmp/resolv.conf.d/resolv.conf.auto'
option nonwildcard '1'
option localservice '1'
option ednspacket_max '1232'
option filter_aaaa '0'
option filter_a '0'
config dhcp 'lan'
option interface 'lan'
option ignore '1'
config dhcp 'wan'
option interface 'wan'
option ignore '1'
config odhcpd 'odhcpd'
option maindhcp '0'
option leasefile '/tmp/hosts/odhcpd'
option leasetrigger '/usr/sbin/odhcpd-update'
option loglevel '4'
@@ -0,0 +1,4 @@
config dropbear
option PasswordAuth 'on'
option RootPasswordAuth 'on'
option Port '22'
@@ -0,0 +1,122 @@
config defaults
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option synflood_protect '1'
option fullcone '1'
option flow_offloading '0'
config zone
option name 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
list network 'lan'
list network 'wg0'
config zone
option name 'wan'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'
list network 'wan'
list network 'wan6'
config forwarding
option src 'lan'
option dest 'wan'
config rule
option name 'Allow-DHCP-Renew'
option src 'wan'
option proto 'udp'
option dest_port '68'
option target 'ACCEPT'
option family 'ipv4'
config rule
option name 'Allow-Ping'
option src 'wan'
option proto 'icmp'
option icmp_type 'echo-request'
option family 'ipv4'
option target 'ACCEPT'
config rule
option name 'Allow-IGMP'
option src 'wan'
option proto 'igmp'
option family 'ipv4'
option target 'ACCEPT'
config rule
option name 'Allow-DHCPv6'
option src 'wan'
option proto 'udp'
option dest_port '546'
option family 'ipv6'
option target 'ACCEPT'
config rule
option name 'Allow-MLD'
option src 'wan'
option proto 'icmp'
option src_ip 'fe80::/10'
list icmp_type '130/0'
list icmp_type '131/0'
list icmp_type '132/0'
list icmp_type '143/0'
option family 'ipv6'
option target 'ACCEPT'
config rule
option name 'Allow-ICMPv6-Input'
option src 'wan'
option proto 'icmp'
list icmp_type 'echo-request'
list icmp_type 'echo-reply'
list icmp_type 'destination-unreachable'
list icmp_type 'packet-too-big'
list icmp_type 'time-exceeded'
list icmp_type 'bad-header'
list icmp_type 'unknown-header-type'
list icmp_type 'router-solicitation'
list icmp_type 'neighbour-solicitation'
list icmp_type 'router-advertisement'
list icmp_type 'neighbour-advertisement'
option limit '1000/sec'
option family 'ipv6'
option target 'ACCEPT'
config rule
option name 'Allow-ICMPv6-Forward'
option src 'wan'
option dest '*'
option proto 'icmp'
list icmp_type 'echo-request'
list icmp_type 'echo-reply'
list icmp_type 'destination-unreachable'
list icmp_type 'packet-too-big'
list icmp_type 'time-exceeded'
list icmp_type 'bad-header'
list icmp_type 'unknown-header-type'
option limit '1000/sec'
option family 'ipv6'
option target 'ACCEPT'
config rule
option name 'Allow-IPSec-ESP'
option src 'wan'
option dest 'lan'
option proto 'esp'
option target 'ACCEPT'
config rule
option name 'Allow-ISAKMP'
option src 'wan'
option dest 'lan'
option dest_port '500'
option proto 'udp'
option target 'ACCEPT'
@@ -0,0 +1,49 @@
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config globals 'globals'
option ula_prefix 'fdab:c3fc:a55d::/48'
config device
option name 'br-lan'
option type 'bridge'
list ports 'eth1'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.2'
option netmask '255.255.255.0'
option ip6assign '60'
config interface 'wan'
option device 'eth0'
option proto 'dhcp'
config interface 'wan6'
option device 'eth0'
option proto 'dhcpv6'
config interface 'wg0'
option proto 'wireguard'
option private_key '<REDACTED-YOUR-WG-PRIVATE-KEY'
option listen_port '51820'
list addresses '10.99.0.2/32'
option mtu '1420'
option route_allowed_ips '1'
config wireguard_wg0 'wgpeer'
option public_key 'GPrZqc8t7F3DG/D0ks+GcZQeUYy+3ClFnbSftSqiygQ='
option preshared_key '<REDACTED-YOUR-WG-PSK'
option endpoint_host '10.200.80.93'
option endpoint_port '51820'
option persistent_keepalive '25'
list allowed_ips '10.99.0.1/32'
config route
option interface 'wg0'
option target '10.99.0.1'
option netmask '255.255.255.255'
@@ -0,0 +1,14 @@
config turboacc 'config'
option fastpath 'mediatek_hnat'
option fastpath_mh_eth_hnat '1'
option fastpath_mh_eth_hnat_v6 '1'
option fastpath_mh_eth_hnat_bind_rate '30'
option fastpath_mh_eth_hnat_bind_trap '0'
option fullcone_nat '2'
option hal 'none'
option sw_flow '0'
option hw_flow '0'
option sfe_bridge '0'
option bbr_cca '1'
option dns_caching '0'
option dns_caching_dns '223.5.5.5,223.6.6.6'
@@ -0,0 +1,43 @@
config wifi-device 'MT7981_1_1'
option type 'mtwifi'
option phy 'ra0'
option hwmode '11a'
option band '5g'
option dbdc_main '1'
option channel '36'
option txpower '100'
option htmode 'HE160'
option country 'CN'
option mu_beamformer '1'
option noscan '0'
option serialize '1'
config wifi-iface 'default_MT7981_1_1'
option device 'MT7981_1_1'
option network 'lan'
option mode 'ap'
option ssid 'ImmortalWrt-5G'
option encryption 'none'
option uapsd '0'
config wifi-device 'MT7981_1_2'
option type 'mtwifi'
option phy 'rax0'
option hwmode '11g'
option band '2g'
option dbdc_main '0'
option channel 'auto'
option txpower '100'
option htmode 'HE40'
option country 'CN'
option mu_beamformer '1'
option noscan '1'
option serialize '1'
config wifi-iface 'default_MT7981_1_2'
option device 'MT7981_1_2'
option network 'lan'
option mode 'ap'
option ssid 'ImmortalWrt-2.4G'
option encryption 'none'
option uapsd '0'
@@ -0,0 +1,11 @@
#!/bin/sh
# === MSS clamp persistence (坑 #12 part 3) ===
# 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.
[ "$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
exit 0
@@ -0,0 +1,33 @@
#!/bin/sh
# /etc/hotplug.d/iface/40-wg-track
# 触发: 小路由 WAN iface 起来时 (boot / DHCP renew / link bounce)
# 目的: 跟踪 WG 隧道自动恢复状态,验证坑 #15 是否真根除
# 安装: scp 到 /etc/hotplug.d/iface/40-wg-track + chmod +x
[ "$ACTION" = "ifup" ] || exit 0
case "$INTERFACE" in
wan) ;;
*) exit 0 ;;
esac
WAN_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {print $2; exit}' | cut -d/ -f1)
[ -z "$WAN_IP" ] && exit 0
LOGFILE=/tmp/wg-tunnel-track.log
# 等 30s 让 tunnel 稳定 (handshake init + keepalive 至少跑两轮)
sleep 30
TS=$(date '+%Y-%m-%d %H:%M:%S')
ENDPOINT=$(wg show wg0 2>/dev/null | awk '/endpoint:/ {print $2; exit}')
HANDSHAKE=$(wg show wg0 2>/dev/null | awk -F': ' '/latest handshake/ {print $2}')
TRANSFER=$(wg show wg0 2>/dev/null | awk '/transfer:/ {print $2, $3, $4, $5}')
PING_LOSS=$(ping -c 3 -W 2 10.99.0.1 2>&1 | awk '/packets transmitted/ {print $6}')
STATUS="OK"
[ "$PING_LOSS" != "0%" ] && STATUS="FAIL"
echo "$TS | status=$STATUS | WAN=$WAN_IP | endpoint=$ENDPOINT | handshake=$HANDSHAKE | loss=$PING_LOSS | xfer=$TRANSFER" >> "$LOGFILE"
# Rotate: 只保留最后 200 行
tail -200 "$LOGFILE" > "$LOGFILE.tmp" 2>/dev/null && mv "$LOGFILE.tmp" "$LOGFILE"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
# RPS/RFS tuning for single-RX-queue devices (mtk_soc_eth)
# Distributes softirq across all CPUs; without this, all eth RX runs on CPU0 only.
# Pairs with sysctl tuning in /etc/sysctl.conf (BBR + buffer sizes).
[ -d /sys/class/net/eth1/queues ] && echo 3 > /sys/class/net/eth1/queues/rx-0/rps_cpus
[ -d /sys/class/net/eth0/queues ] && echo 3 > /sys/class/net/eth0/queues/rx-0/rps_cpus
[ -d /sys/class/net/br-lan/queues ] && echo 3 > /sys/class/net/br-lan/queues/rx-0/rps_cpus
[ -d /sys/class/net/ra0/queues ] && echo 3 > /sys/class/net/ra0/queues/rx-0/rps_cpus
[ -d /sys/class/net/rax0/queues ] && echo 3 > /sys/class/net/rax0/queues/rx-0/rps_cpus
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries 2>/dev/null
[ -f /sys/class/net/eth1/queues/rx-0/rps_flow_cnt ] && echo 32768 > /sys/class/net/eth1/queues/rx-0/rps_flow_cnt
[ -f /sys/class/net/br-lan/queues/rx-0/rps_flow_cnt ] && echo 32768 > /sys/class/net/br-lan/queues/rx-0/rps_flow_cnt
# === VXLAN boot fallback (keng #12 part 2) ===
# Hotplug 20-vxlan sometimes misses wg0 ifup at boot (no 10.99 route, no log entry).
# If we got here without vxlan0, manually re-trigger the same script.
[ -e /sys/class/net/vxlan0 ] || ACTION=ifup INTERFACE=wg0 /etc/hotplug.d/net/20-vxlan
# === WiFi band label verifier (v28, log-only) ===
# Aligns UCI wifi labels with physical broadcast once driver is stable.
# Log-only (no uci mutation); see /etc/wifi-band-verify.sh for rationale.
( /etc/wifi-band-verify.sh >/dev/null 2>&1 & )
exit 0
@@ -0,0 +1,3 @@
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
@@ -0,0 +1,66 @@
#!/bin/sh
# 99-custom-config - Apply TR3000-specific defaults on first boot
# Sets LAN IP, WiFi SSID/htmode/uapsd, dnsmasq, IPv6 RA, turboacc BBR
# Runs once (uci-defaults scripts auto-delete after running)
#
# v29 2026-07-22: HONEST labels. UCI idx=1=5g config (driver maps
# band=5g content to the 5G PHY whatever netdev it is this boot).
# idx=2=2g config (maps to 2.4G PHY). ra0/rax0 netdev binding is
# random per boot but physical broadcast always correct (band-content
# invariant). LuCI Overview shows iwinfo truth. Edit forms show UCI
# config. Do NOT use LuCI Save and Apply (triggers wifi reload);
# reboot to apply wireless changes.
. /lib/functions.sh
# ===== LAN IP =====
uci set network.lan.ipaddr="192.168.1.2"
uci set network.lan.netmask="255.255.255.0"
uci commit network
# ===== WiFi SSID + htmode + uapsd (honest labels) =====
uci set wireless.MT7981_1_1.phy="ra0"
uci set wireless.MT7981_1_1.band="5g"
uci set wireless.MT7981_1_1.hwmode="11a"
uci set wireless.MT7981_1_1.htmode="HE160"
uci set wireless.MT7981_1_1.country="CN"
uci set wireless.MT7981_1_1.channel="36"
uci set wireless.MT7981_1_1.dbdc_main="1"
uci set wireless.default_MT7981_1_1.ssid="ImmortalWrt-5G"
uci set wireless.default_MT7981_1_1.uapsd="0"
uci set wireless.default_MT7981_1_1.encryption="none"
uci set wireless.MT7981_1_2.phy="rax0"
uci set wireless.MT7981_1_2.band="2g"
uci set wireless.MT7981_1_2.hwmode="11g"
uci set wireless.MT7981_1_2.htmode="HE40"
uci set wireless.MT7981_1_2.country="CN"
uci set wireless.MT7981_1_2.channel="auto"
uci set wireless.MT7981_1_2.dbdc_main="0"
uci set wireless.default_MT7981_1_2.ssid="ImmortalWrt-2.4G"
uci set wireless.default_MT7981_1_2.uapsd="0"
uci set wireless.default_MT7981_1_2.encryption="none"
uci commit wireless
# ===== dnsmasq enable =====
/etc/init.d/dnsmasq enable
# ===== IPv6 RA disabled =====
uci set dhcp.lan.ra="disabled"
uci set dhcp.lan.dhcpv6="disabled"
uci commit dhcp
# ===== turboacc: BBR as default TCP CCA =====
# /etc/init.d/turboacc force-sets tcp_congestion_control from
# turboacc.config.tcpcca at every boot (S90, after sysctl.d), defaulting
# to cubic and clobbering sysctl.d/12-tcp-bbr.conf. Pin bbr here so the
# 网络加速 page shows BBR active out of the box.
uci set turboacc.config.tcpcca="bbr"
uci commit turboacc
# ===== Firewall: prepare for MSS clamp via hotplug =====
# fw4 reload wipes hotplug MSS clamp rules; the 30-mss-clamp hotplug
# re-adds them on every iface ifup. No static config needed here.
exit 0
@@ -0,0 +1,59 @@
#!/bin/sh
# wifi-band-verify.sh — LOG-ONLY diagnostic. Compares UCI labels against the
# PHYSICAL band each netdev is on and records the verdict to logread.
# NEVER writes UCI: with ra0/rax0 ↔ dat binding random per boot (and band-content
# invariant keeping physical always correct), mutating UCI oscillates across
# wifi reloads. Logging the mismatch is enough — LuCI display patch (wireless.js
# getActiveSSID) makes the UI match iwinfo truth regardless of UCI labels.
# Triggered by rc.local at boot and mtwifi_cfg dispatcher tail.
# Commit-only would still be loop-safe, but useless: detection is non-deterministic
# across reloads, so any commit causes oscillation. Log-only is structurally stable.
LOCK=/tmp/wifi-band-verify.lock
NOW=$(date +%s)
if [ -f "$LOCK" ]; then
LAST=$(cat "$LOCK" 2>/dev/null)
[ -n "$LAST" ] && [ $((NOW - LAST)) -lt 120 ] && exit 0
fi
echo "$NOW" > "$LOCK"
log() { logger -t wifi-band-verify -- "$*"; }
get_ch() { iwinfo "$1" info 2>/dev/null | awk '/Channel:/ {print $4}'; }
get_essid(){ iwinfo "$1" info 2>/dev/null | awk -F"\x22" '/ESSID:/ {print $2}'; }
# Phase 1: wait until both radios report identical channels on two consecutive
# 10s probes (guards against mid-init transient reads; max 18 rounds = 3min).
ra0ch=""; rax0ch=""; stable=0; i=0
while [ $i -lt 18 ]; do
c1=$(get_ch ra0); c2=$(get_ch rax0)
case "$c1" in ""|*[!0-9]*) c1="" ;; esac
case "$c2" in ""|*[!0-9]*) c2="" ;; esac
if [ -n "$c1" ] && [ -n "$c2" ] && [ "$c1" = "$ra0ch" ] && [ "$c2" = "$rax0ch" ]; then
stable=1; break
fi
ra0ch=$c1; rax0ch=$c2
i=$((i+1)); sleep 10
done
[ $stable -eq 1 ] || { log "radios not stable after 3min; skipped"; exit 0; }
# Phase 2: physical band from channel (2g ≤14, 5g ≥36)
band_of() { [ "$1" -le 14 ] && echo 2g || echo 5g; }
ra0band=$(band_of "$ra0ch"); rax0band=$(band_of "$rax0ch")
[ "$ra0band" = "$rax0band" ] && { log "both radios on $ra0band ch$ra0ch/$rax0ch; abnormal"; exit 0; }
# Phase 3: read UCI labels and live ESSIDs, log verdict — NO mutation.
uci -q get wireless.MT7981_1_1 >/dev/null || exit 0
uci -q get wireless.MT7981_1_2 >/dev/null || exit 0
u1=$(uci -q get wireless.MT7981_1_1.band)
u2=$(uci -q get wireless.MT7981_1_2.band)
s1=$(uci -q get wireless.default_MT7981_1_1.ssid)
s2=$(uci -q get wireless.default_MT7981_1_2.ssid)
e1=$(get_essid ra0); e2=$(get_essid rax0)
if [ "$u1" = "$ra0band" ] && [ "$u2" = "$rax0band" ] && [ "$s1" = "$e1" ] && [ "$s2" = "$e2" ]; then
log "consistent: ra0=$ra0band ch$ra0ch [$e1], rax0=$rax0band ch$rax0ch [$e2]; uci=$u1/$u2"
else
log "MISMATCH (no-op): ra0=$ra0band ch$ra0ch [$e1], rax0=$rax0band ch$rax0ch [$e2]; uci=$u1/$u2 ssid=$s1/$s2"
fi
exit 0
+440
View File
@@ -0,0 +1,440 @@
NETIFD_MAIN_DIR="${NETIFD_MAIN_DIR:-/lib/netifd}"
. /usr/share/libubox/jshn.sh
. $NETIFD_MAIN_DIR/utils.sh
CMD_UP=0
CMD_SET_DATA=1
CMD_PROCESS_ADD=2
CMD_PROCESS_KILL_ALL=3
CMD_SET_RETRY=4
add_driver() {
return
}
wireless_setup_vif_failed() {
local error="$1"
echo "Interface $_w_iface setup failed: $error"
}
wireless_setup_failed() {
local error="$1"
echo "Device setup failed: $error"
wireless_set_retry 0
}
prepare_key_wep() {
local key="$1"
local hex=1
echo -n "$key" | grep -qE "[^a-fA-F0-9]" && hex=0
[ "${#key}" -eq 10 -a $hex -eq 1 ] || \
[ "${#key}" -eq 26 -a $hex -eq 1 ] || {
[ "${key:0:2}" = "s:" ] && key="${key#s:}"
key="$(echo -n "$key" | hexdump -ve '1/1 "%02x" ""')"
}
echo "$key"
}
_wdev_prepare_channel() {
json_get_vars channel band hwmode
auto_channel=0
enable_ht=0
htmode=
hwmode="${hwmode##11}"
case "$channel" in
""|0|auto)
channel=0
auto_channel=1
;;
[0-9]*) ;;
*)
wireless_setup_failed "INVALID_CHANNEL"
;;
esac
case "$hwmode" in
a|b|g|ad) ;;
*)
if [ "$channel" -gt 14 ]; then
hwmode=a
else
hwmode=g
fi
;;
esac
case "$band" in
2g) hwmode=g;;
5g|6g) hwmode=a;;
60g) hwmode=ad;;
*)
case "$hwmode" in
*a) band=5g;;
*ad) band=60g;;
*b|*g) band=2g;;
esac
;;
esac
}
_wdev_handler() {
json_load "$data"
json_select config
_wdev_prepare_channel
json_select ..
eval "drv_$1_$2 \"$interface\""
}
_wdev_msg_call() {
local old_cb
json_set_namespace wdev old_cb
"$@"
json_set_namespace $old_cb
}
_wdev_wrapper() {
while [ -n "$1" ]; do
eval "$1() { _wdev_msg_call _$1 \"\$@\"; }"
shift
done
}
_wdev_notify_init() {
local command="$1"; shift;
json_init
json_add_int "command" "$command"
json_add_string "device" "$__netifd_device"
while [ -n "$1" ]; do
local name="$1"; shift
local value="$1"; shift
json_add_string "$name" "$value"
done
json_add_object "data"
}
_wdev_notify() {
local options="$1"
json_close_object
ubus $options call network.wireless notify "$(json_dump)"
}
_wdev_add_variables() {
while [ -n "$1" ]; do
local var="${1%%=*}"
local val="$1"
shift
[[ "$var" = "$val" ]] && continue
val="${val#*=}"
json_add_string "$var" "$val"
done
}
_wireless_add_vif() {
local name="$1"; shift
local ifname="$1"; shift
_wdev_notify_init $CMD_SET_DATA "interface" "$name"
json_add_string "ifname" "$ifname"
_wdev_add_variables "$@"
_wdev_notify
}
_wireless_add_vlan() {
local name="$1"; shift
local ifname="$1"; shift
_wdev_notify_init $CMD_SET_DATA interface "$__cur_interface" "vlan" "$name"
json_add_string "ifname" "$ifname"
_wdev_add_variables "$@"
_wdev_notify
}
_wireless_set_up() {
_wdev_notify_init $CMD_UP
_wdev_notify
}
_wireless_set_data() {
_wdev_notify_init $CMD_SET_DATA
_wdev_add_variables "$@"
_wdev_notify
}
_wireless_add_process() {
_wdev_notify_init $CMD_PROCESS_ADD
local exe="$2"
[ -L "$exe" ] && exe="$(readlink -f "$exe")"
json_add_int pid "$1"
json_add_string exe "$exe"
[ -n "$3" ] && json_add_boolean required 1
[ -n "$4" ] && json_add_boolean keep 1
exe2="$(readlink -f /proc/$1/exe)"
[ "$exe" != "$exe2" ] && echo "WARNING (wireless_add_process): executable path $exe does not match process $1 path ($exe2)"
_wdev_notify
}
_wireless_process_kill_all() {
_wdev_notify_init $CMD_PROCESS_KILL_ALL
[ -n "$1" ] && json_add_int signal "$1"
_wdev_notify
}
_wireless_set_retry() {
_wdev_notify_init $CMD_SET_RETRY
json_add_int retry "$1"
_wdev_notify
}
_wdev_wrapper \
wireless_add_vif \
wireless_add_vlan \
wireless_set_up \
wireless_set_data \
wireless_add_process \
wireless_process_kill_all \
wireless_set_retry \
wireless_vif_parse_encryption() {
json_get_vars encryption
set_default encryption none
auth_mode_open=1
auth_mode_shared=0
auth_type=none
if [ "$hwmode" = "ad" ]; then
wpa_cipher="GCMP"
else
wpa_cipher="CCMP"
fi
case "$encryption" in
*tkip+aes|*tkip+ccmp|*aes+tkip|*ccmp+tkip) wpa_cipher="CCMP TKIP";;
*ccmp256) wpa_cipher="CCMP-256";;
*aes|*ccmp) wpa_cipher="CCMP";;
*tkip) wpa_cipher="TKIP";;
*gcmp256) wpa_cipher="GCMP-256";;
*gcmp) wpa_cipher="GCMP";;
wpa3-192*) wpa_cipher="GCMP-256";;
esac
# 802.11n requires CCMP for WPA
[ "$enable_ht:$wpa_cipher" = "1:TKIP" ] && wpa_cipher="CCMP TKIP"
# Examples:
# psk-mixed/tkip => WPA1+2 PSK, TKIP
# wpa-psk2/tkip+aes => WPA2 PSK, CCMP+TKIP
# wpa2/tkip+aes => WPA2 RADIUS, CCMP+TKIP
case "$encryption" in
wpa2*|wpa3*|*psk2*|psk3*|sae*|owe*)
wpa=2
;;
wpa*mixed*|*psk*mixed*)
wpa=3
;;
wpa*|*psk*)
wpa=1
;;
*)
wpa=0
wpa_cipher=
;;
esac
wpa_pairwise="$wpa_cipher"
case "$encryption" in
owe*)
auth_type=owe
;;
wpa3-192*)
auth_type=eap192
;;
wpa3-mixed*)
auth_type=eap-eap2
;;
wpa3*)
auth_type=eap2
;;
psk3-mixed*|sae-mixed*)
auth_type=psk-sae
;;
psk3*|sae*)
auth_type=sae
;;
*psk*)
auth_type=psk
;;
*wpa*|*8021x*)
auth_type=eap
;;
*wep*)
auth_type=wep
case "$encryption" in
*shared*)
auth_mode_open=0
auth_mode_shared=1
;;
*mixed*)
auth_mode_shared=1
;;
esac
;;
esac
case "$encryption" in
*osen*)
auth_osen=1
;;
esac
}
_wireless_set_brsnoop_isolation() {
local multicast_to_unicast="$1"
local isolate
json_get_vars isolate proxy_arp
[ ${isolate:-0} -gt 0 -o -z "$network_bridge" ] && return
[ ${multicast_to_unicast:-1} -gt 0 -o ${proxy_arp:-0} -gt 0 ] && json_add_boolean isolate 1
}
for_each_interface() {
local _w_types="$1"; shift
local _w_ifaces _w_iface
local _w_type
local _w_found
local multicast_to_unicast
json_get_keys _w_ifaces interfaces
json_select interfaces
for _w_iface in $_w_ifaces; do
json_select "$_w_iface"
if [ -n "$_w_types" ]; then
json_get_var network_bridge bridge
json_get_var network_ifname bridge-ifname
json_get_var multicast_to_unicast multicast_to_unicast
json_select config
_wireless_set_brsnoop_isolation "$multicast_to_unicast"
json_get_var _w_type mode
json_select ..
_w_types=" $_w_types "
[[ "${_w_types%$_w_type*}" = "$_w_types" ]] && {
json_select ..
continue
}
fi
__cur_interface="$_w_iface"
"$@" "$_w_iface"
json_select ..
done
json_select ..
}
for_each_vlan() {
local _w_vlans _w_vlan
json_get_keys _w_vlans vlans
json_select vlans
for _w_vlan in $_w_vlans; do
json_select "$_w_vlan"
json_select config
"$@" "$_w_vlan"
json_select ..
json_select ..
done
json_select ..
}
for_each_station() {
local _w_stas _w_sta
json_get_keys _w_stas stas
json_select stas
for _w_sta in $_w_stas; do
json_select "$_w_sta"
json_select config
"$@" "$_w_sta"
json_select ..
json_select ..
done
json_select ..
}
_wdev_common_device_config() {
config_add_string channel hwmode band htmode noscan
}
_wdev_common_iface_config() {
config_add_string mode ssid encryption 'key:wpakey'
config_add_boolean bridge_isolate
config_add_array tags
}
_wdev_common_vlan_config() {
config_add_string name vid iface
config_add_boolean bridge_isolate
}
_wdev_common_station_config() {
config_add_string mac key vid iface
}
init_wireless_driver() {
name="$1"; shift
cmd="$1"; shift
case "$cmd" in
dump)
add_driver() {
eval "drv_$1_cleanup"
json_init
json_add_string name "$1"
json_add_array device
_wdev_common_device_config
eval "drv_$1_init_device_config"
json_close_array
json_add_array iface
_wdev_common_iface_config
eval "drv_$1_init_iface_config"
json_close_array
json_add_array vlan
_wdev_common_vlan_config
eval "drv_$1_init_vlan_config"
json_close_array
json_add_array station
_wdev_common_station_config
eval "drv_$1_init_station_config"
json_close_array
json_dump
}
;;
setup|teardown)
interface="$1"; shift
data="$1"; shift
export __netifd_device="$interface"
add_driver() {
[[ "$name" == "$1" ]] || return 0
_wdev_handler "$1" "$cmd"
}
;;
esac
}
@@ -0,0 +1,698 @@
#!/usr/bin/lua
--[[
*
* Copyright (C) 2023 hanwckf <hanwckf@vip.qq.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* The Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
]]
local cjson = require "cjson"
local inspect = require "inspect"
local nixio = require "nixio"
local l1parser = require "l1dat_parser"
local datconf = require "datconf"
local utils = require "mtwifi_utils"
local defs = require "mtwifi_defs"
local l1dat = l1parser.load_l1_profile(l1parser.L1_DAT_PATH)
function string:split(sep)
local sep, fields = sep or ":", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
function load_profile(path)
local cfgs = {}
cfgobj = datconf.openfile(path)
if cfgobj then
cfgs = cfgobj:getall()
cfgobj:close()
end
return cfgs
end
function save_profile(cfgs, path)
if not cfgs then
return
end
local datobj = datconf.openfile(path)
datobj:merge(cfgs)
datobj:close(true)
os.execute("sync")
end
function diff_cfg(cfg1, cfg2)
local diff = {}
for k,v in pairs(cfg1) do
if tostring(cfg2[k]) ~= tostring(cfg1[k]) then
diff[k] = {cfg1[k] or "", cfg2[k] or ""}
end
end
for k,v in pairs(cfg2) do
if tostring(cfg2[k]) ~= tostring(cfg1[k]) then
diff[k] = {cfg1[k] or "", cfg2[k] or ""}
end
end
return diff
end
function vif_status(ifname)
local flags = tonumber(utils.read_pipe("cat /sys/class/net/"..ifname.."/flags 2>/dev/null")) or 0
if flags%2 == 1 then
return "up"
end
return "down"
end
function is_dbdc_inited(ifname)
local mac = utils.trim(utils.read_pipe("cat /sys/class/net/"..ifname.."/address 2>/dev/null"))
if mac and mac ~= "00:00:00:00:00:00" then
return true
end
return false
end
function ifup(ifname)
if ifname and #ifname > 0 then
os.execute("ifconfig "..ifname.." up")
end
end
function ifdown(ifname)
if ifname and #ifname > 0 then
os.execute("ifconfig "..ifname.." down")
end
end
function cfg2dat(cfg)
if type(cfg) == type(true) then
if cfg then return 1 else return 0 end
elseif type(cfg) == type(0) or type(cfg) == type("") then
return cfg
end
end
function set_dev_dat(dats, dat, cfg)
if cfg ~= nil then
dats[dat] = cfg2dat(cfg)
end
end
function set_dat(dats, apidx, dat, cfg)
if cfg ~= nil then
dats[dat] = utils.token_set(dats[dat], apidx, cfg2dat(cfg))
end
end
function set_idx_dat(dats, apidx, dat, cfg)
if cfg ~= nil then
dats[dat..tostring(apidx)] = cfg
end
end
function vif_in_dev(vif, dev)
if (vif == dev.main_ifname
or string.match(vif, utils.esc(dev.ext_ifname).."[0-9]+")
or string.match(vif, utils.esc(dev.apcli_ifname).."[0-9]+")
or string.match(vif, utils.esc(dev.wds_ifname).."[0-9]+")
or string.match(vif, utils.esc(dev.mesh_ifname).."[0-9]+")) then
return true
else
return false
end
end
function vif_is_apcli(vif, dev)
if (string.match(vif, utils.esc(dev.apcli_ifname).."[0-9]+")) then
return true
else
return false
end
end
function vif_count(cfg)
local vif_num = 0
local ap_num = 0
local apcli_num = 0
for k,v in pairs(cfg.interfaces) do
vif_num = vif_num + 1
if v.config.mode == "ap" then
ap_num = ap_num + 1
elseif v.config.mode == "sta" then
apcli_num = apcli_num +1
end
end
return vif_num, ap_num, apcli_num
end
function mtwifi_cfg_foreach_vif(cfg, mode, func)
if cfg == nil or func == nil then return end
local vif_num,_,_ = vif_count(cfg)
for idx=0,vif_num-1 do
local v = cfg.interfaces[tostring(idx)]
if v.config.mode and v.config.mode == mode then
func(v)
end
end
end
function __apcli_auto_connect(ifname)
__exec_iwpriv_cmd(ifname, "ApCliEnable", "1")
__exec_iwpriv_cmd(ifname, "ApCliAutoConnect", "3")
end
function mtwifi_up(devname, cfg, restore_vifs, is_dbdc)
nixio.syslog("info", "mtwifi-cfg: start "..devname)
local dev = l1dat.devname_ridx[devname]
local dbdc_main_ifname = defs.dbdc_init_ifname
if not dev then return end
if is_dbdc then
if not is_dbdc_inited(dbdc_main_ifname) then
nixio.syslog("info", "mtwifi-cfg: dbdc card init...")
ifup(dbdc_main_ifname)
utils.sleep(1)
ifdown(dbdc_main_ifname)
end
if restore_vifs and devname ~= cfg.device then
-- restart ap vif
for _,vif in ipairs(restore_vifs) do
if vif_in_dev(vif, dev) and (not vif_is_apcli(vif, dev)) then
nixio.syslog("info", "mtwifi-cfg: restore ap vif: "..vif)
ifup(vif)
end
end
-- restart apcli vif
for _,vif in ipairs(restore_vifs) do
if vif_is_apcli(vif, dev) then
nixio.syslog("info", "mtwifi-cfg: restore apcli vif: "..vif)
ifup(vif)
__apcli_auto_connect(vif)
end
end
return
end
end
local start_vif = function(v)
if (not v.config.disabled) and v.mtwifi_ifname then
local vif = v.mtwifi_ifname
nixio.syslog("info", "mtwifi-cfg: up vif: "..vif)
ifup(vif)
end
end
mtwifi_cfg_foreach_vif(cfg, "ap", start_vif)
mtwifi_cfg_foreach_vif(cfg, "sta", start_vif)
os.execute("startwapp.sh")
end
function mtwifi_down(devname, cfg)
nixio.syslog("info", "mtwifi-cfg: stop "..devname)
local dev = l1dat.devname_ridx[devname]
local reset_vifs = {}
if not dev then return end
for _,vif in ipairs(string.split(utils.read_pipe("ls /sys/class/net"), "\n")) do
if (vif_status(vif) == "up" and vif_in_dev(vif, dev)) then
nixio.syslog("info", "mtwifi-cfg: down vif: "..vif)
ifdown(vif)
if cfg and devname ~= cfg.device then
reset_vifs[#reset_vifs+1] = vif
end
end
end
os.execute("startwapp.sh")
return reset_vifs
end
function mtwifi_reinstall()
nixio.syslog("info", "mtwifi-cfg: unload mtwifi module...")
--os.execute("rmmod mt_whnat")
--os.execute("rmmod mtfwd")
os.execute("rmmod mtk_warp_proxy")
os.execute("rmmod mtk_warp")
--os.execute("rmmod mt7915_mt_wifi")
os.execute("rmmod mt_wifi")
utils.sleep(2)
nixio.syslog("info", "mtwifi-cfg: reload mtwifi module...")
os.execute("modprobe mt_wifi")
--os.execute("modprobe mt7915_mt_wifi")
os.execute("modprobe mtk_warp")
os.execute("modprobe mtk_warp_proxy")
--os.execute("modprobe mtfwd")
--os.execute("modprobe mt_whnat")
end
function __exec_iwpriv_cmd(ifname, key, val)
local cmd = string.format("iwpriv %s set %s=%s", ifname, key, tostring(val))
nixio.syslog("info", "mtwifi-cfg: iwpriv cmd: "..cmd)
os.execute(cmd)
end
function mtwifi_cfg_iwpriv_hook(cfg)
local ap_hook = function(v)
if (not v.config.disabled) and v.mtwifi_ifname then
local vif = v.mtwifi_ifname
for k, j in pairs(defs.iwpriv_ap_cfgs) do
__exec_iwpriv_cmd(vif, j[1], v.config[k] or j[2])
end
end
end
local apcli_hook = function(v)
if (not v.config.disabled) and v.mtwifi_ifname then
local vif = v.mtwifi_ifname
__apcli_auto_connect(vif)
end
end
mtwifi_cfg_foreach_vif(cfg, "ap", ap_hook)
mtwifi_cfg_foreach_vif(cfg, "sta", apcli_hook)
end
function set_chip_cfg(cfg, dats)
local dev_dats
local uci
local dat_key
if not cfg.config.dbdc_main then return end
for k,v in pairs(defs.chip_cfgs) do
uci = k
dat_key = v[1]
-- reset chip cfgs to default
dats[dat_key] = v[2]
-- setup current dev cfg
set_dev_dat(dats, dat_key, cfg.config[uci])
end
-- setup other dev cfg
for k,v in pairs(l1dat.devname_ridx) do
if k ~= cfg.device then
dev_dats = load_profile(v.profile_path)
if dev_dats then
for _,j in pairs(defs.chip_cfgs) do
dat_key = j[1]
set_dev_dat(dev_dats, dat_key, dats[dat_key])
end
save_profile(dev_dats, v.profile_path)
end
end
end
end
function mtwifi_cfg_setup(argv)
local cfg = cjson.decode(argv)
utils.log2file("input = " .. inspect(cfg))
local devname = cfg.device
local dev = l1dat.devname_ridx[devname]
if not dev then return end
local profile = dev.profile_path
local dats = load_profile(profile)
if not dats then
nixio.syslog("err", "mtwifi-cfg: profile ".. profile .. "open failed")
return
end
local dats_orig = load_profile(profile)
local vif_num, bssid_num, apcli_num = vif_count(cfg)
if vif_num == 0 then
nixio.syslog("err", "mtwifi-cfg: not valid vif found!")
return
end
if bssid_num > defs.max_mbssid then
nixio.syslog("err", "mtwifi-cfg: too many vifs!")
return
end
if apcli_num > 1 then
nixio.syslog("err", "mtwifi-cfg: only support one apcli vif!")
end
if bssid_num > 0 then
dats.BssidNum = bssid_num
else
dats.BssidNum = 1
end
-- setup apcli
dats.ApCliEnable = 0
dats.ApCliSsid = ""
dats.ApCliBssid = ""
dats.ApCliAuthMode = ""
dats.ApCliEncrypType = ""
dats.ApCliWPAPSK = ""
dats.ApCliPMFMFPC = 0
dats.ApCliPMFMFPR = 0
dats.ApCliPMFSHA256 = 0
for _,v in pairs(cfg.interfaces) do
if v.config.mode == "sta" then
if not v.config.disabled then
dats.ApCliEnable = 1
end
dats.ApCliSsid = v.config.ssid or ""
dats.ApCliBssid = v.config.bssid or ""
dats.ApCliAuthMode = defs.enc2dat[v.config.encryption][1]
dats.ApCliEncrypType = defs.enc2dat[v.config.encryption][2]
dats.ApCliWPAPSK = v.config.key or ""
if dats.ApCliAuthMode == "OWE" or dats.ApCliAuthMode == "WPA3PSK" then
dats.ApCliPMFMFPC = 1
dats.ApCliPMFMFPR = 1
dats.ApCliPMFSHA256 = 0
end
break
end
end
-- setup chip cfgs
set_chip_cfg(cfg, dats)
-- setup dev cfgs
if cfg.config.twt then
dats.TWTSupport = cfg.config.twt
else
dats.TWTSupport = 0
end
if type(cfg.config.country) == type("") and #cfg.config.country == 2 then
dats.CountryCode = cfg.config.country
if cfg.config.band == "2g" then
dats.CountryRegion = defs.countryRegions[cfg.config.country][1]
elseif cfg.config.band == "5g" then
dats.CountryRegionABand = defs.countryRegions[cfg.config.country][2]
end
end
if cfg.config.channel == "auto" then
dats.AutoChannelSelect = 3
dats.Channel = 0
else
dats.AutoChannelSelect = 0
dats.Channel = cfg.config.channel
end
if cfg.config.htmode == "HT20" or cfg.config.htmode == "VHT20" or cfg.config.htmode == "HE20" then
dats.HT_BW = 0
dats.VHT_BW = 0
elseif cfg.config.htmode == "HT40" or cfg.config.htmode == "VHT40" or cfg.config.htmode == "HE40" then
dats.HT_BW = 1
dats.VHT_BW = 0
if cfg.config.noscan ~= nil and cfg.config.noscan == "1" then
dats.HT_BSSCoexistence = 0
else
dats.HT_BSSCoexistence = 1
end
elseif cfg.config.htmode == "VHT80" or cfg.config.htmode == "HE80" then
dats.HT_BW = 1
dats.VHT_BW = 1
elseif cfg.config.htmode == "VHT160" or cfg.config.htmode == "HE160" then
dats.HT_BW = 1
dats.VHT_BW = 2
end
-- DBDC merge quirk workaround (Cudy TR3000 / MT7981B):
-- ra0/rax0 <-> dat binding is RANDOM per boot (dat.MacAddress
-- writes flip across reboots). The driver applies PHY-correct
-- band/channel/htmode/SSID per netdev regardless of which dat it
-- bound (band-content invariant), so physical broadcast is always
-- correct: ra0 broadcasts 2.4G HE40, rax0 broadcasts 5G HE160 ch36.
-- Only UCI labels vs iwinfo can disagree. mtwifi_cfg writes
-- VHT_Sec80_Channel based on cfg.config.band (not idx), so the 5g
-- profile carries Sec80=155 whether it lands in b0.dat or b1.dat;
-- the 2g profile harmlessly carries VHT_BW=2 + Sec80=155 (Sec80
-- ignored on 2.4G).
if cfg.config.band == "2g" then
dats.HT_BW = 1
dats.VHT_BW = 2
dats.VHT_Sec80_Channel = 155
elseif cfg.config.band == "5g" and (cfg.config.htmode == "HE160" or cfg.config.htmode == "VHT160") then
dats.VHT_Sec80_Channel = 155
end
if cfg.config.txpower and cfg.config.txpower < 100 then
dats.PERCENTAGEenable = 1
dats.TxPower = cfg.config.txpower
else
dats.PERCENTAGEenable = 0
dats.TxPower = 100
end
if cfg.config.mu_beamformer then
dats.ETxBfEnCond = 1
if dats.ApCliEnable == 1 then
dats.MUTxRxEnable = 3
else
dats.MUTxRxEnable = 3
end
dats.ITxBfEn = 1
else
dats.ETxBfEnCond = 0
dats.MUTxRxEnable = 0
dats.ITxBfEn = 0
end
local WirelessMode
if cfg.config.band == "2g" then
if string.sub(cfg.config.htmode,1,2) == "HE" then
WirelessMode = 16 -- PHY_11AX_24G
else
WirelessMode = 9 -- PHY_11BGN_MIXED
end
elseif cfg.config.band == "5g" then
if string.sub(cfg.config.htmode,1,2) == "HE" then
WirelessMode = 17 -- PHY_11AX_5G
else
WirelessMode = 15 -- PHY_11VHT_N_MIXED
end
end
-- reset vif cfgs to default
for k,v in pairs(defs.vif_cfgs) do
dats[k] = ""
for i = 1, bssid_num do
dats[k] = utils.token_set(dats[k], i, v)
end
end
for k,v in pairs(defs.vif_cfgs_idx) do
for i = 1, defs.max_mbssid do
dats[k..tostring(i)] = ""
end
for i = 1, bssid_num do
dats[k..tostring(i)] = v
end
end
for k,v in pairs(defs.vif_acl) do
for i = 0, defs.max_mbssid-1 do
dats[k..tostring(i)] = ""
end
for i = 0, bssid_num-1 do
dats[k..tostring(i)] = v
end
end
-- setup vif cfgs
local apidx = 1
for idx = 0,vif_num-1 do
v = cfg.interfaces[tostring(idx)]
if v.config.mode == "ap" then
set_idx_dat(dats, apidx, "SSID", v.config.ssid)
set_idx_dat(dats, apidx, "WPAPSK", v.config.key or "")
set_dat(dats, apidx, "NoForwarding", 0)
set_dat(dats, apidx, "HideSSID", v.config.hidden)
set_dat(dats, apidx, "WmmCapable", v.config.wmm)
set_dat(dats, apidx, "RRMEnable", v.config.ieee80211k)
set_dat(dats, apidx, "FtSupport", v.config.ieee80211r)
set_dat(dats, apidx, "RekeyInterval", v.config.wpa_group_rekey)
set_dat(dats, apidx, "MuMimoDlEnable",0)
set_dat(dats, apidx, "MuMimoUlEnable",0)
set_dat(dats, apidx, "MuOfdmaDlEnable",v.config.ofdma_dl)
set_dat(dats, apidx, "MuOfdmaUlEnable",v.config.ofdma_ul)
set_dat(dats, apidx, "HT_AMSDU",v.config.amsdu)
set_dat(dats, apidx, "HT_AutoBA",v.config.autoba)
set_dat(dats, apidx, "APSDCapable",v.config.uapsd)
set_dat(dats, apidx, "RTSThreshold", v.config.rts)
set_dat(dats, apidx, "FragThreshold",v.config.frag)
set_dat(dats, apidx, "WirelessMode", WirelessMode)
if v.config.macfilter then
if v.config.macfilter == "allow" then
set_idx_dat(dats, apidx-1, "AccessPolicy", 1)
elseif v.config.macfilter == "deny" then
set_idx_dat(dats, apidx-1, "AccessPolicy", 2)
end
end
if v.config.maclist then
local maclist = ""
for i, v in ipairs(v.config.maclist) do
if i > defs.max_acl_entry then
break
end
if #maclist > 0 then
maclist = maclist .. ";" .. v
else
maclist = v
end
end
set_idx_dat(dats, apidx-1, "AccessControlList", maclist)
end
local authmode = defs.enc2dat[v.config.encryption][1]
set_dat(dats, apidx, "AuthMode", authmode)
set_dat(dats, apidx, "EncrypType", defs.enc2dat[v.config.encryption][2])
if authmode == "OWE" or authmode == "WPA3PSK" then
set_dat(dats, apidx, "PMFMFPC", 1)
set_dat(dats, apidx, "PMFMFPR", 1)
set_dat(dats, apidx, "PMFSHA256", 0)
elseif authmode == "WPA2PSKWPA3PSK" then
set_dat(dats, apidx, "PMFMFPC", 1)
set_dat(dats, apidx, "PMFMFPR", 0)
set_dat(dats, apidx, "PMFSHA256", 0)
end
if not (authmode == "OPEN" or authmode == "OWE") then
set_dat(dats, apidx, "RekeyMethod", "TIME")
end
apidx = apidx + 1
end
end
local reinstall_wifidrv = false
local cfg_diff = diff_cfg(dats_orig, dats)
utils.log2file("diff = " .. inspect(cfg_diff))
save_profile(dats, profile)
for _,v in pairs(defs.reinstall_cfgs) do
if cfg_diff[v] ~= nil then
if utils.exists("/sys/module/mt_wifi") == false then
nixio.syslog("err", "mtwifi-cfg: mtwifi module is build-in, please reboot the device!")
return
end
reinstall_wifidrv = true
end
end
if string.find(profile, "dbdc") then
if reinstall_wifidrv then
for k, _ in pairs(l1dat.devname_ridx) do
mtwifi_down(k)
end
mtwifi_reinstall()
for k, _ in pairs(l1dat.devname_ridx) do
if k == devname then
mtwifi_up(k, cfg, nil, true)
else
os.execute("/sbin/wifi up " .. k) -- TODO: may cause deadlock
end
end
else
local restore_vifs = {}
local restart_other_dbdc_dev = false
if next(cfg_diff) ~= nil then
restart_other_dbdc_dev = true
end
if restart_other_dbdc_dev then
for k, _ in pairs(l1dat.devname_ridx) do
local ret = mtwifi_down(k, cfg)
for _, v in ipairs(ret) do
table.insert(restore_vifs, v)
end
end
if #restore_vifs > 0 then
nixio.syslog("info", "mtwifi-cfg: dbdc restore_vifs: "..inspect.inspect(restore_vifs))
end
for k, _ in pairs(l1dat.devname_ridx) do
mtwifi_up(k, cfg, restore_vifs, true)
end
else
mtwifi_down(devname)
mtwifi_up(devname, cfg, nil, true)
end
end
else
mtwifi_down(devname)
if reinstall_wifidrv then
mtwifi_reinstall()
end
mtwifi_up(devname, cfg)
end
mtwifi_cfg_iwpriv_hook(cfg)
end
function mtwifi_cfg_down(devname)
if devname then
mtwifi_down(devname)
else
for k, _ in pairs(l1dat.devname_ridx) do
mtwifi_down(k)
end
end
end
local action = {
["down"] = function(devname)
mtwifi_cfg_down(devname)
end,
["setup"] = function()
local argv = io.read()
if #argv > 0 then
mtwifi_cfg_setup(argv)
os.execute("( /etc/wifi-band-verify.sh >/dev/null 2>&1 & )")
end
end
}
if #arg == 1 then
action[arg[1]]()
elseif #arg == 2 then
action[arg[1]](arg[2])
end
@@ -0,0 +1,98 @@
#!/bin/sh
#
# Copyright (C) 2023, hanwckf <hanwckf@vip.qq.com>
#
# v21 2026-07-21: HONEST labels (UCI now matches physical reality).
# Driver binds profiles by MacAddress match, NOT by l1profile order:
# b0.dat (MacAddress=2.4G eeprom MAC) -> rax0 netdev (2.4G HW)
# b1.dat (MacAddress=5G eeprom MAC) -> ra0 netdev (5G HW)
# v21 additionally swaps INDEX0_profile_path in l1profile.dat to
# "b1.dat;b0.dat", so l1dat_parser maps: idx=1 (ra0) -> b1.dat,
# idx=2 (rax0) -> b0.dat. Result: each UCI section writes the DAT that
# the driver actually binds to its phy, so UCI band/ssid can be honest:
# idx=1 (ra0, 5G HW): band=5g HE160 ch 36 ssid=ImmortalWrt-5G
# idx=2 (rax0, 2.4G HW): band=2g HE40 ch auto ssid=ImmortalWrt-2.4G
# Kernel DBDC merge quirk (unchanged): VHT_BW / VHT_Sec80_Channel for
# ra0's slot are taken from b0.dat, so mtwifi_cfg still forces
# VHT_BW=2 + VHT_Sec80_Channel=155 into the band==2g profile.
# Validated live on v20 2026-07-21: ra0 2401Mbps HE160 ch36,
# rax0 HE40, DBDC_card0.dat identical to v20 working state.
append DRIVERS "mtwifi"
detect_mtwifi() {
local idx ifname
local band hwmode htmode htbsscoex ssid dbdc_main channel
if [ -d "/sys/module/mt_wifi" ]; then
dev_list="$(l1util list)"
for dev in $dev_list; do
ifname="$(l1util get ${dev} main_ifname)"
idx="$(l1util get ${dev} subidx)"
if [ $idx -eq 1 ]; then
# v29: UCI idx=1 is the 5G config. Driver maps band=5g content
# to the 5G PHY (whichever netdev it is this boot).
dbdc_main="1"
band="5g"
hwmode="11a"
htmode="HE160"
htbsscoex="0"
ssid="ImmortalWrt-5G"
txpower="100"
channel="36"
else
# v29: UCI idx=2 is the 2.4G config.
dbdc_main="0"
band="2g"
hwmode="11g"
htmode="HE40"
htbsscoex="1"
ssid="ImmortalWrt-2.4G"
txpower="100"
channel="auto"
fi
config_get type ${dev//./_} type
if [ "$type" != "mtwifi" ]; then
uci -q batch <<-EOF
set wireless.${dev//./_}=wifi-device
set wireless.${dev//./_}.type=mtwifi
set wireless.${dev//./_}.phy=${ifname}
set wireless.${dev//./_}.hwmode=${hwmode}
set wireless.${dev//./_}.band=${band}
set wireless.${dev//./_}.dbdc_main=${dbdc_main}
set wireless.${dev//./_}.channel=${channel}
set wireless.${dev//./_}.txpower=${txpower}
set wireless.${dev//./_}.htmode=${htmode}
set wireless.${dev//./_}.country=CN
set wireless.${dev//./_}.mu_beamformer=1
set wireless.${dev//./_}.noscan=${htbsscoex}
set wireless.${dev//./_}.serialize=1
set wireless.default_${dev//./_}=wifi-iface
set wireless.default_${dev//./_}.device=${dev//./_}
set wireless.default_${dev//./_}.network=lan
set wireless.default_${dev//./_}.mode=ap
set wireless.default_${dev//./_}.ssid=${ssid}
set wireless.default_${dev//./_}.encryption=none
set wireless.default_${dev//./_}.uapsd=0
EOF
else
uci -q batch <<-EOF
set wireless.${dev//./_}.phy=${ifname}
set wireless.${dev//./_}.hwmode=${hwmode}
set wireless.${dev//./_}.band=${band}
set wireless.${dev//./_}.dbdc_main=${dbdc_main}
set wireless.${dev//./_}.channel=${channel}
set wireless.${dev//./_}.htmode=${htmode}
set wireless.${dev//./_}.country=CN
set wireless.${dev//./_}.mu_beamformer=1
set wireless.${dev//./_}.noscan=${htbsscoex}
set wireless.${dev//./_}.serialize=1
set wireless.default_${dev//./_}.device=${dev//./_}
set wireless.default_${dev//./_}.network=lan
set wireless.default_${dev//./_}.mode=ap
EOF
fi
uci -q commit wireless
done
fi
}
@@ -0,0 +1,194 @@
#!/bin/sh
#
# Copyright (c) 2023, hanwckf <hanwckf@vip.qq.com>
#
. /lib/netifd/netifd-wireless.sh
init_wireless_driver "$@"
LOCK_FILE="/tmp/mtwifi.lock"
MTWIFI_MAX_AP_IDX=15
MTWIFI_MAX_APCLI_IDX=0
MTWIFI_CFG_IFNAME_KEY="mtwifi_ifname"
drv_mtwifi_init_device_config() {
config_add_int txpower beacon_int dtim_period
config_add_boolean mu_beamformer dbdc_main whnat bandsteering
config_add_string country twt
}
drv_mtwifi_init_iface_config() {
config_add_string 'ssid:string' macfilter bssid kicklow assocthres steeringthresold
config_add_boolean wmm hidden isolate ieee80211k ieee80211r
config_add_int wpa_group_rekey frag rts
config_add_array 'maclist:list(macaddr)' 'steeringbssid:list(macaddr)'
config_add_boolean mumimo_dl mumimo_ul ofdma_dl ofdma_ul amsdu autoba uapsd
}
drv_mtwifi_cleanup() {
return
}
mtwifi_vif_ap_config() {
local name="$1"
local ifname=""
local disabled=""
json_select config
json_get_var disabled disabled
json_select ..
[ "$disabled" = "1" ] && return
json_get_var ifname $MTWIFI_CFG_IFNAME_KEY
[ -z "$ifname" ] && return
# v33: record vif for the deferred physical remap (see
# mtwifi_deferred_phys_remap). The ra*/rax* <-> PHY binding is random
# per boot and the second netdev may not exist yet at this point, so
# the remap runs after setup completes.
echo "$name $ifname" >> "/tmp/mtwifi_vifs.$MTWIFI_CUR_DEV"
logger -t "netifd-mtwifi" "add $ifname to vifidx $name"
wireless_add_vif "$name" "$ifname"
}
mtwifi_phys_ifname() {
# Echo the netdev that PHYSICALLY carries the given device's content.
# The section's dat (l1profile profile_path) always programs its own
# PHY, and the dat's MacAddress equals the MAC of whichever netdev
# physically carries it this boot.
local dat mac n
dat=$(l1util get "${1//_/.}" profile_path 2>/dev/null)
[ -f "$dat" ] || return
mac=$(sed -n 's/^MacAddress=//p' "$dat" | head -1)
[ -n "$mac" ] || return
for n in /sys/class/net/ra* /sys/class/net/rax*; do
[ -f "$n/address" ] || continue
if [ "$(cat "$n/address")" = "$mac" ]; then
basename "$n"
return
fi
done
}
mtwifi_deferred_phys_remap() {
# Re-notify netifd with the physical ifname for each vif of device $1
# once the netdev shows up, so ubus status (and everything derived
# from it: LuCI title, freqlist, channel dropdown, bridge membership)
# matches reality. UCI writes stay section-based and unaffected.
local dev="$1" vifs="/tmp/mtwifi_vifs.$1"
local name static_if phys tries=0
[ -s "$vifs" ] || return
while [ $tries -lt 45 ]; do
phys=$(mtwifi_phys_ifname "$dev")
[ -n "$phys" ] && break
sleep 1
tries=$((tries+1))
done
[ -n "$phys" ] || return
while read -r name static_if; do
[ -n "$name" ] || continue
[ "$phys" = "$static_if" ] && continue
logger -t "netifd-mtwifi" "deferred physical remap: $dev $static_if -> $phys"
__netifd_device="$dev"
wireless_add_vif "$name" "$phys"
done < "$vifs"
rm -f "$vifs"
}
mtwifi_vif_sta_config() {
local name="$1"
local ifname=""
local disabled=""
json_select config
json_get_var disabled disabled
json_select ..
[ "$disabled" = "1" ] && return
json_get_var ifname $MTWIFI_CFG_IFNAME_KEY
if [ -n "$ifname" ]; then
logger -t "netifd-mtwifi" "add $ifname to vifidx $name"
wireless_add_vif "$name" "$ifname"
fi
}
mtwifi_vif_ap_set_data() {
local ifname=""
if [ ! $AP_IDX -gt $MTWIFI_MAX_AP_IDX ]; then
ifname="${MTWIFI_AP_IF_PREFIX}${AP_IDX}"
AP_IDX=$((AP_IDX+1))
fi
json_add_string "$MTWIFI_CFG_IFNAME_KEY" "$ifname"
}
mtwifi_vif_sta_set_data() {
local ifname=""
if [ ! $APCLI_IDX -gt $MTWIFI_MAX_APCLI_IDX ]; then
ifname="${MTWIFI_APCLI_IF_PREFIX}${APCLI_IDX}"
APCLI_IDX=$((APCLI_IDX+1))
fi
json_add_string "$MTWIFI_CFG_IFNAME_KEY" "$ifname"
}
drv_mtwifi_setup() {
ubus -t 120 wait_for network.interface.lan
local dev="$1"
# UCI section names use underscores (e.g., MT7981_1_1) but l1util/l1dat
# expect dotted names (e.g., MT7981.1.1). Convert before any lookup.
local dev_dot="${dev//_/.}"
json_add_string device "$dev_dot"
MTWIFI_CUR_DEV="$dev"
rm -f "/tmp/mtwifi_vifs.$dev"
lock $LOCK_FILE
logger -t "netifd-mtwifi" "up: $dev ($dev_dot)"
MTWIFI_AP_IF_PREFIX="$(l1util get $dev_dot ext_ifname)"
MTWIFI_APCLI_IF_PREFIX="$(l1util get $dev_dot apcli_ifname)"
AP_IDX=0
for_each_interface ap mtwifi_vif_ap_set_data
APCLI_IDX=0
for_each_interface sta mtwifi_vif_sta_set_data
json_dump | /sbin/mtwifi_cfg setup
for_each_interface ap mtwifi_vif_ap_config
for_each_interface sta mtwifi_vif_sta_config
wireless_set_up
lock -u $LOCK_FILE
( mtwifi_deferred_phys_remap "$dev" ) >/dev/null 2>&1 </dev/null &
}
drv_mtwifi_teardown() {
local dev="$1"
local dev_dot="${dev//_/.}"
lock $LOCK_FILE
logger -t "netifd-mtwifi" "down: $dev ($dev_dot)"
rm -f "/tmp/mtwifi_vifs.$dev"
/sbin/mtwifi_cfg down "$dev_dot"
lock -u $LOCK_FILE
}
add_driver mtwifi
@@ -0,0 +1,16 @@
Default
INDEX0=MT7981
INDEX0_profile_path=/etc/wireless/mediatek/mt7981.dbdc.b0.dat;/etc/wireless/mediatek/mt7981.dbdc.b1.dat
INDEX0_init_script=/lib/wifi/mtwifi.lua;/lib/wifi/mtwifi.lua
INDEX0_init_compatible=mtwifi;mtwifi
INDEX0_EEPROM_offset=0x0
INDEX0_EEPROM_size=0x5000
INDEX0_EEPROM_name=e2p
INDEX0_main_ifname=ra0;rax0
INDEX0_ext_ifname=ra;rax
INDEX0_wds_ifname=wds;wdsx
INDEX0_apcli_ifname=apcli;apclix
INDEX0_mesh_ifname=mesh;meshx
INDEX0_nvram_zone=dev1;dev2
INDEX0_single_sku_path=/etc/wireless/mediatek/mt7981-sku.dat
INDEX0_bf_sku_path=/etc/wireless/mediatek/mt7981-sku-bf.dat