Skip to content

#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

VariableValeur exempleDescription
DSCP_TABLE_NAMErgz_dscpNom de la table nftables DSCP
QOS_INTERFACEeth0Interface de sortie WAN
MGMT_PORTS_TCP22,1812,1813,3799,9090,3000,9200,5601,9200Ports TCP MGMT → CS6
MGMT_PORTS_UDP161,1812,1813Ports UDP MGMT → CS6
INTERACTIVE_PORTS_TCP80,443,53,587,25Ports TCP Interactive → CS3/AF41
INTERACTIVE_PORTS_UDP53,123Ports UDP Interactive → CS3
BULK_PORTS_TCP6881-6889,6667Ports TCP Bulk → CS1

Règles nftables DSCP

nft
# 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

bash
#!/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

bash
# 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ègleApplication
LL#40Critique : JAMAIS nft flush ruleset — cela détruirait les chains Docker (iptables-nft). Uniquement nft delete table inet rgz_dscp 2>/dev/null || true
LL#41La 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#33restart: unless-stopped dans docker-compose.core.yml — le script DSCP est idempotent
LL#27Logs Docker rgz-gateway : max-size: 10m, max-file: 3

Cohabitation avec les tables Docker

bash
# 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

bash
# 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.sh dans docker/gateway/entrypoint.sh (avant htb_setup.sh)
  • [ ] Ajouter nft delete table inet rgz_dscp dans la section cleanup de entrypoint.sh (LL#41)
  • [ ] Tester la cohabitation avec les tables Docker (ne pas casser ip nat Docker)
  • [ ] 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

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone