foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Tyler Fowler <tyler@localhost> | 2026-10-30 03:53:54 -0700 |
|---|---|---|
| commit | 235b98b1bd5539477d1c2b19d811c5f18d4f1ecf | |
| parent | d478ebf57a05d03aaa6ac392ee6b3d12edc8c543 | |
| tree | browse files at this commit | |
Stage 12: networking (0.12.0) - virtio-net driver (legacy, interrupt-driven via PIC IRQ 11) + smoltcp 0.14 (ARP, IPv4, ICMP, UDP, TCP, DHCP, DNS); a netd thread polls on IRQs/timers - BSD-style socket syscalls with Linux numbers (socket 41, connect 42, accept 43, sendto 44, recvfrom 45, bind 49, listen 50, setsockopt 54), plus clock_gettime 228 and our own resolve 503 / netinfo 504; blocking on wait queues with SO_RCVTIMEO deadlines - programs: ifconfig (ip), ping, host (nslookup), fetch (wget), httpd - spurious PIC IRQ 7/15 handlers (fixes a double fault) - tests/net.rs (10 tests: DHCP, ICMP, UDP/TFTP, TCP echo + HTTP GET, ECONNREFUSED, recv timeout, programs, leak check, DNS or skip); self-test covers ifconfig/ping/fetch/wget to D:/host/httpd via hostfwd - README networking chapter, roadmap; boot.png shows ifconfig/ping/fetch
| A | fetch.rs | +136 -0 |
| A | host.rs | +35 -0 |
| A | httpd.rs | +132 -0 |
| A | ifconfig.rs | +47 -0 |
| A | ping.rs | +103 -0 |
| M | sh.rs | +7 -1 |
| M | ulib.rs | +161 -0 |
7 files changed, 621 insertions, 1 deletion
@@ -0,0 +1,136 @@+//! `fetch [-o file] http://host[:port]/path` (alias `wget`): download a+//! web page or file with HTTP/1.0 and print it, or save it with `-o`.+//!+//! HTTP is just text over a TCP connection. We send+//! GET /path HTTP/1.0\r\n+//! Host: example.com\r\n+//! \r\n+//! and the server answers with a status line ("HTTP/1.0 200 OK"), header+//! lines, an empty line, then the body. With HTTP/1.0 the server closes+//! the connection when the body is done, so "read until EOF" is the whole+//! protocol on our side. (Plain HTTP only: HTTPS would need TLS.)+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++use core::fmt::Write;++/// "http://host:port/path" -> (host, port, path)+fn parse_url(url: &str) -> Option<(&str, u16, &str)> {+ let rest = url.strip_prefix("http://")?;+ let (hostport, path) = match rest.find('/') {+ Some(i) => (&rest[..i], &rest[i..]),+ None => (rest, "/"),+ };+ let (host, port) = match hostport.split_once(':') {+ Some((h, p)) => (h, p.parse().ok()?),+ None => (hostport, 80),+ };+ if host.is_empty() { None } else { Some((host, port, path)) }+}++fn main() -> i64 {+ let (mut out_path, mut url) = (None, None);+ let mut i = 1;+ while let Some(a) = ulib::arg(i) {+ if a == "-o" || a == "-O" {+ i += 1;+ out_path = ulib::arg(i);+ } else {+ url = Some(a);+ }+ i += 1;+ }+ let Some((host, port, path)) = url.and_then(parse_url) else {+ eprintln!("usage: fetch [-o file] http://host[:port]/path");+ return 2;+ };++ // 1. Name -> address (DNS), 2. connect (TCP handshake).+ let ip = match ulib::resolve(host) {+ Ok(ip) => ip,+ Err(e) => {+ eprintln!("fetch: {}: {}", host, ulib::strerror(e));+ return 1;+ }+ };+ let start = ulib::now_us();+ let fd = ulib::socket(ulib::SOCK_STREAM, 0);+ let r = if fd < 0 { fd } else { ulib::connect(fd, ip, port) };+ if r < 0 {+ eprintln!("fetch: connect to {}:{}: {}", ip, port, ulib::strerror(r));+ return 1;+ }++ // 3. Send the request.+ let mut request = alloc::string::String::new();+ let _ = write!(request, "GET {} HTTP/1.0\r\nHost: {}\r\nUser-Agent: rustbelt-fetch/0.12\r\nConnection: close\r\n\r\n", path, host);+ if ulib::write_all(fd, request.as_bytes()) < 0 {+ eprintln!("fetch: send failed");+ return 1;+ }++ // 4. Read the response: headers first (up to the empty line)...+ let mut head = alloc::vec::Vec::new();+ let mut buf = [0u8; 4096];+ let body_start = loop {+ let n = ulib::read(fd, &mut buf);+ if n <= 0 {+ eprintln!("fetch: connection closed before the headers ended");+ return 1;+ }+ head.extend_from_slice(&buf[..n as usize]);+ if let Some(i) = head.windows(4).position(|w| w == b"\r\n\r\n") {+ break i + 4;+ }+ if head.len() > 16 * 1024 {+ eprintln!("fetch: headers too long");+ return 1;+ }+ };+ let status_line = head.split(|&b| b == b'\r').next().unwrap_or(b"");+ let status_line = core::str::from_utf8(status_line).unwrap_or("?");+ let code: u32 = status_line.split(' ').nth(1).and_then(|c| c.parse().ok()).unwrap_or(0);++ // ...then the body, to stdout or the file, until the server closes.+ let out = match out_path {+ Some(p) => ulib::open(p, ulib::O_CREAT | ulib::O_TRUNC),+ None => 1,+ };+ if out < 0 {+ eprintln!("fetch: {}: {}", out_path.unwrap(), ulib::strerror(out));+ return 1;+ }+ let mut total = head.len() - body_start;+ ulib::write_all(out, &head[body_start..]);+ loop {+ let n = ulib::read(fd, &mut buf);+ if n < 0 {+ eprintln!("fetch: read: {}", ulib::strerror(n));+ break;+ }+ if n == 0 {+ break; // the server closed: that's the end of the body+ }+ if ulib::write_all(out, &buf[..n as usize]) < 0 {+ eprintln!("fetch: write failed");+ return 1;+ }+ total += n as usize;+ }+ ulib::close(fd);+ let ms = ((ulib::now_us() - start) / 1000).max(1);+ if let Some(p) = out_path {+ ulib::close(out);+ println!(+ "fetch: {}, saved {} bytes to {} in {} ms ({} KiB/s)",+ status_line, total, p, ms, total as u64 * 1000 / 1024 / ms+ );+ }+ if (200..300).contains(&code) { 0 } else { 1 }+}++extern crate alloc;
@@ -0,0 +1,35 @@+//! `host name` (alias `nslookup`): look up a name's IPv4 addresses.+//!+//! DNS turns names into addresses. We send a UDP query to the DNS server+//! DHCP told us about (QEMU's is 10.0.2.3, which forwards to the host's+//! resolver), asking for the name's "A" (IPv4 address) records. smoltcp+//! builds and parses the DNS packets; the kernel's `resolve` syscall wraps+//! it (on Linux, DNS lives in the C library instead).+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++fn main() -> i64 {+ let Some(name) = ulib::arg(1) else {+ eprintln!("usage: host name");+ return 2;+ };+ let mut addrs = [[0u8; 4]; 8];+ let n = ulib::resolve_all(name, &mut addrs);+ if n < 0 {+ let why = if n == -ulib::ETIMEDOUT { "no answer from the DNS server" } else { ulib::strerror(n) };+ eprintln!("host: {}: {}", name, why);+ return 1;+ }+ if n == 0 {+ println!("{} has no IPv4 address", name);+ return 1;+ }+ for a in &addrs[..n as usize] {+ println!("{} has address {}", name, ulib::Ip(*a));+ }+ 0+}
@@ -0,0 +1,132 @@+//! `httpd [-p port] [-n count] [-t seconds]`: a tiny web server.+//!+//! The server side of sockets: `bind` to a port, `listen`, then `accept`+//! connections one by one. Each request gets one answer and the connection+//! is closed (HTTP/1.0 style). `/` is a greeting page; any other path is a+//! file from the current directory (`cd B:` then `httpd` serves the FAT32+//! disk).+//!+//! QEMU's user-mode network hides the guest behind NAT, so to reach this+//! from the host QEMU must forward a port, e.g.+//! -netdev user,id=net0,hostfwd=tcp:127.0.0.1:5580-:80+//! and then `curl http://127.0.0.1:5580/` on the host.+//!+//! `-n` stops after that many requests; `-t` gives up if no request comes+//! within that many seconds (both used by the self-test).+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++extern crate alloc;+use alloc::format;++fn main() -> i64 {+ let (mut port, mut max_requests, mut timeout_s) = (80u16, u64::MAX, 0u64);+ let mut i = 1;+ while let Some(a) = ulib::arg(i) {+ let value = ulib::arg(i + 1).and_then(|v| v.parse::<u64>().ok());+ match (a, value) {+ ("-p", Some(v)) => port = v as u16,+ ("-n", Some(v)) => max_requests = v,+ ("-t", Some(v)) => timeout_s = v,+ _ => {+ eprintln!("usage: httpd [-p port] [-n count] [-t seconds]");+ return 2;+ }+ }+ i += 2;+ }++ let fd = ulib::socket(ulib::SOCK_STREAM, 0);+ for r in [fd, ulib::bind(fd, port), ulib::listen(fd, 4)] {+ if r < 0 {+ eprintln!("httpd: {}", ulib::strerror(r));+ return 1;+ }+ }+ ulib::set_recv_timeout(fd, timeout_s * 1000); // 0 = wait forever+ println!("[httpd] listening on port {}", port);++ let mut served = 0;+ while served < max_requests {+ let (conn, peer) = ulib::accept(fd);+ if conn < 0 {+ println!("[httpd] no request within {} s, giving up", timeout_s);+ return 1;+ }+ served += 1;+ handle(conn, peer, served);+ ulib::close(conn);+ }+ println!("[httpd] served {} request{}, bye", served, if served == 1 { "" } else { "s" });+ 0+}++/// Read one request, send one response.+fn handle(conn: i64, peer: ulib::SockAddrIn, visitor: u64) {+ ulib::set_recv_timeout(conn, 5000);+ let mut req = [0u8; 2048];+ let mut len = 0;+ while len < req.len() && !req[..len].windows(4).any(|w| w == b"\r\n\r\n") {+ let n = ulib::read(conn, &mut req[len..]);+ if n <= 0 {+ return; // gave up or hung up+ }+ len += n as usize;+ }+ let line = req[..len].split(|&b| b == b'\r').next().unwrap_or(b"");+ let line = core::str::from_utf8(line).unwrap_or("");+ let mut words = line.split(' ');+ let (method, path) = (words.next().unwrap_or(""), words.next().unwrap_or("/"));++ let (status, bytes) = if method != "GET" {+ respond(conn, "405 Method Not Allowed", b"only GET, sorry\n")+ } else if path == "/" {+ let mut uts = [0u8; 6 * 65];+ let u = ulib::uname(&mut uts);+ let body = format!(+ "Hello from Rustbelt! {} {} ({})\nYou are visitor {}. Ask for /path to get a file from my current directory.\n",+ u[0], u[2], u[3], visitor+ );+ respond(conn, "200 OK", body.as_bytes())+ } else if path.contains("..") {+ respond(conn, "403 Forbidden", b"no ..\n")+ } else {+ send_file(conn, &path[1..])+ };+ println!("[httpd] {}:{} {} {} -> {} ({} bytes)", peer.ip(), peer.port(), method, path, status, bytes);+}++fn respond(conn: i64, status: &'static str, body: &[u8]) -> (&'static str, usize) {+ let head = format!("HTTP/1.0 {}\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nServer: rustbelt-httpd\r\n\r\n", status, body.len());+ ulib::write_all(conn, head.as_bytes());+ ulib::write_all(conn, body);+ (&status[..3], body.len())+}++fn send_file(conn: i64, path: &str) -> (&'static str, usize) {+ let mut st = ulib::Stat::default();+ if ulib::stat(path, &mut st) < 0 || st.kind != ulib::KIND_FILE {+ return respond(conn, "404 Not Found", b"not found\n");+ }+ let fd = ulib::open(path, 0);+ if fd < 0 {+ return respond(conn, "404 Not Found", b"not found\n");+ }+ let head = format!("HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: {}\r\n\r\n", st.size);+ ulib::write_all(conn, head.as_bytes());+ let mut buf = [0u8; 4096];+ let mut sent = 0;+ loop {+ let n = ulib::read(fd, &mut buf);+ if n <= 0 || ulib::write_all(conn, &buf[..n as usize]) < 0 {+ break;+ }+ sent += n as usize;+ }+ ulib::close(fd);+ ("200", sent)+}
@@ -0,0 +1,47 @@+//! `ifconfig` (alias `ip`): show the network card and our IP address.+//!+//! eth0: virtio-net MAC 52:54:00:12:34:56 IRQ 11+//! inet 10.0.2.15/24 gateway 10.0.2.2 DNS 10.0.2.3 (DHCP)+//! RX 12 packets (2 KiB) TX 9 packets (1 KiB)+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++use ulib::Ip;++fn main() -> i64 {+ let mut info = ulib::netinfo();+ if info.present == 0 {+ eprintln!("ifconfig: no network card (QEMU needs -device virtio-net-pci)");+ return 1;+ }+ // Right after boot DHCP may still be running: give it up to 3 s.+ for _ in 0..30 {+ if info.configured != 0 {+ break;+ }+ ulib::sleep_ms(100);+ info = ulib::netinfo();+ }+ let m = info.mac;+ println!(+ "eth0: virtio-net MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} IRQ {}",+ m[0], m[1], m[2], m[3], m[4], m[5], info.irq+ );+ if info.configured != 0 {+ println!(+ " inet {}/{} gateway {} DNS {} (DHCP)",+ Ip(info.address), info.prefix_len, Ip(info.gateway), Ip(info.dns)+ );+ } else {+ println!(" no address yet (waiting for DHCP)");+ }+ println!(+ " RX {} packets ({} KiB) TX {} packets ({} KiB)",+ info.rx_packets, info.rx_bytes / 1024, info.tx_packets, info.tx_bytes / 1024+ );+ 0+}
@@ -0,0 +1,103 @@+//! `ping [-c count] [-i ms] host`: send ICMP echo requests, time the replies.+//!+//! ICMP is IP's "control message" protocol. An *echo request* (type 8) asks+//! a host to send the same bytes straight back as an *echo reply* (type 0).+//! The packet: type, code, checksum (2 bytes), identifier (2), sequence+//! number (2), then any payload. We use a Linux-style "ping socket"+//! (SOCK_DGRAM + IPPROTO_ICMP): the kernel fills in the identifier and+//! checksum and only gives us replies to *our* requests.+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++const PAYLOAD: usize = 56; // like Linux's ping: 56 + 8 header = 64 bytes++fn main() -> i64 {+ let mut count = 4u16;+ let mut interval_ms = 1000;+ let mut host = None;+ let mut i = 1;+ while let Some(a) = ulib::arg(i) {+ match a {+ "-c" => { i += 1; count = ulib::arg(i).and_then(|v| v.parse().ok()).unwrap_or(4); }+ "-i" => { i += 1; interval_ms = ulib::arg(i).and_then(|v| v.parse().ok()).unwrap_or(1000); }+ _ => host = Some(a),+ }+ i += 1;+ }+ let Some(host) = host else {+ eprintln!("usage: ping [-c count] [-i interval_ms] host");+ return 2;+ };+ let ip = match ulib::resolve(host) {+ Ok(ip) => ip,+ Err(e) => {+ eprintln!("ping: {}: {}", host, ulib::strerror(e));+ return 2;+ }+ };+ let fd = ulib::socket(ulib::SOCK_DGRAM, ulib::IPPROTO_ICMP);+ if fd < 0 {+ eprintln!("ping: socket: {}", ulib::strerror(fd));+ return 2;+ }+ ulib::set_recv_timeout(fd, 1000); // a reply later than 1 s counts as lost+ println!("PING {} ({}): {} data bytes", host, ip, PAYLOAD);++ let (mut received, mut min, mut max, mut total) = (0u64, u64::MAX, 0u64, 0u64);+ for seq in 1..=count {+ // Echo request: type 8, code 0, checksum and id (kernel fills in), seq.+ let mut packet = [0u8; 8 + PAYLOAD];+ packet[0] = 8;+ packet[6..8].copy_from_slice(&seq.to_be_bytes());+ for (j, b) in packet[8..].iter_mut().enumerate() {+ *b = j as u8; // a recognizable payload+ }+ let sent_at = ulib::now_us();+ let n = ulib::sendto(fd, &packet, ip, 0);+ if n < 0 {+ eprintln!("ping: sendto: {}", ulib::strerror(n));+ return 2;+ }+ // Wait for *this* reply (a late one for an older seq is skipped).+ let mut reply = [0u8; 8 + PAYLOAD + 64];+ loop {+ let (n, from) = ulib::recvfrom(fd, &mut reply);+ if n < 0 {+ println!("Request timeout for icmp_seq={}", seq);+ break;+ }+ let n = n as usize;+ if n >= 8 && reply[0] == 0 && reply[6..8] == seq.to_be_bytes() {+ let us = ulib::now_us() - sent_at;+ let ok = reply[8..n] == packet[8..];+ println!(+ "{} bytes from {}: icmp_seq={} time={}.{:02} ms{}",+ n, from.ip(), seq, us / 1000, us % 1000 / 10,+ if ok { "" } else { " (payload differs!)" }+ );+ received += 1;+ (min, max, total) = (min.min(us), max.max(us), total + us);+ break;+ }+ }+ if seq < count {+ ulib::sleep_ms(interval_ms);+ }+ }+ ulib::close(fd);+ let loss = (count as u64 - received) * 100 / count.max(1) as u64;+ println!("--- {} ping statistics ---", host);+ println!("{} packets transmitted, {} received, {}% packet loss", count, received, loss);+ if received > 0 {+ let avg = total / received;+ println!(+ "rtt min/avg/max = {}.{:02}/{}.{:02}/{}.{:02} ms",+ min / 1000, min % 1000 / 10, avg / 1000, avg % 1000 / 10, max / 1000, max % 1000 / 10+ );+ }+ if received > 0 { 0 } else { 1 }+}
@@ -30,13 +30,19 @@ use shparse::Drive; use ulib::*; /// Built-in aliases (DOS names for the Unix-y programs).-const DEFAULT_ALIASES: &[(&str, &str)] = &[("dir", "ls"), ("type", "cat"), ("del", "rm"), ("md", "mkdir"), ("copy", "cp")];+/// Plus Linux names for the network tools.+const DEFAULT_ALIASES: &[(&str, &str)] = &[+ ("dir", "ls"), ("type", "cat"), ("del", "rm"), ("md", "mkdir"), ("copy", "cp"),+ ("ip", "ifconfig"), ("nslookup", "host"), ("wget", "fetch"),+]; const HELP: &str = "\ Rustbelt shell commands builtins cd [dir] pwd echo [text] ver drives alias [name=value] history jobs wait [pid] cls / clear help exit [code] programs ls [-l] cat wc grep [-i] [-v] mkdir rm touch cp sleep sysinfo+ network ifconfig (ip) ping [-c n] host host name (nslookup)+ fetch [-o file] http://... (wget) httpd [-p port] [-n count] (bin dirs) hello demo evil spin [rounds] overflow child sh run [--trace] prog args run a program (--trace prints its syscalls) start prog args run it in the background (or: prog args &)
@@ -42,6 +42,17 @@ pub const SYS_GETDENTS64: u64 = 217; pub const SYS_SPAWN: u64 = 500; pub const SYS_MOUNTS: u64 = 501; pub const SYS_SYSINFO: u64 = 502;+pub const SYS_SOCKET: u64 = 41;+pub const SYS_CONNECT: u64 = 42;+pub const SYS_ACCEPT: u64 = 43;+pub const SYS_SENDTO: u64 = 44;+pub const SYS_RECVFROM: u64 = 45;+pub const SYS_BIND: u64 = 49;+pub const SYS_LISTEN: u64 = 50;+pub const SYS_SETSOCKOPT: u64 = 54;+pub const SYS_CLOCK_GETTIME: u64 = 228;+pub const SYS_RESOLVE: u64 = 503;+pub const SYS_NETINFO: u64 = 504; pub const O_CREAT: u64 = 0o100; pub const O_TRUNC: u64 = 0o1000;@@ -64,6 +75,7 @@ pub const ENOTDIR: i64 = 20; pub const EISDIR: i64 = 21; pub const ESPIPE: i64 = 29; pub const ENOTEMPTY: i64 = 39;+pub const ETIMEDOUT: i64 = 110; /// Same layout as the kernel's `fs::file::Stat`. #[repr(C)]@@ -365,10 +377,159 @@ pub fn strerror(errno: i64) -> &'static str { 39 => "Directory not empty", 40 => "Too many levels of symbolic links", 95 => "Operation not supported",+ 88 => "Socket operation on non-socket",+ 89 => "Destination address required",+ 90 => "Message too long",+ 93 => "Protocol not supported",+ 97 => "Address family not supported",+ 98 => "Address already in use",+ 100 => "Network is down",+ 101 => "Network is unreachable",+ 104 => "Connection reset by peer",+ 106 => "Already connected",+ 107 => "Not connected",+ 110 => "Connection timed out",+ 111 => "Connection refused", _ => "Unknown error", } } +// --- Networking ------------------------------------------------------------------+//+// BSD sockets, the API every OS copied from 1983's 4.2BSD Unix: `socket()`+// makes an fd, `connect()` dials, read/write move data, `close()` hangs up.+// Servers `bind()` a port, `listen()`, and `accept()` each caller.++pub const AF_INET: u64 = 2;+pub const SOCK_STREAM: u64 = 1; // TCP+pub const SOCK_DGRAM: u64 = 2; // UDP (or ICMP with IPPROTO_ICMP)+pub const IPPROTO_ICMP: u64 = 1;++/// An IPv4 address that prints as "10.0.2.15".+#[derive(Clone, Copy, PartialEq, Eq, Default)]+pub struct Ip(pub [u8; 4]);++impl fmt::Display for Ip {+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {+ let [a, b, c, d] = self.0;+ write!(f, "{}.{}.{}.{}", a, b, c, d)+ }+}++/// "10.0.2.2" -> Ip, or None if it isn't a dotted-quad address.+pub fn parse_ip(s: &str) -> Option<Ip> {+ let mut ip = [0u8; 4];+ let mut parts = s.split('.');+ for b in ip.iter_mut() {+ *b = parts.next()?.parse().ok()?;+ }+ if parts.next().is_some() { None } else { Some(Ip(ip)) }+}++/// Linux's `struct sockaddr_in` (port and address big-endian).+#[repr(C)]+#[derive(Clone, Copy, Default)]+pub struct SockAddrIn {+ pub family: u16,+ pub port_be: u16,+ pub addr: [u8; 4],+ pub zero: [u8; 8],+}++impl SockAddrIn {+ pub fn new(ip: Ip, port: u16) -> SockAddrIn {+ SockAddrIn { family: AF_INET as u16, port_be: port.to_be(), addr: ip.0, zero: [0; 8] }+ }+ pub fn ip(&self) -> Ip {+ Ip(self.addr)+ }+ pub fn port(&self) -> u16 {+ u16::from_be(self.port_be)+ }+}++pub fn socket(ty: u64, protocol: u64) -> i64 {+ syscall3(SYS_SOCKET, AF_INET, ty, protocol)+}+pub fn connect(fd: i64, ip: Ip, port: u16) -> i64 {+ let sa = SockAddrIn::new(ip, port);+ syscall3(SYS_CONNECT, fd as u64, &sa as *const _ as u64, 16)+}+pub fn bind(fd: i64, port: u16) -> i64 {+ let sa = SockAddrIn::new(Ip([0; 4]), port);+ syscall3(SYS_BIND, fd as u64, &sa as *const _ as u64, 16)+}+pub fn listen(fd: i64, backlog: u64) -> i64 {+ syscall3(SYS_LISTEN, fd as u64, backlog, 0)+}+/// Wait for a connection: (new fd or -errno, caller's address).+pub fn accept(fd: i64) -> (i64, SockAddrIn) {+ let mut sa = SockAddrIn::default();+ let fd = syscall3(SYS_ACCEPT, fd as u64, &mut sa as *mut _ as u64, 0);+ (fd, sa)+}+pub fn sendto(fd: i64, buf: &[u8], ip: Ip, port: u16) -> i64 {+ let sa = SockAddrIn::new(ip, port);+ syscall5(SYS_SENDTO, fd as u64, buf.as_ptr() as u64, buf.len() as u64, 0, &sa as *const _ as u64)+}+/// Receive one datagram (or some stream bytes): (length or -errno, sender).+pub fn recvfrom(fd: i64, buf: &mut [u8]) -> (i64, SockAddrIn) {+ let mut sa = SockAddrIn::default();+ let n = syscall5(SYS_RECVFROM, fd as u64, buf.as_mut_ptr() as u64, buf.len() as u64, 0, &mut sa as *mut _ as u64);+ (n, sa)+}+/// SO_RCVTIMEO: recv/accept give up with -EAGAIN after `ms` (0 = never).+pub fn set_recv_timeout(fd: i64, ms: u64) -> i64 {+ let tv = [ms / 1000, (ms % 1000) * 1000]; // struct timeval+ syscall5(SYS_SETSOCKOPT, fd as u64, 1, 20, tv.as_ptr() as u64, 16)+}+/// Microseconds since boot (clock_gettime(CLOCK_MONOTONIC)).+pub fn now_us() -> u64 {+ let mut ts = [0u64; 2];+ syscall3(SYS_CLOCK_GETTIME, 1, ts.as_mut_ptr() as u64, 0);+ ts[0] * 1_000_000 + ts[1] / 1000+}+/// Look up a host name with DNS (or parse it if it's already an address).+pub fn resolve(name: &str) -> Result<Ip, i64> {+ if let Some(ip) = parse_ip(name) {+ return Ok(ip);+ }+ let mut out = [[0u8; 4]; 1];+ match syscall5(SYS_RESOLVE, name.as_ptr() as u64, name.len() as u64, out.as_mut_ptr() as u64, 1, 0) {+ n if n < 0 => Err(n),+ 0 => Err(-2), // no A record: ENOENT+ _ => Ok(Ip(out[0])),+ }+}+/// All IPv4 addresses for `name` (up to `out.len()`); count or -errno.+pub fn resolve_all(name: &str, out: &mut [[u8; 4]]) -> i64 {+ syscall5(SYS_RESOLVE, name.as_ptr() as u64, name.len() as u64, out.as_mut_ptr() as u64, out.len() as u64, 0)+}++/// What `ifconfig` shows; same layout as the kernel's `net::NetInfo`.+#[repr(C)]+#[derive(Clone, Copy, Default)]+pub struct NetInfo {+ pub present: u8,+ pub configured: u8,+ pub mac: [u8; 6],+ pub address: [u8; 4],+ pub prefix_len: u8,+ pub irq: u8,+ pub _pad: [u8; 2],+ pub gateway: [u8; 4],+ pub dns: [u8; 4],+ pub rx_packets: u64,+ pub rx_bytes: u64,+ pub tx_packets: u64,+ pub tx_bytes: u64,+}+pub fn netinfo() -> NetInfo {+ let mut info = NetInfo::default();+ syscall3(SYS_NETINFO, &mut info as *mut NetInfo as u64, 0, 0);+ info+}+ pub struct Stdout; impl fmt::Write for Stdout {