aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/UDP/UpLink.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterServer/UDP/UpLink.pm')
-rwxr-xr-xlib/MasterServer/UDP/UpLink.pm169
1 files changed, 169 insertions, 0 deletions
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;