X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=inline;f=mbd%2Fmbd.pl;h=4843b0bb60cfea9a62388340e854749a8a3b3d4a;hb=e30bd8928e466b1b7b33440352af079779bd0611;hp=90ee4d6b63bc50bc6cb9b2f7fbb765d2512b7d96;hpb=c8f763bc89ec770a9f924667c8a276d356d30b0a;p=nms diff --git a/mbd/mbd.pl b/mbd/mbd.pl index 90ee4d6..4843b0b 100644 --- a/mbd/mbd.pl +++ b/mbd/mbd.pl @@ -4,32 +4,11 @@ use warnings; use Socket; use Net::CIDR; use Net::RawIP; +use Time::HiRes; require './access_list.pl'; require './nets.pl'; - -sub expand_range { - my $range = shift; - - if ($range =~ /^(\d+)\.\.(\d+)$/) { - return $1..$2; - } else { - return $range; - } -} - -sub match_ranges { - my ($elem, $ranges) = @_; - - for my $range (@$ranges) { - if ($range =~ /^(\d+)\.\.(\d+)$/) { - return 1 if ($elem >= $1 && $elem <= $2); - } else { - return 1 if ($elem == $range); - } - } - - return 0; -} +require './survey.pl'; +require './mbd.pm'; sub fhbits { my $bits = 0; @@ -39,16 +18,38 @@ sub fhbits { return $bits; } -# Find what ports we need to listen on -my %port_hash = (); -for my $e (@Config::access_list) { - for my $r (@{$e->{'ports'}}) { - for my $p (expand_range($r)) { - $port_hash{$p} = 1; - } +# 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 @ports = sort { $a <=> $b } keys %port_hash; + +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() , $Config::survey_port_low, $Config::survey_port_high ); # Open a socket for each port my @socks = (); @@ -70,6 +71,21 @@ 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 port " . $active_surveys{$sport}{dport} . ": " . + $active_surveys{$sport}{num} . " active servers.\n"; + $active_surveys{$sport}{active} = 0; + } + if ($age > $Config::survey_time * 3.0) { + delete $active_surveys{$sport}; + } + } + for my $sock (@socks) { next unless (vec($rout, fileno($sock), 1) == 1); @@ -79,31 +95,97 @@ while (1) { 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; for my $rule (@Config::access_list) { - if (match_ranges($dport, $rule->{'ports'}) && - match_ranges($size, $rule->{'sizes'})) { - $pass = 1; + next unless (mbd::match_ranges($dport, $rule->{'ports'})); + next unless (mbd::match_ranges($size, $rule->{'sizes'})); + + if ($rule->{'filter'}) { + next unless ($rule->{'filter'}($data)); } + + $pass = 1; + last; } + 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; + + # 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{$dport})) { + my $age = Time::HiRes::tv_interval($last_survey{$dport}, $now); + if ($age < $Config::survey_freq) { + $survey = 0; + } + } + # New survey; find an unused port + my $survey_sport; + 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, + num => 0 + }; + $last_survey{$dport} = $now; + + last; + } + } + + 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) { - next if (Net::CIDR::cidrlookup(inet_ntoa($saddr), $net)); + next if (cache_cidrlookup(inet_ntoa($saddr), $net)); - my ($range) = Net::CIDR::cidr2range($net); + my ($range) = cache_cidrrange($net); $range =~ /-(.*?)$/; my $broadcast = $1; - print inet_ntoa($saddr), ", $dport, $size bytes => $broadcast\n"; - $sendsock->set({ ip => { saddr => inet_ntoa($saddr), @@ -116,6 +198,29 @@ while (1) { } }); $sendsock->send; + + if ($survey) { + $sendsock->set({ + ip => { + saddr => $Config::survey_ip, + daddr => $broadcast + }, + udp => { + source => $survey_sport, + dest => $dport, + data => $data + } + }); + $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"; } } }