原分流逻辑
客户端请求DNS解析 -> ROS(转发DNS请求) -> mosdns 国内域名转发到国内DNS, 国外域名转发给mihomo(real ip, 自建无污染DOH服务)
客户端向IP发起访问 -> ROS(根据cn_ip_cidr地址列表判断, 匹配则直连, 否则转发到mihomo机器) -> clash
加入oxidns后的逻辑
客户端请求DNS解析 -> ROS(转发DNS请求) -> oxidns根据geosite/geoip解析 -> 国外地址写入ROS的address-list
客户端向IP发起访问 -> ROS(根据oxidns_proxy_v4地址列表判断, 匹配就转发mihomo, 不匹配直连)
优势
- 使用oxidns后, ROS上的国内外判断的地址列表更少, oxidns_proxy_v4里的地址没有几条, 基本只有当前正在访问的网页或地址, ROS上理论的路由匹配运算更少, 可能更快
- ROS上无需维护国内ip地址列表, 经历过一次某个地址列表把HK的阿里云IP设置成了国内, 如果机器的线路不太好, 可能需要走梯子. <- 这是常有的情况, 服务器用了一段时间就开始限制带宽, 慢的要死(甚至是60+/月的轻量服务器)
install.sh
适合PVE LXC进行一键安装, 适合Debian的LXC, 理论上Debian虚拟机也可以, 脚本里没有使用sudo, 因此适合用户为root, 或者 sudo ./install.sh
PS: 官方的仓库可能发布 1.1.4 版本, 但实际的release deb文件为 1.1.4-1, 导致not found, 因此是下载二进制压缩包进行安装, 直接把官方的安装脚本内容整合进来了. 路径则是参考deb的目录结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
#!/bin/sh
# OxiDNS 一键部署脚本 - 整合官方安装逻辑 + Debian 包目录约定
# 用法: 以 root 身份运行 (LXC 容器无需 sudo)
#
# 安装方式: 内嵌官方安装逻辑, GitHub 下载链接加 hub.linx.run 前缀加速
# 路径布局 (与 Debian 包默认约定一致):
# /usr/bin/oxidns <- 二进制
# /etc/oxidns/
# config.yaml <- 配置文件
# rules/ <- 规则文件 (geosite.dat, geoip.dat, adguard.txt 等)
# /var/lib/oxidns/ <- -d 工作目录
# webui/ -> /usr/share/oxidns/webui <- WebUI
# oxidns.log <- 日志
# dns_cache.dump <- 缓存持久化
# systemd 服务: -c /etc/oxidns/config.yaml -d /var/lib/oxidns
set -eu
# ============================================================
# 环境变量 (与官方安装脚本一致)
# ============================================================
REPO="${OXIDNS_REPO:-svenshi/oxidns}"
VERSION="${OXIDNS_VERSION:-latest}"
TARGET="${OXIDNS_TARGET:-}"
GITHUB_MIRROR="https://ghfast.top"
OXIDNS_ETC="/etc/oxidns"
OXIDNS_VAR="/var/lib/oxidns"
OXIDNS_RULES="${OXIDNS_ETC}/rules"
OXIDNS_SHARE="/usr/share/oxidns"
# ============================================================
# 官方安装脚本的辅助函数
# ============================================================
log() {
printf '%s\n' "$*"
}
warn() {
printf 'warning: %s\n' "$*" >&2
}
err() {
printf 'error: %s\n' "$*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || err "required command not found: $1"
}
is_truthy() {
case "$1" in
1|true|TRUE|yes|YES|on|ON) return 0 ;;
*) return 1 ;;
esac
}
is_root() {
[ "$(id -u 2>/dev/null || printf '1')" = "0" ]
}
detect_target() {
os="$(uname -s)"
arch="$(uname -m)"
case "$os:$arch" in
Linux:x86_64|Linux:amd64)
printf 'x86_64-unknown-linux-musl'
;;
Linux:aarch64|Linux:arm64)
printf 'aarch64-unknown-linux-musl'
;;
Linux:i386|Linux:i686)
printf 'i686-unknown-linux-musl'
;;
Linux:armv7l|Linux:armv7)
printf 'arm-unknown-linux-musleabihf'
;;
Darwin:x86_64|Darwin:amd64)
printf 'x86_64-apple-darwin'
;;
Darwin:arm64|Darwin:aarch64)
printf 'aarch64-apple-darwin'
;;
FreeBSD:x86_64|FreeBSD:amd64)
printf 'x86_64-unknown-freebsd'
;;
*)
err "unsupported platform: $os $arch. Set OXIDNS_TARGET to override."
;;
esac
}
download() {
url="$1"
out="$2"
# 为 GitHub 下载链接添加加速前缀
case "$url" in
https://github.com/*)
url="${GITHUB_MIRROR}/${url}"
;;
esac
if command -v curl >/dev/null 2>&1; then
curl -fL --retry 3 --retry-delay 2 -o "$out" "$url"
elif command -v wget >/dev/null 2>&1; then
wget -O "$out" "$url"
else
err "curl or wget is required to download OxiDNS"
fi
}
contains_path() {
dir="$1"
case ":${PATH:-}:" in
*:"$dir":*) return 0 ;;
*) return 1 ;;
esac
}
# ============================================================
# 主流程
# ============================================================
echo "============================================"
echo " OxiDNS 部署脚本 (集成安装 + Debian 目录约定)"
echo "============================================"
is_root || err "此脚本需要以 root 身份运行"
# --- 检测目标平台 ---
if [ -z "$TARGET" ]; then
TARGET="$(detect_target)"
fi
echo " 目标平台: ${TARGET}"
case "$TARGET" in
*windows*|*msvc*)
err "Windows targets are installed with scripts/install.ps1"
;;
*)
ARCHIVE_EXT="tar.gz"
;;
esac
# [1/7] 更新系统并安装依赖
echo "[1/7] 更新系统并安装依赖..."
apt-get update -y
apt-get install -y curl ca-certificates
# [2/7] 下载并解压 OxiDNS (内嵌官方安装逻辑 + 加速)
echo "[2/7] 下载 OxiDNS..."
ASSET="oxidns-$TARGET.$ARCHIVE_EXT"
if [ "$VERSION" = "latest" ]; then
URL="https://github.com/$REPO/releases/latest/download/$ASSET"
else
URL="https://github.com/$REPO/releases/download/$VERSION/$ASSET"
fi
need_cmd tar
need_cmd mkdir
need_cmd cp
need_cmd chmod
need_cmd mv
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/oxidns-install.XXXXXX")"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT HUP INT TERM
ARCHIVE="$TMP_DIR/$ASSET"
UNPACK_DIR="$TMP_DIR/unpack"
log " 下载 $ASSET ($VERSION) ..."
download "$URL" "$ARCHIVE"
mkdir -p "$UNPACK_DIR"
tar -xzf "$ARCHIVE" -C "$UNPACK_DIR"
[ -f "$UNPACK_DIR/oxidns" ] || err "archive does not contain oxidns"
[ -f "$UNPACK_DIR/config.yaml" ] || err "archive does not contain config.yaml"
# [3/7] 安装到 Debian 包目录布局
echo "[3/7] 安装到 Debian 包目录布局..."
# 创建目录结构
mkdir -p "${OXIDNS_ETC}"
mkdir -p "${OXIDNS_VAR}"
mkdir -p "${OXIDNS_RULES}"
mkdir -p "${OXIDNS_SHARE}"
# 安装二进制到 /usr/bin/oxidns (deb 包默认位置)
cp "$UNPACK_DIR/oxidns" /usr/bin/oxidns.tmp
chmod 755 /usr/bin/oxidns.tmp
mv /usr/bin/oxidns.tmp /usr/bin/oxidns
echo " 二进制已安装到 /usr/bin/oxidns"
# 保留默认配置为示例
if [ -f "${OXIDNS_ETC}/config.yaml" ]; then
cp "$UNPACK_DIR/config.yaml" "${OXIDNS_ETC}/config.yaml.example"
echo " 默认配置已保存为 ${OXIDNS_ETC}/config.yaml.example"
else
cp "$UNPACK_DIR/config.yaml" "${OXIDNS_ETC}/config.yaml"
echo " 默认配置已安装到 ${OXIDNS_ETC}/config.yaml"
fi
# 安装 LICENSE
if [ -f "$UNPACK_DIR/LICENSE" ]; then
cp "$UNPACK_DIR/LICENSE" "${OXIDNS_ETC}/LICENSE"
fi
# 安装 WebUI 到 /usr/share/oxidns/webui + 软链接到 /var/lib/oxidns/webui
if [ -d "$UNPACK_DIR/webui" ]; then
rm -rf "${OXIDNS_SHARE}/webui"
cp -R "$UNPACK_DIR/webui" "${OXIDNS_SHARE}/webui"
echo " WebUI 已安装到 ${OXIDNS_SHARE}/webui"
rm -rf "${OXIDNS_VAR}/webui"
ln -s "${OXIDNS_SHARE}/webui" "${OXIDNS_VAR}/webui"
echo " 已创建软链接 ${OXIDNS_VAR}/webui -> ${OXIDNS_SHARE}/webui"
fi
# 清理旧的一键脚本安装目录 (如有)
if [ -d /opt/oxidns ]; then
echo " 清理旧安装目录 /opt/oxidns ..."
rm -rf /opt/oxidns
fi
# 清理旧的 /usr/local/bin/oxidns 软链接 (如有)
if [ -L /usr/local/bin/oxidns ]; then
rm -f /usr/local/bin/oxidns
echo " 已移除旧软链接 /usr/local/bin/oxidns"
fi
# [4/7] 部署自定义配置文件
echo "[4/7] 部署配置文件..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null || dirname "$0")" && pwd)"
if [ -f "${SCRIPT_DIR}/config.yaml" ]; then
# 备份已有配置
if [ -f "${OXIDNS_ETC}/config.yaml" ]; then
cp "${OXIDNS_ETC}/config.yaml" "${OXIDNS_ETC}/config.yaml.bak.$(date +%Y%m%d%H%M%S)"
fi
cp "${SCRIPT_DIR}/config.yaml" "${OXIDNS_ETC}/config.yaml"
echo " config.yaml 已部署到 ${OXIDNS_ETC}/"
else
echo " 警告: 未找到 config.yaml, 请手动放置到 ${OXIDNS_ETC}/"
fi
if [ -f "${SCRIPT_DIR}/hosts.txt" ]; then
cp "${SCRIPT_DIR}/hosts.txt" "${OXIDNS_RULES}/hosts.txt"
echo " hosts.txt 已部署到 ${OXIDNS_RULES}/"
else
touch "${OXIDNS_RULES}/hosts.txt"
echo " 已创建空的 hosts.txt"
fi
# [5/7] 校验配置
echo "[5/7] 校验配置文件..."
if /usr/bin/oxidns check -c "${OXIDNS_ETC}/config.yaml" -d "${OXIDNS_VAR}" >/dev/null 2>&1; then
echo " 配置校验通过"
else
echo " 配置校验失败, 请检查配置文件"
fi
# [6/7] 配置 systemd 服务
echo "[6/7] 配置 systemd 服务..."
cat > /etc/systemd/system/oxidns.service <<EOF
[Unit]
Description=OxiDNS - High Performance DNS Policy Engine
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/oxidns start -c ${OXIDNS_ETC}/config.yaml -d ${OXIDNS_VAR}
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable oxidns
# [7/7] 启动服务
echo "[7/7] 启动 OxiDNS 服务..."
systemctl restart oxidns
sleep 2
systemctl status oxidns --no-pager || true
echo ""
echo "============================================"
echo " OxiDNS 部署完成!"
echo "============================================"
echo ""
echo " 二进制: /usr/bin/oxidns"
echo " 配置文件: ${OXIDNS_ETC}/config.yaml"
echo " 规则目录: ${OXIDNS_RULES}"
echo " 运行目录: ${OXIDNS_VAR}"
echo " 日志查看: journalctl -u oxidns -f"
echo " 状态查看: systemctl status oxidns"
echo " WebUI: http://<本机IP>:9088"
echo ""
echo " 首次启动会自动下载 geosite.dat / geoip.dat / 广告规则,"
echo " 请耐心等待下载完成。"
|
config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# OxiDNS 配置文件
# 部署目标: PVE LXC (Debian) + RouterOS 联动
# 功能: 国内外分流 DNS + 广告过滤 + hosts 规则 + 非国内 IP 同步 RouterOS address-list
runtime:
worker_threads: 4
api:
http:
listen: "0.0.0.0:9088"
auth:
type: basic
username: "admin"
password: "oxidns"
webui:
root: "./webui"
index: "index.html"
log:
level: info
file: "./oxidns.log"
rotation:
type: daily
max_files: 7
# ============================================================
# 规则订阅下载
# ============================================================
plugins:
# --- 订阅下载: geosite.dat / geoip.dat / 广告规则 ---
- tag: subscription_download
type: download
args:
timeout: 120s
startup_if_missing: true
downloads:
- url: "https://ghfast.top/https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat"
dir: "/etc/oxidns/rules"
filename: "geosite.dat"
- url: "https://ghfast.top/https://github.com/v2fly/v2ray-core/releases/latest/download/geoip.dat"
dir: "/etc/oxidns/rules"
filename: "geoip.dat"
- url: "https://ghfast.top/https://raw.githubusercontent.com/AdguardTeam/HostlistsRegistry/refs/heads/main/assets/filter_1.txt"
dir: "/etc/oxidns/rules"
filename: "adguard.txt"
- url: "https://anti-ad.net/easylist.txt"
dir: "/etc/oxidns/rules"
filename: "antiad.txt"
# --- 定向刷新 provider (热重载) ---
- tag: reload_rule_providers
type: reload_provider
args:
- "$geosite_cn"
- "$geosite_non_cn"
- "$geoip_cn"
- "$ad_rules"
# --- 订阅刷新 sequence ---
- tag: subscription_refresh
type: sequence
args:
- exec: "$subscription_download"
- exec: "$reload_rule_providers"
# --- 定时任务: 每 6 小时自动更新规则 ---
- tag: subscription_cron
type: cron
args:
timezone: "Asia/Shanghai"
jobs:
- name: refresh_rule_subscriptions
interval: 6h
executors:
- "$subscription_refresh"
# ============================================================
# Provider: 规则集
# ============================================================
# --- 国内域名规则 ---
- tag: geosite_cn
type: geosite
args:
file: "/etc/oxidns/rules/geosite.dat"
selectors:
- "cn"
# --- 非国内域名规则 ---
- tag: geosite_non_cn
type: geosite
args:
file: "/etc/oxidns/rules/geosite.dat"
selectors:
- "geolocation-!cn"
# --- 国内 IP 规则 ---
- tag: geoip_cn
type: geoip
args:
file: "/etc/oxidns/rules/geoip.dat"
selectors:
- "cn"
# --- 广告规则 (AdGuard 格式 + anti-ad) ---
- tag: ad_rules
type: adguard_rule
args:
files:
- "/etc/oxidns/rules/adguard.txt"
- "/etc/oxidns/rules/antiad.txt"
# ============================================================
# 本地 hosts 规则
# ============================================================
- tag: local_hosts
type: hosts
args:
files:
- "/etc/oxidns/rules/hosts.txt"
short_circuit: true
# ============================================================
# 广告拦截分支
# ============================================================
- tag: blocked
type: sequence
args:
- exec: "black_hole 0.0.0.0 ::"
- exec: accept
# ============================================================
# 缓存
# ============================================================
- tag: cache_main
type: cache
args:
size: 8192
short_circuit: true
lazy_cache_ttl: 120
cache_negative: true
max_negative_ttl: 300
max_positive_ttl: 600
dump_file: "./dns_cache.dump"
dump_interval: 600
# ============================================================
# 上游 DNS
# ============================================================
# --- 国内上游: 阿里 / 114 / 腾讯 ---
- tag: forward_cn
type: forward
args:
concurrent: 2
upstreams:
- addr: "udp://223.5.5.5:53"
timeout: 3s
- addr: "udp://114.114.114.114:53"
timeout: 3s
- addr: "udp://119.29.29.29:53"
timeout: 3s
# --- 国外上游: 走 10.10.0.254:1053 ---
- tag: forward_overseas
type: forward
args:
upstreams:
- addr: "udp://10.10.0.254:1053"
timeout: 5s
# ============================================================
# RouterOS address-list 联动
# ============================================================
# --- 非国内 IP 写入 RouterOS address-list ---
- tag: ros_policy
type: ros_address_list
args:
address: "<ROS IP>:8728"
username: "<ROS username>"
password: "<ROS password>"
async: true
address_list4: "oxidns_proxy_v4"
address_list6: "oxidns_proxy_v6"
comment_prefix: "oxidns"
min_ttl: 60
max_ttl: 1800
fixed_ttl: 300
cleanup_on_shutdown: true
# ============================================================
# 主策略 sequence
# ============================================================
- tag: seq_main
type: sequence
args:
# 1. 本地 hosts 优先
- exec: "$local_hosts"
# 2. 广告域名拦截
- matches: "question $ad_rules"
exec: goto blocked
# 3. 缓存查询
- exec: "$cache_main"
# 4. 国内域名 -> 国内上游
- matches:
- "!has_resp"
- "qname $geosite_cn"
exec: "$forward_cn"
# 5. 非国内域名 -> 国外上游 + RouterOS 联动
- matches:
- "!has_resp"
- "qname $geosite_non_cn"
exec: "$forward_overseas"
# 非国内域名解析后, 把 IP 写入 RouterOS address-list
- matches:
- "has_resp"
- "qname $geosite_non_cn"
exec: "$ros_policy"
# 6. 兜底: 未匹配到 geosite 规则的域名
# 先走国内上游尝试解析
- matches: "!has_resp"
exec: "$forward_cn"
# 7. 国内上游解析成功, 判断响应 IP 是否为非国内 IP
# 如果是非国内 IP, 也写入 RouterOS address-list
- matches:
- "has_resp"
- "!resp_ip $geoip_cn"
exec: "$ros_policy"
# ============================================================
# DNS 服务器入口
# ============================================================
- tag: udp_server_53
type: udp_server
args:
entry: "seq_main"
listen: ":53"
- tag: tcp_server_53
type: tcp_server
args:
entry: "seq_main"
listen: ":53"
|
PS: <ROS IP> <ROS username> <ROS password>需要修改. forward_overseas 为mihomo的dns监听地址
未来展望
一种可能的分流方案.
在oxidns上配置多个分流域名列表, 例如steam, google, AI, microsoft等, 每个列表匹配到之后均联动映射到RouterOS的address-list. ROS根据address-list去把流量路由到不同的机器.
这里的每台机器可以只运行某个区域的节点.
比如 LXC-HK上的mihomo用过滤器加载所有HK的节点, LXC-US则只加载US节点, LXC-AI则只加载家宽节点.
此方案唯一的缺点就是要在局域网开好几台服务器, 维护不方便, 而且不好统一查看连接信息.
应该可以通过指定docker容器的IP的方式, 让这些mihomo都运行在docker容器里