Network Protocol: Connecting to a Server
Mar 17, 2014Have you ever wondered what’s going on under the hood when you play a game of efficiency ctf, with all that action of players shooting, jumping, dying, picking up flags, and spectators chatting? Here’s the explanation! Read on and learn how Sauerbraten’s networking works.
Sauerbraten’s networking code is based on the ENet library. Without going into too much detail, ENet is a layer on top of UDP offering (optional) sequential and/or reliable transmission of packets (similar to TCP), the concept of using multiple channels on one connection, and more.
For this article, we assume a simple connect to a vanilla server with default config, nothing fancy like auth-on-connect or server passwords will be covered in-depth.
Channels
Sauerbraten uses 3 channels for communication between server and client:
- channel 0 is for transmitting position data (that is, where in the map is a client?)
- channel 1 is for game events, like a player shooting, a player using a teleport, a player joining the game, chat messages, and much more
- channel 2 is used in “coop edit” mode to send maps (
/sendmap
and/getmap
)
Requesting a CN and server information
When you try to connect to a server (for example by clicking on it in the server browser), your client will establish a connection to the server over ENet. The server will notice that you want to connect and will add you as a client and assign you a client number (CN). The server will then reply with a “server info” packet, which looks something like this (when you display the decimal values of the bytes):
1 0 128 3 1 129 184 252 162 0 0 108 111 99 97 108 32 116 101 115 116 32
115 101 114 118 101 114 32 50 0 0
Here’s a short listing of the parts that make up a “server info” packet, followed by a more detailed explanation:
1 → N_SERVINFO
0 → cn given to you by the server
128 3 1 → protocol number (3 + 256 = 259)
129 184 252 162 0 → session ID (184 + 64512 + 10616832 + 0 = 10681528)
0 → not password-protected
108 111 ... 50 0 → server description ("local test server 2")
0 → server auth domain ("")
N_SERVINFO
The N_SERVINFO
byte is part of a list of network message codes that is known to the client and the server (you can find that list in src/fpsgame/game.h
). N_SERVINFO
just happens to have 1
as its byte representation; it doesn’t really matter what value it is, as long as the client and server both know the meaning of it.
cn
The next number is the CN the server assigned to your connection. If you are the first client to connect to a server, it is usually 0
. Connecting clients get the lowest free number (not a requirement, but it is this way in the vanilla server and in all server mods).
protocol number
Next up is the protocol number (259
in collect edition). Don’t get confused by the weird three byte representation; it will be explained later.
session ID
The session ID is always generated, but only actually used when the server requires you to provide a password in order to connect. The password you type in is hashed together with this session ID and your CN, and only the hash is then sent to the server and compared to the server’s hash.
passwort-protected indicator
The following byte can be either 0
or 1
, and indicates if the server requires you to provide a password or not.
server description
The server description (C/C++ strings always end with a 0x00
byte). This is the same description that you can also read in your server browser.
server auth domain
Last comes the server’s auth domain for which a connecting client has to provide an auth key in order to be let in (an empty string signals the server does not use auth-on-connect; however, it does not mean the server doesn’t use local auth domains at all).
Note regarding integer compression
Notice how the protocol number (259
in collect edition) consists of three bytes (also, the CN was only one byte even though it is a uint32
internally and should be four bytes): this is because Sauerbraten uses an encoding for integer numbers that saves bandwitdh:
- values less than
128
and greater than-127
(i.e. fit into one byte) will simply be sent as one byte - values that would fit into two bytes are sent as two bytes, preceeded by a
0x80
byte (128
in decimal) - all other values will be sent as their normal four bytes, preceeded by a
0x81
byte (129
in decimal)
This works well for Sauerbraten even though big numbers need five bytes instead of just four, since most numbers sent are small and only take up one byte or two bytes (three after encoding).
Joining the game
After receiving a CN, Session ID and the server information, the client replies with a N_CONNECT
packet, which looks like this:
0 124 121 101 115 86 73 58 112 105 120 0 1 0 0 0
0 → N_CONNECT
124 121 ... 120 0 → player name ("|yesVI:pix")
1 → player model ID
0 → connect password hash ("")
0 → auth domain ("")
0 → auth name ("")
N_CONNECT
N_CONNECT
(like N_SERVINFO
) tells the server what kind of packet this is so it knows what parts will come next.
player name
The next bytes up to a 0x00
byte are the player’s name.
player model ID
Up next is the ID of the player model the client uses, so that other clients know what player model to show for that player.
connect password hash
Next comes a string (all bytes up to 0x00
) containing the hashed connect password the user entered, in case the N_SERVINFO
packet told the client that this server is password protected.
auth domain
and auth name
Last come the auth domain (which is the same as the server auth domain the server sent in the N_SERVINFO
packet) as a string and the name corresponding to the auth key the client found for the specified auth domain. For more information on the auth system in Sauerbraten, read the article on Sauerbraten’s Auth Mechanism.
Personal note: I find N_CONNECT
to be a very confusing name for this packet: on the ENet level, you are already connected when you get the N_SERVINFO
packet. “N_JOIN
” would be a more apt name I think.)
Welcoming the client
When the server receives the N_CONNECT
packet, it checks for a number of things:
- is the password correct? (if needed)
- if not, is the server in private mode maybe?
- is the auth key valid? (if needed)
- is there a free spot (server not full)?
- is the IP that connected banned?
If everything is OK, it prepares the client’s on-server representation for playing and then sends a welcome packet back to the client that joined.
The server then welcomes the client by sending a number of packets. Here is a (small) example of that list of packets making up the “welcome packet“:
2 22 114 101 105 115 115 101 110 0 5 0 33 87 58 1 3 1 -1 61 0 103 111
111 100 0 -1 17 0 0 100 100 100 1 2 20 20 10 10 20 0 37 1 0 12 1 0 3
100 100 100 1 2 16 4 3 8 15 0 -1 3 1 67 111 111 107 105 101 0 101 118
105 108 0 1
2 → N_WELCOME
22 → N_MAPCHANGE
114 101 ... 110 0 → map name ("reissen")
5 → game mode (effic)
0 → whether or not the server has representations
for items (quad, health boost, etc); 0 means it
already has them
33 → N_TIMEUP
87 → seconds left to play
58 → N_CURRENTMASTER
1 → veto
3 → a CN
1 → the privilege of client with CN 3
-1 → end of privileges list
61 → N_SETTEAM
0 → CN of the client whose team to set (= client
receiving this packet)
103 111 111 100 0 → team name ("good")
-1 → end of N_SETTEAM packet
17 → N_SPAWNSTATE
0 → CN of the client the spawn state date belongs to
(= client receiving this packet)
0 → client's life sequence (changes at every
respawn)
100 → client's health
100 → maximum health the player can have (can
change when health boost is picked up)
100 → armour
1 → armour type (pre-defined constant: 0 = blue,
1 = green, 2 = yellow)
2 → weapon the player is currently using (pre-
defined: 2 = minigun)
20 → shotgun ammo
20 → minigun ammo
10 → rocket launcher ammo
10 → rifle ammo
20 → grenades ammo
0 → pistol ammo
37 → N_RESUME
1 → CN of another player
0 → player 1's current state (0 = alive, ...)
12 → frags
1 → flags player 1 scored
0 → time left for player 1's quad damage, if he
has it
3 → player 1's life sequence (changes at every
respawn)
100 → player 1's health
100 → maximum health the player can have (can change
when health boost is picked up)
100 → armour
1 → armour type (pre-defined constant: 0 = blue,
1 = green, 2 = yellow)
2 → weapon the player is currently using (pre-
defined: 2 = minigun)
16 → shotgun ammo
4 → minigun ammo
3 → rocket launcher ammo
8 → rifle ammo
15 → grenades ammo
0 → pistol ammo
-1 → end of N_RESUME packet
3 → N_INITCLIENT
1 → CN of another client
67 111 ... 101 0 → name of client with above CN ("Cookie")
101 118 105 108 0 → team name of client with above CN ("evil")
1 → player model ID of client with above CN
Basically, every time you see a network message code (N_*
), a new game packet begins:
N_WELCOME
tells the client to close the server browser in case it is openN_MAPCHANGE
tells the client what map to load and what mode is played on that mapN_TIMEUP
tells the client how many seconds are left to play (so the clock is set correctly when joining mid-game)N_CURRENTMASTER
is followed by the master mode currently set on the server and a list of CNs of players with higher than normal privilege (this list can be empty; the end is marked by a-1
)N_SETTEAM
tells the client what team the player was put intoN_SPAWNSTATE
tells the client what state to use when the player spawnsN_RESUME
tells the client to start the countdown and is followed by a list of the current game state of each client already connected to the server (again, ends when-1
is read)- now, one or more
N_INITCLIENT
packets follow, each describing a client’s name, team and player model (if more than one client are connected, there will be multipleN_INITCLIENT
packets)
After the server sent the above packet to the new client, it will notify all other clients of the newly connected client by sending a N_INITCLIENT
packet to each of them containing the info of the new client. In case the new client was automatically set to spectator (e.g. when the server is locked), all other clients will also receive a N_SPECTATOR
packet.
At the end of all this, the client is now doing all the stuff it has to do in order to get “up-to-speed” with the other players, i.e. load the map, put itself into the correct team, set the local player’s spawn state, etc. As far as the server is concerned, the client is now treated exactly the same as all the other clients, because the process of joining the current game is now finished.