Skip to content

Heartbeat

congma edited this page Mar 8, 2013 · 1 revision

A heartbeat mechanism seems to be in place. Observing the original client app we can notice a keepalive UDP packet being sent to the server port 3335. The data payload is the "UID" (their internal name for a login session identifier) as a signed long long (8-byte) number, in little endian. The endianness is rather counter-intuitive, as one would expect the payload in network i.e. big endian.

The server upon receiving the heartbeat packet sends back a packet containing a 16-byte payload. So far we know the following about these data:

  • The last 8 bytes are padded with zeros.
  • The first 8 bytes are the same UID (again in little endian order) as long as the login session of that UID is "alive", i.e. in valid use by the current user. For a "dead" session the number is the signed long long number -2 (0xfeffffffffffffff).

After a session becomes dead, either by kicking, logging-out or other exceptions, sending further heartbeat packets is useless, and the server will continue to return 0xfeffffffffffffff. The only thing you can do is to log-in again, possibly kicking other users first. Therefore we drop the connection and kill the daemon after receiving the "death packet".

As UDP transmission is unreliable, we know nothing about the status of our heartbeat after it is sent, and it's quite possible to miss the server response. Not receiving the server response shouldn't be an exception that leads to termination of the program.

The original Linux client seems to ignore the server return packets altogether, closing the socket after send()ing the heartbeat. This causes the OS to generate ICMP Port Unreachable packets directed at the server for each return packet. Our implementation has no such problem.

We create a new socket for heartbeat communication in each cycle, and let Python's garbage collection dispose of the last one. In this way we cycle through random local UDP ports.

Clone this wiki locally