DEB=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION)_all.deb
DSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc
-all:
- $(MAKE) -C PVE
-
.PHONY: dinstall
dinstall: deb
dpkg -i $(DEB)
-$(BUILDDIR): PVE debian
- rm -rf $(BUILDDIR)
- rsync -a * $(BUILDDIR)
- echo "git clone git://git.proxmox.com/git/pve-network.git\\ngit checkout $(shell git rev-parse HEAD)" > $(BUILDDIR)/debian/SOURCE
+$(BUILDDIR): src debian
+ rm -rf $@ $@.tmp
+ cp -a src $@.tmp
+ cp -a debian $@.tmp/
+ echo "git clone git://git.proxmox.com/git/pve-network.git\\ngit checkout $(shell git rev-parse HEAD)" > $@.tmp/debian/SOURCE
+ mv $@.tmp $@
.PHONY: deb
deb: $(DEB)
clean:
rm -rf *~ *.deb *.changes $(PACKAGE)-[0-9]*/ $(PACKAGE)*.tar* *.build *.buildinfo *.dsc
-.PHONY: test
-test:
- $(MAKE) -C test
-
-.PHONY: install
-install:
- $(MAKE) -C PVE install
-
.PHONY: upload
upload: $(DEB)
tar cf - $(DEB)|ssh -X repoman@repo.proxmox.com -- upload --product pve --dist bullseye
+++ /dev/null
-
-.PHONY: install
-install:
- make -C Network install
+++ /dev/null
-SOURCES=SDN.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/$$i; done
- make -C SDN install
+++ /dev/null
-package PVE::API2::Network::SDN;
-
-use strict;
-use warnings;
-
-use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
-use PVE::Exception qw(raise_param_exc);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RESTHandler;
-use PVE::RPCEnvironment;
-use PVE::SafeSyslog;
-use PVE::Tools qw(run_command);
-use PVE::Network::SDN;
-
-use PVE::API2::Network::SDN::Controllers;
-use PVE::API2::Network::SDN::Vnets;
-use PVE::API2::Network::SDN::Zones;
-use PVE::API2::Network::SDN::Ipams;
-use PVE::API2::Network::SDN::Dns;
-
-use base qw(PVE::RESTHandler);
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Vnets",
- path => 'vnets',
-});
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Zones",
- path => 'zones',
-});
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Controllers",
- path => 'controllers',
-});
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Ipams",
- path => 'ipams',
-});
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Dns",
- path => 'dns',
-});
-
-__PACKAGE__->register_method({
- name => 'index',
- path => '',
- method => 'GET',
- description => "Directory index.",
- permissions => {
- check => ['perm', '/', [ 'SDN.Audit' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {},
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {
- id => { type => 'string' },
- },
- },
- links => [ { rel => 'child', href => "{id}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $res = [
- { id => 'vnets' },
- { id => 'zones' },
- { id => 'controllers' },
- { id => 'ipams' },
- { id => 'dns' },
- ];
-
- return $res;
- }});
-
-my $create_reload_network_worker = sub {
- my ($nodename) = @_;
-
- # FIXME: how to proxy to final node ?
- my $upid;
- run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub {
- my $line = shift;
- if ($line =~ /^["']?(UPID:[^\s"']+)["']?$/) {
- $upid = $1;
- }
- });
- #my $upid = PVE::API2::Network->reload_network_config(node => $nodename});
- my $res = PVE::Tools::upid_decode($upid);
-
- return $res->{pid};
-};
-
-__PACKAGE__->register_method ({
- name => 'reload',
- protected => 1,
- path => '',
- method => 'PUT',
- description => "Apply sdn controller changes && reload.",
- permissions => {
- check => ['perm', '/sdn', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- },
- returns => {
- type => 'string',
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- PVE::Network::SDN::commit_config();
-
- my $code = sub {
- $rpcenv->{type} = 'priv'; # to start tasks in background
- PVE::Cluster::check_cfs_quorum();
- my $nodelist = PVE::Cluster::get_nodelist();
- for my $node (@$nodelist) {
- my $pid = eval { $create_reload_network_worker->($node) };
- warn $@ if $@;
- }
-
- # FIXME: use libpve-apiclient (like in cluster join) to create
- # tasks and moitor the tasks.
-
- return;
- };
-
- return $rpcenv->fork_worker('reloadnetworkall', undef, $authuser, $code);
-
- }});
-
-
-1;
+++ /dev/null
-package PVE::API2::Network::SDN::Controllers;
-
-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::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::Network::SDN::Controllers::Plugin;
-use PVE::Network::SDN::Controllers::EvpnPlugin;
-use PVE::Network::SDN::Controllers::BgpPlugin;
-use PVE::Network::SDN::Controllers::FaucetPlugin;
-
-use Storable qw(dclone);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-
-use PVE::RESTHandler;
-
-use base qw(PVE::RESTHandler);
-
-my $sdn_controllers_type_enum = PVE::Network::SDN::Controllers::Plugin->lookup_types();
-
-my $api_sdn_controllers_config = sub {
- my ($cfg, $id) = @_;
-
- my $scfg = dclone(PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id));
- $scfg->{controller} = $id;
- $scfg->{digest} = $cfg->{digest};
-
- return $scfg;
-};
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "SDN controllers index.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/<controller>'",
- user => 'all',
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- type => {
- description => "Only list sdn controllers of specific type",
- type => 'string',
- enum => $sdn_controllers_type_enum,
- optional => 1,
- },
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => { controller => { type => 'string' },
- type => { type => 'string' },
- state => { type => 'string', optional => 1 },
- pending => { optional => 1},
- },
- },
- links => [ { rel => 'child', href => "{controller}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Controllers::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{controllers};
- } else {
- $cfg = PVE::Network::SDN::Controllers::config();
- }
-
- my @sids = PVE::Network::SDN::Controllers::sdn_controllers_ids($cfg);
- my $res = [];
- foreach my $id (@sids) {
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
- next if !$rpcenv->check_any($authuser, "/sdn/controllers/$id", $privs, 1);
-
- my $scfg = &$api_sdn_controllers_config($cfg, $id);
- next if $param->{type} && $param->{type} ne $scfg->{type};
-
- my $plugin_config = $cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
- push @$res, $scfg;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'read',
- path => '{controller}',
- method => 'GET',
- description => "Read sdn controller configuration.",
- permissions => {
- check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']],
- },
-
- parameters => {
- additionalProperties => 0,
- properties => {
- controller => get_standard_option('pve-sdn-controller-id'),
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => { type => 'object' },
- code => sub {
- my ($param) = @_;
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Controllers::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{controllers};
- } else {
- $cfg = PVE::Network::SDN::Controllers::config();
- }
-
- return &$api_sdn_controllers_config($cfg, $param->{controller});
- }});
-
-__PACKAGE__->register_method ({
- name => 'create',
- protected => 1,
- path => '',
- method => 'POST',
- description => "Create a new sdn controller object.",
- permissions => {
- check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $type = extract_param($param, 'type');
- my $id = extract_param($param, 'controller');
-
- my $plugin = PVE::Network::SDN::Controllers::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 $controller_cfg = PVE::Network::SDN::Controllers::config();
-
- my $scfg = undef;
- if ($scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id, 1)) {
- die "sdn controller object ID '$id' already defined\n";
- }
-
- $controller_cfg->{ids}->{$id} = $opts;
- $plugin->on_update_hook($id, $controller_cfg);
-
- PVE::Network::SDN::Controllers::write_config($controller_cfg);
-
- }, "create sdn controller object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'update',
- protected => 1,
- path => '{controller}',
- method => 'PUT',
- description => "Update sdn controller object configuration.",
- permissions => {
- check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'controller');
- my $digest = extract_param($param, 'digest');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $controller_cfg = PVE::Network::SDN::Controllers::config();
-
- PVE::SectionConfig::assert_if_modified($controller_cfg, $digest);
-
- my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id);
-
- my $plugin = PVE::Network::SDN::Controllers::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($id, $controller_cfg);
-
- PVE::Network::SDN::Controllers::write_config($controller_cfg);
-
-
- }, "update sdn controller object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'delete',
- protected => 1,
- path => '{controller}',
- method => 'DELETE',
- description => "Delete sdn controller object configuration.",
- permissions => {
- check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- controller => get_standard_option('pve-sdn-controller-id', {
- completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers,
- }),
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'controller');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $cfg = PVE::Network::SDN::Controllers::config();
-
- my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id);
-
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type});
-
- my $zone_cfg = PVE::Network::SDN::Zones::config();
-
- $plugin->on_delete_hook($id, $zone_cfg);
-
- delete $cfg->{ids}->{$id};
- PVE::Network::SDN::Controllers::write_config($cfg);
-
- }, "delete sdn controller object failed");
-
-
- return undef;
- }});
-
-1;
+++ /dev/null
-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;
+++ /dev/null
-package PVE::API2::Network::SDN::Ipams;
-
-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::Ipams;
-use PVE::Network::SDN::Ipams::Plugin;
-use PVE::Network::SDN::Ipams::PVEPlugin;
-use PVE::Network::SDN::Ipams::PhpIpamPlugin;
-use PVE::Network::SDN::Ipams::NetboxPlugin;
-
-use Storable qw(dclone);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-
-use PVE::RESTHandler;
-
-use base qw(PVE::RESTHandler);
-
-my $sdn_ipams_type_enum = PVE::Network::SDN::Ipams::Plugin->lookup_types();
-
-my $api_sdn_ipams_config = sub {
- my ($cfg, $id) = @_;
-
- my $scfg = dclone(PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id));
- $scfg->{ipam} = $id;
- $scfg->{digest} = $cfg->{digest};
-
- return $scfg;
-};
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "SDN ipams index.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/<ipam>'",
- user => 'all',
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- type => {
- description => "Only list sdn ipams of specific type",
- type => 'string',
- enum => $sdn_ipams_type_enum,
- optional => 1,
- },
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => { ipam => { type => 'string'},
- type => { type => 'string'},
- },
- },
- links => [ { rel => 'child', href => "{ipam}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
-
- my $cfg = PVE::Network::SDN::Ipams::config();
-
- my @sids = PVE::Network::SDN::Ipams::sdn_ipams_ids($cfg);
- my $res = [];
- foreach my $id (@sids) {
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
- next if !$rpcenv->check_any($authuser, "/sdn/ipams/$id", $privs, 1);
-
- my $scfg = &$api_sdn_ipams_config($cfg, $id);
- next if $param->{type} && $param->{type} ne $scfg->{type};
-
- my $plugin_config = $cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- push @$res, $scfg;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'read',
- path => '{ipam}',
- method => 'GET',
- description => "Read sdn ipam configuration.",
- permissions => {
- check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']],
- },
-
- parameters => {
- additionalProperties => 0,
- properties => {
- ipam => get_standard_option('pve-sdn-ipam-id'),
- },
- },
- returns => { type => 'object' },
- code => sub {
- my ($param) = @_;
-
- my $cfg = PVE::Network::SDN::Ipams::config();
-
- return &$api_sdn_ipams_config($cfg, $param->{ipam});
- }});
-
-__PACKAGE__->register_method ({
- name => 'create',
- protected => 1,
- path => '',
- method => 'POST',
- description => "Create a new sdn ipam object.",
- permissions => {
- check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $type = extract_param($param, 'type');
- my $id = extract_param($param, 'ipam');
-
- my $plugin = PVE::Network::SDN::Ipams::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 $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $controller_cfg = PVE::Network::SDN::Controllers::config();
-
- my $scfg = undef;
- if ($scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id, 1)) {
- die "sdn ipam object ID '$id' already defined\n";
- }
-
- $ipam_cfg->{ids}->{$id} = $opts;
-
- my $plugin_config = $opts;
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- $plugin->on_update_hook($plugin_config);
-
- PVE::Network::SDN::Ipams::write_config($ipam_cfg);
-
- }, "create sdn ipam object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'update',
- protected => 1,
- path => '{ipam}',
- method => 'PUT',
- description => "Update sdn ipam object configuration.",
- permissions => {
- check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'ipam');
- my $digest = extract_param($param, 'digest');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
-
- PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest);
-
- my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id);
-
- my $plugin = PVE::Network::SDN::Ipams::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::Ipams::write_config($ipam_cfg);
-
- }, "update sdn ipam object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'delete',
- protected => 1,
- path => '{ipam}',
- method => 'DELETE',
- description => "Delete sdn ipam object configuration.",
- permissions => {
- check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- ipam => get_standard_option('pve-sdn-ipam-id', {
- completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams,
- }),
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'ipam');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $cfg = PVE::Network::SDN::Ipams::config();
-
- my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id);
-
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type});
-
- my $vnet_cfg = PVE::Network::SDN::Vnets::config();
-
- delete $cfg->{ids}->{$id};
- PVE::Network::SDN::Ipams::write_config($cfg);
-
- }, "delete sdn zone object failed");
-
- return undef;
- }});
-
-1;
+++ /dev/null
-SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done
- make -C Zones install
-
+++ /dev/null
-package PVE::API2::Network::SDN::Subnets;
-
-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::Exception qw(raise raise_param_exc);
-use PVE::Network::SDN;
-use PVE::Network::SDN::Subnets;
-use PVE::Network::SDN::SubnetPlugin;
-use PVE::Network::SDN::Vnets;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Ipams;
-use PVE::Network::SDN::Ipams::Plugin;
-
-use Storable qw(dclone);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-
-use PVE::RESTHandler;
-
-use base qw(PVE::RESTHandler);
-
-my $api_sdn_subnets_config = sub {
- my ($cfg, $id) = @_;
-
- my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id));
- $scfg->{subnet} = $id;
- $scfg->{digest} = $cfg->{digest};
-
- return $scfg;
-};
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "SDN subnets index.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/<subnet>'",
- user => 'all',
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- vnet => get_standard_option('pve-sdn-vnet-id'),
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {},
- },
- links => [ { rel => 'child', href => "{subnet}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- my $vnetid = $param->{vnet};
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Subnets::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{subnets};
- } else {
- $cfg = PVE::Network::SDN::Subnets::config();
- }
-
- my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg);
- my $res = [];
- foreach my $id (@sids) {
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
- next if !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid/subnets/$id", $privs, 1);
-
- my $scfg = &$api_sdn_subnets_config($cfg, $id);
- next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid;
- push @$res, $scfg;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'read',
- path => '{subnet}',
- method => 'GET',
- description => "Read sdn subnet configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets/{vnet}/subnets/{subnet}', ['SDN.Allocate']],
- },
-
- parameters => {
- additionalProperties => 0,
- properties => {
- vnet => get_standard_option('pve-sdn-vnet-id'),
- subnet => get_standard_option('pve-sdn-subnet-id', {
- completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets,
- }),
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => { type => 'object' },
- code => sub {
- my ($param) = @_;
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Subnets::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{subnets};
- } else {
- $cfg = PVE::Network::SDN::Subnets::config();
- }
-
- my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet});
-
- raise_param_exc({ vnet => "wrong vnet"}) if $param->{vnet} ne $scfg->{vnet};
-
- return $scfg;
- }});
-
-__PACKAGE__->register_method ({
- name => 'create',
- protected => 1,
- path => '',
- method => 'POST',
- description => "Create a new sdn subnet object.",
- permissions => {
- check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::SubnetPlugin->createSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $type = extract_param($param, 'type');
- my $cidr = extract_param($param, 'subnet');
-
- # create /etc/pve/sdn directory
- PVE::Cluster::check_cfs_quorum();
- mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn';
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $cfg = PVE::Network::SDN::Subnets::config();
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $vnet_cfg = PVE::Network::SDN::Vnets::config();
- my $vnet = $param->{vnet};
- my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone};
- my $zone = $zone_cfg->{ids}->{$zoneid};
- my $id = $cidr =~ s/\//-/r;
- $id = "$zoneid-$id";
-
- my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1);
-
- my $scfg = undef;
- if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) {
- die "sdn subnet object ID '$id' already defined\n";
- }
-
- $cfg->{ids}->{$id} = $opts;
-
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id);
- PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet);
-
- PVE::Network::SDN::Subnets::write_config($cfg);
-
- }, "create sdn subnet object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'update',
- protected => 1,
- path => '{subnet}',
- method => 'PUT',
- description => "Update sdn subnet object configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'subnet');
- my $digest = extract_param($param, 'digest');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
-
- my $cfg = PVE::Network::SDN::Subnets::config();
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $vnet_cfg = PVE::Network::SDN::Vnets::config();
- my $vnet = $param->{vnet};
- my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone};
- my $zone = $zone_cfg->{ids}->{$zoneid};
-
- my $scfg = &$api_sdn_subnets_config($cfg, $id);
-
- PVE::SectionConfig::assert_if_modified($cfg, $digest);
-
- my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1);
- $cfg->{ids}->{$id} = $opts;
-
- raise_param_exc({ ipam => "you can't change ipam"}) if $opts->{ipam} && $scfg->{ipam} && $opts->{ipam} ne $scfg->{ipam};
-
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id);
- PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg);
-
- PVE::Network::SDN::Subnets::write_config($cfg);
-
- }, "update sdn subnet object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'delete',
- protected => 1,
- path => '{subnet}',
- method => 'DELETE',
- description => "Delete sdn subnet object configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- vnet => get_standard_option('pve-sdn-vnet-id'),
- subnet => get_standard_option('pve-sdn-subnet-id', {
- completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets,
- }),
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'subnet');
-
- PVE::Network::SDN::lock_sdn_config(
- sub {
- my $cfg = PVE::Network::SDN::Subnets::config();
-
- my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1);
-
- my $vnets_cfg = PVE::Network::SDN::Vnets::config();
-
- PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg);
-
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $vnet = $param->{vnet};
- my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone};
- my $zone = $zone_cfg->{ids}->{$zoneid};
-
- PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg);
-
- delete $cfg->{ids}->{$id};
-
- PVE::Network::SDN::Subnets::write_config($cfg);
-
- }, "delete sdn subnet object failed");
-
-
- return undef;
- }});
-
-1;
+++ /dev/null
-package PVE::API2::Network::SDN::Vnets;
-
-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::Zones;
-use PVE::Network::SDN::Zones::Plugin;
-use PVE::Network::SDN::Vnets;
-use PVE::Network::SDN::VnetPlugin;
-use PVE::Network::SDN::Subnets;
-use PVE::API2::Network::SDN::Subnets;
-
-use Storable qw(dclone);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-use PVE::Exception qw(raise raise_param_exc);
-
-use PVE::RESTHandler;
-
-use base qw(PVE::RESTHandler);
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Subnets",
- path => '{vnet}/subnets',
-});
-
-my $api_sdn_vnets_config = sub {
- my ($cfg, $id) = @_;
-
- my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id));
- $scfg->{vnet} = $id;
- $scfg->{digest} = $cfg->{digest};
-
- return $scfg;
-};
-
-my $api_sdn_vnets_deleted_config = sub {
- my ($cfg, $running_cfg, $id) = @_;
-
- if (!$cfg->{ids}->{$id}) {
-
- my $vnet_cfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($running_cfg->{vnets}, $id));
- $vnet_cfg->{state} = "deleted";
- $vnet_cfg->{vnet} = $id;
- return $vnet_cfg;
- }
-};
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "SDN vnets index.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'"
- ." permissions on '/sdn/vnets/<vnet>'",
- user => 'all',
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {},
- },
- links => [ { rel => 'child', href => "{vnet}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Vnets::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{vnets};
- } else {
- $cfg = PVE::Network::SDN::Vnets::config();
- }
-
- my @sids = PVE::Network::SDN::Vnets::sdn_vnets_ids($cfg);
- my $res = [];
- foreach my $id (@sids) {
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
- next if !$rpcenv->check_any($authuser, "/sdn/vnets/$id", $privs, 1);
-
- my $scfg = &$api_sdn_vnets_config($cfg, $id);
- push @$res, $scfg;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'read',
- path => '{vnet}',
- method => 'GET',
- description => "Read sdn vnet configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets/{vnet}', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- vnet => get_standard_option('pve-sdn-vnet-id', {
- completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets,
- }),
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => { type => 'object' },
- code => sub {
- my ($param) = @_;
-
- my $cfg = {};
- if($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Vnets::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{vnets};
- } else {
- $cfg = PVE::Network::SDN::Vnets::config();
- }
-
- return $api_sdn_vnets_config->($cfg, $param->{vnet});
- }});
-
-__PACKAGE__->register_method ({
- name => 'create',
- protected => 1,
- path => '',
- method => 'POST',
- description => "Create a new sdn vnet object.",
- permissions => {
- check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::VnetPlugin->createSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $type = extract_param($param, 'type');
- my $id = extract_param($param, 'vnet');
-
- PVE::Cluster::check_cfs_quorum();
- mkdir("/etc/pve/sdn");
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $cfg = PVE::Network::SDN::Vnets::config();
- my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 1, 1);
-
- if (PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id, 1)) {
- die "sdn vnet object ID '$id' already defined\n";
- }
- $cfg->{ids}->{$id} = $opts;
-
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $zoneid = $cfg->{ids}->{$id}->{zone};
- my $plugin_config = $zone_cfg->{ids}->{$zoneid};
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- $plugin->vnet_update_hook($cfg, $id, $zone_cfg);
-
- PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg);
-
- PVE::Network::SDN::Vnets::write_config($cfg);
-
- }, "create sdn vnet object failed");
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'update',
- protected => 1,
- path => '{vnet}',
- method => 'PUT',
- description => "Update sdn vnet object configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::VnetPlugin->updateSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'vnet');
- my $digest = extract_param($param, 'digest');
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $cfg = PVE::Network::SDN::Vnets::config();
-
- PVE::SectionConfig::assert_if_modified($cfg, $digest);
-
-
- my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1);
- raise_param_exc({ zone => "missing zone"}) if !$opts->{zone};
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($id);
- raise_param_exc({ zone => "can't change zone if subnets exists"}) if($subnets && $opts->{zone} ne $cfg->{ids}->{$id}->{zone});
-
- $cfg->{ids}->{$id} = $opts;
-
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $zoneid = $cfg->{ids}->{$id}->{zone};
- my $plugin_config = $zone_cfg->{ids}->{$zoneid};
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- $plugin->vnet_update_hook($cfg, $id, $zone_cfg);
-
- PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg);
-
- PVE::Network::SDN::Vnets::write_config($cfg);
-
- }, "update sdn vnet object failed");
-
- return undef;
- }
-});
-
-__PACKAGE__->register_method ({
- name => 'delete',
- protected => 1,
- path => '{vnet}',
- method => 'DELETE',
- description => "Delete sdn vnet object configuration.",
- permissions => {
- check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- vnet => get_standard_option('pve-sdn-vnet-id', {
- completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets,
- }),
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'vnet');
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $cfg = PVE::Network::SDN::Vnets::config();
- my $scfg = PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id); # check if exists
- my $vnet_cfg = PVE::Network::SDN::Vnets::config();
-
- PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg);
-
- delete $cfg->{ids}->{$id};
- PVE::Network::SDN::Vnets::write_config($cfg);
-
- }, "delete sdn vnet object failed");
-
-
- return undef;
- }
-});
-
-1;
+++ /dev/null
-package PVE::API2::Network::SDN::Zones;
-
-use strict;
-use warnings;
-
-use Storable qw(dclone);
-
-use PVE::Cluster qw(cfs_read_file cfs_write_file);
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::RPCEnvironment;
-use PVE::SafeSyslog;
-use PVE::Tools qw(extract_param);
-
-use PVE::Network::SDN::Dns;
-use PVE::Network::SDN::Subnets;
-use PVE::Network::SDN::Vnets;
-use PVE::Network::SDN;
-
-use PVE::Network::SDN::Zones::EvpnPlugin;
-use PVE::Network::SDN::Zones::FaucetPlugin;
-use PVE::Network::SDN::Zones::Plugin;
-use PVE::Network::SDN::Zones::QinQPlugin;
-use PVE::Network::SDN::Zones::SimplePlugin;
-use PVE::Network::SDN::Zones::VlanPlugin;
-use PVE::Network::SDN::Zones::VxlanPlugin;
-use PVE::Network::SDN::Zones;
-
-use PVE::RESTHandler;
-use base qw(PVE::RESTHandler);
-
-my $sdn_zones_type_enum = PVE::Network::SDN::Zones::Plugin->lookup_types();
-
-my $api_sdn_zones_config = sub {
- my ($cfg, $id) = @_;
-
- my $scfg = dclone(PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id));
- $scfg->{zone} = $id;
- $scfg->{digest} = $cfg->{digest};
-
- if ($scfg->{nodes}) {
- $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
- }
-
- if ($scfg->{exitnodes}) {
- $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes});
- }
-
- my $pending = $scfg->{pending};
- if ($pending->{nodes}) {
- $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes});
- }
-
- if ($pending->{exitnodes}) {
- $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes});
- }
-
- return $scfg;
-};
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "SDN zones index.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>'",
- user => 'all',
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- type => {
- description => "Only list SDN zones of specific type",
- type => 'string',
- enum => $sdn_zones_type_enum,
- optional => 1,
- },
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- },
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => { zone => { type => 'string'},
- type => { type => 'string'},
- mtu => { type => 'integer', optional => 1 },
- dns => { type => 'string', optional => 1},
- reversedns => { type => 'string', optional => 1},
- dnszone => { type => 'string', optional => 1},
- ipam => { type => 'string', optional => 1},
- pending => { optional => 1},
- state => { type => 'string', optional => 1},
- nodes => { type => 'string', optional => 1},
- },
- },
- links => [ { rel => 'child', href => "{zone}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- my $cfg = {};
- if ($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Zones::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{zones};
- } else {
- $cfg = PVE::Network::SDN::Zones::config();
- }
-
- my @sids = PVE::Network::SDN::Zones::sdn_zones_ids($cfg);
- my $res = [];
- for my $id (@sids) {
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
- next if !$rpcenv->check_any($authuser, "/sdn/zones/$id", $privs, 1);
-
- my $scfg = &$api_sdn_zones_config($cfg, $id);
- next if $param->{type} && $param->{type} ne $scfg->{type};
-
- my $plugin_config = $cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- push @$res, $scfg;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'read',
- path => '{zone}',
- method => 'GET',
- description => "Read sdn zone configuration.",
- permissions => {
- check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']],
- },
-
- parameters => {
- additionalProperties => 0,
- properties => {
- zone => get_standard_option('pve-sdn-zone-id'),
- running => {
- type => 'boolean',
- optional => 1,
- description => "Display running config.",
- },
- pending => {
- type => 'boolean',
- optional => 1,
- description => "Display pending config.",
- }
- },
- },
- returns => { type => 'object' },
- code => sub {
- my ($param) = @_;
-
- my $cfg = {};
- if ($param->{pending}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- my $config = PVE::Network::SDN::Zones::config();
- $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones');
- } elsif ($param->{running}) {
- my $running_cfg = PVE::Network::SDN::running_config();
- $cfg = $running_cfg->{zones};
- } else {
- $cfg = PVE::Network::SDN::Zones::config();
- }
-
- return &$api_sdn_zones_config($cfg, $param->{zone});
- }});
-
-__PACKAGE__->register_method ({
- name => 'create',
- protected => 1,
- path => '',
- method => 'POST',
- description => "Create a new sdn zone object.",
- permissions => {
- check => ['perm', '/sdn/zones', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Zones::Plugin->createSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $type = extract_param($param, 'type');
- my $id = extract_param($param, 'zone');
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type);
- my $opts = $plugin->check_config($id, $param, 1, 1);
-
- PVE::Cluster::check_cfs_quorum();
- mkdir("/etc/pve/sdn");
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $controller_cfg = PVE::Network::SDN::Controllers::config();
- my $dns_cfg = PVE::Network::SDN::Dns::config();
-
- my $scfg = undef;
- if ($scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id, 1)) {
- die "sdn zone object ID '$id' already defined\n";
- }
-
- my $dnsserver = $opts->{dns};
- raise_param_exc({ dns => "$dnsserver don't exist"})
- if $dnsserver && !$dns_cfg->{ids}->{$dnsserver};
-
- my $reversednsserver = $opts->{reversedns};
- raise_param_exc({ reversedns => "$reversednsserver don't exist"})
- if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver};
-
- my $dnszone = $opts->{dnszone};
- raise_param_exc({ dnszone => "missing dns server"})
- if $dnszone && !$dnsserver;
-
- my $ipam = $opts->{ipam};
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam};
-
- $zone_cfg->{ids}->{$id} = $opts;
- $plugin->on_update_hook($id, $zone_cfg, $controller_cfg);
-
- PVE::Network::SDN::Zones::write_config($zone_cfg);
-
- }, "create sdn zone object failed");
-
- return;
- }});
-
-__PACKAGE__->register_method ({
- name => 'update',
- protected => 1,
- path => '{zone}',
- method => 'PUT',
- description => "Update sdn zone object configuration.",
- permissions => {
- check => ['perm', '/sdn/zones', ['SDN.Allocate']],
- },
- parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(),
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'zone');
- my $digest = extract_param($param, 'digest');
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- my $controller_cfg = PVE::Network::SDN::Controllers::config();
- my $dns_cfg = PVE::Network::SDN::Dns::config();
-
- PVE::SectionConfig::assert_if_modified($zone_cfg, $digest);
-
- my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id);
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type});
- my $opts = $plugin->check_config($id, $param, 0, 1);
-
- if ($opts->{ipam} && !$scfg->{ipam} || $opts->{ipam} ne $scfg->{ipam}) {
-
- # don't allow ipam change if subnet are defined for now, need to implement resync ipam content
- my $subnets_cfg = PVE::Network::SDN::Subnets::config();
- for my $subnetid (sort keys %{$subnets_cfg->{ids}}) {
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid);
- raise_param_exc({ ipam => "can't change ipam if a subnet is already defined in this zone"})
- if $subnet->{zone} eq $id;
- }
- }
-
- $zone_cfg->{ids}->{$id} = $opts;
-
- my $dnsserver = $opts->{dns};
- raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver};
-
- my $reversednsserver = $opts->{reversedns};
- raise_param_exc({ reversedns => "$reversednsserver don't exist"}) if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver};
-
- my $dnszone = $opts->{dnszone};
- raise_param_exc({ dnszone => "missing dns server"}) if $dnszone && !$dnsserver;
-
- my $ipam = $opts->{ipam};
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam};
-
- $plugin->on_update_hook($id, $zone_cfg, $controller_cfg);
-
- PVE::Network::SDN::Zones::write_config($zone_cfg);
-
- }, "update sdn zone object failed");
-
- return;
- }});
-
-__PACKAGE__->register_method ({
- name => 'delete',
- protected => 1,
- path => '{zone}',
- method => 'DELETE',
- description => "Delete sdn zone object configuration.",
- permissions => {
- check => ['perm', '/sdn/zones', ['SDN.Allocate']],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- zone => get_standard_option('pve-sdn-zone-id', {
- completion => \&PVE::Network::SDN::Zones::complete_sdn_zones,
- }),
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- my $id = extract_param($param, 'zone');
-
- PVE::Network::SDN::lock_sdn_config(sub {
- my $cfg = PVE::Network::SDN::Zones::config();
- my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id);
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type});
- my $vnet_cfg = PVE::Network::SDN::Vnets::config();
-
- $plugin->on_delete_hook($id, $vnet_cfg);
-
- delete $cfg->{ids}->{$id};
-
- PVE::Network::SDN::Zones::write_config($cfg);
- }, "delete sdn zone object failed");
-
- return;
- }});
-
-1;
+++ /dev/null
-package PVE::API2::Network::SDN::Zones::Content;
-
-use strict;
-use warnings;
-use Data::Dumper;
-
-use PVE::SafeSyslog;
-use PVE::Cluster;
-use PVE::INotify;
-use PVE::Exception qw(raise_param_exc);
-use PVE::RPCEnvironment;
-use PVE::RESTHandler;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Network::SDN;
-
-use base qw(PVE::RESTHandler);
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "List zone content.",
- permissions => {
- check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1],
- },
- protected => 1,
- proxyto => 'node',
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- zone => get_standard_option('pve-sdn-zone-id', {
- completion => \&PVE::Network::SDN::Zones::complete_sdn_zone,
- }),
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {
- vnet => {
- description => "Vnet identifier.",
- type => 'string',
- },
- status => {
- description => "Status.",
- type => 'string',
- optional => 1,
- },
- statusmsg => {
- description => "Status details",
- type => 'string',
- optional => 1,
- },
- },
- },
- links => [ { rel => 'child', href => "{vnet}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
-
- my $authuser = $rpcenv->get_user();
-
- my $zoneid = $param->{zone};
-
- my $res = [];
-
- my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
-
- foreach my $id (keys %{$vnet_status}) {
- if ($vnet_status->{$id}->{zone} eq $zoneid) {
- my $item->{vnet} = $id;
- $item->{status} = $vnet_status->{$id}->{'status'};
- $item->{statusmsg} = $vnet_status->{$id}->{'statusmsg'};
- push @$res,$item;
- }
- }
-
- return $res;
- }});
-
-1;
+++ /dev/null
-SOURCES=Status.pm Content.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/Zones/$$i; done
+++ /dev/null
-package PVE::API2::Network::SDN::Zones::Status;
-
-use strict;
-use warnings;
-
-use File::Path;
-use File::Basename;
-use PVE::Tools;
-use PVE::INotify;
-use PVE::Cluster;
-use PVE::API2::Network::SDN::Zones::Content;
-use PVE::RESTHandler;
-use PVE::RPCEnvironment;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Exception qw(raise_param_exc);
-
-use base qw(PVE::RESTHandler);
-
-__PACKAGE__->register_method ({
- subclass => "PVE::API2::Network::SDN::Zones::Content",
- path => '{zone}/content',
-});
-
-__PACKAGE__->register_method ({
- name => 'index',
- path => '',
- method => 'GET',
- description => "Get status for all zones.",
- permissions => {
- description => "Only list entries where you have 'SDN.Audit'",
- user => 'all',
- },
- protected => 1,
- proxyto => 'node',
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node')
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {
- zone => get_standard_option('pve-sdn-zone-id'),
- status => {
- description => "Status of zone",
- type => 'string',
- enum => ['available', 'pending', 'error'],
- },
- },
- },
- links => [ { rel => 'child', href => "{zone}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- my $rpcenv = PVE::RPCEnvironment::get();
- my $authuser = $rpcenv->get_user();
-
- my $localnode = PVE::INotify::nodename();
-
- my $res = [];
-
- my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
-
- foreach my $id (sort keys %{$zone_status}) {
- my $item->{zone} = $id;
- $item->{status} = $zone_status->{$id}->{'status'};
- push @$res, $item;
- }
-
- return $res;
- }});
-
-__PACKAGE__->register_method ({
- name => 'diridx',
- path => '{zone}',
- method => 'GET',
- description => "",
- permissions => {
- check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- zone => get_standard_option('pve-sdn-zone-id'),
- },
- },
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {
- subdir => { type => 'string' },
- },
- },
- links => [ { rel => 'child', href => "{subdir}" } ],
- },
- code => sub {
- my ($param) = @_;
- my $res = [
- { subdir => 'content' },
- ];
-
- return $res;
- }});
-
-1;
+++ /dev/null
-all:
-
-.PHONY: install
-install:
- make -C Network install
- make -C API2 install
+++ /dev/null
-SOURCES=SDN.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/$$i; done
- make -C SDN install
+++ /dev/null
-package PVE::Network::SDN;
-
-use strict;
-use warnings;
-
-use Data::Dumper;
-use JSON;
-
-use PVE::Network::SDN::Vnets;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::Network::SDN::Subnets;
-
-use PVE::Tools qw(extract_param dir_glob_regex run_command);
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-
-
-my $running_cfg = "sdn/.running-config";
-
-my $parse_running_cfg = sub {
- my ($filename, $raw) = @_;
-
- my $cfg = {};
-
- return $cfg if !defined($raw) || $raw eq '';
-
- eval {
- $cfg = from_json($raw);
- };
- return {} if $@;
-
- return $cfg;
-};
-
-my $write_running_cfg = sub {
- my ($filename, $cfg) = @_;
-
- my $json = to_json($cfg);
-
- return $json;
-};
-
-PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg);
-
-
-# improve me : move status code inside plugins ?
-
-sub ifquery_check {
-
- my $cmd = ['ifquery', '-a', '-c', '-o','json'];
-
- my $result = '';
- my $reader = sub { $result .= shift };
-
- eval {
- run_command($cmd, outfunc => $reader);
- };
-
- my $resultjson = decode_json($result);
- my $interfaces = {};
-
- foreach my $interface (@$resultjson) {
- my $name = $interface->{name};
- $interfaces->{$name} = {
- status => $interface->{status},
- config => $interface->{config},
- config_status => $interface->{config_status},
- };
- }
-
- return $interfaces;
-}
-
-sub status {
-
- my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status();
- return($zone_status, $vnet_status);
-}
-
-sub running_config {
- return cfs_read_file($running_cfg);
-}
-
-sub pending_config {
- my ($running_cfg, $cfg, $type) = @_;
-
- my $pending = {};
-
- my $running_objects = $running_cfg->{$type}->{ids};
- my $config_objects = $cfg->{ids};
-
- foreach my $id (sort keys %{$running_objects}) {
- my $running_object = $running_objects->{$id};
- my $config_object = $config_objects->{$id};
- foreach my $key (sort keys %{$running_object}) {
- $pending->{$id}->{$key} = $running_object->{$key};
- if(!keys %{$config_object}) {
- $pending->{$id}->{state} = "deleted";
- } elsif (!defined($config_object->{$key})) {
- $pending->{$id}->{"pending"}->{$key} = 'deleted';
- $pending->{$id}->{state} = "changed";
- } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key})
- ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) {
- $pending->{$id}->{state} = "changed";
- }
- }
- $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
- }
-
- foreach my $id (sort keys %{$config_objects}) {
- my $running_object = $running_objects->{$id};
- my $config_object = $config_objects->{$id};
-
- foreach my $key (sort keys %{$config_object}) {
- my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key};
- my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key};
- if($key eq 'type' || $key eq 'vnet') {
- $pending->{$id}->{$key} = $config_value;
- } else {
- $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value);
- }
- if(!keys %{$running_object}) {
- $pending->{$id}->{state} = "new";
- } elsif (!defined($running_value) && defined($config_value)) {
- $pending->{$id}->{state} = "changed";
- }
- }
- $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
- }
-
- return {ids => $pending};
-
-}
-
-sub commit_config {
-
- my $cfg = cfs_read_file($running_cfg);
- my $version = $cfg->{version};
-
- if ($version) {
- $version++;
- } else {
- $version = 1;
- }
-
- my $vnets_cfg = PVE::Network::SDN::Vnets::config();
- my $zones_cfg = PVE::Network::SDN::Zones::config();
- my $controllers_cfg = PVE::Network::SDN::Controllers::config();
- my $subnets_cfg = PVE::Network::SDN::Subnets::config();
-
- my $vnets = { ids => $vnets_cfg->{ids} };
- my $zones = { ids => $zones_cfg->{ids} };
- my $controllers = { ids => $controllers_cfg->{ids} };
- my $subnets = { ids => $subnets_cfg->{ids} };
-
- $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
-
- cfs_write_file($running_cfg, $cfg);
-}
-
-sub lock_sdn_config {
- my ($code, $errmsg) = @_;
-
- cfs_lock_file($running_cfg, undef, $code);
-
- if (my $err = $@) {
- $errmsg ? die "$errmsg: $err" : die $err;
- }
-}
-
-sub get_local_vnets {
-
- my $rpcenv = PVE::RPCEnvironment::get();
-
- my $authuser = $rpcenv->get_user();
-
- my $nodename = PVE::INotify::nodename();
-
- my $cfg = PVE::Network::SDN::running_config();
- my $vnets_cfg = $cfg->{vnets};
- my $zones_cfg = $cfg->{zones};
-
- my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg);
-
- my $vnets = {};
-
- foreach my $vnetid (@vnetids) {
-
- my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid);
- my $zoneid = $vnet->{zone};
- my $comments = $vnet->{alias};
-
- my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
-
- next if !$zoneid;
- next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid", $privs, 1) && !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid", $privs, 1);
-
- my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid);
-
- next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename};
- my $ipam = $zone_config->{ipam} ? 1 : 0;
- my $vlanaware = $vnet->{vlanaware} ? 1 : 0;
- $vnets->{$vnetid} = { type => 'vnet', active => '1', ipam => $ipam, vlanaware => $vlanaware, comments => $comments };
- }
-
- return $vnets;
-}
-
-sub generate_zone_config {
- my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config();
- PVE::Network::SDN::Zones::write_etc_network_config($raw_config);
-}
-
-sub generate_controller_config {
- my ($reload) = @_;
-
- my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config();
- PVE::Network::SDN::Controllers::write_controller_config($raw_config);
-
- PVE::Network::SDN::Controllers::reload_controller() if $reload;
-}
-
-sub encode_value {
- my ($type, $key, $value) = @_;
-
- if ($key eq 'nodes' || $key eq 'exitnodes') {
- if(ref($value) eq 'HASH') {
- return join(',', sort keys(%$value));
- } else {
- return $value;
- }
- }
-
- return $value;
-}
-
-
-#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 if $raw eq '';
-
- my $json = '';
- eval {
- $json = from_json($raw);
- };
- die "api response is not a json" if $@;
-
- return $json;
-}
-
-1;
+++ /dev/null
-package PVE::Network::SDN::Controllers;
-
-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::SDN::Vnets;
-use PVE::Network::SDN::Zones;
-
-use PVE::Network::SDN::Controllers::EvpnPlugin;
-use PVE::Network::SDN::Controllers::BgpPlugin;
-use PVE::Network::SDN::Controllers::FaucetPlugin;
-use PVE::Network::SDN::Controllers::Plugin;
-PVE::Network::SDN::Controllers::EvpnPlugin->register();
-PVE::Network::SDN::Controllers::BgpPlugin->register();
-PVE::Network::SDN::Controllers::FaucetPlugin->register();
-PVE::Network::SDN::Controllers::Plugin->init();
-
-
-sub sdn_controllers_config {
- my ($cfg, $id, $noerr) = @_;
-
- die "no sdn controller 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/controllers.cfg");
- $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}};
- return $config;
-}
-
-sub write_config {
- my ($cfg) = @_;
-
- cfs_write_file("sdn/controllers.cfg", $cfg);
-}
-
-sub lock_sdn_controllers_config {
- my ($code, $errmsg) = @_;
-
- cfs_lock_file("sdn/controllers.cfg", undef, $code);
- if (my $err = $@) {
- $errmsg ? die "$errmsg: $err" : die $err;
- }
-}
-
-sub sdn_controllers_ids {
- my ($cfg) = @_;
-
- return sort keys %{$cfg->{ids}};
-}
-
-sub complete_sdn_controller {
- my ($cmdname, $pname, $cvalue) = @_;
-
- my $cfg = PVE::Network::SDN::running_config();
-
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ];
-}
-
-sub generate_controller_config {
-
- my $cfg = PVE::Network::SDN::running_config();
- my $vnet_cfg = $cfg->{vnets};
- my $zone_cfg = $cfg->{zones};
- my $controller_cfg = $cfg->{controllers};
-
- return if !$vnet_cfg && !$zone_cfg && !$controller_cfg;
-
- #read main config for physical interfaces
- my $current_config_file = "/etc/network/interfaces";
- my $fh = IO::File->new($current_config_file);
- my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
- $fh->close();
-
- # check uplinks
- my $uplinks = {};
- foreach my $id (keys %{$interfaces_config->{ifaces}}) {
- my $interface = $interfaces_config->{ifaces}->{$id};
- if (my $uplink = $interface->{'uplink-id'}) {
- die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
- $interface->{name} = $id;
- $uplinks->{$interface->{'uplink-id'}} = $interface;
- }
- }
-
- # generate configuration
- my $config = {};
-
- foreach my $id (sort keys %{$controller_cfg->{ids}}) {
- my $plugin_config = $controller_cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
- $plugin->generate_controller_config($plugin_config, $controller_cfg, $id, $uplinks, $config);
- }
-
- foreach my $id (sort keys %{$zone_cfg->{ids}}) {
- my $plugin_config = $zone_cfg->{ids}->{$id};
- my $controllerid = $plugin_config->{controller};
- next if !$controllerid;
- my $controller = $controller_cfg->{ids}->{$controllerid};
- if ($controller) {
- my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
- $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $controller_cfg, $id, $uplinks, $config);
- }
- }
-
- foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
- my $plugin_config = $vnet_cfg->{ids}->{$id};
- my $zoneid = $plugin_config->{zone};
- next if !$zoneid;
- my $zone = $zone_cfg->{ids}->{$zoneid};
- next if !$zone;
- my $controllerid = $zone->{controller};
- next if !$controllerid;
- my $controller = $controller_cfg->{ids}->{$controllerid};
- if ($controller) {
- my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
- $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $zone, $zoneid, $id, $config);
- }
- }
-
- return $config;
-}
-
-
-sub reload_controller {
-
- my $cfg = PVE::Network::SDN::running_config();
- my $controller_cfg = $cfg->{controllers};
-
- return if !$controller_cfg;
-
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- my $plugin_config = $controller_cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
- $plugin->reload_controller();
- }
-}
-
-sub generate_controller_rawconfig {
- my ($config) = @_;
-
- my $cfg = PVE::Network::SDN::running_config();
- my $controller_cfg = $cfg->{controllers};
- return if !$controller_cfg;
-
- my $rawconfig = "";
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- my $plugin_config = $controller_cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
- $rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config);
- }
- return $rawconfig;
-}
-
-sub write_controller_config {
- my ($config) = @_;
-
- my $cfg = PVE::Network::SDN::running_config();
- my $controller_cfg = $cfg->{controllers};
- return if !$controller_cfg;
-
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- my $plugin_config = $controller_cfg->{ids}->{$id};
- my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
- $plugin->write_controller_config($plugin_config, $config);
- }
-}
-
-1;
-
+++ /dev/null
-package PVE::Network::SDN::Controllers::BgpPlugin;
-
-use strict;
-use warnings;
-
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw(run_command file_set_contents file_get_contents);
-
-use PVE::Network::SDN::Controllers::Plugin;
-use PVE::Network::SDN::Zones::Plugin;
-use Net::IP;
-
-use base('PVE::Network::SDN::Controllers::Plugin');
-
-sub type {
- return 'bgp';
-}
-
-sub properties {
- return {
- 'bgp-multipath-as-path-relax' => {
- type => 'boolean',
- optional => 1,
- },
- ebgp => {
- type => 'boolean',
- optional => 1,
- description => "Enable ebgp. (remote-as external)",
- },
- 'ebgp-multihop' => {
- type => 'integer',
- optional => 1,
- },
- loopback => {
- description => "source loopback interface.",
- type => 'string'
- },
- node => get_standard_option('pve-node'),
- };
-}
-
-sub options {
- return {
- 'node' => { optional => 0 },
- 'asn' => { optional => 0 },
- 'peers' => { optional => 0 },
- 'bgp-multipath-as-path-relax' => { optional => 1 },
- 'ebgp' => { optional => 1 },
- 'ebgp-multihop' => { optional => 1 },
- 'loopback' => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_controller_config {
- my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
-
- my @peers;
- @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
-
- my $asn = $plugin_config->{asn};
- my $ebgp = $plugin_config->{ebgp};
- my $ebgp_multihop = $plugin_config->{'ebgp-multihop'};
- my $loopback = $plugin_config->{loopback};
- my $multipath_relax = $plugin_config->{'bgp-multipath-as-path-relax'};
-
- my $local_node = PVE::INotify::nodename();
-
-
- return if !$asn;
- return if $local_node ne $plugin_config->{node};
-
- my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
-
- my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
-
- my $remoteas = $ebgp ? "external" : $asn;
-
- #global options
- my @controller_config = (
- "bgp router-id $ifaceip",
- "no bgp default ipv4-unicast",
- "coalesce-time 1000"
- );
-
- push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
-
- @controller_config = ();
- if($ebgp) {
- push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback;
- }
-
- push @controller_config, "bgp bestpath as-path multipath-relax" if $multipath_relax;
-
- #BGP neighbors
- if(@peers) {
- push @controller_config, "neighbor BGP peer-group";
- push @controller_config, "neighbor BGP remote-as $remoteas";
- push @controller_config, "neighbor BGP bfd";
- push @controller_config, "neighbor BGP ebgp-multihop $ebgp_multihop" if $ebgp && $ebgp_multihop;
- }
-
- # BGP peers
- foreach my $address (@peers) {
- push @controller_config, "neighbor $address peer-group BGP";
- }
- push(@{$bgp->{""}}, @controller_config);
-
- # address-family unicast
- if (@peers) {
- my $ipversion = Net::IP::ip_is_ipv6($ifaceip) ? "ipv6" : "ipv4";
- my $mask = Net::IP::ip_is_ipv6($ifaceip) ? "/128" : "32";
-
- push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "network $ifaceip/$mask") if $loopback;
- push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP activate");
- push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP soft-reconfiguration inbound");
- }
-
- if ($loopback) {
- $config->{frr_prefix_list}->{loopbacks_ips}->{10} = "permit 0.0.0.0/0 le 32";
- push(@{$config->{frr}->{''}}, "ip protocol bgp route-map correct_src");
-
- my $routemap_config = ();
- push @{$routemap_config}, "match ip address prefix-list loopbacks_ips";
- push @{$routemap_config}, "set src $ifaceip";
- my $routemap = { rule => $routemap_config, action => "permit" };
- push(@{$config->{frr_routemap}->{'correct_src'}}, $routemap);
- }
-
- return $config;
-}
-
-sub generate_controller_zone_config {
- my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
-
-}
-
-sub on_delete_hook {
- my ($class, $controllerid, $zone_cfg) = @_;
-
- # verify that zone is associated to this controller
- foreach my $id (keys %{$zone_cfg->{ids}}) {
- my $zone = $zone_cfg->{ids}->{$id};
- die "controller $controllerid is used by $id"
- if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
- }
-}
-
-sub on_update_hook {
- my ($class, $controllerid, $controller_cfg) = @_;
-
- # we can only have 1 bgp controller by node
- my $local_node = PVE::INotify::nodename();
- my $controllernb = 0;
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- next if $id eq $controllerid;
- my $controller = $controller_cfg->{ids}->{$id};
- next if $controller->{type} ne "bgp";
- next if $controller->{node} ne $local_node;
- $controllernb++;
- die "only 1 bgp controller can be defined" if $controllernb > 1;
- }
-}
-
-sub generate_controller_rawconfig {
- my ($class, $plugin_config, $config) = @_;
- return "";
-}
-
-sub write_controller_config {
- my ($class, $plugin_config, $config) = @_;
- return;
-}
-
-sub reload_controller {
- my ($class) = @_;
- return;
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Controllers::EvpnPlugin;
-
-use strict;
-use warnings;
-
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw(run_command file_set_contents file_get_contents);
-
-use PVE::Network::SDN::Controllers::Plugin;
-use PVE::Network::SDN::Zones::Plugin;
-use Net::IP;
-
-use base('PVE::Network::SDN::Controllers::Plugin');
-
-sub type {
- return 'evpn';
-}
-
-sub properties {
- return {
- asn => {
- type => 'integer',
- description => "autonomous system number",
- minimum => 0,
- maximum => 4294967296
- },
- peers => {
- description => "peers address list.",
- type => 'string', format => 'ip-list'
- },
- };
-}
-
-sub options {
- return {
- 'asn' => { optional => 0 },
- 'peers' => { optional => 0 },
- };
-}
-
-# Plugin implementation
-sub generate_controller_config {
- my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
-
- my @peers;
- @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
-
- my $local_node = PVE::INotify::nodename();
-
- my $asn = $plugin_config->{asn};
- my $ebgp = undef;
- my $loopback = undef;
- my $autortas = undef;
- my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
- if ($bgprouter) {
- $ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn};
- $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
- $asn = $bgprouter->{asn} if $bgprouter->{asn};
- $autortas = $plugin_config->{'asn'} if $ebgp;
- }
-
- return if !$asn;
-
- my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
-
- my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
-
- my $remoteas = $ebgp ? "external" : $asn;
-
- #global options
- my @controller_config = (
- "bgp router-id $ifaceip",
- "no bgp default ipv4-unicast",
- "coalesce-time 1000",
- );
-
- push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
-
- @controller_config = ();
-
- #VTEP neighbors
- push @controller_config, "neighbor VTEP peer-group";
- push @controller_config, "neighbor VTEP remote-as $remoteas";
- push @controller_config, "neighbor VTEP bfd";
-
- if($ebgp && $loopback) {
- push @controller_config, "neighbor VTEP ebgp-multihop 10";
- push @controller_config, "neighbor VTEP update-source $loopback";
- }
-
- # VTEP peers
- foreach my $address (@peers) {
- next if $address eq $ifaceip;
- push @controller_config, "neighbor $address peer-group VTEP";
- }
-
- push(@{$bgp->{""}}, @controller_config);
-
- # address-family l2vpn
- @controller_config = ();
- push @controller_config, "neighbor VTEP route-map MAP_VTEP_IN in";
- push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out";
- push @controller_config, "neighbor VTEP activate";
- push @controller_config, "advertise-all-vni";
- push @controller_config, "autort as $autortas" if $autortas;
- push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
-
- my $routemap = { rule => undef, action => "permit" };
- push(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap );
- push(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap );
-
- return $config;
-}
-
-sub generate_controller_zone_config {
- my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
-
- my $local_node = PVE::INotify::nodename();
-
- my $vrf = "vrf_$id";
- my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
- my $exitnodes = $plugin_config->{'exitnodes'};
- my $exitnodes_primary = $plugin_config->{'exitnodes-primary'};
- my $advertisesubnets = $plugin_config->{'advertise-subnets'};
- my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'};
- my $rt_import;
- $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] if $plugin_config->{'rt-import'};
-
- my $asn = $controller->{asn};
- my @peers;
- @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'};
- my $ebgp = undef;
- my $loopback = undef;
- my $autortas = undef;
- my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
- if($bgprouter) {
- $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn};
- $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
- $asn = $bgprouter->{asn} if $bgprouter->{asn};
- $autortas = $controller->{'asn'} if $ebgp;
- }
-
- return if !$vrf || !$vrfvxlan || !$asn;
-
- my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
-
- # vrf
- my @controller_config = ();
- push @controller_config, "vni $vrfvxlan";
- push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config);
-
- #main vrf router
- @controller_config = ();
- push @controller_config, "bgp router-id $ifaceip";
-# push @controller_config, "!";
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config);
-
- if ($autortas) {
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target import $autortas:$vrfvxlan");
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target export $autortas:$vrfvxlan");
- }
-
- my $is_gateway = $exitnodes->{$local_node};
-
- if ($is_gateway) {
-
- if (!$exitnodes_primary || $exitnodes_primary eq $local_node) {
- #filter default type5 route coming from other exit nodes on primary node or both nodes if no primary is defined.
- my $routemap_config = ();
- push @{$routemap_config}, "match evpn route-type prefix";
- my $routemap = { rule => $routemap_config, action => "deny" };
- unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap);
- } elsif ($exitnodes_primary ne $local_node) {
- my $routemap_config = ();
- push @{$routemap_config}, "match evpn vni $vrfvxlan";
- push @{$routemap_config}, "match evpn route-type prefix";
- push @{$routemap_config}, "set metric 200";
- my $routemap = { rule => $routemap_config, action => "permit" };
- unshift(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap);
- }
-
-
- if (!$exitnodes_local_routing) {
- @controller_config = ();
- #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
- push @controller_config, "import vrf $vrf";
- push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
- push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
-
- @controller_config = ();
- #redistribute connected to be able to route to local vms on the gateway
- push @controller_config, "redistribute connected";
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
- }
-
- @controller_config = ();
- #add default originate to announce 0.0.0.0/0 type5 route in evpn
- push @controller_config, "default-originate ipv4";
- push @controller_config, "default-originate ipv6";
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
- } elsif ($advertisesubnets) {
-
- @controller_config = ();
- #redistribute connected networks
- push @controller_config, "redistribute connected";
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
-
- @controller_config = ();
- #advertise connected networks type5 route in evpn
- push @controller_config, "advertise ipv4 unicast";
- push @controller_config, "advertise ipv6 unicast";
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
- }
-
- if ($rt_import) {
- @controller_config = ();
- foreach my $rt (sort @{$rt_import}) {
- push @controller_config, "route-target import $rt";
- }
- push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
- }
-
- return $config;
-}
-
-sub generate_controller_vnet_config {
- my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_;
-
- my $exitnodes = $zone->{'exitnodes'};
- my $exitnodes_local_routing = $zone->{'exitnodes-local-routing'};
-
- return if !$exitnodes_local_routing;
-
- my $local_node = PVE::INotify::nodename();
- my $is_gateway = $exitnodes->{$local_node};
-
- return if !$is_gateway;
-
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
- my @controller_config = ();
- foreach my $subnetid (sort keys %{$subnets}) {
- my $subnet = $subnets->{$subnetid};
- my $cidr = $subnet->{cidr};
- push @controller_config, "ip route $cidr 10.255.255.2 xvrf_$zoneid";
- }
- push(@{$config->{frr}->{''}}, @controller_config);
-}
-
-sub on_delete_hook {
- my ($class, $controllerid, $zone_cfg) = @_;
-
- # verify that zone is associated to this controller
- foreach my $id (keys %{$zone_cfg->{ids}}) {
- my $zone = $zone_cfg->{ids}->{$id};
- die "controller $controllerid is used by $id"
- if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
- }
-}
-
-sub on_update_hook {
- my ($class, $controllerid, $controller_cfg) = @_;
-
- # we can only have 1 evpn controller / 1 asn by server
-
- my $controllernb = 0;
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- next if $id eq $controllerid;
- my $controller = $controller_cfg->{ids}->{$id};
- next if $controller->{type} ne "evpn";
- $controllernb++;
- die "only 1 global evpn controller can be defined" if $controllernb >= 1;
- }
-}
-
-sub find_bgp_controller {
- my ($nodename, $controller_cfg) = @_;
-
- my $controller = undef;
- foreach my $id (keys %{$controller_cfg->{ids}}) {
- $controller = $controller_cfg->{ids}->{$id};
- next if $controller->{type} ne 'bgp';
- next if $controller->{node} ne $nodename;
- last;
- }
-
- return $controller;
-}
-
-
-sub sort_frr_config {
- my $order = {};
- $order->{''} = 0;
- $order->{'vrf'} = 1;
- $order->{'ipv4 unicast'} = 1;
- $order->{'ipv6 unicast'} = 2;
- $order->{'l2vpn evpn'} = 3;
-
- my $a_val = 100;
- my $b_val = 100;
-
- $a_val = $order->{$a} if defined($order->{$a});
- $b_val = $order->{$b} if defined($order->{$b});
-
- if ($a =~ /bgp (\d+)$/) {
- $a_val = 2;
- }
-
- if ($b =~ /bgp (\d+)$/) {
- $b_val = 2;
- }
-
- return $a_val <=> $b_val;
-}
-
-sub generate_frr_recurse{
- my ($final_config, $content, $parentkey, $level) = @_;
-
- my $keylist = {};
- $keylist->{vrf} = 1;
- $keylist->{'address-family'} = 1;
- $keylist->{router} = 1;
-
- my $exitkeylist = {};
- $exitkeylist->{vrf} = 1;
- $exitkeylist->{'address-family'} = 1;
-
- my $simple_exitkeylist = {};
- $simple_exitkeylist->{router} = 1;
-
- # FIXME: make this generic
- my $paddinglevel = undef;
- if ($level == 1 || $level == 2) {
- $paddinglevel = $level - 1;
- } elsif ($level == 3 || $level == 4) {
- $paddinglevel = $level - 2;
- }
-
- my $padding = "";
- $padding = ' ' x ($paddinglevel) if $paddinglevel;
-
- if (ref $content eq 'HASH') {
- foreach my $key (sort sort_frr_config keys %$content) {
- if ($parentkey && defined($keylist->{$parentkey})) {
- push @{$final_config}, $padding."!";
- push @{$final_config}, $padding."$parentkey $key";
- } elsif ($key ne '' && !defined($keylist->{$key})) {
- push @{$final_config}, $padding."$key";
- }
-
- my $option = $content->{$key};
- generate_frr_recurse($final_config, $option, $key, $level+1);
-
- push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
- push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey});
- }
- }
-
- if (ref $content eq 'ARRAY') {
- push @{$final_config}, map { $padding . "$_" } @$content;
- }
-}
-
-sub generate_frr_routemap {
- my ($final_config, $routemaps) = @_;
-
- foreach my $id (sort keys %$routemaps) {
-
- my $routemap = $routemaps->{$id};
- my $order = 0;
- foreach my $seq (@$routemap) {
- $order++;
- next if !defined($seq->{action});
- my @config = ();
- push @config, "!";
- push @config, "route-map $id $seq->{action} $order";
- my $rule = $seq->{rule};
- push @config, map { " $_" } @$rule;
- push @{$final_config}, @config;
- push @{$final_config}, "exit";
- }
- }
-}
-
-sub generate_frr_list {
- my ($final_config, $lists, $type) = @_;
-
- my $config = [];
-
- for my $id (sort keys %$lists) {
- my $list = $lists->{$id};
-
- for my $seq (sort keys %$list) {
- my $rule = $list->{$seq};
- push @$config, "$type $id seq $seq $rule";
- }
- }
-
- if (@$config > 0) {
- push @{$final_config}, "!", @$config;
- }
-}
-
-sub generate_controller_rawconfig {
- my ($class, $plugin_config, $config) = @_;
-
- my $nodename = PVE::INotify::nodename();
-
- my $final_config = [];
- push @{$final_config}, "frr version 8.2.2";
- push @{$final_config}, "frr defaults datacenter";
- push @{$final_config}, "hostname $nodename";
- push @{$final_config}, "log syslog informational";
- push @{$final_config}, "service integrated-vtysh-config";
- push @{$final_config}, "!";
-
- if (-e "/etc/frr/frr.conf.local") {
- my $local_conf = file_get_contents("/etc/frr/frr.conf.local");
- parse_merge_frr_local_config($config, $local_conf);
- }
-
- generate_frr_recurse($final_config, $config->{frr}, undef, 0);
- generate_frr_list($final_config, $config->{frr_access_list}, "access-list");
- generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list");
- generate_frr_routemap($final_config, $config->{frr_routemap});
-
- push @{$final_config}, "!";
- push @{$final_config}, "line vty";
- push @{$final_config}, "!";
-
- my $rawconfig = join("\n", @{$final_config});
-
- return if !$rawconfig;
- return $rawconfig;
-}
-
-sub parse_merge_frr_local_config {
- my ($config, $local_conf) = @_;
-
- my $section = \$config->{""};
- my $router = undef;
- my $routemap = undef;
- my $routemap_config = ();
- my $routemap_action = undef;
-
- while ($local_conf =~ /^\s*(.+?)\s*$/gm) {
- my $line = $1;
- $line =~ s/^\s+|\s+$//g;
-
- if ($line =~ m/^router (.+)$/) {
- $router = $1;
- $section = \$config->{'frr'}->{'router'}->{$router}->{""};
- next;
- } elsif ($line =~ m/^vrf (.+)$/) {
- $section = \$config->{'frr'}->{'vrf'}->{$1};
- next;
- } elsif ($line =~ m/address-family (.+)$/) {
- $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
- next;
- } elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
- $routemap = $1;
- $routemap_config = ();
- $routemap_action = $2;
- $section = \$config->{'frr_routemap'}->{$routemap};
- next;
- } elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
- $config->{'frr_access_list'}->{$1}->{$2} = $3;
- next;
- } elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
- $config->{'frr_prefix_list'}->{$1}->{$2} = $3;
- next;
- } elsif($line =~ m/^exit-address-family$/) {
- next;
- } elsif($line =~ m/^exit$/) {
- if($router) {
- $section = \$config->{''};
- $router = undef;
- } elsif($routemap) {
- push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
- $section = \$config->{''};
- $routemap = undef;
- $routemap_action = undef;
- $routemap_config = ();
- }
- next;
- } elsif($line =~ m/!/) {
- next;
- }
-
- next if !$section;
- if($routemap) {
- push(@{$routemap_config}, $line);
- } else {
- push(@{$$section}, $line);
- }
- }
-}
-
-sub write_controller_config {
- my ($class, $plugin_config, $config) = @_;
-
- my $rawconfig = $class->generate_controller_rawconfig($plugin_config, $config);
- return if !$rawconfig;
- return if !-d "/etc/frr";
-
- file_set_contents("/etc/frr/frr.conf", $rawconfig);
-}
-
-sub reload_controller {
- my ($class) = @_;
-
- my $conf_file = "/etc/frr/frr.conf";
- my $bin_path = "/usr/lib/frr/frr-reload.py";
-
- if (!-e $bin_path) {
- warn "missing $bin_path. Please install frr-pythontools package";
- return;
- }
-
- my $err = sub {
- my $line = shift;
- if ($line =~ /ERROR:/) {
- warn "$line \n";
- }
- };
-
- if (-e $conf_file && -e $bin_path) {
- eval {
- run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err);
- };
- if ($@) {
- warn "frr reload command fail. Restarting frr.";
- eval { run_command(['systemctl', 'restart', 'frr']); };
- }
- }
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Controllers::FaucetPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Controllers::Plugin;
-use PVE::Tools;
-use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
-use CPAN::Meta::YAML;
-use Encode;
-
-use base('PVE::Network::SDN::Controllers::Plugin');
-
-sub type {
- return 'faucet';
-}
-
-sub properties {
- return {
- };
-}
-
-# Plugin implementation
-sub generate_controller_config {
- my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
-
-}
-
-sub generate_controller_zone_config {
- my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
-
- my $dpid = $plugin_config->{'dp-id'};
- my $dphex = printf("%x",$dpid);
-
- my $zone_config = {
- dp_id => $dphex,
- hardware => "Open vSwitch",
- };
-
- $config->{faucet}->{dps}->{$id} = $zone_config;
-
-}
-
-
-sub generate_controller_vnet_config {
- my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_;
-
- my $mac = $plugin_config->{mac};
- my $ipv4 = $plugin_config->{ipv4};
- my $ipv6 = $plugin_config->{ipv6};
- my $tag = $plugin_config->{tag};
- my $alias = $plugin_config->{alias};
-
- my @ips = ();
- push @ips, $ipv4 if $ipv4;
- push @ips, $ipv6 if $ipv6;
-
- my $vlan_config = { vid => $tag };
-
- $vlan_config->{description} = $alias if $alias;
- $vlan_config->{faucet_mac} = $mac if $mac;
- $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0;
-
- $config->{faucet}->{vlans}->{$vnetid} = $vlan_config;
-
- push(@{$config->{faucet}->{routers}->{$zoneid}->{vlans}} , $vnetid);
-
-}
-
-sub write_controller_config {
- my ($class, $plugin_config, $config) = @_;
-
- my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet}));
-
- return if !$rawconfig;
- return if !-d "/etc/faucet";
-
- my $frr_config_file = "/etc/faucet/faucet.yaml";
-
- my $writefh = IO::File->new($frr_config_file,">");
- print $writefh $rawconfig;
- $writefh->close();
-}
-
-sub reload_controller {
- my ($class) = @_;
-
- my $conf_file = "/etc/faucet/faucet.yaml";
- my $bin_path = "/usr/bin/faucet";
-
- if (-e $conf_file && -e $bin_path) {
- PVE::Tools::run_command(['systemctl', 'reload', 'faucet']);
- }
-}
-
-1;
-
+++ /dev/null
-SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Controllers/$$i; done
+++ /dev/null
-package PVE::Network::SDN::Controllers::Plugin;
-
-use strict;
-use warnings;
-
-use PVE::Tools;
-use PVE::JSONSchema;
-use PVE::Cluster;
-
-use Data::Dumper;
-use PVE::JSONSchema qw(get_standard_option);
-use base qw(PVE::SectionConfig);
-
-PVE::Cluster::cfs_register_file('sdn/controllers.cfg',
- sub { __PACKAGE__->parse_config(@_); },
- sub { __PACKAGE__->write_config(@_); }
-);
-
-PVE::JSONSchema::register_standard_option('pve-sdn-controller-id', {
- description => "The SDN controller object identifier.",
- type => 'string', format => 'pve-sdn-controller-id',
-});
-
-PVE::JSONSchema::register_format('pve-sdn-controller-id', \&parse_sdn_controller_id);
-sub parse_sdn_controller_id {
- my ($id, $noerr) = @_;
-
- if ($id !~ m/^[a-z][a-z0-9_-]*[a-z0-9]$/i) {
- return undef if $noerr;
- die "controller ID '$id' contains illegal characters\n";
- }
- die "controller ID '$id' can't be more length than 64 characters\n" if length($id) > 64;
- return $id;
-}
-
-my $defaultData = {
-
- propertyList => {
- type => {
- description => "Plugin type.",
- type => 'string', format => 'pve-configid',
- type => 'string',
- },
- controller => get_standard_option('pve-sdn-controller-id',
- { completion => \&PVE::Network::SDN::complete_sdn_controller }),
- },
-};
-
-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 generate_sdn_config {
- my ($class, $plugin_config, $node, $data, $ctime) = @_;
-
- die "please implement inside plugin";
-}
-
-sub generate_controller_config {
- my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-
-sub generate_controller_zone_config {
- my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub generate_controller_vnet_config {
- my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_;
-
-}
-
-sub generate_controller_rawconfig {
- my ($class, $plugin_config, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub write_controller_config {
- my ($class, $plugin_config, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub controller_reload {
- my ($class) = @_;
-
- die "please implement inside plugin";
-}
-
-sub on_delete_hook {
- my ($class, $controllerid, $zone_cfg) = @_;
-
- # do nothing by default
-}
-
-sub on_update_hook {
- my ($class, $controllerid, $controller_cfg) = @_;
-
- # do nothing by default
-}
-
-1;
+++ /dev/null
-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;
-
+++ /dev/null
-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
+++ /dev/null
-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 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 },
- reversev6mask => { 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, $zone, $hostname, $ip, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub add_ptr_record {
- my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub del_ptr_record {
- my ($class, $plugin_config, $zone, $ip, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub del_a_record {
- my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub verify_zone {
- my ($class, $plugin_config, $zone, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub get_reversedns_zone {
- my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
-
- die "please implement inside plugin";
-}
-
-sub on_update_hook {
- my ($class, $plugin_config) = @_;
-}
-
-1;
+++ /dev/null
-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;
-
-
+++ /dev/null
-package PVE::Network::SDN::Ipams;
-
-use strict;
-use warnings;
-
-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::Ipams::PVEPlugin;
-use PVE::Network::SDN::Ipams::NetboxPlugin;
-use PVE::Network::SDN::Ipams::PhpIpamPlugin;
-use PVE::Network::SDN::Ipams::Plugin;
-
-PVE::Network::SDN::Ipams::PVEPlugin->register();
-PVE::Network::SDN::Ipams::NetboxPlugin->register();
-PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
-PVE::Network::SDN::Ipams::Plugin->init();
-
-
-sub sdn_ipams_config {
- my ($cfg, $id, $noerr) = @_;
-
- die "no sdn ipam 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/ipams.cfg");
- #add default internal pve
- $config->{ids}->{pve}->{type} = 'pve';
- return $config;
-}
-
-sub get_plugin_config {
- my ($vnet) = @_;
- my $ipamid = $vnet->{ipam};
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- return $ipam_cfg->{ids}->{$ipamid};
-}
-
-sub write_config {
- my ($cfg) = @_;
-
- cfs_write_file("sdn/ipams.cfg", $cfg);
-}
-
-sub sdn_ipams_ids {
- my ($cfg) = @_;
-
- return keys %{$cfg->{ids}};
-}
-
-sub complete_sdn_vnet {
- my ($cmdname, $pname, $cvalue) = @_;
-
- my $cfg = PVE::Network::SDN::Ipams::config();
-
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ];
-}
-
-1;
-
+++ /dev/null
-SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Ipams/$$i; done
+++ /dev/null
-package PVE::Network::SDN::Ipams::NetboxPlugin;
-
-use strict;
-use warnings;
-use PVE::INotify;
-use PVE::Cluster;
-use PVE::Tools;
-
-use base('PVE::Network::SDN::Ipams::Plugin');
-
-sub type {
- return 'netbox';
-}
-
-sub properties {
- return {
- };
-}
-
-sub options {
-
- return {
- url => { optional => 0},
- token => { optional => 0 },
- };
-}
-
-# Plugin implementation
-
-sub add_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $gateway = $subnet->{gateway};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
-
- #create subnet
- if (!$internalid) {
-
- my $params = { prefix => $cidr };
-
- eval {
- my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", $headers, $params);
- };
- if ($@) {
- die "error add subnet to ipam: $@" if !$noerr;
- }
- }
-
-}
-
-sub del_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $gateway = $subnet->{gateway};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
- return if !$internalid;
-
- return; #fixme: check that prefix is empty exluding gateway, before delete
-
- eval {
- PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
- };
- if ($@) {
- die "error deleting subnet from ipam: $@" if !$noerr;
- }
-
-}
-
-sub add_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
-
- my $mask = $subnet->{mask};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
- $description .= " mac:$mac" if $mac && $description;
-
- my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
-
- eval {
- PVE::Network::SDN::api_request("POST", "$url/ipam/ip-addresses/", $headers, $params);
- };
-
- if ($@) {
- if($is_gateway) {
- die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr;
- } else {
- die "error add subnet ip to ipam: ip already exist: $@" if !$noerr;
- }
- }
-}
-
-sub update_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
-
- my $mask = $subnet->{mask};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
- $description .= " mac:$mac" if $mac && $description;
-
- my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
-
- my $ip_id = get_ip_id($url, $ip, $headers);
- die "can't find ip $ip in ipam" if !$ip_id;
-
- eval {
- PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
- };
- if ($@) {
- die "error update ip $ip : $@" if !$noerr;
- }
-}
-
-sub add_next_freeip {
- my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
-
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
- $description .= " mac:$mac" if $mac && $description;
-
- my $params = { dns_name => $hostname, description => $description };
-
- my $ip = undef;
- eval {
- my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params);
- $ip = $result->{address};
- };
-
- if ($@) {
- die "can't find free ip in subnet $cidr: $@" if !$noerr;
- }
-
- return $ip;
-}
-
-sub del_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
-
- return if !$ip;
-
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
- my $ip_id = get_ip_id($url, $ip, $headers);
- die "can't find ip $ip in ipam" if !$ip_id;
-
- eval {
- PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers);
- };
- if ($@) {
- die "error delete ip $ip : $@" if !$noerr;
- }
-}
-
-sub verify_api {
- my ($class, $plugin_config) = @_;
-
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-
-
- eval {
- PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers);
- };
- if ($@) {
- die "Can't connect to netbox api: $@";
- }
-}
-
-sub on_update_hook {
- my ($class, $plugin_config) = @_;
-
- PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config);
-}
-
-#helpers
-
-sub get_prefix_id {
- my ($url, $cidr, $headers) = @_;
-
- my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
- my $data = @{$result->{results}}[0];
- my $internalid = $data->{id};
- return $internalid;
-}
-
-sub get_ip_id {
- my ($url, $ip, $headers) = @_;
- my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
- my $data = @{$result->{results}}[0];
- my $ip_id = $data->{id};
- return $ip_id;
-}
-
-sub is_ip_gateway {
- my ($url, $ip, $headers) = @_;
- my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
- my $data = @{$result->{data}}[0];
- my $description = $data->{description};
- my $is_gateway = 1 if $description eq 'gateway';
- return $is_gateway;
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Ipams::PVEPlugin;
-
-use strict;
-use warnings;
-use PVE::INotify;
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file);
-use PVE::Tools;
-use JSON;
-use NetAddr::IP qw(:lower);
-
-use Net::IP;
-use Digest::SHA;
-
-use base('PVE::Network::SDN::Ipams::Plugin');
-
-
-my $ipamdb_file = "priv/ipam.db";
-
-PVE::Cluster::cfs_register_file($ipamdb_file,
- sub { PVE::Network::SDN::Ipams::PVEPlugin->parse_config(@_); },
- sub { PVE::Network::SDN::Ipams::PVEPlugin->write_config(@_); });
-
-sub type {
- return 'pve';
-}
-
-sub properties {
-}
-
-sub options {
-}
-
-# Plugin implementation
-
-sub add_subnet {
- my ($class, $plugin_config, $subnetid, $subnet) = @_;
-
- my $cidr = $subnet->{cidr};
- my $zone = $subnet->{zone};
- my $gateway = $subnet->{gateway};
-
-
- cfs_lock_file($ipamdb_file, undef, sub {
- my $db = {};
- $db = read_db();
-
- $db->{zones}->{$zone} = {} if !$db->{zones}->{$zone};
- my $zonedb = $db->{zones}->{$zone};
-
- if(!$zonedb->{subnets}->{$cidr}) {
- #create subnet
- $zonedb->{subnets}->{$cidr}->{ips} = {};
- write_db($db);
- }
- });
- die "$@" if $@;
-}
-
-sub del_subnet {
- my ($class, $plugin_config, $subnetid, $subnet) = @_;
-
- my $cidr = $subnet->{cidr};
- my $zone = $subnet->{zone};
-
- cfs_lock_file($ipamdb_file, undef, sub {
-
- my $db = read_db();
-
- my $dbzone = $db->{zones}->{$zone};
- die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
- my $dbsubnet = $dbzone->{subnets}->{$cidr};
- die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
-
- die "cannot delete subnet '$cidr', not empty\n" if keys %{$dbsubnet->{ips}} > 0;
-
- delete $dbzone->{subnets}->{$cidr};
-
- write_db($db);
- });
- die "$@" if $@;
-
-}
-
-sub add_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
-
- my $cidr = $subnet->{cidr};
- my $zone = $subnet->{zone};
-
- cfs_lock_file($ipamdb_file, undef, sub {
-
- my $db = read_db();
- my $dbzone = $db->{zones}->{$zone};
- die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
- my $dbsubnet = $dbzone->{subnets}->{$cidr};
- die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
-
- die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway}));
- $dbsubnet->{ips}->{$ip} = {};
- $dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway;
-
- write_db($db);
- });
- die "$@" if $@;
-}
-
-sub update_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
- return;
-}
-
-sub add_next_freeip {
- my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description) = @_;
-
- my $cidr = $subnet->{cidr};
- my $network = $subnet->{network};
- my $zone = $subnet->{zone};
- my $mask = $subnet->{mask};
- my $freeip = undef;
-
- cfs_lock_file($ipamdb_file, undef, sub {
-
- my $db = read_db();
- my $dbzone = $db->{zones}->{$zone};
- die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
- my $dbsubnet = $dbzone->{subnets}->{$cidr};
- die "subnet '$cidr' doesn't exist in IPAM DB" if !$dbsubnet;
-
- if (Net::IP::ip_is_ipv4($network) && $mask == 32) {
- die "cannot find free IP in subnet '$cidr'\n" if defined($dbsubnet->{ips}->{$network});
- $freeip = $network;
- } else {
- my $iplist = NetAddr::IP->new($cidr);
- my $lastip = $iplist->last()->canon();
- $iplist++ if Net::IP::ip_is_ipv4($network); #skip network address for ipv4
- while(1) {
- my $ip = $iplist->canon();
- if (defined($dbsubnet->{ips}->{$ip})) {
- last if $ip eq $lastip;
- $iplist++;
- next;
- }
- $freeip = $ip;
- last;
- }
- }
-
- die "can't find free ip in subnet '$cidr'\n" if !$freeip;
-
- $dbsubnet->{ips}->{$freeip} = {};
-
- write_db($db);
- });
- die "$@" if $@;
-
- return "$freeip/$mask";
-}
-
-sub del_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
-
- my $cidr = $subnet->{cidr};
- my $zone = $subnet->{zone};
-
- cfs_lock_file($ipamdb_file, undef, sub {
-
- my $db = read_db();
- die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone};
- my $dbzone = $db->{zones}->{$zone};
- die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr};
- my $dbsubnet = $dbzone->{subnets}->{$cidr};
-
- die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip});
- delete $dbsubnet->{ips}->{$ip};
-
- write_db($db);
- });
- die "$@" if $@;
-}
-
-#helpers
-
-sub read_db {
- my $db = cfs_read_file($ipamdb_file);
- return $db;
-}
-
-sub write_db {
- my ($cfg) = @_;
-
- my $json = to_json($cfg);
- cfs_write_file($ipamdb_file, $json);
-}
-
-sub write_config {
- my ($class, $filename, $cfg) = @_;
-
- return $cfg;
-}
-
-sub parse_config {
- my ($class, $filename, $raw) = @_;
-
- $raw = '{}' if !defined($raw) ||$raw eq '';
- my $cfg = from_json($raw);
-
- return $cfg;
-}
-
-1;
+++ /dev/null
-package PVE::Network::SDN::Ipams::PhpIpamPlugin;
-
-use strict;
-use warnings;
-use PVE::INotify;
-use PVE::Cluster;
-use PVE::Tools;
-
-use base('PVE::Network::SDN::Ipams::Plugin');
-
-sub type {
- return 'phpipam';
-}
-
-sub properties {
- return {
- url => {
- type => 'string',
- },
- token => {
- type => 'string',
- },
- section => {
- type => 'integer',
- },
- };
-}
-
-sub options {
-
- return {
- url => { optional => 0},
- token => { optional => 0 },
- section => { optional => 0 },
- };
-}
-
-# Plugin implementation
-
-sub add_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $network = $subnet->{network};
- my $mask = $subnet->{mask};
-
- my $gateway = $subnet->{gateway};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- #search subnet
- my $internalid = get_prefix_id($url, $cidr, $headers);
-
- #create subnet
- if (!$internalid) {
- my $params = { subnet => $network,
- mask => $mask,
- sectionId => $section,
- };
-
- eval {
- PVE::Network::SDN::api_request("POST", "$url/subnets/", $headers, $params);
- };
- if ($@) {
- die "error add subnet to ipam: $@" if !$noerr;
- }
- }
-
-}
-
-sub del_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
- return if !$internalid;
-
- return; #fixme: check that prefix is empty exluding gateway, before delete
-
- eval {
- PVE::Network::SDN::api_request("DELETE", "$url/subnets/$internalid", $headers);
- };
- if ($@) {
- die "error deleting subnet from ipam: $@" if !$noerr;
- }
-
-}
-
-sub add_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
-
- my $params = { ip => $ip,
- subnetId => $internalid,
- hostname => $hostname,
- description => $description,
- };
- $params->{is_gateway} = 1 if $is_gateway;
- $params->{mac} = $mac if $mac;
-
- eval {
- PVE::Network::SDN::api_request("POST", "$url/addresses/", $headers, $params);
- };
-
- if ($@) {
- if($is_gateway) {
- die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr;
- } else {
- die "error add subnet ip to ipam: ip $ip already exist: $@" if !$noerr;
- }
- }
-}
-
-sub update_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- my $ip_id = get_ip_id($url, $ip, $headers);
- die "can't find ip addresse in ipam" if !$ip_id;
-
- my $params = {
- hostname => $hostname,
- description => $description,
- };
- $params->{is_gateway} = 1 if $is_gateway;
- $params->{mac} = $mac if $mac;
-
- eval {
- PVE::Network::SDN::api_request("PATCH", "$url/addresses/$ip_id", $headers, $params);
- };
-
- if ($@) {
- die "ipam: error update subnet ip $ip: $@" if !$noerr;
- }
-}
-
-sub add_next_freeip {
- my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
-
- my $cidr = $subnet->{cidr};
- my $mask = $subnet->{mask};
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $section = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- my $internalid = get_prefix_id($url, $cidr, $headers);
-
- my $params = { hostname => $hostname,
- description => $description,
- };
-
- $params->{mac} = $mac if $mac;
-
- my $ip = undef;
- eval {
- my $result = PVE::Network::SDN::api_request("POST", "$url/addresses/first_free/$internalid/", $headers, $params);
- $ip = $result->{data};
- };
-
- if ($@) {
- die "can't find free ip in subnet $cidr: $@" if !$noerr;
- }
-
- return "$ip/$mask" if $ip && $mask;
-}
-
-sub del_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
-
- return if !$ip;
-
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- my $ip_id = get_ip_id($url, $ip, $headers);
- return if !$ip_id;
-
- eval {
- PVE::Network::SDN::api_request("DELETE", "$url/addresses/$ip_id", $headers);
- };
- if ($@) {
- die "error delete ip $ip: $@" if !$noerr;
- }
-}
-
-sub verify_api {
- my ($class, $plugin_config) = @_;
-
- my $url = $plugin_config->{url};
- my $token = $plugin_config->{token};
- my $sectionid = $plugin_config->{section};
- my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
-
- eval {
- PVE::Network::SDN::api_request("GET", "$url/sections/$sectionid", $headers);
- };
- if ($@) {
- die "Can't connect to phpipam api: $@";
- }
-}
-
-sub on_update_hook {
- my ($class, $plugin_config) = @_;
-
- PVE::Network::SDN::Ipams::PhpIpamPlugin::verify_api($class, $plugin_config);
-}
-
-
-#helpers
-
-sub get_prefix_id {
- my ($url, $cidr, $headers) = @_;
-
- my $result = PVE::Network::SDN::api_request("GET", "$url/subnets/cidr/$cidr", $headers);
- my $data = @{$result->{data}}[0];
- my $internalid = $data->{id};
- return $internalid;
-}
-
-sub get_ip_id {
- my ($url, $ip, $headers) = @_;
- my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
- my $data = @{$result->{data}}[0];
- my $ip_id = $data->{id};
- return $ip_id;
-}
-
-sub is_ip_gateway {
- my ($url, $ip, $headers) = @_;
- my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
- my $data = @{$result->{data}}[0];
- my $is_gateway = $data->{is_gateway};
- return $is_gateway;
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Ipams::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/ipams.cfg',
- sub { __PACKAGE__->parse_config(@_); },
- sub { __PACKAGE__->write_config(@_); });
-
-PVE::JSONSchema::register_standard_option('pve-sdn-ipam-id', {
- description => "The SDN ipam object identifier.",
- type => 'string', format => 'pve-sdn-ipam-id',
-});
-
-PVE::JSONSchema::register_format('pve-sdn-ipam-id', \&parse_sdn_ipam_id);
-sub parse_sdn_ipam_id {
- my ($id, $noerr) = @_;
-
- if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
- return undef if $noerr;
- die "ipam ID '$id' contains illegal characters\n";
- }
- return $id;
-}
-
-my $defaultData = {
-
- propertyList => {
- type => {
- description => "Plugin type.",
- type => 'string', format => 'pve-configid',
- type => 'string',
- },
- ipam => get_standard_option('pve-sdn-ipam-id',
- { completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipam }),
- },
-};
-
-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_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub del_subnet {
- my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub add_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub update_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
- # only update ip attributes (mac,hostname,..). Don't change the ip addresses itself, as some ipam
- # don't allow ip address change without del/add
-
- die "please implement inside plugin";
-}
-
-sub add_next_freeip {
- my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub del_ip {
- my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
-
- die "please implement inside plugin";
-}
-
-sub on_update_hook {
- my ($class, $plugin_config) = @_;
-}
-
-1;
+++ /dev/null
-SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/$$i; done
- make -C Controllers install
- make -C Zones install
- make -C Ipams install
- make -C Dns install
-
+++ /dev/null
-package PVE::Network::SDN::SubnetPlugin;
-
-use strict;
-use warnings;
-
-use Net::IP;
-use Net::Subnet qw(subnet_matcher);
-
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Network::SDN::Ipams;
-use PVE::Network::SDN::Vnets;
-
-use base qw(PVE::SectionConfig);
-
-PVE::Cluster::cfs_register_file('sdn/subnets.cfg',
- sub { __PACKAGE__->parse_config(@_); },
- sub { __PACKAGE__->write_config(@_); });
-
-PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', {
- description => "The SDN subnet object identifier.",
- type => 'string', format => 'pve-sdn-subnet-id',
- type => 'string'
-});
-
-PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id);
-sub parse_sdn_subnet_id {
- my ($id, $noerr) = @_;
-
- my $cidr = "";
- if($id =~ /\//) {
- $cidr = $id;
- } else {
- my ($zone, $ip, $mask) = split(/-/, $id);
- $cidr = "$ip/$mask";
- }
-
- if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) ||
- PVE::JSONSchema::pve_verify_cidrv6($cidr, 1)))
- {
- return undef if $noerr;
- die "value does not look like a valid CIDR network\n";
- }
- return $id;
-}
-
-my $defaultData = {
-
- propertyList => {
- subnet => get_standard_option('pve-sdn-subnet-id',
- { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }),
- },
-};
-
-sub type {
- return 'subnet';
-}
-
-sub private {
- return $defaultData;
-}
-
-sub properties {
- return {
- vnet => {
- type => 'string',
- description => "associated vnet",
- },
- gateway => {
- type => 'string', format => 'ip',
- description => "Subnet Gateway: Will be assign on vnet for layer3 zones",
- },
- snat => {
- type => 'boolean',
- description => "enable masquerade for this subnet if pve-firewall",
- },
-# #cloudinit, dhcp options
-# routes => {
-# type => 'string',
-# description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
-# },
- dnszoneprefix => {
- type => 'string', format => 'dns-name',
- description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com",
- },
- };
-}
-
-sub options {
- return {
- vnet => { optional => 0 },
- gateway => { optional => 1 },
-# routes => { optional => 1 },
- snat => { optional => 1 },
- dnszoneprefix => { optional => 1 },
- };
-}
-
-sub on_update_hook {
- my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_;
-
- my $cidr = $subnet->{cidr};
- my $mask = $subnet->{mask};
-
- my $subnet_matcher = subnet_matcher($cidr);
-
- my $vnetid = $subnet->{vnet};
- my $gateway = $subnet->{gateway};
- my $ipam = $zone->{ipam};
- my $dns = $zone->{dns};
- my $dnszone = $zone->{dnszone};
- my $reversedns = $zone->{reversedns};
-
- my $old_gateway = $old_subnet->{gateway} if $old_subnet;
- my $mac = undef;
-
- if($vnetid) {
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
- raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet;
- raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware};
- $mac = $vnet->{mac};
- }
-
- my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32;
-
- #for /32 pointopoint, we allow gateway outside the subnet
- raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint;
-
-
- if ($ipam) {
- PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
-
- #don't register gateway for pointopoint
- return if $pointopoint;
-
- #delete gateway on removal
- if (!defined($gateway) && $old_gateway) {
- eval {
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
- };
- warn if $@;
- }
- if(!$old_gateway || $gateway && $gateway ne $old_gateway) {
- my $hostname = "$vnetid-gw";
- my $description = "gateway";
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, $description, 1);
- }
-
- #delete old gateway after update
- if($gateway && $old_gateway && $gateway ne $old_gateway) {
- eval {
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
- };
- warn if $@;
- }
- }
-}
-
-sub on_delete_hook {
- my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_;
-
- return;
-}
-
-1;
+++ /dev/null
-package PVE::Network::SDN::Subnets;
-
-use strict;
-use warnings;
-
-use Net::Subnet qw(subnet_matcher);
-use Net::IP;
-use NetAddr::IP qw(:lower);
-
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-use PVE::Network::SDN::Dns;
-use PVE::Network::SDN::Ipams;
-
-use PVE::Network::SDN::SubnetPlugin;
-PVE::Network::SDN::SubnetPlugin->register();
-PVE::Network::SDN::SubnetPlugin->init();
-
-sub sdn_subnets_config {
- my ($cfg, $id, $noerr) = @_;
-
- die "no sdn subnet ID specified\n" if !$id;
-
- my $scfg = $cfg->{ids}->{$id};
- die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg);
-
- if($scfg) {
- my ($zone, $network, $mask) = split(/-/, $id);
- $scfg->{cidr} = "$network/$mask";
- $scfg->{zone} = $zone;
- $scfg->{network} = $network;
- $scfg->{mask} = $mask;
- }
-
- return $scfg;
-}
-
-sub config {
- my $config = cfs_read_file("sdn/subnets.cfg");
-}
-
-sub write_config {
- my ($cfg) = @_;
-
- cfs_write_file("sdn/subnets.cfg", $cfg);
-}
-
-sub sdn_subnets_ids {
- my ($cfg) = @_;
-
- return sort keys %{$cfg->{ids}};
-}
-
-sub complete_sdn_subnet {
- my ($cmdname, $pname, $cvalue) = @_;
-
- my $cfg = PVE::Network::SDN::Subnets::config();
-
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ];
-}
-
-sub get_subnet {
- my ($subnetid, $running) = @_;
-
- my $cfg = {};
- if($running) {
- my $cfg = PVE::Network::SDN::running_config();
- $cfg = $cfg->{subnets};
- } else {
- $cfg = PVE::Network::SDN::Subnets::config();
- }
-
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1);
- return $subnet;
-}
-
-sub find_ip_subnet {
- my ($ip, $mask, $subnets) = @_;
-
- my $subnet = undef;
- my $subnetid = undef;
-
- foreach my $id (sort keys %{$subnets}) {
-
- next if $mask ne $subnets->{$id}->{mask};
- my $cidr = $subnets->{$id}->{cidr};
- my $subnet_matcher = subnet_matcher($cidr);
- next if !$subnet_matcher->($ip);
- $subnet = $subnets->{$id};
- $subnetid = $id;
- last;
- }
- die "can't find any subnet for ip $ip" if !$subnet;
-
- return ($subnetid, $subnet);
-}
-
-sub verify_dns_zone {
- 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);
-}
-
-sub get_reversedns_zone {
- my ($subnetid, $subnet, $dns, $ip) = @_;
-
- return if !$subnetid || !$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->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip);
-}
-
-sub add_dns_record {
- my ($zone, $dns, $hostname, $ip) = @_;
- return if !$zone || !$dns || !$hostname || !$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->add_a_record($plugin_config, $zone, $hostname, $ip);
-
-}
-
-sub add_dns_ptr_record {
- my ($reversezone, $zone, $dns, $hostname, $ip) = @_;
-
- return if !$zone || !$reversezone || !$dns || !$hostname || !$ip;
-
- $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);
-}
-
-sub del_dns_record {
- my ($zone, $dns, $hostname, $ip) = @_;
-
- return if !$zone || !$dns || !$hostname || !$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_a_record($plugin_config, $zone, $hostname, $ip);
-}
-
-sub del_dns_ptr_record {
- 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 add_subnet {
- my ($zone, $subnetid, $subnet) = @_;
-
- my $ipam = $zone->{ipam};
- return if !$ipam;
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $plugin_config = $ipam_cfg->{ids}->{$ipam};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- $plugin->add_subnet($plugin_config, $subnetid, $subnet);
-}
-
-sub del_subnet {
- my ($zone, $subnetid, $subnet) = @_;
-
- my $ipam = $zone->{ipam};
- return if !$ipam;
- my $ipam_cfg = PVE::Network::SDN::Ipams::config();
- my $plugin_config = $ipam_cfg->{ids}->{$ipam};
- my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
- $plugin->del_subnet($plugin_config, $subnetid, $subnet);
-}
-
-sub next_free_ip {
- my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_;
-
- my $cidr = undef;
- my $ip = undef;
- $description = '' if !$description;
-
- my $ipamid = $zone->{ipam};
- my $dns = $zone->{dns};
- my $dnszone = $zone->{dnszone};
- my $reversedns = $zone->{reversedns};
- my $dnszoneprefix = $subnet->{dnszoneprefix};
-
- $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
-
- #verify dns zones before ipam
- verify_dns_zone($dnszone, $dns) if !$skipdns;
-
- 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});
- eval {
- $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description);
- ($ip, undef) = split(/\//, $cidr);
- };
- die $@ if $@;
- }
-
- eval {
- my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
-
- if(!$skipdns) {
- #add dns
- add_dns_record($dnszone, $dns, $hostname, $ip);
- #add reverse dns
- add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
- }
- };
- if ($@) {
- #rollback
- my $err = $@;
- eval {
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
- };
- die $err;
- }
- return $cidr;
-}
-
-sub add_ip {
- my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_;
-
- return if !$subnet || !$ip;
-
- my $ipaddr = NetAddr::IP->new($ip);
- $ip = $ipaddr->canon();
-
- my $ipamid = $zone->{ipam};
- my $dns = $zone->{dns};
- my $dnszone = $zone->{dnszone};
- my $reversedns = $zone->{reversedns};
- my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
- my $dnszoneprefix = $subnet->{dnszoneprefix};
-
- $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
-
- #verify dns zones before ipam
- if(!$skipdns) {
- 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});
-
- eval {
- $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
- };
- die $@ if $@;
- }
-
- eval {
- if(!$skipdns) {
- #add dns
- add_dns_record($dnszone, $dns, $hostname, $ip);
- #add reverse dns
- add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
- }
- };
- if ($@) {
- #rollback
- my $err = $@;
- eval {
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
- };
- die $err;
- }
-}
-
-sub update_ip {
- my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
-
- return if !$subnet || !$ip;
-
- my $ipaddr = NetAddr::IP->new($ip);
- $ip = $ipaddr->canon();
-
- my $ipamid = $zone->{ipam};
- my $dns = $zone->{dns};
- my $dnszone = $zone->{dnszone};
- my $reversedns = $zone->{reversedns};
- my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
- my $dnszoneprefix = $subnet->{dnszoneprefix};
-
- $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
-
- #verify dns zones before ipam
- if(!$skipdns) {
- 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});
- eval {
- $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description);
- };
- die $@ if $@;
- }
-
- return if $hostname eq $oldhostname;
-
- eval {
- if(!$skipdns) {
- #add dns
- del_dns_record($dnszone, $dns, $oldhostname, $ip);
- add_dns_record($dnszone, $dns, $hostname, $ip);
- #add reverse dns
- del_dns_ptr_record($reversednszone, $reversedns, $ip);
- add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
- }
- };
-}
-
-sub del_ip {
- my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_;
-
- return if !$subnet || !$ip;
-
- my $ipaddr = NetAddr::IP->new($ip);
- $ip = $ipaddr->canon();
-
- my $ipamid = $zone->{ipam};
- my $dns = $zone->{dns};
- my $dnszone = $zone->{dnszone};
- my $reversedns = $zone->{reversedns};
- my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
- my $dnszoneprefix = $subnet->{dnszoneprefix};
- $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
-
- if(!$skipdns) {
- 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, $subnet, $ip);
- }
-
- eval {
- if(!$skipdns) {
- del_dns_record($dnszone, $dns, $hostname, $ip);
- del_dns_ptr_record($reversednszone, $reversedns, $ip);
- }
- };
- if ($@) {
- warn $@;
- }
-}
-
-1;
+++ /dev/null
-package PVE::Network::SDN::VnetPlugin;
-
-use strict;
-use warnings;
-
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::JSONSchema qw(get_standard_option);
-
-use PVE::SectionConfig;
-use base qw(PVE::SectionConfig);
-
-PVE::Cluster::cfs_register_file('sdn/vnets.cfg',
- sub { __PACKAGE__->parse_config(@_); },
- sub { __PACKAGE__->write_config(@_); });
-
-PVE::JSONSchema::register_standard_option('pve-sdn-vnet-id', {
- description => "The SDN vnet object identifier.",
- type => 'string', format => 'pve-sdn-vnet-id',
-});
-
-PVE::JSONSchema::register_format('pve-sdn-vnet-id', \&parse_sdn_vnet_id);
-sub parse_sdn_vnet_id {
- my ($id, $noerr) = @_;
-
- if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
- return undef if $noerr;
- die "vnet ID '$id' contains illegal characters\n";
- }
- die "vnet ID '$id' can't be more length than 8 characters\n" if length($id) > 8;
- return $id;
-}
-
-my $defaultData = {
-
- propertyList => {
- vnet => get_standard_option('pve-sdn-vnet-id',
- { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }),
- },
-};
-
-sub type {
- return 'vnet';
-}
-
-sub private {
- return $defaultData;
-}
-
-sub properties {
- return {
- zone => {
- type => 'string',
- description => "zone id",
- },
- type => {
- description => "Type",
- optional => 1,
- },
- tag => {
- type => 'integer',
- description => "vlan or vxlan id",
- },
- vlanaware => {
- type => 'boolean',
- description => 'Allow vm VLANs to pass through this vnet.',
- },
- alias => {
- type => 'string',
- description => "alias name of the vnet",
- pattern => qr/[\(\)-_.\w\d\s]{0,256}/i,
- maxLength => 256,
- optional => 1,
- },
- };
-}
-
-sub options {
- return {
- zone => { optional => 0},
- tag => { optional => 1},
- alias => { optional => 1 },
- vlanaware => { optional => 1 },
- };
-}
-
-sub on_delete_hook {
- my ($class, $vnetid, $vnet_cfg) = @_;
-
- #verify if subnets are associated
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid);
- raise_param_exc({ vnet => "Can't delete vnet if subnets exists"}) if $subnets;
-}
-
-sub on_update_hook {
- my ($class, $vnetid, $vnet_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
- my $tag = $vnet->{tag};
- my $vlanaware = $vnet->{vlanaware};
-
- #don't allow vlanaware change if subnets are defined
- if($vnet->{vlanaware}) {
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid);
- raise_param_exc({ vlanaware => "vlanaware vnet is not compatible with subnets"}) if $subnets;
- }
-}
-
-1;
+++ /dev/null
-package PVE::Network::SDN::Vnets;
-
-use strict;
-use warnings;
-
-use Net::IP;
-
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
-use PVE::Network::SDN;
-use PVE::Network::SDN::Subnets;
-use PVE::Network::SDN::Zones;
-
-use PVE::Network::SDN::VnetPlugin;
-PVE::Network::SDN::VnetPlugin->register();
-PVE::Network::SDN::VnetPlugin->init();
-
-sub sdn_vnets_config {
- my ($cfg, $id, $noerr) = @_;
-
- die "no sdn vnet ID specified\n" if !$id;
-
- my $scfg = $cfg->{ids}->{$id};
- die "sdn vnet '$id' does not exist\n" if (!$noerr && !$scfg);
-
- return $scfg;
-}
-
-sub config {
- return cfs_read_file("sdn/vnets.cfg");
-}
-
-sub write_config {
- my ($cfg) = @_;
-
- cfs_write_file("sdn/vnets.cfg", $cfg);
-}
-
-sub sdn_vnets_ids {
- my ($cfg) = @_;
-
- return sort keys %{$cfg->{ids}};
-}
-
-sub complete_sdn_vnet {
- my ($cmdname, $pname, $cvalue) = @_;
-
- my $cfg = PVE::Network::SDN::Vnets::config();
-
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ];
-}
-
-sub get_vnet {
- my ($vnetid, $running) = @_;
-
- return if !$vnetid;
-
- my $scfg = {};
- if($running) {
- my $cfg = PVE::Network::SDN::running_config();
- $scfg = $cfg->{vnets};
- } else {
- $scfg = PVE::Network::SDN::Vnets::config();
- }
-
- my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1);
-
- return $vnet;
-}
-
-sub get_subnets {
- my ($vnetid) = @_;
-
- return if !$vnetid;
-
- my $subnets = undef;
- my $subnets_cfg = PVE::Network::SDN::Subnets::config();
- foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) {
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid);
- next if !$subnet->{vnet} || $subnet->{vnet} ne $vnetid;
- $subnets->{$subnetid} = $subnet;
- }
- return $subnets;
-
-}
-
-sub get_subnet_from_vnet_cidr {
- my ($vnetid, $cidr) = @_;
-
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
- my $zoneid = $vnet->{zone};
- my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
-
- my ($ip, $mask) = split(/\//, $cidr);
- die "ip address is not in cidr format" if !$mask;
-
- my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $mask, $subnets);
-
- return ($zone, $subnetid, $subnet, $ip);
-}
-
-sub get_next_free_cidr {
- my ($vnetid, $hostname, $mac, $description, $ipversion, $skipdns) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
- my $zoneid = $vnet->{zone};
- my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
-
- return if !$zone->{ipam};
-
- $ipversion = 4 if !$ipversion;
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
- my $ip = undef;
- my $subnetcount = 0;
-
- foreach my $subnetid (sort keys %{$subnets}) {
- my $subnet = $subnets->{$subnetid};
- my $network = $subnet->{network};
-
- next if $ipversion != Net::IP::ip_get_version($network);
- $subnetcount++;
-
- eval {
- $ip = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns);
- };
- warn $@ if $@;
- last if $ip;
- }
- die "can't find any free ip" if !$ip && $subnetcount > 0;
-
- return $ip;
-}
-
-sub add_cidr {
- my ($vnetid, $cidr, $hostname, $mac, $description, $skipdns) = @_;
-
- return if !$vnetid;
-
- my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, undef, $skipdns);
-}
-
-sub update_cidr {
- my ($vnetid, $cidr, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
-
- return if !$vnetid;
-
- my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
- PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns);
-}
-
-sub del_cidr {
- my ($vnetid, $cidr, $hostname, $skipdns) = @_;
-
- return if !$vnetid;
-
- my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $skipdns);
-}
-
-
-
-1;
+++ /dev/null
-package PVE::Network::SDN::Zones;
-
-use strict;
-use warnings;
-
-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::Vnets;
-use PVE::Network::SDN::Zones::VlanPlugin;
-use PVE::Network::SDN::Zones::QinQPlugin;
-use PVE::Network::SDN::Zones::VxlanPlugin;
-use PVE::Network::SDN::Zones::EvpnPlugin;
-use PVE::Network::SDN::Zones::FaucetPlugin;
-use PVE::Network::SDN::Zones::SimplePlugin;
-use PVE::Network::SDN::Zones::Plugin;
-
-PVE::Network::SDN::Zones::VlanPlugin->register();
-PVE::Network::SDN::Zones::QinQPlugin->register();
-PVE::Network::SDN::Zones::VxlanPlugin->register();
-PVE::Network::SDN::Zones::EvpnPlugin->register();
-PVE::Network::SDN::Zones::FaucetPlugin->register();
-PVE::Network::SDN::Zones::SimplePlugin->register();
-PVE::Network::SDN::Zones::Plugin->init();
-
-my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
-
-sub sdn_zones_config {
- my ($cfg, $id, $noerr) = @_;
-
- die "no sdn zone 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/zones.cfg");
- return $config;
-}
-
-sub get_plugin_config {
- my ($vnet) = @_;
- my $zoneid = $vnet->{zone};
- my $zone_cfg = PVE::Network::SDN::Zones::config();
- return $zone_cfg->{ids}->{$zoneid};
-}
-
-sub write_config {
- my ($cfg) = @_;
-
- cfs_write_file("sdn/zones.cfg", $cfg);
-}
-
-sub sdn_zones_ids {
- my ($cfg) = @_;
-
- return sort keys %{$cfg->{ids}};
-}
-
-sub complete_sdn_zone {
- my ($cmdname, $pname, $cvalue) = @_;
-
- my $cfg = PVE::Network::SDN::running_config();
-
- return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
-}
-
-sub get_zone {
- my ($zoneid, $running) = @_;
-
- my $cfg = {};
- if($running) {
- my $cfg = PVE::Network::SDN::running_config();
- $cfg = $cfg->{vnets};
- } else {
- $cfg = PVE::Network::SDN::Zones::config();
- }
-
- my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
-
- return $zone;
-}
-
-
-sub generate_etc_network_config {
-
- my $cfg = PVE::Network::SDN::running_config();
-
- my $version = $cfg->{version};
- my $vnet_cfg = $cfg->{vnets};
- my $zone_cfg = $cfg->{zones};
- my $subnet_cfg = $cfg->{subnets};
- my $controller_cfg = $cfg->{controllers};
- return if !$vnet_cfg && !$zone_cfg;
-
- my $interfaces_config = PVE::INotify::read_file('interfaces');
-
- #generate configuration
- my $config = {};
- my $nodename = PVE::INotify::nodename();
-
- for my $id (sort keys %{$vnet_cfg->{ids}}) {
- my $vnet = $vnet_cfg->{ids}->{$id};
- my $zone = $vnet->{zone};
-
- if (!$zone) {
- warn "can't generate vnet '$id': no zone assigned!\n";
- next;
- }
-
- my $plugin_config = $zone_cfg->{ids}->{$zone};
-
- if (!defined($plugin_config)) {
- warn "can't generate vnet '$id': zone $zone don't exist\n";
- next;
- }
-
- next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
-
- my $controller;
- if (my $controllerid = $plugin_config->{controller}) {
- $controller = $controller_cfg->{ids}->{$controllerid};
- }
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- eval {
- $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
- };
- if (my $err = $@) {
- warn "zone $zone : vnet $id : $err\n";
- next;
- }
- }
-
- my $raw_network_config = "\#version:$version\n";
- foreach my $iface (sort keys %$config) {
- $raw_network_config .= "\n";
- $raw_network_config .= "auto $iface\n";
- $raw_network_config .= "iface $iface\n";
- foreach my $option (@{$config->{$iface}}) {
- $raw_network_config .= "\t$option\n";
- }
- }
-
- return $raw_network_config;
-}
-
-sub write_etc_network_config {
- my ($rawconfig) = @_;
-
- return if !$rawconfig;
-
- my $writefh = IO::File->new($local_network_sdn_file,">");
- print $writefh $rawconfig;
- $writefh->close();
-}
-
-sub read_etc_network_config_version {
- my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
-
- return if !defined($versionstr);
-
- if ($versionstr =~ m/^\#version:(\d+)$/) {
- return $1;
- }
-}
-
-sub ifquery_check {
-
- my $cmd = ['ifquery', '-a', '-c', '-o','json'];
-
- my $result = '';
- my $reader = sub { $result .= shift };
-
- eval {
- run_command($cmd, outfunc => $reader);
- };
-
- my $resultjson = decode_json($result);
- my $interfaces = {};
-
- foreach my $interface (@$resultjson) {
- my $name = $interface->{name};
- $interfaces->{$name} = {
- status => $interface->{status},
- config => $interface->{config},
- config_status => $interface->{config_status},
- };
- }
-
- return $interfaces;
-}
-
-my $warned_about_reload;
-
-sub status {
-
- my $err_config = undef;
-
- my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
- my $cfg = PVE::Network::SDN::running_config();
- my $sdn_version = $cfg->{version};
-
- return if !$sdn_version;
-
- if (!$local_version) {
- $err_config = "local sdn network configuration is not yet generated, please reload";
- if (!$warned_about_reload) {
- $warned_about_reload = 1;
- warn "$err_config\n";
- }
- } elsif ($local_version < $sdn_version) {
- $err_config = "local sdn network configuration is too old, please reload";
- if (!$warned_about_reload) {
- $warned_about_reload = 1;
- warn "$err_config\n";
- }
- } else {
- $warned_about_reload = 0;
- }
-
- my $status = ifquery_check();
-
- my $vnet_cfg = $cfg->{vnets};
- my $zone_cfg = $cfg->{zones};
- my $nodename = PVE::INotify::nodename();
-
- my $vnet_status = {};
- my $zone_status = {};
-
- for my $id (sort keys %{$zone_cfg->{ids}}) {
- next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename};
- $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
- }
-
- foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
- my $vnet = $vnet_cfg->{ids}->{$id};
- my $zone = $vnet->{zone};
- next if !defined($zone);
-
- my $plugin_config = $zone_cfg->{ids}->{$zone};
-
- if (!defined($plugin_config)) {
- $vnet_status->{$id}->{status} = 'error';
- $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured";
- next;
- }
-
- next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
-
- $vnet_status->{$id}->{zone} = $zone;
- $vnet_status->{$id}->{status} = 'available';
-
- if ($err_config) {
- $vnet_status->{$id}->{status} = 'pending';
- $vnet_status->{$id}->{statusmsg} = $err_config;
- next;
- }
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status);
- if (@{$err_msg} > 0) {
- $vnet_status->{$id}->{status} = 'error';
- $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg});
- $zone_status->{$id}->{status} = 'error';
- }
- }
-
- return ($zone_status, $vnet_status);
-}
-
-sub tap_create {
- my ($iface, $bridge) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
- if (!$vnet) { # fallback for classic bridge
- PVE::Network::tap_create($iface, $bridge);
- return;
- }
-
- my $plugin_config = get_plugin_config($vnet);
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
-}
-
-sub veth_create {
- my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
- if (!$vnet) { # fallback for classic bridge
- PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
- return;
- }
-
- my $plugin_config = get_plugin_config($vnet);
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
-}
-
-sub tap_plug {
- my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
- if (!$vnet) { # fallback for classic bridge
- my $interfaces_config = PVE::INotify::read_file('interfaces');
- my $opts = {};
- $opts->{learning} = 0 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'};
- PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts);
- return;
- }
-
- my $plugin_config = get_plugin_config($vnet);
- my $nodename = PVE::INotify::nodename();
-
- die "vnet $bridge is not allowed on this node\n"
- if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
-
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
-}
-
-sub add_bridge_fdb {
- my ($iface, $macaddr, $bridge, $firewall) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
- if (!$vnet) { # fallback for classic bridge
- PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall);
- return;
- }
-
- my $plugin_config = get_plugin_config($vnet);
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'};
-}
-
-sub del_bridge_fdb {
- my ($iface, $macaddr, $bridge, $firewall) = @_;
-
- my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
- if (!$vnet) { # fallback for classic bridge
- PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall);
- return;
- }
-
- my $plugin_config = get_plugin_config($vnet);
- my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
- PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'};
-}
-
-1;
-
+++ /dev/null
-package PVE::Network::SDN::Zones::EvpnPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Zones::VxlanPlugin;
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::Tools qw($IPV4RE);
-use PVE::INotify;
-use PVE::Cluster;
-use PVE::Tools;
-use Net::IP;
-
-use PVE::Network::SDN::Controllers::EvpnPlugin;
-
-use base('PVE::Network::SDN::Zones::VxlanPlugin');
-
-sub type {
- return 'evpn';
-}
-
-PVE::JSONSchema::register_format('pve-sdn-bgp-rt', \&pve_verify_sdn_bgp_rt);
-sub pve_verify_sdn_bgp_rt {
- my ($rt) = @_;
-
- if ($rt =~ m/^(\d+):(\d+)$/) {
- my $asn = $1;
- my $id = $2;
-
- if ($asn < 0 || $asn > 4294967295) {
- die "value does not look like a valid bgp route-target\n";
- }
- if ($id < 0 || $id > 4294967295) {
- die "value does not look like a valid bgp route-target\n";
- }
- } else {
- die "value does not look like a valid bgp route-target\n";
- }
- return $rt;
-}
-
-sub properties {
- return {
- 'vrf-vxlan' => {
- type => 'integer',
- description => "l3vni.",
- },
- 'controller' => {
- type => 'string',
- description => "Frr router name",
- },
- 'mac' => {
- type => 'string',
- description => "Anycast logical router mac address",
- optional => 1, format => 'mac-addr'
- },
- 'exitnodes' => get_standard_option('pve-node-list'),
- 'exitnodes-local-routing' => {
- type => 'boolean',
- description => "Allow exitnodes to connect to evpn guests",
- optional => 1
- },
- 'exitnodes-primary' => get_standard_option('pve-node', {
- description => "Force traffic to this exitnode first."}),
- 'advertise-subnets' => {
- type => 'boolean',
- description => "Advertise evpn subnets if you have silent hosts",
- optional => 1
- },
- 'disable-arp-nd-suppression' => {
- type => 'boolean',
- description => "Disable ipv4 arp && ipv6 neighbour discovery suppression",
- optional => 1
- },
- 'rt-import' => {
- type => 'string',
- description => "Route-Target import",
- optional => 1, format => 'pve-sdn-bgp-rt-list'
- }
- };
-}
-
-sub options {
- return {
- nodes => { optional => 1},
- 'vrf-vxlan' => { optional => 0 },
- controller => { optional => 0 },
- exitnodes => { optional => 1 },
- 'exitnodes-local-routing' => { optional => 1 },
- 'exitnodes-primary' => { optional => 1 },
- 'advertise-subnets' => { optional => 1 },
- 'disable-arp-nd-suppression' => { optional => 1 },
- 'rt-import' => { optional => 1 },
- mtu => { optional => 1 },
- mac => { optional => 1 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- my $tag = $vnet->{tag};
- my $alias = $vnet->{alias};
- my $mac = $plugin_config->{'mac'};
-
- my $vrf_iface = "vrf_$zoneid";
- my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
- my $local_node = PVE::INotify::nodename();
-
- die "missing vxlan tag" if !$tag;
- die "missing controller" if !$controller;
- warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware};
-
- my @peers = PVE::Tools::split_list($controller->{'peers'});
- my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg);
- my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
- my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
- my $is_evpn_gateway = $plugin_config->{'exitnodes'}->{$local_node};
- my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'};
-
-
- my $mtu = 1450;
- $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu};
- $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
-
- #vxlan interface
- my $vxlan_iface = "vxlan_$vnetid";
- my @iface_config = ();
- push @iface_config, "vxlan-id $tag";
- push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
- push @iface_config, "bridge-learning off";
- push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'};
-
- push @iface_config, "mtu $mtu" if $mtu;
- push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface};
-
- #vnet bridge
- @iface_config = ();
-
- my $address = {};
- my $ipv4 = undef;
- my $ipv6 = undef;
- my $enable_forward_v4 = undef;
- my $enable_forward_v6 = undef;
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
- foreach my $subnetid (sort keys %{$subnets}) {
- my $subnet = $subnets->{$subnetid};
- my $cidr = $subnet->{cidr};
- my $mask = $subnet->{mask};
-
- my $gateway = $subnet->{gateway};
- if ($gateway) {
- push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway});
- $address->{$gateway} = 1;
- }
-
- my $iptables = undef;
- my $checkrouteip = undef;
- my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4;
-
- if ($ipversion == 6) {
- $ipv6 = 1;
- $iptables = "ip6tables";
- $checkrouteip = '2001:4860:4860::8888';
- $enable_forward_v6 = 1 if $gateway;
- } else {
- $ipv4 = 1;
- $iptables = "iptables";
- $checkrouteip = '8.8.8.8';
- $enable_forward_v4 = 1 if $gateway;
- }
-
- if ($subnet->{snat}) {
-
- #find outgoing interface
- my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip);
- if ($outip && $outiface && $is_evpn_gateway) {
- #use snat, faster than masquerade
- push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
- push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
- #add conntrack zone once on outgoing interface
- push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1";
- push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1";
- }
- }
- }
-
- push @iface_config, "hwaddress $mac" if $mac;
- push @iface_config, "bridge_ports $vxlan_iface";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "alias $alias" if $alias;
- push @iface_config, "ip-forward on" if $enable_forward_v4;
- push @iface_config, "ip6-forward on" if $enable_forward_v6;
- push @iface_config, "arp-accept on" if $ipv4||$ipv6;
- push @iface_config, "vrf $vrf_iface" if $vrf_iface;
- push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
- if ($vrf_iface) {
- #vrf interface
- @iface_config = ();
- push @iface_config, "vrf-table auto";
- if(!$is_evpn_gateway) {
- push @iface_config, "post-up ip route add vrf $vrf_iface unreachable default metric 4278198272";
- } else {
- push @iface_config, "post-up ip route del vrf $vrf_iface unreachable default metric 4278198272";
- }
-
- push(@{$config->{$vrf_iface}}, @iface_config) if !$config->{$vrf_iface};
-
- if ($vrfvxlan) {
- #l3vni vxlan interface
- my $iface_vrf_vxlan = "vrfvx_$zoneid";
- @iface_config = ();
- push @iface_config, "vxlan-id $vrfvxlan";
- push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
- push @iface_config, "bridge-learning off";
- push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'};
- push @iface_config, "mtu $mtu" if $mtu;
- push(@{$config->{$iface_vrf_vxlan}}, @iface_config) if !$config->{$iface_vrf_vxlan};
-
- #l3vni bridge
- my $brvrf = "vrfbr_$zoneid";
- @iface_config = ();
- push @iface_config, "bridge-ports $iface_vrf_vxlan";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "vrf $vrf_iface";
- push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf};
- }
-
- if ( $is_evpn_gateway && $exitnodes_local_routing ) {
- #add a veth pair for local cross-vrf routing
- my $iface_xvrf = "xvrf_$zoneid";
- my $iface_xvrfp = "xvrfp_$zoneid";
-
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "address 10.255.255.1/30";
- push @iface_config, "veth-peer-name $iface_xvrfp";
- push @iface_config, "mtu ".($mtu+50) if $mtu;
- push(@{$config->{$iface_xvrf}}, @iface_config) if !$config->{$iface_xvrf};
-
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "address 10.255.255.2/30";
- push @iface_config, "veth-peer-name $iface_xvrf";
- push @iface_config, "vrf $vrf_iface";
- push @iface_config, "mtu ".($mtu+50) if $mtu;
- push(@{$config->{$iface_xvrfp}}, @iface_config) if !$config->{$iface_xvrfp};
- }
- }
- return $config;
-}
-
-sub on_update_hook {
- my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
-
- # verify that controller exist
- my $controller = $zone_cfg->{ids}->{$zoneid}->{controller};
- if (!defined($controller_cfg->{ids}->{$controller})) {
- die "controller $controller don't exist";
- } else {
- die "$controller is not a evpn controller type" if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn';
- }
-
- #vrf-vxlan need to be defined
-
- my $vrfvxlan = $zone_cfg->{ids}->{$zoneid}->{'vrf-vxlan'};
- # verify that vrf-vxlan is not already declared in another zone
- foreach my $id (keys %{$zone_cfg->{ids}}) {
- next if $id eq $zoneid;
- die "vrf-vxlan $vrfvxlan is already declared in $id"
- if (defined($zone_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $zone_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
- }
-
- if (!defined($zone_cfg->{ids}->{$zoneid}->{'mac'})) {
- my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
- $zone_cfg->{ids}->{$zoneid}->{'mac'} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
- }
-}
-
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
- my $tag = $vnet->{tag};
-
- raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag);
- raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216;
-
- # verify that tag is not already defined globally (vxlan-id are unique)
- foreach my $id (keys %{$vnet_cfg->{ids}}) {
- next if $id eq $vnetid;
- my $othervnet = $vnet_cfg->{ids}->{$id};
- my $other_tag = $othervnet->{tag};
- my $other_zoneid = $othervnet->{zone};
- my $other_zone = $zone_cfg->{ids}->{$other_zoneid};
- next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn';
- raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag;
- }
-}
-
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Zones::FaucetPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Zones::VlanPlugin;
-
-use base('PVE::Network::SDN::Zones::VlanPlugin');
-
-sub type {
- return 'faucet';
-}
-
-sub properties {
- return {
- 'dp-id' => {
- type => 'integer',
- description => 'Faucet dataplane id',
- },
- };
-}
-
-sub options {
-
- return {
- nodes => { optional => 1},
- 'dp-id' => { optional => 0 },
-# 'uplink-id' => { optional => 0 },
- 'controller' => { optional => 0 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $controller, $config) = @_;
-
- my $mtu = $vnet->{mtu};
- my $uplink = $plugin_config->{'uplink-id'};
- my $dpid = $plugin_config->{'dp-id'};
- my $dphex = printf("%x",$dpid); #fixme :should be 16characters hex
-
- my $iface = $uplinks->{$uplink}->{name};
- $iface = "uplink${uplink}" if !$iface;
-
- #tagged interface
- my @iface_config = ();
- push @iface_config, "ovs_type OVSPort";
- push @iface_config, "ovs_bridge $zoneid";
- push @iface_config, "ovs_mtu $mtu" if $mtu;
- push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
-
- #vnet bridge
- @iface_config = ();
- push @iface_config, "ovs_port $iface";
- push @iface_config, "ovs_type OVSBridge";
- push @iface_config, "ovs_mtu $mtu" if $mtu;
-
- push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex";
- push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true";
- push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure";
- push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653";
-
- push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid};
-
- return $config;
-}
-
-
-1;
-
-
+++ /dev/null
-SOURCES=Plugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetPlugin.pm EvpnPlugin.pm QinQPlugin.pm SimplePlugin.pm
-
-
-PERL5DIR=${DESTDIR}/usr/share/perl5
-
-.PHONY: install
-install:
- for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Zones/$$i; done
+++ /dev/null
-package PVE::Network::SDN::Zones::Plugin;
-
-use strict;
-use warnings;
-
-use PVE::Tools qw(run_command);
-use PVE::JSONSchema;
-use PVE::Cluster;
-use PVE::Network;
-
-use PVE::JSONSchema qw(get_standard_option);
-use base qw(PVE::SectionConfig);
-
-PVE::Cluster::cfs_register_file(
- 'sdn/zones.cfg',
- sub { __PACKAGE__->parse_config(@_); },
- sub { __PACKAGE__->write_config(@_); },
-);
-
-PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', {
- description => "The SDN zone object identifier.",
- type => 'string', format => 'pve-sdn-zone-id',
-});
-
-PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id);
-sub parse_sdn_zone_id {
- my ($id, $noerr) = @_;
-
- if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
- return undef if $noerr;
- die "zone ID '$id' contains illegal characters\n";
- }
- die "zone ID '$id' can't be more length than 8 characters\n" if length($id) > 8;
- return $id;
-}
-
-my $defaultData = {
-
- propertyList => {
- type => {
- description => "Plugin type.",
- type => 'string', format => 'pve-configid',
- type => 'string',
- },
- nodes => get_standard_option('pve-node-list', { optional => 1 }),
- zone => get_standard_option('pve-sdn-zone-id', {
- completion => \&PVE::Network::SDN::Zones::complete_sdn_zone,
- }),
- ipam => {
- type => 'string',
- description => "use a specific ipam",
- optional => 1,
- },
- },
-};
-
-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 decode_value {
- my ($class, $type, $key, $value) = @_;
-
- if ($key eq 'nodes' || $key eq 'exitnodes') {
- my $res = {};
-
- foreach my $node (PVE::Tools::split_list($value)) {
- if (PVE::JSONSchema::pve_verify_node_name($node)) {
- $res->{$node} = 1;
- }
- }
-
- return $res;
- }
-
- return $value;
-}
-
-sub encode_value {
- my ($class, $type, $key, $value) = @_;
-
- if ($key eq 'nodes' || $key eq 'exitnodes') {
- return join(',', keys(%$value));
- }
-
- return $value;
-}
-
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub generate_controller_config {
- my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub generate_controller_vnet_config {
- my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_;
-
-}
-
-sub write_controller_config {
- my ($class, $plugin_config, $config) = @_;
-
- die "please implement inside plugin";
-}
-
-sub controller_reload {
- my ($class) = @_;
-
- die "please implement inside plugin";
-}
-
-sub on_delete_hook {
- my ($class, $zoneid, $vnet_cfg) = @_;
-
- # verify that no vnet are associated to this zone
- foreach my $id (keys %{$vnet_cfg->{ids}}) {
- my $vnet = $vnet_cfg->{ids}->{$id};
- die "zone $zoneid is used by vnet $id"
- if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid);
- }
-}
-
-sub on_update_hook {
- my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
-
- # do nothing by default
-}
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- # do nothing by default
-}
-
-#helpers
-sub parse_tag_number_or_range {
- my ($str, $max, $tag) = @_;
-
- my @elements = split(/,/, $str);
- my $count = 0;
- my $allowed = undef;
-
- die "extraneous commas in list\n" if $str ne join(',', @elements);
- foreach my $item (@elements) {
- if ($item =~ m/^([0-9]+)-([0-9]+)$/) {
- $count += 2;
- my ($port1, $port2) = ($1, $2);
- die "invalid port '$port1'\n" if $port1 > $max;
- die "invalid port '$port2'\n" if $port2 > $max;
- die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2;
-
- if ($tag && $tag >= $port1 && $tag <= $port2){
- $allowed = 1;
- last;
- }
-
- } elsif ($item =~ m/^([0-9]+)$/) {
- $count += 1;
- my $port = $1;
- die "invalid port '$port'\n" if $port > $max;
-
- if ($tag && $tag == $port){
- $allowed = 1;
- last;
- }
- }
- }
- die "tag $tag is not allowed" if $tag && !$allowed;
-
- return (scalar(@elements) > 1);
-}
-
-sub status {
- my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
-
- my $err_msg = [];
-
- # ifaces to check
- my $ifaces = [ $vnetid ];
-
- foreach my $iface (@{$ifaces}) {
- if (!$status->{$iface}->{status}) {
- push @$err_msg, "missing $iface";
- } elsif ($status->{$iface}->{status} ne 'pass') {
- push @$err_msg, "error $iface";
- }
- }
- return $err_msg;
-}
-
-
-sub tap_create {
- my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_;
-
- PVE::Network::tap_create($iface, $vnetid);
-}
-
-sub veth_create {
- my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_;
-
- PVE::Network::veth_create($veth, $vethpeer, $vnetid, $hwaddr);
-}
-
-sub tap_plug {
- my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_;
-
- my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$vnetid/bridge/vlan_filtering");
- die "vm vlans are not allowed on vnet $vnetid" if !$vlan_aware && ($tag || $trunks);
-
- my $opts = {};
- $opts->{learning} = 0 if $plugin_config->{'bridge-disable-mac-learning'};
- PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate, $opts);
-}
-
-#helper
-
-sub get_uplink_iface {
- my ($interfaces_config, $uplink) = @_;
-
- my $iface = undef;
- foreach my $id (keys %{$interfaces_config->{ifaces}}) {
- my $interface = $interfaces_config->{ifaces}->{$id};
- if (my $iface_uplink = $interface->{'uplink-id'}) {
- next if $iface_uplink ne $uplink;
- if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') {
- warn "uplink $uplink is not a physical or bond interface";
- next;
- }
- $iface = $id;
- }
- }
-
- #create a dummy uplink interface if no uplink found
- if(!$iface) {
- warn "can't find uplink $uplink in physical interface";
- $iface = "uplink${uplink}";
- }
-
- return $iface;
-}
-
-sub get_local_route_ip {
- my ($targetip) = @_;
-
- my $ip = undef;
- my $interface = undef;
-
- run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub {
- if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) {
- $ip = $1;
- }
- if ($_[0] =~ m/dev (\S+)/) {
- $interface = $1;
- }
-
- });
- return ($ip, $interface);
-}
-
-
-sub find_local_ip_interface_peers {
- my ($peers, $iface) = @_;
-
- my $network_config = PVE::INotify::read_file('interfaces');
- my $ifaces = $network_config->{ifaces};
-
- #if iface is defined, return ip if exist (if not,try to find it on other ifaces)
- if ($iface) {
- my $ip = $ifaces->{$iface}->{address};
- return ($ip,$iface) if $ip;
- }
-
- #is a local ip member of peers list ?
- foreach my $address (@{$peers}) {
- while (my $interface = each %$ifaces) {
- my $ip = $ifaces->{$interface}->{address};
- if ($ip && $ip eq $address) {
- return ($ip, $interface);
- }
- }
- }
-
- #if peer is remote, find source with ip route
- foreach my $address (@{$peers}) {
- my ($ip, $interface) = get_local_route_ip($address);
- return ($ip, $interface);
- }
-}
-
-sub find_bridge {
- my ($bridge) = @_;
-
- die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge";
-}
-
-sub is_vlanaware {
- my ($bridge) = @_;
-
- return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering");
-}
-
-sub is_ovs {
- my ($bridge) = @_;
-
- my $is_ovs = !-d "/sys/class/net/$bridge/brif";
- return $is_ovs;
-}
-
-sub get_bridge_ifaces {
- my ($bridge) = @_;
-
- my @bridge_ifaces = ();
- my $dir = "/sys/class/net/$bridge/brif";
- PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub {
- push @bridge_ifaces, $_[0];
- });
-
- return @bridge_ifaces;
-}
-1;
+++ /dev/null
-package PVE::Network::SDN::Zones::QinQPlugin;
-
-use strict;
-use warnings;
-
-use PVE::Exception qw(raise raise_param_exc);
-
-use PVE::Network::SDN::Zones::Plugin;
-
-use base('PVE::Network::SDN::Zones::Plugin');
-
-sub type {
- return 'qinq';
-}
-
-sub properties {
- return {
- tag => {
- type => 'integer',
- minimum => 0,
- description => "Service-VLAN Tag",
- },
- mtu => {
- type => 'integer',
- description => "MTU",
- optional => 1,
- },
- 'vlan-protocol' => {
- type => 'string',
- enum => ['802.1q', '802.1ad'],
- default => '802.1q',
- optional => 1,
- }
- };
-}
-
-sub options {
- return {
- nodes => { optional => 1},
- 'tag' => { optional => 0 },
- 'bridge' => { optional => 0 },
- 'bridge-disable-mac-learning' => { optional => 1 },
- 'mtu' => { optional => 1 },
- 'vlan-protocol' => { optional => 1 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- my ($bridge, $mtu, $stag) = $plugin_config->@{'bridge', 'mtu', 'tag'};
- my $vlanprotocol = $plugin_config->{'vlan-protocol'};
-
- PVE::Network::SDN::Zones::Plugin::find_bridge($bridge);
-
- my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
- my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
-
- my @iface_config = ();
- my $zone_notag_uplink = "ln_${zoneid}";
- my $zone_notag_uplinkpeer = "pr_${zoneid}";
- my $zone = "z_${zoneid}";
-
- my $vnet_bridge_ports = "";
- if (my $ctag = $vnet->{tag}) {
- $vnet_bridge_ports = "$zone.$ctag";
- } else {
- $vnet_bridge_ports = $zone_notag_uplinkpeer;
- }
-
- my $zone_bridge_ports = "";
- if ($is_ovs) {
- # ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet
-
- $vlanprotocol = "802.1q" if !$vlanprotocol;
- my $svlan_iface = "sv_".$zoneid;
-
- # ovs dot1q-tunnel port
- @iface_config = ();
- push @iface_config, "ovs_type OVSIntPort";
- push @iface_config, "ovs_bridge $bridge";
- push @iface_config, "ovs_mtu $mtu" if $mtu;
- push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol";
- push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface};
-
- # redefine main ovs bridge, ifupdown2 will merge ovs_ports
- @{$config->{$bridge}}[0] = "ovs_ports" if !@{$config->{$bridge}}[0];
- my @ovs_ports = split / / , @{$config->{$bridge}}[0];
- @{$config->{$bridge}}[0] .= " $svlan_iface" if !grep( $_ eq $svlan_iface, @ovs_ports );
-
- $zone_bridge_ports = $svlan_iface;
-
- } elsif ($vlan_aware) {
- # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet
-
- if ($vlanprotocol) {
- @iface_config = ();
- push @iface_config, "bridge-vlan-protocol $vlanprotocol";
- push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge};
- }
-
- $zone_bridge_ports = "$bridge.$stag";
-
- } else {
- # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet
-
- my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
-
- for my $bridge_iface (@bridge_ifaces) {
- # use named vlan interface to avoid too long names
- my $svlan_iface = "sv_$zoneid";
-
- # svlan
- @iface_config = ();
- push @iface_config, "vlan-raw-device $bridge_iface";
- push @iface_config, "vlan-id $stag";
- push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol;
- push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface};
-
- $zone_bridge_ports = $svlan_iface;
- last;
- }
- }
-
- # veth peer for notag vnet
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "veth-peer-name $zone_notag_uplinkpeer";
- push(@{$config->{$zone_notag_uplink}}, @iface_config) if !$config->{$zone_notag_uplink};
-
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "veth-peer-name $zone_notag_uplink";
- push(@{$config->{$zone_notag_uplinkpeer}}, @iface_config) if !$config->{$zone_notag_uplinkpeer};
-
- # zone vlan aware bridge
- @iface_config = ();
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "bridge-stp off";
- push @iface_config, "bridge-ports $zone_bridge_ports $zone_notag_uplink";
- push @iface_config, "bridge-fd 0";
- push @iface_config, "bridge-vlan-aware yes";
- push @iface_config, "bridge-vids 2-4094";
- push(@{$config->{$zone}}, @iface_config) if !$config->{$zone};
-
- # vnet bridge
- @iface_config = ();
- push @iface_config, "bridge_ports $vnet_bridge_ports";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- if($vnet->{vlanaware}) {
- push @iface_config, "bridge-vlan-aware yes";
- push @iface_config, "bridge-vids 2-4094";
- }
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "alias $vnet->{alias}" if $vnet->{alias};
- push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-}
-
-sub status {
- my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
-
- my $bridge = $plugin_config->{bridge};
- my $err_msg = [];
-
- if (!-d "/sys/class/net/$bridge") {
- push @$err_msg, "missing $bridge";
- return $err_msg;
- }
-
- my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
-
- my $tag = $vnet->{tag};
- my $vnet_uplink = "ln_".$vnetid;
- my $vnet_uplinkpeer = "pr_".$vnetid;
- my $zone_notag_uplink = "ln_".$zone;
- my $zone_notag_uplinkpeer = "pr_".$zone;
- my $zonebridge = "z_$zone";
-
- # ifaces to check
- my $ifaces = [ $vnetid, $bridge ];
-
- push @$ifaces, $zonebridge;
- push @$ifaces, $zone_notag_uplink;
- push @$ifaces, $zone_notag_uplinkpeer;
-
- if (!$vlan_aware) {
- my $svlan_iface = "sv_$zone";
- push @$ifaces, $svlan_iface;
- }
-
- foreach my $iface (@{$ifaces}) {
- if (!$status->{$iface}->{status}) {
- push @$err_msg, "missing $iface";
- } elsif ($status->{$iface}->{status} ne 'pass') {
- push @$err_msg, "error $iface";
- }
- }
- return $err_msg;
-}
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
-
- my $tag = $vnet->{tag};
- raise_param_exc({ tag => "VLAN tag maximal value is 4096" }) if $tag && $tag > 4096;
-
- # verify that tag is not already defined in another vnet on same zone
- for my $id (sort keys %{$vnet_cfg->{ids}}) {
- next if $id eq $vnetid;
- my $other_vnet = $vnet_cfg->{ids}->{$id};
- next if $vnet->{zone} ne $other_vnet->{zone};
- my $other_tag = $other_vnet->{tag};
- if ($tag) {
- raise_param_exc({ tag => "tag $tag already exist in zone $vnet->{zone} vnet $id"})
- if $other_tag && $tag eq $other_tag;
- } else {
- raise_param_exc({ tag => "tag-less vnet already exists in zone $vnet->{zone} vnet $id"})
- if !$other_tag;
- }
- }
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Zones::SimplePlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Zones::Plugin;
-use PVE::Exception qw(raise raise_param_exc);
-use PVE::Cluster;
-use PVE::Tools;
-
-use base('PVE::Network::SDN::Zones::Plugin');
-
-sub type {
- return 'simple';
-}
-
-sub properties {
- return {
- dns => {
- type => 'string',
- description => "dns api server",
- },
- reversedns => {
- type => 'string',
- description => "reverse dns api server",
- },
- dnszone => {
- type => 'string', format => 'dns-name',
- description => "dns domain zone ex: mydomain.com",
- }
- };
-}
-
-sub options {
- return {
- nodes => { optional => 1},
- mtu => { optional => 1 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- return $config if$config->{$vnetid}; # nothing to do
-
- my $mac = $vnet->{mac};
- my $alias = $vnet->{alias};
- my $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
-
- # vnet bridge
- my @iface_config = ();
-
- my $address = {};
- my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
-
- my $ipv4 = undef;
- my $ipv6 = undef;
- my $enable_forward_v4 = undef;
- my $enable_forward_v6 = undef;
-
- foreach my $subnetid (sort keys %{$subnets}) {
- my $subnet = $subnets->{$subnetid};
- my $cidr = $subnet->{cidr};
- my $mask = $subnet->{mask};
-
- my $gateway = $subnet->{gateway};
- if ($gateway) {
- push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway});
- $address->{$gateway} = 1;
- }
-
- my $iptables = undef;
- my $checkrouteip = undef;
- my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4;
-
- if ( $ipversion == 6) {
- $ipv6 = 1;
- $iptables = "ip6tables";
- $checkrouteip = '2001:4860:4860::8888';
- $enable_forward_v6 = 1 if $gateway;
- } else {
- $ipv4 = 1;
- $iptables = "iptables";
- $checkrouteip = '8.8.8.8';
- $enable_forward_v4 = 1 if $gateway;
- }
-
- #add route for /32 pointtopoint
- push @iface_config, "up ip route add $cidr dev $vnetid" if $mask == 32 && $ipversion == 4;
- if ($subnet->{snat}) {
- #find outgoing interface
- my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip);
- if ($outip && $outiface) {
- #use snat, faster than masquerade
- push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
- push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
- #add conntrack zone once on outgoing interface
- push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1";
- push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1";
- }
- }
- }
-
- push @iface_config, "hwaddress $mac" if $mac;
- push @iface_config, "bridge_ports none";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- if ($vnet->{vlanaware}) {
- push @iface_config, "bridge-vlan-aware yes";
- push @iface_config, "bridge-vids 2-4094";
- }
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "alias $alias" if $alias;
- push @iface_config, "ip-forward on" if $enable_forward_v4;
- push @iface_config, "ip6-forward on" if $enable_forward_v6;
-
- push @{$config->{$vnetid}}, @iface_config;
-
- return $config;
-}
-
-sub status {
- my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
-
- # ifaces to check
- my $ifaces = [ $vnetid ];
- my $err_msg = [];
- foreach my $iface (@{$ifaces}) {
- if (!$status->{$iface}->{status}) {
- push @$err_msg, "missing $iface";
- } elsif ($status->{$iface}->{status} ne 'pass') {
- push @$err_msg, "error iface $iface";
- }
- }
- return $err_msg;
-}
-
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
- my $tag = $vnet->{tag};
-
- raise_param_exc({ tag => "vlan tag is not allowed on simple zone"}) if defined($tag);
-
- if (!defined($vnet->{mac})) {
- my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
- $vnet->{mac} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
- }
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Zones::VlanPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Zones::Plugin;
-use PVE::Exception qw(raise raise_param_exc);
-
-use base('PVE::Network::SDN::Zones::Plugin');
-
-sub type {
- return 'vlan';
-}
-
-PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange);
-sub pve_verify_sdn_vlanrange {
- my ($vlanstr) = @_;
-
- PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096');
-
- return $vlanstr;
-}
-
-sub properties {
- return {
- 'bridge' => {
- type => 'string',
- },
- 'bridge-disable-mac-learning' => {
- type => 'boolean',
- description => "Disable auto mac learning.",
- }
- };
-}
-
-sub options {
-
- return {
- nodes => { optional => 1},
- 'bridge' => { optional => 0 },
- 'bridge-disable-mac-learning' => { optional => 1 },
- mtu => { optional => 1 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- my $bridge = $plugin_config->{bridge};
- PVE::Network::SDN::Zones::Plugin::find_bridge($bridge);
-
- my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
- my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
-
- my $tag = $vnet->{tag};
- my $alias = $vnet->{alias};
- my $mtu = $plugin_config->{mtu};
-
- my $vnet_uplink = "ln_".$vnetid;
- my $vnet_uplinkpeer = "pr_".$vnetid;
-
- my @iface_config = ();
-
- if($is_ovs) {
-
- # keep vmbrXvY for compatibility with existing network
- # eth0----ovs vmbr0--(ovsintport tag)---->vnet---->vm
-
- @iface_config = ();
- push @iface_config, "ovs_type OVSIntPort";
- push @iface_config, "ovs_bridge $bridge";
- push @iface_config, "ovs_mtu $mtu" if $mtu;
- if($vnet->{vlanaware}) {
- push @iface_config, "ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=$tag";
- } else {
- push @iface_config, "ovs_options tag=$tag";
- }
- push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink};
-
- #redefine main ovs bridge, ifupdown2 will merge ovs_ports
- @iface_config = ();
- push @iface_config, "ovs_ports $vnet_uplink";
- push(@{$config->{$bridge}}, @iface_config);
-
- } elsif ($vlan_aware) {
- # eth0----vlanaware bridge vmbr0--(vmbr0.X tag)---->vnet---->vm
- $vnet_uplink = "$bridge.$tag";
- } else {
-
- # keep vmbrXvY for compatibility with existing network
- # eth0<---->eth0.X----vmbr0v10------vnet---->vm
-
- my $bridgevlan = $bridge."v".$tag;
-
- my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
-
- my $bridge_ports = "";
- foreach my $bridge_iface (@bridge_ifaces) {
- $bridge_ports .= " $bridge_iface.$tag";
- }
-
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "veth-peer-name $vnet_uplinkpeer";
- push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink};
-
- @iface_config = ();
- push @iface_config, "link-type veth";
- push @iface_config, "veth-peer-name $vnet_uplink";
- push(@{$config->{$vnet_uplinkpeer}}, @iface_config) if !$config->{$vnet_uplinkpeer};
-
- @iface_config = ();
- push @iface_config, "bridge_ports $bridge_ports $vnet_uplinkpeer";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- push(@{$config->{$bridgevlan}}, @iface_config) if !$config->{$bridgevlan};
- }
-
- #vnet bridge
- @iface_config = ();
- push @iface_config, "bridge_ports $vnet_uplink";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- if($vnet->{vlanaware}) {
- push @iface_config, "bridge-vlan-aware yes";
- push @iface_config, "bridge-vids 2-4094";
- }
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "alias $alias" if $alias;
- push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
- return $config;
-}
-
-sub status {
- my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
-
- my $bridge = $plugin_config->{bridge};
-
- my $err_msg = [];
- if (!-d "/sys/class/net/$bridge") {
- push @$err_msg, "missing $bridge";
- return $err_msg;
- }
-
- my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
- my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
-
- my $tag = $vnet->{tag};
- my $vnet_uplink = "ln_".$vnetid;
- my $vnet_uplinkpeer = "pr_".$vnetid;
-
- # ifaces to check
- my $ifaces = [ $vnetid, $bridge ];
- if($is_ovs) {
- push @$ifaces, $vnet_uplink;
- } elsif (!$vlan_aware) {
- my $bridgevlan = $bridge."v".$tag;
- push @$ifaces, $bridgevlan;
- push @$ifaces, $vnet_uplink;
- push @$ifaces, $vnet_uplinkpeer;
- }
-
- foreach my $iface (@{$ifaces}) {
- if (!$status->{$iface}->{status}) {
- push @$err_msg, "missing $iface";
- } elsif ($status->{$iface}->{status} ne 'pass') {
- push @$err_msg, "error iface $iface";
- }
- }
- return $err_msg;
-}
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
- my $tag = $vnet->{tag};
-
- raise_param_exc({ tag => "missing vlan tag"}) if !defined($vnet->{tag});
- raise_param_exc({ tag => "vlan tag max value is 4096"}) if $vnet->{tag} > 4096;
-
- # verify that tag is not already defined in another vnet on same zone
- foreach my $id (keys %{$vnet_cfg->{ids}}) {
- next if $id eq $vnetid;
- my $othervnet = $vnet_cfg->{ids}->{$id};
- my $other_tag = $othervnet->{tag};
- next if $vnet->{zone} ne $othervnet->{zone};
- raise_param_exc({ tag => "tag $tag already exist in vnet $id"}) if $other_tag && $tag eq $other_tag;
- }
-}
-
-1;
-
-
+++ /dev/null
-package PVE::Network::SDN::Zones::VxlanPlugin;
-
-use strict;
-use warnings;
-use PVE::Network::SDN::Zones::Plugin;
-use PVE::Tools qw($IPV4RE);
-use PVE::INotify;
-use PVE::Network::SDN::Controllers::EvpnPlugin;
-use PVE::Exception qw(raise raise_param_exc);
-
-use base('PVE::Network::SDN::Zones::Plugin');
-
-PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange);
-sub pve_verify_sdn_vxlanrange {
- my ($vxlanstr) = @_;
-
- PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216');
-
- return $vxlanstr;
-}
-
-sub type {
- return 'vxlan';
-}
-
-sub properties {
- return {
- 'peers' => {
- description => "peers address list.",
- type => 'string', format => 'ip-list'
- },
- };
-}
-
-sub options {
- return {
- nodes => { optional => 1},
- peers => { optional => 0 },
- mtu => { optional => 1 },
- dns => { optional => 1 },
- reversedns => { optional => 1 },
- dnszone => { optional => 1 },
- ipam => { optional => 1 },
- };
-}
-
-# Plugin implementation
-sub generate_sdn_config {
- my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
-
- my $tag = $vnet->{tag};
- my $alias = $vnet->{alias};
- my $multicastaddress = $plugin_config->{'multicast-address'};
- my @peers;
- @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
- my $vxlan_iface = "vxlan_$vnetid";
-
- die "missing vxlan tag" if !$tag;
-
- my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers);
-
- my $mtu = 1450;
- $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu};
- $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
-
- #vxlan interface
- my @iface_config = ();
- push @iface_config, "vxlan-id $tag";
-
- for my $address (@peers) {
- next if $address eq $ifaceip;
- push @iface_config, "vxlan_remoteip $address";
- }
-
-
- push @iface_config, "mtu $mtu" if $mtu;
- push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface};
-
- #vnet bridge
- @iface_config = ();
- push @iface_config, "bridge_ports $vxlan_iface";
- push @iface_config, "bridge_stp off";
- push @iface_config, "bridge_fd 0";
- if ($vnet->{vlanaware}) {
- push @iface_config, "bridge-vlan-aware yes";
- push @iface_config, "bridge-vids 2-4094";
- }
- push @iface_config, "mtu $mtu" if $mtu;
- push @iface_config, "alias $alias" if $alias;
- push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
-
- return $config;
-}
-
-sub vnet_update_hook {
- my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
-
- my $vnet = $vnet_cfg->{ids}->{$vnetid};
- my $tag = $vnet->{tag};
-
- raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag);
- raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216;
-
- # verify that tag is not already defined globally (vxlan-id are unique)
- for my $id (sort keys %{$vnet_cfg->{ids}}) {
- next if $id eq $vnetid;
- my $othervnet = $vnet_cfg->{ids}->{$id};
- my $other_tag = $othervnet->{tag};
- my $other_zoneid = $othervnet->{zone};
- my $other_zone = $zone_cfg->{ids}->{$other_zoneid};
- next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn';
- raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag;
- }
-}
-
-1;
-
-
--- /dev/null
+all:
+ $(MAKE) -C PVE
+
+.PHONY: clean
+clean:
+ $(MAKE) -C test $@
+ $(MAKE) -C PVE $@
+
+.PHONY: test
+test:
+ $(MAKE) -C $@
+
+.PHONY: install
+install:
+ $(MAKE) -C PVE $@
--- /dev/null
+
+.PHONY: install
+install:
+ make -C Network install
--- /dev/null
+SOURCES=SDN.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/$$i; done
+ make -C SDN install
--- /dev/null
+package PVE::API2::Network::SDN;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
+use PVE::Exception qw(raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::SafeSyslog;
+use PVE::Tools qw(run_command);
+use PVE::Network::SDN;
+
+use PVE::API2::Network::SDN::Controllers;
+use PVE::API2::Network::SDN::Vnets;
+use PVE::API2::Network::SDN::Zones;
+use PVE::API2::Network::SDN::Ipams;
+use PVE::API2::Network::SDN::Dns;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Vnets",
+ path => 'vnets',
+});
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Zones",
+ path => 'zones',
+});
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Controllers",
+ path => 'controllers',
+});
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Ipams",
+ path => 'ipams',
+});
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Dns",
+ path => 'dns',
+});
+
+__PACKAGE__->register_method({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "Directory index.",
+ permissions => {
+ check => ['perm', '/', [ 'SDN.Audit' ]],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {},
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ id => { type => 'string' },
+ },
+ },
+ links => [ { rel => 'child', href => "{id}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $res = [
+ { id => 'vnets' },
+ { id => 'zones' },
+ { id => 'controllers' },
+ { id => 'ipams' },
+ { id => 'dns' },
+ ];
+
+ return $res;
+ }});
+
+my $create_reload_network_worker = sub {
+ my ($nodename) = @_;
+
+ # FIXME: how to proxy to final node ?
+ my $upid;
+ run_command(['pvesh', 'set', "/nodes/$nodename/network"], outfunc => sub {
+ my $line = shift;
+ if ($line =~ /^["']?(UPID:[^\s"']+)["']?$/) {
+ $upid = $1;
+ }
+ });
+ #my $upid = PVE::API2::Network->reload_network_config(node => $nodename});
+ my $res = PVE::Tools::upid_decode($upid);
+
+ return $res->{pid};
+};
+
+__PACKAGE__->register_method ({
+ name => 'reload',
+ protected => 1,
+ path => '',
+ method => 'PUT',
+ description => "Apply sdn controller changes && reload.",
+ permissions => {
+ check => ['perm', '/sdn', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ },
+ returns => {
+ type => 'string',
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ PVE::Network::SDN::commit_config();
+
+ my $code = sub {
+ $rpcenv->{type} = 'priv'; # to start tasks in background
+ PVE::Cluster::check_cfs_quorum();
+ my $nodelist = PVE::Cluster::get_nodelist();
+ for my $node (@$nodelist) {
+ my $pid = eval { $create_reload_network_worker->($node) };
+ warn $@ if $@;
+ }
+
+ # FIXME: use libpve-apiclient (like in cluster join) to create
+ # tasks and moitor the tasks.
+
+ return;
+ };
+
+ return $rpcenv->fork_worker('reloadnetworkall', undef, $authuser, $code);
+
+ }});
+
+
+1;
--- /dev/null
+package PVE::API2::Network::SDN::Controllers;
+
+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::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Network::SDN::Controllers::EvpnPlugin;
+use PVE::Network::SDN::Controllers::BgpPlugin;
+use PVE::Network::SDN::Controllers::FaucetPlugin;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $sdn_controllers_type_enum = PVE::Network::SDN::Controllers::Plugin->lookup_types();
+
+my $api_sdn_controllers_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id));
+ $scfg->{controller} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ return $scfg;
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN controllers index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/controllers/<controller>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ type => {
+ description => "Only list sdn controllers of specific type",
+ type => 'string',
+ enum => $sdn_controllers_type_enum,
+ optional => 1,
+ },
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => { controller => { type => 'string' },
+ type => { type => 'string' },
+ state => { type => 'string', optional => 1 },
+ pending => { optional => 1},
+ },
+ },
+ links => [ { rel => 'child', href => "{controller}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Controllers::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{controllers};
+ } else {
+ $cfg = PVE::Network::SDN::Controllers::config();
+ }
+
+ my @sids = PVE::Network::SDN::Controllers::sdn_controllers_ids($cfg);
+ my $res = [];
+ foreach my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/controllers/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_controllers_config($cfg, $id);
+ next if $param->{type} && $param->{type} ne $scfg->{type};
+
+ my $plugin_config = $cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{controller}',
+ method => 'GET',
+ description => "Read sdn controller configuration.",
+ permissions => {
+ check => ['perm', '/sdn/controllers/{controller}', ['SDN.Allocate']],
+ },
+
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ controller => get_standard_option('pve-sdn-controller-id'),
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Controllers::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'controllers');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{controllers};
+ } else {
+ $cfg = PVE::Network::SDN::Controllers::config();
+ }
+
+ return &$api_sdn_controllers_config($cfg, $param->{controller});
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn controller object.",
+ permissions => {
+ check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Controllers::Plugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $id = extract_param($param, 'controller');
+
+ my $plugin = PVE::Network::SDN::Controllers::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 $controller_cfg = PVE::Network::SDN::Controllers::config();
+
+ my $scfg = undef;
+ if ($scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id, 1)) {
+ die "sdn controller object ID '$id' already defined\n";
+ }
+
+ $controller_cfg->{ids}->{$id} = $opts;
+ $plugin->on_update_hook($id, $controller_cfg);
+
+ PVE::Network::SDN::Controllers::write_config($controller_cfg);
+
+ }, "create sdn controller object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{controller}',
+ method => 'PUT',
+ description => "Update sdn controller object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Controllers::Plugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'controller');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $controller_cfg = PVE::Network::SDN::Controllers::config();
+
+ PVE::SectionConfig::assert_if_modified($controller_cfg, $digest);
+
+ my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($controller_cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Controllers::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($id, $controller_cfg);
+
+ PVE::Network::SDN::Controllers::write_config($controller_cfg);
+
+
+ }, "update sdn controller object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{controller}',
+ method => 'DELETE',
+ description => "Delete sdn controller object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/controllers', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ controller => get_standard_option('pve-sdn-controller-id', {
+ completion => \&PVE::Network::SDN::Controllers::complete_sdn_controllers,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'controller');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $cfg = PVE::Network::SDN::Controllers::config();
+
+ my $scfg = PVE::Network::SDN::Controllers::sdn_controllers_config($cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($scfg->{type});
+
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+
+ $plugin->on_delete_hook($id, $zone_cfg);
+
+ delete $cfg->{ids}->{$id};
+ PVE::Network::SDN::Controllers::write_config($cfg);
+
+ }, "delete sdn controller object failed");
+
+
+ return undef;
+ }});
+
+1;
--- /dev/null
+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;
--- /dev/null
+package PVE::API2::Network::SDN::Ipams;
+
+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::Ipams;
+use PVE::Network::SDN::Ipams::Plugin;
+use PVE::Network::SDN::Ipams::PVEPlugin;
+use PVE::Network::SDN::Ipams::PhpIpamPlugin;
+use PVE::Network::SDN::Ipams::NetboxPlugin;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $sdn_ipams_type_enum = PVE::Network::SDN::Ipams::Plugin->lookup_types();
+
+my $api_sdn_ipams_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id));
+ $scfg->{ipam} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ return $scfg;
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN ipams index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/ipams/<ipam>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ type => {
+ description => "Only list sdn ipams of specific type",
+ type => 'string',
+ enum => $sdn_ipams_type_enum,
+ optional => 1,
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => { ipam => { type => 'string'},
+ type => { type => 'string'},
+ },
+ },
+ links => [ { rel => 'child', href => "{ipam}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+
+ my $cfg = PVE::Network::SDN::Ipams::config();
+
+ my @sids = PVE::Network::SDN::Ipams::sdn_ipams_ids($cfg);
+ my $res = [];
+ foreach my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/ipams/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_ipams_config($cfg, $id);
+ next if $param->{type} && $param->{type} ne $scfg->{type};
+
+ my $plugin_config = $cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{ipam}',
+ method => 'GET',
+ description => "Read sdn ipam configuration.",
+ permissions => {
+ check => ['perm', '/sdn/ipams/{ipam}', ['SDN.Allocate']],
+ },
+
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ ipam => get_standard_option('pve-sdn-ipam-id'),
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = PVE::Network::SDN::Ipams::config();
+
+ return &$api_sdn_ipams_config($cfg, $param->{ipam});
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn ipam object.",
+ permissions => {
+ check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Ipams::Plugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $id = extract_param($param, 'ipam');
+
+ my $plugin = PVE::Network::SDN::Ipams::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 $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $controller_cfg = PVE::Network::SDN::Controllers::config();
+
+ my $scfg = undef;
+ if ($scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id, 1)) {
+ die "sdn ipam object ID '$id' already defined\n";
+ }
+
+ $ipam_cfg->{ids}->{$id} = $opts;
+
+ my $plugin_config = $opts;
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $plugin->on_update_hook($plugin_config);
+
+ PVE::Network::SDN::Ipams::write_config($ipam_cfg);
+
+ }, "create sdn ipam object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{ipam}',
+ method => 'PUT',
+ description => "Update sdn ipam object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Ipams::Plugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'ipam');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+
+ PVE::SectionConfig::assert_if_modified($ipam_cfg, $digest);
+
+ my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($ipam_cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Ipams::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::Ipams::write_config($ipam_cfg);
+
+ }, "update sdn ipam object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{ipam}',
+ method => 'DELETE',
+ description => "Delete sdn ipam object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/ipams', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ ipam => get_standard_option('pve-sdn-ipam-id', {
+ completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipams,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'ipam');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $cfg = PVE::Network::SDN::Ipams::config();
+
+ my $scfg = PVE::Network::SDN::Ipams::sdn_ipams_config($cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($scfg->{type});
+
+ my $vnet_cfg = PVE::Network::SDN::Vnets::config();
+
+ delete $cfg->{ids}->{$id};
+ PVE::Network::SDN::Ipams::write_config($cfg);
+
+ }, "delete sdn zone object failed");
+
+ return undef;
+ }});
+
+1;
--- /dev/null
+SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done
+ make -C Zones install
+
--- /dev/null
+package PVE::API2::Network::SDN::Subnets;
+
+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::Exception qw(raise raise_param_exc);
+use PVE::Network::SDN;
+use PVE::Network::SDN::Subnets;
+use PVE::Network::SDN::SubnetPlugin;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Ipams;
+use PVE::Network::SDN::Ipams::Plugin;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+my $api_sdn_subnets_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id));
+ $scfg->{subnet} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ return $scfg;
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN subnets index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/subnets/<subnet>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vnet => get_standard_option('pve-sdn-vnet-id'),
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {},
+ },
+ links => [ { rel => 'child', href => "{subnet}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $vnetid = $param->{vnet};
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Subnets::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{subnets};
+ } else {
+ $cfg = PVE::Network::SDN::Subnets::config();
+ }
+
+ my @sids = PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg);
+ my $res = [];
+ foreach my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid/subnets/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_subnets_config($cfg, $id);
+ next if !$scfg->{vnet} || $scfg->{vnet} ne $vnetid;
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{subnet}',
+ method => 'GET',
+ description => "Read sdn subnet configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets/{vnet}/subnets/{subnet}', ['SDN.Allocate']],
+ },
+
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vnet => get_standard_option('pve-sdn-vnet-id'),
+ subnet => get_standard_option('pve-sdn-subnet-id', {
+ completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets,
+ }),
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Subnets::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'subnets');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{subnets};
+ } else {
+ $cfg = PVE::Network::SDN::Subnets::config();
+ }
+
+ my $scfg = &$api_sdn_subnets_config($cfg, $param->{subnet});
+
+ raise_param_exc({ vnet => "wrong vnet"}) if $param->{vnet} ne $scfg->{vnet};
+
+ return $scfg;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn subnet object.",
+ permissions => {
+ check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::SubnetPlugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $cidr = extract_param($param, 'subnet');
+
+ # create /etc/pve/sdn directory
+ PVE::Cluster::check_cfs_quorum();
+ mkdir("/etc/pve/sdn") if ! -d '/etc/pve/sdn';
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $cfg = PVE::Network::SDN::Subnets::config();
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $vnet_cfg = PVE::Network::SDN::Vnets::config();
+ my $vnet = $param->{vnet};
+ my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone};
+ my $zone = $zone_cfg->{ids}->{$zoneid};
+ my $id = $cidr =~ s/\//-/r;
+ $id = "$zoneid-$id";
+
+ my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1);
+
+ my $scfg = undef;
+ if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) {
+ die "sdn subnet object ID '$id' already defined\n";
+ }
+
+ $cfg->{ids}->{$id} = $opts;
+
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id);
+ PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet);
+
+ PVE::Network::SDN::Subnets::write_config($cfg);
+
+ }, "create sdn subnet object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{subnet}',
+ method => 'PUT',
+ description => "Update sdn subnet object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::SubnetPlugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'subnet');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+
+ my $cfg = PVE::Network::SDN::Subnets::config();
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $vnet_cfg = PVE::Network::SDN::Vnets::config();
+ my $vnet = $param->{vnet};
+ my $zoneid = $vnet_cfg->{ids}->{$vnet}->{zone};
+ my $zone = $zone_cfg->{ids}->{$zoneid};
+
+ my $scfg = &$api_sdn_subnets_config($cfg, $id);
+
+ PVE::SectionConfig::assert_if_modified($cfg, $digest);
+
+ my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 0, 1);
+ $cfg->{ids}->{$id} = $opts;
+
+ raise_param_exc({ ipam => "you can't change ipam"}) if $opts->{ipam} && $scfg->{ipam} && $opts->{ipam} ne $scfg->{ipam};
+
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id);
+ PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg);
+
+ PVE::Network::SDN::Subnets::write_config($cfg);
+
+ }, "update sdn subnet object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{subnet}',
+ method => 'DELETE',
+ description => "Delete sdn subnet object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets/{vnet}/subnets', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vnet => get_standard_option('pve-sdn-vnet-id'),
+ subnet => get_standard_option('pve-sdn-subnet-id', {
+ completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnets,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'subnet');
+
+ PVE::Network::SDN::lock_sdn_config(
+ sub {
+ my $cfg = PVE::Network::SDN::Subnets::config();
+
+ my $scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1);
+
+ my $vnets_cfg = PVE::Network::SDN::Vnets::config();
+
+ PVE::Network::SDN::SubnetPlugin->on_delete_hook($id, $cfg, $vnets_cfg);
+
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $vnet = $param->{vnet};
+ my $zoneid = $vnets_cfg->{ids}->{$vnet}->{zone};
+ my $zone = $zone_cfg->{ids}->{$zoneid};
+
+ PVE::Network::SDN::Subnets::del_subnet($zone, $id, $scfg);
+
+ delete $cfg->{ids}->{$id};
+
+ PVE::Network::SDN::Subnets::write_config($cfg);
+
+ }, "delete sdn subnet object failed");
+
+
+ return undef;
+ }});
+
+1;
--- /dev/null
+package PVE::API2::Network::SDN::Vnets;
+
+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::Zones;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::VnetPlugin;
+use PVE::Network::SDN::Subnets;
+use PVE::API2::Network::SDN::Subnets;
+
+use Storable qw(dclone);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+use PVE::Exception qw(raise raise_param_exc);
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Subnets",
+ path => '{vnet}/subnets',
+});
+
+my $api_sdn_vnets_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id));
+ $scfg->{vnet} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ return $scfg;
+};
+
+my $api_sdn_vnets_deleted_config = sub {
+ my ($cfg, $running_cfg, $id) = @_;
+
+ if (!$cfg->{ids}->{$id}) {
+
+ my $vnet_cfg = dclone(PVE::Network::SDN::Vnets::sdn_vnets_config($running_cfg->{vnets}, $id));
+ $vnet_cfg->{state} = "deleted";
+ $vnet_cfg->{vnet} = $id;
+ return $vnet_cfg;
+ }
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN vnets index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate'"
+ ." permissions on '/sdn/vnets/<vnet>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {},
+ },
+ links => [ { rel => 'child', href => "{vnet}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Vnets::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{vnets};
+ } else {
+ $cfg = PVE::Network::SDN::Vnets::config();
+ }
+
+ my @sids = PVE::Network::SDN::Vnets::sdn_vnets_ids($cfg);
+ my $res = [];
+ foreach my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/vnets/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_vnets_config($cfg, $id);
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{vnet}',
+ method => 'GET',
+ description => "Read sdn vnet configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets/{vnet}', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vnet => get_standard_option('pve-sdn-vnet-id', {
+ completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets,
+ }),
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = {};
+ if($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Vnets::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'vnets');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{vnets};
+ } else {
+ $cfg = PVE::Network::SDN::Vnets::config();
+ }
+
+ return $api_sdn_vnets_config->($cfg, $param->{vnet});
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn vnet object.",
+ permissions => {
+ check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::VnetPlugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $id = extract_param($param, 'vnet');
+
+ PVE::Cluster::check_cfs_quorum();
+ mkdir("/etc/pve/sdn");
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $cfg = PVE::Network::SDN::Vnets::config();
+ my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 1, 1);
+
+ if (PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id, 1)) {
+ die "sdn vnet object ID '$id' already defined\n";
+ }
+ $cfg->{ids}->{$id} = $opts;
+
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $zoneid = $cfg->{ids}->{$id}->{zone};
+ my $plugin_config = $zone_cfg->{ids}->{$zoneid};
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ $plugin->vnet_update_hook($cfg, $id, $zone_cfg);
+
+ PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg);
+
+ PVE::Network::SDN::Vnets::write_config($cfg);
+
+ }, "create sdn vnet object failed");
+
+ return undef;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{vnet}',
+ method => 'PUT',
+ description => "Update sdn vnet object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::VnetPlugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'vnet');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $cfg = PVE::Network::SDN::Vnets::config();
+
+ PVE::SectionConfig::assert_if_modified($cfg, $digest);
+
+
+ my $opts = PVE::Network::SDN::VnetPlugin->check_config($id, $param, 0, 1);
+ raise_param_exc({ zone => "missing zone"}) if !$opts->{zone};
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($id);
+ raise_param_exc({ zone => "can't change zone if subnets exists"}) if($subnets && $opts->{zone} ne $cfg->{ids}->{$id}->{zone});
+
+ $cfg->{ids}->{$id} = $opts;
+
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $zoneid = $cfg->{ids}->{$id}->{zone};
+ my $plugin_config = $zone_cfg->{ids}->{$zoneid};
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ $plugin->vnet_update_hook($cfg, $id, $zone_cfg);
+
+ PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg);
+
+ PVE::Network::SDN::Vnets::write_config($cfg);
+
+ }, "update sdn vnet object failed");
+
+ return undef;
+ }
+});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{vnet}',
+ method => 'DELETE',
+ description => "Delete sdn vnet object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/vnets', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ vnet => get_standard_option('pve-sdn-vnet-id', {
+ completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnets,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'vnet');
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $cfg = PVE::Network::SDN::Vnets::config();
+ my $scfg = PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $id); # check if exists
+ my $vnet_cfg = PVE::Network::SDN::Vnets::config();
+
+ PVE::Network::SDN::VnetPlugin->on_delete_hook($id, $vnet_cfg);
+
+ delete $cfg->{ids}->{$id};
+ PVE::Network::SDN::Vnets::write_config($cfg);
+
+ }, "delete sdn vnet object failed");
+
+
+ return undef;
+ }
+});
+
+1;
--- /dev/null
+package PVE::API2::Network::SDN::Zones;
+
+use strict;
+use warnings;
+
+use Storable qw(dclone);
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+use PVE::SafeSyslog;
+use PVE::Tools qw(extract_param);
+
+use PVE::Network::SDN::Dns;
+use PVE::Network::SDN::Subnets;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN;
+
+use PVE::Network::SDN::Zones::EvpnPlugin;
+use PVE::Network::SDN::Zones::FaucetPlugin;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Network::SDN::Zones::QinQPlugin;
+use PVE::Network::SDN::Zones::SimplePlugin;
+use PVE::Network::SDN::Zones::VlanPlugin;
+use PVE::Network::SDN::Zones::VxlanPlugin;
+use PVE::Network::SDN::Zones;
+
+use PVE::RESTHandler;
+use base qw(PVE::RESTHandler);
+
+my $sdn_zones_type_enum = PVE::Network::SDN::Zones::Plugin->lookup_types();
+
+my $api_sdn_zones_config = sub {
+ my ($cfg, $id) = @_;
+
+ my $scfg = dclone(PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id));
+ $scfg->{zone} = $id;
+ $scfg->{digest} = $cfg->{digest};
+
+ if ($scfg->{nodes}) {
+ $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
+ }
+
+ if ($scfg->{exitnodes}) {
+ $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes});
+ }
+
+ my $pending = $scfg->{pending};
+ if ($pending->{nodes}) {
+ $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes});
+ }
+
+ if ($pending->{exitnodes}) {
+ $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes});
+ }
+
+ return $scfg;
+};
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "SDN zones index.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit' or 'SDN.Allocate' permissions on '/sdn/zones/<zone>'",
+ user => 'all',
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ type => {
+ description => "Only list SDN zones of specific type",
+ type => 'string',
+ enum => $sdn_zones_type_enum,
+ optional => 1,
+ },
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ },
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => { zone => { type => 'string'},
+ type => { type => 'string'},
+ mtu => { type => 'integer', optional => 1 },
+ dns => { type => 'string', optional => 1},
+ reversedns => { type => 'string', optional => 1},
+ dnszone => { type => 'string', optional => 1},
+ ipam => { type => 'string', optional => 1},
+ pending => { optional => 1},
+ state => { type => 'string', optional => 1},
+ nodes => { type => 'string', optional => 1},
+ },
+ },
+ links => [ { rel => 'child', href => "{zone}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $cfg = {};
+ if ($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Zones::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{zones};
+ } else {
+ $cfg = PVE::Network::SDN::Zones::config();
+ }
+
+ my @sids = PVE::Network::SDN::Zones::sdn_zones_ids($cfg);
+ my $res = [];
+ for my $id (@sids) {
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+ next if !$rpcenv->check_any($authuser, "/sdn/zones/$id", $privs, 1);
+
+ my $scfg = &$api_sdn_zones_config($cfg, $id);
+ next if $param->{type} && $param->{type} ne $scfg->{type};
+
+ my $plugin_config = $cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ push @$res, $scfg;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'read',
+ path => '{zone}',
+ method => 'GET',
+ description => "Read sdn zone configuration.",
+ permissions => {
+ check => ['perm', '/sdn/zones/{zone}', ['SDN.Allocate']],
+ },
+
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ zone => get_standard_option('pve-sdn-zone-id'),
+ running => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display running config.",
+ },
+ pending => {
+ type => 'boolean',
+ optional => 1,
+ description => "Display pending config.",
+ }
+ },
+ },
+ returns => { type => 'object' },
+ code => sub {
+ my ($param) = @_;
+
+ my $cfg = {};
+ if ($param->{pending}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ my $config = PVE::Network::SDN::Zones::config();
+ $cfg = PVE::Network::SDN::pending_config($running_cfg, $config, 'zones');
+ } elsif ($param->{running}) {
+ my $running_cfg = PVE::Network::SDN::running_config();
+ $cfg = $running_cfg->{zones};
+ } else {
+ $cfg = PVE::Network::SDN::Zones::config();
+ }
+
+ return &$api_sdn_zones_config($cfg, $param->{zone});
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'create',
+ protected => 1,
+ path => '',
+ method => 'POST',
+ description => "Create a new sdn zone object.",
+ permissions => {
+ check => ['perm', '/sdn/zones', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Zones::Plugin->createSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $type = extract_param($param, 'type');
+ my $id = extract_param($param, 'zone');
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($type);
+ my $opts = $plugin->check_config($id, $param, 1, 1);
+
+ PVE::Cluster::check_cfs_quorum();
+ mkdir("/etc/pve/sdn");
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $controller_cfg = PVE::Network::SDN::Controllers::config();
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+ my $scfg = undef;
+ if ($scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id, 1)) {
+ die "sdn zone object ID '$id' already defined\n";
+ }
+
+ my $dnsserver = $opts->{dns};
+ raise_param_exc({ dns => "$dnsserver don't exist"})
+ if $dnsserver && !$dns_cfg->{ids}->{$dnsserver};
+
+ my $reversednsserver = $opts->{reversedns};
+ raise_param_exc({ reversedns => "$reversednsserver don't exist"})
+ if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver};
+
+ my $dnszone = $opts->{dnszone};
+ raise_param_exc({ dnszone => "missing dns server"})
+ if $dnszone && !$dnsserver;
+
+ my $ipam = $opts->{ipam};
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam};
+
+ $zone_cfg->{ids}->{$id} = $opts;
+ $plugin->on_update_hook($id, $zone_cfg, $controller_cfg);
+
+ PVE::Network::SDN::Zones::write_config($zone_cfg);
+
+ }, "create sdn zone object failed");
+
+ return;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'update',
+ protected => 1,
+ path => '{zone}',
+ method => 'PUT',
+ description => "Update sdn zone object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/zones', ['SDN.Allocate']],
+ },
+ parameters => PVE::Network::SDN::Zones::Plugin->updateSchema(),
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'zone');
+ my $digest = extract_param($param, 'digest');
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ my $controller_cfg = PVE::Network::SDN::Controllers::config();
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+
+ PVE::SectionConfig::assert_if_modified($zone_cfg, $digest);
+
+ my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type});
+ my $opts = $plugin->check_config($id, $param, 0, 1);
+
+ if ($opts->{ipam} && !$scfg->{ipam} || $opts->{ipam} ne $scfg->{ipam}) {
+
+ # don't allow ipam change if subnet are defined for now, need to implement resync ipam content
+ my $subnets_cfg = PVE::Network::SDN::Subnets::config();
+ for my $subnetid (sort keys %{$subnets_cfg->{ids}}) {
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid);
+ raise_param_exc({ ipam => "can't change ipam if a subnet is already defined in this zone"})
+ if $subnet->{zone} eq $id;
+ }
+ }
+
+ $zone_cfg->{ids}->{$id} = $opts;
+
+ my $dnsserver = $opts->{dns};
+ raise_param_exc({ dns => "$dnsserver don't exist"}) if $dnsserver && !$dns_cfg->{ids}->{$dnsserver};
+
+ my $reversednsserver = $opts->{reversedns};
+ raise_param_exc({ reversedns => "$reversednsserver don't exist"}) if $reversednsserver && !$dns_cfg->{ids}->{$reversednsserver};
+
+ my $dnszone = $opts->{dnszone};
+ raise_param_exc({ dnszone => "missing dns server"}) if $dnszone && !$dnsserver;
+
+ my $ipam = $opts->{ipam};
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ raise_param_exc({ ipam => "$ipam not existing"}) if $ipam && !$ipam_cfg->{ids}->{$ipam};
+
+ $plugin->on_update_hook($id, $zone_cfg, $controller_cfg);
+
+ PVE::Network::SDN::Zones::write_config($zone_cfg);
+
+ }, "update sdn zone object failed");
+
+ return;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'delete',
+ protected => 1,
+ path => '{zone}',
+ method => 'DELETE',
+ description => "Delete sdn zone object configuration.",
+ permissions => {
+ check => ['perm', '/sdn/zones', ['SDN.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ zone => get_standard_option('pve-sdn-zone-id', {
+ completion => \&PVE::Network::SDN::Zones::complete_sdn_zones,
+ }),
+ },
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ my $id = extract_param($param, 'zone');
+
+ PVE::Network::SDN::lock_sdn_config(sub {
+ my $cfg = PVE::Network::SDN::Zones::config();
+ my $scfg = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $id);
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($scfg->{type});
+ my $vnet_cfg = PVE::Network::SDN::Vnets::config();
+
+ $plugin->on_delete_hook($id, $vnet_cfg);
+
+ delete $cfg->{ids}->{$id};
+
+ PVE::Network::SDN::Zones::write_config($cfg);
+ }, "delete sdn zone object failed");
+
+ return;
+ }});
+
+1;
--- /dev/null
+package PVE::API2::Network::SDN::Zones::Content;
+
+use strict;
+use warnings;
+use Data::Dumper;
+
+use PVE::SafeSyslog;
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::Exception qw(raise_param_exc);
+use PVE::RPCEnvironment;
+use PVE::RESTHandler;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Network::SDN;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "List zone content.",
+ permissions => {
+ check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1],
+ },
+ protected => 1,
+ proxyto => 'node',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ zone => get_standard_option('pve-sdn-zone-id', {
+ completion => \&PVE::Network::SDN::Zones::complete_sdn_zone,
+ }),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ vnet => {
+ description => "Vnet identifier.",
+ type => 'string',
+ },
+ status => {
+ description => "Status.",
+ type => 'string',
+ optional => 1,
+ },
+ statusmsg => {
+ description => "Status details",
+ type => 'string',
+ optional => 1,
+ },
+ },
+ },
+ links => [ { rel => 'child', href => "{vnet}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $zoneid = $param->{zone};
+
+ my $res = [];
+
+ my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
+
+ foreach my $id (keys %{$vnet_status}) {
+ if ($vnet_status->{$id}->{zone} eq $zoneid) {
+ my $item->{vnet} = $id;
+ $item->{status} = $vnet_status->{$id}->{'status'};
+ $item->{statusmsg} = $vnet_status->{$id}->{'statusmsg'};
+ push @$res,$item;
+ }
+ }
+
+ return $res;
+ }});
+
+1;
--- /dev/null
+SOURCES=Status.pm Content.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/Zones/$$i; done
--- /dev/null
+package PVE::API2::Network::SDN::Zones::Status;
+
+use strict;
+use warnings;
+
+use File::Path;
+use File::Basename;
+use PVE::Tools;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::API2::Network::SDN::Zones::Content;
+use PVE::RESTHandler;
+use PVE::RPCEnvironment;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Exception qw(raise_param_exc);
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Network::SDN::Zones::Content",
+ path => '{zone}/content',
+});
+
+__PACKAGE__->register_method ({
+ name => 'index',
+ path => '',
+ method => 'GET',
+ description => "Get status for all zones.",
+ permissions => {
+ description => "Only list entries where you have 'SDN.Audit'",
+ user => 'all',
+ },
+ protected => 1,
+ proxyto => 'node',
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node')
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ zone => get_standard_option('pve-sdn-zone-id'),
+ status => {
+ description => "Status of zone",
+ type => 'string',
+ enum => ['available', 'pending', 'error'],
+ },
+ },
+ },
+ links => [ { rel => 'child', href => "{zone}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+ my $authuser = $rpcenv->get_user();
+
+ my $localnode = PVE::INotify::nodename();
+
+ my $res = [];
+
+ my ($zone_status, $vnet_status) = PVE::Network::SDN::status();
+
+ foreach my $id (sort keys %{$zone_status}) {
+ my $item->{zone} = $id;
+ $item->{status} = $zone_status->{$id}->{'status'};
+ push @$res, $item;
+ }
+
+ return $res;
+ }});
+
+__PACKAGE__->register_method ({
+ name => 'diridx',
+ path => '{zone}',
+ method => 'GET',
+ description => "",
+ permissions => {
+ check => ['perm', '/sdn/zones/{zone}', ['SDN.Audit'], any => 1],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ zone => get_standard_option('pve-sdn-zone-id'),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ subdir => { type => 'string' },
+ },
+ },
+ links => [ { rel => 'child', href => "{subdir}" } ],
+ },
+ code => sub {
+ my ($param) = @_;
+ my $res = [
+ { subdir => 'content' },
+ ];
+
+ return $res;
+ }});
+
+1;
--- /dev/null
+all:
+
+.PHONY: install
+install:
+ make -C Network install
+ make -C API2 install
+
+clean:
--- /dev/null
+SOURCES=SDN.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/$$i; done
+ make -C SDN install
--- /dev/null
+package PVE::Network::SDN;
+
+use strict;
+use warnings;
+
+use Data::Dumper;
+use JSON;
+
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::Network::SDN::Subnets;
+
+use PVE::Tools qw(extract_param dir_glob_regex run_command);
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+
+
+my $running_cfg = "sdn/.running-config";
+
+my $parse_running_cfg = sub {
+ my ($filename, $raw) = @_;
+
+ my $cfg = {};
+
+ return $cfg if !defined($raw) || $raw eq '';
+
+ eval {
+ $cfg = from_json($raw);
+ };
+ return {} if $@;
+
+ return $cfg;
+};
+
+my $write_running_cfg = sub {
+ my ($filename, $cfg) = @_;
+
+ my $json = to_json($cfg);
+
+ return $json;
+};
+
+PVE::Cluster::cfs_register_file($running_cfg, $parse_running_cfg, $write_running_cfg);
+
+
+# improve me : move status code inside plugins ?
+
+sub ifquery_check {
+
+ my $cmd = ['ifquery', '-a', '-c', '-o','json'];
+
+ my $result = '';
+ my $reader = sub { $result .= shift };
+
+ eval {
+ run_command($cmd, outfunc => $reader);
+ };
+
+ my $resultjson = decode_json($result);
+ my $interfaces = {};
+
+ foreach my $interface (@$resultjson) {
+ my $name = $interface->{name};
+ $interfaces->{$name} = {
+ status => $interface->{status},
+ config => $interface->{config},
+ config_status => $interface->{config_status},
+ };
+ }
+
+ return $interfaces;
+}
+
+sub status {
+
+ my ($zone_status, $vnet_status) = PVE::Network::SDN::Zones::status();
+ return($zone_status, $vnet_status);
+}
+
+sub running_config {
+ return cfs_read_file($running_cfg);
+}
+
+sub pending_config {
+ my ($running_cfg, $cfg, $type) = @_;
+
+ my $pending = {};
+
+ my $running_objects = $running_cfg->{$type}->{ids};
+ my $config_objects = $cfg->{ids};
+
+ foreach my $id (sort keys %{$running_objects}) {
+ my $running_object = $running_objects->{$id};
+ my $config_object = $config_objects->{$id};
+ foreach my $key (sort keys %{$running_object}) {
+ $pending->{$id}->{$key} = $running_object->{$key};
+ if(!keys %{$config_object}) {
+ $pending->{$id}->{state} = "deleted";
+ } elsif (!defined($config_object->{$key})) {
+ $pending->{$id}->{"pending"}->{$key} = 'deleted';
+ $pending->{$id}->{state} = "changed";
+ } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key})
+ ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) {
+ $pending->{$id}->{state} = "changed";
+ }
+ }
+ $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
+ }
+
+ foreach my $id (sort keys %{$config_objects}) {
+ my $running_object = $running_objects->{$id};
+ my $config_object = $config_objects->{$id};
+
+ foreach my $key (sort keys %{$config_object}) {
+ my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key};
+ my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key};
+ if($key eq 'type' || $key eq 'vnet') {
+ $pending->{$id}->{$key} = $config_value;
+ } else {
+ $pending->{$id}->{"pending"}->{$key} = $config_value if !defined($running_value) || ($config_value ne $running_value);
+ }
+ if(!keys %{$running_object}) {
+ $pending->{$id}->{state} = "new";
+ } elsif (!defined($running_value) && defined($config_value)) {
+ $pending->{$id}->{state} = "changed";
+ }
+ }
+ $pending->{$id}->{"pending"} = {} if $pending->{$id}->{state} && !defined($pending->{$id}->{"pending"});
+ }
+
+ return {ids => $pending};
+
+}
+
+sub commit_config {
+
+ my $cfg = cfs_read_file($running_cfg);
+ my $version = $cfg->{version};
+
+ if ($version) {
+ $version++;
+ } else {
+ $version = 1;
+ }
+
+ my $vnets_cfg = PVE::Network::SDN::Vnets::config();
+ my $zones_cfg = PVE::Network::SDN::Zones::config();
+ my $controllers_cfg = PVE::Network::SDN::Controllers::config();
+ my $subnets_cfg = PVE::Network::SDN::Subnets::config();
+
+ my $vnets = { ids => $vnets_cfg->{ids} };
+ my $zones = { ids => $zones_cfg->{ids} };
+ my $controllers = { ids => $controllers_cfg->{ids} };
+ my $subnets = { ids => $subnets_cfg->{ids} };
+
+ $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
+
+ cfs_write_file($running_cfg, $cfg);
+}
+
+sub lock_sdn_config {
+ my ($code, $errmsg) = @_;
+
+ cfs_lock_file($running_cfg, undef, $code);
+
+ if (my $err = $@) {
+ $errmsg ? die "$errmsg: $err" : die $err;
+ }
+}
+
+sub get_local_vnets {
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $nodename = PVE::INotify::nodename();
+
+ my $cfg = PVE::Network::SDN::running_config();
+ my $vnets_cfg = $cfg->{vnets};
+ my $zones_cfg = $cfg->{zones};
+
+ my @vnetids = PVE::Network::SDN::Vnets::sdn_vnets_ids($vnets_cfg);
+
+ my $vnets = {};
+
+ foreach my $vnetid (@vnetids) {
+
+ my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_cfg, $vnetid);
+ my $zoneid = $vnet->{zone};
+ my $comments = $vnet->{alias};
+
+ my $privs = [ 'SDN.Audit', 'SDN.Allocate' ];
+
+ next if !$zoneid;
+ next if !$rpcenv->check_any($authuser, "/sdn/zones/$zoneid", $privs, 1) && !$rpcenv->check_any($authuser, "/sdn/vnets/$vnetid", $privs, 1);
+
+ my $zone_config = PVE::Network::SDN::Zones::sdn_zones_config($zones_cfg, $zoneid);
+
+ next if defined($zone_config->{nodes}) && !$zone_config->{nodes}->{$nodename};
+ my $ipam = $zone_config->{ipam} ? 1 : 0;
+ my $vlanaware = $vnet->{vlanaware} ? 1 : 0;
+ $vnets->{$vnetid} = { type => 'vnet', active => '1', ipam => $ipam, vlanaware => $vlanaware, comments => $comments };
+ }
+
+ return $vnets;
+}
+
+sub generate_zone_config {
+ my $raw_config = PVE::Network::SDN::Zones::generate_etc_network_config();
+ PVE::Network::SDN::Zones::write_etc_network_config($raw_config);
+}
+
+sub generate_controller_config {
+ my ($reload) = @_;
+
+ my $raw_config = PVE::Network::SDN::Controllers::generate_controller_config();
+ PVE::Network::SDN::Controllers::write_controller_config($raw_config);
+
+ PVE::Network::SDN::Controllers::reload_controller() if $reload;
+}
+
+sub encode_value {
+ my ($type, $key, $value) = @_;
+
+ if ($key eq 'nodes' || $key eq 'exitnodes') {
+ if(ref($value) eq 'HASH') {
+ return join(',', sort keys(%$value));
+ } else {
+ return $value;
+ }
+ }
+
+ return $value;
+}
+
+
+#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 if $raw eq '';
+
+ my $json = '';
+ eval {
+ $json = from_json($raw);
+ };
+ die "api response is not a json" if $@;
+
+ return $json;
+}
+
+1;
--- /dev/null
+package PVE::Network::SDN::Controllers;
+
+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::SDN::Vnets;
+use PVE::Network::SDN::Zones;
+
+use PVE::Network::SDN::Controllers::EvpnPlugin;
+use PVE::Network::SDN::Controllers::BgpPlugin;
+use PVE::Network::SDN::Controllers::FaucetPlugin;
+use PVE::Network::SDN::Controllers::Plugin;
+PVE::Network::SDN::Controllers::EvpnPlugin->register();
+PVE::Network::SDN::Controllers::BgpPlugin->register();
+PVE::Network::SDN::Controllers::FaucetPlugin->register();
+PVE::Network::SDN::Controllers::Plugin->init();
+
+
+sub sdn_controllers_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn controller 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/controllers.cfg");
+ $config = cfs_read_file("sdn/controllers.cfg") if !keys %{$config->{ids}};
+ return $config;
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/controllers.cfg", $cfg);
+}
+
+sub lock_sdn_controllers_config {
+ my ($code, $errmsg) = @_;
+
+ cfs_lock_file("sdn/controllers.cfg", undef, $code);
+ if (my $err = $@) {
+ $errmsg ? die "$errmsg: $err" : die $err;
+ }
+}
+
+sub sdn_controllers_ids {
+ my ($cfg) = @_;
+
+ return sort keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_controller {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::running_config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_controllers_ids($cfg) ];
+}
+
+sub generate_controller_config {
+
+ my $cfg = PVE::Network::SDN::running_config();
+ my $vnet_cfg = $cfg->{vnets};
+ my $zone_cfg = $cfg->{zones};
+ my $controller_cfg = $cfg->{controllers};
+
+ return if !$vnet_cfg && !$zone_cfg && !$controller_cfg;
+
+ #read main config for physical interfaces
+ my $current_config_file = "/etc/network/interfaces";
+ my $fh = IO::File->new($current_config_file);
+ my $interfaces_config = PVE::INotify::read_etc_network_interfaces(1,$fh);
+ $fh->close();
+
+ # check uplinks
+ my $uplinks = {};
+ foreach my $id (keys %{$interfaces_config->{ifaces}}) {
+ my $interface = $interfaces_config->{ifaces}->{$id};
+ if (my $uplink = $interface->{'uplink-id'}) {
+ die "uplink-id $uplink is already defined on $uplinks->{$uplink}" if $uplinks->{$uplink};
+ $interface->{name} = $id;
+ $uplinks->{$interface->{'uplink-id'}} = $interface;
+ }
+ }
+
+ # generate configuration
+ my $config = {};
+
+ foreach my $id (sort keys %{$controller_cfg->{ids}}) {
+ my $plugin_config = $controller_cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+ $plugin->generate_controller_config($plugin_config, $controller_cfg, $id, $uplinks, $config);
+ }
+
+ foreach my $id (sort keys %{$zone_cfg->{ids}}) {
+ my $plugin_config = $zone_cfg->{ids}->{$id};
+ my $controllerid = $plugin_config->{controller};
+ next if !$controllerid;
+ my $controller = $controller_cfg->{ids}->{$controllerid};
+ if ($controller) {
+ my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
+ $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $controller_cfg, $id, $uplinks, $config);
+ }
+ }
+
+ foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
+ my $plugin_config = $vnet_cfg->{ids}->{$id};
+ my $zoneid = $plugin_config->{zone};
+ next if !$zoneid;
+ my $zone = $zone_cfg->{ids}->{$zoneid};
+ next if !$zone;
+ my $controllerid = $zone->{controller};
+ next if !$controllerid;
+ my $controller = $controller_cfg->{ids}->{$controllerid};
+ if ($controller) {
+ my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
+ $controller_plugin->generate_controller_vnet_config($plugin_config, $controller, $zone, $zoneid, $id, $config);
+ }
+ }
+
+ return $config;
+}
+
+
+sub reload_controller {
+
+ my $cfg = PVE::Network::SDN::running_config();
+ my $controller_cfg = $cfg->{controllers};
+
+ return if !$controller_cfg;
+
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ my $plugin_config = $controller_cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+ $plugin->reload_controller();
+ }
+}
+
+sub generate_controller_rawconfig {
+ my ($config) = @_;
+
+ my $cfg = PVE::Network::SDN::running_config();
+ my $controller_cfg = $cfg->{controllers};
+ return if !$controller_cfg;
+
+ my $rawconfig = "";
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ my $plugin_config = $controller_cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+ $rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config);
+ }
+ return $rawconfig;
+}
+
+sub write_controller_config {
+ my ($config) = @_;
+
+ my $cfg = PVE::Network::SDN::running_config();
+ my $controller_cfg = $cfg->{controllers};
+ return if !$controller_cfg;
+
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ my $plugin_config = $controller_cfg->{ids}->{$id};
+ my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
+ $plugin->write_controller_config($plugin_config, $config);
+ }
+}
+
+1;
+
--- /dev/null
+package PVE::Network::SDN::Controllers::BgpPlugin;
+
+use strict;
+use warnings;
+
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw(run_command file_set_contents file_get_contents);
+
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Network::SDN::Zones::Plugin;
+use Net::IP;
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+ return 'bgp';
+}
+
+sub properties {
+ return {
+ 'bgp-multipath-as-path-relax' => {
+ type => 'boolean',
+ optional => 1,
+ },
+ ebgp => {
+ type => 'boolean',
+ optional => 1,
+ description => "Enable ebgp. (remote-as external)",
+ },
+ 'ebgp-multihop' => {
+ type => 'integer',
+ optional => 1,
+ },
+ loopback => {
+ description => "source loopback interface.",
+ type => 'string'
+ },
+ node => get_standard_option('pve-node'),
+ };
+}
+
+sub options {
+ return {
+ 'node' => { optional => 0 },
+ 'asn' => { optional => 0 },
+ 'peers' => { optional => 0 },
+ 'bgp-multipath-as-path-relax' => { optional => 1 },
+ 'ebgp' => { optional => 1 },
+ 'ebgp-multihop' => { optional => 1 },
+ 'loopback' => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+ my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+
+ my @peers;
+ @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
+
+ my $asn = $plugin_config->{asn};
+ my $ebgp = $plugin_config->{ebgp};
+ my $ebgp_multihop = $plugin_config->{'ebgp-multihop'};
+ my $loopback = $plugin_config->{loopback};
+ my $multipath_relax = $plugin_config->{'bgp-multipath-as-path-relax'};
+
+ my $local_node = PVE::INotify::nodename();
+
+
+ return if !$asn;
+ return if $local_node ne $plugin_config->{node};
+
+ my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
+
+ my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
+
+ my $remoteas = $ebgp ? "external" : $asn;
+
+ #global options
+ my @controller_config = (
+ "bgp router-id $ifaceip",
+ "no bgp default ipv4-unicast",
+ "coalesce-time 1000"
+ );
+
+ push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
+
+ @controller_config = ();
+ if($ebgp) {
+ push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback;
+ }
+
+ push @controller_config, "bgp bestpath as-path multipath-relax" if $multipath_relax;
+
+ #BGP neighbors
+ if(@peers) {
+ push @controller_config, "neighbor BGP peer-group";
+ push @controller_config, "neighbor BGP remote-as $remoteas";
+ push @controller_config, "neighbor BGP bfd";
+ push @controller_config, "neighbor BGP ebgp-multihop $ebgp_multihop" if $ebgp && $ebgp_multihop;
+ }
+
+ # BGP peers
+ foreach my $address (@peers) {
+ push @controller_config, "neighbor $address peer-group BGP";
+ }
+ push(@{$bgp->{""}}, @controller_config);
+
+ # address-family unicast
+ if (@peers) {
+ my $ipversion = Net::IP::ip_is_ipv6($ifaceip) ? "ipv6" : "ipv4";
+ my $mask = Net::IP::ip_is_ipv6($ifaceip) ? "/128" : "32";
+
+ push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "network $ifaceip/$mask") if $loopback;
+ push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP activate");
+ push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP soft-reconfiguration inbound");
+ }
+
+ if ($loopback) {
+ $config->{frr_prefix_list}->{loopbacks_ips}->{10} = "permit 0.0.0.0/0 le 32";
+ push(@{$config->{frr}->{''}}, "ip protocol bgp route-map correct_src");
+
+ my $routemap_config = ();
+ push @{$routemap_config}, "match ip address prefix-list loopbacks_ips";
+ push @{$routemap_config}, "set src $ifaceip";
+ my $routemap = { rule => $routemap_config, action => "permit" };
+ push(@{$config->{frr_routemap}->{'correct_src'}}, $routemap);
+ }
+
+ return $config;
+}
+
+sub generate_controller_zone_config {
+ my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+}
+
+sub on_delete_hook {
+ my ($class, $controllerid, $zone_cfg) = @_;
+
+ # verify that zone is associated to this controller
+ foreach my $id (keys %{$zone_cfg->{ids}}) {
+ my $zone = $zone_cfg->{ids}->{$id};
+ die "controller $controllerid is used by $id"
+ if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
+ }
+}
+
+sub on_update_hook {
+ my ($class, $controllerid, $controller_cfg) = @_;
+
+ # we can only have 1 bgp controller by node
+ my $local_node = PVE::INotify::nodename();
+ my $controllernb = 0;
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ next if $id eq $controllerid;
+ my $controller = $controller_cfg->{ids}->{$id};
+ next if $controller->{type} ne "bgp";
+ next if $controller->{node} ne $local_node;
+ $controllernb++;
+ die "only 1 bgp controller can be defined" if $controllernb > 1;
+ }
+}
+
+sub generate_controller_rawconfig {
+ my ($class, $plugin_config, $config) = @_;
+ return "";
+}
+
+sub write_controller_config {
+ my ($class, $plugin_config, $config) = @_;
+ return;
+}
+
+sub reload_controller {
+ my ($class) = @_;
+ return;
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Controllers::EvpnPlugin;
+
+use strict;
+use warnings;
+
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw(run_command file_set_contents file_get_contents);
+
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Network::SDN::Zones::Plugin;
+use Net::IP;
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+ return 'evpn';
+}
+
+sub properties {
+ return {
+ asn => {
+ type => 'integer',
+ description => "autonomous system number",
+ minimum => 0,
+ maximum => 4294967296
+ },
+ peers => {
+ description => "peers address list.",
+ type => 'string', format => 'ip-list'
+ },
+ };
+}
+
+sub options {
+ return {
+ 'asn' => { optional => 0 },
+ 'peers' => { optional => 0 },
+ };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+ my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
+
+ my @peers;
+ @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
+
+ my $local_node = PVE::INotify::nodename();
+
+ my $asn = $plugin_config->{asn};
+ my $ebgp = undef;
+ my $loopback = undef;
+ my $autortas = undef;
+ my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
+ if ($bgprouter) {
+ $ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn};
+ $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+ $asn = $bgprouter->{asn} if $bgprouter->{asn};
+ $autortas = $plugin_config->{'asn'} if $ebgp;
+ }
+
+ return if !$asn;
+
+ my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
+
+ my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
+
+ my $remoteas = $ebgp ? "external" : $asn;
+
+ #global options
+ my @controller_config = (
+ "bgp router-id $ifaceip",
+ "no bgp default ipv4-unicast",
+ "coalesce-time 1000",
+ );
+
+ push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
+
+ @controller_config = ();
+
+ #VTEP neighbors
+ push @controller_config, "neighbor VTEP peer-group";
+ push @controller_config, "neighbor VTEP remote-as $remoteas";
+ push @controller_config, "neighbor VTEP bfd";
+
+ if($ebgp && $loopback) {
+ push @controller_config, "neighbor VTEP ebgp-multihop 10";
+ push @controller_config, "neighbor VTEP update-source $loopback";
+ }
+
+ # VTEP peers
+ foreach my $address (@peers) {
+ next if $address eq $ifaceip;
+ push @controller_config, "neighbor $address peer-group VTEP";
+ }
+
+ push(@{$bgp->{""}}, @controller_config);
+
+ # address-family l2vpn
+ @controller_config = ();
+ push @controller_config, "neighbor VTEP route-map MAP_VTEP_IN in";
+ push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out";
+ push @controller_config, "neighbor VTEP activate";
+ push @controller_config, "advertise-all-vni";
+ push @controller_config, "autort as $autortas" if $autortas;
+ push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
+
+ my $routemap = { rule => undef, action => "permit" };
+ push(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap );
+ push(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap );
+
+ return $config;
+}
+
+sub generate_controller_zone_config {
+ my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+ my $local_node = PVE::INotify::nodename();
+
+ my $vrf = "vrf_$id";
+ my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
+ my $exitnodes = $plugin_config->{'exitnodes'};
+ my $exitnodes_primary = $plugin_config->{'exitnodes-primary'};
+ my $advertisesubnets = $plugin_config->{'advertise-subnets'};
+ my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'};
+ my $rt_import;
+ $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] if $plugin_config->{'rt-import'};
+
+ my $asn = $controller->{asn};
+ my @peers;
+ @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'};
+ my $ebgp = undef;
+ my $loopback = undef;
+ my $autortas = undef;
+ my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
+ if($bgprouter) {
+ $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn};
+ $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+ $asn = $bgprouter->{asn} if $bgprouter->{asn};
+ $autortas = $controller->{'asn'} if $ebgp;
+ }
+
+ return if !$vrf || !$vrfvxlan || !$asn;
+
+ my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
+
+ # vrf
+ my @controller_config = ();
+ push @controller_config, "vni $vrfvxlan";
+ push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config);
+
+ #main vrf router
+ @controller_config = ();
+ push @controller_config, "bgp router-id $ifaceip";
+# push @controller_config, "!";
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config);
+
+ if ($autortas) {
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target import $autortas:$vrfvxlan");
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target export $autortas:$vrfvxlan");
+ }
+
+ my $is_gateway = $exitnodes->{$local_node};
+
+ if ($is_gateway) {
+
+ if (!$exitnodes_primary || $exitnodes_primary eq $local_node) {
+ #filter default type5 route coming from other exit nodes on primary node or both nodes if no primary is defined.
+ my $routemap_config = ();
+ push @{$routemap_config}, "match evpn route-type prefix";
+ my $routemap = { rule => $routemap_config, action => "deny" };
+ unshift(@{$config->{frr_routemap}->{'MAP_VTEP_IN'}}, $routemap);
+ } elsif ($exitnodes_primary ne $local_node) {
+ my $routemap_config = ();
+ push @{$routemap_config}, "match evpn vni $vrfvxlan";
+ push @{$routemap_config}, "match evpn route-type prefix";
+ push @{$routemap_config}, "set metric 200";
+ my $routemap = { rule => $routemap_config, action => "permit" };
+ unshift(@{$config->{frr_routemap}->{'MAP_VTEP_OUT'}}, $routemap);
+ }
+
+
+ if (!$exitnodes_local_routing) {
+ @controller_config = ();
+ #import /32 routes of evpn network from vrf1 to default vrf (for packet return)
+ push @controller_config, "import vrf $vrf";
+ push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
+ push(@{$config->{frr}->{router}->{"bgp $asn"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
+
+ @controller_config = ();
+ #redistribute connected to be able to route to local vms on the gateway
+ push @controller_config, "redistribute connected";
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
+ }
+
+ @controller_config = ();
+ #add default originate to announce 0.0.0.0/0 type5 route in evpn
+ push @controller_config, "default-originate ipv4";
+ push @controller_config, "default-originate ipv6";
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
+ } elsif ($advertisesubnets) {
+
+ @controller_config = ();
+ #redistribute connected networks
+ push @controller_config, "redistribute connected";
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
+
+ @controller_config = ();
+ #advertise connected networks type5 route in evpn
+ push @controller_config, "advertise ipv4 unicast";
+ push @controller_config, "advertise ipv6 unicast";
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
+ }
+
+ if ($rt_import) {
+ @controller_config = ();
+ foreach my $rt (sort @{$rt_import}) {
+ push @controller_config, "route-target import $rt";
+ }
+ push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
+ }
+
+ return $config;
+}
+
+sub generate_controller_vnet_config {
+ my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_;
+
+ my $exitnodes = $zone->{'exitnodes'};
+ my $exitnodes_local_routing = $zone->{'exitnodes-local-routing'};
+
+ return if !$exitnodes_local_routing;
+
+ my $local_node = PVE::INotify::nodename();
+ my $is_gateway = $exitnodes->{$local_node};
+
+ return if !$is_gateway;
+
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+ my @controller_config = ();
+ foreach my $subnetid (sort keys %{$subnets}) {
+ my $subnet = $subnets->{$subnetid};
+ my $cidr = $subnet->{cidr};
+ push @controller_config, "ip route $cidr 10.255.255.2 xvrf_$zoneid";
+ }
+ push(@{$config->{frr}->{''}}, @controller_config);
+}
+
+sub on_delete_hook {
+ my ($class, $controllerid, $zone_cfg) = @_;
+
+ # verify that zone is associated to this controller
+ foreach my $id (keys %{$zone_cfg->{ids}}) {
+ my $zone = $zone_cfg->{ids}->{$id};
+ die "controller $controllerid is used by $id"
+ if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
+ }
+}
+
+sub on_update_hook {
+ my ($class, $controllerid, $controller_cfg) = @_;
+
+ # we can only have 1 evpn controller / 1 asn by server
+
+ my $controllernb = 0;
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ next if $id eq $controllerid;
+ my $controller = $controller_cfg->{ids}->{$id};
+ next if $controller->{type} ne "evpn";
+ $controllernb++;
+ die "only 1 global evpn controller can be defined" if $controllernb >= 1;
+ }
+}
+
+sub find_bgp_controller {
+ my ($nodename, $controller_cfg) = @_;
+
+ my $controller = undef;
+ foreach my $id (keys %{$controller_cfg->{ids}}) {
+ $controller = $controller_cfg->{ids}->{$id};
+ next if $controller->{type} ne 'bgp';
+ next if $controller->{node} ne $nodename;
+ last;
+ }
+
+ return $controller;
+}
+
+
+sub sort_frr_config {
+ my $order = {};
+ $order->{''} = 0;
+ $order->{'vrf'} = 1;
+ $order->{'ipv4 unicast'} = 1;
+ $order->{'ipv6 unicast'} = 2;
+ $order->{'l2vpn evpn'} = 3;
+
+ my $a_val = 100;
+ my $b_val = 100;
+
+ $a_val = $order->{$a} if defined($order->{$a});
+ $b_val = $order->{$b} if defined($order->{$b});
+
+ if ($a =~ /bgp (\d+)$/) {
+ $a_val = 2;
+ }
+
+ if ($b =~ /bgp (\d+)$/) {
+ $b_val = 2;
+ }
+
+ return $a_val <=> $b_val;
+}
+
+sub generate_frr_recurse{
+ my ($final_config, $content, $parentkey, $level) = @_;
+
+ my $keylist = {};
+ $keylist->{vrf} = 1;
+ $keylist->{'address-family'} = 1;
+ $keylist->{router} = 1;
+
+ my $exitkeylist = {};
+ $exitkeylist->{vrf} = 1;
+ $exitkeylist->{'address-family'} = 1;
+
+ my $simple_exitkeylist = {};
+ $simple_exitkeylist->{router} = 1;
+
+ # FIXME: make this generic
+ my $paddinglevel = undef;
+ if ($level == 1 || $level == 2) {
+ $paddinglevel = $level - 1;
+ } elsif ($level == 3 || $level == 4) {
+ $paddinglevel = $level - 2;
+ }
+
+ my $padding = "";
+ $padding = ' ' x ($paddinglevel) if $paddinglevel;
+
+ if (ref $content eq 'HASH') {
+ foreach my $key (sort sort_frr_config keys %$content) {
+ if ($parentkey && defined($keylist->{$parentkey})) {
+ push @{$final_config}, $padding."!";
+ push @{$final_config}, $padding."$parentkey $key";
+ } elsif ($key ne '' && !defined($keylist->{$key})) {
+ push @{$final_config}, $padding."$key";
+ }
+
+ my $option = $content->{$key};
+ generate_frr_recurse($final_config, $option, $key, $level+1);
+
+ push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
+ push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey});
+ }
+ }
+
+ if (ref $content eq 'ARRAY') {
+ push @{$final_config}, map { $padding . "$_" } @$content;
+ }
+}
+
+sub generate_frr_routemap {
+ my ($final_config, $routemaps) = @_;
+
+ foreach my $id (sort keys %$routemaps) {
+
+ my $routemap = $routemaps->{$id};
+ my $order = 0;
+ foreach my $seq (@$routemap) {
+ $order++;
+ next if !defined($seq->{action});
+ my @config = ();
+ push @config, "!";
+ push @config, "route-map $id $seq->{action} $order";
+ my $rule = $seq->{rule};
+ push @config, map { " $_" } @$rule;
+ push @{$final_config}, @config;
+ push @{$final_config}, "exit";
+ }
+ }
+}
+
+sub generate_frr_list {
+ my ($final_config, $lists, $type) = @_;
+
+ my $config = [];
+
+ for my $id (sort keys %$lists) {
+ my $list = $lists->{$id};
+
+ for my $seq (sort keys %$list) {
+ my $rule = $list->{$seq};
+ push @$config, "$type $id seq $seq $rule";
+ }
+ }
+
+ if (@$config > 0) {
+ push @{$final_config}, "!", @$config;
+ }
+}
+
+sub generate_controller_rawconfig {
+ my ($class, $plugin_config, $config) = @_;
+
+ my $nodename = PVE::INotify::nodename();
+
+ my $final_config = [];
+ push @{$final_config}, "frr version 8.2.2";
+ push @{$final_config}, "frr defaults datacenter";
+ push @{$final_config}, "hostname $nodename";
+ push @{$final_config}, "log syslog informational";
+ push @{$final_config}, "service integrated-vtysh-config";
+ push @{$final_config}, "!";
+
+ if (-e "/etc/frr/frr.conf.local") {
+ my $local_conf = file_get_contents("/etc/frr/frr.conf.local");
+ parse_merge_frr_local_config($config, $local_conf);
+ }
+
+ generate_frr_recurse($final_config, $config->{frr}, undef, 0);
+ generate_frr_list($final_config, $config->{frr_access_list}, "access-list");
+ generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list");
+ generate_frr_routemap($final_config, $config->{frr_routemap});
+
+ push @{$final_config}, "!";
+ push @{$final_config}, "line vty";
+ push @{$final_config}, "!";
+
+ my $rawconfig = join("\n", @{$final_config});
+
+ return if !$rawconfig;
+ return $rawconfig;
+}
+
+sub parse_merge_frr_local_config {
+ my ($config, $local_conf) = @_;
+
+ my $section = \$config->{""};
+ my $router = undef;
+ my $routemap = undef;
+ my $routemap_config = ();
+ my $routemap_action = undef;
+
+ while ($local_conf =~ /^\s*(.+?)\s*$/gm) {
+ my $line = $1;
+ $line =~ s/^\s+|\s+$//g;
+
+ if ($line =~ m/^router (.+)$/) {
+ $router = $1;
+ $section = \$config->{'frr'}->{'router'}->{$router}->{""};
+ next;
+ } elsif ($line =~ m/^vrf (.+)$/) {
+ $section = \$config->{'frr'}->{'vrf'}->{$1};
+ next;
+ } elsif ($line =~ m/address-family (.+)$/) {
+ $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
+ next;
+ } elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
+ $routemap = $1;
+ $routemap_config = ();
+ $routemap_action = $2;
+ $section = \$config->{'frr_routemap'}->{$routemap};
+ next;
+ } elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
+ $config->{'frr_access_list'}->{$1}->{$2} = $3;
+ next;
+ } elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
+ $config->{'frr_prefix_list'}->{$1}->{$2} = $3;
+ next;
+ } elsif($line =~ m/^exit-address-family$/) {
+ next;
+ } elsif($line =~ m/^exit$/) {
+ if($router) {
+ $section = \$config->{''};
+ $router = undef;
+ } elsif($routemap) {
+ push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
+ $section = \$config->{''};
+ $routemap = undef;
+ $routemap_action = undef;
+ $routemap_config = ();
+ }
+ next;
+ } elsif($line =~ m/!/) {
+ next;
+ }
+
+ next if !$section;
+ if($routemap) {
+ push(@{$routemap_config}, $line);
+ } else {
+ push(@{$$section}, $line);
+ }
+ }
+}
+
+sub write_controller_config {
+ my ($class, $plugin_config, $config) = @_;
+
+ my $rawconfig = $class->generate_controller_rawconfig($plugin_config, $config);
+ return if !$rawconfig;
+ return if !-d "/etc/frr";
+
+ file_set_contents("/etc/frr/frr.conf", $rawconfig);
+}
+
+sub reload_controller {
+ my ($class) = @_;
+
+ my $conf_file = "/etc/frr/frr.conf";
+ my $bin_path = "/usr/lib/frr/frr-reload.py";
+
+ if (!-e $bin_path) {
+ warn "missing $bin_path. Please install frr-pythontools package";
+ return;
+ }
+
+ my $err = sub {
+ my $line = shift;
+ if ($line =~ /ERROR:/) {
+ warn "$line \n";
+ }
+ };
+
+ if (-e $conf_file && -e $bin_path) {
+ eval {
+ run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err);
+ };
+ if ($@) {
+ warn "frr reload command fail. Restarting frr.";
+ eval { run_command(['systemctl', 'restart', 'frr']); };
+ }
+ }
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Controllers::FaucetPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Tools;
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use CPAN::Meta::YAML;
+use Encode;
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+ return 'faucet';
+}
+
+sub properties {
+ return {
+ };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+ my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
+
+}
+
+sub generate_controller_zone_config {
+ my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+ my $dpid = $plugin_config->{'dp-id'};
+ my $dphex = printf("%x",$dpid);
+
+ my $zone_config = {
+ dp_id => $dphex,
+ hardware => "Open vSwitch",
+ };
+
+ $config->{faucet}->{dps}->{$id} = $zone_config;
+
+}
+
+
+sub generate_controller_vnet_config {
+ my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_;
+
+ my $mac = $plugin_config->{mac};
+ my $ipv4 = $plugin_config->{ipv4};
+ my $ipv6 = $plugin_config->{ipv6};
+ my $tag = $plugin_config->{tag};
+ my $alias = $plugin_config->{alias};
+
+ my @ips = ();
+ push @ips, $ipv4 if $ipv4;
+ push @ips, $ipv6 if $ipv6;
+
+ my $vlan_config = { vid => $tag };
+
+ $vlan_config->{description} = $alias if $alias;
+ $vlan_config->{faucet_mac} = $mac if $mac;
+ $vlan_config->{faucet_vips} = \@ips if scalar @ips > 0;
+
+ $config->{faucet}->{vlans}->{$vnetid} = $vlan_config;
+
+ push(@{$config->{faucet}->{routers}->{$zoneid}->{vlans}} , $vnetid);
+
+}
+
+sub write_controller_config {
+ my ($class, $plugin_config, $config) = @_;
+
+ my $rawconfig = encode('UTF-8', CPAN::Meta::YAML::Dump($config->{faucet}));
+
+ return if !$rawconfig;
+ return if !-d "/etc/faucet";
+
+ my $frr_config_file = "/etc/faucet/faucet.yaml";
+
+ my $writefh = IO::File->new($frr_config_file,">");
+ print $writefh $rawconfig;
+ $writefh->close();
+}
+
+sub reload_controller {
+ my ($class) = @_;
+
+ my $conf_file = "/etc/faucet/faucet.yaml";
+ my $bin_path = "/usr/bin/faucet";
+
+ if (-e $conf_file && -e $bin_path) {
+ PVE::Tools::run_command(['systemctl', 'reload', 'faucet']);
+ }
+}
+
+1;
+
--- /dev/null
+SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Controllers/$$i; done
--- /dev/null
+package PVE::Network::SDN::Controllers::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools;
+use PVE::JSONSchema;
+use PVE::Cluster;
+
+use Data::Dumper;
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/controllers.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); }
+);
+
+PVE::JSONSchema::register_standard_option('pve-sdn-controller-id', {
+ description => "The SDN controller object identifier.",
+ type => 'string', format => 'pve-sdn-controller-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-controller-id', \&parse_sdn_controller_id);
+sub parse_sdn_controller_id {
+ my ($id, $noerr) = @_;
+
+ if ($id !~ m/^[a-z][a-z0-9_-]*[a-z0-9]$/i) {
+ return undef if $noerr;
+ die "controller ID '$id' contains illegal characters\n";
+ }
+ die "controller ID '$id' can't be more length than 64 characters\n" if length($id) > 64;
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ type => {
+ description => "Plugin type.",
+ type => 'string', format => 'pve-configid',
+ type => 'string',
+ },
+ controller => get_standard_option('pve-sdn-controller-id',
+ { completion => \&PVE::Network::SDN::complete_sdn_controller }),
+ },
+};
+
+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 generate_sdn_config {
+ my ($class, $plugin_config, $node, $data, $ctime) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub generate_controller_config {
+ my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+
+sub generate_controller_zone_config {
+ my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub generate_controller_vnet_config {
+ my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_;
+
+}
+
+sub generate_controller_rawconfig {
+ my ($class, $plugin_config, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub write_controller_config {
+ my ($class, $plugin_config, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub controller_reload {
+ my ($class) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub on_delete_hook {
+ my ($class, $controllerid, $zone_cfg) = @_;
+
+ # do nothing by default
+}
+
+sub on_update_hook {
+ my ($class, $controllerid, $controller_cfg) = @_;
+
+ # do nothing by default
+}
+
+1;
--- /dev/null
+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;
+
--- /dev/null
+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
--- /dev/null
+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 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 },
+ reversev6mask => { 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, $zone, $hostname, $ip, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub add_ptr_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub del_ptr_record {
+ my ($class, $plugin_config, $zone, $ip, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub del_a_record {
+ my ($class, $plugin_config, $zone, $hostname, $ip, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub verify_zone {
+ my ($class, $plugin_config, $zone, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub get_reversedns_zone {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+}
+
+1;
--- /dev/null
+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;
+
+
--- /dev/null
+package PVE::Network::SDN::Ipams;
+
+use strict;
+use warnings;
+
+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::Ipams::PVEPlugin;
+use PVE::Network::SDN::Ipams::NetboxPlugin;
+use PVE::Network::SDN::Ipams::PhpIpamPlugin;
+use PVE::Network::SDN::Ipams::Plugin;
+
+PVE::Network::SDN::Ipams::PVEPlugin->register();
+PVE::Network::SDN::Ipams::NetboxPlugin->register();
+PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
+PVE::Network::SDN::Ipams::Plugin->init();
+
+
+sub sdn_ipams_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn ipam 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/ipams.cfg");
+ #add default internal pve
+ $config->{ids}->{pve}->{type} = 'pve';
+ return $config;
+}
+
+sub get_plugin_config {
+ my ($vnet) = @_;
+ my $ipamid = $vnet->{ipam};
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ return $ipam_cfg->{ids}->{$ipamid};
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/ipams.cfg", $cfg);
+}
+
+sub sdn_ipams_ids {
+ my ($cfg) = @_;
+
+ return keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_vnet {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::Ipams::config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ];
+}
+
+1;
+
--- /dev/null
+SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Ipams/$$i; done
--- /dev/null
+package PVE::Network::SDN::Ipams::NetboxPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+
+use base('PVE::Network::SDN::Ipams::Plugin');
+
+sub type {
+ return 'netbox';
+}
+
+sub properties {
+ return {
+ };
+}
+
+sub options {
+
+ return {
+ url => { optional => 0},
+ token => { optional => 0 },
+ };
+}
+
+# Plugin implementation
+
+sub add_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $gateway = $subnet->{gateway};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+
+ #create subnet
+ if (!$internalid) {
+
+ my $params = { prefix => $cidr };
+
+ eval {
+ my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/", $headers, $params);
+ };
+ if ($@) {
+ die "error add subnet to ipam: $@" if !$noerr;
+ }
+ }
+
+}
+
+sub del_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $gateway = $subnet->{gateway};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+ return if !$internalid;
+
+ return; #fixme: check that prefix is empty exluding gateway, before delete
+
+ eval {
+ PVE::Network::SDN::api_request("DELETE", "$url/ipam/prefixes/$internalid/", $headers);
+ };
+ if ($@) {
+ die "error deleting subnet from ipam: $@" if !$noerr;
+ }
+
+}
+
+sub add_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+
+ my $mask = $subnet->{mask};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+ $description .= " mac:$mac" if $mac && $description;
+
+ my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
+
+ eval {
+ PVE::Network::SDN::api_request("POST", "$url/ipam/ip-addresses/", $headers, $params);
+ };
+
+ if ($@) {
+ if($is_gateway) {
+ die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr;
+ } else {
+ die "error add subnet ip to ipam: ip already exist: $@" if !$noerr;
+ }
+ }
+}
+
+sub update_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+
+ my $mask = $subnet->{mask};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+ $description .= " mac:$mac" if $mac && $description;
+
+ my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
+
+ my $ip_id = get_ip_id($url, $ip, $headers);
+ die "can't find ip $ip in ipam" if !$ip_id;
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params);
+ };
+ if ($@) {
+ die "error update ip $ip : $@" if !$noerr;
+ }
+}
+
+sub add_next_freeip {
+ my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+ $description .= " mac:$mac" if $mac && $description;
+
+ my $params = { dns_name => $hostname, description => $description };
+
+ my $ip = undef;
+ eval {
+ my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params);
+ $ip = $result->{address};
+ };
+
+ if ($@) {
+ die "can't find free ip in subnet $cidr: $@" if !$noerr;
+ }
+
+ return $ip;
+}
+
+sub del_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
+
+ return if !$ip;
+
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+ my $ip_id = get_ip_id($url, $ip, $headers);
+ die "can't find ip $ip in ipam" if !$ip_id;
+
+ eval {
+ PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers);
+ };
+ if ($@) {
+ die "error delete ip $ip : $@" if !$noerr;
+ }
+}
+
+sub verify_api {
+ my ($class, $plugin_config) = @_;
+
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+
+ eval {
+ PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers);
+ };
+ if ($@) {
+ die "Can't connect to netbox api: $@";
+ }
+}
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+
+ PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config);
+}
+
+#helpers
+
+sub get_prefix_id {
+ my ($url, $cidr, $headers) = @_;
+
+ my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/prefixes/?q=$cidr", $headers);
+ my $data = @{$result->{results}}[0];
+ my $internalid = $data->{id};
+ return $internalid;
+}
+
+sub get_ip_id {
+ my ($url, $ip, $headers) = @_;
+ my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
+ my $data = @{$result->{results}}[0];
+ my $ip_id = $data->{id};
+ return $ip_id;
+}
+
+sub is_ip_gateway {
+ my ($url, $ip, $headers) = @_;
+ my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
+ my $data = @{$result->{data}}[0];
+ my $description = $data->{description};
+ my $is_gateway = 1 if $description eq 'gateway';
+ return $is_gateway;
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Ipams::PVEPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file);
+use PVE::Tools;
+use JSON;
+use NetAddr::IP qw(:lower);
+
+use Net::IP;
+use Digest::SHA;
+
+use base('PVE::Network::SDN::Ipams::Plugin');
+
+
+my $ipamdb_file = "priv/ipam.db";
+
+PVE::Cluster::cfs_register_file($ipamdb_file,
+ sub { PVE::Network::SDN::Ipams::PVEPlugin->parse_config(@_); },
+ sub { PVE::Network::SDN::Ipams::PVEPlugin->write_config(@_); });
+
+sub type {
+ return 'pve';
+}
+
+sub properties {
+}
+
+sub options {
+}
+
+# Plugin implementation
+
+sub add_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $zone = $subnet->{zone};
+ my $gateway = $subnet->{gateway};
+
+
+ cfs_lock_file($ipamdb_file, undef, sub {
+ my $db = {};
+ $db = read_db();
+
+ $db->{zones}->{$zone} = {} if !$db->{zones}->{$zone};
+ my $zonedb = $db->{zones}->{$zone};
+
+ if(!$zonedb->{subnets}->{$cidr}) {
+ #create subnet
+ $zonedb->{subnets}->{$cidr}->{ips} = {};
+ write_db($db);
+ }
+ });
+ die "$@" if $@;
+}
+
+sub del_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $zone = $subnet->{zone};
+
+ cfs_lock_file($ipamdb_file, undef, sub {
+
+ my $db = read_db();
+
+ my $dbzone = $db->{zones}->{$zone};
+ die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
+ my $dbsubnet = $dbzone->{subnets}->{$cidr};
+ die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
+
+ die "cannot delete subnet '$cidr', not empty\n" if keys %{$dbsubnet->{ips}} > 0;
+
+ delete $dbzone->{subnets}->{$cidr};
+
+ write_db($db);
+ });
+ die "$@" if $@;
+
+}
+
+sub add_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $zone = $subnet->{zone};
+
+ cfs_lock_file($ipamdb_file, undef, sub {
+
+ my $db = read_db();
+ my $dbzone = $db->{zones}->{$zone};
+ die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
+ my $dbsubnet = $dbzone->{subnets}->{$cidr};
+ die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
+
+ die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway}));
+ $dbsubnet->{ips}->{$ip} = {};
+ $dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway;
+
+ write_db($db);
+ });
+ die "$@" if $@;
+}
+
+sub update_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
+ return;
+}
+
+sub add_next_freeip {
+ my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $network = $subnet->{network};
+ my $zone = $subnet->{zone};
+ my $mask = $subnet->{mask};
+ my $freeip = undef;
+
+ cfs_lock_file($ipamdb_file, undef, sub {
+
+ my $db = read_db();
+ my $dbzone = $db->{zones}->{$zone};
+ die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
+ my $dbsubnet = $dbzone->{subnets}->{$cidr};
+ die "subnet '$cidr' doesn't exist in IPAM DB" if !$dbsubnet;
+
+ if (Net::IP::ip_is_ipv4($network) && $mask == 32) {
+ die "cannot find free IP in subnet '$cidr'\n" if defined($dbsubnet->{ips}->{$network});
+ $freeip = $network;
+ } else {
+ my $iplist = NetAddr::IP->new($cidr);
+ my $lastip = $iplist->last()->canon();
+ $iplist++ if Net::IP::ip_is_ipv4($network); #skip network address for ipv4
+ while(1) {
+ my $ip = $iplist->canon();
+ if (defined($dbsubnet->{ips}->{$ip})) {
+ last if $ip eq $lastip;
+ $iplist++;
+ next;
+ }
+ $freeip = $ip;
+ last;
+ }
+ }
+
+ die "can't find free ip in subnet '$cidr'\n" if !$freeip;
+
+ $dbsubnet->{ips}->{$freeip} = {};
+
+ write_db($db);
+ });
+ die "$@" if $@;
+
+ return "$freeip/$mask";
+}
+
+sub del_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $zone = $subnet->{zone};
+
+ cfs_lock_file($ipamdb_file, undef, sub {
+
+ my $db = read_db();
+ die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone};
+ my $dbzone = $db->{zones}->{$zone};
+ die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr};
+ my $dbsubnet = $dbzone->{subnets}->{$cidr};
+
+ die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip});
+ delete $dbsubnet->{ips}->{$ip};
+
+ write_db($db);
+ });
+ die "$@" if $@;
+}
+
+#helpers
+
+sub read_db {
+ my $db = cfs_read_file($ipamdb_file);
+ return $db;
+}
+
+sub write_db {
+ my ($cfg) = @_;
+
+ my $json = to_json($cfg);
+ cfs_write_file($ipamdb_file, $json);
+}
+
+sub write_config {
+ my ($class, $filename, $cfg) = @_;
+
+ return $cfg;
+}
+
+sub parse_config {
+ my ($class, $filename, $raw) = @_;
+
+ $raw = '{}' if !defined($raw) ||$raw eq '';
+ my $cfg = from_json($raw);
+
+ return $cfg;
+}
+
+1;
--- /dev/null
+package PVE::Network::SDN::Ipams::PhpIpamPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+
+use base('PVE::Network::SDN::Ipams::Plugin');
+
+sub type {
+ return 'phpipam';
+}
+
+sub properties {
+ return {
+ url => {
+ type => 'string',
+ },
+ token => {
+ type => 'string',
+ },
+ section => {
+ type => 'integer',
+ },
+ };
+}
+
+sub options {
+
+ return {
+ url => { optional => 0},
+ token => { optional => 0 },
+ section => { optional => 0 },
+ };
+}
+
+# Plugin implementation
+
+sub add_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $network = $subnet->{network};
+ my $mask = $subnet->{mask};
+
+ my $gateway = $subnet->{gateway};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ #search subnet
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+
+ #create subnet
+ if (!$internalid) {
+ my $params = { subnet => $network,
+ mask => $mask,
+ sectionId => $section,
+ };
+
+ eval {
+ PVE::Network::SDN::api_request("POST", "$url/subnets/", $headers, $params);
+ };
+ if ($@) {
+ die "error add subnet to ipam: $@" if !$noerr;
+ }
+ }
+
+}
+
+sub del_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+ return if !$internalid;
+
+ return; #fixme: check that prefix is empty exluding gateway, before delete
+
+ eval {
+ PVE::Network::SDN::api_request("DELETE", "$url/subnets/$internalid", $headers);
+ };
+ if ($@) {
+ die "error deleting subnet from ipam: $@" if !$noerr;
+ }
+
+}
+
+sub add_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+
+ my $params = { ip => $ip,
+ subnetId => $internalid,
+ hostname => $hostname,
+ description => $description,
+ };
+ $params->{is_gateway} = 1 if $is_gateway;
+ $params->{mac} = $mac if $mac;
+
+ eval {
+ PVE::Network::SDN::api_request("POST", "$url/addresses/", $headers, $params);
+ };
+
+ if ($@) {
+ if($is_gateway) {
+ die "error add subnet ip to ipam: ip $ip already exist: $@" if !is_ip_gateway($url, $ip, $headers) && !$noerr;
+ } else {
+ die "error add subnet ip to ipam: ip $ip already exist: $@" if !$noerr;
+ }
+ }
+}
+
+sub update_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ my $ip_id = get_ip_id($url, $ip, $headers);
+ die "can't find ip addresse in ipam" if !$ip_id;
+
+ my $params = {
+ hostname => $hostname,
+ description => $description,
+ };
+ $params->{is_gateway} = 1 if $is_gateway;
+ $params->{mac} = $mac if $mac;
+
+ eval {
+ PVE::Network::SDN::api_request("PATCH", "$url/addresses/$ip_id", $headers, $params);
+ };
+
+ if ($@) {
+ die "ipam: error update subnet ip $ip: $@" if !$noerr;
+ }
+}
+
+sub add_next_freeip {
+ my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $mask = $subnet->{mask};
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $section = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ my $internalid = get_prefix_id($url, $cidr, $headers);
+
+ my $params = { hostname => $hostname,
+ description => $description,
+ };
+
+ $params->{mac} = $mac if $mac;
+
+ my $ip = undef;
+ eval {
+ my $result = PVE::Network::SDN::api_request("POST", "$url/addresses/first_free/$internalid/", $headers, $params);
+ $ip = $result->{data};
+ };
+
+ if ($@) {
+ die "can't find free ip in subnet $cidr: $@" if !$noerr;
+ }
+
+ return "$ip/$mask" if $ip && $mask;
+}
+
+sub del_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
+
+ return if !$ip;
+
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ my $ip_id = get_ip_id($url, $ip, $headers);
+ return if !$ip_id;
+
+ eval {
+ PVE::Network::SDN::api_request("DELETE", "$url/addresses/$ip_id", $headers);
+ };
+ if ($@) {
+ die "error delete ip $ip: $@" if !$noerr;
+ }
+}
+
+sub verify_api {
+ my ($class, $plugin_config) = @_;
+
+ my $url = $plugin_config->{url};
+ my $token = $plugin_config->{token};
+ my $sectionid = $plugin_config->{section};
+ my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+ eval {
+ PVE::Network::SDN::api_request("GET", "$url/sections/$sectionid", $headers);
+ };
+ if ($@) {
+ die "Can't connect to phpipam api: $@";
+ }
+}
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+
+ PVE::Network::SDN::Ipams::PhpIpamPlugin::verify_api($class, $plugin_config);
+}
+
+
+#helpers
+
+sub get_prefix_id {
+ my ($url, $cidr, $headers) = @_;
+
+ my $result = PVE::Network::SDN::api_request("GET", "$url/subnets/cidr/$cidr", $headers);
+ my $data = @{$result->{data}}[0];
+ my $internalid = $data->{id};
+ return $internalid;
+}
+
+sub get_ip_id {
+ my ($url, $ip, $headers) = @_;
+ my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
+ my $data = @{$result->{data}}[0];
+ my $ip_id = $data->{id};
+ return $ip_id;
+}
+
+sub is_ip_gateway {
+ my ($url, $ip, $headers) = @_;
+ my $result = PVE::Network::SDN::api_request("GET", "$url/addresses/search/$ip", $headers);
+ my $data = @{$result->{data}}[0];
+ my $is_gateway = $data->{is_gateway};
+ return $is_gateway;
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Ipams::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/ipams.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-ipam-id', {
+ description => "The SDN ipam object identifier.",
+ type => 'string', format => 'pve-sdn-ipam-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-ipam-id', \&parse_sdn_ipam_id);
+sub parse_sdn_ipam_id {
+ my ($id, $noerr) = @_;
+
+ if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
+ return undef if $noerr;
+ die "ipam ID '$id' contains illegal characters\n";
+ }
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ type => {
+ description => "Plugin type.",
+ type => 'string', format => 'pve-configid',
+ type => 'string',
+ },
+ ipam => get_standard_option('pve-sdn-ipam-id',
+ { completion => \&PVE::Network::SDN::Ipams::complete_sdn_ipam }),
+ },
+};
+
+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_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub del_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub add_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub update_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+ # only update ip attributes (mac,hostname,..). Don't change the ip addresses itself, as some ipam
+ # don't allow ip address change without del/add
+
+ die "please implement inside plugin";
+}
+
+sub add_next_freeip {
+ my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub del_ip {
+ my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub on_update_hook {
+ my ($class, $plugin_config) = @_;
+}
+
+1;
--- /dev/null
+SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/$$i; done
+ make -C Controllers install
+ make -C Zones install
+ make -C Ipams install
+ make -C Dns install
+
--- /dev/null
+package PVE::Network::SDN::SubnetPlugin;
+
+use strict;
+use warnings;
+
+use Net::IP;
+use Net::Subnet qw(subnet_matcher);
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Network::SDN::Ipams;
+use PVE::Network::SDN::Vnets;
+
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/subnets.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', {
+ description => "The SDN subnet object identifier.",
+ type => 'string', format => 'pve-sdn-subnet-id',
+ type => 'string'
+});
+
+PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id);
+sub parse_sdn_subnet_id {
+ my ($id, $noerr) = @_;
+
+ my $cidr = "";
+ if($id =~ /\//) {
+ $cidr = $id;
+ } else {
+ my ($zone, $ip, $mask) = split(/-/, $id);
+ $cidr = "$ip/$mask";
+ }
+
+ if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) ||
+ PVE::JSONSchema::pve_verify_cidrv6($cidr, 1)))
+ {
+ return undef if $noerr;
+ die "value does not look like a valid CIDR network\n";
+ }
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ subnet => get_standard_option('pve-sdn-subnet-id',
+ { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }),
+ },
+};
+
+sub type {
+ return 'subnet';
+}
+
+sub private {
+ return $defaultData;
+}
+
+sub properties {
+ return {
+ vnet => {
+ type => 'string',
+ description => "associated vnet",
+ },
+ gateway => {
+ type => 'string', format => 'ip',
+ description => "Subnet Gateway: Will be assign on vnet for layer3 zones",
+ },
+ snat => {
+ type => 'boolean',
+ description => "enable masquerade for this subnet if pve-firewall",
+ },
+# #cloudinit, dhcp options
+# routes => {
+# type => 'string',
+# description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
+# },
+ dnszoneprefix => {
+ type => 'string', format => 'dns-name',
+ description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com",
+ },
+ };
+}
+
+sub options {
+ return {
+ vnet => { optional => 0 },
+ gateway => { optional => 1 },
+# routes => { optional => 1 },
+ snat => { optional => 1 },
+ dnszoneprefix => { optional => 1 },
+ };
+}
+
+sub on_update_hook {
+ my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $mask = $subnet->{mask};
+
+ my $subnet_matcher = subnet_matcher($cidr);
+
+ my $vnetid = $subnet->{vnet};
+ my $gateway = $subnet->{gateway};
+ my $ipam = $zone->{ipam};
+ my $dns = $zone->{dns};
+ my $dnszone = $zone->{dnszone};
+ my $reversedns = $zone->{reversedns};
+
+ my $old_gateway = $old_subnet->{gateway} if $old_subnet;
+ my $mac = undef;
+
+ if($vnetid) {
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+ raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet;
+ raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware};
+ $mac = $vnet->{mac};
+ }
+
+ my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32;
+
+ #for /32 pointopoint, we allow gateway outside the subnet
+ raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint;
+
+
+ if ($ipam) {
+ PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
+
+ #don't register gateway for pointopoint
+ return if $pointopoint;
+
+ #delete gateway on removal
+ if (!defined($gateway) && $old_gateway) {
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
+ };
+ warn if $@;
+ }
+ if(!$old_gateway || $gateway && $gateway ne $old_gateway) {
+ my $hostname = "$vnetid-gw";
+ my $description = "gateway";
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, $description, 1);
+ }
+
+ #delete old gateway after update
+ if($gateway && $old_gateway && $gateway ne $old_gateway) {
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
+ };
+ warn if $@;
+ }
+ }
+}
+
+sub on_delete_hook {
+ my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_;
+
+ return;
+}
+
+1;
--- /dev/null
+package PVE::Network::SDN::Subnets;
+
+use strict;
+use warnings;
+
+use Net::Subnet qw(subnet_matcher);
+use Net::IP;
+use NetAddr::IP qw(:lower);
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Network::SDN::Dns;
+use PVE::Network::SDN::Ipams;
+
+use PVE::Network::SDN::SubnetPlugin;
+PVE::Network::SDN::SubnetPlugin->register();
+PVE::Network::SDN::SubnetPlugin->init();
+
+sub sdn_subnets_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn subnet ID specified\n" if !$id;
+
+ my $scfg = $cfg->{ids}->{$id};
+ die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg);
+
+ if($scfg) {
+ my ($zone, $network, $mask) = split(/-/, $id);
+ $scfg->{cidr} = "$network/$mask";
+ $scfg->{zone} = $zone;
+ $scfg->{network} = $network;
+ $scfg->{mask} = $mask;
+ }
+
+ return $scfg;
+}
+
+sub config {
+ my $config = cfs_read_file("sdn/subnets.cfg");
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/subnets.cfg", $cfg);
+}
+
+sub sdn_subnets_ids {
+ my ($cfg) = @_;
+
+ return sort keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_subnet {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::Subnets::config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ];
+}
+
+sub get_subnet {
+ my ($subnetid, $running) = @_;
+
+ my $cfg = {};
+ if($running) {
+ my $cfg = PVE::Network::SDN::running_config();
+ $cfg = $cfg->{subnets};
+ } else {
+ $cfg = PVE::Network::SDN::Subnets::config();
+ }
+
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1);
+ return $subnet;
+}
+
+sub find_ip_subnet {
+ my ($ip, $mask, $subnets) = @_;
+
+ my $subnet = undef;
+ my $subnetid = undef;
+
+ foreach my $id (sort keys %{$subnets}) {
+
+ next if $mask ne $subnets->{$id}->{mask};
+ my $cidr = $subnets->{$id}->{cidr};
+ my $subnet_matcher = subnet_matcher($cidr);
+ next if !$subnet_matcher->($ip);
+ $subnet = $subnets->{$id};
+ $subnetid = $id;
+ last;
+ }
+ die "can't find any subnet for ip $ip" if !$subnet;
+
+ return ($subnetid, $subnet);
+}
+
+sub verify_dns_zone {
+ 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);
+}
+
+sub get_reversedns_zone {
+ my ($subnetid, $subnet, $dns, $ip) = @_;
+
+ return if !$subnetid || !$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->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip);
+}
+
+sub add_dns_record {
+ my ($zone, $dns, $hostname, $ip) = @_;
+ return if !$zone || !$dns || !$hostname || !$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->add_a_record($plugin_config, $zone, $hostname, $ip);
+
+}
+
+sub add_dns_ptr_record {
+ my ($reversezone, $zone, $dns, $hostname, $ip) = @_;
+
+ return if !$zone || !$reversezone || !$dns || !$hostname || !$ip;
+
+ $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);
+}
+
+sub del_dns_record {
+ my ($zone, $dns, $hostname, $ip) = @_;
+
+ return if !$zone || !$dns || !$hostname || !$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_a_record($plugin_config, $zone, $hostname, $ip);
+}
+
+sub del_dns_ptr_record {
+ 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 add_subnet {
+ my ($zone, $subnetid, $subnet) = @_;
+
+ my $ipam = $zone->{ipam};
+ return if !$ipam;
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipam};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $plugin->add_subnet($plugin_config, $subnetid, $subnet);
+}
+
+sub del_subnet {
+ my ($zone, $subnetid, $subnet) = @_;
+
+ my $ipam = $zone->{ipam};
+ return if !$ipam;
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipam};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ $plugin->del_subnet($plugin_config, $subnetid, $subnet);
+}
+
+sub next_free_ip {
+ my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_;
+
+ my $cidr = undef;
+ my $ip = undef;
+ $description = '' if !$description;
+
+ my $ipamid = $zone->{ipam};
+ my $dns = $zone->{dns};
+ my $dnszone = $zone->{dnszone};
+ my $reversedns = $zone->{reversedns};
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ #verify dns zones before ipam
+ verify_dns_zone($dnszone, $dns) if !$skipdns;
+
+ 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});
+ eval {
+ $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description);
+ ($ip, undef) = split(/\//, $cidr);
+ };
+ die $@ if $@;
+ }
+
+ eval {
+ my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
+
+ if(!$skipdns) {
+ #add dns
+ add_dns_record($dnszone, $dns, $hostname, $ip);
+ #add reverse dns
+ add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
+ }
+ };
+ if ($@) {
+ #rollback
+ my $err = $@;
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
+ };
+ die $err;
+ }
+ return $cidr;
+}
+
+sub add_ip {
+ my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_;
+
+ return if !$subnet || !$ip;
+
+ my $ipaddr = NetAddr::IP->new($ip);
+ $ip = $ipaddr->canon();
+
+ my $ipamid = $zone->{ipam};
+ my $dns = $zone->{dns};
+ my $dnszone = $zone->{dnszone};
+ my $reversedns = $zone->{reversedns};
+ my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ #verify dns zones before ipam
+ if(!$skipdns) {
+ 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});
+
+ eval {
+ $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
+ };
+ die $@ if $@;
+ }
+
+ eval {
+ if(!$skipdns) {
+ #add dns
+ add_dns_record($dnszone, $dns, $hostname, $ip);
+ #add reverse dns
+ add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
+ }
+ };
+ if ($@) {
+ #rollback
+ my $err = $@;
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
+ };
+ die $err;
+ }
+}
+
+sub update_ip {
+ my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
+
+ return if !$subnet || !$ip;
+
+ my $ipaddr = NetAddr::IP->new($ip);
+ $ip = $ipaddr->canon();
+
+ my $ipamid = $zone->{ipam};
+ my $dns = $zone->{dns};
+ my $dnszone = $zone->{dnszone};
+ my $reversedns = $zone->{reversedns};
+ my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ #verify dns zones before ipam
+ if(!$skipdns) {
+ 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});
+ eval {
+ $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+ };
+ die $@ if $@;
+ }
+
+ return if $hostname eq $oldhostname;
+
+ eval {
+ if(!$skipdns) {
+ #add dns
+ del_dns_record($dnszone, $dns, $oldhostname, $ip);
+ add_dns_record($dnszone, $dns, $hostname, $ip);
+ #add reverse dns
+ del_dns_ptr_record($reversednszone, $reversedns, $ip);
+ add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
+ }
+ };
+}
+
+sub del_ip {
+ my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_;
+
+ return if !$subnet || !$ip;
+
+ my $ipaddr = NetAddr::IP->new($ip);
+ $ip = $ipaddr->canon();
+
+ my $ipamid = $zone->{ipam};
+ my $dns = $zone->{dns};
+ my $dnszone = $zone->{dnszone};
+ my $reversedns = $zone->{reversedns};
+ my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
+ my $dnszoneprefix = $subnet->{dnszoneprefix};
+ $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
+
+ if(!$skipdns) {
+ 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, $subnet, $ip);
+ }
+
+ eval {
+ if(!$skipdns) {
+ del_dns_record($dnszone, $dns, $hostname, $ip);
+ del_dns_ptr_record($reversednszone, $reversedns, $ip);
+ }
+ };
+ if ($@) {
+ warn $@;
+ }
+}
+
+1;
--- /dev/null
+package PVE::Network::SDN::VnetPlugin;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
+
+use PVE::SectionConfig;
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file('sdn/vnets.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); });
+
+PVE::JSONSchema::register_standard_option('pve-sdn-vnet-id', {
+ description => "The SDN vnet object identifier.",
+ type => 'string', format => 'pve-sdn-vnet-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-vnet-id', \&parse_sdn_vnet_id);
+sub parse_sdn_vnet_id {
+ my ($id, $noerr) = @_;
+
+ if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
+ return undef if $noerr;
+ die "vnet ID '$id' contains illegal characters\n";
+ }
+ die "vnet ID '$id' can't be more length than 8 characters\n" if length($id) > 8;
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ vnet => get_standard_option('pve-sdn-vnet-id',
+ { completion => \&PVE::Network::SDN::Vnets::complete_sdn_vnet }),
+ },
+};
+
+sub type {
+ return 'vnet';
+}
+
+sub private {
+ return $defaultData;
+}
+
+sub properties {
+ return {
+ zone => {
+ type => 'string',
+ description => "zone id",
+ },
+ type => {
+ description => "Type",
+ optional => 1,
+ },
+ tag => {
+ type => 'integer',
+ description => "vlan or vxlan id",
+ },
+ vlanaware => {
+ type => 'boolean',
+ description => 'Allow vm VLANs to pass through this vnet.',
+ },
+ alias => {
+ type => 'string',
+ description => "alias name of the vnet",
+ pattern => qr/[\(\)-_.\w\d\s]{0,256}/i,
+ maxLength => 256,
+ optional => 1,
+ },
+ };
+}
+
+sub options {
+ return {
+ zone => { optional => 0},
+ tag => { optional => 1},
+ alias => { optional => 1 },
+ vlanaware => { optional => 1 },
+ };
+}
+
+sub on_delete_hook {
+ my ($class, $vnetid, $vnet_cfg) = @_;
+
+ #verify if subnets are associated
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid);
+ raise_param_exc({ vnet => "Can't delete vnet if subnets exists"}) if $subnets;
+}
+
+sub on_update_hook {
+ my ($class, $vnetid, $vnet_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+ my $tag = $vnet->{tag};
+ my $vlanaware = $vnet->{vlanaware};
+
+ #don't allow vlanaware change if subnets are defined
+ if($vnet->{vlanaware}) {
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid);
+ raise_param_exc({ vlanaware => "vlanaware vnet is not compatible with subnets"}) if $subnets;
+ }
+}
+
+1;
--- /dev/null
+package PVE::Network::SDN::Vnets;
+
+use strict;
+use warnings;
+
+use Net::IP;
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Network::SDN;
+use PVE::Network::SDN::Subnets;
+use PVE::Network::SDN::Zones;
+
+use PVE::Network::SDN::VnetPlugin;
+PVE::Network::SDN::VnetPlugin->register();
+PVE::Network::SDN::VnetPlugin->init();
+
+sub sdn_vnets_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn vnet ID specified\n" if !$id;
+
+ my $scfg = $cfg->{ids}->{$id};
+ die "sdn vnet '$id' does not exist\n" if (!$noerr && !$scfg);
+
+ return $scfg;
+}
+
+sub config {
+ return cfs_read_file("sdn/vnets.cfg");
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/vnets.cfg", $cfg);
+}
+
+sub sdn_vnets_ids {
+ my ($cfg) = @_;
+
+ return sort keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_vnet {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::Vnets::config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_vnet_ids($cfg) ];
+}
+
+sub get_vnet {
+ my ($vnetid, $running) = @_;
+
+ return if !$vnetid;
+
+ my $scfg = {};
+ if($running) {
+ my $cfg = PVE::Network::SDN::running_config();
+ $scfg = $cfg->{vnets};
+ } else {
+ $scfg = PVE::Network::SDN::Vnets::config();
+ }
+
+ my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1);
+
+ return $vnet;
+}
+
+sub get_subnets {
+ my ($vnetid) = @_;
+
+ return if !$vnetid;
+
+ my $subnets = undef;
+ my $subnets_cfg = PVE::Network::SDN::Subnets::config();
+ foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) {
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid);
+ next if !$subnet->{vnet} || $subnet->{vnet} ne $vnetid;
+ $subnets->{$subnetid} = $subnet;
+ }
+ return $subnets;
+
+}
+
+sub get_subnet_from_vnet_cidr {
+ my ($vnetid, $cidr) = @_;
+
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+ my $zoneid = $vnet->{zone};
+ my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
+
+ my ($ip, $mask) = split(/\//, $cidr);
+ die "ip address is not in cidr format" if !$mask;
+
+ my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $mask, $subnets);
+
+ return ($zone, $subnetid, $subnet, $ip);
+}
+
+sub get_next_free_cidr {
+ my ($vnetid, $hostname, $mac, $description, $ipversion, $skipdns) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+ my $zoneid = $vnet->{zone};
+ my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
+
+ return if !$zone->{ipam};
+
+ $ipversion = 4 if !$ipversion;
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+ my $ip = undef;
+ my $subnetcount = 0;
+
+ foreach my $subnetid (sort keys %{$subnets}) {
+ my $subnet = $subnets->{$subnetid};
+ my $network = $subnet->{network};
+
+ next if $ipversion != Net::IP::ip_get_version($network);
+ $subnetcount++;
+
+ eval {
+ $ip = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns);
+ };
+ warn $@ if $@;
+ last if $ip;
+ }
+ die "can't find any free ip" if !$ip && $subnetcount > 0;
+
+ return $ip;
+}
+
+sub add_cidr {
+ my ($vnetid, $cidr, $hostname, $mac, $description, $skipdns) = @_;
+
+ return if !$vnetid;
+
+ my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, undef, $skipdns);
+}
+
+sub update_cidr {
+ my ($vnetid, $cidr, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
+
+ return if !$vnetid;
+
+ my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
+ PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns);
+}
+
+sub del_cidr {
+ my ($vnetid, $cidr, $hostname, $skipdns) = @_;
+
+ return if !$vnetid;
+
+ my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $skipdns);
+}
+
+
+
+1;
--- /dev/null
+package PVE::Network::SDN::Zones;
+
+use strict;
+use warnings;
+
+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::Vnets;
+use PVE::Network::SDN::Zones::VlanPlugin;
+use PVE::Network::SDN::Zones::QinQPlugin;
+use PVE::Network::SDN::Zones::VxlanPlugin;
+use PVE::Network::SDN::Zones::EvpnPlugin;
+use PVE::Network::SDN::Zones::FaucetPlugin;
+use PVE::Network::SDN::Zones::SimplePlugin;
+use PVE::Network::SDN::Zones::Plugin;
+
+PVE::Network::SDN::Zones::VlanPlugin->register();
+PVE::Network::SDN::Zones::QinQPlugin->register();
+PVE::Network::SDN::Zones::VxlanPlugin->register();
+PVE::Network::SDN::Zones::EvpnPlugin->register();
+PVE::Network::SDN::Zones::FaucetPlugin->register();
+PVE::Network::SDN::Zones::SimplePlugin->register();
+PVE::Network::SDN::Zones::Plugin->init();
+
+my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
+
+sub sdn_zones_config {
+ my ($cfg, $id, $noerr) = @_;
+
+ die "no sdn zone 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/zones.cfg");
+ return $config;
+}
+
+sub get_plugin_config {
+ my ($vnet) = @_;
+ my $zoneid = $vnet->{zone};
+ my $zone_cfg = PVE::Network::SDN::Zones::config();
+ return $zone_cfg->{ids}->{$zoneid};
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file("sdn/zones.cfg", $cfg);
+}
+
+sub sdn_zones_ids {
+ my ($cfg) = @_;
+
+ return sort keys %{$cfg->{ids}};
+}
+
+sub complete_sdn_zone {
+ my ($cmdname, $pname, $cvalue) = @_;
+
+ my $cfg = PVE::Network::SDN::running_config();
+
+ return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
+}
+
+sub get_zone {
+ my ($zoneid, $running) = @_;
+
+ my $cfg = {};
+ if($running) {
+ my $cfg = PVE::Network::SDN::running_config();
+ $cfg = $cfg->{vnets};
+ } else {
+ $cfg = PVE::Network::SDN::Zones::config();
+ }
+
+ my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
+
+ return $zone;
+}
+
+
+sub generate_etc_network_config {
+
+ my $cfg = PVE::Network::SDN::running_config();
+
+ my $version = $cfg->{version};
+ my $vnet_cfg = $cfg->{vnets};
+ my $zone_cfg = $cfg->{zones};
+ my $subnet_cfg = $cfg->{subnets};
+ my $controller_cfg = $cfg->{controllers};
+ return if !$vnet_cfg && !$zone_cfg;
+
+ my $interfaces_config = PVE::INotify::read_file('interfaces');
+
+ #generate configuration
+ my $config = {};
+ my $nodename = PVE::INotify::nodename();
+
+ for my $id (sort keys %{$vnet_cfg->{ids}}) {
+ my $vnet = $vnet_cfg->{ids}->{$id};
+ my $zone = $vnet->{zone};
+
+ if (!$zone) {
+ warn "can't generate vnet '$id': no zone assigned!\n";
+ next;
+ }
+
+ my $plugin_config = $zone_cfg->{ids}->{$zone};
+
+ if (!defined($plugin_config)) {
+ warn "can't generate vnet '$id': zone $zone don't exist\n";
+ next;
+ }
+
+ next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
+
+ my $controller;
+ if (my $controllerid = $plugin_config->{controller}) {
+ $controller = $controller_cfg->{ids}->{$controllerid};
+ }
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ eval {
+ $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
+ };
+ if (my $err = $@) {
+ warn "zone $zone : vnet $id : $err\n";
+ next;
+ }
+ }
+
+ my $raw_network_config = "\#version:$version\n";
+ foreach my $iface (sort keys %$config) {
+ $raw_network_config .= "\n";
+ $raw_network_config .= "auto $iface\n";
+ $raw_network_config .= "iface $iface\n";
+ foreach my $option (@{$config->{$iface}}) {
+ $raw_network_config .= "\t$option\n";
+ }
+ }
+
+ return $raw_network_config;
+}
+
+sub write_etc_network_config {
+ my ($rawconfig) = @_;
+
+ return if !$rawconfig;
+
+ my $writefh = IO::File->new($local_network_sdn_file,">");
+ print $writefh $rawconfig;
+ $writefh->close();
+}
+
+sub read_etc_network_config_version {
+ my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
+
+ return if !defined($versionstr);
+
+ if ($versionstr =~ m/^\#version:(\d+)$/) {
+ return $1;
+ }
+}
+
+sub ifquery_check {
+
+ my $cmd = ['ifquery', '-a', '-c', '-o','json'];
+
+ my $result = '';
+ my $reader = sub { $result .= shift };
+
+ eval {
+ run_command($cmd, outfunc => $reader);
+ };
+
+ my $resultjson = decode_json($result);
+ my $interfaces = {};
+
+ foreach my $interface (@$resultjson) {
+ my $name = $interface->{name};
+ $interfaces->{$name} = {
+ status => $interface->{status},
+ config => $interface->{config},
+ config_status => $interface->{config_status},
+ };
+ }
+
+ return $interfaces;
+}
+
+my $warned_about_reload;
+
+sub status {
+
+ my $err_config = undef;
+
+ my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
+ my $cfg = PVE::Network::SDN::running_config();
+ my $sdn_version = $cfg->{version};
+
+ return if !$sdn_version;
+
+ if (!$local_version) {
+ $err_config = "local sdn network configuration is not yet generated, please reload";
+ if (!$warned_about_reload) {
+ $warned_about_reload = 1;
+ warn "$err_config\n";
+ }
+ } elsif ($local_version < $sdn_version) {
+ $err_config = "local sdn network configuration is too old, please reload";
+ if (!$warned_about_reload) {
+ $warned_about_reload = 1;
+ warn "$err_config\n";
+ }
+ } else {
+ $warned_about_reload = 0;
+ }
+
+ my $status = ifquery_check();
+
+ my $vnet_cfg = $cfg->{vnets};
+ my $zone_cfg = $cfg->{zones};
+ my $nodename = PVE::INotify::nodename();
+
+ my $vnet_status = {};
+ my $zone_status = {};
+
+ for my $id (sort keys %{$zone_cfg->{ids}}) {
+ next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename};
+ $zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
+ }
+
+ foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
+ my $vnet = $vnet_cfg->{ids}->{$id};
+ my $zone = $vnet->{zone};
+ next if !defined($zone);
+
+ my $plugin_config = $zone_cfg->{ids}->{$zone};
+
+ if (!defined($plugin_config)) {
+ $vnet_status->{$id}->{status} = 'error';
+ $vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured";
+ next;
+ }
+
+ next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
+
+ $vnet_status->{$id}->{zone} = $zone;
+ $vnet_status->{$id}->{status} = 'available';
+
+ if ($err_config) {
+ $vnet_status->{$id}->{status} = 'pending';
+ $vnet_status->{$id}->{statusmsg} = $err_config;
+ next;
+ }
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ my $err_msg = $plugin->status($plugin_config, $zone, $id, $vnet, $status);
+ if (@{$err_msg} > 0) {
+ $vnet_status->{$id}->{status} = 'error';
+ $vnet_status->{$id}->{statusmsg} = join(',', @{$err_msg});
+ $zone_status->{$id}->{status} = 'error';
+ }
+ }
+
+ return ($zone_status, $vnet_status);
+}
+
+sub tap_create {
+ my ($iface, $bridge) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+ if (!$vnet) { # fallback for classic bridge
+ PVE::Network::tap_create($iface, $bridge);
+ return;
+ }
+
+ my $plugin_config = get_plugin_config($vnet);
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ $plugin->tap_create($plugin_config, $vnet, $iface, $bridge);
+}
+
+sub veth_create {
+ my ($veth, $vethpeer, $bridge, $hwaddr) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+ if (!$vnet) { # fallback for classic bridge
+ PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
+ return;
+ }
+
+ my $plugin_config = get_plugin_config($vnet);
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ $plugin->veth_create($plugin_config, $vnet, $veth, $vethpeer, $bridge, $hwaddr);
+}
+
+sub tap_plug {
+ my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+ if (!$vnet) { # fallback for classic bridge
+ my $interfaces_config = PVE::INotify::read_file('interfaces');
+ my $opts = {};
+ $opts->{learning} = 0 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'};
+ PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts);
+ return;
+ }
+
+ my $plugin_config = get_plugin_config($vnet);
+ my $nodename = PVE::INotify::nodename();
+
+ die "vnet $bridge is not allowed on this node\n"
+ if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
+
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ $plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
+}
+
+sub add_bridge_fdb {
+ my ($iface, $macaddr, $bridge, $firewall) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+ if (!$vnet) { # fallback for classic bridge
+ PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall);
+ return;
+ }
+
+ my $plugin_config = get_plugin_config($vnet);
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ PVE::Network::add_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'};
+}
+
+sub del_bridge_fdb {
+ my ($iface, $macaddr, $bridge, $firewall) = @_;
+
+ my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
+ if (!$vnet) { # fallback for classic bridge
+ PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall);
+ return;
+ }
+
+ my $plugin_config = get_plugin_config($vnet);
+ my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
+ PVE::Network::del_bridge_fdb($iface, $macaddr, $firewall) if $plugin_config->{'bridge-disable-mac-learning'};
+}
+
+1;
+
--- /dev/null
+package PVE::Network::SDN::Zones::EvpnPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::VxlanPlugin;
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw($IPV4RE);
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+use Net::IP;
+
+use PVE::Network::SDN::Controllers::EvpnPlugin;
+
+use base('PVE::Network::SDN::Zones::VxlanPlugin');
+
+sub type {
+ return 'evpn';
+}
+
+PVE::JSONSchema::register_format('pve-sdn-bgp-rt', \&pve_verify_sdn_bgp_rt);
+sub pve_verify_sdn_bgp_rt {
+ my ($rt) = @_;
+
+ if ($rt =~ m/^(\d+):(\d+)$/) {
+ my $asn = $1;
+ my $id = $2;
+
+ if ($asn < 0 || $asn > 4294967295) {
+ die "value does not look like a valid bgp route-target\n";
+ }
+ if ($id < 0 || $id > 4294967295) {
+ die "value does not look like a valid bgp route-target\n";
+ }
+ } else {
+ die "value does not look like a valid bgp route-target\n";
+ }
+ return $rt;
+}
+
+sub properties {
+ return {
+ 'vrf-vxlan' => {
+ type => 'integer',
+ description => "l3vni.",
+ },
+ 'controller' => {
+ type => 'string',
+ description => "Frr router name",
+ },
+ 'mac' => {
+ type => 'string',
+ description => "Anycast logical router mac address",
+ optional => 1, format => 'mac-addr'
+ },
+ 'exitnodes' => get_standard_option('pve-node-list'),
+ 'exitnodes-local-routing' => {
+ type => 'boolean',
+ description => "Allow exitnodes to connect to evpn guests",
+ optional => 1
+ },
+ 'exitnodes-primary' => get_standard_option('pve-node', {
+ description => "Force traffic to this exitnode first."}),
+ 'advertise-subnets' => {
+ type => 'boolean',
+ description => "Advertise evpn subnets if you have silent hosts",
+ optional => 1
+ },
+ 'disable-arp-nd-suppression' => {
+ type => 'boolean',
+ description => "Disable ipv4 arp && ipv6 neighbour discovery suppression",
+ optional => 1
+ },
+ 'rt-import' => {
+ type => 'string',
+ description => "Route-Target import",
+ optional => 1, format => 'pve-sdn-bgp-rt-list'
+ }
+ };
+}
+
+sub options {
+ return {
+ nodes => { optional => 1},
+ 'vrf-vxlan' => { optional => 0 },
+ controller => { optional => 0 },
+ exitnodes => { optional => 1 },
+ 'exitnodes-local-routing' => { optional => 1 },
+ 'exitnodes-primary' => { optional => 1 },
+ 'advertise-subnets' => { optional => 1 },
+ 'disable-arp-nd-suppression' => { optional => 1 },
+ 'rt-import' => { optional => 1 },
+ mtu => { optional => 1 },
+ mac => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ my $tag = $vnet->{tag};
+ my $alias = $vnet->{alias};
+ my $mac = $plugin_config->{'mac'};
+
+ my $vrf_iface = "vrf_$zoneid";
+ my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
+ my $local_node = PVE::INotify::nodename();
+
+ die "missing vxlan tag" if !$tag;
+ die "missing controller" if !$controller;
+ warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware};
+
+ my @peers = PVE::Tools::split_list($controller->{'peers'});
+ my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg);
+ my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+ my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
+ my $is_evpn_gateway = $plugin_config->{'exitnodes'}->{$local_node};
+ my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'};
+
+
+ my $mtu = 1450;
+ $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu};
+ $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
+
+ #vxlan interface
+ my $vxlan_iface = "vxlan_$vnetid";
+ my @iface_config = ();
+ push @iface_config, "vxlan-id $tag";
+ push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
+ push @iface_config, "bridge-learning off";
+ push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'};
+
+ push @iface_config, "mtu $mtu" if $mtu;
+ push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface};
+
+ #vnet bridge
+ @iface_config = ();
+
+ my $address = {};
+ my $ipv4 = undef;
+ my $ipv6 = undef;
+ my $enable_forward_v4 = undef;
+ my $enable_forward_v6 = undef;
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+ foreach my $subnetid (sort keys %{$subnets}) {
+ my $subnet = $subnets->{$subnetid};
+ my $cidr = $subnet->{cidr};
+ my $mask = $subnet->{mask};
+
+ my $gateway = $subnet->{gateway};
+ if ($gateway) {
+ push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway});
+ $address->{$gateway} = 1;
+ }
+
+ my $iptables = undef;
+ my $checkrouteip = undef;
+ my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4;
+
+ if ($ipversion == 6) {
+ $ipv6 = 1;
+ $iptables = "ip6tables";
+ $checkrouteip = '2001:4860:4860::8888';
+ $enable_forward_v6 = 1 if $gateway;
+ } else {
+ $ipv4 = 1;
+ $iptables = "iptables";
+ $checkrouteip = '8.8.8.8';
+ $enable_forward_v4 = 1 if $gateway;
+ }
+
+ if ($subnet->{snat}) {
+
+ #find outgoing interface
+ my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip);
+ if ($outip && $outiface && $is_evpn_gateway) {
+ #use snat, faster than masquerade
+ push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
+ push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
+ #add conntrack zone once on outgoing interface
+ push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1";
+ push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1";
+ }
+ }
+ }
+
+ push @iface_config, "hwaddress $mac" if $mac;
+ push @iface_config, "bridge_ports $vxlan_iface";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "alias $alias" if $alias;
+ push @iface_config, "ip-forward on" if $enable_forward_v4;
+ push @iface_config, "ip6-forward on" if $enable_forward_v6;
+ push @iface_config, "arp-accept on" if $ipv4||$ipv6;
+ push @iface_config, "vrf $vrf_iface" if $vrf_iface;
+ push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+ if ($vrf_iface) {
+ #vrf interface
+ @iface_config = ();
+ push @iface_config, "vrf-table auto";
+ if(!$is_evpn_gateway) {
+ push @iface_config, "post-up ip route add vrf $vrf_iface unreachable default metric 4278198272";
+ } else {
+ push @iface_config, "post-up ip route del vrf $vrf_iface unreachable default metric 4278198272";
+ }
+
+ push(@{$config->{$vrf_iface}}, @iface_config) if !$config->{$vrf_iface};
+
+ if ($vrfvxlan) {
+ #l3vni vxlan interface
+ my $iface_vrf_vxlan = "vrfvx_$zoneid";
+ @iface_config = ();
+ push @iface_config, "vxlan-id $vrfvxlan";
+ push @iface_config, "vxlan-local-tunnelip $ifaceip" if $ifaceip;
+ push @iface_config, "bridge-learning off";
+ push @iface_config, "bridge-arp-nd-suppress on" if !$plugin_config->{'disable-arp-nd-suppression'};
+ push @iface_config, "mtu $mtu" if $mtu;
+ push(@{$config->{$iface_vrf_vxlan}}, @iface_config) if !$config->{$iface_vrf_vxlan};
+
+ #l3vni bridge
+ my $brvrf = "vrfbr_$zoneid";
+ @iface_config = ();
+ push @iface_config, "bridge-ports $iface_vrf_vxlan";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "vrf $vrf_iface";
+ push(@{$config->{$brvrf}}, @iface_config) if !$config->{$brvrf};
+ }
+
+ if ( $is_evpn_gateway && $exitnodes_local_routing ) {
+ #add a veth pair for local cross-vrf routing
+ my $iface_xvrf = "xvrf_$zoneid";
+ my $iface_xvrfp = "xvrfp_$zoneid";
+
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "address 10.255.255.1/30";
+ push @iface_config, "veth-peer-name $iface_xvrfp";
+ push @iface_config, "mtu ".($mtu+50) if $mtu;
+ push(@{$config->{$iface_xvrf}}, @iface_config) if !$config->{$iface_xvrf};
+
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "address 10.255.255.2/30";
+ push @iface_config, "veth-peer-name $iface_xvrf";
+ push @iface_config, "vrf $vrf_iface";
+ push @iface_config, "mtu ".($mtu+50) if $mtu;
+ push(@{$config->{$iface_xvrfp}}, @iface_config) if !$config->{$iface_xvrfp};
+ }
+ }
+ return $config;
+}
+
+sub on_update_hook {
+ my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
+
+ # verify that controller exist
+ my $controller = $zone_cfg->{ids}->{$zoneid}->{controller};
+ if (!defined($controller_cfg->{ids}->{$controller})) {
+ die "controller $controller don't exist";
+ } else {
+ die "$controller is not a evpn controller type" if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn';
+ }
+
+ #vrf-vxlan need to be defined
+
+ my $vrfvxlan = $zone_cfg->{ids}->{$zoneid}->{'vrf-vxlan'};
+ # verify that vrf-vxlan is not already declared in another zone
+ foreach my $id (keys %{$zone_cfg->{ids}}) {
+ next if $id eq $zoneid;
+ die "vrf-vxlan $vrfvxlan is already declared in $id"
+ if (defined($zone_cfg->{ids}->{$id}->{'vrf-vxlan'}) && $zone_cfg->{ids}->{$id}->{'vrf-vxlan'} eq $vrfvxlan);
+ }
+
+ if (!defined($zone_cfg->{ids}->{$zoneid}->{'mac'})) {
+ my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
+ $zone_cfg->{ids}->{$zoneid}->{'mac'} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
+ }
+}
+
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+ my $tag = $vnet->{tag};
+
+ raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag);
+ raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216;
+
+ # verify that tag is not already defined globally (vxlan-id are unique)
+ foreach my $id (keys %{$vnet_cfg->{ids}}) {
+ next if $id eq $vnetid;
+ my $othervnet = $vnet_cfg->{ids}->{$id};
+ my $other_tag = $othervnet->{tag};
+ my $other_zoneid = $othervnet->{zone};
+ my $other_zone = $zone_cfg->{ids}->{$other_zoneid};
+ next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn';
+ raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag;
+ }
+}
+
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Zones::FaucetPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::VlanPlugin;
+
+use base('PVE::Network::SDN::Zones::VlanPlugin');
+
+sub type {
+ return 'faucet';
+}
+
+sub properties {
+ return {
+ 'dp-id' => {
+ type => 'integer',
+ description => 'Faucet dataplane id',
+ },
+ };
+}
+
+sub options {
+
+ return {
+ nodes => { optional => 1},
+ 'dp-id' => { optional => 0 },
+# 'uplink-id' => { optional => 0 },
+ 'controller' => { optional => 0 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $uplinks, $controller, $config) = @_;
+
+ my $mtu = $vnet->{mtu};
+ my $uplink = $plugin_config->{'uplink-id'};
+ my $dpid = $plugin_config->{'dp-id'};
+ my $dphex = printf("%x",$dpid); #fixme :should be 16characters hex
+
+ my $iface = $uplinks->{$uplink}->{name};
+ $iface = "uplink${uplink}" if !$iface;
+
+ #tagged interface
+ my @iface_config = ();
+ push @iface_config, "ovs_type OVSPort";
+ push @iface_config, "ovs_bridge $zoneid";
+ push @iface_config, "ovs_mtu $mtu" if $mtu;
+ push(@{$config->{$iface}}, @iface_config) if !$config->{$iface};
+
+ #vnet bridge
+ @iface_config = ();
+ push @iface_config, "ovs_port $iface";
+ push @iface_config, "ovs_type OVSBridge";
+ push @iface_config, "ovs_mtu $mtu" if $mtu;
+
+ push @iface_config, "ovs_extra set bridge $zoneid other-config:datapath-id=$dphex";
+ push @iface_config, "ovs_extra set bridge $zoneid other-config:disable-in-band=true";
+ push @iface_config, "ovs_extra set bridge $zoneid fail_mode=secure";
+ push @iface_config, "ovs_extra set-controller $vnetid tcp:127.0.0.1:6653";
+
+ push(@{$config->{$zoneid}}, @iface_config) if !$config->{$zoneid};
+
+ return $config;
+}
+
+
+1;
+
+
--- /dev/null
+SOURCES=Plugin.pm VlanPlugin.pm VxlanPlugin.pm FaucetPlugin.pm EvpnPlugin.pm QinQPlugin.pm SimplePlugin.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+ for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Zones/$$i; done
--- /dev/null
+package PVE::Network::SDN::Zones::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(run_command);
+use PVE::JSONSchema;
+use PVE::Cluster;
+use PVE::Network;
+
+use PVE::JSONSchema qw(get_standard_option);
+use base qw(PVE::SectionConfig);
+
+PVE::Cluster::cfs_register_file(
+ 'sdn/zones.cfg',
+ sub { __PACKAGE__->parse_config(@_); },
+ sub { __PACKAGE__->write_config(@_); },
+);
+
+PVE::JSONSchema::register_standard_option('pve-sdn-zone-id', {
+ description => "The SDN zone object identifier.",
+ type => 'string', format => 'pve-sdn-zone-id',
+});
+
+PVE::JSONSchema::register_format('pve-sdn-zone-id', \&parse_sdn_zone_id);
+sub parse_sdn_zone_id {
+ my ($id, $noerr) = @_;
+
+ if ($id !~ m/^[a-z][a-z0-9]*[a-z0-9]$/i) {
+ return undef if $noerr;
+ die "zone ID '$id' contains illegal characters\n";
+ }
+ die "zone ID '$id' can't be more length than 8 characters\n" if length($id) > 8;
+ return $id;
+}
+
+my $defaultData = {
+
+ propertyList => {
+ type => {
+ description => "Plugin type.",
+ type => 'string', format => 'pve-configid',
+ type => 'string',
+ },
+ nodes => get_standard_option('pve-node-list', { optional => 1 }),
+ zone => get_standard_option('pve-sdn-zone-id', {
+ completion => \&PVE::Network::SDN::Zones::complete_sdn_zone,
+ }),
+ ipam => {
+ type => 'string',
+ description => "use a specific ipam",
+ optional => 1,
+ },
+ },
+};
+
+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 decode_value {
+ my ($class, $type, $key, $value) = @_;
+
+ if ($key eq 'nodes' || $key eq 'exitnodes') {
+ my $res = {};
+
+ foreach my $node (PVE::Tools::split_list($value)) {
+ if (PVE::JSONSchema::pve_verify_node_name($node)) {
+ $res->{$node} = 1;
+ }
+ }
+
+ return $res;
+ }
+
+ return $value;
+}
+
+sub encode_value {
+ my ($class, $type, $key, $value) = @_;
+
+ if ($key eq 'nodes' || $key eq 'exitnodes') {
+ return join(',', keys(%$value));
+ }
+
+ return $value;
+}
+
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub generate_controller_config {
+ my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub generate_controller_vnet_config {
+ my ($class, $plugin_config, $controller, $zoneid, $vnetid, $config) = @_;
+
+}
+
+sub write_controller_config {
+ my ($class, $plugin_config, $config) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub controller_reload {
+ my ($class) = @_;
+
+ die "please implement inside plugin";
+}
+
+sub on_delete_hook {
+ my ($class, $zoneid, $vnet_cfg) = @_;
+
+ # verify that no vnet are associated to this zone
+ foreach my $id (keys %{$vnet_cfg->{ids}}) {
+ my $vnet = $vnet_cfg->{ids}->{$id};
+ die "zone $zoneid is used by vnet $id"
+ if ($vnet->{type} eq 'vnet' && defined($vnet->{zone}) && $vnet->{zone} eq $zoneid);
+ }
+}
+
+sub on_update_hook {
+ my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
+
+ # do nothing by default
+}
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ # do nothing by default
+}
+
+#helpers
+sub parse_tag_number_or_range {
+ my ($str, $max, $tag) = @_;
+
+ my @elements = split(/,/, $str);
+ my $count = 0;
+ my $allowed = undef;
+
+ die "extraneous commas in list\n" if $str ne join(',', @elements);
+ foreach my $item (@elements) {
+ if ($item =~ m/^([0-9]+)-([0-9]+)$/) {
+ $count += 2;
+ my ($port1, $port2) = ($1, $2);
+ die "invalid port '$port1'\n" if $port1 > $max;
+ die "invalid port '$port2'\n" if $port2 > $max;
+ die "backwards range '$port1:$port2' not allowed, did you mean '$port2:$port1'?\n" if $port1 > $port2;
+
+ if ($tag && $tag >= $port1 && $tag <= $port2){
+ $allowed = 1;
+ last;
+ }
+
+ } elsif ($item =~ m/^([0-9]+)$/) {
+ $count += 1;
+ my $port = $1;
+ die "invalid port '$port'\n" if $port > $max;
+
+ if ($tag && $tag == $port){
+ $allowed = 1;
+ last;
+ }
+ }
+ }
+ die "tag $tag is not allowed" if $tag && !$allowed;
+
+ return (scalar(@elements) > 1);
+}
+
+sub status {
+ my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
+
+ my $err_msg = [];
+
+ # ifaces to check
+ my $ifaces = [ $vnetid ];
+
+ foreach my $iface (@{$ifaces}) {
+ if (!$status->{$iface}->{status}) {
+ push @$err_msg, "missing $iface";
+ } elsif ($status->{$iface}->{status} ne 'pass') {
+ push @$err_msg, "error $iface";
+ }
+ }
+ return $err_msg;
+}
+
+
+sub tap_create {
+ my ($class, $plugin_config, $vnet, $iface, $vnetid) = @_;
+
+ PVE::Network::tap_create($iface, $vnetid);
+}
+
+sub veth_create {
+ my ($class, $plugin_config, $vnet, $veth, $vethpeer, $vnetid, $hwaddr) = @_;
+
+ PVE::Network::veth_create($veth, $vethpeer, $vnetid, $hwaddr);
+}
+
+sub tap_plug {
+ my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_;
+
+ my $vlan_aware = PVE::Tools::file_read_firstline("/sys/class/net/$vnetid/bridge/vlan_filtering");
+ die "vm vlans are not allowed on vnet $vnetid" if !$vlan_aware && ($tag || $trunks);
+
+ my $opts = {};
+ $opts->{learning} = 0 if $plugin_config->{'bridge-disable-mac-learning'};
+ PVE::Network::tap_plug($iface, $vnetid, $tag, $firewall, $trunks, $rate, $opts);
+}
+
+#helper
+
+sub get_uplink_iface {
+ my ($interfaces_config, $uplink) = @_;
+
+ my $iface = undef;
+ foreach my $id (keys %{$interfaces_config->{ifaces}}) {
+ my $interface = $interfaces_config->{ifaces}->{$id};
+ if (my $iface_uplink = $interface->{'uplink-id'}) {
+ next if $iface_uplink ne $uplink;
+ if($interface->{type} ne 'eth' && $interface->{type} ne 'bond') {
+ warn "uplink $uplink is not a physical or bond interface";
+ next;
+ }
+ $iface = $id;
+ }
+ }
+
+ #create a dummy uplink interface if no uplink found
+ if(!$iface) {
+ warn "can't find uplink $uplink in physical interface";
+ $iface = "uplink${uplink}";
+ }
+
+ return $iface;
+}
+
+sub get_local_route_ip {
+ my ($targetip) = @_;
+
+ my $ip = undef;
+ my $interface = undef;
+
+ run_command(['/sbin/ip', 'route', 'get', $targetip], outfunc => sub {
+ if ($_[0] =~ m/src ($PVE::Tools::IPRE)/) {
+ $ip = $1;
+ }
+ if ($_[0] =~ m/dev (\S+)/) {
+ $interface = $1;
+ }
+
+ });
+ return ($ip, $interface);
+}
+
+
+sub find_local_ip_interface_peers {
+ my ($peers, $iface) = @_;
+
+ my $network_config = PVE::INotify::read_file('interfaces');
+ my $ifaces = $network_config->{ifaces};
+
+ #if iface is defined, return ip if exist (if not,try to find it on other ifaces)
+ if ($iface) {
+ my $ip = $ifaces->{$iface}->{address};
+ return ($ip,$iface) if $ip;
+ }
+
+ #is a local ip member of peers list ?
+ foreach my $address (@{$peers}) {
+ while (my $interface = each %$ifaces) {
+ my $ip = $ifaces->{$interface}->{address};
+ if ($ip && $ip eq $address) {
+ return ($ip, $interface);
+ }
+ }
+ }
+
+ #if peer is remote, find source with ip route
+ foreach my $address (@{$peers}) {
+ my ($ip, $interface) = get_local_route_ip($address);
+ return ($ip, $interface);
+ }
+}
+
+sub find_bridge {
+ my ($bridge) = @_;
+
+ die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge";
+}
+
+sub is_vlanaware {
+ my ($bridge) = @_;
+
+ return PVE::Tools::file_read_firstline("/sys/class/net/$bridge/bridge/vlan_filtering");
+}
+
+sub is_ovs {
+ my ($bridge) = @_;
+
+ my $is_ovs = !-d "/sys/class/net/$bridge/brif";
+ return $is_ovs;
+}
+
+sub get_bridge_ifaces {
+ my ($bridge) = @_;
+
+ my @bridge_ifaces = ();
+ my $dir = "/sys/class/net/$bridge/brif";
+ PVE::Tools::dir_glob_foreach($dir, '(((eth|bond)\d+|en[^.]+)(\.\d+)?)', sub {
+ push @bridge_ifaces, $_[0];
+ });
+
+ return @bridge_ifaces;
+}
+1;
--- /dev/null
+package PVE::Network::SDN::Zones::QinQPlugin;
+
+use strict;
+use warnings;
+
+use PVE::Exception qw(raise raise_param_exc);
+
+use PVE::Network::SDN::Zones::Plugin;
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+sub type {
+ return 'qinq';
+}
+
+sub properties {
+ return {
+ tag => {
+ type => 'integer',
+ minimum => 0,
+ description => "Service-VLAN Tag",
+ },
+ mtu => {
+ type => 'integer',
+ description => "MTU",
+ optional => 1,
+ },
+ 'vlan-protocol' => {
+ type => 'string',
+ enum => ['802.1q', '802.1ad'],
+ default => '802.1q',
+ optional => 1,
+ }
+ };
+}
+
+sub options {
+ return {
+ nodes => { optional => 1},
+ 'tag' => { optional => 0 },
+ 'bridge' => { optional => 0 },
+ 'bridge-disable-mac-learning' => { optional => 1 },
+ 'mtu' => { optional => 1 },
+ 'vlan-protocol' => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ my ($bridge, $mtu, $stag) = $plugin_config->@{'bridge', 'mtu', 'tag'};
+ my $vlanprotocol = $plugin_config->{'vlan-protocol'};
+
+ PVE::Network::SDN::Zones::Plugin::find_bridge($bridge);
+
+ my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
+ my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
+
+ my @iface_config = ();
+ my $zone_notag_uplink = "ln_${zoneid}";
+ my $zone_notag_uplinkpeer = "pr_${zoneid}";
+ my $zone = "z_${zoneid}";
+
+ my $vnet_bridge_ports = "";
+ if (my $ctag = $vnet->{tag}) {
+ $vnet_bridge_ports = "$zone.$ctag";
+ } else {
+ $vnet_bridge_ports = $zone_notag_uplinkpeer;
+ }
+
+ my $zone_bridge_ports = "";
+ if ($is_ovs) {
+ # ovs--->ovsintport(dot1q-tunnel tag)------->vlanawarebrige-----(tag)--->vnet
+
+ $vlanprotocol = "802.1q" if !$vlanprotocol;
+ my $svlan_iface = "sv_".$zoneid;
+
+ # ovs dot1q-tunnel port
+ @iface_config = ();
+ push @iface_config, "ovs_type OVSIntPort";
+ push @iface_config, "ovs_bridge $bridge";
+ push @iface_config, "ovs_mtu $mtu" if $mtu;
+ push @iface_config, "ovs_options vlan_mode=dot1q-tunnel tag=$stag other_config:qinq-ethtype=$vlanprotocol";
+ push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface};
+
+ # redefine main ovs bridge, ifupdown2 will merge ovs_ports
+ @{$config->{$bridge}}[0] = "ovs_ports" if !@{$config->{$bridge}}[0];
+ my @ovs_ports = split / / , @{$config->{$bridge}}[0];
+ @{$config->{$bridge}}[0] .= " $svlan_iface" if !grep( $_ eq $svlan_iface, @ovs_ports );
+
+ $zone_bridge_ports = $svlan_iface;
+
+ } elsif ($vlan_aware) {
+ # VLAN_aware_brige-(tag)----->vlanwarebridge-(tag)----->vnet
+
+ if ($vlanprotocol) {
+ @iface_config = ();
+ push @iface_config, "bridge-vlan-protocol $vlanprotocol";
+ push(@{$config->{$bridge}}, @iface_config) if !$config->{$bridge};
+ }
+
+ $zone_bridge_ports = "$bridge.$stag";
+
+ } else {
+ # eth--->eth.x(svlan)----->vlanwarebridge-(tag)----->vnet---->vnet
+
+ my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
+
+ for my $bridge_iface (@bridge_ifaces) {
+ # use named vlan interface to avoid too long names
+ my $svlan_iface = "sv_$zoneid";
+
+ # svlan
+ @iface_config = ();
+ push @iface_config, "vlan-raw-device $bridge_iface";
+ push @iface_config, "vlan-id $stag";
+ push @iface_config, "vlan-protocol $vlanprotocol" if $vlanprotocol;
+ push(@{$config->{$svlan_iface}}, @iface_config) if !$config->{$svlan_iface};
+
+ $zone_bridge_ports = $svlan_iface;
+ last;
+ }
+ }
+
+ # veth peer for notag vnet
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "veth-peer-name $zone_notag_uplinkpeer";
+ push(@{$config->{$zone_notag_uplink}}, @iface_config) if !$config->{$zone_notag_uplink};
+
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "veth-peer-name $zone_notag_uplink";
+ push(@{$config->{$zone_notag_uplinkpeer}}, @iface_config) if !$config->{$zone_notag_uplinkpeer};
+
+ # zone vlan aware bridge
+ @iface_config = ();
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "bridge-stp off";
+ push @iface_config, "bridge-ports $zone_bridge_ports $zone_notag_uplink";
+ push @iface_config, "bridge-fd 0";
+ push @iface_config, "bridge-vlan-aware yes";
+ push @iface_config, "bridge-vids 2-4094";
+ push(@{$config->{$zone}}, @iface_config) if !$config->{$zone};
+
+ # vnet bridge
+ @iface_config = ();
+ push @iface_config, "bridge_ports $vnet_bridge_ports";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ if($vnet->{vlanaware}) {
+ push @iface_config, "bridge-vlan-aware yes";
+ push @iface_config, "bridge-vids 2-4094";
+ }
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "alias $vnet->{alias}" if $vnet->{alias};
+ push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+}
+
+sub status {
+ my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
+
+ my $bridge = $plugin_config->{bridge};
+ my $err_msg = [];
+
+ if (!-d "/sys/class/net/$bridge") {
+ push @$err_msg, "missing $bridge";
+ return $err_msg;
+ }
+
+ my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
+
+ my $tag = $vnet->{tag};
+ my $vnet_uplink = "ln_".$vnetid;
+ my $vnet_uplinkpeer = "pr_".$vnetid;
+ my $zone_notag_uplink = "ln_".$zone;
+ my $zone_notag_uplinkpeer = "pr_".$zone;
+ my $zonebridge = "z_$zone";
+
+ # ifaces to check
+ my $ifaces = [ $vnetid, $bridge ];
+
+ push @$ifaces, $zonebridge;
+ push @$ifaces, $zone_notag_uplink;
+ push @$ifaces, $zone_notag_uplinkpeer;
+
+ if (!$vlan_aware) {
+ my $svlan_iface = "sv_$zone";
+ push @$ifaces, $svlan_iface;
+ }
+
+ foreach my $iface (@{$ifaces}) {
+ if (!$status->{$iface}->{status}) {
+ push @$err_msg, "missing $iface";
+ } elsif ($status->{$iface}->{status} ne 'pass') {
+ push @$err_msg, "error $iface";
+ }
+ }
+ return $err_msg;
+}
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+
+ my $tag = $vnet->{tag};
+ raise_param_exc({ tag => "VLAN tag maximal value is 4096" }) if $tag && $tag > 4096;
+
+ # verify that tag is not already defined in another vnet on same zone
+ for my $id (sort keys %{$vnet_cfg->{ids}}) {
+ next if $id eq $vnetid;
+ my $other_vnet = $vnet_cfg->{ids}->{$id};
+ next if $vnet->{zone} ne $other_vnet->{zone};
+ my $other_tag = $other_vnet->{tag};
+ if ($tag) {
+ raise_param_exc({ tag => "tag $tag already exist in zone $vnet->{zone} vnet $id"})
+ if $other_tag && $tag eq $other_tag;
+ } else {
+ raise_param_exc({ tag => "tag-less vnet already exists in zone $vnet->{zone} vnet $id"})
+ if !$other_tag;
+ }
+ }
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Zones::SimplePlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Exception qw(raise raise_param_exc);
+use PVE::Cluster;
+use PVE::Tools;
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+sub type {
+ return 'simple';
+}
+
+sub properties {
+ return {
+ dns => {
+ type => 'string',
+ description => "dns api server",
+ },
+ reversedns => {
+ type => 'string',
+ description => "reverse dns api server",
+ },
+ dnszone => {
+ type => 'string', format => 'dns-name',
+ description => "dns domain zone ex: mydomain.com",
+ }
+ };
+}
+
+sub options {
+ return {
+ nodes => { optional => 1},
+ mtu => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ return $config if$config->{$vnetid}; # nothing to do
+
+ my $mac = $vnet->{mac};
+ my $alias = $vnet->{alias};
+ my $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
+
+ # vnet bridge
+ my @iface_config = ();
+
+ my $address = {};
+ my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+
+ my $ipv4 = undef;
+ my $ipv6 = undef;
+ my $enable_forward_v4 = undef;
+ my $enable_forward_v6 = undef;
+
+ foreach my $subnetid (sort keys %{$subnets}) {
+ my $subnet = $subnets->{$subnetid};
+ my $cidr = $subnet->{cidr};
+ my $mask = $subnet->{mask};
+
+ my $gateway = $subnet->{gateway};
+ if ($gateway) {
+ push @iface_config, "address $gateway/$mask" if !defined($address->{$gateway});
+ $address->{$gateway} = 1;
+ }
+
+ my $iptables = undef;
+ my $checkrouteip = undef;
+ my $ipversion = Net::IP::ip_is_ipv6($gateway) ? 6 : 4;
+
+ if ( $ipversion == 6) {
+ $ipv6 = 1;
+ $iptables = "ip6tables";
+ $checkrouteip = '2001:4860:4860::8888';
+ $enable_forward_v6 = 1 if $gateway;
+ } else {
+ $ipv4 = 1;
+ $iptables = "iptables";
+ $checkrouteip = '8.8.8.8';
+ $enable_forward_v4 = 1 if $gateway;
+ }
+
+ #add route for /32 pointtopoint
+ push @iface_config, "up ip route add $cidr dev $vnetid" if $mask == 32 && $ipversion == 4;
+ if ($subnet->{snat}) {
+ #find outgoing interface
+ my ($outip, $outiface) = PVE::Network::SDN::Zones::Plugin::get_local_route_ip($checkrouteip);
+ if ($outip && $outiface) {
+ #use snat, faster than masquerade
+ push @iface_config, "post-up $iptables -t nat -A POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
+ push @iface_config, "post-down $iptables -t nat -D POSTROUTING -s '$cidr' -o $outiface -j SNAT --to-source $outip";
+ #add conntrack zone once on outgoing interface
+ push @iface_config, "post-up $iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1";
+ push @iface_config, "post-down $iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1";
+ }
+ }
+ }
+
+ push @iface_config, "hwaddress $mac" if $mac;
+ push @iface_config, "bridge_ports none";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ if ($vnet->{vlanaware}) {
+ push @iface_config, "bridge-vlan-aware yes";
+ push @iface_config, "bridge-vids 2-4094";
+ }
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "alias $alias" if $alias;
+ push @iface_config, "ip-forward on" if $enable_forward_v4;
+ push @iface_config, "ip6-forward on" if $enable_forward_v6;
+
+ push @{$config->{$vnetid}}, @iface_config;
+
+ return $config;
+}
+
+sub status {
+ my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
+
+ # ifaces to check
+ my $ifaces = [ $vnetid ];
+ my $err_msg = [];
+ foreach my $iface (@{$ifaces}) {
+ if (!$status->{$iface}->{status}) {
+ push @$err_msg, "missing $iface";
+ } elsif ($status->{$iface}->{status} ne 'pass') {
+ push @$err_msg, "error iface $iface";
+ }
+ }
+ return $err_msg;
+}
+
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+ my $tag = $vnet->{tag};
+
+ raise_param_exc({ tag => "vlan tag is not allowed on simple zone"}) if defined($tag);
+
+ if (!defined($vnet->{mac})) {
+ my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
+ $vnet->{mac} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
+ }
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Zones::VlanPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Exception qw(raise raise_param_exc);
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+sub type {
+ return 'vlan';
+}
+
+PVE::JSONSchema::register_format('pve-sdn-vlanrange', \&pve_verify_sdn_vlanrange);
+sub pve_verify_sdn_vlanrange {
+ my ($vlanstr) = @_;
+
+ PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vlanstr, '4096');
+
+ return $vlanstr;
+}
+
+sub properties {
+ return {
+ 'bridge' => {
+ type => 'string',
+ },
+ 'bridge-disable-mac-learning' => {
+ type => 'boolean',
+ description => "Disable auto mac learning.",
+ }
+ };
+}
+
+sub options {
+
+ return {
+ nodes => { optional => 1},
+ 'bridge' => { optional => 0 },
+ 'bridge-disable-mac-learning' => { optional => 1 },
+ mtu => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ my $bridge = $plugin_config->{bridge};
+ PVE::Network::SDN::Zones::Plugin::find_bridge($bridge);
+
+ my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
+ my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
+
+ my $tag = $vnet->{tag};
+ my $alias = $vnet->{alias};
+ my $mtu = $plugin_config->{mtu};
+
+ my $vnet_uplink = "ln_".$vnetid;
+ my $vnet_uplinkpeer = "pr_".$vnetid;
+
+ my @iface_config = ();
+
+ if($is_ovs) {
+
+ # keep vmbrXvY for compatibility with existing network
+ # eth0----ovs vmbr0--(ovsintport tag)---->vnet---->vm
+
+ @iface_config = ();
+ push @iface_config, "ovs_type OVSIntPort";
+ push @iface_config, "ovs_bridge $bridge";
+ push @iface_config, "ovs_mtu $mtu" if $mtu;
+ if($vnet->{vlanaware}) {
+ push @iface_config, "ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=$tag";
+ } else {
+ push @iface_config, "ovs_options tag=$tag";
+ }
+ push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink};
+
+ #redefine main ovs bridge, ifupdown2 will merge ovs_ports
+ @iface_config = ();
+ push @iface_config, "ovs_ports $vnet_uplink";
+ push(@{$config->{$bridge}}, @iface_config);
+
+ } elsif ($vlan_aware) {
+ # eth0----vlanaware bridge vmbr0--(vmbr0.X tag)---->vnet---->vm
+ $vnet_uplink = "$bridge.$tag";
+ } else {
+
+ # keep vmbrXvY for compatibility with existing network
+ # eth0<---->eth0.X----vmbr0v10------vnet---->vm
+
+ my $bridgevlan = $bridge."v".$tag;
+
+ my @bridge_ifaces = PVE::Network::SDN::Zones::Plugin::get_bridge_ifaces($bridge);
+
+ my $bridge_ports = "";
+ foreach my $bridge_iface (@bridge_ifaces) {
+ $bridge_ports .= " $bridge_iface.$tag";
+ }
+
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "veth-peer-name $vnet_uplinkpeer";
+ push(@{$config->{$vnet_uplink}}, @iface_config) if !$config->{$vnet_uplink};
+
+ @iface_config = ();
+ push @iface_config, "link-type veth";
+ push @iface_config, "veth-peer-name $vnet_uplink";
+ push(@{$config->{$vnet_uplinkpeer}}, @iface_config) if !$config->{$vnet_uplinkpeer};
+
+ @iface_config = ();
+ push @iface_config, "bridge_ports $bridge_ports $vnet_uplinkpeer";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ push(@{$config->{$bridgevlan}}, @iface_config) if !$config->{$bridgevlan};
+ }
+
+ #vnet bridge
+ @iface_config = ();
+ push @iface_config, "bridge_ports $vnet_uplink";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ if($vnet->{vlanaware}) {
+ push @iface_config, "bridge-vlan-aware yes";
+ push @iface_config, "bridge-vids 2-4094";
+ }
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "alias $alias" if $alias;
+ push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+ return $config;
+}
+
+sub status {
+ my ($class, $plugin_config, $zone, $vnetid, $vnet, $status) = @_;
+
+ my $bridge = $plugin_config->{bridge};
+
+ my $err_msg = [];
+ if (!-d "/sys/class/net/$bridge") {
+ push @$err_msg, "missing $bridge";
+ return $err_msg;
+ }
+
+ my $vlan_aware = PVE::Network::SDN::Zones::Plugin::is_vlanaware($bridge);
+ my $is_ovs = PVE::Network::SDN::Zones::Plugin::is_ovs($bridge);
+
+ my $tag = $vnet->{tag};
+ my $vnet_uplink = "ln_".$vnetid;
+ my $vnet_uplinkpeer = "pr_".$vnetid;
+
+ # ifaces to check
+ my $ifaces = [ $vnetid, $bridge ];
+ if($is_ovs) {
+ push @$ifaces, $vnet_uplink;
+ } elsif (!$vlan_aware) {
+ my $bridgevlan = $bridge."v".$tag;
+ push @$ifaces, $bridgevlan;
+ push @$ifaces, $vnet_uplink;
+ push @$ifaces, $vnet_uplinkpeer;
+ }
+
+ foreach my $iface (@{$ifaces}) {
+ if (!$status->{$iface}->{status}) {
+ push @$err_msg, "missing $iface";
+ } elsif ($status->{$iface}->{status} ne 'pass') {
+ push @$err_msg, "error iface $iface";
+ }
+ }
+ return $err_msg;
+}
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+ my $tag = $vnet->{tag};
+
+ raise_param_exc({ tag => "missing vlan tag"}) if !defined($vnet->{tag});
+ raise_param_exc({ tag => "vlan tag max value is 4096"}) if $vnet->{tag} > 4096;
+
+ # verify that tag is not already defined in another vnet on same zone
+ foreach my $id (keys %{$vnet_cfg->{ids}}) {
+ next if $id eq $vnetid;
+ my $othervnet = $vnet_cfg->{ids}->{$id};
+ my $other_tag = $othervnet->{tag};
+ next if $vnet->{zone} ne $othervnet->{zone};
+ raise_param_exc({ tag => "tag $tag already exist in vnet $id"}) if $other_tag && $tag eq $other_tag;
+ }
+}
+
+1;
+
+
--- /dev/null
+package PVE::Network::SDN::Zones::VxlanPlugin;
+
+use strict;
+use warnings;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Tools qw($IPV4RE);
+use PVE::INotify;
+use PVE::Network::SDN::Controllers::EvpnPlugin;
+use PVE::Exception qw(raise raise_param_exc);
+
+use base('PVE::Network::SDN::Zones::Plugin');
+
+PVE::JSONSchema::register_format('pve-sdn-vxlanrange', \&pve_verify_sdn_vxlanrange);
+sub pve_verify_sdn_vxlanrange {
+ my ($vxlanstr) = @_;
+
+ PVE::Network::SDN::Zones::Plugin::parse_tag_number_or_range($vxlanstr, '16777216');
+
+ return $vxlanstr;
+}
+
+sub type {
+ return 'vxlan';
+}
+
+sub properties {
+ return {
+ 'peers' => {
+ description => "peers address list.",
+ type => 'string', format => 'ip-list'
+ },
+ };
+}
+
+sub options {
+ return {
+ nodes => { optional => 1},
+ peers => { optional => 0 },
+ mtu => { optional => 1 },
+ dns => { optional => 1 },
+ reversedns => { optional => 1 },
+ dnszone => { optional => 1 },
+ ipam => { optional => 1 },
+ };
+}
+
+# Plugin implementation
+sub generate_sdn_config {
+ my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
+
+ my $tag = $vnet->{tag};
+ my $alias = $vnet->{alias};
+ my $multicastaddress = $plugin_config->{'multicast-address'};
+ my @peers;
+ @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
+ my $vxlan_iface = "vxlan_$vnetid";
+
+ die "missing vxlan tag" if !$tag;
+
+ my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers);
+
+ my $mtu = 1450;
+ $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu};
+ $mtu = $plugin_config->{mtu} if $plugin_config->{mtu};
+
+ #vxlan interface
+ my @iface_config = ();
+ push @iface_config, "vxlan-id $tag";
+
+ for my $address (@peers) {
+ next if $address eq $ifaceip;
+ push @iface_config, "vxlan_remoteip $address";
+ }
+
+
+ push @iface_config, "mtu $mtu" if $mtu;
+ push(@{$config->{$vxlan_iface}}, @iface_config) if !$config->{$vxlan_iface};
+
+ #vnet bridge
+ @iface_config = ();
+ push @iface_config, "bridge_ports $vxlan_iface";
+ push @iface_config, "bridge_stp off";
+ push @iface_config, "bridge_fd 0";
+ if ($vnet->{vlanaware}) {
+ push @iface_config, "bridge-vlan-aware yes";
+ push @iface_config, "bridge-vids 2-4094";
+ }
+ push @iface_config, "mtu $mtu" if $mtu;
+ push @iface_config, "alias $alias" if $alias;
+ push(@{$config->{$vnetid}}, @iface_config) if !$config->{$vnetid};
+
+ return $config;
+}
+
+sub vnet_update_hook {
+ my ($class, $vnet_cfg, $vnetid, $zone_cfg) = @_;
+
+ my $vnet = $vnet_cfg->{ids}->{$vnetid};
+ my $tag = $vnet->{tag};
+
+ raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag);
+ raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216;
+
+ # verify that tag is not already defined globally (vxlan-id are unique)
+ for my $id (sort keys %{$vnet_cfg->{ids}}) {
+ next if $id eq $vnetid;
+ my $othervnet = $vnet_cfg->{ids}->{$id};
+ my $other_tag = $othervnet->{tag};
+ my $other_zoneid = $othervnet->{zone};
+ my $other_zone = $zone_cfg->{ids}->{$other_zoneid};
+ next if $other_zone->{type} ne 'vxlan' && $other_zone->{type} ne 'evpn';
+ raise_param_exc({ tag => "vxlan tag $tag already exist in vnet $id in zone $other_zoneid "}) if $other_tag && $tag eq $other_tag;
+ }
+}
+
+1;
+
+
--- /dev/null
+all: test
+
+test: test_zones test_ipams test_dns test_subnets
+
+test_zones: run_test_zones.pl
+ ./run_test_zones.pl
+
+test_ipams: run_test_ipams.pl
+ ./run_test_ipams.pl
+
+test_dns: run_test_dns.pl
+ ./run_test_dns.pl
+
+test_subnets: run_test_subnets.pl
+ ./run_test_subnets.pl
+
+clean:
--- /dev/null
+Here a sample of command with pvesh to manage the sdn.
+
+
+#create a vlan transportzone
+pvesh create /cluster/sdn/zones/ --zone vlanzone --type vlan --ipam pve --bridge vmbr0
+#create a vnet on vlanzone
+pvesh create /cluster/sdn/vnets/ --vnet vnet100 --type vnet --zone vlanzone --tag 100
+#create a subnet on vlanzone
+pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.0.0/24 --gateway 192.168.0.1
+
+
+#create a layer2 vxlan unicast transportzone
+pvesh create /cluster/sdn/zones/ --zone vxlanunicastzone --type vxlan --ipam pve --peers 192.168.0.1,192.168.0.2,192.168.0.3
+
+#create an evpn controller
+pvesh create /cluster/sdn/controllers/ --controller evpn1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234
+
+#add a ebgp peer
+pvesh create /cluster/sdn/controllers/ --controller bgp1 --type bgp --peers 192.168.0.253,192.168.0.254 --asn 1234 --ebgp --node pxnode1
+
+#create a layer2 vxlan bgpevpn transportzone
+pvesh create /cluster/sdn/zones/ --zone layer2evpnzone --type evpn --ipam pve --controller evpn1
+
+#create a layer3 routable vxlan bgpevpn transportzone + exit-nodes
+pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller evpn1 --vrf-vxlan 4000 --exit-nodes pxnode1,pxnode2
+
+
+
+#create a vnet in the transportzone
+pvesh create /cluster/sdn/vnets/ --vnet vnet10 --type vnet --zone vlanzone --tag 10
+
+#create a vnet in the transportzone with subnets for evpn routing
+pvesh create /cluster/sdn/vnets/ --vnet vnet11 --type vnet --zone layer3evpnzone --tag 11 --mac c8:1f:66:f8:62:8d
+pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.0.0/24 --gateway 10.0.0.1
+pvesh create /cluster/sdn/vnets/ --vnet vnet12 --type vnet --zone layer3evpnzone --tag 12 --mac c8:1f:66:f8:62:8e
+pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.1.0/24 --gateway 10.0.1.1
+
+#display running configuration
+pvesh get /cluster/sdn/vnets --running
+pvesh get /cluster/sdn/zones --running
+pvesh get /cluster/sdn/controllers --running
+pvesh get /cluster/sdn/vnets/vnetX/subnets --running
+
+
+#display pending configuration
+pvesh get /cluster/sdn/vnets --pending
+pvesh get /cluster/sdn/zones --pending
+pvesh get /cluster/sdn/controllers --pending
+pvesh get /cluster/sdn/vnets/vnetX/subnets --pending
+
+
+#apply changes from /etc/pve/sdn.cfg.new to /etc/pve/sdn.cfg
+pvesh set /cluster/sdn
+
+
+#generate local /etc/network/interfaces.d/sdn and reload (need to be called on each node)
+ pvesh set /nodes/<node>/network
+
+
+display transporzone status on all cluster nodes
+#pvesh get /cluster/resources
+┌────────────────────────────────────┬─────────┬───────┬───────────┬─────────┬───────┬────────┬─────────────┬────────────┬────────────┬───────────────┬──────┬───────────┬──────────────┬────────────────┐
+│ id │ type │ cpu │ disk │ hastate │ level │ maxcpu │ maxdisk │ maxmem │ mem │ node │ pool │ status │ storage │ uptime │
+│ sdn/node1/transportzone10 │ sdn │ │ │ │ │ │ │ │ │ kvmformation1 │ │ error │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+│ sdn/node1/zone1 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+│ sdn/node1/zone4 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+
+
+
+
+#list all transport zones of a node
+
+pvesh get /nodes/<node>/sdn/zones/
+ ┌─────────────────┬───────────┐
+ │ sdn │ status │
+ ├─────────────────┼───────────┤
+ │ transportzone10 │ error │
+ ├─────────────────┼───────────┤
+ │ zone1 │ available │
+ ├─────────────────┼───────────┤
+ │ zone4 │ available │
+ └─────────────────┴───────────┘
+
+
+#list all vnet status from a node transportzone
+
+pveset get /nodes/<node>/sdn/zones/<transportzone>/content
+
+ ┌─────────┬────────┐
+ │ vnet │ status │
+ ├─────────┼────────┤
+ │ vnet100 │ error │
+ ├─────────┼────────┤
+ │ vnet101 │ error │
+ └─────────┴────────┘
+
+
+
+
--- /dev/null
+use strict;
+use warnings;
+use File::Copy;
+use PVE::Cluster qw(cfs_read_file);
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use Data::Dumper;
+
+PVE::Network::SDN::commit_config();
+my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config();
+
+PVE::Network::SDN::Zones::write_etc_network_config($network_config);
+print "/etc/network/interfaces.d/sdn\n";
+print $network_config;
+print "\n";
+
+my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config();
+
+if ($controller_config) {
+ print Dumper($controller_config);
+ PVE::Network::SDN::Controllers::write_controller_config($controller_config);
+}
--- /dev/null
+use strict;
+use warnings;
+use PVE::Network::SDN;
+use Data::Dumper;
+
+my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
+
+print Dumper($vnet_status);
+print Dumper($transport_status);
--- /dev/null
+{
+ 'ids' => {
+ 'powerdns' => {
+ 'url' => 'http://localhost:8881/api/v1/servers/localhost',
+ 'type' => 'powerdns',
+ 'key' => '1234',
+ 'ttl' => '3600'
+ },
+ },
+}
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"},{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"},{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
\ No newline at end of file
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"1.0.0.10.in-addr.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"1.0.0.10.in-addr.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"1.0.0.10.in-addr.arpa.","records":[],"type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[],"type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
--- /dev/null
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'GET',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com?rrsets=false')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple", dns => "powerdns", reversedns => "powerdns", dnszone => "domain.com" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/1/available-ips/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"prefix":"10.0.0.0/24"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'DELETE',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"address":"192.168.0.1/24","description":null,"dns_name":"toto"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
--- /dev/null
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "netbox" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","is_gateway":1,"mac":"da:65:8f:18:9b:6f","subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","mac":"da:65:8f:18:9b:6f","subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","mac":"da:65:8f:18:9b:6f"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/first_free/1/')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"mask":"24","sectionId":1,"subnet":"10.0.0.0"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/subnets/')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'DELETE',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"description":null,"hostname":"toto","ip":"192.168.0.1","is_gateway":null,"subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","is_gateway":1,"mac":"da:65:8f:18:9b:6f"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
+ }, 'HTTP::Request' );
--- /dev/null
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "phpipam" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+use Net::IP;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './dns/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $dnsid) = split(/\//, $path);
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_dns;
+ $pve_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns');
+ $pve_sdn_dns->mock(
+ config => sub {
+ my $dns_config = read_sdn_config ("$path/dns_config");
+ return $dns_config;
+ },
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ config => sub {
+ return $sdn_config;
+ },
+ api_request => sub {
+ my ($method, $url, $headers, $data) = @_;
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ my $encoded_data = $js->encode($data) if $data;
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+ die Dumper($req);
+ }
+ );
+
+
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+ my $plugin_config = $dns_cfg->{ids}->{$dnsid};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+
+ #test params;
+ my @ips = ("10.0.0.1", "2001:4860:4860::8888");
+ my $zone = "domain.com";
+ my $hostname = "myhostname";
+
+ foreach my $ip (@ips) {
+
+ my $ipversion = Net::IP::ip_is_ipv6($ip) ? "ipv6" : "ipv4";
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+ my $ip2 = $type eq 'AAAA' ? '2001:4860:4860::8844' : '127.0.0.1';
+ my $fqdn = $hostname.".".$zone.".";
+
+ my $sdn_dns_plugin = Test::MockModule->new($plugin);
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+ return undef;
+ }
+ );
+
+ ## add_a_record
+ my $test = "add_a_record";
+ my $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ my $name = "$dnsid $test";
+
+ $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_ptr_record
+ $test = "add_ptr_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_ptr_record
+ $test = "del_ptr_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_ptr_record($plugin_config, $zone, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_a_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ 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 => '3600',
+ records => [ $record ] };
+ return $rrset;
+ }
+ );
+
+ $test = "del_a_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## del_a_multiple_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ my $record = { content => $ip,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $record2 = { content => $ip2,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => '3600',
+ records => [ $record, $record2 ] };
+ return $rrset;
+ }
+ );
+
+ $test = "del_a_multiple_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_a_multiple_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ my $record2 = { content => $ip2,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => '3600',
+ records => [ $record2 ] };
+ return $rrset;
+ }
+ );
+
+ $test = "add_a_multiple_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+ }
+
+ ## verify_zone
+ my $test = "verify_zone";
+ my $expected = Dumper read_sdn_config("$path/expected.$test");
+ my $name = "$dnsid $test";
+
+ $plugin->verify_zone($plugin_config, $zone, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+}
+
+done_testing();
+
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+#my @plugins = <./ipams/*>;
+my @plugins = read_dir( './ipams/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $ipamid) = split(/\//, $path);
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ );
+
+ my $pve_sdn_ipam;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ config => sub {
+ return $sdn_config;
+ },
+ api_request => sub {
+ my ($method, $url, $headers, $data) = @_;
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ my $encoded_data = $js->encode($data) if $data;
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+ die Dumper($req);
+ }
+ );
+
+
+
+ #test params;
+ my $subnetid = "myzone-10.0.0.0-24";
+ my $ip = "10.0.0.1";
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $is_gateway = 1;
+
+
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+
+ 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 $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ get_prefix_id => sub {
+ return 1;
+ },
+ get_ip_id => sub {
+ return 1;
+ },
+ is_ip_gateway => sub {
+ return 1;
+ }
+ );
+
+ ## add_ip
+ my $test = "add_ip";
+ my $expected = Dumper read_sdn_config("$path/expected.$test");
+ my $name = "$ipamid $test";
+
+ $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_next_freeip
+ $test = "add_next_freeip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_ip
+ $test = "del_ip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## update_ip
+ $test = "update_ip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+ $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_ip_notgateway
+ $is_gateway = undef;
+ $test = "add_ip_notgateway";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ $sdn_ipam_plugin->mock(
+ get_prefix_id => sub {
+ return undef;
+ },
+ );
+
+ ## add_subnet
+ $test = "add_subnet";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+}
+
+done_testing();
+
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './subnets/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $testid) = split(/\//, $path);
+
+ print "test: $testid\n";
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ return;
+ }
+ );
+
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+
+ #test params;
+ my $subnets = $sdn_config->{subnets}->{ids};
+ my $subnetid = (keys %{$subnets})[0];
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+
+ my $subnet_cidr = $subnet->{cidr};
+ my $iplist = NetAddr::IP->new($subnet_cidr);
+ $iplist++ if Net::IP::ip_is_ipv4($iplist->canon()); #skip network address for ipv4
+ my $ip = $iplist->canon();
+ $iplist++;
+ my $ipnextfree = $iplist->canon();
+ $iplist++;
+ my $ip2 = $iplist->canon();
+
+ my $ip3 = undef;
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $is_gateway = 1;
+ my $ipamdb = {};
+
+ my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
+ my $ipam = $zone->{ipam};
+
+ my $plugin;
+ my $sdn_ipam_plugin;
+ if($ipam) {
+ $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
+ $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ read_db => sub {
+ return $ipamdb;
+ },
+ write_db => sub {
+ my ($cfg) = @_;
+ $ipamdb = $cfg;
+ }
+ );
+ }
+
+ my $pve_sdn_ipams;
+ $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_ipams->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ ## add_subnet
+ my $test = "add_subnet $subnetid";
+ my $name = "$testid $test";
+ my $result = undef;
+ my $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
+
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_ip $ip";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ if($ipam) {
+ ## add_already_exist_ip
+ $test = "add_already_exist_ip $ip";
+ $name = "$testid $test";
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+ }
+
+ ## add_second_ip
+ $test = "add_second_ip $ip2";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_next_free
+ $test = "find_next_freeip ($ipnextfree)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ $ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ }
+
+ ## del_ip
+ $test = "del_ip $ip";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ if($ipam){
+ ## del_subnet_not_empty
+ $test = "del_subnet_not_empty $subnetid";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
+ };
+
+ if ($@) {
+ is ($result, $expected, $name);
+ } else {
+ fail("$name : $@");
+ }
+ }
+
+
+ ## add_ip_rollback_failing_dns
+ $test = "add_ip_rollback_failing_dns";
+
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ die "error add dns record";
+ return;
+ }
+ );
+
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ if($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+ } else {
+ fail("$name : $@");
+ }
+
+
+ ## del_empty_subnet
+ $test = "del_empty_subnet";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{}}}}';
+
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip2, $hostname);
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip3, $hostname);
+
+ eval {
+ PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+}
+
+done_testing();
+
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+use NetAddr::IP qw(:lower);
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './vnets/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $testid) = split(/\//, $path);
+
+ print "test: $testid\n";
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+ my $pve_sdn_zones;
+ $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones');
+ $pve_sdn_zones->mock(
+ config => sub {
+ return $sdn_config->{zones};
+ },
+ );
+
+ my $pve_sdn_vnets;
+ $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets');
+ $pve_sdn_vnets->mock(
+ config => sub {
+ return $sdn_config->{vnets};
+ },
+ );
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ return;
+ }
+ );
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ #test params;
+ #test params;
+ my $subnets = $sdn_config->{subnets}->{ids};
+
+ my $subnetid = (sort keys %{$subnets})[0];
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+ my $subnet_cidr = $subnet->{cidr};
+ my $iplist = NetAddr::IP->new($subnet_cidr);
+ my $mask = $iplist->masklen();
+ my $ipversion = undef;
+
+ if (Net::IP::ip_is_ipv4($iplist->canon())){
+ $iplist++; #skip network address for ipv4
+ $ipversion = 4;
+ } else {
+ $ipversion = 6;
+ }
+
+ my $cidr1 = $iplist->canon()."/$mask";
+ $iplist++;
+ my $cidr2 = $iplist->canon()."/$mask";
+ my $cidr_outofrange = '8.8.8.8/8';
+
+ my $subnetid2 = (sort keys %{$subnets})[1];
+ my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1);
+ my $subnet2_cidr = $subnet2->{cidr};
+ my $iplist2 = NetAddr::IP->new($subnet2_cidr);
+ $iplist2++;
+ my $cidr3 = $iplist2->canon()."/$mask";
+ $iplist2++;
+ my $cidr4 = $iplist2->canon()."/$mask";
+
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $ipamdb = read_sdn_config ("$path/ipam.db");
+
+ my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
+ my $ipam = $zone->{ipam};
+
+ my $plugin;
+ my $sdn_ipam_plugin;
+ if($ipam) {
+ $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
+ $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ read_db => sub {
+ return $ipamdb;
+ },
+ write_db => sub {
+ my ($cfg) = @_;
+ $ipamdb = $cfg;
+ }
+ );
+ }
+
+ my $pve_sdn_ipams;
+ $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_ipams->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ my $vnetid = "myvnet";
+
+ ## add_ip
+ my $test = "add_cidr $cidr1";
+ my $name = "$testid $test";
+ my $result = undef;
+ my $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_already_exist_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } elsif($ipam) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_cidr $cidr2";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_ip_out_of_range_subnets $cidr_outofrange";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+
+ ## add_ip
+ $test = "add_cidr $cidr4";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+
+ $test = "find_next_free_cidr_in_second_subnet ($cidr3)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = $ipam ? $cidr3 : undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is ($result, $expected, $name);
+ }
+
+
+ $test = "del_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr $cidr3";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr not exist $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } elsif($ipam) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr outofrange $cidr_outofrange";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+
+ $test = "find_next_free_cidr_in_first_subnet ($cidr1)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = $ipam ? $cidr1 : undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is ($result, $expected, $name);
+ }
+
+ $test = "update_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "update_cidr deleted $cidr3";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+}
+
+done_testing();
+
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+
+sub read_sdn_config {
+ my ($file) = @_;
+
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @tests = grep { -d } glob './zones/*/*';
+
+foreach my $test (@tests) {
+
+ my $sdn_config = read_sdn_config ("./$test/sdn_config");
+
+ open my $fh1, '<', "./$test/interfaces" or die "can't read interfaces file";
+ my $interfaces_config = PVE::INotify::__read_etc_network_interfaces($fh1, undef, undef);
+ close $fh1;
+
+ my $pve_common_inotify;
+ $pve_common_inotify = Test::MockModule->new('PVE::INotify');
+ $pve_common_inotify->mock(
+ nodename => sub {
+ return 'localhost';
+ },
+ read_file => sub {
+ return $interfaces_config;
+ },
+ );
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ );
+
+ my $pve_sdn_zones_plugin;
+ $pve_sdn_zones_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin');
+ $pve_sdn_zones_plugin->mock(
+ get_local_route_ip => sub {
+ my $outiface = "vmbr0";
+ my $outip = $interfaces_config->{ifaces}->{$outiface}->{address};
+ return ($outip, $outiface);
+ },
+ is_vlanaware => sub {
+ return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'};
+ },
+ is_ovs => sub {
+ return 1 if $interfaces_config->{ifaces}->{vmbr0}->{'type'} eq 'OVSBridge';
+ },
+ get_bridge_ifaces => sub {
+ return ('eth0');
+ },
+ find_bridge => sub {
+ return;
+ }
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ running_config => sub {
+ return $sdn_config;
+ },
+ );
+
+ my $name = $test;
+ my $expected = read_file("./$test/expected_sdn_interfaces");
+
+ my $result = "";
+ eval {
+ $result = PVE::Network::SDN::Zones::generate_etc_network_config();
+ };
+
+ if (my $err = $@) {
+ fail($name);
+ } else {
+ is ($result, $expected, $name);
+ }
+
+ if ($sdn_config->{controllers}) {
+ my $expected = read_file("./$test/expected_controller_config");
+ my $controller_rawconfig = "";
+
+ eval {
+ my $config = PVE::Network::SDN::Controllers::generate_controller_config();
+ $controller_rawconfig = PVE::Network::SDN::Controllers::generate_controller_rawconfig($config);
+ };
+ if (my $err = $@) {
+ fail($name);
+ } else {
+ is ($controller_rawconfig, $expected, $name);
+ }
+ }
+}
+
+done_testing();
+
+
--- /dev/null
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-2a0a:1580:2000::-56' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
--- /dev/null
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "192.168.0.0/30" => {
+ "ips" =>{
+ }
+ },
+ "192.168.1.0/30" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
+
--- /dev/null
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-192.168.1.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
--- /dev/null
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "192.168.0.0/30" => {
+ "ips" =>{
+ }
+ },
+ "192.168.1.0/30" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
+
--- /dev/null
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-192.168.1.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
--- /dev/null
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "2001:db8:85a3::8a2e:370:7334/127" => {
+ "ips" =>{
+ }
+ },
+ "2001:db8:85a3::8a2e:371:7334/127" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
--- /dev/null
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2001:db8:85a3::8a2e:370:7334-127' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2001:db8:85a3::8a2e:371:7334-127' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ advertise ipv4 unicast
+ advertise ipv6 unicast
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'advertise-subnets' => 1 } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'disable-arp-nd-suppression' => 1 } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65001
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as external
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP bfd
+ neighbor BGP ebgp-multihop 3
+ neighbor 192.168.0.252 peer-group BGP
+ neighbor 192.168.0.253 peer-group BGP
+ !
+ address-family ipv4 unicast
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ autort as 65000
+ exit-address-family
+exit
+!
+router bgp 65001 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65000:1000
+ route-target export 65000:1000
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone",
+ },
+ },
+ },
+
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "evpnctl",
+ 'vrf-vxlan' => 1000,
+ },
+ },
+ },
+ controllers => {
+ ids => {
+ evpnctl => {
+ type => "evpn",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ },
+ localhost => {
+ type => "bgp",
+ 'peers' => '192.168.0.252,192.168.0.253',
+ ebgp => "1",
+ 'ebgp-multihop' => '3',
+ asn => "65001",
+ node => "localhost",
+ },
+ },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ },
+ },
+}
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+ip protocol bgp route-map correct_src
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65001
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as external
+ neighbor VTEP bfd
+ neighbor VTEP ebgp-multihop 10
+ neighbor VTEP update-source dummy1
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ bgp disable-ebgp-connected-route-check
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP bfd
+ neighbor 172.16.0.254 peer-group BGP
+ neighbor 172.17.0.254 peer-group BGP
+ !
+ address-family ipv4 unicast
+ network 192.168.0.1/32
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ autort as 65000
+ exit-address-family
+exit
+!
+router bgp 65001 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65000:1000
+ route-target export 65000:1000
+ exit-address-family
+exit
+!
+ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+route-map correct_src permit 1
+ match ip address prefix-list loopbacks_ips
+ set src 192.168.0.1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto eth0
+iface eth0 inet static
+ address 172.16.0.1/24
+
+auto eth1
+iface eth1 inet static
+ address 172.17.0.1/24
+
+auto dummy1
+iface dummy1 inet static
+ address 192.168.0.1/32
+ link-type dummy
+
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000 } },
+ },
+ controllers => {
+ ids => {
+ evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" },
+ localhost => { type => "bgp", 'peers' => '172.16.0.254,172.17.0.254', ebgp => "1", asn => "65001", loopback => 'dummy1', node => "localhost" },
+ },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+ip route 10.0.0.0/24 10.255.255.2 xvrf_myzone
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto xvrf_myzone
+iface xvrf_myzone
+ link-type veth
+ address 10.255.255.1/30
+ veth-peer-name xvrfp_myzone
+ mtu 1500
+
+auto xvrfp_myzone
+iface xvrfp_myzone
+ link-type veth
+ address 10.255.255.2/30
+ veth-peer-name xvrf_myzone
+ vrf vrf_myzone
+ mtu 1500
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 }, 'exitnodes-local-routing' => 1 },
+ },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+ match evpn vni 1000
+ match evpn route-type prefix
+ set metric 200
+exit
+!
+route-map MAP_VTEP_OUT permit 2
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'exitnodes-primary' => "othernode", exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto myvnet2
+iface myvnet2
+ address 2a08:2142:302:3::1/64
+ post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports vxlan_myvnet2
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet2
+iface vxlan_myvnet2
+ vxlan-id 200
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => "200", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ 'snat' => 1
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '2a08:2142:302:3::1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ address 2a08:2142:302:3::1/64
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 2a08:2142:302:3::1/64
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ bgp bestpath as-path multipath-relax
+ neighbor BGP peer-group
+ neighbor BGP remote-as 65000
+ neighbor BGP bfd
+ neighbor 192.168.0.1 peer-group BGP
+ neighbor 192.168.0.2 peer-group BGP
+ neighbor 192.168.0.3 peer-group BGP
+ !
+ address-family ipv4 unicast
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone",
+ },
+ },
+ },
+
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "evpnctl",
+ 'vrf-vxlan' => 1000,
+ },
+ },
+ },
+ controllers => {
+ ids => {
+ evpnctl => {
+ type => "evpn",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ },
+ localhost => {
+ type => "bgp",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ 'bgp-multipath-as-path-relax' => "1",
+ asn => "65000",
+ node => "localhost",
+ },
+ },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ },
+ },
+}
--- /dev/null
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65001:1000
+ route-target import 65002:1000
+ route-target import 65003:1000
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
\ No newline at end of file
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'rt-import' => '65001:1000,65002:1000,65003:1000' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto sv_myzone2
+iface sv_myzone2
+ vlan-raw-device eth0
+ vlan-id 20
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports sv_myzone2 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => 100,
+ type => "vnet",
+ zone => "myzone"
+ },
+ myvnet2 => {
+ type => "vnet",
+ zone => "myzone"
+ },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => {
+ bridge => "vmbr0",
+ tag => 10,
+ ipam => "pve",
+ type => "qinq",
+ },
+ },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports vmbr0.20 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto vmbr0
+iface vmbr0
+ bridge-vlan-protocol 802.1ad
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+ vlan-protocol 802.1ad
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto sv_myzone2
+iface sv_myzone2
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=20 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone sv_myzone2
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports sv_myzone2 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1ad
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 144.76.100.65/29
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
+
+auto myvnet2
+iface myvnet2
+ address 144.76.0.1/32
+ up ip route add 144.76.200.65/32 dev myvnet2
+ up ip route add 144.76.200.66/32 dev myvnet2
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
--- /dev/null
+auto eth0
+iface eth0 inet static
+ address 144.76.0.1
+ netmask 255.255.255.255
+ pointopoint 172.31.1.1
+ gateway 172.31.1.1
\ No newline at end of file
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ myvnet2 => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-144.76.100.64-29' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '144.76.100.65',
+ },
+ 'myzone-144.76.200.65-32' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '144.76.0.1',
+ },
+ 'myzone-144.76.200.66-32' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '144.76.0.1',
+ },
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 192.168.0.1/24
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '192.168.0.1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 192.168.0.1/24
+ address 2a08:2142:302:3::1/64
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
+ ip6-forward on
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '192.168.0.1',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ address 2a08:2142:302:3::1/64
+ post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip6-forward on
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
--- /dev/null
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ link-type veth
+ veth-peer-name pr_myvnet
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myvnet
+iface pr_myvnet
+ link-type veth
+ veth-peer-name ln_myvnet
+
+auto vmbr0v100
+iface vmbr0v100
+ bridge_ports eth0.100 pr_myvnet
+ bridge_stp off
+ bridge_fd 0
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vmbr0.100
+ bridge_stp off
+ bridge_fd 0
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vmbr0.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
--- /dev/null
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4096
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", vlanaware => 1, zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options tag=100
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+
+auto vmbr0
+iface vmbr0
+ ovs_ports ln_myvnet
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=100
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto vmbr0
+iface vmbr0
+ ovs_ports ln_myvnet
--- /dev/null
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan_remoteip 192.168.0.2
+ vxlan_remoteip 192.168.0.3
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
+ },
+}
--- /dev/null
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan_remoteip 192.168.0.2
+ vxlan_remoteip 192.168.0.3
+ mtu 1450
--- /dev/null
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
--- /dev/null
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
+ },
+}
+++ /dev/null
-all: test
-
-test: test_zones test_ipams test_dns test_subnets
-
-test_zones: run_test_zones.pl
- ./run_test_zones.pl
-
-test_ipams: run_test_ipams.pl
- ./run_test_ipams.pl
-
-test_dns: run_test_dns.pl
- ./run_test_dns.pl
-
-test_subnets: run_test_subnets.pl
- ./run_test_subnets.pl
+++ /dev/null
-Here a sample of command with pvesh to manage the sdn.
-
-
-#create a vlan transportzone
-pvesh create /cluster/sdn/zones/ --zone vlanzone --type vlan --ipam pve --bridge vmbr0
-#create a vnet on vlanzone
-pvesh create /cluster/sdn/vnets/ --vnet vnet100 --type vnet --zone vlanzone --tag 100
-#create a subnet on vlanzone
-pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.0.0/24 --gateway 192.168.0.1
-
-
-#create a layer2 vxlan unicast transportzone
-pvesh create /cluster/sdn/zones/ --zone vxlanunicastzone --type vxlan --ipam pve --peers 192.168.0.1,192.168.0.2,192.168.0.3
-
-#create an evpn controller
-pvesh create /cluster/sdn/controllers/ --controller evpn1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234
-
-#add a ebgp peer
-pvesh create /cluster/sdn/controllers/ --controller bgp1 --type bgp --peers 192.168.0.253,192.168.0.254 --asn 1234 --ebgp --node pxnode1
-
-#create a layer2 vxlan bgpevpn transportzone
-pvesh create /cluster/sdn/zones/ --zone layer2evpnzone --type evpn --ipam pve --controller evpn1
-
-#create a layer3 routable vxlan bgpevpn transportzone + exit-nodes
-pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller evpn1 --vrf-vxlan 4000 --exit-nodes pxnode1,pxnode2
-
-
-
-#create a vnet in the transportzone
-pvesh create /cluster/sdn/vnets/ --vnet vnet10 --type vnet --zone vlanzone --tag 10
-
-#create a vnet in the transportzone with subnets for evpn routing
-pvesh create /cluster/sdn/vnets/ --vnet vnet11 --type vnet --zone layer3evpnzone --tag 11 --mac c8:1f:66:f8:62:8d
-pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.0.0/24 --gateway 10.0.0.1
-pvesh create /cluster/sdn/vnets/ --vnet vnet12 --type vnet --zone layer3evpnzone --tag 12 --mac c8:1f:66:f8:62:8e
-pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.1.0/24 --gateway 10.0.1.1
-
-#display running configuration
-pvesh get /cluster/sdn/vnets --running
-pvesh get /cluster/sdn/zones --running
-pvesh get /cluster/sdn/controllers --running
-pvesh get /cluster/sdn/vnets/vnetX/subnets --running
-
-
-#display pending configuration
-pvesh get /cluster/sdn/vnets --pending
-pvesh get /cluster/sdn/zones --pending
-pvesh get /cluster/sdn/controllers --pending
-pvesh get /cluster/sdn/vnets/vnetX/subnets --pending
-
-
-#apply changes from /etc/pve/sdn.cfg.new to /etc/pve/sdn.cfg
-pvesh set /cluster/sdn
-
-
-#generate local /etc/network/interfaces.d/sdn and reload (need to be called on each node)
- pvesh set /nodes/<node>/network
-
-
-display transporzone status on all cluster nodes
-#pvesh get /cluster/resources
-┌────────────────────────────────────┬─────────┬───────┬───────────┬─────────┬───────┬────────┬─────────────┬────────────┬────────────┬───────────────┬──────┬───────────┬──────────────┬────────────────┐
-│ id │ type │ cpu │ disk │ hastate │ level │ maxcpu │ maxdisk │ maxmem │ mem │ node │ pool │ status │ storage │ uptime │
-│ sdn/node1/transportzone10 │ sdn │ │ │ │ │ │ │ │ │ kvmformation1 │ │ error │ │ │
-├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
-│ sdn/node1/zone1 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
-├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
-│ sdn/node1/zone4 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
-├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
-
-
-
-
-#list all transport zones of a node
-
-pvesh get /nodes/<node>/sdn/zones/
- ┌─────────────────┬───────────┐
- │ sdn │ status │
- ├─────────────────┼───────────┤
- │ transportzone10 │ error │
- ├─────────────────┼───────────┤
- │ zone1 │ available │
- ├─────────────────┼───────────┤
- │ zone4 │ available │
- └─────────────────┴───────────┘
-
-
-#list all vnet status from a node transportzone
-
-pveset get /nodes/<node>/sdn/zones/<transportzone>/content
-
- ┌─────────┬────────┐
- │ vnet │ status │
- ├─────────┼────────┤
- │ vnet100 │ error │
- ├─────────┼────────┤
- │ vnet101 │ error │
- └─────────┴────────┘
-
-
-
-
+++ /dev/null
-use strict;
-use warnings;
-use File::Copy;
-use PVE::Cluster qw(cfs_read_file);
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use Data::Dumper;
-
-PVE::Network::SDN::commit_config();
-my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config();
-
-PVE::Network::SDN::Zones::write_etc_network_config($network_config);
-print "/etc/network/interfaces.d/sdn\n";
-print $network_config;
-print "\n";
-
-my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config();
-
-if ($controller_config) {
- print Dumper($controller_config);
- PVE::Network::SDN::Controllers::write_controller_config($controller_config);
-}
+++ /dev/null
-use strict;
-use warnings;
-use PVE::Network::SDN;
-use Data::Dumper;
-
-my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
-
-print Dumper($vnet_status);
-print Dumper($transport_status);
+++ /dev/null
-{
- 'ids' => {
- 'powerdns' => {
- 'url' => 'http://localhost:8881/api/v1/servers/localhost',
- 'type' => 'powerdns',
- 'key' => '1234',
- 'ttl' => '3600'
- },
- },
-}
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"},{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"},{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
\ No newline at end of file
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"1.0.0.10.in-addr.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"1.0.0.10.in-addr.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"A"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"AAAA"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"DELETE","name":"1.0.0.10.in-addr.arpa.","records":[],"type":"PTR"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"rrsets":[{"changetype":"DELETE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[],"type":"PTR"}]}',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
- }, 'HTTP::Request' );
-
+++ /dev/null
-bless( {
- '_content' => '',
- '_headers' => bless( {
- '::std_case' => {
- 'x-api-key' => 'X-API-Key'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'x-api-key' => '1234'
- }, 'HTTP::Headers' ),
- '_method' => 'GET',
- '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com?rrsets=false')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type =>"simple", dns => "powerdns", reversedns => "powerdns", dnszone => "domain.com" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-bless( {
- '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/1/available-ips/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"prefix":"10.0.0.0/24"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'DELETE',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"address":"192.168.0.1/24","description":null,"dns_name":"toto"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
- '_headers' => bless( {
- 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
- 'content-type' => 'application/json; charset=UTF-8'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
- }, 'HTTP::Request' );
+++ /dev/null
-{
- 'ids' => {
- 'phpipam' => {
- 'url' => 'https://localhost/api/apiadmin',
- 'type' => 'phpipam',
- 'section' => 1,
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- },
- 'pve' => {
- 'type' => 'pve'
- },
- 'netbox' => {
- 'token' => '0123456789abcdef0123456789abcdef01234567',
- 'type' => 'netbox',
- 'url' => 'http://localhost:8000/api'
- }
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "netbox" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-bless( {
- '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","is_gateway":1,"mac":"da:65:8f:18:9b:6f","subnetId":1}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","mac":"da:65:8f:18:9b:6f","subnetId":1}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"description":"mydescription","hostname":"myhostname","mac":"da:65:8f:18:9b:6f"}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/first_free/1/')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"mask":"24","sectionId":1,"subnet":"10.0.0.0"}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/subnets/')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'DELETE',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"description":null,"hostname":"toto","ip":"192.168.0.1","is_gateway":null,"subnetId":1}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'POST',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-bless( {
- '_content' => '{"description":"mydescription","hostname":"myhostname","is_gateway":1,"mac":"da:65:8f:18:9b:6f"}',
- '_headers' => bless( {
- '::std_case' => {
- 'token' => 'Token'
- },
- 'content-type' => 'application/json; charset=UTF-8',
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- }, 'HTTP::Headers' ),
- '_method' => 'PATCH',
- '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
- }, 'HTTP::Request' );
+++ /dev/null
-{
- 'ids' => {
- 'phpipam' => {
- 'url' => 'https://localhost/api/apiadmin',
- 'type' => 'phpipam',
- 'section' => 1,
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- },
- 'pve' => {
- 'type' => 'pve'
- },
- 'netbox' => {
- 'token' => '0123456789abcdef0123456789abcdef01234567',
- 'type' => 'netbox',
- 'url' => 'http://localhost:8000/api'
- }
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "phpipam" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib qw(..);
-use File::Slurp;
-use Net::IP;
-
-use Test::More;
-use Test::MockModule;
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use JSON;
-
-use Data::Dumper qw(Dumper);
-$Data::Dumper::Sortkeys = 1;
-
-sub read_sdn_config {
- my ($file) = @_;
- # Read structure back in again
- open my $in, '<', $file or die $!;
- my $sdn_config;
- {
- local $/; # slurp mode
- $sdn_config = eval <$in>;
- }
- close $in;
-
- return $sdn_config;
-}
-
-
-my @plugins = read_dir( './dns/', prefix => 1 ) ;
-
-foreach my $path (@plugins) {
-
- my (undef, $dnsid) = split(/\//, $path);
- my $sdn_config = read_sdn_config ("$path/sdn_config");
-
-
- my $pve_sdn_dns;
- $pve_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns');
- $pve_sdn_dns->mock(
- config => sub {
- my $dns_config = read_sdn_config ("$path/dns_config");
- return $dns_config;
- },
- );
-
- my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
- $sdn_module->mock(
- config => sub {
- return $sdn_config;
- },
- api_request => sub {
- my ($method, $url, $headers, $data) = @_;
-
- my $js = JSON->new;
- $js->canonical(1);
-
- my $encoded_data = $js->encode($data) if $data;
- my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
- die Dumper($req);
- }
- );
-
-
-
- my $dns_cfg = PVE::Network::SDN::Dns::config();
- my $plugin_config = $dns_cfg->{ids}->{$dnsid};
- my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
-
- #test params;
- my @ips = ("10.0.0.1", "2001:4860:4860::8888");
- my $zone = "domain.com";
- my $hostname = "myhostname";
-
- foreach my $ip (@ips) {
-
- my $ipversion = Net::IP::ip_is_ipv6($ip) ? "ipv6" : "ipv4";
- my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
- my $ip2 = $type eq 'AAAA' ? '2001:4860:4860::8844' : '127.0.0.1';
- my $fqdn = $hostname.".".$zone.".";
-
- my $sdn_dns_plugin = Test::MockModule->new($plugin);
- $sdn_dns_plugin->mock(
-
- get_zone_content => sub {
- return undef;
- },
- get_zone_rrset => sub {
- return undef;
- }
- );
-
- ## add_a_record
- my $test = "add_a_record";
- my $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- my $name = "$dnsid $test";
-
- $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## add_ptr_record
- $test = "add_ptr_record";
- $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- $name = "$dnsid $test";
-
- $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
-
- ## del_ptr_record
- $test = "del_ptr_record";
- $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- $name = "$dnsid $test";
-
- $plugin->del_ptr_record($plugin_config, $zone, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
-
- ## del_a_record
-
- $sdn_dns_plugin->mock(
-
- get_zone_content => sub {
- return undef;
- },
- get_zone_rrset => sub {
-
- 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 => '3600',
- records => [ $record ] };
- return $rrset;
- }
- );
-
- $test = "del_a_record";
- $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- $name = "$dnsid $test";
-
- $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## del_a_multiple_record
-
- $sdn_dns_plugin->mock(
-
- get_zone_content => sub {
- return undef;
- },
- get_zone_rrset => sub {
-
- my $record = { content => $ip,
- disabled => JSON::false,
- name => $fqdn,
- type => $type,
- priority => 0 };
-
- my $record2 = { content => $ip2,
- disabled => JSON::false,
- name => $fqdn,
- type => $type,
- priority => 0 };
-
- my $rrset = { name => $fqdn,
- type => $type,
- ttl => '3600',
- records => [ $record, $record2 ] };
- return $rrset;
- }
- );
-
- $test = "del_a_multiple_record";
- $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- $name = "$dnsid $test";
-
- $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## add_a_multiple_record
-
- $sdn_dns_plugin->mock(
-
- get_zone_content => sub {
- return undef;
- },
- get_zone_rrset => sub {
-
- my $record2 = { content => $ip2,
- disabled => JSON::false,
- name => $fqdn,
- type => $type,
- priority => 0 };
-
- my $rrset = { name => $fqdn,
- type => $type,
- ttl => '3600',
- records => [ $record2 ] };
- return $rrset;
- }
- );
-
- $test = "add_a_multiple_record";
- $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
- $name = "$dnsid $test";
-
- $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
- }
-
- ## verify_zone
- my $test = "verify_zone";
- my $expected = Dumper read_sdn_config("$path/expected.$test");
- my $name = "$dnsid $test";
-
- $plugin->verify_zone($plugin_config, $zone, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
-}
-
-done_testing();
-
-
+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib qw(..);
-use File::Slurp;
-
-use Test::More;
-use Test::MockModule;
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::INotify;
-use JSON;
-
-use Data::Dumper qw(Dumper);
-$Data::Dumper::Sortkeys = 1;
-
-sub read_sdn_config {
- my ($file) = @_;
- # Read structure back in again
- open my $in, '<', $file or die $!;
- my $sdn_config;
- {
- local $/; # slurp mode
- $sdn_config = eval <$in>;
- }
- close $in;
-
- return $sdn_config;
-}
-
-
-#my @plugins = <./ipams/*>;
-my @plugins = read_dir( './ipams/', prefix => 1 ) ;
-
-foreach my $path (@plugins) {
-
- my (undef, $ipamid) = split(/\//, $path);
- my $sdn_config = read_sdn_config ("$path/sdn_config");
-
-
- my $pve_sdn_subnets;
- $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
- $pve_sdn_subnets->mock(
- config => sub {
- return $sdn_config->{subnets};
- },
- );
-
- my $pve_sdn_ipam;
- $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Ipams');
- $pve_sdn_subnets->mock(
- config => sub {
- my $ipam_config = read_sdn_config ("$path/ipam_config");
- return $ipam_config;
- },
- );
-
- my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
- $sdn_module->mock(
- config => sub {
- return $sdn_config;
- },
- api_request => sub {
- my ($method, $url, $headers, $data) = @_;
-
- my $js = JSON->new;
- $js->canonical(1);
-
- my $encoded_data = $js->encode($data) if $data;
- my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
- die Dumper($req);
- }
- );
-
-
-
- #test params;
- my $subnetid = "myzone-10.0.0.0-24";
- my $ip = "10.0.0.1";
- my $hostname = "myhostname";
- my $mac = "da:65:8f:18:9b:6f";
- my $description = "mydescription";
- my $is_gateway = 1;
-
-
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
-
- 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 $sdn_ipam_plugin = Test::MockModule->new($plugin);
- $sdn_ipam_plugin->mock(
- get_prefix_id => sub {
- return 1;
- },
- get_ip_id => sub {
- return 1;
- },
- is_ip_gateway => sub {
- return 1;
- }
- );
-
- ## add_ip
- my $test = "add_ip";
- my $expected = Dumper read_sdn_config("$path/expected.$test");
- my $name = "$ipamid $test";
-
- $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## add_next_freeip
- $test = "add_next_freeip";
- $expected = Dumper read_sdn_config("$path/expected.$test");
- $name = "$ipamid $test";
-
- $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
-
- ## del_ip
- $test = "del_ip";
- $expected = Dumper read_sdn_config("$path/expected.$test");
- $name = "$ipamid $test";
-
- $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## update_ip
- $test = "update_ip";
- $expected = Dumper read_sdn_config("$path/expected.$test");
- $name = "$ipamid $test";
- $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- ## add_ip_notgateway
- $is_gateway = undef;
- $test = "add_ip_notgateway";
- $expected = Dumper read_sdn_config("$path/expected.$test");
- $name = "$ipamid $test";
-
- $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
- $sdn_ipam_plugin->mock(
- get_prefix_id => sub {
- return undef;
- },
- );
-
- ## add_subnet
- $test = "add_subnet";
- $expected = Dumper read_sdn_config("$path/expected.$test");
- $name = "$ipamid $test";
-
- $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1);
-
- if ($@) {
- is ($@, $expected, $name);
- } else {
- fail($name);
- }
-
-}
-
-done_testing();
-
-
+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib qw(..);
-use File::Slurp;
-
-use Test::More;
-use Test::MockModule;
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::INotify;
-use JSON;
-
-use Data::Dumper qw(Dumper);
-$Data::Dumper::Sortkeys = 1;
-
-sub read_sdn_config {
- my ($file) = @_;
- # Read structure back in again
- open my $in, '<', $file or die $!;
- my $sdn_config;
- {
- local $/; # slurp mode
- $sdn_config = eval <$in>;
- }
- close $in;
-
- return $sdn_config;
-}
-
-
-my @plugins = read_dir( './subnets/', prefix => 1 ) ;
-
-foreach my $path (@plugins) {
-
- my (undef, $testid) = split(/\//, $path);
-
- print "test: $testid\n";
- my $sdn_config = read_sdn_config ("$path/sdn_config");
-
-
- my $pve_sdn_subnets;
- $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
- $pve_sdn_subnets->mock(
- config => sub {
- return $sdn_config->{subnets};
- },
- verify_dns_zone => sub {
- return;
- },
- add_dns_record => sub {
- return;
- }
- );
-
-
- my $js = JSON->new;
- $js->canonical(1);
-
-
- #test params;
- my $subnets = $sdn_config->{subnets}->{ids};
- my $subnetid = (keys %{$subnets})[0];
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
-
- my $subnet_cidr = $subnet->{cidr};
- my $iplist = NetAddr::IP->new($subnet_cidr);
- $iplist++ if Net::IP::ip_is_ipv4($iplist->canon()); #skip network address for ipv4
- my $ip = $iplist->canon();
- $iplist++;
- my $ipnextfree = $iplist->canon();
- $iplist++;
- my $ip2 = $iplist->canon();
-
- my $ip3 = undef;
- my $hostname = "myhostname";
- my $mac = "da:65:8f:18:9b:6f";
- my $description = "mydescription";
- my $is_gateway = 1;
- my $ipamdb = {};
-
- my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
- my $ipam = $zone->{ipam};
-
- my $plugin;
- my $sdn_ipam_plugin;
- if($ipam) {
- $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
- $sdn_ipam_plugin = Test::MockModule->new($plugin);
- $sdn_ipam_plugin->mock(
- read_db => sub {
- return $ipamdb;
- },
- write_db => sub {
- my ($cfg) = @_;
- $ipamdb = $cfg;
- }
- );
- }
-
- my $pve_sdn_ipams;
- $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
- $pve_sdn_ipams->mock(
- config => sub {
- my $ipam_config = read_sdn_config ("$path/ipam_config");
- return $ipam_config;
- },
- );
-
- ## add_subnet
- my $test = "add_subnet $subnetid";
- my $name = "$testid $test";
- my $result = undef;
- my $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{}}}}}}';
-
- eval {
- PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
-
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
-
- ## add_ip
- $test = "add_ip $ip";
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1}}}}}}}';
-
- eval {
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
-
- if($ipam) {
- ## add_already_exist_ip
- $test = "add_already_exist_ip $ip";
- $name = "$testid $test";
-
- eval {
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
- };
-
- if ($@) {
- is (undef, undef, $name);
- } else {
- fail("$name : $@");
- }
- }
-
- ## add_second_ip
- $test = "add_second_ip $ip2";
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ip2.'":{}}}}}}}';
-
- eval {
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
-
- ## add_next_free
- $test = "find_next_freeip ($ipnextfree)";
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
-
- eval {
- $ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- }
-
- ## del_ip
- $test = "del_ip $ip";
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
-
- eval {
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname);
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
-
- if($ipam){
- ## del_subnet_not_empty
- $test = "del_subnet_not_empty $subnetid";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
- };
-
- if ($@) {
- is ($result, $expected, $name);
- } else {
- fail("$name : $@");
- }
- }
-
-
- ## add_ip_rollback_failing_dns
- $test = "add_ip_rollback_failing_dns";
-
- $pve_sdn_subnets->mock(
- config => sub {
- return $sdn_config->{subnets};
- },
- verify_dns_zone => sub {
- return;
- },
- add_dns_record => sub {
- die "error add dns record";
- return;
- }
- );
-
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
-
- eval {
- PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
- };
-
- if ($@) {
- if($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
- } else {
- fail("$name : $@");
- }
-
-
- ## del_empty_subnet
- $test = "del_empty_subnet";
- $name = "$testid $test";
- $result = undef;
- $expected = '{"zones":{"myzone":{"subnets":{}}}}';
-
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip2, $hostname);
- PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip3, $hostname);
-
- eval {
- PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
- };
-
- if ($@) {
- fail("$name : $@");
- } elsif($ipam) {
- $result = $js->encode($plugin->read_db());
- is ($result, $expected, $name);
- } else {
- is (undef, undef, $name);
- }
-
-}
-
-done_testing();
-
-
+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib qw(..);
-use File::Slurp;
-use NetAddr::IP qw(:lower);
-
-use Test::More;
-use Test::MockModule;
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::INotify;
-use JSON;
-
-use Data::Dumper qw(Dumper);
-$Data::Dumper::Sortkeys = 1;
-
-sub read_sdn_config {
- my ($file) = @_;
- # Read structure back in again
- open my $in, '<', $file or die $!;
- my $sdn_config;
- {
- local $/; # slurp mode
- $sdn_config = eval <$in>;
- }
- close $in;
- return $sdn_config;
-}
-
-
-my @plugins = read_dir( './vnets/', prefix => 1 ) ;
-
-foreach my $path (@plugins) {
-
- my (undef, $testid) = split(/\//, $path);
-
- print "test: $testid\n";
- my $sdn_config = read_sdn_config ("$path/sdn_config");
-
- my $pve_sdn_zones;
- $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones');
- $pve_sdn_zones->mock(
- config => sub {
- return $sdn_config->{zones};
- },
- );
-
- my $pve_sdn_vnets;
- $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets');
- $pve_sdn_vnets->mock(
- config => sub {
- return $sdn_config->{vnets};
- },
- );
-
- my $pve_sdn_subnets;
- $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
- $pve_sdn_subnets->mock(
- config => sub {
- return $sdn_config->{subnets};
- },
- verify_dns_zone => sub {
- return;
- },
- add_dns_record => sub {
- return;
- }
- );
-
- my $js = JSON->new;
- $js->canonical(1);
-
- #test params;
- #test params;
- my $subnets = $sdn_config->{subnets}->{ids};
-
- my $subnetid = (sort keys %{$subnets})[0];
- my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
- my $subnet_cidr = $subnet->{cidr};
- my $iplist = NetAddr::IP->new($subnet_cidr);
- my $mask = $iplist->masklen();
- my $ipversion = undef;
-
- if (Net::IP::ip_is_ipv4($iplist->canon())){
- $iplist++; #skip network address for ipv4
- $ipversion = 4;
- } else {
- $ipversion = 6;
- }
-
- my $cidr1 = $iplist->canon()."/$mask";
- $iplist++;
- my $cidr2 = $iplist->canon()."/$mask";
- my $cidr_outofrange = '8.8.8.8/8';
-
- my $subnetid2 = (sort keys %{$subnets})[1];
- my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1);
- my $subnet2_cidr = $subnet2->{cidr};
- my $iplist2 = NetAddr::IP->new($subnet2_cidr);
- $iplist2++;
- my $cidr3 = $iplist2->canon()."/$mask";
- $iplist2++;
- my $cidr4 = $iplist2->canon()."/$mask";
-
- my $hostname = "myhostname";
- my $mac = "da:65:8f:18:9b:6f";
- my $description = "mydescription";
- my $ipamdb = read_sdn_config ("$path/ipam.db");
-
- my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
- my $ipam = $zone->{ipam};
-
- my $plugin;
- my $sdn_ipam_plugin;
- if($ipam) {
- $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
- $sdn_ipam_plugin = Test::MockModule->new($plugin);
- $sdn_ipam_plugin->mock(
- read_db => sub {
- return $ipamdb;
- },
- write_db => sub {
- my ($cfg) = @_;
- $ipamdb = $cfg;
- }
- );
- }
-
- my $pve_sdn_ipams;
- $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
- $pve_sdn_ipams->mock(
- config => sub {
- my $ipam_config = read_sdn_config ("$path/ipam_config");
- return $ipam_config;
- },
- );
-
- my $vnetid = "myvnet";
-
- ## add_ip
- my $test = "add_cidr $cidr1";
- my $name = "$testid $test";
- my $result = undef;
- my $expected = '';
-
- eval {
- PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- ## add_ip
- $test = "add_already_exist_cidr $cidr1";
- $name = "$testid $test";
- $result = undef;
- $expected = '';
-
- eval {
- PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
- };
-
- if ($@) {
- is (undef, undef, $name);
- } elsif($ipam) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- ## add_ip
- $test = "add_cidr $cidr2";
- $name = "$testid $test";
- $result = undef;
- $expected = '';
-
- eval {
- PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- ## add_ip
- $test = "add_ip_out_of_range_subnets $cidr_outofrange";
- $name = "$testid $test";
- $result = undef;
- $expected = '';
-
- eval {
- PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description);
- };
-
- if ($@) {
- is (undef, undef, $name);
- } else {
- fail("$name : $@");
- }
-
- ## add_ip
- $test = "add_cidr $cidr4";
- $name = "$testid $test";
- $result = undef;
- $expected = '';
-
- eval {
- PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
-
- $test = "find_next_free_cidr_in_second_subnet ($cidr3)";
- $name = "$testid $test";
- $result = undef;
- $expected = $ipam ? $cidr3 : undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is ($result, $expected, $name);
- }
-
-
- $test = "del_cidr $cidr1";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- $test = "del_cidr $cidr3";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- $test = "del_cidr not exist $cidr1";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
- };
-
- if ($@) {
- is (undef, undef, $name);
- } elsif($ipam) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- $test = "del_cidr outofrange $cidr_outofrange";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname);
- };
-
- if ($@) {
- is (undef, undef, $name);
- } else {
- fail("$name : $@");
- }
-
- $test = "find_next_free_cidr_in_first_subnet ($cidr1)";
- $name = "$testid $test";
- $result = undef;
- $expected = $ipam ? $cidr1 : undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is ($result, $expected, $name);
- }
-
- $test = "update_cidr $cidr1";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
- $test = "update_cidr deleted $cidr3";
- $name = "$testid $test";
- $result = undef;
- $expected = undef;
-
- eval {
- $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
- };
-
- if ($@) {
- fail("$name : $@");
- } else {
- is (undef, undef, $name);
- }
-
-}
-
-done_testing();
-
-
+++ /dev/null
-#!/usr/bin/perl
-
-use strict;
-use warnings;
-
-use lib qw(..);
-use File::Slurp;
-
-use Test::More;
-use Test::MockModule;
-
-use PVE::Network::SDN;
-use PVE::Network::SDN::Zones;
-use PVE::Network::SDN::Controllers;
-use PVE::INotify;
-
-sub read_sdn_config {
- my ($file) = @_;
-
- # Read structure back in again
- open my $in, '<', $file or die $!;
- my $sdn_config;
- {
- local $/; # slurp mode
- $sdn_config = eval <$in>;
- }
- close $in;
-
- return $sdn_config;
-}
-
-
-my @tests = grep { -d } glob './zones/*/*';
-
-foreach my $test (@tests) {
-
- my $sdn_config = read_sdn_config ("./$test/sdn_config");
-
- open my $fh1, '<', "./$test/interfaces" or die "can't read interfaces file";
- my $interfaces_config = PVE::INotify::__read_etc_network_interfaces($fh1, undef, undef);
- close $fh1;
-
- my $pve_common_inotify;
- $pve_common_inotify = Test::MockModule->new('PVE::INotify');
- $pve_common_inotify->mock(
- nodename => sub {
- return 'localhost';
- },
- read_file => sub {
- return $interfaces_config;
- },
- );
-
- my $pve_sdn_subnets;
- $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
- $pve_sdn_subnets->mock(
- config => sub {
- return $sdn_config->{subnets};
- },
- );
-
- my $pve_sdn_zones_plugin;
- $pve_sdn_zones_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin');
- $pve_sdn_zones_plugin->mock(
- get_local_route_ip => sub {
- my $outiface = "vmbr0";
- my $outip = $interfaces_config->{ifaces}->{$outiface}->{address};
- return ($outip, $outiface);
- },
- is_vlanaware => sub {
- return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'};
- },
- is_ovs => sub {
- return 1 if $interfaces_config->{ifaces}->{vmbr0}->{'type'} eq 'OVSBridge';
- },
- get_bridge_ifaces => sub {
- return ('eth0');
- },
- find_bridge => sub {
- return;
- }
- );
-
- my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
- $sdn_module->mock(
- running_config => sub {
- return $sdn_config;
- },
- );
-
- my $name = $test;
- my $expected = read_file("./$test/expected_sdn_interfaces");
-
- my $result = "";
- eval {
- $result = PVE::Network::SDN::Zones::generate_etc_network_config();
- };
-
- if (my $err = $@) {
- fail($name);
- } else {
- is ($result, $expected, $name);
- }
-
- if ($sdn_config->{controllers}) {
- my $expected = read_file("./$test/expected_controller_config");
- my $controller_rawconfig = "";
-
- eval {
- my $config = PVE::Network::SDN::Controllers::generate_controller_config();
- $controller_rawconfig = PVE::Network::SDN::Controllers::generate_controller_rawconfig($config);
- };
- if (my $err = $@) {
- fail($name);
- } else {
- is ($controller_rawconfig, $expected, $name);
- }
- }
-}
-
-done_testing();
-
-
+++ /dev/null
-{
- 'ids' => {
- 'phpipam' => {
- 'url' => 'https://localhost/api/apiadmin',
- 'type' => 'phpipam',
- 'section' => 1,
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- },
- 'pve' => {
- 'type' => 'pve'
- },
- 'netbox' => {
- 'token' => '0123456789abcdef0123456789abcdef01234567',
- 'type' => 'netbox',
- 'url' => 'http://localhost:8000/api'
- }
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type =>"simple" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-{
- 'ids' => {
- 'phpipam' => {
- 'url' => 'https://localhost/api/apiadmin',
- 'type' => 'phpipam',
- 'section' => 1,
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- },
- 'pve' => {
- 'type' => 'pve'
- },
- 'netbox' => {
- 'token' => '0123456789abcdef0123456789abcdef01234567',
- 'type' => 'netbox',
- 'url' => 'http://localhost:8000/api'
- }
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type =>"simple" } },
- },
-
- subnets => {
- ids => { 'myzone-2a0a:1580:2000::-56' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-{
- 'ids' => {
- 'phpipam' => {
- 'url' => 'https://localhost/api/apiadmin',
- 'type' => 'phpipam',
- 'section' => 1,
- 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
- },
- 'pve' => {
- 'type' => 'pve'
- },
- 'netbox' => {
- 'token' => '0123456789abcdef0123456789abcdef01234567',
- 'type' => 'netbox',
- 'url' => 'http://localhost:8000/api'
- }
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { type =>"simple" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
+++ /dev/null
-{
- "zones" => {
- "myzone" => {
- "subnets" => {
- "192.168.0.0/30" => {
- "ips" =>{
- }
- },
- "192.168.1.0/30" => {
- "ips" =>{
- }
- },
- }
- }
- }
-}
-
+++ /dev/null
-{
- 'ids' => {
- 'pve' => {
- 'type' => 'pve'
- },
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type =>"simple" } },
- },
-
- subnets => {
- ids => {
- 'myzone-192.168.0.0-30' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- 'myzone-192.168.1.0-30' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- }
-
- }
-}
+++ /dev/null
-{
- "zones" => {
- "myzone" => {
- "subnets" => {
- "192.168.0.0/30" => {
- "ips" =>{
- }
- },
- "192.168.1.0/30" => {
- "ips" =>{
- }
- },
- }
- }
- }
-}
-
+++ /dev/null
-{
- 'ids' => {
- 'pve' => {
- 'type' => 'pve'
- },
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { type =>"simple" } },
- },
-
- subnets => {
- ids => {
- 'myzone-192.168.0.0-30' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- 'myzone-192.168.1.0-30' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- }
-
- }
-}
+++ /dev/null
-{
- "zones" => {
- "myzone" => {
- "subnets" => {
- "2001:db8:85a3::8a2e:370:7334/127" => {
- "ips" =>{
- }
- },
- "2001:db8:85a3::8a2e:371:7334/127" => {
- "ips" =>{
- }
- },
- }
- }
- }
-}
+++ /dev/null
-{
- 'ids' => {
- 'pve' => {
- 'type' => 'pve'
- },
- },
-}
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type =>"simple" } },
- },
-
- subnets => {
- ids => {
- 'myzone-2001:db8:85a3::8a2e:370:7334-127' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- 'myzone-2001:db8:85a3::8a2e:371:7334-127' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- }
-
- }
-}
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family ipv4 unicast
- redistribute connected
- exit-address-family
- !
- address-family ipv6 unicast
- redistribute connected
- exit-address-family
- !
- address-family l2vpn evpn
- advertise ipv4 unicast
- advertise ipv6 unicast
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'advertise-subnets' => 1 } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'disable-arp-nd-suppression' => 1 } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65001
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as external
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- neighbor BGP peer-group
- neighbor BGP remote-as external
- neighbor BGP bfd
- neighbor BGP ebgp-multihop 3
- neighbor 192.168.0.252 peer-group BGP
- neighbor 192.168.0.253 peer-group BGP
- !
- address-family ipv4 unicast
- neighbor BGP activate
- neighbor BGP soft-reconfiguration inbound
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- autort as 65000
- exit-address-family
-exit
-!
-router bgp 65001 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family l2vpn evpn
- route-target import 65000:1000
- route-target export 65000:1000
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => {
- tag => "100",
- type => "vnet",
- zone => "myzone",
- },
- },
- },
-
- zones => {
- ids => {
- myzone => {
- ipam => "pve",
- type => "evpn",
- controller => "evpnctl",
- 'vrf-vxlan' => 1000,
- },
- },
- },
- controllers => {
- ids => {
- evpnctl => {
- type => "evpn",
- 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
- asn => "65000",
- },
- localhost => {
- type => "bgp",
- 'peers' => '192.168.0.252,192.168.0.253',
- ebgp => "1",
- 'ebgp-multihop' => '3',
- asn => "65001",
- node => "localhost",
- },
- },
- },
-
- subnets => {
- ids => {
- 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- },
- },
- },
-}
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-ip protocol bgp route-map correct_src
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65001
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as external
- neighbor VTEP bfd
- neighbor VTEP ebgp-multihop 10
- neighbor VTEP update-source dummy1
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- bgp disable-ebgp-connected-route-check
- neighbor BGP peer-group
- neighbor BGP remote-as external
- neighbor BGP bfd
- neighbor 172.16.0.254 peer-group BGP
- neighbor 172.17.0.254 peer-group BGP
- !
- address-family ipv4 unicast
- network 192.168.0.1/32
- neighbor BGP activate
- neighbor BGP soft-reconfiguration inbound
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- autort as 65000
- exit-address-family
-exit
-!
-router bgp 65001 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family l2vpn evpn
- route-target import 65000:1000
- route-target export 65000:1000
- exit-address-family
-exit
-!
-ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-route-map correct_src permit 1
- match ip address prefix-list loopbacks_ips
- set src 192.168.0.1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto eth0
-iface eth0 inet static
- address 172.16.0.1/24
-
-auto eth1
-iface eth1 inet static
- address 172.17.0.1/24
-
-auto dummy1
-iface dummy1 inet static
- address 192.168.0.1/32
- link-type dummy
-
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000 } },
- },
- controllers => {
- ids => {
- evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" },
- localhost => { type => "bgp", 'peers' => '172.16.0.254,172.17.0.254', ebgp => "1", asn => "65001", loopback => 'dummy1', node => "localhost" },
- },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family ipv4 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family ipv6 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family ipv4 unicast
- redistribute connected
- exit-address-family
- !
- address-family ipv6 unicast
- redistribute connected
- exit-address-family
- !
- address-family l2vpn evpn
- default-originate ipv4
- default-originate ipv6
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN deny 1
- match evpn route-type prefix
-exit
-!
-route-map MAP_VTEP_IN permit 2
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-ip route 10.0.0.0/24 10.255.255.2 xvrf_myzone
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family l2vpn evpn
- default-originate ipv4
- default-originate ipv6
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN deny 1
- match evpn route-type prefix
-exit
-!
-route-map MAP_VTEP_IN permit 2
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto xvrf_myzone
-iface xvrf_myzone
- link-type veth
- address 10.255.255.1/30
- veth-peer-name xvrfp_myzone
- mtu 1500
-
-auto xvrfp_myzone
-iface xvrfp_myzone
- link-type veth
- address 10.255.255.2/30
- veth-peer-name xvrf_myzone
- vrf vrf_myzone
- mtu 1500
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 }, 'exitnodes-local-routing' => 1 },
- },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- },
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family ipv4 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family ipv6 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family ipv4 unicast
- redistribute connected
- exit-address-family
- !
- address-family ipv6 unicast
- redistribute connected
- exit-address-family
- !
- address-family l2vpn evpn
- default-originate ipv4
- default-originate ipv6
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
- match evpn vni 1000
- match evpn route-type prefix
- set metric 200
-exit
-!
-route-map MAP_VTEP_OUT permit 2
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'exitnodes-primary' => "othernode", exitnodes => { 'localhost' => 1 } } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family ipv4 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family ipv6 unicast
- import vrf vrf_myzone
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family ipv4 unicast
- redistribute connected
- exit-address-family
- !
- address-family ipv6 unicast
- redistribute connected
- exit-address-family
- !
- address-family l2vpn evpn
- default-originate ipv4
- default-originate ipv6
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN deny 1
- match evpn route-type prefix
-exit
-!
-route-map MAP_VTEP_IN permit 2
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
- post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto myvnet2
-iface myvnet2
- address 2a08:2142:302:3::1/64
- post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
- post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
- bridge_ports vxlan_myvnet2
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip6-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet2
-iface vxlan_myvnet2
- vxlan-id 200
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- myvnet2 => { tag => "200", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => {
- 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- 'snat' => 1
- },
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet2',
- 'gateway' => '2a08:2142:302:3::1',
- 'snat' => 1
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- address 2a08:2142:302:3::1/64
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- ip6-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => {
- 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- },
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '2a08:2142:302:3::1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => {
- 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 2a08:2142:302:3::1/64
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip6-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => {
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '2a08:2142:302:3::1',
- }
- }
- }
-}
-
-
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- bgp bestpath as-path multipath-relax
- neighbor BGP peer-group
- neighbor BGP remote-as 65000
- neighbor BGP bfd
- neighbor 192.168.0.1 peer-group BGP
- neighbor 192.168.0.2 peer-group BGP
- neighbor 192.168.0.3 peer-group BGP
- !
- address-family ipv4 unicast
- neighbor BGP activate
- neighbor BGP soft-reconfiguration inbound
- exit-address-family
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => {
- tag => "100",
- type => "vnet",
- zone => "myzone",
- },
- },
- },
-
- zones => {
- ids => {
- myzone => {
- ipam => "pve",
- type => "evpn",
- controller => "evpnctl",
- 'vrf-vxlan' => 1000,
- },
- },
- },
- controllers => {
- ids => {
- evpnctl => {
- type => "evpn",
- 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
- asn => "65000",
- },
- localhost => {
- type => "bgp",
- 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
- 'bgp-multipath-as-path-relax' => "1",
- asn => "65000",
- node => "localhost",
- },
- },
- },
-
- subnets => {
- ids => {
- 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- },
- },
- },
-}
+++ /dev/null
-frr version 8.2.2
-frr defaults datacenter
-hostname localhost
-log syslog informational
-service integrated-vtysh-config
-!
-!
-vrf vrf_myzone
- vni 1000
-exit-vrf
-!
-router bgp 65000
- bgp router-id 192.168.0.1
- no bgp default ipv4-unicast
- coalesce-time 1000
- neighbor VTEP peer-group
- neighbor VTEP remote-as 65000
- neighbor VTEP bfd
- neighbor 192.168.0.2 peer-group VTEP
- neighbor 192.168.0.3 peer-group VTEP
- !
- address-family l2vpn evpn
- neighbor VTEP route-map MAP_VTEP_IN in
- neighbor VTEP route-map MAP_VTEP_OUT out
- neighbor VTEP activate
- advertise-all-vni
- exit-address-family
-exit
-!
-router bgp 65000 vrf vrf_myzone
- bgp router-id 192.168.0.1
- !
- address-family l2vpn evpn
- route-target import 65001:1000
- route-target import 65002:1000
- route-target import 65003:1000
- exit-address-family
-exit
-!
-route-map MAP_VTEP_IN permit 1
-exit
-!
-route-map MAP_VTEP_OUT permit 1
-exit
-!
-line vty
-!
\ No newline at end of file
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- hwaddress A2:1D:CB:1A:C0:8B
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
- ip-forward on
- arp-accept on
- vrf vrf_myzone
-
-auto vrf_myzone
-iface vrf_myzone
- vrf-table auto
- post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
-
-auto vrfbr_myzone
-iface vrfbr_myzone
- bridge-ports vrfvx_myzone
- bridge_stp off
- bridge_fd 0
- mtu 1450
- vrf vrf_myzone
-
-auto vrfvx_myzone
-iface vrfvx_myzone
- vxlan-id 1000
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan-local-tunnelip 192.168.0.1
- bridge-learning off
- bridge-arp-nd-suppress on
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", zone => "myzone" },
- },
- },
-
- zones => {
- ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'rt-import' => '65001:1000,65002:1000,65003:1000' } },
- },
- controllers => {
- ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto ln_myzone2
-iface ln_myzone2
- link-type veth
- veth-peer-name pr_myzone2
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto myvnet2
-iface myvnet2
- bridge_ports z_myzone.101
- bridge_stp off
- bridge_fd 0
-
-auto myvnet3
-iface myvnet3
- bridge_ports z_myzone2.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto pr_myzone2
-iface pr_myzone2
- link-type veth
- veth-peer-name ln_myzone2
-
-auto sv_myzone
-iface sv_myzone
- vlan-raw-device eth0
- vlan-id 10
-
-auto sv_myzone2
-iface sv_myzone2
- vlan-raw-device eth0
- vlan-id 20
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto z_myzone2
-iface z_myzone2
- bridge-stp off
- bridge-ports sv_myzone2 ln_myzone2
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
- myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
- },
- },
- zones => {
- ids => {
- myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
- myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
- },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto myvnet2
-iface myvnet2
- bridge_ports pr_myzone
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- vlan-raw-device eth0
- vlan-id 10
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => {
- tag => 100,
- type => "vnet",
- zone => "myzone"
- },
- myvnet2 => {
- type => "vnet",
- zone => "myzone"
- },
- },
- },
- zones => {
- ids => {
- myzone => {
- bridge => "vmbr0",
- tag => 10,
- ipam => "pve",
- type => "qinq",
- },
- },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto ln_myzone2
-iface ln_myzone2
- link-type veth
- veth-peer-name pr_myzone2
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto myvnet2
-iface myvnet2
- bridge_ports z_myzone.101
- bridge_stp off
- bridge_fd 0
-
-auto myvnet3
-iface myvnet3
- bridge_ports z_myzone2.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto pr_myzone2
-iface pr_myzone2
- link-type veth
- veth-peer-name ln_myzone2
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports vmbr0.10 ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto z_myzone2
-iface z_myzone2
- bridge-stp off
- bridge-ports vmbr0.20 ln_myzone2
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vids 2-4094
- bridge-vlan-aware 1
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
- myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
- },
- },
- zones => {
- ids => {
- myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
- myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
- },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports pr_myzone
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports vmbr0.10 ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vids 2-4094
- bridge-vlan-aware 1
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports vmbr0.10 ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vids 2-4094
- bridge-vlan-aware 1
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto vmbr0
-iface vmbr0
- bridge-vlan-protocol 802.1ad
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports vmbr0.10 ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vids 2-4094
- bridge-vlan-aware 1
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- vlan-raw-device eth0
- vlan-id 10
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- vlan-raw-device eth0
- vlan-id 10
- vlan-protocol 802.1ad
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto ln_myzone2
-iface ln_myzone2
- link-type veth
- veth-peer-name pr_myzone2
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto myvnet2
-iface myvnet2
- bridge_ports z_myzone.101
- bridge_stp off
- bridge_fd 0
-
-auto myvnet3
-iface myvnet3
- bridge_ports z_myzone2.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto pr_myzone2
-iface pr_myzone2
- link-type veth
- veth-peer-name ln_myzone2
-
-auto sv_myzone
-iface sv_myzone
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
-
-auto sv_myzone2
-iface sv_myzone2
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel tag=20 other_config:qinq-ethtype=802.1q
-
-auto vmbr0
-iface vmbr0
- ovs_ports sv_myzone sv_myzone2
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto z_myzone2
-iface z_myzone2
- bridge-stp off
- bridge-ports sv_myzone2 ln_myzone2
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
- myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
- },
- },
- zones => {
- ids => {
- myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
- myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
- },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports pr_myzone
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
-
-auto vmbr0
-iface vmbr0
- ovs_ports sv_myzone
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
-
-auto vmbr0
-iface vmbr0
- ovs_ports sv_myzone
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myzone
-iface ln_myzone
- link-type veth
- veth-peer-name pr_myzone
-
-auto myvnet
-iface myvnet
- bridge_ports z_myzone.100
- bridge_stp off
- bridge_fd 0
-
-auto pr_myzone
-iface pr_myzone
- link-type veth
- veth-peer-name ln_myzone
-
-auto sv_myzone
-iface sv_myzone
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1ad
-
-auto vmbr0
-iface vmbr0
- ovs_ports sv_myzone
-
-auto z_myzone
-iface z_myzone
- bridge-stp off
- bridge-ports sv_myzone ln_myzone
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports none
- bridge_stp off
- bridge_fd 0
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 144.76.100.65/29
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip-forward on
-
-auto myvnet2
-iface myvnet2
- address 144.76.0.1/32
- up ip route add 144.76.200.65/32 dev myvnet2
- up ip route add 144.76.200.66/32 dev myvnet2
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip-forward on
+++ /dev/null
-auto eth0
-iface eth0 inet static
- address 144.76.0.1
- netmask 255.255.255.255
- pointopoint 172.31.1.1
- gateway 172.31.1.1
\ No newline at end of file
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- myvnet2 => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
-
- subnets => {
- ids => {
- 'myzone-144.76.100.64-29' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '144.76.100.65',
- },
- 'myzone-144.76.200.65-32' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet2',
- 'gateway' => '144.76.0.1',
- },
- 'myzone-144.76.200.66-32' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet2',
- 'gateway' => '144.76.0.1',
- },
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 192.168.0.1/24
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip-forward on
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
-
- subnets => {
- ids => { 'myzone-192.168.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '192.168.0.1',
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 10.0.0.1/24
- post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
- post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip-forward on
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
-
- subnets => {
- ids => { 'myzone-10.0.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '10.0.0.1',
- 'snat' => 1
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 192.168.0.1/24
- address 2a08:2142:302:3::1/64
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip-forward on
- ip6-forward on
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
- subnets => {
- ids => {
- 'myzone-192.168.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '192.168.0.1',
- },
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '2a08:2142:302:3::1',
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports none
- bridge_stp off
- bridge_fd 0
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
- subnets => {
- ids => {
- 'myzone-192.168.0.0-24' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- },
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- address 2a08:2142:302:3::1/64
- post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
- post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
- post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
- bridge_ports none
- bridge_stp off
- bridge_fd 0
- ip6-forward on
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "simple" } },
- },
-
- subnets => {
- ids => {
- 'myzone-2a08:2142:302:3::-64' => {
- 'type' => 'subnet',
- 'vnet' => 'myvnet',
- 'gateway' => '2a08:2142:302:3::1',
- 'snat' => 1
- }
- }
- }
-}
-
-
+++ /dev/null
-#version:1
-
-auto ln_myvnet
-iface ln_myvnet
- link-type veth
- veth-peer-name pr_myvnet
-
-auto myvnet
-iface myvnet
- bridge_ports ln_myvnet
- bridge_stp off
- bridge_fd 0
-
-auto pr_myvnet
-iface pr_myvnet
- link-type veth
- veth-peer-name ln_myvnet
-
-auto vmbr0v100
-iface vmbr0v100
- bridge_ports eth0.100 pr_myvnet
- bridge_stp off
- bridge_fd 0
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports vmbr0.100
- bridge_stp off
- bridge_fd 0
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vids 2-4094
- bridge-vlan-aware 1
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports vmbr0.100
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet manual
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4096
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => "100", type => "vnet", vlanaware => 1, zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myvnet
-iface ln_myvnet
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options tag=100
-
-auto myvnet
-iface myvnet
- bridge_ports ln_myvnet
- bridge_stp off
- bridge_fd 0
-
-auto vmbr0
-iface vmbr0
- ovs_ports ln_myvnet
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto ln_myvnet
-iface ln_myvnet
- ovs_type OVSIntPort
- ovs_bridge vmbr0
- ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=100
-
-auto myvnet
-iface myvnet
- bridge_ports ln_myvnet
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
-
-auto vmbr0
-iface vmbr0
- ovs_ports ln_myvnet
+++ /dev/null
-auto eth0
-iface eth0 inet manual
- ovs_type OVSPort
- ovs_bridge vmbr0
-
-auto vmbr0
-iface vmbr0 inet manual
- ovs_type OVSBridge
- ovs_ports eth0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan_remoteip 192.168.0.2
- vxlan_remoteip 192.168.0.3
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
- },
-}
+++ /dev/null
-#version:1
-
-auto myvnet
-iface myvnet
- bridge_ports vxlan_myvnet
- bridge_stp off
- bridge_fd 0
- bridge-vlan-aware yes
- bridge-vids 2-4094
- mtu 1450
-
-auto vxlan_myvnet
-iface vxlan_myvnet
- vxlan-id 100
- vxlan_remoteip 192.168.0.2
- vxlan_remoteip 192.168.0.3
- mtu 1450
+++ /dev/null
-auto vmbr0
-iface vmbr0 inet static
- address 192.168.0.1/24
- gateway 192.168.0.254
- bridge-ports eth0
- bridge-stp off
- bridge-fd 0
+++ /dev/null
-{
- version => 1,
- vnets => {
- ids => {
- myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
- },
- },
- zones => {
- ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
- },
-}