Network protocol - Liquid War 6
Description: Liquid War 6

Next: Technical HOWTOs, Previous: Use as a library, Up: Hacker's guide

### 3.14 Network protocol

This section describes how Liquid War 6 handles network messages. Note that for now this is purely theorical, more of a draft, a plan, it might change before being implemented.

#### 3.14.1 No server, no client, only nodes

Liquid War 6 does not really have the notion of server or client, any instance of the program can act as both server and client, therefore we use the term node.

A node listens on a given port, in both TCP and UDP, and can connect to other nodes on the same port. The main identifier of a node is its public url, which is typically of the form `http://<ip-address>:<port>/`. This url is very important for it is (or at least should be) a unique identifier of the node over the network.

Liquid War has 3 ways to communicate:

- raw TCP, this is the LW6 protocol, the easiest to implement and debug, probably the most reliable one, but not always the fastest. This involves the two modules `mod-tcp` and `mod-tcpd`.
- HTTP over TCP, this is a hack which allows the game to communicate through HTTP proxies for instance. Additionnally, the fact any node is a web server enables peering with a simple web browser. Web server facility requires `mod-httpd` and client part requires `mod-http` which might or not be available, depending on how the game was compiled.
- raw UDP, this is another version of the LW6 protocol, this is in theory the fastest way to communicate, it requires `mod-udp` and `mod-udpd` to work. Using UDP only was not an option when conceiving Liquid War since it's interesting to have other solutions if, for instance, a firewall does not allow you to use UDP the way you want.

On each of these channels, messages can be exchanged in two modes, an “out of band” mode (AKA “oob”), and a regular message oriented, here we speak of “connection”.

#### 3.14.2 Out of band messages

There are only 3 out of band messages:

- `PING`: requests for a simple `PONG http://server:port/` answer, this is just to check if a server is a valid server, and if the URL we used to connect on it is the correct one.
- `INFO`: requests for a list of key/attributes pairs, which describe the node, telling for instance its version, its uptime, and so on.
- `LIST`: requests for a list of other nodess this node is aware of.

You can test out of band messages by simply connecting on your server with a command like:

    telnet localhost 8056

At the telnet prompt, simply type:

    INFO

and press return, and you should have a description of your node.

The complete syntax of OOB messages is:

    <COMMAND> [password] [url]

The `password` and `url` parameters are optionnal. `password` can contain either the plain text password or a checksum calculated from the password which is, for security reasons, seeded with the public url of the node we're connecting to, so that this password can't be re-used on another node. `url` is simply a clue we give to the other node to help find us, the other node will automatically try to detect our IP address and use standard LW6 port 8056, but if for some reason we use a different setting, this is a good way to pass the hint.

Here are examples of valid messages:

    LIST
    PING http://myhost.mydomain:1234/
    INFO secret http://myhost.mydomain:1234/
    LIST 12ab34cd

If there's only one argument, the parser will first try and interpret it as a URL. If it's not parseable that way, it will consider it's a password. The password, in turn, may be specified as clear text or as a 32-bit checksum.

As far as OOB is concerned, TCP and UDP work almost the same, HTTP is a bit different, the OOB commands are accessed through the following URLs:

- `/ping.txt`
- `/info.txt`
- `/list.txt`

OOB messages are usually sent many times in redundant mode on the network, as there's no tracking of them, sending them through multiple channels ensures they make their way to the right place.

The parser for these messages is located in `src/lib/msg/msg-oob.c`.

#### 3.14.3 Regular messages overview

All messages that are non-OOB share a common syntax. This is called the “envelope” of messages.

The general syntax is:

    LW6 <VERSION> <PASSWORD_CHECKSUM> <PHYSICAL_TICKET_SIG> <LOGICAL_TICKET_SIG> <PHYSICAL_FROM_ID> <PHYSICAL_TO_ID> <LOGICAL_FROM_ID> <LOGICAL_TO_ID> <MSG>

Here's an example:

    LW6 0.1.3485 - 2d1dbed7 - 3003300330033003 2002200220022002 - - DATA 8 0 1 1000000035005 3003300330033003 SET 3001 11 1 1 0

In this example, the messages carried is `DATA 8 0 1 1000000035005 3003300330033003 SET 3001 11 1 1 0`, the rest is part of the envelope protocol.

Here's what those fields mean:

- `LW6`: should always be LW6, this is a marker to make sure we're speaking the right protocol.
- `<VERSION>`: the version of the program sending the message, the receiver of the message should check this version is compatible.
- `<PASSWORD_CHECKSUM>`: the password checksum, while a clear password should still be correctly interpreted, as for OOB messages, there's no reason to send the cleartext password, so the checksum is just fine. Note that the checksum is short, and vulnerable to brute-force attacks. If you want strong protection, the general advice is to tunnel your connections through SSL or TLS, use a VPN, LW6 won't implement “fortress mode”, third party tools should do this much better. If undefined, should be replaced by the dash character `-`.
- `<PHYSICAL_TICKET_SIG>`: a signature done by the sender, which is unique for the combination message+from+to. This means two different messages will generate two differents signatures, but different senders and/or receivers will also change this, so it's not possible, unless one has the “ticket” to fake a message. This is clearly not bullet-proof, and more specifically, brute-force attacks and/or network listening could break the protocol and/or reveal the ticket, still, this is a good way to make sure that if something is inconsistent, someone is trying to cheat. As every node maintain its own game state, a cheater can “only” be a nuisance by sending wrong key presses, but in the long run it will be defeated by the fact that an attacker should intercept and modify all messages on all channels (tcp, udp, http ...) and make sure the official, real informations, never makes its way to the right node. This is quite hard to achieve, very likely, an inconsistency will be detected, nodes concerned should be disconnected, period. When sending the first messages, ticket might not be exchanged yet, so there's no way to calculate this, during this period, `ffffffff` is sent, and checksum errors are ignored.
- `<LOGICAL_TICKET_SIG>`: another signature, but this one concerns the physical sender/receiver. If the physical sender is the logical sender, and the physical receiver is the logical receiver, that is, if physical and logical nodes are the same pair of nodes, then it need not be defined and can be replaced by the dash character `-`. In fact, in that case, the physical and logical signatures are obviously the same. However (not implemented yet) the protocol is designed so that nodes can act as messages forwarders, in that case they have no knowledge of the secret ticket to use, so this ticket is here to ensure message consistency for the final, real (logical) receiver of the message.
- `<PHYSICAL_FROM_ID>`: the id of the physical sender, the node that created the message.
- `<PHYSICAL_TO_ID>`: the id of the physical receiver, the node that should receive the message.
- `<LOGICAL_FROM_ID>`: the id of the logical sender, if it's the same than the physical sender, can be replaced by the dash character `-`.
- `<LOGICAL_TO_ID>`: the id of the logical receiver, if it's the same than the physical receiver, can be replaced by the dash character `-`.
- `<MSG>`: the message itself, it might in turn be separated by spaces, or whatever the message delimiter is. It should not be too long, as it must be sendable on the network by UDP, so it must fit within the MTU (about 1.4 kb) with all the protocol (envelope) stuff before it. In practice, it's cut into 1.2 kb parts by `libdat`, the constant `_LW6DAT_ATOM_MAX_SIZE` is used to split big messages in smaller parts.

It's implemented in `src/lib/msg/msg-envelope.c`.

#### 3.14.4 Regular control messages

To establish a connection, maintain it, and do the OOB job, a set of control message is used. Those messages carry a bunch of informations about who is sending them, in fact, they just contain the informations that would otherwise be handled by out-of-band messages, but it's convenient to have the information first-hand rather than relying on the other protocol.

The syntax is:

    LW6 <VERSION> <PASSWORD_CHECKSUM> <PHYSICAL_TICKET_SIG> <LOGICAL_TICKET_SIG> <PHYSICAL_FROM_ID> <PHYSICAL_TO_ID> <LOGICAL_FROM_ID> <LOGICAL_TO_ID> <COMMAND> <PROGRAM> <VERSION> <CODENAME> <STAMP> <ID> <URL> <TITLE> <DESCRIPTION> <HAS_PASSWORD> <BENCH> <OPEN_RELAY> <UPTIME> <COMMUNITY_ID> <ROUND> <LEVEL> <REQUIRED_BENCH> <NB_COLORS> <MAX_NB_COLORS> <NB_CURSORS> <MAX_NB_CURSORS> <NB_NODES> <MAX_NB_NODES> <PEER_LIST> <COMMAND_ARGS>

Example:

    LW6 0.1.3485 - ffffffff - 1001100110011001 2002200220022002 - - HELLO liquidwar6 0.1.3485 "Davy Crockett" 3485 1001100110011001 http://localhost:8057/ cGF0 RHVtbXkgdGVzdCBub2RlIEE= 0 10 0 5 372057f45b3d2ba5 10005 "Default map" 5 4 10 4 26 1 12 ""

The fields, starting from `LW6` up to (and including) `<LOGICAL_TO_ID>` are part of the envelope, described previously.

The message fields are:

- `<COMMAMD>`: described below, the main command,
- `<PROGRAM>`: should be `liquidwar6`
- `<VERSION>`: the version of the program. Yes, this is also in the envelope, but one could think of instances relaying informations for other peers, in that case this could prove useful.
- `<CODENAME>`: the code name of the program.
- `<STAMP>`: the stamp, it's normally contained within the version, but this avoids parsing issues.
- `<ID>`: the node id (could be inferred from envelope, but repeated here).
- `<URL>`: the node url (could be inferred from envelope, but repeated here).
- `<TITLE>`: the readable title of the node, base64 encrypted.
- `<DESCRIPTION>`: the readable description of the node, base64 encrypted.
- `<HAS_PASSWORD>`: wether it's protected by a password or not, 0 if not, 1 if protected.
- `<BENCH>`: the bench of the node, giving its CPU strength in an arbitrary unit.
- `<OPEN_RELAY>`: wether the node act as an open relay, 0 if not, 1 if in relaying.
- `<UPTIME>`: node uptime, in seconds.
- `<COMMUNITY_ID>`: the community id, a unique id shared by all nodes connected to a game session.
- `<ROUND>`: the current round id.
- `<REQUIRED_BENCH>`: the minimum bench required to connect to this node (used to avoid slow nodes connecting to way-too-fast games).
- `<NB_COLORS>`: number of colors playing.
- `<MAX_NB_COLORS>`: maximum number of colors allowed on this node.
- `<NB_CURSORS>`: number of cursors playing.
- `<MAX_NB_CURSORS>`: maximum number of cursors allowed on this node.
- `<NB_NODES>`: number of nodes connected.
- `<MAX_NB_NODES>`: maximum number of nodes allowed on this node.
- `<PEER_LIST>`: list of peers connected to this node.
- `<COMMAND_ARGS>`: command-specific arguments

Here are the different possibilities for the `<COMMAND>` field.

- `HELLO`: is used when connecting, this should be the first message sent. In itself, the message means pretty much noting, it just says “I'm here” which could be infered from any other message. No command args.
- `TICKET <ticket>`: is used to inform the caller of the ticket we use. The ticket sent from A to B is ised by B to sign messages for A. A node typically sents a different ticket to all its peers so when sending the same message to A and C, B will typically use two different tickets, thus generating two different signatures, and if sending the exact same string to C, A generate yet another signature as it will have sent another ticket. There's one optional argument, which is the ticket itself, a 64-bit hexa integer.
- `FOO <key> <serial>`: is sent on a regular basis, it's really the replacement of the OOB `PING` message, it will update the peer status and maintain consistent informations. It has two arguments, the first one is `key`, a 32-bit hexa integer, which will, upon `BAR` message reception, used to figure out “OK, this is the `BAR` message associated to this `FOO` message I sent before”. The second one, `serial`, is used to inform the peer that, possibly, there are new messages to fetch from us. The peer, in turn, might fire `MISS` messages, but without this feature, peers could “fall asleep” and forget to pump messages, especially on non-reliable connections.
- `BAR <key> <serial>`: is the response to `FOO` which is received on a regular basis, it's really the replacement of the OOB `PONG` message, it will update the peer status and maintain consistent informations. It has two arguments, the first one is `key`, a 32-bit hexa integer, which will, upon reception, ne matched against a corresponding `FOO` messaged, used to figure out “OK, this is the `BAR` message associated to this `FOO` message I sent before”. The second one, `serial`, is used to inform the peer that, possibly, there are new messages to fetch from us. The peer, in turn, might fire `MISS` messages, but without this feature, peers could “fall asleep” and forget to pump messages, especially on non-reliable connections.
- `JOIN <seq> <serial>`: Used to join a game. In fact, having said `HELLO` and exchanged `FOO` and `BAR` messages does not mean one has joined the game for real. The reason for this is that those messages help establishing a stable communication channel, then one needs to come in with the right `seq` and `serial`. There are basically two cases. First case, `seq` can be zero, in that case it means we're trying to connect on an existing server, which will in turn send a `JOIN` message with a non-zero value, giving the current seq. Second case, `seq` is non-zero, in that case it means we're answering a connection request. In both cases, `serial` is a serial number other peers should use as a minimum limit, and never ask for messages with a `serial` lower than that.
- `GOODBYE`: symetric of `HELLO`, should be called on disconnection, however, the peers should handle the case when no `GOODBYE` message is sent, this is just about being polite. No command args.

It's implemented in `src/lib/msg/msg-cmd.c`.

#### 3.14.5 Regular MISS messages

Todo...

#### 3.14.6 Regular META messages

Todo...

#### 3.14.7 Regular DATA messages

Todo...

#### 3.14.8 Other raw technical stuff (WIP)

TCP messages:

    LW6 [<passwd>] <version> <client-id>
    <from-id> <to-id> <serial> <i> <n> <sig> MSG1
    <from-id> <to-id> <serial> <i> <n> <sig> MSG2

TCP oobs:

    <return> # only works anonymous, same as INFO
    INFO [<passwd>] [<public-url>]
    LIST [<passwd>] [<public-url>]
    PING [<passwd>] [<public-url>]

UDP messages:

    LW6 [<passwd>] <version> <client-id> <from-id> <to-id> <serial> <i> <n> <sig> MSG1
    LW6 [<passwd>] <version> <client-id> <from-id> <to-id> <serial> <i> <n> <sig> MSG2

UDP oobs:

    INFO [<passwd>] [<public-url>]
    LIST [<passwd>] [<public-url>]
    PING [<passwd>] [<public-url>]

HTTP messages:

client id & password passed in HTTP headers

    /lw6/version/<from-id>/<to-id>/<serial>/<i>/<n>/sig/MSG1
    /lw6/version/<from-id>/<to-id>/<serial>/<i>/<n>/sig/MSG2

HTTP public URLs:

    / -> index.html
    /index.html
    /favicon.ico
    /screenshot.jpeg
    /robots.txt
    /gpl.txt
    /info.txt
    /list.txt
    /ping.txt

MSG syntax:

    <round> <server-id> <command> <arg1> ... <argN>

COMMAND examples:

    2 1234abcd1234abcd REGISTER
    3 1234abcd1234abcd ADD 5678 YELLOW
    4 1234abcd1234abcd SET 5678 20 5
    10 1234abcd1234abcd NOP
    400 1234abcd1234abcd REMOVE 5678
    1000 1234abcd1234abcd UNREGISTER

Sig is a checksum on string:

    <from-id> <to-id> <serial> <i> <n> MSG