该方案为自用组网方案,本次仅作记录思路和配置方法,做备忘使用。如有可取之处可配合自身需求参考本文档
网络结构及主要使用原理

各组件功能:
爱快:
负责主要上网、运行虚拟机、流量分流
OpenWRT:
负责科学上网
ADGuard Home:
负责面对用户端的DNS请求,并缓存、去广告、客户端分组(不用的IP可以请求不同的上游)
MosDNS:
负责DNS分流,尽量保证返回给ADGuard Home的IP正确,不会出现国内返回国外,国外返回国内,以及DNS污染
PowerDNS:
负责私有DNS域名配置和解析,可选安装,因为大部分其实使用ADGuard Home的DNS重写就能满足,这里不做赘述。
如果你的设备资源有限,ADGuard Home和MosDNS和PowerDNS可部署到一台虚拟机里,如果还不够可以直接使用OpenWRT的ADGuard Home和MosDNS插件;但这里不会说明这些方式的配置方式,但原理都差不多,可以自己琢磨。
使用原理
用户发起访问请求后,流量首先经过 ADGuard Home 进行 DNS 去广告过滤与缓存查询;未命中缓存的请求将被递送至 MosDNS 执行智能分流解析。获得目标 IP 后,数据包送达 爱快主路由,路由器根据目的 IP 的地理位置属性执行策略路由:国内流量自动切换至 WAN1 拨号线路,海外流量自动切换至 WAN2 静态线路。
开始部署
需要用到的设备和文件
- X86软路由一台
这里使用的Dell R620作为主路由,CPU:E5-2630L v2*1,内存:64G, 硬盘:240G。 - rocky9 镜像
可以在阿里云镜像站下载:https://developer.aliyun.com/mirror/rockylinux ,如果不喜欢可以选自己习惯的,可以运行ADGuard Home和MosDNS,部署宝塔就行。 - OpenWRT镜像
选择你喜欢的即可,我这里用的iStoreOS:https://site.istoreos.com/firmware/download?devicename=x86_64 - IkuaiOS镜像
主路由,且虚拟机也在爱快上设置。主打一个all in one;下载地址:https://www.ikuai8.com/component/download - 演示IP段:
爱快:172.25.52.1/24
OpenWRT:192.168.240.1/24
爱快路由配置
根据官网教程安装好后,先保证正常上网,再做出如下设置
设置WAN1口 为拨号或自动DHCP,并设置为默认网关(先保证正常上网)
设置WAN2口 为静态IP,并设置IP地址为:192.168.240.10,子网掩码为:255.255.255.0,网关为:192.168.240.1
设置LAN1口为172.25.52.1, DHCP服务器分配的DNS为233.5.5.5(先保证正常上网)
部署虚拟机
- 安装OpenWRT
将解压后的IMG文件上传到爱快,在第一次创建虚拟机时,在磁盘类型中,选择引用磁盘,并填入该IMG文件地址。
在磁盘类型中,选择新建磁盘,并设置容量,我这里因为硬盘大,容量随便的设置的50G。
在网卡中,网卡模式选默认,桥接接口选LAN1口,作为OpenWRT的WAN口。
在网卡中,网卡模式选默认,桥接接口选WAN2口,作为OpenWRT的LAN口。
其余设置根据喜好来即可,安装教程参考官网教程。安装好后删除引用的磁盘即可正常使用
安装完成后,可通过命令 quickstart 修改LAN口IP为:192.168.240.1
如果安装好的istoreos无法正常启动,可尝试关闭UEFI启动 - 安装ADGuard Home
正常部署rocky9即可,在部署时可将IP设置为:172.25.52.253 - 安装MosDNS
正常部署rocky9即可,在部署时可将IP设置为:172.25.52.252
安装及设置ADGuard Home
SSH登录172.25.52.253,输入以下命令安装AdGuardHome
# 下载AdGuardHome
wget https://static.adguard.com/adguardhome/release/AdGuardHome_linux_amd64.tar.gz
# 解压
tar -xvf AdGuardHome_linux_amd64.tar.gz
# 安装服务
cp -rf AdGuardHome /
cd /AdGuardHome
./AdGuardHome -s install
安装完后,进入网页http://172.25.52.253:3000进行基础设置,网页端口改为80,DNS端口改为53
然后 使用http://172.25.52.253重新进入AdGuardHome;在设置-DNS设置中,将上游DNS服务器 改为172.25.52.252
如果你需要使用PowerDNS,则改为172.25.52.252:5353,然后使用[/example.local/]10.61.6.252 来指定内部私有域名的上游服务器
后备 DNS 服务器为:
https://dns.alidns.com/dns-query
https://dns.pub/dns-query
Bootstrap DNS 服务器为:127.0.0.1
过滤器DNS重写填入以下设置(以下设置可参考填入,只是为了方便解析域名)
dns.google 8.8.8.8
dns.google 8.8.4.4
dns.quad9.net 9.9.9.9
dns.quad9.net 149.112.112.112
one.one.one.one 1.1.1.1
one.one.one.one 1.0.0.1
dns.alidns.com 223.5.5.5
dns.alidns.com 223.6.6.6
dns.pub 1.12.12.12
dns.pub 120.53.53.53
dns.adguard-dns.com 94.140.15.15
dns.adguard-dns.com 94.140.14.14
dns.cloudflare.com 162.159.61.8
dns.cloudflare.com 172.64.41.8
安装及设置MosDNS+PowerDNS
SSH登录172.25.52.253,输入以下命令安装宝塔
# 官方安装命令
if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh;fi;bash install_panel.sh ed8484bec
可选操作:安装PowerDNS和Poweradmin,网上根据其他教程来
PowerDNS项目地址:https://github.com/PowerDNS/pdns
Poweradmin项目地址:https://github.com/poweradmin/poweradmin
安装MosDNS:
宝塔安装:进程守护管理器、Go、Python3
先使用go编译安装v2dat
cd ~
git clone https://github.com/urlesistiana/v2dat.git
cd v2dat
go build -o v2dat
cp v2dat /usr/local/bin/
# 如果无法执行v2dat,就加个权限
# 因为网络原因编译失败执行以下命令,然后再重新编译
go env -w GOPROXY=https://goproxy.cn,direct
MosDNS项目地址:https://github.com/IrineSistiana/mosdns
在宝塔中/www/wwwroot/目录下创建MosDNS文件夹,然后进入该文件夹
使用宝塔的上传/下载功能,下载MosDNS,这里以v5.3.4为例, 下载完成后解压。
# 下载地址
https://github.com/IrineSistiana/mosdns/releases/download/v5.3.4/mosdns-linux-amd64.zip
然后创建log和rules目录,用于存放日志和规则
在宝塔计划任务中,创建定时任务计划,类型为Shell脚本,时间看个人,执行用户root,脚本内容如下:
!!!脚本内容已经更新,详细查看 设置多线负载部分
#!/bin/bash
set -e # 遇到错误立即退出
# --- 配置区域 ---
v2dat_dir="/www/wwwroot/MosDNS"
RULES_DIR="$v2dat_dir/rules"
TEMP_DOWNLOAD_DIR="$v2dat_dir/tmp_download"
# 镜像加速配置
USE_MIRROR=false # 是否使用镜像加速,true 启用,false 禁用
MIRROR_URL="" # 镜像地址,请确保以 / 结尾
# ---------------
# 检查 v2dat 是否可用
if ! command -v v2dat &> /dev/null; then
echo "错误:未找到 v2dat 命令,请先安装 v2dat"
exit 1
fi
# 拼接下载 URL 的函数
construct_url() {
local raw_url="$1"
if [ "$USE_MIRROR" = true ] && [ -n "$MIRROR_URL" ]; then
echo "${MIRROR_URL}${raw_url}"
else
echo "$raw_url"
fi
}
update_rules() {
echo "正在初始化目录..."
mkdir -p "$RULES_DIR"
mkdir -p "$TEMP_DOWNLOAD_DIR"
rm -rf "$TEMP_DOWNLOAD_DIR"/*
echo "正在下载 GeoIP 和 GeoSite 数据文件..."
# 原始下载地址
GEOIP_RAW="https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip-only-cn-private.dat"
GEOSITE_RAW="https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
# 应用镜像(如果启用)
GEOIP_URL=$(construct_url "$GEOIP_RAW")
GEOSITE_URL=$(construct_url "$GEOSITE_RAW")
# 下载 geoip.dat
echo "下载 geoip.dat 从: $GEOIP_URL"
curl --connect-timeout 10 -m 120 -kfSL -o "$TEMP_DOWNLOAD_DIR/geoip.dat" "$GEOIP_URL"
# 下载 geosite.dat
echo "下载 geosite.dat 从: $GEOSITE_URL"
curl --connect-timeout 10 -m 120 -kfSL -o "$TEMP_DOWNLOAD_DIR/geosite.dat" "$GEOSITE_URL"
echo "正在使用 v2dat 解包规则文件..."
# 解包 geoip:cn → geoip_cn.txt
v2dat unpack geoip -o "$TEMP_DOWNLOAD_DIR" -f cn "$TEMP_DOWNLOAD_DIR/geoip.dat"
# 解包 geosite:cn → geosite_cn.txt
v2dat unpack geosite -o "$TEMP_DOWNLOAD_DIR" -f cn "$TEMP_DOWNLOAD_DIR/geosite.dat"
# 解包 geosite:geolocation-!cn → geosite_geolocation-!cn.txt
v2dat unpack geosite -o "$TEMP_DOWNLOAD_DIR" -f 'geolocation-!cn' "$TEMP_DOWNLOAD_DIR/geosite.dat"
# 解包 geosite:apple → geosite_apple.txt
v2dat unpack geosite -o "$TEMP_DOWNLOAD_DIR" -f apple "$TEMP_DOWNLOAD_DIR/geosite.dat"
echo "正在替换旧规则至 $RULES_DIR..."
cp -a "$TEMP_DOWNLOAD_DIR"/geoip_cn.txt "$TEMP_DOWNLOAD_DIR"/geosite_cn.txt "$TEMP_DOWNLOAD_DIR"/geosite_geolocation-!cn.txt "$TEMP_DOWNLOAD_DIR"/geosite_apple.txt "$RULES_DIR/"
# 可选:如果 mosdns 以 www 用户运行,需确保新规则可被读取
if [ -d "$RULES_DIR" ]; then
OWNER=$(stat -c "%U:%G" "$RULES_DIR")
echo "同步规则文件权限为 $OWNER ..."
chown -R "$OWNER" "$RULES_DIR"/*
chmod 644 "$RULES_DIR"/*.txt
chmod 755 "$RULES_DIR"
fi
}
# 2. 执行下载与解包
update_rules
# 3. 清理临时目录
echo "正在清理下载缓存..."
rm -rf "$TEMP_DOWNLOAD_DIR"
# 4. 关闭服务
echo "正在关闭 mosdns 服务..."
cd "$v2dat_dir" || { echo "错误:无法进入目录 $v2dat_dir"; exit 1; }
if [ -x "./mosdns" ]; then
echo "正在停止现有 mosdns 进程..."
if pkill -f "mosdns.*start"; then
echo "mosdns 进程已终止。"
else
echo "未找到运行中的 mosdns 进程。"
fi
sleep 1
echo "mosdns 服务已关闭。"
else
echo "错误:在 $v2dat_dir 中未找到可执行的 mosdns 文件。"
exit 1
fi
设置好后可以手动执行下,看看有没有报错,正常运行后/www/wwwroot/MosDNS/rules 目录下会多几个文件:
geoip_cn.txt 大陆IP
geosite_apple.txt 苹果域名
geosite_cn.txt 大陆域名
geosite_geolocation-!cn.txt GFW列表
如果因为网络问题无法下载geoip-only-cn-private.dat和geosite.dat,则在脚本的镜像加速配置添加GitHub加速代理。
PS: 因为这个时候还没有启动MosDNS,所以关闭MosDNS部分代码报错是正常的。
然后继续在rules 目录下添加几个文件
greylist.txt 文件,内容如下:
pixiv.net
google.com
devv.ai
reddit.com
v2ex.com
docker.io
whitelist.txt 文件,内容如下:
domain:bing.com
domain:live.com
domain:msn.com
domain:ntp.org
domain:office.com
domain:qlogo.cn
domain:qq.com
domain:redhat.com
domain:gov.cn
keyword:douyin
keyword:microsoft
keyword:windows
greylist.txt 灰名单:文件内的域名为强制指定国外DNS服务器解析,每个域名一行,支持域名匹配规则
whitelist.txt 白名单:文件内的域名为强制指定国内DNS服务器解析,每个域名一行,支持域名匹配规则
回到目录MosDNS目录下,修改config.yaml文件为以下内容:
log:
level: info
file: /www/wwwroot/MosDNS/log/mosdns.log
api:
http: 0.0.0.0:9091
include: []
plugins:
- tag: geosite_cn
type: domain_set
args:
files:
- /www/wwwroot/MosDNS/rules/geosite_cn.txt
- tag: geoip_cn
type: ip_set
args:
files:
- /www/wwwroot/MosDNS/rules/geoip_cn.txt
- tag: geosite_apple
type: domain_set
args:
files:
- /www/wwwroot/MosDNS/rules/geosite_apple.txt
- tag: geosite_no_cn
type: domain_set
args:
files:
- /www/wwwroot/MosDNS/rules/geosite_geolocation-!cn.txt
- tag: whitelist
type: domain_set
args:
files:
- /www/wwwroot/MosDNS/rules/whitelist.txt
- tag: greylist
type: domain_set
args:
files:
- /www/wwwroot/MosDNS/rules/greylist.txt
- tag: forward_xinfeng_udp
type: forward
args:
concurrent: 2
upstreams:
- addr: 61.139.2.69
- addr: 218.6.200.139
- tag: forward_local
type: forward
args:
concurrent: 2
upstreams:
- addr: https://dns.alidns.com/dns-query
bootstrap: 172.25.52.253
enable_pipeline: true
insecure_skip_verify: false
idle_timeout: 30
enable_http3: true
- addr: https://doh.pub/dns-query
bootstrap: 172.25.52.253
enable_pipeline: true
insecure_skip_verify: false
idle_timeout: 30
- tag: forward_remote
type: forward
args:
concurrent: 2
upstreams:
- addr: tls://8.8.8.8
bootstrap: 172.25.52.253
enable_pipeline: true
insecure_skip_verify: false
idle_timeout: 30
- addr: tls://1.1.1.1
bootstrap: 172.25.52.253
enable_pipeline: true
insecure_skip_verify: false
idle_timeout: 30
- tag: forward_remote_upstream
type: sequence
args:
- exec: prefer_ipv4
- exec: $forward_remote
- tag: modify_ttl
type: sequence
args:
- exec: ttl 0-0
# has_resp_sequence 简化:统一修改 TTL 后接受响应
- tag: has_resp_sequence
type: sequence
args:
- exec: $modify_ttl
- matches: has_resp
exec: accept
- tag: query_is_non_local_ip
type: sequence
args:
- exec: $forward_local
- matches: '!resp_ip $geoip_cn'
exec: drop_resp
- tag: fallback
type: fallback
args:
primary: forward_remote_upstream
secondary: forward_remote_upstream
threshold: 500
always_standby: true
- tag: apple_domain_fallback
type: fallback
args:
primary: query_is_non_local_ip
secondary: forward_xinfeng_udp
threshold: 100
always_standby: true
- tag: query_is_apple_domain
type: sequence
args:
- matches: '!qname $geosite_apple'
exec: return
- exec: $apple_domain_fallback
- tag: query_is_whitelist_domain
type: sequence
args:
- matches: qname $whitelist
exec: $forward_local
- tag: query_is_greylist_domain
type: sequence
args:
- matches: qname $greylist
exec: $forward_remote_upstream
- tag: query_is_local_domain
type: sequence
args:
- matches: qname $geosite_cn
exec: $forward_local
- tag: query_is_no_local_domain
type: sequence
args:
- matches: qname $geosite_no_cn
exec: $forward_remote_upstream
# 主处理序列
- tag: main_sequence
type: sequence
args:
- exec: jump has_resp_sequence
- exec: $query_is_apple_domain
- exec: jump has_resp_sequence
- exec: $query_is_whitelist_domain
- exec: jump has_resp_sequence
- exec: $query_is_greylist_domain
- exec: jump has_resp_sequence
- exec: $query_is_local_domain
- exec: jump has_resp_sequence
- exec: $query_is_no_local_domain
- exec: jump has_resp_sequence
- exec: $fallback
- tag: udp_server
type: udp_server
args:
entry: main_sequence
listen: ':5353'
- tag: tcp_server
type: tcp_server
args:
entry: main_sequence
listen: ':5353'
61.139.2.69218.6.200.139
以上DNS需要修改为自己运营商的DNS
如果没有PowerDNS,可以将5353改为53
该配置执行流程为:DNS 查询进入 main_sequence,按顺序判断 Apple 域名、白名单、灰名单、国内域名、国外域名,匹配后调用对应转发器(国内 DoH、国外 DoT、备用 UDP),并对每个响应修改 TTL 后返回;国内 DNS 的结果还会检查 IP 归属地,若非国内 IP 则丢弃,防止污染。未匹配规则则默认走国外 DNS
打开进程守护管理器,添加进程守护,随便起个名字
#启动用户
www
# 启动命令
/www/wwwroot/MosDNS/mosdns start -d /www/wwwroot/MosDNS
# 进程目录
/www/wwwroot/MosDNS/
# 其他默认即可
确定后,进程管理显示运行中,则MosDNS运行起来了,可以使用nslookup命令进行测试。
设置多线负载和端口分流
多线负载算是爱快里最底层的一个分流设置了,使用该功能的情况下,可以完美保留协议分流、端口分流、域名分流等功能。
在爱快系统设置-登录管理-账号管理中新建一个账户,需要多线负载和新功能可读写权限。访问地址0.0.0.0/0。
部署 Auto_Update_Operator
项目地址:https://github.com/chenanmo/Auto_Update_Operator,该项目主要用于自动更新大陆IP地址到爱快,实现流量分流,为早期方案。数据源为苍狼山庄:https://ispip.clang.cn/ , 如果不喜欢且有能力可以使用geoip-only-cn-private.dat替代该方案。(后续有时间也会使用geoip-only-cn-private.dat,保持和MosDNS的脚本一致)
使用宝塔部署容器,环境变量如下
# 该部分内容作废
#爱快地址
HOST_URL=http://172.25.52.1
IKUAI_NAME=刚刚新建的用户
IKUAI_PASSWORD=刚刚新建的用户的密码
# IP地址文本链接地址
CHINAIP_URL=https://ispip.clang.cn/all_cn_apnic.txt
# 自定义运营商名字
OPERATOR_NAME=中国大陆
# 更新时间
CUSTOM_RUNTIME=08:00
在宝塔的计划任务中, 修改之前用来更新MosDNS 规则的代码:
# -*- coding: utf-8 -*-
"""
自动更新 MosDNS 规则文件并上传国内 IPv4 CIDR 至爱快自定义运营商。
依赖:Python 3.6+(仅标准库),系统需安装 v2dat 命令。
"""
import os
import sys
import json
import base64
import hashlib
import ipaddress
import urllib.request
import urllib.parse
import http.cookiejar
import subprocess
import shutil
import time
from pathlib import Path
# ==================== 配置区域(请根据实际情况修改) ====================
# 目录配置
MOSDNS_DIR = "/www/wwwroot/MosDNS"
RULES_DIR = f"{MOSDNS_DIR}/rules"
TEMP_DOWNLOAD_DIR = f"{MOSDNS_DIR}/tmp_download"
# 镜像加速配置
# 是否使用镜像加速,true 启用,false 禁用
USE_MIRROR = false
# 镜像地址,请确保以 / 结尾
MIRROR_URL = ""
# 爱快路由器配置
# 替换为您的爱快路由器地址
IKUAI_HOST_URL = "http://172.25.52.1"
# 替换为您的爱快用户名
IKUAI_USERNAME = "刚刚新建的用户"
# 替换为您的爱快密码
IKUAI_PASSWORD = "刚刚新建的用户的密码"
OPERATOR_NAME = "中国大陆"
# 下载地址(原始)
GEOIP_RAW_URL = "https://github.com/Loyalsoldier/geoip/releases/latest/download/geoip-only-cn-private.dat"
GEOSITE_RAW_URL = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
# 是否重启 MosDNS
RESTART_MOSDNS = True
# =====================================================================
def construct_url(raw_url: str) -> str:
"""根据配置拼接镜像加速 URL"""
if USE_MIRROR and MIRROR_URL:
return MIRROR_URL.rstrip('/') + '/' + raw_url.lstrip('/')
return raw_url
def download_file(url: str, dest_path: str, timeout: int = 120) -> bool:
"""下载文件到指定路径"""
print(f"正在下载: {url}")
try:
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=timeout) as resp:
with open(dest_path, 'wb') as f:
f.write(resp.read())
print(f"下载完成: {dest_path}")
return True
except Exception as e:
print(f"下载失败: {e}")
return False
def run_v2dat_unpack(dat_type: str, dat_file: str, output_dir: str, filter_name: str) -> bool:
"""调用 v2dat 解包指定类型的规则"""
cmd = [
"v2dat", "unpack", dat_type,
"-o", output_dir,
"-f", filter_name,
dat_file
]
print(f"执行命令: {' '.join(cmd)}")
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
print(f"解包成功: {dat_type}:{filter_name}")
return True
except subprocess.CalledProcessError as e:
print(f"解包失败: {e.stderr}")
return False
def prepare_directories():
"""创建必要目录并清空临时目录"""
Path(RULES_DIR).mkdir(parents=True, exist_ok=True)
temp_dir = Path(TEMP_DOWNLOAD_DIR)
if temp_dir.exists():
shutil.rmtree(temp_dir)
temp_dir.mkdir(parents=True, exist_ok=True)
def download_and_unpack():
"""下载 geoip.dat 和 geosite.dat 并解包"""
geoip_url = construct_url(GEOIP_RAW_URL)
geosite_url = construct_url(GEOSITE_RAW_URL)
geoip_dat = os.path.join(TEMP_DOWNLOAD_DIR, "geoip.dat")
geosite_dat = os.path.join(TEMP_DOWNLOAD_DIR, "geosite.dat")
if not download_file(geoip_url, geoip_dat):
sys.exit(1)
if not download_file(geosite_url, geosite_dat):
sys.exit(1)
# 解包需要的规则
unpack_tasks = [
("geoip", geoip_dat, "cn"),
("geosite", geosite_dat, "cn"),
("geosite", geosite_dat, "geolocation-!cn"),
("geosite", geosite_dat, "apple"),
]
for dtype, dat_file, filter_name in unpack_tasks:
run_v2dat_unpack(dtype, dat_file, TEMP_DOWNLOAD_DIR, filter_name)
# 复制到 RULES_DIR
print(f"复制解包后的规则文件到 {RULES_DIR}")
for txt_file in Path(TEMP_DOWNLOAD_DIR).glob("*.txt"):
shutil.copy2(txt_file, RULES_DIR)
# 设置权限(如果 MosDNS 以非 root 运行)
try:
# 获取 RULES_DIR 的属主
stat_info = os.stat(RULES_DIR)
uid, gid = stat_info.st_uid, stat_info.st_gid
for f in Path(RULES_DIR).glob("*.txt"):
os.chown(f, uid, gid)
os.chmod(f, 0o644)
os.chmod(RULES_DIR, 0o755)
print("规则文件权限已同步")
except Exception as e:
print(f"权限设置失败(可忽略): {e}")
# ==================== 爱快 API 操作类 ====================
class IKuaiAPI:
def __init__(self, host_url: str, username: str, password: str):
self.host_url = host_url.rstrip('/')
self.username = username
self.password = password
self.login_url = f"{self.host_url}/Action/login"
self.api_url = f"{self.host_url}/Action/call"
self.cookie_jar = http.cookiejar.CookieJar()
self.opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cookie_jar)
)
self._logged_in = False
@staticmethod
def _encode_password(pwd: str):
"""返回 (md5, base64) 元组"""
md5 = hashlib.md5(pwd.encode()).hexdigest()
salted = ("salt_11" + pwd).encode("ASCII")
b64 = base64.b64encode(salted).decode()
return md5, b64
def login(self) -> bool:
md5_pass, b64_pass = self._encode_password(self.password)
payload = {
"username": self.username,
"passwd": md5_pass,
"pass": b64_pass,
"remember_password": ""
}
data = json.dumps(payload).encode('utf-8')
headers = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0'
}
req = urllib.request.Request(self.login_url, data=data, headers=headers)
try:
with self.opener.open(req, timeout=10) as resp:
result = json.loads(resp.read().decode())
if result.get("Result") == 10000:
print("登录爱快成功")
self._logged_in = True
return True
else:
print(f"登录失败: {result}")
return False
except Exception as e:
print(f"登录请求异常: {e}")
return False
def _api_call(self, func_name: str, action: str, param: dict) -> dict:
if not self._logged_in:
raise RuntimeError("未登录")
payload = {
"func_name": func_name,
"action": action,
"param": param
}
data = json.dumps(payload).encode('utf-8')
headers = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0'
}
req = urllib.request.Request(self.api_url, data=data, headers=headers)
try:
with self.opener.open(req, timeout=60) as resp:
return json.loads(resp.read().decode())
except Exception as e:
print(f"API 调用异常: {e}")
return {"Result": -1, "Error": str(e)}
def get_custom_isp_list(self) -> list:
"""获取所有自定义运营商(自动分页)"""
all_data = []
start = 0
limit = 100
while True:
resp = self._api_call("custom_isp", "show", {
"TYPE": "total,data",
"limit": f"{start},{limit}",
"ORDER_BY": "",
"ORDER": ""
})
if not resp or resp.get("Result") != 30000:
break
data = resp.get("Data", {}).get("data", [])
if not data:
break
all_data.extend(data)
if len(data) < limit:
break
start += limit
return all_data
def delete_operator_by_name(self, name: str) -> int:
"""删除指定名称的所有运营商条目,返回删除数量"""
isp_list = self.get_custom_isp_list()
deleted = 0
for isp in isp_list:
if isp.get("name") == name:
isp_id = isp.get("id")
resp = self._api_call("custom_isp", "del", {"id": isp_id})
if resp and resp.get("Result") == 30000:
print(f"删除运营商 '{name}' (ID: {isp_id}) 成功")
deleted += 1
else:
print(f"删除运营商 '{name}' (ID: {isp_id}) 失败: {resp}")
return deleted
def add_operator(self, name: str, ipgroup: str, comment: str = "") -> bool:
"""添加运营商,ipgroup 为逗号分隔的 CIDR 字符串"""
resp = self._api_call("custom_isp", "add", {
"name": name,
"ipgroup": ipgroup,
"comment": comment
})
if resp and resp.get("Result") == 30000:
count = len(ipgroup.split(','))
print(f"上传 {name} 成功 (本次 {count} 条)")
return True
else:
print(f"上传 {name} 失败: {resp}")
return False
def filter_ipv4_cidr(file_path: str) -> list:
"""从 geoip_cn.txt 中提取 IPv4 CIDR(过滤 IPv6)"""
ipv4_list = []
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
try:
net = ipaddress.ip_network(line, strict=False)
if net.version == 4:
ipv4_list.append(line)
except ValueError:
continue
print(f"从 {file_path} 提取到 {len(ipv4_list)} 条 IPv4 CIDR")
return ipv4_list
def chunk_list(lst, chunk_size=5000):
"""将列表切分为指定大小的块"""
for i in range(0, len(lst), chunk_size):
yield lst[i:i+chunk_size]
def upload_to_ikuai(ipv4_file: str):
"""上传 IPv4 国内 IP 到爱快自定义运营商"""
if not os.path.exists(ipv4_file):
print(f"警告:文件 {ipv4_file} 不存在,跳过上传。")
return
ipv4_cidrs = filter_ipv4_cidr(ipv4_file)
if not ipv4_cidrs:
print("没有有效的 IPv4 CIDR,退出上传。")
return
api = IKuaiAPI(IKUAI_HOST_URL, IKUAI_USERNAME, IKUAI_PASSWORD)
if not api.login():
print("登录爱快失败,终止上传。")
sys.exit(1)
print(f"正在删除已有运营商 '{OPERATOR_NAME}'...")
deleted = api.delete_operator_by_name(OPERATOR_NAME)
if deleted > 0:
print(f"已删除 {deleted} 个同名运营商条目")
success_chunks = 0
total_chunks = (len(ipv4_cidrs) + 4999) // 5000
for idx, chunk in enumerate(chunk_list(ipv4_cidrs, 5000), 1):
ipgroup = ",".join(chunk)
print(f"正在上传第 {idx}/{total_chunks} 组...")
if api.add_operator(OPERATOR_NAME, ipgroup):
success_chunks += 1
else:
print("上传中断,请检查爱快状态。")
sys.exit(1)
print(f"爱快运营商 IP 更新完成,共上传 {success_chunks} 组(总计 {len(ipv4_cidrs)} 条)")
def restart_mosdns():
"""重启 MosDNS 服务(根据实际情况调整启动命令)"""
os.chdir(MOSDNS_DIR)
mosdns_bin = "./mosdns"
if not os.path.isfile(mosdns_bin) or not os.access(mosdns_bin, os.X_OK):
print(f"错误:{mosdns_bin} 不存在或不可执行")
return
print("正在停止 MosDNS 进程...")
try:
subprocess.run(["pkill", "-f", "mosdns.*start"], check=False)
time.sleep(2)
except Exception as e:
print(f"停止进程出错(可忽略): {e}")
print("正在启动 MosDNS...")
# 根据您实际启动命令修改,这里示例为后台运行
cmd = ["./mosdns", "start", "-c", "config.yaml"]
try:
subprocess.Popen(cmd, stdout=open("mosdns.log", "a"), stderr=subprocess.STDOUT)
print("MosDNS 已启动")
except Exception as e:
print(f"启动 MosDNS 失败: {e}")
def main():
print("=== 开始更新 MosDNS 规则与爱快自定义运营商 ===")
# 1. 准备目录
prepare_directories()
# 2. 下载并解包
download_and_unpack()
# 3. 上传 IPv4 CN 到爱快
ipv4_file = os.path.join(TEMP_DOWNLOAD_DIR, "geoip_cn.txt")
upload_to_ikuai(ipv4_file)
# 4. 清理临时目录
print("清理临时文件...")
shutil.rmtree(TEMP_DOWNLOAD_DIR, ignore_errors=True)
# 5. 重启 MosDNS(可选)
if RESTART_MOSDNS:
restart_mosdns()
print("=== 全部操作完成 ===")
if __name__ == "__main__":
main()
更新完后,现在可以在多线负载的自定义营运商中,看到新出现的中国大陆。
然后开始设置多线负载,新建一个规则:
运营商为全部,开启WAN2 线路,其他默认。保存
再新建一个规则:
运营商为中国大陆,开启WAN1线路,其他默认。保存
现在,多线负载就设置好了
现在设置端口分流,在流控分流-端口分流中新建一条规则:
线路为WAN1,源地址填入OpenWRT的WAN口地址:172.25.52.254,其他默认,然后保存。该规则用于防止OpenWRT发生回环
再新建一条规则:
线路为WAN1,源地址填入DNS服务器的地址:172.25.52.253和172.25.52.252,目的地址填入国内DNS服务商地址(见下方)其他默认,然后保存。该规则强制国内DNS请求走国内
223.5.5.5 阿里云
223.6.6.6 阿里云
120.53.53.53 腾讯云
1.12.12.12 腾讯云
119.29.29.29 腾讯云
再新建一条规则:
线路为WAN2,源地址填入DNS服务器的地址:172.25.52.253和172.25.52.252,目的地址填入国外DNS服务商地址(见下方)其他默认,然后保存。该规则强制国外DNS请求走国外
104.16.133.229 cloudflare
104.16.132.229 cloudflare
1.1.1.1 cloudflare
1.0.0.1 cloudflare
8.8.8.8 google
8.8.4.4 google
9.9.9.9 quad9
149.112.112.112 quad9
94.140.15.15 adguard
94.140.14.14 adguard
有其他DNS地址也可以往里面补充,我目前只填了我用的,爱快的设置部分基本就完了。
OpenWRT配置
只讲主要的
通过地址:http://192.168.240.1 登录 istoreos
设置WAN口IP为:172.25.52.254
自定义DNS为:172.25.52.253
设置OpenClash插件
插件设置-模式设置,使用Redir-Host(TUN)模式。
插件设置-DNS设置中, 禁用本地DNS劫持。
覆写设置-DNS设置中,启用自定义DNS设置,并将NameServer、FallBack、Default-NameServer都添加一条规则:
服务器地址:10.61.6.253
服务器端口:53
服务器类型:UDP
创建好后,禁用其他规则,让OpenClash的DNS查询也走ADGuard Home。(记得应用设置)
代理模式可以设置为全局模式,因为在爱快已完成了分流。不放心也可以继续使用规则模式。
测试
本机电脑设置DNS为10.61.6.253,然后进行国内、国外网站访问测试,如果正常,说明一切都设置好了,可以将爱快的DHCP服务器中的DNS改为10.61.6.253了。
如果不正常,重新阅读本文。看看是不是哪里漏了什么

Comments NOTHING