jablonka.czprosek.czf

sedlo

Subversion Repositories:
[/] [branches/] [sedlo.pl] - Rev 10 Go to most recent revision

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

Powered by WebSVN 2.2.1