#! /usr/bin/perl
use strict;
use warnings;
use Socket;
use Net::CIDR;
use Net::RawIP;
require './access_list.pl';
require './nets.pl';
require './mbd.pm';

sub fhbits {
	my $bits = 0;
	for my $fh (@_) {
		vec($bits, fileno($fh), 1) = 1;
	}
	return $bits;
}

my @ports = mbd::find_all_ports();

# Open a socket for each port
my @socks = ();
my $udp = getprotobyname("udp");
for my $p (@ports) {
	my $sock;
	socket($sock, PF_INET, SOCK_DGRAM, $udp);
	bind($sock, sockaddr_in($p, INADDR_ANY));
	push @socks, $sock;
}

my $sendsock = Net::RawIP->new({udp => {}});

print "Listening on " . scalar @ports . " ports.\n";

# Main loop
while (1) {
	my $rin = fhbits(@socks);
	my $rout;

	my $nfound = select($rout=$rin, undef, undef, undef);
	for my $sock (@socks) {
		next unless (vec($rout, fileno($sock), 1) == 1);

		my $data;
		my $addr = recv($sock, $data, 8192, 0);   # jumbo broadcast! :-P
		my ($sport, $saddr) = sockaddr_in($addr);
		my ($dport, $daddr) = sockaddr_in(getsockname($sock));
		my $size = length($data);

		# 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 (mbd::match_ranges($dport, $rule->{'ports'}) &&
			    mbd::match_ranges($size, $rule->{'sizes'})) {
				$pass = 1;
			}
		}

		if (!$pass) {
			print "$dport, $size bytes => filtered\n";
		}

		next unless $pass;

		for my $net (@Config::networks) {
			next if (Net::CIDR::cidrlookup(inet_ntoa($saddr), $net));

			my ($range) = Net::CIDR::cidr2range($net);
			$range =~ /-(.*?)$/;
			my $broadcast = $1;

			print inet_ntoa($saddr), ", $dport, $size bytes => $broadcast\n";

			$sendsock->set({
				ip => {
					saddr => inet_ntoa($saddr),
					daddr => $broadcast
				},
				udp => {
					source => $sport,
					dest => $dport,
					data => $data
				}
			});
			$sendsock->send;
		}
	}
}

