Skip to main content

wasmtime_wasi/sockets/
mod.rs

1use core::fmt;
2use core::future::Future;
3use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
4use core::ops::Deref;
5use rustix::fd::AsFd;
6use rustix::io::Errno;
7use rustix::net::sockopt;
8use std::pin::Pin;
9use std::sync::Arc;
10use std::task::Poll;
11use tracing::debug;
12use wasmtime::component::{HasData, ResourceTable};
13
14pub(crate) mod ip_name_lookup;
15mod tcp;
16mod udp;
17pub use tcp::TcpSocket;
18pub(crate) use tcp::{TcpListenStream, TcpReceiveStream, TcpSendStream};
19pub use udp::UdpSocket;
20
21/// A helper struct which implements [`HasData`] for the `wasi:sockets` APIs.
22///
23/// This can be useful when directly calling `add_to_linker` functions directly,
24/// such as [`wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker`] as the
25/// `D` type parameter. See [`HasData`] for more information about the type
26/// parameter's purpose.
27///
28/// When using this type you can skip the [`WasiSocketsView`] trait, for
29/// example.
30///
31/// [`wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker`]: crate::p2::bindings::sockets::tcp::add_to_linker
32///
33/// # Examples
34///
35/// ```
36/// use wasmtime::component::{Linker, ResourceTable};
37/// use wasmtime::{Engine, Result};
38/// use wasmtime_wasi::sockets::*;
39///
40/// struct MyStoreState {
41///     table: ResourceTable,
42///     sockets: WasiSocketsCtx,
43/// }
44///
45/// fn main() -> Result<()> {
46///     let engine = Engine::default();
47///     let mut linker = Linker::new(&engine);
48///
49///     wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker::<MyStoreState, WasiSockets>(
50///         &mut linker,
51///         |state| WasiSocketsCtxView {
52///             ctx: &mut state.sockets,
53///             table: &mut state.table,
54///         },
55///     )?;
56///     Ok(())
57/// }
58/// ```
59pub struct WasiSockets;
60
61impl HasData for WasiSockets {
62    type Data<'a> = WasiSocketsCtxView<'a>;
63}
64
65#[derive(Clone, Default)]
66pub struct WasiSocketsCtx {
67    pub(crate) socket_addr_check: SocketAddrCheck,
68    pub(crate) allowed_network_uses: AllowedNetworkUses,
69}
70
71pub struct WasiSocketsCtxView<'a> {
72    pub ctx: &'a mut WasiSocketsCtx,
73    pub table: &'a mut ResourceTable,
74}
75
76pub trait WasiSocketsView: Send {
77    fn sockets(&mut self) -> WasiSocketsCtxView<'_>;
78}
79
80#[derive(Copy, Clone, Default)]
81pub(crate) struct AllowedNetworkUses {
82    pub(crate) ip_name_lookup: bool,
83    pub(crate) udp: bool,
84    pub(crate) tcp: bool,
85}
86
87impl AllowedNetworkUses {
88    pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
89        if !self.udp {
90            return Err(std::io::Error::new(
91                std::io::ErrorKind::PermissionDenied,
92                "UDP is not allowed",
93            ));
94        }
95
96        Ok(())
97    }
98
99    pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
100        if !self.tcp {
101            return Err(std::io::Error::new(
102                std::io::ErrorKind::PermissionDenied,
103                "TCP is not allowed",
104            ));
105        }
106
107        Ok(())
108    }
109}
110
111/// A check that will be called for each socket address that is used of whether the address is permitted.
112#[derive(Clone)]
113pub(crate) struct SocketAddrCheck(
114    Arc<
115        dyn Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
116            + Send
117            + Sync,
118    >,
119);
120
121impl SocketAddrCheck {
122    /// A check that will be called for each socket address that is used.
123    ///
124    /// Returning `true` will permit socket connections to the `SocketAddr`,
125    /// while returning `false` will reject the connection.
126    pub(crate) fn new(
127        f: impl Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
128        + Send
129        + Sync
130        + 'static,
131    ) -> Self {
132        Self(Arc::new(f))
133    }
134
135    pub(crate) async fn check(
136        &self,
137        addr: SocketAddr,
138        reason: SocketAddrUse,
139    ) -> std::io::Result<()> {
140        if (self.0)(addr, reason).await {
141            Ok(())
142        } else {
143            Err(std::io::Error::new(
144                std::io::ErrorKind::PermissionDenied,
145                "An address was not permitted by the socket address check.",
146            ))
147        }
148    }
149}
150
151impl Deref for SocketAddrCheck {
152    type Target = dyn Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
153        + Send
154        + Sync;
155
156    fn deref(&self) -> &Self::Target {
157        self.0.as_ref()
158    }
159}
160
161impl Default for SocketAddrCheck {
162    fn default() -> Self {
163        Self(Arc::new(|_, _| Box::pin(async { false })))
164    }
165}
166
167/// The reason what a socket address is being used for.
168#[derive(Clone, Copy, Debug)]
169pub enum SocketAddrUse {
170    /// Binding TCP socket.
171    ///
172    /// This is invoked for both explicit calls to `bind` as well as implicit
173    /// binds that are about to be performed by the OS as part of
174    /// e.g. `connect` & `listen`.
175    ///
176    /// The address that is passed to the check is the address provided to
177    /// `bind` for explicit binds, or the wildcard address for implicit binds.
178    TcpBind,
179
180    /// Put a TCP socket in listener mode.
181    ///
182    /// If the socket was already bound at the time of the call, the actual
183    /// local address of the socket is passed to the check. If the socket is
184    /// about to be implicitly bound by `listen`, the wildcard address is passed.
185    TcpListen,
186
187    /// Accepting a new client TCP socket.
188    ///
189    /// The address passed to the check is the remote address of the client that
190    /// is being accepted. If the check fails, the client socket will be
191    /// silently dropped before reaching the guest.
192    TcpAccept,
193
194    /// Connecting a TCP socket.
195    ///
196    /// The address passed to the check is the remote address that the socket is
197    /// attempting to connect to.
198    TcpConnect,
199
200    /// Binding UDP socket.
201    ///
202    /// This is invoked for both explicit calls to `bind` as well as implicit
203    /// binds that are about to be performed by the OS as part of
204    /// e.g. `connect` & `send`.
205    ///
206    /// The address that is passed to the check is the address provided to
207    /// `bind` for explicit binds, or the wildcard address for implicit binds.
208    UdpBind,
209
210    /// Sending a datagram on a UDP socket.
211    ///
212    /// The address passed to the check is the remote address that the socket is
213    /// attempting to send to.
214    UdpSend,
215
216    /// Receiving a datagram on a UDP socket.
217    ///
218    /// The address passed to the check is the remote address of the datagram
219    /// that is being received. If the check fails, the datagram will be
220    /// silently dropped before reaching the guest.
221    UdpReceive,
222}
223
224#[derive(Copy, Clone, Eq, PartialEq)]
225pub(crate) enum SocketAddressFamily {
226    Ipv4,
227    Ipv6,
228}
229
230/// A utility type that separates
231/// (1) polling a future for completion and
232/// (2) obtaining the output of a future
233/// into separate operations. This is a common pattern in WASI 0.2.
234pub(crate) enum MaybeReady<T> {
235    Pending(Pin<Box<dyn Future<Output = T> + Send>>),
236    Ready(T),
237}
238impl<T> MaybeReady<T> {
239    pub(crate) fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
240        Self::Pending(Box::pin(fut))
241    }
242
243    /// Poll the future and attempt to resolve it immediately. If the future is
244    /// not ready yet, it will be moved to a background task.
245    pub(crate) fn poll_or_spawn(fut: impl Future<Output = T> + Send + 'static) -> Self
246    where
247        T: Send + 'static,
248    {
249        let mut fut = Box::pin(fut);
250        match crate::runtime::with_ambient_tokio_runtime(|| fut.as_mut().poll(&mut noop_cx())) {
251            Poll::Ready(val) => Self::Ready(val),
252            Poll::Pending => Self::new(crate::runtime::spawn(fut)),
253        }
254    }
255    pub(crate) fn unwrap_ready(self) -> T {
256        match self {
257            Self::Ready(val) => val,
258            Self::Pending(_) => panic!("future not ready"),
259        }
260    }
261    pub(crate) fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<&mut T> {
262        match self {
263            Self::Pending(fut) => match fut.as_mut().poll(cx) {
264                Poll::Ready(val) => {
265                    *self = Self::Ready(val);
266                    Poll::Ready(match self {
267                        Self::Ready(val) => val,
268                        _ => unreachable!(),
269                    })
270                }
271                Poll::Pending => Poll::Pending,
272            },
273            Self::Ready(val) => Poll::Ready(val),
274        }
275    }
276    pub(crate) async fn into_future(self) -> T {
277        match self {
278            Self::Ready(val) => val,
279            Self::Pending(fut) => fut.await,
280        }
281    }
282}
283
284pub(crate) fn noop_cx() -> std::task::Context<'static> {
285    std::task::Context::from_waker(futures::task::noop_waker_ref())
286}
287
288#[derive(Clone, Copy, Debug)]
289pub enum ErrorCode {
290    AccessDenied,
291    NotSupported,
292    InvalidArgument,
293    OutOfMemory,
294    Timeout,
295    InvalidState,
296    AddressNotBindable,
297    AddressInUse,
298    RemoteUnreachable,
299    ConnectionRefused,
300    ConnectionBroken,
301    ConnectionReset,
302    ConnectionAborted,
303    DatagramTooLarge,
304    Other,
305}
306
307impl fmt::Display for ErrorCode {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        fmt::Debug::fmt(self, f)
310    }
311}
312
313impl std::error::Error for ErrorCode {}
314
315impl From<std::io::Error> for ErrorCode {
316    fn from(value: std::io::Error) -> Self {
317        (&value).into()
318    }
319}
320
321impl From<&std::io::Error> for ErrorCode {
322    fn from(value: &std::io::Error) -> Self {
323        // Attempt the more detailed native error code first:
324        if let Some(errno) = Errno::from_io_error(value) {
325            return errno.into();
326        }
327
328        match value.kind() {
329            std::io::ErrorKind::AddrInUse => Self::AddressInUse,
330            std::io::ErrorKind::AddrNotAvailable => Self::AddressNotBindable,
331            std::io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
332            std::io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
333            std::io::ErrorKind::ConnectionReset => Self::ConnectionReset,
334            std::io::ErrorKind::InvalidInput => Self::InvalidArgument,
335            std::io::ErrorKind::NotConnected => Self::InvalidState,
336            std::io::ErrorKind::OutOfMemory => Self::OutOfMemory,
337            std::io::ErrorKind::PermissionDenied => Self::AccessDenied,
338            std::io::ErrorKind::TimedOut => Self::Timeout,
339            std::io::ErrorKind::Unsupported => Self::NotSupported,
340            std::io::ErrorKind::HostUnreachable => Self::RemoteUnreachable,
341            std::io::ErrorKind::NetworkUnreachable => Self::RemoteUnreachable,
342            std::io::ErrorKind::NetworkDown => Self::RemoteUnreachable,
343            std::io::ErrorKind::BrokenPipe => Self::ConnectionBroken,
344            _ => {
345                debug!("unknown I/O error: {value}");
346                Self::Other
347            }
348        }
349    }
350}
351
352impl From<Errno> for ErrorCode {
353    fn from(value: Errno) -> Self {
354        (&value).into()
355    }
356}
357
358impl From<&Errno> for ErrorCode {
359    fn from(value: &Errno) -> Self {
360        match *value {
361            #[cfg(not(windows))]
362            Errno::PERM => Self::AccessDenied,
363            Errno::ACCESS => Self::AccessDenied,
364            Errno::ADDRINUSE => Self::AddressInUse,
365            Errno::ADDRNOTAVAIL => Self::AddressNotBindable,
366            Errno::TIMEDOUT => Self::Timeout,
367            #[cfg(not(windows))]
368            Errno::PIPE => Self::ConnectionBroken,
369            Errno::CONNREFUSED => Self::ConnectionRefused,
370            Errno::CONNRESET => Self::ConnectionReset,
371            Errno::CONNABORTED => Self::ConnectionAborted,
372            Errno::INVAL => Self::InvalidArgument,
373            Errno::HOSTUNREACH => Self::RemoteUnreachable,
374            Errno::HOSTDOWN => Self::RemoteUnreachable,
375            Errno::NETDOWN => Self::RemoteUnreachable,
376            Errno::NETUNREACH => Self::RemoteUnreachable,
377            #[cfg(target_os = "linux")]
378            Errno::NONET => Self::RemoteUnreachable,
379            Errno::ISCONN => Self::InvalidState,
380            Errno::NOTCONN => Self::InvalidState,
381            Errno::DESTADDRREQ => Self::InvalidState,
382            Errno::MSGSIZE => Self::DatagramTooLarge,
383            #[cfg(not(windows))]
384            Errno::NOMEM => Self::OutOfMemory,
385            Errno::NOBUFS => Self::OutOfMemory,
386            Errno::OPNOTSUPP => Self::NotSupported,
387            Errno::NOPROTOOPT => Self::NotSupported,
388            Errno::PFNOSUPPORT => Self::NotSupported,
389            Errno::PROTONOSUPPORT => Self::NotSupported,
390            Errno::PROTOTYPE => Self::NotSupported,
391            Errno::SOCKTNOSUPPORT => Self::NotSupported,
392            Errno::AFNOSUPPORT => Self::NotSupported,
393
394            // FYI, EINPROGRESS should have already been handled by connect.
395            _ => {
396                debug!("unknown I/O error: {value}");
397                Self::Other
398            }
399        }
400    }
401}
402
403fn is_deprecated_ipv4_compatible(addr: Ipv6Addr) -> bool {
404    matches!(addr.segments(), [0, 0, 0, 0, 0, 0, _, _])
405        && addr != Ipv6Addr::UNSPECIFIED
406        && addr != Ipv6Addr::LOCALHOST
407}
408
409pub(crate) fn is_valid_address_family(addr: IpAddr, socket_family: SocketAddressFamily) -> bool {
410    match (socket_family, addr) {
411        (SocketAddressFamily::Ipv4, IpAddr::V4(..)) => true,
412        (SocketAddressFamily::Ipv6, IpAddr::V6(ipv6)) => {
413            // Reject IPv4-*compatible* IPv6 addresses. They have been deprecated
414            // since 2006, OS handling of them is inconsistent and our own
415            // validations don't take them into account either.
416            // Note that these are not the same as IPv4-*mapped* IPv6 addresses.
417            !is_deprecated_ipv4_compatible(ipv6) && ipv6.to_ipv4_mapped().is_none()
418        }
419        _ => false,
420    }
421}
422
423pub(crate) fn is_valid_remote_address(addr: SocketAddr) -> bool {
424    !addr.ip().to_canonical().is_unspecified() && addr.port() != 0
425}
426
427pub(crate) fn is_valid_unicast_address(addr: IpAddr) -> bool {
428    match addr.to_canonical() {
429        IpAddr::V4(ipv4) => !ipv4.is_multicast() && !ipv4.is_broadcast(),
430        IpAddr::V6(ipv6) => !ipv6.is_multicast(),
431    }
432}
433
434pub(crate) fn to_ipv4_addr(addr: (u8, u8, u8, u8)) -> Ipv4Addr {
435    let (x0, x1, x2, x3) = addr;
436    Ipv4Addr::new(x0, x1, x2, x3)
437}
438
439pub(crate) fn from_ipv4_addr(addr: Ipv4Addr) -> (u8, u8, u8, u8) {
440    let [x0, x1, x2, x3] = addr.octets();
441    (x0, x1, x2, x3)
442}
443
444pub(crate) fn to_ipv6_addr(addr: (u16, u16, u16, u16, u16, u16, u16, u16)) -> Ipv6Addr {
445    let (x0, x1, x2, x3, x4, x5, x6, x7) = addr;
446    Ipv6Addr::new(x0, x1, x2, x3, x4, x5, x6, x7)
447}
448
449pub(crate) fn from_ipv6_addr(addr: Ipv6Addr) -> (u16, u16, u16, u16, u16, u16, u16, u16) {
450    let [x0, x1, x2, x3, x4, x5, x6, x7] = addr.segments();
451    (x0, x1, x2, x3, x4, x5, x6, x7)
452}
453
454/*
455 * Syscalls wrappers with (opinionated) portability fixes.
456 */
457
458fn normalize_get_buffer_size(value: usize) -> usize {
459    if cfg!(target_os = "linux") {
460        // Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead.
461        // getsockopt returns this internally doubled value.
462        // We'll half the value to at least get it back into the same ballpark that the application requested it in.
463        //
464        // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
465        value / 2
466    } else {
467        value
468    }
469}
470
471fn normalize_set_buffer_size(value: usize) -> usize {
472    value.clamp(1, i32::MAX as usize)
473}
474
475fn get_ip_ttl(fd: impl AsFd) -> Result<u8, ErrorCode> {
476    let v = sockopt::ip_ttl(fd)?;
477    let Ok(v) = v.try_into() else {
478        return Err(ErrorCode::NotSupported);
479    };
480    Ok(v)
481}
482
483fn get_ipv6_unicast_hops(fd: impl AsFd) -> Result<u8, ErrorCode> {
484    let v = sockopt::ipv6_unicast_hops(fd)?;
485    Ok(v)
486}
487
488pub(crate) fn get_unicast_hop_limit(
489    fd: impl AsFd,
490    family: SocketAddressFamily,
491) -> Result<u8, ErrorCode> {
492    match family {
493        SocketAddressFamily::Ipv4 => get_ip_ttl(fd),
494        SocketAddressFamily::Ipv6 => get_ipv6_unicast_hops(fd),
495    }
496}
497
498pub(crate) fn set_unicast_hop_limit(
499    fd: impl AsFd,
500    family: SocketAddressFamily,
501    value: u8,
502) -> Result<(), ErrorCode> {
503    if value == 0 {
504        // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
505        //
506        // A well-behaved IP application should never send out new packets with TTL 0.
507        // We validate the value ourselves because OS'es are not consistent in this.
508        // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
509        return Err(ErrorCode::InvalidArgument);
510    }
511    match family {
512        SocketAddressFamily::Ipv4 => {
513            sockopt::set_ip_ttl(fd, value.into())?;
514        }
515        SocketAddressFamily::Ipv6 => {
516            sockopt::set_ipv6_unicast_hops(fd, Some(value))?;
517        }
518    }
519    Ok(())
520}
521
522pub(crate) fn get_receive_buffer_size(fd: impl AsFd) -> Result<u64, ErrorCode> {
523    let v = sockopt::socket_recv_buffer_size(fd)?;
524    Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX))
525}
526
527pub(crate) fn set_receive_buffer_size(fd: impl AsFd, value: u64) -> Result<usize, ErrorCode> {
528    if value == 0 {
529        // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
530        return Err(ErrorCode::InvalidArgument);
531    }
532    let value = value.try_into().unwrap_or(usize::MAX);
533    let value = normalize_set_buffer_size(value);
534    match sockopt::set_socket_recv_buffer_size(fd, value) {
535        // Most platforms (Linux, Windows, Fuchsia, Solaris, Illumos, Haiku, ESP-IDF, ..and more?) treat the value
536        // passed to SO_SNDBUF/SO_RCVBUF as a performance tuning hint and silently clamp the input if it exceeds
537        // their capability.
538        // As far as I can see, only the *BSD family views this option as a hard requirement and fails when the
539        // value is out of range. We normalize this behavior in favor of the more commonly understood
540        // "performance hint" semantics. In other words; even ENOBUFS is "Ok".
541        // A future improvement could be to query the corresponding sysctl on *BSD platforms and clamp the input
542        // `size` ourselves, to completely close the gap with other platforms.
543        //
544        // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
545        Err(Errno::NOBUFS) => {}
546        Err(err) => return Err(err.into()),
547        _ => {}
548    };
549    Ok(value)
550}
551
552pub(crate) fn get_send_buffer_size(fd: impl AsFd) -> Result<u64, ErrorCode> {
553    let v = sockopt::socket_send_buffer_size(fd)?;
554    Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX))
555}
556
557pub(crate) fn set_send_buffer_size(fd: impl AsFd, value: u64) -> Result<usize, ErrorCode> {
558    if value == 0 {
559        // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
560        return Err(ErrorCode::InvalidArgument);
561    }
562    let value = value.try_into().unwrap_or(usize::MAX);
563    let value = normalize_set_buffer_size(value);
564    match sockopt::set_socket_send_buffer_size(fd, value) {
565        // See comment in `set_receive_buffer_size` for why we ignore NOBUFS.
566        Err(Errno::NOBUFS) => {}
567        Err(err) => return Err(err.into()),
568        _ => {}
569    };
570    Ok(value)
571}
572
573pub(crate) fn unspecified_addr(family: SocketAddressFamily) -> SocketAddr {
574    let ip = match family {
575        SocketAddressFamily::Ipv4 => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
576        SocketAddressFamily::Ipv6 => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
577    };
578    SocketAddr::new(ip, 0)
579}