Лимиты пользователей
Ядро ограничивает пользователей входящих само — встраивающему приложению не нужно оборачивать соединения. Лимит задаётся для пары «входящий» + «пользователь», той же, что в трафике пользователей, а с user: "*" — по умолчанию для всех пользователей входящего, у которых нет своего лимита.
| Лимит | Что делает |
|---|---|
speed-up, speed-down |
Скорость отдачи и загрузки, байт в секунду. Одна «корзина токенов» на пользователя, общая для всех его TCP-соединений и UDP-сессий. |
speed-group |
Общая скорость для нескольких пользователей: у всех, кто назвал одну группу (на любых входящих), одна пара «корзин» на всех. Так одно устройство и пять устройств одного человека получают одну и ту же скорость. Скорость группы — speed-up и speed-down её участников, задавайте им одинаковые; остальные лимиты у каждого свои. Смена скорости не сбрасывает «корзину»: потраченное раньше учитывается и при новой скорости. В лимите по умолчанию (user: "*") группа объединяет всех пользователей входящего без своего лимита в одну скорость на всех — обычно её задают в лимитах конкретных пользователей. |
max-conns |
Сколько соединений и UDP-сессий пользователь держит одновременно. Лишнее новое соединение отклоняется. |
max-ips |
Сколько разных адресов пользователя онлайн. Соединение с нового адреса сверх лимита отклоняется, уже подключённые адреса работают. Адрес считается онлайн ещё минуту после закрытия последнего соединения — как в списке ips. |
quota |
Бюджет трафика в байтах, отдача + загрузка. Когда он исчерпан, соединения пользователя закрываются, а новые отклоняются до сброса или увеличения квоты. |
expire-at |
Время (unix-секунды или RFC 3339), после которого соединения пользователя закрываются, а новые отклоняются. |
Ноль или отсутствующий ключ — без ограничения.
user-limits: - { inbound: vless-in, user: alice, speed-up: 10M, speed-down: 80Mbps, max-conns: 64, max-ips: 3, quota: 100G, expire-at: 1767225600 } - { inbound: vless-in, user: "*", speed-down: 20M, max-ips: 2, quota: 50G }Свой лимит пользователя заменяет лимит по умолчанию целиком, а не дополняет его.
Единицы
Заголовок раздела «Единицы»- Размер (
quota) — в байтах. Число без суффикса — байты;K,M,G,T(можно сB:GB) — степени 1000;Ki,Mi,Gi,Ti(GiB) — степени 1024.100G= 100 000 000 000 байт. - Скорость — в байтах в секунду, в той же записи, можно с
/sилиps:10M,10MB/sи10MBps— это 10 000 000 байт/с. Строчнаяbозначает биты:80Mbps(или80Mb) = 80 мегабит/с = 10 000 000 байт/с.
Как работает
Заголовок раздела «Как работает»- Проверка
expire-at,quota,max-connsиmax-ipsидёт в туннеле после сопоставления правил и до подключения к исходящему: отклонённое соединение закрывается, никуда не подключаясь. В журнал пишется строка уровняinfoс причиной, напримерrejected by the user limits: max-conns 64 reached(не чаще раза в секунду на пользователя, пропущенные строки посчитаны). - Каждый отказ считается в счётчике пользователя:
rejectedиrejectReasons(expired,quota,max-conns,max-ips) вGET /traffic/users. - Квота считает те же байты, что и
up+down: полезную нагрузку на исходящей стороне, без накладных расходов протокола входящего. Перерасход ограничен тем, что успело пройти до закрытия соединений. - Скорость ограничивается в путях чтения и записи соединения. У пользователя с лимитом скорости копирование идёт через буфер, а не через
splice(иначе ядро двигает до мегабайта за шаг и скорость скачет); у пользователей без лимитаspliceи прочие быстрые пути не меняются. Лимит, выставленный во время уже идущегоsplice, тоже соблюдается — шагами по объёмуsplice. - Пользователь без лимитов не платит ничего сверх учёта трафика: проверка — одно сравнение указателя с
nil. - Лимиты требуют атрибуции соединений: при
user-stats: falseпользователи с лимитами всё равно учитываются, пока есть хоть один лимит. - UDP через SOCKS5 (
socks,mixedсudp: true): датаграммы UDP ASSOCIATE несут пользователя, который его аутентифицировал, поэтому каждая UDP-сессия считается в лимитах этого пользователя. Ассоциация закреплена за одним UDP-портом клиента: портом из запроса, если клиент указал там свой адрес, иначе портом первой датаграммы. Другие сокеты с того же IP (другой клиент за тем же NAT) ею воспользоваться не могут. Само управляющее TCP-соединение ассоциации вmax-connsне считается.
Перезагрузка конфига
Заголовок раздела «Перезагрузка конфига»Перезагрузка заменяет все лимиты лимитами из конфига: лимиты, выставленные через API, пропадают. Состояние пользователя, у которого лимит остался, сохраняется: использованная квота, «корзины» скорости (если скорость не изменилась) и таймер срока (если expire-at не изменился). Использованная квота обнуляется только сбросом или удалением лимита пользователя.
Запросы идут к external-controller с тем же secret, что и остальной API.
GET /limits/users?inbound=…&user=…
Заголовок раздела «GET /limits/users?inbound=…&user=…»Лимиты и их состояние; параметры необязательные. В списке — лимиты по умолчанию ("user": "*"), свои лимиты пользователей и пользователи, получившие лимит по умолчанию ("inherited": true).
[ { "inbound": "vless-in", "user": "alice", "speedUp": 10000000, "speedDown": 10000000, "maxConns": 64, "maxIPs": 3, "quota": 100000000000, "expireAt": 1767225600, "source": "config", "used": 52428800, "remaining": 99947571200, "quotaExceeded": false, "expired": false }]used — байты, засчитанные в квоту с момента появления лимита или сброса, remaining — остаток (null, если квоты нет), source — config или api.
PUT /limits/users?inbound=…&user=…
Заголовок раздела «PUT /limits/users?inbound=…&user=…»Задаёт лимит пользователя (user=* — по умолчанию для входящего). Тело — JSON с ключами раздела user-limits (или их camelCase-вариантами) и теми же единицами; отсутствующий ключ — без ограничения. Использованная квота пользователя сохраняется. Ответ — состояние лимита.
curl -X PUT -H "Authorization: Bearer $SECRET" \ "http://127.0.0.1:9090/limits/users?inbound=vless-in&user=alice" \ -d '{"speed-down": "80Mbps", "max-conns": 64, "quota": "100G"}'DELETE /limits/users?inbound=…&user=…
Заголовок раздела «DELETE /limits/users?inbound=…&user=…»Удаляет лимит; пользователь возвращается к лимиту по умолчанию, если он есть. 404, если лимита нет.
POST /limits/users/reset-quota?inbound=…&user=…
Заголовок раздела «POST /limits/users/reset-quota?inbound=…&user=…»Обнуляет использованную квоту подходящих пользователей (без параметров — всех) и снова пускает тех, кто её исчерпал. Ответ: {"reset": 2}.
users := statistic.DefaultManager.Users()_ = users.SetConfigLimits([]statistic.UserLimit{{Inbound: "vless-in", User: "*", MaxIPs: 2}})_ = users.SetLimit(statistic.UserLimit{Inbound: "vless-in", User: "alice", SpeedDown: 10_000_000, Quota: 100e9})st, ok := users.GetLimit(statistic.UserKey{Inbound: "vless-in", User: "alice"})users.ResetQuota(func(k statistic.UserKey) bool { return k.User == "alice" })users.DeleteLimit(statistic.UserKey{Inbound: "vless-in", User: "alice"})
// своё ядро поверх туннеля: проверить лимиты до подключенияadm, err := statistic.DefaultManager.Admit(metadata) // *statistic.LimitError при отказеconn := statistic.NewTCPTrackerAdmitted(remote, statistic.DefaultManager, metadata, rule, 0, 0, adm)statistic.ParseSize и statistic.ParseRate разбирают единицы так же, как конфиг.
