
Script to implement Q-Feeds directly on NFtables or IPtables
Автоматизированный список блокировки IP-адресов вредоносного ПО для серверов Linux — поддерживает nftables и iptables+ipset
Получите бесплатный ключ API на tip.qfeeds.com.
git clone https://github.com/Q-Feeds/NFtables-IPtables-integration-script.git cd NFtables-IPtables-integration-script chmod +x qfeeds-installer.sh qfeeds-uninstaller.sh
### Шаг 3: Запустите установщик от имени root```bash
sudo ./qfeeds-installer.sh
Установщик:
Ваш сервер теперь защищён. Задача cron проверяет обновления каждые 20 минут (настраивается), а фактические вызовы API происходят только тогда, когда позволяет ваша лицензия.
Это решение периодически загружает последнюю ленту угроз от Q-Feeds и применяет её в качестве правил брандмауэра, позволяя вам:
Установщик автоматически определяет, какой бэкенд брандмауэра доступен:
| Приоритет | Определение |
|---|
Определённый бэкенд сохраняется в файле конфигурации. Скрипты обновления и удаления используют его для выполнения правильных команд брандмауэра.
Оба бэкенда используют одинаковую стратегию разделённых наборов для максимальной производительности:
Бэкенд nftables:``` ┌─────────────────────────────────────────────────────────┐ │ table ip qfeeds │ │ │ │ ┌─────────────────────────┐ ┌───────────────────────┐ │ │ │ qfeeds_blacklist_v4 │ │ qfeeds_blacklist_v4 │ │ │ │ (hash set) │ │ _nets (interval set) │ │ │ │ │ │ │ │ │ │ Individual IPs │ │ CIDR ranges │ │ │ │ ~99% of entries │ │ ~1% of entries │ │ │ │ O(1) lookup & insert │ │ O(log n) lookup │ │ │ └─────────────────────────┘ └───────────────────────┘ │ │ │ │ ┌─────────────────────────┐ │ │ │ qfeeds_whitelist_v4 │ │ │ │ (interval set) │ │ │ │ Your allowed IPs/CIDRs │ │ │ └─────────────────────────┘ │ │ │ │ chain input-chain (hook input, priority 0, accept) │ │ → ip saddr @qfeeds_whitelist_v4 accept │ │ → ip saddr @qfeeds_blacklist_v4 drop │ │ → ip saddr @qfeeds_blacklist_v4_nets drop │ │ │ │ chain output-chain (if enabled) │ │ → ip daddr @qfeeds_whitelist_v4 accept │ │ → ip daddr @qfeeds_blacklist_v4 drop │ │ → ip daddr @qfeeds_blacklist_v4_nets drop │ └─────────────────────────────────────────────────────────┘
**iptables+ipset бэкенд:**```
┌──────────────────────────────────────────────────────────┐
│ ipset sets │
│ │
│ ┌─────────────────────────┐ ┌────────────────────────┐ │
│ │ qfeeds_blacklist_v4 │ │ qfeeds_blacklist_v4 │ │
│ │ (hash:ip) │ │ _nets (hash:net) │ │
│ │ maxelem 1000000 │ │ maxelem 65536 │ │
│ │ │ │ │ │
│ │ Individual IPs │ │ CIDR ranges │ │
│ └─────────────────────────┘ └────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ qfeeds_whitelist_v4 │ │
│ │ (hash:net) │ │
│ └─────────────────────────┘ │
│ │
│ iptables: INPUT/OUTPUT jump to a dedicated chain │
│ (jump rule tagged -m comment "qfeeds"): │
│ │
│ chain QFEEDS_INPUT (rebuilt each run, in order): │
│ -m set --match-set whitelist_v4 src -j ACCEPT │
│ -m set --match-set blacklist_v4 src -j DROP │
│ -m set --match-set blacklist_v4_nets src -j DROP │
│ (QFEEDS_OUTPUT mirrors this with dst, if enabled) │
└──────────────────────────────────────────────────────────┘
Та же структура существует для IPv6 (таблица ip6 qfeeds или ip6tables + family inet6 ipsets).
Почему два типа наборов?
┌──────────────────────────────────────────────────────┐ │ 1. Check license schedule (licenses.php API) │ │ → Skip run if not yet time for next update │ │ 2. Determine sync mode (full or diff) │ │ 3. Fetch IPv4 feed (ipv6=0) and IPv6 feed │ │ (ipv6=only) separately │ │ 4. Separate IPs from CIDRs in awk │ │ 5. Batch-load into hash set (IPs) and net/interval │ │ set (CIDRs) │ │ 6. Update whitelist sets from config │ │ 7. Persist rules │ └──────────────────────────────────────────────────────┘
### Полная синхронизация и дифференциальная синхронизация
| Режим | Когда | Что делает |
|------|------|-------------|
| **Полная синхронизация** | Первый запуск, принудительное обновление, после сбоя дифференциальной синхронизации, когда локальный набор потерял свою базовую линию (пуст или намного меньше ожидаемого), или когда последняя синхронизация старше `FULL_SYNC_MAX_AGE` (по умолчанию 24 часа) | Сначала загружает и проверяет каждый фид, затем очищает и перезагружает наборы черных списков. Набор очищается только после получения валидных данных, поэтому неудачная загрузка никогда не оставит вас без защиты |
| **Дифференциальная синхронизация** | Последующие запуски (только фид `malware_ip`) с исправным локальным набором | Загружает только добавления (`+`) и удаления (`-`) с момента последнего получения |
Дифференциальная синхронизация выполняется **для каждого API-ключа** — API отслеживает вашу последнюю успешную загрузку и возвращает только изменения с того момента. Если дифференциальная синхронизация завершается ошибкой, скрипт автоматически переключается на полную синхронизацию.
> **Самовосстановление:** Дифференциальные обновления только патчат существующий набор. Если этот набор когда-либо будет потерян или обрезан — например, после перезагрузки, когда правила брандмауэра не сохранились, ручной очистки или предыдущей частичной синхронизации — обновлятор обнаруживает отсутствие базовой линии (количество живых элементов равно 0 или значительно меньше последнего записанного числа) и принудительно выполняет полную перестройку вместо применения дифференциального обновления к пустому набору. В качестве дополнительной страховки также выполняется периодическая полная синхронизация (по умолчанию каждые 24 часа, через `FULL_SYNC_MAX_AGE`).
### Планирование на основе лицензии
Перед каждым запуском обновлятор проверяет API лицензий Q-Feeds (`licenses.php`). Если временная метка `next_update` вашей лицензии еще не достигнута, скрипт завершается досрочно, не совершая ненужных вызовов API. Задача cron выполняется часто (по умолчанию каждые 20 минут), но реальные обновления происходят только тогда, когда это позволяет ваша лицензия.
---
## ✅ Предварительные требования
Перед установкой убедитесь, что у вас есть:
- [x] **Сервер Linux** с **nftables** или **iptables** (Debian, Ubuntu, CentOS, Fedora, Arch, Alpine)
- [x] **Доступ root** — установщик и обновлятор должны запускаться от root
- [x] **API-токен Q-Feeds** — получите бесплатно на [tip.qfeeds.com](https://tip.qfeeds.com/)
- [x] **Доступ в Интернет** — серверу необходимо подключаться к `api.qfeeds.com`
Установщик автоматически установит необходимые зависимости:
- **Бэкенд nftables**: `nftables`, `curl`, `jq`, `util-linux`
- **Бэкенд iptables**: `iptables`, `ipset`, `curl`, `jq`, `util-linux`
---
## 📝 Подробное руководство по установке
### 1. Получите свой API-токен
Посетите [tip.qfeeds.com](https://tip.qfeeds.com/), чтобы получить бесплатный API-токен Q-Feeds.
### 2. Скачайте и запустите```bash
git clone https://github.com/Q-Feeds/NFtables-IPtables-integration-script.git
cd NFtables-IPtables-integration-script
chmod +x qfeeds-installer.sh qfeeds-uninstaller.sh
sudo ./qfeeds-installer.sh
Установщик задаст следующие вопросы:
Enter your Q-Feeds API Token:
Ваш токен от [tip.qfeeds.com](https://tip.qfeeds.com/). Установщик отказывается продолжать, если он пуст.
#### Тип ленты```
Enter feed type [default: malware_ip]:
По умолчанию используется malware_ip. Изменяйте это только если Q-Feeds предоставил вам другой тип фида.
Enter the limit of IPs to fetch (leave empty for no limit):
Нажмите Enter, чтобы снять ограничение (рекомендуется). Введите число, чтобы установить ограничение размера ленты.
#### Направленная блокировка```
Block INCOMING connections from malicious IPs? [Y/n]:
Block OUTGOING connections to malicious IPs? [y/N]:
Configure a whitelist of IPs/CIDRs that must NEVER be blocked? [y/N]: Enter IPv4 whitelist (comma-separated, e.g. 1.2.3.4,5.6.7.8): Enter IPv6 whitelist (comma-separated, e.g. 2001:db8::1):
Добавьте сюда ваши IP-адреса управления, чтобы гарантированно не быть заблокированным, даже если они появятся в ленте. Правила белого списка всегда проверяются **до** правил черного списка.
#### Расписание Cron```
Enter cron schedule (e.g., '*/20 * * * *') [default: */20 * * * *]:
Как часто программа обновления проверяет новые данные. По умолчанию — каждые 20 минут. Планирование на основе лицензии гарантирует, что API вызывается только тогда, когда ваша лицензия разрешает обновление.
Повторный запуск установщика с настраиваемым crontab: Если запись Q-Feeds в cron уже существует, установщик запрашивает подтверждение перед её изменением:
An existing Q-Feeds cron entry was found in the current crontab. Replace it with a fresh default entry? Choosing 'no' keeps your crontab unchanged [y/N]:Ответьте
no(по умолчанию), чтобы сохранить текущий crontab. При чистой первой установке существующая запись отсутствует, и этот запрос пропускается. Для автоматических установок задайтеQFEEDS_SKIP_CRON=1, чтобы оставить crontab без изменений без запроса.
Все настройки хранятся в /etc/qfeeds/qfeeds_config.conf. Вы можете редактировать этот файл напрямую без перезапуска установщика. Изменения вступят в силу при следующем запуске cron.
nft list table ip qfeeds
nft list set ip qfeeds qfeeds_blacklist_v4 | grep -oP '\d+.\d+.\d+.\d+' | wc -l
nft list set ip qfeeds qfeeds_blacklist_v4_nets | head -20
nft list set ip6 qfeeds qfeeds_blacklist_v6 | wc -l
### iptables+ipset бэкенд```bash
# List all Q-Feeds ipsets and their sizes
ipset list -t | grep -A4 qfeeds
# Count loaded IPv4 IPs
ipset list qfeeds_blacklist_v4 | tail -n +9 | wc -l
# Show loaded CIDR ranges
ipset list qfeeds_blacklist_v4_nets | tail -n +9 | head -20
# Show the qfeeds jump rule in INPUT, then the dedicated chain's block rules
iptables -L INPUT -n --line-numbers | grep qfeeds
iptables -L QFEEDS_INPUT -n
ip6tables -L QFEEDS_INPUT -n
tail -20 /var/log/qfeeds_blocklist.log
grep -i "error" /var/log/qfeeds_blocklist.log
sudo /usr/local/bin/update_qfeeds_blocklist.sh
sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh
sudo crontab -l | grep qfeeds
## 🔍 Устранение неполадок
### Общие
**Установка не удалась с ошибкой "Unable to locate package"**
- Установщик автоматически определяет ваш дистрибутив (Debian/Ubuntu, CentOS/RHEL, Fedora, Arch, Alpine). Если определение не удалось, установите зависимости вручную: `curl`, `jq`, `util-linux` (для `flock`), а также `nftables` или `iptables`+`ipset`.
**Наборы правил пусты после установки**
- Проверьте журнал: `tail -50 /var/log/qfeeds_blocklist.log`
- Проверьте правильность вашего API-токена
- Попробуйте принудительное обновление: `sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh`
**"Ещё не время. Следующее обновление запланировано на..."**
- Программа обновления соблюдает график вашей лицензии. Это сообщение означает, что cron запущен, но ваша лицензия пока не разрешает обновление. Это нормально — следующий запуск cron проверит снова.
- Установщик Linux хранит локально кэшированный индекс `licenses.php` и использует кэшированное значение `next_update` в качестве шлюза расписания. После успешного извлечения он обновляет этот локальный индекс для следующего цикла.
**Правила не сохраняются после перезагрузки**
- Если установлен `netfilter-persistent`, правила сохраняются автоматически
- **nftables**: если `netfilter-persistent` отсутствует, программа обновления теперь автоматически записывает полный набор правил в `/etc/nftables.conf` и включает службу `nftables`; вы также можете сохранить вручную с помощью `nft list ruleset > /etc/nftables.conf`
- **iptables**: программа обновления сохраняет с помощью `ipset save > /etc/ipset.conf` и `iptables-save`; вы также можете сохранить вручную
- Даже если сохранение полностью не удалось, программа обновления самовосстанавливается: при следующем запуске она обнаруживает пустой набор после перезагрузки и перестраивает его с полной синхронизацией
### Специфические для nftables
**"Batch nft -f не удался. Переход на пошаговое выполнение..."**
- Это нормально, особенно в контейнерах LXC, где ограничен буфер netlink ядра (`wmem_max`). Пошаговое выполнение работает корректно и быстро (~10 секунд для 400k+ IP-адресов).
**Синтаксическая ошибка: "unexpected string"**
- Убедитесь, что у вас установлена свежая версия nftables. Скрипт использует синтаксис `ip saddr`/`ip daddr`, который требует nftables 0.9+.
**"Error: Could not process rule: Message too long"**
- Это ограничение буфера netlink, обычно в контейнерах LXC. Скрипт автоматически переходит на пошаговое выполнение. Если вы видите это в журнале вместе с успешной загрузкой, то всё работает как задумано.
### Специфические для iptables+ipset
**"ipset restore не удался"**
- Проверьте, установлен ли `ipset`: `command -v ipset`
- Проверьте журнал на наличие конкретных ошибок: `grep -i "error" /var/log/qfeeds_blocklist.log`
- Убедитесь, что модуль ipset загружен: `lsmod | grep ip_set`
**Правила iptables не отображаются**
- Правила блокировки находятся в выделенных цепочках `QFEEDS_INPUT` / `QFEEDS_OUTPUT`; `INPUT`/`OUTPUT` содержат только переход `-j QFEEDS_INPUT` с тегом `qfeeds`
- Проверьте с помощью: `iptables -L INPUT -n | grep qfeeds` (переход) и `iptables -L QFEEDS_INPUT -n` (правила блокировки)
- Правило перехода использует `-m comment --comment "qfeeds"` для идентификации
- Убедитесь, что модуль `xt_set` загружен: `modprobe xt_set`
**"ipset create ... не удалось"**
- На очень старых ядрах типы `hash:ip` или `hash:net` могут быть недоступны. Обновите ядро или установите `ipset` из более нового репозитория.
---
## 🗑️ Удаление```bash
sudo ./qfeeds-uninstaller.sh
Деинсталлятор удаляет всё в зависимости от обнаруженного бэкенда:
nftables бэкенд:
ip qfeeds и ip6 qfeeds (включая все цепочки, правила и наборы)iptables бэкенд:
qfeeds (включая правила перехода)QFEEDS_INPUT / QFEEDS_OUTPUTqfeeds_blacklist_v4, qfeeds_blacklist_v4_nets, qfeeds_whitelist_v4 и эквиваленты для IPv6)Оба бэкенда:
/etc/qfeeds/)/usr/local/bin/update_qfeeds_blocklist.sh)Если файл конфигурации отсутствует, деинсталлятор пытается выполнить очистку для обоих бэкендов.
Примечание: Деинсталлятор не удаляет системные пакеты (curl, jq, ipset и т.д.), которые были установлены как зависимости.
Этот проект лицензирован в соответствии с Apache License 2.0 — подробнее см. в файле LICENSE.
Используйте на свой страх и риск.
Пожалуйста, протестируйте эти скрипты в вашей среде перед использованием в production. Автор не несёт ответственности за любые проблемы или ущерб, которые могут возникнуть при их использовании.
Помощь ИИ: Части этого проекта (код, исправления и документация) были написаны с помощью инструментов ИИ и впоследствии проверены мейнтейнерами. Хотя мы тестируем и проверяем изменения, пожалуйста, просмотрите скрипты самостоятельно перед запуском и сообщайте о любых подозрительных моментах.
| Бэкенд |
|---|
| 1-й | найдена команда nft | nftables |
| 2-й | найдена команда iptables | iptables+ipset |
| — | ничего не найдено | Ошибка (выход) |
| Переменная | Описание | Значение по умолчанию |
|---|
BACKEND | Бэкенд брандмауэра (nftables или iptables) | (автоопределено) |
API_TOKEN | Ваш токен API Q-Feeds | (обязательно) |
FEED_TYPE | Тип получаемой ленты | malware_ip |
LIMIT | Максимальное количество IP для получения (пусто = без ограничений) | (пусто) |
BLOCK_INCOMING | Блокировать входящие с IP из чёрного списка | yes |
BLOCK_OUTGOING | Блокировать исходящие на IP из чёрного списка | no |
WHITELIST_V4 | Разделённый запятыми белый список IPv4 | (пусто) |
WHITELIST_V6 | Разделённый запятыми белый список IPv6 | (пусто) |
LOG_FILE | Путь к файлу журнала | /var/log/qfeeds_blocklist.log |
FULL_SYNC_MAX_AGE | Максимальный возраст в секундах, после которого принудительно выполняется полная повторная синхронизация (защита в глубину) | 86400 (24h) |
| Путь | Назначение |
|---|
/etc/qfeeds/qfeeds_config.conf | Файл конфигурации |
/etc/qfeeds/.last_sync | Файл состояния для отслеживания полной/дифференциальной синхронизации |
/etc/qfeeds/.last_count | Последнее количество успешных элементов, используется для обнаружения потери базовой линии |
/usr/local/bin/update_qfeeds_blocklist.sh | Скрипт обновления (запускается через cron) |
/var/log/qfeeds_blocklist.log | Файл журнала |