aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer
diff options
context:
space:
mode:
authorDarkelarious <darkelarious@333networks.com>2015-02-11 21:12:44 +0100
committerDarkelarious <darkelarious@333networks.com>2015-02-11 21:12:44 +0100
commit534626943a0a5e251e5465376f3de3fb71b25e91 (patch)
treeb5b4550c1cdb69c6933aa571244881eaacc72ffe /lib/MasterServer
parente0ada80f8582cf3b28e70b8f18de10aa505159ae (diff)
downloadMasterServer-Perl-534626943a0a5e251e5465376f3de3fb71b25e91.tar.gz
MasterServer-Perl-534626943a0a5e251e5465376f3de3fb71b25e91.zip
ability to query UCC applets (Pg only)
Diffstat (limited to 'lib/MasterServer')
-rwxr-xr-xlib/MasterServer/Core/Core.pm104
-rwxr-xr-xlib/MasterServer/Core/Logging.pm6
-rwxr-xr-xlib/MasterServer/Core/Secure.pm39
-rwxr-xr-xlib/MasterServer/Core/Util.pm6
-rwxr-xr-xlib/MasterServer/Core/Version.pm10
-rwxr-xr-xlib/MasterServer/Database/Pg/dbBeacon.pm150
-rwxr-xr-xlib/MasterServer/Database/Pg/dbCore.pm6
-rwxr-xr-xlib/MasterServer/Database/Pg/dbServerlist.pm28
-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
12 files changed, 357 insertions, 228 deletions
diff --git a/lib/MasterServer/Core/Core.pm b/lib/MasterServer/Core/Core.pm
index 02ec32a..e4e4836 100755
--- a/lib/MasterServer/Core/Core.pm
+++ b/lib/MasterServer/Core/Core.pm
@@ -5,36 +5,31 @@ use strict;
use warnings;
use AnyEvent;
use Exporter 'import';
-use Data::Dumper 'Dumper';
use DBI;
our @EXPORT = qw | halt main |;
-##
-## Halt
-## Handle shutting down the program for whatever reason.
+################################################################################
+## Handle shutting down the program in case a fatal error occurs.
+## TODO: lockfile!
+################################################################################
sub halt {
my $self = shift;
- # When other processes are still
- # running, set all other scopes
- # to null/undef?
-
# log shutdown
$self->log("stop", "Stopping the masterserver now.");
+ # clear all other timers, network servers, etc
+ $self->{dbh}->disconnect() if (defined $self->{dbh});
+ $self->{scope} = undef;
+
# and send signal to condition var
$self->{must_halt}->send;
-
- # allow everything to be written to the logs
- sleep(2);
-
- exit;
}
-##
-## Main
-## Initialize all processes and start them
+################################################################################
+## Initialize all processes and start various functions
+################################################################################
sub main {
my $self = shift;
@@ -47,21 +42,19 @@ sub main {
# keep several objects alive outside their original scope
$self->{scope} = ();
-
- # Startup procedure
+ # startup procedure information
$self->log("info", "333networks Master Server Application.");
$self->log("info", "Build: $self->{build_type}");
$self->log("info", "Version: $self->{build_version}");
- $self->log("info", " Written by $self->{build_author}");
+ $self->log("info", "Written by $self->{build_author}");
$self->log("info", "Logs are written to $self->{log_dir}");
-
# determine the type of database and load the appropriate module
- { # start db type
+ {
# read from login
my @db_type = split(':', $self->{dblogin}->[0]);
- # format supported (yet)?
+ # format supported?
if ( "Pg SQLite" =~ m/$db_type[1]/i) {
# inform us what DB we try to load
@@ -72,14 +65,16 @@ sub main {
# Connect to database
$self->{dbh} = $self->database_login();
+
+ # and test whether we succeeded.
+ $self->halt() unless (defined $self->{dbh});
}
else {
# raise error and halt
$self->log("fatal", "The masterserver could not determine the chosen database type.");
$self->halt();
}
- } # end db type
-
+ }
# start the listening service (listen for UDP beacons)
$self->{scope}->{beacon_catcher} = $self->beacon_catcher();
@@ -87,64 +82,19 @@ sub main {
# start the beacon checker service (query entries from the pending list)
$self->{scope}->{beacon_checker} = $self->beacon_checker() if ($self->{beacon_checker_enabled});
+ # query other masterserver applets to get more server addresses
+ $self->{scope}->{ucc_applet_query} = $self->ucc_applet_query_scheduler() if ($self->{master_applet_enabled});
- $self->log("info", "All modules loaded. Starting...");
-
-
-=pod
-
- ##############################################################################
- ##
- ## Initiate Scheduled tasks
- ##
- ## Main Tasks
- ## beacon_catcher (udp server)
- ## beacon_checker (udp client, timer)
- ## browser_server (tcp server)
- ##
- ## Synchronization
- ## ucc_applet_query (tcp client, timer)
- ## syncer_scheduler (tcp client, timer)
- ##
- ## 333networks website specific
- ## ut_server_query (udp client, timer)
- ##
- ## Core Functions
- ## maintenance (timer, dbi)
- ## statistics (timer, dbi)
- ##
- ## (store objects in hash to keep them alive outside their own scopes)
- ##############################################################################
- ## servers
- $ae{beacon_catcher} = $self->beacon_catcher();
- $ae{beacon_checker} = $self->beacon_checker_scheduler();
- $ae{browser_server} = $self->browser_server();
-
- # synchronizing
- $ae{ucc_applet_query} = $self->ucc_applet_query_scheduler();
- $ae{syncer_scheduler} = $self->syncer_scheduler();
-
- # status info for UT servers (333networks site)
- $ae{ut_server_scheduler} = $self->ut_server_scheduler();
-
- # maintenance
- $ae{maintenance_runner} = $self->maintenance_runner();
- $ae{stats_runner} = $self->stats_runner();
-=cut
-
-
-
-
-
-
-
-
-
+ # all modules loaded. Running...
+ $self->log("info", "All modules loaded. Masterserver is now running.");
# prevent main program from ending prematurely
$self->{must_halt}->recv;
- $self->log("stop", "Logging off. Enjoy your day.");
+ $self->log("stop", "Shutting down NOW!");
+
+ # time for a beer.
+ exit;
}
1;
diff --git a/lib/MasterServer/Core/Logging.pm b/lib/MasterServer/Core/Logging.pm
index 312a0f8..c503e1a 100755
--- a/lib/MasterServer/Core/Logging.pm
+++ b/lib/MasterServer/Core/Logging.pm
@@ -9,10 +9,8 @@ use Exporter 'import';
our @EXPORT = qw| log |;
################################################################################
-#
-# Log to file and print to screen.
-# args: $self, message_type, message
-#
+## Log to file and print to screen.
+## args: $self, message_type, message
################################################################################
sub log {
my ($self, $type, $msg) = @_;
diff --git a/lib/MasterServer/Core/Secure.pm b/lib/MasterServer/Core/Secure.pm
index dbe9c1f..3b85498 100755
--- a/lib/MasterServer/Core/Secure.pm
+++ b/lib/MasterServer/Core/Secure.pm
@@ -8,7 +8,10 @@ use Exporter 'import';
our @EXPORT = qw| secure_string validated_beacon validated_request validate_string charshift get_validate_string|;
-## generate a random string of 6 characters long for the \secure\ challenge
+################################################################################
+# generate a random string of 6 characters long for the \secure\ challenge
+# returns string
+################################################################################
sub secure_string {
# spit out a random string, only uppercase characters
my @c = ('A'..'Z');
@@ -19,7 +22,10 @@ sub secure_string {
return $s;
}
-## Check if beacon has a valid response.
+################################################################################
+# authenticate the \validate\ response for the \secure\ challenge.
+# returns 1 on valid response, 0 on invalid
+################################################################################
sub validated_beacon {
my ($self, $gamename, $secure, $enctype, $validate) = @_;
@@ -30,15 +36,18 @@ sub validated_beacon {
$enctype = 0 unless $enctype;
if ($self->{ignore_beacon_key} =~ m/$gamename/i){
- $self->log("secure", "Ignored beacon validation for $gamename.");
+ $self->log("secure", "ignored beacon validation for $gamename");
return 1;
}
- # compare received response with challenge
+ # compare received response with expected response
return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
}
-## Check if request has valid response
+################################################################################
+# authenticate the \validate\ response for the \secure\ challenge.
+# returns 1 on valid response, 0 on invalid
+################################################################################
sub validated_request {
my ($self, $gamename, $secure, $enctype, $validate) = @_;
@@ -50,31 +59,29 @@ sub validated_request {
# ignore games and beacons that are listed
if ($self->{ignore_browser_key} =~ m/$gamename/i){
- $self->log("secure", "Ignored browser validation for $gamename.");
+ $self->log("secure", "ignored browser validation for $gamename");
return 1;
}
- # compare received response with challenge
+ # compare received response with expected response
return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
}
################################################################################
-# calculate the \validate\ response for the \secure\ challenge.
-# args: gamename, secure_string, encryption type
+# process the validate string as a response to the secure challenge
# returns: validate string (usually 8 characters long)
-# !! requires cipher hash to be configured in config! (imported or else)
################################################################################
sub validate_string {
my ($self, $game, $sec, $enc) = @_;
# get cipher from gamename
- my $cip = $self->{game}->{$game}->{key} || "XXXXXX";
+ my $cip = $self->{game}->{$game}->{key} || "000000";
- # don't accept challenge longer than 16 characters -- usually h@xx0rs
+ # don't accept challenge longer than 16 characters (because vulnerable in UE)
if (length $sec > 16) {
- return "0"}
+ return "invalid!"}
- # check for valid encryption choises
+ # check for valid encryption choices
my $enc_val = (defined $enc && 0 <= $enc && $enc <= 2) ? $enc : 0;
# calculate and return validate string
@@ -98,7 +105,7 @@ sub charshift {
}
################################################################################
-# algorithm to calculate the response to the secure/validate query. processes
+# algorithm to process the response to the secure/validate query. processes
# the secure_string and returns the challenge_string with which GameSpy secure
# protocol authenticates games.
#
@@ -111,7 +118,7 @@ sub charshift {
#
# args: game cipher, 6-char challenge string, encryption type
# returns: validate string (usually 8 characters long)
-# !! requires cipher hash to be configured in config! (imported or else)
+# !! requires cipher hash to be configured in config! (imported or otherwise)
################################################################################
sub get_validate_string {
my ($self, $cipher_string, $secure_string, $enctype) = @_;
diff --git a/lib/MasterServer/Core/Util.pm b/lib/MasterServer/Core/Util.pm
index 001137d..eb4d509 100755
--- a/lib/MasterServer/Core/Util.pm
+++ b/lib/MasterServer/Core/Util.pm
@@ -9,15 +9,19 @@ use Exporter 'import';
our @EXPORT = qw| valid_address ip2country |;
+################################################################################
## return the abbreviated country based on IP
+################################################################################
sub ip2country {
my ($self, $ip) = @_;
my $reg = IP::Country::Fast->new();
return $reg->inet_atocc($ip);
}
+################################################################################
## Verify whether a given domain name or IP address and port are valid.
-## returns true/false if valid ip + port
+## returns 1/0 if valid/invalid ip + port
+################################################################################
sub valid_address {
my ($self, $a, $p) = @_;
diff --git a/lib/MasterServer/Core/Version.pm b/lib/MasterServer/Core/Version.pm
index 5bd3f1c..30972e4 100755
--- a/lib/MasterServer/Core/Version.pm
+++ b/lib/MasterServer/Core/Version.pm
@@ -9,9 +9,9 @@ our @EXPORT = qw| version |;
################################################################################
-#
-# Version information
-#
+##
+## Version information
+##
################################################################################
sub version {
my $self = shift;
@@ -29,10 +29,10 @@ sub version {
$self->{build_type} = "333networks Masterserver-Perl";
# version
- $self->{build_version} = "0.2";
+ $self->{build_version} = "2.0.4";
# date yyyy-mm-dd
- $self->{build_date} = "2015-01-31";
+ $self->{build_date} = "2015-02-11";
#author, email
$self->{build_author} = "Darkelarious, darkelarious\@333networks.com";
diff --git a/lib/MasterServer/Database/Pg/dbBeacon.pm b/lib/MasterServer/Database/Pg/dbBeacon.pm
index e396810..735eaf9 100755
--- a/lib/MasterServer/Database/Pg/dbBeacon.pm
+++ b/lib/MasterServer/Database/Pg/dbBeacon.pm
@@ -6,14 +6,16 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw| add_beacon
+ add_pending
remove_pending
- set_direct_beacon
get_pending_beacon
get_pending_info
get_next_pending |;
+################################################################################
## Update beacon in serverlist or pending list. Add if beacon does not exist in
-## either list. Return 0,1,2 if success in adding or -1 on error
+## either list. Return 0,1,2 if success in adding or -1 on error.
+################################################################################
sub add_beacon {
my ($self, $ip, $beaconport, $heartbeat, $gamename, $secure) = @_;
@@ -35,7 +37,7 @@ sub add_beacon {
return 0 if ($u > 0);
# if it is already in the pending list, update it with a new challenge
- $u = $self->{dbh}->do(
+ $u = $self->{dbh}->do(
"UPDATE pending
SET added = NOW(),
beaconport = ?,
@@ -52,8 +54,13 @@ sub add_beacon {
return 1 if ($u > 0);
# if not found, add it
- $u = $self->{dbh}->do(
- "INSERT INTO pending (ip, beaconport, heartbeat, gamename, secure)
+ $u = $self->{dbh}->do(
+ "INSERT INTO pending (
+ ip,
+ beaconport,
+ heartbeat,
+ gamename,
+ secure)
SELECT ?, ?, ?, ?, ?",
undef, $ip, $beaconport, $heartbeat, lc $gamename, $secure);
@@ -68,83 +75,128 @@ sub add_beacon {
return -1;
}
-## server checks out, remove entry from the pending list.
-sub remove_pending {
- my ($self, $id) = @_;
-
+################################################################################
+## Add an address to the database that was obtained via a method other than
+## an udp beacon. Return 0,1,2 if success in adding or -1 on error.
+################################################################################
+sub add_pending {
+ my ($self, $ip, $port, $gamename, $secure) = @_;
+
# if address is in list, update the timestamp
- my $u = $self->{dbh}->do("DELETE FROM pending WHERE id = ?", undef, $id);
+ my $u = $self->{dbh}->do(
+ "UPDATE serverlist
+ SET updated = NOW()
+ WHERE ip = ?
+ AND port = ?",
+ undef, $ip, $port);
+
+ # notify
+ $self->log("update", "updated serverlist with $ip:$port") if ($u > 0);
- # notify
- $self->log("deleted", "removed pending id $id from the list of pending servers") if ($u > 0);
+ # if updated, return 0
+ return 0 if ($u > 0);
+
+ # if it is already in the pending list, update it with a new challenge
+ $u = $self->{dbh}->do(
+ "UPDATE pending
+ SET added = NOW(),
+ secure = ?
+ WHERE ip = ?
+ AND heartbeat = ?",
+ undef, $secure, $ip, $port);
+
+ # notify
+ $self->log("update", "updated pending with $ip:$port") if ($u > 0);
- # it was added to pending
- return 2 if ($u > 0);
+ # return 1 if updated
+ return 1 if ($u > 0);
- # or else report error
- $self->log("error", "an error occurred deleting server $id from the pending list");
+ # if not found, add it
+ $u = $self->{dbh}->do(
+ "INSERT INTO pending (
+ ip,
+ heartbeat,
+ gamename,
+ secure)
+ SELECT ?, ?, ?, ?",
+ undef, $ip, $port, $gamename, $secure);
+
+ # notify
+ $self->log("add", "$ip:$port added pending $gamename") if ($u > 0);
+
+ # return 2 if added new
+ return 2 if ($u > 0);
+
+ # else
return -1;
}
-
-## mark server as "direct beacon to this masterserver"
-sub set_direct_beacon {
- my ($self, $ip, $port) = @_;
+################################################################################
+## Remove an entry from the pending list. Returns 0 if removed or -1 in case
+## of error(s).
+################################################################################
+sub remove_pending {
+ my ($self, $id) = @_;
- # update or add server to serverlist
- my $u = $self->{dbh}->do("UPDATE serverlist
- SET b333ms = TRUE
- WHERE ip = ?
- AND port = ?",
- undef, $ip, $port);
-
- # notify
- $self->log("update", "$ip:$port is a direct beacon.") if ($u > 0);
+ # if address is in list, update the timestamp
+ my $u = $self->{dbh}->do(
+ "DELETE FROM pending
+ WHERE id = ?",
+ undef, $id);
- # if found, updated; done
- return 0 if ($u > 0);
+ # notify
+ $self->log("delete", "removed pending id $id from pending") if ($u > 0);
+
+ # it was removed from pending
+ return 2 if ($u > 0);
# or else report error
- $self->log("error", "an error occurred setting server $ip:$port as direct beacon");
+ $self->log("error", "error deleting server $id from pending");
return -1;
}
-
-
-## Get pending server by ip, beacon port.
+################################################################################
+## Get pending server by ip, beacon port. Returns * or undef
+################################################################################
sub get_pending_beacon {
my ($self, $ip, $port) = @_;
# if address is in list, update the timestamp
return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND beaconport = ?",
- undef, $ip, $port)->[0];
+ "SELECT * FROM pending
+ WHERE ip = ?
+ AND beaconport = ?",
+ undef, $ip, $port)->[0];
}
-## Same as get_pending_beacon, but with heartbeat port as identifier
+################################################################################
+## Get pending server by ip, heartbeat port. Returns * or undef
+################################################################################
sub get_pending_info {
my ($self, $ip, $port) = @_;
# if address is in list, update the timestamp
return $self->{dbh}->selectall_arrayref(
- "SELECT * FROM pending
- WHERE ip = ?
- AND heartbeat = ?",
- undef, $ip, $port)->[0];
+ "SELECT * FROM pending
+ WHERE ip = ?
+ AND heartbeat = ?",
+ undef, $ip, $port)->[0];
}
-## Get server info from any entry with an id higher than the provided one.
+################################################################################
+## Get server info from any entry with an id higher than the provided one. The
+## server is added to pending at least 15 seconds ago. Returns info or undef.
+################################################################################
sub get_next_pending {
my ($self, $id) = @_;
# get 1 pending id that is older than 15s
return $self->{dbh}->selectall_arrayref(
- "SELECT id, ip, heartbeat, secure FROM pending
- WHERE added < (NOW() - INTERVAL '15 SECONDS')
- AND id > ?
- ORDER BY id ASC LIMIT 1", undef, $id)->[0];
+ "SELECT id, ip, heartbeat, secure FROM pending
+ WHERE added < (NOW() - INTERVAL '15 SECONDS')
+ AND id > ?
+ ORDER BY id ASC LIMIT 1",
+ undef, $id)->[0];
}
diff --git a/lib/MasterServer/Database/Pg/dbCore.pm b/lib/MasterServer/Database/Pg/dbCore.pm
index f89cc68..8f55ebb 100755
--- a/lib/MasterServer/Database/Pg/dbCore.pm
+++ b/lib/MasterServer/Database/Pg/dbCore.pm
@@ -8,9 +8,8 @@ use Exporter 'import';
our @EXPORT = qw| database_login |;
################################################################################
-## database_login
## login to the database with credentials provided in the config file.
-## returns dbh object
+## returns dbh object or quits application on error.
################################################################################
sub database_login {
my $self = shift;
@@ -20,6 +19,7 @@ sub database_login {
# verify that the database connected
if (defined $dbh) {
+
# log the event
$self->log("load","Connected to the Postgres database.");
@@ -37,7 +37,7 @@ sub database_login {
$self->halt();
}
- # unreachable
+ # return empty element
return undef;
}
diff --git a/lib/MasterServer/Database/Pg/dbServerlist.pm b/lib/MasterServer/Database/Pg/dbServerlist.pm
index b1a787e..832a08f 100755
--- a/lib/MasterServer/Database/Pg/dbServerlist.pm
+++ b/lib/MasterServer/Database/Pg/dbServerlist.pm
@@ -9,8 +9,10 @@ our @EXPORT = qw| add_to_serverlist
update_serverlist
get_next_server |;
-## beacon was verified or otherwise accepted and will noe now be added to the
+################################################################################
+## beacon was verified or otherwise accepted and will now be added to the
## serverlist.
+################################################################################
sub add_to_serverlist {
my ($self, $ip, $port, $gamename) = @_;
@@ -44,13 +46,9 @@ sub add_to_serverlist {
}
################################################################################
-##
-## Subroutine update_serverlist
-##
-## Same as add_to_serverlist (above), but does not add the server to serverlist
-## if it does not exist in serverlist.
-##
-## Args: ip, port %info
+## same as add_to_serverlist above, but does not add the server to serverlist
+## if it does not exist in serverlist. it must be added by another function
+## first.
################################################################################
sub update_serverlist {
my ($self, $ip, $port, $s) = @_;
@@ -69,7 +67,7 @@ sub update_serverlist {
$ip, $port);
# notify
- $self->log("update", "server $ip:$port was updated: $s->{hostname}") if ($u > 0);
+ $self->log("update", "server $ip:$port info updated") if ($u > 0);
# return 0 if updated
return 0 if ($u > 0);
@@ -80,15 +78,9 @@ sub update_serverlist {
}
################################################################################
-##
-## Subroutine get_next_server
-##
-## Get a server address of the next server in line to be
-## queried for game info. Query must be older than 30 seconds (in case it just
-## got added) and not older than 3 hours.
-##
-## Args: $id --> id of a server address entry
-## Returns: hash {id, ip, port} of the NEXT entry in line.
+## get a server address of the next server in line to be queried for game info.
+## query must be older than 30 seconds (in case it just got added) and not
+## older than 3 hours.
################################################################################
sub get_next_server {
my ($self, $id) = @_;
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;