]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Network.pm
739ef874f766e212f2291d05b6c5e33ed75952a7
[pve-manager.git] / PVE / API2 / Network.pm
1 package PVE::API2::Network;
2
3 use strict;
4 use warnings;
5
6 use Net::IP qw(:PROC);
7 use PVE::Tools qw(extract_param);
8 use PVE::SafeSyslog;
9 use PVE::INotify;
10 use PVE::Exception qw(raise_param_exc);
11 use PVE::RESTHandler;
12 use PVE::RPCEnvironment;
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::AccessControl;
15 use IO::File;
16
17 use base qw(PVE::RESTHandler);
18
19 my $iflockfn = "/etc/network/.pve-interfaces.lock";
20
21 my $bond_mode_enum = [
22 'balance-rr',
23 'active-backup', # OVS and Linux
24 'balance-xor',
25 'broadcast',
26 '802.3ad',
27 'balance-tlb',
28 'balance-alb',
29 'balance-slb', # OVS
30 'lacp-balance-slb', # OVS
31 'lacp-balance-tcp', # OVS
32 ];
33
34 my $network_type_enum = ['bridge', 'bond', 'eth', 'alias',
35 'OVSBridge', 'OVSBond', 'OVSPort', 'OVSIntPort'];
36
37 my $confdesc = {
38 type => {
39 description => "Network interface type",
40 type => 'string',
41 enum => [@$network_type_enum, 'unknown'],
42 },
43 autostart => {
44 description => "Automatically start interface on boot.",
45 type => 'boolean',
46 optional => 1,
47 },
48 bridge_ports => {
49 description => "Specify the iterfaces you want to add to your bridge.",
50 optional => 1,
51 type => 'string', format => 'pve-iface-list',
52 },
53 ovs_ports => {
54 description => "Specify the iterfaces you want to add to your bridge.",
55 optional => 1,
56 type => 'string', format => 'pve-iface-list',
57 },
58 ovs_options => {
59 description => "OVS interface options.",
60 optional => 1,
61 type => 'string',
62 maxLength => 1024,
63 },
64 ovs_bridge => {
65 description => "The OVS bridge associated with a OVS port. This is required when you create an OVS port.",
66 optional => 1,
67 type => 'string', format => 'pve-iface',
68 },
69 slaves => {
70 description => "Specify the interfaces used by the bonding device.",
71 optional => 1,
72 type => 'string', format => 'pve-iface-list',
73 },
74 ovs_bonds => {
75 description => "Specify the interfaces used by the bonding device.",
76 optional => 1,
77 type => 'string', format => 'pve-iface-list',
78 },
79 bond_mode => {
80 description => "Bonding mode.",
81 optional => 1,
82 type => 'string', enum => $bond_mode_enum,
83 },
84 bond_xmit_hash_policy => {
85 description => "Selects the transmit hash policy to use for slave selection in balance-xor and 802.3ad modes.",
86 optional => 1,
87 type => 'string',
88 enum => ['layer2', 'layer2+3', 'layer3+4' ],
89 },
90 gateway => {
91 description => 'Default gateway address.',
92 type => 'string', format => 'ipv4',
93 optional => 1,
94 },
95 netmask => {
96 description => 'Network mask.',
97 type => 'string', format => 'ipv4mask',
98 optional => 1,
99 requires => 'address',
100 },
101 address => {
102 description => 'IP address.',
103 type => 'string', format => 'ipv4',
104 optional => 1,
105 requires => 'netmask',
106 }
107 };
108
109 sub json_config_properties {
110 my $prop = shift;
111
112 foreach my $opt (keys %$confdesc) {
113 $prop->{$opt} = $confdesc->{$opt};
114 }
115
116 return $prop;
117 }
118
119 __PACKAGE__->register_method({
120 name => 'index',
121 path => '',
122 method => 'GET',
123 permissions => { user => 'all' },
124 description => "List available networks",
125 proxyto => 'node',
126 parameters => {
127 additionalProperties => 0,
128 properties => {
129 node => get_standard_option('pve-node'),
130 type => {
131 description => "Only list specific interface types.",
132 type => 'string',
133 enum => $network_type_enum,
134 optional => 1,
135 },
136 },
137 },
138 returns => {
139 type => "array",
140 items => {
141 type => "object",
142 properties => {},
143 },
144 links => [ { rel => 'child', href => "{iface}" } ],
145 },
146 code => sub {
147 my ($param) = @_;
148
149 my $rpcenv = PVE::RPCEnvironment::get();
150
151 my $tmp = PVE::INotify::read_file('interfaces', 1);
152 my $config = $tmp->{data};
153 my $changes = $tmp->{changes};
154
155 $rpcenv->set_result_attrib('changes', $changes) if $changes;
156
157 delete $config->{lo}; # do not list the loopback device
158
159 if ($param->{type}) {
160 foreach my $k (keys %$config) {
161 delete $config->{$k} if $param->{type} ne $config->{$k}->{type};
162 }
163 }
164
165 return PVE::RESTHandler::hash_to_array($config, 'iface');
166 }});
167
168 __PACKAGE__->register_method({
169 name => 'revert_network_changes',
170 path => '',
171 method => 'DELETE',
172 permissions => {
173 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
174 },
175 protected => 1,
176 description => "Revert network configuration changes.",
177 proxyto => 'node',
178 parameters => {
179 additionalProperties => 0,
180 properties => {
181 node => get_standard_option('pve-node'),
182 },
183 },
184 returns => { type => "null" },
185 code => sub {
186 my ($param) = @_;
187
188 unlink "/etc/network/interfaces.new";
189
190 return undef;
191 }});
192
193 my $check_duplicate_gateway = sub {
194 my ($config, $newiface) = @_;
195
196 foreach my $iface (keys %$config) {
197 raise_param_exc({ gateway => "Default gateway already exists on interface '$iface'." })
198 if ($newiface ne $iface) && $config->{$iface}->{gateway};
199 }
200 };
201
202 my $check_ipv4_settings = sub {
203 my ($address, $netmask) = @_;
204
205 my $binip = Net::IP::ip_iptobin($address, 4);
206 my $binmask = Net::IP::ip_iptobin($netmask, 4);
207 my $broadcast = Net::IP::ip_iptobin('255.255.255.255', 4);
208 my $binhost = $binip | $binmask;
209
210 raise_param_exc({ address => "$address is not a valid host ip address." })
211 if ($binhost eq $binmask) || ($binhost eq $broadcast);
212 };
213
214 __PACKAGE__->register_method({
215 name => 'create_network',
216 path => '',
217 method => 'POST',
218 permissions => {
219 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
220 },
221 description => "Create network device configuration",
222 protected => 1,
223 proxyto => 'node',
224 parameters => {
225 additionalProperties => 0,
226 properties => json_config_properties({
227 node => get_standard_option('pve-node'),
228 iface => get_standard_option('pve-iface')}),
229 },
230 returns => { type => 'null' },
231 code => sub {
232 my ($param) = @_;
233
234 my $node = extract_param($param, 'node');
235 my $iface = extract_param($param, 'iface');
236
237 my $code = sub {
238 my $config = PVE::INotify::read_file('interfaces');
239
240 raise_param_exc({ iface => "interface already exists" })
241 if $config->{$iface};
242
243 &$check_duplicate_gateway($config, $iface)
244 if $param->{gateway};
245
246 &$check_ipv4_settings($param->{address}, $param->{netmask})
247 if $param->{address};
248
249 $param->{method} = $param->{address} ? 'static' : 'manual';
250
251 if ($param->{type} =~ m/^OVS/) {
252 -x '/usr/bin/ovs-vsctl' ||
253 die "Open VSwitch is not installed (need package 'openvswitch-switch')\n";
254 }
255
256 if ($param->{type} eq 'OVSIntPort' || $param->{type} eq 'OVSBond') {
257 my $brname = $param->{ovs_bridge};
258 raise_param_exc({ ovs_bridge => "parameter is required" }) if !$brname;
259 my $br = $config->{$brname};
260 raise_param_exc({ ovs_bridge => "bridge '$brname' does not exist" }) if !$br;
261 raise_param_exc({ ovs_bridge => "interface '$brname' is no OVS bridge" })
262 if $br->{type} ne 'OVSBridge';
263
264 my @ports = split (/\s+/, $br->{ovs_ports} || '');
265 $br->{ovs_ports} = join(' ', @ports, $iface)
266 if ! grep { $_ eq $iface } @ports;
267 }
268
269 $config->{$iface} = $param;
270
271 PVE::INotify::write_file('interfaces', $config);
272 };
273
274 PVE::Tools::lock_file($iflockfn, 10, $code);
275 die $@ if $@;
276
277 return undef;
278 }});
279
280 __PACKAGE__->register_method({
281 name => 'update_network',
282 path => '{iface}',
283 method => 'PUT',
284 permissions => {
285 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
286 },
287 description => "Update network device configuration",
288 protected => 1,
289 proxyto => 'node',
290 parameters => {
291 additionalProperties => 0,
292 properties => json_config_properties({
293 node => get_standard_option('pve-node'),
294 iface => get_standard_option('pve-iface'),
295 delete => {
296 type => 'string', format => 'pve-configid-list',
297 description => "A list of settings you want to delete.",
298 optional => 1,
299 }}),
300 },
301 returns => { type => 'null' },
302 code => sub {
303 my ($param) = @_;
304
305 my $node = extract_param($param, 'node');
306 my $iface = extract_param($param, 'iface');
307 my $delete = extract_param($param, 'delete');
308
309 my $code = sub {
310 my $config = PVE::INotify::read_file('interfaces');
311
312 raise_param_exc({ iface => "interface does not exist" })
313 if !$config->{$iface};
314
315 foreach my $k (PVE::Tools::split_list($delete)) {
316 delete $config->{$iface}->{$k};
317 }
318
319 &$check_duplicate_gateway($config, $iface)
320 if $param->{gateway};
321
322 &$check_ipv4_settings($param->{address}, $param->{netmask})
323 if $param->{address};
324
325 $param->{method} = $param->{address} ? 'static' : 'manual';
326
327 foreach my $k (keys %$param) {
328 $config->{$iface}->{$k} = $param->{$k};
329 }
330
331 PVE::INotify::write_file('interfaces', $config);
332 };
333
334 PVE::Tools::lock_file($iflockfn, 10, $code);
335 die $@ if $@;
336
337 return undef;
338 }});
339
340 __PACKAGE__->register_method({
341 name => 'network_config',
342 path => '{iface}',
343 method => 'GET',
344 permissions => {
345 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
346 },
347 description => "Read network device configuration",
348 proxyto => 'node',
349 parameters => {
350 additionalProperties => 0,
351 properties => {
352 node => get_standard_option('pve-node'),
353 iface => get_standard_option('pve-iface'),
354 },
355 },
356 returns => {
357 type => "object",
358 properties => {
359 type => {
360 type => 'string',
361 },
362 method => {
363 type => 'string',
364 },
365 },
366 },
367 code => sub {
368 my ($param) = @_;
369
370 my $config = PVE::INotify::read_file('interfaces');
371
372 raise_param_exc({ iface => "interface does not exist" })
373 if !$config->{$param->{iface}};
374
375 return $config->{$param->{iface}};
376 }});
377
378 __PACKAGE__->register_method({
379 name => 'delete_network',
380 path => '{iface}',
381 method => 'DELETE',
382 permissions => {
383 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
384 },
385 description => "Delete network device configuration",
386 protected => 1,
387 proxyto => 'node',
388 parameters => {
389 additionalProperties => 0,
390 properties => {
391 node => get_standard_option('pve-node'),
392 iface => get_standard_option('pve-iface'),
393 },
394 },
395 returns => { type => 'null' },
396 code => sub {
397 my ($param) = @_;
398
399 my $code = sub {
400 my $config = PVE::INotify::read_file('interfaces');
401
402 raise_param_exc({ iface => "interface does not exist" })
403 if !$config->{$param->{iface}};
404
405 my $d = $config->{$param->{iface}};
406 if ($d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') {
407 if (my $brname = $d->{ovs_bridge}) {
408 if (my $br = $config->{$brname}) {
409 if ($br->{ovs_ports}) {
410 my @ports = split (/\s+/, $br->{ovs_ports});
411 my @new = grep { $_ ne $param->{iface} } @ports;
412 $br->{ovs_ports} = join(' ', @new);
413 }
414 }
415 }
416 }
417
418 delete $config->{$param->{iface}};
419
420 PVE::INotify::write_file('interfaces', $config);
421 };
422
423 PVE::Tools::lock_file($iflockfn, 10, $code);
424 die $@ if $@;
425
426 return undef;
427 }});