diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js index 1525f289..4e6a77f9 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/tor-shield/api.js @@ -105,7 +105,7 @@ var callSettings = rpc.declare({ var callSaveSettings = rpc.declare({ object: 'luci.tor-shield', method: 'save_settings', - params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes', 'apply_now'], + params: ['mode', 'dns_over_tor', 'kill_switch', 'socks_port', 'trans_port', 'dns_port', 'lan_proxy', 'exit_nodes', 'exclude_exit_nodes', 'strict_nodes', 'apply_now'], expect: { } }); @@ -173,8 +173,8 @@ return baseclass.extend({ getBridges: function() { return callBridges(); }, setBridges: function(enabled, type) { return callSetBridges(enabled, type); }, getSettings: function() { return callSettings(); }, - saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now) { - return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now !== false ? '1' : '0'); + saveSettings: function(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, lan_proxy, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now) { + return callSaveSettings(mode, dns_over_tor, kill_switch, socks_port, trans_port, dns_port, lan_proxy, exit_nodes, exclude_exit_nodes, strict_nodes, apply_now !== false ? '1' : '0'); }, formatBytes: formatBytes, diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js index bb59bacb..5e095961 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js @@ -477,7 +477,8 @@ return view.extend({ [ { id: 'anonymous', name: _('Full Anonymity'), icon: '\u{1F6E1}', emoji: '\u{1F9D9}', desc: _('All traffic through Tor'), features: ['\u{2705} ' + _('Kill Switch'), '\u{2705} ' + _('DNS Protection'), '\u{2705} ' + _('Full Routing')] }, { id: 'selective', name: _('Selective Apps'), icon: '\u{1F3AF}', emoji: '\u{1F50D}', desc: _('SOCKS proxy mode'), features: ['\u{26AA} ' + _('No Kill Switch'), '\u{26AA} ' + _('Manual Config'), '\u{2705} ' + _('App Control')] }, - { id: 'censored', name: _('Bypass Censorship'), icon: '\u{1F513}', emoji: '\u{1F30D}', desc: _('Bridge connections'), features: ['\u{2705} ' + _('obfs4 Bridges'), '\u{2705} ' + _('Anti-Censorship'), '\u{2705} ' + _('Stealth Mode')] } + { id: 'censored', name: _('Bypass Censorship'), icon: '\u{1F513}', emoji: '\u{1F30D}', desc: _('Bridge connections'), features: ['\u{2705} ' + _('obfs4 Bridges'), '\u{2705} ' + _('Anti-Censorship'), '\u{2705} ' + _('Stealth Mode')] }, + { id: 'server', name: _('Server Mode'), icon: '\u{1F5A5}', emoji: '\u{1F5A5}\uFE0F', desc: _('Public IP + Tor outbound'), features: ['\u{2705} ' + _('Public IP Preserved'), '\u{2705} ' + _('Outbound via Tor'), '\u{2705} ' + _('LAN Clients Anonymized')] } ].map(function(preset) { var isSelected = self.currentPreset === preset.id; return E('div', { diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js index cc96db84..025a87ab 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/settings.js @@ -18,6 +18,7 @@ return view.extend({ mode: form.querySelector('[name="mode"]').value, dns_over_tor: form.querySelector('[name="dns_over_tor"]').checked ? '1' : '0', kill_switch: form.querySelector('[name="kill_switch"]').checked ? '1' : '0', + lan_proxy: form.querySelector('[name="lan_proxy"]').checked ? '1' : '0', socks_port: parseInt(form.querySelector('[name="socks_port"]').value) || 9050, trans_port: parseInt(form.querySelector('[name="trans_port"]').value) || 9040, dns_port: parseInt(form.querySelector('[name="dns_port"]').value) || 9053, @@ -37,6 +38,7 @@ return view.extend({ settings.socks_port, settings.trans_port, settings.dns_port, + settings.lan_proxy, settings.exit_nodes, settings.exclude_exit_nodes, settings.strict_nodes, @@ -112,6 +114,20 @@ return view.extend({ ]), E('p', { 'style': 'font-size: 12px; color: var(--tor-text-muted); margin-top: 4px; margin-left: 24px;' }, _('Block all non-Tor traffic if the connection drops. Prevents IP leaks.')) + ]), + + // LAN Proxy + E('div', { 'style': 'margin-bottom: 20px;' }, [ + E('label', { 'style': 'display: flex; align-items: center; gap: 8px; cursor: pointer;' }, [ + E('input', { + 'type': 'checkbox', + 'name': 'lan_proxy', + 'checked': data.lan_proxy + }), + E('span', { 'style': 'font-weight: 600;' }, _('LAN Client Proxy')) + ]), + E('p', { 'style': 'font-size: 12px; color: var(--tor-text-muted); margin-top: 4px; margin-left: 24px;' }, + _('Route LAN client traffic through Tor via PREROUTING. Used by Server Mode to anonymize outbound traffic while preserving inbound connections.')) ]) ]) ]), diff --git a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield index dcb85ba8..905e0f48 100755 --- a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield +++ b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield @@ -155,11 +155,12 @@ do_enable() { # Load preset configuration config_load "$CONFIG" - local preset_mode preset_dns preset_kill preset_bridges + local preset_mode preset_dns preset_kill preset_bridges preset_lan_proxy config_get preset_mode "$preset" mode 'transparent' config_get preset_dns "$preset" dns_over_tor '1' config_get preset_kill "$preset" kill_switch '1' config_get preset_bridges "$preset" use_bridges '0' + config_get preset_lan_proxy "$preset" lan_proxy '0' # Apply preset settings uci set tor-shield.main.enabled='1' @@ -167,6 +168,7 @@ do_enable() { uci set tor-shield.main.dns_over_tor="$preset_dns" uci set tor-shield.main.kill_switch="$preset_kill" uci set tor-shield.main.current_preset="$preset" + uci set tor-shield.trans.lan_proxy="$preset_lan_proxy" if [ "$preset_bridges" = "1" ]; then uci set tor-shield.bridges.enabled='1' @@ -599,6 +601,14 @@ get_presets() { json_add_string "description" "Use bridges to bypass network restrictions" json_close_object + # Server preset + json_add_object + json_add_string "id" "server" + json_add_string "name" "Server Mode" + json_add_string "icon" "server" + json_add_string "description" "Serve websites publicly while routing outbound through Tor" + json_close_object + json_close_array json_dump } @@ -684,12 +694,14 @@ get_settings() { json_add_string "socks_address" "$socks_addr" # Transparent proxy settings - local trans_port dns_port + local trans_port dns_port lan_proxy config_get trans_port trans port '9040' config_get dns_port trans dns_port '9053' + config_get lan_proxy trans lan_proxy '0' json_add_int "trans_port" "$trans_port" json_add_int "dns_port" "$dns_port" + json_add_boolean "lan_proxy" "$lan_proxy" # Security settings local exit_nodes exclude_exit strict_nodes @@ -841,7 +853,7 @@ save_settings() { json_load "$input" # Get values from input BEFORE json_init (which wipes loaded JSON) - local mode dns_over_tor kill_switch socks_port trans_port dns_port + local mode dns_over_tor kill_switch socks_port trans_port dns_port lan_proxy local exit_nodes exclude_exit strict_nodes apply_now json_get_var mode mode json_get_var dns_over_tor dns_over_tor @@ -849,6 +861,7 @@ save_settings() { json_get_var socks_port socks_port json_get_var trans_port trans_port json_get_var dns_port dns_port + json_get_var lan_proxy lan_proxy json_get_var exit_nodes exit_nodes json_get_var exclude_exit exclude_exit_nodes json_get_var strict_nodes strict_nodes @@ -886,6 +899,7 @@ save_settings() { [ -n "$socks_port" ] && uci set tor-shield.socks.port="$socks_port" [ -n "$trans_port" ] && uci set tor-shield.trans.port="$trans_port" [ -n "$dns_port" ] && uci set tor-shield.trans.dns_port="$dns_port" + [ -n "$lan_proxy" ] && uci set tor-shield.trans.lan_proxy="$lan_proxy" [ -n "$exit_nodes" ] && uci set tor-shield.security.exit_nodes="$exit_nodes" [ -n "$exclude_exit" ] && uci set tor-shield.security.exclude_exit_nodes="$exclude_exit" [ -n "$strict_nodes" ] && uci set tor-shield.security.strict_nodes="$strict_nodes" @@ -930,7 +944,7 @@ do_restart() { case "$1" in list) - echo '{"status":{},"enable":{"preset":"str"},"disable":{},"restart":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"refresh_ips":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"},"excluded_destinations":{},"add_excluded_destination":{"destination":"str"},"remove_excluded_destination":{"destination":"str"},"apply_exclusions":{}}' + echo '{"status":{},"enable":{"preset":"str"},"disable":{},"restart":{},"circuits":{},"new_identity":{},"check_leaks":{},"hidden_services":{},"add_hidden_service":{"name":"str","local_port":"int","virtual_port":"int"},"remove_hidden_service":{"name":"str"},"exit_ip":{},"refresh_ips":{},"bandwidth":{},"presets":{},"bridges":{},"set_bridges":{"enabled":"bool","type":"str"},"settings":{},"save_settings":{"mode":"str","dns_over_tor":"bool","kill_switch":"bool","socks_port":"int","trans_port":"int","dns_port":"int","lan_proxy":"bool","exit_nodes":"str","exclude_exit_nodes":"str","strict_nodes":"bool"},"excluded_destinations":{},"add_excluded_destination":{"destination":"str"},"remove_excluded_destination":{"destination":"str"},"apply_exclusions":{}}' ;; call) case "$2" in diff --git a/package/secubox/secubox-app-tor/files/etc/config/tor-shield b/package/secubox/secubox-app-tor/files/etc/config/tor-shield index 5ab2cc99..0b724b7a 100644 --- a/package/secubox/secubox-app-tor/files/etc/config/tor-shield +++ b/package/secubox/secubox-app-tor/files/etc/config/tor-shield @@ -29,6 +29,14 @@ config preset 'censored' option use_bridges '1' option dns_over_tor '1' +config preset 'server' + option name 'Server Mode' + option icon 'server' + option mode 'transparent' + option dns_over_tor '1' + option kill_switch '1' + option lan_proxy '1' + config proxy 'socks' option port '9050' option address '127.0.0.1' @@ -36,6 +44,7 @@ config proxy 'socks' config transparent 'trans' option port '9040' option dns_port '9053' + option lan_proxy '0' list excluded_ips '192.168.0.0/16' list excluded_ips '10.0.0.0/8' list excluded_ips '172.16.0.0/12' diff --git a/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield b/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield index 3b3d4320..23698b79 100755 --- a/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield +++ b/package/secubox/secubox-app-tor/files/etc/init.d/tor-shield @@ -173,13 +173,16 @@ setup_iptables() { # Get Tor user ID local tor_uid=$(id -u tor 2>/dev/null || echo "tor") - # Remove from OUTPUT chain first (to allow chain deletion) + # 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 - iptables -t nat -F TOR_SHIELD 2>/dev/null - iptables -t nat -X TOR_SHIELD 2>/dev/null + 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 @@ -212,9 +215,14 @@ setup_iptables() { 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 + # 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 + + # LAN client Tor routing via PREROUTING + setup_lan_proxy } add_excluded_ip() { @@ -225,14 +233,50 @@ 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 +} + +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 OUTPUT chain + # 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 chains - iptables -t nat -F TOR_SHIELD 2>/dev/null - iptables -t nat -X TOR_SHIELD 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 } diff --git a/package/secubox/secubox-app-tor/files/usr/sbin/torctl b/package/secubox/secubox-app-tor/files/usr/sbin/torctl index 603d701c..a8abb584 100644 --- a/package/secubox/secubox-app-tor/files/usr/sbin/torctl +++ b/package/secubox/secubox-app-tor/files/usr/sbin/torctl @@ -14,7 +14,7 @@ Usage: torctl [options] Commands: status Show Tor Shield status - enable [preset] Enable Tor Shield (presets: anonymous, selective, censored) + enable [preset] Enable Tor Shield (presets: anonymous, selective, censored, server) disable Disable Tor Shield restart Restart Tor Shield identity Get new Tor identity (new circuits) @@ -29,6 +29,7 @@ Options: Examples: torctl enable anonymous Enable with full anonymity preset + torctl enable server Enable server mode (public IP + Tor outbound) torctl status Show current status torctl identity Request new circuits torctl exit-ip Show Tor exit IP @@ -150,17 +151,19 @@ cmd_enable() { # Load preset configuration config_load "$CONFIG" - local preset_mode preset_dns preset_kill preset_bridges + local preset_mode preset_dns preset_kill preset_bridges preset_lan_proxy config_get preset_mode "$preset" mode 'transparent' config_get preset_dns "$preset" dns_over_tor '1' config_get preset_kill "$preset" kill_switch '1' config_get preset_bridges "$preset" use_bridges '0' + config_get preset_lan_proxy "$preset" lan_proxy '0' # Apply preset settings uci set tor-shield.main.enabled='1' uci set tor-shield.main.mode="$preset_mode" uci set tor-shield.main.dns_over_tor="$preset_dns" uci set tor-shield.main.kill_switch="$preset_kill" + uci set tor-shield.trans.lan_proxy="$preset_lan_proxy" if [ "$preset_bridges" = "1" ]; then uci set tor-shield.bridges.enabled='1'