tcp_only_start.sh 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. ROOT_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
  5. PACKAGE_PARENT="$(dirname "$ROOT_DIR")"
  6. PACKAGE_NAME="$(basename "$ROOT_DIR")"
  7. source "$SCRIPT_DIR/runtime-lib.sh"
  8. CONFIG_PATH="${1:-$ROOT_DIR/config-tcp.json}"
  9. RUNTIME_USER="${MYNETSPEEDER_TCP_USER:-mynetspeeder}"
  10. PID_FILE="/var/run/mynetspeeder-tcp-edge.pid"
  11. LOG_FILE="/var/log/mynetspeeder-tcp-edge.log"
  12. CHAIN4="MYNETSPEEDER_TCP"
  13. CHAIN6="MYNETSPEEDER_TCP6"
  14. SSH_PORTS="${MYNETSPEEDER_TCP_SSH_PORTS:-}"
  15. CAPTURE_UID="${MYNETSPEEDER_TCP_CAPTURE_UID:-}"
  16. SELF_EXCLUDE_V4="127.0.0.0/8 169.254.0.0/16"
  17. SELF_EXCLUDE_V6="::1/128 fe80::/10"
  18. require_root
  19. if [[ ! -f "$CONFIG_PATH" ]]; then
  20. echo "config not found: $CONFIG_PATH"
  21. exit 1
  22. fi
  23. LISTEN_HOST_FROM_CONFIG="$(python3 - <<'PY' "$CONFIG_PATH"
  24. import json, sys
  25. cfg = json.load(open(sys.argv[1]))
  26. print(cfg.get("listen_host", "127.0.0.1"))
  27. PY
  28. )"
  29. LISTEN_PORT_FROM_CONFIG="$(python3 - <<'PY' "$CONFIG_PATH"
  30. import json, sys
  31. cfg = json.load(open(sys.argv[1]))
  32. port = int(cfg.get("listen_port", 19080) or 19080)
  33. print(port if port > 0 else 19080)
  34. PY
  35. )"
  36. LISTEN_HOST="${MYNETSPEEDER_TCP_LISTEN_HOST:-$LISTEN_HOST_FROM_CONFIG}"
  37. LISTEN_PORT="${MYNETSPEEDER_TCP_LISTEN_PORT:-$LISTEN_PORT_FROM_CONFIG}"
  38. if ! [[ "$LISTEN_PORT" =~ ^[0-9]+$ ]]; then
  39. echo "listen port must be numeric"
  40. exit 1
  41. fi
  42. if [[ -n "$CAPTURE_UID" ]] && ! [[ "$CAPTURE_UID" =~ ^[0-9]+$ ]]; then
  43. echo "capture uid must be numeric"
  44. exit 1
  45. fi
  46. if [[ -z "$SSH_PORTS" && -n "${SSH_CONNECTION:-}" ]]; then
  47. SSH_PORTS="${SSH_CONNECTION##* }"
  48. fi
  49. SSH_PORT_ARRAY=()
  50. if [[ -n "$SSH_PORTS" ]]; then
  51. IFS=',' read -r -a SSH_PORT_ARRAY <<< "$SSH_PORTS"
  52. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  53. [[ "$ssh_port" =~ ^[0-9]+$ ]] || { echo "ssh ports must be numeric, got: $ssh_port"; exit 1; }
  54. done
  55. fi
  56. ensure_runtime_user "$RUNTIME_USER"
  57. ensure_log_file "$LOG_FILE" "$RUNTIME_USER"
  58. stop_pid_file "$PID_FILE" "edge-tcp --listen-host ${LISTEN_HOST} --listen-port ${LISTEN_PORT} --config ${CONFIG_PATH}"
  59. cleanup_rules() {
  60. ensure_nat_jump_absent iptables nat OUTPUT -p tcp -j "$CHAIN4"
  61. ensure_nat_chain_absent iptables "$CHAIN4"
  62. ensure_nat_jump_absent ip6tables nat OUTPUT -p tcp -j "$CHAIN6"
  63. ensure_nat_chain_absent ip6tables "$CHAIN6"
  64. }
  65. cleanup_required=1
  66. cleanup_done=0
  67. cleanup_on_exit() {
  68. if [[ "$cleanup_done" -eq 1 || "$cleanup_required" -eq 0 ]]; then
  69. return 0
  70. fi
  71. cleanup_done=1
  72. cleanup_rules
  73. stop_pid_file "$PID_FILE" "edge-tcp --listen-host ${LISTEN_HOST} --listen-port ${LISTEN_PORT} --config ${CONFIG_PATH}"
  74. }
  75. trap 'cleanup_on_exit' ERR EXIT INT TERM
  76. readarray -t RELAY_HOSTS < <(python3 - <<'PY' "$CONFIG_PATH"
  77. import json, sys
  78. cfg = json.load(open(sys.argv[1]))
  79. for relay in cfg.get("relays", []):
  80. print(relay["host"])
  81. PY
  82. )
  83. ARGV_JSON="$(python3 - <<'PY' "$PACKAGE_NAME" "$LISTEN_HOST" "$LISTEN_PORT" "$CONFIG_PATH"
  84. import json, sys
  85. package_name, listen_host, listen_port, config_path = sys.argv[1:]
  86. print(json.dumps([
  87. "python3", "-m", package_name, "edge-tcp",
  88. "--listen-host", listen_host,
  89. "--listen-port", listen_port,
  90. "--config", config_path,
  91. ]))
  92. PY
  93. )"
  94. start_python_service "$RUNTIME_USER" "$PACKAGE_PARENT" "$PACKAGE_PARENT" "$LOG_FILE" "$PID_FILE" \
  95. "edge-tcp --listen-host ${LISTEN_HOST} --listen-port ${LISTEN_PORT} --config ${CONFIG_PATH}" "$ARGV_JSON" >/dev/null
  96. if ! wait_for_tcp_listen "$LISTEN_HOST" "$LISTEN_PORT" 15; then
  97. tail -n 50 "$LOG_FILE" || true
  98. exit 1
  99. fi
  100. iptables -t nat -N "$CHAIN4" 2>/dev/null || true
  101. iptables -t nat -F "$CHAIN4"
  102. iptables -t nat -A "$CHAIN4" -m addrtype --dst-type LOCAL -j RETURN
  103. for cidr in $SELF_EXCLUDE_V4; do
  104. iptables -t nat -A "$CHAIN4" -d "$cidr" -j RETURN
  105. done
  106. iptables -t nat -A "$CHAIN4" -m owner --uid-owner "$RUNTIME_USER" -j RETURN
  107. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  108. iptables -t nat -A "$CHAIN4" -p tcp --sport "$ssh_port" -j RETURN
  109. done
  110. for host in "${RELAY_HOSTS[@]}"; do
  111. [[ -n "$host" && "$host" != *:* ]] && iptables -t nat -A "$CHAIN4" -d "$host" -j RETURN
  112. done
  113. if [[ -n "$CAPTURE_UID" ]]; then
  114. iptables -t nat -A "$CHAIN4" -p tcp -m owner --uid-owner "$CAPTURE_UID" -j REDIRECT --to-ports "$LISTEN_PORT"
  115. else
  116. iptables -t nat -A "$CHAIN4" -p tcp -j REDIRECT --to-ports "$LISTEN_PORT"
  117. fi
  118. ensure_nat_jump_absent iptables nat OUTPUT -p tcp -j "$CHAIN4"
  119. iptables -t nat -A OUTPUT -p tcp -j "$CHAIN4"
  120. if command -v ip6tables >/dev/null 2>&1 && ip6tables -t nat -S >/dev/null 2>&1; then
  121. ip6tables -t nat -N "$CHAIN6" 2>/dev/null || true
  122. ip6tables -t nat -F "$CHAIN6"
  123. ip6tables -t nat -A "$CHAIN6" -m addrtype --dst-type LOCAL -j RETURN
  124. for cidr in $SELF_EXCLUDE_V6; do
  125. ip6tables -t nat -A "$CHAIN6" -d "$cidr" -j RETURN
  126. done
  127. ip6tables -t nat -A "$CHAIN6" -m owner --uid-owner "$RUNTIME_USER" -j RETURN
  128. for ssh_port in "${SSH_PORT_ARRAY[@]}"; do
  129. ip6tables -t nat -A "$CHAIN6" -p tcp --sport "$ssh_port" -j RETURN
  130. done
  131. for host in "${RELAY_HOSTS[@]}"; do
  132. [[ -n "$host" && "$host" == *:* ]] && ip6tables -t nat -A "$CHAIN6" -d "$host" -j RETURN
  133. done
  134. if [[ -n "$CAPTURE_UID" ]]; then
  135. ip6tables -t nat -A "$CHAIN6" -p tcp -m owner --uid-owner "$CAPTURE_UID" -j REDIRECT --to-ports "$LISTEN_PORT"
  136. else
  137. ip6tables -t nat -A "$CHAIN6" -p tcp -j REDIRECT --to-ports "$LISTEN_PORT"
  138. fi
  139. ensure_nat_jump_absent ip6tables nat OUTPUT -p tcp -j "$CHAIN6"
  140. ip6tables -t nat -A OUTPUT -p tcp -j "$CHAIN6"
  141. fi
  142. cleanup_required=0
  143. trap - ERR EXIT INT TERM
  144. echo "tcp-only started on ${LISTEN_HOST}:${LISTEN_PORT}"