#28 — nftables-dscp
PLANIFIÉ
Priorité: 🔴 CRITIQUE · Type: TYPE E · Conteneur: rgz-gateway · Code: scripts/qos/dscp_marking.sh
Dépendances: #7 rgz-gateway
Description
L'outil nftables-dscp implémente le marquage DSCP (Differentiated Services Code Point) des paquets IP à l'entrée dans le réseau ACCESS. Ce marquage constitue la première étape du pipeline QoS : chaque paquet reçoit une étiquette DSCP selon sa nature (trafic de gestion, trafic interactif, ou trafic en volume), étiquette que le module HTB (#27) utilise ensuite pour classer le paquet dans la bonne file d'attente avec le bon débit.
DSCP est un champ de 6 bits dans l'en-tête IP (RFC 2474, successeur de ToS). Le réseau ACCESS utilise les valeurs standard IETF : CS6/CS7 pour le MGMT, CS3/AF41 pour l'interactif, CS1/BE pour le bulk. Ce choix garantit l'interopérabilité avec les équipements CPE LiteBeam qui lisent et honorent ces valeurs de manière native.
Le marquage est réalisé via une table nftables dédiée rgz_dscp dans le hook postrouting (chaîne mangle). Cette table est séparée des tables de filtrage et NAT du gateway (#7, #32) pour éviter toute interférence. Le script dscp_marking.sh crée cette table au démarrage en respectant la règle LL#40 : suppression sélective de la table custom avant recréation, sans jamais toucher au ruleset global ni aux chains Docker.
Les règles DSCP sont statiques (définies à partir des ports de destination connus) et complétées par une règle par défaut qui marque tout trafic non classifié en CS1 (Bulk). Ceci garantit qu'aucun paquet ne traverse le réseau sans étiquette QoS.
Architecture Interne
Paquet IP entrant (hook postrouting, priorité mangle)
│
▼
Table inet rgz_dscp :: chain postrouting
│
├── Port dest SSH (22) ? → DSCP = CS6 (0x30, valeur décimale 48)
├── Port dest RADIUS (1812/1813/3799) ? → DSCP = CS6
├── Port dest Monitoring (9090, 3000, 9200, 5601) ? → DSCP = CS6
├── Port dest SNMP (161/udp) ? → DSCP = CS6
│ CLASSE HTB → 1:10 MGMT
│
├── Port dest DNS (53) ? → DSCP = CS3 (0x18, valeur décimale 24)
├── Port dest HTTP (80) ? → DSCP = CS3
├── Port dest HTTPS (443) ? → DSCP = AF41 (0x22, valeur décimale 34)
├── Port dest NTP (123/udp) ? → DSCP = CS3
├── Port dest SMTP (587) ? → DSCP = CS3
│ CLASSE HTB → 1:20 Interactive
│
├── Port dest P2P BitTorrent (6881-6889) ? → DSCP = CS1 (0x08, décimale 8)
├── Port dest IRC (6667) ? → DSCP = CS1
│ CLASSE HTB → 1:30 Bulk
│
└── Défaut (tout le reste) → DSCP = CS1 (Bulk)
CLASSE HTB → 1:30 (défaut)
Note : marquage en postrouting → paquets sortants vers WAN
Le DBA (#26) via CoA met à jour les limites MIR sur le CPE (sens inverse)Configuration
Variables d'environnement
| Variable | Valeur exemple | Description |
|---|---|---|
DSCP_TABLE_NAME | rgz_dscp | Nom de la table nftables DSCP |
QOS_INTERFACE | eth0 | Interface de sortie WAN |
MGMT_PORTS_TCP | 22,1812,1813,3799,9090,3000,9200,5601,9200 | Ports TCP MGMT → CS6 |
MGMT_PORTS_UDP | 161,1812,1813 | Ports UDP MGMT → CS6 |
INTERACTIVE_PORTS_TCP | 80,443,53,587,25 | Ports TCP Interactive → CS3/AF41 |
INTERACTIVE_PORTS_UDP | 53,123 | Ports UDP Interactive → CS3 |
BULK_PORTS_TCP | 6881-6889,6667 | Ports TCP Bulk → CS1 |
Règles nftables DSCP
# config générée par dscp_marking.sh
# Table nftables dédiée au marquage DSCP (séparée du filtrage #7 et #32)
table inet rgz_dscp {
chain postrouting {
# Hook postrouting, priorité mangle (avant nat, après routing)
type filter hook postrouting priority mangle; policy accept;
# === CLASSE MGMT — CS6 (0x30) ===
# Trafic de gestion : SSH, RADIUS, CoA, monitoring
tcp dport { 22, 1812, 1813, 3799, 9090, 3000, 9200, 5601 } \
ip dscp set cs6
udp dport { 161, 1812, 1813, 3799 } \
ip dscp set cs6
# === CLASSE INTERACTIVE — CS3 (0x18) et AF41 (0x22) ===
# DNS, NTP, portail captif
tcp dport { 53, 80, 25, 587 } \
ip dscp set cs3
udp dport { 53, 123 } \
ip dscp set cs3
# HTTPS (portail captif + APIs) → AF41 (mieux classifié que CS3)
tcp dport 443 \
ip dscp set af41
# === CLASSE BULK — CS1 (0x08) ===
# Trafic P2P, IRC, non-prioritaire
tcp dport { 6881-6889, 6667 } \
ip dscp set cs1
# === DÉFAUT — CS1 (Bulk) pour tout trafic non classifié ===
# Garantit qu'aucun paquet n'échappe au marquage QoS
ip dscp set cs1
}
}Script dscp_marking.sh
#!/bin/sh
# scripts/qos/dscp_marking.sh
# Applique les règles nftables de marquage DSCP
# Conforme LL#40 : suppression sélective (jamais nft flush ruleset)
# Conforme LL#41 : table custom rgz_dscp supprimée et recrée proprement
set -e
TABLE="${DSCP_TABLE_NAME:-rgz_dscp}"
echo "[DSCP] Suppression table ${TABLE} si elle existe (LL#40)..."
nft delete table inet "${TABLE}" 2>/dev/null || true
echo "[DSCP] Application règles DSCP marking..."
nft -f /etc/nftables/dscp.conf
echo "[DSCP] Marquage DSCP opérationnel. Table: ${TABLE}"
nft list table inet "${TABLE}"Commandes Utiles
# Vérifier la table DSCP nftables active
docker exec rgz-gateway nft list table inet rgz_dscp
# Vérifier toutes les tables (ne pas confondre avec les tables Docker)
docker exec rgz-gateway nft list ruleset
# Tester le marquage DSCP d'un paquet SSH (devrait apparaître CS6)
docker exec rgz-gateway nft monitor trace # puis initier une connexion SSH
# Supprimer la table DSCP manuellement (conforme LL#40)
docker exec rgz-gateway nft delete table inet rgz_dscp 2>/dev/null || true
# Recharger le marquage DSCP sans redémarrer le conteneur
docker exec rgz-gateway /scripts/qos/dscp_marking.sh
# Vérifier les compteurs de paquets marqués par classe
docker exec rgz-gateway nft list table inet rgz_dscp -a
# Lister toutes les tables nftables (vérifier cohabitation avec Docker)
docker exec rgz-gateway nft list tables
# Voir les règles iptables/nftables générées par Docker (ne pas toucher)
docker exec rgz-gateway iptables -t mangle -L -n -v
# Logs de démarrage DSCP
docker logs rgz-gateway --tail=20 | grep -i "DSCP\|nft\|dscp"Sécurité
| Règle | Application |
|---|---|
| LL#40 | Critique : JAMAIS nft flush ruleset — cela détruirait les chains Docker (iptables-nft). Uniquement nft delete table inet rgz_dscp 2>/dev/null || true |
| LL#41 | La table rgz_dscp persiste sur le host même après docker restart rgz-gateway. L'entrypoint.sh doit systématiquement la supprimer avant recréation |
| LL#33 | restart: unless-stopped dans docker-compose.core.yml — le script DSCP est idempotent |
| LL#27 | Logs Docker rgz-gateway : max-size: 10m, max-file: 3 |
Cohabitation avec les tables Docker
# Tables nftables créées par Docker (NE PAS TOUCHER) :
# - ip nat
# - ip filter
# - ip6 nat
# - ip6 filter
# Tables RGZ custom (gérées par les scripts) :
# - inet rgz_filter (#7 rgz-gateway — deny-all, NAT)
# - inet rgz_dscp (#28 — ce fichier, marquage DSCP)
# - inet rgz_reseller_N (#32 — généré dynamiquement par nftables_generator.py)Ordre de Chargement dans entrypoint.sh
# docker/gateway/entrypoint.sh — ordre obligatoire (LL#41)
# 1. Supprimer toutes les tables RGZ custom au démarrage
nft delete table inet rgz_filter 2>/dev/null || true
nft delete table inet rgz_dscp 2>/dev/null || true
# 2. Recréer dans l'ordre
/scripts/gateway/nftables_setup.sh # #7 rgz_filter (deny-all, NAT, VLAN)
/scripts/qos/dscp_marking.sh # #28 rgz_dscp (ce script)
/scripts/qos/htb_setup.sh # #27 HTB + fq_codel (classif. débit)Implémentation TODO
- [ ] Créer
scripts/qos/dscp_marking.sh(suppression + rechargement conforme LL#40) - [ ] Créer
config/nftables/dscp.conf(règles nftables DSCP statiques) - [ ] Intégrer
dscp_marking.shdansdocker/gateway/entrypoint.sh(avant htb_setup.sh) - [ ] Ajouter
nft delete table inet rgz_dscpdans la section cleanup deentrypoint.sh(LL#41) - [ ] Tester la cohabitation avec les tables Docker (ne pas casser
ip natDocker) - [ ] Valider le marquage CS6 sur paquets SSH avec
nft monitor trace - [ ] Valider le marquage AF41 sur paquets HTTPS (portail captif)
- [ ] Valider le marquage CS1 sur trafic P2P (ports 6881-6889)
- [ ] Valider que le marquage DSCP par défaut (CS1) capture le trafic résiduel
- [ ] Documenter les ports manquants (à ajouter selon usage réel terrain Bénin)
Dernière mise à jour: 2026-02-21