Spec couche 0 — Famille net
Spécification technique — Version 1.0
Vue d’ensemble de la famille
Le module air-sys-syscall::net expose les primitives socket synchrones. Conformément à la Décision 2 de l’ADR-022, les opérations existent en parallèle de leurs équivalents io_uring, avec types et conventions partagés.
Périmètre de la famille.
Trois catégories d’opérations :
-
Setup des sockets :
socket,bind,listen,getsockopt,setsockopt. Pas d’équivalent io_uring sur Linux 5.15. -
Opérations connectées synchrones :
connect,accept4,send,recv,sendmsg,recvmsg,shutdown. Toutes ont leur équivalent io_uring exposé au Temps 2b. -
Opérations utilitaires :
getsockname,getpeername,socketpair.
Caractéristiques transverses de la famille.
-
CLOEXEC universel. Tous les FDs sockets créés ont CLOEXEC par défaut.
-
NOSIGNAL par défaut sur send. Les sends synchrones désactivent SIGPIPE par défaut.
-
Types partagés avec io_uring.
SocketAddr,MessageFlags,AcceptFlags, etc. sont les mêmes types qu’au Temps 2b. -
Validation des familles. Les fonctions valident la cohérence entre la famille du socket et l’adresse utilisée.
Sous-section 1 : Setup des sockets
socket
#![allow(unused)]
fn main() {
pub fn socket(
domain: SocketDomain,
ty: SocketType,
protocol: i32,
) -> Result<OwnedFd, Errno>;
}
Le wrapper Air utilise SOCK_CLOEXEC systématiquement.
bind
#![allow(unused)]
fn main() {
pub fn bind(sock: BorrowedFd<'_>, address: &SocketAddr) -> Result<(), Errno>;
}
Pour les Unix sockets path-based, le pattern correct est de tenter unlinkat avant bind, en ignorant ENOENT. Un helper Air en couche 1 (bind_unix_socket_replacing) facilitera ce pattern.
listen
#![allow(unused)]
fn main() {
pub fn listen(sock: BorrowedFd<'_>, backlog: u32) -> Result<(), Errno>;
}
getsockopt et setsockopt
API typée par option (cf. convention 3 de l’ADR-021). Plutôt qu’un wrapper générique, Air expose une fonction par option courante :
#![allow(unused)]
fn main() {
pub fn get_so_keepalive(sock: BorrowedFd<'_>) -> Result<bool, Errno>;
pub fn set_so_keepalive(sock: BorrowedFd<'_>, enable: bool) -> Result<(), Errno>;
pub fn get_so_reuseaddr(sock: BorrowedFd<'_>) -> Result<bool, Errno>;
pub fn set_so_reuseaddr(sock: BorrowedFd<'_>, enable: bool) -> Result<(), Errno>;
pub fn get_so_reuseport(sock: BorrowedFd<'_>) -> Result<bool, Errno>;
pub fn set_so_reuseport(sock: BorrowedFd<'_>, enable: bool) -> Result<(), Errno>;
pub fn get_tcp_nodelay(sock: BorrowedFd<'_>) -> Result<bool, Errno>;
pub fn set_tcp_nodelay(sock: BorrowedFd<'_>, enable: bool) -> Result<(), Errno>;
pub fn get_so_rcvbuf(sock: BorrowedFd<'_>) -> Result<u32, Errno>;
pub fn set_so_rcvbuf(sock: BorrowedFd<'_>, size: u32) -> Result<(), Errno>;
pub fn get_so_sndbuf(sock: BorrowedFd<'_>) -> Result<u32, Errno>;
pub fn set_so_sndbuf(sock: BorrowedFd<'_>, size: u32) -> Result<(), Errno>;
pub fn get_so_error(sock: BorrowedFd<'_>) -> Result<Option<Errno>, Errno>;
pub fn get_so_peercred(sock: BorrowedFd<'_>) -> Result<UnixCredentials, Errno>;
pub fn get_so_linger(sock: BorrowedFd<'_>) -> Result<LingerOption, Errno>;
pub fn set_so_linger(sock: BorrowedFd<'_>, linger: LingerOption) -> Result<(), Errno>;
pub enum LingerOption {
Disabled,
Enabled(Duration),
}
pub fn set_so_bindtodevice(sock: BorrowedFd<'_>, ifname: &CStr) -> Result<(), Errno>;
pub fn get_so_bindtodevice(sock: BorrowedFd<'_>) -> Result<CString, Errno>;
// TCP-specific
pub fn set_tcp_keepidle(sock: BorrowedFd<'_>, seconds: u32) -> Result<(), Errno>;
pub fn set_tcp_keepintvl(sock: BorrowedFd<'_>, seconds: u32) -> Result<(), Errno>;
pub fn set_tcp_keepcnt(sock: BorrowedFd<'_>, count: u32) -> Result<(), Errno>;
// IP-specific
pub fn set_ip_ttl(sock: BorrowedFd<'_>, ttl: u8) -> Result<(), Errno>;
pub fn set_ipv6_v6only(sock: BorrowedFd<'_>, v6only: bool) -> Result<(), Errno>;
// Options à STRUCTURE — descellement additif `couche-0-v1.13` (arbitrage BDFL).
// Délais (`SO_RCVTIMEO`/`SO_SNDTIMEO`, `struct timeval` 16 o sur LP64) :
// `None` ↔ timeval nul = « pas de délai » (sémantique kernel).
pub fn set_receive_timeout(sock: BorrowedFd<'_>, timeout: Option<Timeval>) -> Result<(), Errno>;
pub fn receive_timeout(sock: BorrowedFd<'_>) -> Result<Option<Timeval>, Errno>;
pub fn set_send_timeout(sock: BorrowedFd<'_>, timeout: Option<Timeval>) -> Result<(), Errno>;
pub fn send_timeout(sock: BorrowedFd<'_>) -> Result<Option<Timeval>, Errno>;
// Appartenance multicast (`IP_ADD/DROP_MEMBERSHIP` `ip_mreq` 8 o ;
// `IPV6_ADD/DROP_MEMBERSHIP` `ipv6_mreq` 20 o). Types ABI `#[repr(C)]` fidèles.
pub fn join_multicast_v4(sock: BorrowedFd<'_>, membership: &IpMembershipV4) -> Result<(), Errno>;
pub fn leave_multicast_v4(sock: BorrowedFd<'_>, membership: &IpMembershipV4) -> Result<(), Errno>;
pub fn join_multicast_v6(sock: BorrowedFd<'_>, membership: &IpMembershipV6) -> Result<(), Errno>;
pub fn leave_multicast_v6(sock: BorrowedFd<'_>, membership: &IpMembershipV6) -> Result<(), Errno>;
// struct ip_mreq (8 o) — deux in_addr (ordre réseau) ; struct ipv6_mreq (20 o).
pub struct IpMembershipV4 { pub multiaddr: [u8; 4], pub interface: [u8; 4] }
pub struct IpMembershipV6 { pub multiaddr: [u8; 16], pub interface: u32 /* ifindex */ }
}
Descellement
couche-0-v1.13(options à structure). ADR-021 conv. 3 impose des fonctions dédiées typées (jamais unsetsockopt(level, optname, buf)générique) : ces wrappers construisent eux-mêmes letimeval/ip_mreq/ipv6_mreq(aucun parsing d’octets externes ⇒ pas de fuzz d’entrée). Le seul décodage est celui dutimevalrendu pargetsockopt, isolé dans une fonction puredecode_timeoutet property-testé (None ⟺ {0,0}). Layout ABI verrouillé par assertions const (size_of). Écarte lesetsockopt_bytesgénérique envisagé par ADR-080 (v1.12, jamais implémenté).
Si un développeur a besoin d’une option non couverte, deux voies : RFC pour ajouter la fonction wrapper, ou API raw dans air-sys-syscall::net::raw.
Sous-section 2 : Opérations synchrones
connect
#![allow(unused)]
fn main() {
pub fn connect(sock: BorrowedFd<'_>, address: &SocketAddr) -> Result<(), Errno>;
}
Pour les sockets non-blocking, retourne EINPROGRESS immédiatement.
accept4
#![allow(unused)]
fn main() {
pub fn accept4(
listener: BorrowedFd<'_>,
flags: AcceptFlags,
) -> Result<AcceptResult, Errno>;
}
AcceptFlags::CLOEXEC activé par défaut. accept4 préféré à accept parce qu’il permet de passer CLOEXEC atomiquement.
send, recv, sendmsg, recvmsg, shutdown
#![allow(unused)]
fn main() {
pub fn send(
sock: BorrowedFd<'_>,
buf: &[u8],
flags: MessageFlags,
) -> Result<usize, Errno>;
pub fn recv(
sock: BorrowedFd<'_>,
buf: &mut [u8],
flags: MessageFlags,
) -> Result<usize, Errno>;
pub fn sendmsg(
sock: BorrowedFd<'_>,
request: &SendMessageRequest,
) -> Result<SendMessageResult, Errno>;
pub fn recvmsg(
sock: BorrowedFd<'_>,
request: &mut ReceiveMessageRequest,
) -> Result<ReceiveMessageResult, Errno>;
pub fn shutdown(
sock: BorrowedFd<'_>,
mode: ShutdownMode,
) -> Result<(), Errno>;
}
Différence sémantique avec io_uring.
Les versions synchrones prennent des références (&[u8], &mut [u8]) plutôt que des valeurs possédées. Pas besoin de transfert d’ownership puisque l’opération est synchrone.
NOSIGNAL par défaut. Comme en io_uring, MessageFlags::NOSIGNAL est activé par défaut dans le wrapper send synchrone.
FD passing via sendmsg/recvmsg. Identique au Temps 2b : FDs reçus auto-encapsulés en OwnedFd, ancillaires typés.
Sous-section 3 : Opérations utilitaires
getsockname et getpeername
#![allow(unused)]
fn main() {
pub fn getsockname(sock: BorrowedFd<'_>) -> Result<SocketAddr, Errno>;
pub fn getpeername(sock: BorrowedFd<'_>) -> Result<SocketAddr, Errno>;
}
socketpair
#![allow(unused)]
fn main() {
pub fn socketpair(
domain: SocketDomain,
ty: SocketType,
protocol: i32,
) -> Result<(OwnedFd, OwnedFd), Errno>;
}
Seuls les Unix sockets supportent socketpair sur Linux. CLOEXEC activé par défaut sur les deux FDs.
Récapitulatif de la famille net
Fonctions exposées :
| Catégorie | Fonctions principales |
|---|---|
| Setup | socket, bind, listen |
| Options | getsockopt/setsockopt typés (~20 options exposées) |
| Connexion sync | connect, accept4 |
| I/O sync | send, recv, sendmsg, recvmsg, shutdown |
| Utilitaires | getsockname, getpeername, socketpair |
Total : ~30 fonctions publiques principales.
Syscalls non-wrappés (listés dans UNSUPPORTED.md) :
accept(sans le 4) : remplacé par accept4.sendto,recvfrom: couverts via sendmsg/recvmsg.sendmmsg,recvmmsg: opérations batched, à évaluer pour ajout futur.
Types ajoutés à air-sys-types
Principalement réutilisation des types déjà introduits au Temps 2b (SocketAddr, MessageFlags, etc.). Quelques ajouts :
SocketOptionLevelLingerOption- Constantes diverses pour les options
Soit ~3 types supplémentaires.
Décisions de fond émergées dans la famille net
1. API typée par option pour setsockopt/getsockopt.
Application stricte de la convention 3 de l’ADR-021. Le coût en volume de code est compensé par la sûreté et la documentation par option.
2. Pas de wrapper accept non-accept4.
accept4 est strictement supérieur.
3. NOSIGNAL par défaut.
Cohérent avec io_uring. SIGPIPE est presque toujours indésirable.
4. socketpair retourne un tuple.
Choix idiomatique Rust.
Licence du document : MPL 2.0
Statut : Spécification technique du module air-sys-syscall::net (couche 0).