Myrddin: Networking

Networking

pkg std =
        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))

        generic hosttonet   : (v : @a -> @a)
        generic nettohost   : (v : @a -> @a)
;;

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.

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.

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.