
Script para implementar Q-Feeds directamente en NFtables o IPtables
Lista de bloqueo automatizada de IP de malware para servidores Linux — compatible con nftables e iptables+ipset
Obtén una clave API gratuita en 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
### Paso 3: Ejecute el instalador como root```bash
sudo ./qfeeds-installer.sh
El instalador:
Su servidor ahora está protegido. La tarea cron comprueba las actualizaciones cada 20 minutos (configurable), y las llamadas a la API solo ocurren cuando su licencia lo permite.
Esta solución descarga periódicamente la última fuente de inteligencia de amenazas de Q-Feeds y la aplica como reglas de firewall, permitiéndole:
El instalador detecta automáticamente qué backend de firewall está disponible:
| Prioridad | Detección | Backend |
|---|---|---|
| 1º | comando nft encontrado | nftables |
| 2º | comando iptables encontrado | iptables+ipset |
| — | Ninguno encontrado | Error (salir) |
El backend detectado se almacena en el archivo de configuración. Los scripts de actualización y desinstalación lo usan para ejecutar los comandos de firewall correctos.
Ambos backends utilizan la misma estrategia de conjuntos divididos para un rendimiento máximo:
Backend 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 │ └─────────────────────────────────────────────────────────┘
**backend de 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) │
└──────────────────────────────────────────────────────────┘
La misma estructura existe para IPv6 (ip6 qfeeds table o ip6tables + family inet6 ipsets).
¿Por qué dos 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 │ └──────────────────────────────────────────────────────┘
### Sincronización completa vs Sincronización diferencial
| Modo | Cuándo | Qué hace |
|------|--------|---------|
| **Sincronización completa** | Primera ejecución, actualización forzada, después de un fallo diferencial, cuando el conjunto local ha perdido su línea base (vacío o mucho más pequeño de lo esperado), o cuando la última sincronización es más antigua que `FULL_SYNC_MAX_AGE` (por defecto 24h) | Obtiene y valida cada fuente primero, luego vacía y recarga los conjuntos de la lista negra. El conjunto solo se vacía una vez que se tienen datos válidos, por lo que una obtención fallida nunca te deja desprotegido |
| **Sincronización diferencial** | Ejecuciones posteriores (solo fuente `malware_ip`) con un conjunto local saludable | Obtiene solo las adiciones (`+`) y eliminaciones (`-`) desde la última extracción |
La sincronización diferencial es **por clave API** — la API rastrea tu última extracción exitosa y solo devuelve los cambios desde entonces. Si una sincronización diferencial falla, el script automáticamente recurre a una sincronización completa.
> **Autocuración:** Las actualizaciones diferenciales solo parchean el conjunto existente. Si ese conjunto se pierde o se trunca — por ejemplo, un reinicio donde las reglas del cortafuegos no se persistieron, un vaciado manual, o una sincronización parcial previa — el actualizador detecta la línea base faltante (el recuento de elementos vivos es 0 o muy inferior al último recuento registrado) y fuerza una reconstrucción completa en lugar de aplicar diferenciales sobre un conjunto vacío. Como red de seguridad adicional, también fuerza una sincronización completa periódica (cada 24h por defecto, mediante `FULL_SYNC_MAX_AGE`).
### Programación basada en licencia
El actualizador verifica la API de licencia de Q-Feeds (`licenses.php`) antes de cada ejecución. Si la marca de tiempo `next_update` de tu licencia aún no se ha alcanzado, el script finaliza anticipadamente sin hacer llamadas API innecesarias. La tarea cron se ejecuta con frecuencia (por defecto: cada 20 minutos), pero las actualizaciones reales solo ocurren cuando tu licencia lo permite.
---
## ✅ Requisitos previos
Antes de instalar, asegúrate de tener:
- [x] **Servidor Linux** con **nftables** o **iptables** (Debian, Ubuntu, CentOS, Fedora, Arch, Alpine)
- [x] **Acceso root** — el instalador y el actualizador deben ejecutarse como root
- [x] **Token API de Q-Feeds** — obtén el tuyo gratis en [tip.qfeeds.com](https://tip.qfeeds.com/)
- [x] **Acceso a Internet** — el servidor necesita alcanzar `api.qfeeds.com`
El instalador instalará automáticamente las dependencias requeridas:
- **Backend nftables**: `nftables`, `curl`, `jq`, `util-linux`
- **Backend iptables**: `iptables`, `ipset`, `curl`, `jq`, `util-linux`
---
## 📝 Guía de instalación detallada
### 1. Obtén tu token API
Visita [tip.qfeeds.com](https://tip.qfeeds.com/) para obtener tu token API gratuito de Q-Feeds.
### 2. Descarga y ejecuta```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
El instalador hará las siguientes preguntas:
Enter your Q-Feeds API Token:
Su token de [tip.qfeeds.com](https://tip.qfeeds.com/). El instalador se niega a continuar si está vacío.
#### Tipo de Feed```
Enter feed type [default: malware_ip]:
Default es malware_ip. Solo cambie esto si Q-Feeds le ha proporcionado un tipo de feed diferente.
Enter the limit of IPs to fetch (leave empty for no limit):
Presiona Enter para sin límite (recomendado). Ingresa un número para limitar el tamaño de la fuente.
#### Bloqueo Direccional```
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):
Agregue su(s) IP(s) de gestión aquí para asegurarse de nunca quedarse bloqueado, incluso si aparecen en la fuente. Las reglas de lista blanca siempre se verifican **antes** que las reglas de lista negra.
#### Cron Schedule```
Enter cron schedule (e.g., '*/20 * * * *') [default: */20 * * * *]:
Con qué frecuencia el actualizador verifica nuevos datos. El valor predeterminado es cada 20 minutos. La programación basada en licencias garantiza que la API solo se llame cuando su licencia lo permita.
Volver a ejecutar el instalador con un crontab personalizado: Si ya existe una entrada de cron de Q-Feeds, el instalador pregunta antes de modificarla:
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(el valor predeterminado) para mantener su crontab existente. Una instalación limpia por primera vez no tiene una entrada existente y omite este mensaje. Para instalaciones desatendidas, configureQFEEDS_SKIP_CRON=1para dejar el crontab sin tocar sin que se le solicite.
Toda la configuración se almacena en /etc/qfeeds/qfeeds_config.conf. Puede editar este archivo directamente sin volver a ejecutar el instalador. Los cambios surten efecto en la próxima ejecución de cron.
| Variable | Descripción | Predeterminado |
|---|---|---|
BACKEND | Backend de cortafuegos (nftables o iptables) | (detectado automáticamente) |
API_TOKEN | Su token de API de Q-Feeds | (requerido) |
FEED_TYPE | Tipo de feed a obtener | malware_ip |
LIMIT | Máximo de IPs a obtener (vacío = sin límite) | (vacío) |
BLOCK_INCOMING | Bloquear entradas desde IPs en lista negra | yes |
BLOCK_OUTGOING | Bloquear salidas hacia IPs en lista negra | no |
WHITELIST_V4 | Lista blanca IPv4 separada por comas | (vacío) |
WHITELIST_V6 | Lista blanca IPv6 separada por comas | (vacío) |
LOG_FILE | Ruta al archivo de registro | /var/log/qfeeds_blocklist.log |
FULL_SYNC_MAX_AGE | Edad máxima en segundos antes de forzar una resincronización completa (defensa en profundidad) | 86400 (24h) |
| Ruta | Propósito |
|---|---|
/etc/qfeeds/qfeeds_config.conf | Archivo de configuración |
/etc/qfeeds/.last_sync | Archivo de estado para el seguimiento de sincronización completa/diferencial |
/etc/qfeeds/.last_count | Último recuento de elementos exitoso, utilizado para detectar una pérdida de línea base |
/usr/local/bin/update_qfeeds_blocklist.sh | Script de actualización (se ejecuta mediante cron) |
/var/log/qfeeds_blocklist.log | Archivo de registro |
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 backend```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
---
## 🔍 Solución de problemas
### General
**La instalación falla con "Unable to locate package"**
- El instalador detecta automáticamente tu distribución (Debian/Ubuntu, CentOS/RHEL, Fedora, Arch, Alpine). Si falla la detección, instala las dependencias manualmente: `curl`, `jq`, `util-linux` (para `flock`), además de `nftables` o `iptables`+`ipset`.
**Los sets están vacíos después de la instalación**
- Revisa el registro: `tail -50 /var/log/qfeeds_blocklist.log`
- Verifica que tu token API sea correcto
- Prueba una actualización forzada: `sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh`
**"Not time yet. Next update scheduled at..."**
- El actualizador respeta tu calendario de licencias. Este mensaje significa que el cron se ejecutó, pero tu licencia aún no permite una actualización. Es normal: la próxima ejecución de cron lo volverá a comprobar.
- El instalador de Linux mantiene un índice local en caché `licenses.php` y usa el `next_update` en caché como puerta de horario. Después de una descarga exitosa, actualiza ese índice local para el siguiente ciclo.
**Las reglas no persisten después de reiniciar**
- Si `netfilter-persistent` está instalado, las reglas se guardan automáticamente
- **nftables**: si `netfilter-persistent` está ausente, el actualizador ahora escribe el conjunto completo de reglas en `/etc/nftables.conf` automáticamente y habilita el servicio `nftables`; aún puedes guardar manualmente con `nft list ruleset > /etc/nftables.conf`
- **iptables**: el actualizador guarda con `ipset save > /etc/ipset.conf` e `iptables-save`; también puedes guardar manualmente
- Incluso si la persistencia falla por completo, el actualizador se autocura: en la siguiente ejecución detecta el conjunto vacío después de un reinicio y lo reconstruye con una sincronización completa
### Específico de nftables
**"Batch nft -f failed. Falling back to per-command execution..."**
- Esto es normal, especialmente en contenedores LXC donde el búfer netlink del kernel (`wmem_max`) está restringido. La alternativa por comando funciona correctamente y es rápida (~10 segundos para más de 400k IPs).
**Error de sintaxis: "unexpected string"**
- Asegúrate de estar ejecutando una versión reciente de nftables. El script usa la sintaxis `ip saddr`/`ip daddr` que requiere nftables 0.9+.
**"Error: Could not process rule: Message too long"**
- Este es el límite del búfer netlink, típicamente en contenedores LXC. El script vuelve automáticamente a la ejecución por comando. Si ves esto en el registro junto con una carga exitosa, está funcionando como se espera.
### Específico de iptables+ipset
**"ipset restore failed"**
- Verifica que `ipset` esté instalado: `command -v ipset`
- Revisa el registro para errores específicos: `grep -i "error" /var/log/qfeeds_blocklist.log`
- Asegúrate de que el módulo ipset esté cargado: `lsmod | grep ip_set`
**Las reglas de iptables no aparecen**
- Las reglas de bloqueo residen en las cadenas dedicadas `QFEEDS_INPUT` / `QFEEDS_OUTPUT`; `INPUT`/`OUTPUT` solo contienen un salto `-j QFEEDS_INPUT` etiquetado con el comentario `qfeeds`
- Verifica con: `iptables -L INPUT -n | grep qfeeds` (el salto) y `iptables -L QFEEDS_INPUT -n` (las reglas de bloqueo)
- La regla de salto usa `-m comment --comment "qfeeds"` para identificación
- Asegúrate de que el módulo `xt_set` esté cargado: `modprobe xt_set`
**"ipset create ... failed"**
- En kernels muy antiguos, los tipos `hash:ip` o `hash:net` pueden no estar disponibles. Actualiza tu kernel o instala `ipset` desde un repositorio más reciente.
---
## 🗑️ Desinstalación```bash
sudo ./qfeeds-uninstaller.sh
El desinstalador elimina todo según el backend detectado:
Backend nftables:
ip qfeeds y ip6 qfeeds (incluyendo todas las cadenas, reglas y conjuntos)Backend iptables:
qfeeds (incluyendo las reglas de salto)QFEEDS_INPUT / QFEEDS_OUTPUTqfeeds_blacklist_v4, qfeeds_blacklist_v4_nets, qfeeds_whitelist_v4, y los equivalentes IPv6)Ambos backends:
/etc/qfeeds/)/usr/local/bin/update_qfeeds_blocklist.sh)Si el archivo de configuración falta, el desinstalador intenta la limpieza para ambos backends.
Nota: El desinstalador no elimina los paquetes del sistema (curl, jq, ipset, etc.) que se instalaron como dependencias.
Este proyecto está licenciado bajo la Apache License 2.0 - consulte el archivo LICENSE para más detalles.
Úselo bajo su propio riesgo.
Pruebe estos scripts en su entorno antes de implementarlos en producción. El autor no es responsable de ningún problema o daño que pueda surgir de su uso.
Asistencia de IA: Partes de este proyecto (código, correcciones y documentación) fueron escritas con la ayuda de herramientas de IA y posteriormente revisadas por los mantenedores. Aunque probamos y revisamos los cambios, revise los scripts usted mismo antes de ejecutarlos y reporte cualquier cosa que parezca incorrecta.