diff options
| author | Thomas Lamprecht <t.lamprecht@proxmox.com> | 2023-05-25 18:10:14 +0200 |
|---|---|---|
| committer | Thomas Lamprecht <t.lamprecht@proxmox.com> | 2023-05-25 18:18:57 +0200 |
| commit | 6029cbb071c3722c717eebbafaf1b373f3edaadc (patch) | |
| tree | 456d7aff44d2ae220d1671f77da7528174d53fe6 /src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm | |
| parent | cead0f28af4aceee83af6636d4f5ffb2d2f6c6b1 (diff) | |
separate packaging and source build system
like almost all of our repos do nowadays, modern git can detect such
things on rebase so in development stuff should be hopefully not too
much affected by this.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Diffstat (limited to 'src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm')
| -rw-r--r-- | src/PVE/Network/SDN/Dns/PowerdnsPlugin.pm | 329 |
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; + + |
