From 534626943a0a5e251e5465376f3de3fb71b25e91 Mon Sep 17 00:00:00 2001 From: Darkelarious Date: Wed, 11 Feb 2015 21:12:44 +0100 Subject: ability to query UCC applets (Pg only) --- lib/MasterServer/Core/Core.pm | 104 +++++-------------- lib/MasterServer/Core/Logging.pm | 6 +- lib/MasterServer/Core/Secure.pm | 39 ++++--- lib/MasterServer/Core/Util.pm | 6 +- lib/MasterServer/Core/Version.pm | 10 +- lib/MasterServer/Database/Pg/dbBeacon.pm | 150 ++++++++++++++++++--------- lib/MasterServer/Database/Pg/dbCore.pm | 6 +- lib/MasterServer/Database/Pg/dbServerlist.pm | 28 ++--- lib/MasterServer/UDP/BeaconCatcher.pm | 13 ++- lib/MasterServer/UDP/BeaconChecker.pm | 50 +++------ lib/MasterServer/UDP/DatagramProcessor.pm | 88 +++++++++++++--- lib/MasterServer/UDP/UCCAppletQuery.pm | 85 +++++++++++++++ 12 files changed, 357 insertions(+), 228 deletions(-) create mode 100755 lib/MasterServer/UDP/UCCAppletQuery.pm (limited to 'lib/MasterServer') 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; -- cgit v1.2.3