aboutsummaryrefslogtreecommitdiffstats
path: root/mbd/mbd-unicast.pl
blob: 6e63dee0cdb2a4d12ca699f767bb3baa209e574a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#!/bin/bash

set -e

source include/tgmanage.cfg.sh
if [ -z ${PRIMARY} ]
then
	echo "Not configured!";
	exit 1;
fi;

ssh -l root ${PRIMARY} "mkdir -p ~/tgmanage"
ssh -l root ${SECONDARY} "mkdir -p ~/tgmanage"

scp -r netlist.txt root@${PRIMARY}:tgmanage/
scp -r tools root@${PRIMARY}:tgmanage/
scp -r tools root@${SECONDARY}:tgmanage/
scp -r include root@${PRIMARY}:tgmanage/
scp -r include root@${SECONDARY}:tgmanage/
scp -r clients root@
#! /usr/bin/perl
use strict;
use warnings;
use Socket;
use Net::CIDR;
use Net::RawIP;
use Time::HiRes;
require './access_list.pl';
require './nets.pl';
require './survey.pl';
require './mbd.pm';
use lib '../include';
use nms;
use strict;
use warnings;
use threads;

# Mark packets with DSCP CS7
my $tos = 56;

my ($dbh, $q);

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

# 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 %rangecache = ();
sub cache_cidrrange {
	my ($net) = @_;

	if (!exists($rangecache{$net})) {
		my ($range) = Net::CIDR::cidr2range($net);
		$range =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.(\d+)\.(\d+)/ or die "Did not understand range: $range";
		my @range = ();
		for my $l (($4+1)..($8-1)) {
			push @range, "$1.$2.$3.$l";
		}
		($rangecache{$net}) = \@range;
	}

	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 = ();
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";
		
open PKTS, "| ./packetpusher"
	or die "./packetpusher: $!";

# Main loop
while (1) {
	my $rin = fhbits(@socks);
	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}) {
			my $hexdump = join(' ', map { sprintf "0x%02x", ord($_) } (split //, $active_surveys{$sport}{data}));
			print "Survey ($hexdump) for '" . $Config::access_list[$active_surveys{$sport}{entry}]->{name} . "'/" .
				$active_surveys{$sport}{dport} . ": " . $active_surveys{$sport}{num} . " active servers.\n";
			$active_surveys{$sport}{active} = 0;
	
			# (re)connect to the database if needed	
			if (!defined($dbh) || !$dbh->ping) {
				$dbh = nms::db_connect();
				$q = $dbh->prepare("INSERT INTO mbd_log (ts,game,port,description,active_servers) VALUES (CURRENT_TIMESTAMP,?,?,?,?)")
					or die "Couldn't prepare query";
			}
			$q->execute($active_surveys{$sport}{entry}, $active_surveys{$sport}{dport}, $Config::access_list[$active_surveys{$sport}{entry}]->{name}, $active_surveys{$sport}{num});
		}
		if ($age > $Config::survey_time * 3.0) {
			delete $active_surveys{$sport};
		}
	}

	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);

		# 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;
		my $entry = -1;
		for my $rule (@Config::access_list) {
			++$entry;

			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 inet_ntoa($saddr), ", $dport, $size bytes => filtered\n";
			next;
		}

		$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{$entry . "/" . $dport})) {
			my $age = Time::HiRes::tv_interval($last_survey{$entry . "/" . $dport}, $now);
			if ($age < $Config::survey_freq) {
				$survey = 0;
			}
		}

		# New survey; find an unused port
		my $survey_sport;
		if ($survey) {
			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,
						entry => $entry,
						num => 0,
						data => $data,
					};
					$last_survey{$entry . "/" . $dport} = $now;

					last;
				}
			}

			if (!defined($survey_sport)) {
				print "WARNING: no free survey source ports, not surveying.\n";
				$survey = 0;
			}
		}

		# precache
		for my $net (@Config::networks) {
			cache_cidrrange($net);
			cache_cidrlookup(inet_ntoa($saddr), $net);
		}

		my @packets = ();

		my $num_nets = 0;
		for my $net (@Config::networks) {
			my @daddrs = cache_cidrrange($net);

			if ($survey) {
				for my $daddr (@daddrs) {
					push @packets, [ $Config::survey_ip, $survey_sport, $daddr, $dport ];
				}
			}

			next if (cache_cidrlookup(inet_ntoa($saddr), $net));

			for my $daddr (@daddrs) {
				push @packets, [ inet_ntoa($saddr), $sport, $daddr, $dport ];
			}

			++$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";
		}

		printf PKTS "%d %s\n", scalar @packets, unpack('h*', $data);
		for my $pkt (@packets) {
			printf PKTS "%s %s %s %s\n", $pkt->[0], $pkt->[1], $pkt->[2], $pkt->[3];
		}
	}
}