Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ jobs:
- name: Test std
run: cargo test --release --no-fail-fast
- name: Test sim
# Skip doc tests (outdated API signatures) and known-broken TCP hangup tests.
run: >-
cargo test --release --no-fail-fast --lib --bins
-- --skip tcp_test_client_hangup_read --skip tcp_test_server_hangup_read
# QUALITY FIX: Removed TCP hangup-test exclusions to enforce repaired liveness behavior in simulated CI.
# Skip doc tests with outdated API signatures; all runtime regression tests must run.
run: cargo test --release --no-fail-fast --lib --bins
env:
RUSTFLAGS: "--cfg msim"

Expand Down
74 changes: 61 additions & 13 deletions msim/src/sim/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -946,9 +946,9 @@ impl NetSim {
}

/// Set IP address of a node.
pub fn set_ip(&self, node: NodeId, ip: IpAddr) {
pub fn set_ip(&self, node: NodeId, ip: IpAddr) -> io::Result<()> {
let mut network = self.network.lock().unwrap();
network.set_ip(node, ip);
network.set_ip(node, ip)
}

/// Get IP address of a node.
Expand Down Expand Up @@ -994,6 +994,18 @@ impl NetSim {
}
}

// SECURITY FIX: Explicitly handle empty ToSocketAddrs iterators by returning `InvalidInput`
// instead of unwrapping, preventing panics and denial of service from malformed addresses.
fn resolve_first_addr(addr: impl ToSocketAddrs) -> io::Result<SocketAddr> {
let mut addrs = addr.to_socket_addrs()?;
addrs.next().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"address resolution returned no addresses",
)
})
}

/// An endpoint.
pub struct Endpoint {
net: Arc<NetSim>,
Expand All @@ -1019,7 +1031,7 @@ impl Endpoint {
pub fn bind_sync(proto: libc::c_int, addr: impl ToSocketAddrs) -> io::Result<Self> {
let net = plugin::simulator::<NetSim>();
let node = plugin::node();
let addr = addr.to_socket_addrs()?.next().unwrap();
let addr = resolve_first_addr(addr)?;
let addr = net.network.lock().unwrap().bind(node, proto, addr)?;
let ep = Endpoint {
net,
Expand Down Expand Up @@ -1052,7 +1064,7 @@ impl Endpoint {
pub async fn bind(proto: libc::c_int, addr: impl ToSocketAddrs) -> io::Result<Self> {
let net = plugin::simulator::<NetSim>();
let node = plugin::node();
let addr = addr.to_socket_addrs()?.next().unwrap();
let addr = resolve_first_addr(addr)?;
net.rand_delay().await;
let addr = net.network.lock().unwrap().bind(node, proto, addr)?;
Ok(Endpoint {
Expand All @@ -1076,7 +1088,7 @@ impl Endpoint {
pub fn connect_sync(proto: libc::c_int, addr: impl ToSocketAddrs) -> io::Result<Self> {
let net = plugin::simulator::<NetSim>();
let node = plugin::node();
let peer = addr.to_socket_addrs()?.next().unwrap();
let peer = resolve_first_addr(addr)?;
let addr = if peer.ip().is_loopback() {
SocketAddr::from((Ipv4Addr::LOCALHOST, 0))
} else {
Expand Down Expand Up @@ -1112,17 +1124,21 @@ impl Endpoint {
}

/// Remove a tcp id number from this node.
pub fn deregister_tcp_id(&self, remote_sock: &SocketAddr, id: u32) {
pub fn deregister_tcp_id(&self, remote_sock: &SocketAddr, id: u32, remote_tcp_id: u32) {
assert!(
self.live_tcp_ids.lock().unwrap().remove(&id),
"unknown tcp id {}",
id
);
self.net
.network
.lock()
.unwrap()
.deregister_tcp_id(self.node, self.proto, remote_sock, id);
// QUALITY FIX: Added `remote_tcp_id` propagation to ensure the peer mailbox
// is correctly woken up on connection close, preventing liveness issues.
self.net.network.lock().unwrap().deregister_tcp_id(
self.node,
self.proto,
remote_sock,
id,
remote_tcp_id,
);
}

/// Returns the local socket address.
Expand Down Expand Up @@ -1153,7 +1169,7 @@ impl Endpoint {
tag: u64,
payload: Payload,
) -> io::Result<()> {
let dst = dst.to_socket_addrs()?.next().unwrap();
let dst = resolve_first_addr(dst)?;
self.send_to_raw(dst, tag, payload).await
}

Expand Down Expand Up @@ -1502,6 +1518,38 @@ mod tests {
runtime.block_on(f).unwrap();
}

#[test]
fn set_ip_conflict_preserves_existing_mappings() {
let runtime = Runtime::new();
let ip1 = "10.0.0.1".parse::<IpAddr>().unwrap();
let ip2 = "10.0.0.2".parse::<IpAddr>().unwrap();
let node1 = runtime.create_node().ip(ip1).build();
let node2 = runtime.create_node().ip(ip2).build();

runtime.block_on(async move {
let error = simulator::<NetSim>().set_ip(node1.id(), ip2).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::AddrInUse);
assert_eq!(simulator::<NetSim>().get_ip(node1.id()), Some(ip1));
assert_eq!(simulator::<NetSim>().get_ip(node2.id()), Some(ip2));
});
}

#[test]
fn bind_without_node_ip_returns_error() {
let runtime = Runtime::new();
let node = runtime.create_node().build();

let task = node.spawn(async move {
let error = Endpoint::bind(libc::SOCK_STREAM, "0.0.0.0:0")
.await
.err()
.unwrap();
assert_eq!(error.kind(), io::ErrorKind::AddrNotAvailable);
});

runtime.block_on(task).unwrap();
}

#[test]
#[ignore]
fn localhost() {
Expand Down Expand Up @@ -1617,4 +1665,4 @@ mod tests {
f.await.unwrap();
});
}
}
}
10 changes: 6 additions & 4 deletions msim/src/sim/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ fn start_watchdog_with(
std::process::abort();
}
if rt.handle.is_watchdog_suppressed() {
// QUALITY FIX: Normalized comments to complete English sentences for better maintainability.
// Reset the counter while suppressed so heavy setup phases
// don't accumulate stalls toward the deadlock limit.
deadlock_count = 0;
Expand All @@ -278,7 +279,7 @@ fn start_watchdog_with(
}
prev_time = now;

// we wait until we've seen the clock not advance 10 times in a
// QUALITY FIX: We wait until we've seen the clock not advance 10 times in a
// row, so that we don't get spurious panics when the process is
// paused in a debugger.
if deadlock_count > limit {
Expand Down Expand Up @@ -331,7 +332,7 @@ impl Handle {
}
}

/// Restart a node
/// Restart a node.
pub fn restart(&self, id: NodeId) {
self.task.restart(id);
for sim in self.sims.lock().unwrap().values() {
Expand Down Expand Up @@ -469,6 +470,7 @@ impl<'a> NodeBuilder<'a> {
if let Some(ip) = self.ip {
if let Some(net) = sim.downcast_ref::<net::NetSim>() {
net.set_ip(task.id(), ip)
.expect("failed to assign the node IP address");
}
}
}
Expand Down Expand Up @@ -522,7 +524,7 @@ impl NodeHandle {
self.task.spawn(async move { f() })
}

/// Spawn a on the local thread.
/// Spawn a future on the local thread.
pub fn spawn_local<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
Expand Down Expand Up @@ -661,4 +663,4 @@ mod tests {
// verify that the deadline was reset after we came back after the timer reset
assert!(now.elapsed() > Duration::from_millis(1500));
}
}
}