diff options
Diffstat (limited to 'lib/MasterServer/UDP')
| -rwxr-xr-x | lib/MasterServer/UDP/BeaconCatcher.pm | 13 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/BeaconChecker.pm | 50 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/DatagramProcessor.pm | 88 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/UCCAppletQuery.pm | 85 |
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; |
