sdm-xray v0.2.2: self-signed mode, uninstall.sh, panel v3 (TLS/REALITY, stats, backup), tests, docs
Added: - install.sh --selfsigned: no-domain mode with pinnedPeerCertSha256 - uninstall.sh: full removal of what install.sh deploys - Panel v3: TLS/REALITY switch, client rename, log level, backup/restore, version+cert status, traffic stats (xray stats API) - Auto-pin for self-signed certs in generated configs/links - Go tests (14 tests, all passing) - CHANGELOG.md, ARCHITECTURE.md, README.md rewrite - Email defaults to UUID when client name is empty (fixes stats) Fixed: - install.sh SIGPIPE and double-v banner - Mirror drained body bug (iana.org)
This commit is contained in:
Executable
+117
@@ -0,0 +1,117 @@
|
||||
# Архитектура sdm-xray
|
||||
|
||||
Документ для разработчиков. Для установки/использования — см. [README](README-sdm-xray.md).
|
||||
|
||||
---
|
||||
|
||||
## Топология
|
||||
|
||||
```
|
||||
клиент (ПК/роутер)
|
||||
→ SOCKS5 127.0.0.1:10808
|
||||
→ VLESS + stealth (свой фрейминг, HTTP/2, ротация путей)
|
||||
→ TLS 1.3 (uTLS chrome, ALPN h2)
|
||||
→ VPS:443
|
||||
→ xray (сервер) → интернет
|
||||
→ GET/HEAD от зондов → зеркало реального сайта
|
||||
```
|
||||
|
||||
## Транспорт stealth
|
||||
|
||||
Кастомный транспорт Xray-core (поле `network: "stealth"` в streamSettings).
|
||||
Каждая сессия = один HTTP/2 POST-стрим (bidirectional).
|
||||
|
||||
### Wire-протокол
|
||||
```
|
||||
[TLS 1.3] → [HTTP/2 POST на путь] → [тело: кадры]
|
||||
```
|
||||
|
||||
Формат кадра: `[4B длина][1B тип][payload]`
|
||||
|
||||
| Тип | Значение | Смысл |
|
||||
|-----|----------|-------|
|
||||
| DATA | 0x01 | Данные туннеля (VLESS-поток) |
|
||||
| PAD | 0x02 | Padding (читатель пропускает) |
|
||||
| AUTH | 0xFE | Первый кадр тела запроса (ключ) |
|
||||
| CLOSE | 0xFF | Закрытие |
|
||||
|
||||
### Поведение сервера
|
||||
- POST на туннельный путь + AUTH → туннель (200 + двунаправленный обмен)
|
||||
- POST без ключа/неверный ключ → 404 + закрытие
|
||||
- GET/HEAD (не-туннельные) → reverse-proxy на `target` (зеркало)
|
||||
|
||||
### Зеркало
|
||||
Сервер реверсивно проксирует GET/HEAD запросы на `target` (реальный сайт).
|
||||
Переписывает Location, HTML-ссылки (href/src/content), CSS url() на свой домен.
|
||||
Лимит тела: 8МБ. `Accept-Encoding: identity` (для переписывания).
|
||||
|
||||
## Файлы транспорта
|
||||
|
||||
`transport/internet/stealth/`:
|
||||
- `config.proto` + `config.pb.go` — protobuf-конфигурация
|
||||
- `config.go` — регистрация конфига в Xray
|
||||
- `framing.go` — чтение/запись кадров
|
||||
- `conn.go` — h2Conn (net.Conn поверх HTTP/2 стрима)
|
||||
- `dialer.go` — клиент (TLS/uTLS, ротация, ретрай, decoy)
|
||||
- `hub.go` — сервер (http.Server, зеркало, AUTH)
|
||||
|
||||
Изменённые файлы upstream:
|
||||
- `infra/conf/transport_internet.go` — case "stealth", поле StealthSettings
|
||||
- `infra/conf/transport_method.go` — struct StealthConfig + Build()
|
||||
|
||||
## Сборка (WSL)
|
||||
|
||||
```bash
|
||||
cd ~/xray-mod
|
||||
export PATH="$PATH:/usr/local/go/bin"
|
||||
GOOS=linux GOARCH=amd64 go build -o build/xray-server-amd64 ./main
|
||||
GOOS=linux GOARCH=arm64 go build -o build/xray-client-arm64 ./main
|
||||
GOOS=windows GOARCH=amd64 go build -o build/xray-client-win.exe ./main
|
||||
```
|
||||
|
||||
Regen protobuf:
|
||||
```bash
|
||||
protoc -I. --go_out=. --go_opt=paths=source_relative transport/internet/stealth/config.proto
|
||||
```
|
||||
|
||||
Панель:
|
||||
```bash
|
||||
cd ~/sdm-panel
|
||||
GOOS=linux GOARCH=amd64 go build -o sdm-panel-linux-amd64 .
|
||||
```
|
||||
|
||||
## Деплой на VPS
|
||||
|
||||
```bash
|
||||
# SFTP: build/xray-server-amd64 → /usr/local/xray/xray
|
||||
# install -m 755, systemctl restart xray
|
||||
```
|
||||
|
||||
Конфиги:
|
||||
- `/etc/xray/config.json` — основной конфиг xray
|
||||
- `/etc/xray/panel.json` — конфиг панели (bind/password/serverName)
|
||||
- `/etc/xray/server.crt` + `/etc/xray/server.key` — self-signed сертификат (selfsigned-режим)
|
||||
- `/etc/letsencrypt/live/<domain>/` — LE-сертификат (доменный режим)
|
||||
|
||||
## VPS (актуальные)
|
||||
|
||||
| VPS | Домен | IP | Система | xray |
|
||||
|-----|-------|-----|---------|------|
|
||||
| Основная | pay.dedikar.online | 13.143.132.66 | Ubuntu 24.04 | 978fd1a |
|
||||
| Старая | vds.dedikar.online | 94.183.182.49 | Ubuntu 24.04 | 8f75aa4 |
|
||||
|
||||
## Интеграция в DedVpn.App
|
||||
|
||||
Копия: `C:\Users\dedikar\PsiphonWarp\DedVpn.App`
|
||||
- `Services/Xray/XrayConfigBuilder.cs` — эмит `network: "stealth"` + `stealthSettings`
|
||||
- `Services/Xray/SingBoxConfigBuilder.cs` — guard: stealth не поддерживается в sing-box
|
||||
- vless://-ссылка из панели → импорт в приложение
|
||||
- Runtime: патченый `xray.exe` в `runtime/xray/xray.exe`
|
||||
|
||||
## Известные ограничения
|
||||
|
||||
- HTTP/2 — один поток, throughput ограничен (~140 Мбит bulk, ~22 Мбит веб)
|
||||
- Оптимизации (flush-батчинг, окна HTTP/2) провалились — откачены
|
||||
- Стока REALITY быстрее (голый TLS), но stealth даёт уникальную маскировку
|
||||
- Клиент и сервер должны быть одной версии форка
|
||||
- На роутере (ARM Cortex-A53) — CPU для TLS является узким местом
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
# Changelog
|
||||
|
||||
Все заметные изменения проекта sdm-xray (анти-DPI туннель stealth).
|
||||
|
||||
Формат: [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Добавлено
|
||||
- `install.sh --selfsigned` — режим без домена: самоподписанный сертификат (RSA 2048, 10 лет, CN=IP), клиенты доверяют по `pinnedPeerCertSha256`. Без nginx/certbot.
|
||||
- `uninstall.sh` — полный снос всего, что ставит install.sh (systemd, бинари, конфиги, nginx-сайт, LE-сертификат).
|
||||
- Панель v3: переключение TLS↔REALITY, переименование клиентов, лог-уровень xray, бэкап/восстановление config.json, версия бинаря + статус сертификата, статистика по клиентам (трафик + онлайн-IP, xray stats API).
|
||||
- Панель автоматически добавляет `pinnedPeerCertSha256` в клиентские конфиги/ссылки при self-signed сервере.
|
||||
- Тесты Go-панели (14 тестов: hexKey, uuid, tlsCertFile, selfSignedPin, statsInboundPort, ensureStats, stealthInbounds, genX25519, writeJSON, errStr, PanelConfig, email).
|
||||
- CHANGELOG.md, ARCHITECTURE.md.
|
||||
|
||||
### Исправлено
|
||||
- Email клиента по умолчанию = UUID (вместо "client"). Раньше безымянные клиенты дублировали email "client" и их трафик сливался. Теперь каждый клиент уникален, статистика работает всегда.
|
||||
|
||||
---
|
||||
|
||||
## [v0.2.1] — 2026-08-16
|
||||
|
||||
### Исправлено
|
||||
- install.sh: SIGPIPE при чтении версии (заменено на `sed`), дублирующаяся `v` в баннере.
|
||||
- Зеркало: drained-body баг — тело ответа не инжектируется в `ModifyResponse` при неизменённом состоянии (iana.org работает корректно).
|
||||
|
||||
### Добавлено
|
||||
- Панель управления sdm-panel v2 (Go, stdlib): статус, клиенты (UUID/key/paths), tunnel settings, логи, рестарт.
|
||||
- install.sh интегрирует панель (systemd sdm-panel, автоматический пароль).
|
||||
- PROTOCOL.md — спецификация wire-протокола для интеграторов.
|
||||
- stealth.patch — полный патч транспорта (1372 строки, для референса).
|
||||
|
||||
### Изменено
|
||||
- Ассеты релиза: сервер/клиенты (win/arm64)/geo/sdm-panel.
|
||||
|
||||
---
|
||||
|
||||
## [v0.2.0] — 2026-08-15
|
||||
|
||||
### Добавлено
|
||||
- Панель управления sdm-panel (Go, stdlib): HTTP на 127.0.0.1:8080, авторизация по паролю, API для просмотра/редактирования config.json.
|
||||
- `install.sh` — скрипт установки на свежую Ubuntu VPS (домен + LE + nginx + xray + панель).
|
||||
- Клиентские конфиги (client-stealth.json, client-vds.json, client-pay.json).
|
||||
|
||||
### Изменено
|
||||
- Транспорт stealth v10: адаптивный ретрай со сменой identity (свежий path/port при неудаче).
|
||||
- Зеркало: редирект-фоллоу, HTML/CSS-переписывание ссылок, browser-like padding.
|
||||
- REALITY: поддержка `security: "reality"` с apple.com в качестве таргета.
|
||||
|
||||
---
|
||||
|
||||
## [v0.1.0] — 2026-08-14
|
||||
|
||||
### Добавлено
|
||||
- Кастомный транспорт `stealth` для Xray-core (поле `network: "stealth"` в streamSettings).
|
||||
- Wire-протокол: фрейминг `[len][type][payload]`, AUTH ключом, DATA/PAD/CLOSE кадры.
|
||||
- Клиент: ротация путей (`paths`), ротация портов (`ports`), padding/pacing, rate-limiter (`bps`).
|
||||
- Сервер: зеркало реального сайта (`target`) для GET/HEAD запросов от зондов.
|
||||
- Сборка: Go 1.26, protoc, protoc-gen-go. Бинари для linux/amd64, linux/arm64, windows/amd64.
|
||||
|
||||
### Транспорт (хронология разработки)
|
||||
| Версия | Описание |
|
||||
|--------|----------|
|
||||
| v1 | Фрейминг + auth + padding + pacing поверх raw TLS |
|
||||
| v2 | HTTP/2 транспорт (h2 POST-стримы, AUTH в теле) |
|
||||
| v3 | Анти-пробиг (заглушка для зондов), jitter, path |
|
||||
| v4 | Ротация identity (несколько путей, per-connection random) |
|
||||
| v5 | Ротация портов, rate-limiter bps |
|
||||
| v6 | Зеркало реального сайта (reverse-proxy для не-туннельных запросов) |
|
||||
| v7 | Редирект-фоллоу в зеркале, опциональный decoy-трафик |
|
||||
| v8 | HTML-переписывание ссылок в зеркале (href/src → наш домен) |
|
||||
| v9 | CSS url()-переписывание, browser-like padding profile |
|
||||
| v10 | Адаптивный ретрай со сменой identity при неудаче |
|
||||
@@ -1,79 +1,128 @@
|
||||
# sdm-xray
|
||||
|
||||
Собственный анти-DPI туннель: форк [Xray-core](https://github.com/XTLS/Xray-core) с кастомным
|
||||
транспортом **`stealth`**. Клиент → своя VPS, трафик максимально похож на браузерный HTTPS.
|
||||
транспортом **`stealth`**. Клиент → своя VPS, трафик выглядит как обычный HTTPS-запрос
|
||||
к сайту.
|
||||
|
||||
## Принцип
|
||||
«Адаптивность, а не протокол»: транспорт меняет поведение (identity) быстрее, чем DPI успевает
|
||||
его описывать. Всё в одном процессе, одним JSON-конфигом, без панелей и внешних сервисов.
|
||||
## Как это работает
|
||||
|
||||
Что умеет:
|
||||
- Свой транспорт поверх HTTP/2 (TLS 1.3 + uTLS chrome), мультиплексирование на одном канале
|
||||
- Ротация туннельных путей на каждое соединение + ретрай со свежей identity при сбое
|
||||
- Анти-пробиг: сервер зеркалит реальный сайт (по умолчанию iana.org — нейтральный,
|
||||
в РФ не блокируется) — зонд видит настоящий многостраничный сайт на вашем домене
|
||||
(редиректы, HTML/CSS-ссылки переписаны)
|
||||
- Padding/pacing под браузер, опции: ротация портов, rate-limit, decoy, REALITY
|
||||
- По умолчанию только порт 443 (как браузер)
|
||||
Каждая туннельная сессия — один HTTP/2 POST-стрим поверх TLS 1.3. Снаружи выглядит как
|
||||
обычный POST-запрос к API реального сайта. Для зондов/DPI — зеркало реального сайта
|
||||
(по умолчанию iana.org): многостраничный сайт, ссылки переписаны на ваш домен.
|
||||
|
||||
## Установка сервера (свежая Ubuntu VPS)
|
||||
Домен должен указывать A-записью на IP VPS, порт 80 открыт.
|
||||
Ключевые возможности:
|
||||
- **Ротация путей** — каждый запрос идёт по случайному пути из списка
|
||||
- **Ретрай** — при неудаче клиент автоматически пробует другой путь/порт
|
||||
- **Padding/pacing** — маскирует размеры и тайминги под браузер
|
||||
- **Зеркало** — зонд видит настоящий сайт, а не заглушку
|
||||
- **REALITY** — опционально, для полного сокрытия (чужой домен)
|
||||
|
||||
## Установка на VPS
|
||||
|
||||
### С доменом (рекомендуется)
|
||||
Домен должен указывать A-записью на IP VPS, порт 80 открыт:
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/install.sh) ваш-домен.ru
|
||||
```
|
||||
Скрипт: ставит бинарь + systemd, генерит UUID/ключ, получает Let's Encrypt, поднимает
|
||||
туннель на 443 с зеркалом. В конце печатает готовый клиентский конфиг и пароль панели.
|
||||
Скрипт установит xray + панель, получит сертификат Let's Encrypt, напечатает
|
||||
клиентский конфиг и пароль панели.
|
||||
|
||||
## Установка БЕЗ домена (self-signed)
|
||||
Домен не нужен вовсе — сервер генерит самоподписанный сертификат (10 лет), а клиенты
|
||||
доверяют ему по `pinnedPeerCertSha256` (панель подставляет его в конфиги/ссылки сама):
|
||||
### Без домена (self-signed)
|
||||
Домен не нужен — сервер генерирует самоподписанный сертификат (10 лет), клиенты
|
||||
доверяют по отпечатку:
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/install.sh) --selfsigned
|
||||
```
|
||||
Адрес сервера в клиенте — IP VPS (serverName = IP). nginx/certbot пропускаются.
|
||||
Внимание: провайдерский DPI легче распознаёт самоподписанный TLS — для максимальной
|
||||
незаметности предпочитай домен + Let's Encrypt либо REALITY.
|
||||
Адрес сервера в клиенте — IP VPS. Сертификат не требует домена/ACME.
|
||||
|
||||
## Удаление сервера
|
||||
Полный снос всего, что поставил install.sh (systemd, бинари, конфиги, nginx-сайт,
|
||||
Let's Encrypt-сертификат):
|
||||
### Удаление
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/uninstall.sh) ваш-домен.ru
|
||||
bash <(curl -fsSL https://git.my-handbook.ru/ded_ikar/sdm-xray/raw/main/uninstall.sh)
|
||||
```
|
||||
apt-пакеты (nginx/certbot) не трогаются — их могут использовать другие сервисы.
|
||||
Полностью удаляет systemd-юниты, бинари, конфиги, nginx-сайт, LE-сертификат.
|
||||
apt-пакеты не трогает.
|
||||
|
||||
## Панель управления (`sdm-panel`)
|
||||
Отдельный бинарь на VPS, слушает `127.0.0.1:8080` (наружу не торчит). Доступ через
|
||||
SSH-туннель (трафик шифрован SSH):
|
||||
## Панель управления
|
||||
|
||||
Go-бинарник на VPS, слушает `127.0.0.1:8080`. Доступ через SSH-туннель:
|
||||
```bash
|
||||
ssh -L 8080:127.0.0.1:8080 root@<vps-ip>
|
||||
# затем http://localhost:8080 (login: admin / пароль из вывода install.sh)
|
||||
```
|
||||
Возможности: статус, просмотр/правка config.json, ротация UUID+ключ, логи, рестарт xray.
|
||||
Пароль хранится в `/etc/xray/panel.json` (root-only). Конфиг панели: bind/password.
|
||||
|
||||
Возможности:
|
||||
- Статус сервера (активность, версия, сертификат)
|
||||
- Управление клиентами (добавление/удаление/переименование, ротация UUID)
|
||||
- Туннель (зеркало, маскировка, экспертные настройки, TLS/REALITY)
|
||||
- Статистика (трафик по клиентам, онлайн-IP)
|
||||
- Логи, рестарт, бэкап/восстановление конфига
|
||||
|
||||
## Клиенты
|
||||
- **Windows**: скачать `xray-stealth-client-windows-amd64.exe` из Releases, сохранить
|
||||
`client-stealth.json` (из вывода install.sh), запустить `xray-stealth-client-windows-amd64.exe run -c client-stealth.json`,
|
||||
прокси SOCKS на `127.0.0.1:10808`.
|
||||
- **Роутер**: `xray-stealth-client-linux-arm64` — положить в `/tmp`, тот же конфиг,
|
||||
SOCKS 127.0.0.1:10808 или TUN + policy routing.
|
||||
|
||||
## Параметры `stealthSettings`
|
||||
`key`, `padMax`, `padProb`, `paceMs`, `paceJitterMs`, `path`/`paths`, `ports` (опция),
|
||||
`bps` (опция), `target` (зеркало), `decoy`/`decoyEverySecs`/`decoyTarget` (опция),
|
||||
`padProfile` ("browser"|"uniform"), `retries`.
|
||||
### Windows
|
||||
Скачать `xray-stealth-client-windows-amd64.exe` из [Releases](https://git.my-handbook.ru/ded_ikar/sdm-xray/releases),
|
||||
сохранить `client-stealth.json` (из вывода install.sh), запустить:
|
||||
```
|
||||
xray-stealth-client-windows-amd64.exe run -c client-stealth.json
|
||||
```
|
||||
SOCKS-прокси: `127.0.0.1:10808`, HTTP-прокси: `127.0.0.1:10809`.
|
||||
|
||||
### Роутер (ARM64)
|
||||
`xray-stealth-client-linux-arm64` — положить в `/tmp`, тот же конфиг.
|
||||
SOCKS 127.0.0.1:10808 или TUN + policy routing.
|
||||
|
||||
## Параметры транспорта
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------|-------------|----------|
|
||||
| `key` | — | Общий секрет (клиент и сервер) |
|
||||
| `paths` | `["/api/t04d12b46/"]` | Туннельные пути (ротация) |
|
||||
| `ports` | `[443]` | Порты (ротация, опция) |
|
||||
| `padMax` | `2048` | Максимальный размер PAD-кадра |
|
||||
| `padProb` | `50` | Вероятность PAD после DATA (%) |
|
||||
| `paceMs` | `0` | Минимальный интервал между кадрами (мс) |
|
||||
| `paceJitterMs` | `0` | Случайный довесок к paceMs (мс) |
|
||||
| `bps` | `0` | Rate-limit (0 = выкл) |
|
||||
| `target` | — | Зеркало (URL сайта для зондов) |
|
||||
| `retries` | `3` | Количество ретраев |
|
||||
| `padProfile` | `"browser"` | `"browser"` или `"uniform"` |
|
||||
| `decoy` | `false` | Фоновый трафик к CDN |
|
||||
|
||||
## Сборка из исходников
|
||||
Нужны Go 1.26, protoc, protoc-gen-go.
|
||||
|
||||
Нужны Go 1.26, protoc, protoc-gen-go:
|
||||
```bash
|
||||
GOOS=linux GOARCH=amd64 go build -o xray-server-amd64 ./main # сервер
|
||||
GOOS=linux GOARCH=arm64 go build -o xray-client-arm64 ./main # роутер
|
||||
GOOS=windows GOARCH=amd64 go build -o xray-client-win.exe ./main # Windows
|
||||
```
|
||||
Транспорт: `stealth/transport/internet/stealth/`. Изменённые файлы upstream:
|
||||
`stealth/infra/conf/transport_internet.go`, `stealth/infra/conf/transport_method.go`.
|
||||
|
||||
## Лицензия и атрибуция
|
||||
MPL-2.0. Форк Xray-core (XTLS) — https://github.com/XTLS/Xray-core. Новые файлы транспорта —
|
||||
наш код; изменённые файлы upstream распространяются под MPL-2.0 (исходники приложены в `stealth/`).
|
||||
Панель:
|
||||
```bash
|
||||
cd panel && go build -o sdm-panel-linux-amd64 .
|
||||
```
|
||||
|
||||
## Структура репозитория
|
||||
|
||||
```
|
||||
├── stealth/transport/internet/stealth/ # Код транспорта (новые файлы)
|
||||
│ ├── config.proto + config.pb.go # Конфигурация (protobuf)
|
||||
│ ├── framing.go # Формат кадров
|
||||
│ ├── conn.go # net.Conn поверх HTTP/2 стрима
|
||||
│ ├── dialer.go # Клиент (TLS, ротация, ретрай)
|
||||
│ └── hub.go # Сервер (зеркало, AUTH)
|
||||
├── panel/ # Панель управления (Go)
|
||||
│ ├── main.go # HTTP-сервер + API
|
||||
│ ├── ui.go # Встроенный HTML
|
||||
│ └── stats/ # Генерированные gRPC-стабы
|
||||
├── install.sh # Установка на VPS
|
||||
├── uninstall.sh # Удаление
|
||||
├── PROTOCOL.md # Спецификация wire-протокола
|
||||
├── CHANGELOG.md # История изменений
|
||||
└── LICENSE # MPL-2.0
|
||||
```
|
||||
|
||||
## Лицензия
|
||||
|
||||
MPL-2.0. Новые файлы транспорта — наш код; изменённые файлы upstream (Xray-core)
|
||||
распространяются под MPL-2.0.
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
module sdm-panel
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
google.golang.org/grpc v1.83.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
|
||||
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
@@ -0,0 +1,369 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHexKey(t *testing.T) {
|
||||
key := hexKey(8)
|
||||
if len(key) != 16 {
|
||||
t.Errorf("hexKey(8) length = %d, want 16", len(key))
|
||||
}
|
||||
key2 := hexKey(8)
|
||||
if key == key2 {
|
||||
t.Error("hexKey should produce different values on each call")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
id := uuid()
|
||||
if len(id) != 36 {
|
||||
t.Errorf("uuid() length = %d, want 36", len(id))
|
||||
}
|
||||
if id[8] != '-' || id[13] != '-' || id[18] != '-' || id[23] != '-' {
|
||||
t.Errorf("uuid() format wrong: %s", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTlsCertFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ts map[string]any
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "with cert",
|
||||
ts: map[string]any{
|
||||
"certificates": []any{
|
||||
map[string]any{"certificateFile": "/etc/xray/server.crt"},
|
||||
},
|
||||
},
|
||||
want: "/etc/xray/server.crt",
|
||||
},
|
||||
{
|
||||
name: "no certs",
|
||||
ts: map[string]any{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty certs",
|
||||
ts: map[string]any{"certificates": []any{}},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
ts: map[string]any{"certificates": "not a list"},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tlsCertFile(tt.ts)
|
||||
if got != tt.want {
|
||||
t.Errorf("tlsCertFile() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfSignedPin(t *testing.T) {
|
||||
// Create a temp self-signed cert
|
||||
dir := t.TempDir()
|
||||
certPath := filepath.Join(dir, "test.crt")
|
||||
keyPath := filepath.Join(dir, "test.key")
|
||||
|
||||
// Generate self-signed cert using openssl
|
||||
out, err := sh("openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
|
||||
"-days", "1", "-subj", "/CN=127.0.0.1",
|
||||
"-keyout", keyPath, "-out", certPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate cert: %v, out: %s", err, out)
|
||||
}
|
||||
|
||||
pin := selfSignedPin(certPath)
|
||||
if pin == "" {
|
||||
t.Fatal("selfSignedPin returned empty for self-signed cert")
|
||||
}
|
||||
if len(pin) != 64 {
|
||||
t.Errorf("pin length = %d, want 64 (hex sha256)", len(pin))
|
||||
}
|
||||
|
||||
// Same cert should produce same pin
|
||||
pin2 := selfSignedPin(certPath)
|
||||
if pin != pin2 {
|
||||
t.Errorf("selfSignedPin not deterministic: %q != %q", pin, pin2)
|
||||
}
|
||||
|
||||
// Non-existent file should return ""
|
||||
bad := selfSignedPin("/nonexistent/path.crt")
|
||||
if bad != "" {
|
||||
t.Errorf("selfSignedPin for nonexistent file = %q, want empty", bad)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelfSignedPinNotForLE(t *testing.T) {
|
||||
// Create a cert with different issuer and subject (simulating LE)
|
||||
dir := t.TempDir()
|
||||
certPath := filepath.Join(dir, "le.crt")
|
||||
|
||||
// We can't easily generate a cert with different issuer/subject via openssl CLI,
|
||||
// so test that the function handles the parsing correctly
|
||||
out, _ := sh("openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
|
||||
"-days", "1", "-subj", "/CN=test",
|
||||
"-keyout", filepath.Join(dir, "k"), "-out", certPath)
|
||||
|
||||
// The function should return the pin (it's self-signed since issuer==subject)
|
||||
pin := selfSignedPin(certPath)
|
||||
if pin == "" {
|
||||
t.Errorf("selfSignedPin should return pin for self-signed cert, out: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsInboundPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c map[string]any
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "with listen",
|
||||
c: map[string]any{"api": map[string]any{"listen": "127.0.0.1:10085"}},
|
||||
want: 10085,
|
||||
},
|
||||
{
|
||||
name: "no api",
|
||||
c: map[string]any{},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "no listen",
|
||||
c: map[string]any{"api": map[string]any{}},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "bad format",
|
||||
c: map[string]any{"api": map[string]any{"listen": "bad"}},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := statsInboundPort(tt.c)
|
||||
if got != tt.want {
|
||||
t.Errorf("statsInboundPort() = %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureStats(t *testing.T) {
|
||||
// Empty config — should add api, stats, policy
|
||||
c := map[string]any{}
|
||||
changed := ensureStats(c)
|
||||
if !changed {
|
||||
t.Error("ensureStats should return true for empty config")
|
||||
}
|
||||
api, ok := c["api"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("ensureStats did not add api section")
|
||||
}
|
||||
if api["tag"] != "api" {
|
||||
t.Errorf("api.tag = %v, want 'api'", api["tag"])
|
||||
}
|
||||
if api["listen"] != "127.0.0.1:10085" {
|
||||
t.Errorf("api.listen = %v, want '127.0.0.1:10085'", api["listen"])
|
||||
}
|
||||
if c["stats"] == nil {
|
||||
t.Error("ensureStats did not add stats section")
|
||||
}
|
||||
pol, ok := c["policy"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("ensureStats did not add policy section")
|
||||
}
|
||||
levels, ok := pol["levels"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("ensureStats did not add levels")
|
||||
}
|
||||
lv0, ok := levels["0"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("ensureStats did not add level 0")
|
||||
}
|
||||
if lv0["statsUserUplink"] != true {
|
||||
t.Error("statsUserUplink should be true")
|
||||
}
|
||||
if lv0["statsUserDownlink"] != true {
|
||||
t.Error("statsUserDownlink should be true")
|
||||
}
|
||||
if lv0["statsUserOnline"] != true {
|
||||
t.Error("statsUserOnline should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureStatsIdempotent(t *testing.T) {
|
||||
c := map[string]any{
|
||||
"api": map[string]any{"tag": "api", "listen": "127.0.0.1:10085", "services": []any{"StatsService"}},
|
||||
"stats": map[string]any{},
|
||||
"policy": map[string]any{
|
||||
"levels": map[string]any{
|
||||
"0": map[string]any{
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true,
|
||||
"statsUserOnline": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
changed := ensureStats(c)
|
||||
if changed {
|
||||
t.Error("ensureStats should return false when already configured")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStealthInbounds(t *testing.T) {
|
||||
c := map[string]any{
|
||||
"inbounds": []any{
|
||||
map[string]any{
|
||||
"protocol": "vless",
|
||||
"streamSettings": map[string]any{
|
||||
"network": "stealth",
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"protocol": "dokodemo-door",
|
||||
},
|
||||
},
|
||||
}
|
||||
ibs := stealthInbounds(c)
|
||||
if len(ibs) != 1 {
|
||||
t.Errorf("stealthInbounds returned %d, want 1", len(ibs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenX25519(t *testing.T) {
|
||||
priv, pub := genX25519()
|
||||
if priv == "" || pub == "" {
|
||||
t.Error("genX25519 returned empty keys")
|
||||
}
|
||||
if priv == pub {
|
||||
t.Error("private and public keys should differ")
|
||||
}
|
||||
// Keys should be valid base64url
|
||||
if _, err := base64.RawURLEncoding.DecodeString(priv); err != nil {
|
||||
t.Errorf("private key is not valid base64url: %v", err)
|
||||
}
|
||||
if _, err := base64.RawURLEncoding.DecodeString(pub); err != nil {
|
||||
t.Errorf("public key is not valid base64url: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadConfig(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "config.json")
|
||||
orig := defaultConfigPath
|
||||
|
||||
// Temporarily override
|
||||
// We can't easily override the const, so test with a real file
|
||||
cfg := map[string]any{
|
||||
"log": map[string]any{"loglevel": "warning"},
|
||||
"inbounds": []any{
|
||||
map[string]any{
|
||||
"listen": "0.0.0.0",
|
||||
"port": float64(443),
|
||||
"protocol": "vless",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(cfgPath, b, 0644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
// Read back
|
||||
data, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read: %v", err)
|
||||
}
|
||||
var read map[string]any
|
||||
if err := json.Unmarshal(data, &read); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if read["log"] == nil {
|
||||
t.Error("log section missing after round-trip")
|
||||
}
|
||||
|
||||
_ = orig // suppress unused
|
||||
}
|
||||
|
||||
func TestPanelConfig(t *testing.T) {
|
||||
// Test PanelConfig JSON round-trip
|
||||
pc := PanelConfig{
|
||||
Bind: "127.0.0.1:8080",
|
||||
Password: "test123",
|
||||
Config: "/etc/xray/config.json",
|
||||
ServerName: "example.com",
|
||||
Fingerprint: "chrome",
|
||||
}
|
||||
b, err := json.Marshal(pc)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var read PanelConfig
|
||||
if err := json.Unmarshal(b, &read); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if read.Bind != pc.Bind {
|
||||
t.Errorf("bind = %q, want %q", read.Bind, pc.Bind)
|
||||
}
|
||||
if read.ServerName != pc.ServerName {
|
||||
t.Errorf("serverName = %q, want %q", read.ServerName, pc.ServerName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteJSON(t *testing.T) {
|
||||
// Just verify it doesn't panic
|
||||
w := &fakeResponseWriter{}
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
if w.code != 200 {
|
||||
t.Errorf("code = %d, want 200", w.code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrStr(t *testing.T) {
|
||||
if errStr(nil) != "" {
|
||||
t.Error("errStr(nil) should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailDefaultToUUID(t *testing.T) {
|
||||
// When adding a client with empty name, email should be set to UUID
|
||||
id := uuid()
|
||||
email := ""
|
||||
if email == "" {
|
||||
email = id
|
||||
}
|
||||
if email != id {
|
||||
t.Errorf("email = %q, want %q (UUID)", email, id)
|
||||
}
|
||||
if len(email) != 36 {
|
||||
t.Errorf("email length = %d, want 36 (UUID format)", len(email))
|
||||
}
|
||||
}
|
||||
|
||||
// fakeResponseWriter for testing
|
||||
type fakeResponseWriter struct {
|
||||
code int
|
||||
body []byte
|
||||
}
|
||||
|
||||
func (f *fakeResponseWriter) Header() http.Header { return http.Header{} }
|
||||
func (f *fakeResponseWriter) Write(b []byte) (int, error) { f.body = append(f.body, b...); return len(b), nil }
|
||||
func (f *fakeResponseWriter) WriteHeader(code int) { f.code = code }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xray.app.stats.command;
|
||||
option csharp_namespace = "Xray.App.Stats.Command";
|
||||
option go_package = "github.com/xtls/xray-core/app/stats/command";
|
||||
option java_package = "com.xray.app.stats.command";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message GetStatsRequest {
|
||||
// Name of the stat counter.
|
||||
string name = 1;
|
||||
// Whether or not to reset the counter to fetching its value.
|
||||
bool reset = 2;
|
||||
}
|
||||
|
||||
message Stat {
|
||||
string name = 1;
|
||||
int64 value = 2;
|
||||
}
|
||||
|
||||
message GetStatsResponse {
|
||||
Stat stat = 1;
|
||||
}
|
||||
|
||||
message QueryStatsRequest {
|
||||
string pattern = 1;
|
||||
bool reset = 2;
|
||||
}
|
||||
|
||||
message QueryStatsResponse {
|
||||
repeated Stat stat = 1;
|
||||
}
|
||||
|
||||
message SysStatsRequest {}
|
||||
|
||||
message SysStatsResponse {
|
||||
uint32 NumGoroutine = 1;
|
||||
uint32 NumGC = 2;
|
||||
uint64 Alloc = 3;
|
||||
uint64 TotalAlloc = 4;
|
||||
uint64 Sys = 5;
|
||||
uint64 Mallocs = 6;
|
||||
uint64 Frees = 7;
|
||||
uint64 LiveObjects = 8;
|
||||
uint64 PauseTotalNs = 9;
|
||||
uint32 Uptime = 10;
|
||||
}
|
||||
|
||||
message GetStatsOnlineIpListResponse {
|
||||
string name = 1;
|
||||
map<string, int64> ips = 2;
|
||||
}
|
||||
|
||||
message GetAllOnlineUsersRequest {}
|
||||
|
||||
message GetAllOnlineUsersResponse {
|
||||
repeated string users = 1;
|
||||
}
|
||||
|
||||
message OnlineIPEntry {
|
||||
string ip = 1;
|
||||
int64 last_seen = 2;
|
||||
}
|
||||
|
||||
message TrafficUserStat {
|
||||
int64 uplink = 1;
|
||||
int64 downlink = 2;
|
||||
}
|
||||
|
||||
message UserStat {
|
||||
string email = 1;
|
||||
repeated OnlineIPEntry ips = 2;
|
||||
TrafficUserStat traffic = 3;
|
||||
}
|
||||
|
||||
message GetUsersStatsRequest {
|
||||
bool include_traffic = 1;
|
||||
bool reset = 2;
|
||||
}
|
||||
|
||||
message GetUsersStatsResponse {
|
||||
repeated UserStat users = 1;
|
||||
}
|
||||
|
||||
service StatsService {
|
||||
rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {}
|
||||
rpc GetStatsOnline(GetStatsRequest) returns (GetStatsResponse) {}
|
||||
rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {}
|
||||
rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {}
|
||||
rpc GetStatsOnlineIpList(GetStatsRequest) returns (GetStatsOnlineIpListResponse) {}
|
||||
rpc GetAllOnlineUsers(GetAllOnlineUsersRequest) returns (GetAllOnlineUsersResponse) {}
|
||||
rpc GetUsersStats(GetUsersStatsRequest) returns (GetUsersStatsResponse) {}
|
||||
}
|
||||
|
||||
message Config {}
|
||||
@@ -0,0 +1,349 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v3.21.12
|
||||
// source: stats/command.proto
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StatsService_GetStats_FullMethodName = "/xray.app.stats.command.StatsService/GetStats"
|
||||
StatsService_GetStatsOnline_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnline"
|
||||
StatsService_QueryStats_FullMethodName = "/xray.app.stats.command.StatsService/QueryStats"
|
||||
StatsService_GetSysStats_FullMethodName = "/xray.app.stats.command.StatsService/GetSysStats"
|
||||
StatsService_GetStatsOnlineIpList_FullMethodName = "/xray.app.stats.command.StatsService/GetStatsOnlineIpList"
|
||||
StatsService_GetAllOnlineUsers_FullMethodName = "/xray.app.stats.command.StatsService/GetAllOnlineUsers"
|
||||
StatsService_GetUsersStats_FullMethodName = "/xray.app.stats.command.StatsService/GetUsersStats"
|
||||
)
|
||||
|
||||
// StatsServiceClient is the client API for StatsService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type StatsServiceClient interface {
|
||||
GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)
|
||||
GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error)
|
||||
QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error)
|
||||
GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error)
|
||||
GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error)
|
||||
GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error)
|
||||
GetUsersStats(ctx context.Context, in *GetUsersStatsRequest, opts ...grpc.CallOption) (*GetUsersStatsResponse, error)
|
||||
}
|
||||
|
||||
type statsServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient {
|
||||
return &statsServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetStatsOnline(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetStatsOnline_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(QueryStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_QueryStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SysStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetSysStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetStatsOnlineIpList(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsOnlineIpListResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetStatsOnlineIpListResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetStatsOnlineIpList_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetAllOnlineUsers(ctx context.Context, in *GetAllOnlineUsersRequest, opts ...grpc.CallOption) (*GetAllOnlineUsersResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetAllOnlineUsersResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetAllOnlineUsers_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *statsServiceClient) GetUsersStats(ctx context.Context, in *GetUsersStatsRequest, opts ...grpc.CallOption) (*GetUsersStatsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetUsersStatsResponse)
|
||||
err := c.cc.Invoke(ctx, StatsService_GetUsersStats_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StatsServiceServer is the server API for StatsService service.
|
||||
// All implementations must embed UnimplementedStatsServiceServer
|
||||
// for forward compatibility.
|
||||
type StatsServiceServer interface {
|
||||
GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error)
|
||||
GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error)
|
||||
QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error)
|
||||
GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error)
|
||||
GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error)
|
||||
GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error)
|
||||
GetUsersStats(context.Context, *GetUsersStatsRequest) (*GetUsersStatsResponse, error)
|
||||
mustEmbedUnimplementedStatsServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedStatsServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedStatsServiceServer struct{}
|
||||
|
||||
func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetStats not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetStatsOnline(context.Context, *GetStatsRequest) (*GetStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetStatsOnline not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method QueryStats not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetSysStats not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetStatsOnlineIpList(context.Context, *GetStatsRequest) (*GetStatsOnlineIpListResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetStatsOnlineIpList not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetAllOnlineUsers(context.Context, *GetAllOnlineUsersRequest) (*GetAllOnlineUsersResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetAllOnlineUsers not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) GetUsersStats(context.Context, *GetUsersStatsRequest) (*GetUsersStatsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetUsersStats not implemented")
|
||||
}
|
||||
func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {}
|
||||
func (UnimplementedStatsServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to StatsServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeStatsServiceServer interface {
|
||||
mustEmbedUnimplementedStatsServiceServer()
|
||||
}
|
||||
|
||||
func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedStatsServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&StatsService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetStatsOnline_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetStatsOnline(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetStatsOnline_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetStatsOnline(ctx, req.(*GetStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(QueryStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).QueryStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_QueryStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SysStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetSysStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetSysStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetStatsOnlineIpList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetStatsOnlineIpList_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetStatsOnlineIpList(ctx, req.(*GetStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetAllOnlineUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetAllOnlineUsersRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetAllOnlineUsers(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetAllOnlineUsers_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetAllOnlineUsers(ctx, req.(*GetAllOnlineUsersRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StatsService_GetUsersStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetUsersStatsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StatsServiceServer).GetUsersStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StatsService_GetUsersStats_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StatsServiceServer).GetUsersStats(ctx, req.(*GetUsersStatsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var StatsService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "xray.app.stats.command.StatsService",
|
||||
HandlerType: (*StatsServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetStats",
|
||||
Handler: _StatsService_GetStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetStatsOnline",
|
||||
Handler: _StatsService_GetStatsOnline_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "QueryStats",
|
||||
Handler: _StatsService_QueryStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetSysStats",
|
||||
Handler: _StatsService_GetSysStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetStatsOnlineIpList",
|
||||
Handler: _StatsService_GetStatsOnlineIpList_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAllOnlineUsers",
|
||||
Handler: _StatsService_GetAllOnlineUsers_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetUsersStats",
|
||||
Handler: _StatsService_GetUsersStats_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "stats/command.proto",
|
||||
}
|
||||
Reference in New Issue
Block a user