🚀 运行: ...
IP: ...
ISP: 检测中...

【神器发布】ImmortalWrt/OpenWrt 局域网设备管控脚本 V9 旗舰版 (支持批量操作/防绕过)

你是否遇到过以下烦恼?

  • 想给孩子的手机断网,却发现他依然能看 YouTube/刷 TikTok(因为流量走了 Passwall/OpenClash 代理,绕过了普通防火墙规则)?
  • 路由器后台的访问控制功能太弱,操作繁琐,还得一个个点击应用?
  • 市面上的脚本界面简陋,功能单一,甚至拉黑了还能继续通过现有连接下载?

今天为大家分享一个专为 ImmortalWrt 和 FW4 (nftables) 打造的终端级设备管控神器 —— DevCtrl V9 全能旗舰版

它不仅界面精美、操作丝滑,更重要的是它采用了内核级优先阻断技术,专治各种“关不掉”的顽固流量。

✨ V9 旗舰版核心特性

  1. 🛡️ 天网模式 (Priority -450):这是本脚本最核心的黑科技。它将拦截关卡设在 Prerouting 链,且优先级高达 -450。这意味着数据包刚接触路由器网卡就会被拦截,比 Passwall、OpenClash 等科学插件的优先级更高。彻底解决“拉黑了还能看外网视频”的痛点。
  2. 🚀 批量极速操作:支持批量指令!想同时拉黑第 1、3、5 台设备?只需输入 1 3 5 回车即可,无需重复操作。
  3. 🎨 现代化 UI 界面:重构的竖向操作面板,配备状态仪表盘。在线设备亮绿灯 🟢,离线设备显灰色 ⚫,状态一目了然。
  4. ⚡ 实时连接熔断:拉黑瞬间自动执行 conntrack flush,强制切断该设备当前正在进行的视频流或游戏连接,秒级生效。
  5. 🔰 安全防误触:智能识别当前 SSH 管理终端 IP,禁止拉黑自己,防止把自己关在门外。

📸 界面预览


🛠️ 安装教程

使用 SSH 连接到您的路由器(工具推荐:Putty, Xshell, 或 Windows 终端),复制下方所有代码,在终端内粘贴并回车即可一键安装。

系统要求:ImmortalWrt / OpenWrt 21.02+ (基于 FW4/nftables)

Bash

cat << 'INSTALL_EOF' > /tmp/install_devctrl_v9_fix.sh
#!/bin/bash

# ================= 变量定义 =================
APP_PATH="/root/devctrl"
PY_FILE="$APP_PATH/core.py"
MGR_FILE="$APP_PATH/manager.sh"

BAN_LIST="/etc/devctrl_blacklist.txt"
WHT_LIST="/etc/devctrl_whitelist.txt"

NFT_CONFIG="/tmp/devctrl_rules.nft"
LINK_BIN="/usr/bin/dev"
mkdir -p "$APP_PATH"

echo "-----------------------------------------------"
echo "正在安装 设备管控 V9 (BusyBox修正版 + 白/黑名单编辑 + Nano)..."
echo "特性: 修复日志grep报错 / 批量拉黑 / UI微调 / 编辑白名单&黑名单(强制nano)"
echo "-----------------------------------------------"

# 1. 依赖安装方案1强制安装 nano
opkg update 2>/dev/null
opkg install python3 python3-base bash conntrack nano 2>/dev/null

# 预创建名单文件
touch "$BAN_LIST" "$WHT_LIST"
chmod 600 "$BAN_LIST" "$WHT_LIST" 2>/dev/null

# 2. 部署 V9 核心引擎
cat << 'EOF' > "$PY_FILE"
import subprocess, os, sys, time, re

BLACKLIST_FILE = "/etc/devctrl_blacklist.txt"
WHITELIST_FILE = "/etc/devctrl_whitelist.txt"
NFT_CONFIG_FILE = "/tmp/devctrl_rules.nft"

MAC_RE = re.compile(r"^[0-9A-F]{2}(:[0-9A-F]{2}){5}$")

# === 颜色与图标定义 ===
C_RESET  = "\033[0m"
C_BOLD   = "\033[1m"
C_RED    = "\033[38;5;196m"
C_GREEN  = "\033[38;5;46m"
C_YELLOW = "\033[38;5;226m"
C_BLUE   = "\033[38;5;39m"
C_PURPLE = "\033[38;5;135m"
C_CYAN   = "\033[38;5;51m"
C_WHITE  = "\033[38;5;255m"
C_GRAY   = "\033[38;5;243m"
C_DARK   = "\033[38;5;237m"

ICON_ON  = f"{C_GREEN}🟢{C_RESET}"
ICON_OFF = f"{C_GRAY}⚫{C_RESET}"
ICON_BAN = f"{C_RED}🚫{C_RESET}"
ICON_OK  = f"{C_GREEN}✅{C_RESET}"
ICON_PAUSE = f"{C_YELLOW}⏸️ {C_RESET}"
ICON_ME  = f"{C_PURPLE}💻{C_RESET}"
ICON_WHT = f"{C_CYAN}⭐{C_RESET}"

def run_cmd(cmd):
    try:
        subprocess.run(cmd, shell=True, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except:
        pass

def run_cmd_output(cmd):
    try:
        res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        return res.stdout.strip()
    except:
        return ""

def _load_set(path):
    if not os.path.exists(path):
        return set()
    out = set()
    try:
        with open(path, "r") as f:
            for line in f:
                s = line.strip()
                if not s or s.startswith("#"):
                    continue
                s = s.upper()
                if MAC_RE.match(s):
                    out.add(s)
    except:
        pass
    return out

def _save_set(path, mac_set):
    try:
        with open(path, "w") as f:
            for mac in sorted(mac_set):
                f.write(mac + "\n")
    except:
        pass

def load_blacklist():
    return _load_set(BLACKLIST_FILE)

def save_blacklist(mac_set):
    _save_set(BLACKLIST_FILE, mac_set)

def load_whitelist():
    return _load_set(WHITELIST_FILE)

def save_whitelist(mac_set):
    _save_set(WHITELIST_FILE, mac_set)

def is_firewall_active():
    res = run_cmd_output("nft list table inet devctrl_v6")
    return "enforce_block_early" in res

def apply_firewall():
    blacklist = load_blacklist()
    whitelist = load_whitelist()

    # 白名单优先即使同时在黑名单也视为白名单放行
    blacklist = set(m for m in blacklist if m not in whitelist)

    w_elements = ""
    b_elements = ""
    if whitelist:
        w_elements = ", ".join([f"{mac}" for mac in sorted(whitelist)])
        w_elements = f"elements = {{ {w_elements} }}"
    if blacklist:
        b_elements = ", ".join([f"{mac}" for mac in sorted(blacklist)])
        b_elements = f"elements = {{ {b_elements} }}"

    nft_content = f"""
table inet devctrl_v6 {{
    set whitelisted_macs {{
        type ether_addr
        flags interval
        {w_elements}
    }}

    set blacklisted_macs {{
        type ether_addr
        flags interval
        {b_elements}
    }}

    chain enforce_block_early {{
        type filter hook prerouting priority -450; policy accept;

        # 白名单优先放行
        ether saddr @whitelisted_macs accept

        # 黑名单拦截
        ether saddr @blacklisted_macs log prefix "BAN_ALL: " drop
    }}
}}
"""
    with open(NFT_CONFIG_FILE, "w") as f:
        f.write(nft_content)

    run_cmd("nft delete table inet devctrl_v4")
    run_cmd("nft delete table inet devctrl_v6")
    run_cmd(f"nft -f {NFT_CONFIG_FILE}")

def stop_firewall():
    run_cmd("nft delete table inet devctrl_v6")

def flush_conntrack(target_mac, dev_list):
    for dev in dev_list:
        if dev["mac"] == target_mac:
            ip = dev.get("ip")
            if ip:
                run_cmd(f"conntrack -D -s {ip}")
                run_cmd(f"conntrack -D -d {ip}")

def get_current_ssh_ip():
    val = run_cmd_output("echo $SSH_CLIENT")
    return val.split()[0] if val else "未知"

def get_devices():
    leases = {}
    try:
        with open("/tmp/dhcp.leases", "r") as f:
            for line in f:
                parts = line.split()
                if len(parts) >= 4:
                    leases[parts[1].upper()] = {"ip": parts[2], "name": parts[3]}
    except:
        pass

    devices = []
    res = run_cmd_output("ip -4 neigh show | grep -v FAILED")
    seen_macs = set()
    for line in res.splitlines():
        parts = line.split()
        if len(parts) < 5:
            continue
        try:
            mac_idx = parts.index("lladdr") + 1
            mac = parts[mac_idx].upper()
        except:
            continue
        if len(mac) != 17:
            continue

        seen_macs.add(mac)
        name = leases.get(mac, {}).get("name", "*")
        devices.append({"mac": mac, "ip": parts[0], "name": name, "online": True})

    for mac, info in leases.items():
        if mac not in seen_macs:
            devices.append({"mac": mac, "ip": info["ip"], "name": info["name"], "online": False})

    return devices

# === V9 新版界面渲染 ===
def show_menu():
    if not is_firewall_active():
        apply_firewall()

    my_ip = get_current_ssh_ip()
    dev_list_temp = get_devices()
    my_mac = next((d["mac"] for d in dev_list_temp if d["ip"] == my_ip), "")

    while True:
        whitelist = load_whitelist()
        blacklist = load_blacklist()
        active = is_firewall_active()

        os.system("clear")

        # === 顶部仪表盘 ===
        print(f"{C_BLUE}╭{'─'*62}╮{C_RESET}")
        print(f"{C_BLUE}│{C_RESET} {C_BOLD}{C_CYAN}🛡️ 设备管控终端 V9{C_RESET} {C_GRAY}(BusyBox版){C_RESET}".ljust(74) + f"{C_BLUE}│{C_RESET}")

        status_txt = f"{C_GREEN}保护中{C_RESET}" if active else f"{C_RED}已停止{C_RESET}"
        ban_txt = f"{C_RED}{len(blacklist)}{C_RESET}" if blacklist else f"{C_GREEN}0{C_RESET}"
        wht_txt = f"{C_CYAN}{len(whitelist)}{C_RESET}" if whitelist else f"{C_GREEN}0{C_RESET}"
        print(f"{C_BLUE}│{C_RESET} 引擎: [{status_txt}] | 拉黑: [{ban_txt}] | 白名单: [{wht_txt}] | 终端: {C_YELLOW}{my_ip}{C_RESET}".ljust(102) + f"{C_BLUE}│{C_RESET}")
        print(f"{C_BLUE}╰{'─'*62}╯{C_RESET}")

        # === 设备列表 ===
        print(f"{C_DARK} {'ID':<4} {'状态':<9} {'IP地址':<16} {'MAC地址':<18} {'设备名称'}{C_RESET}")
        print(f"{C_DARK} {'─'*62}{C_RESET}")

        dev_list = get_devices()
        dev_list.sort(key=lambda x: (not x["online"], x["ip"]))

        for idx, dev in enumerate(dev_list):
            mac = dev["mac"]

            if mac in whitelist:
                status = f"{ICON_WHT} {C_CYAN}白名单{C_RESET}"
            elif mac in blacklist:
                status = f"{ICON_BAN} {C_RED}拦截{C_RESET}" if active else f"{ICON_PAUSE}{C_YELLOW}待拦{C_RESET}"
            else:
                status = f"{ICON_OK} {C_GREEN}允许{C_RESET}"

            online_icon = ICON_ON if dev["online"] else ICON_OFF
            is_me_icon = ICON_ME if mac == my_mac else ""

            name = dev["name"] if dev["name"] != "*" else f"{C_GRAY}未知设备{C_RESET}"
            name = name[:18]

            row_color = C_WHITE if dev["online"] else C_GRAY
            print(f" {C_BOLD}{idx+1:<4}{C_RESET} {status}  {row_color}{dev['ip']:<16} {mac:<18} {online_icon} {name} {is_me_icon}{C_RESET}")

        # === 底部竖向操作面板 ===
        print(f"\n{C_BLUE}╭─ 功能面板 {'─'*48}╮{C_RESET}")
        print(f"{C_BLUE}│{C_RESET}  {C_BOLD}{C_CYAN}[序号]{C_RESET} 批量切换(如1 3) {C_DARK}|{C_RESET}  {C_BOLD}{C_GREEN}[S]{C_RESET} 重载引擎      {C_BLUE}│{C_RESET}")
        print(f"{C_BLUE}│{C_RESET}  {C_BOLD}{C_YELLOW}[R]{C_RESET}    刷新列表      {C_DARK}|{C_RESET}  {C_BOLD}{C_RED}[P]{C_RESET} 暂停防护      {C_BLUE}│{C_RESET}")
        print(f"{C_BLUE}│{C_RESET}  {C_BOLD}{C_BLUE}[L]{C_RESET}    实时日志      {C_DARK}|{C_RESET}  {C_BOLD}{C_RED}[U]{C_RESET} 卸载脚本      {C_BLUE}│{C_RESET}")
        print(f"{C_BLUE}│{C_RESET}  {C_BOLD}{C_CYAN}[W]{C_RESET}    编辑白名单    {C_DARK}|{C_RESET}  {C_BOLD}{C_CYAN}[B]{C_RESET} 编辑黑名单    {C_BLUE}│{C_RESET}")
        print(f"{C_BLUE}╰{'─'*62}╯{C_RESET}")
        print(f"{C_GRAY}  退出请按 [0]{C_RESET}")

        choice = input(f"\n👉 {C_BOLD}请输入指令:{C_RESET} ").strip().lower()

        # === 指令处理 ===
        if choice == "0":
            sys.exit(0)
        elif choice == "l":
            sys.exit(99)
        elif choice == "u":
            sys.exit(88)
        elif choice == "w":
            sys.exit(77)  # manager.sh 负责 nano 编辑
        elif choice == "b":
            sys.exit(66)  # manager.sh 负责 nano 编辑

        elif choice == "r":
            print("正在刷新...")
            time.sleep(0.1)
            continue

        elif choice == "s":
            print("正在重载...")
            apply_firewall()
            time.sleep(0.3)

        elif choice == "p":
            print("正在暂停...")
            stop_firewall()
            time.sleep(0.3)

        else:
            # === 批量序号处理逻辑 ===
            try:
                indexes = [int(x) for x in choice.replace(",", " ").split()]
                if not indexes:
                    continue

                changed = False
                whitelist = load_whitelist()
                blacklist = load_blacklist()

                for idx in indexes:
                    real_idx = idx - 1
                    if 0 <= real_idx < len(dev_list):
                        target = dev_list[real_idx]
                        mac = target["mac"]

                        if mac == my_mac:
                            print(f"{C_RED}❌ 跳过本机 (安全保护){C_RESET}")
                            continue

                        if mac in whitelist:
                            print(f"{C_YELLOW}⚠️  该设备在白名单中,禁止拉黑: {mac}{C_RESET}")
                            continue

                        if mac in blacklist:
                            blacklist.remove(mac)
                        else:
                            blacklist.add(mac)
                            if active:
                                flush_conntrack(mac, dev_list)
                        changed = True

                if changed:
                    save_blacklist(blacklist)
                    if active:
                        apply_firewall()
                    print(f"{C_GREEN}✅ 批量操作已执行{C_RESET}")
                    time.sleep(0.5)

            except ValueError:
                print(f"{C_YELLOW}⚠️  无效指令,请输入序号或功能键{C_RESET}")
                time.sleep(0.5)

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "boot":
        apply_firewall()
    else:
        try:
            show_menu()
        except KeyboardInterrupt:
            sys.exit(0)
EOF
chmod +x "$PY_FILE"

# 3. 配置服务
cat << 'EOF' > "/etc/init.d/devctrl"
#!/bin/sh /etc/rc.common
START=99
USE_PROCD=1
SCRIPT_PATH="/root/devctrl/core.py"
PYTHON_BIN="/usr/bin/python3"
start_service() { $PYTHON_BIN $SCRIPT_PATH boot; }
EOF
chmod +x "/etc/init.d/devctrl"
/etc/init.d/devctrl enable
/etc/init.d/devctrl restart >/dev/null 2>&1

# 4. 管理脚本 (修正 logread | grep 问题 + /黑名单编辑强制 nano)
cat << 'EOF' > "$MGR_FILE"
#!/bin/bash
PY_CORE="/root/devctrl/core.py"
BAN_LIST="/etc/devctrl_blacklist.txt"
WHT_LIST="/etc/devctrl_whitelist.txt"

sanitize_list() {
    # 清洗: 去空行/注释转大写去重仅保留合法 MAC
    # BusyBox awk 兼容写法
    local f="$1"
    [ -f "$f" ] || return 0
    awk '
        function ord(ch){ return index("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F !\"#$%&'\''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", ch)-1 }
        {
            gsub(/\r/,"",$0)
            line=$0
            sub(/^[ \t]+/,"",line); sub(/[ \t]+$/,"",line)
            if(line=="" || substr(line,1,1)=="#") next

            # 转大写
            out=""
            for(i=1;i<=length(line);i++){
                c=substr(line,i,1)
                if(c>="a"&&c<="z") c=sprintf("%c", ord(c)-32)
                out=out c
            }
            line=out

            if(line ~ /^[0-9A-F]{2}(:[0-9A-F]{2}){5}$/){
                if(!seen[line]++){ print line }
            }
        }
    ' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"
}

edit_list() {
    local f="$1"
    local title="$2"
    clear
    echo -e "\033[36m=== $title ===\033[0m"
    echo "说明:"
    echo "1) 每行一个 MAC例如: AA:BB:CC:DD:EE:FF"
    echo "2) 支持以 # 开头的注释行"
    echo "3) 保存退出后会自动清洗格式并重载引擎"
    echo

    touch "$f" 2>/dev/null
    chmod 600 "$f" 2>/dev/null

    # 方案1强制使用 nano
    if ! command -v nano >/dev/null 2>&1; then
        echo -e "\033[31m未找到 nano请安装: opkg install nano\033[0m"
        read -p "按回车返回..." _
        return
    fi

    nano "$f"
    sanitize_list "$f"

    # 编辑完成后重载规则
    /usr/bin/python3 /root/devctrl/core.py boot >/dev/null 2>&1
    echo -e "\033[32m已保存并重载。\033[0m"
    read -p "按回车返回..." _
}

show_log() {
    clear
    echo -e "\033[36m=== 拦截日志 ( Ctrl+C 返回) ===\033[0m"
    trap 'return' SIGINT
    # 修正: 去掉 BusyBox 不支持的 --line-buffered 参数
    logread -f | grep "BAN_ALL" | while read line; do
        echo -e "\033[31m$line\033[0m"
    done
    trap - SIGINT
}

do_uninstall() {
    clear
    read -p "确定要卸载吗? (y/n): " c
    if [[ "$c" == "y" ]]; then
        /etc/init.d/devctrl stop 2>/dev/null
        /etc/init.d/devctrl disable 2>/dev/null
        nft delete table inet devctrl_v6 2>/dev/null
        rm -rf /root/devctrl /etc/init.d/devctrl /usr/bin/dev
        echo -e "\033[32m已卸载。\033[0m"; exit 0
    fi
}

while true; do
    python3 "$PY_CORE"
    c=$?
    if   [ $c -eq 99 ]; then show_log;
    elif [ $c -eq 88 ]; then do_uninstall;
    elif [ $c -eq 77 ]; then edit_list "$WHT_LIST" "编辑白名单";
    elif [ $c -eq 66 ]; then edit_list "$BAN_LIST" "编辑黑名单";
    elif [ $c -eq 0  ]; then clear; exit 0;
    else read; fi
done
EOF
chmod +x "$MGR_FILE"
ln -sf "$MGR_FILE" "$LINK_BIN"

echo "-----------------------------------------------"
echo "✅ V9 (修正版 + 白/黑名单编辑 + 强制nano) 安装完成!"
echo "输入 dev 即可使用"
echo "白名单: /etc/devctrl_whitelist.txt"
echo "黑名单: /etc/devctrl_blacklist.txt"
echo "-----------------------------------------------"
INSTALL_EOF

bash /tmp/install_devctrl_v9_fix.sh && rm -f /tmp/install_devctrl_v9_fix.sh

🕹️ 使用指南

  1. 启动管理:在终端输入 dev 即可进入图形化管理界面。
  2. 基本操作
    • 拉黑/解封:输入设备对应的 序号(例如 2)并回车。
    • 批量操作:输入多个序号并用空格隔开,例如 1 3 5,一次搞定。
  3. 功能按键
    • [S] 重载引擎:强制重新应用防火墙规则(通常不仅不需要,脚本自动处理,但可用于手动纠错)。
    • [R] 刷新列表:重新扫描局域网设备,更新在线状态。
    • [P] 暂停防护:临时允许所有人上网(例如全家聚会时),再次按 S 可恢复拦截。
    • [L] 实时日志:查看被拦截设备的连接请求,看看谁在悄悄上网。
    • [U] 卸载脚本:一键清理所有文件和规则,还你一个干净的系统。

⚠️ 免责声明:本脚本仅用于个人家庭网络管理,请勿用于非法用途或公共网络环境。操作前请确保您对 SSH 和 OpenWrt 有基础了解。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注