757 lines
25 KiB
Shell
757 lines
25 KiB
Shell
#!/system/bin/sh
|
|
# ============================================================================
|
|
# RKNnoVPN — iptables.sh
|
|
# Whitelist-mode transparent proxy via TPROXY + iptables.
|
|
# Main orchestration only: env/snapshot/apply/teardown/status.
|
|
# Rule rendering and listener verification live in scripts/lib/.
|
|
# POSIX sh compatible. No bashisms.
|
|
# ============================================================================
|
|
|
|
set -eu
|
|
set -f
|
|
|
|
TAG="RKNnoVPN:iptables"
|
|
SCRIPT_VERSION="v2.2.2"
|
|
SCRIPT_DIR="${0%/*}"
|
|
|
|
if [ -f "${SCRIPT_DIR}/lib/rknnovpn_env.sh" ]; then
|
|
. "${SCRIPT_DIR}/lib/rknnovpn_env.sh"
|
|
fi
|
|
if [ -f "${SCRIPT_DIR}/lib/rknnovpn_netstack.sh" ]; then
|
|
. "${SCRIPT_DIR}/lib/rknnovpn_netstack.sh"
|
|
fi
|
|
if [ -f "${SCRIPT_DIR}/lib/rknnovpn_iptables_rules.sh" ]; then
|
|
. "${SCRIPT_DIR}/lib/rknnovpn_iptables_rules.sh"
|
|
fi
|
|
|
|
CHAIN_PREFIX="RKNNOVPN"
|
|
CHAIN_OUT="${CHAIN_PREFIX}_OUT"
|
|
CHAIN_IN="${CHAIN_PREFIX}_IN"
|
|
CHAIN_PRE="${CHAIN_PREFIX}_PRE"
|
|
CHAIN_APP="${CHAIN_PREFIX}_APP"
|
|
CHAIN_BYPASS="${CHAIN_PREFIX}_BYPASS"
|
|
CHAIN_DNS="${CHAIN_PREFIX}_DNS"
|
|
CHAIN_DIVERT="${CHAIN_PREFIX}_DIVERT"
|
|
|
|
SNAPSHOT_DIR="${RUN_DIR:-${RKNNOVPN_DIR:-/data/adb/modules/rknnovpn}/run}"
|
|
IPT_WAIT="${IPT_WAIT:--w 100}"
|
|
|
|
RESERVED_IPV4="
|
|
0.0.0.0/8
|
|
10.0.0.0/8
|
|
100.64.0.0/10
|
|
127.0.0.0/8
|
|
169.254.0.0/16
|
|
172.16.0.0/12
|
|
192.0.0.0/24
|
|
192.0.2.0/24
|
|
192.88.99.0/24
|
|
192.168.0.0/16
|
|
198.18.0.0/15
|
|
198.51.100.0/24
|
|
203.0.113.0/24
|
|
224.0.0.0/3
|
|
"
|
|
|
|
RESERVED_IPV6="
|
|
::1/128
|
|
fe80::/10
|
|
fc00::/7
|
|
ff00::/8
|
|
::ffff:0:0/96
|
|
"
|
|
|
|
log_info() {
|
|
echo "[${TAG}] INFO: $*"
|
|
}
|
|
|
|
log_warn() {
|
|
echo "[${TAG}] WARN: $*" >&2
|
|
}
|
|
|
|
log_error() {
|
|
echo "[${TAG}] ERROR: $*" >&2
|
|
}
|
|
|
|
require_rule_renderer() {
|
|
if ! command -v gen_mangle_v4 >/dev/null 2>&1 || ! command -v check_local_listener_protection >/dev/null 2>&1; then
|
|
log_error "missing scripts/lib/rknnovpn_iptables_rules.sh"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_env() {
|
|
missing=""
|
|
|
|
for var in TPROXY_PORT DNS_PORT API_PORT FWMARK ROUTE_TABLE \
|
|
ROUTE_TABLE_V6 CORE_GID APP_MODE RKNNOVPN_DIR; do
|
|
eval "val=\${${var}:-}"
|
|
if [ -z "$val" ]; then
|
|
missing="${missing} ${var}"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$missing" ]; then
|
|
log_error "Missing required environment variables:${missing}"
|
|
exit 1
|
|
fi
|
|
|
|
PROXY_MODE="${PROXY_MODE:-tproxy}"
|
|
PROXY_UIDS="${PROXY_UIDS:-}"
|
|
DIRECT_UIDS="${DIRECT_UIDS:-}"
|
|
BYPASS_UIDS="${BYPASS_UIDS:-}"
|
|
SOCKS_PORT="${SOCKS_PORT:-0}"
|
|
HTTP_PORT="${HTTP_PORT:-0}"
|
|
XRAY_SIDECAR_PORT="${XRAY_SIDECAR_PORT:-0}"
|
|
CHAIN_PROXY_PORTS="${CHAIN_PROXY_PORTS:-}"
|
|
CHAIN_PROXY_UIDS="${CHAIN_PROXY_UIDS:-}"
|
|
CHAIN_PROXY_RULES="${CHAIN_PROXY_RULES:-}"
|
|
ROUTE_RULE_PREF="${ROUTE_RULE_PREF:-10000}"
|
|
ROUTE_RULE_PREF_V6="${ROUTE_RULE_PREF_V6:-10001}"
|
|
IPV6_MODE="${IPV6_MODE:-mirror}"
|
|
IPV6_FAIL_CLOSED="${IPV6_FAIL_CLOSED:-1}"
|
|
DNS_MODE="${DNS_MODE:-per_uid}"
|
|
DNS_SCOPE="${DNS_SCOPE:-}"
|
|
SHARING_MODE="${SHARING_MODE:-off}"
|
|
SHARING_IFACES="${SHARING_IFACES:-}"
|
|
|
|
if [ -z "$DNS_SCOPE" ]; then
|
|
case "$DNS_MODE:$APP_MODE" in
|
|
off:*) DNS_SCOPE="off" ;;
|
|
all:*) DNS_SCOPE="all" ;;
|
|
per_uid:blacklist|uid:blacklist) DNS_SCOPE="all_except_uids" ;;
|
|
per_uid:*|uid:*) DNS_SCOPE="uids" ;;
|
|
*) DNS_SCOPE="off" ;;
|
|
esac
|
|
fi
|
|
|
|
validate_runtime_values
|
|
}
|
|
|
|
ipv6_mangle_available() {
|
|
command -v ip6tables >/dev/null 2>&1 &&
|
|
ip6tables ${IPT_WAIT} -t mangle -L >/dev/null 2>&1 &&
|
|
command -v ip6tables-restore >/dev/null 2>&1
|
|
}
|
|
|
|
ip_rule_present() {
|
|
_family="$1"
|
|
_table="$2"
|
|
if [ "$_family" = "6" ]; then
|
|
_cmd="ip -6 rule show"
|
|
else
|
|
_cmd="ip rule show"
|
|
fi
|
|
$_cmd 2>/dev/null | awk -v mark="$(printf '%s' "$FWMARK" | tr 'A-F' 'a-f')" -v table="$_table" '
|
|
{
|
|
line=tolower($0)
|
|
n=split(line, f, /[[:space:]]+/)
|
|
mark_ok=0
|
|
table_ok=0
|
|
for (i=1; i<n; i++) {
|
|
if (f[i] == "fwmark" && mark != "" && (f[i+1] == mark || index(f[i+1], mark "/") == 1)) { mark_ok=1 }
|
|
if ((f[i] == "lookup" || f[i] == "table") && table != "" && f[i+1] == table) { table_ok=1 }
|
|
}
|
|
if (mark_ok && table_ok) { found=1; exit }
|
|
}
|
|
END { exit found ? 0 : 1 }'
|
|
}
|
|
|
|
local_route_present() {
|
|
_family="$1"
|
|
_table="$2"
|
|
if [ "$_family" = "6" ]; then
|
|
ip -6 route show table "$_table" 2>/dev/null | grep -q "local default dev lo"
|
|
else
|
|
ip route show table "$_table" 2>/dev/null | grep -q "local default dev lo"
|
|
fi
|
|
}
|
|
|
|
validate_uint() {
|
|
_name="$1"
|
|
_value="$2"
|
|
case "$_value" in
|
|
''|*[!0-9]*)
|
|
log_error "Invalid ${_name}: expected unsigned integer"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
validate_port() {
|
|
_name="$1"
|
|
_value="$2"
|
|
_allow_zero="${3:-0}"
|
|
validate_uint "$_name" "$_value"
|
|
_min=1
|
|
[ "$_allow_zero" = "1" ] && _min=0
|
|
if ! awk -v v="$_value" -v min="$_min" 'BEGIN { exit ((v + 0) >= min && (v + 0) <= 65535) ? 0 : 1 }'; then
|
|
if [ "$_allow_zero" = "1" ]; then
|
|
log_error "Invalid ${_name}: expected 0 or 1-65535"
|
|
else
|
|
log_error "Invalid ${_name}: expected 1-65535"
|
|
fi
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
validate_hex_mark() {
|
|
_name="$1"
|
|
_value="$2"
|
|
case "$_value" in
|
|
0x[0-9a-fA-F]*)
|
|
;;
|
|
*)
|
|
log_error "Invalid ${_name}: expected hex mark like 0x2023"
|
|
exit 1
|
|
;;
|
|
esac
|
|
case "${_value#0x}" in
|
|
''|*[!0-9a-fA-F]*)
|
|
log_error "Invalid ${_name}: expected hex mark like 0x2023"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
validate_enum() {
|
|
_name="$1"
|
|
_value="$2"
|
|
_allowed="$3"
|
|
case " ${_allowed} " in
|
|
*" ${_value} "*) ;;
|
|
*)
|
|
log_error "Invalid ${_name}: ${_value}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
validate_uint_list() {
|
|
_name="$1"
|
|
_value="$2"
|
|
_item=""
|
|
for _item in $_value; do
|
|
validate_uint "$_name" "$_item"
|
|
done
|
|
}
|
|
|
|
validate_port_list() {
|
|
_name="$1"
|
|
_value="$2"
|
|
_item=""
|
|
for _item in $_value; do
|
|
validate_port "$_name" "$_item" 0
|
|
done
|
|
}
|
|
|
|
validate_port_uid_rules() {
|
|
_name="$1"
|
|
_value="$2"
|
|
_item=""
|
|
_port=""
|
|
_uid=""
|
|
for _item in $_value; do
|
|
case "$_item" in
|
|
*:*)
|
|
_port="${_item%%:*}"
|
|
_uid="${_item#*:}"
|
|
validate_port "$_name port" "$_port" 0
|
|
validate_uint "$_name uid" "$_uid"
|
|
;;
|
|
*)
|
|
log_error "Invalid ${_name}: expected port:uid entries"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
validate_port_conflicts() {
|
|
_core_seen=""
|
|
for _entry in \
|
|
"TPROXY_PORT:${TPROXY_PORT}" \
|
|
"DNS_PORT:${DNS_PORT}" \
|
|
"API_PORT:${API_PORT}" \
|
|
"SOCKS_PORT:${SOCKS_PORT}" \
|
|
"HTTP_PORT:${HTTP_PORT}" \
|
|
"XRAY_SIDECAR_PORT:${XRAY_SIDECAR_PORT}"; do
|
|
_name="${_entry%%:*}"
|
|
_port="${_entry#*:}"
|
|
[ "$_port" = "0" ] && continue
|
|
case " ${_core_seen} " in
|
|
*" ${_port} "*) log_error "Port conflict: ${_name}=${_port} is already used"; exit 1 ;;
|
|
esac
|
|
_core_seen="${_core_seen} ${_port}"
|
|
done
|
|
_chain_seen=""
|
|
for _port in ${CHAIN_PROXY_PORTS}; do
|
|
case " ${_core_seen} " in
|
|
*" ${_port} "*) log_error "Port conflict: CHAIN_PROXY_PORTS contains reserved listener port ${_port}"; exit 1 ;;
|
|
esac
|
|
case " ${_chain_seen} " in
|
|
*" ${_port} "*) log_error "Port conflict: CHAIN_PROXY_PORTS repeats port ${_port}"; exit 1 ;;
|
|
esac
|
|
_chain_seen="${_chain_seen} ${_port}"
|
|
done
|
|
for _rule in ${CHAIN_PROXY_RULES}; do
|
|
_port="${_rule%%:*}"
|
|
case " ${_core_seen} " in
|
|
*" ${_port} "*) log_error "Port conflict: CHAIN_PROXY_RULES contains reserved listener port ${_port}"; exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
ipv6_required() {
|
|
[ "${IPV6_FAIL_CLOSED:-1}" = "1" ] || return 1
|
|
case "${IPV6_MODE:-mirror}" in
|
|
disable|disabled|off|ipv4_only|IPv4_ONLY|DISABLE|DISABLED|OFF) return 1 ;;
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
validate_runtime_values() {
|
|
validate_port TPROXY_PORT "$TPROXY_PORT" 0
|
|
validate_port DNS_PORT "$DNS_PORT" 0
|
|
validate_port API_PORT "$API_PORT" 1
|
|
validate_port SOCKS_PORT "$SOCKS_PORT" 1
|
|
validate_port HTTP_PORT "$HTTP_PORT" 1
|
|
validate_port XRAY_SIDECAR_PORT "$XRAY_SIDECAR_PORT" 1
|
|
validate_uint CORE_GID "$CORE_GID"
|
|
validate_uint ROUTE_TABLE "$ROUTE_TABLE"
|
|
validate_uint ROUTE_TABLE_V6 "$ROUTE_TABLE_V6"
|
|
validate_uint ROUTE_RULE_PREF "$ROUTE_RULE_PREF"
|
|
validate_uint ROUTE_RULE_PREF_V6 "$ROUTE_RULE_PREF_V6"
|
|
validate_hex_mark FWMARK "$FWMARK"
|
|
validate_enum APP_MODE "$APP_MODE" "whitelist blacklist all off"
|
|
validate_enum DNS_SCOPE "$DNS_SCOPE" "off all uids all_except_uids"
|
|
validate_enum DNS_MODE "$DNS_MODE" "off all per_uid uid"
|
|
validate_enum PROXY_MODE "$PROXY_MODE" "tproxy"
|
|
validate_enum SHARING_MODE "$SHARING_MODE" "off hotspot"
|
|
validate_port_list CHAIN_PROXY_PORTS "$CHAIN_PROXY_PORTS"
|
|
validate_uint_list CHAIN_PROXY_UIDS "$CHAIN_PROXY_UIDS"
|
|
validate_port_uid_rules CHAIN_PROXY_RULES "$CHAIN_PROXY_RULES"
|
|
validate_port_conflicts
|
|
validate_uint_list PROXY_UIDS "$PROXY_UIDS"
|
|
validate_uint_list DIRECT_UIDS "$DIRECT_UIDS"
|
|
validate_uint_list BYPASS_UIDS "$BYPASS_UIDS"
|
|
IPV6_MANGLE_APPLIED="${IPV6_MANGLE_APPLIED:-0}"
|
|
IPV6_ROUTE_APPLIED="${IPV6_ROUTE_APPLIED:-0}"
|
|
validate_enum IPV6_MANGLE_APPLIED "$IPV6_MANGLE_APPLIED" "0 1"
|
|
validate_enum IPV6_ROUTE_APPLIED "$IPV6_ROUTE_APPLIED" "0 1"
|
|
validate_enum IPV6_FAIL_CLOSED "$IPV6_FAIL_CLOSED" "0 1"
|
|
BLOCK_QUIC="${BLOCK_QUIC:-0}"
|
|
validate_enum BLOCK_QUIC "$BLOCK_QUIC" "0 1"
|
|
}
|
|
|
|
write_snapshot_var() {
|
|
_name="$1"
|
|
eval "_value=\${${_name}:-}"
|
|
printf "%s='%s'\n" "$_name" "$_value"
|
|
}
|
|
|
|
save_snapshot() {
|
|
mkdir -p "${SNAPSHOT_DIR}"
|
|
|
|
validate_runtime_values
|
|
_tmp="${SNAPSHOT_DIR}/env.sh.tmp.$$"
|
|
{
|
|
echo "# RKNnoVPN runtime snapshot - generated at $(date)"
|
|
for _name in TPROXY_PORT DNS_PORT API_PORT SOCKS_PORT HTTP_PORT XRAY_SIDECAR_PORT \
|
|
CHAIN_PROXY_PORTS CHAIN_PROXY_UIDS CHAIN_PROXY_RULES \
|
|
FWMARK ROUTE_TABLE ROUTE_TABLE_V6 CORE_GID APP_MODE \
|
|
PROXY_UIDS DIRECT_UIDS BYPASS_UIDS DNS_SCOPE DNS_MODE \
|
|
PROXY_MODE ROUTE_RULE_PREF ROUTE_RULE_PREF_V6 IPV6_MODE \
|
|
IPV6_FAIL_CLOSED IPV6_MANGLE_APPLIED IPV6_ROUTE_APPLIED \
|
|
BLOCK_QUIC SHARING_MODE; do
|
|
write_snapshot_var "$_name"
|
|
done
|
|
} > "$_tmp"
|
|
chmod 0600 "$_tmp" 2>/dev/null || true
|
|
mv "$_tmp" "${SNAPSHOT_DIR}/env.sh"
|
|
|
|
log_info "Runtime snapshot saved to ${SNAPSHOT_DIR}/env.sh"
|
|
}
|
|
|
|
load_snapshot() {
|
|
if [ -f "${SNAPSHOT_DIR}/env.sh" ]; then
|
|
_line=""
|
|
_name=""
|
|
_value=""
|
|
while IFS= read -r _line || [ -n "$_line" ]; do
|
|
case "$_line" in
|
|
''|\#*) continue ;;
|
|
esac
|
|
_name="${_line%%=*}"
|
|
_value="${_line#*=}"
|
|
case "$_value" in
|
|
\'*\')
|
|
_value="${_value#\'}"
|
|
_value="${_value%\'}"
|
|
;;
|
|
*)
|
|
log_error "Invalid runtime snapshot entry for ${_name}"
|
|
return 1
|
|
;;
|
|
esac
|
|
case "$_name" in
|
|
TPROXY_PORT) TPROXY_PORT="$_value" ;;
|
|
DNS_PORT) DNS_PORT="$_value" ;;
|
|
API_PORT) API_PORT="$_value" ;;
|
|
SOCKS_PORT) SOCKS_PORT="$_value" ;;
|
|
HTTP_PORT) HTTP_PORT="$_value" ;;
|
|
XRAY_SIDECAR_PORT) XRAY_SIDECAR_PORT="$_value" ;;
|
|
CHAIN_PROXY_PORTS) CHAIN_PROXY_PORTS="$_value" ;;
|
|
CHAIN_PROXY_UIDS) CHAIN_PROXY_UIDS="$_value" ;;
|
|
CHAIN_PROXY_RULES) CHAIN_PROXY_RULES="$_value" ;;
|
|
FWMARK) FWMARK="$_value" ;;
|
|
ROUTE_TABLE) ROUTE_TABLE="$_value" ;;
|
|
ROUTE_TABLE_V6) ROUTE_TABLE_V6="$_value" ;;
|
|
ROUTE_RULE_PREF) ROUTE_RULE_PREF="$_value" ;;
|
|
ROUTE_RULE_PREF_V6) ROUTE_RULE_PREF_V6="$_value" ;;
|
|
CORE_GID) CORE_GID="$_value" ;;
|
|
APP_MODE) APP_MODE="$_value" ;;
|
|
PROXY_UIDS) PROXY_UIDS="$_value" ;;
|
|
DIRECT_UIDS) DIRECT_UIDS="$_value" ;;
|
|
BYPASS_UIDS) BYPASS_UIDS="$_value" ;;
|
|
DNS_SCOPE) DNS_SCOPE="$_value" ;;
|
|
DNS_MODE) DNS_MODE="$_value" ;;
|
|
PROXY_MODE) PROXY_MODE="$_value" ;;
|
|
IPV6_MODE) IPV6_MODE="$_value" ;;
|
|
IPV6_FAIL_CLOSED) IPV6_FAIL_CLOSED="$_value" ;;
|
|
IPV6_MANGLE_APPLIED) IPV6_MANGLE_APPLIED="$_value" ;;
|
|
IPV6_ROUTE_APPLIED) IPV6_ROUTE_APPLIED="$_value" ;;
|
|
BLOCK_QUIC) BLOCK_QUIC="$_value" ;;
|
|
SHARING_MODE) SHARING_MODE="$_value" ;;
|
|
*)
|
|
log_error "Unknown runtime snapshot key ${_name}"
|
|
return 1
|
|
;;
|
|
esac
|
|
done < "${SNAPSHOT_DIR}/env.sh"
|
|
validate_runtime_values
|
|
log_info "Loaded runtime snapshot from ${SNAPSHOT_DIR}/env.sh"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
setup_policy_routing() {
|
|
teardown_policy_routing
|
|
|
|
IPV6_ROUTE_APPLIED=0
|
|
|
|
mkdir -p "${SNAPSHOT_DIR}"
|
|
_ip_rule_err="${SNAPSHOT_DIR}/ip_rule.err"
|
|
_ip_route_err="${SNAPSHOT_DIR}/ip_route.err"
|
|
: > "${_ip_rule_err}"
|
|
: > "${_ip_route_err}"
|
|
|
|
if ! ip rule add fwmark "${FWMARK}" table "${ROUTE_TABLE}" pref "${ROUTE_RULE_PREF}" 2>"${_ip_rule_err}" && ! ip_rule_present 4 "$ROUTE_TABLE"; then
|
|
_detail="$(cat "${_ip_rule_err}" 2>/dev/null || true)"
|
|
log_error "Failed to add IPv4 policy rule fwmark ${FWMARK} table ${ROUTE_TABLE}: ${_detail:-unknown error}"
|
|
return 1
|
|
fi
|
|
if ! ip route add local default dev lo table "${ROUTE_TABLE}" 2>"${_ip_route_err}" && ! local_route_present 4 "$ROUTE_TABLE"; then
|
|
_detail="$(cat "${_ip_route_err}" 2>/dev/null || true)"
|
|
log_error "Failed to add IPv4 local route table ${ROUTE_TABLE}: ${_detail:-unknown error}"
|
|
return 1
|
|
fi
|
|
|
|
if ip -6 rule add fwmark "${FWMARK}" table "${ROUTE_TABLE_V6}" pref "${ROUTE_RULE_PREF_V6}" 2>/dev/null || ip_rule_present 6 "$ROUTE_TABLE_V6"; then
|
|
if ip -6 route add local default dev lo table "${ROUTE_TABLE_V6}" 2>/dev/null || local_route_present 6 "$ROUTE_TABLE_V6"; then
|
|
IPV6_ROUTE_APPLIED=1
|
|
else
|
|
if ipv6_required; then
|
|
log_error "IPv6 local route unavailable and IPv6 fail-closed is enabled"
|
|
while ip -6 rule del fwmark "${FWMARK}" table "${ROUTE_TABLE_V6}" 2>/dev/null; do :; done
|
|
ip -6 route del local default dev lo table "${ROUTE_TABLE_V6}" 2>/dev/null || true
|
|
return 1
|
|
fi
|
|
log_warn "IPv6 local route unavailable; continuing with IPv4-only routing"
|
|
while ip -6 rule del fwmark "${FWMARK}" table "${ROUTE_TABLE_V6}" 2>/dev/null; do :; done
|
|
ip -6 route del local default dev lo table "${ROUTE_TABLE_V6}" 2>/dev/null || true
|
|
fi
|
|
else
|
|
if ipv6_required; then
|
|
log_error "IPv6 policy routing unavailable and IPv6 fail-closed is enabled"
|
|
return 1
|
|
fi
|
|
log_warn "IPv6 policy routing unavailable; continuing with IPv4-only routing"
|
|
fi
|
|
|
|
log_info "Policy routing configured (table=${ROUTE_TABLE}, mark=${FWMARK}, ipv6=${IPV6_ROUTE_APPLIED})"
|
|
}
|
|
|
|
teardown_policy_routing() {
|
|
rknnovpn_delete_policy_routes
|
|
log_info "Policy routing removed"
|
|
}
|
|
|
|
flush_chains() {
|
|
ipt="$1"
|
|
|
|
log_info "Flushing ${ipt} chains..."
|
|
|
|
while ${ipt} ${IPT_WAIT} -t mangle -D OUTPUT -j ${CHAIN_OUT} 2>/dev/null; do :; done
|
|
while ${ipt} ${IPT_WAIT} -t mangle -D PREROUTING -j ${CHAIN_PRE} 2>/dev/null; do :; done
|
|
while ${ipt} ${IPT_WAIT} -t filter -D INPUT -j ${CHAIN_IN} 2>/dev/null; do :; done
|
|
|
|
for chain in ${CHAIN_OUT} ${CHAIN_PRE} ${CHAIN_APP} ${CHAIN_BYPASS} ${CHAIN_DIVERT}; do
|
|
${ipt} ${IPT_WAIT} -t mangle -F ${chain} 2>/dev/null || true
|
|
${ipt} ${IPT_WAIT} -t mangle -X ${chain} 2>/dev/null || true
|
|
done
|
|
${ipt} ${IPT_WAIT} -t filter -F ${CHAIN_IN} 2>/dev/null || true
|
|
${ipt} ${IPT_WAIT} -t filter -X ${CHAIN_IN} 2>/dev/null || true
|
|
|
|
while ${ipt} ${IPT_WAIT} -t nat -D OUTPUT -j ${CHAIN_DNS} 2>/dev/null; do :; done
|
|
${ipt} ${IPT_WAIT} -t nat -F ${CHAIN_DNS} 2>/dev/null || true
|
|
${ipt} ${IPT_WAIT} -t nat -X ${CHAIN_DNS} 2>/dev/null || true
|
|
|
|
log_info "${ipt} chains flushed and removed"
|
|
}
|
|
|
|
do_start() {
|
|
log_info "========================================="
|
|
log_info "Starting RKNnoVPN iptables ${SCRIPT_VERSION} (mode=${APP_MODE}, proxy=${PROXY_MODE})"
|
|
log_info " TPROXY_PORT=${TPROXY_PORT}"
|
|
log_info " DNS_PORT=${DNS_PORT}"
|
|
log_info " API_PORT=${API_PORT}"
|
|
log_info " SOCKS_PORT=${SOCKS_PORT}"
|
|
log_info " HTTP_PORT=${HTTP_PORT}"
|
|
log_info " CHAIN_PROXY_PORTS=${CHAIN_PROXY_PORTS:-<none>}"
|
|
log_info " CHAIN_PROXY_UIDS=${CHAIN_PROXY_UIDS:-<none>}"
|
|
log_info " CHAIN_PROXY_RULES=${CHAIN_PROXY_RULES:-<none>}"
|
|
log_info " FWMARK=${FWMARK}"
|
|
log_info " CORE_GID=${CORE_GID}"
|
|
log_info " PROXY_UIDS=${PROXY_UIDS:-<none>}"
|
|
log_info " DIRECT_UIDS=${DIRECT_UIDS:-<none>}"
|
|
log_info " BYPASS_UIDS=${BYPASS_UIDS:-<none>}"
|
|
log_info " DNS_SCOPE=${DNS_SCOPE}"
|
|
log_info "========================================="
|
|
|
|
IPV6_MANGLE_APPLIED=0
|
|
IPV6_ROUTE_APPLIED=0
|
|
|
|
flush_chains iptables
|
|
if ipv6_mangle_available; then
|
|
flush_chains ip6tables
|
|
else
|
|
if ipv6_required; then
|
|
log_error "IPv6 iptables mangle/restore unavailable and IPv6 fail-closed is enabled"
|
|
exit 1
|
|
fi
|
|
log_warn "IPv6 iptables mangle/restore unavailable; continuing with IPv4-only interception"
|
|
fi
|
|
if ! setup_policy_routing; then
|
|
do_stop
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "${SNAPSHOT_DIR}"
|
|
gen_mangle_v4 > "${SNAPSHOT_DIR}/iptables.rules"
|
|
gen_mangle_v6 > "${SNAPSHOT_DIR}/ip6tables.rules"
|
|
log_info "Rules files generated"
|
|
|
|
log_info "Applying IPv4 rules..."
|
|
if ! iptables-restore ${IPT_WAIT} --noflush < "${SNAPSHOT_DIR}/iptables.rules"; then
|
|
log_error "iptables-restore failed for IPv4!"
|
|
cat "${SNAPSHOT_DIR}/iptables.rules" >&2
|
|
do_stop
|
|
exit 1
|
|
fi
|
|
|
|
if ipv6_mangle_available; then
|
|
log_info "Applying IPv6 rules..."
|
|
if ! ip6tables-restore ${IPT_WAIT} --noflush < "${SNAPSHOT_DIR}/ip6tables.rules"; then
|
|
cat "${SNAPSHOT_DIR}/ip6tables.rules" >&2
|
|
flush_chains ip6tables
|
|
IPV6_MANGLE_APPLIED=0
|
|
IPV6_ROUTE_APPLIED=0
|
|
while ip -6 rule del fwmark ${FWMARK} table ${ROUTE_TABLE_V6} 2>/dev/null; do :; done
|
|
ip -6 route del local default dev lo table ${ROUTE_TABLE_V6} 2>/dev/null || true
|
|
if ipv6_required; then
|
|
log_error "ip6tables-restore failed for IPv6 and IPv6 fail-closed is enabled"
|
|
do_stop
|
|
exit 1
|
|
fi
|
|
log_warn "ip6tables-restore failed for IPv6; continuing with IPv4-only interception"
|
|
else
|
|
IPV6_MANGLE_APPLIED=1
|
|
fi
|
|
else
|
|
if ipv6_required; then
|
|
log_error "Skipping IPv6 rules apply is forbidden because IPv6 fail-closed is enabled"
|
|
do_stop
|
|
exit 1
|
|
fi
|
|
log_warn "Skipping IPv6 rules apply: ip6tables/ip6tables-restore unavailable"
|
|
fi
|
|
|
|
if ! iptables ${IPT_WAIT} -t mangle -L ${CHAIN_OUT} -n >/dev/null 2>&1; then
|
|
log_error "Verification failed: ${CHAIN_OUT} not found in mangle table!"
|
|
do_stop
|
|
exit 1
|
|
fi
|
|
if ipv6_mangle_available; then
|
|
if ! ip6tables ${IPT_WAIT} -t mangle -L ${CHAIN_OUT} -n >/dev/null 2>&1; then
|
|
log_warn "Verification degraded: ${CHAIN_OUT} not found in ip6tables mangle table"
|
|
fi
|
|
fi
|
|
|
|
save_snapshot
|
|
|
|
log_info "========================================="
|
|
log_info "RKNnoVPN iptables rules applied successfully"
|
|
log_info "========================================="
|
|
}
|
|
|
|
do_stop() {
|
|
log_info "Stopping RKNnoVPN iptables..."
|
|
|
|
if [ -f "${SNAPSHOT_DIR}/env.sh" ]; then
|
|
log_info "Loading runtime snapshot for teardown..."
|
|
if ! load_snapshot; then
|
|
log_warn "Runtime snapshot is invalid; continuing teardown with current environment"
|
|
fi
|
|
else
|
|
log_warn "No runtime snapshot found — using current environment"
|
|
fi
|
|
|
|
flush_chains iptables
|
|
if command -v ip6tables >/dev/null 2>&1; then
|
|
flush_chains ip6tables
|
|
fi
|
|
teardown_policy_routing
|
|
|
|
if [ -d "${SNAPSHOT_DIR}" ]; then
|
|
rm -f "${SNAPSHOT_DIR}/iptables.rules"
|
|
rm -f "${SNAPSHOT_DIR}/ip6tables.rules"
|
|
rm -f "${SNAPSHOT_DIR}/iptables_backup.rules"
|
|
rm -f "${SNAPSHOT_DIR}/ip6tables_backup.rules"
|
|
rm -f "${SNAPSHOT_DIR}/ip_rule.err"
|
|
rm -f "${SNAPSHOT_DIR}/ip_route.err"
|
|
rm -f "${SNAPSHOT_DIR}/env.sh"
|
|
log_info "Snapshot files cleaned up"
|
|
fi
|
|
|
|
log_info "RKNnoVPN iptables rules removed"
|
|
}
|
|
|
|
do_status() {
|
|
missing=0
|
|
|
|
if ! iptables ${IPT_WAIT} -t mangle -L "${CHAIN_OUT}" -n >/dev/null 2>&1; then
|
|
log_error "missing IPv4 mangle chain ${CHAIN_OUT}"
|
|
missing=1
|
|
fi
|
|
if ! iptables ${IPT_WAIT} -t mangle -L "${CHAIN_PRE}" -n >/dev/null 2>&1; then
|
|
log_error "missing IPv4 mangle chain ${CHAIN_PRE}"
|
|
missing=1
|
|
fi
|
|
if ! iptables ${IPT_WAIT} -t mangle -C OUTPUT -j "${CHAIN_OUT}" >/dev/null 2>&1; then
|
|
log_error "missing IPv4 OUTPUT hook for ${CHAIN_OUT}"
|
|
missing=1
|
|
fi
|
|
if ! iptables ${IPT_WAIT} -t mangle -C PREROUTING -j "${CHAIN_PRE}" >/dev/null 2>&1; then
|
|
log_error "missing IPv4 PREROUTING hook for ${CHAIN_PRE}"
|
|
missing=1
|
|
fi
|
|
if ! iptables ${IPT_WAIT} -t filter -C INPUT -j "${CHAIN_IN}" >/dev/null 2>&1; then
|
|
log_error "missing IPv4 INPUT hook for ${CHAIN_IN}"
|
|
missing=1
|
|
fi
|
|
if ! check_local_listener_protection iptables; then
|
|
missing=1
|
|
fi
|
|
|
|
IPV6_MANGLE_APPLIED="${IPV6_MANGLE_APPLIED:-0}"
|
|
IPV6_ROUTE_APPLIED="${IPV6_ROUTE_APPLIED:-0}"
|
|
|
|
if [ "$IPV6_MANGLE_APPLIED" = "1" ]; then
|
|
if ! ip6tables ${IPT_WAIT} -t mangle -L "${CHAIN_OUT}" -n >/dev/null 2>&1; then
|
|
log_error "missing IPv6 mangle chain ${CHAIN_OUT}"
|
|
missing=1
|
|
fi
|
|
if ! ip6tables ${IPT_WAIT} -t mangle -L "${CHAIN_PRE}" -n >/dev/null 2>&1; then
|
|
log_error "missing IPv6 mangle chain ${CHAIN_PRE}"
|
|
missing=1
|
|
fi
|
|
if ! ip6tables ${IPT_WAIT} -t mangle -C OUTPUT -j "${CHAIN_OUT}" >/dev/null 2>&1; then
|
|
log_error "missing IPv6 OUTPUT hook for ${CHAIN_OUT}"
|
|
missing=1
|
|
fi
|
|
if ! ip6tables ${IPT_WAIT} -t mangle -C PREROUTING -j "${CHAIN_PRE}" >/dev/null 2>&1; then
|
|
log_error "missing IPv6 PREROUTING hook for ${CHAIN_PRE}"
|
|
missing=1
|
|
fi
|
|
if ! ip6tables ${IPT_WAIT} -t filter -C INPUT -j "${CHAIN_IN}" >/dev/null 2>&1; then
|
|
log_error "missing IPv6 INPUT hook for ${CHAIN_IN}"
|
|
missing=1
|
|
fi
|
|
if ! check_local_listener_protection ip6tables; then
|
|
missing=1
|
|
fi
|
|
fi
|
|
|
|
if ! ip_rule_present 4 "$ROUTE_TABLE"; then
|
|
log_error "missing IPv4 policy rule fwmark ${FWMARK} lookup ${ROUTE_TABLE}"
|
|
missing=1
|
|
fi
|
|
if ! ip route show table "${ROUTE_TABLE}" | grep -q "local default dev lo"; then
|
|
log_error "missing IPv4 local route in table ${ROUTE_TABLE}"
|
|
missing=1
|
|
fi
|
|
if [ "$IPV6_ROUTE_APPLIED" = "1" ]; then
|
|
if ! ip_rule_present 6 "$ROUTE_TABLE_V6"; then
|
|
log_error "missing IPv6 policy rule fwmark ${FWMARK} lookup ${ROUTE_TABLE_V6}"
|
|
missing=1
|
|
fi
|
|
if ! local_route_present 6 "$ROUTE_TABLE_V6"; then
|
|
log_error "missing IPv6 local route in table ${ROUTE_TABLE_V6}"
|
|
missing=1
|
|
fi
|
|
fi
|
|
|
|
if [ "$missing" -ne 0 ]; then
|
|
exit 1
|
|
fi
|
|
log_info "RKNnoVPN iptables hooks are present"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start)
|
|
require_rule_renderer
|
|
validate_env
|
|
do_start
|
|
;;
|
|
stop)
|
|
SNAPSHOT_DIR="${RUN_DIR:-${RKNNOVPN_DIR:-/data/adb/modules/rknnovpn}/run}"
|
|
if [ -f "${SNAPSHOT_DIR}/env.sh" ]; then
|
|
if ! load_snapshot; then
|
|
log_warn "Runtime snapshot is invalid; continuing teardown with defaults"
|
|
fi
|
|
fi
|
|
FWMARK="${FWMARK:-0x2023}"
|
|
ROUTE_TABLE="${ROUTE_TABLE:-2023}"
|
|
ROUTE_TABLE_V6="${ROUTE_TABLE_V6:-2024}"
|
|
do_stop
|
|
;;
|
|
status)
|
|
require_rule_renderer
|
|
SNAPSHOT_DIR="${RUN_DIR:-${RKNNOVPN_DIR:-/data/adb/modules/rknnovpn}/run}"
|
|
if [ -f "${SNAPSHOT_DIR}/env.sh" ]; then
|
|
load_snapshot
|
|
fi
|
|
validate_env
|
|
do_status
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|status}"
|
|
echo ""
|
|
echo "Environment variables must be set by the caller (daemon)."
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
exit 0
|