Myrddin: Networking

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.