Networking
pkg std =
/* general networking */
type netaddr = union
`Ipv4 byte[4]
`Ipv6 byte[16]
;;
/* network connections */
const dial : (dialstr : byte[:] -> result(fd, byte[:]))
const announce : (ds : byte[:] -> result(fd, byte[:]))
const listen : (sock : fd -> result(fd, byte[:]))
const accept : (lfd : fd -> result(fd, byte[:]))
/* ip parsing */
const ipparse : (ip : byte[:] -> option(netaddr))
const ip4parse : (ip : byte[:] -> option(netaddr))
const ip6parse : (ip : byte[:] -> option(netaddr))
/* dialstring formatting */
const netaddr : (addr : byte[:], net : byte[:], port : byte[:] -> byte[:]
generic hosttonet : (v : @a -> @a)
generic nettohost : (v : @a -> @a)
/* DNS */
type rectype = union
`DnsA /* host address */
`DnsNS /* authoritative name server */
`DnsCNAME /* canonical name for an alias */
`DnsSOA /* marks the start of a zone of authority */
`DnsWKS /* well known service description */
`DnsPTR /* domain name pointer */
`DnsHINFO /* host information */
`DnsMINFO /* mailbox or mail list information */
`DnsMX /* mail exchange */
`DnsTXT /* text strings */
`DnsAAAA /* ipv6 host address */
;;
type resolveerr = union
`Badhost
`Badsrv
`Badquery
`Badresp
;;
type hostinfo = struct
fam : sys.sockfam
stype : sys.socktype
ttl : uint32
addr : netaddr
;;
const resolve : (host : byte[:] -> result(hostinfo[:], resolveerr))
const resolvemx : (host : byte[:] -> result(hostinfo[:], resolveerr))
const resolverec : (host : byte[:], t : rectype -> result(hostinfo[:], resolveerr))
;;
Summary
This group of functions contains the basic, portable networking functionality provided by libstd. There are other packages shipped which provide access to the underlying functionality used to implement this code, and which may provide more control.
The bulk of the functionality is fairly low level. Most of the client side
networking should be done using nothing more than dial
and announce
Dial describes the endpoint to connect to in the form proto!host!service
.
proto
can be any supported protocol. The host specified can be an IP
address, hostname, or path to a local socket. The service ismay be either a
named service, or a protocol specific port. If the port is not a component of
the address (eg, Unix domain sockets) then it should be ommitted.
On plan 9, the full dial(2) dial string syntax is suported.
Data Types
This contains the infomation that we extract by resolving a host.
type ipaddr = union
`Ipv4 byte[4]
`Ipv6 byte[16]
;;
This contains an IP address. Either V4 or V6 is supported.
Connections
const dial : (dialstr : byte[:] -> result(fd, byte[:]))
Dial connects to a dial string as described in the summary, returning either a file descriptor on success, or an error description on failure. The FD returned is a connection to the server.
const announce : (ds : byte[:] -> result(fd, byte[:]))
Announce sets up a file descriptor which is ready to listen for connections, and binds it to an address. Wildcards can be specified with '*' within the dial string.
const listen : (sock : fd -> result(fd, byte[:]))
Listen specifies that the socket created with announce
is willing to accept
connections.
const accept : (lfd : fd -> result(fd, byte[:]))
Accept takes the returned file descriptor from listen, and returns a file descriptor that is prepared for reading and writing.
IP Parsing
const ipparse : (ip : byte[:] -> option(netaddr))
Ipparse will parse an IP address. This will recognize either V4 or V6
addresses, and `Some `Ipv4 bits
or `Some `Ipv6 bits
as appropriate, or
`None
if the address can't be parsed.
const ip4parse : (ip : byte[:] -> option(netaddr))
Parses an Ipv4 address from the string ip
. The syntax expected is dotted
quad notation. Returns `Some `Ipv4 bits
if the address parses successfully, or
`None
if parsing fails.
const ip6parse : (ip : byte[:] -> option(netaddr))
Parses an Ipv6 address from the string ip
. Returns `Some `Ipv4 bits
if
the address parses successfully, or `None
if parsing fails. Zones are
currently not supported. This is a bug.
Network address formatting
const netaddr : (addr : byte[:], net : byte[:], port : byte[:] -> byte[:])
Creates a string appropriate from std.dial
or std.announce
from a tuple of
addr
, net
, and port
. The addr
parameter at minimum needs a host name,
but may contain other dial string components. If there are other dial string
components, they will not be overridden.
For example:
std.netaddr("myrlang.org", "tcp", "80")
=> "tcp!myrlang.org!80"
But std.netaddr("udp!myrlang.org!999", "tcp", "80") => "udp!myrlang.org!999"
Endian flipping
generic hosttonet : (v : @a -> @a)
generic nettohost : (v : @a -> @a)
Flips the bits in an integer to match the expected. These functions should be avoided in favor of explicit packing functions.
DNS Types
type rectype = union
`DnsA /* host address */
`DnsNS /* authoritative name server */
`DnsCNAME /* canonical name for an alias */
`DnsSOA /* marks the start of a zone of authority */
`DnsWKS /* well known service description */
`DnsPTR /* domain name pointer */
`DnsHINFO /* host information */
`DnsMINFO /* mailbox or mail list information */
`DnsMX /* mail exchange */
`DnsTXT /* text strings */
`DnsAAAA /* ipv6 host address */
;;
This union contains all of the record types that we claim to know how to resolve. At the moment, few of them have been tested sufficiently (only A records can reasonably be said to be exercised).
type resolveerr = union
`Badhost
`Badsrv
`Badquery
`Badresp
;;
This union contains the errors that we can encounter when trying to resolve a host.
type hostinfo = struct
fam : sys.sockfam
stype : sys.socktype
ttl : uint32
addr : netaddr
;;
DNS Resolution
const resolve : (host : byte[:] -> result(hostinfo[:], resolveerr))
Resolves the A or AAAA record for the host host
using DNS. This function
does caching, expiring based on the TTL. Returns all of the host info entries sent
back by DNS or found in the cache on success, or a resolve error on failure.
const resolvemx : (host : byte[:] -> result(hostinfo[:], resolveerr))
Resolves the MX record for the host host
using DNS. This function does
caching, expiring based on the TTL. Returns all of the host info entries sent
back by DNS or found in the cache on success, or a resolve error on failure.
const resolverec : (host : byte[:], t : rectype -> result(hostinfo[:], resolveerr))
Resolves a record from DNS. This function's interface is slightly broken, as it will never work for TXT or CNAME records.
Examples
Some simple examples of how to use the Myrddin networking API
Echo Server
use std
const Dialstr = "tcp!*!1234"
const main = {
var lfd, afd
var buf : byte[1024]
match std.announce(Dialstr)
| `std.Ok f: lfd = f
| `std.Err e: std.fatal("unable to announce on {}: {}\n", Dialstr, e)
;;
match std.listen(lfd)
| `std.Ok f: afd = f
| `std.Err e: std.fatal("unable to listen on {}: {}\n", Dialstr, e)
;;
while true
match std.accept(afd)
| `std.Ok fd:
match std.read(fd, buf[:])
| `std.Ok n:
std.writeall(fd, buf[:n])
| `std.Err e:
std.put("lost conection while reading: {}\n", e)
;;
std.close(fd)
| `std.Err e:
std.fatal("unable to accept connection on {}: {}\n", Dialstr, e)
;;
;;
}
Echo Client
use std
const Dialstr = "tcp!localhost!1234"
const main = {args : byte[:][:]
var req, resp
match std.dial(Dialstr)
| `std.Ok fd:
req = std.strjoin(args[1:], " ")
std.writeall(fd, req)
resp = std.try(std.fslurp(fd))
std.put("{}\n", resp)
| `std.Err e:
std.fatal("unable to dial {}: {}\n", Dialstr, e)
;;
}
Bugs
The errors returned are strings, when they should be unions with default formatting functions.