wasi_sock (wasm v0.1.0)
View SourceSockets, as WASI descriptors.
Read this if you are changing how the socket syscalls behave. You grant the
network in wasi_net and the descriptor table lives in wasi_preview1; what is
left is here: opening, binding, connecting, moving bytes, and turning a POSIX
reason into the errno the module expects.
Sockets are passive ({active, false}) and owned by the process that opened
them, which is the instance process, so they close when it exits. Nothing here
spawns anything or holds state of its own.
A socket exists before it is a socket
sock_open gets a descriptor, not a file descriptor: nothing is opened until
the module says what the socket is for. gen_tcp has no unbound socket to hand
out, and holding an operating system socket for a descriptor that may never be
used would spend a real resource on a maybe. A pending handle records the
family and type; sock_bind remembers an address; sock_connect and
sock_listen are what actually open something.
Reading hands back what it could not deliver
gen_tcp:recv(S, 0, T) returns whatever has arrived, which can be more than the
caller's buffers hold. The excess goes back to the caller to keep against the
next read rather than being dropped, because dropping it loses bytes from the
middle of a stream, silently, and only under load.
Summary
Functions
Take the next inbound connection.
Claim a local address.
Connect to an already-checked address.
Map a POSIX reason onto a WASI errno.
Which address family this socket speaks.
The WASI filetype a descriptor holding this socket reports.
Start accepting. The address was checked when it was bound.
The local end, which is how a module learns the port an ephemeral bind got.
A descriptor with a family and a type, and nothing open behind it yet.
Bind and listen in one step, for a socket the host opens on the module's behalf. The endpoint has already been checked against the grant.
The remote end of a connected socket.
Read up to Want bytes, or whatever has arrived if Want is 0.
Read one datagram, and say where it came from.
Send all of Data, or fail.
Send one datagram to an already-checked address.
Half-close. How is the WASI sdflags bitmask: 1 read, 2 write.
Stream or datagram, whether or not anything is open yet.
Types
-nominal handle() :: {pending, inet | inet6, stream | dgram} | {bound, inet | inet6, stream, {inet:ip_address(), 0..65535}} | {listen, gen_tcp:socket()} | {stream, gen_tcp:socket()} | {dgram, gen_udp:socket()}.
An open socket, tagged with what it is.
The tag is what makes "accept on a connected socket" an errno rather than a
surprise: the operations that need a listener match on it. pending and
bound hold no operating system resource at all.
Functions
-spec accept(handle(), timeout()) -> {ok, handle()} | {error, non_neg_integer()}.
Take the next inbound connection.
-spec bind(handle(), wasi_net:endpoint()) -> {ok, handle()} | {error, non_neg_integer()}.
Claim a local address.
A datagram socket is opened here, because a bound datagram socket is already usable. A stream socket only remembers the address: whether it becomes a listener or the source address of an outbound connection is not known yet.
-spec close(handle()) -> ok.
-spec connect(handle(), wasi_net:endpoint(), timeout()) -> {ok, handle()} | {error, non_neg_integer()}.
Connect to an already-checked address.
The address arrives as a tuple and leaves as a tuple. No name is resolved here, so there is nothing between the capability check and the syscall that could move the target.
-spec errno(atom()) -> non_neg_integer().
Map a POSIX reason onto a WASI errno.
timeout becomes EAGAIN rather than ETIMEDOUT: a read that waited its
allowance and found nothing is the same event a non-blocking read reports, and
programs written against sockets already handle EAGAIN by retrying.
-spec family(handle()) -> inet | inet6.
Which address family this socket speaks.
-spec filetype(handle()) -> non_neg_integer().
The WASI filetype a descriptor holding this socket reports.
-spec getopt(handle(), non_neg_integer()) -> {ok, integer()} | {error, non_neg_integer()}.
-spec listen(handle(), non_neg_integer()) -> {ok, handle()} | {error, non_neg_integer()}.
Start accepting. The address was checked when it was bound.
-spec local(handle()) -> {ok, {inet:ip_address(), 0..65535}} | {error, non_neg_integer()}.
The local end, which is how a module learns the port an ephemeral bind got.
-spec open(inet | inet6, stream | dgram) -> {ok, handle()}.
A descriptor with a family and a type, and nothing open behind it yet.
-spec open_listener(wasi_net:endpoint()) -> {ok, handle()} | {error, non_neg_integer()}.
Bind and listen in one step, for a socket the host opens on the module's behalf. The endpoint has already been checked against the grant.
-spec peer(handle()) -> {ok, {inet:ip_address(), 0..65535}} | {error, non_neg_integer()}.
The remote end of a connected socket.
-spec recv(handle(), non_neg_integer(), timeout()) -> {ok, binary()} | eof | {error, non_neg_integer()}.
Read up to Want bytes, or whatever has arrived if Want is 0.
Returns everything the socket gave up. The caller decides how much of it fits in the module's buffers and keeps the rest.
-spec recv_from(handle(), non_neg_integer(), timeout()) -> {ok, binary(), {inet:ip_address(), 0..65535}} | {error, non_neg_integer()}.
Read one datagram, and say where it came from.
A datagram that does not fit is truncated, which is what the protocol does: the
rest of it is gone, and ROFLAGS_RECV_DATA_TRUNCATED is how the caller is told.
-spec send(handle(), binary()) -> ok | {error, non_neg_integer()}.
Send all of Data, or fail.
-spec send_to(handle(), binary(), wasi_net:endpoint()) -> {ok, handle()} | {error, non_neg_integer()}.
Send one datagram to an already-checked address.
-spec setopt(handle(), non_neg_integer(), integer()) -> ok | {error, non_neg_integer()}.
-spec shutdown(handle(), non_neg_integer()) -> ok | {error, non_neg_integer()}.
Half-close. How is the WASI sdflags bitmask: 1 read, 2 write.
-spec type(handle()) -> stream | dgram.
Stream or datagram, whether or not anything is open yet.