
Script to implement Q-Feeds directly on NFtables or IPtables
Lista de bloqueio automatizada de IPs de malware para servidores Linux — suporta nftables e iptables+ipset
Obtenha uma chave de API gratuita em 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
### Step 3: Execute o instalador como root```bash
sudo ./qfeeds-installer.sh
O instalador irá:
O seu servidor está agora protegido. A tarefa cron verifica atualizações a cada 20 minutos (configurável), e as chamadas reais à API só acontecem quando a sua licença permite.
Esta solução baixa periodicamente o feed de inteligência de ameaças mais recente do Q-Feeds e aplica-o como regras de firewall, permitindo-lhe:
O instalador deteta automaticamente qual o backend de firewall disponível:
O backend detetado é armazenado no ficheiro de configuração. Os scripts de atualização e desinstalação utilizam-no para executar os comandos de firewall corretos.
Ambos os backends utilizam a mesma estratégia de conjuntos divididos para máximo desempenho:
nftables backend:``` ┌─────────────────────────────────────────────────────────┐ │ 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 backend:**```
┌──────────────────────────────────────────────────────────┐
│ 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) │
└──────────────────────────────────────────────────────────┘
A mesma estrutura existe para IPv6 (tabela ip6 qfeeds ou ip6tables + family inet6 ipsets).
Por que dois tipos de conjuntos?
┌──────────────────────────────────────────────────────┐ │ 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 vs Diff Sync
| Mode | When | What it does |
|------|------|-------------|
| **Full sync** | First run, forced update, after a diff failure, when the local set has lost its baseline (empty or much smaller than expected), or when the last sync is older than `FULL_SYNC_MAX_AGE` (default 24h) | Fetches and validates each feed first, then flushes and reloads the blacklist sets. The set is only flushed once valid data is in hand, so a failed fetch never leaves you unprotected |
| **Diff sync** | Subsequent runs (`malware_ip` feed only) with a healthy local set | Fetches only additions (`+`) and removals (`-`) since last pull |
The diff sync is **per API key** — the API tracks your last successful pull and only returns changes since then. If a diff fails, the script automatically falls back to a full sync.
> **Self-healing:** Diff updates only patch the existing set. If that set is ever lost or truncated — for example a reboot where the firewall rules were not persisted, a manual flush, or a previous partial sync — the updater detects the missing baseline (live element count is 0 or far below the last recorded count) and forces a full rebuild instead of diffing onto an empty set. As an extra safety net it also forces a periodic full sync (every 24h by default, via `FULL_SYNC_MAX_AGE`).
### License-Based Scheduling
The updater checks the Q-Feeds license API (`licenses.php`) before every run. If your license's `next_update` timestamp hasn't been reached yet, the script exits early without making unnecessary API calls. The cron job runs frequently (default: every 20 minutes), but actual updates only happen when your license allows.
---
## ✅ Prerequisites
Before installing, ensure you have:
- [x] **Linux server** with **nftables** or **iptables** (Debian, Ubuntu, CentOS, Fedora, Arch, Alpine)
- [x] **Root access** — the installer and updater must run as root
- [x] **Q-Feeds API Token** — get yours free at [tip.qfeeds.com](https://tip.qfeeds.com/)
- [x] **Internet access** — the server needs to reach `api.qfeeds.com`
The installer will automatically install required dependencies:
- **nftables backend**: `nftables`, `curl`, `jq`, `util-linux`
- **iptables backend**: `iptables`, `ipset`, `curl`, `jq`, `util-linux`
---
## 📝 Detailed Installation Guide
### 1. Get Your API Token
Visit [tip.qfeeds.com](https://tip.qfeeds.com/) to obtain your free Q-Feeds API token.
### 2. Download and Run```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
O instalador fará as seguintes perguntas:
Enter your Q-Feeds API Token:
Seu token de [tip.qfeeds.com](https://tip.qfeeds.com/). O instalador se recusa a continuar se estiver vazio.
#### Tipo de Feed```
Enter feed type [default: malware_ip]:
O padrão é malware_ip. Só altere isto se a Q-Feeds lhe forneceu um tipo de feed diferente.
Enter the limit of IPs to fetch (leave empty for no limit):
Pressione Enter para sem limite (recomendado). Digite um número para limitar o tamanho do feed.
#### Bloqueio Direcional```
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):
Adicione seus IP(s) de gerenciamento aqui para garantir que você nunca seja bloqueado, mesmo que eles apareçam no feed. As regras de whitelist são sempre verificadas **antes** das regras de blacklist.
#### Cron Schedule```
Enter cron schedule (e.g., '*/20 * * * *') [default: */20 * * * *]:
Com que frequência o atualizador verifica novos dados. O padrão é a cada 20 minutos. O agendamento baseado em licença garante que a API seja chamada apenas quando sua licença permitir uma atualização.
Reexecutando o instalador com um crontab personalizado: Se uma entrada cron do Q-Feeds já existir, o instalador pergunta antes de tocá-la:
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]:Responda
no(o padrão) para manter seu crontab existente. Uma instalação limpa pela primeira vez não tem entrada existente e ignora este prompt. Para instalações desassistidas, definaQFEEDS_SKIP_CRON=1para deixar o crontab intocado sem solicitação.
Todas as configurações são armazenadas em /etc/qfeeds/qfeeds_config.conf. Você pode editar este arquivo diretamente sem reexecutar o instalador. As alterações entram em vigor na próxima execução do 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 back-end```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
---
## 🔍 Solução de Problemas
### Geral
**Falha na instalação com "Unable to locate package"**
- O instalador detecta automaticamente a sua distribuição (Debian/Ubuntu, CentOS/RHEL, Fedora, Arch, Alpine). Se a deteção falhar, instale as dependências manualmente: `curl`, `jq`, `util-linux` (para `flock`), além de `nftables` ou `iptables`+`ipset`.
**Conjuntos estão vazios após a instalação**
- Verifique o registo: `tail -50 /var/log/qfeeds_blocklist.log`
- Verifique se o seu token de API está correto
- Tente uma atualização forçada: `sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh`
**"Ainda não é hora. Próxima atualização agendada para..."**
- O atualizador respeita a sua agenda de licença. Esta mensagem significa que o cron foi executado, mas a sua licença ainda não permite uma atualização. Isto é normal — a próxima execução do cron verificará novamente.
- O instalador Linux mantém um índice `licenses.php` em cache local e usa o `next_update` em cache como controlador de agenda. Após uma obtenção bem-sucedida, ele atualiza esse índice local para o próximo ciclo.
**As regras não persistem após reinicialização**
- Se `netfilter-persistent` estiver instalado, as regras são salvas automaticamente
- **nftables**: se `netfilter-persistent` estiver ausente, o atualizador agora escreve o conjunto completo de regras em `/etc/nftables.conf` automaticamente e ativa o serviço `nftables`; ainda pode salvar manualmente com `nft list ruleset > /etc/nftables.conf`
- **iptables**: o atualizador salva com `ipset save > /etc/ipset.conf` e `iptables-save`; também pode salvar manualmente
- Mesmo que a persistência falhe completamente, o atualizador se auto-recupera: na próxima execução, ele deteta o conjunto vazio após uma reinicialização e o reconstrói com uma sincronização completa
### Específico do nftables
**"Batch nft -f falhou. Recorrendo à execução por comando..."**
- Isto é normal, especialmente em contentores LXC onde o buffer netlink do kernel (`wmem_max`) está restrito. A alternativa de execução por comando funciona corretamente e é rápida (~10 segundos para mais de 400 mil IPs).
**Erro de sintaxe: "unexpected string"**
- Certifique-se de que está a executar uma versão recente do nftables. O script usa a sintaxe `ip saddr`/`ip daddr` que requer nftables 0.9+.
**"Error: Could not process rule: Message too long"**
- Este é o limite do buffer netlink, tipicamente em contentores LXC. O script recorre automaticamente à execução por comando. Se vir isto no registo juntamente com um carregamento bem-sucedido, está a funcionar como pretendido.
### Específico do iptables+ipset
**"ipset restore failed"**
- Verifique se `ipset` está instalado: `command -v ipset`
- Verifique o registo para erros específicos: `grep -i "error" /var/log/qfeeds_blocklist.log`
- Certifique-se de que o módulo ipset está carregado: `lsmod | grep ip_set`
**As regras iptables não aparecem**
- As regras de bloqueio residem nas cadeias dedicadas `QFEEDS_INPUT` / `QFEEDS_OUTPUT`; `INPUT`/`OUTPUT` apenas contêm um salto `-j QFEEDS_INPUT` marcado com o comentário `qfeeds`
- Verifique com: `iptables -L INPUT -n | grep qfeeds` (o salto) e `iptables -L QFEEDS_INPUT -n` (as regras de bloqueio)
- A regra de salto usa `-m comment --comment "qfeeds"` para identificação
- Certifique-se de que o módulo `xt_set` está carregado: `modprobe xt_set`
**"ipset create ... failed"**
- Em kernels muito antigos, os tipos `hash:ip` ou `hash:net` podem não estar disponíveis. Atualize o seu kernel ou instale o `ipset` a partir de um repositório mais recente.
---
## 🗑️ Desinstalação```bash
sudo ./qfeeds-uninstaller.sh
O desinstalador remove tudo com base no backend detectado:
nftables backend:
ip qfeeds e ip6 qfeeds (incluindo todas as chains, regras e sets)iptables backend:
qfeeds (incluindo as regras de salto)QFEEDS_INPUT / QFEEDS_OUTPUTqfeeds_blacklist_v4, qfeeds_blacklist_v4_nets, qfeeds_whitelist_v4 e equivalentes IPv6)Ambos os backends:
/etc/qfeeds/)/usr/local/bin/update_qfeeds_blocklist.sh)Se o arquivo de configuração estiver ausente, o desinstalador tenta a limpeza para ambos os backends.
Nota: O desinstalador não remove pacotes do sistema (curl, jq, ipset, etc.) que foram instalados como dependências.
Este projeto está licenciado sob a Apache License 2.0 - consulte o arquivo LICENSE para obter detalhes.
Use por sua conta e risco.
Teste esses scripts em seu ambiente antes de implantá-los em produção. O autor não é responsável por quaisquer problemas ou danos que possam ocorrer com seu uso.
Assistência de IA: Partes deste projeto (código, correções e documentação) foram escritas com a ajuda de ferramentas de IA e posteriormente revisadas pelos mantenedores. Embora testemos e revisemos as alterações, revise os scripts por conta própria antes de executá-los e relate qualquer coisa que pareça estranha.
| Prioridade | Deteção | Backend |
|---|
| 1º | comando nft encontrado | nftables |
| 2º | comando iptables encontrado | iptables+ipset |
| — | Nenhum encontrado | Erro (saída) |
| Variável | Descrição | Padrão |
|---|
BACKEND | Backend do firewall (nftables ou iptables) | (auto-detectado) |
API_TOKEN | Seu token da API Q-Feeds | (obrigatório) |
FEED_TYPE | Tipo de feed a buscar | malware_ip |
LIMIT | Máx. de IPs a buscar (vazio = sem limite) | (vazio) |
BLOCK_INCOMING | Bloquear entrada de IPs na lista negra | yes |
BLOCK_OUTGOING | Bloquear saída para IPs na lista negra | no |
WHITELIST_V4 | Lista branca IPv4 separada por vírgulas | (vazio) |
WHITELIST_V6 | Lista branca IPv6 separada por vírgulas | (vazio) |
LOG_FILE | Caminho para o arquivo de log | /var/log/qfeeds_blocklist.log |
FULL_SYNC_MAX_AGE | Idade máxima em segundos antes de forçar uma ressincronização completa (defesa em profundidade) | 86400 (24h) |
| Caminho | Finalidade |
|---|
/etc/qfeeds/qfeeds_config.conf | Arquivo de configuração |
/etc/qfeeds/.last_sync | Arquivo de estado para rastreamento de sincronização completa/diferencial |
/etc/qfeeds/.last_count | Última contagem de elementos bem-sucedida, usada para detectar uma linha de base perdida |
/usr/local/bin/update_qfeeds_blocklist.sh | Script de atualização (executado via cron) |
/var/log/qfeeds_blocklist.log | Arquivo de log |