当前位置:首页>Linux>动态控制linux防火墙及休眠唤醒 - 微信小程序MQTT实现

动态控制linux防火墙及休眠唤醒 - 微信小程序MQTT实现

  • 2026-09-09 11:21:19
动态控制linux防火墙及休眠唤醒 - 微信小程序MQTT实现

本文搭建一套基于 NAS 的内网远程管控方案,依托微信小程序实现两大核心功能:

  1. 远程动态调整linux防火墙,仅放行指定 IP 接入
  2. 远程控制NAS主机休眠,支持休眠指定时间后自动唤醒

整套系统以 NAS 作为 7×24 小时运行的控制中枢,内部部署 Mosquitto MQTT 消息服务作为通信枢纽,Broker 同步开启 WebSocket 协议用于前端长连接交互,由于mosqutto原生websocket协议兼容问题,使用 Nginx 反向代理mosqutto的websocket服务。整体只需要对外开启一个端口,并使用密码认证。

一、前言/需求背景

1. 业务痛点

  1. 内网NAS、台式机无法在外网便捷管控,远程访问存在安全隐患;
  2. 防火墙白名单需要频繁修改,传统SSH登录操作繁琐,无移动端可视化操作入口;
  3. 台式机闲置耗电,外出时可一键设置休眠;
  4. 微信小程序无需安装APP,轻量化、随时可用,是最优移动端控制载体。 通过这种方式,可以实现仅允许机主当前所在网络的公网ip访问,配置操作几乎无成本,且大大降低恶意攻击的成功率。

2. 整体实现目标

通过微信小程序,在外网下发控制指令,NAS统一执行两类操作:

  1. 防火墙动态管理:添加/删除指定IP地址及重置为默认规则;
  2. 电脑电源管控:使用rtcwake设置休眠时长、时间到达后自动唤醒; 基于MQTT消息队列解耦,Nginx做WSS反向代理适配小程序网络限制。 如果休眠一段时间后,需要提前唤醒,需要搭配wol机制,现在的家庭路由器一般都有远程唤醒功能,或者有对应的插件。

二、整体技术架构详解

1. 技术栈清单

层级
技术组件
作用说明
移动端
uni-app + mqtt.js
微信小程序,下发指令、展示状态
网关代理
Nginx
18443端口WebSocket协议转发、域名统一入口
消息队列
Mosquitto
消息订阅分发,解耦小程序与NAS后端服务
NAS后端
Node
订阅MQTT指令,调用系统脚本管控防火墙、电脑电源
底层系统
nftables rtcwake
防火墙规则修改、设置休眠时长

2. 架构流程图说明

3. 核心设计优势

  1. 解耦架构:MQTT消息中间件隔离前端与底层系统,小程序无需直连NAS内网端口,安全隔离;
  2. 适配小程序限制:小程序强制要求WSS加密、443标准端口,Nginx统一处理证书与端口转发;
  3. 低资源占用:Mosquitto轻量运行在NAS,长连接低功耗,不占用大量系统资源;
  4. 可扩展:新增设备控制逻辑仅需拓展NAS服务,前端无需大幅改造;
  5. 安全可控:Mosquitto开启账号密码鉴权,防火墙仅放行指定IP,双层安全防护。

三、核心组件部署配置

1. Mosquitto配置(开启WebSocket支持)

listener 8083 172.18.0.1protocol websocketsallow_anonymous falsepassword_file /etc/mosquitto/users.passacl_file /etc/mosquitto/aclfilemax_inflight_bytes 1048576max_inflight_messages 20max_keepalive 300max_packet_size 524288max_queued_bytes 4194304max_qos 2max_queued_messages 1000retain_available truemax_connections 30

这种方式配置的websocket协议,在浏览器中无法连接,使用nginx进行一次代理就可以了。

2. Nginx WSS反向代理配置(适配小程序)

除了使用小程序控制,也可以打包成h5页面,部署到nginx中,这样通过浏览器也可以进行操作。小程序中可连接的websocket地址,需要到小程序管理后台配置,不如h5页面自由灵活。

server {    listen 18443 ssl;    server_name mqtt.sskxyz.top;    ssl_certificate_key /etc/nginx/certs/privkey.pem;    ssl_certificate /etc/nginx/certs/fullchain.pem;    ssl_protocols  TLSv1.2  TLSv1.3;    ssl_prefer_server_ciphers on;    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;    ssl_session_cache   shared:SSL:512k;    ssl_session_tickets off;    ssl_stapling on;    ssl_stapling_verify on;    ssl_session_timeout  5m;	# 代理mosquitto websocket协议    location = /ws {        proxy_pass http://172.18.0.1:8083;        proxy_http_version 1.1;        proxy_set_header Upgrade $http_upgrade;        proxy_set_header Connection "upgrade";        proxy_set_header Host $host;        proxy_read_timeout 30s;        proxy_send_timeout 30s;        proxy_connect_timeout 30s;        proxy_buffering off;    }    # h5页面部署    location /wxwork/ {        root /opt/websites/web;        try_files $uri $uri/index.html /wxwork/index.html;    }	# 兼容jsonp的公网地址获取配置    location /ipaddr {        default_type application/javascript;        if ($request_method = OPTIONS) {            add_header Access-Control-Allow-Origin *;            add_header Access-Control-Allow-Methods GET,OPTIONS;            return 204;        }        # 没有callback就默认json格式        if ($arg_callback = "") {            return 200 '{"ip":"$remote_addr"}';        }        # 有callback = 返回标准JSONP格式        return 200 "$arg_callback({\"ip\":\"$remote_addr\"});";    }}

3. NAS控制服务核心逻辑简述

  1. 启动时连接本地Mosquitto,订阅指令Topic: bussiness/nftables;
  2. 消息JSON格式规范示例:
// 防火墙修改指令{"action": "add/remove/reset","ip_addr": "114.xx.xx.xx"}// 电脑定时休眠指令{"action": "sleep","duration": 60}
  1. 使用node实现本地命令调用,日志记录到sqlite中:

防火墙控制:

import net from 'net'import type { IService } from './service';import type { Action } from '../models/model';import { executeShellCommand, Sqlite } from '../utils'const insertStmt = 'INSERT INTO firewall_rules (ip_addr, action) VALUES (?, ?)';const updateStmt = 'UPDATE firewall_rules SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?';export class NftablesService implements IService {    support(context: Action): boolean {        return ['add', 'remove', 'reset'].includes(context.action)    }    execute(context: Action): Promise<string> {        let { action, ip_addr } = { ...context }        if (!net.isIP(ip_addr!)) {            console.error(`[-] 安全拦截: 无效的 IP 地址格式 "${ip_addr}"`);            return Promise.reject(`无效的 IP 地址格式 ${ip_addr}`)        }        let nftCommand = this.command(action, ip_addr!)        const id: number | bigint = Sqlite.insert(insertStmt, ip_addr, action)        return executeShellCommand(nftCommand).then(resp => {            Sqlite.update(updateStmt, 'success', id)            return resp;        }).catch(err => {            console.error(`[CMD Error] 命令执行失败: ${err}`);            Sqlite.update(updateStmt, id, 'failed')            return err;        })    }    command(action: string, ip_addr: string): string {        let nftCommand = 'nft list ruleset '        switch (action) {            case 'add':                nftCommand = `nft add element inet filter allowed_dynamic { ${ip_addr} }`;                break;            case 'remove':                nftCommand = `nft delete element inet filter allowed_dynamic { ${ip_addr} }`;                break;            case 'reset':                nftCommand = 'nft -f /etc/nftables.conf'                break;        }        return nftCommand;    }}

休眠控制:

import { Action } from "../models/model";import { IService } from "./service";import { executeShellCommand } from "../utils";export class SuspendService implements IService {    support(context: Action): boolean {        return context.action === 'sleep'    }    execute(context: Action): Promise<string> {        let { duration } = { ...context }        let wake_time = Math.floor(Date.now() / 1000) + duration! * 60;        let nftCommand = `rtcwake -t ${wake_time}  -vm mem`        return executeShellCommand(nftCommand).then(res => {            console.info("- 执行休眠成功")            return `执行休眠成功: ${res}`        }).catch(err => {            console.error(`- 执行休眠失败: ${err}`)            return `执行休眠成功: ${err}`        })    }}

4. 控制界面效果

H5示例

小程序示例: 

四、附录

以上内容解决了白天nas访问、休眠控制的问题,如果希望晚上11点到早上8点,nas自动休眠,可以配置一个定时脚本,之前有介绍过,这里再贴一下:

休眠控制脚本:

#!/bin/bashfunctiondisable() {#sed -i 's/AllowSuspend=.*/AllowSuspend=no/' /etc/systemd/sleep.confprintf'%s\n'',s/AllowSuspend=.*/AllowSuspend=no/g' w q | ed -s /etc/systemd/sleep.conf}functionenable() {#sed -i 's/AllowSuspend=.*/AllowSuspend=yes/' /etc/systemd/sleep.confprintf'%s\n'',s/AllowSuspend=.*/AllowSuspend=yes/g' w q | ed -s /etc/systemd/sleep.conf}functionstatus() {    yes_no=$(sed -n '/AllowSuspend=/p' /etc/systemd/sleep.conf  | cut -d '=' -f2)echo$yes_no}curr_time=$(date +%H%M)wake_time=$(date -d "next day 08:05" +%s)stat=$(status)if [ $curr_time -gt 2230 -o $curr_time -lt 0800 ];thenif [ "no" = $stat ];thenenablefi    rtcwake -t $wake_time  -vm memelseif [ "yes" = $stat ];thendisablefifi

systemd定时器:

root@fnos:~# systemctl cat sched_sleep.service# /lib/systemd/system/sched_sleep.service[Unit]Description=Schedule Sleep FNOS[Service]Type=oneshotUser=rootExecStart=/root/sleep.sh[Install]WantedBy=multi-user.target# 定时配置,每10分钟执行一次root@fnos:~# systemctl cat sched_sleep.timer# /lib/systemd/system/sched_sleep.timer[Unit]Description=Schedule Sleep FNOS[Timer]OnCalendar=*:0/5RandomizedDelaySec=10mPersistent=true[Install]WantedBy=timers.target

防火墙参考配置: 仅允许18443这一个端口对外可访问,供小程序和h5连接,将来源IP地址通过小程序加入后,才可以访问其它服务,将服务暴露范围最小化。 如果有vpn服务也可以对外暴露,但要配置比较安全的证书和用户名密码。

#!/usr/sbin/nft -fflush rulesettable inet filter {set allowed_dynamic {type ipv4_addr; flags dynamic    }    chain input {type filter hook input priority filter; policy drop        ip6 nexthdr icmpv6 counter accept        udp dport mdns counter accept        ct state related,established counter accept        iifname "lo" accept        iifname "enp0s31f6-ovs" udp dport { 53, 67 } accept        iifname "enp0s31f6-ovs" tcp dport { 53, 67 } accept        ip saddr { 192.168.5.0/24, 10.8.0.0/24, 172.18.0.0/24 } counter accept        tcp dport { 61194, 8883 } counter accept        ip saddr @allowed_dynamic tcp dport { 5667,60022 } counter accept    }    chain forward {type filter hook forward priority filter; policy drop        ct state related, established counter accept        ip daddr 172.18.0.2 tcp dport 18443 counter accept        ip saddr @allowed_dynamic iifname "enp0s31f6-ovs"  counter accept        ip saddr 192.168.5.0/24 counter accept        iifname { "tun0", "br-691295fe2d05" } counter accept    }    chain output {type filter hook output priority filter; policy accept    }}table inet nat {    chain postrouting {type nat hook postrouting priority srcnat        ip saddr 10.8.0.0/24 counter masquerade        ip saddr 172.18.0.0/24 counter masquerade    }    chain prerouting {type nat hook prerouting priority dstnat        ip daddr 192.168.5.254 tcp dport 18443 counter dnat to 172.18.0.2:18443        ip daddr 192.168.5.254 tcp dport { 443, 8443 } counter dnat to 172.18.0.2:443        ip daddr 192.168.5.254 tcp dport 3000 counter dnat to 172.18.0.6:3000        ip daddr 192.168.5.254 tcp dport 9090 counter dnat to 172.18.0.8:9090        ip daddr 192.168.5.254 tcp dport 18789 counter dnat to 172.18.0.168:18789        ip daddr 192.168.5.254 tcp dport 18790 counter dnat to 172.18.0.168:18790        ip daddr 192.168.5.254 tcp dport 3001 counter dnat to 172.18.0.20:3000        ip daddr 192.168.5.254 tcp dport 12345 counter dnat to 172.18.0.99:12345        ip daddr 192.168.5.254 tcp dport 54321 counter dnat to 172.18.0.200:5432    }}

在做了上面这些之后,我就把安全中心这个应用卸载掉了。

最新文章

随机文章