Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec couche 1 — air-netlink (transport netlink générique, patron sans-IO)

Spécification technique — Version 0.1 (proposition). Couche 1 « Primitives système ». Tranchée par ADR-079.

Cadrage. air-netlink est un transport netlink générique de couche 1 (pair d’air-socket) : message framing, codec d’attributs TLV (rtattr/nlattr), corrélation requête/réponse/dump. Maison, pur Rust, zéro-C, zéro unsafe. Il ne connaît aucune famille en propre : rtnetlink (routes/adresses/liens), nl80211 (wifi), nftables, genetlink se posent au-dessus de lui. Suit le patron sans-IO 9-composants (note réseau §2.2).

Position et méthode

Netlink est un protocole filaire kernel↔userspace : socket(AF_NETLINK, …) + messages nlmsghdr porteurs d’attributs TLV. C’est un plan de contrôle (on interroge/modifie l’état du noyau : routes, adresses, liens…), distinct du transport de données d’air-socket. Comme air-socket::dns (sans-IO, fuzzé), le cœur de air-netlink est pur (parse/sérialise/corrèle), le pilote I/O (le socket réel) étant mince.

Premier consommateur : air-network::AirNetworkPathMonitor (L2) — énumérer et surveiller interfaces/adresses/routes via rtnetlink. Autres consommateurs prévus : daemons WireGuard/DHCP (L5), air-config (projection de routes), wifi (nl80211).

Dépendances

  • Tierces externes : ZÉRO. Entièrement maison (netlink n’a pas de crypto).
  • Couche 0 (après descellement couche-0-v1.11, ADR-079) :
    • SocketDomain::Netlink (AF_NETLINK = 16) — additif à l’enum typé ;
    • SocketAddrNetlink (sockaddr_nl, 12 o) — additif pour bind ;
    • réutilise tel quel socket/bind/sendmsg/recvmsg génériques déjà présents (air-sys-syscall::net) — aucun nouveau syscall.
  • air-base-core (L1)AirError/AirResult (ADR-019).
  • Zéro unsafe exposé ; parsing TLV borné (get(), longueurs validées, alignement NLMSG_ALIGN/RTA_ALIGN vérifié — jamais de lecture hors-borne ni de boucle non bornée).

air-device inchangé (ADR-079) : son socket NETLINK_KOBJECT_UEVENT reste spécifique. Migration future sur air-netlink possible, non requise.

#ComposantRéalisation air-netlinkPur ?
1FramerMessageFramer — délimite le flux en messages nlmsghdr (len/type/flags/seq/pid), gère le multipart (NLM_F_MULTINLMSG_DONE) et l’alignement
2CodecMessageCodec (en-tête) + AttributeCodec (TLV rtattr/nlattr)la surface hostile, décodage borné + imbrication (NLA_F_NESTED)
3StateMachineRequestStateMachine — corrélation par numéro de séquence, sémantique dump (NLM_F_DUMP → multipart), ACK/erreur (NLMSG_ERROR, code errno)
4Handshaker// NO HANDSHAKE : netlink n'a ni auth ni accord de clé.— (absent voulu)
5Flow Controller// NO FLOW CONTROL : socket noyau. ENOBUFS (buffer plein) = signalé à l'appelant (resync dump), pas de fenêtrage.— (absent voulu)
6Multiplexer// MINIMAL : abonnement à des groupes multicast (RTNLGRP_*) sur le socket ; pas de streams applicatifs.✅ (minimal)
7Timer Manager// MINIMAL : timeout de requête optionnel (horloge injectée). Pas de retransmission (kernel fiable en unicast).✅ (horloge injectée)
8Session ContextNetlinkSession — compteur de séquence, nl_pid du socket, groupes abonnés, requêtes en attente
9Extension hooksNetlinkFamily + modules de famille typés (rtnetlink, …) construits au-dessus du cœur générique

3 composants absents/minimaux marqués (Handshaker/Flow absents ; Mux/Timer minimaux) — l’absence est intentionnelle et visible (note §2.2).

Inventaire des objets — ~15 objets

A. Surface publique générique — ~9

#ObjetRôle
1NetlinkSocketRAII — possède le fd AF_NETLINK ; open/bind/send/recv (pilote I/O)
2NetlinkFamilyenum/protocole (Route, Generic, Netfilter…) au socket()
3NetlinkGroupgroupe multicast à joindre (abonnement aux notifications)
4NetlinkMessage<'a>en-tête + payload (vue empruntée, zéro-copie)
5NetlinkHeadernlmsghdr typé (type, flags, seq, pid)
6NetlinkAttribute<'a>attribut TLV décodé (type + valeur bornée)
7NetlinkRequest (+ Builder)construit un message (flags REQUEST/ACK/DUMP)
8DumpIter<'a>itérateur borné sur une réponse multipart (dump)
9NetlinkErrorenum (ADR-019) — mappe NLMSG_ERROR (errno) + erreurs de décodage

B. Cœur sans-IO — ~4

MessageFramer, MessageCodec + AttributeCodec (TLV), RequestStateMachine, NetlinkSession.

RtnetlinkClient (helpers typés : list_links/list_addresses/list_routes en dump, subscribe(link|addr|route)) + RtLink/RtAddress/RtRoute (vues typées des attributs). (Les autres familles — nl80211, nftables — viendront en modules séparés.)

Codecs (bytes ↔ messages/attributs)

#![allow(unused)]
fn main() {
/// Lecteur borné + alignement netlink (NLMSG_ALIGN/RTA_ALIGN) — jamais d'indexation
/// paniquante, jamais de boucle sur une longueur mensongère (Principe 3).
pub struct NetlinkReader<'a> { /* … */ }
impl<'a> NetlinkReader<'a> {
    pub fn header(&mut self) -> Result<NetlinkHeader, DecodeError>;   // 16 o, len validée
    pub fn attributes(&mut self) -> AttributeIter<'a>;               // TLV bornés, alignés
    pub fn nested(&self, attr: &NetlinkAttribute<'a>) -> AttributeIter<'a>; // NLA_F_NESTED
}

/// Attribut TLV : [ len(u16) ][ type(u16) ][ value… aligné 4 ].
pub struct NetlinkAttribute<'a> { pub kind: u16, pub value: &'a [u8] }
// AttributeCodec::decode : borne value par len ; refuse len < 4 ou débordant le message.
}

Décision — cœur générique, familles au-dessus. Le codec ne connaît pas la sémantique des attributs (RTA_DST, IFLA_IFNAME…) : il rend des (type, bytes) bornés. Les modules de famille (rtnetlink) interprètent. ⇒ la surface hostile (décodage TLV) est une seule fois, fuzzée une seule fois, réutilisée partout.

Pilote I/O & machine à états (multi-thread, mono-propriétaire)

#![allow(unused)]
fn main() {
impl NetlinkSocket {
    pub fn open(family: NetlinkFamily) -> AirResult<Self>;          // socket(AF_NETLINK,…) RAII
    pub fn bind(&self, groups: &[NetlinkGroup]) -> AirResult<()>;   // sockaddr_nl
    pub fn send(&self, request: &NetlinkRequest) -> AirResult<u32>; // rend le seq
    pub fn recv<'b>(&self, buf: &'b mut [u8]) -> AirResult<NetlinkReader<'b>>;
}
}
  • Cœur sans-IO testable sans socket : on lui jette des octets → messages/ attributs/erreurs (fuzz). Le pilote (NetlinkSocket) est le seul contact couche 0.
  • Thread model : NetlinkSocket Send/!Sync (un fd, un propriétaire, zéro verrou) — comme les objets fd d’air-socket. Plusieurs sockets = plusieurs threads, indépendants.

Sécurité

  • Surface hostile = messages du noyau (attributs TLV en boucle). Défenses : toutes les longueurs validées (len ≥ 4, len ≤ reste du message), alignement vérifié, imbrication bornée (profondeur max sur NLA_F_NESTED, anti-explosion), jamais d’allocation dictée par une longueur non vérifiée. Fuzz obligatoire sur AttributeCodec/MessageFramer.
  • Authentification kernel : ne traiter que les messages dont nl_pid == 0 (émanant du noyau) pour les notifications — même discipline que le socket uevent d’air-device.
  • Privilèges : les opérations d’écriture (ajout de route/adresse) exigent CAP_NET_ADMIN — l’appelant (daemon) porte le privilège ; air-netlink n’élève rien.

Stratégie de tests

  • Couverture 100 % (couche 1) lignes + branches.
  • Cœur sans-IO : unitaires + property-based (round-trip decode∘encode = id sur messages valides) + fuzz (fuzz_netlink_attributes, fuzz_netlink_message : octets hostiles → zéro panic, borne d’imbrication respectée).
  • RequestStateMachine : dump multipart (→ NLMSG_DONE), NLMSG_ERROR (errno mappé), corrélation de séquence.
  • Pilote I/O : test réel AF_NETLINK/NETLINK_ROUTE sur la machine (dump des liens → au moins lo présent), abonnement à RTNLGRP_LINK (interface synthétique up/down si root, sinon test décodeur).
  • Note fuzz : la surface de parsing d’octets externes est le cœur → fuzz couvert.

Récapitulatif & décisions

FamilleObjets
A. Surface publique générique~9
B. Cœur sans-IO~4
C. Module rtnetlink~2 + valeurs
Total~15 (3 composants absents/minimaux marqués)
  1. Transport netlink générique, couche 1 (ADR-079, option B) — pair d’air-socket, réutilisable par tous les consommateurs (path monitor, WireGuard, DHCP, config, wifi).
  2. Cœur générique, familles au-dessus — la surface hostile TLV décodée une fois, fuzzée une fois.
  3. Zéro-C, zéro unsafe, zéro tierce ; RAII pour le fd ; Send/!Sync.
  4. Additif couche 0 modéré (AF_NETLINK + sockaddr_nl) — syscalls génériques déjà présents, aucun nouveau syscall.
  5. air-device inchangé ; migration future possible, non requise.

Travail à reprendre

  • Additif couche 0 couche-0-v1.11 : SocketDomain::Netlink + SocketAddrNetlink (RFC de descellement, ADR-079) — prérequis d’implémentation.
  • Consommateur : câbler air-network::AirNetworkPathMonitor sur RtnetlinkClient.
  • Familles supplémentaires : nl80211 (état/signal wifi → enrichit AirInterfaceType::WiFi), nftables (futur air-firewall), genetlink.
  • Migration du socket uevent d’air-device sur air-netlink (non bloquante).
  • ABI C différée.

Licence du document : MPL 2.0 Statut : Spécification air-netlink (couche 1) v0.1 — transport netlink générique sans-IO, tranché par ADR-079 (option B, couche 1). Additif couche 0 AF_NETLINK requis. Implémentation à suivre.