Инструкция: telemt inbound SYN limiter через nftables (v2)
Итоговая инструкция для сервера с telemt в Docker. Основной метод — входящий per-client limiter по SYN от внешнего клиента к контейнеру telemt.
telemt на некоторых провайдерах, где обычное подключение может зависать, долго устанавливаться или нестабильно проходить начальный TCP-этап.
SYN к telemt:443 отдельно для каждого внешнего IP.
Рабочее правило использует meter { ip saddr ... limit rate over 1/second burst 1 packets }.
telemt, а аккуратно ограничить частые повторные входящие SYN от одного и того же внешнего IP. Для клиента это выглядит мягче, чем глобально резать ответы сервера.
SYN,ACK от сервера, tcp sport 443 и глобальным limit rate over ... оставлен ниже только как архив/история проверки. Для текущей рабочей схемы использовать новый inbound per-client метод.
Проблемы после применения указанных здесь настроек
1. Иногда бывает долгое подключение к Telemt. Много клиентов одновременно пытаются установить соединение. Просто подождите ~ 10 минут после перезагрузки telemt и станет лучше.
2. Telemt может продолжать писать Telegram handshake timeout и Operation timed out, но при inbound per-client SYN limiter такие ошибки должны идти заметно менее плотными пачками.
- Простая команда без Docker
- Что именно ограничивается
- Настройки telemt
- Ручной тест нового inbound per-client метода
- Скрипт применения nft-правила
- Watcher для автоматического обновления правила
- systemd service
- Проверка работы
- Подбор параметров
- Временное отключение и возврат правила
- Deprecated: старый sport/SYN-ACK метод
1. Простая команда без Docker
Если telemt запущен прямо на этом же сервере без Docker, VM или отдельного bridge, пакет приходит в локальный процесс. Для такого случая нужен hook input, а не hook forward.
IP="1.1.1.1"
PORT="443"
nft delete table inet telemt_limit 2>/dev/null
nft add table inet telemt_limit
nft 'add chain inet telemt_limit input { type filter hook input priority 0; policy accept; }'
nft "add rule inet telemt_limit input \
ip daddr $IP tcp dport $PORT \
tcp flags & (syn | ack) == syn \
meter telemt_in_syn_per_client { ip saddr timeout 60s limit rate over 1/second burst 1 packets } \
counter drop comment \"telemt_in_syn_per_client_1ps_burst1\""
nft list chain inet telemt_limit input
IP — локальный адрес сервера, на который приходит подключение к telemt. Если telemt слушает на всех адресах, обычно можно указать публичный IP сервера.
2. Что именно ограничивается
Новый метод режет не ответ сервера SYN,ACK, а первый входящий SYN от клиента:
client_ip:random_port → telemt_container:443 SYN
Так как telemt работает в Docker bridge, пакет к контейнеру проходит через netfilter hook forward. Поэтому правило создаётся в chain с hook forward.
Ключ per-client лимита — ip saddr, потому что во входящем SYN внешний клиент является источником:
ip daddr $TELEMT_IP tcp dport 443
tcp flags & (syn | ack) == syn
meter telemt_in_syn_per_client { ip saddr timeout 60s limit rate over 1/second burst 1 packets }
counter drop
| Часть правила | Смысл |
|---|---|
ip daddr $TELEMT_IP | Пакет направлен в контейнер telemt. |
tcp dport 443 | Вход на порт 443. |
tcp flags & (syn | ack) == syn | Матчится именно первый SYN клиента, без ACK. |
meter ... { ip saddr ... } | Каждому внешнему IP — свой bucket, а не одна глобальная очередь на всех. |
timeout 60s | Состояние meter для IP очищается через 60 секунд неактивности. |
3. Настройки telemt
Эти параметры не обязательны. Их можно не трогать, если после nft-правила подключение работает нормально. Ниже указан базовый пример со значениями по умолчанию. Если остаются долгие подключения, обрывы или нестабильность на отдельных провайдерах, эти значения можно аккуратно увеличить.
[general]
tg_connect = 10
[timeouts]
client_handshake = 15
client_keepalive = 60
| Параметр | За что отвечает |
|---|---|
tg_connect = 10 | Базовый таймаут подключения telemt к upstream Telegram DC. Если upstream Telegram отвечает нестабильно, можно попробовать увеличить до 30. |
client_handshake = 15 | Базовое ожидание начального handshake клиента. Если клиент долго проходит начальное подключение, можно попробовать увеличить до 120. |
client_keepalive = 60 | Базовое ожидание активности клиента. При мобильной сети, NAT и нестабильных соединениях можно попробовать увеличить до 90. |
tg_connect = 30, client_handshake = 120, client_keepalive = 90. Это не обязательные значения, а запасной вариант для проблемных сетей.
Если параметры менялись, после изменения конфига перезапустить контейнер:
cd /opt/telemt
docker compose restart telemt
4. Ручной тест нового inbound per-client метода
Команды для быстрого теста без установки watcher:
nft delete table inet telemt_limit 2>/dev/null
TELEMT_IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' telemt | awk 'NF {print; exit}')"
echo "TELEMT_IP=$TELEMT_IP"
nft add table inet telemt_limit
nft 'add chain inet telemt_limit forward { type filter hook forward priority 0; policy accept; }'
nft "add rule inet telemt_limit forward \
ip daddr $TELEMT_IP tcp dport 443 \
tcp flags & (syn | ack) == syn \
meter telemt_in_syn_per_client { ip saddr timeout 60s limit rate over 1/second burst 1 packets } \
counter drop comment \"telemt_in_syn_per_client_1ps_burst1\""
nft list chain inet telemt_limit forward
Ожидаемый вид:
table inet telemt_limit {
chain forward {
type filter hook forward priority filter; policy accept;
ip daddr 172.x.x.x tcp dport 443 tcp flags syn / syn,ack meter telemt_in_syn_per_client size 65535 { ip saddr timeout 1m limit rate over 1/second burst 1 packets } counter packets 0 bytes 0 drop comment "telemt_in_syn_per_client_1ps_burst1"
}
}
5. Скрипт применения nft-правила
Постоянный скрипт для нового метода:
cat > /usr/local/sbin/telemt-in-syn-limit.sh <<'EOF'
#!/bin/sh
set -eu
CONTAINER="${1:-telemt}"
TABLE="telemt_limit"
CHAIN="forward"
PORT="${PORT:-443}"
RATE="${RATE:-1/second}"
BURST="${BURST:-1}"
METER_TIMEOUT="${METER_TIMEOUT:-60s}"
IP=""
for i in $(seq 1 60); do
RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)"
if [ "$RUNNING" = "true" ]; then
IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' "$CONTAINER" | awk 'NF {print; exit}')"
if [ -n "$IP" ]; then
break
fi
fi
sleep 1
done
if [ -z "$IP" ]; then
echo "Could not get IP for container: $CONTAINER" >&2
exit 1
fi
nft delete table inet "$TABLE" 2>/dev/null || true
nft add table inet "$TABLE"
nft "add chain inet $TABLE $CHAIN { type filter hook forward priority 0; policy accept; }"
nft "add rule inet $TABLE $CHAIN ip daddr $IP tcp dport $PORT tcp flags & (syn | ack) == syn meter telemt_in_syn_per_client { ip saddr timeout $METER_TIMEOUT limit rate over $RATE burst $BURST packets } counter drop comment \"telemt_in_syn_per_client_${RATE}_burst_${BURST}\""
echo "Applied telemt inbound SYN per-client limiter:"
echo "container=$CONTAINER ip=$IP port=$PORT rate=$RATE burst=$BURST meter_timeout=$METER_TIMEOUT"
nft list chain inet "$TABLE" "$CHAIN"
EOF
chmod +x /usr/local/sbin/telemt-in-syn-limit.sh
Применение вручную:
/usr/local/sbin/telemt-in-syn-limit.sh telemt
Применение с временным переопределением значений:
RATE="1/second" BURST="1" METER_TIMEOUT="60s" /usr/local/sbin/telemt-in-syn-limit.sh telemt
6. Watcher для автоматического обновления правила
Watcher проверяет IP контейнера каждые 5 секунд. Если IP изменился, правило пересоздаётся.
cat > /usr/local/sbin/telemt-in-syn-watch.sh <<'EOF'
#!/bin/sh
set -u
CONTAINER="${1:-telemt}"
INTERVAL="${INTERVAL:-5}"
LAST_IP=""
echo "Watching Docker container for inbound SYN limiter: $CONTAINER"
while true; do
RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)"
if [ "$RUNNING" = "true" ]; then
IP="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' "$CONTAINER" 2>/dev/null | awk 'NF {print; exit}')"
if [ -n "$IP" ] && [ "$IP" != "$LAST_IP" ]; then
echo "Container IP changed: ${LAST_IP:-none} -> $IP"
if /usr/local/sbin/telemt-in-syn-limit.sh "$CONTAINER"; then
LAST_IP="$IP"
else
echo "Failed to apply nft inbound SYN rule for $CONTAINER" >&2
fi
fi
else
if [ -n "$LAST_IP" ]; then
echo "Container $CONTAINER is not running"
LAST_IP=""
fi
fi
sleep "$INTERVAL"
done
EOF
chmod +x /usr/local/sbin/telemt-in-syn-watch.sh
7. systemd service
Новый systemd service для inbound SYN метода:
cat > /etc/systemd/system/telemt-in-syn-watch.service <<'EOF'
[Unit]
Description=Watch telemt Docker container and refresh nft inbound SYN per-client limiter
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/sbin/telemt-in-syn-watch.sh telemt
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now telemt-in-syn-watch.service
Если старый сервис ещё существует, лучше остановить его, чтобы он не пересоздавал таблицу старым методом:
systemctl disable --now telemt-synack-watch.service 2>/dev/null || true
systemctl restart telemt-in-syn-watch.service
systemctl status telemt-in-syn-watch.service --no-pager
journalctl -u telemt-in-syn-watch.service -n 30 --no-pager
8. Проверка работы
Проверить IP контейнера:
docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} {{.State.Status}}' telemt
Проверить nft-правило:
nft list chain inet telemt_limit forward
Основной счётчик в новом методе:
telemt_in_syn_per_client_1ps_burst1
| Счётчик | Что означает |
|---|---|
telemt_in_syn_per_client_1ps_burst1 | Дропы сверх лимита 1/second burst 1 для конкретного внешнего IP. |
Проверка через tcpdump:
tcpdump -ni any 'tcp and dst port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'
Ожидаемое направление:
client_ip.random_port > 172.x.x.x.443: Flags [S]
9. Подбор параметров
Текущий рабочий вариант:
RATE=1/second
BURST=1
METER_TIMEOUT=60s
| Вариант | Когда пробовать |
|---|---|
1/second burst 1 | Предпочтительный рабочий вариант. Жёсткий per-client режим для входящих SYN. Использовать как основной вариант, если подключение клиента к telemt нестабильно у некоторых провайдеров. |
1/second burst 3 | Если отдельным клиентам не хватает совсем короткого burst, но хочется сохранить rate 1/sec. |
2/second burst 5 | Более мягкая альтернатива для сервера с большим числом клиентов. |
METER_TIMEOUT=30s | Быстрее очищать состояние per-IP meter. |
METER_TIMEOUT=120s | Дольше помнить IP, если клиенты часто ретраят с паузами. |
Пример временного теста другого значения:
nft delete table inet telemt_limit 2>/dev/null
RATE="1/second" BURST="3" METER_TIMEOUT="60s" /usr/local/sbin/telemt-in-syn-limit.sh telemt
nft list chain inet telemt_limit forward
10. Временное отключение и возврат правила
Для чистого теста без правила:
systemctl stop telemt-in-syn-watch.service
nft delete table inet telemt_limit 2>/dev/null
Проверка, что таблицы нет:
nft list tables | grep telemt_limit
Вернуть новый inbound per-client метод:
/usr/local/sbin/telemt-in-syn-limit.sh telemt
systemctl start telemt-in-syn-watch.service
11. Deprecated: старый sport/SYN-ACK метод
SYN,ACK от telemt через tcp sport 443. На текущем тесте более удачным оказался новый входящий per-client SYN limiter. Если нет отдельной причины возвращаться к старому варианту, использовать новый метод выше.
Показать старый метод с исходящими SYN,ACK
Старый тестовый вариант:
nft delete table inet telemt_test 2>/dev/null
nft add table inet telemt_test
nft 'add chain inet telemt_test forward { type filter hook forward priority 0; policy accept; }'
nft 'add rule inet telemt_test forward ip saddr 172.21.0.4 tcp sport 443 tcp flags & (syn | ack) == (syn | ack) limit rate over 1/second burst 1 packets counter drop comment "telemt_synack_drop_over_1ps"'
Старый постоянный вариант был завязан на направление:
telemt_container:443 → client_ip:random_port SYN,ACK
И матчился через:
ip saddr $TELEMT_IP tcp sport 443
tcp flags & (syn | ack) == (syn | ack)
Почему метод отправлен в deprecated:
- Он работает с ответами сервера, а не с входящими попытками клиента.
- Глобальный
limitбезmeterсоздаёт общий bucket на всех клиентов. - Новый inbound per-client SYN метод работает ближе к источнику проблемы: ограничивает входящие попытки клиента, а не исходящие ответы сервера.
Короткий итог
telemt в Docker
client_mss не используется
основной nft limiter: inbound SYN per-client
match: ip daddr $TELEMT_IP tcp dport 443 tcp flags syn / syn,ack
meter key: ip saddr
rate: 1/second
burst: 1
meter timeout: 60s
tg_connect = 10
client_handshake = 15
client_keepalive = 60
старый sport/SYN-ACK метод: deprecated