aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/UDP/UpLink.pm
blob: e5c703b921379fed45bd06e7d2f9f4e0aa89f79e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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;