sedlo |
Subversion Repositories: |
Compare with Previous - Blame - Download
#!/usr/bin/perl
# author : Petr Simandl www.simandl.cz
# rewrite to Perl: Jan Spitalnik www.spitalnik.net
# release date : 29/08/2004
# name : sedlo
# description : dynamic side routing tables tool
# license : GPL
# TODO:
# 2) add logging :-) create some convenience fnc that can store either to STDOUT or file
# 3) pridat podporu pro deamonize - default time je 5minut
use strict;
use LWP::UserAgent;
use Fcntl qw(:DEFAULT :flock);
use Getopt::Long;
use Pod::Usage;
use Storable;
# Main config files
my($sedlo_config) = "/etc/sedlo.conf";
my($sedlo_spool_dir) = "/var/cache/sedlo/";
my($sedlo_spool_config) = "sedlo.conf";
# iproute2 config files
my($rttables_config) = "/etc/iproute2/rt_tables";
my($rttables_min) = 110;
my($rttables_max) = 200;
my($ip_nodef) = "10.0.0.0/8";
# Logging
# 0 = no logging, 1 = basic logging, 2 = extensive logging
my($debug_level) = 0;
#################################################
############# DON'T MODIFY BELOW ################
#################################################
# define version
use constant VERSION => "0.0.3";
# define error codes
use constant E_MISSING_CMD => -1; # missing command
use constant E_MISSING_CFG => -2; # missing configuration file
use constant E_MISSING_MCNF => -3; # missing mcnf stanza in main config. file
use constant E_NONET => -4; # Problems with network communication
use constant E_CREATSPOOL => -5; # Problems creating spool directory
use constant E_INVSTANZA => -6; # Invalid stanza in config. file
use constant E_MISC => -7; # Other kind of error
use constant E_OK => 0; # Everything is OK
use constant TRUE => 1; # All's OK
use constant FALSE => 0; # Baaad
# Commands
my($IP_CMD) = get_command("ip");
# Check config files
check_configs($sedlo_config, $rttables_config);
# Where is main cfg file?
my($main_cfg_url) = get_main_cfg_url($sedlo_config);
# Parse command line
my($opt_show_version) = 0;
my($opt_show_help) = 0;
my($opt_show_report) = 0;
my($opt_no_get_cfg) = 0;
my($opt_info1) = 0;
my($opt_info2) = 0;
Getopt::Long::Configure("bundling");
GetOptions('version|v' => \$opt_show_version,
'help|h' => \$opt_show_help,
'report|r' => \$opt_show_report,
'nogetcfg|n' => \$opt_no_get_cfg,
'info1|1' => \$opt_info1,
'info2|2' => \$opt_info2);
parse_cmd_line();
# create spool directory
create_spool_dir($sedlo_spool_dir);
if(not $opt_no_get_cfg)
{
# Download main config. file
get_main_config();
}
# Merge main config with local config
merge_main_and_local_config();
# Remove duplicates from spool config file
remove_dups_from_config();
# Create routing tables
create_tables();
# Fill routing tables
fill_tables();
# Fill routing rules
fill_rules();
################### Util Functions ##########################
# Name: fill_rules()
# Desc: Fills routing tables with rules
# Param: none
sub fill_rules()
{
my(%IPs) = ret_IPs();
my(@iGW_array);
my(@cmd_out);
my($IP);
my($iGW);
foreach $IP (keys(%IPs))
{
@iGW_array = @{ Storable::thaw($IPs{$IP}) };
my($we_added_this_entry) = FALSE;
foreach $iGW (@iGW_array)
{
if($iGW =~ /^$/)
{
next;
}
# Check for existing entries
@cmd_out = `$IP_CMD route ls table $iGW 2>&1`;
if($! == 0 && $we_added_this_entry == FALSE)
{
my($has_entry) = TRUE;
# Remove any existing entries
while($has_entry)
{
@cmd_out = `$IP_CMD rule ls 2>&1`;
my($tmp_line);
$has_entry = FALSE;
foreach $tmp_line (@cmd_out)
{
if($tmp_line =~ /$IP/)
{
$has_entry = TRUE;
}
}
if($has_entry)
{
if($debug_level > 1)
{
print "Info: Removing old rules for $IP.\n";
}
@cmd_out = `$IP_CMD rule del from $IP 2>&1`;
@cmd_out = `$IP_CMD rule del from $IP to $ip_nodef 2>&1`;
}
}
if ($debug_level > 1)
{
print "Info: Creating new rules to send $IP to table $iGW.\n";
}
# Add new rules
@cmd_out = `$IP_CMD rule add from $IP lookup $iGW 2>&1`;
@cmd_out = `$IP_CMD rule add from $IP to $ip_nodef lookup main 2>&1`;
$we_added_this_entry = TRUE;
}
else
{
if($debug_level > 1)
{
if($we_added_this_entry)
{
print "Info: iGW '$iGW' with lower priority!\n";
}
else
{
print "Info: Non-existant table!\n";
}
}
}
}
}
}
# Name: ret_IPs()
# Desc: Returns hash of IP containing array of iGWs as value.
# Param: none
sub ret_IPs()
{
open(SEDLO_SPOOL_CONFIG, "< $sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_SH);
my($line);
my(%IPs);
while($line = <SEDLO_SPOOL_CONFIG>)
{
chomp($line);
if($line =~ m/^ip\s+/)
{
my(@iGWs) = split(/\s/, $line);
# Remove ^ip
shift(@iGWs);
# Store IP address
my($IP) = $iGWs[0];
# Remove IP address
shift(@iGWs);
# Remove name of subnet
shift(@iGWs);
$IPs{$IP} = Storable::freeze(\@iGWs);
}
}
close(SEDLO_SPOOL_CONFIG);
return %IPs;
}
# Name: fill_tables()
# Desc: Fills routing tables
# Param: none
sub fill_tables()
{
my(%iGWs) = get_iGWs_with_IPs();
my($iGW);
my($iGW_IP);
my($iGW_name);
my($iGW_via);
my(@cmd_tmp);
foreach $iGW (keys %iGWs)
{
$iGW_IP = $iGW;
$iGW_name = $iGWs{$iGW};
@cmd_tmp = `$IP_CMD route ls 2>&1`;
my($line);
foreach $line (@cmd_tmp)
{
if($line =~ /$iGW_IP\s+via\s+(\w+)/)
{
$iGW_via = $1;
}
}
@cmd_tmp = `$IP_CMD route ls table $iGW_name 2>&1`;
if($! != 0)
{
@cmd_tmp=`$IP_CMD route flush table $iGW_name 2>&1`;
}
if(not defined $iGW_via)
{
if($debug_level > 1)
{
print "Info: Route not found for iGW $iGW_name - leaving table empty.\n";
}
}
else
{
@cmd_tmp=`$IP_CMD route add 0.0.0.0/1 via $iGW_via table $iGW_name 2>&1`;
@cmd_tmp=`$IP_CMD route add 128.0.0.0/1 via $iGW_via table $iGW_name 2>&1`;
if($debug_level > 1)
{
print "Info: Table filled for iGW $iGW_name.\n";
}
}
}
}
# Name: create_tables()
# Desc: Creates routing tables
# Parm: none
sub create_tables()
{
my(@iGWs) = get_iGWs();
my($iGW);
foreach $iGW (@iGWs)
{
if(not ret_rttb_config_entry($iGW, 2))
{
my($count) = $rttables_max;
while($count != $rttables_min)
{
if(not ret_rttb_config_entry($count, 1))
{
open(RTTABLES_CONFIG, ">> $rttables_config") or die("Error: Can't open $rttables_config!\n");
flock(RTTABLES_CONFIG, LOCK_SH);
print RTTABLES_CONFIG "$count\t$iGW\n";
close(RTTABLES_CONFIG);
$count = $rttables_min;
next;
}
$count--;
}
}
else
{
if($debug_level > 1)
{
print "Table found for $iGW, no action taken.\n";
}
}
}
}
# Name: ret_rttb_config_entry()
# Desc: Returns true/false if found routing table config entry for given IP/Hostname
# Second parameter says whether you want to match against cost (1) or against
# name of the table (2)
# Parm: string, int
sub ret_rttb_config_entry()
{
my($entry, $type) = @_;
open(RTTABLES_CONFIG, "< $rttables_config") or die("Error: Can't open $rttables_config!\n");
flock(RTTABLES_CONFIG, LOCK_SH);
my($line);
while($line = <RTTABLES_CONFIG>)
{
chomp($line);
# Cost
if($type == 1)
{
if($line =~ /^$entry\s+\w+/)
{
return TRUE;
}
}
# Name of table
elsif($type == 2)
{
if($line =~ /^\w+\s+$entry/)
{
return TRUE;
}
}
else
{
print "Error: Invalid argument to ret_rttb_config_entry()!\n";
exit(E_MISC);
}
}
close(RTTABLES_CONFIG);
return FALSE;
}
# Name: get_iGWs()
# Desc: Returns iGWs from config. file
# Parm: none
sub get_iGWs()
{
my(@iGWs);
open(SEDLO_SPOOL_CONFIG, "< $sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_SH);
my($line);
while($line = <SEDLO_SPOOL_CONFIG>)
{
chomp($line);
if($line =~ /^igw\s+(.*)\s+(.*)/)
{
push(@iGWs, $2);
}
}
close(SEDLO_SPOOL_CONFIG);
return @iGWs;
}
# Name: get_iGWs_with_IPs()
# Desc: Returns iGWs with IPs from config. file
# Parm: none
sub get_iGWs_with_IPs()
{
my(%iGWs);
open(SEDLO_SPOOL_CONFIG, "< $sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_SH);
my($line);
while($line = <SEDLO_SPOOL_CONFIG>)
{
chomp($line);
if($line =~ /^igw\s+(.*)\s+(.*)/)
{
$iGWs{$1} = $2;
}
}
close(SEDLO_SPOOL_CONFIG);
return %iGWs;
}
# Name: parse_cmd_line()
# Desc: Parses command line options
# Param: none
sub parse_cmd_line()
{
if($opt_show_version)
{
print "$0 ".VERSION."\n";
exit(E_OK);
}
elsif($opt_show_help)
{
show_help();
exit(E_OK);
}
elsif($opt_show_report)
{
show_report();
exit(E_OK);
}
elsif($opt_info1)
{
$debug_level = 1;
}
elsif($opt_info2)
{
$debug_level = 2;
}
}
# Name: show_report()
# Desc: Prints routing tables and rules
# Param: none
sub show_report()
{
print "##### SEDLO #####\n";
print "Version:".VERSION."\n";
print "local config: $sedlo_config\n";
print "main config: $main_cfg_url\n";
print "##### TABLES #####\n";
open(RTTABLES, "< $rttables_config") or die("Can't open $rttables_config!\n");
flock(RTTABLES, LOCK_SH);
my($line);
while($line = <RTTABLES>)
{
print $line;
}
close(RTTABLES);
print "##### RULES #####\n";
my(@cmd_out) = `$IP_CMD rule ls 2>&1`;
foreach $line (@cmd_out)
{
print $line;
}
}
# Name: show_help()
# Desc: Prints help screen
# Param: none
sub show_help()
{
pod2usage(1);
}
# Name: create_spool_dir()
# Desc: Creates spool dir if not present
# Param: string
sub create_spool_dir()
{
my($spool_dir) = @_;
if(not -e $spool_dir)
{
if(not mkdir($spool_dir))
{
print "Error: Can't create spool dir in $spool_dir!\n";
exit(E_CREATSPOOL);
}
}
}
# Name: get_main_config()
# Desc: Downloads configuration file
# Param: string
sub get_main_config()
{
my($user_agent) = LWP::UserAgent->new;
my($request);
my($response);
$user_agent->agent("Sedlo/".VERSION);
$request = HTTP::Request->new(GET => $main_cfg_url);
$response = $user_agent->request($request);
if($response->is_error)
{
print "Error: Can't download $main_cfg_url!\n";
exit(E_NONET);
}
# sedlo.config.main
open(SEDLO_CONFIG_MAIN, ">$sedlo_spool_dir$sedlo_spool_config.main")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config.main");
flock(SEDLO_CONFIG_MAIN, LOCK_EX);
print SEDLO_CONFIG_MAIN $response->content;
close(SEDLO_CONFIG_MAIN);
}
# Name: merge_main_and_local_config()
# Desc: Merges main and local configuration files
# Param: none
sub merge_main_and_local_config()
{
my($file);
my(@config_files) = ($sedlo_config, $sedlo_spool_dir.$sedlo_spool_config.".main");
open(SEDLO_SPOOL_CONFIG, ">>$sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_EX);
foreach $file (@config_files)
{
open(DATA, "< $file") or die("Error: Can't open $file!\n");
flock(DATA, LOCK_SH);
my($line);
while($line = <DATA>)
{
if($line =~ /^mcnf\s+/)
{
print SEDLO_SPOOL_CONFIG "$line";
}
elsif($line =~ /^igw\s+/)
{
print SEDLO_SPOOL_CONFIG "$line";
}
elsif($line =~ /^ip\s+/)
{
print SEDLO_SPOOL_CONFIG "$line";
}
elsif($line =~ /^\#/ or $line =~ /^\s+$/ or $line =~ /^$/)
{
next;
}
else
{
print "Error: Invalid stanza in $file!\n";
print "Trace -->$line<--\n";
exit(E_INVSTANZA);
}
}
close(DATA);
}
close(SEDLO_SPOOL_CONFIG);
}
# Name: remove_dups_from_config
# Desc: Removes duplicate lines from spool config
# Param: none
sub remove_dups_from_config()
{
# Now lets dump duplicate entries in $SEDLO_SPOOL_CONFIG
open(SEDLO_SPOOL_CONFIG, "< $sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_SH);
my($line);
my(%uniq);
while($line = <SEDLO_SPOOL_CONFIG>)
{
if($line !~ /^\#/)
{
$uniq{$line} = TRUE;
}
}
close(SEDLO_SPOOL_CONFIG);
open(SEDLO_SPOOL_CONFIG, "> $sedlo_spool_dir$sedlo_spool_config")
or die("Error: Can't open $sedlo_spool_dir$sedlo_spool_config!\n");
flock(SEDLO_SPOOL_CONFIG, LOCK_SH);
print SEDLO_SPOOL_CONFIG "# generated file\n";
print SEDLO_SPOOL_CONFIG keys(%uniq);
close(SEDLO_SPOOL_CONFIG);
}
# Name: get_main_cfg_url()
# Desc: Returns URL of main config file
# Param: string
sub get_main_cfg_url()
{
my($sedlo_conf) = @_;
my($line);
my($cfg_file);
open(SEDLO_CFG, "< $sedlo_conf") or die("Error: Can't open $sedlo_conf!\n");
while($line = <SEDLO_CFG>)
{
if($line =~ /^mcnf\s+(.*)/)
{
$cfg_file = $1;
}
}
if (not defined $cfg_file)
{
print "Error: Can't find 'mcnf' stanza in config file!\n";
exit(E_MISSING_MCNF);
}
else
{
return $cfg_file;
}
}
# Name: check_configs()
# Desc: Check for existance of configuration files
# Param: string, string
sub check_configs()
{
my($sedlo_config, $rttables_config) = @_;
if (not -e $sedlo_config)
{
print "Error: Missing configuration file '$sedlo_config'!\n";
exit(E_MISSING_CFG);
}
if (not -e $rttables_config)
{
print "Error: Missing configuration file '$rttables_config'!\n";
exit(E_MISSING_CFG);
}
}
# Name: get_command()
# Desc: Returns path for specified command
# Param: string
sub get_command()
{
my($command) = @_;
my($path) = "PATH=/bin:/sbin:/usr/bin:/usr/sbin:usr/local/bin:/usr/local/sbin";
my($cmd_path) = `$path which $command`;
chomp($cmd_path);
if ($cmd_path =~ /not found/)
{
print "Error: can't find '$command' in \$PATH!\n";
exit(E_MISSING_CMD);
}
return $cmd_path;
}
__END__
=head1 NAME
sedlo - Program na nastavovani default routy podle danych pravidel
=head1 SYNOPSIS
sedlo [options]
=head1 OPTIONS
=over 8
=item B<--version, -v>
Vypise verzi programu
=item B<--help, -h>
Vypise tuto napovedu
=item B<--report, -r>
Vypise prehled pravidel a tabulek
=item B<--nogetcfg, -n>
Zajisti ze se nedude znovu nacitat konfigurace a pouzije se predchozi z cache
=item B<--info1, -1>
Nastavi upovidanost na 1 (malo).
=item B<--info2, -2>
Nastavi upovidanost na 2 (hodne).
=back
=head1 DESCRIPTIOn
B<Sedlo> stahne hlavni seznam a spolecne s lokalnim seznamem pravidel nastavi default routy
pro jednotlive podsite tak, jak jsou zaznamenany v konfiguracnim souboru. Jeho hlavni vyuziti
je v prostredi, kde je vice ISP a pouziva se dynamicke routovani (OSPF).
(This documentation is crap ;-) If you feel adventurous send patches :-)
=cut