Testing a small http-client on Linux using no_std & embedded reqwless.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

implement default posix backend and dns mode feature model

rektide ce4a3500 8795386b

+152 -3
+51
Cargo.lock
··· 62 62 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 63 63 64 64 [[package]] 65 + name = "bitflags" 66 + version = "2.11.0" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" 69 + 70 + [[package]] 65 71 name = "block-buffer" 66 72 version = "0.10.4" 67 73 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 350 356 ] 351 357 352 358 [[package]] 359 + name = "errno" 360 + version = "0.3.14" 361 + source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 363 + dependencies = [ 364 + "libc", 365 + "windows-sys", 366 + ] 367 + 368 + [[package]] 353 369 name = "ff" 354 370 version = "0.13.1" 355 371 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 501 517 checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" 502 518 503 519 [[package]] 520 + name = "linux-raw-sys" 521 + version = "0.12.1" 522 + source = "registry+https://github.com/rust-lang/crates.io-index" 523 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 524 + 525 + [[package]] 504 526 name = "nb" 505 527 version = "1.1.0" 506 528 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 666 688 "embedded-nal-async", 667 689 "libc", 668 690 "reqwless", 691 + "rustix", 669 692 ] 670 693 671 694 [[package]] ··· 688 711 ] 689 712 690 713 [[package]] 714 + name = "rustix" 715 + version = "1.1.4" 716 + source = "registry+https://github.com/rust-lang/crates.io-index" 717 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 718 + dependencies = [ 719 + "bitflags", 720 + "errno", 721 + "libc", 722 + "linux-raw-sys", 723 + "windows-sys", 724 + ] 725 + 726 + [[package]] 691 727 name = "sec1" 692 728 version = "0.7.3" 693 729 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 804 840 version = "0.9.5" 805 841 source = "registry+https://github.com/rust-lang/crates.io-index" 806 842 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 843 + 844 + [[package]] 845 + name = "windows-link" 846 + version = "0.2.1" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 849 + 850 + [[package]] 851 + name = "windows-sys" 852 + version = "0.61.2" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 855 + dependencies = [ 856 + "windows-link", 857 + ] 807 858 808 859 [[package]] 809 860 name = "zerocopy"
+6 -1
Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [features] 7 - default = [] 7 + default = ["backend-posix-libc", "dns-getaddrinfo"] 8 + backend-posix-libc = [] 9 + backend-rustix = ["dep:rustix"] 10 + dns-getaddrinfo = [] 11 + dns-ip-only = [] 8 12 https = ["reqwless/embedded-tls", "dep:der"] 9 13 10 14 [dependencies] ··· 14 18 der = { version = "0.8", default-features = false, features = ["derive", "oid", "time", "heapless"], optional = true } 15 19 libc = { version = "0.2", default-features = false } 16 20 reqwless = { version = "0.14", default-features = false } 21 + rustix = { version = "1.1", default-features = false, features = ["net"], optional = true } 17 22 18 23 [profile.dev] 19 24 panic = "abort"
+16
README.md
··· 20 20 21 21 - `no_std` + `no_main` binary using Linux libc syscalls 22 22 - Uses `reqwless` with `default-features = false` 23 + - Default feature set is `backend-posix-libc` + `dns-getaddrinfo` 24 + - Planned `backend-rustix` exists as a feature gate but is intentionally not implemented yet 25 + - Alternate DNS mode `dns-ip-only` rejects hostnames and only accepts literal IP hosts 23 26 - HTTPS is optional via `--features https` and currently uses `TlsVerify::None` (insecure baseline) 24 27 - Uses fixed-size stack buffers for request/response and output chunks 25 28 - Performs best-effort low-allocation operation (no `String`/`Vec` in app code) 29 + 30 + ## Feature examples 31 + 32 + ```bash 33 + # Default build: posix-libc backend + getaddrinfo DNS mode 34 + cargo run -- http://example.com 35 + 36 + # HTTPS on default backend/DNS mode 37 + cargo run --features https -- https://example.com 38 + 39 + # Minimal DNS mode (literal IP hosts only) 40 + cargo run --no-default-features --features backend-posix-libc,dns-ip-only -- http://93.184.216.34 41 + ```
+79 -2
src/main.rs
··· 3 3 4 4 // pattern: Imperative Shell 5 5 6 + #[cfg(all(feature = "backend-posix-libc", feature = "backend-rustix"))] 7 + compile_error!("enable exactly one backend: backend-posix-libc or backend-rustix"); 8 + 9 + #[cfg(not(any(feature = "backend-posix-libc", feature = "backend-rustix")))] 10 + compile_error!("no backend selected; enable backend-posix-libc or backend-rustix"); 11 + 12 + #[cfg(all(feature = "dns-getaddrinfo", feature = "dns-ip-only"))] 13 + compile_error!("enable exactly one DNS mode: dns-getaddrinfo or dns-ip-only"); 14 + 15 + #[cfg(not(any(feature = "dns-getaddrinfo", feature = "dns-ip-only")))] 16 + compile_error!("no DNS mode selected; enable dns-getaddrinfo or dns-ip-only"); 17 + 18 + #[cfg(feature = "backend-rustix")] 19 + compile_error!("backend-rustix is not implemented yet; use backend-posix-libc"); 20 + 6 21 use core::ffi::{CStr, c_char, c_int, c_void}; 7 22 use core::future::Future; 8 - use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 23 + use core::net::{IpAddr, SocketAddr}; 24 + #[cfg(feature = "dns-getaddrinfo")] 25 + use core::net::{Ipv4Addr, Ipv6Addr}; 9 26 use core::panic::PanicInfo; 10 27 use core::pin::Pin; 11 28 use core::ptr; ··· 24 41 const EXIT_USAGE: c_int = 2; 25 42 const EXIT_FAILURE: c_int = 1; 26 43 44 + #[cfg(feature = "dns-getaddrinfo")] 27 45 const DNS_HOST_MAX: usize = 255; 28 46 #[cfg(feature = "https")] 29 47 const TLS_BUFFER_SIZE: usize = 16 * 1024; ··· 43 61 MissingUrl, 44 62 #[cfg(not(feature = "https"))] 45 63 HttpsFeatureDisabled, 64 + #[cfg(feature = "dns-ip-only")] 65 + DnsModeIpOnly, 46 66 InvalidUtf8, 67 + #[cfg(feature = "dns-getaddrinfo")] 47 68 UrlTooLong, 69 + #[cfg(feature = "dns-getaddrinfo")] 48 70 NulInHost, 49 71 DnsLookupFailed, 50 72 AddrTypeMismatch, ··· 58 80 Self::MissingUrl => ErrorKind::InvalidInput, 59 81 #[cfg(not(feature = "https"))] 60 82 Self::HttpsFeatureDisabled => ErrorKind::Unsupported, 83 + #[cfg(feature = "dns-ip-only")] 84 + Self::DnsModeIpOnly => ErrorKind::Unsupported, 61 85 Self::InvalidUtf8 => ErrorKind::InvalidData, 86 + #[cfg(feature = "dns-getaddrinfo")] 62 87 Self::UrlTooLong => ErrorKind::InvalidInput, 88 + #[cfg(feature = "dns-getaddrinfo")] 63 89 Self::NulInHost => ErrorKind::InvalidInput, 64 90 Self::DnsLookupFailed => ErrorKind::NotFound, 65 91 Self::AddrTypeMismatch => ErrorKind::AddrNotAvailable, ··· 89 115 Self::MissingUrl => "missing url argument", 90 116 #[cfg(not(feature = "https"))] 91 117 Self::HttpsFeatureDisabled => "https support not enabled", 118 + #[cfg(feature = "dns-ip-only")] 119 + Self::DnsModeIpOnly => "dns mode only accepts ip literals", 92 120 Self::InvalidUtf8 => "url argument must be valid utf-8", 121 + #[cfg(feature = "dns-getaddrinfo")] 93 122 Self::UrlTooLong => "hostname is too long", 123 + #[cfg(feature = "dns-getaddrinfo")] 94 124 Self::NulInHost => "hostname contains NUL byte", 95 125 Self::DnsLookupFailed => "dns lookup failed", 96 126 Self::AddrTypeMismatch => "address type mismatch", ··· 188 218 return Err(AppError::AddrTypeMismatch); 189 219 } 190 220 191 - resolve_host(host, addr_type) 221 + #[cfg(feature = "dns-getaddrinfo")] 222 + { 223 + return resolve_host(host, addr_type); 224 + } 225 + 226 + #[cfg(feature = "dns-ip-only")] 227 + { 228 + let _ = host; 229 + let _ = addr_type; 230 + Err(AppError::DnsModeIpOnly) 231 + } 192 232 } 193 233 194 234 async fn get_host_by_address(&self, _addr: IpAddr, _result: &mut [u8]) -> Result<usize, Self::Error> { ··· 220 260 } 221 261 222 262 async fn download_to_stdout(url: &str) -> Result<(), AppError> { 263 + #[cfg(feature = "dns-ip-only")] 264 + ensure_url_uses_ip_literal_host(url)?; 265 + 223 266 if url.starts_with("https://") { 224 267 return download_https_to_stdout(url).await; 225 268 } ··· 272 315 Ok(()) 273 316 } 274 317 318 + #[cfg(feature = "dns-ip-only")] 319 + fn ensure_url_uses_ip_literal_host(url: &str) -> Result<(), AppError> { 320 + let after_scheme = if let Some(rest) = url.strip_prefix("http://") { 321 + rest 322 + } else if let Some(rest) = url.strip_prefix("https://") { 323 + rest 324 + } else { 325 + return Ok(()); 326 + }; 327 + 328 + let authority = after_scheme.split_once('/').map_or(after_scheme, |(prefix, _)| prefix); 329 + let authority = authority.split_once('?').map_or(authority, |(prefix, _)| prefix); 330 + let authority = authority.split_once('#').map_or(authority, |(prefix, _)| prefix); 331 + 332 + let host_port = authority.rsplit_once('@').map_or(authority, |(_, hp)| hp); 333 + let host = if host_port.starts_with('[') { 334 + let end = host_port.find(']').ok_or(AppError::DnsModeIpOnly)?; 335 + &host_port[1..end] 336 + } else { 337 + host_port.split_once(':').map_or(host_port, |(h, _)| h) 338 + }; 339 + 340 + if host.parse::<IpAddr>().is_err() { 341 + return Err(AppError::DnsModeIpOnly); 342 + } 343 + 344 + Ok(()) 345 + } 346 + 275 347 fn print_usage() { 276 348 #[cfg(feature = "https")] 277 349 write_stderr(b"usage: reqwless-linux <http://url|https://url>\n"); ··· 294 366 cstr.to_str().map_err(|_| AppError::InvalidUtf8) 295 367 } 296 368 369 + #[cfg(feature = "dns-getaddrinfo")] 297 370 fn resolve_host(host: &str, addr_type: AddrType) -> Result<IpAddr, AppError> { 298 371 if host.len() > DNS_HOST_MAX { 299 372 return Err(AppError::UrlTooLong); ··· 443 516 AppError::MissingUrl => write_stderr(b"missing url argument\n"), 444 517 #[cfg(not(feature = "https"))] 445 518 AppError::HttpsFeatureDisabled => write_stderr(b"https url requires --features https\n"), 519 + #[cfg(feature = "dns-ip-only")] 520 + AppError::DnsModeIpOnly => write_stderr(b"dns-ip-only mode accepts literal IP hosts only\n"), 446 521 AppError::InvalidUtf8 => write_stderr(b"url argument must be valid utf-8\n"), 522 + #[cfg(feature = "dns-getaddrinfo")] 447 523 AppError::UrlTooLong => write_stderr(b"hostname is too long\n"), 524 + #[cfg(feature = "dns-getaddrinfo")] 448 525 AppError::NulInHost => write_stderr(b"hostname contains NUL byte\n"), 449 526 AppError::DnsLookupFailed => write_stderr(b"dns lookup failed\n"), 450 527 AppError::AddrTypeMismatch => write_stderr(b"address type mismatch\n"),