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-mdnsimplémente mDNS (RFC 6762) + DNS-SD (RFC 6763) — la découverte/publication de service en réseau local (≈ Bonjour), consommée parair-network::AirBrowser/advertise. Maison, pur Rust, zéro-C, zérounsafe. 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-socketpossè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-mdnsré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
unsafeexposé ; parsing défensif (noms bornés,get()).
Additifs
air-socket— INSTRUITS (ADR-080).air-mdnsrequiert deair-socket: (1) multicast UDP typé (join_multicast_*/set_multicast_*, via l’additif couche 0couche-0-v1.12setsockoptstructuré pourip_mreq) ; (2) codec DNS public + SRV/TXT/PTR (réutilise le codec de noms borné/ fuzzé, zéro duplication). Descellement additifcouche-1-v1.x.
Anatomie : les 9 composants appliqués à mDNS
| # | Composant | Réalisation air-mdns | Pur ? |
|---|---|---|---|
| 1 | Framer | // TRIVIAL : un datagramme UDP = un message DNS complet (pas de framing de flux) | ✅ (trivial) |
| 2 | Codec | DnsMessageCodec (réutilisé d’air-socket) + ResourceRecordCodec (PTR/SRV/TXT/A/AAAA) | ✅ |
| 3 | StateMachine | MdnsStateMachine — probe → announce → répondre/interroger (RFC 6762 §8) + résolution de conflit (§9) | ✅ |
| 4 | Handshaker | // NO HANDSHAKE : mDNS n'a ni auth ni accord de clé. | — (absent voulu) |
| 5 | Flow Controller | // NO FLOW CONTROL : datagrammes multicast, pas de fenêtrage. | — (absent voulu) |
| 6 | Multiplexer | // NO MULTIPLEXING : un canal multicast partagé, pas de streams. | — (absent voulu) |
| 7 | Timer Manager | MdnsTimers — délais de probing (250 ms ×3), ré-annonce, TTL du cache, intervalles de requête (backoff) ; horloge injectée | ✅ (horloge injectée) |
| 8 | Session Context | RecordCache (réponses connues + TTL) + PublishedServices (nos enregistrements) | ✅ |
| 9 | Extension 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 | ≈ Apple | Rôle |
|---|---|---|---|
| 1 | MdnsBrowser | NWBrowser | découvre les instances d’un type de service |
| 2 | MdnsResponder | NWListener.service | publie/annonce nos services |
| 3 | ServiceRegistration (+ Builder) | — | ce qu’on publie (instance, type, port, TXT) |
| 4 | DiscoveredService | NWBrowser.Result | instance découverte (nom, endpoint, TXT) |
| 5 | ServiceType | (_http._tcp) | newtype type DNS-SD validé |
| 6 | ServiceInstanceName | — | newtype nom d’instance (UTF-8, ≤ 63 o par label) |
| 7 | TxtRecord | — | clés/valeurs TXT (bytes, pas UTF-8 présumé) |
| 8 | MdnsError | — | enum (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_parsed’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
| Famille | Objets |
|---|---|
| A. Surface publique | ~8 |
| B. Cœur sans-IO | ~6 |
| Total | ~14 (4 composants absents/triviaux marqués) |
- mDNS + DNS-SD moderne, borné (RFC 6762/6763) ; unicast récursif reste
air-socket::resolver. - Réutilise le codec DNS fuzzé d’
air-socket— zéro duplication de parseur. - Zéro-C, zéro
unsafe, zéro crate tierce. - 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). - Async 1ʳᵉ classe (ADR-038), responder/browser
Send/!Sync.
Travail à reprendre
- Additifs
air-socket— instruits (ADR-080) : codec DNS public SRV/TXT/PTR + multicast UDP typé (couche-0-v1.12setsockoptstructuré). 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.