From c3f8d65a4fb1f5674557ee67cf7f74369df86ad1 Mon Sep 17 00:00:00 2001 From: Darkelarious Date: Sat, 19 Nov 2016 20:56:04 +0100 Subject: Massive improvements on efficiency, robustness, security, reliability and more --- lib/MasterServer/UDP/UpLink.pm | 169 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100755 lib/MasterServer/UDP/UpLink.pm (limited to 'lib/MasterServer/UDP/UpLink.pm') diff --git a/lib/MasterServer/UDP/UpLink.pm b/lib/MasterServer/UDP/UpLink.pm new file mode 100755 index 0000000..e5c703b --- /dev/null +++ b/lib/MasterServer/UDP/UpLink.pm @@ -0,0 +1,169 @@ +package MasterServer::UDP::UpLink; + +use strict; +use warnings; +use Encode; +use AnyEvent::Handle::UDP; +use Socket qw(sockaddr_in inet_ntoa); +use Exporter 'import'; + +our @EXPORT = qw| send_heartbeats + do_uplink + process_uplink_response + process_udp_secure + process_udp_basic |; + +################################################################################ +## Broadcast heartbeats to other masterservers +## +################################################################################ +sub send_heartbeats { + my $self = shift; + + # 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()}) { + # send uplink + $self->do_uplink($uplink->{ip}, $uplink->{udp}); + } + } +} + + +################################################################################ +## Do an uplink to other 333networks-based masterservers so we can be shared +## along the 333networks synchronization protocol. Other 333networks-based +## masterservers are shared in this way too. +################################################################################ +sub do_uplink { + my ($self, $ip, $port) = @_; + + # do not proceed if not all information is available + return unless (defined $ip && defined $port && $port > 0); + + # debug spamming + $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 + on_recv => sub {$self->process_uplink_response(@_)}, + ); + + # Send heardbeat + $udp_client->push_send("\\heartbeat\\$self->{beacon_port}\\gamename\\333networks"); +} + +################################################################################ +## Process requests received after uplinking +## +################################################################################ +sub process_uplink_response { + # $self, beacon address, handle, packed client address + my ($self, $b, $udp, $pa) = @_; + + # unpack ip from packed client address + 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 + if (length $b > 64) { + # log + $self->log("attack","length exceeded in uplink response: $peer_addr:$port sent $b"); + + # truncate and try to continue + $b = substr $b, 0, 64; + } + + # check if this is a secure challenge + $self->process_udp_secure($udp, $pa, $b, $peer_addr) + if ($b =~ m/\\secure\\/); +} + + +################################################################################ +## Process the received secure query and respond with the correct response +## +################################################################################ +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 + 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 + my $response = ""; + + # provide basic information if asked for (not uncommon) + if (defined $r{basic}) { + # compile basic string (identical to process_udp_basic) + + # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1 + $response .= "\\gamename\\333networks" + . "\\gamever\\$self->{short_version}" + . "\\location\\0" + . "\\hostname\\$self->{masterserver_hostname}" + . "\\hostport\\$self->{listen_port}"; + } + + # we only respond with gamename = 333networks + if (defined $r{secure}) { + # get response + $response .= "\\validate\\" + . $self->validate_string(gamename => "333networks", + enctype => 0, + secure => $r{secure}); + } + + # send the response to the \basic\\secure\wookie query + $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; -- cgit v1.2.3