8 require './access_list.pl';
10 require './survey.pl';
22 vec($bits, fileno($fh), 1) = 1;
27 # used for rate limiting
31 my %active_surveys = ();
35 sub cache_cidrlookup {
36 my ($addr, $net) = @_;
37 my $key = $addr . " " . $net;
39 if (!exists($cidrcache{$key})) {
40 $cidrcache{$key} = Net::CIDR::cidrlookup($addr, $net);
42 return $cidrcache{$key};
49 if (!exists($rangecache{$net})) {
50 ($rangecache{$net}) = Net::CIDR::cidr2range($net);
53 return $rangecache{$net};
56 open LOG, ">>", "mbd.log";
58 my @ports = ( mbd::find_all_ports() , $Config::survey_port_low .. $Config::survey_port_high );
60 # Open a socket for each port
62 my $udp = getprotobyname("udp");
65 socket($sock, PF_INET, SOCK_DGRAM, $udp);
66 bind($sock, sockaddr_in($p, INADDR_ANY));
70 my $sendsock = Net::RawIP->new({udp => {}});
72 print "Listening on " . scalar @ports . " ports.\n";
76 my $rin = fhbits(@socks);
79 my $nfound = select($rout=$rin, undef, undef, undef);
80 my $now = [Time::HiRes::gettimeofday];
82 # First of all, close any surveys that are due.
83 for my $sport (keys %active_surveys) {
84 my $age = Time::HiRes::tv_interval($active_surveys{$sport}{start}, $now);
85 if ($age > $Config::survey_time && $active_surveys{$sport}{active}) {
86 print "Survey for '" . $Config::access_list[$active_surveys{$sport}{entry}]->{name} . "'/" .
87 $active_surveys{$sport}{dport} . ": " . $active_surveys{$sport}{num} . " active servers.\n";
88 $active_surveys{$sport}{active} = 0;
90 # (re)connect to the database if needed
91 if (!defined($dbh) || !$dbh->ping) {
92 $dbh = nms::db_connect();
93 $q = $dbh->prepare("INSERT INTO mbd_log (ts,game,port,description,active_servers) VALUES (CURRENT_TIMESTAMP,?,?,?,?)")
94 or die "Couldn't prepare query";
96 $q->execute($active_surveys{$sport}{entry}, $active_surveys{$sport}{dport}, $Config::access_list[$active_surveys{$sport}{entry}]->{name}, $active_surveys{$sport}{num});
98 if ($age > $Config::survey_time * 3.0) {
99 delete $active_surveys{$sport};
103 for my $sock (@socks) {
104 next unless (vec($rout, fileno($sock), 1) == 1);
107 my $addr = recv($sock, $data, 8192, 0); # jumbo broadcast! :-P
108 my ($sport, $saddr) = sockaddr_in($addr);
109 my ($dport, $daddr) = sockaddr_in(getsockname($sock));
110 my $size = length($data);
112 # Check if this is a survey reply
113 if ($dport >= $Config::survey_port_low && $dport <= $Config::survey_port_high) {
114 if (!exists($active_surveys{$dport})) {
115 print "WARNING: Unknown survey port $dport, ignoring\n";
118 if (!$active_surveys{$dport}{active}) {
123 ++$active_surveys{$dport}{num};
129 if (exists($last_sent{$saddr}{$dport})) {
130 my $elapsed = Time::HiRes::tv_interval($last_sent{$saddr}{$dport}, $now);
131 if ($elapsed < 1.0) {
132 print LOG "$dport $size 2\n";
133 print inet_ntoa($saddr), ", $dport, $size bytes => rate-limited ($elapsed secs since last)\n";
138 # We don't get the packet's destination address, but I guess this should do...
139 # Check against the ACL.
142 for my $rule (@Config::access_list) {
145 next unless (mbd::match_ranges($dport, $rule->{'ports'}));
146 next unless (mbd::match_ranges($size, $rule->{'sizes'}));
148 if ($rule->{'filter'}) {
149 next unless ($rule->{'filter'}($data));
156 print LOG "$dport $size $pass\n";
159 print inet_ntoa($saddr), ", $dport, $size bytes => filtered\n";
163 $last_sent{$saddr}{$dport} = $now;
165 # The packet is OK! Do we already have a recent enough survey
166 # for this port, or should we use this packet?
168 if (exists($last_survey{$entry . "/" . $dport})) {
169 my $age = Time::HiRes::tv_interval($last_survey{$entry . "/" . $dport}, $now);
170 if ($age < $Config::survey_freq) {
175 # New survey; find an unused port
178 for my $port ($Config::survey_port_low..$Config::survey_port_high) {
179 if (!exists($active_surveys{$port})) {
180 $survey_sport = $port;
182 $active_surveys{$port} = {
189 $last_survey{$entry . "/" . $dport} = $now;
195 if (!defined($survey_sport)) {
196 print "WARNING: no free survey source ports, not surveying.\n";
202 for my $net (@Config::networks) {
203 my ($range) = cache_cidrrange($net);
210 saddr => $Config::survey_ip,
214 source => $survey_sport,
222 next if (cache_cidrlookup(inet_ntoa($saddr), $net));
226 saddr => inet_ntoa($saddr),
241 print inet_ntoa($saddr), ", $dport, $size bytes => ($num_nets networks) [+survey from port $survey_sport]\n";
243 print inet_ntoa($saddr), ", $dport, $size bytes => ($num_nets networks)\n";