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