
Script to implement Q-Feeds directly on NFtables or IPtables
Liste de blocage IP automatisée pour les serveurs Linux — prend en charge nftables et iptables+ipset
Obtenez une clé API gratuite sur 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
### Étape 3 : Exécuter l'installateur en tant que root```bash
sudo ./qfeeds-installer.sh
L’installateur va :
Votre serveur est maintenant protégé. La tâche cron vérifie les mises à jour toutes les 20 minutes (configurable), et les appels API réels n’ont lieu que lorsque votre licence le permet.
Cette solution télécharge périodiquement le dernier flux de renseignements sur les menaces de Q-Feeds et l’applique sous forme de règles de pare-feu, vous permettant de :
L’installateur détecte automatiquement le sous-jacent de pare-feu disponible :
Le sous-jacent détecté est stocké dans le fichier de configuration. Les scripts de mise à jour et de désinstallation l’utilisent pour exécuter les commandes de pare-feu appropriées.
Les deux sous-jacents utilisent la même stratégie de jeu divisé pour des performances maximales :
Sous-jacent 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 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) │
└──────────────────────────────────────────────────────────┘
La même structure existe pour IPv6 (ip6 qfeeds table ou ip6tables + family inet6 ipsets).
Pourquoi deux types d'ensembles ?
┌──────────────────────────────────────────────────────┐ │ 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 │ └──────────────────────────────────────────────────────┘
### Synchronisation complète vs synchronisation différentielle
| Mode | Quand | Ce qu'il fait |
|------|------|-------------|
| **Synchronisation complète** | Première exécution, mise à jour forcée, après un échec de diff, lorsque l'ensemble local a perdu sa référence (vide ou beaucoup plus petit que prévu), ou lorsque la dernière synchronisation est plus ancienne que `FULL_SYNC_MAX_AGE` (24h par défaut) | Récupère et valide d'abord chaque flux, puis vide et recharge les ensembles de blacklist. L'ensemble n'est vidé qu'après avoir obtenu des données valides, donc un échec de récupération ne vous laisse jamais sans protection |
| **Synchronisation différentielle** | Exécutions suivantes (flux `malware_ip` uniquement) avec un ensemble local sain | Récupère uniquement les ajouts (`+`) et les suppressions (`-`) depuis la dernière récupération |
La synchronisation différentielle est **par clé API** — l'API suit votre dernière récupération réussie et ne renvoie que les changements depuis cette date. Si un diff échoue, le script bascule automatiquement vers une synchronisation complète.
> **Auto-réparation :** Les mises à jour différentielles ne patchent que l'ensemble existant. Si cet ensemble est perdu ou tronqué – par exemple après un redémarrage où les règles du pare-feu n'ont pas été persistées, une vidange manuelle ou une synchronisation partielle antérieure – le programme de mise à jour détecte la référence manquante (le nombre d'éléments en direct est 0 ou bien inférieur au dernier nombre enregistré) et force une reconstruction complète au lieu d'appliquer un diff sur un ensemble vide. Comme filet de sécurité supplémentaire, il force également une synchronisation complète périodique (toutes les 24h par défaut, via `FULL_SYNC_MAX_AGE`).
### Planification basée sur la licence
Le programme de mise à jour vérifie l'API de licence Q-Feeds (`licenses.php`) avant chaque exécution. Si l'horodatage `next_update` de votre licence n'est pas encore atteint, le script se termine prématurément sans effectuer d'appels API inutiles. La tâche cron s'exécute fréquemment (par défaut : toutes les 20 minutes), mais les mises à jour réelles n'ont lieu que lorsque votre licence le permet.
---
## ✅ Prérequis
Avant l'installation, assurez-vous d'avoir :
- [x] **Serveur Linux** avec **nftables** ou **iptables** (Debian, Ubuntu, CentOS, Fedora, Arch, Alpine)
- [x] **Accès root** — l'installateur et le programme de mise à jour doivent s'exécuter en tant que root
- [x] **Jeton API Q-Feeds** — obtenez le vôtre gratuitement sur [tip.qfeeds.com](https://tip.qfeeds.com/)
- [x] **Accès Internet** — le serveur doit pouvoir atteindre `api.qfeeds.com`
L'installateur installera automatiquement les dépendances nécessaires :
- **Backend nftables** : `nftables`, `curl`, `jq`, `util-linux`
- **Backend iptables** : `iptables`, `ipset`, `curl`, `jq`, `util-linux`
---
## 📝 Guide d'installation détaillé
### 1. Obtenez votre jeton API
Visitez [tip.qfeeds.com](https://tip.qfeeds.com/) pour obtenir votre jeton API Q-Feeds gratuit.
### 2. Téléchargez et exécutez```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
L'installateur posera les questions suivantes:
Enter your Q-Feeds API Token:
Votre jeton depuis [tip.qfeeds.com](https://tip.qfeeds.com/). L'installateur refuse de continuer si vide.
#### Type de flux```
Enter feed type [default: malware_ip]:
La valeur par défaut est malware_ip. Modifiez-la uniquement si Q-Feeds vous a fourni un type de flux différent.
Enter the limit of IPs to fetch (leave empty for no limit):
Appuyez sur Entrée pour aucune limite (recommandé). Entrez un nombre pour limiter la taille du flux.
#### Blocage directionnel```
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):
Ajoutez vos IP de gestion ici pour garantir que vous ne serez jamais verrouillé, même si elles apparaissent dans le flux. Les règles de liste blanche sont toujours vérifiées **avant** les règles de liste noire.
#### Planification Cron```
Enter cron schedule (e.g., '*/20 * * * *') [default: */20 * * * *]:
How often the updater checks for new data. Default is every 20 minutes. The license-based scheduling ensures the API is only called when your license allows an update.
Réexécution de l'installateur avec une crontab personnalisée : Si une entrée cron Q-Feeds existe déjà, l'installateur demande avant de la modifier :
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]:Répondez
no(par défaut) pour conserver votre crontab existante. Une première installation propre n'a aucune entrée existante et ignore cette invite. Pour les installations en mode non interactif, définissezQFEEDS_SKIP_CRON=1pour laisser la crontab inchangée sans être invité.
Tous les paramètres sont stockés dans /etc/qfeeds/qfeeds_config.conf. Vous pouvez modifier ce fichier directement sans réexécuter l'installateur. Les modifications prennent effet lors de la prochaine exécution de 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 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
## 🔍 Dépannage
### Général
**L'installation échoue avec « Unable to locate package »**
- L'installateur détecte automatiquement votre distribution (Debian/Ubuntu, CentOS/RHEL, Fedora, Arch, Alpine). Si la détection échoue, installez les dépendances manuellement : `curl`, `jq`, `util-linux` (pour `flock`), ainsi que `nftables` ou `iptables`+`ipset`.
**Les ensembles sont vides après l'installation**
- Vérifiez le journal : `tail -50 /var/log/qfeeds_blocklist.log`
- Vérifiez que votre jeton API est correct
- Essayez une mise à jour forcée : `sudo QFEEDS_FORCE_UPDATE=1 /usr/local/bin/update_qfeeds_blocklist.sh`
**« Pas encore l'heure. Prochaine mise à jour prévue à... »**
- L'utilitaire de mise à jour respecte votre calendrier de licence. Ce message signifie que cron s'est exécuté, mais que votre licence n'autorise pas encore une mise à jour. C'est normal — la prochaine exécution de cron vérifiera à nouveau.
- L'installateur Linux conserve un index local mis en cache de `licenses.php` et utilise le `next_update` mis en cache comme porte d'entrée du planning. Après un téléchargement réussi, il actualise cet index local pour le cycle suivant.
**Les règles ne persistent pas après un redémarrage**
- Si `netfilter-persistent` est installé, les règles sont sauvegardées automatiquement
- **nftables** : si `netfilter-persistent` est absent, l'utilitaire de mise à jour écrit désormais l'ensemble des règles dans `/etc/nftables.conf` automatiquement et active le service `nftables` ; vous pouvez toujours sauvegarder manuellement avec `nft list ruleset > /etc/nftables.conf`
- **iptables** : l'utilitaire de mise à jour sauvegarde avec `ipset save > /etc/ipset.conf` et `iptables-save` ; vous pouvez aussi sauvegarder manuellement
- Même si la persistance échoue complètement, l'utilitaire de mise à jour s'auto-répare : lors de la prochaine exécution, il détecte l'ensemble vide après un redémarrage et le reconstruit avec une synchronisation complète.
### Spécifique à nftables
**« Batch nft -f failed. Falling back to per-command execution... »**
- Ceci est normal, surtout sur les conteneurs LXC où le tampon netlink du noyau (`wmem_max`) est limité. Le repli par commande fonctionne correctement et est rapide (~10 secondes pour 400k+ IPs).
**Erreur de syntaxe : « unexpected string »**
- Assurez-vous d'exécuter une version récente de nftables. Le script utilise la syntaxe `ip saddr`/`ip daddr` qui nécessite nftables 0.9+.
**« Error: Could not process rule: Message too long »**
- Il s'agit de la limite du tampon netlink, typiquement dans les conteneurs LXC. Le script revient automatiquement à une exécution par commande. Si vous voyez ceci dans le journal en même temps qu'un chargement réussi, cela fonctionne comme prévu.
### Spécifique à iptables+ipset
**« ipset restore failed »**
- Vérifiez que `ipset` est installé : `command -v ipset`
- Vérifiez le journal pour des erreurs spécifiques : `grep -i "error" /var/log/qfeeds_blocklist.log`
- Assurez-vous que le module ipset est chargé : `lsmod | grep ip_set`
**Les règles iptables n'apparaissent pas**
- Les règles de blocage se trouvent dans les chaînes dédiées `QFEEDS_INPUT` / `QFEEDS_OUTPUT` ; `INPUT`/`OUTPUT` ne contiennent qu'un saut `-j QFEEDS_INPUT` marqué avec le commentaire `qfeeds`
- Vérifiez avec : `iptables -L INPUT -n | grep qfeeds` (le saut) et `iptables -L QFEEDS_INPUT -n` (les règles de blocage)
- La règle de saut utilise `-m comment --comment "qfeeds"` pour l'identification
- Assurez-vous que le module `xt_set` est chargé : `modprobe xt_set`
**« ipset create ... a échoué »**
- Sur des noyaux très anciens, les types `hash:ip` ou `hash:net` peuvent ne pas être disponibles. Mettez à niveau votre noyau ou installez `ipset` depuis un dépôt plus récent.
---
## 🗑️ Désinstallation```bash
sudo ./qfeeds-uninstaller.sh
Le désinstalleur supprime tout en fonction du backend détecté :
Backend nftables :
ip qfeeds et ip6 qfeeds (y compris toutes les chaînes, règles et ensembles)Backend iptables :
qfeeds (y compris les règles de saut)QFEEDS_INPUT / QFEEDS_OUTPUTqfeeds_blacklist_v4, qfeeds_blacklist_v4_nets, qfeeds_whitelist_v4 et les équivalents IPv6)Les deux backends :
/etc/qfeeds/)/usr/local/bin/update_qfeeds_blocklist.sh)Si le fichier de configuration est manquant, le désinstalleur tente un nettoyage pour les deux backends.
Note : Le désinstalleur ne supprime pas les paquets système (curl, jq, ipset, etc.) qui ont été installés comme dépendances.
Ce projet est sous licence Apache License 2.0 - voir le fichier LICENSE pour plus de détails.
Utilisation à vos propres risques.
Veuillez tester ces scripts dans votre environnement avant de les déployer en production. L'auteur n'est pas responsable des problèmes ou dommages pouvant résulter de leur utilisation.
Assistance IA : Des parties de ce projet (code, corrections et documentation) ont été écrites avec l'aide d'outils IA et ont ensuite été revues par les mainteneurs. Bien que nous testions et révisions les modifications, veuillez examiner les scripts vous-même avant de les exécuter et signalez tout ce qui vous semble anormal.
| Priorité | Détection | Sous-jacent |
|---|
| 1ère | Commande nft trouvée | nftables |
| 2ème | Commande iptables trouvée | iptables+ipset |
| — | Aucune trouvée | Erreur (arrêt) |
| Variable | Description | Valeur par défaut |
|---|
BACKEND | Backend de pare-feu (nftables ou iptables) | (détection automatique) |
API_TOKEN | Votre jeton API Q-Feeds | (obligatoire) |
FEED_TYPE | Type de flux à récupérer | malware_ip |
LIMIT | Nombre max d'IP à récupérer (vide = aucune limite) | (vide) |
BLOCK_INCOMING | Bloquer le trafic entrant depuis des IP blacklistées | yes |
BLOCK_OUTGOING | Bloquer le trafic sortant vers des IP blacklistées | no |
WHITELIST_V4 | Liste blanche IPv4 séparée par des virgules | (vide) |
WHITELIST_V6 | Liste blanche IPv6 séparée par des virgules | (vide) |
LOG_FILE | Chemin vers le fichier journal | /var/log/qfeeds_blocklist.log |
FULL_SYNC_MAX_AGE | Âge maximal en secondes avant qu'une resynchronisation complète ne soit forcée (défense en profondeur) | 86400 (24h) |
| Chemin | Objectif |
|---|
/etc/qfeeds/qfeeds_config.conf | Fichier de configuration |
/etc/qfeeds/.last_sync | Fichier d'état pour le suivi de la synchronisation complète/différentielle |
/etc/qfeeds/.last_count | Dernier nombre d'éléments réussi, utilisé pour détecter une perte de base de référence |
/usr/local/bin/update_qfeeds_blocklist.sh | Script de mise à jour (exécuté via cron) |
/var/log/qfeeds_blocklist.log | Fichier journal |