aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/UDP
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterServer/UDP')
-rwxr-xr-xlib/MasterServer/UDP/BeaconCatcher.pm13
-rwxr-xr-xlib/MasterServer/UDP/BeaconChecker.pm50
-rwxr-xr-xlib/MasterServer/UDP/DatagramProcessor.pm88
-rwxr-xr-xlib/MasterServer/UDP/UCCAppletQuery.pm85
4 files changed, 181 insertions, 55 deletions
diff --git a/lib/MasterServer/UDP/BeaconCatcher.pm b/lib/MasterServer/UDP/BeaconCatcher.pm
index b3d2c18..685c33b 100755
--- a/lib/MasterServer/UDP/BeaconCatcher.pm
+++ b/lib/MasterServer/UDP/BeaconCatcher.pm
@@ -9,9 +9,10 @@ use Exporter 'import';
our @EXPORT = qw| beacon_catcher on_beacon_receive|;
-##
-## Receive UDP beacons according the \heartbeat\7778\gamename\ut\ format
-## where "ut" depicts the game and 7778 the query port of the game.
+################################################################################
+## Receive UDP beacons with \heartbeat\7778\gamename\ut\ format
+## where "ut" is the game name and 7778 the query port of the server.
+################################################################################
sub beacon_catcher {
my $self = shift;
@@ -36,7 +37,9 @@ sub beacon_catcher {
return $udp_server;
}
-## process (new) beacons
+################################################################################
+## Determine the concent of the received information and process it.
+################################################################################
sub on_beacon_receive {
# $self, beacon address, handle, packed client address
my ($self, $b, $udp, $pa) = @_;
@@ -59,7 +62,7 @@ sub on_beacon_receive {
$self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port)
if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/);
- # or if this is a secure response, verify the response code and add mark it verified
+ # or if this is a secure response, verify the response
$self->process_udp_validate($b, $peer_addr, $port, undef)
if ($b =~ m/\\validate\\/);
}
diff --git a/lib/MasterServer/UDP/BeaconChecker.pm b/lib/MasterServer/UDP/BeaconChecker.pm
index 5fc2b38..98ee1e2 100755
--- a/lib/MasterServer/UDP/BeaconChecker.pm
+++ b/lib/MasterServer/UDP/BeaconChecker.pm
@@ -3,16 +3,12 @@ package MasterServer::UDP::BeaconChecker;
use strict;
use warnings;
-use Encode;
use AnyEvent::Handle::UDP;
use Exporter 'import';
our @EXPORT = qw| beacon_checker query_udp_server|;
################################################################################
-##
-## Beacon Checker Module
-##
## When addresses are stored in the 'pending' list, they are supposed to be
## queried immediately with the secure/validate challenge to testify that
## the server is genuine and alive.
@@ -21,26 +17,19 @@ our @EXPORT = qw| beacon_checker query_udp_server|;
## servers are verified with a secure-challenge on their heartbeat ports,
## which are designed to respond to secure queries, as well as status queries.
##
-## Querying pending servers should only happen when beacons are already
-## several seconds old. We do not want to interfere with the existing
-## construction where servers respond immediately.
-##
## Addresses collected by other scripts, whether from the UCC applet or manual
-## input via the website, are added to the pending query table. It is more
+## input via the website, are added to the pending list. It is more
## important to verify pending beacons and new server addresses, than to
## update the status of existing addresses. Therefore, pending addresses are
## prioritized.
-##
################################################################################
sub beacon_checker {
my $self = shift;
$self->log("load", "UDP Beacon Checker is loaded.");
# queue -- which address is next in line?
- my %q = ( pending_id => 0,
- server_id => 0,
- start_time => time+$self->{beacon_checker_time}[0]-1, #time+grace
- );
+ my %q = ( pending_id => 0, server_id => 0,
+ start_time => time+$self->{beacon_checker_time}[0]-1); #time+grace
# go through all servers one by one, new and old
my $server_info = AnyEvent->timer (
@@ -56,15 +45,9 @@ sub beacon_checker {
$q{start_time} = time;
}
- # id, ip, port reference of the server to be queried
- my $n;
-
- #
- # Pending Servers
- #
# See if there are pending servers, and use existing secure string for
# the challenge.
- $n = $self->get_next_pending($q{pending_id});
+ my $n = $self->get_next_pending($q{pending_id});
# if any entries were found, proceed
if ( $n->[0] ) {
@@ -75,7 +58,7 @@ sub beacon_checker {
# query the server
$self->query_udp_server($n->[1], $n->[2], $n->[3]);
- # work done. Wait for the next round for the next task.
+ # work done. Wait for the next round for the next timer tick.
return;
}
@@ -88,7 +71,7 @@ sub beacon_checker {
# next server id will be > $n
$q{server_id} = $n->[0];
- # query the server
+ # query the server (no secure string)
$self->query_udp_server($n->[1], $n->[2], "");
# work done. Wait for the next round for the next task.
@@ -99,29 +82,23 @@ sub beacon_checker {
# added, they are immediately queried on the next round.
# From here on, just count down until the cycle is complete.
- # DEBUG (spams badly)
+ # debug (spams badly)
$self->log("debug_spam", "Checker timer: t=".(time - $q{start_time}));
}
);
+ # at the start of the module, remind host how often this happens
$self->log("info", "Verifying servers every $self->{beacon_checker_time}[2] seconds.");
- # return the timer to keep it alive outside of scope
+ # return the timer object to keep it alive outside of this scope
return $server_info;
}
################################################################################
-##
-## UDP Server Query function
-##
-## Get the server status from any server over UDP and store the
-## received information in the database.
-##
-## $secure determines the type of query: secure/pending or information
-##
-## Args: $ip, $port, payload
-## Function is called by AnyEvent->timer() above
+## Get the server status from any server over UDP and store the received
+## information in the database. $secure determines the type of query:
+## secure/pending or information.
################################################################################
sub query_udp_server {
my ($self, $ip, $port, $secure) = @_;
@@ -146,7 +123,8 @@ sub query_udp_server {
if ($buf =~ m/\\validate\\/){
$self->process_udp_validate($buf, $ip, undef, $port);
}
- # if gamename, ver, hostname and hostport are available, it should have been \basic\info
+ # if gamename, ver, hostname and hostport are available, it should
+ # have been \basic\info
elsif ($buf =~ m/\\gamename\\/ && $buf =~ m/\\gamever\\/
&& $buf =~ m/\\hostname\\/ && $buf =~ m/\\hostport\\/) {
$self->process_query_response($buf, $ip, $port);
diff --git a/lib/MasterServer/UDP/DatagramProcessor.pm b/lib/MasterServer/UDP/DatagramProcessor.pm
index 53f5228..c658264 100755
--- a/lib/MasterServer/UDP/DatagramProcessor.pm
+++ b/lib/MasterServer/UDP/DatagramProcessor.pm
@@ -7,10 +7,16 @@ use Encode;
use AnyEvent::Handle::UDP;
use Exporter 'import';
-our @EXPORT = qw| process_udp_beacon process_udp_validate process_query_response |;
+our @EXPORT = qw| process_udp_beacon
+ process_udp_validate
+ process_query_response
+ process_ucc_applet_query |;
-
-## process beacons that have a \heartbeat\ and \gamename\ format
+################################################################################
+## Process datagrams from beacons that have \heartbeat\ and \gamename\ keys
+## in the stringbuffer. If necessary, authenticate first with the secure/val
+## challenge.
+################################################################################
sub process_udp_beacon {
# $self, handle, packed address, udp data, peer ip address, $port
my ($self, $udp, $pa, $buf, $peer_addr, $port) = @_;
@@ -70,8 +76,10 @@ sub process_udp_beacon {
}
}
-
-## process the received validate query and determine whether the server is allowed in our database
+################################################################################
+## Process the received validate query and determine whether the server is
+## allowed in our database. Either provide heartbeat OR port, not both.
+################################################################################
sub process_udp_validate {
# $self, udp data, ip, port
my ($self, $buf, $peer_addr, $port, $heartbeat) = @_;
@@ -83,7 +91,9 @@ sub process_udp_validate {
# get our existing knowledge about this server from the database
# if the heartbeat/queryport known? then use that instead as beacon ports --> may vary after server restarts!
- my $pending = (defined $heartbeat) ? $self->get_pending_info($peer_addr, $heartbeat) : $self->get_pending_beacon($peer_addr, $port);
+ my $pending = (defined $heartbeat)
+ ? $self->get_pending_info($peer_addr, $heartbeat)
+ : $self->get_pending_beacon($peer_addr, $port);
# if indeed in the pending list, check -- if this entry is not (longer) in the list, it
# was either removed by the BeaconChecker or cleaned out in maintenance (after X hours).
@@ -93,12 +103,13 @@ sub process_udp_validate {
my $enc = (defined $r{enctype}) ? $r{enctype} : 0;
# database may not contain the correct gamename (ucc applet, incomplete beacon, change of gameserver)
- $pending->[4] = (defined $r{gamename} && exists $self->{game}->{lc $r{gamename}}) ? $r{gamename} : $pending->[4];
+ $pending->[4] = (defined $r{gamename} && exists $self->{game}->{lc $r{gamename}})
+ ? $r{gamename} : $pending->[4];
- # verify challenge gamename secure enctype validate_response
- my $val = $self->validated_beacon($pending->[4], $pending->[5], $enc, $r{validate});
+ # verify challenge gamename secure enctype validate_response
+ my $val = $self->validated_beacon($pending->[4], $pending->[5], $enc, $r{validate});
- # log challenge results (compensate if $port was not provided)
+ # log challenge results ($port may not have been provided)
$port = (defined $port) ? $port : $heartbeat;
$self->log("secure", "$peer_addr:$port validated with $val for $pending->[4]");
@@ -110,18 +121,22 @@ sub process_udp_validate {
# remove the entry from pending if successfully added
$self->remove_pending($pending->[0]) if ( $sa >= 0);
-
- # and set as direct beacon
- $self->set_direct_beacon($pending->[1], $pending->[3]);
}
else {
# else failed validation
$self->log("error","beacon $peer_addr:$port failed validation for $pending->[4] (details: $pending->[5] sent, got $r{validate})");
}
}
+ else {
+ # else failed validation
+ $self->log("error","server not found in pending for $peer_addr and unknown heartbeat/port");
+ }
}
-## process query data that was obtained with \basic\ and/or \info\
+################################################################################
+## Process query data that was obtained with \basic\ and/or \info\ from the
+## beacon checker module.
+################################################################################
sub process_query_response {
# $self, udp data, ip, port
my ($self, $buf, $ip, $port) = @_;
@@ -166,4 +181,49 @@ sub process_query_response {
}
}
+################################################################################
+## Process the list of addresses that was received after querying the UCC applet
+## and store them in the pending list.
+################################################################################
+sub process_ucc_applet_query {
+ my ($self, $buf, $ms) = @_;
+ $buf = encode('UTF-8', $buf);
+
+ # counter
+ my $c = 0;
+
+ # database types such as SQLite are slow, therefore use transactions.
+ $self->{dbh}->begin_work;
+
+ # parse $buf into an array of [ip, port]
+ foreach my $l (split(/\\/, $buf)) {
+
+ # search for \ip\255.255.255.255:7778\, contains ':'
+ if ($l =~ /:/) {
+ my ($a,$p) = $l =~ /(.*):(.*)/;
+
+ # check if address entry is valid
+ if ($self->valid_address($a,$p)) {
+ # count number of valid addresses
+ $c++;
+
+ # print address
+ $self->log("debug_spam", "applet query added $ms->{game}\t$a\t$p");
+
+ # add server
+ $self->add_pending($a, $p, $ms->{game}, $self->secure_string());
+ }
+ # invalid address, log
+ else {$self->log("error", "invalid address found at master applet $ms->{ip}: $l!");}
+ }
+ }
+
+ # end transaction, commit
+ $self->{dbh}->commit;
+
+ # print findings
+ $self->log("applet","found $c addresses at $ms->{ip} for $ms->{game}.");
+
+}
+
1;
diff --git a/lib/MasterServer/UDP/UCCAppletQuery.pm b/lib/MasterServer/UDP/UCCAppletQuery.pm
new file mode 100755
index 0000000..bd7bb73
--- /dev/null
+++ b/lib/MasterServer/UDP/UCCAppletQuery.pm
@@ -0,0 +1,85 @@
+
+package MasterServer::UDP::UCCAppletQuery;
+
+use strict;
+use warnings;
+use AnyEvent;
+use AnyEvent::Handle;
+use Exporter 'import';
+
+our @EXPORT = qw| ucc_applet_query_scheduler query_applet |;
+
+################################################################################
+## Query other UCC applets periodically to get a list of online servers.
+################################################################################
+sub ucc_applet_query_scheduler {
+ my $self = shift;
+ $self->log("load", "UCC Applet Query Scheduler is loaded.");
+
+ my $i = 0;
+ return AnyEvent->timer (
+ after => $self->{master_applet_time}[0],
+ interval => $self->{master_applet_time}[1],
+ cb => sub {
+ # check if there's a master server entry to be queried. If not, return
+ # to zero and go all over again.
+ $i = 0 unless $self->{master_applet}[$i];
+ return if (!defined $self->{master_applet}[$i]);
+
+ # perform the query
+ $self->query_applet($self->{master_applet}[$i]);
+
+ #increment counter
+ $i++;
+ }
+ );
+}
+
+################################################################################
+## The UCC Applet (Epic Megagames, Inc.) functions as a master server for one
+## single game. However, it does not always follow the defined protocol.
+## This module connects with UCC masterserver applets to receive the list.
+################################################################################
+sub query_applet {
+ my ($self, $ms) = @_;
+
+ # be nice to notify
+ $self->log("applet","[TCP] > start querying $ms->{ip}:$ms->{port} for '$ms->{game}' games");
+
+ # list to store all IPs in.
+ my $master_list = "";
+
+ # connection handle
+ my $handle;
+ $handle = new AnyEvent::Handle(
+ connect => [$ms->{ip} => $ms->{port}],
+ timeout => 5,
+ poll => 'r',
+ on_error => sub {$self->log("error", "$! on $ms->{ip}:$ms->{port}."); $handle->destroy;},
+ on_eof => sub {$self->process_ucc_applet_query($master_list, $ms); $handle->destroy;},
+ on_read => sub {
+
+ # receive and clear buffer
+ my $m = $_[0]->rbuf;
+ $_[0]->rbuf = "";
+
+ # remove string terminator
+ chop $m if $m =~ m/secure/;
+
+ # part 1: receive \basic\\secure\$key
+ if ($m =~ m/\\basic\\\\secure\\/) {
+ # skip to part 3: also request the list \list\gamename\ut
+ #$handle->push_write("\\list\\\\gamename\\$ms->{game}");
+ $handle->push_write("\\list\\");
+ }
+
+ # part 3b: receive the entire list in multiple steps.
+ if ($m =~ m/\\ip\\/) {
+ # add buffer to the list
+ $master_list .= $m;
+ }
+ }
+ );
+}
+
+1;