summaryrefslogtreecommitdiff
path: root/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
diff options
context:
space:
mode:
Diffstat (limited to 'src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm')
-rw-r--r--src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm329
1 files changed, 329 insertions, 0 deletions
diff --git a/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
new file mode 100644
index 0000000..096d131
--- /dev/null
+++ b/src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
@@ -0,0 +1,329 @@
+package PVE::Network::SDN::Dns::PowerdnsPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+use JSON;
+use Net::IP;
+use NetAddr::IP qw(:lower);
+use base('PVE::Network::SDN::Dns::Plugin');
+
+sub type {
+ return 'powerdns';
+}
+
+sub properties {
+ return {
+ url => {
+ type => 'string',
+ },
+ key => {
+ type => 'string',
+ },
+ reversemaskv6 => {
+ type => 'integer'
+ },
+ };
+}
+
+sub options {
+
+ return {
+ url => { optional => 0},
+ key => { optional => 0 },
+ ttl => { optional => 1 },
+ reversemaskv6 => { optional => 1, description => "force a different netmask for the ipv6 reverse zone name." },
+
+ };
+}
+
+# Plugin implementation
+
+sub add_a_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400;
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+ my $fqdn = $hostname.".".$zone.".";
+
+ my $zonecontent = get_zone_content($plugin_config, $zone);
+ my $existing_rrset = get_zone_rrset($zonecontent, $fqdn);
+
+ my $final_records = [];
+ my $foundrecord = undef;
+ foreach my $record (@{$existing_rrset->{records}}) {
+ if($record->{content} eq $ip) {
+ $foundrecord = 1;
+ next;
+ }
+ push @$final_records, $record;
+ }
+ return if $foundrecord;
+
+ my $record = { content => $ip,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ push @$final_records, $record;
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => $ttl,
+ changetype => "REPLACE",
+ records => $final_records };
+
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error add $fqdn to zone $zone: $@" if !$noerr;
+ }
+}
+
+sub add_ptr_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $ttl = $plugin_config->{ttl} ? $plugin_config->{ttl} : 14400;
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+ $hostname .= ".";
+
+ my $reverseip = Net::IP->new($ip)->reverse_ip();
+
+ my $type = "PTR";
+
+ my $record = { content => $hostname,
+ disabled => JSON::false,
+ name => $reverseip,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $reverseip,
+ type => $type,
+ ttl => $ttl,
+ changetype => "REPLACE",
+ records => [ $record ] };
+
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error add $reverseip to zone $zone: $@" if !$noerr;
+ }
+}
+
+sub del_a_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+ my $fqdn = $hostname.".".$zone.".";
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+
+ my $zonecontent = get_zone_content($plugin_config, $zone);
+ my $existing_rrset = get_zone_rrset($zonecontent, $fqdn);
+
+ my $final_records = [];
+ my $foundrecord = undef;
+ foreach my $record (@{$existing_rrset->{records}}) {
+ if ($record->{content} eq $ip) {
+ $foundrecord = 1;
+ next;
+ }
+ push @$final_records, $record;
+ }
+ return if !$foundrecord;
+
+ my $rrset = {};
+
+ if (scalar (@{$final_records}) > 0) {
+ #if we still have other records, we rewrite them without removed ip
+ $rrset = { name => $fqdn,
+ type => $type,
+ ttl => $existing_rrset->{ttl},
+ changetype => "REPLACE",
+ records => $final_records };
+
+ } else {
+
+ $rrset = { name => $fqdn,
+ type => $type,
+ changetype => "DELETE",
+ records => [] };
+ }
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error delete $fqdn from zone $zone: $@" if !$noerr;
+ }
+}
+
+sub del_ptr_record {
+ my ($class, $plugin_config, $zone, $ip, $noerr) = @_;
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $reverseip = Net::IP->new($ip)->reverse_ip();
+
+ my $type = "PTR";
+
+ my $rrset = { name => $reverseip,
+ type => $type,
+ changetype => "DELETE",
+ records => [] };
+
+ my $params = { rrsets => [ $rrset ] };
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+ };
+
+ if ($@) {
+ die "error delete $reverseip from zone $zone: $@" if !$noerr;
+ }
+}
+
+sub verify_zone {
+ my ($class, $plugin_config, $zone, $noerr) = @_;
+
+ #verify that api is working
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ eval {
+ PVE::Network::SDN::api_request("GET", "$url/zones/$zone?rrsets=false", $headers);
+ };
+
+ if ($@) {
+ die "can't read zone $zone: $@" if !$noerr;
+ }
+}
+
+sub get_reversedns_zone {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $mask = $subnet->{mask};
+
+ my $zone = "";
+
+ if (Net::IP::ip_is_ipv4($ip)) {
+ my ($ipblock1, $ipblock2, $ipblock3, $ipblock4) = split(/\./, $ip);
+
+ my $ipv4 = NetAddr::IP->new($cidr);
+ #private addresse #powerdns built-in private zone : serve-rfc1918
+ if($ipv4->is_rfc1918()) {
+ if ($ipblock1 == 192) {
+ $zone = "168.192.in-addr.arpa.";
+ } elsif ($ipblock1 == 172) {
+ $zone = "16-31.172.in-addr.arpa.";
+ } elsif ($ipblock1 == 10) {
+ $zone = "10.in-addr.arpa.";
+ }
+
+ } else {
+ #public ipv4 : RIPE,ARIN,AFRNIC
+ #. Delegations can be managed in IPv4 on bit boundaries (/8, /16 or /24s), and IPv6 networks can be managed on nibble boundaries (every 4 bits of the IPv6 address)
+ #One or more /24 type zones need to be created if your address space has a prefix length between /17 and /24.
+ # If your prefix length is between /16 and /9 you will have to request one or more delegations for /16 type zones.
+
+ if ($mask <= 24) {
+ $zone = "$ipblock3.$ipblock2.$ipblock1.in-addr.arpa.";
+ } elsif ($mask <= 16) {
+ $zone = "$ipblock2.$ipblock1.in-addr.arpa.";
+ } elsif ($mask <= 8) {
+ $zone = "$ipblock1.in-addr.arpa.";
+ }
+ }
+ } else {
+ $mask = $plugin_config->{reversemaskv6} if $plugin_config->{reversemaskv6};
+ die "reverse dns zone mask need to be a multiple of 4" if ($mask % 4);
+ my $networkv6 = NetAddr::IP->new($cidr)->network();
+ $zone = Net::IP->new($networkv6)->reverse_ip();
+ }
+
+ return $zone;
+}
+
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+
+ #verify that api is working
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ eval {
+ PVE::Network::SDN::api_request("GET", "$url", $headers);
+ };
+
+ if ($@) {
+ die "dns api error: $@";
+ }
+}
+
+
+sub get_zone_content {
+ my ($plugin_config, $zone) = @_;
+
+ #verify that api is working
+
+ my $url = $plugin_config->{url};
+ my $key = $plugin_config->{key};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'X-API-Key' => $key];
+
+ my $result = undef;
+ eval {
+ $result = PVE::Network::SDN::api_request("GET", "$url/zones/$zone", $headers);
+ };
+
+ if ($@) {
+ die "can't read zone $zone: $@";
+ }
+ return $result;
+}
+
+sub get_zone_rrset {
+ my ($zonecontent, $name) = @_;
+
+ my $rrsetresult = undef;
+ foreach my $rrset (@{$zonecontent->{rrsets}}) {
+ next if $rrset->{name} ne $name;
+ $rrsetresult = $rrset;
+ last;
+ }
+ return $rrsetresult;
+}
+
+1;
+
+