]> git.proxmox.com Git - pve-network.git/blame - src/PVE/Network/SDN/SubnetPlugin.pm
sdn: validate dhcp-range in API
[pve-network.git] / src / PVE / Network / SDN / SubnetPlugin.pm
CommitLineData
c33dd818
AD
1package PVE::Network::SDN::SubnetPlugin;
2
3use strict;
4use warnings;
5
d1ab9bdb
TL
6use Net::IP;
7use Net::Subnet qw(subnet_matcher);
8
c33dd818 9use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
c33dd818 10use PVE::Exception qw(raise raise_param_exc);
d1ab9bdb 11use PVE::JSONSchema qw(get_standard_option);
e612faf6 12use PVE::Network::SDN::Ipams;
d1ab9bdb
TL
13use PVE::Network::SDN::Vnets;
14
15use base qw(PVE::SectionConfig);
c33dd818
AD
16
17PVE::Cluster::cfs_register_file('sdn/subnets.cfg',
18 sub { __PACKAGE__->parse_config(@_); },
19 sub { __PACKAGE__->write_config(@_); });
20
21PVE::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
27PVE::JSONSchema::register_format('pve-sdn-subnet-id', \&parse_sdn_subnet_id);
28sub parse_sdn_subnet_id {
29 my ($id, $noerr) = @_;
30
e8736dac
AD
31 my $cidr = "";
32 if($id =~ /\//) {
33 $cidr = $id;
34 } else {
35 my ($zone, $ip, $mask) = split(/-/, $id);
36 $cidr = "$ip/$mask";
37 }
c33dd818
AD
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
48my $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
56sub type {
57 return 'subnet';
58}
59
60sub private {
61 return $defaultData;
62}
63
290fa5c9
SH
64my $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
75PVE::JSONSchema::register_format('pve-sdn-dhcp-range', $dhcp_range_fmt);
76
d4938d7a
SL
77sub validate_dhcp_ranges {
78 my ($subnet) = @_;
79
80 my $cidr = $subnet->{cidr};
81 my $subnet_matcher = subnet_matcher($cidr);
82
83 my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
84
85 foreach my $dhcp_range (@$dhcp_ranges) {
86 my $dhcp_start = $dhcp_range->{'start-address'};
87 my $dhcp_end = $dhcp_range->{'end-address'};
88
89 my $start_ip = new Net::IP($dhcp_start);
90 raise_param_exc({ 'dhcp-range' => "start-adress is not a valid IP $dhcp_start" }) if !$start_ip;
91
92 my $end_ip = new Net::IP($dhcp_end);
93 raise_param_exc({ 'dhcp-range' => "end-adress is not a valid IP $dhcp_end" }) if !$end_ip;
94
95 if (Net::IP::ip_bincomp($end_ip->binip(), 'lt', $start_ip->binip()) == 1) {
96 raise_param_exc({ 'dhcp-range' => "start-address $dhcp_start must be smaller than end-address $dhcp_end" })
97 }
98
99 raise_param_exc({ 'dhcp-range' => "start-address $dhcp_start is not in subnet $cidr" }) if !$subnet_matcher->($dhcp_start);
100 raise_param_exc({ 'dhcp-range' => "end-address $dhcp_end is not in subnet $cidr" }) if !$subnet_matcher->($dhcp_end);
101 }
102}
103
c33dd818
AD
104sub properties {
105 return {
e612faf6
AD
106 vnet => {
107 type => 'string',
108 description => "associated vnet",
109 },
c33dd818
AD
110 gateway => {
111 type => 'string', format => 'ip',
112 description => "Subnet Gateway: Will be assign on vnet for layer3 zones",
113 },
114 snat => {
115 type => 'boolean',
116 description => "enable masquerade for this subnet if pve-firewall",
117 },
f6f2aa16
AD
118# #cloudinit, dhcp options
119# routes => {
120# type => 'string',
121# description => "static routes [network=<network>:gateway=<ip>,network=<network>:gateway=<ip>,... ]",
122# },
ee4f339e 123 dnszoneprefix => {
f6f2aa16 124 type => 'string', format => 'dns-name',
ee4f339e 125 description => "dns domain zone prefix ex: 'adm' -> <hostname>.adm.mydomain.com",
c33dd818 126 },
290fa5c9
SH
127 'dhcp-range' => {
128 type => 'array',
129 description => 'A list of DHCP ranges for this subnet',
130 optional => 1,
131 items => {
132 type => 'string',
133 format => 'pve-sdn-dhcp-range',
134 }
135 },
136 'dhcp-dns-server' => {
42ff574f 137 type => 'string', format => 'ip',
290fa5c9
SH
138 description => 'IP address for the DNS server',
139 optional => 1,
140 },
c33dd818
AD
141 };
142}
143
144sub options {
145 return {
3926d9a7 146 vnet => { optional => 0 },
c33dd818 147 gateway => { optional => 1 },
f6f2aa16 148# routes => { optional => 1 },
c33dd818 149 snat => { optional => 1 },
ee4f339e 150 dnszoneprefix => { optional => 1 },
290fa5c9
SH
151 'dhcp-range' => { optional => 1 },
152 'dhcp-dns-server' => { optional => 1 },
c33dd818
AD
153 };
154}
155
156sub on_update_hook {
4ad78442 157 my ($class, $zone, $subnetid, $subnet, $old_subnet) = @_;
c33dd818 158
e8736dac
AD
159 my $cidr = $subnet->{cidr};
160 my $mask = $subnet->{mask};
161
ee4f339e
AD
162 my $subnet_matcher = subnet_matcher($cidr);
163
e612faf6 164 my $vnetid = $subnet->{vnet};
ee4f339e 165 my $gateway = $subnet->{gateway};
331e2330 166 my $ipam = $zone->{ipam};
4ad78442
AD
167 my $dns = $zone->{dns};
168 my $dnszone = $zone->{dnszone};
169 my $reversedns = $zone->{reversedns};
ee4f339e 170
e612faf6 171 my $old_gateway = $old_subnet->{gateway} if $old_subnet;
e9365ab0 172 my $mac = undef;
e612faf6
AD
173
174 if($vnetid) {
175 my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
176 raise_param_exc({ vnet => "$vnetid don't exist"}) if !$vnet;
7416e82d 177 raise_param_exc({ vnet => "you can't add a subnet on a vlanaware vnet"}) if $vnet->{vlanaware};
e9365ab0 178 $mac = $vnet->{mac};
e612faf6
AD
179 }
180
e8736dac
AD
181 my $pointopoint = 1 if Net::IP::ip_is_ipv4($gateway) && $mask == 32;
182
e612faf6 183 #for /32 pointopoint, we allow gateway outside the subnet
e8736dac
AD
184 raise_param_exc({ gateway => "$gateway is not in subnet $cidr"}) if $gateway && !$subnet_matcher->($gateway) && !$pointopoint;
185
d4938d7a 186 validate_dhcp_ranges($subnet);
70b03506 187
e612faf6 188 if ($ipam) {
77ec7eb2 189 PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
e612faf6 190
e8736dac
AD
191 #don't register gateway for pointopoint
192 return if $pointopoint;
193
194 #delete gateway on removal
e612faf6
AD
195 if (!defined($gateway) && $old_gateway) {
196 eval {
4ad78442 197 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
e612faf6
AD
198 };
199 warn if $@;
200 }
201 if(!$old_gateway || $gateway && $gateway ne $old_gateway) {
ceb972a9 202 my $hostname = "$vnetid-gw";
7ba17817 203 PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, undef, 1);
e612faf6
AD
204 }
205
e8736dac 206 #delete old gateway after update
e612faf6
AD
207 if($gateway && $old_gateway && $gateway ne $old_gateway) {
208 eval {
4ad78442 209 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $old_subnet, $old_gateway);
e612faf6
AD
210 };
211 warn if $@;
212 }
213 }
c33dd818
AD
214}
215
58a7773a
AD
216sub on_delete_hook {
217 my ($class, $subnetid, $subnet_cfg, $vnet_cfg) = @_;
218
58a7773a
AD
219 return;
220}
221
c33dd818 2221;