Page MenuHomeVyOS Platform

dhcpd-config.pl

Authored By
hexes
Feb 7 2020, 5:20 AM
Size
57 KB
Referenced Files
None
Subscribers
None

dhcpd-config.pl

#!/usr/bin/perl
# Module: dhcpd-config.pl
#
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# A copy of the GNU General Public License is available as
# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'.
# You can also obtain it by writing to the Free Software Foundation,
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc.
# All Rights Reserved.
#
# Author: Marat Nepomanyashy
# Date: December 2007
# Description: Script to setup dhcp server
#
# **** End License ****
use strict;
use lib "/opt/vyatta/share/perl5/";
use Getopt::Long;
my $out;
my $init;
GetOptions(
"out=s" => \$out,
"init=s" => \$init
);
my $error = 0;
my $genout = '';
my $genout_initial = "# generated by $0\n\n";
my $genout_failover = '';
my $exclude_ips_count = 0;
my $split_for_static_ip;
use NetAddr::IP; # This library is available via libnetaddr-ip-perl.deb
use Vyatta::Config;
my $vcDHCP = new Vyatta::Config();
my @names;
my $disabled = 0;
my $genout_initial_static_route_count = 0;
my $genout_initial_wpad_count = 0;
sub replace_quot {
my $line = shift;
my $count = $line =~ s/\"/\"/g;
if ( $count != '' and $count % 2 ) {
print "Error: unbalanced quotes [$line]\n";
$error = 1;
}
return $line;
}
my $dm_before;
my $dm_after;
$vcDHCP->setLevel('service dhcp-server');
if ( $vcDHCP->exists('.') ) {
# Ubiquiti dhcp option 43 globals
$genout_initial .= "option space ubnt;\n";
$genout_initial .= "option ubnt.unifi-address code 1 = ip-address;\n\n";
$genout_initial .= "class \"ubnt\" {\n";
$genout_initial .= "\tmatch if substring (option vendor-class-identifier ";
$genout_initial .= ", 0, 4) = \"ubnt\";\n";
$genout_initial .= "\toption vendor-class-identifier \"ubnt\";\n";
$genout_initial .= "\tvendor-option-space ubnt;\n}\n\n";
my $hostfile_val = $vcDHCP->returnValue('hostfile-update');
if (not defined $hostfile_val) {
$hostfile_val = "disabled";
}
my $static_arp_val = $vcDHCP->returnValue('static-arp');
if (not defined $static_arp_val) {
$static_arp_val = "disabled";
}
if ($hostfile_val eq 'enable' || $static_arp_val eq 'enable') {
# hooks to call to update /etc/hosts and static ARP for DHCP leases
$genout_initial .= "on commit {\n";
$genout_initial .= "\tset ClientName = ";
$genout_initial .= "pick-first-value(host-decl-name, ";
$genout_initial .= "option fqdn.hostname, ";
$genout_initial .= "option host-name);\n";
$genout_initial .= "\tset ClientIp = binary-to-ascii(10, 8, \".\", ";
$genout_initial .= "leased-address);\n";
$genout_initial .= "\tset ClientMac = binary-to-ascii(16, 8, \":\", ";
$genout_initial .= "substring(hardware, 1, 6));\n";
$genout_initial .= "\tset ClientDomain = pick-first-value(";
$genout_initial .= "config-option domain-name, \"..YYZ!\");\n";
$genout_initial .= "\texecute(\"/opt/vyatta/sbin/on-dhcp-event.sh\", ";
$genout_initial .= "\"commit\", ClientName, ClientIp, ClientMac, ";
$genout_initial .= "ClientDomain, \"$hostfile_val\", \"$static_arp_val\");\n";
$genout_initial .= "}\n\n";
$genout_initial .= "on release {\n";
$genout_initial .= "\tset ClientName = ";
$genout_initial .= "pick-first-value(host-decl-name, ";
$genout_initial .= "option fqdn.hostname, option host-name);\n";
$genout_initial .= "\tset ClientIp = binary-to-ascii(10, 8, \".\",";
$genout_initial .= "leased-address);\n";
$genout_initial .= "\tset ClientMac = binary-to-ascii(16, 8, \":\",";
$genout_initial .= "substring(hardware, 1, 6));\n";
$genout_initial .= "\tset ClientDomain = pick-first-value(";
$genout_initial .= "config-option domain-name, \"..YYZ!\");\n";
$genout_initial .= "\texecute(\"/opt/vyatta/sbin/on-dhcp-event.sh\", ";
$genout_initial .= "\"release\", ClientName, ClientIp, ClientMac, ";
$genout_initial .= "ClientDomain, \"$hostfile_val\", \"$static_arp_val\");\n";
$genout_initial .= "}\n\n";
$genout_initial .= "on expiry {\n";
$genout_initial .= "\tset ClientName = ";
$genout_initial .= "pick-first-value(host-decl-name, ";
$genout_initial .= "option fqdn.hostname, option host-name);\n";
$genout_initial .= "\tset ClientIp = binary-to-ascii(10, 8, \".\",";
$genout_initial .= "leased-address);\n";
$genout_initial .= "\tset ClientMac = binary-to-ascii(16, 8, \":\",";
$genout_initial .= "substring(hardware, 1, 6));\n";
$genout_initial .= "\tset ClientDomain = pick-first-value(";
$genout_initial .= "config-option domain-name, \"..YYZ!\");\n";
$genout_initial .= "\texecute(\"/opt/vyatta/sbin/on-dhcp-event.sh\", ";
$genout_initial .= "\"release\", ClientName, ClientIp, ClientMac, ";
$genout_initial .= "ClientDomain, \"$hostfile_val\", \"$static_arp_val\");\n";
$genout_initial .= "}\n\n";
} else {
system("sudo sed -i '/ #on-dhcp-event /d' /etc/hosts");
my $cmd = "pid=`cat /var/run/dnsmasq/dnsmasq.pid 2> /dev/null`;";
$cmd .= "if [ -n \"\$pid\" ]; then sudo kill -SIGHUP \$pid; fi";
system($cmd);
}
my $disabled_val = $vcDHCP->returnValue('disabled');
my $use_dm = $vcDHCP->returnValue('use-dnsmasq');
$dm_after = (defined($use_dm) && $use_dm eq 'enable');
$use_dm = $vcDHCP->returnOrigValue('use-dnsmasq');
$dm_before = (defined($use_dm) && $use_dm eq 'enable');
$disabled = ((defined($disabled_val) && $disabled_val eq 'true')
|| $dm_after);
if ($disabled) {
my $msg = <<"EOM";
Warning: DHCP server will be deactivated because
'service dhcp-server disabled' is 'true'
EOM
if (!$dm_after) {
print STDERR $msg;
}
$genout_initial .= "\n";
$genout_initial .= '# ' . $msg;
$genout_initial .= "\n";
}
# The ISC DHCPD server version V3.0.3 refuses to start without the 'ddns-update-style' parameter.
# if the user specifies to use dyanmic DNS update then use the 'interim' setting otherwise use 'none' for it.
$vcDHCP->setLevel('service dhcp-server dynamic-dns-update');
my $dynamic_DNS_update = $vcDHCP->returnValue('enable');
if ( defined($dynamic_DNS_update) && $dynamic_DNS_update eq 'true' ) {
$genout_initial .= "ddns-update-style interim;\n";
}
else {
$genout_initial .= "ddns-update-style none;\n\n";
}
$vcDHCP->setLevel('service dhcp-server');
my @global_params = $vcDHCP->returnValues("global-parameters");
if ( @global_params > 0 ) {
$genout_initial .=
"# The following "
. scalar @global_params
. " lines were added as global-parameters in the CLI and \n"
. "# have not been validated\n";
foreach my $line (@global_params) {
my $decoded_line = replace_quot($line);
$genout_initial .= "$decoded_line\n";
}
$genout_initial .= "\n";
}
$vcDHCP->setLevel('service dhcp-server shared-network-name');
my $totalSubnetsLeased = 0;
my $totalSubnetsMatched = 0;
my $subnet_count = 0;
my @all_subnets;
my $failover_subnets = -1;
my @failover_local_address_list;
my @failover_peer_address_list;
my @failover_name_list;
my @failover_status_list;
# get ip-addresses of all broadcast interfaces on system, later we'll
# then check that atleast one subnet is defined such that dhcp-server
# is listening on atleast one broadcast interface on the system
use Vyatta::Misc;
my @intf_ips = Vyatta::Misc::getInterfacesIPadresses("broadcast");
# start with getting dhcp-server configuration from CLI
@names = $vcDHCP->listNodes();
my %mapping_hash = ();
if ( @names == 0 ) {
print STDERR <<"EOM";
No DHCP shared networks configured.
At least one DHCP shared network must be configured.
EOM
$error = 1;
}
else {
foreach my $name (@names) {
# skip shared-network if disabled
my $shared_ntwrk_disabled = $vcDHCP->exists("$name disable");
if ( defined $shared_ntwrk_disabled ) {
next;
}
$genout .= "shared-network $name {\n";
my @subnets = $vcDHCP->listNodes("$name subnet");
if ( @subnets == 0 ) {
print STDERR <<"EOM";
No DHCP lease subnets configured for shared network name '$name'.
At least one DHCP lease subnet must be configured for each shared network.
EOM
$error = 1;
}
else {
my $authoritative = $vcDHCP->returnValue("$name authoritative");
if ( $authoritative eq 'enable' ) {
$genout .= "\tauthoritative;\n";
}
else {
$genout .= "\tnot authoritative;\n";
}
my @shared_network_params =
$vcDHCP->returnValues("$name shared-network-parameters");
if ( @shared_network_params > 0 ) {
$genout .=
"# The following "
. scalar @shared_network_params
. " lines were added as shared-network-parameters in the CLI and have not been validated\n";
foreach my $line (@shared_network_params) {
my $decoded_line = replace_quot($line);
$genout .= "\t$decoded_line\n";
}
}
if ( @subnets > 1 ) {
my $nets = join( ', ', sort(@subnets) );
print STDOUT <<"EOM";
DHCP server warning: Multiple subnets configured under shared-network-name '$name'
This implies that $nets share the same physical network
EOM
}
foreach my $subnet (@subnets) {
my $naipNetwork = new NetAddr::IP("$subnet");
$all_subnets[$subnet_count] = $naipNetwork;
$subnet_count++;
if ( defined($naipNetwork) ) {
$totalSubnetsLeased++;
foreach my $address (@intf_ips) {
if (
doCheckIfAddressPLInsideNetwork(
$address, $naipNetwork
)
)
{
$totalSubnetsMatched++;
}
}
my $sub = $naipNetwork->network()->addr();
my $netmask = $naipNetwork->mask();
my @startips_after_split = ();
my @stopips_after_split = ();
$genout .= "\tsubnet $sub netmask $netmask {\n";
my @dns_servers = $vcDHCP->returnValues(
"$name subnet $subnet dns-server");
if ( @dns_servers > 0 ) {
$genout .= "\t\toption domain-name-servers ";
my $num = 0;
foreach my $dns_server (@dns_servers) {
if ( $dns_server ne '' ) {
if ( $num > 0 ) {
$genout .= ', ';
}
$genout .= "$dns_server";
$num++;
}
}
$genout .= ";\n";
}
my @ntp_servers = $vcDHCP->returnValues(
"$name subnet $subnet ntp-server");
if ( @ntp_servers > 0 ) {
$genout .= "\t\toption ntp-servers ";
my $num = 0;
foreach my $ntp_server (@ntp_servers) {
if ( $ntp_server ne '' ) {
if ( $num > 0 ) {
$genout .= ', ';
}
$genout .= "$ntp_server";
$num++;
}
}
$genout .= ";\n";
}
my @subnet_params = $vcDHCP->returnValues(
"$name subnet $subnet subnet-parameters");
if ( @subnet_params > 0 ) {
$genout .=
"# The following "
. scalar @subnet_params
. " lines were added as subnet-parameters in the CLI and have not been validated\n";
foreach my $line (@subnet_params) {
my $decoded_line = replace_quot($line);
$genout .= "\t\t$decoded_line\n";
}
}
my @pop_servers = $vcDHCP->returnValues(
"$name subnet $subnet pop-server");
if ( @pop_servers > 0 ) {
$genout .= "\t\toption pop-server ";
my $num = 0;
foreach my $pop_server (@pop_servers) {
if ( $pop_server ne '' ) {
if ( $num > 0 ) {
$genout .= ', ';
}
$genout .= "$pop_server";
$num++;
}
}
$genout .= ";\n";
}
my @smtp_servers = $vcDHCP->returnValues(
"$name subnet $subnet smtp-server");
if ( @smtp_servers > 0 ) {
$genout .= "\t\toption smtp-server ";
my $num = 0;
foreach my $smtp_server (@smtp_servers) {
if ( $smtp_server ne '' ) {
if ( $num > 0 ) {
$genout .= ', ';
}
$genout .= "$smtp_server";
$num++;
}
}
$genout .= ";\n";
}
my @time_servers = $vcDHCP->returnValues(
"$name subnet $subnet time-server");
if ( @time_servers > 0 ) {
$genout .= "\t\toption time-servers ";
my $num = 0;
foreach my $time_server (@time_servers) {
if ( $time_server ne '' ) {
if ( $num > 0 ) {
$genout .= ', ';
}
$genout .= "$time_server";
$num++;
}
}
$genout .= ";\n";
}
my @wins_servers = $vcDHCP->returnValues(
"$name subnet $subnet wins-server");
if ( @wins_servers > 0 ) {
$genout .= "\t\toption netbios-name-servers ";
my $num_netbios = 0;
foreach my $wins_server (@wins_servers) {
if ( $wins_server ne '' ) {
if ( $num_netbios > 0 ) {
$genout .= ', ';
}
$genout .= "$wins_server";
$num_netbios++;
}
}
$genout .= ";\n";
}
my $destination_subnet = $vcDHCP->returnValue(
"$name subnet $subnet static-route destination-subnet"
);
my $router_for_destination = $vcDHCP->returnValue(
"$name subnet $subnet static-route router");
if ( $destination_subnet ne ''
&& $router_for_destination ne '' )
{
if ( $genout_initial_static_route_count == 0 ) {
$genout_initial .=
"option rfc3442-static-route code 121 = string;\n";
$genout_initial .=
"option windows-static-route code 249 = string;\n";
$genout_initial_static_route_count = 1;
}
my $slash_position =
rindex( $destination_subnet, '/' ) + 1;
my $destination_subnet_prefix =
substr( $destination_subnet, $slash_position );
my $destination_naipNetwork =
new NetAddr::IP("$destination_subnet");
my $sub = $destination_naipNetwork->addr();
my $hex_subnet = converttohex($sub);
my $prefix_plus_subnet =
prefix_and_subnet( $destination_subnet_prefix,
$hex_subnet );
my $router_naip =
new NetAddr::IP("$router_for_destination");
my $hex_router = converttohex($router_naip);
my $hex_route = $prefix_plus_subnet . $hex_router;
$genout .=
"\t\toption rfc3442-static-route $hex_route;\n";
$genout .=
"\t\toption windows-static-route $hex_route;\n";
}
elsif ($destination_subnet eq ''
&& $router_for_destination eq '' )
{
# do nothing, basically static-route has not been configured
}
else {
print STDERR <<"EOM";
Please specify the missing DHCP static-route parameter:
destination-subnet | router
EOM
$error = 1;
}
my $ip_forwarding = $vcDHCP->returnValue(
"$name subnet $subnet ip-forwarding enable");
if ( defined($ip_forwarding) ) {
if ( $ip_forwarding eq 'true' ) {
$genout .= "\t\toption ip-forwarding true;\n";
}
else {
$genout .= "\t\toption ip-forwarding false;\n";
}
}
my $default_router = $vcDHCP->returnValue(
"$name subnet $subnet default-router");
if ( $default_router ne '' ) {
$genout .= "\t\toption routers $default_router;\n";
}
my $unifi_controller = $vcDHCP->returnValue(
"$name subnet $subnet unifi-controller");
if ( $unifi_controller ne '' ) {
$genout .= "\t\toption ubnt.unifi-address";
$genout .= " $unifi_controller;\n";
}
my $server_identifier = $vcDHCP->returnValue(
"$name subnet $subnet server-identifier");
if ( $server_identifier ne '' ) {
$genout .=
"\t\toption dhcp-server-identifier $server_identifier;\n";
}
my $domain_name = $vcDHCP->returnValue(
"$name subnet $subnet domain-name");
my @domain_names = split ' ', $domain_name;
my $first_domain_name = $domain_names[0];
if ( scalar (@domain_names) > 0 ) {
$genout .=
"\t\toption domain-name \"$first_domain_name\";\n";
my $domain_search = join ', ', map { qq/"$_"/ } @domain_names;
$genout .=
"\t\toption domain-search $domain_search;\n";
}
my $tftp_server_name = $vcDHCP->returnValue(
"$name subnet $subnet tftp-server-name");
if ( $tftp_server_name ne '' ) {
$genout .=
"\t\toption tftp-server-name \"$tftp_server_name\";\n";
}
my $bootfile_name = $vcDHCP->returnValue(
"$name subnet $subnet bootfile-name");
if ( $bootfile_name ne '' ) {
$genout .=
"\t\toption bootfile-name \"$bootfile_name\";\n"
. "\t\tfilename \"$bootfile_name\";\n";
}
my $bootfile_server = $vcDHCP->returnValue(
"$name subnet $subnet bootfile-server");
if ( $bootfile_server ne '' ) {
$genout .= "\t\tnext-server $bootfile_server;\n";
}
my $time_offset = $vcDHCP->returnValue(
"$name subnet $subnet time-offset");
if ( $time_offset ne '' ) {
$genout .= "\t\toption time-offset $time_offset;\n";
}
my $wpad_url =
$vcDHCP->returnValue("$name subnet $subnet wpad-url");
if ( $wpad_url ne '' ) {
if ( $genout_initial_wpad_count == 0 ) {
$genout_initial .=
"option wpad-url code 252 = text;\n\n";
$genout_initial_wpad_count = 1;
}
$genout .= "\t\toption wpad-url \"$wpad_url\";\n";
}
my $client_prefix_length = $vcDHCP->returnValue(
"$name subnet $subnet client-prefix-length");
if ( $client_prefix_length ne '' ) {
my $naip2 = new NetAddr::IP(
"255.255.255.255/$client_prefix_length");
my $client_subnet_mask = $naip2->network()->addr();
$genout .=
"\t\toption subnet-mask $client_subnet_mask;\n";
}
my $lease =
$vcDHCP->returnValue("$name subnet $subnet lease");
if ( $lease ne '' ) {
$genout .= "\t\tdefault-lease-time $lease;\n";
$genout .= "\t\tmax-lease-time $lease;\n";
}
my @ranges =
$vcDHCP->listNodes("$name subnet $subnet start");
my $genout_failover_start = "";
my $genout_ranges = "";
my $write_failover_pool = "";
my $failover_local_address = $vcDHCP->returnValue(
"$name subnet $subnet failover local-address");
my $failover_peer_address = $vcDHCP->returnValue(
"$name subnet $subnet failover peer-address");
my $failover_name = $vcDHCP->returnValue(
"$name subnet $subnet failover name");
my $failover_status = $vcDHCP->returnValue(
"$name subnet $subnet failover status");
if ( $failover_local_address ne ''
&& $failover_peer_address ne ''
&& $failover_name ne ''
&& $failover_status ne '' )
{
my $is_there = 0;
foreach my $elt (@failover_name_list) {
if ( $elt eq $failover_name ) {
$is_there = 1;
last;
}
}
if ( $is_there == 0 ) {
if ( @ranges > 0 ) {
$failover_subnets = $failover_subnets + 1;
$failover_local_address_list
[$failover_subnets] =
$failover_local_address;
$failover_peer_address_list
[$failover_subnets] =
$failover_peer_address;
$failover_name_list[$failover_subnets] =
$failover_name;
$failover_status_list[$failover_subnets] =
$failover_status;
$write_failover_pool = "\t";
$genout_failover_start .= "\t\tpool {\n";
# need to write ranges under a pool when configuring failover
$genout_failover_start .=
"\t\t\tfailover peer \"$failover_name\";\n";
$genout_failover_start .=
"\t\t\tdeny dynamic bootp clients;\n";
}
else {
print STDERR <<"EOM";
Atleast one start-stop range must be configured for $subnet to set up DHCP failover
EOM
$error = 1;
}
}
else {
print STDERR <<"EOM";
Failover names should be unique: '$failover_name' has already been configured
EOM
$error = 1;
}
}
elsif ($failover_local_address eq ''
&& $failover_peer_address eq ''
&& $failover_name eq ''
&& $failover_status eq '' )
{
# do nothing, basically failover has not been configured
}
else {
print STDERR <<"EOM";
Please set one or more of the missing DHCP failover parameters:
local-address | peer-address | name | status
EOM
$error = 1;
}
my @naip_conflict_start;
my @naip_conflict_stop;
my @zero_to_ranges;
my $range_conflict_error = 1
; #prevents showing range conflict errors if basic errors for start-stop occur as well
my $ranges_stop_count = 0;
my @ranges_stop;
if ( @ranges == 0 ) {
$range_conflict_error = 0;
my @exclude_ips = $vcDHCP->returnValues(
"$name subnet $subnet exclude");
if ( @exclude_ips > 0 ) {
print STDERR <<"EOM";
Atleast one start-stop range must be configured for $subnet to exclude IP
EOM
$error = 1;
}
}
else {
foreach my $start (@ranges) {
my $naipStart = new NetAddr::IP($start);
if ( !$naipStart->within($naipNetwork) ) {
print STDERR <<"EOM";
Start DHCP lease IP '$start' is outside of the DHCP lease network '$subnet'
under shared network '$name'.
EOM
$error = 1;
$range_conflict_error = 0;
}
my $stop = $vcDHCP->returnValue(
"$name subnet $subnet start $start stop");
if ( defined $stop ) {
my $naipStop = new NetAddr::IP($stop);
if ( !$naipStop->within($naipNetwork) ) {
print STDERR <<"EOM";
Stop DHCP lease IP '$stop' is outside of the DHCP lease network '$subnet'
under shared network '$name'.
EOM
$error = 1;
$range_conflict_error = 0;
}
if ( $naipStop < $naipStart ) {
print STDERR <<"EOM";
Stop DHCP lease IP '$stop' should be an address equal to or later
than the Start DHCP lease IP '$start'
EOM
$error = 1;
$range_conflict_error = 0;
}
$ranges_stop[$ranges_stop_count] = $stop;
$ranges_stop_count++;
}
else {
print STDERR
"Stop DHCP lease IP not defined for Start DHCP lease IP '$start'\n";
$error = 1;
$range_conflict_error = 0;
}
}
my $range_count;
if ($range_conflict_error) {
my $start_count = 0;
my $stop_count = 0;
foreach my $conflict_start (@ranges) {
$naip_conflict_start[$start_count] =
new NetAddr::IP($conflict_start);
$start_count++;
}
foreach my $conflict_stop (@ranges_stop) {
$naip_conflict_stop[$stop_count] =
new NetAddr::IP($conflict_stop);
$stop_count++;
}
$range_count = scalar(@ranges) - 1;
@zero_to_ranges = ( 0 .. $range_count );
for my $i (@zero_to_ranges) {
for my $j (@zero_to_ranges) {
if ( $i == $j ) {
next;
}
else {
if (
(
$naip_conflict_start[$j] <=
$naip_conflict_start[$i]
)
and
( $naip_conflict_start[$i] <=
$naip_conflict_stop[$j] )
)
{
print STDERR <<"EOM";
Conflicting DHCP lease ranges: Start IP '$ranges[$i]'
lies in DHCP lease range '$ranges[$j]'-'$ranges_stop[$j]'.
EOM
$error = 1;
}
elsif (
(
$naip_conflict_start[$j] <=
$naip_conflict_stop[$i]
)
and ( $naip_conflict_stop[$i] <=
$naip_conflict_stop[$j] )
)
{
print STDERR <<"EOM";
Conflicting DHCP lease ranges: Stop IP '$ranges_stop[$i]'
lies in DHCP lease range '$ranges[$j]'-'$ranges_stop[$j]'.
EOM
$error = 1;
}
}
}
}
}
@startips_after_split = @ranges;
@stopips_after_split = @ranges_stop;
if ( $error == 0 ) {
my @exclude_ips = $vcDHCP->returnValues(
"$name subnet $subnet exclude");
if ( @exclude_ips > 0 ) {
# do a check that all these exclude ips are inside the subnet
foreach my $each_exclude_ip (@exclude_ips) {
my $naipexcludeip =
new NetAddr::IP($each_exclude_ip);
if (
!$naipexcludeip->within(
$naipNetwork)
)
{
my $naipexcludeip_addr =
$naipexcludeip->addr();
print STDERR <<"EOM";
Exclude IP '$naipexcludeip_addr' is outside of the DHCP lease network
'$subnet' under shared network '$name'.
EOM
$error = 1;
}
}
if ( $error == 0 ) {
$exclude_ips_count =
scalar(@exclude_ips);
$split_for_static_ip = 0;
my (
$split_ranges_start_ref,
$split_ranges_stop_ref
)
= split_ranges( @ranges, @ranges_stop,
@exclude_ips );
@startips_after_split =
@$split_ranges_start_ref;
@stopips_after_split =
@$split_ranges_stop_ref;
my $split_range_count =
scalar(@startips_after_split) - 1;
my @zero_to_split_ranges =
( 0 .. $split_range_count );
for my $split_range_ips (
@zero_to_split_ranges)
{
$genout_ranges .=
$write_failover_pool;
$genout_ranges .=
"\t\trange $startips_after_split[$split_range_ips] $stopips_after_split[$split_range_ips];\n";
}
}
}
else {
# write all the ranges as you got em
for my $range_ips (@zero_to_ranges) {
$genout_ranges .= $write_failover_pool;
$genout_ranges .=
"\t\trange $ranges[$range_ips] $ranges_stop[$range_ips];\n";
}
}
}
}
my $genout_failover_end = "";
if ( !( $write_failover_pool eq "" ) ) {
$genout_failover_end .= "\t\t}\n"; # end of pool
}
my @static_mapping = $vcDHCP->listNodes(
"$name subnet $subnet static-mapping");
my $mapping_cnt = 0;
foreach my $static_mapping (@static_mapping) {
my $mapping_disabled = $vcDHCP->exists(
"$name subnet $subnet static-mapping $static_mapping disable"
);
if ( defined $mapping_disabled ) {
# remove disabled static-mapping from array
delete $static_mapping[$mapping_cnt];
}
$mapping_cnt++;
}
if ( @static_mapping == 0 && @ranges == 0 ) {
print STDERR
"Warning: No DHCP start-stop range or active static-mapping set for subnet $subnet\n";
}
foreach my $static_mapping (@static_mapping) {
next if !defined $static_mapping;
my $ip_address = $vcDHCP->returnValue(
"$name subnet $subnet static-mapping $static_mapping ip-address"
);
if ( !defined($ip_address) || $ip_address eq '' ) {
print STDERR <<"EOM";
No static DHCP lease IP address specified for mapping '$static_mapping'
under shared network name '$name'.
EOM
$error = 1;
}
else {
my $naipIP = new NetAddr::IP($ip_address);
if ( !$naipIP->within($naipNetwork) ) {
print STDERR <<"EOM";
Static DHCP lease IP '$ip_address' under mapping '$static_mapping'
under shared network name '$name' is outside of the DHCP lease network '$subnet'.
EOM
$error = 1;
}
my $ip_in_range = 0;
my $equals_exclude_ip = 0;
for my $i (@zero_to_ranges) {
if ( ( $naip_conflict_start[$i] <= $naipIP )
and
( $naipIP <= $naip_conflict_stop[$i] ) )
{
$ip_in_range = 1;
}
}
my $key = $static_mapping;
foreach my $dname (@domain_names) {
$key .= '.'.$dname;
if ( $mapping_hash{$key} ) {
my $used = $mapping_hash{$static_mapping};
print STDERR <<"EOM";
Static DHCP lease IP '$ip_address' under static mapping '$static_mapping'
under shared network name '$name' is already is in by static-mapping '$used'.
EOM
$error = 1;
}
}
if ( $ip_in_range == 1 ) {
my @exclude_ips = $vcDHCP->returnValues(
"$name subnet $subnet exclude");
foreach my $exclude_ip (@exclude_ips) {
if ( $ip_address eq $exclude_ip ) {
$equals_exclude_ip = 1;
}
}
if ( $equals_exclude_ip == 0 ) {
# we are in part of code where we have checked that static-ip does not
# match any exclude ip in this subnet, so split ranges to remove this
# static IP from start-stop ranges configured for this subnet
$split_for_static_ip = 1;
my @static_ip = ($ip_address);
# need to generate new ranges so empty previously stored ranges string
$genout_ranges = "";
my (
$split_ranges_start_ref,
$split_ranges_stop_ref
)
= split_ranges( @startips_after_split,
@stopips_after_split, @static_ip );
@startips_after_split =
@$split_ranges_start_ref;
@stopips_after_split =
@$split_ranges_stop_ref;
my $split_range_count =
scalar(@startips_after_split) - 1;
my @zero_to_split_ranges =
( 0 .. $split_range_count );
for my $split_range_ips (
@zero_to_split_ranges)
{
$genout_ranges .=
$write_failover_pool;
$genout_ranges .=
"\t\trange $startips_after_split[$split_range_ips] $stopips_after_split[$split_range_ips];\n";
}
}
}
}
my $key = $static_mapping;
if ( scalar (@domain_names) > 0 ) {
foreach my $dname (@domain_names) {
$key .= '.'.$dname;
$mapping_hash{$key} = $ip_address;
}
} else {
$mapping_hash{$key} = $ip_address;
}
my $mac_address = $vcDHCP->returnValue(
"$name subnet $subnet static-mapping $static_mapping mac-address"
);
if ( !defined($mac_address) || $mac_address eq '' )
{
print STDERR <<"EOM";
No static DHCP lease mac address specified for static mapping '$static_mapping'
under shared network name '$name'.
EOM
$error = 1;
}
if ( defined($ip_address)
&& $ip_address ne ''
&& defined($mac_address)
&& $mac_address ne '' )
{
$genout .= "\t\thost $static_mapping";
if ($first_domain_name) {
$genout .= ".$first_domain_name";
}
$genout .= " {\n";
$genout .= "\t\t\tfixed-address $ip_address;\n";
$genout .=
"\t\t\thardware ethernet $mac_address;\n";
my @static_mapping_params =
$vcDHCP->returnValues(
"$name subnet $subnet static-mapping $static_mapping static-mapping-parameters"
);
if ( @static_mapping_params > 0 ) {
$genout .=
"# The following "
. scalar @static_mapping_params
. " lines were added as static-mapping-parameters in the CLI and have not been validated\n";
foreach my $line (@static_mapping_params) {
my $decoded_line = replace_quot($line);
$genout .= "\t\t\t$decoded_line\n";
}
}
$genout .= "\t\t}\n";
}
}
$genout .=
$genout_failover_start
. $genout_ranges
. $genout_failover_end;
$genout .= "\t}\n";
# if failover is configured then there needs to be a dynamic range or else
# dhcpd will exit with an error on reading dhcpd.conf
# check here that there is still a dynamic range present after splits
# for exclude IPs and static-mappings if failover configured for this subnet
if ( !( $genout_failover_start eq "" ) ) {
if ( @startips_after_split == 0 ) {
print STDERR <<"EOM";
DHCP server error: All IP addresses defined in start-stop ranges under
'shared-network-name $name subnet $subnet' have either been excluded
or assigned using 'static-mapping'. To configure DHCP failover there
needs to be atleast one IP address that is to be assigned dynamically.
EOM
$error = 1;
}
}
}
}
}
$genout .= "}\n";
}
}
if ( $failover_subnets >= 0 ) {
my @zero_to_failover_subnets = ( 0 .. $failover_subnets );
for my $failover (@zero_to_failover_subnets) {
if ( $failover_status_list[$failover] eq 'primary' ) {
# add stuff for primary DHCP server
$genout_failover .=
"failover peer \"$failover_name_list[$failover]\" {\n";
$genout_failover .= "primary;\n";
$genout_failover .=
"address $failover_local_address_list[$failover];\n";
$genout_failover .= "port 520;\n";
$genout_failover .=
"peer address $failover_peer_address_list[$failover];\n";
$genout_failover .= "peer port 520;\n";
$genout_failover .= "max-response-delay 30;\n";
$genout_failover .= "max-unacked-updates 10;\n";
$genout_failover .= "load balance max seconds 3;\n";
$genout_failover .= "mclt 1800;\n";
$genout_failover .= "split 128;\n";
$genout_failover .= "}\n";
}
elsif ( $failover_status_list[$failover] eq 'secondary' ) {
# add stuff for secondary DHCP server
$genout_failover .=
"failover peer \"$failover_name_list[$failover]\" {\n";
$genout_failover .= "secondary;\n";
$genout_failover .=
"address $failover_local_address_list[$failover];\n";
$genout_failover .= "port 520;\n";
$genout_failover .=
"peer address $failover_peer_address_list[$failover];\n";
$genout_failover .= "peer port 520;\n";
$genout_failover .= "max-response-delay 30;\n";
$genout_failover .= "max-unacked-updates 10;\n";
$genout_failover .= "load balance max seconds 3;\n";
$genout_failover .= "}\n";
}
}
}
my @zero_to_subnet_count = ( 0 .. ( $subnet_count - 1 ) );
for my $iloop (@zero_to_subnet_count) {
for my $jloop (@zero_to_subnet_count) {
if ( $iloop == $jloop ) {
next;
}
else {
if ( $all_subnets[$jloop]->within( $all_subnets[$iloop] ) ) {
print STDERR
"Conflicting subnet ranges: $all_subnets[$jloop] overlaps $all_subnets[$iloop]\n";
$error = 1;
}
}
}
}
if ( $totalSubnetsLeased > 0 && $totalSubnetsMatched == 0 ) {
print STDERR <<"EOM";
DHCP server error: None of the DHCP lease subnets are inside any of the subnets
configured on broadcast interfaces. At least one DHCP lease subnet must be set
such that DHCP server listens on a minimum of one broadcast interface
EOM
$error = 1;
}
if ($error) {
print STDERR
"DHCP server configuration commit aborted due to error(s).\n";
exit(1);
}
$genout = $genout_initial . $genout_failover . $genout;
}
my $output;
if ($out) {
open $output, '>', $out
or die "Can't open $out : $!";
select $output;
}
print $genout;
select STDOUT;
close $output if ($output);
if ($init) {
if ( @names == 0 || $disabled ) {
exec "$init stop";
}
else {
if ($dm_before) {
system("/etc/init.d/dnsmasq stop >/dev/null 2>&1");
}
exec "$init restart";
}
}
sub doCheckIfAddressPLInsideNetwork {
my ( $address, $naipNetwork ) = @_;
if ( !defined($address) || !defined($naipNetwork) ) {
return 0;
}
my $naipSM = new NetAddr::IP($address);
if ( defined($naipSM) ) {
my $subnetIA = $naipSM->network()->addr();
my $naipIA = new NetAddr::IP( $subnetIA, $naipSM->masklen() );
if ( defined($naipIA) && $naipNetwork->within($naipIA) ) {
return 1;
}
}
return 0;
}
sub converttohex {
my ($ipv4_address) = @_;
my $dot_char = ".";
$ipv4_address .= $dot_char;
my @dot_indices;
my @decimal_numbers;
my $hex_string;
for my $i ( 0 .. 3 ) {
$dot_indices[$i] = index( $ipv4_address, $dot_char );
$decimal_numbers[$i] = substr( $ipv4_address, 0, $dot_indices[$i] );
$ipv4_address = substr( $ipv4_address, $dot_indices[$i] + 1 );
$hex_string .= sprintf( "%02x", $decimal_numbers[$i] );
if ( $i != 3 ) {
$hex_string .= ":";
}
}
return $hex_string;
}
sub prefix_and_subnet {
my ( $prefix, $subnet ) = @_;
my $hex_prefix .= sprintf( "%02x", $prefix );
my $prefix_subnet_string = "";
if ( $prefix == 0 ) {
# do nothing as this needs to be an empty string
}
elsif ( ( $prefix >= 1 ) && ( $prefix <= 8 ) ) {
$prefix_subnet_string = $hex_prefix . ":" . substr( $subnet, 0, 3 );
}
elsif ( ( $prefix >= 9 ) && ( $prefix <= 16 ) ) {
$prefix_subnet_string = $hex_prefix . ":" . substr( $subnet, 0, 6 );
}
elsif ( ( $prefix >= 17 ) && ( $prefix <= 24 ) ) {
$prefix_subnet_string = $hex_prefix . ":" . substr( $subnet, 0, 9 );
}
elsif ( ( $prefix >= 25 ) && ( $prefix <= 32 ) ) {
$prefix_subnet_string = $hex_prefix . ":" . $subnet . ":";
}
return $prefix_subnet_string;
}
# split_ranges()is used for splitting dynamic IP ranges before writing them to dhcpd.conf
# it is either called when one or more IP addresses have been excluded from a subnet or
# when a static-mapping is given an IP that exists in one of the defined start-stop ranges
# when called to split ranges from static-mappings part of code, variable $split_for_static_ip
# is set to 1 so that code exclusive to excluding IP addresses is not executed in function
# note: when $split_for_static_ip!=0 it also serves to calculate indices of
# passed arrays else $exclude_ips_count is used for the same purpose
sub split_ranges {
my (@all_ips) = @_;
my $all_ips_count = scalar(@all_ips);
my $exclude_ips_index;
if ( $split_for_static_ip == 0 ) {
$exclude_ips_index = $all_ips_count - $exclude_ips_count;
}
else {
$exclude_ips_index = $all_ips_count - $split_for_static_ip;
}
my $stop_ips_index = $exclude_ips_index / 2;
my @start_ips;
my @stop_ips;
my @exclude_ips;
my $stop_count;
my $exclude_count;
my $exclude_not_in_ranges;
for my $temp_count ( 0 .. ( $stop_ips_index - 1 ) ) {
$start_ips[$temp_count] = $all_ips[$temp_count];
}
for my $temp_count ( $stop_ips_index .. ( $exclude_ips_index - 1 ) ) {
$stop_ips[$stop_count] = $all_ips[$temp_count];
$stop_count = $stop_count + 1;
}
for my $temp_count ( $exclude_ips_index .. ( $all_ips_count - 1 ) ) {
$exclude_ips[$exclude_count] = $all_ips[$temp_count];
$exclude_count = $exclude_count + 1;
}
@exclude_ips = sort(@exclude_ips);
for my $excludeip (@exclude_ips) {
$exclude_not_in_ranges = 1;
my $naipexcludeip = new NetAddr::IP($excludeip);
my $range_count = scalar(@start_ips) - 1;
my @zero_to_ranges = ( 0 .. $range_count );
for my $count (@zero_to_ranges) {
my $naipstartip = new NetAddr::IP( $start_ips[$count] );
my $naipstopip = new NetAddr::IP( $stop_ips[$count] );
if ( ( $naipstartip <= $naipexcludeip )
&& ( $naipexcludeip <= $naipstopip ) )
{
$exclude_not_in_ranges = 0;
if ( $naipstartip == $naipexcludeip ) {
my $new_naipstartip =
new NetAddr::IP( $start_ips[$count], '0.0.0.0' ) + 1;
# need to add prefix '/0' in the above statement as default for ip addresses: '/32'
# does not work in constant addition operator this should work correctly as we
# already make sure the start, stop and exclude ips are within the subnet
$start_ips[$count] = $new_naipstartip->addr();
}
elsif ( $naipexcludeip == $naipstopip ) {
my $new_naipstopip =
new NetAddr::IP( $stop_ips[$count], '0.0.0.0' ) - 1;
$stop_ips[$count] = $new_naipstopip->addr();
}
else {
my $naipsplit_stop =
new NetAddr::IP( $excludeip, '0.0.0.0' ) - 1;
my $naipsplit_start =
new NetAddr::IP( $excludeip, '0.0.0.0' ) + 1;
$stop_ips[$count] = $naipsplit_stop->addr();
$start_ips[ $range_count + 1 ] = $naipsplit_start->addr();
$stop_ips[ $range_count + 1 ] = $naipstopip->addr();
}
}
}
if ( $exclude_not_in_ranges == 1 && $split_for_static_ip == 0 ) {
print STDOUT <<"EOM";
DHCP server warning:
exclude IP address '$excludeip' does not lie in any of the start-stop ranges
EOM
}
}
# this is done to eliminate incoorect ranges where start ip > stop ip after splitting
# this would only happen when you're excluding an ip where
# start ip = stop ip = exclude ip i.e. only 1 ip in range or
# you're excluding stop ip and the ip before the stop ip has also been excluded
my $new_range_count = scalar(@start_ips) - 1;
my @new_ranges = ( 0 .. $new_range_count );
my $tempcount = 0;
my @new_start_ips;
my @new_stop_ips;
foreach my $rangecount (@new_ranges) {
if (
!(
new NetAddr::IP( $start_ips[$rangecount] ) >
new NetAddr::IP( $stop_ips[$rangecount] )
)
)
{
$new_start_ips[$tempcount] = $start_ips[$rangecount];
$new_stop_ips[$tempcount] = $stop_ips[$rangecount];
$tempcount++;
}
}
if ( $split_for_static_ip == 0 ) {
if ( @new_start_ips == 0 ) {
print STDOUT <<"EOM";
DHCP server error:
Cannot exclude all IP addresses defined in start-stop ranges for a subnet
EOM
exit 1;
}
}
return ( \@new_start_ips, \@new_stop_ips );
}

File Metadata

Mime Type
text/x-perl
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
1d/8e/83e2a1b78725aeb88da48e4b2797
Default Alt Text
dhcpd-config.pl (57 KB)

Event Timeline