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 2 — air-mdns (mDNS + DNS-SD pur Rust, patron sans-IO)

Spécification technique — Version 0.1 (proposition). Couche 2 « Frameworks système ».

Cadrage. air-mdns implémente mDNS (RFC 6762) + DNS-SD (RFC 6763) — la découverte/publication de service en réseau local (≈ Bonjour), consommée par air-network::AirBrowser/advertise. Maison, pur Rust, zéro-C, zéro unsafe. Suit le patron sans-IO 9-composants (note réseau §2.2) et réutilise le codec DNS RFC 1035 d’air-socket (déjà fuzzé) plutôt que de le réimplémenter (règle des 80 %, ADR-024).

Position et méthode

mDNS = DNS sur UDP multicast (224.0.0.251:5353 / ff02::fb) sans serveur : chaque hôte répond pour ses propres noms. DNS-SD = une convention d’enregistrements DNS (PTR/SRV/TXT) au-dessus. air-mdns est un protocole filaire → cœur sans-IO + pilote I/O mince (air-socket UDP multicast, air-runtime timers).

Décision — réutilisation du codec DNS. air-socket possède déjà un décodeur DNS RFC 1035 fuzzé (air-socket::dns, fuzz_dns_parse). mDNS partage exactement le format de message (mêmes en-têtes, mêmes noms compressés). air-mdns réutilise ce codec (noms/en-têtes) et n’ajoute que les types d’enregistrement DNS-SD (PTR/SRV/TXT) s’ils manquent. ⇒ Petit additif possible à air-socket : exposer publiquement le codec de noms + les record types SRV/TXT/PTR (cf. « Travail à reprendre »). Zéro duplication de parseur hostile.

Périmètre — moderne, sûr, borné

  • mDNS RFC 6762 : requêtes/réponses multicast, probing + announcing (§8), résolution de conflits (§9), cache avec TTL, known-answer suppression.
  • DNS-SD RFC 6763 : énumération de types (PTR), résolution d’instance (SRV+ TXT), sous-types.
  • Bornes anti-abus (surface hostile réseau local) : noms ≤ 255 octets, compression suivie avec borne (jamais de boucle — comme le résolveur DNS d’air-socket), taille de paquet bornée, cache plafonné (anti-flood).
  • Omis : DNS unicast récursif (c’est air-socket::resolver), DNSSEC (hors mDNS local), mises à jour dynamiques serveur.

Dépendances

  • Tierces externes : ZÉRO. Entièrement maison (mDNS n’a pas de crypto).
  • Crates Air : air-socket (L1, UDP multicast + codec DNS réutilisé), air-runtime (L1, timers de probing/announce/cache), air-base-core (L1, AirError/AirDuration).
  • Zéro unsafe exposé ; parsing défensif (noms bornés, get()).

Additifs air-socket — INSTRUITS (ADR-080). air-mdns requiert de air-socket : (1) multicast UDP typé (join_multicast_*/ set_multicast_*, via l’additif couche 0 couche-0-v1.12 setsockopt structuré pour ip_mreq) ; (2) codec DNS public + SRV/TXT/PTR (réutilise le codec de noms borné/ fuzzé, zéro duplication). Descellement additif couche-1-v1.x.

Anatomie : les 9 composants appliqués à mDNS

#ComposantRéalisation air-mdnsPur ?
1Framer// TRIVIAL : un datagramme UDP = un message DNS complet (pas de framing de flux)✅ (trivial)
2CodecDnsMessageCodec (réutilisé d’air-socket) + ResourceRecordCodec (PTR/SRV/TXT/A/AAAA)
3StateMachineMdnsStateMachineprobe → announce → répondre/interroger (RFC 6762 §8) + résolution de conflit (§9)
4Handshaker// NO HANDSHAKE : mDNS n'a ni auth ni accord de clé.— (absent voulu)
5Flow Controller// NO FLOW CONTROL : datagrammes multicast, pas de fenêtrage.— (absent voulu)
6Multiplexer// NO MULTIPLEXING : un canal multicast partagé, pas de streams.— (absent voulu)
7Timer ManagerMdnsTimers — délais de probing (250 ms ×3), ré-annonce, TTL du cache, intervalles de requête (backoff) ; horloge injectée✅ (horloge injectée)
8Session ContextRecordCache (réponses connues + TTL) + PublishedServices (nos enregistrements)
9Extension hooks// MINIMAL : sous-types DNS-SD, options futures (unicast-response bit).✅ (minimal)

4 composants absents/triviaux marqués (Framer trivial ; Handshaker/Flow/Mux absents par conception) — l’absence est intentionnelle et visible (note §2.2).

Inventaire des objets — ~14 objets

A. Surface publique — ~8

#Objet≈ AppleRôle
1MdnsBrowserNWBrowserdécouvre les instances d’un type de service
2MdnsResponderNWListener.servicepublie/annonce nos services
3ServiceRegistration (+ Builder)ce qu’on publie (instance, type, port, TXT)
4DiscoveredServiceNWBrowser.Resultinstance découverte (nom, endpoint, TXT)
5ServiceType(_http._tcp)newtype type DNS-SD validé
6ServiceInstanceNamenewtype nom d’instance (UTF-8, ≤ 63 o par label)
7TxtRecordclés/valeurs TXT (bytes, pas UTF-8 présumé)
8MdnsErrorenum (ADR-019)

B. Cœur sans-IO — ~6

DnsMessageCodec (réutilisé), ResourceRecordCodec, MdnsStateMachine, MdnsTimers, RecordCache, PublishedServices.

Codecs (bytes ↔ enregistrements)

#![allow(unused)]
fn main() {
pub enum ResourceRecord<'a> {
    Ptr  { name: DnsName<'a>, target: DnsName<'a> },            // _http._tcp.local → Instance._http._tcp.local
    Srv  { priority: u16, weight: u16, port: u16, target: DnsName<'a> },
    Txt  { pairs: TxtPairs<'a> },                               // clés=valeurs (bytes)
    A    { addr: Ipv4Addr }, Aaaa { addr: Ipv6Addr },
    // OMIS : types DNS obsolètes / non pertinents en mDNS local.
}
// ResourceRecordCodec::decode borne les longueurs ; noms via le codec d'air-socket
// (compression suivie AVEC borne — jamais de boucle infinie, comme le résolveur DNS).
}

Machine à états — multi-thread (mono-propriétaire, sans-IO)

#![allow(unused)]
fn main() {
pub enum MdnsEvent {
    ServiceAdded(DiscoveredService), ServiceRemoved(ServiceInstanceName),
    ServiceUpdated(DiscoveredService), WantsTransmit, NameConflict(ServiceInstanceName),
}

impl MdnsBrowser {
    pub fn new(service_type: ServiceType) -> Result<Self, MdnsError>;
    /// SANS-IO : ingère un datagramme multicast reçu → événements de découverte.
    pub fn recv(&mut self, now: AirInstant, datagram: &[u8]) -> Result<Vec<MdnsEvent>, MdnsError>;
    /// SANS-IO : prochain datagramme à émettre (requête, avec known-answer suppression).
    pub fn poll_transmit(&mut self, now: AirInstant) -> Option<Vec<u8>>;
    pub fn poll_timeout(&self) -> Option<AirInstant>;
    pub fn handle_timeout(&mut self, now: AirInstant) -> Result<Vec<MdnsEvent>, MdnsError>;
}
// MdnsResponder : symétrique côté publication (probe→announce, répond aux requêtes,
//   défend ses noms, résout les conflits par renommage RFC 6762 §9).
}

Thread model : MdnsBrowser/MdnsResponder Send/!Sync (mono-propriétaires) ; le pilote I/O (io/) joint le groupe multicast et pompe le socket. Async 1ʳᵉ classe (air-runtime), façade bloquante de confort.

Sécurité

  • Surface hostile = réseau local (n’importe qui sur le lien peut émettre). Défenses : parsing borné (réutilise le durcissement DNS d’air-socket), cache plafonné (anti-flood), rate-limiting des réponses (RFC 6762 §6), jamais d’exécution ni d’allocation non bornée sur un paquet reçu.
  • Pas de confiance implicite : une instance découverte est une donnée, pas une autorité ; toute connexion vers un endpoint découvert passe par air-network (TLS si la pile le déclare). mDNS ne fournit aucune authentification (par conception du protocole) — documenté clairement.

Stratégie de tests

  • Couverture couche 2 > 90 %.
  • Codec fuzzé (réutilise/étend fuzz_dns_parse d’air-socket) : noms compressés hostiles, records tronqués → zéro panic, borne de compression respectée.
  • StateMachine property/model-based : probe/announce/conflit (deux répondeurs, même nom → renommage) ; known-answer suppression.
  • Timers virtual clock : probing 3×250 ms, ré-annonce, expiration TTL déterministe.
  • Interop : vs avahi/mDNSResponder (Apple) sur boucle multicast locale.
  • Pilote I/O : multicast loopback réel.

Récapitulatif & décisions

FamilleObjets
A. Surface publique~8
B. Cœur sans-IO~6
Total~14 (4 composants absents/triviaux marqués)
  1. mDNS + DNS-SD moderne, borné (RFC 6762/6763) ; unicast récursif reste air-socket::resolver.
  2. Réutilise le codec DNS fuzzé d’air-socket — zéro duplication de parseur.
  3. Zéro-C, zéro unsafe, zéro crate tierce.
  4. Sécurité = parsing borné + cache plafonné + rate-limit ; mDNS n’authentifie rien (par conception) → la confiance vient de la couche au-dessus (air-network/TLS).
  5. Async 1ʳᵉ classe (ADR-038), responder/browser Send/!Sync.

Travail à reprendre

  • Additifs air-socketinstruits (ADR-080) : codec DNS public SRV/TXT/PTR + multicast UDP typé (couche-0-v1.12 setsockopt structuré). Prérequis d’implémentation.
  • Consommateur : air-network::AirBrowser/advertise (câble mDNS).
  • Sous-types DNS-SD, hot-reload des services publiés ; ABI C différée.

Licence du document : MPL 2.0 Statut : Spécification air-mdns (couche 2) v0.1 — mDNS + DNS-SD pur Rust, patron sans-IO, réutilise le codec DNS d’air-socket. Implémentation à suivre.