#!/bin/sh /etc/rc.common # SecuBox Tor Shield - Tor anonymization service # Copyright (C) 2025 CyberMind.fr START=95 STOP=10 USE_PROCD=1 PROG=/usr/sbin/torctl CONFIG=tor-shield TORRC=/var/run/tor/torrc TOR_DATA=/var/lib/tor TOR_RUN=/var/run/tor . /lib/functions.sh generate_torrc() { local enabled mode dns_over_tor socks_port socks_addr trans_port dns_port local bridges_enabled bridge_type exit_nodes exclude_exit_nodes strict_nodes config_load "$CONFIG" config_get enabled main enabled '0' config_get mode main mode 'transparent' config_get dns_over_tor main dns_over_tor '1' config_get socks_port socks port '9050' config_get socks_addr socks address '127.0.0.1' config_get trans_port trans port '9040' config_get dns_port trans dns_port '9053' config_get bridges_enabled bridges enabled '0' config_get bridge_type bridges type 'obfs4' config_get exit_nodes security exit_nodes '' config_get exclude_exit_nodes security exclude_exit_nodes '' config_get strict_nodes security strict_nodes '0' mkdir -p "$TOR_RUN" "$TOR_DATA" # Clean up stale files that may have wrong ownership rm -f "$TOR_RUN/tor.pid" "$TOR_RUN/control" "$TOR_RUN/control_auth_cookie" 2>/dev/null rm -f "$TOR_DATA/lock" 2>/dev/null chown tor:tor "$TOR_RUN" "$TOR_DATA" chmod 700 "$TOR_RUN" "$TOR_DATA" cat > "$TORRC" << EOF # SecuBox Tor Shield - Auto-generated config # Do not edit - managed by tor-shield User tor DataDirectory $TOR_DATA Log notice syslog ControlSocket $TOR_RUN/control ControlSocketsGroupWritable 1 CookieAuthentication 1 CookieAuthFile $TOR_RUN/control_auth_cookie CookieAuthFileGroupReadable 1 # SOCKS proxy SocksPort $socks_addr:$socks_port SocksPolicy accept 127.0.0.1 SocksPolicy accept 192.168.0.0/16 SocksPolicy accept 10.0.0.0/8 SocksPolicy reject * EOF # Transparent proxy mode if [ "$mode" = "transparent" ]; then cat >> "$TORRC" << EOF # Transparent proxy TransPort 0.0.0.0:$trans_port EOF fi # DNS over Tor if [ "$dns_over_tor" = "1" ]; then cat >> "$TORRC" << EOF # DNS over Tor DNSPort 0.0.0.0:$dns_port AutomapHostsOnResolve 1 AutomapHostsSuffixes .onion,.exit VirtualAddrNetworkIPv4 10.192.0.0/10 EOF fi # Bridge configuration - only enable if bridges are configured if [ "$bridges_enabled" = "1" ]; then # Count bridge lines first BRIDGE_COUNT=0 count_bridge() { BRIDGE_COUNT=$((BRIDGE_COUNT + 1)); } config_list_foreach bridges bridge_lines count_bridge # Only add UseBridges if there are actual bridges configured if [ "$BRIDGE_COUNT" -gt 0 ]; then cat >> "$TORRC" << EOF # Bridge mode UseBridges 1 EOF # Add bridge lines from config config_list_foreach bridges bridge_lines add_bridge_line fi fi # Exit node restrictions if [ -n "$exit_nodes" ]; then echo "ExitNodes $exit_nodes" >> "$TORRC" fi if [ -n "$exclude_exit_nodes" ]; then echo "ExcludeExitNodes $exclude_exit_nodes" >> "$TORRC" fi if [ "$strict_nodes" = "1" ]; then echo "StrictNodes 1" >> "$TORRC" fi # Hidden services config_foreach add_hidden_service hidden_service # GeoIP files if [ -f /usr/share/tor/geoip ]; then echo "GeoIPFile /usr/share/tor/geoip" >> "$TORRC" fi if [ -f /usr/share/tor/geoip6 ]; then echo "GeoIPv6File /usr/share/tor/geoip6" >> "$TORRC" fi # Ensure torrc is readable by tor user chown tor:tor "$TORRC" } add_bridge_line() { echo "Bridge $1" >> "$TORRC" } # Configure dnsmasq to bypass Tor DNS for excluded domains # This ensures opkg and system services can resolve domains directly setup_dnsmasq_bypass() { local dnsmasq_conf="/tmp/dnsmasq.d/tor-shield-bypass.conf" local upstream_dns="" # Get upstream DNS from WAN interface upstream_dns=$(uci -q get network.wan.dns | awk '{print $1}') [ -z "$upstream_dns" ] && upstream_dns=$(cat /tmp/resolv.conf.d/resolv.conf.auto 2>/dev/null | grep nameserver | head -1 | awk '{print $2}') [ -z "$upstream_dns" ] && upstream_dns="1.1.1.1" # Fallback to Cloudflare mkdir -p /tmp/dnsmasq.d # Generate dnsmasq server directives for excluded domains # These domains will be resolved directly, bypassing Tor DNS { echo "# Tor Shield DNS bypass - auto-generated" echo "# Domains listed here are resolved directly (not through Tor)" config_load "$CONFIG" config_list_foreach trans excluded_domains _add_dnsmasq_server "$upstream_dns" } > "$dnsmasq_conf" # Restart dnsmasq to apply changes if [ -f "$dnsmasq_conf" ] && [ -s "$dnsmasq_conf" ]; then /etc/init.d/dnsmasq restart 2>/dev/null & logger -t tor-shield "DNS bypass configured for excluded domains (upstream: $upstream_dns)" fi } _add_dnsmasq_server() { local domain="$1" local upstream="$2" [ -n "$domain" ] && echo "server=/$domain/$upstream" } cleanup_dnsmasq_bypass() { rm -f /tmp/dnsmasq.d/tor-shield-bypass.conf /etc/init.d/dnsmasq restart 2>/dev/null & } # Exclude public DNS servers when DNS over Tor is disabled # This allows DNS resolution to work with the kill switch enabled exclude_dns_servers() { local dns_servers="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 149.112.112.112" # Also get WAN DNS servers local wan_dns=$(uci -q get network.wan.dns) [ -n "$wan_dns" ] && dns_servers="$dns_servers $wan_dns" # Get DNS from resolv.conf local resolv_dns=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') [ -n "$resolv_dns" ] && dns_servers="$dns_servers $resolv_dns" # Add each DNS server to iptables exclusion (nat and filter) for dns in $dns_servers; do # Skip private IPs (already excluded) case "$dns" in 127.*|10.*|192.168.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) continue ;; esac iptables -t nat -A TOR_SHIELD -d "$dns" -j RETURN 2>/dev/null done logger -t tor-shield "DNS servers excluded from Tor routing (dns_over_tor disabled)" } # Add DNS servers to filter chain (for kill switch) exclude_dns_servers_filter() { local dns_servers="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 149.112.112.112" # Also get WAN DNS servers local wan_dns=$(uci -q get network.wan.dns) [ -n "$wan_dns" ] && dns_servers="$dns_servers $wan_dns" # Get DNS from resolv.conf local resolv_dns=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') [ -n "$resolv_dns" ] && dns_servers="$dns_servers $resolv_dns" # Add each DNS server to filter ACCEPT for dns in $dns_servers; do case "$dns" in 127.*|10.*|192.168.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) continue ;; esac iptables -t filter -A TOR_SHIELD -d "$dns" -j ACCEPT 2>/dev/null done } add_hidden_service() { local cfg="$1" local enabled name local_port virtual_port config_get enabled "$cfg" enabled '0' [ "$enabled" = "1" ] || return config_get name "$cfg" name "hidden_$cfg" config_get local_port "$cfg" local_port '80' config_get virtual_port "$cfg" virtual_port '80' local hs_dir="$TOR_DATA/hidden_service_$name" mkdir -p "$hs_dir" chown tor:tor "$hs_dir" chmod 700 "$hs_dir" cat >> "$TORRC" << EOF # Hidden Service: $name HiddenServiceDir $hs_dir HiddenServicePort $virtual_port 127.0.0.1:$local_port EOF } setup_iptables() { local mode trans_port dns_port dns_over_tor kill_switch wan_input_allow config_load "$CONFIG" config_get mode main mode 'transparent' config_get kill_switch main kill_switch '1' config_get dns_over_tor main dns_over_tor '1' config_get wan_input_allow main wan_input_allow '0' config_get trans_port trans port '9040' config_get dns_port trans dns_port '9053' # Get Tor user ID local tor_uid=$(id -u tor 2>/dev/null || echo "tor") # Remove from chains first (to allow chain deletion) iptables -t nat -D OUTPUT -j TOR_SHIELD 2>/dev/null iptables -t filter -D OUTPUT -j TOR_SHIELD 2>/dev/null iptables -t nat -D PREROUTING -i br-lan -j TOR_SHIELD_LAN 2>/dev/null # Clear existing Tor rules for chain in TOR_SHIELD TOR_SHIELD_LAN; do iptables -t nat -F $chain 2>/dev/null iptables -t nat -X $chain 2>/dev/null done iptables -t filter -F TOR_SHIELD 2>/dev/null iptables -t filter -X TOR_SHIELD 2>/dev/null # Clean up nftables rules for server mode cleanup_nftables_wan [ "$mode" = "transparent" ] || return 0 # Setup dnsmasq bypass for excluded domains (critical fix for opkg) setup_dnsmasq_bypass # Create chains (ignore "already exists" errors) iptables -t nat -N TOR_SHIELD 2>/dev/null || true iptables -t filter -N TOR_SHIELD 2>/dev/null || true # Exclude Tor traffic iptables -t nat -A TOR_SHIELD -m owner --uid-owner $tor_uid -j RETURN # Exclude local networks config_list_foreach trans excluded_ips add_excluded_ip # When DNS over Tor is disabled, exclude public DNS servers # so the kill switch doesn't block DNS queries if [ "$dns_over_tor" != "1" ]; then exclude_dns_servers fi # Exclude domains (resolve to IP and bypass Tor) config_list_foreach trans excluded_domains add_excluded_domain # Redirect DNS if enabled if [ "$dns_over_tor" = "1" ]; then iptables -t nat -A TOR_SHIELD -p udp --dport 53 -j REDIRECT --to-ports $dns_port iptables -t nat -A TOR_SHIELD -p tcp --dport 53 -j REDIRECT --to-ports $dns_port fi # Redirect TCP to transparent proxy iptables -t nat -A TOR_SHIELD -p tcp -j REDIRECT --to-ports $trans_port # Add to OUTPUT chain iptables -t nat -A OUTPUT -j TOR_SHIELD # Kill switch - block non-Tor traffic if [ "$kill_switch" = "1" ]; then iptables -t filter -A TOR_SHIELD -m owner --uid-owner $tor_uid -j ACCEPT iptables -t filter -A TOR_SHIELD -d 127.0.0.0/8 -j ACCEPT config_list_foreach trans excluded_ips add_excluded_filter_ip config_list_foreach trans excluded_domains add_excluded_domain_filter # When DNS over Tor is disabled, allow DNS server traffic through kill switch if [ "$dns_over_tor" != "1" ]; then exclude_dns_servers_filter fi # Allow response packets for inbound connections (HAProxy, etc) iptables -t filter -A TOR_SHIELD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -t filter -A TOR_SHIELD -j REJECT iptables -t filter -A OUTPUT -j TOR_SHIELD fi # Server mode: Allow WAN inbound traffic (for published services) if [ "$wan_input_allow" = "1" ]; then setup_nftables_wan_allow fi # LAN client Tor routing via PREROUTING setup_lan_proxy } # nftables helpers for server mode WAN access cleanup_nftables_wan() { # Remove any previous tor-shield nftables rules if command -v nft >/dev/null 2>&1; then nft delete chain inet fw4 tor_shield_wan_in 2>/dev/null || true fi } setup_nftables_wan_allow() { # Server mode: Ensure WAN inbound is allowed # This creates nftables rules to allow inbound connections from WAN # Works with OpenWrt firewall4 (nftables-based) if ! command -v nft >/dev/null 2>&1; then echo "Warning: nft command not found, WAN input rules may not apply" return 1 fi # Check if fw4 table exists (OpenWrt firewall4) if ! nft list table inet fw4 >/dev/null 2>&1; then echo "Note: fw4 table not found, using legacy firewall" return 0 fi # Create chain for Tor Shield WAN input if not exists nft add chain inet fw4 tor_shield_wan_in 2>/dev/null || true # Allow all established/related (responses to outgoing) nft add rule inet fw4 tor_shield_wan_in ct state established,related accept 2>/dev/null || true # Allow new inbound on common service ports (HAProxy, HTTPS, HTTP) # These are the ports typically used for published services nft add rule inet fw4 tor_shield_wan_in tcp dport 80 accept 2>/dev/null || true nft add rule inet fw4 tor_shield_wan_in tcp dport 443 accept 2>/dev/null || true nft add rule inet fw4 tor_shield_wan_in tcp dport 8443 accept 2>/dev/null || true # Hook into input_wan chain (before drop) # First remove any existing jump to avoid duplicates nft delete rule inet fw4 input_wan jump tor_shield_wan_in 2>/dev/null || true # Add jump at the beginning of input_wan nft insert rule inet fw4 input_wan jump tor_shield_wan_in 2>/dev/null || true echo "Server mode: WAN inbound allowed on ports 80, 443, 8443" } add_excluded_ip() { iptables -t nat -A TOR_SHIELD -d "$1" -j RETURN } add_excluded_filter_ip() { iptables -t filter -A TOR_SHIELD -d "$1" -j ACCEPT } add_excluded_lan_ip() { iptables -t nat -A TOR_SHIELD_LAN -d "$1" -j RETURN } # Resolve domain to IP and exclude from Tor routing add_excluded_domain() { local domain="$1" [ -z "$domain" ] && return # Resolve IPv4 addresses using nslookup (available on OpenWrt) local ips=$(nslookup "$domain" 2>/dev/null | tail -n+3 | grep "Address" | awk '{print $2}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') if [ -n "$ips" ]; then for ip in $ips; do iptables -t nat -A TOR_SHIELD -d "$ip" -j RETURN logger -t tor-shield "Excluded domain $domain -> $ip" done else logger -t tor-shield "Warning: Could not resolve domain $domain" fi } add_excluded_domain_filter() { local domain="$1" [ -z "$domain" ] && return local ips=$(nslookup "$domain" 2>/dev/null | tail -n+3 | grep "Address" | awk '{print $2}' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$') for ip in $ips; do iptables -t filter -A TOR_SHIELD -d "$ip" -j ACCEPT done } setup_lan_proxy() { local lan_proxy config_get lan_proxy trans lan_proxy '0' [ "$lan_proxy" = "1" ] || return 0 local trans_port dns_port dns_over_tor config_get trans_port trans port '9040' config_get dns_port trans dns_port '9053' config_get dns_over_tor main dns_over_tor '1' # Create PREROUTING chain iptables -t nat -N TOR_SHIELD_LAN 2>/dev/null || true # Exclude local destinations config_list_foreach trans excluded_ips add_excluded_lan_ip # Redirect DNS if [ "$dns_over_tor" = "1" ]; then iptables -t nat -A TOR_SHIELD_LAN -p udp --dport 53 -j REDIRECT --to-ports $dns_port iptables -t nat -A TOR_SHIELD_LAN -p tcp --dport 53 -j REDIRECT --to-ports $dns_port fi # Redirect TCP to Tor iptables -t nat -A TOR_SHIELD_LAN -p tcp -j REDIRECT --to-ports $trans_port # Apply to LAN interface iptables -t nat -A PREROUTING -i br-lan -j TOR_SHIELD_LAN } remove_iptables() { # Remove from chains iptables -t nat -D OUTPUT -j TOR_SHIELD 2>/dev/null iptables -t filter -D OUTPUT -j TOR_SHIELD 2>/dev/null iptables -t nat -D PREROUTING -i br-lan -j TOR_SHIELD_LAN 2>/dev/null # Flush and remove all chains for chain in TOR_SHIELD TOR_SHIELD_LAN; do iptables -t nat -F $chain 2>/dev/null iptables -t nat -X $chain 2>/dev/null done iptables -t filter -F TOR_SHIELD 2>/dev/null iptables -t filter -X TOR_SHIELD 2>/dev/null # Clean up nftables rules for server mode cleanup_nftables_wan # Clean up dnsmasq bypass rules cleanup_dnsmasq_bypass } start_service() { local enabled config_load "$CONFIG" config_get enabled main enabled '0' [ "$enabled" = "1" ] || { echo "Tor Shield is disabled. Enable with: uci set tor-shield.main.enabled=1" return 0 } # Generate torrc generate_torrc # Setup iptables rules setup_iptables # Start Tor via procd procd_open_instance tor procd_set_param command /usr/sbin/tor -f "$TORRC" procd_set_param respawn 3600 5 5 procd_set_param stdout 1 procd_set_param stderr 1 procd_set_param pidfile "$TOR_RUN/tor.pid" procd_close_instance } stop_service() { # Remove iptables rules remove_iptables # Kill tor process if [ -f "$TOR_RUN/tor.pid" ]; then kill $(cat "$TOR_RUN/tor.pid") 2>/dev/null rm -f "$TOR_RUN/tor.pid" fi } service_triggers() { procd_add_reload_trigger "$CONFIG" } reload_service() { stop start } restart_service() { stop start } status() { "$PROG" status }