使用oxidns来简化原有RouterOS上分流逻辑

原分流逻辑

客户端请求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, 不匹配直连)

优势

  1. 使用oxidns后, ROS上的国内外判断的地址列表更少, oxidns_proxy_v4里的地址没有几条, 基本只有当前正在访问的网页或地址, ROS上理论的路由匹配运算更少, 可能更快
  2. 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容器里

Licensed under CC BY-NC-SA 4.0
记录平时瞎折腾遇到的各种问题, 方便查找