#!/usr/bin/perl # # Module: vyatta-interfaces.pl # # **** License **** # Version: VPL 1.0 # # The contents of this file are subject to the Vyatta Public License # Version 1.0 ("License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.vyatta.com/vpl # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc. # All Rights Reserved. # # Author: Stig Thormodsrud # Date: November 2007 # Description: Script to assign addresses to interfaces. # # **** End License **** # use lib "/opt/vyatta/share/perl5/"; use VyattaConfig; use VyattaMisc; use Getopt::Long; use NetAddr::IP; use strict; use warnings; my $dhcp_daemon = '/sbin/dhclient'; my $dhcp_conf = '/etc/dhcp3/dhclient.conf'; my $dhcp_pid = '/var/run/dhclient.pid'; my $dhcp_leases = '/var/lib/dhcp3/dhclient.leases'; my ($eth_update, $eth_delete, $addr, $dev); GetOptions("eth-addr-update=s" => \$eth_update, "eth-addr-delete=s" => \$eth_delete, "valid-addr=s" => \$addr, "dev=s" => \$dev, ); if (defined $eth_update) { update_eth_addrs($eth_update, $dev); } if (defined $eth_delete) { delete_eth_addrs($eth_delete, $dev); } if (defined $addr) { is_valid_addr($addr, $dev); } sub is_ip_configured { my ($intf, $ip) = @_; my $wc = `ip addr show $intf | grep $ip | wc -l`; if (defined $wc and $wc > 0) { return 1; } else { return 0; } } sub is_ip_duplicate { my ($intf, $ip) = @_; # # get a list of all ipv4 and ipv6 addresses # my @ipaddrs = `ip addr show | grep inet | cut -d" " -f6`; chomp @ipaddrs; my %ipaddrs_hash = map { $_ => 1 } @ipaddrs; if (defined $ipaddrs_hash{$ip}) { # # allow dup if it's the same interface # if (is_ip_configured($intf, $ip)) { return 0; } return 1; } else { return 0; } } sub is_dhcp_running { if (-f $dhcp_pid) { my $pid = `cat $dhcp_pid`; chomp $pid; my $ps = `ps -p $pid -o comm=`; if (defined($ps) && $ps ne "") { return 1; } } return 0; } sub dhcp_start_daemon { my $cmd = "$dhcp_daemon -q -nw &"; system($cmd); } sub dhcp_stop_daemon { if (is_dhcp_running()) { my $pid = `cat $dhcp_pid`; system("kill $pid"); } system("rm -f $dhcp_pid"); } sub dhcp_restart_daemon { if (is_dhcp_running()) { dhcp_stop_daemon(); } dhcp_start_daemon(); } sub dhcp_release_addr { my $intf = shift; my $cmd = "$dhcp_daemon -q -r $intf 2> /dev/null"; system($cmd); } sub dhcp_write_file { my ($file, $data) = @_; open(my $fh, '>', $file) || die "Couldn't open $file - $!"; print $fh $data; close $fh; } sub dhcp_conf_header { my $output; my $date = `date`; chomp $date; $output = "#\n# autogenerated by vyatta-interfaces.pl on $date\n#\n"; $output .= "request subnet-mask, broadcast-address, time-offset, routers,\n"; $output .= "\tdomain-name, domain-name-servers, host-name,\n"; $output .= "\tinterface-mtu;\n\n"; return $output; } sub dhcp_get_interfaces { my @dhcp_intfs; my $config = new VyattaConfig; $config->setLevel("interfaces ethernet"); my @eths = $config->listNodes(); foreach my $eth (@eths) { $config->setLevel("interfaces ethernet $eth"); if ($config->exists("address")) { my @addrs = $config->returnValues("address"); foreach my $addr (@addrs) { if (defined $addr && $addr eq "dhcp") { push @dhcp_intfs, $eth; } } } $config->setLevel("interfaces ethernet $eth vif"); my @vifs = $config->listNodes(); foreach my $vif (@vifs) { $config->setLevel("interfaces ethernet $eth vif $vif"); my @addrs = $config->returnValues("address"); foreach my $addr (@addrs) { if (defined $addr && $addr eq "dhcp") { push @dhcp_intfs, "$eth.$vif"; } } } } return @dhcp_intfs; } sub is_dhcp_enabled { my $intf = shift; my $config = new VyattaConfig; if ($intf =~ m/(\w+)\.(\d)/) { $config->setLevel("interfaces ethernet $1 vif $2"); } else { $config->setLevel("interfaces ethernet $intf"); } my @addrs = $config->returnOrigValues("address"); foreach my $addr (@addrs) { if (defined $addr && $addr eq "dhcp") { return 1; } } return 0; } sub is_address_enabled { my $intf = shift; my $config = new VyattaConfig; if ($intf =~ m/(\w+)\.(\d)/) { $config->setLevel("interfaces ethernet $1 vif $2"); } else { $config->setLevel("interfaces ethernet $intf"); } my @addrs = $config->returnOrigValues("address"); foreach my $addr (@addrs) { if (defined $addr && $addr ne "dhcp") { return 1; } } return 0; } sub dhcp_update_config { my $output = dhcp_conf_header(); my $config = new VyattaConfig; my $dhcp_instances = 0; my @dhcp_intfs = dhcp_get_interfaces(); foreach my $intf (@dhcp_intfs) { $output .= "interface \"$intf\" {\n}\n\n"; $dhcp_instances++; } if ($dhcp_instances > 0) { my $conf_file = $dhcp_conf; dhcp_write_file($conf_file, $output); dhcp_restart_daemon(); } return $dhcp_instances; } sub update_dhcp_client { my $dhcp_instances = dhcp_update_config(); if ($dhcp_instances == 0) { dhcp_stop_daemon(); } } sub is_ip_v4_or_v6 { my $addr = shift; my $ip = NetAddr::IP->new($addr); if (defined $ip && $ip->version() == 4) { # # the call to IP->new() will accept 1.1 and consider # it to be 1.1.0.0, so add a check to force all # 4 octets to be defined # if ($addr !~ /\d+\.\d+\.\d+\.\d+/) { return undef; } return 4; } $ip = NetAddr::IP->new6($addr); if (defined $ip && $ip->version() == 6) { return 6; } return undef; } sub update_eth_addrs { my ($addr, $intf) = @_; if ($addr eq "dhcp") { update_dhcp_client(); return; } my $version = is_ip_v4_or_v6($addr); if (!defined $version) { exit 1; } if (is_ip_configured($intf, $addr)) { # # treat this as informational, don't fail # print "Address $addr already configured on $intf\n"; exit 0; } if ($version == 4) { return system("ip addr add $addr broadcast + dev $intf"); } if ($version == 6) { return system("ip -6 addr add $addr dev $intf"); } print "Error: Invalid address/prefix [$addr] for interface $intf\n"; exit 1; } sub delete_eth_addrs { my ($addr, $intf) = @_; if ($addr eq "dhcp") { dhcp_release_addr($intf); update_dhcp_client(); system("rm -f /var/lib/dhcp3/dhclient_$intf\_lease"); return; } my $version = is_ip_v4_or_v6($addr); if (!defined $version) { exit 1; } if ($version == 4) { return system("ip addr del $addr dev $intf"); } if ($version == 6) { return system("ip -6 addr del $addr dev $intf"); } } sub is_valid_addr { my ($addr_net, $intf) = @_; if ($addr_net eq "dhcp") { if ($intf eq "lo") { print "Error: can't use dhcp client on loopback interface\n"; exit 1; } if (is_dhcp_enabled($intf)) { print "Error: dhcp already configured for $intf\n"; exit 1; } if (is_address_enabled($intf)) { print "Error: remove static addresses before enabling dhcp for $intf\n"; exit 1; } exit 0; } my ($addr, $net); if ($addr_net =~ m/^([0-9\.\:]+)\/(\d+)$/) { $addr = $1; $net = $2; } else { exit 1; } my $version = is_ip_v4_or_v6($addr_net); if (!defined $version) { exit 1; } if (is_dhcp_enabled($intf)) { print "Error: remove dhcp before adding static addresses for $intf\n"; exit 1; } if (is_ip_duplicate($intf, $addr_net)) { print "Error: duplicate address/prefix [$addr_net]\n"; exit 1; } if ($version == 4) { if ($net > 0 && $net <= 32) { exit 0; } } if ($version == 6) { if ($net > 1 && $net <= 128) { exit 0; } } exit 1; } exit 0; # end of file