Перейти к содержимому
НовостиGitHubСкачать

Лимиты пользователей

Ядро ограничивает пользователей входящих само — встраивающему приложению не нужно оборачивать соединения. Лимит задаётся для пары «входящий» + «пользователь», той же, что в трафике пользователей, а с 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.

Лимиты и их состояние; параметры необязательные. В списке — лимиты по умолчанию ("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.

Задаёт лимит пользователя (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"}'

Удаляет лимит; пользователь возвращается к лимиту по умолчанию, если он есть. 404, если лимита нет.

Обнуляет использованную квоту подходящих пользователей (без параметров — всех) и снова пускает тех, кто её исчерпал. Ответ: {"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 разбирают единицы так же, как конфиг.