forked from kimchi-project/kimchi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
network.py
502 lines (349 loc) · 12.2 KB
/
network.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#
# Project Kimchi
#
# Copyright IBM Corp, 2015-2017
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
import ethtool
import glob
import ipaddr
import os
from distutils.spawn import find_executable
from wok.stringutils import encode_value
from wok.utils import run_command
APrivateNets = ipaddr.IPNetwork("10.0.0.0/8")
BPrivateNets = ipaddr.IPNetwork("172.16.0.0/12")
CPrivateNets = ipaddr.IPNetwork('192.168.0.0/16')
PrivateNets = [CPrivateNets, BPrivateNets, APrivateNets]
DefaultNetsPool = [ipaddr.IPNetwork('192.168.122.0/23'),
ipaddr.IPNetwork('192.168.124.0/22'),
ipaddr.IPNetwork('192.168.128.0/17')]
NET_PATH = '/sys/class/net'
NIC_PATH = '/sys/class/net/*/device'
BRIDGE_PATH = '/sys/class/net/*/bridge'
BONDING_PATH = '/sys/class/net/*/bonding'
WLAN_PATH = '/sys/class/net/*/wireless'
NET_BRPORT = '/sys/class/net/%s/brport'
NET_MASTER = '/sys/class/net/%s/master'
PROC_NET_VLAN = '/proc/net/vlan/'
BONDING_SLAVES = '/sys/class/net/%s/bonding/slaves'
BRIDGE_PORTS = '/sys/class/net/%s/brif'
def wlans():
"""Get all wlans declared in /sys/class/net/*/wireless.
Returns:
List[str]: a list with the wlans found.
"""
return [b.split('/')[-2] for b in glob.glob(WLAN_PATH)]
def nics():
"""Get all nics of the host.
This function returns every nic, including those
that might be loaded from an usb port.
Returns:
List[str]: a list with the nics found.
"""
return list(set([b.split('/')[-2] for b in glob.glob(NIC_PATH)]) -
set(wlans()))
def is_nic(iface):
"""Checks if iface is a nic.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a nic, False otherwise.
"""
return encode_value(iface) in map(encode_value, nics())
def bondings():
"""Get all bondings of the host.
Returns:
List[str]: a list with the bonds found.
"""
return [b.split('/')[-2] for b in glob.glob(BONDING_PATH)]
def is_bonding(iface):
"""Checks if iface is a bond.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a bond, False otherwise.
"""
return encode_value(iface) in map(encode_value, bondings())
def vlans():
"""Get all vlans of the host.
Returns:
List[str]: a list with the vlans found.
"""
return list(set([b.split('/')[-1]
for b in glob.glob(NET_PATH + '/*')]) &
set([b.split('/')[-1]
for b in glob.glob(PROC_NET_VLAN + '*')]))
def is_vlan(iface):
"""Checks if iface is a vlan.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a vlan, False otherwise.
"""
return encode_value(iface) in map(encode_value, vlans())
def bridges():
"""Get all bridges of the host.
Returns:
List[str]: a list with the bridges found.
"""
return list(set([b.split('/')[-2] for b in glob.glob(BRIDGE_PATH)] +
ovs_bridges()))
def is_bridge(iface):
"""Checks if iface is a bridge.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a bridge, False otherwise.
"""
return encode_value(iface) in map(encode_value, bridges())
def is_openvswitch_running():
"""Checks if the openvswitch service is running in the host.
Returns:
bool: True if openvswitch service is running, False otherwise.
"""
cmd = ['systemctl', 'is-active', 'openvswitch', '--quiet']
_, _, r_code = run_command(cmd, silent=True)
return r_code == 0
def ovs_bridges():
"""Get the OVS Bridges of the host.
In some distributions, like Fedora, the files bridge and brif are
not created under /sys/class/net/<ovsbridge> for OVS bridges.
These specific functions allows one to differentiate OVS bridges
from other types of bridges.
Returns:
List[str]: a list with the OVS bridges found.
"""
if not is_openvswitch_running():
return []
ovs_cmd = find_executable("ovs-vsctl")
# openvswitch not installed: there is no OVS bridge configured
if ovs_cmd is None:
return []
out, _, r_code = run_command([ovs_cmd, 'list-br'], silent=True)
if r_code != 0:
return []
return [x.strip() for x in out.rstrip('\n').split('\n') if x.strip()]
def is_ovs_bridge(iface):
"""Checks if iface is an OVS bridge.
In some distributions, like Fedora, the files bridge and brif are
not created under /sys/class/net/<ovsbridge> for OVS bridges.
These specific functions allows one to differentiate OVS bridges
from other types of bridges.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is an OVS bridge, False otherwise.
"""
return iface in ovs_bridges()
def ovs_bridge_ports(ovsbr):
"""Get the ports of a OVS bridge.
In some distributions, like Fedora, the files bridge and brif are
not created under /sys/class/net/<ovsbridge> for OVS bridges.
These specific functions allows one to differentiate OVS bridges
from other types of bridges.
Args:
ovsbr (str): name of the OVS bridge
Returns:
List[str]: a list with the ports of this bridge.
"""
if not is_openvswitch_running():
return []
ovs_cmd = find_executable("ovs-vsctl")
# openvswitch not installed: there is no OVS bridge configured
if ovs_cmd is None:
return []
out, _, r_code = run_command([ovs_cmd, 'list-ports', ovsbr], silent=True)
if r_code != 0:
return []
return [x.strip() for x in out.rstrip('\n').split('\n') if x.strip()]
def all_interfaces():
"""Returns all interfaces of the host.
Returns:
List[str]: a list with all interfaces of the host.
"""
return [d.rsplit("/", 1)[-1] for d in glob.glob(NET_PATH + '/*')]
def slaves(bonding):
"""Get all slaves from a bonding.
Args:
bonding (str): the name of the bond.
Returns:
List[str]: a list with all slaves.
"""
with open(BONDING_SLAVES % bonding) as bonding_file:
res = bonding_file.readline().split()
return res
def ports(bridge):
"""Get all ports from a bridge.
Args:
bridge (str): the name of the OVS bridge.
Returns:
List[str]: a list with all ports.
"""
if bridge in ovs_bridges():
return ovs_bridge_ports(bridge)
return os.listdir(BRIDGE_PORTS % bridge)
def is_brport(nic):
"""Checks if nic is a port of a bridge.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a port of a bridge, False otherwise.
"""
ovs_brports = []
for ovsbr in ovs_bridges():
ovs_brports += ovs_bridge_ports(ovsbr)
return os.path.exists(NET_BRPORT % nic) or nic in ovs_brports
def is_bondlave(nic):
"""Checks if nic is a bond slave.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a bond slave, False otherwise.
"""
return os.path.exists(NET_MASTER % nic)
def operstate(dev):
"""Get the operstate status of a device.
Args:
dev (str): name of the device.
Returns:
str: "up" or "down"
"""
flags = ethtool.get_flags(encode_value(dev))
return 'up' if flags & (ethtool.IFF_RUNNING | ethtool.IFF_UP) else 'down'
def get_vlan_device(vlan):
""" Return the device of the given VLAN.
Args:
vlan (str): the vlan name.
Returns:
str: the device of the VLAN.
"""
dev = None
if os.path.exists(PROC_NET_VLAN + vlan):
with open(PROC_NET_VLAN + vlan) as vlan_file:
for line in vlan_file:
if "Device:" in line:
dummy, dev = line.split()
break
return dev
def get_bridge_port_device(bridge):
"""Return the nics list that belongs to a port of 'bridge'.
Args:
bridge (str): the bridge name.
Returns:
List[str]: the nic list.
"""
# br --- v --- bond --- nic1
if encode_value(bridge) not in map(encode_value, bridges()):
raise ValueError('unknown bridge %s' % bridge)
nics_list = []
for port in ports(bridge):
if encode_value(port) in map(encode_value, vlans()):
device = get_vlan_device(port)
if encode_value(device) in map(encode_value, bondings()):
nics_list.extend(slaves(device))
else:
nics_list.append(device)
if encode_value(port) in map(encode_value, bondings()):
nics_list.extend(slaves(port))
else:
nics_list.append(port)
return nics_list
def aggregated_bridges():
"""Get the list of aggregated bridges of the host.
Returns:
List[str]: the aggregated bridges list.
"""
return [bridge for bridge in bridges() if
(set(get_bridge_port_device(bridge)) & set(nics()))]
def bare_nics():
"""Get the list of bare nics of the host.
A nic is called bare when it is not a port of a bridge
or a slave of bond.
Returns:
List[str]: the list of bare nics of the host.
"""
return [nic for nic in nics() if not (is_brport(nic) or is_bondlave(nic))]
def is_bare_nic(iface):
"""Checks if iface is a bare nic.
Args:
iface (str): name of the interface.
Returns:
bool: True if iface is a bare nic, False otherwise.
"""
return encode_value(iface) in map(encode_value, bare_nics())
# The nic will not be exposed when it is a port of a bridge or
# a slave of bond.
# The bridge will not be exposed when all it's port are tap.
def all_favored_interfaces():
"""Get the list of all favored interfaces of the host.
The nic will not be exposed when it is a port of a bridge or
a slave of bond. The bridge will not be exposed when all its
port are tap.
Returns:
List[str]: the list of favored interfaces.
"""
return aggregated_bridges() + bare_nics() + bondings()
def get_interface_type(iface):
"""Get the interface type of iface.
Types supported: nic, bonding, bridge, vlan. If the type
can't be verified, 'unknown' is returned.
Args:
iface (str): the interface name.
Returns:
str: the interface type.
"""
try:
if is_nic(iface):
return "nic"
if is_bonding(iface):
return "bonding"
if is_bridge(iface):
return "bridge"
if is_vlan(iface):
return "vlan"
return 'unknown'
except IOError:
return 'unknown'
def get_dev_macaddr(dev):
info = ethtool.get_interfaces_info(dev)[0]
return info.mac_address
def get_dev_netaddr(dev):
info = ethtool.get_interfaces_info(dev)[0]
return (info.ipv4_address and
"%s/%s" % (info.ipv4_address, info.ipv4_netmask) or '')
def get_dev_netaddrs():
nets = []
for dev in ethtool.get_devices():
devnet = get_dev_netaddr(dev)
devnet and nets.append(ipaddr.IPNetwork(devnet))
return nets
# used_nets should include all the subnet allocated in libvirt network
# will get host network by get_dev_netaddrs
def get_one_free_network(used_nets, nets_pool=None):
if nets_pool is None:
nets_pool = PrivateNets
def _get_free_network(nets, used_nets):
for net in nets.subnet(new_prefix=24):
if not any(net.overlaps(used) for used in used_nets):
return str(net)
return None
used_nets = used_nets + get_dev_netaddrs()
for nets in nets_pool:
net = _get_free_network(nets, used_nets)
if net:
return net
return None