# circular — оркестратор циклического переключения стратегий
`circular` — Lua-оркестратор, который автоматически переключает стратегию десинхронизации при обнаружении неудач (RST, ретрансмиссии, HTTP-редирект DPI).
**Требует** перехвата входящего трафика (`--in-range`) для детектирования RST и HTTP-ответов.
---
## Синтаксис
```
--lua-desync=circular[:param=val[:...]]
--lua-desync=<func>:strategy=1[:final]
--lua-desync=<func>:strategy=2
...
```
Каждый подчинённый инстанс **обязан** иметь `strategy=N`, где N начинается с 1 без пропусков.
---
## Параметры circular
| Параметр | Описание | По умолчанию |
|---|---|---|
| `fails=N` | Порог неудач для переключения стратегии | `3` |
| `time=N` | Сбросить счётчик неудач, если последняя была > N секунд назад | `60` |
| `failure_detector=func` | Имя кастомной Lua-функции детектора неудач | `standard_failure_detector` |
| `success_detector=func` | Имя кастомной Lua-функции детектора успеха | `standard_success_detector` |
| `hostkey=func` | Имя кастомной функции генератора ключа хоста | `standard_hostkey` |
| `key=string` | Имя таблицы в `autostate` — изоляция состояния между несколькими `circular` | автоматически |
| `nld=N` | Обрезать hostname до N-уровневого домена (`static.a.google.com` → `google.com` при `nld=2`) | без обрезки |
| `reqhost` | Не работать, если hostname неизвестен (только IP) | выкл. |
---
## Параметры на подчинённых инстансах
| Параметр | Описание |
|---|---|
| `strategy=N` | **Обязательно.** Номер стратегии, начиная с 1, без пропусков |
| `final` | Финальная стратегия — ротация на ней останавливается |
---
## Параметры standard_failure_detector
Передаются прямо в `circular` (не в подчинённые инстансы).
### TCP
| Параметр | Описание | По умолчанию |
|---|---|---|
| `retrans=N` | Кол-во ретрансмиссий = неудача | `3` |
| `maxseq=N` | Считать ретрансмиссии только до этой relative sequence | `32768` |
| `reset` | Слать RST ретрансмиттеру (ускоряет разрыв зависшего соединения) | выкл. |
| `inseq=N` | Входящий RST считается неудачей только до этой rseq | `4096` |
| `no_rst` | Отключить триггер по входящему RST | выкл. |
| `no_http_redirect` | Отключить триггер по HTTP-редиректу DPI | выкл. |
### UDP
| Параметр | Описание | По умолчанию |
|---|---|---|
| `udp_out=N` | Неудача если исходящих пакетов >= N | `4` |
| `udp_in=N` | Неудача если входящих пакетов <= N | `1` |
---
## Параметры standard_success_detector
| Параметр | Описание | По умолчанию |
|---|---|---|
| `maxseq=N` | TCP: если исходящая rseq > N — соединение успешно | `32768` |
| `inseq=N` | TCP: если входящая rseq > N — соединение успешно | `4096` |
| `udp_in=N` | UDP: если входящих пакетов > N — успех | `1` |
---
## Стратегия из нескольких фаз
Одна стратегия может включать **несколько инстансов** с одинаковым `strategy=N`.
`circular` проходит весь план и выполняет **все** инстансы, у которых номер совпадает — по порядку.
```bash
--lua-desync=circular:fails=1:time=300
--lua-desync=fake:blob=fake_default_http:repeats=4:strategy=1 # фаза 1
--lua-desync=multisplit:pos=2:seqovl=211:strategy=1 # фаза 2
--lua-desync=multidisorder:pos=host:strategy=2:final
```
Для стратегии 1 последовательно выполнятся `fake` → `multisplit`.
### Форматирование в config
Параметры внутри `NFQWS2_OPT="..."` можно переносить на новые строки и делать отступы — shell разбирает их как обычные пробелы:
```bash
NFQWS2_OPT="
--filter-tcp=443 --filter-l7=tls <HOSTLIST> --payload=tls_client_hello
--out-range=-d1000
--in-range=-s5556
--lua-desync=circular:fails=1:time=300:retrans=3:nld=2
--lua-desync=fake:blob=fake_default_http:repeats=4:strategy=1
--lua-desync=multisplit:pos=2:seqovl=211:strategy=1
--lua-desync=multidisorder:pos=host:strategy=2:final
--new
"
```
---
## Пример
```
--in-range=-s5556
--out-range=-d1000
--lua-desync=circular:fails=1:time=300:retrans=3:nld=2
--lua-desync=multisplit:pos=2:strategy=1
--lua-desync=fake:blob=fake_default_http:strategy=2
--lua-desync=multidisorder:pos=host:strategy=3:final
```
- `fails=1` — переключить стратегию после 1 неудачи
- `time=300` — сбросить счётчик если последняя неудача была > 5 мин назад
- `retrans=3` — неудача = 3 ретрансмиссии
- `nld=2` — ключ хоста = 2-уровневый домен (`google.com`)
- `final` на стратегии 3 — остановить ротацию на ней
---
## Как работает
1. Пакет приходит в `circular` с `ctx`
2. `circular` берёт план (все подчинённые инстансы) и отменяет нормальный цикл C
3. Проверяет детектор неудач/успеха для текущего соединения
4. Если неудач >= `fails` — переходит к следующей стратегии `(N % total) + 1`
5. Если достигнута `final`-стратегия — ротация останавливается
6. Выполняет **только** инстансы с `strategy=текущая`
Состояние (номер стратегии, счётчик неудач) хранится **per-host** в глобальной таблице `autostate` и переживает отдельные соединения.