wasi_net (wasm v0.1.0)
View SourceThe network capability.
Write a grant to say which addresses and ports an instance may reach, and which it may accept on. It is evaluated here, in one place, with no sockets in sight, so you can test the decision directly rather than through a syscall.
#{net => #{connect => [{tcp, ~"10.0.0.0/8", {8000, 8099}}],
listen => [{tcp, ~"127.0.0.1", 8080}],
resolve => allow,
max_sockets => 32,
timeout => 30000}}Write a rule as {Proto, Addr, Port}. Proto is tcp or udp. Addr is an
IP tuple, a binary address, or a binary CIDR. Port is an integer, {Lo, Hi},
or any.
Leave out net and there is no network at all. Leave out connect and there is
no outbound, leave out listen and there is no inbound, so #{net => #{}}
grants nothing and is refused exactly as if you had left the key out.
You name addresses, never names
You cannot write a rule that says "example.com". A name would have to be
resolved to be checked and resolved again to be used, and the two answers can
differ. Naming addresses removes that window rather than narrowing it:
sock_connect checks the literal address it was handed and passes the same
parsed tuple to gen_tcp, so nothing between the check and the syscall can move
the target. sock_getaddrinfo is a separate capability, and its answers get no
authority from having been resolved.
IPv4-mapped IPv6
::ffff:127.0.0.1 reaches the same host as 127.0.0.1 on every stack, so a
matcher comparing tuples would let it past a 127.0.0.0/8 grant. Every address
is normalised out of the ::ffff:0:0/96 block before matching, and the
normalised form is what the caller then connects to, so the address checked and
the address used are the same one.
IPv4-compatible addresses (::1.2.3.4, the deprecated block) are not
normalised. ::0.0.0.1 and ::1 are the same address, so normalising the
block would make loopback ambiguous. They stay IPv6 and need an IPv6 rule.
What a rule does not do
Nothing is denied implicitly. ~"0.0.0.0/0" really does include link-local and
cloud metadata addresses, and this module will not second-guess you. Name what
you mean. See docs/security.md.
Summary
Types
A concrete peer, as the syscall layer knows it.
A parsed grant. none is no network at all.
What a socket call is asking to do.
Functions
Ask whether the grant permits this endpoint, for this kind of use.
The endpoints the host should open a listening socket on.
Parse the net value from a capability configuration.
How many sockets an instance may hold open at once.
Fold an IPv4-mapped IPv6 address onto the IPv4 address it reaches.
Parse a textual address. It takes everything inet:parse_address/1 does, and
normalises the result.
Is sock_getaddrinfo granted?
How long a blocking socket operation may wait.
Types
-nominal endpoint() :: {tcp | udp, inet:ip_address(), 0..65535}.
A concrete peer, as the syscall layer knows it.
-nominal grant() :: none | #{connect := [rule()], listen := [rule()], resolve := boolean(), max_sockets := pos_integer(), timeout := timeout()}.
A parsed grant. none is no network at all.
-nominal kind() :: connect | listen.
What a socket call is asking to do.
Functions
Ask whether the grant permits this endpoint, for this kind of use.
connect and listen are separate lists and neither implies the other. An
accepted connection is not checked against connect: inbound is not outbound,
and you granted the listener it arrived on already.
The endpoints the host should open a listening socket on.
Only a rule naming one address and one port describes something bindable. A CIDR or a port range is permission for the module to bind within it, which is the extension's business, not a socket the host can open on its behalf.
Parse the net value from a capability configuration.
It raises {bad_net_grant, Term} on anything it cannot read. This runs where
the import map is built, in your own process, because a typo in a CIDR is a
configuration error and you should hear about it as one rather than meeting it
later as a refused connection.
-spec max_sockets(grant()) -> non_neg_integer().
How many sockets an instance may hold open at once.
-spec normalise(inet:ip_address()) -> inet:ip_address().
Fold an IPv4-mapped IPv6 address onto the IPv4 address it reaches.
You get everything else back unchanged, including the IPv4-compatible block:
::0.0.0.1 and ::1 are the same address, so folding that block would make
loopback ambiguous.
-spec parse(binary() | string()) -> {ok, inet:ip_address()} | error.
Parse a textual address. It takes everything inet:parse_address/1 does, and
normalises the result.
Is sock_getaddrinfo granted?
How long a blocking socket operation may wait.