diff --git a/docs/knowledge-base/08_UCI_CONFIGURATION_SCHEMA.md b/docs/knowledge-base/08_UCI_CONFIGURATION_SCHEMA.md index 60e588f9..744116c9 100644 --- a/docs/knowledge-base/08_UCI_CONFIGURATION_SCHEMA.md +++ b/docs/knowledge-base/08_UCI_CONFIGURATION_SCHEMA.md @@ -51,6 +51,12 @@ Defines an individual manual proxy outbound node. | `sid` | `string` | Conditional | Short ID for VLESS Reality (hex string). | | `fingerprint` | `string` | No | TLS fingerprint: `chrome`, `firefox`, `safari`, `randomized`. | | `method` | `string` | Conditional | Cipher method for Shadowsocks (e.g. `2022-blake3-aes-128-gcm`). | +| `routing_mode` | `string` | No | Routing mode: `rules` (default), `direct`, `section`. | +| `routing_section` | `string` | No | Bound routing section when `routing_mode = 'section'`. | +| `isolate_lan_for_users` | `boolean` | No | Restrict access to private/LAN IP addresses (`1` / `0`). | +| `isolated_users` | `list string` | No | Specific usernames (`auth_user`) restricted from LAN. If empty, restricts all users of the inbound. | +| `isolated_subnets` | `list string` | No | Optional custom subnets to block for isolated users (defaults to `ip_is_private: true`). | +| `custom_route_rules` | `list string` | No | Raw JSON sing-box route rule objects injected before general routing rules. | --- diff --git a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js index 43eadc2c..471114ff 100644 --- a/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js +++ b/luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js @@ -2720,6 +2720,29 @@ function createServerContent(section, options = {}) { return loadRoutingSectionChoices(this); }; + o = section.option( + form.Flag, + "isolate_lan_for_users", + _("Restrict LAN access for specific users"), + _( + "Blocks access to private/LAN IP addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) for selected users before applying routing mode.", + ), + ); + o.modalonly = true; + o.rmempty = true; + + o = section.option( + form.DynamicList, + "isolated_users", + _("Restricted users (auth_user)"), + _( + "User names (e.g. from VLESS/Trojan/SS/JSON inbound users) that are forbidden from reaching LAN subnets. Leave empty to restrict all users on this inbound.", + ), + ); + o.depends("isolate_lan_for_users", "1"); + o.modalonly = true; + o.rmempty = true; + o = section.option( form.TextValue, "inbound_json", diff --git a/luci-app-tachyon/po/ru/tachyon.lmo b/luci-app-tachyon/po/ru/tachyon.lmo index fbc94d0b..66a100c6 100644 Binary files a/luci-app-tachyon/po/ru/tachyon.lmo and b/luci-app-tachyon/po/ru/tachyon.lmo differ diff --git a/luci-app-tachyon/po/ru/tachyon.po b/luci-app-tachyon/po/ru/tachyon.po index fb4cb479..22254200 100644 --- a/luci-app-tachyon/po/ru/tachyon.po +++ b/luci-app-tachyon/po/ru/tachyon.po @@ -5146,3 +5146,16 @@ msgstr "Не удалось автоматически применить стр msgid "History cleared" msgstr "История очищена" + +msgid "Restrict LAN access for specific users" +msgstr "Ограничить доступ к локальной сети для отдельных пользователей" + +msgid "Blocks access to private/LAN IP addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) for selected users before applying routing mode." +msgstr "Блокирует доступ к приватным/локальным IP-адресам (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 и др.) для указанных пользователей перед применением режима маршрутизации." + +msgid "Restricted users (auth_user)" +msgstr "Изолированные пользователи (auth_user)" + +msgid "User names (e.g. from VLESS/Trojan/SS/JSON inbound users) that are forbidden from reaching LAN subnets. Leave empty to restrict all users on this inbound." +msgstr "Имена пользователей (например, из пользователей VLESS/Trojan/SS/JSON inbound), которым запрещен доступ к подсетям LAN. Оставьте пустым, чтобы ограничить всех пользователей данного inbound." + diff --git a/luci-app-tachyon/po/ru/tachyon.ru.lmo b/luci-app-tachyon/po/ru/tachyon.ru.lmo index bc0283e5..66a100c6 100644 Binary files a/luci-app-tachyon/po/ru/tachyon.ru.lmo and b/luci-app-tachyon/po/ru/tachyon.ru.lmo differ diff --git a/luci-app-tachyon/po/templates/tachyon.pot b/luci-app-tachyon/po/templates/tachyon.pot index 1cef5033..872c36ba 100644 --- a/luci-app-tachyon/po/templates/tachyon.pot +++ b/luci-app-tachyon/po/templates/tachyon.pot @@ -7686,3 +7686,20 @@ msgstr "" #: src/tachyon/tabs/diagnostic/partials/renderStrategyFuzzerModal.ts msgid "History cleared" msgstr "" + +#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js +msgid "Restrict LAN access for specific users" +msgstr "" + +#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js +msgid "Blocks access to private/LAN IP addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.) for selected users before applying routing mode." +msgstr "" + +#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js +msgid "Restricted users (auth_user)" +msgstr "" + +#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js +msgid "User names (e.g. from VLESS/Trojan/SS/JSON inbound users) that are forbidden from reaching LAN subnets. Leave empty to restrict all users on this inbound." +msgstr "" + diff --git a/tachyon/files/etc/config/tachyon b/tachyon/files/etc/config/tachyon index 822f8ff5..ae917ae7 100644 --- a/tachyon/files/etc/config/tachyon +++ b/tachyon/files/etc/config/tachyon @@ -173,6 +173,10 @@ config settings 'settings' # option reality_short_id '5a' # option reality_max_time_difference '1m' # option transport 'tcp' +# # Restrict LAN / private network access for specific guest users: +# #option isolate_lan_for_users '1' +# #list isolated_users 'friend-1' +# #list isolated_users 'friend-2' # # sing-box-extended only: # #option transport 'xhttp' # #option transport_path '/xhttp' diff --git a/tachyon/files/usr/lib/singbox/generator_routes.uc b/tachyon/files/usr/lib/singbox/generator_routes.uc index 4c6c481e..80f8b277 100644 --- a/tachyon/files/usr/lib/singbox/generator_routes.uc +++ b/tachyon/files/usr/lib/singbox/generator_routes.uc @@ -1548,6 +1548,40 @@ function add_server_routes(config, servers, sections) { runtime_servers.add_sniff_rule(config, server); let inbound = runtime_constants.server_inbound_tag(server[".name"]); + + let isolate_lan = bool_option(server, "isolate_lan_for_users", false) || bool_option(server, "block_private_for_users", false); + let isolated_users = list_option(server, "isolated_users"); + if (length(isolated_users) == 0) + isolated_users = list_option(server, "blocked_users"); + + if (isolate_lan) { + let isolate_rule = { + action: "reject", + inbound: inbound, + ip_is_private: true + }; + if (length(isolated_users) > 0) + isolate_rule.auth_user = isolated_users; + let isolated_subnets = list_option(server, "isolated_subnets"); + if (length(isolated_subnets) > 0) + isolate_rule.ip_cidr = isolated_subnets; + push(config.route.rules, isolate_rule); + } + + let custom_rules = list_option(server, "custom_route_rules"); + if (length(custom_rules) == 0) + custom_rules = list_option(server, "custom_rules_json"); + for (let rule_str in custom_rules) { + try { + let r = json(rule_str); + if (type(r) == "object") { + if (!r.inbound) + r.inbound = inbound; + push(config.route.rules, r); + } + } catch(e) {} + } + let routing_mode = option(server, "routing_mode", "rules"); if (routing_mode == "rules") { runtime_servers.clone_rules_for_inbound( diff --git a/tests/server_isolated_users.sh b/tests/server_isolated_users.sh new file mode 100644 index 00000000..d48f0ac3 --- /dev/null +++ b/tests/server_isolated_users.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +set -eo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TACHYON_LIB="${TACHYON_LIB:-$ROOT_DIR/tachyon/files/usr/lib}" +if [ ! -d "$TACHYON_LIB" ]; then + TACHYON_LIB="/usr/lib/tachyon" +fi +WORK_DIR="$(mktemp -d)" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +fixture_with_server() { + cat >"$WORK_DIR/fixture.json" <