The Open Source LAN Project

The backend of your LAN, freed.

Containing Your Game Servers - Docker and Game Servers

Updated 2017-02-19 to include build process optimisations

Over recent history, we’ve been updating our gameservers-docker repository with some useful dockerfiles. Many are still a work in progress, but all are usable. Let’s take a walk through some examples to see the utility of this approach.

tl;dr

Get a game server with no effort.

1
2
3
4
5
6
7
8
9
10
11
12
# Do these once to set up the image
git clone https://github.com/OpenSourceLAN/gameservers-docker.git .

# Download the prop hunt map packs (this is not auto-downloaded because
# of its size and you may want a different map pack)
wget --output-document tf2-prophunt/PHMapEssentialsBZ2.7z\
 https://github.com/powerlord/sourcemod-prophunt/releases/download/maps/PHMapEssentialsBZ2.7z

./build.sh tf2-prophunt

# Do this every time you need to start an additional
./start_server tf2-prophunt

Almost all resources are automatically downloaded (eg, steamcmd, sourcemod) and so no action is needed from you to download them. There are a handful of files that we chose not to auto-download for various reasons. The build scripts will not let you build without them, so you can’t accidentally miss them.

Building the image

First we need to build our images. Let’s build a TF2 prophunt image first.

The images are built in a heirachy. base is the common ancestor of all of our images, so that comes first. If you try and build a descendant image and a dependency is not already built, the script will build it for you.

In this tutorial, we will do it the long and hard way so that you understand how it is structured.

All of the folders in the repository have a build.sh file. cd in to base and run ./build.sh. Alternatively, you can use build.sh in the root of the repository and pass an image name to the script as an argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sirsquidness@squid ~/projects/gameservers-docker/base $ ./build.sh 
Sending build context to Docker daemon 3.072 kB
Step 1/5 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2/5 : RUN sed -i 's/archive.ubuntu.com/au.archive.ubuntu.com/' /etc/apt/sources.list
 ---> Using cache
 ---> a4a4d42f1211
Step 3/5 : RUN apt-get update  && apt-get dist-upgrade -y &&  apt-get install -y unzip p7zip curl wget lib32gcc1 iproute2 vim-tiny bzip2 jq &&    apt-get clean
 ---> Using cache
 ---> 0325eb88ec60
Step 4/5 : RUN echo "Australia/Melbourne" > /etc/timezone
 ---> Using cache
 ---> deb81775de67
Step 5/5 : RUN ln -fs /usr/share/zoneinfo/Australia/Melbourne /etc/localtime
 ---> Using cache
 ---> 5efc7e451ece
Successfully built 5efc7e451ece

Note that the timezone is automatically detected based on your system’s local timezone.

The base image provides a set of common utilities, such as curl, jq, and common dependencies for games.

The next image in the chain for tf2-prophunt is steamcmd, which is a tool used to install Steam game servers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sirsquidness@squid ~/projects/gameservers-docker/base $ cd ../steamcmd
sirsquidness@squid ~/projects/gameservers-docker/steamcmd $ ./build.sh 
Sending build context to Docker daemon 3.072 kB
Step 1/9 : FROM base
 ---> 28c1c378be5d
Step 2/9 : RUN useradd -m steam
 ---> Running in a0d250c083fa
 ---> 23f68f0a2e62
[...snip....]
[----] Verifying installation...
Steam Console Client (c) Valve Corporation
-- type 'quit' to exit --
Loading Steam API...Created shared memory when not owner SteamController_Shared_mem
OK.

Connecting anonymously to Steam Public...Logged in OK
Waiting for license info...OK
 ---> b4a6b84eeb5f
Removing intermediate container 49628529285b
Successfully built b4a6b84eeb5f

Great! steamcmd image is now built. If you looked carefully, you’d have noticed that we downloaded the steamcmd installation files automatically. This is a semi-pattern in this repository. Where it makes sense, the build scripts will download dependencies automatically. But some other dependencies require manual action as we will see soon.

tf2-prophunt depends on the tf2 image. Build this now. TF2 is a large server - nearly 8GB - so make sure that you have plenty of disk space free and something to entertain you while it downloads.

1
2
3
4
5
6
7
sirsquidness@squid ~/projects/gameservers-docker/tf2 $ ./build.sh 
Sending build context to Docker daemon 6.144 kB
Step 1/8 : FROM steamcmd
 ---> b4a6b84eeb5f
Step 2/8 : USER steam
 ---> Running in ae23e6b04ec1
 ---> d810f2252646

And so on. Some time later, it finishes.

Right now, you can run a vanilla TF2 server - what joy!

Documentation is still a work in progress, but you can check the start-tf2.sh script to see what environment variables are available. Let’s set a hostname and an RCON password on a vanilla TF2 server!

docker run --net=host -it --rm -e "SV_HOSTNAME=A super cool TF2 server" -e "RCON_PASSWORD=OSLisSuperCool" -e LAN=1 tf2

Boom, LAN server. But it’s just a plain TF2 server. Time for a PropHunt server.

The tf2-prophunt download script downloads a few dependencies, such a metamod and sourcemod. However, it does not download the PH map packs. PHMapEssentialsBZ2.7z is few hundred MB and there a few options available, so download the PH map pack that is right for you. Place the 7z file in the tf2-prophunt directory, and run the build script

1
2
3
4
5
sirsquidness@squid ~/projects/gameservers-docker/tf2 $ cd ../tf2-prophunt/
sirsquidness@squid ~/projects/gameservers-docker/tf2-prophunt $ wget https://github.com/powerlord/sourcemod-prophunt/releases/download/maps/PHMapEssentialsBZ2.7z
[...snip...]
sirsquidness@squid ~/projects/gameservers-docker/tf2-prophunt $ ./build.sh
[...snip...]

And now you have a TF2 Prophunt server installed with only a few super simple commands.

Running a server

As we’re building docker images, you’re free to use the docker command line as you see fit. During the image building, there was an example of doing just that to prove that the server worked.

But to make it easier, there is a simple helper script included in the root of the repository.

The simplest use of the script is to invoke it by itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sirsquidness@squid ~/projects/gameservers-docker $ ./start_server.sh 
Please select from one of the following images, or ctrl+c to exit:
7daystodie
armagetron
base
csgo
csgo-comp
csgo-ebot
csgo-gg
csgo-warmod
factorio
hl2dm
minecraft
openttd
quake3
steamcmd
tf2
tf2-prophunt
trackmania
unreal4
Image: 

Type in the name of the server you want, press enter, and away it goes!

I typed tf2-prophunt and pressed enter, and this is what happened:

1
2
3
Image: tf2-prophunt
docker run --tty --interactive --net host --restart=unless-stopped --name tf2-prophunt_1 --detach tf2-prophunt
54da57b4c67c8e669667f43ecca88a0317c8284e91003e2d4c62ac82f0546b52
1
2
3
sirsquidness@squid ~/projects/gameservers-docker $ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
cd105fff9efc        tf2-prophunt        "./start-tf2-proph..."   28 seconds ago      Up 27 seconds                           tf2-prophunt_1

We can also pass the name of the server directly to the script.

1
2
3
sirsquidness@squid ~/projects/gameservers-docker $ ./start_server.sh tf2-prophunt
docker run --tty --interactive --net host --restart=unless-stopped --name tf2-prophunt_1 --detach tf2-prophunt
4bac06dd1a6a2437ecbdcd4d8e91c449c1360965927d7a7b5daf86053a2d072f

Notice the convention for --name - tf2-prophunt_1. Every server started will get a unique number. You can use this name to get a console to the server.

1
2
3
4
sirsquidness@squid ~/projects/gameservers-docker $ docker attach  tf2-prophunt_1 
status
hostname: TF2 Prophunt Server
version : 3694595/24 3694595 secure

Press ctrl+p, then ctrl+q to detach again. Don’t press ctrl+c here! If you press ctrl+c, it will send the kill signal to the game server and it will shut down.

Of course, that’s not always convenient. So let’s add an RCON password.

Configuring the servers

Most of the images are set up to accept environmental variables to dictate behaviour. For example, a common one in most of the images is RCON_PASSWORD.

The start_server.sh script doesn’t currently have a simple way of password long env vars through, so let’s use docker directly.

1
docker run --net=host -it --rm -e "SV_HOSTNAME=A super cool TF2 server" -e "RCON_PASSWORD=OSLisSuperCool" -e LAN=1 tf2

Cool!

Data persistence

Some images support exporting important data out of the running container so that you can delete and/or upgrade your containers without losing data. Currently only factorio and 7daystodie support this.

The start_server.sh script will automatically detect games that support this and create a folder of the same name as the container in the data folder.

Please double check that it is working before relying on it - file and folder permissions are finnicky for exported mounts in docker. The easiest (and worst!) way to make it work is making the folder globally readable and writable - chmod -R 777 data/.

A Different Way of Implementing a Steam Cache

Update 2017-01-28: I released a copy of the tools run your steam cache in this manner. Requires a Squid proxy, and a NodeJS application running somewhere.

There are a few different implementations of the so called ‘LAN Cache’. All of these rely on DNS to have the Steam client connect to the cache.

While a DNS override is trivial to implement, it is not the only way to get a Steam cache working. DNS overrides also require mainteance - occasionally the list of hostnames used for content distribution changes, and that means Steam clients will bypass your cache if you don’t catch that.

How the Steam client discovers its CDN

The Steam client is configured with a number of Content Servers (CS) which it bootstraps from. You can see the list in %YourSteamDirectory%\config\config.vdf, and look for the CS line. Each of the addresses in this semicolon separated list can be used to bootstrap.

The first entry in my list is 103.10.125.136:80. Before beginning a download, Steam will make a request to an address like this: http://103.10.125.136:80/serverlist/53/2/. This returns a server list. The first number (53) is the download region ID, Australia - VIC in this example. The second number is the maximum number of servers to return.

Here’s an example response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

"serverlist"
{
  "0"
  {
      "type"      "CS"
      "sourceid"      "119"
      "cell"      "52"
      "load"      "72"
      "weightedload"      "80"
      "numentriesinclientlist"        "1"
      "host" "10.0.0.194"
      "vhost"     "valve5.steamcontent.com"
  }
  "1"
  {
      "type"      "CDN"
      "vhost"     "cdn.edgecast.cs.steampowered.com"
      "host" "10.0.0.194"
      "load"      "0"
      "weightedload"      "130"
      "sourceid"      "32"
      "numentriesinclientlist"        "2"
  }
}

There are two entries returned, since we asked for two in the request. The first entry is a CS entry, valve5.steamcontent.com. steamcontent.com is a new domain that Valve has just started using in the last few weeks. Some of these are owned by Valve, some are owned by ISPs. The distiction is irrelevant to us. It is part of cell 52 which is the Australia - NSW region.

The second entry is a CDN entry, cdn.edgecast.cs.steampowered.com. EdgeCast is a Content Distribution Network (CDN) which Valve uses to efficiently distribute data around the globe without needing to deploy their own infrastructure.

Once the Steam client has a list of content servers, it makes an initsession HTTP request, which is an authentication step and is not cached. It then downloads a manfiest file for each depot in the package. For example, if we were to download Swordy, it has one depot. All data for a depot lives in a folder beginning with /depot/381301, where the number is the depot ID - Swordy’s only one in this case. The current manifest can be found at /depot/381301/manifest/571621603566489671/5

The manifest lists all of the files for the depot. For example, one file from the above Swordy depot is /depot/381301/chunk/914fc04dc7b3330544afb6ecde72bdfad003eabb. Steam downloads and decrypts and/or decompresses, and boom - your game is downloaded.

Using this knowledge to run a cache

If you were paying attention, you may have noticed in the server list response that each entry had two fields named host and vhost, and that host was set to an RFC1918 address, 10.0.0.194. This is not what Steam returned to us - in fact, this is how we’ve been running our Steam cache since SteamPipe was first implemented in late 2012.

All traffic leaving our LAN goes via a transparent Squid proxy which does not itself do any caching for Steam. Instead, we use Squid to rewrite the URL of requests that match the regex of /\/serverlist\/(\d+)\/(\d+)\/ and redirect them to our own server list generator.

The rewriting is done using a url_rewrite_program. The URL of every request is passed to this program and the program returns either the original URL if it’s not a Steam serverlist request, or a URL that points to our cache if it is a serverlist.

Our own server list generator will receive the request. It takes an original genuine server list from Valve and replaces the Host field with the address of our own cache, before returning that to the Steam client. At present, it fetches the original upstream server list every time, but it could also cache the lists as they do not change regularly.

Because the vhost field remains in tact, the Steam client will then make a HTTP request to our cache’s address, but it will include in the HTTP headers a Host header, such as Host: valve5.steamcontent.com. This way, our cache server can easily identify where the request was meant to be sent to, so in a cache miss situation we can forward the request on with no issues.

And there it is

And that’s how Steam downloads your games! This alternative method of caching games has some extra complexity and overhead - it requires running a transparent proxy and has more moving parts, but it means you do not need to keep a list of DNS entries up to date. Isn’t that lovely?

The scripts used to perform this magic haven’t been released yet, but will be “soon”. If you’d like a copy of them in their current state, get in touch via the links at the top of the page and we’ll organise something.

Projects Up and Our First Major Event

After a long time going “one day”, I’ve finally released some software under the Open Source LAN name. a It’s all on GitHub under the OpenSourceLAN organisation.

steam-discover is a tool to report data about how many people are playing which steam games on your LAN. Includes a simple live updating graph which you could include on your website, and connectors to send the data to arbitrary places like Redis, ELK, syslog or similar.

srcds-log-receiver is a simple tool that records all of the SRCDS logs sent to it. No need to worry about copying all of the logs from your game servers - they’re already in one spot.

origin-docker is a terribly named repository of a single docker container which contains Nginx set up to cache all of the major game distributions for your LAN.

Vectorama becomes the first LAN which I’m not involved with to use OpenSourceLAN software. Check out this thread on reddit to see how they used SteamDiscover and ELK to create a super cool dashboard of Steam activity at their event.

Placeholder

What is this place? You will find out soon.

Eager to see if anything has happened yet? Maybe something has appeared on the Gitlab page. Maybe we’ve posted something to the subreddit. Maybe we haven’t published anything yet and you’re jumping the gun.