diff --git a/docs/configuration.md b/docs/configuration.md index e0ec86b..9d3fc3d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -118,6 +118,75 @@ Default bundle groups: The menu is interactive: arrows move the selection, space toggles a group, Enter applies. +## HAPP client routing profile + +The subscription endpoint returns a client-side routing profile to HAPP in the `routing:` response +header. The managed default, `xrayebator-default`, sets `GlobalProxy: "true"`, so every destination +except private IPv4 ranges is tunnelled. + +That is a different knob from [Bypass routing](#bypass-routing). Bypass changes where **the server** +sends a request; the client still tunnels it, so the destination sees the VPS address. On a node +without a cascade the catch-all outbound is already `direct`, so bypass cannot change what a Russian +site sees. Keeping domestic traffic out of the tunnel is only possible in the client profile. + +### Override file + +| Variable | Default | Meaning | +|---|---|---| +| `HAPP_ROUTING_ENABLED` | `true` | Set to `false` to stop sending the `routing:` header | +| `HAPP_ROUTING_JSON_FILE` | `/usr/local/etc/xray/.happ_routing.json` | Operator override | + +If the override file exists and passes HAPP schema validation, it replaces the managed default for +every subscriber. Malformed JSON is ignored in favour of the default rather than broadcast. Restart +`xrayebator-sub.service` is not required: the file is read per request. + +Verdicts are evaluated per destination. `GlobalProxy: "false"` makes `direct` the default and sends +only `ProxySites` and `ProxyIp` through the tunnel; `GlobalProxy: "true"` inverts that and treats +`DirectSites` and `DirectIp` as the exceptions. + +### Split-tunnel example + +Domestic traffic stays on the mobile carrier, blocked services go through the VPS: + +```json +{ + "Name": "xrayebator-split", + "GlobalProxy": "false", + "RemoteDNSType": "DoH", + "RemoteDNSDomain": "https://cloudflare-dns.com/dns-query", + "RemoteDNSIP": "1.1.1.1", + "DomesticDNSType": "DoU", + "DomesticDNSDomain": "", + "DomesticDNSIP": "77.88.8.8", + "Geoipurl": "https://example.com/geo/geoip.dat", + "Geositeurl": "https://example.com/geo/geosite.dat", + "LastUpdated": "1788700000", + "DnsHosts": { "cloudflare-dns.com": "1.1.1.1" }, + "DirectSites": ["domain:ru", "domain:xn--p1ai"], + "DirectIp": ["geoip:private", "geoip:ru"], + "ProxySites": ["domain:google.com", "domain:youtube.com", "domain:instagram.com"], + "ProxyIp": ["149.154.160.0/20", "91.108.4.0/22", "91.105.192.0/23"], + "BlockSites": [], + "BlockIp": [], + "DomainStrategy": "IPIfNonMatch", + "FakeDNS": "false" +} +``` + +Three things are easy to get wrong: + +- **Telegram travels to IP addresses, not domains.** Domain rules never match it. Take the ranges + from instead of writing them from memory; the list + changes, and a missing range silently degrades media downloads while chat still works. +- **`LastUpdated` must grow.** HAPP re-imports a profile only when the value is higher than the one + it already stored. +- **Geo databases must be reachable by the client.** The managed default points at + `/sub//geoip.dat`, which is per-subscriber. An operator-wide override cannot embed one + subscriber's token, so host the databases somewhere every client can fetch them. + +While a client downloads new geo databases the previous profile keeps running, so a failed download +leaves routing unchanged rather than broken. + ## Cascade and upstream nodes The cascade is a server-side outbound and routing mode, not a new client profile. The client keeps diff --git a/docs/ru/configuration.md b/docs/ru/configuration.md index 5b69102..5fd3938 100644 --- a/docs/ru/configuration.md +++ b/docs/ru/configuration.md @@ -119,6 +119,75 @@ Bypass добавляет правила в Xray routing, чтобы выбра Меню интерактивное: стрелки двигают выбор, пробел включает и выключает группу, Enter применяет. +## Профиль маршрутизации клиента HAPP + +Эндпоинт подписки возвращает клиенту профиль маршрутизации в заголовке ответа `routing:`. +Управляемый профиль по умолчанию, `xrayebator-default`, задаёт `GlobalProxy: "true"`, поэтому в +туннель уходит всё, кроме приватных диапазонов IPv4. + +Это не то же самое, что [Bypass routing](#bypass-routing). Bypass меняет то, куда запрос отправляет +**сервер**; клиент всё равно заворачивает его в туннель, и адресат видит адрес VPS. На ноде без +каскада общий outbound и так `direct`, поэтому bypass не меняет то, что видит российский сайт. +Не пускать домашний трафик в туннель можно только профилем клиента. + +### Файл переопределения + +| Переменная | По умолчанию | Значение | +|---|---|---| +| `HAPP_ROUTING_ENABLED` | `true` | `false` полностью отключает заголовок `routing:` | +| `HAPP_ROUTING_JSON_FILE` | `/usr/local/etc/xray/.happ_routing.json` | Переопределение оператором | + +Если файл существует и проходит проверку схемы HAPP, он заменяет управляемый профиль для всех +подписчиков. Некорректный JSON игнорируется в пользу профиля по умолчанию, а не рассылается +клиентам. Перезапуск `xrayebator-sub.service` не нужен: файл читается на каждый запрос. + +Вердикт выносится для каждого адресата. При `GlobalProxy: "false"` поведением по умолчанию +становится `direct`, а в туннель уходят только `ProxySites` и `ProxyIp`. При `GlobalProxy: "true"` +всё наоборот, и исключениями работают `DirectSites` и `DirectIp`. + +### Пример раздельной маршрутизации + +Домашний трафик остаётся у оператора, заблокированные сервисы идут через VPS: + +```json +{ + "Name": "xrayebator-split", + "GlobalProxy": "false", + "RemoteDNSType": "DoH", + "RemoteDNSDomain": "https://cloudflare-dns.com/dns-query", + "RemoteDNSIP": "1.1.1.1", + "DomesticDNSType": "DoU", + "DomesticDNSDomain": "", + "DomesticDNSIP": "77.88.8.8", + "Geoipurl": "https://example.com/geo/geoip.dat", + "Geositeurl": "https://example.com/geo/geosite.dat", + "LastUpdated": "1788700000", + "DnsHosts": { "cloudflare-dns.com": "1.1.1.1" }, + "DirectSites": ["domain:ru", "domain:xn--p1ai"], + "DirectIp": ["geoip:private", "geoip:ru"], + "ProxySites": ["domain:google.com", "domain:youtube.com", "domain:instagram.com"], + "ProxyIp": ["149.154.160.0/20", "91.108.4.0/22", "91.105.192.0/23"], + "BlockSites": [], + "BlockIp": [], + "DomainStrategy": "IPIfNonMatch", + "FakeDNS": "false" +} +``` + +Три места, где легко ошибиться: + +- **Telegram ходит по IP-адресам, а не по доменам.** Доменные правила его не ловят. Диапазоны нужно + брать со страницы , а не по памяти: список меняется, + а пропущенный диапазон тихо портит загрузку медиа, пока переписка продолжает работать. +- **`LastUpdated` обязан расти.** HAPP переимпортирует профиль только когда значение больше уже + сохранённого. +- **Geo-базы должны быть доступны клиенту.** Управляемый профиль ссылается на + `/sub//geoip.dat`, а этот путь привязан к конкретному подписчику. Общее переопределение не + может содержать чужой токен, поэтому базы нужно выложить туда, откуда их скачает любой клиент. + +Пока клиент скачивает новые geo-базы, продолжает работать предыдущий профиль, поэтому неудачная +загрузка оставляет маршрутизацию прежней, а не ломает её. + ## Каскад и upstream-ноды Каскад — серверный режим outbound и routing, а не новый профиль клиента. Клиент продолжает diff --git a/docs/zh-CN/configuration.md b/docs/zh-CN/configuration.md index c4c8bd3..ef8ac6e 100644 --- a/docs/zh-CN/configuration.md +++ b/docs/zh-CN/configuration.md @@ -110,6 +110,68 @@ UFW 由安装脚本自行管理:安装 `ufw` 包,若 UFW 未启用则执行 菜单是交互式的:方向键移动光标,空格切换分组,回车应用。 +## HAPP 客户端路由配置 + +订阅端点通过响应头 `routing:` 向 HAPP 下发客户端路由配置。托管的默认配置 `xrayebator-default` +设置了 `GlobalProxy: "true"`,因此除私有 IPv4 段之外的所有目标都会走隧道。 + +这与[分流路由](#分流路由)不是同一个开关。分流路由改变的是**服务端**把请求发往何处;客户端仍然会把它 +送进隧道,目标站点看到的依旧是 VPS 的地址。在没有级联的节点上,兜底 outbound 本来就是 `direct`, +所以分流路由无法改变俄罗斯站点看到的结果。只有客户端配置才能让本国流量不进入隧道。 + +### 覆盖文件 + +| 变量 | 默认值 | 含义 | +|---|---|---| +| `HAPP_ROUTING_ENABLED` | `true` | 设为 `false` 则不再下发 `routing:` 响应头 | +| `HAPP_ROUTING_JSON_FILE` | `/usr/local/etc/xray/.happ_routing.json` | 运维方的覆盖文件 | + +如果覆盖文件存在并通过 HAPP 结构校验,它会对所有订阅者取代托管的默认配置。格式错误的 JSON 会被忽略 +并回退到默认配置,而不会下发给客户端。无需重启 `xrayebator-sub.service`:该文件在每次请求时读取。 + +判定是按目标逐个进行的。`GlobalProxy: "false"` 时默认行为是 `direct`,只有 `ProxySites` 和 +`ProxyIp` 走隧道;`GlobalProxy: "true"` 则相反,`DirectSites` 和 `DirectIp` 成为例外。 + +### 分流示例 + +本国流量留在运营商网络,被封锁的服务经由 VPS: + +```json +{ + "Name": "xrayebator-split", + "GlobalProxy": "false", + "RemoteDNSType": "DoH", + "RemoteDNSDomain": "https://cloudflare-dns.com/dns-query", + "RemoteDNSIP": "1.1.1.1", + "DomesticDNSType": "DoU", + "DomesticDNSDomain": "", + "DomesticDNSIP": "77.88.8.8", + "Geoipurl": "https://example.com/geo/geoip.dat", + "Geositeurl": "https://example.com/geo/geosite.dat", + "LastUpdated": "1788700000", + "DnsHosts": { "cloudflare-dns.com": "1.1.1.1" }, + "DirectSites": ["domain:ru", "domain:xn--p1ai"], + "DirectIp": ["geoip:private", "geoip:ru"], + "ProxySites": ["domain:google.com", "domain:youtube.com", "domain:instagram.com"], + "ProxyIp": ["149.154.160.0/20", "91.108.4.0/22", "91.105.192.0/23"], + "BlockSites": [], + "BlockIp": [], + "DomainStrategy": "IPIfNonMatch", + "FakeDNS": "false" +} +``` + +三个容易出错的地方: + +- **Telegram 按 IP 地址通信,而不是域名。** 域名规则匹配不到它。请从 + 获取网段,不要凭记忆填写:该列表会变化,遗漏一个 + 网段会悄悄拖慢媒体下载,而聊天看起来仍然正常。 +- **`LastUpdated` 必须递增。** 只有当该值大于已保存的值时,HAPP 才会重新导入配置。 +- **geo 数据库必须能被客户端访问。** 托管的默认配置指向 `/sub//geoip.dat`,该路径与具体订阅者 + 绑定。面向全体的覆盖文件无法内嵌某一个订阅者的 token,因此请把数据库放在任何客户端都能下载的位置。 + +客户端下载新的 geo 数据库期间,先前的配置继续生效,因此下载失败只会保持路由不变,而不会使其损坏。 + ## 级联与上游节点 级联是服务端的出站与路由模式,而不是新的客户端配置档。客户端仍然连接当前 VPS: