]> git.sesse.net Git - nms/blobdiff - mbd/mbd.pl
Merge.
[nms] / mbd / mbd.pl
index 7fcc8d8100a3a67e6e2f41bd763d797c684e4a06..4900c1b21498a4a6f32e399f3fee2cab1daaf58f 100644 (file)
@@ -4,9 +4,17 @@ use warnings;
 use Socket;
 use Net::CIDR;
 use Net::RawIP;
+use Time::HiRes;
 require './access_list.pl';
 require './nets.pl';
+require './survey.pl';
 require './mbd.pm';
+use lib '../include';
+use nms;
+use strict;
+use warnings;
+
+my ($dbh, $q);
 
 sub fhbits {
        my $bits = 0;
@@ -16,9 +24,38 @@ sub fhbits {
        return $bits;
 }
 
+# used for rate limiting
+my %last_sent = ();
+
+# for own surveying
+my %active_surveys = ();
+my %last_survey = ();
+
+my %cidrcache = ();
+sub cache_cidrlookup {
+       my ($addr, $net) = @_;
+       my $key = $addr . " " . $net;
+
+       if (!exists($cidrcache{$key})) {
+               $cidrcache{$key} = Net::CIDR::cidrlookup($addr, $net);
+       }
+       return $cidrcache{$key};
+}
+
+my %rangecache = ();
+sub cache_cidrrange {
+       my ($net) = @_;
+
+       if (!exists($rangecache{$net})) {
+               ($rangecache{$net}) = Net::CIDR::cidr2range($net);
+       }
+
+       return $rangecache{$net};
+}
+
 open LOG, ">>", "mbd.log";
 
-my @ports = mbd::find_all_ports();
+my @ports = ( mbd::find_all_ports() , $Config::survey_port_low .. $Config::survey_port_high );
 
 # Open a socket for each port
 my @socks = ();
@@ -40,6 +77,29 @@ while (1) {
        my $rout;
 
        my $nfound = select($rout=$rin, undef, undef, undef);
+       my $now = [Time::HiRes::gettimeofday];
+
+       # First of all, close any surveys that are due.
+       for my $sport (keys %active_surveys) {
+               my $age = Time::HiRes::tv_interval($active_surveys{$sport}{start}, $now);
+               if ($age > $Config::survey_time && $active_surveys{$sport}{active}) {
+                       print "Survey for '" . $Config::access_list[$active_surveys{$sport}{entry}]->{name} . "'/" .
+                               $active_surveys{$sport}{dport} . ": " . $active_surveys{$sport}{num} . " active servers.\n";
+                       $active_surveys{$sport}{active} = 0;
+       
+                       # (re)connect to the database if needed 
+                       if (!defined($dbh) || !$dbh->ping) {
+                               $dbh = nms::db_connect();
+                               $q = $dbh->prepare("INSERT INTO mbd_log (ts,game,port,description,active_servers) VALUES (CURRENT_TIMESTAMP,?,?,?,?)")
+                                       or die "Couldn't prepare query";
+                       }
+                       $q->execute($active_surveys{$sport}{entry}, $active_surveys{$sport}{dport}, $Config::access_list[$active_surveys{$sport}{entry}]->{name}, $active_surveys{$sport}{num});
+               }
+               if ($age > $Config::survey_time * 3.0) {
+                       delete $active_surveys{$sport};
+               }
+       }
+
        for my $sock (@socks) {
                next unless (vec($rout, fileno($sock), 1) == 1);
 
@@ -48,11 +108,40 @@ while (1) {
                my ($sport, $saddr) = sockaddr_in($addr);
                my ($dport, $daddr) = sockaddr_in(getsockname($sock));
                my $size = length($data);
+       
+               # Check if this is a survey reply
+               if ($dport >= $Config::survey_port_low && $dport <= $Config::survey_port_high) {
+                       if (!exists($active_surveys{$dport})) {
+                               print "WARNING: Unknown survey port $dport, ignoring\n";
+                               next;
+                       }
+                       if (!$active_surveys{$dport}{active}) {
+                               # remains
+                               next;
+                       }
+                       
+                       ++$active_surveys{$dport}{num};
 
+                       next;
+               }
+               
+               # Rate limiting
+               if (exists($last_sent{$saddr}{$dport})) {
+                       my $elapsed = Time::HiRes::tv_interval($last_sent{$saddr}{$dport}, $now);
+                       if ($elapsed < 1.0) {
+                               print LOG "$dport $size 2\n";
+                               print inet_ntoa($saddr), ", $dport, $size bytes => rate-limited ($elapsed secs since last)\n";
+                               next;
+                       }
+               }
+               
                # We don't get the packet's destination address, but I guess this should do...
                # Check against the ACL.
                my $pass = 0;
+               my $entry = -1;
                for my $rule (@Config::access_list) {
+                       ++$entry;
+
                        next unless (mbd::match_ranges($dport, $rule->{'ports'}));
                        next unless (mbd::match_ranges($size, $rule->{'sizes'}));
 
@@ -67,19 +156,70 @@ while (1) {
                print LOG "$dport $size $pass\n";
 
                if (!$pass) {
-                       print "$dport, $size bytes => filtered\n";
+                       print inet_ntoa($saddr), ", $dport, $size bytes => filtered\n";
+                       next;
                }
 
-               next unless $pass;
+               $last_sent{$saddr}{$dport} = $now;
 
-               for my $net (@Config::networks) {
-                       next if (Net::CIDR::cidrlookup(inet_ntoa($saddr), $net));
+               # The packet is OK! Do we already have a recent enough survey
+               # for this port, or should we use this packet?
+               my $survey = 1;
+               if (exists($last_survey{$entry . "/" . $dport})) {
+                       my $age = Time::HiRes::tv_interval($last_survey{$entry . "/" . $dport}, $now);
+                       if ($age < $Config::survey_freq) {
+                               $survey = 0;
+                       }
+               }
+
+               # New survey; find an unused port
+               my $survey_sport;
+               if ($survey) {
+                       for my $port ($Config::survey_port_low..$Config::survey_port_high) {
+                               if (!exists($active_surveys{$port})) {
+                                       $survey_sport = $port;
+
+                                       $active_surveys{$port} = {
+                                               start => $now,
+                                               active => 1,
+                                               dport => $dport,
+                                               entry => $entry,
+                                               num => 0
+                                       };
+                                       $last_survey{$entry . "/" . $dport} = $now;
+
+                                       last;
+                               }
+                       }
 
-                       my ($range) = Net::CIDR::cidr2range($net);
+                       if (!defined($survey_sport)) {
+                               print "WARNING: no free survey source ports, not surveying.\n";
+                               $survey = 0;
+                       }
+               }
+
+               my $num_nets = 0;
+               for my $net (@Config::networks) {
+                       my ($range) = cache_cidrrange($net);
                        $range =~ /-(.*?)$/;
                        my $broadcast = $1;
 
-                       print inet_ntoa($saddr), ", $dport, $size bytes => $broadcast\n";
+                       if ($survey) {
+                               $sendsock->set({
+                                       ip => {
+                                               saddr => $Config::survey_ip,
+                                               daddr => $broadcast
+                                       },
+                                       udp => {
+                                               source => $survey_sport,
+                                               dest => $dport,
+                                               data => $data
+                                       }
+                               });
+                               $sendsock->send;
+                       }
+
+                       next if (cache_cidrlookup(inet_ntoa($saddr), $net));
 
                        $sendsock->set({
                                ip => {
@@ -93,6 +233,14 @@ while (1) {
                                }
                        });
                        $sendsock->send;
+
+                       ++$num_nets;
+               }
+
+               if ($survey) {
+                       print inet_ntoa($saddr), ", $dport, $size bytes => ($num_nets networks) [+survey from port $survey_sport]\n";
+               } else {
+                       print inet_ntoa($saddr), ", $dport, $size bytes => ($num_nets networks)\n";
                }
        }
 }