Next: Technical HOWTOs, Previous: Use as a library, Up: Hacker's guide [Contents][Index]
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.
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:
mod-tcp
and mod-tcpd
.
mod-httpd
and client part requires mod-http
which might or
not be available, depending on how the game was compiled.
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”.
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
.
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
.
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
.
Todo...
Todo...
Todo...
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
Next: Technical HOWTOs, Previous: Use as a library, Up: Hacker's guide [Contents][Index]