mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
gex-kernel: inet over loopback with no network stack; SHUT_WR across a page; poll and recvmsg fixes
- socket(AF_INET) without a NetStack (the browser with no Wisp relay) returned EAFNOSUPPORT, so tests/wasm32/inet.c failed every check from bind on in run-browser.sh. It now gets `net::NoNet`: bind, listen, connect and accept over loopback work as natively; a connection out is ENETUNREACH. - RemoteEnd (TCP between a page's processes, through the global kernel) had no shutdown_write, so shutdown(SHUT_WR) was a no-op and the far end's recv waited forever. It is the global kernel's shutdown, which is SHUT_WR's. - poll: a negative fd is an unused slot (revents 0), not POLLNVAL. musl >=1.2.4 res_msend polls with fd -1 for its TCP slots; POLLNVAL there made every getaddrinfo wait out its 5 s timeout (wineboot's host-name lookup). - recvmsg on an inet socket writes msg_name (the sender), as recvfrom does. inet.c: checks for both; it passes natively and in run-browser.sh (x86). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_1896c5b302a54696aaa92c5f4047f20b
This commit is contained in:
1 parent
7c316bae4e
commit
f43fba88f0
3 files changed
+65
-3
No files matched your search
@@ -72,6 +72,18 @@ pub trait NetStack: Send + Sync + std::fmt::Debug {
|
||||
fn tcp(&self) -> Result<Arc<dyn Transport>, Errno>;
|
||||
}
|
||||
|
||||
/// No network behind the kernel (the browser without a Wisp relay): inet
|
||||
/// sockets still work over loopback -- bind, listen, connect and the
|
||||
/// kernel's own DNS answers -- and a connection out is unreachable.
|
||||
#[derive(Debug)]
|
||||
pub struct NoNet;
|
||||
|
||||
impl NetStack for NoNet {
|
||||
fn tcp(&self) -> Result<Arc<dyn Transport>, Errno> {
|
||||
Err(Errno::ENETUNREACH)
|
||||
}
|
||||
}
|
||||
|
||||
// -- names ------------------------------------------------------------------
|
||||
|
||||
/// The private range the synthetic answers come from. 240.0.0.0/4 is reserved
|
||||
@@ -309,6 +321,10 @@ impl Transport for RemoteEnd {
|
||||
fn close(&self) {
|
||||
(self.remote.close)(self.id);
|
||||
}
|
||||
/// The global kernel's shutdown is SHUT_WR's: the far end reads to its end.
|
||||
fn shutdown_write(&self) {
|
||||
(self.remote.shutdown)(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The global kernel's name for a TCP port (abstract, so no file appears).
|
||||
|
||||
@@ -873,6 +873,15 @@ pub fn poll_revents(p: &PollFd) -> u16 {
|
||||
const POLLIN: u16 = 0x001;
|
||||
const POLLOUT: u16 = 0x004;
|
||||
const POLLNVAL: u16 = 0x020;
|
||||
// A negative descriptor is a slot the caller is not using: its events
|
||||
// are ignored and it reports nothing, as on Linux. musl's resolver keeps
|
||||
// one such slot per query for a TCP retry; reporting POLLNVAL there made
|
||||
// every `ppoll` return at once with nothing to read, and the resolver
|
||||
// spun until its 5 s timeout -- on every `getaddrinfo` (Wine's
|
||||
// `wineboot` resolving the host name was 5 s of each session boot).
|
||||
if p.fd < 0 {
|
||||
return 0;
|
||||
}
|
||||
let Some(h) = &p.handle else {
|
||||
return POLLNVAL;
|
||||
};
|
||||
@@ -5394,9 +5403,9 @@ impl Kernel {
|
||||
let file = OpenFile::new(handle, "socket:[netlink]".to_string(), flags, true, true);
|
||||
return self.fds.insert(file, ty & SOCK_CLOEXEC != 0);
|
||||
}
|
||||
let Some(net) = self.net.clone() else {
|
||||
return Err(Errno::EAFNOSUPPORT);
|
||||
};
|
||||
// Without a network stack (the browser with no relay), loopback still
|
||||
// works: Wine's in-process server and Winsock use it.
|
||||
let net = self.net.clone().unwrap_or_else(|| Arc::new(net::NoNet) as Arc<dyn net::NetStack>);
|
||||
if domain != AF_INET {
|
||||
return Err(Errno::EAFNOSUPPORT);
|
||||
}
|
||||
@@ -6314,9 +6323,23 @@ impl Kernel {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let is_unix = unix_sock.is_some();
|
||||
let received = unix_sock.map(|s| s.take_received()).unwrap_or_default();
|
||||
let written = self.write_scm_rights(m, control, controllen, received)?;
|
||||
self.msg_wr(m, msg, 5, written)?;
|
||||
// Where the datagram came from, as `recvfrom` says it. musl's
|
||||
// resolver (1.2.4 on) reads its answers with `recvmsg` and drops any
|
||||
// whose source is not a nameserver it asked: with the name left
|
||||
// unwritten every answer was dropped, and each `getaddrinfo` waited
|
||||
// out the resolver's 5 s timeout and failed.
|
||||
let name = self.msg_rd(m, msg, 0)?;
|
||||
if name != 0 && !is_unix {
|
||||
if let Ok(sock) = self.socket_of(fd) {
|
||||
let (ip, port) = sock.last_from().or_else(|| sock.peer()).unwrap_or((0, 0));
|
||||
let namelen = msg + if self.compat { 4 } else { 8 };
|
||||
self.sys_write_sockaddr(m, ip, port, name, namelen)?;
|
||||
}
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
@@ -6954,6 +6977,15 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::vfs::TmpFs;
|
||||
|
||||
#[test]
|
||||
fn poll_skips_negative_descriptors() {
|
||||
let slot = |fd| PollFd { addr: 0, fd, events: 1, handle: None, writable: false };
|
||||
// An unused slot reports nothing; a closed descriptor is POLLNVAL.
|
||||
assert_eq!(poll_revents(&slot(-1)), 0);
|
||||
assert_eq!(poll_revents(&slot(7)), 0x020);
|
||||
assert!(!poll_ready(&[slot(-1), slot(-1)]));
|
||||
}
|
||||
|
||||
fn setup() -> (Vec<u8>, Kernel) {
|
||||
let mem = vec![0u8; 0x40000];
|
||||
let mut k = Kernel::new(0x1000, 0x10000, "/prog".into());
|
||||
|
||||
@@ -59,6 +59,20 @@ int main(void)
|
||||
ok(poll(&p, 1, 5000) == 1, "poll: a datagram");
|
||||
len = sizeof b;
|
||||
ok(recvfrom(u1, buf, sizeof buf, 0, (struct sockaddr *)&b, &len) == 5 && !memcmp(buf, "dgram", 5), "recvfrom");
|
||||
{
|
||||
/* What musl's resolver does: poll with unused (negative) slots,
|
||||
* which report nothing, and recvmsg, which names the sender. */
|
||||
struct pollfd ps[3] = {{.fd = -1}, {.fd = -1}, {.fd = u1, .events = POLLIN}};
|
||||
struct sockaddr_in from = {0}, me = {0};
|
||||
struct iovec io = {buf, sizeof buf};
|
||||
struct msghdr mh = {.msg_name = &from, .msg_namelen = sizeof from, .msg_iov = &io, .msg_iovlen = 1};
|
||||
socklen_t ml = sizeof me;
|
||||
ok(sendto(u2, "again", 5, 0, (struct sockaddr *)&a, sizeof a) == 5, "sendto again");
|
||||
ok(poll(ps, 3, 5000) == 1 && ps[0].revents == 0 && ps[2].revents == POLLIN, "poll: negative slots report nothing");
|
||||
ok(recvmsg(u1, &mh, 0) == 5 && !memcmp(buf, "again", 5), "recvmsg");
|
||||
ok(getsockname(u2, (struct sockaddr *)&me, &ml) == 0 && mh.msg_namelen == sizeof from
|
||||
&& from.sin_family == AF_INET && from.sin_port == me.sin_port, "recvmsg names the sender");
|
||||
}
|
||||
close(u1); close(u2);
|
||||
printf("%d failures\n", fails);
|
||||
return fails != 0;
|
||||
|
||||
Reference in new issue
Block a user