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 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 :

  1. Setup des sockets : socket, bind, listen, getsockopt, setsockopt. Pas d’équivalent io_uring sur Linux 5.15.

  2. Opérations connectées synchrones : connect, accept4, send, recv, sendmsg, recvmsg, shutdown. Toutes ont leur équivalent io_uring exposé au Temps 2b.

  3. Opérations utilitaires : getsockname, getpeername, socketpair.

Caractéristiques transverses de la famille.

  1. CLOEXEC universel. Tous les FDs sockets créés ont CLOEXEC par défaut.

  2. NOSIGNAL par défaut sur send. Les sends synchrones désactivent SIGPIPE par défaut.

  3. Types partagés avec io_uring. SocketAddr, MessageFlags, AcceptFlags, etc. sont les mêmes types qu’au Temps 2b.

  4. 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 un setsockopt(level, optname, buf) générique) : ces wrappers construisent eux-mêmes le timeval/ip_mreq/ipv6_mreq (aucun parsing d’octets externes ⇒ pas de fuzz d’entrée). Le seul décodage est celui du timeval rendu par getsockopt, isolé dans une fonction pure decode_timeout et property-testé (None ⟺ {0,0}). Layout ABI verrouillé par assertions const (size_of). Écarte le setsockopt_bytes gé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égorieFonctions principales
Setupsocket, bind, listen
Optionsgetsockopt/setsockopt typés (~20 options exposées)
Connexion syncconnect, accept4
I/O syncsend, recv, sendmsg, recvmsg, shutdown
Utilitairesgetsockname, 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 :

  • SocketOptionLevel
  • LingerOption
  • 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).