diff options
| author | Darkelarious <darkelarious@333networks.com> | 2015-05-19 22:00:40 +0200 |
|---|---|---|
| committer | Darkelarious <darkelarious@333networks.com> | 2015-05-19 22:00:40 +0200 |
| commit | 2c7d62f38944f61e7eafea155c6128521d16aed9 (patch) | |
| tree | bd473e6fb9dbbf8e0fdc67b87f0a4ac251fada7b /lib/MasterServer/TCP | |
| parent | 534626943a0a5e251e5465376f3de3fb71b25e91 (diff) | |
| download | MasterServer-Perl-2c7d62f38944f61e7eafea155c6128521d16aed9.tar.gz MasterServer-Perl-2c7d62f38944f61e7eafea155c6128521d16aed9.zip | |
Beta with support for Pg and SQLite
Diffstat (limited to 'lib/MasterServer/TCP')
| -rwxr-xr-x | lib/MasterServer/TCP/BrowserHost.pm | 68 | ||||
| -rwxr-xr-x | lib/MasterServer/TCP/Handler.pm | 280 | ||||
| -rwxr-xr-x | lib/MasterServer/TCP/ListCompiler.pm | 114 | ||||
| -rwxr-xr-x | lib/MasterServer/TCP/Syncer.pm | 166 |
4 files changed, 628 insertions, 0 deletions
diff --git a/lib/MasterServer/TCP/BrowserHost.pm b/lib/MasterServer/TCP/BrowserHost.pm new file mode 100755 index 0000000..b30b27e --- /dev/null +++ b/lib/MasterServer/TCP/BrowserHost.pm @@ -0,0 +1,68 @@ + +package MasterServer::TCP::BrowserHost; + +use strict; +use warnings; +use AnyEvent::Socket; +use AnyEvent::Handle; +use Exporter 'import'; + +our @EXPORT = qw| browser_host clean_tcp_handle|; + +################################################################################ +## wait for incoming TCP connections from game clients and other masterservers. +## respond with secure/validate, contact info and/or server lists. +## allow other masterservers to synchronize +################################################################################ +sub browser_host { + my $self = shift; + + # log: TCP host is active + $self->log("load","Loading TCP Browser Host."); + + my $browser = tcp_server undef, $self->{listen_port}, sub { + my ($fh, $a, $p) = @_; + + # validated? yes = 1 no = 0 + my $auth = 0; + + # debug -- new connection opened + $self->log("tcp","New connection from $a:$p"); + + # prep a challenge + my $secure = $self->secure_string(); + + # handle received data + my $h; $h = AnyEvent::Handle->new( + fh => $fh, + poll => 'r', + timeout => 1, + on_eof => sub {$self->clean_tcp_handle(@_)}, + on_error => sub {$self->clean_tcp_handle(@_)}, + on_read => sub {$self->read_tcp_handle($h, $a, $p, $secure, @_)}, + ); + + # part 1: send \basic\\secure\$key\ + $h->push_write("\\basic\\\\secure\\$secure\\final\\"); + + # keep handle alive longer and store authentication info + $self->{browser_clients}->{$h} = [$h, $auth]; + return; + }; + + # startup of TCP server complete + $self->log("load", "Listening for TCP connections on port $self->{listen_port}."); + return $browser; +} + +################################################################################ +## clean handles on timeouts, completed requests and/or errors +################################################################################ +sub clean_tcp_handle{ + my ($self, $c) = @_; + # clean and close the connection + delete ($self->{browser_clients}->{$c}); + $c->destroy(); +} + +1; diff --git a/lib/MasterServer/TCP/Handler.pm b/lib/MasterServer/TCP/Handler.pm new file mode 100755 index 0000000..bf6beb9 --- /dev/null +++ b/lib/MasterServer/TCP/Handler.pm @@ -0,0 +1,280 @@ + +package MasterServer::TCP::Handler; + +use strict; +use warnings; +use AnyEvent::Socket; +use AnyEvent::Handle; +use Exporter 'import'; + +our @EXPORT = qw| read_tcp_handle + handle_validate + handle_about + handle_list + handle_sync |; + +################################################################################ +## wait for incoming TCP connections from game clients and other masterservers. +## respond with secure/validate, contact info and/or server lists. +## allow other masterservers to synchronize +################################################################################ +sub read_tcp_handle { + my ($self, $h, $a, $p, $secure, $c) = @_; + + # clear the buffer + my $m = $c->rbuf; + $c->rbuf = ""; + + # did the client validate already? + my $val = $self->{browser_clients}->{$h}[1]; + + # in case of errors, save the original message + my $rxbuf = $m; + + # allow multiple blocks to add to the response string + my $response = ""; + + # replace empty values for the string "undef" and replace line endings from netcatters + # parse the received data and extrapolate all the query commands found + my %r = (); + $m =~ s/\\\\/\\undef\\/; + $m =~ s/\n//; + $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; + + # secure/validate challenge + # part 2: receive \gamename\ut\location\0\validate\$validate\final\ + $val = $self->handle_validate(\%r, $h, $secure, $a, $p) + if (exists $r{validate} && !$val); + + # about query + $response .= $self->handle_about($r{about}, $a, $p) if (exists $r{about}); + + # return address list + # part 3: wait for the requested action: \list\gamename\ut\ + $self->handle_list($val, \%r, $c, $a, $p) if (exists $r{list} && exists $r{gamename}); + + # Sync request from another 333networks-based masterserver. Respond with list + # of requested games (or all games). + $self->handle_sync($val, \%r, $c, $a, $p) if (exists $r{sync}); + + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # improper syntax/protocol -- no valid commands found + # respond with an error. + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + if ($m =~ m/!(about|sync|validate|list)/) { + + # error message to client + $c->push_write("\\echo\\333networks did not understand your request. ". + "Contact us via 333networks.com\\final\\"); + + # and log it + $self->log("error","invalid request from Browser $a:$p with unknown message \"$rxbuf\"", $self->{log_settings}->{handler_error}); + } # end if weird query + else { + $c->push_write($response . "\\final\\") if ($response ne ""); + } + +} + + +################################################################################ +## The master server opens the connection with the \secure\ challenge. The +## client should respond with basic information about itself and the +## \validate\ response. In this code block we verify the challenge/response. +################################################################################ +sub handle_validate { + my ($self, $r, $h, $secure, $a, $p) = @_; + + # auth var init + my $val = 0; + + # pass or fail the secure challenge + if (exists $r->{gamename} && exists $self->{game}->{$r->{gamename}}) { + # game exists and we have the key to verify the response + $val = $self->validated_request($r->{gamename}, $secure, $r->{enctype}, $r->{validate}); + + # update for future queries + $self->{browser_clients}->{$h}[1] = $val; + } + elsif (exists $r->{gamename}) { + # log + $self->log("support", "received unknown gamename request \"$r->{gamename}\" from $a:$p"); + } + + # log + $self->log("secure","$a:$p validated with $val for $r->{gamename}, $secure, $r->{validate}"); + + # return auth status + return $val; +} + + +################################################################################ +## \about\ query. +## Can contain the values "contact", "build", "Address" or "support", where +## - contact: return contact information; see config.pl +## - build: build info, also version.pm +## - address: address and ports +## - support: return a list of games currently in the database +## - undef: all of the above +## +## NOTE: client does not need to validate to be allowed to perform this +## query. +################################################################################ +sub handle_about { + my ($self, $about, $a, $p) = @_; + my $response = ""; + + # + # contact info + # + if ($about =~ /^contact$/i or $about =~ /^undef$/i) { + $response .= "\\about\\$self->{contact_details}"; + + # log/print + $self->log("about","communicating to $a:$p my contact information."); + } + + # + # build info + # + if ($about =~ /^build$/i or $about =~ /^undef$/i) { + + $response .= "\\build\\$self->{build_type} $self->{build_version} written " + . "by $self->{build_author}, released $self->{build_date}"; + + # log/print + $self->log("about","telling $a:$p my build info."); + } + + # + # address info + # + if ($about =~ /^address$/i or $about =~ /^undef$/i) { + + $response .= "\\address\\$self->{masterserver_address}" + . "\\listen_port\\$self->{listen_port}" + . "\\beacon_port\\$self->{beacon_port}"; + + # log/print + $self->log("about","telling $a:$p my address/config info."); + } + + # + # support info + # + if ($about =~ /^support$/i or $about =~ /^undef$/i) { + + # string games in database + my $sg = $self->get_gamenames(); + my $sgs = ""; + for (@{$sg}) { + $sgs .= " " if (length $sgs > 0); + $sgs .= $_->[0]; + } + + # print response + $response .= "\\support\\$sgs"; + + #log/print + $self->log("about","telling $a:$p which games are supported."); + } + + # return response string + return $response; +} + +################################################################################ +## At this point, the client should be validated and ready to request with +## the \secure\ command and is allowed to ask for the list. +## The \list\ cmd is NOT compatible with combined queries +## (like \about\contact\list\) +################################################################################ +sub handle_list { + my ($self, $val, $r, $c, $a, $p) = @_; + + # confirm validation + if ($val && exists $r->{gamename}) { + + # prepare the list + my $data = ""; + + # determine the return format + if ($self->{hex_format} =~ m/$r->{gamename}/i or $r->{gamename} =~ /^cmp$/i) { + # return addresses as byte format (ip=ABCD port=EF) + $data .= $self->compile_list_cmp($r->{gamename}); + } + else { + # return addresses as regular \ip\127.0.0.1:7777\ format + $data .= $self->compile_list($r->{gamename}); + } + + # finalize response string + $data .= "\\final\\"; + + # immediately send to client + $c->push_write($data); + + # log successful (debug) + $self->log("list","$a:$p successfully retrieved the list for $r->{gamename}."); + + # clean and close the connection + $self->clean_tcp_handle($c); + } + + # proper syntax/protocol, but incorrect validation. Therefore respond with + # an 'empty' list, returning only \final\. + else { + # return error/empty list + $c->push_write("\\echo\\333networks failed to validate your request. Use the correct authorization cipher!\\final\\"); + + # log it too + $self->log("error","browser $a:$p failed validation for $r->{gamename}"); + + # clean and close the connection + $self->clean_tcp_handle($c); + } +} + +################################################################################ +## Respond to \sync\ requests from other 333networks-based masterservers. After +## validation, sync behaves in much the same way as \list\, +################################################################################ +sub handle_sync { + my ($self, $val, $r, $c, $a, $p) = @_; + + # alternate part 3: wait for the requested action: \sync\(all|list of games) + $self->log("tcp","Sync request from $a:$p found"); + + if ($val && exists $r->{sync}) { + + # compile list of addresses + my $data = $self->compile_sync($r->{sync}); + $data .= "\\final\\"; + + # send to remote client + $c->push_write($data); + + # log successful (debug) + $self->log("sync","$a:$p successfully synced."); + + # clean and close the connection + $self->clean_tcp_handle($c); + + } + # proper syntax/protocol, but incorrect validation. Therefore respond with + # an 'empty' list, returning only \final\. + else { + + # return error/empty list + $c->push_write("\\echo\\333networks failed to validate your request. Use a proper authorization key!\\final\\"); + + # log it too + $self->log("error","$a:$p failed synchronization."); + + # clean and close the connection + $self->clean_tcp_handle($c); + } +} + +1; diff --git a/lib/MasterServer/TCP/ListCompiler.pm b/lib/MasterServer/TCP/ListCompiler.pm new file mode 100755 index 0000000..56bb256 --- /dev/null +++ b/lib/MasterServer/TCP/ListCompiler.pm @@ -0,0 +1,114 @@ + +package MasterServer::TCP::ListCompiler; + +use strict; +use warnings; +use Exporter 'import'; + +our @EXPORT = qw| compile_list compile_list_cmp compile_sync |; + +################################################################################ +## compile the list of \ip\ip:port\ addresses and parse them into the +## plaintext return string. +################################################################################ +sub compile_list { + my ($self, $gamename) = @_; + + # get the list from database + my $serverlist = $self->get_game_list($gamename); + + # prepare empty return string + my $response_string = ""; + + # add address as regular \ip\127.0.0.1:7777\ format + for (@{$serverlist}){ + + # append \ip\ip:port to string + $response_string .= "\\ip\\$_->[0]:$_->[1]"; + } + + # return the string with data + return $response_string; +} + +################################################################################ +## compile the list of binary ip:port addresses and parse them into the +## ABCDE return string. +################################################################################ +sub compile_list_cmp { + my ($self, $gamename) = @_; + + # get the list from database + my $serverlist = $self->get_game_list($gamename); + + # prepare empty return string + my $response_string = ""; + + # compile a return string + for (@{$serverlist}){ + + # convert ip address to ABCDEF mode + my ($A, $B, $C, $D) = ($_->[0] =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/); + my ($E, $F) = ($_->[1] >> 8, $_->[1] & 0xFF); + + # print as chr string of 6 bytes long + my $bin = ""; $bin .= (chr $A) . (chr $B) . (chr $C) . (chr $D) . (chr $E) . (chr $F); + + # append to list of addresses + $response_string .= $bin; + } + + # return the string with data + return $response_string; +} + + +################################################################################ +## compile a list of all requested games --or-- if not specified, a list of +## all games +################################################################################ +sub compile_sync { + my ($self, $sync) = @_; + + # prepare empty return string + my $response_string = ""; + my @games; + + # client requests to sync all games + if ($sync eq "all") { + # get array of gamenames from db + my $sg = $self->get_gamenames(); + for (@{$sg}) {push @games, $_->[0];} + } + # only selected games + else { + # split request into array + @games = split " ", $sync; + } + + # only get unique values from array + @games = map { $_ => 1 } @games; + + # get the list for every requested gamename + for my $g (@games) { + + # $g is now a gamename -- check if it's supported. Else ignore. + if (exists $self->{game}->{$g}) { + + # get list from database + my $list = $self->get_game_list($g); + + # add all games to string separated by spaces + my $gamestring = ""; + foreach $_ (@{$list}) {$gamestring .= "$_->[0]:$_->[1] ";} + + # if it contains at least one entry, add the list to the response list + $response_string .= "\\$g\\$gamestring" if (length $gamestring >= 7); + } + } + + # return \gamename\addresses\gamename2\addresses2 list + return $response_string; +} + +1; diff --git a/lib/MasterServer/TCP/Syncer.pm b/lib/MasterServer/TCP/Syncer.pm new file mode 100755 index 0000000..dea20a5 --- /dev/null +++ b/lib/MasterServer/TCP/Syncer.pm @@ -0,0 +1,166 @@ + +package MasterServer::TCP::Syncer; + +use strict; +use warnings; +use AnyEvent; +use AnyEvent::Handle; +use Exporter 'import'; +use Data::Dumper 'Dumper'; + +our @EXPORT = qw| syncer_scheduler sync_with_master process_sync_list|; + +################################################################################ +## Syncer Scheduler +## Request the masterlist for selected or all games from other +## 333networks-based masterservers. +################################################################################ +sub syncer_scheduler { + my $self = shift; + + # log active + $self->log("load", "Synchronisation module active."); + + # go through the list of provided addresses + my $i = 0; + return AnyEvent->timer ( + after => $self->{sync_time}[0], + interval => $self->{sync_time}[1], + cb => sub { + # check if there's a master server entry to be synced. If not, return + # to zero and go all over again. + $i = 0 unless $self->{sync_masters}[$i]; + return if (!defined $self->{sync_masters}[$i]); + + # synchronze with master $i + $self->log("tcp", "Attempting to synchronize with $self->{sync_masters}[$i]->{address}"); + $self->sync_with_master($self->{sync_masters}[$i]); + + #increment counter + $i++; + } + ); +} + +################################################################################ +## Sends synchronization request to another 333networks based master server and +## receives the list of games. +################################################################################ +sub sync_with_master { + my ($self, $ms) = @_; + + # list to store all IPs in. + my $sync_list = ""; + + # connection handle + my $handle; + $handle = new AnyEvent::Handle( + connect => [$ms->{address} => $ms->{port}], + timeout => 3, + poll => 'r', + on_error => sub {$self->log("error","$! on $ms->{address} $ms->{port}"); $handle->destroy;}, + on_eof => sub {$self->process_sync_list($sync_list, $ms); $handle->destroy;}, + on_read => sub { + # receive and clear buffer + my $m = $_[0]->rbuf; + $_[0]->rbuf = ""; + + # remove string terminator: sometimes trailing slashes are added or + # forgotten by sender, so \secure\abcdef is actually \secure\abcdef{\0} + chop $m if $m =~ m/secure/; + + # part 1: receive \basic\\secure\$key + if ($m =~ m/basic\\\\secure/) { + + # hash $m into %r + my %r = (); + $m =~ s/\\\\/\\undef\\/; + $m =~ s/\n//; + $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; + + # respond to the validate challenge + my $validate = $self->validate_string("333networks", $r{secure}, $r{enctype}); + + # part 2: send \gamename\ut\location\0\validate\$validate\final\ + $handle->push_write("\\gamename\\333networks\\location\\0\\validate\\$validate\\final\\"); + + # part 3: request the list \sync\gamenames consisting of space-seperated game names or "all" + my $request = "\\sync\\".(($self->{sync_games}[0] == 0) ? "all" : $self->{sync_games}[1])."\\final\\"; + + # push the request to remote host + $handle->push_write($request); + + # clean up $m for future receivings + $m = ""; + + } # end secure + + # part 4: receive the entire list in multiple steps + $sync_list .= $m; + }, + ); +} + +################################################################################ +## Process the list of addresses that was received after querying the UCC applet +## and store them in the pending list. +################################################################################ +sub process_sync_list { + my ($self, $m, $ms) = @_; + + # replace empty values for the string "undef" and replace line endings from netcatters + # parse hash {gamename => list of ips seperated by space} + my %r = (); + $m =~ s/\\\\/\\undef\\/; + $m =~ s/\n//; + $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; + + # counter + my $c = 0; + + # iterate through the gamenames and addresses + while ( my ($gn,$addr) = each %r) { + + # only process gamenames that are in our list for supported games (supportedgames.pl) + if (defined $gn && exists $self->{game}->{lc $gn}) { + + # database types such as SQLite are slow, therefore use transactions. + $self->{dbh}->begin_work; + + # l(ocations, \label\ip:port\) split up in a(ddress) and p(ort) + foreach my $l (split(/ /, $addr)) { + + # search for \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++; + + # add server + $self->syncer_add($a, $p, $gn, $self->secure_string()); + + # print address + $self->log("add", "syncer added $gn\t$a\t$p"); + } + else { + # invalid address, log + $self->log("error", "invalid address found while syncing at $ms->{address}: $l!"); + } + + } # endif ($l =~ /:/) + } # end for / / + + # end transaction, commit + $self->{dbh}->commit; + + } # end defined $gn + } # end while + + # end message + $self->log("sync", "received $c addresses after syncing from $ms->{address}"); +} + +1; |
