Skip to content

Commit 49e00ac

Browse files
authored
feat: Support Bind and Listen opcodes (#325)
* feat: Support Bind and Listen opcodes * Create a separate test for bind/listen
1 parent 927bc45 commit 49e00ac

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

io-uring-test/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ fn test<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
140140

141141
tests::net::test_tcp_shutdown(&mut ring, &test)?;
142142
tests::net::test_socket(&mut ring, &test)?;
143+
tests::net::test_socket_bind_listen(&mut ring, &test)?;
143144
tests::net::test_udp_recvmsg_multishot(&mut ring, &test)?;
144145
tests::net::test_udp_recvmsg_multishot_trunc(&mut ring, &test)?;
145146
tests::net::test_udp_send_with_dest(&mut ring, &test)?;

io-uring-test/src/tests/net.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,143 @@ pub fn test_socket<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
15901590
Ok(())
15911591
}
15921592

1593+
pub fn test_socket_bind_listen<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
1594+
ring: &mut IoUring<S, C>,
1595+
test: &Test,
1596+
) -> anyhow::Result<()> {
1597+
use socket2::{Domain, Protocol, Socket, Type};
1598+
1599+
require!(
1600+
test;
1601+
test.probe.is_supported(opcode::Socket::CODE);
1602+
test.probe.is_supported(opcode::Bind::CODE);
1603+
test.probe.is_supported(opcode::Listen::CODE);
1604+
);
1605+
1606+
println!("test socket_bind_listen");
1607+
1608+
// Open a TCP socket through old-style `socket(2)` syscall.
1609+
// This is used both as a kernel sanity check, and for comparing the returned io-uring FD.
1610+
let plain_socket = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap();
1611+
let plain_fd = plain_socket.as_raw_fd();
1612+
1613+
let socket_fd_op = opcode::Socket::new(
1614+
Domain::IPV4.into(),
1615+
Type::STREAM.into(),
1616+
Protocol::TCP.into(),
1617+
);
1618+
unsafe {
1619+
ring.submission()
1620+
.push(&socket_fd_op.build().user_data(42).into())
1621+
.expect("queue is full");
1622+
}
1623+
ring.submit_and_wait(1)?;
1624+
1625+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
1626+
assert_eq!(cqes.len(), 1);
1627+
assert_eq!(cqes[0].user_data(), 42);
1628+
assert!(cqes[0].result() >= 0);
1629+
let io_uring_socket = unsafe { Socket::from_raw_fd(cqes[0].result()) };
1630+
assert!(io_uring_socket.as_raw_fd() != plain_fd);
1631+
assert_eq!(cqes[0].flags(), 0);
1632+
1633+
// Try to bind.
1634+
{
1635+
let server_addr: std::net::SocketAddr = "127.0.0.1:0".parse().unwrap();
1636+
let server_addr: socket2::SockAddr = server_addr.into();
1637+
let op = io_uring::opcode::Bind::new(
1638+
io_uring::types::Fd(io_uring_socket.as_raw_fd()),
1639+
server_addr.as_ptr() as *const _,
1640+
server_addr.len(),
1641+
)
1642+
.build()
1643+
.user_data(2345);
1644+
unsafe {
1645+
ring.submission().push(&op.into()).expect("queue is full");
1646+
}
1647+
ring.submit_and_wait(1)?;
1648+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
1649+
assert_eq!(cqes.len(), 1);
1650+
assert_eq!(cqes[0].user_data(), 2345);
1651+
assert_eq!(cqes[0].result(), 0);
1652+
assert_eq!(cqes[0].flags(), 0);
1653+
1654+
assert_eq!(
1655+
io_uring_socket
1656+
.local_addr()
1657+
.expect("no local addr")
1658+
.as_socket_ipv4()
1659+
.expect("no IPv4 address")
1660+
.ip(),
1661+
server_addr.as_socket_ipv4().unwrap().ip()
1662+
);
1663+
}
1664+
1665+
// Try to listen.
1666+
{
1667+
let op =
1668+
io_uring::opcode::Listen::new(io_uring::types::Fd(io_uring_socket.as_raw_fd()), 128)
1669+
.build()
1670+
.user_data(3456);
1671+
unsafe {
1672+
ring.submission().push(&op.into()).expect("queue is full");
1673+
}
1674+
ring.submit_and_wait(1)?;
1675+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
1676+
assert_eq!(cqes.len(), 1);
1677+
assert_eq!(cqes[0].user_data(), 3456);
1678+
assert_eq!(cqes[0].result(), 0);
1679+
assert_eq!(cqes[0].flags(), 0);
1680+
1681+
// Ensure the socket is actually in the listening state.
1682+
_ = TcpStream::connect(
1683+
io_uring_socket
1684+
.local_addr()
1685+
.unwrap()
1686+
.as_socket_ipv4()
1687+
.unwrap(),
1688+
)?;
1689+
}
1690+
1691+
// Close both sockets, to avoid leaking FDs.
1692+
drop(plain_socket);
1693+
drop(io_uring_socket);
1694+
1695+
// Cleanup all fixed files (if any), then reserve slot 0.
1696+
let _ = ring.submitter().unregister_files();
1697+
ring.submitter().register_files_sparse(1).unwrap();
1698+
1699+
let fixed_socket_op = opcode::Socket::new(
1700+
Domain::IPV4.into(),
1701+
Type::DGRAM.into(),
1702+
Protocol::UDP.into(),
1703+
);
1704+
let dest_slot = types::DestinationSlot::try_from_slot_target(0).unwrap();
1705+
unsafe {
1706+
ring.submission()
1707+
.push(
1708+
&fixed_socket_op
1709+
.file_index(Some(dest_slot))
1710+
.build()
1711+
.user_data(55)
1712+
.into(),
1713+
)
1714+
.expect("queue is full");
1715+
}
1716+
ring.submit_and_wait(1)?;
1717+
1718+
let cqes: Vec<cqueue::Entry> = ring.completion().map(Into::into).collect();
1719+
assert_eq!(cqes.len(), 1);
1720+
assert_eq!(cqes[0].user_data(), 55);
1721+
assert_eq!(cqes[0].result(), 0);
1722+
assert_eq!(cqes[0].flags(), 0);
1723+
1724+
// If the fixed-socket operation worked properly, this must not fail.
1725+
ring.submitter().unregister_files().unwrap();
1726+
1727+
Ok(())
1728+
}
1729+
15931730
pub fn test_udp_recvmsg_multishot<S: squeue::EntryMarker, C: cqueue::EntryMarker>(
15941731
ring: &mut IoUring<S, C>,
15951732
test: &Test,

src/opcode.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,3 +2062,49 @@ opcode! {
20622062
Entry(sqe)
20632063
}
20642064
}
2065+
2066+
// === 6.11 ===
2067+
2068+
opcode! {
2069+
/// Bind a socket, equivalent to `bind(2)`.
2070+
pub struct Bind {
2071+
fd: { impl sealed::UseFixed },
2072+
addr: { *const libc::sockaddr },
2073+
addrlen: { libc::socklen_t }
2074+
;;
2075+
}
2076+
2077+
pub const CODE = sys::IORING_OP_BIND;
2078+
2079+
pub fn build(self) -> Entry {
2080+
let Bind { fd, addr, addrlen } = self;
2081+
2082+
let mut sqe = sqe_zeroed();
2083+
sqe.opcode = Self::CODE;
2084+
assign_fd!(sqe.fd = fd);
2085+
sqe.__bindgen_anon_2.addr = addr as _;
2086+
sqe.__bindgen_anon_1.off = addrlen as _;
2087+
Entry(sqe)
2088+
}
2089+
}
2090+
2091+
opcode! {
2092+
/// Listen on a socket, equivalent to `listen(2)`.
2093+
pub struct Listen {
2094+
fd: { impl sealed::UseFixed },
2095+
backlog: { i32 },
2096+
;;
2097+
}
2098+
2099+
pub const CODE = sys::IORING_OP_LISTEN;
2100+
2101+
pub fn build(self) -> Entry {
2102+
let Listen { fd, backlog } = self;
2103+
2104+
let mut sqe = sqe_zeroed();
2105+
sqe.opcode = Self::CODE;
2106+
assign_fd!(sqe.fd = fd);
2107+
sqe.len = backlog as _;
2108+
Entry(sqe)
2109+
}
2110+
}

0 commit comments

Comments
 (0)