Skip to content

Threesixty

 4 min Xbox 360 through a compressed VPN to DigitalOcean, with a live player map, traffic-based host estimates, and replayable captures.
Published

Mar 27, 2013

I used Threesixty while playing Xbox 360 so I could see who I was playing in real time and see who the map thought was hosting. The game already had a network connecting us. I wanted to see that network.

I mostly remember using it with Halo. When the map showed a game forming around players outside the United States, I left it before it started and kept looking for another one. That was the real reason for the map. A bad host means bad gameplay, and I could see the bad host coming.

The implementation was small: a Bash launcher, one Python application, a uWSGI configuration, and an OpenLayers page.

The tunnel came first

I ran the Xbox traffic through a compressed VPN to a DigitalOcean server. Play felt faster and more stable. My idea was to give the consumer ISP one steady destination and let the remote server handle the onward traffic to the other players. The local connection could concentrate on hurling packets down the tunnel.

I was playing Halo through it, getting host fairly often, and finding it noticeably more consistent than playing directly through the home connection. I was also experimenting during DigitalOcean’s early VirtIO rollout, when enabling the virtual network and disk drivers still meant opening a support ticket.

The map grew around that setup. The capture launcher watched vpn0, where the peer traffic was available to inspect.

Let tshark do the packet work

The launcher used tshark’s existing packet decoder and GeoIP fields. It selected the console’s traffic around port 3074, then emitted tab-separated rows containing relative packet time, frame length, destination address and hostname, longitude, latitude, city, and country. Python received those fields already decoded.

The interesting boundary was the pipe. Inside the receiving shell group:

export TSHARK_FD
exec {TSHARK_FD}<&0 </dev/null

Bash duplicated the incoming stream onto a numbered descriptor and exported its number. Standard input became /dev/null; the capture stayed open across the uWSGI launch. Python recovered it with:

_fd = os.fdopen(int(os.environ['TSHARK_FD']))

There was no second connection protocol to negotiate and no temporary file to tail. The descriptor was the connection. The application wrapped it in gevent’s FileObjectPosix and read complete rows cooperatively.

The same stream could run again

The launcher could select a live interface or a saved pcap. Both produced the same tshark columns, so the Python consumer did not need a separate replay implementation.

Each packet’s capture-relative timestamp became a wall-clock deadline:

self.ts = offset + round(self.ts - seek, 6)

The reader waited until that deadline before handing the packet to the leaderboard, sleeping in roughly thirtieth-of-a-second increments. A capture could therefore exercise the map at its recorded pace instead of dumping every peer into the display immediately. The seek parameter also let the reader skip the beginning of a recording.

That was approximate playback, with wall-clock timing and ordinary scheduling delay. It was enough to keep the distinction between a player arriving, a burst of traffic, and a player going quiet visible during development.

Background loops started through a small decorator:

def gpfork(fn):
    postfork(gevent.Greenlet(fn).start)

The callback registered a greenlet to start after uWSGI forked. The capture reader, statistics printer, rate sampler, and inactive-peer reaper all used that same startup path.

Which dot was the host?

The Python side accumulated frame lengths for each destination and sampled them once per second. A four-sample deque smoothed the activity; four zero samples made a peer eligible for removal. The field was called bps, but the implementation summed bytes, without multiplying by eight.

Peers were ordered by their recent traffic. The local player could receive the leading rank when the mean peer traffic exceeded twice its standard deviation. The display estimated the host from that traffic pattern rather than decoding a host announcement from the game.

The mean and variance were computed in one pass. A late commit adjusted the threshold because a joining player could spike the standard deviation. That is a fairly direct record of the problem: the network changed, the estimate jumped, and the display needed to settle again.

GeoIP provided approximate network locations. It did not provide player identities or exact physical positions. The map’s labels came from resolved hosts and location fields, not from Xbox account data.

A small, live display

The browser page polled a GeoJSON endpoint every two seconds. The leading rank became a larger orange-red dot, other ranked peers were gold, and unranked entries were pale. Hovering exposed the available hostname and location. The page offered map and satellite layers and fitted the view to the current player extent.

It also contained a function called _mapjar: pan one pixel, then pan back, with animation disabled. Some updates needed a nudge before OpenLayers would repaint. That workaround belongs to the application as much as the cleaner descriptor handoff does.

The tunnel and the map served different parts of the same experiment: improve the connection I was playing through, then make its peers and likely host visible. The old Python, tshark GeoIP integration, and map services put this implementation firmly in its era.