v1.3.16 feat: support bind_interface for multi-NIC servers

- Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC
- Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj

v1.3.16 feat: 支持指定网络出口,多网卡绑定

- sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡
- v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj
This commit is contained in:
fscarmen
2026-07-15 23:06:26 +00:00
parent c62368ebcc
commit 4f29ea5c92
3 changed files with 89 additions and 17 deletions
+3
View File
@@ -24,6 +24,8 @@
* * * * * *
## 1.更新信息 ## 1.更新信息
2026.07.16 v1.3.16 1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj
2026.07.01 v1.3.15 Add v2rayN Finalmask field for hysteria2 realm subscription output; v2rayN 订阅输出增加 Hysteria2 Realm 的 Finalmask 字段 2026.07.01 v1.3.15 Add v2rayN Finalmask field for hysteria2 realm subscription output; v2rayN 订阅输出增加 Hysteria2 Realm 的 Finalmask 字段
2026.05.29 v1.3.14 Added optional custom warp route rule management, available after installation via [sb -d] without affecting the main installation flow; 增加可选的自定义 warp 路由规则管理,不影响主程序安装流程,安装后可通过 [sb -d] 按需管理 2026.05.29 v1.3.14 Added optional custom warp route rule management, available after installation via [sb -d] without affecting the main installation flow; 增加可选的自定义 warp 路由规则管理,不影响主程序安装流程,安装后可通过 [sb -d] 按需管理
@@ -417,6 +419,7 @@ bash <(wget -qO- https://raw.githubusercontent.com/fscarmen/sing-box/main/sing-b
| --HY2_REALM | [true, false],是否启用 Hysteria2 Realm,true 为启用。适用于没有公网入口、住宅 NAT、CGNAT 或需要回国打洞的机器,有公网入口时不建议使用,默认为 false | | --HY2_REALM | [true, false],是否启用 Hysteria2 Realm,true 为启用。适用于没有公网入口、住宅 NAT、CGNAT 或需要回国打洞的机器,有公网入口时不建议使用,默认为 false |
| --HY2_WARP | [true, false],是否启用 Realm 的 WARP 辅助打洞,true 为启用。适用于 NAT 严格环境;设置为 true 时会自动启用 Realm,默认为 false | | --HY2_WARP | [true, false],是否启用 Realm 的 WARP 辅助打洞,true 为启用。适用于 NAT 严格环境;设置为 true 时会自动启用 Realm,默认为 false |
| --REALITY_PRIVATE | reality 密钥 | | --REALITY_PRIVATE | reality 密钥 |
| --BIND_INTERFACE | 指定网络出口,绑定到特定网卡,如 eth0<br>值为 default 或不指定则不绑定 |
| --NODE_NAME_CONFIRM | 节点名 | | --NODE_NAME_CONFIRM | 节点名 |
+6 -3
View File
@@ -71,8 +71,7 @@ ARGO_AUTH=''
# 端口跳跃范围,用于 Hysteria2 协议,格式为 起始端口:结束端口 # 端口跳跃范围,用于 Hysteria2 协议,格式为 起始端口:结束端口
# Port hopping range for Hysteria2 protocol, format: start_port:end_port # Port hopping range for Hysteria2 protocol, format: start_port:end_port
HY2_PORT_HOPPING_RANGE='' PORT_HOPPING_RANGE=''
# 是否启用 Hysteria2 Realm / Enable Hysteria2 Realm: true or false # 是否启用 Hysteria2 Realm / Enable Hysteria2 Realm: true or false
# 适用于回国或者没有公网入口的机器;有公网入口时不建议使用 # 适用于回国或者没有公网入口的机器;有公网入口时不建议使用
@@ -84,7 +83,6 @@ HY2_REALM=''
# Useful for strict NAT environments such as symmetric NAT. Setting this to true automatically enables HY2_REALM # Useful for strict NAT environments such as symmetric NAT. Setting this to true automatically enables HY2_REALM
HY2_WARP='' HY2_WARP=''
# Reality 的私钥, 留空则随机生成 # Reality 的私钥, 留空则随机生成
# Reality private key. Random generation if left empty # Reality private key. Random generation if left empty
REALITY_PRIVATE='' REALITY_PRIVATE=''
@@ -92,3 +90,8 @@ REALITY_PRIVATE=''
# 节点名称,支持 emoji 表情 # 节点名称,支持 emoji 表情
# Node name, emoji supported # Node name, emoji supported
NODE_NAME_CONFIRM='' NODE_NAME_CONFIRM=''
# 指定网络出口,绑定到指定网卡 / Bind network interface to a specific network adapter
# 例如 / e.g. BIND_INTERFACE=eth0
# 留空则不指定 / Leave empty to not specify
BIND_INTERFACE=''
+79 -13
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 当前脚本版本号 # 当前脚本版本号
VERSION='v1.3.15 (2026.07.01)' VERSION='v1.3.16 (2026.07.16)'
# Github 反代加速代理 # Github 反代加速代理
GITHUB_PROXY=('https://hub.glowp.xyz/' 'https://proxy.vvvv.ee/') GITHUB_PROXY=('https://hub.glowp.xyz/' 'https://proxy.vvvv.ee/')
@@ -40,8 +40,8 @@ mkdir -p "$TEMP_DIR"
E[0]="Language:\n 1. English (default) \n 2. 简体中文" E[0]="Language:\n 1. English (default) \n 2. 简体中文"
C[0]="${E[0]}" C[0]="${E[0]}"
E[1]="1. Added Hysteria2 Realm support for machines without public inbound access, with optional WARP-assisted hole punching; 2. add v2rayN Finalmask field for hysteria2 realm subscription output" E[1]="1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj"
C[1]="1. 增加 Hysteria2 Realm 支持,适用于没有公网入口的机器,并可选 WARP 辅助打洞; 2. v2rayN 订阅输出增加 Realm Finalmask 字段" C[1]="1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj"
E[2]="Downloading Sing-box. Please wait a seconds ..." E[2]="Downloading Sing-box. Please wait a seconds ..."
C[2]="下载 Sing-box 中,请稍等 ..." C[2]="下载 Sing-box 中,请稍等 ..."
E[3]="Input errors up to 5 times.The script is aborted." E[3]="Input errors up to 5 times.The script is aborted."
@@ -374,6 +374,14 @@ E[167]="Close Realm"
C[167]="关闭 Realm" C[167]="关闭 Realm"
E[168]="Open Realm" E[168]="Open Realm"
C[168]="开启 Realm" C[168]="开启 Realm"
E[169]="Bind network interface (current: \${_val:-default})"
C[169]="指定网络出口 (当前: \${_val:-默认})"
E[170]="Please select network interface:"
C[170]="请选择网络接口:"
E[171]="1. Default (not specified)"
C[171]="1. 默认(不指定)"
E[172]="Bound interface updated to: "
C[172]="绑定接口已更新为: "
# 自定义字体彩色,read 函数 # 自定义字体彩色,read 函数
warning() { echo -e "\033[31m\033[01m$*\033[0m"; } # 红色 warning() { echo -e "\033[31m\033[01m$*\033[0m"; } # 红色
@@ -514,7 +522,7 @@ statistics_of_run_times() {
{ wget --no-check-certificate -qO- --timeout=3 "https://stat.cloudflare.now.cc/updateStats?script=${SCRIPT}" > $TEMP_DIR/statistics 2>/dev/null || true; }& { wget --no-check-certificate -qO- --timeout=3 "https://stat.cloudflare.now.cc/updateStats?script=${SCRIPT}" > $TEMP_DIR/statistics 2>/dev/null || true; }&
elif grep -q 'get' <<< "$UPDATE_OR_GET"; then elif grep -q 'get' <<< "$UPDATE_OR_GET"; then
[ -s $TEMP_DIR/statistics ] && [[ $(cat $TEMP_DIR/statistics) =~ \"todayCount\":([0-9]+),\"totalCount\":([0-9]+) ]] && local TODAY="${BASH_REMATCH[1]}" && local TOTAL="${BASH_REMATCH[2]}" && rm -f $TEMP_DIR/statistics [ -s $TEMP_DIR/statistics ] && [[ $(cat $TEMP_DIR/statistics) =~ \"todayCount\":([0-9]+),\"totalCount\":([0-9]+) ]] && local TODAY="${BASH_REMATCH[1]}" && local TOTAL="${BASH_REMATCH[2]}" && rm -f $TEMP_DIR/statistics
hint "\n*******************************************\n\n $(text 55) \n" hint "\n *******************************************\n\n $(text 55) \n"
fi fi
} }
@@ -668,6 +676,10 @@ change_config() {
local FP_NOW=$(awk -F '"' '/"fingerprint"/{print $4; exit}' ${WORK_DIR}/list) local FP_NOW=$(awk -F '"' '/"fingerprint"/{print $4; exit}' ${WORK_DIR}/list)
[ -n "$FP_NOW" ] && MENU_IDX+=(164) && MENU_KEY+=(fingerprint) && MENU_VAL+=("$FP_NOW") [ -n "$FP_NOW" ] && MENU_IDX+=(164) && MENU_KEY+=(fingerprint) && MENU_VAL+=("$FP_NOW")
# 指定网络出口
local BIND_IFACE_NOW=$(awk -F '"' '/"bind_interface"[[:space:]]*:[[:space:]]*"/{print $4}' "${WORK_DIR}/conf/01_outbounds.json" 2>/dev/null)
MENU_IDX+=(169) && MENU_KEY+=(bindinterface) && MENU_VAL+=("${BIND_IFACE_NOW:-default}")
# Hysteria2 带宽和端口跳跃(仅在 Hysteria2 已安装时显示) # Hysteria2 带宽和端口跳跃(仅在 Hysteria2 已安装时显示)
if ls ${WORK_DIR}/conf/*_${NODE_TAG[1]}_inbounds.json >/dev/null 2>&1; then if ls ${WORK_DIR}/conf/*_${NODE_TAG[1]}_inbounds.json >/dev/null 2>&1; then
local HY2_LINE=$(grep 'type: hysteria2' ${WORK_DIR}/subscribe/proxies) local HY2_LINE=$(grep 'type: hysteria2' ${WORK_DIR}/subscribe/proxies)
@@ -854,6 +866,60 @@ change_config() {
[[ ! "${NEW_VAL,,}" =~ ^[0-9a-z]+$ ]] && error " $(text 166) " || FINGER_PRINT="$NEW_VAL" [[ ! "${NEW_VAL,,}" =~ ^[0-9a-z]+$ ]] && error " $(text 166) " || FINGER_PRINT="$NEW_VAL"
export_list export_list
return return
elif [ "$KEY" = "bindinterface" ]; then
# 指定网络出口 — 获取系统接口列表 + 选择 + 更新 JSON
local IFACE_LIST=() CHOOSE_BIND IDX=2 TMP_FILE="${WORK_DIR}/conf/01_outbounds.json.tmp"
if command -v ip >/dev/null 2>&1; then
while read -r _ iface; do
iface="${iface%%:*}"
iface="${iface%%@*}"
[ "$iface" != "lo" ] && IFACE_LIST+=("$iface")
done < <(ip -o link show up 2>/dev/null)
elif command -v ifconfig >/dev/null 2>&1; then
while read -r iface _; do
iface="${iface%%:}"
[ "$iface" != "lo" ] && IFACE_LIST+=("$iface")
done < <(ifconfig -a 2>/dev/null | awk '/^[a-zA-Z]/')
else
for _if in /sys/class/net/*; do
_if="${_if##*/}"
[ "$_if" != "lo" ] && IFACE_LIST+=("$_if")
done
fi
mapfile -t IFACE_LIST < <(printf '%s\n' "${IFACE_LIST[@]}" | sort -u)
[ "${#IFACE_LIST[@]}" -eq 0 ] && warning " $(text 172) " && return
hint "\n $(text 170) \n"
hint " $(text 171) "
for _if in "${IFACE_LIST[@]}"; do
hint " $IDX. $_if"
((IDX++))
done
hint " 0. $(text 35)"
hint ""
reading " $(text 24) " CHOOSE_BIND
if [[ "$CHOOSE_BIND" == "1" || "${CHOOSE_BIND,,}" == "default" ]]; then
jq_exec '.outbounds |= map(if .tag == "direct" then del(.bind_interface) else . end)' \
"${WORK_DIR}/conf/01_outbounds.json" > "$TMP_FILE" && mv "$TMP_FILE" "${WORK_DIR}/conf/01_outbounds.json"
info " $(text 172) $(text 171 | sed 's/^1\. //')"
elif [[ "$CHOOSE_BIND" =~ ^[0-9]+$ ]] && [ "$CHOOSE_BIND" -ge 2 ] && [ "$CHOOSE_BIND" -le "$((IDX - 1))" ]; then
local SELECTED_IF="${IFACE_LIST[$((CHOOSE_BIND - 2))]}"
jq_exec --arg iface "$SELECTED_IF" '.outbounds |= map(if .tag == "direct" then .bind_interface = $iface else . end)' \
"${WORK_DIR}/conf/01_outbounds.json" > "$TMP_FILE" && mv "$TMP_FILE" "${WORK_DIR}/conf/01_outbounds.json"
info " $(text 172) $SELECTED_IF"
elif [ "$CHOOSE_BIND" == "0" ]; then
return
else
warning " Invalid selection " && return
fi
cmd_systemctl restart sing-box
sleep 2
cmd_systemctl status sing-box &>/dev/null && info "\n Sing-box $(text 28) $(text 37) \n" || warning "\n Sing-box $(text 27) $(text 38) \n"
export_list
return
fi fi
hint "" hint ""
@@ -3409,7 +3475,8 @@ EOF
"outbounds":[ "outbounds":[
{ {
"type":"direct", "type":"direct",
"tag":"direct" "tag":"direct"${BIND_INTERFACE:+,
"bind_interface":"${BIND_INTERFACE}"}
} }
] ]
} }
@@ -4723,15 +4790,10 @@ vless://${UUID[11]}@${SERVER_IP_1}:${PORT_XTLS_REALITY}?encryption=none${VISION_
if [ -n "$PORT_HYSTERIA2" ]; then if [ -n "$PORT_HYSTERIA2" ]; then
[[ -n "$PORT_HOPPING_START" && -n "$PORT_HOPPING_END" ]] && local V2RAYN_PARAMS=",\"Ports\":\"${PORT_HOPPING_START}-${PORT_HOPPING_END}\",\"HopInterval\":\"30s\"" [[ -n "$PORT_HOPPING_START" && -n "$PORT_HOPPING_END" ]] && local V2RAYN_PARAMS=",\"Ports\":\"${PORT_HOPPING_START}-${PORT_HOPPING_END}\",\"HopInterval\":\"30s\""
local REALM_PARAMS="" local REALM_PARAMS=""
[ "$IS_HY2_REALM" = 'is_hy2_realm' ] && REALM_PARAMS=",\"Finalmask\":\"{\\n \\\"udp\\\": [\\n {\\n \\\"type\\\": \\\"realm\\\",\\n \\\"settings\\\": {\\n \\\"url\\\": \\\"realm://public@realm.hy2.io:443/${UUID[12]}\\\",\\n \\\"stunServers\\\": [\\n \\\"stun.nextcloud.com:3478\\\",\\n \\\"stun.sip.us:3478\\\",\\n \\\"turn.cloudflare.com:3478\\\",\\n \\\"global.stun.twilio.com:3478\\\"\\n ]\\n }\\n }\\n ],\\n \\\"quicParams\\\": {\\n \\\"congestion\\\": \\\"bbr\\\"\\n }\\n }\"" [ "$IS_HY2_REALM" = 'is_hy2_realm' ] && REALM_PARAMS="\"Hy2RealmUrl\":\"realm://public@realm.hy2.io:443/${UUID[12]}?stun=stun.nextcloud.com:3478&stun=stun.sip.us:3478&stun=turn.cloudflare.com:3478&stun=global.stun.twilio.com:3478\","
local HY2_JSON_1="{\"ConfigType\":7,\"ConfigVersion\":4,\"Remarks\":\"${NODE_NAME[12]} ${NODE_TAG[1]}\",\"Address\":\"${SERVER_IP}\",\"Port\":${PORT_HYSTERIA2},\"Password\":\"${UUID[12]}\",\"StreamSecurity\":\"tls\",\"AllowInsecure\":\"false\",\"Sni\":\"${TLS_SERVER}\",\"Cert\":\"${CERT_URL_2}\""
local HY2_JSON_2=",\"ProtoExtraObj\":{\"UpMbps\":${HY2_UP:-200},\"DownMbps\":${HY2_DOWN:-1000}"
local HY2_JSON_3="}}"
local CLEAN_V2RAYN_PARAMS="${V2RAYN_PARAMS:-}"
local V2RAYN_SUBSCRIBE+=" local V2RAYN_SUBSCRIBE+="
---------------------------- ----------------------------
v2rayn://hysteria2/$(printf "%s%s%s%s%s\n" "$HY2_JSON_1" "$REALM_PARAMS" "${HY2_JSON_2%$'\n'*}" "${CLEAN_V2RAYN_PARAMS%$'\n'*}" "$HY2_JSON_3" | base64 -w0 | tr '+/' '-_' | tr -d '=')" v2rayn://hysteria2/$(echo -n "{\"ConfigType\":7,\"ConfigVersion\":4,\"Remarks\":\"${NODE_NAME[12]} ${NODE_TAG[1]}\",\"Address\":\"${SERVER_IP}\",\"Port\":${PORT_HYSTERIA2},\"Password\":\"${UUID[12]}\",\"StreamSecurity\":\"tls\",\"AllowInsecure\":\"false\",\"Sni\":\"${TLS_SERVER}\",\"Cert\":\"${CERT_URL_2}\",\"ProtoExtraObj\":{"${REALM_PARAMS}"\"UpMbps\":${HY2_UP:-200},\"DownMbps\":${HY2_DOWN:-1000}}}" | base64 -w0 | tr '+/' '-_' | tr -d '=')"
fi fi
[ -n "$PORT_TUIC" ] && local V2RAYN_SUBSCRIBE+=" [ -n "$PORT_TUIC" ] && local V2RAYN_SUBSCRIBE+="
@@ -5114,7 +5176,7 @@ $(info "$(echo "{ \"outbounds\":[ ${OUTBOUND_REPLACE%,} ] }" | ${WORK_DIR}/jq)
${PROMPT} ${PROMPT}
$(text 72)") $(text 72)")
" "
[ "$IS_SUB" = 'is_sub' ] && EXPORT_LIST_FILE+=" [ "$IS_SUB" = 'is_sub' ] && EXPORT_LIST_FILE+="
@@ -5917,6 +5979,10 @@ for z in ${!ALL_PARAMETER[@]}; do
--HY2_WARP|--REALM_WARP|--WARP_REALM ) --HY2_WARP|--REALM_WARP|--WARP_REALM )
((z++)); [[ "${ALL_PARAMETER[z],,}" =~ ^(true|1|y|yes)$ ]] && IS_HY2_WARP=is_hy2_warp && IS_HY2_REALM=is_hy2_realm ((z++)); [[ "${ALL_PARAMETER[z],,}" =~ ^(true|1|y|yes)$ ]] && IS_HY2_WARP=is_hy2_warp && IS_HY2_REALM=is_hy2_realm
;; ;;
--BIND_INTERFACE )
((z++)); BIND_INTERFACE=${ALL_PARAMETER[z]}
[[ "${BIND_INTERFACE,,}" = "default" ]] && unset BIND_INTERFACE
;;
--REALITY_PRIVATE ) --REALITY_PRIVATE )
((z++)); REALITY_PRIVATE=${ALL_PARAMETER[z]} ((z++)); REALITY_PRIVATE=${ALL_PARAMETER[z]}
;; ;;