tcp_only_start.sh 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. usage() {
  4. cat <<'EOF'
  5. Usage: tcp_only_start.sh [config_path]
  6. Options via env:
  7. MYNETSPEEDER_LISTEN_HOST edge 监听地址,默认读取配置或 127.0.0.1
  8. MYNETSPEEDER_LISTEN_PORT edge 监听端口,默认读取配置或 19080
  9. MYNETSPEEDER_USER 运行用户,默认 mynetspeeder
  10. MYNETSPEEDER_LOG_MAX_MB 单个日志最大大小,默认 50
  11. MYNETSPEEDER_LOG_BACKUPS 日志轮转保留份数,默认 3
  12. EOF
  13. }
  14. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  15. ROOT_DIR="$(dirname "$SCRIPT_DIR")"
  16. PACKAGE_PARENT="$(dirname "$ROOT_DIR")"
  17. PACKAGE_NAME="$(basename "$ROOT_DIR")"
  18. CONFIG_PATH="${1:-$ROOT_DIR/config.json}"
  19. RUNTIME_USER="${MYNETSPEEDER_USER:-mynetspeeder}"
  20. PID_FILE="/var/run/mynetspeeder-edge.pid"
  21. LOG_FILE="/var/log/mynetspeeder-edge.log"
  22. LOG_MAX_MB="${MYNETSPEEDER_LOG_MAX_MB:-50}"
  23. LOG_BACKUPS="${MYNETSPEEDER_LOG_BACKUPS:-3}"
  24. CHAIN4="MYNETSPEEDER"
  25. CHAIN6="MYNETSPEEDER6"
  26. SSH_PORTS="${MYNETSPEEDER_SSH_PORTS:-}"
  27. SELF_EXCLUDE_V4="127.0.0.0/8 169.254.0.0/16"
  28. SELF_EXCLUDE_V6="::1/128 fe80::/10"
  29. if [[ $EUID -ne 0 ]]; then
  30. echo "need root"
  31. exit 1
  32. fi
  33. if [[ ! -f "$CONFIG_PATH" ]]; then
  34. echo "config not found: $CONFIG_PATH"
  35. exit 1
  36. fi
  37. LISTEN_HOST_FROM_CONFIG=$(python3 - <<'PY' "$CONFIG_PATH"
  38. import json, sys
  39. cfg = json.load(open(sys.argv[1]))
  40. print(cfg.get("listen_host", cfg.get("socks_host", "127.0.0.1")))
  41. PY
  42. )
  43. LISTEN_PORT_FROM_CONFIG=$(python3 - <<'PY' "$CONFIG_PATH"
  44. import json, sys
  45. cfg = json.load(open(sys.argv[1]))
  46. port = int(cfg.get("listen_port", cfg.get("socks_port", 19080)) or 19080)
  47. print(port if port > 0 else 19080)
  48. PY
  49. )
  50. LISTEN_HOST="${MYNETSPEEDER_LISTEN_HOST:-$LISTEN_HOST_FROM_CONFIG}"
  51. LISTEN_PORT="${MYNETSPEEDER_LISTEN_PORT:-$LISTEN_PORT_FROM_CONFIG}"
  52. if ! [[ "$LISTEN_PORT" =~ ^[0-9]+$ ]]; then
  53. echo "listen port must be numeric"
  54. exit 1
  55. fi
  56. if [[ -z "$SSH_PORTS" && -n "${SSH_CONNECTION:-}" ]]; then
  57. SSH_PORTS="${SSH_CONNECTION##* }"
  58. fi
  59. SSH_PORT_ARRAY=()
  60. if [[ -n "$SSH_PORTS" ]]; then
  61. IFS=',' read -r -a SSH_PORT_ARRAY <<< "$SSH_PORTS"
  62. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  63. [[ "$ssh_port" =~ ^[0-9]+$ ]] || { echo "ssh ports must be numeric, got: $ssh_port"; exit 1; }
  64. done
  65. fi
  66. id -u "$RUNTIME_USER" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$RUNTIME_USER"
  67. mkdir -p /var/log
  68. touch "$LOG_FILE"
  69. chown "$RUNTIME_USER":"$RUNTIME_USER" "$LOG_FILE"
  70. if ! [[ "$LOG_MAX_MB" =~ ^[0-9]+$ ]] || ! [[ "$LOG_BACKUPS" =~ ^[0-9]+$ ]]; then
  71. echo "log limits must be numeric"
  72. exit 1
  73. fi
  74. LOG_MAX_BYTES=$((LOG_MAX_MB * 1024 * 1024))
  75. WAIT_TIMEOUT="${MYNETSPEEDER_LISTEN_TIMEOUT:-15}"
  76. ensure_rule() {
  77. local cmd="$1"
  78. local table="$2"
  79. local chain="$3"
  80. shift 3
  81. if ! "$cmd" -t "$table" -C "$chain" "$@" >/dev/null 2>&1; then
  82. "$cmd" -t "$table" -A "$chain" "$@"
  83. fi
  84. }
  85. wait_for_listen() {
  86. local host="$1"
  87. local port="$2"
  88. local log_file="$3"
  89. local log_pattern="$4"
  90. local label="$5"
  91. local timeout_sec="${6:-10}"
  92. local deadline=$((SECONDS + timeout_sec))
  93. local pattern
  94. pattern="${host//./\\.}:${port}( |$)"
  95. while (( SECONDS < deadline )); do
  96. if ss -H -lntp 2>/dev/null | grep -qE "$pattern"; then
  97. return 0
  98. fi
  99. if [[ -f "$log_file" ]] && grep -qE "$log_pattern" "$log_file" 2>/dev/null; then
  100. return 0
  101. fi
  102. sleep 0.2
  103. done
  104. echo "$label failed to listen"
  105. return 1
  106. }
  107. add_exclusions_v4() {
  108. iptables -t nat -A "$CHAIN4" -m addrtype --dst-type LOCAL -j RETURN
  109. for cidr in $SELF_EXCLUDE_V4; do
  110. iptables -t nat -A "$CHAIN4" -d "$cidr" -j RETURN
  111. done
  112. iptables -t nat -A "$CHAIN4" -m owner --uid-owner "$RUNTIME_USER" -j RETURN
  113. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  114. iptables -t nat -A "$CHAIN4" -p tcp --sport "$ssh_port" -j RETURN
  115. done
  116. }
  117. add_exclusions_v6() {
  118. ip6tables -t nat -A "$CHAIN6" -m addrtype --dst-type LOCAL -j RETURN
  119. for cidr in $SELF_EXCLUDE_V6; do
  120. ip6tables -t nat -A "$CHAIN6" -d "$cidr" -j RETURN
  121. done
  122. ip6tables -t nat -A "$CHAIN6" -m owner --uid-owner "$RUNTIME_USER" -j RETURN
  123. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  124. ip6tables -t nat -A "$CHAIN6" -p tcp --sport "$ssh_port" -j RETURN
  125. done
  126. }
  127. pkill -f 'python3 -m mynetspeeder edge' || true
  128. INLINE_CONFIG_JSON="$(python3 - <<'PY' "$CONFIG_PATH"
  129. import json, sys
  130. from pathlib import Path
  131. cfg = json.loads(Path(sys.argv[1]).read_text())
  132. cfg["relays"] = []
  133. cfg["socks_port"] = 0
  134. cfg["direct_redundancy"] = max(int(cfg.get("direct_redundancy", 3) or 3), 3)
  135. cfg["direct_redundancy_v4"] = max(int(cfg.get("direct_redundancy_v4", cfg["direct_redundancy"]) or cfg["direct_redundancy"]), 3)
  136. cfg["direct_redundancy_v6"] = max(int(cfg.get("direct_redundancy_v6", 2) or 2), 2)
  137. cfg["direct_max_redundancy"] = max(int(cfg.get("direct_max_redundancy", 4) or 4), 4)
  138. cfg["direct_open_timeout"] = min(float(cfg.get("direct_open_timeout", 2.5) or 2.5), 2.5)
  139. cfg["tcp_connect_happy_eyeballs_delay"] = min(float(cfg.get("tcp_connect_happy_eyeballs_delay", 0.1) or 0.1), 0.1)
  140. cfg["tcp_warmup_bytes"] = max(int(cfg.get("tcp_warmup_bytes", 524288) or 524288), 524288)
  141. cfg["tcp_loser_grace_ms"] = min(int(cfg.get("tcp_loser_grace_ms", 80) or 80), 80)
  142. print(json.dumps(cfg, ensure_ascii=False))
  143. PY
  144. )"
  145. echo "tcp-only config prepared: relays cleared, socks disabled"
  146. runuser -u "$RUNTIME_USER" -- env MYNETSPEEDER_CONFIG_JSON="$INLINE_CONFIG_JSON" bash -lc "export PYTHONUNBUFFERED=1; export PYTHONPATH=${PACKAGE_PARENT}; cd ${PACKAGE_PARENT} && exec nohup python3 -m ${PACKAGE_NAME} edge --listen-host ${LISTEN_HOST} --listen-port ${LISTEN_PORT} --config ${CONFIG_PATH} 2>&1 | python3 ${ROOT_DIR}/scripts/rotate-log.py ${LOG_FILE} ${LOG_MAX_BYTES} ${LOG_BACKUPS}" &
  147. EDGE_PID=$!
  148. echo "$EDGE_PID" > "$PID_FILE"
  149. if ! wait_for_listen "$LISTEN_HOST" "$LISTEN_PORT" "$LOG_FILE" "transparent tcp listening on" "edge" "$WAIT_TIMEOUT"; then
  150. tail -n 50 "$LOG_FILE" || true
  151. exit 1
  152. fi
  153. iptables -t nat -N "$CHAIN4" 2>/dev/null || true
  154. iptables -t nat -F "$CHAIN4"
  155. add_exclusions_v4
  156. iptables -t nat -A "$CHAIN4" -p tcp -j REDIRECT --to-ports "$LISTEN_PORT"
  157. ensure_rule iptables nat OUTPUT -p tcp -j "$CHAIN4"
  158. IP6_ENABLED=0
  159. IP6_NAT_SUPPORTED=0
  160. if command -v ip6tables >/dev/null 2>&1; then
  161. if ip6tables -t nat -S >/dev/null 2>&1; then
  162. IP6_ENABLED=1
  163. IP6_NAT_SUPPORTED=1
  164. ip6tables -t nat -N "$CHAIN6" 2>/dev/null || true
  165. ip6tables -t nat -F "$CHAIN6"
  166. add_exclusions_v6
  167. ip6tables -t nat -A "$CHAIN6" -p tcp -j REDIRECT --to-ports "$LISTEN_PORT"
  168. ensure_rule ip6tables nat OUTPUT -p tcp -j "$CHAIN6"
  169. fi
  170. fi
  171. iptables -t nat -C OUTPUT -p tcp -j "$CHAIN4" >/dev/null 2>&1 || { echo "self-check failed: ipv4 tcp output hook missing"; exit 1; }
  172. if [[ "$IP6_ENABLED" == "1" && "$IP6_NAT_SUPPORTED" == "1" ]]; then
  173. ip6tables -t nat -C OUTPUT -p tcp -j "$CHAIN6" >/dev/null 2>&1 || { echo "self-check failed: ipv6 tcp output hook missing"; exit 1; }
  174. fi
  175. echo "mynetspeeder tcp-only mode started on ${LISTEN_HOST}:${LISTEN_PORT}"
  176. echo "tcp: enabled"
  177. echo "udp: disabled"
  178. echo "relays: cleared from config"
  179. echo "pid file: $PID_FILE"
  180. echo "log file: $LOG_FILE"
  181. echo "log max: ${LOG_MAX_MB}MB x ${LOG_BACKUPS}"