#!/bin/sh . /lib/functions.sh . /usr/share/libubox/jshn.sh STATE_CONFIG="onekeyap" STATE_SECTION="main" BACKUP_DIR="/etc/onekeyap" NETWORK_BACKUP="${BACKUP_DIR}/network.backup" DHCP_BACKUP="${BACKUP_DIR}/dhcp.backup" RELOAD_LOG="/tmp/onekeyap-reload.log" append_unique() { local var="$1" local item current shift eval "current=\${$var}" for item in "$@"; do [ -n "$item" ] || continue case " $current " in *" $item "*) ;; *) current="${current:+$current }$item" ;; esac done eval "$var=\$current" } ensure_state() { if ! uci -q get "${STATE_CONFIG}.${STATE_SECTION}" >/dev/null; then uci batch <<-EOF >/dev/null set ${STATE_CONFIG}.${STATE_SECTION}=settings set ${STATE_CONFIG}.${STATE_SECTION}.mode='router' set ${STATE_CONFIG}.${STATE_SECTION}.fallback_ip='10.0.0.1' set ${STATE_CONFIG}.${STATE_SECTION}.fallback_mask='255.255.255.0' set ${STATE_CONFIG}.${STATE_SECTION}.fallback_cidr='24' EOF uci commit "${STATE_CONFIG}" >/dev/null fi } state_get() { uci -q get "${STATE_CONFIG}.${STATE_SECTION}.$1" } state_set() { uci set "${STATE_CONFIG}.${STATE_SECTION}.$1=$2" } state_set_list() { local option="$1" local item shift uci -q delete "${STATE_CONFIG}.${STATE_SECTION}.${option}" for item in "$@"; do [ -n "$item" ] || continue uci add_list "${STATE_CONFIG}.${STATE_SECTION}.${option}=${item}" done } reply_json() { local code="${1:-0}" local mode="${2}" local message="${3}" json_init json_add_int code "${code}" [ -n "${mode}" ] && json_add_string mode "${mode}" [ -n "${message}" ] && json_add_string message "${message}" json_dump } json_add_words() { local name="$1" local item shift json_add_array "${name}" for item in "$@"; do [ -n "${item}" ] || continue json_add_string "" "${item}" done json_close_array } collect_ipv4_addrs() { local dev="$1" [ -n "${dev}" ] || return 0 ip -o -4 addr show dev "${dev}" 2>/dev/null | awk '{ print $4 }' } find_device_section_match="" find_device_section_cb() { local section="$1" local target="$2" local name config_get name "${section}" name [ "${name}" = "${target}" ] && find_device_section_match="${section}" } find_device_section() { local target="$1" find_device_section_match="" config_load network config_foreach find_device_section_cb device "${target}" printf '%s\n' "${find_device_section_match}" } collect_interface_devices() { local iface="$1" local direct dev section ports type direct="$(uci -q get "network.${iface}.device")" [ -n "${direct}" ] || direct="$(uci -q get "network.${iface}.device")" [ -n "${direct}" ] || return 0 for dev in ${direct}; do case "${dev}" in @*) continue ;; esac section="$(find_device_section "${dev}")" type="$(uci -q get "network.${section}.type")" if [ -n "${section}" ] && [ "${type}" = "bridge" ]; then ports="$(uci -q get "network.${section}.ports")" if [ -n "${ports}" ]; then printf '%s\n' "${ports}" else printf '%s\n' "${dev}" fi else printf '%s\n' "${dev}" fi done } zone_networks="" find_zone_networks_cb() { local section="$1" local target="$2" local name network config_get name "${section}" name [ "${name}" = "${target}" ] || return 0 config_get network "${section}" network zone_networks="${network}" } get_wan_networks() { local iface result="" zone_networks="" config_load firewall config_foreach find_zone_networks_cb zone "wan" if [ -n "${zone_networks}" ]; then for iface in ${zone_networks}; do uci -q get "network.${iface}" >/dev/null || continue append_unique result "${iface}" done fi if [ -z "${result}" ]; then for iface in wan wan6; do uci -q get "network.${iface}" >/dev/null || continue append_unique result "${iface}" done fi printf '%s\n' "${result}" } get_lan_ports() { local result="" item for item in $(collect_interface_devices lan); do append_unique result "${item}" done printf '%s\n' "${result}" } get_wan_ports() { local iface result="" item for iface in $(get_wan_networks); do for item in $(collect_interface_devices "${iface}"); do append_unique result "${item}" done done printf '%s\n' "${result}" } ensure_lan_bridge() { local current_lan_device bridge_section final_device port current_lan_device="$(uci -q get network.lan.device)" final_device="${current_lan_device}" if [ -n "${current_lan_device}" ]; then bridge_section="$(find_device_section "${current_lan_device}")" fi if [ -n "${bridge_section}" ] && [ "$(uci -q get "network.${bridge_section}.type")" = "bridge" ]; then final_device="${current_lan_device}" else final_device="br-lan" bridge_section="$(find_device_section "${final_device}")" [ -n "${bridge_section}" ] || bridge_section="$(uci add network device)" fi uci set "network.${bridge_section}=device" uci set "network.${bridge_section}.name=${final_device}" uci set "network.${bridge_section}.type=bridge" uci -q delete "network.${bridge_section}.ports" for port in "$@"; do [ -n "${port}" ] || continue uci add_list "network.${bridge_section}.ports=${port}" done uci set "network.lan.device=${final_device}" uci -q delete network.lan.device uci -q delete network.lan.type printf '%s\n' "${final_device}" } schedule_reload() { local target_mode="$1" ( sleep 1 /etc/init.d/network restart /etc/init.d/dnsmasq restart /etc/init.d/odhcpd restart /etc/init.d/firewall restart [ "${target_mode}" = "ap" ] && /usr/libexec/onekeyap refresh-fallback ) >"${RELOAD_LOG}" 2>&1 & } get_lan_runtime_device() { local json dev json="$(ubus call network.interface.lan status 2>/dev/null)" if [ -n "${json}" ]; then json_load "${json}" json_get_var dev l3_device [ -n "${dev}" ] || json_get_var dev device json_cleanup fi [ -n "${dev}" ] || dev="$(uci -q get network.lan.device)" [ -n "${dev}" ] || dev="$(state_get ap_lan_device)" printf '%s\n' "${dev}" } refresh_fallback() { local mode dev fallback_ip fallback_cidr ensure_state mode="$(state_get mode)" [ "${mode}" = "ap" ] || exit 0 dev="$(get_lan_runtime_device)" fallback_ip="$(state_get fallback_ip)" fallback_cidr="$(state_get fallback_cidr)" [ -n "${dev}" ] || exit 0 [ -n "${fallback_ip}" ] || exit 0 [ -n "${fallback_cidr}" ] || fallback_cidr="24" ip -o -4 addr show dev "${dev}" 2>/dev/null | grep -q " ${fallback_ip}/${fallback_cidr} " && exit 0 ip addr add "${fallback_ip}/${fallback_cidr}" dev "${dev}" 2>/dev/null } remove_current_fallback() { local dev fallback_ip fallback_cidr dev="$(get_lan_runtime_device)" fallback_ip="$(state_get fallback_ip)" fallback_cidr="$(state_get fallback_cidr)" [ -n "${dev}" ] || return 0 [ -n "${fallback_ip}" ] || return 0 [ -n "${fallback_cidr}" ] || fallback_cidr="24" ip addr del "${fallback_ip}/${fallback_cidr}" dev "${dev}" 2>/dev/null } status() { local mode button_text fallback_ip fallback_cidr local current_lan_ports current_lan_ipv4 local saved_lan_ports saved_wan_ports local lan_runtime_dev item current_lan_ports_words="" current_lan_ipv4_words="" ensure_state mode="$(state_get mode)" [ -n "${mode}" ] || mode="router" if [ "${mode}" = "ap" ]; then button_text="开启路由器模式" else button_text="开启 AP 模式" fi fallback_ip="$(state_get fallback_ip)" fallback_cidr="$(state_get fallback_cidr)" [ -n "${fallback_ip}" ] || fallback_ip="10.0.0.1" [ -n "${fallback_cidr}" ] || fallback_cidr="24" current_lan_ports="$(get_lan_ports)" saved_lan_ports="$(state_get last_lan_ports)" saved_wan_ports="$(state_get last_wan_ports)" lan_runtime_dev="$(get_lan_runtime_device)" for item in ${current_lan_ports}; do append_unique current_lan_ports_words "${item}" done for item in $(collect_ipv4_addrs "${lan_runtime_dev}"); do append_unique current_lan_ipv4_words "${item}" done json_init json_add_int code 0 json_add_string mode "${mode}" json_add_string button_text "${button_text}" json_add_string fallback_ip "${fallback_ip}/${fallback_cidr}" json_add_words current_lan_ports ${current_lan_ports_words} json_add_words current_lan_ipv4 ${current_lan_ipv4_words} json_add_words saved_lan_ports ${saved_lan_ports} json_add_words saved_wan_ports ${saved_wan_ports} json_dump } enable_ap() { local mode lan_ports wan_ports all_ports="" lan_device iface item ensure_state mode="$(state_get mode)" if [ "${mode}" = "ap" ]; then reply_json 0 "ap" "当前已经是 AP 模式。" return 0 fi mkdir -p "${BACKUP_DIR}" || { reply_json 1 "router" "无法创建备份目录。" return 0 } cp /etc/config/network "${NETWORK_BACKUP}" || { reply_json 1 "router" "备份 network 配置失败。" return 0 } cp /etc/config/dhcp "${DHCP_BACKUP}" || { reply_json 1 "router" "备份 dhcp 配置失败。" return 0 } lan_ports="$(get_lan_ports)" wan_ports="$(get_wan_ports)" for item in ${lan_ports} ${wan_ports}; do append_unique all_ports "${item}" done if [ -z "${all_ports}" ]; then reply_json 1 "router" "未检测到可并入 LAN 的接口。" return 0 fi lan_device="$(ensure_lan_bridge ${all_ports})" uci set network.lan.proto='dhcp' uci -q delete network.lan.ipaddr uci -q delete network.lan.netmask uci -q delete network.lan.gateway uci -q delete network.lan.broadcast uci -q delete network.lan.ip6assign uci -q delete network.lan.ip6hint uci -q delete network.lan.ip6ifaceid uci -q delete network.lan.ip6class for iface in $(get_wan_networks); do uci -q get "network.${iface}" >/dev/null || continue uci set "network.${iface}.proto=none" uci set "network.${iface}.auto=0" uci -q delete "network.${iface}.device" uci -q delete "network.${iface}.device" done if ! uci -q get dhcp.lan >/dev/null; then uci set dhcp.lan=dhcp uci set dhcp.lan.interface='lan' fi uci set dhcp.lan.ignore='1' uci set dhcp.lan.dhcpv4='disabled' uci set dhcp.lan.dhcpv6='disabled' uci set dhcp.lan.ra='disabled' uci set dhcp.lan.ndp='disabled' state_set mode 'ap' state_set ap_lan_device "${lan_device}" state_set_list last_lan_ports ${lan_ports} state_set_list last_wan_ports ${wan_ports} state_set_list last_wan_networks $(get_wan_networks) uci commit network >/dev/null uci commit dhcp >/dev/null uci commit "${STATE_CONFIG}" >/dev/null schedule_reload ap reply_json 0 "ap" "AP 模式切换请求已提交,WAN 口会并入 LAN,并保留 10.0.0.1/24 作为保底地址。" } enable_router() { local mode ensure_state mode="$(state_get mode)" if [ "${mode}" != "ap" ]; then reply_json 0 "router" "当前已经是路由器模式。" return 0 fi if [ ! -f "${NETWORK_BACKUP}" ] || [ ! -f "${DHCP_BACKUP}" ]; then reply_json 1 "ap" "未找到原始配置备份,无法恢复路由器模式。" return 0 fi remove_current_fallback cp "${NETWORK_BACKUP}" /etc/config/network || { reply_json 1 "ap" "恢复 network 配置失败。" return 0 } cp "${DHCP_BACKUP}" /etc/config/dhcp || { reply_json 1 "ap" "恢复 dhcp 配置失败。" return 0 } state_set mode 'router' uci commit "${STATE_CONFIG}" >/dev/null schedule_reload router reply_json 0 "router" "路由器模式恢复请求已提交,原始 WAN/LAN 配置会被还原。" } case "$1" in status) status ;; enable-ap) enable_ap ;; enable-router) enable_router ;; refresh-fallback) refresh_fallback ;; *) reply_json 1 "" "用法: $0 {status|enable-ap|enable-router|refresh-fallback}" ;; esac