Network Protocol: Connecting to a ServerMar 17, 2014
Have 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.
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 (
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 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
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.
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).
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.
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.
The following byte can be either
1, and indicates if the server requires you to provide a password or not.
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
128and 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
- all other values will be sent as their normal four bytes, preceeded by a
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_SERVINFO) tells the server what kind of packet this is so it knows what parts will come next.
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
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_WELCOMEtells the client to close the server browser in case it is open
N_MAPCHANGEtells the client what map to load and what mode is played on that map
N_TIMEUPtells the client how many seconds are left to play (so the clock is set correctly when joining mid-game)
N_CURRENTMASTERis 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
N_SETTEAMtells the client what team the player was put into
N_SPAWNSTATEtells the client what state to use when the player spawns
N_RESUMEtells 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
- now, one or more
N_INITCLIENTpackets follow, each describing a client’s name, team and player model (if more than one client are connected, there will be multiple
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
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.