]> git.proxmox.com Git - pve-network.git/commitdiff
add DNS plugin
authorAlexandre Derumier <aderumier@odiso.com>
Mon, 5 Oct 2020 15:08:52 +0000 (17:08 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 6 Oct 2020 16:12:38 +0000 (18:12 +0200)
Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
12 files changed:
PVE/API2/Network/SDN.pm
PVE/API2/Network/SDN/Dns.pm [new file with mode: 0644]
PVE/API2/Network/SDN/Makefile
PVE/Network/SDN/Dns.pm [new file with mode: 0644]
PVE/Network/SDN/Dns/Makefile [new file with mode: 0644]
PVE/Network/SDN/Dns/Plugin.pm [new file with mode: 0644]
PVE/Network/SDN/Dns/PowerdnsPlugin.pm [new file with mode: 0644]
PVE/Network/SDN/Ipams/Plugin.pm
PVE/Network/SDN/Makefile
PVE/Network/SDN/SubnetPlugin.pm
PVE/Network/SDN/Subnets.pm
PVE/Network/SDN/Vnets.pm

index 736204f76a943342a2c475499fc01cdfbfa4e085..4aef654e498670f0de3a8a11a5b6f378b3a7fd38 100644 (file)
@@ -16,6 +16,7 @@ use PVE::API2::Network::SDN::Vnets;
 use PVE::API2::Network::SDN::Zones;
 use PVE::API2::Network::SDN::Subnets;
 use PVE::API2::Network::SDN::Ipams;
+use PVE::API2::Network::SDN::Dns;
 
 use base qw(PVE::RESTHandler);
 
@@ -44,6 +45,11 @@ __PACKAGE__->register_method ({
     path => 'ipams',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Network::SDN::Dns",
+    path => 'dns',
+});
+
 __PACKAGE__->register_method({
     name => 'index',
     path => '',
@@ -75,6 +81,7 @@ __PACKAGE__->register_method({
            { id => 'controllers' },
            { id => 'subnets' },
            { id => 'ipams' },
+           { id => 'dns' },
        ];
 
        return $res;
diff --git a/PVE/API2/Network/SDN/Dns.pm b/PVE/API2/Network/SDN/Dns.pm
new file mode 100644 (file)
index 0000000..3d08552
--- /dev/null
@@ -0,0 +1,242 @@
+package PVE::API2::Network::SDN::Dns;
+
+use strict;
+use warnings;
+
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+use PVE::Cluster qw(cfs_read_file cfs_write_file);
+use PVE::Network::SDN;
+use PVE::Network::SDN::Dns;
+use PVE::Network::SDN::Dns::Plugin;
+use PVE::Network::SDN::Dns::PowerdnsPlugin;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $sdn_dns_type_enum = PVE::Network::SDN::Dns::Plugin->lookup_types();
+
+my $api_sdn_dns_config = sub {
+    my ($cfg, $id) = @_;
+
+    my $scfg = dclone(PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id));
+    $scfg->{dns} = $id;
+    $scfg->{digest} = $cfg->{digest};
+
+    return $scfg;
+};
+
+__PACKAGE__->register_method ({
+    name => 'index',
+    path => '',
+    method => 'GET',
+    description => "SDN dns index.",
+    permissions => {
+       description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/dns/<dns>'",
+       user => 'all',
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           type => {
+               description => "Only list sdn dns of specific type",
+               type => 'string',
+               enum => $sdn_dns_type_enum,
+               optional => 1,
+           },
+       },
+    },
+    returns => {
+       type => 'array',
+       items => {
+           type => "object",
+           properties => { dns => { type => 'string'},
+                           type => { type => 'string'},
+                         },
+       },
+       links => [ { rel => 'child', href => "{dns}" } ],
+    },
+    code => sub {
+       my ($param) = @_;
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $authuser = $rpcenv->get_user();
+
+
+       my $cfg = PVE::Network::SDN::Dns::config();
+
+       my @sids = PVE::Network::SDN::Dns::sdn_dns_ids($cfg);
+       my $res = [];
+       foreach my $id (@sids) {
+           my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+           next if !$rpcenv->check_any($authuser, "/sdn/dns/$id", $privs, 1);
+
+           my $scfg = &$api_sdn_dns_config($cfg, $id);
+           next if $param->{type} && $param->{type} ne $scfg->{type};
+
+           my $plugin_config = $cfg->{ids}->{$id};
+           my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+           push @$res, $scfg;
+       }
+
+       return $res;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'read',
+    path => '{dns}',
+    method => 'GET',
+    description => "Read sdn dns configuration.",
+    permissions => {
+       check => ['perm', '/sdn/dns/{dns}', ['SDN.Allocate']],
+   },
+
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           dns => get_standard_option('pve-sdn-dns-id'),
+       },
+    },
+    returns => { type => 'object' },
+    code => sub {
+       my ($param) = @_;
+
+       my $cfg = PVE::Network::SDN::Dns::config();
+
+       return &$api_sdn_dns_config($cfg, $param->{dns});
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create',
+    protected => 1,
+    path => '',
+    method => 'POST',
+    description => "Create a new sdn dns object.",
+    permissions => {
+       check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+    },
+    parameters => PVE::Network::SDN::Dns::Plugin->createSchema(),
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $type = extract_param($param, 'type');
+       my $id = extract_param($param, 'dns');
+
+       my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($type);
+       my $opts = $plugin->check_config($id, $param, 1, 1);
+
+        # create /etc/pve/sdn directory
+        PVE::Cluster::check_cfs_quorum();
+        mkdir("/etc/pve/sdn");
+
+        PVE::Network::SDN::lock_sdn_config(
+           sub {
+
+               my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+               my $scfg = undef;
+               if ($scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id, 1)) {
+                   die "sdn dns object ID '$id' already defined\n";
+               }
+
+               $dns_cfg->{ids}->{$id} = $opts;
+
+               my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($opts->{type});
+               $plugin->on_update_hook($opts);
+
+               PVE::Network::SDN::Dns::write_config($dns_cfg);
+
+           }, "create sdn dns object failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'update',
+    protected => 1,
+    path => '{dns}',
+    method => 'PUT',
+    description => "Update sdn dns object configuration.",
+    permissions => {
+       check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+    },
+    parameters => PVE::Network::SDN::Dns::Plugin->updateSchema(),
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $id = extract_param($param, 'dns');
+       my $digest = extract_param($param, 'digest');
+
+        PVE::Network::SDN::lock_sdn_config(
+        sub {
+
+           my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+           PVE::SectionConfig::assert_if_modified($dns_cfg, $digest);
+
+           my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($dns_cfg, $id);
+
+           my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type});
+           my $opts = $plugin->check_config($id, $param, 0, 1);
+
+           foreach my $k (%$opts) {
+               $scfg->{$k} = $opts->{$k};
+           }
+
+           $plugin->on_update_hook($scfg);
+
+           PVE::Network::SDN::Dns::write_config($dns_cfg);
+
+           }, "update sdn dns object failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'delete',
+    protected => 1,
+    path => '{dns}',
+    method => 'DELETE',
+    description => "Delete sdn dns object configuration.",
+    permissions => {
+       check => ['perm', '/sdn/dns', ['SDN.Allocate']],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           dns => get_standard_option('pve-sdn-dns-id', {
+                completion => \&PVE::Network::SDN::Dns::complete_sdn_dns,
+            }),
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $id = extract_param($param, 'dns');
+
+        PVE::Network::SDN::lock_sdn_config(
+           sub {
+
+               my $cfg = PVE::Network::SDN::Dns::config();
+
+               my $scfg = PVE::Network::SDN::Dns::sdn_dns_config($cfg, $id);
+
+               my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($scfg->{type});
+
+               delete $cfg->{ids}->{$id};
+               PVE::Network::SDN::Dns::write_config($cfg);
+
+           }, "delete sdn dns object failed");
+
+       return undef;
+    }});
+
+1;
index 1117dfa587b595d564a82eb0035ab206ad5b0d8f..3683fa4bb9989683515947a89b34059e41d825d3 100644 (file)
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm
+SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/PVE/Network/SDN/Dns.pm b/PVE/Network/SDN/Dns.pm
new file mode 100644 (file)
index 0000000..c2e153a
--- /dev/null
@@ -0,0 +1,57 @@
+package PVE::Network::SDN::Dns;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use JSON;
+
+use PVE::Tools qw(extract_param dir_glob_regex run_command);
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Network;
+
+use PVE::Network::SDN::Dns::PowerdnsPlugin;
+use PVE::Network::SDN::Dns::Plugin;
+
+PVE::Network::SDN::Dns::PowerdnsPlugin->register();
+PVE::Network::SDN::Dns::Plugin->init();
+
+
+sub sdn_dns_config {
+    my ($cfg, $id, $noerr) = @_;
+
+    die "no sdn dns ID specified\n" if !$id;
+
+    my $scfg = $cfg->{ids}->{$id};
+    die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
+
+    return $scfg;
+}
+
+sub config {
+    my $config = cfs_read_file("sdn/dns.cfg");
+    return $config;
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file("sdn/dns.cfg", $cfg);
+}
+
+sub sdn_dns_ids {
+    my ($cfg) = @_;
+
+    return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_dns {
+    my ($cmdname, $pname, $cvalue) = @_;
+
+    my $cfg = PVE::Network::SDN::Dns::config();
+
+    return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Dns::sdn_dns_ids($cfg) ];
+}
+
+1;
+
diff --git a/PVE/Network/SDN/Dns/Makefile b/PVE/Network/SDN/Dns/Makefile
new file mode 100644 (file)
index 0000000..81cd2a1
--- /dev/null
@@ -0,0 +1,8 @@
+SOURCES=Plugin.pm PowerdnsPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+       for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Dns/$$i; done
diff --git a/PVE/Network/SDN/Dns/Plugin.pm b/PVE/Network/SDN/Dns/Plugin.pm
new file mode 100644 (file)
index 0000000..baa9316
--- /dev/null
@@ -0,0 +1,117 @@
+package PVE::Network::SDN::Dns::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(run_command);
+use PVE::JSONSchema;
+use PVE::Cluster;
+use HTTP::Request;
+use LWP::UserAgent;
+use JSON;
+
+use Data::Dumper;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/dns.cfg',
+                                sub { __PACKAGE__->parse_config(@_); },
+                                sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-dns-id', {
+    description => "The SDN dns object identifier.",
+    type => 'string', format => 'pve-sdn-dns-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-dns-id', \&parse_sdn_dns_id);
+sub parse_sdn_dns_id {
+    my ($id, $noerr) = @_;
+
+    if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
+       return undef if $noerr;
+       die "dns ID '$id' contains illegal characters\n";
+    }
+    return $id;
+}
+
+my $defaultData = {
+
+    propertyList => {
+       type => {
+           description => "Plugin type.",
+           type => 'string', format => 'pve-configid',
+       },
+        ttl => { type => 'integer', optional => 1 },
+        dns => get_standard_option('pve-sdn-dns-id',
+            { completion => \&PVE::Network::SDN::Dns::complete_sdn_dns }),
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
+        my ($type, $id) = (lc($1), $2);
+       my $errmsg = undef; # set if you want to skip whole section
+       eval { PVE::JSONSchema::pve_verify_configid($type); };
+       $errmsg = $@ if $@;
+       my $config = {}; # to return additional attributes
+       return ($type, $id, $errmsg, $config);
+    }
+    return undef;
+}
+
+
+sub add_a_record {
+    my ($class, $plugin_config, $type, $zone, $reversezone, $hostname, $ip) = @_;
+}
+
+sub del_a_record {
+    my ($class, $plugin_config, $hostname, $ip) = @_;
+}
+
+sub on_update_hook {
+    my ($class, $plugin_config) = @_;
+}
+
+#helpers
+sub api_request {
+    my ($method, $url, $headers, $data) = @_;
+
+    my $encoded_data = to_json($data) if $data;
+
+    my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+
+    my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 30);
+    my $proxy = undef;
+
+    if ($proxy) {
+        $ua->proxy(['http', 'https'], $proxy);
+    } else {
+        $ua->env_proxy;
+    }
+
+    $ua->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
+
+    my $response = $ua->request($req);
+    my $code = $response->code;
+
+    if ($code !~ /^2(\d+)$/) {
+        my $msg = $response->message || 'unknown';
+        die "Invalid response from server: $code $msg\n";
+    }
+
+    my $raw = '';
+    if (defined($response->decoded_content)) {
+       $raw = $response->decoded_content;
+    } else {
+       $raw = $response->content;
+    }
+    return from_json($raw) if $raw ne '';
+}
+
+1;
diff --git a/PVE/Network/SDN/Dns/PowerdnsPlugin.pm b/PVE/Network/SDN/Dns/PowerdnsPlugin.pm
new file mode 100644 (file)
index 0000000..f02c2f1
--- /dev/null
@@ -0,0 +1,201 @@
+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 base('PVE::Network::SDN::Dns::Plugin');
+
+sub type {
+    return 'powerdns';
+}
+
+sub properties {
+    return {
+       url => {
+           type => 'string',
+       },
+       key => {
+           type => 'string',
+       },
+    };
+}
+
+sub options {
+
+    return {
+        url => { optional => 0},
+        key => { optional => 0 },
+        ttl => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+
+sub add_a_record {
+    my ($class, $plugin_config, $zone, $hostname, $ip) = @_;
+
+    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 $record = { content => $ip, 
+                   disabled => JSON::false, 
+                  name => $fqdn, 
+                   type => $type, 
+                   priority => 0 };
+
+    my $rrset = { name => $fqdn, 
+                 type => $type, 
+                   ttl =>  $ttl, 
+                 changetype => "REPLACE",
+                 records => [ $record ] };
+
+
+    my $params = { rrsets => [ $rrset ] };
+
+    eval {
+       PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+    };
+
+    if ($@) {
+       die "error add $fqdn to zone $zone: $@";
+    }
+}
+
+sub add_ptr_record {
+    my ($class, $plugin_config, $zone, $hostname, $ip) = @_;
+
+    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 = join(".", reverse(split(/\./, $ip))).".in-addr.arpa.";
+    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::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+    };
+
+    if ($@) {
+       die "error add $reverseip to zone $zone: $@";
+    }
+}
+
+sub del_a_record {
+    my ($class, $plugin_config, $zone, $hostname, $ip) = @_;
+
+    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 $rrset = { name => $fqdn, 
+                 type => $type, 
+                 changetype => "DELETE",
+                 records => [] };
+
+    my $params = { rrsets => [ $rrset ] };
+
+    eval {
+       PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+    };
+
+    if ($@) {
+       die "error delete $fqdn from zone $zone: $@";
+    }
+}
+
+sub del_ptr_record {
+    my ($class, $plugin_config, $zone, $ip) = @_;
+
+    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 = join(".", reverse(split(/\./, $ip))).".in-addr.arpa.";
+    my $type = "PTR";
+
+    my $rrset = { name => $reverseip, 
+                 type => $type, 
+                 changetype => "DELETE",
+                 records => [] };
+
+    my $params = { rrsets => [ $rrset ] };
+
+    eval {
+       PVE::Network::SDN::Dns::Plugin::api_request("PATCH", "$url/zones/$zone", $headers, $params);
+    };
+
+    if ($@) {
+       die "error delete $reverseip from zone $zone: $@";
+    }
+}
+
+sub verify_zone {
+    my ($class, $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];
+
+    eval {
+        PVE::Network::SDN::Dns::Plugin::api_request("GET", "$url/zones/$zone", $headers);
+    };
+
+    if ($@) {
+        die "can't read zone $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::Dns::Plugin::api_request("GET", "$url", $headers);
+    };
+
+    if ($@) {
+       die "dns api error: $@";
+    }
+}
+
+1;
+
+
index e31fb057518f2c80c0133a23ca5ed06d13b3f941..9330ba92993375b14ef5c9753f49b0eaf8de2d75 100644 (file)
@@ -114,7 +114,7 @@ sub api_request {
     my $response = $ua->request($req);
     my $code = $response->code;
 
-    if ($code !~ /2(\d+)$/) {
+    if ($code !~ /^2(\d+)$/) {
         my $msg = $response->message || 'unknown';
         die "Invalid response from server: $code $msg\n";
     }
index fb6885608e6a11142ab830b7b7ff3a63e31ada91..92cfcd0e80264834f1a052a70358b96f574ca521 100644 (file)
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm
+SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
@@ -9,4 +9,5 @@ install:
        make -C Controllers install
        make -C Zones install
        make -C Ipams install
+       make -C Dns install
 
index 6224065767062d9c52da87623dd7ca41a528ee90..32a9d78259da267a9b122939e5009c7ef2f91746 100644 (file)
@@ -65,22 +65,25 @@ sub properties {
             type => 'string',
             description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
         },
-       #cloudinit, dhcp options
-        nameservers => {
-            type => 'string', format => 'address-list',
-            description => " dns nameserver",
+        dns => {
+            type => 'string',
+            description => "dns api server",
         },
-       #cloudinit, dhcp options
-        searchdomain => {
+        reversedns => {
             type => 'string',
+            description => "reverse dns api server",
         },
-        dhcp => {
-            type => 'boolean',
-            description => "enable dhcp for this subnet",
+        dnszone => {
+            type => 'string',
+            description => "dns domain zone  ex: mydomain.com",
         },
-        dns_driver => {
+        reversednszone => {
             type => 'string',
-            description => "Develop some dns registrations plugins (powerdns,...)",
+            description => "reverse dns zone ex: 0.168.192.in-addr.arpa",
+        },
+        dnszoneprefix => {
+            type => 'string',
+            description => "dns domain zone prefix  ex: 'adm' -> <hostname>.adm.mydomain.com",
         },
         ipam => {
             type => 'string',
@@ -93,11 +96,12 @@ sub options {
     return {
        gateway => { optional => 1 },
        routes => { optional => 1 },
-       nameservers => { optional => 1 },
-       searchdomain => { optional => 1 },
        snat => { optional => 1 },
-       dhcp => { optional => 1 },
-       dns_driver => { optional => 1 },
+       dns => { optional => 1 },
+       reversedns => { optional => 1 },
+       dnszone => { optional => 1 },
+       reversednszone => { optional => 1 },
+       dnszoneprefix => { optional => 1 },
        ipam => { optional => 1 },
     };
 }
@@ -105,12 +109,26 @@ sub options {
 sub on_update_hook {
     my ($class, $subnetid, $subnet_cfg) = @_;
 
-    my $subnet = $subnetid =~ s/-/\//r;
-    my $subnet_matcher = subnet_matcher($subnet);
+    my $cidr = $subnetid =~ s/-/\//r;
+    my $subnet_matcher = subnet_matcher($cidr);
+
+    my $subnet = $subnet_cfg->{ids}->{$subnetid};
 
-    my $gateway = $subnet_cfg->{ids}->{$subnetid}->{gateway};
+    my $gateway = $subnet->{gateway};
+    my $dns = $subnet->{dns};
+    my $dnszone = $subnet->{dnszone};
+    my $reversedns = $subnet->{reversedns};
+    my $reversednszone = $subnet->{reversednszone};
+
+    #to: for /32 pointotoping, allow gateway outside the subnet
     raise_param_exc({ gateway => "$gateway is not in subnet $subnet"}) if $gateway && !$subnet_matcher->($gateway);
 
+    raise_param_exc({ dns => "missing dns provider"}) if $dnszone && !$dns;
+    raise_param_exc({ dnszone => "missing dns zone"}) if $dns && !$dnszone;
+    raise_param_exc({ reversedns => "missing dns provider"}) if $reversednszone && !$reversedns;
+    raise_param_exc({ reversednszone => "missing dns zone"}) if $reversedns && !$reversednszone;
+    raise_param_exc({ reversedns => "missing forward dns zone"}) if $reversednszone && !$dnszone;
+
 }
 
 sub on_delete_hook {
index 3ce2d44ab24653cb2c27b17f801f21b55dd220d8..07ef68868a78c01ba0e40af11a86faffd61aa1d0 100644 (file)
@@ -5,8 +5,10 @@ use warnings;
 
 use Net::Subnet qw(subnet_matcher);
 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use Net::IP;
 
 use PVE::Network::SDN::Ipams;
+use PVE::Network::SDN::Dns;
 use PVE::Network::SDN::SubnetPlugin;
 PVE::Network::SDN::SubnetPlugin->register();
 PVE::Network::SDN::SubnetPlugin->init();
@@ -75,41 +77,173 @@ sub find_ip_subnet {
     return ($subnetid, $subnet);
 }
 
+my $verify_dns_zone = sub {
+    my ($zone, $dns) = @_;
+
+    return if !$zone || !$dns;
+
+    my $dns_cfg = PVE::Network::SDN::Dns::config();
+    my $plugin_config = $dns_cfg->{ids}->{$dns};
+    my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+    $plugin->verify_zone($plugin_config, $zone);
+};
+
+my $add_dns_record = sub {
+    my ($zone, $dns, $hostname, $dnszoneprefix, $ip) = @_;
+    return if !$zone || !$dns || !$hostname || !$ip;
+
+    $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+    my $dns_cfg = PVE::Network::SDN::Dns::config();
+    my $plugin_config = $dns_cfg->{ids}->{$dns};
+    my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+    $plugin->add_a_record($plugin_config, $zone, $hostname, $ip);
+
+};
+
+my $add_dns_ptr_record = sub {
+    my ($reversezone, $zone, $dns, $hostname, $dnszoneprefix, $ip) = @_;
+
+    return if !$zone || !$reversezone || !$dns || !$hostname || !$ip;
+
+    $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+    $hostname .= ".$zone";
+    my $dns_cfg = PVE::Network::SDN::Dns::config();
+    my $plugin_config = $dns_cfg->{ids}->{$dns};
+    my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+    $plugin->add_ptr_record($plugin_config, $reversezone, $hostname, $ip);
+};
+
+my $del_dns_record = sub {
+    my ($zone, $dns, $hostname, $dnszoneprefix, $ip) = @_;
+
+    return if !$zone || !$dns || !$hostname || !$ip;
+
+    $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+    my $dns_cfg = PVE::Network::SDN::Dns::config();
+    my $plugin_config = $dns_cfg->{ids}->{$dns};
+    my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+    $plugin->del_a_record($plugin_config, $zone, $hostname, $ip);
+};
+
+my $del_dns_ptr_record = sub {
+    my ($reversezone, $dns, $ip) = @_;
+
+    return if !$reversezone || !$dns || !$ip;
+
+    my $dns_cfg = PVE::Network::SDN::Dns::config();
+    my $plugin_config = $dns_cfg->{ids}->{$dns};
+    my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+    $plugin->del_ptr_record($plugin_config, $reversezone, $ip);
+};
+
 sub next_free_ip {
-    my ($subnetid, $subnet) = @_;
+    my ($subnetid, $subnet, $hostname) = @_;
+
+    my $cidr = undef;
+    my $ip = undef;
 
     my $ipamid = $subnet->{ipam};
-    return if !$ipamid;
+    my $dns = $subnet->{dns};
+    my $dnszone = $subnet->{dnszone};
+    my $reversedns = $subnet->{reversedns};
+    my $reversednszone = $subnet->{reversednszone};
+    my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+    #verify dns zones before ipam
+    &$verify_dns_zone($dnszone, $dns);
+    &$verify_dns_zone($reversednszone, $reversedns);
+
+    if($ipamid) {
+       my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+       my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+       my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+       $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet);
+       ($ip, undef) = split(/\//, $cidr);
+    }
 
-    my $ipam_cfg = PVE::Network::SDN::Ipams::config();
-    my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
-    my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
-    my $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet);
-    return $ip;
+    eval {
+       #add dns
+       &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+       #add reverse dns
+       &$add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $dnszoneprefix, $ip);
+    };
+    if ($@) {
+       #rollback
+       my $err = $@;
+       eval {
+           PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname)
+       };
+       die $err;
+    }
+    return $cidr;
 }
 
 sub add_ip {
-    my ($subnetid, $subnet, $ip) = @_;
+    my ($subnetid, $subnet, $ip, $hostname) = @_;
 
     my $ipamid = $subnet->{ipam};
-    return if !$ipamid;
+    my $dns = $subnet->{dns};
+    my $dnszone = $subnet->{dnszone};
+    my $reversedns = $subnet->{reversedns};
+    my $reversednszone = $subnet->{reversednszone};
+    my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+    #verify dns zones before ipam
+    &$verify_dns_zone($dnszone, $dns);
+    &$verify_dns_zone($reversednszone, $reversedns);
+
+    if ($ipamid) {
+       my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+       my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+       my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+       $plugin->add_ip($plugin_config, $subnetid, $ip);
+    }
 
-    my $ipam_cfg = PVE::Network::SDN::Ipams::config();
-    my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
-    my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
-    $plugin->add_ip($plugin_config, $subnetid, $ip);
+    eval {
+       #add dns
+       &$add_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+       #add reverse dns
+       &$add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $dnszoneprefix, $ip);
+    };
+    if ($@) {
+       #rollback
+       my $err = $@;
+       eval {
+           PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname)
+       };
+       die $err;
+    }
 }
 
 sub del_ip {
-    my ($subnetid, $subnet, $ip) = @_;
+    my ($subnetid, $subnet, $ip, $hostname) = @_;
 
     my $ipamid = $subnet->{ipam};
-    return if !$ipamid;
+    my $dns = $subnet->{dns};
+    my $dnszone = $subnet->{dnszone};
+    my $reversedns = $subnet->{reversedns};
+    my $reversednszone = $subnet->{reversednszone};
+    my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+    &$verify_dns_zone($dnszone, $dns);
+    &$verify_dns_zone($reversednszone, $reversedns);
+
+    if ($ipamid) {
+       my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+       my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+       my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+       $plugin->del_ip($plugin_config, $subnetid, $ip);
+    }
 
-    my $ipam_cfg = PVE::Network::SDN::Ipams::config();
-    my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
-    my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
-    $plugin->del_ip($plugin_config, $subnetid, $ip);
+    eval {
+       &$del_dns_record($dnszone, $dns, $hostname, $dnszoneprefix, $ip);
+       &$del_dns_ptr_record($reversednszone, $reversedns, $ip);
+    };
+    if ($@) {
+       warn $@;
+    }
 }
 
 1;
index 6ea3a9a5cf9c86f7959250da0127283d79cfc118..c9916b1ca3938912b25b7bd29686612b3ace2230 100644 (file)
@@ -55,7 +55,7 @@ sub get_vnet {
 }
 
 sub get_next_free_ip {
-    my ($vnet, $ipversion) = @_;
+    my ($vnet, $hostname, $ipversion) = @_;
 
     $ipversion = 4 if !$ipversion;
     my $subnets_cfg = PVE::Network::SDN::Subnets::config();
@@ -71,7 +71,7 @@ sub get_next_free_ip {
        $subnet = $subnets_cfg->{ids}->{$subnetid};
        if ($subnet && $subnet->{ipam}) {
            eval {
-               $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet);
+               $ip = PVE::Network::SDN::Subnets::next_free_ip($subnetid, $subnet, $hostname);
            };
            warn $@ if $@;
        }
@@ -83,23 +83,23 @@ sub get_next_free_ip {
 }
 
 sub add_ip {
-    my ($vnet, $cidr, $name) = @_;
+    my ($vnet, $cidr, $hostname) = @_;
 
     my ($ip, $mask) = split(/\//, $cidr);
     my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets});
     return if !$subnet->{ipam};
 
-    PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip);
+    PVE::Network::SDN::Subnets::add_ip($subnetid, $subnet, $ip, $hostname);
 }
 
 sub del_ip {
-    my ($vnet, $cidr) = @_;
+    my ($vnet, $cidr, $hostname) = @_;
 
     my ($ip, $mask) = split(/\//, $cidr);
     my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $vnet->{subnets});
     return if !$subnet->{ipam};
 
-    PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip);
+    PVE::Network::SDN::Subnets::del_ip($subnetid, $subnet, $ip, $hostname);
 }
 
 1;