Skip to main content

ax_net/
lib.rs

1//! Unified network stack for TGOSKits systems.
2//!
3//! ax-net provides the socket-facing API used by kernels and syscall layers,
4//! while delegating TCP/IP protocol mechanics to smoltcp. The crate exposes
5//! TCP, UDP, raw IPv4/IPv6 sockets, Unix domain sockets, optional vsock, DNS,
6//! DHCP helpers, readiness polling, and interface/control-plane queries.
7//!
8//! # Architecture
9//!
10//! The stack intentionally uses one smoltcp `Interface` and one global
11//! `SocketSet`. Multiple physical or virtual devices are aggregated below that
12//! protocol core by `router::Router`, which acts as a multi-device smoltcp
13//! `Device`. This keeps socket ownership, port tables, listen queues, and
14//! routing decisions centralized instead of duplicating socket state per NIC.
15//!
16//! # Execution Model
17//!
18//! A unique CPU-pinned protocol executor owns every smoltcp poll. Socket methods
19//! publish generations with `request_poll()` and then rely on poll/waker
20//! readiness; they never synchronously become a second protocol owner. Separate
21//! CPU-pinned queue executors own hard-IRQ continuation, DMA reclaim/refill, and
22//! bounded queue polling. Preallocated SPSC rings transfer move-only frame tokens
23//! between those two ownership domains.
24//!
25//! # Main Modules
26//!
27//! - `service`: owns the smoltcp interface and control plane.
28//! - `poll_runtime`: owns generation-based protocol scheduling.
29//! - `queue_runtime`: owns IRQ affinity domains and queue executors.
30//! - `router`: aggregates protocol ports, route lookup, and loopback.
31//! - `socket`, `tcp`, `udp`, `raw`: POSIX-like IP socket surface.
32//! - `listen_table`, `orphan`, `wrapper`: side tables around smoltcp sockets.
33//! - `unix` and `vsock`: local transports outside the smoltcp IP path.
34
35#![no_std]
36
37#[macro_use]
38extern crate log;
39extern crate alloc;
40#[cfg(all(test, not(target_os = "none")))]
41extern crate ax_runtime as _;
42#[cfg(test)]
43extern crate std;
44
45mod addr;
46mod config;
47mod consts;
48mod device;
49mod dhcp_server;
50mod error;
51mod general;
52mod ip_tos;
53mod listen_table;
54/// Socket option types and the [`Configurable`](options::Configurable) trait.
55pub mod options;
56mod orphan;
57mod poll_runtime;
58mod queue_runtime;
59/// Raw socket implementation.
60pub mod raw;
61mod readiness;
62mod router;
63mod rx_meta;
64mod service;
65mod socket;
66pub(crate) mod state;
67/// TCP socket implementation.
68pub mod tcp;
69/// UDP socket implementation.
70pub mod udp;
71/// Unix domain socket implementation.
72pub mod unix;
73/// Vsock socket implementation.
74#[cfg(feature = "vsock")]
75pub mod vsock;
76mod wrapper;
77
78use alloc::{
79    borrow::ToOwned, boxed::Box, format, string::String, sync::Arc, task::Wake, vec, vec::Vec,
80};
81use core::{
82    net::{IpAddr, Ipv4Addr},
83    sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering},
84    time::Duration,
85};
86
87use ax_lazyinit::{LazyLock, OnceLock};
88use ax_sync::Mutex;
89use axpoll::IoEvents;
90use axpoll_set::PollSet;
91pub use error::{NetError, NetResult};
92use rand_chacha::ChaCha20Rng;
93use rand_core::{RngCore, SeedableRng};
94pub use rd_net::{WifiLinkPolicy, WifiOperation, WifiTransaction, Wpa2Pmk};
95use smoltcp::{
96    socket::dns::{self, GetQueryResultError, StartQueryError},
97    wire::{DnsQueryType, EthernetAddress, IpAddress, Ipv4Address, Ipv4Cidr},
98};
99
100#[cfg(feature = "vsock")]
101pub use self::device::{VsockDevice, VsockDeviceInput, VsockDeviceList, VsockRuntimeError};
102use self::{
103    addr::mask_from_prefix,
104    device::{EthernetDevice, LoopbackDevice},
105    listen_table::ListenTable,
106    poll_runtime::{ProtocolPollBudget, ProtocolPollRuntime},
107    router::{RouteTable, Router, Rule, SharedRouteTable},
108    service::{NetControl, NetInterface, Service},
109    wrapper::SocketSetWrapper,
110};
111pub use self::{
112    config::{
113        DeviceBinding, InterfaceConfig, InterfaceFlags, InterfaceId, InterfaceInfo, InterfaceKind,
114        InterfaceMatcher, Ipv4InterfaceConfig, NetworkConfig, RouteInfo, StaticIpConfig,
115    },
116    device::{ArpEntry, EthernetFramePort, EthernetFramePortList, NetDeviceError, NetDeviceResult},
117    queue_runtime::{
118        NetQueueStats, NetworkDeviceInput, NetworkQueueRuntime, NetworkRuntimeBuilder,
119        NetworkRuntimeError, PinnedNetIrqAction, PinnedNetIrqError, PinnedNetIrqOutcome,
120        PinnedNetIrqRegistrar, PinnedNetIrqRegistration, ResolvedNetIrqSource, TxQueueDiscipline,
121    },
122    readiness::poll_socket_io,
123    router::NetDevStats,
124    socket::{
125        CMsgData, ConnectStatus, IpCmsg, RecvFlags, RecvOptions, SendFlags, SendOptions, Shutdown,
126        Socket, SocketAddrEx, SocketCmsg, SocketOps, SocketWaitPolicy,
127    },
128};
129
130static LISTEN_TABLE: LazyLock<ListenTable> = LazyLock::new(ListenTable::new);
131static SOCKET_SET: LazyLock<SocketSetWrapper> = LazyLock::new(SocketSetWrapper::new);
132
133static SERVICE: OnceLock<Mutex<Service>> = OnceLock::new();
134static NET_CONTROL: OnceLock<Arc<NetControl>> = OnceLock::new();
135static QUEUE_RUNTIME: OnceLock<Mutex<NetworkQueueRuntime>> = OnceLock::new();
136static WIFI_INTERFACES: OnceLock<Vec<WifiInterfaceControl>> = OnceLock::new();
137static WIFI_ENTROPY: OnceLock<Mutex<WifiEntropy>> = OnceLock::new();
138static PROTOCOL_POLL: ProtocolPollRuntime = ProtocolPollRuntime::new();
139static PROTOCOL_AFFINITY_STATUS: AtomicU8 = AtomicU8::new(0);
140type DeferredPollEntry = (Arc<PollSet>, IoEvents);
141static DEFERRED_POLL_WAKE_PENDING: AtomicBool = AtomicBool::new(false);
142static DEFERRED_POLL_WAKES: LazyLock<Mutex<Vec<DeferredPollEntry>>> =
143    LazyLock::new(|| Mutex::new(Vec::new()));
144
145struct WifiInterfaceControl {
146    ifname: alloc::string::String,
147    device_index: usize,
148    mac: EthernetAddress,
149    handle: queue_runtime::WifiRuntimeHandle,
150}
151
152struct WifiEntropy {
153    generator: ChaCha20Rng,
154}
155
156impl WifiEntropy {
157    fn from_seed(seed: [u8; 32]) -> Self {
158        Self {
159            generator: ChaCha20Rng::from_seed(seed),
160        }
161    }
162
163    fn next_connection_entropy(&mut self) -> [u8; 32] {
164        let mut entropy = [0; 32];
165        self.generator.fill_bytes(&mut entropy);
166        entropy
167    }
168}
169
170fn next_wifi_connection_entropy() -> NetResult<[u8; 32]> {
171    if WIFI_ENTROPY.get().is_none() {
172        let seed = ax_hal::boot::boot_entropy().ok_or(NetError::EntropyUnavailable)?;
173        WIFI_ENTROPY.call_once(|| Mutex::new(WifiEntropy::from_seed(seed)));
174    }
175    Ok(WIFI_ENTROPY
176        .get()
177        .expect("Wi-Fi entropy was initialized above")
178        .lock()
179        .next_connection_entropy())
180}
181
182pub(crate) struct DeferPollWake {
183    pub(crate) poll: Arc<PollSet>,
184    pub(crate) ready: IoEvents,
185}
186
187impl Wake for DeferPollWake {
188    fn wake(self: Arc<Self>) {
189        self.wake_by_ref();
190    }
191
192    fn wake_by_ref(self: &Arc<Self>) {
193        // smoltcp invokes socket wakers from the protocol executor after
194        // updating readiness. The socket set may still be locked there, so
195        // defer the actual PollSet wake to the protocol executor outer loop.
196        defer_poll_wake(self.poll.clone(), self.ready);
197    }
198}
199
200#[derive(Clone)]
201pub(crate) struct ReadinessVersion(Arc<AtomicU64>);
202
203impl ReadinessVersion {
204    pub(crate) fn new() -> Self {
205        Self(Arc::new(AtomicU64::new(0)))
206    }
207
208    pub(crate) fn publish(&self) {
209        // The protocol state change happens before this release operation;
210        // polling observes both through `current` before reporting readiness.
211        self.0.fetch_add(1, Ordering::Release);
212    }
213
214    pub(crate) fn current(&self) -> u64 {
215        self.0.load(Ordering::Acquire)
216    }
217}
218
219pub(crate) struct SocketDeferPollWake {
220    poll: Arc<PollSet>,
221    ready: IoEvents,
222    readiness_version: ReadinessVersion,
223}
224
225impl SocketDeferPollWake {
226    pub(crate) fn new(
227        poll: Arc<PollSet>,
228        ready: IoEvents,
229        readiness_version: ReadinessVersion,
230    ) -> Self {
231        Self {
232            poll,
233            ready,
234            readiness_version,
235        }
236    }
237}
238
239impl Wake for SocketDeferPollWake {
240    fn wake(self: Arc<Self>) {
241        self.wake_by_ref();
242    }
243
244    fn wake_by_ref(self: &Arc<Self>) {
245        self.readiness_version.publish();
246        defer_poll_wake(self.poll.clone(), self.ready);
247    }
248}
249
250pub(crate) const fn receive_starts_next_edge(consumed: usize, remaining: usize) -> bool {
251    consumed != 0 && remaining == 0
252}
253
254const DHCP_BOOTSTRAP_TIMEOUT: Duration = Duration::from_secs(2);
255
256fn get_service() -> ax_sync::MutexGuard<'static, Service> {
257    SERVICE
258        .get()
259        .expect("Network service not initialized")
260        .lock()
261}
262
263pub(crate) fn get_control() -> &'static NetControl {
264    NET_CONTROL
265        .get()
266        .expect("Network service not initialized")
267        .as_ref()
268}
269
270fn map_driver_net_error(error: rd_net::NetError) -> NetError {
271    match error {
272        rd_net::NetError::DeviceNotPresent => NetError::NoSuchDevice,
273        rd_net::NetError::NotSupported | rd_net::NetError::IrqUnavailable => {
274            NetError::OperationNotSupported
275        }
276        rd_net::NetError::Retry => NetError::ResourceBusy,
277        rd_net::NetError::NoMemory => NetError::NoMemory,
278        rd_net::NetError::LinkDown => NetError::NoSuchDeviceOrAddress,
279        rd_net::NetError::InvalidParts => NetError::InvalidData,
280        rd_net::NetError::Stopped | rd_net::NetError::DmaShutdownUnconfirmed => NetError::BadState,
281        rd_net::NetError::Other(_) => NetError::BackendIo,
282    }
283}
284
285/// Atomically reconfigures one wireless interface through its fixed-CPU queue
286/// owner, then commits the matching protocol-side IP/DHCP role.
287///
288/// The calling task only submits a bounded command and waits for completion. It
289/// never gains access to the wireless control endpoint or SDIO/MMIO state.
290pub fn reconfigure_wifi(ifname: &str, mut transaction: WifiTransaction) -> NetResult {
291    let interface = WIFI_INTERFACES
292        .get()
293        .and_then(|interfaces| {
294            interfaces
295                .iter()
296                .find(|interface| interface.ifname == ifname)
297        })
298        .ok_or(NetError::NoSuchDevice)?;
299
300    if transaction.needs_connect_entropy() {
301        transaction.provide_connect_entropy(next_wifi_connection_entropy()?);
302        log::info!("[wifi] {ifname}: secure connection entropy prepared");
303    }
304
305    log::info!("[wifi] {ifname}: submitting control transaction");
306    if let Err(error) = interface.handle.submit(transaction.clone()) {
307        log::error!("[wifi] {ifname}: control transaction failed: {error:?}");
308        return Err(map_driver_net_error(error));
309    }
310    log::info!("[wifi] {ifname}: control transaction complete");
311
312    let mut service = get_service();
313    match transaction.operation() {
314        WifiOperation::Connect { .. } => {
315            service.reconfigure_as_sta(interface.device_index, interface.mac);
316        }
317        WifiOperation::Disconnect => {
318            service.reconfigure_as_disconnected(interface.device_index);
319        }
320        WifiOperation::StartOpenAccessPoint { .. } => {
321            let policy = transaction.link_policy().ok_or(NetError::InvalidInput)?;
322            service.reconfigure_as_ap(
323                interface.device_index,
324                Ipv4Address::from(policy.ip),
325                policy.prefix_len,
326                policy.dhcp_server_client_ip.map(Ipv4Address::from),
327            );
328        }
329    }
330    drop(service);
331    request_poll();
332    Ok(())
333}
334
335#[cfg(test)]
336mod wifi_entropy_tests {
337    use alloc::boxed::Box;
338
339    use super::{NetError, WifiEntropy, default_interface_name, map_driver_net_error};
340
341    #[test]
342    fn one_seed_produces_unique_entropy_for_each_connection() {
343        let mut source = WifiEntropy::from_seed([0x5a; 32]);
344        let first = source.next_connection_entropy();
345        let second = source.next_connection_entropy();
346        assert_ne!(first, second);
347        assert_ne!(first, [0; 32]);
348        assert_ne!(second, [0; 32]);
349    }
350
351    #[test]
352    fn wifi_capability_preserves_the_driver_registered_interface_name() {
353        assert_eq!(default_interface_name(0, "wlan0", true), "wlan0");
354        assert_eq!(default_interface_name(0, "virtio-net", false), "eth0");
355    }
356
357    #[test]
358    fn driver_io_failures_do_not_become_bad_user_addresses() {
359        let driver_error = rd_net::NetError::Other(Box::new(ax_io::IoError::Io));
360        assert_eq!(map_driver_net_error(driver_error), NetError::BackendIo);
361    }
362
363    #[test]
364    fn missing_driver_device_maps_to_no_such_device() {
365        assert_eq!(
366            map_driver_net_error(rd_net::NetError::DeviceNotPresent),
367            NetError::NoSuchDevice
368        );
369    }
370}
371
372/// Initializes the network subsystem by NIC devices.
373///
374/// # Panics
375///
376/// Panics if called more than once, or if the configuration contains invalid values.
377pub fn init_network(
378    queue_runtime: NetworkQueueRuntime,
379    mut frame_ports: EthernetFramePortList,
380    config: NetworkConfig,
381) {
382    if SERVICE.get().is_some() {
383        panic!("init_network() called more than once");
384    }
385
386    info!("Initialize network subsystem...");
387
388    validate_config(&config);
389
390    let routes: SharedRouteTable = Arc::new(ax_sync::SpinRwLock::new(RouteTable::new()));
391    let mut router = Router::new(routes.clone());
392    let mut interfaces = Vec::new();
393    let mut dns = Vec::new();
394
395    let lo_ip = register_loopback(&mut router, &mut interfaces);
396
397    if frame_ports.is_empty() {
398        warn!("  No network device found!");
399    }
400
401    let mut used_configs = vec![false; config.interfaces.len()];
402    let mut dhcp_ifaces = Vec::new();
403    let mut eth_ips = Vec::new();
404    let mut wifi_dhcp_servers = Vec::new();
405    let mut wifi_interfaces = Vec::new();
406
407    for (order, dev) in frame_ports.drain(..).enumerate() {
408        info!("  use NIC {}: {:?}", order, dev.device_name());
409        let wifi_capable = queue_runtime.wifi_handle(order).is_some();
410        let default_name = default_interface_name(order, dev.device_name(), wifi_capable);
411        let mac = EthernetAddress(dev.mac_address());
412        let cfg_idx = find_interface_config(
413            &config.interfaces,
414            &mut used_configs,
415            queue_runtime.discovery_order(order),
416            mac,
417            dev.device_name(),
418        );
419        let cfg = cfg_idx.map(|idx| &config.interfaces[idx]);
420        let name = cfg.map_or(default_name, |cfg| cfg.name.clone());
421        if interfaces.iter().any(|interface| interface.name == name) {
422            panic!("interface name conflict: {}", name);
423        }
424        let id = InterfaceId::new((order as u32) + 2);
425        let metric = cfg.map_or(100, |cfg| cfg.metric);
426        let wifi_policy = queue_runtime.initial_wifi_policy(order);
427        let static_ip = cfg.and_then(|cfg| cfg.static_ip.as_ref());
428        let ipv4 = static_ip
429            .map(|cfg| Ipv4Cidr::new(Ipv4Address::from(cfg.ip.octets()), cfg.prefix_len))
430            .or_else(|| {
431                (cfg.is_none())
432                    .then_some(wifi_policy)
433                    .flatten()
434                    .map(|policy| Ipv4Cidr::new(Ipv4Address::from(policy.ip), policy.prefix_len))
435            });
436        let gateway = static_ip.and_then(|cfg| {
437            (!cfg.gateway.is_unspecified()).then(|| Ipv4Address::from(cfg.gateway.octets()))
438        });
439        let dhcp_enabled = cfg.map_or(wifi_policy.is_none(), |cfg| cfg.dhcp);
440        let eth_dev = router.add_device(id, Box::new(EthernetDevice::new(name.clone(), dev, ipv4)));
441
442        if let Some(handle) = queue_runtime.wifi_handle(order) {
443            info!(
444                "  Wi-Fi control for {name} is owned by CPU {}",
445                handle.owner_cpu()
446            );
447            wifi_interfaces.push(WifiInterfaceControl {
448                ifname: name.clone(),
449                device_index: order,
450                mac,
451                handle,
452            });
453        }
454
455        info!("{name}:");
456        info!("  id:   {}", id.get());
457        info!("  mac:  {}", mac);
458        if let Some(ipv4) = ipv4 {
459            router.set_ipv4_config(
460                eth_dev,
461                id,
462                metric,
463                Some(ipv4),
464                gateway.map(IpAddress::Ipv4),
465            );
466            eth_ips.push(ipv4);
467            info!("  mode: static");
468            info!("  ip:   {}/{}", ipv4.address(), ipv4.prefix_len());
469            if let Some(gateway) = gateway {
470                info!("  gw:   {}", gateway);
471            }
472        } else if dhcp_enabled {
473            dhcp_ifaces.push((id, eth_dev, name.clone(), mac, metric));
474            info!("  mode: dhcp");
475        } else {
476            info!("  mode: none");
477        }
478        if cfg.is_none()
479            && let Some(policy) = wifi_policy
480            && let Some(client_ip) = policy.dhcp_server_client_ip
481        {
482            wifi_dhcp_servers.push((
483                order,
484                Ipv4Address::from(policy.ip),
485                Ipv4Address::from(client_ip),
486                mask_from_prefix(policy.prefix_len),
487            ));
488        }
489        if let Some(cfg) = cfg {
490            dns.extend(
491                cfg.dns_servers
492                    .iter()
493                    .copied()
494                    .map(|server| config::DnsServerEntry {
495                        server: Ipv4Address::from(server.octets()),
496                        interface_id: id,
497                        metric,
498                        source: config::DnsSource::Static,
499                    }),
500            );
501        }
502        interfaces.push(NetInterface {
503            id,
504            name,
505            kind: InterfaceKind::Ethernet,
506            mac: Some(mac),
507            ipv4,
508            gateway,
509            mtu: consts::STANDARD_MTU,
510            metric,
511            flags: InterfaceFlags::UP
512                | InterfaceFlags::RUNNING
513                | InterfaceFlags::BROADCAST
514                | InterfaceFlags::MULTICAST,
515        });
516    }
517
518    ensure_all_interface_configs_used(&config, &used_configs);
519
520    add_default_dns_servers(&config, &mut dns);
521
522    for name in router.device_names() {
523        info!("Device: {}", name);
524    }
525    let control = Arc::new(NetControl::new(interfaces, routes, dns));
526    let mut service = Service::new(router, control.clone());
527    service.iface.update_ip_addrs(|ip_addrs| {
528        ip_addrs.push(lo_ip.into()).unwrap();
529        for ip in eth_ips {
530            ip_addrs.push(ip.into()).unwrap();
531        }
532    });
533    for (id, dev, name, mac, metric) in dhcp_ifaces {
534        service.enable_dhcp(id, dev, name, mac, metric);
535    }
536    for (dev, server_ip, client_ip, subnet_mask) in wifi_dhcp_servers {
537        service.enable_dhcp_server(dev, server_ip, client_ip, subnet_mask);
538    }
539    let dhcp_enabled = service.dhcp_enabled();
540    let protocol_owner_cpu = queue_runtime.protocol_owner_cpu();
541    NET_CONTROL.call_once(|| control);
542    SERVICE.call_once(|| Mutex::new(service));
543    WIFI_INTERFACES.call_once(|| wifi_interfaces);
544    QUEUE_RUNTIME.call_once(|| Mutex::new(queue_runtime));
545    start_protocol_executor(protocol_owner_cpu);
546    if dhcp_enabled {
547        wait_for_dhcp_bootstrap();
548    }
549}
550
551fn validate_config(config: &NetworkConfig) {
552    for cfg in &config.interfaces {
553        if cfg.name == "lo" {
554            panic!("interface name 'lo' is reserved");
555        }
556        if cfg.dhcp && cfg.static_ip.is_some() {
557            panic!(
558                "interface {} has both DHCP and static IP configured",
559                cfg.name
560            );
561        }
562        if let Some(static_cfg) = &cfg.static_ip {
563            if static_cfg.ip.is_unspecified() {
564                panic!("Invalid static IP for {}: unspecified address", cfg.name);
565            }
566            if static_cfg.prefix_len > 32 {
567                panic!("Invalid static IP for {}: prefix length > 32", cfg.name);
568            }
569        }
570        for (i, dns) in cfg.dns_servers.iter().enumerate() {
571            if dns.is_unspecified() {
572                panic!(
573                    "Invalid DNS server for {} at index {}: unspecified address",
574                    cfg.name, i
575                );
576            }
577        }
578    }
579    for (i, dns) in config.default_dns_servers.iter().enumerate() {
580        if dns.is_unspecified() {
581            panic!("Invalid DNS server at index {}: unspecified address", i);
582        }
583    }
584}
585
586fn register_loopback(router: &mut Router, interfaces: &mut Vec<NetInterface>) -> Ipv4Cidr {
587    let lo_id = InterfaceId::LOOPBACK;
588    let lo_dev = router.add_device(lo_id, Box::new(LoopbackDevice::new()));
589
590    let lo_ip = Ipv4Cidr::new(Ipv4Address::new(127, 0, 0, 1), 8);
591    router.add_rule(Rule::new(
592        lo_ip.into(),
593        None,
594        lo_dev,
595        lo_id,
596        lo_ip.address().into(),
597        0,
598    ));
599    interfaces.push(NetInterface {
600        id: lo_id,
601        name: "lo".to_owned(),
602        kind: InterfaceKind::Loopback,
603        mac: None,
604        ipv4: Some(lo_ip),
605        gateway: None,
606        mtu: consts::STANDARD_MTU,
607        metric: 0,
608        flags: InterfaceFlags::UP | InterfaceFlags::RUNNING | InterfaceFlags::LOOPBACK,
609    });
610    lo_ip
611}
612
613fn ensure_all_interface_configs_used(config: &NetworkConfig, used_configs: &[bool]) {
614    for (i, used) in used_configs.iter().enumerate() {
615        if !used {
616            panic!(
617                "interface config {} did not match any device",
618                config.interfaces[i].name
619            );
620        }
621    }
622}
623
624fn default_interface_name(order: usize, driver_name: &str, wifi_capable: bool) -> String {
625    if wifi_capable {
626        driver_name.into()
627    } else {
628        format!("eth{order}")
629    }
630}
631
632fn add_default_dns_servers(config: &NetworkConfig, dns: &mut Vec<config::DnsServerEntry>) {
633    dns.extend(
634        config
635            .default_dns_servers
636            .iter()
637            .copied()
638            .map(|server| config::DnsServerEntry {
639                server: Ipv4Address::from(server.octets()),
640                interface_id: InterfaceId::LOOPBACK,
641                metric: u32::MAX,
642                source: config::DnsSource::Fallback,
643            }),
644    );
645}
646
647fn find_interface_config(
648    configs: &[InterfaceConfig],
649    used: &mut [bool],
650    order: usize,
651    mac: EthernetAddress,
652    driver_name: &str,
653) -> Option<usize> {
654    let mut matched = None;
655    for (idx, cfg) in configs.iter().enumerate() {
656        if used[idx] {
657            continue;
658        }
659        let is_match = match &cfg.match_by {
660            InterfaceMatcher::ByOrder(expected) => *expected == order,
661            InterfaceMatcher::ByMac(expected) => *expected == mac,
662            InterfaceMatcher::ByDriverName(expected) => expected == driver_name,
663        };
664        if is_match {
665            if matched.is_some() {
666                panic!("multiple interface configs match device {}", driver_name);
667            }
668            matched = Some(idx);
669        }
670    }
671    if let Some(idx) = matched {
672        used[idx] = true;
673    }
674    matched
675}
676
677/// Init vsock subsystem by vsock devices.
678#[cfg(feature = "vsock")]
679pub fn init_vsock(
680    vsock_devs: device::VsockDeviceList,
681    registrar: &dyn PinnedNetIrqRegistrar,
682    active_cpus: ax_task::sched::CpuSet,
683) -> Result<(), VsockRuntimeError> {
684    info!("Initialize vsock subsystem...");
685    if vsock_devs.is_empty() {
686        warn!("  No vsock device found!");
687        return Ok(());
688    }
689    let owner_cpu = QUEUE_RUNTIME
690        .get()
691        .expect("vsock initialization requires the network queue runtime")
692        .lock()
693        .protocol_owner_cpu();
694    if !active_cpus.contains(ax_task::sched::CpuId::new(owner_cpu as u32)) {
695        return Err(VsockRuntimeError::InvalidTopology);
696    }
697    device::init_vsock_device(vsock_devs, registrar, owner_cpu, active_cpus.topology_len())
698}
699
700fn poll_protocol_until_idle(budget: &mut ProtocolPollBudget) {
701    loop {
702        let more = get_service().poll(&mut SOCKET_SET.inner.lock());
703        if budget.consume(ax_hal::time::monotonic_time_nanos()) {
704            // Device owners share this CPU with the protocol executor. Deliver
705            // readiness and release CPU ownership with all network locks dropped.
706            drain_deferred_poll_wakes();
707            yield_network_thread();
708            budget.reset(ax_hal::time::monotonic_time_nanos());
709        }
710        if !more {
711            return;
712        }
713    }
714}
715
716/// Request network polling.
717///
718/// This is the lightweight entry used by socket and device paths.
719pub fn request_poll() {
720    let _ = PROTOCOL_POLL.request();
721}
722
723/// Waits for the unique protocol executor to dispatch all work published by
724/// this caller.
725///
726/// [`request_poll`] only wakes the protocol executor; the actual dispatch happens
727/// later. A socket that is closed in the same breath as its last send would
728/// otherwise be torn down before the executor runs, discarding the datagram still
729/// queued in its TX buffer. Draining egress here mirrors Linux, where a sent
730/// datagram already sits in the peer's receive buffer and `close()` cannot
731/// unsend it. Must not be called while holding `SOCKET_SET.inner`.
732pub(crate) fn flush_egress() {
733    let generation = PROTOCOL_POLL.request();
734    #[cfg(test)]
735    {
736        // Host unit tests install protocol state without starting an ArceOS
737        // scheduler.  Completing the generation exercises the wait contract
738        // without letting the caller execute smoltcp as a second owner.
739        PROTOCOL_POLL.complete(generation);
740    }
741    #[cfg(not(test))]
742    PROTOCOL_POLL.wait_for_completion(generation);
743}
744
745pub(crate) fn defer_poll_wake(poll: Arc<PollSet>, ready: IoEvents) {
746    DEFERRED_POLL_WAKES.lock().push((poll, ready));
747    if !DEFERRED_POLL_WAKE_PENDING.swap(true, Ordering::AcqRel) {
748        PROTOCOL_POLL.schedule();
749    }
750}
751
752fn drain_deferred_poll_wakes() {
753    loop {
754        let wakes = {
755            let mut wakes = DEFERRED_POLL_WAKES.lock();
756            if wakes.is_empty() {
757                DEFERRED_POLL_WAKE_PENDING.store(false, Ordering::Release);
758                return;
759            }
760            core::mem::take(&mut *wakes)
761        };
762        for (poll, ready) in wakes {
763            // Readiness was published before the wake was deferred, and no
764            // service/socket/device locks are held while draining.
765            unsafe { poll.wake(ready) };
766        }
767    }
768}
769
770/// Returns ARP/neighbor entries collected from all devices.
771pub fn arp_entries() -> Vec<ArpEntry> {
772    get_service().arp_entries()
773}
774
775/// Returns per-interface RX/TX byte and packet counters for `/proc/net/dev`.
776pub fn net_dev_stats() -> Vec<NetDevStats> {
777    get_service().net_dev_stats()
778}
779
780/// Returns a snapshot of all configured network interfaces.
781pub fn interfaces() -> Vec<InterfaceInfo> {
782    get_control().interfaces()
783}
784
785/// Looks up an interface snapshot by name.
786pub fn interface_by_name(name: &str) -> Option<InterfaceInfo> {
787    get_control().interface_by_name(name)
788}
789
790/// Looks up an interface snapshot by stable interface id.
791pub fn interface_by_id(id: InterfaceId) -> Option<InterfaceInfo> {
792    get_control().interface_by_id(id)
793}
794
795/// Returns the IPv4 configuration for an interface by name.
796pub fn ipv4_config(name: &str) -> Option<Ipv4InterfaceConfig> {
797    get_control().ipv4_config(name)
798}
799
800/// Assigns a static IPv4 address to an interface at runtime.
801pub fn set_interface_ipv4(interface_id: InterfaceId, ip: Ipv4Addr, prefix_len: u8) -> NetResult {
802    {
803        let mut service = get_service();
804        service.configure_static_ipv4(interface_id, Ipv4Address::from(ip.octets()), prefix_len)?;
805    }
806    request_poll();
807    Ok(())
808}
809
810/// Removes a configured IPv4 address from an interface at runtime.
811pub fn remove_interface_ipv4(interface_id: InterfaceId, ip: Ipv4Addr, prefix_len: u8) -> NetResult {
812    {
813        let mut service = get_service();
814        service.remove_static_ipv4(interface_id, Ipv4Address::from(ip.octets()), prefix_len)?;
815    }
816    request_poll();
817    Ok(())
818}
819
820/// Returns public snapshots of configured IPv4 default routes.
821pub fn default_routes() -> Vec<RouteInfo> {
822    get_control().default_routes()
823}
824
825fn next_poll_delay() -> Option<Duration> {
826    let next = {
827        let mut service = get_service();
828        let sockets = SOCKET_SET.inner.lock();
829        service.next_poll_at(&sockets)
830    };
831    let next = next?;
832    let now_micros = ax_hal::time::monotonic_time_nanos() / 1_000;
833    let next_micros = next.total_micros().max(0) as u64;
834    if next_micros <= now_micros {
835        Some(Duration::ZERO)
836    } else {
837        Some(Duration::from_micros(next_micros - now_micros))
838    }
839}
840
841fn start_protocol_executor(owner_cpu: usize) {
842    PROTOCOL_AFFINITY_STATUS.store(0, Ordering::Release);
843    let mut affinity = ax_task::sched::CpuSet::empty(ax_hal::cpu_num());
844    assert!(
845        affinity.insert(ax_task::sched::CpuId::new(owner_cpu as u32)),
846        "network protocol owner CPU {owner_cpu} is outside the runtime topology"
847    );
848    let worker = ax_task::thread::ThreadBuilder::new("net-protocol".to_owned())
849        .affinity(affinity)
850        .spawn(move || {
851            if ax_hal::percpu::this_cpu_id() != owner_cpu {
852                PROTOCOL_AFFINITY_STATUS.store(2, Ordering::Release);
853                return;
854            }
855            PROTOCOL_AFFINITY_STATUS.store(1, Ordering::Release);
856            PROTOCOL_POLL.schedule();
857            protocol_executor_main();
858        })
859        .unwrap_or_else(|error| panic!("failed to spawn network protocol executor: {error}"));
860    worker.detach();
861    while PROTOCOL_AFFINITY_STATUS.load(Ordering::Acquire) == 0 {
862        yield_network_thread();
863    }
864    assert_eq!(
865        PROTOCOL_AFFINITY_STATUS.load(Ordering::Acquire),
866        1,
867        "failed to pin the unique network protocol executor to CPU {owner_cpu}"
868    );
869}
870
871fn protocol_executor_main() {
872    let mut budget = ProtocolPollBudget::new(ax_hal::time::monotonic_time_nanos());
873    loop {
874        if let Some(delay) = next_poll_delay() {
875            let _ = PROTOCOL_POLL.wait_timeout(delay);
876        } else {
877            PROTOCOL_POLL.wait();
878        }
879        drain_deferred_poll_wakes();
880        let completed = PROTOCOL_POLL.requested_generation();
881        poll_protocol_until_idle(&mut budget);
882        PROTOCOL_POLL.complete(completed);
883        drain_deferred_poll_wakes();
884        if PROTOCOL_POLL.finish_cycle(|| DEFERRED_POLL_WAKE_PENDING.load(Ordering::Acquire)) {
885            continue;
886        }
887    }
888}
889
890/// Returns the list of configured DNS servers.
891///
892/// Priority: DHCP-provided servers take precedence over statically configured servers.
893/// If DHCP hasn't provided servers, falls back to the servers from `NetworkConfig`.
894pub fn dns_servers() -> Vec<Ipv4Address> {
895    get_control().dns_servers()
896}
897
898const DNS_DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
899
900/// Resolves an A record using the default DNS timeout.
901pub fn dns_query(name: &str) -> NetResult<Vec<IpAddr>> {
902    dns_query_timeout(name, DNS_DEFAULT_TIMEOUT)
903}
904
905/// Resolves an A record using the configured DNS servers and timeout.
906pub fn dns_query_timeout(name: &str, timeout: Duration) -> NetResult<Vec<IpAddr>> {
907    let servers = dns_servers();
908    if servers.is_empty() {
909        return Err(NetError::NotFound);
910    }
911
912    let servers = servers
913        .into_iter()
914        .filter(|server| {
915            get_control()
916                .select_route(&IpAddress::Ipv4(*server))
917                .is_ok()
918        })
919        .map(IpAddress::Ipv4)
920        .collect::<Vec<_>>();
921    if servers.is_empty() {
922        return Err(NetError::NoSuchDeviceOrAddress);
923    }
924    let handle = SOCKET_SET.add(dns::Socket::new(&servers, vec![]));
925    DnsSocketGuard(handle).query_timeout(name, DnsQueryType::A, timeout)
926}
927
928struct DnsSocketGuard(smoltcp::iface::SocketHandle);
929
930impl DnsSocketGuard {
931    fn query_timeout(
932        &self,
933        name: &str,
934        query_type: DnsQueryType,
935        timeout: Duration,
936    ) -> NetResult<Vec<IpAddr>> {
937        let query_handle = {
938            let mut service = get_service();
939            let mut sockets = SOCKET_SET.inner.lock();
940            sockets.get_mut::<dns::Socket>(self.0).start_query(
941                service.iface.context(),
942                name,
943                query_type,
944            )
945        }
946        .map_err(|err| match err {
947            StartQueryError::NoFreeSlot => NetError::ResourceBusy,
948            StartQueryError::InvalidName => NetError::InvalidInput,
949            StartQueryError::NameTooLong => NetError::InvalidInput,
950        })?;
951
952        let start_time = ax_hal::time::monotonic_time_nanos();
953        let timeout_ns = u64::try_from(timeout.as_nanos()).unwrap_or(u64::MAX);
954        let deadline = start_time.saturating_add(timeout_ns);
955
956        loop {
957            request_poll();
958            match SOCKET_SET.with_socket_mut::<dns::Socket, _, _>(self.0, |socket| {
959                socket
960                    .get_query_result(query_handle)
961                    .map_err(|err| match err {
962                        GetQueryResultError::Pending => NetError::WouldBlock,
963                        GetQueryResultError::Failed => NetError::ConnectionRefused,
964                    })
965            }) {
966                Ok(addrs) => {
967                    return Ok(addrs.into_iter().map(IpAddr::from).collect());
968                }
969                Err(NetError::WouldBlock) => {
970                    if ax_hal::time::monotonic_time_nanos() >= deadline {
971                        return Err(NetError::TimedOut);
972                    }
973                    yield_network_thread();
974                }
975                Err(err) => return Err(err),
976            }
977        }
978    }
979}
980
981pub(crate) fn yield_network_thread() {
982    ax_task::thread::current::yield_current_cpu()
983        .unwrap_or_else(|error| panic!("network executor could not yield: {error}"));
984}
985
986impl Drop for DnsSocketGuard {
987    fn drop(&mut self) {
988        SOCKET_SET.remove(self.0);
989    }
990}
991
992fn wait_for_dhcp_bootstrap() {
993    if get_control().wait_for_dhcp_configuration(DHCP_BOOTSTRAP_TIMEOUT) {
994        return;
995    }
996    warn!("DHCP bootstrap timed out");
997}
998
999#[cfg(test)]
1000mod readiness_version_tests {
1001    use super::{ReadinessVersion, receive_starts_next_edge};
1002
1003    #[test]
1004    fn readiness_wake_publishes_a_new_generation() {
1005        let version = ReadinessVersion::new();
1006        let before = version.current();
1007
1008        version.publish();
1009
1010        assert_eq!(version.current(), before.wrapping_add(1));
1011    }
1012
1013    #[test]
1014    fn only_a_drained_receive_starts_the_next_readiness_edge() {
1015        assert!(!receive_starts_next_edge(0, 0));
1016        assert!(!receive_starts_next_edge(1, 1));
1017        assert!(receive_starts_next_edge(1, 0));
1018    }
1019}