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