componentized/directory

wasi:sockets

interface dependency

WASI socket interfaces for TCP and UDP

Interfaces

Worlds

WIT

package wasi:sockets@0.3.0;

@since(version = 0.3.0)
interface types {
  @since(version = 0.3.0)
  use wasi:clocks/types@0.3.0.{duration};

  /// Error codes.
  ///
  /// In theory, every API can return any error code.
  /// In practice, API's typically only return the errors documented per API
  /// combined with a couple of errors that are always possible:
  /// - `other`
  /// - `access-denied`
  /// - `not-supported`
  /// - `out-of-memory`
  ///
  /// See each individual API for what the POSIX equivalents are. They sometimes differ per API.
  @since(version = 0.3.0)
  variant error-code {
    /// Access denied.
    ///
    /// POSIX equivalent: EACCES, EPERM
    access-denied,
    /// The operation is not supported.
    ///
    /// POSIX equivalent: EOPNOTSUPP, ENOPROTOOPT, EPFNOSUPPORT, EPROTONOSUPPORT, ESOCKTNOSUPPORT
    not-supported,
    /// One of the arguments is invalid.
    ///
    /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT
    invalid-argument,
    /// Not enough memory to complete the operation.
    ///
    /// POSIX equivalent: ENOMEM, ENOBUFS
    out-of-memory,
    /// The operation timed out before it could finish completely.
    ///
    /// POSIX equivalent: ETIMEDOUT
    timeout,
    /// The operation is not valid in the socket's current state.
    invalid-state,
    /// The local address is not available.
    ///
    /// POSIX equivalent: EADDRNOTAVAIL
    address-not-bindable,
    /// A bind operation failed because the provided address is already in
    /// use or because there are no ephemeral ports available.
    ///
    /// POSIX equivalent: EADDRINUSE
    address-in-use,
    /// The remote address is not reachable.
    ///
    /// POSIX equivalent: EHOSTUNREACH, EHOSTDOWN, ENETDOWN, ENETUNREACH, ENONET
    remote-unreachable,
    /// The connection was forcefully rejected.
    ///
    /// POSIX equivalent: ECONNREFUSED
    connection-refused,
    /// A write failed because the connection was broken.
    ///
    /// POSIX equivalent: EPIPE
    connection-broken,
    /// The connection was reset.
    ///
    /// POSIX equivalent: ECONNRESET
    connection-reset,
    /// The connection was aborted.
    ///
    /// POSIX equivalent: ECONNABORTED
    connection-aborted,
    /// The size of a datagram sent to a UDP socket exceeded the maximum
    /// supported size.
    ///
    /// POSIX equivalent: EMSGSIZE
    datagram-too-large,
    /// A catch-all for errors not captured by the existing variants.
    /// Implementations can use this to extend the error type without
    /// breaking existing code.
    other(option<string>),
  }

  @since(version = 0.3.0)
  enum ip-address-family {
    /// Similar to `AF_INET` in POSIX.
    ipv4,
    /// Similar to `AF_INET6` in POSIX.
    ipv6,
  }

  @since(version = 0.3.0)
  type ipv4-address = tuple<u8, u8, u8, u8>;

  @since(version = 0.3.0)
  type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;

  @since(version = 0.3.0)
  variant ip-address {
    ipv4(ipv4-address),
    ipv6(ipv6-address),
  }

  @since(version = 0.3.0)
  record ipv4-socket-address {
    /// sin_port
    port: u16,
    /// sin_addr
    address: ipv4-address,
  }

  @since(version = 0.3.0)
  record ipv6-socket-address {
    /// sin6_port
    port: u16,
    /// sin6_flowinfo
    flow-info: u32,
    /// sin6_addr
    address: ipv6-address,
    /// sin6_scope_id
    scope-id: u32,
  }

  @since(version = 0.3.0)
  variant ip-socket-address {
    ipv4(ipv4-socket-address),
    ipv6(ipv6-socket-address),
  }

  /// A TCP socket resource.
  ///
  /// The socket can be in one of the following states:
  /// - `unbound`
  /// - `bound` (See note below)
  /// - `listening`
  /// - `connecting`
  /// - `connected`
  /// - `closed`
  /// See <https://github.com/WebAssembly/WASI/blob/main/proposals/sockets/TcpSocketOperationalSemantics-0.3.0.md>
  /// for more information.
  ///
  /// Note: Except where explicitly mentioned, whenever this documentation uses
  /// the term "bound" without backticks it actually means: in the `bound` state *or higher*.
  /// (i.e. `bound`, `listening`, `connecting` or `connected`)
  ///
  /// WASI uses shared ownership semantics: the `tcp-socket` handle and all
  /// derived `stream` and `future` values reference a single underlying OS
  /// socket:
  /// - Send/receive streams remain functional after the original `tcp-socket`
  ///   handle is dropped.
  /// - The stream returned by `listen` behaves similarly.
  /// - Client sockets returned by `tcp-socket::listen` are independent and do
  ///   not keep the listening socket alive.
  ///
  /// The OS socket is closed only after the last handle is dropped. This
  /// model has observable effects; for example, it affects when the local
  /// port binding is released.
  ///
  /// In addition to the general error codes documented on the
  /// `types::error-code` type, TCP socket methods may always return
  /// `error(invalid-state)` when in the `closed` state.
  @since(version = 0.3.0)
  resource tcp-socket {
    /// Create a new TCP socket.
    ///
    /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)`
    /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and
    /// can't be configured otherwise.
    ///
    /// Unlike POSIX, WASI sockets have no notion of a socket-level
    /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's
    /// async support.
    ///
    /// # Typical errors
    /// - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
    /// - <https://man7.org/linux/man-pages/man2/socket.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
    @since(version = 0.3.0)
    create: static func(address-family: ip-address-family) -> result<tcp-socket, error-code>;
    /// Bind the socket to the provided IP address and port.
    ///
    /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is
    /// left to the implementation to decide which network interface(s) to
    /// bind to. If the TCP/UDP port is zero, the socket will be bound to a
    /// random free port.
    ///
    /// Bind can be attempted multiple times on the same socket, even with
    /// different arguments on each iteration. But never concurrently and
    /// only as long as the previous bind failed. Once a bind succeeds, the
    /// binding can't be changed anymore.
    ///
    /// # Typical errors
    /// - `invalid-argument`:          The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
    /// - `invalid-argument`:          `local-address` is not a unicast address. (EINVAL)
    /// - `invalid-argument`:          `local-address` is an IPv4-mapped IPv6 address. (EINVAL)
    /// - `invalid-state`:             The socket is already bound. (EINVAL)
    /// - `address-in-use`:            No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)
    /// - `address-in-use`:            Address is already in use. (EADDRINUSE)
    /// - `address-not-bindable`:      `local-address` is not an address that can be bound to. (EADDRNOTAVAIL)
    ///
    /// # Implementors note
    /// The bind operation shouldn't be affected by the TIME_WAIT state of a
    /// recently closed socket on the same local address. In practice this
    /// means that the SO_REUSEADDR socket option should be set implicitly
    /// on all platforms, except on Windows where this is the default
    /// behavior and SO_REUSEADDR performs something different.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
    /// - <https://man7.org/linux/man-pages/man2/bind.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
    @since(version = 0.3.0)
    bind: func(local-address: ip-socket-address) -> result<_, error-code>;
    /// Connect to a remote endpoint.
    ///
    /// On success, the socket is transitioned into the `connected` state
    /// and the `remote-address` of the socket is updated.
    /// The `local-address` may be updated as well, based on the best network
    /// path to `remote-address`. If the socket was not already explicitly
    /// bound, this function will implicitly bind the socket to a random
    /// free port.
    ///
    /// After a failed connection attempt, the socket will be in the `closed`
    /// state and the only valid action left is to `drop` the socket. A single
    /// socket can not be used to connect more than once.
    ///
    /// # Typical errors
    /// - `invalid-argument`:          The `remote-address` has the wrong address family. (EAFNOSUPPORT)
    /// - `invalid-argument`:          `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS)
    /// - `invalid-argument`:          `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos)
    /// - `invalid-argument`:          The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows)
    /// - `invalid-argument`:          The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows)
    /// - `invalid-state`:             The socket is already in the `connecting` state. (EALREADY)
    /// - `invalid-state`:             The socket is already in the `connected` state. (EISCONN)
    /// - `invalid-state`:             The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows)
    /// - `timeout`:                   Connection timed out. (ETIMEDOUT)
    /// - `connection-refused`:        The connection was forcefully rejected. (ECONNREFUSED)
    /// - `connection-reset`:          The connection was reset. (ECONNRESET)
    /// - `connection-aborted`:        The connection was aborted. (ECONNABORTED)
    /// - `remote-unreachable`:        The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
    /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
    /// - <https://man7.org/linux/man-pages/man2/connect.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
    /// - <https://man.freebsd.org/cgi/man.cgi?connect>
    @since(version = 0.3.0)
    connect: async func(remote-address: ip-socket-address) -> result<_, error-code>;
    /// Start listening and return a stream of new inbound connections.
    ///
    /// Transitions the socket into the `listening` state. This can be called
    /// at most once per socket.
    ///
    /// If the socket is not already explicitly bound, this function will
    /// implicitly bind the socket to a random free port.
    ///
    /// Normally, the returned sockets are bound, in the `connected` state
    /// and immediately ready for I/O. Though, depending on exact timing and
    /// circumstances, a newly accepted connection may already be `closed`
    /// by the time the server attempts to perform its first I/O on it. This
    /// is true regardless of whether the WASI implementation uses
    /// "synthesized" sockets or not (see Implementors Notes below).
    ///
    /// The following properties are inherited from the listener socket:
    /// - `address-family`
    /// - `keep-alive-enabled`
    /// - `keep-alive-idle-time`
    /// - `keep-alive-interval`
    /// - `keep-alive-count`
    /// - `hop-limit`
    /// - `receive-buffer-size`
    /// - `send-buffer-size`
    ///
    /// # Typical errors
    /// - `invalid-state`:             The socket is already in the `connected` state. (EISCONN, EINVAL on BSD)
    /// - `invalid-state`:             The socket is already in the `listening` state.
    /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE)
    ///
    /// # Implementors note
    /// This method returns a single perpetual stream that should only close
    /// on fatal errors (if any). Yet, the POSIX' `accept` function may also
    /// return transient errors (e.g. ECONNABORTED). The exact details differ
    /// per operation system. For example, the Linux manual mentions:
    ///
    /// > Linux accept() passes already-pending network errors on the new
    /// > socket as an error code from accept(). This behavior differs from
    /// > other BSD socket implementations. For reliable operation the
    /// > application should detect the network errors defined for the
    /// > protocol after accept() and treat them like EAGAIN by retrying.
    /// > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT,
    /// > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH.
    /// Source: https://man7.org/linux/man-pages/man2/accept.2.html
    ///
    /// WASI implementations have two options to handle this:
    /// - Optionally log it and then skip over non-fatal errors returned by
    ///   `accept`. Guest code never gets to see these failures. Or:
    /// - Synthesize a `tcp-socket` resource that exposes the error when
    ///   attempting to send or receive on it. Guest code then sees these
    ///   failures as regular I/O errors.
    ///
    /// In either case, the stream returned by this `listen` method remains
    /// operational.
    ///
    /// WASI requires `listen` to perform an implicit bind if the socket
    /// has not already been bound. Not all platforms (notably Windows)
    /// exhibit this behavior out of the box. On platforms that require it,
    /// the WASI implementation can emulate this behavior by performing
    /// the bind itself if the guest hasn't already done so.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html>
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html>
    /// - <https://man7.org/linux/man-pages/man2/listen.2.html>
    /// - <https://man7.org/linux/man-pages/man2/accept.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2>
    @since(version = 0.3.0)
    listen: func() -> result<stream<tcp-socket>, error-code>;
    /// Transmit data to peer.
    ///
    /// The caller should close the stream when it has no more data to send
    /// to the peer. Under normal circumstances this will cause a FIN packet
    /// to be sent out. Closing the stream is equivalent to calling
    /// `shutdown(SHUT_WR)` in POSIX.
    ///
    /// This function may be called at most once and returns once the full
    /// contents of the stream are transmitted or an error is encountered.
    ///
    /// # Typical errors
    /// - `invalid-state`:             The socket is not in the `connected` state. (ENOTCONN)
    /// - `invalid-state`:             `send` has already been called on this socket.
    /// - `connection-broken`:         The connection is not writable anymore. (EPIPE, ECONNABORTED on Windows)
    /// - `connection-reset`:          The connection was reset. (ECONNRESET)
    /// - `remote-unreachable`:        The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
    ///
    ///  # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html>
    /// - <https://man7.org/linux/man-pages/man2/send.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2>
    @since(version = 0.3.0)
    send: func(data: stream<u8>) -> future<result<_, error-code>>;
    /// Read data from peer.
    ///
    /// Returns a `stream` of data sent by the peer. The implementation
    /// drops the stream once no more data is available. At that point, the
    /// returned `future` resolves to:
    /// - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or
    /// - `err` if the socket was closed abnormally.
    ///
    /// `receive` may be called only once per socket. Subsequent calls return
    /// a closed stream and a future resolved to `err(invalid-state)`.
    ///
    /// If the caller is not expecting to receive any more data from the peer,
    /// they should drop the stream. Any data still in the receive queue
    /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)`
    /// in POSIX.
    ///
    /// # Typical errors
    /// - `invalid-state`:             The socket is not in the `connected` state. (ENOTCONN)
    /// - `invalid-state`:             `receive` has already been called on this socket.
    /// - `connection-reset`:          The connection was reset. (ECONNRESET)
    /// - `remote-unreachable`:        The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html>
    /// - <https://man7.org/linux/man-pages/man2/recv.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2>
    @since(version = 0.3.0)
    receive: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
    /// Get the bound local address.
    ///
    /// POSIX mentions:
    /// > If the socket has not been bound to a local name, the value
    /// > stored in the object pointed to by `address` is unspecified.
    ///
    /// WASI is stricter and requires `get-local-address` to return
    /// `invalid-state` when the socket hasn't been bound yet.
    ///
    /// # Typical errors
    /// - `invalid-state`: The socket is not bound to any local address.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
    /// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
    /// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
    @since(version = 0.3.0)
    get-local-address: func() -> result<ip-socket-address, error-code>;
    /// Get the remote address.
    ///
    /// # Typical errors
    /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
    /// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
    @since(version = 0.3.0)
    get-remote-address: func() -> result<ip-socket-address, error-code>;
    /// Whether the socket is in the `listening` state.
    ///
    /// Equivalent to the SO_ACCEPTCONN socket option.
    @since(version = 0.3.0)
    get-is-listening: func() -> bool;
    /// Whether this is a IPv4 or IPv6 socket.
    ///
    /// This is the value passed to the constructor.
    ///
    /// Equivalent to the SO_DOMAIN socket option.
    @since(version = 0.3.0)
    get-address-family: func() -> ip-address-family;
    /// Hints the desired listen queue size. Implementations are free to
    /// ignore this.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// Any other value will never cause an error, but it might be silently
    /// clamped and/or rounded.
    ///
    /// # Typical errors
    /// - `not-supported`:        (set) The platform does not support changing the backlog size after the initial listen.
    /// - `invalid-argument`:     (set) The provided value was 0.
    /// - `invalid-state`:        (set) The socket is in the `connecting` or `connected` state.
    @since(version = 0.3.0)
    set-listen-backlog-size: func(value: u64) -> result<_, error-code>;
    /// Enables or disables keepalive.
    ///
    /// The keepalive behavior can be adjusted using:
    /// - `keep-alive-idle-time`
    /// - `keep-alive-interval`
    /// - `keep-alive-count`
    /// These properties can be configured while `keep-alive-enabled` is
    /// false, but only come into effect when `keep-alive-enabled` is true.
    ///
    /// Equivalent to the SO_KEEPALIVE socket option.
    @since(version = 0.3.0)
    get-keep-alive-enabled: func() -> result<bool, error-code>;
    @since(version = 0.3.0)
    set-keep-alive-enabled: func(value: bool) -> result<_, error-code>;
    /// Amount of time the connection has to be idle before TCP starts
    /// sending keepalive packets.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// All other values are accepted without error, but may be
    /// clamped or rounded. As a result, the value read back from
    /// this setting may differ from the value that was set.
    ///
    /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS)
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The provided value was 0.
    @since(version = 0.3.0)
    get-keep-alive-idle-time: func() -> result<duration, error-code>;
    @since(version = 0.3.0)
    set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>;
    /// The time between keepalive packets.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// All other values are accepted without error, but may be
    /// clamped or rounded. As a result, the value read back from
    /// this setting may differ from the value that was set.
    ///
    /// Equivalent to the TCP_KEEPINTVL socket option.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The provided value was 0.
    @since(version = 0.3.0)
    get-keep-alive-interval: func() -> result<duration, error-code>;
    @since(version = 0.3.0)
    set-keep-alive-interval: func(value: duration) -> result<_, error-code>;
    /// The maximum amount of keepalive packets TCP should send before
    /// aborting the connection.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// All other values are accepted without error, but may be
    /// clamped or rounded. As a result, the value read back from
    /// this setting may differ from the value that was set.
    ///
    /// Equivalent to the TCP_KEEPCNT socket option.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The provided value was 0.
    @since(version = 0.3.0)
    get-keep-alive-count: func() -> result<u32, error-code>;
    @since(version = 0.3.0)
    set-keep-alive-count: func(value: u32) -> result<_, error-code>;
    /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
    @since(version = 0.3.0)
    get-hop-limit: func() -> result<u8, error-code>;
    @since(version = 0.3.0)
    set-hop-limit: func(value: u8) -> result<_, error-code>;
    /// Kernel buffer space reserved for sending/receiving on this socket.
    /// Implementations usually treat this as a cap the buffer can grow to,
    /// rather than allocating the full amount immediately.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// All other values are accepted without error, but may be
    /// clamped or rounded. As a result, the value read back from
    /// this setting may differ from the value that was set.
    ///
    /// This is only a performance hint. The implementation may ignore it or
    /// tweak it based on real traffic patterns.
    /// Linux and macOS appear to behave differently depending on whether a
    /// buffer size was explicitly set. When set, they tend to honor it; when
    /// not set, they dynamically adjust the buffer size as the connection
    /// progresses. This is especially noticeable when comparing the values
    /// from before and after connection establishment.
    ///
    /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The provided value was 0.
    @since(version = 0.3.0)
    get-receive-buffer-size: func() -> result<u64, error-code>;
    @since(version = 0.3.0)
    set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
    @since(version = 0.3.0)
    get-send-buffer-size: func() -> result<u64, error-code>;
    @since(version = 0.3.0)
    set-send-buffer-size: func(value: u64) -> result<_, error-code>;
  }

  /// A UDP socket handle.
  @since(version = 0.3.0)
  resource udp-socket {
    /// Create a new UDP socket.
    ///
    /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)`
    /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and
    /// can't be configured otherwise.
    ///
    /// Unlike POSIX, WASI sockets have no notion of a socket-level
    /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's
    /// async support.
    ///
    /// # References:
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
    /// - <https://man7.org/linux/man-pages/man2/socket.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
    @since(version = 0.3.0)
    create: static func(address-family: ip-address-family) -> result<udp-socket, error-code>;
    /// Bind the socket to the provided IP address and port.
    ///
    /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is
    /// left to the implementation to decide which network interface(s) to
    /// bind to. If the port is zero, the socket will be bound to a random
    /// free port.
    ///
    /// # Typical errors
    /// - `invalid-argument`:          The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
    /// - `invalid-state`:             The socket is already bound. (EINVAL)
    /// - `address-in-use`:            No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)
    /// - `address-in-use`:            Address is already in use. (EADDRINUSE)
    /// - `address-not-bindable`:      `local-address` is not an address that can be bound to. (EADDRNOTAVAIL)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
    /// - <https://man7.org/linux/man-pages/man2/bind.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
    @since(version = 0.3.0)
    bind: func(local-address: ip-socket-address) -> result<_, error-code>;
    /// Associate this socket with a specific peer address.
    ///
    /// On success, the `remote-address` of the socket is updated.
    /// The `local-address` may be updated as well, based on the best network
    /// path to `remote-address`. If the socket was not already explicitly
    /// bound, this function will implicitly bind the socket to a random
    /// free port.
    ///
    /// When a UDP socket is "connected", the `send` and `receive` methods
    /// are limited to communicating with that peer only:
    /// - `send` can only be used to send to this destination.
    /// - `receive` will only return datagrams sent from the provided `remote-address`.
    ///
    /// The name "connect" was kept to align with the existing POSIX
    /// terminology. Other than that, this function only changes the local
    /// socket configuration and does not generate any network traffic.
    /// The peer is not aware of this "connection".
    ///
    /// This method may be called multiple times on the same socket to change
    /// its association, but only the most recent one will be effective.
    ///
    /// # Typical errors
    /// - `invalid-argument`:          The `remote-address` has the wrong address family. (EAFNOSUPPORT)
    /// - `invalid-argument`:          The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
    /// - `invalid-argument`:          The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
    /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
    ///
    /// # Implementors note
    /// If the socket is already connected, some platforms (e.g. Linux)
    /// require a disconnect before connecting to a different peer address.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
    /// - <https://man7.org/linux/man-pages/man2/connect.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
    /// - <https://man.freebsd.org/cgi/man.cgi?connect>
    @since(version = 0.3.0)
    connect: func(remote-address: ip-socket-address) -> result<_, error-code>;
    /// Dissociate this socket from its peer address.
    ///
    /// After calling this method, `send` & `receive` are free to communicate
    /// with any remote address again.
    ///
    /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address.
    ///
    /// # Typical errors
    /// - `invalid-state`:           The socket is not connected.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
    /// - <https://man7.org/linux/man-pages/man2/connect.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
    /// - <https://man.freebsd.org/cgi/man.cgi?connect>
    @since(version = 0.3.0)
    disconnect: func() -> result<_, error-code>;
    /// Send a message on the socket to a particular peer.
    ///
    /// If the socket is connected, the peer address may be left empty. In
    /// that case this is equivalent to `send` in POSIX. Otherwise it is
    /// equivalent to `sendto`.
    ///
    /// Additionally, if the socket is connected, a `remote-address` argument
    /// _may_ be provided but then it must be identical to the address
    /// passed to `connect`.
    ///
    /// If the socket has not been explicitly bound, it will be
    /// implicitly bound to a random free port.
    ///
    /// Implementations may trap if the `data` length exceeds 64 KiB.
    ///
    /// # Typical errors
    /// - `invalid-argument`:        The `remote-address` has the wrong address family. (EAFNOSUPPORT)
    /// - `invalid-argument`:        The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
    /// - `invalid-argument`:        The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
    /// - `invalid-argument`:        The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `connect`. (EISCONN)
    /// - `invalid-argument`:        The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ)
    /// - `remote-unreachable`:      The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
    /// - `connection-refused`:      The connection was refused. (ECONNREFUSED)
    /// - `datagram-too-large`:      The datagram is too large. (EMSGSIZE)
    /// - `address-in-use`:          Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE)
    ///
    /// # Implementors note
    /// WASI requires `send` to perform an implicit bind if the socket
    /// has not been bound. Not all platforms (notably Windows) exhibit
    /// this behavior natively. On such platforms, the WASI implementation
    /// should emulate it by performing the bind if the guest has not
    /// already done so.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html>
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html>
    /// - <https://man7.org/linux/man-pages/man2/send.2.html>
    /// - <https://man7.org/linux/man-pages/man2/sendmmsg.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasendmsg>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2>
    @since(version = 0.3.0)
    send: async func(data: list<u8>, remote-address: option<ip-socket-address>) -> result<_, error-code>;
    /// Receive a message on the socket.
    ///
    /// On success, the return value contains a tuple of the received data
    /// and the address of the sender. Theoretical maximum length of the
    /// data is 64 KiB. Though in practice, it will typically be less than
    /// 1500 bytes.
    ///
    /// If the socket is connected, the sender address is guaranteed to
    /// match the remote address passed to `connect`.
    ///
    /// # Typical errors
    /// - `invalid-state`:        The socket has not been bound yet.
    /// - `remote-unreachable`:   The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
    /// - `connection-refused`:   The connection was refused. (ECONNREFUSED)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html>
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html>
    /// - <https://man7.org/linux/man-pages/man2/recv.2.html>
    /// - <https://man7.org/linux/man-pages/man2/recvmmsg.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_wsarecvmsg>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2>
    @since(version = 0.3.0)
    receive: async func() -> result<tuple<list<u8>, ip-socket-address>, error-code>;
    /// Get the current bound address.
    ///
    /// POSIX mentions:
    /// > If the socket has not been bound to a local name, the value
    /// > stored in the object pointed to by `address` is unspecified.
    ///
    /// WASI is stricter and requires `get-local-address` to return
    /// `invalid-state` when the socket hasn't been bound yet.
    ///
    /// # Typical errors
    /// - `invalid-state`: The socket is not bound to any local address.
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
    /// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
    /// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
    @since(version = 0.3.0)
    get-local-address: func() -> result<ip-socket-address, error-code>;
    /// Get the address the socket is currently "connected" to.
    ///
    /// # Typical errors
    /// - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN)
    ///
    /// # References
    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
    /// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
    /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
    @since(version = 0.3.0)
    get-remote-address: func() -> result<ip-socket-address, error-code>;
    /// Whether this is a IPv4 or IPv6 socket.
    ///
    /// This is the value passed to the constructor.
    ///
    /// Equivalent to the SO_DOMAIN socket option.
    @since(version = 0.3.0)
    get-address-family: func() -> ip-address-family;
    /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.
    @since(version = 0.3.0)
    get-unicast-hop-limit: func() -> result<u8, error-code>;
    @since(version = 0.3.0)
    set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;
    /// Kernel buffer space reserved for sending/receiving on this socket.
    /// Implementations usually treat this as a cap the buffer can grow to,
    /// rather than allocating the full amount immediately.
    ///
    /// If the provided value is 0, an `invalid-argument` error is returned.
    /// All other values are accepted without error, but may be
    /// clamped or rounded. As a result, the value read back from
    /// this setting may differ from the value that was set.
    ///
    /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.
    ///
    /// # Typical errors
    /// - `invalid-argument`:     (set) The provided value was 0.
    @since(version = 0.3.0)
    get-receive-buffer-size: func() -> result<u64, error-code>;
    @since(version = 0.3.0)
    set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
    @since(version = 0.3.0)
    get-send-buffer-size: func() -> result<u64, error-code>;
    @since(version = 0.3.0)
    set-send-buffer-size: func(value: u64) -> result<_, error-code>;
  }
}

@since(version = 0.3.0)
interface ip-name-lookup {
  @since(version = 0.3.0)
  use types.{ip-address};

  /// Lookup error codes.
  @since(version = 0.3.0)
  variant error-code {
    /// Access denied.
    ///
    /// POSIX equivalent: EACCES, EPERM
    access-denied,
    /// `name` is a syntactically invalid domain name or IP address.
    ///
    /// POSIX equivalent: EINVAL
    invalid-argument,
    /// Name does not exist or has no suitable associated IP addresses.
    ///
    /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY
    name-unresolvable,
    /// A temporary failure in name resolution occurred.
    ///
    /// POSIX equivalent: EAI_AGAIN
    temporary-resolver-failure,
    /// A permanent failure in name resolution occurred.
    ///
    /// POSIX equivalent: EAI_FAIL
    permanent-resolver-failure,
    /// A catch-all for errors not captured by the existing variants.
    /// Implementations can use this to extend the error type without
    /// breaking existing code.
    other(option<string>),
  }

  /// Resolve an internet host name to a list of IP addresses.
  ///
  /// Unicode domain names are automatically converted to ASCII using IDNA
  /// encoding. If the input is an IP address string, the address is parsed
  /// and returned as-is without making any external requests.
  ///
  /// See the wasi-socket proposal README.md for a comparison with getaddrinfo.
  ///
  /// The results are returned in connection order preference.
  ///
  /// This function never succeeds with 0 results. It either fails or succeeds
  /// with at least one address. Additionally, this function never returns
  /// IPv4-mapped IPv6 addresses.
  ///
  /// # References:
  /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html>
  /// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html>
  /// - <https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo>
  /// - <https://man.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3>
  @since(version = 0.3.0)
  resolve-addresses: async func(name: string) -> result<list<ip-address>, error-code>;
}

@since(version = 0.3.0)
world imports {
  @since(version = 0.3.0)
  import wasi:clocks/types@0.3.0;
  @since(version = 0.3.0)
  import types;
  @since(version = 0.3.0)
  import ip-name-lookup;
}