Skip to content

Instantly share code, notes, and snippets.

@Cazzar
Created December 4, 2018 14:04
Show Gist options
  • Save Cazzar/dde916a9c2dc187356c78df02e251c50 to your computer and use it in GitHub Desktop.
Save Cazzar/dde916a9c2dc187356c78df02e251c50 to your computer and use it in GitHub Desktop.
package Vyatta::DhcpPd;
use strict;
use warnings;
use lib "/opt/vyatta/share/perl5";
use Sys::Syslog qw(:standard :macros);
use File::Compare;
our @EXPORT = qw(get_base_dir get_radvd_conffile get_radvd_gen
get_commit_notdone start_dhcpv6_daemon stop_dhcpv6_daemon
radvd_gen_add radvd_gen_del get_dhcp6c_pidfile
get_dhcp6c_conffile restart_radvd stop_radvd sysctl_set_ra
start_pd_daemon sighup_pd_daemon stop_pd_daemon
find_ipv6_addr pd_write_file validate_sla_id validate_ifid
pd_clear_intf_resolv_conf pd_add_to_resolv_conf
pd_update_resolv_conf);
use base qw(Exporter);
my $basedir = '/var/run';
my $radvd_gen = '/opt/vyatta/sbin/vyatta_gen_radvd.pl';
my $pd_daemon = '/usr/sbin/dhcp6c';
my $pd_logfile = '/var/log/dhcp6c.log';
sub get_base_dir {
return $basedir;
}
sub get_dhcp6s_pidfile {
my $intf = shift;
return "$basedir/dhcpv6-$intf-pd.pid";
}
sub get_dhcp6s_conffile {
my $intf = shift;
return "$basedir/dhcpv6-$intf-pd.conf";
}
sub get_dhcp6s_leasefile {
my $intf = shift;
my $file = "$basedir/dhcpv6-$intf-pd.leases";
system("touch $file");
return $file;
}
sub get_dhcp6c_pidfile {
my $intf = shift;
return "$basedir/dhcp6c-$intf-pd.pid";
}
sub get_dhcp6c_conffile {
my $intf = shift;
return "$basedir/dhcp6c-$intf-pd.conf";
}
sub get_radvd_conffile {
return '/etc/radvd.conf';
}
sub is_running {
my ($pidfile) = @_;
if (-f $pidfile) {
my $pid = `cat $pidfile`;
$pid =~ s/\s+$//; # chomp doesn't remove nl
my $ps = `ps -p $pid -o comm=`;
if (defined($ps) && $ps ne "") {
return 1;
}
}
return 0;
}
sub find_ipv6_addr {
my $intf = shift;
my $line = `ip -6 addr sh dev $intf | grep global`;
if (defined $line and $line ne '') {
my ($junk, $proto, $addr) = split(/\s+/, $line);
my $ip = new6 NetAddr::IP($addr);
next if ! defined $ip;
my $net = $ip->network();
return $net;
}
return;
}
sub get_commit_notdone {
return '/opt/vyatta/config/.commit';
}
sub is_same_as_file {
my ($file, $value) = @_;
return if ! -e $file;
my $mem_file = '';
open my $MF, '+<', \$mem_file or die "couldn't open memfile $!\n";
print $MF $value;
seek($MF, 0, 0);
my $rc = compare($file, $MF);
if ($rc == 0) {
system("echo $file same >> /tmp/rad.log");
} else {
system("echo $file differ >> /tmp/rad.log");
}
return 1 if $rc == 0;
return;
}
sub pd_write_file {
my ($file, $data) = @_;
return 'same' if is_same_as_file($file, $data);
my $fh;
unless (open($fh, '>', $file)) {
syslog(LOG_ERR, "Couldn't open $file - $!");
exit 1;
}
print $fh $data;
close $fh;
}
sub sysctl_set_ra {
my ($intf, $val) = @_;
$intf =~ s/\./\//;
my $sysctl = 'net.ipv6.conf.' . $intf . ".accept_ra=$val";
system("sysctl -w $sysctl >/dev/null 2>&1");
}
sub start_dhcpv6_daemon {
my ($intf, $output) = @_;
my $pidfile = get_dhcp6s_pidfile($intf);
my $conffile = get_dhcp6s_conffile($intf);
my $leasefile = get_dhcp6s_leasefile($intf);
my $cmd;
# Start daemon if it is not running.
# Restart daemon if configuration changed.
if (pd_write_file($conffile, $output) eq 'same') {
return if is_running($pidfile);
} else {
if (is_running($pidfile)) {
my $pid = `cat $pidfile`;
system("kill $pid");
unlink($pidfile);
}
}
$cmd = "/usr/sbin/dhcpd3 -6 -d -pf $pidfile -cf $conffile -lf $leasefile";
system("$cmd 2> /dev/null &");
}
sub stop_dhcpv6_daemon {
my $intf = shift;
my $pidfile = get_dhcp6s_pidfile($intf);
my $conffile = get_dhcp6s_conffile($intf);
return if ! -e $pidfile;
my $pid = `cat $pidfile`;
return if ! defined $pid or $pid eq '';
system("kill $pid");
unlink($pidfile);
unlink($conffile);
}
sub radvd_gen_add {
my ($intf, $opt) = @_;
my $cmd = "sudo $radvd_gen --generate-pd $intf $opt";
system($cmd);
}
sub radvd_gen_del {
my ($intf) = @_;
my $cmd = "sudo $radvd_gen --delete-pd $intf";
system($cmd);
}
sub restart_radvd {
my $cmd = "sudo /etc/init.d/radvd stop > /tmp/junk";
$cmd .= "; sudo /etc/init.d/radvd start > /tmp/junk";
system($cmd);
}
sub stop_radvd {
my $cmd = "sudo /etc/init.d/radvd stop";
system("$cmd > /dev/null");
}
sub start_pd_daemon {
my $ifname = shift;
unlink($pd_logfile);
printf("Starting new daemon...\n");
my $pd_pidfile = get_dhcp6c_pidfile($ifname);
my $pd_conffile = get_dhcp6c_conffile($ifname);
my $cmd = "$pd_daemon -c $pd_conffile -p $pd_pidfile -df $ifname";
system("$cmd 2> $pd_logfile &");
}
sub stop_pd_daemon {
my $ifname = shift;
my $pd_pidfile = get_dhcp6c_pidfile($ifname);
return if ! -e $pd_pidfile;
printf("Stopping daemon...\n");
system('/sbin/start-stop-daemon --stop --quiet '
. '--retry TERM/20/forever/KILL/1 '
. "--pidfile $pd_pidfile --oknodo");
}
sub sighup_pd_daemon {
my $ifname = shift;
my $pd_pidfile = get_dhcp6c_pidfile($ifname);
return if ! -e $pd_pidfile;
my $pid = `cat $pd_pidfile`; chomp $pid;
return if ! defined $pid or $pid eq '';
printf("Signal daemon restart...\n");
system ("kill -SIGHUP $pid");
}
# prefix-id :1
sub validate_sla_id {
my ($sla_len, $sla_id) = @_;
$sla_id =~ s/^[:]+//;
my $ip = new6 NetAddr::IP("::$sla_id");
if (!defined $ip) {
die "Invalid IPv6 ID\n";
}
my $dec_sla_id = $ip->numeric();
if (!defined $dec_sla_id) {
die "Unable to convert prefix-id\n";
}
my $size = (2 ** $sla_len) - 1;
if ($dec_sla_id > $size) {
my $prefix = 64 - $sla_len;
my $hex_size = sprintf("%x", $size);
$ip = new6 NetAddr::IP("$size");
if (!defined $ip) {
die "failed to convert $size\n";
}
my $limit = $ip->short();
die "prefix-id must be less than $limit for prefix /$prefix\n";
}
return $dec_sla_id;
}
# host-address ::1
sub validate_ifid {
my ($ifid) = @_;
$ifid =~ s/^[:]+//;
my $ip = new6 NetAddr::IP("::$ifid");
if (!defined $ip) {
die "Invalid IPv6 host address\n";
}
my $dec_sla_id = $ip->numeric();
if (!defined $dec_sla_id) {
die "Unable to convert IPv6 host address\n";
}
$ip = new6 NetAddr::IP("::ffff:ffff:ffff:ffff");
my $max = $ip->numeric();
if ($dec_sla_id > $max) {
my $host_addr_max = $ip->short();
die "host-address must be less than $host_addr_max\n";
}
return $dec_sla_id;
}
#
# Clear contents from /var/run/pd-resolv-$ifname.conf.
#
sub pd_clear_intf_resolv_conf {
my ($ifname) = @_;
pd_add_to_resolv_conf($ifname, 0, 0, "", "");
}
#
# Generate /var/run/pd-resolv-$ifname.conf based on:
# * $nameservers - string of name servers separated with a space character;
# * $domain - domain to be added to 'search';
# * $sys_domain_set - boolean, indicates if "system domain-name" is set;
# * $nodns_set - boolean, indicates if "set interfaces ethernet eth4 dhcpv6-pd no-dns" is set.
#
# The file is empty if $nodns_set is true. 'search' is not added if $sys_domain_set is true.
#
sub pd_add_to_resolv_conf {
my ($ifname, $nodns_set, $sys_domain_set, $domain, $nameservers) = @_;
my @ns_arr = split / /, $nameservers;
my $intf_resolv_conf="/var/run/pd-resolv-$ifname.conf";
# Reset the PD interface specific resolv.conf.
`echo -n > $intf_resolv_conf`;
if (!$nodns_set) {
# Add received configuration to PD interface specific resolv.conf.
my $comment="# written by /opt/vyatta/share/perl5/Vyatta/DhcpPd.pm";
foreach my $ns (@ns_arr) {
`echo -e "nameserver $ns\t\t$comment" >> $intf_resolv_conf`;
}
if (!$sys_domain_set) {
if ($domain ne "") {
`echo -e "search $domain" >> $intf_resolv_conf`;
}
}
}
pd_update_resolv_conf("/etc/resolv.conf");
}
#
# Update /etc/resolv.conf with the latest data received via PD:
# * add new domains sorted in search order to the 'search' entry but do not remove anything;
# * add 'nameserver' entries at the end so that IPv4 nameservers are preferred.
#
sub pd_update_resolv_conf {
my $base_resolv_conf=shift;
my $tmp_resolv_conf="/tmp/resolv.conf.$$.tmp";
my $main_resolv_conf="/etc/resolv.conf";
my $search = "";
# Reset the working copy of resolv.conf.
`echo -n > $tmp_resolv_conf`;
# Add lines from base resolv.conf that were not generated by this module.
`cat $base_resolv_conf | grep -v /opt/vyatta/share/perl5/Vyatta/DhcpPd.pm >> $tmp_resolv_conf`;
# Prepare the updated 'search' line with domains from PD interfaces.
# Note: comments at the end of the 'search' line are not handled, do not add them!
my @doms;
if (`ls /var/run | grep -e 'pd-resolv-.*\.conf'`) {
@doms = `cat $base_resolv_conf /var/run/pd-resolv-*.conf | awk '/^search/ { for (i = 2; i <= NF; i++) print \$i }' | sort | uniq`;
} else {
@doms = `cat $base_resolv_conf | awk '/^search/ { for (i = 2; i <= NF; i++) print \$i }' | sort | uniq`;
}
if (scalar @doms > 0) {
# Sort the domains in search-order.
@doms = map { join ".", reverse split /\./ }
sort
map { join ".", reverse split /\./ }
@doms;
$search = "search ";
foreach my $dom (@doms) {
chomp $dom;
$search .= " " . $dom;
}
# Add or replace 'search' line.
if (!`cat $tmp_resolv_conf | grep -e '^domain'`) {
if (`cat $tmp_resolv_conf | grep -e '^search'`) {
`sed -i 's/^search.*/$search/' $tmp_resolv_conf`;
} else {
`echo -e '$search' >> $tmp_resolv_conf`;
}
}
}
# Add 'nameserver' lines from all PD interfaces to working copy of resolv.conf.
if (`ls /var/run | grep -e 'pd-resolv-.*\.conf'`) {
`cat /var/run/pd-resolv-*.conf | grep -e "^nameserver" | sort | uniq >> $tmp_resolv_conf`;
}
# Replace /etc/resolv.conf with the new one.
if (`cmp $tmp_resolv_conf $main_resolv_conf 2>&1` ne "") {
`cp $tmp_resolv_conf $main_resolv_conf`;
}
`rm $tmp_resolv_conf`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment