Saturday, 10 June 2017

Anet and I76 Nitro Multiplayer

I76 uses a host server running a program called Anet to co-ordinate online multiplayer.

Activision released the code for the server, compatible with the Nitro riders version of I76, and we can bring this up on a modern machine with a little bit of work.

What's going on behind the scenes

Fundamentally the job of the Anet server is to track lists of available machines. Clients can publish details of games they wish to host, and can also query the Anet server for the details of other machines currently hosting games. They can also request a connection to a client that is hosting a game via the Anet server.

The logical concept in Anet used to track all this is the session: A session is defined (in dp2.c) as "a group of machines" which are associated together: a single online game is a session, as is a chat lobby or any other shared service. Each session has a type number, called a species, which determines what it is (so a Nitro game is "604") and for each session then one of the machines in the session is designated as a host or "master".

So, all client machines connect to the Anet server, and dptab_addPeer() is used to track connected machines. If a machine wants to host a game then it will (can?) send a dp_session_t packet, which describes a session - in this case a game it is running. The Anet server has a list of all the active sessions and the associated host/master and client machines.

Other players which connect to the Anet server can see the available session(s), and can issue join requests. On join requests then the details of the connecting client are passed to the machine hosting the game. At this point the hosting machine and connecting client can then handshake, so the players can communicate directly and run the actual game. Importantly the Anet server is not involved in the actual game data transfer.

There's quite a few details being glossed over there - each machine has it's own list of sessions, which are shared with the Anet server on connecting, and there's a lot of additional message types, group management, scoring and voting logic as well as keepalive pings, host transfers, persistent status (via freeze/thaw), message queuing and html status generation.

Building and Installing on a modern distribution

Build Machine

You'll want a Linux distribution, and my recommendation would be a 32 bit distribution. The modified code should build and run on a 64 bit host, but you will need 32 bit versions of the development libraries anyway. Just using the 32 bit version is simpler.

My Test Setup

Out of the box then (at least for me) Anet doesn't seem to like the case where the server and clients are all sharing a single network segment. The address resolution doesn't cope well and fails to issue the correct connection information to the clients. I believe the SYN handling in dpio_SYN_PACKET_ID is tripping up, but my test gameplaying "machines" are all VirtualBox hosts running Wine, and with bridged ethernet links so things could be hosed on that front.

For me this breaks the simple test case on a private network - at some point I mean to fix this, but for now I just work around it with my network configuration.

My test setup for a basic run is:



Internet |--| Switch |---| Anet Server |
                     |
                     |---| Router |----| Client #1 |
                                           |
                                           |----| VM#1 |
                                           |----| VM#2 |
                                           |-... etc

So the router performs simple DHCP and NAT for the two clients, and the Anet server has a resolvable hostname that the clients can find.

Initially get the source from Dan Kegel's master tarball. This requires a fairly old GCC (2.9 era) to build as is. Something like CentOS or RHEL version 3 would support this and will run in a VM.

Otherwise we need to make a couple of code changes to get this to build, and to reduce the number of warnings

  • Some simple syntax fixes
  • Structure packing conventions have changed: update the the Pack statements to cover the structure rather than individual members
  • print format specifiers updated (consistent use of longs vs ints)
  • thaw buffer is undersized (this trips GCC stack protection)
  • Modify the make files to remove -Werror and specify -m32
  • Fix the library paths for libm and libbsd (/usr/lib/i386-linux-gnu/ on a modern box)
There are still a few mismatches, but that'll do to reduce the noise in the core build.

Install follows the same basic steps as the older version, in terms of setting up an alink user and how to expose the HTML information pages. However we need to

  • Change the machine shell from dash to bash - some setup scripts rely on bash-isms ("dpkg-reconfigure dash" and say no on debian)
  • Switch over the location of information files from the (defunct) activision server to a live server (play.interstate76.com is the one I chose) and use wget rather than a custom perl script.

Actually Building up the Sources

You'll want to grab Dan Kegel's source tarball: http://www.kegel.com/anet/anet-0.10.tar.gz

And there's a patch to this code with my current fixes: tp-anet-changes.patch

Unpack the tarball and patch the source with a line like:


tar -xf anet-0.10.tar.gz
cd anet-0.10
# Permissions fix for a couple of R-only files
chmod -R +rw .
patch -p1 < ../tp-anet-changes.patch
 

Then just type in "make" and things should build and run the autotests. If the build machine doesn't have a good network connection then the build tests will stall: You'll see the tests start with

dptabt1 regression test...+ ./normal/dptabt1
followed by a few more lines and finally
id 2: Sent 3'th variable to h:2; table ndbug, subkey ndbug; len 4
at which point everything will just hang. If that happens then just hit Ctrl-C; you will have to build on a machine with a live network connection to run the tests.