diff options
| author | Darkelarious <darkelarious@333networks.com> | 2017-05-13 14:18:28 +0200 |
|---|---|---|
| committer | Darkelarious <darkelarious@333networks.com> | 2017-05-13 14:20:49 +0200 |
| commit | 34a2c7390ea9662d33258d384e72fff1912343ff (patch) | |
| tree | d96ea33c0107e4906a152aa1de4b5c75b81ba0a8 /lib/MasterServer/UDP | |
| parent | 84af66aba26d2088d5d95c240d176f3edaf17b58 (diff) | |
| download | MasterServer-Perl-34a2c7390ea9662d33258d384e72fff1912343ff.tar.gz MasterServer-Perl-34a2c7390ea9662d33258d384e72fff1912343ff.zip | |
revised synchronization methods, config settings and bug fixesv2.3.0
Diffstat (limited to 'lib/MasterServer/UDP')
| -rwxr-xr-x | lib/MasterServer/UDP/BeaconCatcher.pm | 21 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/BeaconChecker.pm | 29 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/DatagramProcessor.pm | 116 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/UDPTicker.pm | 51 | ||||
| -rwxr-xr-x | lib/MasterServer/UDP/UpLink.pm | 93 |
5 files changed, 130 insertions, 180 deletions
diff --git a/lib/MasterServer/UDP/BeaconCatcher.pm b/lib/MasterServer/UDP/BeaconCatcher.pm index 06fd38a..5c4ff5f 100755 --- a/lib/MasterServer/UDP/BeaconCatcher.pm +++ b/lib/MasterServer/UDP/BeaconCatcher.pm @@ -1,4 +1,3 @@ - package MasterServer::UDP::BeaconCatcher; use strict; @@ -45,8 +44,7 @@ sub on_beacon_receive { my ($port, $iaddr) = sockaddr_in($pa); my $peer_addr = inet_ntoa($iaddr); - # if the beacon has a length longer than a certain amount, assume it is - # a fraud or crash attempt + # assume fraud/crash attempt if response too long if (length $b > 64) { # log $self->log("attack","length exceeded in beacon: $peer_addr:$port sent $b"); @@ -55,17 +53,20 @@ sub on_beacon_receive { $b = substr $b, 0, 64; } - # if a heartbeat format was detected... - $self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port) - if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/); + # FIXME: note to self: order is important when having combined queries! + # TODO: find a more elegant and long-time solution for this. - # or if this is a secure response, verify the response + # if this is a secure response, verify the response $self->process_udp_validate($b, $peer_addr, $port, undef) if ($b =~ m/\\validate\\/); + + # if a heartbeat format was detected... + $self->process_udp_beacon($udp, $pa, $b, $peer_addr, $port) + if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/); - # or if other masterservers check if we're still alive - $self->process_udp_basic($udp, $pa, $b, $peer_addr) - if ($b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/); + # if other masterservers check if we're still alive + $self->process_udp_secure($udp, $pa, $b, $peer_addr) + if ($b =~ m/\\secure\\/ || $b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/); } 1; diff --git a/lib/MasterServer/UDP/BeaconChecker.pm b/lib/MasterServer/UDP/BeaconChecker.pm index f74378d..73220cf 100755 --- a/lib/MasterServer/UDP/BeaconChecker.pm +++ b/lib/MasterServer/UDP/BeaconChecker.pm @@ -1,4 +1,3 @@ - package MasterServer::UDP::BeaconChecker; use strict; @@ -6,7 +5,7 @@ use warnings; use AnyEvent::Handle::UDP; use Exporter 'import'; -our @EXPORT = qw| query_udp_server|; +our @EXPORT = qw| query_udp_server |; ################################################################################ ## Get the server status from any server over UDP and store the received @@ -17,20 +16,28 @@ sub query_udp_server { my ($self, $id, $ip, $port, $secure, $message_type) = @_; my $buf = ""; - # debug spamming - $self->log("udp", "Query server $id ($ip:$port)"); + # debug logging + # $self->log("debug", "Query server $id ($ip:$port)"); # connect with UDP server my $udp_client; $udp_client = AnyEvent::Handle::UDP->new( - # Bind to this host and port connect => [$ip, $port], - timeout => 1, - on_timeout => sub {$udp_client->destroy();}, # don't bother reporting timeouts + timeout => $self->{timeout_time}, + on_timeout => sub {$udp_client->destroy();}, # do not report timeouts on_error => sub {$udp_client->destroy();}, # or errors - on_recv => sub { + on_recv => sub { # add packet to buffer $buf .= $_[0]; + + # FIXME: note to self: order is important when having combined queries! + # TODO: find a more elegant and long-time solution for this. + + # message type 1: \basic\\secure\wookie + # if validate, assume that we sent a \basic\secure request. + if ($buf =~ m/\\validate\\/){ + $self->process_udp_validate($buf, $ip, undef, $port); + } # message type 0: \basic\\info\ # if gamename, ver, hostname and hostport are available, but NOT the value @@ -42,12 +49,6 @@ sub query_udp_server { $self->process_query_response($buf, $ip, $port); } - # message type 1: \basic\\secure\wookie - # if validate, assume that we sent a \basic\secure request. - if ($buf =~ m/\\validate\\/){ - $self->process_udp_validate($buf, $ip, undef, $port); - } - # message type 2: \status\ # contains same info as \basic\\info, but also "listenserver". Only for UT. if ($buf =~ m/\\gamename\\ut/ && diff --git a/lib/MasterServer/UDP/DatagramProcessor.pm b/lib/MasterServer/UDP/DatagramProcessor.pm index d2a3333..2a26763 100755 --- a/lib/MasterServer/UDP/DatagramProcessor.pm +++ b/lib/MasterServer/UDP/DatagramProcessor.pm @@ -38,18 +38,18 @@ sub process_udp_beacon { # if no entry exists, report error. if (defined $game_props) { - - # some games (like bcommander) have a default port and don't send a heartbeat port. - $r{heartbeat} = $game_props->{heartbeat} if ($r{heartbeat} == 0); + + # validate heartbeat data + my $heartbeat = ($r{heartbeat} || $game_props->{default_qport}); # # verify valid server address (ip+port) - if ($self->valid_address($peer_addr,$r{heartbeat})) { + if ($self->valid_address($peer_addr,$heartbeat)) { # check if the entry already was not added within the last 5 seconds, throttle otherwise my $throttle = $self->get_pending( ip => $peer_addr, - heartbeat => $r{heartbeat}, + heartbeat => $heartbeat, gamename => $r{gamename}, after => 5, sort => "added", @@ -64,7 +64,7 @@ sub process_udp_beacon { # or add to pending with new secure string. my $auth = $self->add_server_new(ip => $peer_addr, beaconport => $port, - heartbeat => $r{heartbeat}, + heartbeat => $heartbeat, gamename => $r{gamename}, secure => $secure, direct => 1, @@ -77,8 +77,8 @@ sub process_udp_beacon { # verify that this is a legitimate client by sending the "secure" query $udp->push_send("\\secure\\$secure\\final\\", $pa); - # log this as a new beacon - $self->log("secure", "challenged new beacon $peer_addr:$port with $secure."); + # log this as a new beacon (debug) + #$self->log("secure", "challenged new beacon $peer_addr:$port with $secure."); } } @@ -91,7 +91,7 @@ sub process_udp_beacon { } # unknown game else { - $self->log("invalid","$peer_addr tries to identify as unknown game \"$r{gamename}\"."); + $self->log("support","$peer_addr tries to identify as unknown game \"$r{gamename}\"."); } } @@ -100,13 +100,7 @@ sub process_udp_beacon { # be extrapolated from the heartbeat else { # log - $self->log("support", "received unknown beacon from $peer_addr --> $raw"); - # - # TODO: more practical way to log this to the database: new table - # named "unsupported" where messages are logged by ip, port, gamename (if - # applicable) and TEXT raw message. - # - + $self->log("support", "received unknown beacon from $peer_addr --> '$raw'"); } } @@ -138,7 +132,7 @@ sub process_udp_validate { # was either removed by the BeaconChecker or cleaned out in maintenance (after X hours). if (defined $pending) { - #determine if it uses any enctype + # determine if it uses any enctype my $enc = (defined $r{enctype}) ? $r{enctype} : 0; # database may not contain the correct gamename (ucc applet, incomplete beacon, other game) @@ -169,7 +163,7 @@ sub process_udp_validate { # remove the entry from pending if successfully added $self->remove_pending($pending->{id}) if ( $sa >= 0); } - # was not found, insert clean and remove from pending + # was not found in serverlist, insert clean and remove from pending else { my $sa = $self->add_server_list( ip => $pending->{ip}, @@ -183,37 +177,51 @@ sub process_udp_validate { else { # else failed validation # calculate expected result for log - my $validate_string = $self->validate_string( - gamename => $pending->{gamename}, - secure => $pending->{secure} + + my $validate_string = ""; + if ($pending->{gamename} && $pending->{secure}) { + $validate_string = $self->validate_string( + gamename => $pending->{gamename}, + secure => $pending->{secure} + ); + } + $self->log("secure","$pending->{id} for ". + ($pending->{gamename} || "empty_p_gamename") + ." sent: '". ($pending->{secure} || "empty_p_secure") + ."', expected '". ($validate_string || "empty_v_string") + ."', got '". ($r{validate} || "empty_r_validate") + ."'" ); - $self->log("secure","$pending->{id} for $pending->{gamename} sent: $pending->{secure}, got $r{validate}, expected $validate_string"); } } # if no entry found in pending list else { - # 404 not found - $self->log("error","server not found in pending for $peer_addr:", - ($heartbeat ? $heartbeat : "" ), ($port ? $port : "" ), " !"); + # not found + $self->log("error","server not found in pending for ". + ($peer_addr || "ip") .":". + ($heartbeat || "0") .",". + ($port || "0") ." !"); } } ################################################################################ ## Process query data that was obtained with \basic\ and/or \info\ from the ## beacon checker module. +## FIXME: error checking and data processing. ($_ || "default") instead. ################################################################################ sub process_query_response { # $self, udp data, ip, port my ($self, $buf, $ip, $port) = @_; - #process datastream - my %s; + # process datastream + my %s = (); $buf = encode('UTF-8', $buf); $buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg; + + # check whether the gamename is supported in our db - if (defined $s{gamename} && - length $self->get_game_props($s{gamename})->{cipher} > 1) { + if (exists $s{gamename} && $self->get_game_props($s{gamename})) { # parse variables my %nfo = (); @@ -224,19 +232,22 @@ sub process_query_response { # some mor0ns have hostnames longer than 200 characters $nfo{hostname} = substr $nfo{hostname}, 0, 199 if (length $nfo{hostname} >= 199); - # log results - $self->log("hostname", "$ip:$port is now known as $nfo{hostname}"); + # log results (debug) + # $self->log("hostname", "$ip:$port is now known as $nfo{hostname}"); # add or update in serverlist (assuming validation is complete) - $self->update_server_list( + my $result = $self->update_server_list( ip => $ip, port => $port, gamename => $s{gamename}, %nfo); - + # if address is in pending list, remove it my $pen = $self->get_pending(ip => $ip, heartbeat => $port)->[0]; $self->remove_pending($pen->{id}) if $pen; + + # log potential error + $self->log("support", "no entries were updated for $ip:$port ($s{gamename}), but it was still removed from pending!") if ($result == 0 && $pen); } } @@ -248,7 +259,7 @@ sub process_status_response { # $self, udp data, ip, port my ($self, $buf, $ip, $port) = @_; - #process datastream + # process datastream my %s; $buf = encode('UTF-8', $buf); $buf =~ s/\\([^\\]+)\\([^\\]+)/$s{$1}=$2/eg; @@ -312,16 +323,15 @@ sub process_status_response { # write to db $self->insert_utplayer($serverlist_id->{id}, %player); } - - # - # Prevent null concatenation in logging - $s{numplayers} ||= 0; - $s{maxplayers} ||= 0; - $s{mapname} ||= "Unknown map"; - $s{hostname} ||= "Unknown hostname"; - - # log results - $self->log("utserver", "$serverlist_id->{id}, $ip:$port,\t $s{numplayers}/$s{maxplayers} players, $s{mapname}, $s{hostname}"); + + # log results (debug) + #$self->log("utserver", + # "$serverlist_id->{id}, $ip:$port,\t". + # ($s{numplayers} || "0") ."/". + # ($s{maxplayers} || "0") ."players, ". + # ($s{mapname} || "mapname") .",". + # ($s{hostname} || "hostname") + #); } } @@ -351,28 +361,34 @@ sub process_ucc_applet_query { # count number of valid addresses $c++; - # print address - $self->log("add", "applet query added $ms->{game}\t$a\t$p"); + # print address (debug) + # $self->log("add", "applet query added $ms->{gamename}\t$a\t$p"); # add server $self->add_server_new(ip => $a, beaconport => $p, heartbeat => $p, - gamename => $ms->{game}, + gamename => $ms->{gamename}, secure => $self->secure_string(), updated => time); } # invalid address, log - else {$self->log("error", "invalid address found at master applet $ms->{ip}: $l!");} + else {$self->log("error", "invalid address found at master applet $ms->{ip}, $l!");} } } # end transaction, commit $self->{dbh}->commit; + + # update time if successful applet query + $self->update_master_applet( + ip => $ms->{ip}, + port => $ms->{port}, + gamename => $ms->{gamename}, + ) if ($c > 0); # print findings - $self->log("applet-rx","found $c addresses at $ms->{ip} for $ms->{game}."); - + $self->log("applet-rx","found $c addresses at $ms->{ip} for $ms->{gamename}."); } 1; diff --git a/lib/MasterServer/UDP/UDPTicker.pm b/lib/MasterServer/UDP/UDPTicker.pm index 5a34a8f..6b3a681 100755 --- a/lib/MasterServer/UDP/UDPTicker.pm +++ b/lib/MasterServer/UDP/UDPTicker.pm @@ -1,10 +1,10 @@ - package MasterServer::UDP::UDPTicker; use strict; use warnings; use AnyEvent::Handle::UDP; use Exporter 'import'; +use Data::Dumper 'Dumper'; our @EXPORT = qw| udp_ticker |; @@ -45,7 +45,7 @@ sub udp_ticker { # go through all servers that need querying my $server_info = AnyEvent->timer ( - after => 75, # first give beacons a chance to uplink + after => 120, # first give beacons a chance to uplink interval => 0.2, # 5 addresses per second is fast enough cb => sub { @@ -71,7 +71,7 @@ sub udp_ticker { if (time - $ut_serv{start} > $ut_serv{limit}) { if ($ut_serv{c} > 0) { # done within defined time, reset - %ut_serv = (%ut_serv, %reset) + %ut_serv = (%ut_serv, %reset); } } @@ -89,48 +89,6 @@ sub udp_ticker { %oldserv = (%oldserv, %reset); } } - - # - # else { print "Making overtime!" } - -=pod - # FIXME remove this if above works - - # debug: detect premature resets - if (time - $pending{start} > $pending{limit}) { - if ($pending{c} == 0) { - print "Premature pending reset\n" ; - } - else{$pending{c} = 0;} - } - - if (time - $updater{start} > $updater{limit}) { - if ($updater{c} == 0) { - print "Premature updater reset\n" ; - } - else{$updater{c} = 0;} - } - - if (time - $ut_serv{start} > $ut_serv{limit}) { - if ($ut_serv{c} == 0) { - print "Premature ut_serv reset\n" ; - } - else{$ut_serv{c} = 0;} - } - - if (time - $oldserv{start} > $oldserv{limit}) { - if ($oldserv{c} == 0) { - print "Premature oldserv reset\n" ; - } - else{$oldserv{c} = 0;} - } - - # are we making overtime on any of the counters yet? - %pending = (%pending, %reset) if (time - $pending{start} > $pending{limit}); - %updater = (%updater, %reset) if (time - $updater{start} > $updater{limit}); - %ut_serv = (%ut_serv, %reset) if (time - $ut_serv{start} > $ut_serv{limit}); - %oldserv = (%oldserv, %reset) if (time - $oldserv{start} > $oldserv{limit}); -=cut } # @@ -242,6 +200,7 @@ sub udp_ticker { $n = $self->get_server( next_id => $oldserv{id}, before => 7200, + (defined $self->{firstrun}) ? () : (updated => 86400), # FIXME long firstrun time fixed now? sort => "id", limit => 1, )->[0] if $self->{beacon_checker_enabled}; @@ -271,7 +230,7 @@ sub udp_ticker { if (!defined $self->{firstrun}) { # inform that first run is completed my $t = time-$self->{firstruntime}; - my $t_readable = ($t > 60) ? (($t/60). ":". ($t%60). "minutes") : ($t. "seconds"); + my $t_readable = ($t > 60) ? (int($t/60). " minutes ". ($t%60). " seconds") : ($t. " seconds"); $self->log("info", "First run completed after $t_readable."); $self->{firstrun} = 0; diff --git a/lib/MasterServer/UDP/UpLink.pm b/lib/MasterServer/UDP/UpLink.pm index e5c703b..63cbefb 100755 --- a/lib/MasterServer/UDP/UpLink.pm +++ b/lib/MasterServer/UDP/UpLink.pm @@ -10,8 +10,7 @@ use Exporter 'import'; our @EXPORT = qw| send_heartbeats do_uplink process_uplink_response - process_udp_secure - process_udp_basic |; + process_udp_secure |; ################################################################################ ## Broadcast heartbeats to other masterservers @@ -23,16 +22,21 @@ sub send_heartbeats { # in order to be permitted to sync, you need to share your address too so # others can sync from you too. if ($self->{sync_enabled}) { - - # uplink to every entry of the masterserver brotherhood list - foreach my $uplink (values %{$self->masterserver_list()}) { + + # get serverlist + my $masterserverlist = $self->get_server( + updated => 3600, + gamename => "333networks", + ); + + # uplink to every 333networks-based masterserver + foreach my $ms (@{$masterserverlist}) { # send uplink - $self->do_uplink($uplink->{ip}, $uplink->{udp}); + $self->do_uplink($ms->{ip}, $ms->{port}); } - } + } } - ################################################################################ ## Do an uplink to other 333networks-based masterservers so we can be shared ## along the 333networks synchronization protocol. Other 333networks-based @@ -44,16 +48,15 @@ sub do_uplink { # do not proceed if not all information is available return unless (defined $ip && defined $port && $port > 0); - # debug spamming + # report uplinks to log $self->log("uplink", "Uplink to Masterserver $ip:$port"); # connect with UDP server my $udp_client; $udp_client = AnyEvent::Handle::UDP->new( - # Bind to this host and port connect => [$ip, $port], - timeout => 5, - on_timeout => sub {$udp_client->destroy();}, # don't bother reporting timeouts - on_error => sub {$udp_client->destroy();}, # or errors + timeout => $self->{timeout_time}, + on_timeout => sub {$udp_client->destroy()}, + on_error => sub {$udp_client->destroy()}, on_recv => sub {$self->process_uplink_response(@_)}, ); @@ -73,8 +76,7 @@ sub process_uplink_response { my ($port, $iaddr) = sockaddr_in($pa); my $peer_addr = inet_ntoa($iaddr); - # if the beacon has a length longer than a certain amount, assume it is - # a fraud or crash attempt + # assume fraud/crash attempt if response too long if (length $b > 64) { # log $self->log("attack","length exceeded in uplink response: $peer_addr:$port sent $b"); @@ -91,27 +93,28 @@ sub process_uplink_response { ################################################################################ ## Process the received secure query and respond with the correct response -## +## TODO: expand queries with support for info, rules, players, status, etc ################################################################################ sub process_udp_secure { # $self, handle, packed address, udp data, peer ip address, $port my ($self, $udp, $pa, $buf, $peer_addr) = @_; - - # received secure in $buf: \basic\secure\l8jfVy + + # received secure in $buf: \basic\\secure\wookie my %r; - my $raw = $buf; # raw buffer for logging if necessary + $buf = encode('UTF-8', $buf); $buf =~ s/\\\\/\\undef\\/; $buf =~ s/\n//; $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; - # scope + # response string my $response = ""; - # provide basic information if asked for (not uncommon) - if (defined $r{basic}) { - # compile basic string (identical to process_udp_basic) - + # compile basic string + + # provide basic information if asked for + if (defined $r{basic} || defined $r{status} || defined $r{info}) { + # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1 $response .= "\\gamename\\333networks" . "\\gamever\\$self->{short_version}" @@ -119,51 +122,21 @@ sub process_udp_secure { . "\\hostname\\$self->{masterserver_hostname}" . "\\hostport\\$self->{listen_port}"; } + + # TODO: add queryid -- not because it's useful, but because protocol compliant - # we only respond with gamename = 333networks + # support for secure/validate if (defined $r{secure}) { - # get response + # generate response + $response .= "\\validate\\" . $self->validate_string(gamename => "333networks", enctype => 0, secure => $r{secure}); } - # send the response to the \basic\\secure\wookie query + # send the response $udp->push_send("$response\\final\\", $pa); } -################################################################################ -## Respond to basic or status queries -## TODO: abstract function for this -- otherwise these functions pile up. -################################################################################ -sub process_udp_basic { - # $self, handle, packed address, udp data, peer ip address, $port - my ($self, $udp, $pa, $buf, $peer_addr) = @_; - - # received basic or status in $buf: \basic\ or \status\ - my %r; - $buf = encode('UTF-8', $buf); - $buf =~ s/\\\\/\\undef\\/; - $buf =~ s/\n//; - $buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg; - - # scope - my $basic = ""; - # provide basic information - - if (defined $r{basic} || defined $r{status} || defined $r{info}) { - # compile basic string (identical to process_udp_basic) - - # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1 - $basic = "\\gamename\\333networks" - . "\\gamever\\$self->{short_version}" - . "\\location\\0" - . "\\hostname\\$self->{masterserver_hostname}" - . "\\hostport\\$self->{listen_port}"; - } - - # send the response to the \basic\ or \status\ - $udp->push_send("$basic\\final\\", $pa); -} 1; |
