]> git.sesse.net Git - nms/blob - mbd/mbd.pl
dcc25fe242e8bd843a1741b0821c9f81c7f4978e
[nms] / mbd / mbd.pl
1 #! /usr/bin/perl
2 use strict;
3 use warnings;
4 use Socket;
5 use Net::CIDR;
6 use Net::RawIP;
7 use Time::HiRes;
8 require './access_list.pl';
9 require './nets.pl';
10 require './mbd.pm';
11
12 sub fhbits {
13         my $bits = 0;
14         for my $fh (@_) {
15                 vec($bits, fileno($fh), 1) = 1;
16         }
17         return $bits;
18 }
19
20 # used for rate limiting
21 my %last_sent = ();
22
23 my %cidrcache = ();
24 sub cache_cidrlookup {
25         my ($addr, $net) = @_;
26         my $key = $addr . " " . $net;
27
28         if (!exists($cidrcache{$key})) {
29                 $cidrcache{$key} = Net::CIDR::cidrlookup($addr, $net);
30         }
31         return $cidrcache{$key};
32 }
33
34 my %rangecache = ();
35 sub cache_cidrrange {
36         my ($net) = @_;
37
38         if (!exists($rangecache{$net})) {
39                 ($rangecache{$net}) = Net::CIDR::cidr2range($net);
40         }
41
42         return $rangecache{$net};
43 }
44
45 open LOG, ">>", "mbd.log";
46
47 my @ports = mbd::find_all_ports();
48
49 # Open a socket for each port
50 my @socks = ();
51 my $udp = getprotobyname("udp");
52 for my $p (@ports) {
53         my $sock;
54         socket($sock, PF_INET, SOCK_DGRAM, $udp);
55         bind($sock, sockaddr_in($p, INADDR_ANY));
56         push @socks, $sock;
57 }
58
59 my $sendsock = Net::RawIP->new({udp => {}});
60
61 print "Listening on " . scalar @ports . " ports.\n";
62
63 # Main loop
64 while (1) {
65         my $rin = fhbits(@socks);
66         my $rout;
67
68         my $nfound = select($rout=$rin, undef, undef, undef);
69         for my $sock (@socks) {
70                 next unless (vec($rout, fileno($sock), 1) == 1);
71
72                 my $data;
73                 my $addr = recv($sock, $data, 8192, 0);   # jumbo broadcast! :-P
74                 my ($sport, $saddr) = sockaddr_in($addr);
75                 my ($dport, $daddr) = sockaddr_in(getsockname($sock));
76                 my $size = length($data);
77
78                 # Rate limiting
79                 my $now = [Time::HiRes::gettimeofday];
80                 if (exists($last_sent{$saddr}{$dport})) {
81                         my $elapsed = Time::HiRes::tv_interval($last_sent{$saddr}{$dport}, $now);
82                         if ($elapsed < 1.0) {
83                                 print LOG "$dport $size 2\n";
84                                 print inet_ntoa($saddr), ", $dport, $size bytes => rate-limited ($elapsed secs since last)\n";
85                         }
86                         next;
87                 }
88
89                 # We don't get the packet's destination address, but I guess this should do...
90                 # Check against the ACL.
91                 my $pass = 0;
92                 for my $rule (@Config::access_list) {
93                         next unless (mbd::match_ranges($dport, $rule->{'ports'}));
94                         next unless (mbd::match_ranges($size, $rule->{'sizes'}));
95
96                         if ($rule->{'filter'}) {
97                                 next unless ($rule->{'filter'}($data));
98                         }
99
100                         $pass = 1;
101                         last;
102                 }
103
104                 print LOG "$dport $size $pass\n";
105
106                 if (!$pass) {
107                         print inet_ntoa($saddr), ", $dport, $size bytes => filtered\n";
108                         next;
109                 }
110
111                 $last_sent{$saddr}{$dport} = $now;
112
113                 my $num_nets = 0;
114
115                 for my $net (@Config::networks) {
116                         next if (cache_cidrlookup(inet_ntoa($saddr), $net));
117
118                         my ($range) = cache_cidrrange($net);
119                         $range =~ /-(.*?)$/;
120                         my $broadcast = $1;
121
122                         $sendsock->set({
123                                 ip => {
124                                         saddr => inet_ntoa($saddr),
125                                         daddr => $broadcast
126                                 },
127                                 udp => {
128                                         source => $sport,
129                                         dest => $dport,
130                                         data => $data
131                                 }
132                         });
133                         $sendsock->send;
134                         ++$num_nets;
135                 }
136                 print inet_ntoa($saddr), ", $dport, $size bytes => ($num_nets networks)\n";
137         }
138 }
139