Skip to content

Commit 767be41

Browse files
committed
refactor: reuse socket utilities
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent cfeb70e commit 767be41

File tree

15 files changed

+597
-768
lines changed

15 files changed

+597
-768
lines changed

crates/wasi/src/p2/host/network.rs

Lines changed: 1 addition & 329 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::p2::bindings::sockets::network::{
22
self, ErrorCode, IpAddress, IpAddressFamily, IpSocketAddress, Ipv4SocketAddress,
33
Ipv6SocketAddress,
44
};
5-
use crate::p2::network::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr};
65
use crate::p2::{IoView, SocketError, WasiImpl, WasiView};
6+
use crate::sockets::util::{from_ipv4_addr, from_ipv6_addr, to_ipv4_addr, to_ipv6_addr};
77
use anyhow::Error;
88
use rustix::io::Errno;
99
use std::io;
@@ -242,331 +242,3 @@ impl From<cap_net_ext::AddressFamily> for IpAddressFamily {
242242
}
243243
}
244244
}
245-
246-
pub(crate) mod util {
247-
use std::io;
248-
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
249-
use std::time::Duration;
250-
251-
use crate::sockets::SocketAddressFamily;
252-
use cap_net_ext::{AddressFamily, Blocking, UdpSocketExt};
253-
use rustix::fd::{AsFd, OwnedFd};
254-
use rustix::io::Errno;
255-
use rustix::net::sockopt;
256-
257-
pub fn validate_unicast(addr: &SocketAddr) -> io::Result<()> {
258-
match to_canonical(&addr.ip()) {
259-
IpAddr::V4(ipv4) => {
260-
if ipv4.is_multicast() || ipv4.is_broadcast() {
261-
Err(io::Error::new(
262-
io::ErrorKind::InvalidInput,
263-
"Both IPv4 broadcast and multicast addresses are not supported",
264-
))
265-
} else {
266-
Ok(())
267-
}
268-
}
269-
IpAddr::V6(ipv6) => {
270-
if ipv6.is_multicast() {
271-
Err(io::Error::new(
272-
io::ErrorKind::InvalidInput,
273-
"IPv6 multicast addresses are not supported",
274-
))
275-
} else {
276-
Ok(())
277-
}
278-
}
279-
}
280-
}
281-
282-
pub fn validate_remote_address(addr: &SocketAddr) -> io::Result<()> {
283-
if to_canonical(&addr.ip()).is_unspecified() {
284-
return Err(io::Error::new(
285-
io::ErrorKind::InvalidInput,
286-
"Remote address may not be `0.0.0.0` or `::`",
287-
));
288-
}
289-
290-
if addr.port() == 0 {
291-
return Err(io::Error::new(
292-
io::ErrorKind::InvalidInput,
293-
"Remote port may not be 0",
294-
));
295-
}
296-
297-
Ok(())
298-
}
299-
300-
pub fn validate_address_family(
301-
addr: &SocketAddr,
302-
socket_family: &SocketAddressFamily,
303-
) -> io::Result<()> {
304-
match (socket_family, addr.ip()) {
305-
(SocketAddressFamily::Ipv4, IpAddr::V4(_)) => Ok(()),
306-
(SocketAddressFamily::Ipv6, IpAddr::V6(ipv6)) => {
307-
if is_deprecated_ipv4_compatible(&ipv6) {
308-
// Reject IPv4-*compatible* IPv6 addresses. They have been deprecated
309-
// since 2006, OS handling of them is inconsistent and our own
310-
// validations don't take them into account either.
311-
// Note that these are not the same as IPv4-*mapped* IPv6 addresses.
312-
Err(io::Error::new(
313-
io::ErrorKind::InvalidInput,
314-
"IPv4-compatible IPv6 addresses are not supported",
315-
))
316-
} else if ipv6.to_ipv4_mapped().is_some() {
317-
Err(io::Error::new(
318-
io::ErrorKind::InvalidInput,
319-
"IPv4-mapped IPv6 address passed to an IPv6-only socket",
320-
))
321-
} else {
322-
Ok(())
323-
}
324-
}
325-
_ => Err(io::Error::new(
326-
io::ErrorKind::InvalidInput,
327-
"Address family mismatch",
328-
)),
329-
}
330-
}
331-
332-
// Can be removed once `IpAddr::to_canonical` becomes stable.
333-
pub fn to_canonical(addr: &IpAddr) -> IpAddr {
334-
match addr {
335-
IpAddr::V4(ipv4) => IpAddr::V4(*ipv4),
336-
IpAddr::V6(ipv6) => {
337-
if let Some(ipv4) = ipv6.to_ipv4_mapped() {
338-
IpAddr::V4(ipv4)
339-
} else {
340-
IpAddr::V6(*ipv6)
341-
}
342-
}
343-
}
344-
}
345-
346-
fn is_deprecated_ipv4_compatible(addr: &Ipv6Addr) -> bool {
347-
matches!(addr.segments(), [0, 0, 0, 0, 0, 0, _, _])
348-
&& *addr != Ipv6Addr::UNSPECIFIED
349-
&& *addr != Ipv6Addr::LOCALHOST
350-
}
351-
352-
/*
353-
* Syscalls wrappers with (opinionated) portability fixes.
354-
*/
355-
356-
pub fn udp_socket(family: AddressFamily, blocking: Blocking) -> io::Result<OwnedFd> {
357-
// Delegate socket creation to cap_net_ext. They handle a couple of things for us:
358-
// - On Windows: call WSAStartup if not done before.
359-
// - Set the NONBLOCK and CLOEXEC flags. Either immediately during socket creation,
360-
// or afterwards using ioctl or fcntl. Exact method depends on the platform.
361-
362-
let socket = cap_std::net::UdpSocket::new(family, blocking)?;
363-
Ok(OwnedFd::from(socket))
364-
}
365-
366-
pub fn udp_bind<Fd: AsFd>(sockfd: Fd, addr: &SocketAddr) -> rustix::io::Result<()> {
367-
rustix::net::bind(sockfd, addr).map_err(|error| match error {
368-
// See: https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-bind#:~:text=WSAENOBUFS
369-
// Windows returns WSAENOBUFS when the ephemeral ports have been exhausted.
370-
#[cfg(windows)]
371-
Errno::NOBUFS => Errno::ADDRINUSE,
372-
_ => error,
373-
})
374-
}
375-
376-
pub fn udp_disconnect<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<()> {
377-
match rustix::net::connect_unspec(sockfd) {
378-
// BSD platforms return an error even if the UDP socket was disconnected successfully.
379-
//
380-
// MacOS was kind enough to document this: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/connect.2.html
381-
// > Datagram sockets may dissolve the association by connecting to an
382-
// > invalid address, such as a null address or an address with the address
383-
// > family set to AF_UNSPEC (the error EAFNOSUPPORT will be harmlessly
384-
// > returned).
385-
//
386-
// ... except that this appears to be incomplete, because experiments
387-
// have shown that MacOS actually returns EINVAL, depending on the
388-
// address family of the socket.
389-
#[cfg(target_os = "macos")]
390-
Err(Errno::INVAL | Errno::AFNOSUPPORT) => Ok(()),
391-
r => r,
392-
}
393-
}
394-
395-
// Even though SO_REUSEADDR is a SOL_* level option, this function contain a
396-
// compatibility fix specific to TCP. That's why it contains the `_tcp_` infix instead of `_socket_`.
397-
pub fn set_tcp_reuseaddr<Fd: AsFd>(sockfd: Fd, value: bool) -> rustix::io::Result<()> {
398-
// When a TCP socket is closed, the system may
399-
// temporarily reserve that specific address+port pair in a so called
400-
// TIME_WAIT state. During that period, any attempt to rebind to that pair
401-
// will fail. Setting SO_REUSEADDR to true bypasses that behaviour. Unlike
402-
// the name "SO_REUSEADDR" might suggest, it does not allow multiple
403-
// active sockets to share the same local address.
404-
405-
// On Windows that behavior is the default, so there is no need to manually
406-
// configure such an option. But (!), Windows _does_ have an identically
407-
// named socket option which allows users to "hijack" active sockets.
408-
// This is definitely not what we want to do here.
409-
410-
// Microsoft's own documentation[1] states that we should set SO_EXCLUSIVEADDRUSE
411-
// instead (to the inverse value), however the github issue below[2] seems
412-
// to indicate that that may no longer be correct.
413-
// [1]: https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
414-
// [2]: https://github.com/python-trio/trio/issues/928
415-
416-
#[cfg(not(windows))]
417-
sockopt::set_socket_reuseaddr(sockfd, value)?;
418-
#[cfg(windows)]
419-
let _ = (sockfd, value);
420-
421-
Ok(())
422-
}
423-
424-
pub fn set_tcp_keepidle<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
425-
if value <= Duration::ZERO {
426-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
427-
return Err(Errno::INVAL);
428-
}
429-
430-
// Ensure that the value passed to the actual syscall never gets rounded down to 0.
431-
const MIN_SECS: u64 = 1;
432-
433-
// Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
434-
const MAX_SECS: u64 = i16::MAX as u64;
435-
436-
sockopt::set_tcp_keepidle(
437-
sockfd,
438-
value.clamp(Duration::from_secs(MIN_SECS), Duration::from_secs(MAX_SECS)),
439-
)
440-
}
441-
442-
pub fn set_tcp_keepintvl<Fd: AsFd>(sockfd: Fd, value: Duration) -> rustix::io::Result<()> {
443-
if value <= Duration::ZERO {
444-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
445-
return Err(Errno::INVAL);
446-
}
447-
448-
// Ensure that any fractional value passed to the actual syscall never gets rounded down to 0.
449-
const MIN_SECS: u64 = 1;
450-
451-
// Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
452-
const MAX_SECS: u64 = i16::MAX as u64;
453-
454-
sockopt::set_tcp_keepintvl(
455-
sockfd,
456-
value.clamp(Duration::from_secs(MIN_SECS), Duration::from_secs(MAX_SECS)),
457-
)
458-
}
459-
460-
pub fn set_tcp_keepcnt<Fd: AsFd>(sockfd: Fd, value: u32) -> rustix::io::Result<()> {
461-
if value == 0 {
462-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
463-
return Err(Errno::INVAL);
464-
}
465-
466-
const MIN_CNT: u32 = 1;
467-
// Cap it at Linux' maximum, which appears to have the lowest limit across our supported platforms.
468-
const MAX_CNT: u32 = i8::MAX as u32;
469-
470-
sockopt::set_tcp_keepcnt(sockfd, value.clamp(MIN_CNT, MAX_CNT))
471-
}
472-
473-
pub fn get_ip_ttl<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<u8> {
474-
sockopt::ip_ttl(sockfd)?
475-
.try_into()
476-
.map_err(|_| Errno::OPNOTSUPP)
477-
}
478-
479-
pub fn get_ipv6_unicast_hops<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<u8> {
480-
sockopt::ipv6_unicast_hops(sockfd)
481-
}
482-
483-
pub fn set_ip_ttl<Fd: AsFd>(sockfd: Fd, value: u8) -> rustix::io::Result<()> {
484-
match value {
485-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
486-
//
487-
// A well-behaved IP application should never send out new packets with TTL 0.
488-
// We validate the value ourselves because OS'es are not consistent in this.
489-
// On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
490-
0 => Err(Errno::INVAL),
491-
_ => sockopt::set_ip_ttl(sockfd, value.into()),
492-
}
493-
}
494-
495-
pub fn set_ipv6_unicast_hops<Fd: AsFd>(sockfd: Fd, value: u8) -> rustix::io::Result<()> {
496-
match value {
497-
0 => Err(Errno::INVAL), // See `set_ip_ttl`
498-
_ => sockopt::set_ipv6_unicast_hops(sockfd, Some(value)),
499-
}
500-
}
501-
502-
fn normalize_get_buffer_size(value: usize) -> usize {
503-
if cfg!(target_os = "linux") {
504-
// Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead.
505-
// getsockopt returns this internally doubled value.
506-
// We'll half the value to at least get it back into the same ballpark that the application requested it in.
507-
//
508-
// This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
509-
value / 2
510-
} else {
511-
value
512-
}
513-
}
514-
515-
fn normalize_set_buffer_size(value: usize) -> usize {
516-
value.clamp(1, i32::MAX as usize)
517-
}
518-
519-
pub fn get_socket_recv_buffer_size<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<usize> {
520-
let value = sockopt::socket_recv_buffer_size(sockfd)?;
521-
Ok(normalize_get_buffer_size(value))
522-
}
523-
524-
pub fn get_socket_send_buffer_size<Fd: AsFd>(sockfd: Fd) -> rustix::io::Result<usize> {
525-
let value = sockopt::socket_send_buffer_size(sockfd)?;
526-
Ok(normalize_get_buffer_size(value))
527-
}
528-
529-
pub fn set_socket_recv_buffer_size<Fd: AsFd>(
530-
sockfd: Fd,
531-
value: usize,
532-
) -> rustix::io::Result<()> {
533-
if value == 0 {
534-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
535-
return Err(Errno::INVAL);
536-
}
537-
538-
let value = normalize_set_buffer_size(value);
539-
540-
match sockopt::set_socket_recv_buffer_size(sockfd, value) {
541-
// Most platforms (Linux, Windows, Fuchsia, Solaris, Illumos, Haiku, ESP-IDF, ..and more?) treat the value
542-
// passed to SO_SNDBUF/SO_RCVBUF as a performance tuning hint and silently clamp the input if it exceeds
543-
// their capability.
544-
// As far as I can see, only the *BSD family views this option as a hard requirement and fails when the
545-
// value is out of range. We normalize this behavior in favor of the more commonly understood
546-
// "performance hint" semantics. In other words; even ENOBUFS is "Ok".
547-
// A future improvement could be to query the corresponding sysctl on *BSD platforms and clamp the input
548-
// `size` ourselves, to completely close the gap with other platforms.
549-
//
550-
// This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
551-
Err(Errno::NOBUFS) => Ok(()),
552-
r => r,
553-
}
554-
}
555-
556-
pub fn set_socket_send_buffer_size<Fd: AsFd>(
557-
sockfd: Fd,
558-
value: usize,
559-
) -> rustix::io::Result<()> {
560-
if value == 0 {
561-
// WIT: "If the provided value is 0, an `invalid-argument` error is returned."
562-
return Err(Errno::INVAL);
563-
}
564-
565-
let value = normalize_set_buffer_size(value);
566-
567-
match sockopt::set_socket_send_buffer_size(sockfd, value) {
568-
Err(Errno::NOBUFS) => Ok(()), // See set_socket_recv_buffer_size
569-
r => r,
570-
}
571-
}
572-
}

crates/wasi/src/p2/host/tcp.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,7 @@ where
197197
) -> SocketResult<()> {
198198
let table = self.table();
199199
let socket = table.get_mut(&this)?;
200-
let duration = Duration::from_nanos(value);
201-
socket.set_keep_alive_idle_time(duration)
200+
socket.set_keep_alive_idle_time(value)
202201
}
203202

204203
fn keep_alive_interval(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
@@ -249,7 +248,7 @@ where
249248
let table = self.table();
250249
let socket = table.get(&this)?;
251250

252-
Ok(socket.receive_buffer_size()? as u64)
251+
Ok(socket.receive_buffer_size()?)
253252
}
254253

255254
fn set_receive_buffer_size(
@@ -259,15 +258,14 @@ where
259258
) -> SocketResult<()> {
260259
let table = self.table();
261260
let socket = table.get_mut(&this)?;
262-
let value = value.try_into().unwrap_or(usize::MAX);
263261
socket.set_receive_buffer_size(value)
264262
}
265263

266264
fn send_buffer_size(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<u64> {
267265
let table = self.table();
268266
let socket = table.get(&this)?;
269267

270-
Ok(socket.send_buffer_size()? as u64)
268+
Ok(socket.send_buffer_size()?)
271269
}
272270

273271
fn set_send_buffer_size(
@@ -277,7 +275,6 @@ where
277275
) -> SocketResult<()> {
278276
let table = self.table();
279277
let socket = table.get_mut(&this)?;
280-
let value = value.try_into().unwrap_or(usize::MAX);
281278
socket.set_send_buffer_size(value)
282279
}
283280

0 commit comments

Comments
 (0)