Skip to content

Commit b0a83ce

Browse files
authored
Add connect_unspec (#903)
1 parent b01b482 commit b0a83ce

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

src/backend/libc/net/syscalls.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ pub(crate) fn connect_unix(sockfd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io:
248248
}
249249
}
250250

251+
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
252+
pub(crate) fn connect_unspec(sockfd: BorrowedFd<'_>) -> io::Result<()> {
253+
debug_assert_eq!(c::AF_UNSPEC, 0);
254+
let addr = MaybeUninit::<c::sockaddr_storage>::zeroed();
255+
unsafe {
256+
ret(c::connect(
257+
borrowed_fd(sockfd),
258+
as_ptr(&addr).cast(),
259+
size_of::<c::sockaddr_storage>() as c::socklen_t,
260+
))
261+
}
262+
}
263+
251264
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
252265
pub(crate) fn listen(sockfd: BorrowedFd<'_>, backlog: c::c_int) -> io::Result<()> {
253266
unsafe { ret(c::listen(borrowed_fd(sockfd), backlog)) }

src/backend/linux_raw/net/syscalls.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,34 @@ pub(crate) fn connect_unix(fd: BorrowedFd<'_>, addr: &SocketAddrUnix) -> io::Res
904904
}
905905
}
906906

907+
#[inline]
908+
pub(crate) fn connect_unspec(fd: BorrowedFd<'_>) -> io::Result<()> {
909+
debug_assert_eq!(c::AF_UNSPEC, 0);
910+
let addr = MaybeUninit::<c::sockaddr_storage>::zeroed();
911+
912+
#[cfg(not(target_arch = "x86"))]
913+
unsafe {
914+
ret(syscall_readonly!(
915+
__NR_connect,
916+
fd,
917+
by_ref(&addr),
918+
size_of::<c::sockaddr_storage, _>()
919+
))
920+
}
921+
#[cfg(target_arch = "x86")]
922+
unsafe {
923+
ret(syscall_readonly!(
924+
__NR_socketcall,
925+
x86_sys(SYS_CONNECT),
926+
slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
927+
fd.into(),
928+
by_ref(&addr),
929+
size_of::<c::sockaddr_storage, _>(),
930+
])
931+
))
932+
}
933+
}
934+
907935
#[inline]
908936
pub(crate) fn listen(fd: BorrowedFd<'_>, backlog: c::c_int) -> io::Result<()> {
909937
#[cfg(not(target_arch = "x86"))]

src/net/socket.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,41 @@ pub fn connect_unix<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrUnix) -> io::Result<(
453453
backend::net::syscalls::connect_unix(sockfd.as_fd(), addr)
454454
}
455455

456+
/// `connect(sockfd, {.sa_family = AF_UNSPEC}, sizeof(struct sockaddr))`
457+
/// — Dissolve the socket's association.
458+
///
459+
/// On UDP sockets, BSD platforms report AFNOSUPPORT or INVAL even if the disconnect was successful.
460+
///
461+
/// # References
462+
/// - [Beej's Guide to Network Programming]
463+
/// - [POSIX]
464+
/// - [Linux]
465+
/// - [Apple]
466+
/// - [Winsock2]
467+
/// - [FreeBSD]
468+
/// - [NetBSD]
469+
/// - [OpenBSD]
470+
/// - [DragonFly BSD]
471+
/// - [illumos]
472+
/// - [glibc]
473+
///
474+
/// [Beej's Guide to Network Programming]: https://beej.us/guide/bgnet/html/split/system-calls-or-bust.html#connect
475+
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html
476+
/// [Linux]: https://man7.org/linux/man-pages/man2/connect.2.html
477+
/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
478+
/// [Winsock2]: https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
479+
/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=connect&sektion=2
480+
/// [NetBSD]: https://man.netbsd.org/connect.2
481+
/// [OpenBSD]: https://man.openbsd.org/connect.2
482+
/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=connect&section=2
483+
/// [illumos]: https://illumos.org/man/3SOCKET/connect
484+
/// [glibc]: https://www.gnu.org/software/libc/manual/html_node/Connecting.html
485+
#[inline]
486+
#[doc(alias = "connect")]
487+
pub fn connect_unspec<Fd: AsFd>(sockfd: Fd) -> io::Result<()> {
488+
backend::net::syscalls::connect_unspec(sockfd.as_fd())
489+
}
490+
456491
/// `listen(fd, backlog)`—Enables listening for incoming connections.
457492
///
458493
/// # References

tests/net/connect_bind_send.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,94 @@ fn net_v6_connect() {
194194
assert_eq!(request, &response[..n]);
195195
}
196196

197+
/// Test `connect_unspec`.
198+
#[test]
199+
fn net_v4_connect_unspec() {
200+
const SOME_PORT: u16 = 47;
201+
let localhost_addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, SOME_PORT);
202+
203+
let socket = rustix::net::socket(AddressFamily::INET, SocketType::DGRAM, None).unwrap();
204+
205+
rustix::net::connect_v4(&socket, &localhost_addr).expect("connect_v4");
206+
assert_eq!(getsockname_v4(&socket).unwrap().ip(), &Ipv4Addr::LOCALHOST);
207+
assert_eq!(getpeername_v4(&socket).unwrap(), localhost_addr);
208+
209+
match rustix::net::connect_unspec(&socket) {
210+
// BSD platforms return an error even if the socket was disconnected successfully.
211+
#[cfg(bsd)]
212+
Err(rustix::io::Errno::INVAL | rustix::io::Errno::AFNOSUPPORT) => {}
213+
r => r.expect("connect_unspec"),
214+
}
215+
assert_eq!(
216+
getsockname_v4(&socket).unwrap().ip(),
217+
&Ipv4Addr::UNSPECIFIED
218+
);
219+
assert_eq!(getpeername_v4(&socket), Err(rustix::io::Errno::NOTCONN));
220+
221+
rustix::net::connect_v4(&socket, &localhost_addr).expect("connect_v4");
222+
assert_eq!(getsockname_v4(&socket).unwrap().ip(), &Ipv4Addr::LOCALHOST);
223+
assert_eq!(getpeername_v4(&socket).unwrap(), localhost_addr);
224+
225+
fn getsockname_v4<Fd: rustix::fd::AsFd>(sockfd: Fd) -> rustix::io::Result<SocketAddrV4> {
226+
match rustix::net::getsockname(sockfd)? {
227+
SocketAddrAny::V4(addr_v4) => Ok(addr_v4),
228+
_ => Err(rustix::io::Errno::AFNOSUPPORT),
229+
}
230+
}
231+
232+
fn getpeername_v4<Fd: rustix::fd::AsFd>(sockfd: Fd) -> rustix::io::Result<SocketAddrV4> {
233+
match rustix::net::getpeername(sockfd)? {
234+
Some(SocketAddrAny::V4(addr_v4)) => Ok(addr_v4),
235+
None => Err(rustix::io::Errno::NOTCONN),
236+
_ => Err(rustix::io::Errno::AFNOSUPPORT),
237+
}
238+
}
239+
}
240+
241+
/// Test `connect_unspec`.
242+
#[test]
243+
fn net_v6_connect_unspec() {
244+
const SOME_PORT: u16 = 47;
245+
let localhost_addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, SOME_PORT, 0, 0);
246+
247+
let socket = rustix::net::socket(AddressFamily::INET6, SocketType::DGRAM, None).unwrap();
248+
249+
rustix::net::connect_v6(&socket, &localhost_addr).expect("connect_v6");
250+
assert_eq!(getsockname_v6(&socket).unwrap().ip(), &Ipv6Addr::LOCALHOST);
251+
assert_eq!(getpeername_v6(&socket).unwrap(), localhost_addr);
252+
253+
match rustix::net::connect_unspec(&socket) {
254+
// BSD platforms return an error even if the socket was disconnected successfully.
255+
#[cfg(bsd)]
256+
Err(rustix::io::Errno::INVAL | rustix::io::Errno::AFNOSUPPORT) => {}
257+
r => r.expect("connect_unspec"),
258+
}
259+
assert_eq!(
260+
getsockname_v6(&socket).unwrap().ip(),
261+
&Ipv6Addr::UNSPECIFIED
262+
);
263+
assert_eq!(getpeername_v6(&socket), Err(rustix::io::Errno::NOTCONN));
264+
265+
rustix::net::connect_v6(&socket, &localhost_addr).expect("connect_v6");
266+
assert_eq!(getsockname_v6(&socket).unwrap().ip(), &Ipv6Addr::LOCALHOST);
267+
assert_eq!(getpeername_v6(&socket).unwrap(), localhost_addr);
268+
269+
fn getsockname_v6<Fd: rustix::fd::AsFd>(sockfd: Fd) -> rustix::io::Result<SocketAddrV6> {
270+
match rustix::net::getsockname(sockfd)? {
271+
SocketAddrAny::V6(addr_v6) => Ok(addr_v6),
272+
_ => Err(rustix::io::Errno::AFNOSUPPORT),
273+
}
274+
}
275+
276+
fn getpeername_v6<Fd: rustix::fd::AsFd>(sockfd: Fd) -> rustix::io::Result<SocketAddrV6> {
277+
match rustix::net::getpeername(sockfd)? {
278+
Some(SocketAddrAny::V6(addr_v6)) => Ok(addr_v6),
279+
None => Err(rustix::io::Errno::NOTCONN),
280+
_ => Err(rustix::io::Errno::AFNOSUPPORT),
281+
}
282+
}
283+
}
284+
197285
/// Test `bind_any`.
198286
#[test]
199287
fn net_v4_bind_any() {

0 commit comments

Comments
 (0)