]> git.proxmox.com Git - pve-network.git/blob - src/PVE/Network/SDN/SubnetPlugin.pm
88933f510666787746bbfa8d491fc892bb1db004
[pve-network.git] / src / PVE / Network / SDN / SubnetPlugin.pm
1 package PVE::Network::SDN::SubnetPlugin;
2
3 use strict;
4 use warnings;
5
6 use Net::IP;
7 use Net::Subnet qw(subnet_matcher);
8
9 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
10 use PVE::Exception qw(raise raise_param_exc);
11 use PVE::JSONSchema qw(get_standard_option);
12 use PVE::Network::SDN::Ipams;
13 use PVE::Network::SDN::Vnets;
14
15 use base qw(PVE::SectionConfig);
16
17 PVE::Cluster::cfs_register_file('sdn/subnets.cfg',
18 sub { __PACKAGE__->parse_config(@_); },
19 sub { __PACKAGE__->write_config(@_); });
20
21 PVE::JSONSchema::register_standard_option('pve-sdn-subnet-id', {
22 description => "The SDN subnet object identifier.",
23 type => 'string', format => 'pve-sdn-subnet-id',
24 type => 'string'
25 });
26
27 PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id);
28 sub parse_sdn_subnet_id {
29 my ($id, $noerr) = @_;
30
31 my $cidr = "";
32 if($id =~ /\//) {
33 $cidr = $id;
34 } else {
35 my ($zone, $ip, $mask) = split(/-/, $id);
36 $cidr = "$ip/$mask";
37 }
38
39 if (!(PVE::JSONSchema::pve_verify_cidrv4($cidr, 1) ||
40 PVE::JSONSchema::pve_verify_cidrv6($cidr, 1)))
41 {
42 return undef if $noerr;
43 die "value does not look like a valid CIDR network\n";
44 }
45 return $id;
46 }
47
48 my $defaultData = {
49
50 propertyList => {
51 subnet => get_standard_option('pve-sdn-subnet-id',
52 { completion => \&PVE::Network::SDN::Subnets::complete_sdn_subnet }),
53 },
54 };
55
56 sub type {
57 return 'subnet';
58 }
59
60 sub private {
61 return $defaultData;
62 }
63
64 my $dhcp_range_fmt = {
65 'start-address' => {
66 type => 'ip',
67 description => 'Start address for the DHCP IP range',
68 },
69 'end-address' => {
70 type => 'ip',
71 description => 'End address for the DHCP IP range',
72 },
73 };
74
75 PVE::JSONSchema::register_format('pve-sdn-dhcp-range', $dhcp_range_fmt);
76
77 sub properties {
78 return {
79 vnet => {
80 type => 'string',
81 description => "associated vnet",
82 },
83 gateway => {
84 type => 'string', format => 'ip',
85 description => "Subnet Gateway: Will be assign on vnet for layer3 zones",
86 },
87 snat => {
88 type => 'boolean',
89 description => "enable masquerade for this subnet if pve-firewall",
90 },
91 # #cloudinit, dhcp options
92 # routes => {
93 # type => 'string',
94 # description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
95 # },
96 dnszoneprefix => {
97 type => 'string', format => 'dns-name',
98 description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com",
99 },
100 'dhcp-range' => {
101 type => 'array',
102 description => 'A list of DHCP ranges for this subnet',
103 optional => 1,
104 items => {
105 type => 'string',
106 format => 'pve-sdn-dhcp-range',
107 }
108 },
109 'dhcp-dns-server' => {
110 type => 'ip',
111 description => 'IP address for the DNS server',
112 optional => 1,
113 },
114 };
115 }
116
117 sub options {
118 return {
119 vnet => { optional => 0 },
120 gateway => { optional => 1 },
121 # routes => { optional => 1 },
122 snat => { optional => 1 },
123 dnszoneprefix => { optional => 1 },
124 'dhcp-range' => { optional => 1 },
125 'dhcp-dns-server' => { optional => 1 },
126 };
127 }
128
129 sub on_update_hook {
130 my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_;
131
132 my $cidr = $subnet->{cidr};
133 my $mask = $subnet->{mask};
134
135 my $subnet_matcher = subnet_matcher($cidr);
136
137 my $vnetid = $subnet->{vnet};
138 my $gateway = $subnet->{gateway};
139 my $ipam = $zone->{ipam};
140 my $dns = $zone->{dns};
141 my $dnszone = $zone->{dnszone};
142 my $reversedns = $zone->{reversedns};
143
144 my $old_gateway = $old_subnet->{gateway} if $old_subnet;
145 my $mac = undef;
146
147 if($vnetid) {
148 my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
149 raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet;
150 raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware};
151 $mac = $vnet->{mac};
152 }
153
154 my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32;
155
156 #for /32 pointopoint, we allow gateway outside the subnet
157 raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint;
158
159
160 if ($ipam) {
161 PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
162
163 #don't register gateway for pointopoint
164 return if $pointopoint;
165
166 #delete gateway on removal
167 if (!defined($gateway) && $old_gateway) {
168 eval {
169 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
170 };
171 warn if $@;
172 }
173 if(!$old_gateway || $gateway && $gateway ne $old_gateway) {
174 my $hostname = "$vnetid-gw";
175 PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, undef, 1);
176 }
177
178 #delete old gateway after update
179 if($gateway && $old_gateway && $gateway ne $old_gateway) {
180 eval {
181 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
182 };
183 warn if $@;
184 }
185 }
186 }
187
188 sub on_delete_hook {
189 my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_;
190
191 return;
192 }
193
194 1;