From 4f29ea5c92707716fe5f0dfcccac12c5b5d63407 Mon Sep 17 00:00:00 2001 From: fscarmen <62703343+fscarmen@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:31:17 +0000 Subject: [PATCH] v1.3.16 feat: support bind_interface for multi-NIC servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 3 ++ config.conf | 11 ++++--- sing-box.sh | 92 +++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index ad72bf4..73e54cd 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ * * * ## 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.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_WARP | [true, false],是否启用 Realm 的 WARP 辅助打洞,true 为启用。适用于 NAT 严格环境;设置为 true 时会自动启用 Realm,默认为 false | | --REALITY_PRIVATE | reality 密钥 | +| --BIND_INTERFACE | 指定网络出口,绑定到特定网卡,如 eth0
值为 default 或不指定则不绑定 | | --NODE_NAME_CONFIRM | 节点名 | diff --git a/config.conf b/config.conf index 9fc9e7b..da3823a 100644 --- a/config.conf +++ b/config.conf @@ -71,8 +71,7 @@ ARGO_AUTH='' # 端口跳跃范围,用于 Hysteria2 协议,格式为 起始端口:结束端口 # 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 # 适用于回国或者没有公网入口的机器;有公网入口时不建议使用 @@ -84,11 +83,15 @@ HY2_REALM='' # Useful for strict NAT environments such as symmetric NAT. Setting this to true automatically enables HY2_REALM HY2_WARP='' - # Reality 的私钥, 留空则随机生成 # Reality private key. Random generation if left empty REALITY_PRIVATE='' # 节点名称,支持 emoji 表情 # Node name, emoji supported -NODE_NAME_CONFIRM='' \ No newline at end of file +NODE_NAME_CONFIRM='' + +# 指定网络出口,绑定到指定网卡 / Bind network interface to a specific network adapter +# 例如 / e.g. BIND_INTERFACE=eth0 +# 留空则不指定 / Leave empty to not specify +BIND_INTERFACE='' \ No newline at end of file diff --git a/sing-box.sh b/sing-box.sh index d4ca242..84a28bc 100644 --- a/sing-box.sh +++ b/sing-box.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # 当前脚本版本号 -VERSION='v1.3.15 (2026.07.01)' +VERSION='v1.3.16 (2026.07.16)' # Github 反代加速代理 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. 简体中文" 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" -C[1]="1. 增加 Hysteria2 Realm 支持,适用于没有公网入口的机器,并可选 WARP 辅助打洞; 2. v2rayN 订阅输出增加 Realm 的 Finalmask 字段" +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. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj" E[2]="Downloading Sing-box. Please wait a seconds ..." C[2]="下载 Sing-box 中,请稍等 ..." E[3]="Input errors up to 5 times.The script is aborted." @@ -374,6 +374,14 @@ E[167]="Close Realm" C[167]="关闭 Realm" E[168]="Open 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 函数 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; }& 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 - hint "\n*******************************************\n\n $(text 55) \n" + hint "\n *******************************************\n\n $(text 55) \n" fi } @@ -668,6 +676,10 @@ change_config() { 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") + # 指定网络出口 + 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 已安装时显示) 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) @@ -854,6 +866,60 @@ change_config() { [[ ! "${NEW_VAL,,}" =~ ^[0-9a-z]+$ ]] && error " $(text 166) " || FINGER_PRINT="$NEW_VAL" export_list 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 hint "" @@ -3409,7 +3475,8 @@ EOF "outbounds":[ { "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 [[ -n "$PORT_HOPPING_START" && -n "$PORT_HOPPING_END" ]] && local V2RAYN_PARAMS=",\"Ports\":\"${PORT_HOPPING_START}-${PORT_HOPPING_END}\",\"HopInterval\":\"30s\"" 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 }\"" - - 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:-}" + [ "$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 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 [ -n "$PORT_TUIC" ] && local V2RAYN_SUBSCRIBE+=" @@ -5114,7 +5176,7 @@ $(info "$(echo "{ \"outbounds\":[ ${OUTBOUND_REPLACE%,} ] }" | ${WORK_DIR}/jq) ${PROMPT} - $(text 72)") + $(text 72)") " [ "$IS_SUB" = 'is_sub' ] && EXPORT_LIST_FILE+=" @@ -5917,6 +5979,10 @@ for z in ${!ALL_PARAMETER[@]}; do --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 ;; + --BIND_INTERFACE ) + ((z++)); BIND_INTERFACE=${ALL_PARAMETER[z]} + [[ "${BIND_INTERFACE,,}" = "default" ]] && unset BIND_INTERFACE + ;; --REALITY_PRIVATE ) ((z++)); REALITY_PRIVATE=${ALL_PARAMETER[z]} ;;