Files
iftools/SRC/ifup

912 lines
19 KiB
Perl
Executable File

#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open3;
use IO::Select;
use Fcntl qw( LOCK_EX LOCK_NB );
use Data::Dumper;
open( SELF, "<$0" ) || error( int( $! ), "open(): $0: $!" );
flock( SELF, LOCK_EX|LOCK_NB ) || error( -1, "Another instance is running" );
use constant CONFDIR => '/etc';
use constant CONFIGFILE => CONFDIR . '/network.conf';
use constant NOACT => eval {
for ( my $i = 0; $i <= $#ARGV; $i++ ) {
if ( $ARGV[$i] eq '-n' || $ARGV[$i] eq '--no-act' ) {
splice( @ARGV, $i, 1 );
return( 1 );
}
}
return( 0 );
};
use constant VERBOSE => eval {
for ( my $i = 0; $i <= $#ARGV; $i++ ) {
if ( $ARGV[$i] eq '-v' || $ARGV[$i] eq '--verbose' ) {
splice( @ARGV, $i, 1 );
return( 1 );
}
}
return( 0 );
};
use constant VERSION => '0.1';
sub aliasdown {
my $alias = shift;
my $runningconf = shift;
my $aliases = $runningconf->{$alias->{'dev'}}{'aliases'};
my $result;
if ( my $running = $aliases->{$alias->{'name'}} ) {
if ( VERBOSE ) {
print "Deleting $running->{'address'}"
. " from $alias->{'dev'}"
. " (label $alias->{'dev'}:"
. "$alias->{'name'})\n";
}
$result = runcmd( "ip address delete"
. " $running->{'address'}"
. " dev $running->{'dev'} label"
. " $running->{'dev'}:$alias->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
return( $result->{'stat'} );
}
}
sub aliasup {
my $alias = shift;
my $runningconf = shift;
my $dev = $alias->{'dev'};
my $name = $alias->{'name'};
my $result;
if ( my $old = $runningconf->{$dev}{'aliases'}{$name} ) {
if ( $old->{'address'} eq $alias->{'address'} &&
$old->{'dev'} eq $alias->{'dev'} &&
$old->{'name'} eq $alias->{'name'} ) {
if ( VERBOSE ) {
print STDERR "Interface $dev:$name"
. " is already up\n";
}
return( 1 );
}
if ( my $retval = aliasdown( $old, $runningconf ) ) {
return( $retval );
}
}
if ( VERBOSE ) {
print "Adding $alias->{'address'} to $alias->{'dev'}"
. " (label $alias->{'dev'}"
. ":$alias->{'name'})\n";
}
$result = runcmd( "ip address add $alias->{'address'}"
. " dev $alias->{'dev'}"
. " label $alias->{'dev'}:$alias->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
return( $result->{'stat'} );
}
sub cidr2netmask {
my $cidr = shift;
my $bit = ( 2 ** ( 32 - $cidr ) ) - 1;
my $full_mask = unpack( 'N', pack( 'C4', 255,255,255,255 ) );
my $netmask = join( '.', unpack( 'C4', pack( 'N',
( $full_mask ^ $bit ) ) ) );
return( $netmask );
}
sub error {
my $status = shift;
my $errstr = shift;
my $level = 1;
my @stack = ();
chomp( $errstr );
print "\n";
print STDERR ( caller() )[1] .":\n $errstr at line "
. ( caller() )[2] . "\n";
if ( VERBOSE ) {
if ( caller( $level ) ) {
print "\n=== Stack Trace ===\n";
}
while ( my @trace = caller( $level++ ) ) {
print STDERR " $trace[1]:\n"
. " $trace[3]() called at line $trace[2]\n";
}
}
print "\n";
if ( $status ) {
exit( $status );
}
}
sub getroutes {
my $routes = {};
my $result = runcmd( 'ip route show' );
if ( $result->{'stat'} || ! $result->{'stdout'} ) {
return;
}
open( my $fh, '<', \$result->{'stdout'} );
while ( <$fh> ) {
if ( $_ =~ /^(\S+)\s+via\s+(\S+)\s+dev\s+(\S+)/ ) {
$routes->{$1}{'via'} = $2;
$routes->{$1}{'dev'} = $3;
}
}
close( $fh );
return( $routes );
}
sub ifdown {
my $iface = shift;
my $result = {};
my $stat = 0;
if ( VERBOSE ) {
print "Flushing interface $iface->{'name'}\n";
}
$result = runcmd( "ip addr flush dev $iface->{'name'}" );
if ( $result->{'stat'} ) {
$stat = $result->{'stat'};
}
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
print "Bringing down interface $iface->{'name'}\n";
}
$result = runcmd( "ip link set $iface->{'name'} down" );
if ( $result->{'stat'} ) {
$stat = $result->{'stat'};
}
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
return( $stat );
}
sub ifup {
my $iface = shift;
my $runningconf = shift;
my $routes = getroutes();
my $result;
my $stat = 0;
if ( my $old = $runningconf->{$iface->{'name'}} ) {
if ( ( $old->{'address'} && $iface->{'address'} &&
$old->{'address'} eq $iface->{'address'} ) ||
( $old->{'state'} eq 'UP' && $iface->{'inet'} eq 'dhcp' ) ) {
if ( VERBOSE ) {
print STDERR "Interface $iface->{'name'}"
. " is already up\n";
}
return;
}
if ( VERBOSE ) {
print "Flushing interface $iface->{'name'}\n";
}
$result = runcmd( "ip addr flush dev $iface->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
if ( $iface->{'macaddr'} ) {
if ( VERBOSE ) {
print "Setting MAC address on interface"
. " $iface->{'name'} to $iface->{'macaddr'}\n";
}
$result = runcmd( "ip link set dev $iface->{'name'}"
. " address $iface->{'macaddr'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
if ( VERBOSE ) {
print "Bringing up interface $iface->{'name'}\n";
}
if ( $iface->{'inet'} && $iface->{'inet'} eq 'static' ) {
if ( VERBOSE ) {
print "Setting address to $iface->{'address'}"
. " on interface $iface->{'name'}\n";
}
$result = runcmd( "ip address add $iface->{'address'}"
. " dev $iface->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
elsif ( $iface->{'inet'} && $iface->{'inet'} eq 'loopback' &&
! $runningconf->{$iface->{'name'}}{'name'} ) {
if ( VERBOSE ) {
print "Setting address to $iface->{'address'}"
. " on interface $iface->{'name'}\n";
}
$result = runcmd( "ip -d address add $iface->{'address'}"
. " dev $iface->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
elsif ( $iface->{'inet'} && $iface->{'inet'} eq 'dhcp' ) {
if ( -f '/var/run/dhclient.pid' ) {
system( "dhclient -r $iface->{'name'}" );
}
if ( VERBOSE ) {
print "Starting dhclient for $iface->{'name'}\n";
}
$result = runcmd( "dhclient $iface->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
$result = runcmd( "ip link set $iface->{'name'} up" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
if ( ! $routes->{'default'} && $iface->{'gateway'} ) {
if ( VERBOSE ) {
print "Adding default gateway $iface->{'gateway'}\n";
}
$result = runcmd( "ip route add default"
. " via $iface->{'gateway'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
elsif ( $routes->{'default'}{'via'} && $iface->{'gateway'} &&
$routes->{'default'}{'via'} ne $iface->{'gateway'} &&
$routes->{'default'}{'dev'} eq $iface->{'name'} ) {
if ( VERBOSE ) {
print "Changing default gateway to"
. " $iface->{'gateway'}\n";
}
$result = runcmd( "ip route delete default via"
. " $routes->{'default'}{'via'} dev $iface->{'name'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
$result = runcmd( "ip route add default"
. " via $iface->{'gateway'}" );
if ( VERBOSE ) {
if ( $result->{'stdout'} ) {
print $result->{'stdout'};
}
if ( $result->{'stderr'} ) {
print STDERR $result->{'stderr'};
}
}
if ( $result->{'stat'} ) {
return( $result->{'stat'} );
}
}
if ( $iface->{'routes'} ) {
foreach my $target ( keys( %{$iface->{'routes'}} ) ) {
if ( VERBOSE ) {
print "Adding route $target via"
. " $iface->{'routes'}{$target}"
. " dev $iface->{'name'}\n";
}
system( "ip route add $target via"
. " $iface->{'routes'}{$target}"
. " dev $iface->{'name'}" );
}
}
}
sub listfiles {
my $dir = shift;
my $files = [];
my $sorted = [];
if ( ! -d $dir ) {
return;
}
opendir( DIR, $dir ) || error( int( $! ), "opendir(): $dir: $!" );
while ( readdir( DIR ) ) {
if ( ! -f "$dir/$_" || $_ =~ /^\./ ) {
next;
}
push ( @$files, $_ );
}
@$sorted = sort( @$files );
return( $sorted );
}
sub netmask2cidr {
my $netmask = shift;
my $bits = {
0 => 0,
128 => 1,
192 => 2,
224 => 3,
240 => 4,
248 => 5,
252 => 6,
254 => 7,
255 => 8
};
my $lastoct;
my $cidr = 0;
foreach ( split( /\./, $netmask ) ) {
if ( ( $lastoct && $lastoct != 255 && $_ != 0 ) ||
! defined( $bits->{$_} ) ) {
error( int( $! ), "netmask2cidr(): $netmask:"
. " Invalid netmask" );
}
$cidr += $bits->{$_};
$lastoct = $_;
}
return( $cidr );
}
sub readconf {
my $file = shift;
my $conf = shift || {};
my $iface;
open( my $config, "<$file" ) || error( int( $! ), "open(): $file: $!" );
while ( <$config> ) {
if ( $_ =~ /^\s*#/ ) {
next;
}
if ( $_ =~ /source-directory\s+(\S+)/ ) {
my $path = $1;
my $files;
if ( substr( $path, 0, 1 ) ne '/' ) {
$path = CONFDIR . "/$path";
}
$files = listfiles( $path );
foreach my $file ( @$files ) {
readconf( "$path/$file", $conf );
}
}
elsif ( $_ =~ /auto\s+(\S+)/ && $1 =~ /^(\S+):(\S+)$/ ) {
$conf->{$1}{'aliases'}{$2}{'auto'} = 1;
}
elsif ( $_ =~ /auto\s+(\S+)/ ) {
$conf->{$1}{'auto'} = 1;
}
elsif ( $_ =~ /iface\s+(\S+):(\S+)/ ) {
$iface = "$1:$2";
$conf->{$1}{'aliases'}{$2}{'dev'} = $1;
$conf->{$1}{'aliases'}{$2}{'name'} = $2;
$conf->{$1}{'aliases'}{$2}{'cnt'} =
keys( %{$conf->{$1}{'aliases'}} );
}
elsif ( $_ =~ /iface\s+(\S+)\s+(\S+)\s+(\S+)/ ) {
$iface = $1;
if ( $conf->{$iface}{'inet'} ) {
error( -1, "Multiple instances of $iface"
. " in $file" );
}
$conf->{$iface}{$2} = $3;
$conf->{$iface}{'name'} = $iface;
if ( $3 eq 'loopback' ) {
$conf->{$iface}{'address'} = '127.0.0.1/8';
}
$conf->{$iface}{'cnt'} = scalar( keys( %$conf ) );
}
elsif ( $_ =~ /route\s+(\S+)\s+via\s+(\S+)/ ) {
$conf->{$iface}{'routes'}{$1} = $2;
}
elsif ( $iface && $_ =~ /^\s*(\S+)\s+(\S+)/ ) {
my ( $attr, $value ) = ( $1, $2 );
if ( $iface =~ /^(\S+):(\S+)/ ) {
$conf->{$1}{'aliases'}{$2}{$attr} = $value;
}
else {
$conf->{$iface}{$attr} = $value;
}
}
elsif ( $iface && $_ =~ /^\s*(\S+)\s+(\S+)/ ) {
$conf->{$iface}{$1} = $2;
}
}
close( $config );
foreach my $devname ( keys( %$conf ) ) {
my $dev = $conf->{$devname};
if ( $dev->{'address'} && $dev->{'netmask'} ) {
$dev->{'address'} .= '/'
. netmask2cidr( $dev->{'netmask'} );
undef( $dev->{'netmask'} );
}
if ( ! keys( %{$dev->{'aliases'}} ) ) {
next;
}
foreach my $aliasname ( keys( %{$dev->{'aliases'}} ) ) {
my $alias = $dev->{'aliases'}{$aliasname};
if ( $alias->{'address'} && $alias->{'netmask'} ) {
$alias->{'address'} .= '/'
. netmask2cidr( $alias->{'netmask'} );
undef( $alias->{'netmask'} );
}
}
}
return( $conf );
}
sub runcmd {
if ( NOACT ) {
return( { stat => 0 } );;
}
my $cmd = shift;
my $result = {
stdout => '',
stderr => '',
stat => ''
};
my $sel = IO::Select->new();
my $pid = eval {
return( open3( \*CHLDIN, \*CHLDOUT, \*CHLDERR, $cmd ) );
} || error( int( $! ), "open3(): $cmd: $!" );
close( CHLDIN );
$sel->add( *CHLDOUT, *CHLDERR );
while ( my @fhs = $sel->can_read ) {
foreach my $fh ( @fhs ) {
if ( eof( $fh ) ) {
$sel->remove( $fh );
next;
}
if ( fileno( $fh ) == fileno( *CHLDOUT ) ) {
$result->{'stdout'} .= <$fh>;
}
elsif ( fileno( $fh ) == fileno( *CHLDERR ) ) {
$result->{'stderr'} .= <$fh>;
}
}
}
close( CHLDOUT );
close( CHLDERR );
waitpid( $pid, 0 );
$result->{'stat'} = $? >> 8;
return( $result );
}
sub runningconf {
my $cmd = 'ip -d address show';
my $sel = IO::Select->new();
my $pid = eval {
return( open3( \*CHLDIN, \*CHLDOUT, \*CHLDERR, $cmd ) );
} || error( int( $! ), "open3(): $cmd: $!" );
my $stat;
my $stdout = '';
my $stderr = '';
my $runningconf = {};
my $macaddr;
my $dev;
close( CHLDIN );
$sel->add( *CHLDOUT, *CHLDERR );
while ( my @fhs = $sel->can_read ) {
foreach my $fh ( @fhs ) {
if ( eof( $fh ) ) {
$sel->remove( $fh );
next;
}
if ( fileno( $fh ) == fileno( *CHLDOUT ) ) {
$stdout .= <$fh>;
}
elsif ( fileno( $fh ) == fileno( *CHLDERR ) ) {
$stderr .= <$fh>;
}
}
}
close( CHLDOUT );
close( CHLDERR );
waitpid( $pid, 0 );
$stat = $? >> 8;
if ( $stat ) {
error( int( $! ), "open3: $cmd: $stderr" );
}
open( my $fh, '<', \$stdout ) || error( int( $! ), "open(): $!" );
while ( <$fh> ) {
if ( $_ =~ /^\d+:\s*(\S+):/ ) {
$dev = $1;
}
if ( $_ =~ /\s+mtu\s+(\d+)/ ) {
$runningconf->{$dev}{'mtu'} = $1;
}
if ( $_ =~ /\s+qdisc\s+(\S+)/ ) {
$runningconf->{$dev}{'qdisc'} = $1;
}
if ( $_ =~ /\s+state\s+(\S+)/ ) {
$runningconf->{$dev}{'state'} = $1;
}
if ( $_ =~ /\s+group\s+(\S+)/ ) {
$runningconf->{$dev}{'group'} = $1;
}
if ( $_ =~ /\s+qlen\s+(\S+)/ ) {
$runningconf->{$dev}{'qlen'} = $1;
}
if ( $_ =~ /^\s+link\/(\S+)\s+(\S+)/ ) {
$runningconf->{$dev}{'link'} = $1;
$runningconf->{$dev}{'macaddr'} = $2;
}
if ( $_ =~ /\s+promiscuity\s+(\S+)/ ) {
$runningconf->{$dev}{'promiscuity'} = $1;
}
if ( $_ =~ /^\s+inet\s+(\S+).*\s+(\S+):(\S+)/ ) {
$runningconf->{$2}{'aliases'}{$3}{'address'} = $1;
$runningconf->{$2}{'aliases'}{$3}{'dev'} = $dev;
$runningconf->{$2}{'aliases'}{$3}{'name'} = $3;
}
elsif ( $_ =~ /^\s+inet\s+(\S+).*(\S+)$/ ) {
if ( $runningconf->{$dev}{'address'} ) {
next;
}
$runningconf->{$dev}{'address'} = $1;
}
}
close( $fh );
return( $runningconf );
}
my $conf = readconf( CONFIGFILE );
my $runningconf = runningconf();
my $self = ( split( '/', $0 ) )[-1];
my $ifaces = [];
my $all = eval {
for ( my $i = 0; $i <= $#ARGV; $i++ ) {
if ( $ARGV[$i] eq '-a' || $ARGV[$i] eq '--all' ) {
splice( @ARGV, $i, 1 );
return( 1 );
}
}
};
my $stat = 0;
#print Dumper( $conf ); exit;
if ( grep( $_ eq '-h' || $_ eq '--help', @ARGV ) ) {
print "Usage: $self [OPTIONS] [INTERFACES]\n";
if ( $self eq 'ifup' ) {
print "Bring a network interface up\n\n";
}
elsif ( $self eq 'ifdown' ) {
print "Bring a network interface down\n\n";
}
elsif ( $self eq 'ifreload' ) {
print "Reload interface configuration\n\n";
}
elsif ( $self eq 'ifquery' ) {
print "View current status of interfaces\n\n";
}
print "Options:\n"
. "\t-a, --all\t\tperform actions on all 'auto' interfaces\n"
. "\t-h, --help\t\tprint usage information\n";
if ( $self ne 'ifquery' ) {
print "\t-n, --no-act\t\tdisplay actions without taking them\n"
. "\t-v, --verbose\t\tdisplay actions as they occur\n";
}
print "\t-V, --version\t\tdisplay version information\n";
exit( 0 );
}
elsif ( grep( $_ eq '-V' || $_ eq '--version', @ARGV ) ) {
print "iftools version " . VERSION . "\n";
exit( 0 );
}
if ( ! $all ) {
foreach ( @ARGV ) {
( my $dev = $_ ) =~ s/:.*//;
if ( $runningconf->{$dev} ) {
push( @$ifaces, $_ );
}
elsif ( VERBOSE ) {
print STDERR "$_: No such device\n";
}
}
}
else {
foreach ( sort { $conf->{$a}{'cnt'} <=> $conf->{$b}{'cnt'} }
( keys( %$conf ) ) ) {
( my $dev = $_ ) =~ s/:.*//;
if ( $self eq 'ifup' && ! $conf->{$_}{'auto'} ) {
next;
}
if ( $runningconf->{$dev} ) {
push( @$ifaces, $_ );
}
elsif ( VERBOSE ) {
print STDERR "$_: No such device\n";
next;
}
foreach my $alias ( sort {
$conf->{$dev}{'aliases'}{$a}{'cnt'} <=>
$conf->{$dev}{'aliases'}{$b}{'cnt'} }
keys( %{$conf->{$dev}{'aliases'}} ) ) {
push( @$ifaces, "$dev:$alias" );
}
}
}
foreach my $iface ( @$ifaces ) {
if ( $self eq 'ifup' ) {
if ( $iface =~ /^(\S+):(\S+)/ ) {
$stat = aliasup( $conf->{$1}{'aliases'}{$2},
$runningconf ) || $stat;
}
else {
$stat = ifup( $conf->{$iface}, $runningconf ) || $stat;
}
}
elsif ( $self eq 'ifdown' ) {
if ( $iface =~ /^(\S+):(\S+)/ ) {
$stat = aliasdown( $conf->{$1}{'aliases'}{$2},
$runningconf ) || $stat;
}
else {
$stat = ifdown( $conf->{$iface}, $runningconf ) ||
$stat;
}
}
elsif ( $self eq 'ifreload' ) {
if ( $iface =~ /^(\S+):(\S+)/ ) {
$stat = aliasdown( $conf->{$1}{'aliases'}{$2},
$runningconf ) || $stat;
$runningconf = runningconf();
$stat = aliasup( $conf->{$1}{'aliases'}{$2},
$runningconf ) || $stat;
}
else {
$stat = ifdown( $conf->{$iface}, $runningconf ) ||
$stat;
$runningconf = runningconf();
$stat = ifup( $conf->{$iface}, $runningconf ) ||
$stat;
}
}
elsif ( $self eq 'ifquery' ) {
if ( $iface =~ /^\S+:\S+/ ) {
next;
}
elsif ( ! $runningconf->{$iface} ) {
$runningconf->{$iface} = {
state => 'No such device',
address => '',
macaddr => ''
};
}
print "[$iface]\n"
. " State: $runningconf->{$iface}{'state'}\n";
if ( $runningconf->{$iface}{'address'} ) {
print " Address: $runningconf->{$iface}{'address'}\n";
}
if ( $runningconf->{$iface}{'macaddr'} ) {
print " MAC: $runningconf->{$iface}{'macaddr'}\n";
}
foreach my $name ( sort( keys(
%{$runningconf->{$iface}{'aliases'}} ) ) ) {
my $alias = $runningconf->{$iface}{'aliases'}{$name};
print "[$iface:$name]\n"
. " Address: $alias->{'address'}\n";
}
}
$runningconf = runningconf();
}
close( SELF );
exit( $stat );