2019-11-26 10:00:16 +01:00
|
|
|
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);
|
2020-03-09 10:24:13 +01:00
|
|
|
use PVE::Network;
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
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;
|
2020-07-01 09:10:36 +02:00
|
|
|
use PVE::Network::SDN::Zones::SimplePlugin;
|
2019-11-26 10:00:16 +01:00
|
|
|
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();
|
2020-07-01 09:10:36 +02:00
|
|
|
PVE::Network::SDN::Zones::SimplePlugin->register();
|
2019-11-26 10:00:16 +01:00
|
|
|
PVE::Network::SDN::Zones::Plugin->init();
|
|
|
|
|
2020-04-13 08:13:12 +02:00
|
|
|
my $local_network_sdn_file = "/etc/network/interfaces.d/sdn";
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
sub sdn_zones_config {
|
|
|
|
my ($cfg, $id, $noerr) = @_;
|
|
|
|
|
|
|
|
die "no sdn zone ID specified\n" if !$id;
|
|
|
|
|
|
|
|
my $scfg = $cfg->{ids}->{$id};
|
2019-12-13 12:10:15 +01:00
|
|
|
die "sdn '$id' does not exist\n" if (!$noerr && !$scfg);
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
return $scfg;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub config {
|
2020-04-13 08:13:10 +02:00
|
|
|
my $config = cfs_read_file("sdn/zones.cfg");
|
2019-11-26 10:00:16 +01:00
|
|
|
return $config;
|
|
|
|
}
|
|
|
|
|
2020-04-09 10:26:59 +02:00
|
|
|
sub get_plugin_config {
|
|
|
|
my ($vnet) = @_;
|
|
|
|
my $zoneid = $vnet->{zone};
|
|
|
|
my $zone_cfg = PVE::Network::SDN::Zones::config();
|
|
|
|
return $zone_cfg->{ids}->{$zoneid};
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
sub write_config {
|
|
|
|
my ($cfg) = @_;
|
|
|
|
|
2020-04-13 08:13:10 +02:00
|
|
|
cfs_write_file("sdn/zones.cfg", $cfg);
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub sdn_zones_ids {
|
|
|
|
my ($cfg) = @_;
|
|
|
|
|
2020-10-05 17:09:05 +02:00
|
|
|
return sort keys %{$cfg->{ids}};
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub complete_sdn_zone {
|
|
|
|
my ($cmdname, $pname, $cvalue) = @_;
|
|
|
|
|
2021-04-28 16:07:54 +02:00
|
|
|
my $cfg = PVE::Network::SDN::running_config();
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::sdn_zones_ids($cfg) ];
|
|
|
|
}
|
|
|
|
|
2020-10-05 17:09:06 +02:00
|
|
|
sub get_zone {
|
|
|
|
my ($zoneid, $running) = @_;
|
|
|
|
|
|
|
|
my $cfg = {};
|
|
|
|
if($running) {
|
2021-04-28 16:07:54 +02:00
|
|
|
my $cfg = PVE::Network::SDN::running_config();
|
2020-10-05 17:09:06 +02:00
|
|
|
$cfg = $cfg->{vnets};
|
|
|
|
} else {
|
|
|
|
$cfg = PVE::Network::SDN::Zones::config();
|
|
|
|
}
|
|
|
|
|
|
|
|
my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
|
|
|
|
|
|
|
|
return $zone;
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
sub generate_etc_network_config {
|
|
|
|
|
2021-04-28 16:07:54 +02:00
|
|
|
my $cfg = PVE::Network::SDN::running_config();
|
2020-10-05 17:08:46 +02:00
|
|
|
|
|
|
|
my $version = $cfg->{version};
|
|
|
|
my $vnet_cfg = $cfg->{vnets};
|
|
|
|
my $zone_cfg = $cfg->{zones};
|
|
|
|
my $subnet_cfg = $cfg->{subnets};
|
|
|
|
my $controller_cfg = $cfg->{controllers};
|
2019-11-26 10:00:29 +01:00
|
|
|
return if !$vnet_cfg && !$zone_cfg;
|
2019-11-26 10:00:16 +01:00
|
|
|
|
2019-11-28 09:40:25 +01:00
|
|
|
my $interfaces_config = PVE::INotify::read_file('interfaces');
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
#generate configuration
|
|
|
|
my $config = {};
|
2019-11-26 10:00:23 +01:00
|
|
|
my $nodename = PVE::INotify::nodename();
|
|
|
|
|
2020-05-06 12:29:06 +02:00
|
|
|
for my $id (sort keys %{$vnet_cfg->{ids}}) {
|
2019-11-26 10:00:16 +01:00
|
|
|
my $vnet = $vnet_cfg->{ids}->{$id};
|
2019-11-26 10:00:20 +01:00
|
|
|
my $zone = $vnet->{zone};
|
2019-11-26 10:00:16 +01:00
|
|
|
|
2020-05-06 12:29:06 +02:00
|
|
|
if (!$zone) {
|
|
|
|
warn "can't generate vnet '$id': no zone assigned!\n";
|
2019-11-26 10:00:16 +01:00
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:29 +01:00
|
|
|
my $plugin_config = $zone_cfg->{ids}->{$zone};
|
2019-11-26 10:00:16 +01:00
|
|
|
|
|
|
|
if (!defined($plugin_config)) {
|
2020-05-06 12:29:06 +02:00
|
|
|
warn "can't generate vnet '$id': zone $zone don't exist\n";
|
2019-11-26 10:00:16 +01:00
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:23 +01:00
|
|
|
next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
|
|
|
|
|
2020-05-06 12:29:06 +02:00
|
|
|
my $controller;
|
|
|
|
if (my $controllerid = $plugin_config->{controller}) {
|
|
|
|
$controller = $controller_cfg->{ids}->{$controllerid};
|
2019-11-28 09:40:24 +01:00
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
|
2020-06-02 11:20:24 +02:00
|
|
|
eval {
|
2020-11-25 10:01:37 +01:00
|
|
|
$plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
|
2020-06-02 11:20:24 +02:00
|
|
|
};
|
2020-06-03 11:35:37 +02:00
|
|
|
if (my $err = $@) {
|
|
|
|
warn "zone $zone : vnet $id : $err\n";
|
2020-06-02 11:20:24 +02:00
|
|
|
next;
|
|
|
|
}
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
|
|
|
|
2020-04-13 08:13:12 +02:00
|
|
|
my $raw_network_config = "\#version:$version\n";
|
2020-04-09 10:27:53 +02:00
|
|
|
foreach my $iface (sort keys %$config) {
|
2019-11-26 10:00:16 +01:00
|
|
|
$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;
|
|
|
|
|
2020-04-13 08:13:12 +02:00
|
|
|
my $writefh = IO::File->new($local_network_sdn_file,">");
|
2019-11-26 10:00:16 +01:00
|
|
|
print $writefh $rawconfig;
|
|
|
|
$writefh->close();
|
|
|
|
}
|
|
|
|
|
2020-04-13 08:13:12 +02:00
|
|
|
sub read_etc_network_config_version {
|
|
|
|
my $versionstr = PVE::Tools::file_read_firstline($local_network_sdn_file);
|
2020-05-05 17:22:01 +02:00
|
|
|
|
|
|
|
return if !defined($versionstr);
|
|
|
|
|
2020-04-13 08:13:12 +02:00
|
|
|
if ($versionstr =~ m/^\#version:(\d+)$/) {
|
|
|
|
return $1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-06-03 13:19:26 +02:00
|
|
|
my $warned_about_reload;
|
2020-06-19 13:47:07 +02:00
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
sub status {
|
|
|
|
|
|
|
|
my $err_config = undef;
|
|
|
|
|
2020-04-13 08:13:13 +02:00
|
|
|
my $local_version = PVE::Network::SDN::Zones::read_etc_network_config_version();
|
2021-04-28 16:07:54 +02:00
|
|
|
my $cfg = PVE::Network::SDN::running_config();
|
2020-10-05 17:08:46 +02:00
|
|
|
my $sdn_version = $cfg->{version};
|
2019-11-26 10:00:16 +01:00
|
|
|
|
2020-04-13 08:13:13 +02:00
|
|
|
return if !$sdn_version;
|
2019-11-26 10:00:21 +01:00
|
|
|
|
2020-04-13 08:13:13 +02:00
|
|
|
if (!$local_version) {
|
2019-11-26 10:00:21 +01:00
|
|
|
$err_config = "local sdn network configuration is not yet generated, please reload";
|
2020-06-03 13:19:26 +02:00
|
|
|
if (!$warned_about_reload) {
|
|
|
|
$warned_about_reload = 1;
|
|
|
|
warn "$err_config\n";
|
|
|
|
}
|
2020-04-13 08:13:13 +02:00
|
|
|
} elsif ($local_version < $sdn_version) {
|
|
|
|
$err_config = "local sdn network configuration is too old, please reload";
|
2020-06-03 13:19:26 +02:00
|
|
|
if (!$warned_about_reload) {
|
|
|
|
$warned_about_reload = 1;
|
|
|
|
warn "$err_config\n";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$warned_about_reload = 0;
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
my $status = ifquery_check();
|
|
|
|
|
2020-10-05 17:08:46 +02:00
|
|
|
my $vnet_cfg = $cfg->{vnets};
|
|
|
|
my $zone_cfg = $cfg->{zones};
|
2019-11-26 10:00:23 +01:00
|
|
|
my $nodename = PVE::INotify::nodename();
|
2019-11-26 10:00:16 +01:00
|
|
|
|
2020-06-25 13:41:28 +02:00
|
|
|
my $vnet_status = {};
|
2019-11-26 10:00:29 +01:00
|
|
|
my $zone_status = {};
|
2019-11-26 10:00:16 +01:00
|
|
|
|
2020-07-03 13:46:39 +02:00
|
|
|
for my $id (sort keys %{$zone_cfg->{ids}}) {
|
2022-02-11 10:33:22 +01:00
|
|
|
next if defined($zone_cfg->{ids}->{$id}->{nodes}) && !$zone_cfg->{ids}->{$id}->{nodes}->{$nodename};
|
2020-07-03 13:46:39 +02:00
|
|
|
$zone_status->{$id}->{status} = $err_config ? 'pending' : 'available';
|
2020-06-19 13:47:07 +02:00
|
|
|
}
|
|
|
|
|
2020-04-09 08:03:33 +02:00
|
|
|
foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
|
|
|
|
my $vnet = $vnet_cfg->{ids}->{$id};
|
|
|
|
my $zone = $vnet->{zone};
|
2021-04-28 16:36:59 +02:00
|
|
|
next if !defined($zone);
|
2020-02-10 17:26:05 +01:00
|
|
|
|
2020-04-09 08:03:33 +02:00
|
|
|
my $plugin_config = $zone_cfg->{ids}->{$zone};
|
2021-04-28 16:37:35 +02:00
|
|
|
|
|
|
|
if (!defined($plugin_config)) {
|
|
|
|
$vnet_status->{$id}->{status} = 'error';
|
|
|
|
$vnet_status->{$id}->{statusmsg} = "unknown zone '$zone' configured";
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2020-04-09 08:03:33 +02:00
|
|
|
next if defined($plugin_config->{nodes}) && !$plugin_config->{nodes}->{$nodename};
|
|
|
|
|
2020-06-19 13:47:07 +02:00
|
|
|
$vnet_status->{$id}->{zone} = $zone;
|
|
|
|
$vnet_status->{$id}->{status} = 'available';
|
|
|
|
|
2020-07-03 13:46:39 +02:00
|
|
|
if ($err_config) {
|
2020-06-19 13:47:07 +02:00
|
|
|
$vnet_status->{$id}->{status} = 'pending';
|
|
|
|
$vnet_status->{$id}->{statusmsg} = $err_config;
|
|
|
|
next;
|
|
|
|
}
|
|
|
|
|
2020-04-09 08:03:33 +02:00
|
|
|
my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
|
2020-06-19 13:47:07 +02:00
|
|
|
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';
|
2020-06-25 13:41:28 +02:00
|
|
|
}
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
2020-02-10 17:26:05 +01:00
|
|
|
|
2020-07-03 13:46:39 +02:00
|
|
|
return ($zone_status, $vnet_status);
|
2019-11-26 10:00:16 +01:00
|
|
|
}
|
|
|
|
|
2020-03-09 10:24:13 +01:00
|
|
|
sub tap_create {
|
|
|
|
my ($iface, $bridge) = @_;
|
|
|
|
|
2020-10-05 17:08:46 +02:00
|
|
|
my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
|
2020-04-09 10:27:24 +02:00
|
|
|
if (!$vnet) { # fallback for classic bridge
|
2020-03-09 10:24:13 +01:00
|
|
|
PVE::Network::tap_create($iface, $bridge);
|
2020-04-09 10:27:24 +02:00
|
|
|
return;
|
2020-03-09 10:24:13 +01:00
|
|
|
}
|
|
|
|
|
2020-04-09 10:26:59 +02:00
|
|
|
my $plugin_config = get_plugin_config($vnet);
|
2020-03-09 10:24:13 +01:00
|
|
|
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) = @_;
|
|
|
|
|
2020-10-05 17:08:46 +02:00
|
|
|
my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
|
2020-04-09 10:27:24 +02:00
|
|
|
if (!$vnet) { # fallback for classic bridge
|
2020-03-09 10:24:13 +01:00
|
|
|
PVE::Network::veth_create($veth, $vethpeer, $bridge, $hwaddr);
|
2020-04-09 10:27:24 +02:00
|
|
|
return;
|
2020-03-09 10:24:13 +01:00
|
|
|
}
|
|
|
|
|
2020-04-09 10:26:59 +02:00
|
|
|
my $plugin_config = get_plugin_config($vnet);
|
2020-03-09 10:24:13 +01:00
|
|
|
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) = @_;
|
|
|
|
|
2020-10-05 17:08:46 +02:00
|
|
|
my $vnet = PVE::Network::SDN::Vnets::get_vnet($bridge, 1);
|
2020-04-09 10:27:24 +02:00
|
|
|
if (!$vnet) { # fallback for classic bridge
|
2021-09-24 10:50:59 +02:00
|
|
|
my $interfaces_config = PVE::INotify::read_file('interfaces');
|
|
|
|
my $disablelearning = 1 if $interfaces_config->{ifaces}->{$bridge} && $interfaces_config->{ifaces}->{$bridge}->{'bridge-disable-mac-learning'};
|
|
|
|
PVE::Network::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $disablelearning);
|
2020-03-09 10:24:13 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-09 10:26:59 +02:00
|
|
|
my $plugin_config = get_plugin_config($vnet);
|
2020-03-09 10:24:13 +01:00
|
|
|
my $nodename = PVE::INotify::nodename();
|
|
|
|
|
2020-04-09 10:26:59 +02:00
|
|
|
die "vnet $bridge is not allowed on this node\n"
|
|
|
|
if $plugin_config->{nodes} && !defined($plugin_config->{nodes}->{$nodename});
|
2020-02-10 17:26:10 +01:00
|
|
|
|
|
|
|
my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
|
2020-06-02 11:20:21 +02:00
|
|
|
$plugin->tap_plug($plugin_config, $vnet, $tag, $iface, $bridge, $firewall, $trunks, $rate);
|
2020-02-10 17:26:10 +01:00
|
|
|
}
|
|
|
|
|
2021-09-24 10:51:00 +02:00
|
|
|
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'};
|
|
|
|
}
|
|
|
|
|
2019-11-26 10:00:16 +01:00
|
|
|
1;
|
|
|
|
|