fix: preserve IPv6 settings for HY2 nodes

This commit is contained in:
Arvin
2026-07-30 16:31:55 +08:00
parent e1c9ee49f0
commit d2442778d8
6 changed files with 259 additions and 9 deletions
+29
View File
@@ -71,6 +71,7 @@ bash install.sh
clashctl secret 123 🔐 设置密钥
📌 高级
clashctl lan 🏠 局域网代理管理
clashctl ipv6 🌐 IPv6 / IPv6-only 节点管理
clashctl tun 🧪 Tun 模式管理(需root方式安装)
clashctl boot 🚦 开机代理接管管理
clashctl mixin 🧩 Mixin 配置管理
@@ -134,6 +135,32 @@ clashctl lan off
------
## 🌐 IPv6 / Hysteria2
Mihomo 支持 Hysteria2。若节点服务端只有 IPv6 地址,需要同时启用内核 IPv6 与 DNS AAAA:
```bash
clashctl config kernel mihomo
clashctl ipv6 on
clashctl ipv6 status
```
其他可用操作:
```bash
clashctl ipv6 off
clashctl ipv6 auto
```
- `on`:同时写入 `ipv6: true` 和 `dns.ipv6: true`。
- `off`:同时关闭内核 IPv6 与 DNS AAAA。
- `auto`:保留订阅原有的 IPv6 设置;订阅未配置时继续使用兼容默认值。
- 使用 IPv6-only 节点前,宿主机仍需具备可用的 IPv6 默认路由。HY2 使用 UDP / QUIC,网络或防火墙也必须允许相应 UDP 流量。
也可以在 `.env` 中设置 `CLASH_IPV6=true`、`false` 或 `auto`,然后执行 `clashctl config regen`。
------
OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。
## 🧰 常用管理命令
@@ -297,12 +324,14 @@ CLASH_DOWNLOAD_BASE=https://github.com/WindSpiritSR/clash/releases/download
CLASH_BUNDLED_ASSET_ENABLED=true
CLASH_SHELL_AUTO_RESTORE_PROXY=true
CLASH_PREDOWNLOAD_GEO=true
CLASH_IPV6=auto
```
按需设置即可,不需要每项都写。
- `CLASH_SHELL_AUTO_RESTORE_PROXY`:控制登录 Shell 是否自动恢复上次 `clashon` 写入的代理变量。默认 `true` 保持兼容;如果不希望 SSH 远程登录后自动带上 `http_proxy` / `https_proxy`,设为 `false`,之后仍可手动执行 `clashon`。
- `CLASH_PREDOWNLOAD_GEO`:控制安装期是否预下载 GEO 数据。默认 `true`,会提前下载 `Country.mmdb`、`geoip.metadb`、`GeoIP.dat`、`GeoSite.dat` 等规则分流常用资源;临时部署、只想先跑起来时可设为 `false` 跳过安装期预下载。注意:当最终运行配置实际使用 `GEOIP` 规则时,启动前仍会按需准备 `Country.mmdb`,否则 Mihomo 无法可靠加载该配置。
- `CLASH_IPV6`:可设为 `true`、`false` 或 `auto`。`true` 同时启用内核 IPv6 与 DNS AAAA,适用于 IPv6-only 节点;`auto` 保留订阅中的 IPv6 设置。
#### GitHub 下载加速
+82
View File
@@ -35,6 +35,7 @@ Usage:
mixin 🧩 Mixin 配置管理
relay 🔗 多跳节点管理
lan 🏠 局域网代理管理
ipv6 🌐 IPv6 / IPv6-only 节点管理
📦 Subscription:
@@ -47,6 +48,7 @@ Usage:
clashui 🕹️ 查看 Web 控制台
secret 🔑 查看或设置 Web 密钥
lan on|off|status 🏠 开启 / 关闭局域网代理
ipv6 on|off|auto|status 🌐 管理内核与 DNS IPv6
🧪 Transparent proxy:
tun 🧪 Tun 模式管理
@@ -3083,6 +3085,7 @@ cmd_ui_help_summary() {
printf ' %-18s %s\n' "clashctl ls" "📜 查看订阅列表"
echo "📌 高级"
printf ' %-18s %s\n' "clashctl lan" "🏠 局域网代理管理"
printf ' %-18s %s\n' "clashctl ipv6" "🌐 IPv6 / IPv6-only 节点管理"
printf ' %-18s %s\n' "clashctl tun" "🧪 Tun 模式管理"
printf ' %-18s %s\n' "clashctl mixin" "🧩 Mixin 配置管理"
printf ' %-18s %s\n' "clashctl sub" "🧩 订阅高级管理(启用 / 禁用 / 重命名 / 删除)"
@@ -4190,6 +4193,84 @@ cmd_lan() {
esac
}
print_ipv6_status() {
local mode mode_text kernel_ipv6 dns_ipv6 kernel
mode="$(config_ipv6_mode)"
kernel_ipv6="$(runtime_config_ipv6_enabled 2>/dev/null || echo "等待运行配置生成")"
dns_ipv6="$(runtime_config_dns_ipv6_enabled 2>/dev/null || echo "等待运行配置生成")"
kernel="$(runtime_kernel_type 2>/dev/null || echo unknown)"
case "$mode" in
true) mode_text="已开启" ;;
false) mode_text="已关闭" ;;
*) mode_text="自动(保留订阅设置)" ;;
esac
ui_title "🌐 IPv6"
ui_kv "🔧" "配置模式" "$mode_text"
ui_kv "🚀" "当前内核" "$kernel"
ui_kv "🌐" "内核 IPv6" "$kernel_ipv6"
ui_kv "🔎" "DNS AAAA" "$dns_ipv6"
if [ "$kernel" != "mihomo" ]; then
ui_warn "Hysteria2 建议使用 Mihomo:clashctl config kernel mihomo"
fi
ui_blank
}
cmd_ipv6() {
local action
prepare
action="${1:-status}"
case "$action" in
on|enable)
set_config_ipv6_mode true
regenerate_config
apply_runtime_change_after_config_mutation
success "IPv6 与 DNS AAAA 已开启"
print_ipv6_status
print_config_apply_feedback
;;
off|disable)
set_config_ipv6_mode false
regenerate_config
apply_runtime_change_after_config_mutation
success "IPv6 与 DNS AAAA 已关闭"
print_ipv6_status
print_config_apply_feedback
;;
auto|reset)
set_config_ipv6_mode auto
regenerate_config
apply_runtime_change_after_config_mutation
success "IPv6 已恢复为自动模式"
print_ipv6_status
print_config_apply_feedback
;;
status)
print_ipv6_status
ui_next "IPv6-only / Hysteria2 节点不可用时执行:clashctl ipv6 on"
ui_blank
;;
-h|--help|help)
echo "📜 用法:"
echo " clashctl ipv6 status"
echo " clashctl ipv6 on"
echo " clashctl ipv6 off"
echo " clashctl ipv6 auto"
echo
echo "🌐 说明:"
echo " on 同时启用内核 IPv6 与 DNS AAAA"
echo " off 同时关闭内核 IPv6 与 DNS AAAA"
echo " auto 保留订阅中的 IPv6 设置,缺省时沿用兼容默认值"
;;
*)
die_usage "未知的 ipv6 子命令:$action" "clashctl ipv6 on|off|auto|status"
;;
esac
}
print_profile_use_feedback() {
local profile="$1"
@@ -7872,6 +7953,7 @@ case "$cmd" in
dev) cmd_dev "$@" ;;
config) cmd_config "$@" ;;
lan) cmd_lan "$@" ;;
ipv6) cmd_ipv6 "$@" ;;
mixin) cmd_mixin "$@" ;;
relay) cmd_relay "$@" ;;
profile) cmd_profile "$@" ;;
+26
View File
@@ -1796,6 +1796,32 @@ runtime_config_allow_lan() {
esac
}
runtime_config_ipv6_enabled() {
local file
local value
file="$(runtime_config_file)"
[ -s "$file" ] || return 1
value="$("$(yq_bin)" eval '.ipv6' "$file" 2>/dev/null | head -n 1)"
case "$value" in
false) echo "false" ;;
*) echo "true" ;;
esac
}
runtime_config_dns_ipv6_enabled() {
local file
local value
file="$(runtime_config_file)"
[ -s "$file" ] || return 1
value="$("$(yq_bin)" eval '.dns.ipv6' "$file" 2>/dev/null | head -n 1)"
case "$value" in
true) echo "true" ;;
*) echo "false" ;;
esac
}
runtime_port_value_is_valid() {
local port="${1:-}"
+13 -1
View File
@@ -181,6 +181,17 @@ _clash_for_linux_complete_lan() {
fi
}
_clash_for_linux_complete_ipv6() {
local cur="$1"
local rel_index="$2"
COMPREPLY=()
if [ "$rel_index" -eq 1 ]; then
_clash_for_linux_add_matches "$cur" on off auto status enable disable reset help -h --help
fi
}
_clash_for_linux_complete_mixin() {
local cur="$1"
local rel_index="$2"
@@ -315,7 +326,7 @@ _clash_for_linux_complete_top_level() {
COMPREPLY=()
_clash_for_linux_add_matches "$cur" \
add use ls health select on off status status-next \
boot log logs doctor ui secret tun dev config lan mixin \
boot log logs doctor ui secret tun dev config lan ipv6 mixin \
relay profile sub proxy upgrade update completion help \
-h --help
}
@@ -387,6 +398,7 @@ _clash_for_linux_complete_command() {
boot) _clash_for_linux_complete_boot "$cur" "$rel_index" "$arg1" ;;
config) _clash_for_linux_complete_config "$cur" "$rel_index" "$arg1" ;;
lan) _clash_for_linux_complete_lan "$cur" "$rel_index" ;;
ipv6) _clash_for_linux_complete_ipv6 "$cur" "$rel_index" ;;
mixin) _clash_for_linux_complete_mixin "$cur" "$rel_index" ;;
relay) _clash_for_linux_complete_relay "$cur" "$rel_index" "$arg1" ;;
sub) _clash_for_linux_complete_sub "$cur" "$rel_index" "$arg1" ;;
+60 -2
View File
@@ -276,6 +276,46 @@ config_bool_env_value() {
esac
}
config_ipv6_mode() {
local value
value="${CLASH_IPV6:-}"
[ -n "${value:-}" ] || value="$(read_env_value "CLASH_IPV6" 2>/dev/null || true)"
case "${value:-auto}" in
true|1|yes|on)
echo "true"
;;
false|0|no|off)
echo "false"
;;
auto|"")
echo "auto"
;;
*)
echo "auto"
;;
esac
}
set_config_ipv6_mode() {
local mode="$1"
case "$mode" in
true|false)
write_env_value "CLASH_IPV6" "$mode"
export CLASH_IPV6="$mode"
;;
auto)
unset_env_value "CLASH_IPV6" || true
unset CLASH_IPV6 2>/dev/null || true
;;
*)
die "IPv6 模式只允许 true / false / auto"
;;
esac
}
tun_auto_route() {
config_bool_env_value "CLASH_TUN_AUTO_ROUTE" "true"
}
@@ -801,7 +841,8 @@ normalize_runtime_config() {
local file="$1"
local mixed_port controller tun_enable_value tun_stack_value dns_port_value controller_secret_value
local tun_auto_route_value tun_auto_redirect_value tun_strict_route_value tun_dns_hijack_value
local dashboard_dir_value dashboard_url_value allow_lan_value
local dashboard_dir_value dashboard_url_value allow_lan_value ipv6_mode_value
local ipv6_value dns_ipv6_value
local resolved_ports err_file output
[ -s "$file" ] || die "待规范化的配置文件不存在:$file"
@@ -822,11 +863,27 @@ normalize_runtime_config() {
dashboard_dir_value="$(runtime_dashboard_dir)"
dashboard_url_value="$DEFAULT_DASHBOARD_UI_URL"
allow_lan_value="$(config_allow_lan 2>/dev/null || echo true)"
ipv6_mode_value="$(config_ipv6_mode)"
case "$ipv6_mode_value" in
true|false)
ipv6_value="$ipv6_mode_value"
dns_ipv6_value="$ipv6_mode_value"
;;
*)
ipv6_value="$("$(yq_bin)" eval '.ipv6' "$file" 2>/dev/null | head -n 1 || true)"
dns_ipv6_value="$("$(yq_bin)" eval '.dns.ipv6' "$file" 2>/dev/null | head -n 1 || true)"
case "$ipv6_value" in true|false) ;; *) ipv6_value="true" ;; esac
case "$dns_ipv6_value" in true|false) ;; *) dns_ipv6_value="false" ;; esac
;;
esac
err_file="$(mktemp)"
if ! mixed_port="$mixed_port" \
controller="$controller" \
allow_lan_value="$allow_lan_value" \
ipv6_value="$ipv6_value" \
dns_ipv6_value="$dns_ipv6_value" \
tun_enable_value="$tun_enable_value" \
tun_stack_value="$tun_stack_value" \
tun_auto_route_value="$tun_auto_route_value" \
@@ -847,6 +904,7 @@ normalize_runtime_config() {
.["allow-lan"] = (env(allow_lan_value) == "true") |
.mode = "rule" |
.["log-level"] = (.["log-level"] // "info") |
.ipv6 = (env(ipv6_value) == "true") |
.tun.enable = (env(tun_enable_value) == "true") |
.tun.stack = env(tun_stack_value) |
@@ -858,7 +916,7 @@ normalize_runtime_config() {
.dns.enable = (.dns.enable // true) |
.dns["enhanced-mode"] = (.dns["enhanced-mode"] // "fake-ip") |
.dns.ipv6 = false |
.dns.ipv6 = (env(dns_ipv6_value) == "true") |
.dns.listen = env(dns_listen_value) |
.proxies = (.proxies // []) |
@@ -60,6 +60,7 @@ export RUNTIME_DIR="$tmp_dir/runtime"
export BIN_DIR="$tmp_dir/bin"
export LOG_DIR="$tmp_dir/logs"
export CONFIG_DIR="$tmp_dir/config"
unset CLASH_IPV6
# shellcheck source=scripts/core/config.sh
source "$PROJECT_DIR/scripts/core/config.sh"
@@ -90,31 +91,73 @@ mixed-port: 7890
external-controller: 0.0.0.0:9090
secret: old-secret
allow-lan: false
proxies: []
ipv6: true
dns:
ipv6: true
proxies:
- name: hy2-ipv6
type: hysteria2
server: "2001:db8::1"
port: 443
password: test-password
proxy-groups: []
rules: []
YAML
normalize_runtime_config "$sample_config"
assert_yq() {
assert_file_yq() {
local name="$1"
local expr="$2"
local expected="$3"
local file="$2"
local expr="$3"
local expected="$4"
local actual
actual="$("$BIN_DIR/yq" eval "$expr" "$sample_config")"
actual="$("$BIN_DIR/yq" eval "$expr" "$file")"
if [ "$actual" != "$expected" ]; then
echo "not ok - $name: got '$actual', expected '$expected'" >&2
"$BIN_DIR/yq" eval '.' "$sample_config" >&2 || true
"$BIN_DIR/yq" eval '.' "$file" >&2 || true
return 1
fi
echo "ok - $name"
}
assert_yq() {
assert_file_yq "$1" "$sample_config" "$2" "$3"
}
assert_yq "keeps resolved mixed-port" '.["mixed-port"]' "7891"
assert_yq "removes legacy port" 'has("port")' "false"
assert_yq "removes legacy socks-port" 'has("socks-port")' "false"
assert_yq "removes legacy redir-port" 'has("redir-port")' "false"
assert_yq "removes legacy tproxy-port" 'has("tproxy-port")' "false"
assert_yq "keeps controller normalization" '.["external-controller"]' "0.0.0.0:9090"
assert_yq "preserves subscription IPv6" '.ipv6' "true"
assert_yq "preserves subscription DNS IPv6" '.dns.ipv6' "true"
assert_yq "preserves Hysteria2 proxy type" '.proxies[0].type' "hysteria2"
assert_yq "preserves IPv6-only proxy server" '.proxies[0].server' "2001:db8::1"
CLASH_IPV6=false normalize_runtime_config "$sample_config"
assert_yq "IPv6 off disables kernel IPv6" '.ipv6' "false"
assert_yq "IPv6 off disables DNS IPv6" '.dns.ipv6' "false"
unset CLASH_IPV6
normalize_runtime_config "$sample_config"
assert_yq "auto preserves disabled kernel IPv6" '.ipv6' "false"
assert_yq "auto preserves disabled DNS IPv6" '.dns.ipv6' "false"
CLASH_IPV6=true normalize_runtime_config "$sample_config"
assert_yq "IPv6 on enables kernel IPv6" '.ipv6' "true"
assert_yq "IPv6 on enables DNS IPv6" '.dns.ipv6' "true"
unset CLASH_IPV6
default_config="$tmp_dir/default-ipv6-config.yaml"
cat > "$default_config" <<'YAML'
proxies: []
proxy-groups: []
rules: []
YAML
normalize_runtime_config "$default_config"
assert_file_yq "auto keeps kernel IPv6 default enabled" "$default_config" '.ipv6' "true"
assert_file_yq "auto keeps legacy DNS IPv6 default disabled" "$default_config" '.dns.ipv6' "false"