Plan 9’s ‘clone dance’

Plan 9’s ip(3) interface is wonderful. It makes network programming dramatically easier than any other environment I’ve seen, so much so that many “network programming” tasks can reasonably be done by simple shell scripts. There are two bits that aren’t obvious at first, though, the most important being that the file descriptor returned by reading the clone file (e.g. /net/tcp/clone) needs to be held open while the connection is in use.

clone

This is trivial in C (you call open() and simply don’t close() it), but not at first in a shell. Thankfully, rc makes it pretty easy, once you know how. Here’s an example:

<>[5]/net/tcp/clone {
    n=`{read <[0=5]}
    local=/net/tcp/$n
    echo $local
    rc
}

This opens /net/tcp/clone for both reading and writing (“<>”), gives it file descriptor 5 (“[5]”), and holds that file descriptor open while the commands inside the {} braces are performed. The example above ends with rc, which is useful for development or debugging, but you could substitute the entire block within the {} with a function or command to do what you like. Giving the file descriptor a known number (here, 5) above stdin/stdout/stderr is useful when working interactively because it allows your other commands (the “echo $local”, for example) to behave normally while giving you the ability to explicity send things to the network when you like.

Note also that file descriptor 5 is the ctl file for your new connection. You’ll generallly need to get the connection number (the ‘read’ in the example above) to find the data file ($local/data) in the example.

listen

The second tricky bit is similar. To listen for incoming connections, you read from the ‘listen’ file (i.e. /net/tcp/N/listen). The read will block until it has an incoming connection for you, at which point it will give you a file descriptor for a new ctl file representing that connection, which you’ll also need to hold open. This is because simply announcing you’re listening on a port could result in several connections over time (or at once, if you’re so inclined).

Continuing with the example from above, within the rc you started with file descriptor 5 held open and $local defined, you could do this:

fn listen {
    echo announce $1 >[1=5]
    while() {
        <>[6] $local/listen {
            x=`{read <[0=6]}
            cat /net/tcp/$x/data
        }
    }}

Then ‘listen 54321’ would loop witing for incoming connections on port 54321 and print out the data it gets sent. The “<>[6]” to hold the listen file open, the ‘read’ to get the connection number, and using that to get the data file should all look familiar at this point.

clonedance.rc

The script clonedance combines the above two examples with some possibly useful examples for sending and receiving within the launched rc, and might be useful while developing things this way (or just poking around).