]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/lib/iproute2.py
addons: vxlan: vxlan-vni: support vni change on existing config
[mirror_ifupdown2.git] / ifupdown2 / lib / iproute2.py
1 # Copyright (C) 2017, 2018, 2019 Cumulus Networks, Inc. all rights reserved
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License as
5 # published by the Free Software Foundation; version 2.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 # 02110-1301, USA.
16 #
17 # https://www.gnu.org/licenses/gpl-2.0-standalone.html
18 #
19 # Author:
20 # Julien Fortin, julien@cumulusnetworks.com
21 #
22 # iproute2 -- contains all iproute2 related operation
23 #
24
25 import re
26 import shlex
27 import signal
28 import ipaddress
29 import subprocess
30 import json
31
32 try:
33 from ifupdown2.lib.sysfs import Sysfs
34 from ifupdown2.lib.base_objects import Cache, Requirements
35
36 import ifupdown2.nlmanager.ipnetwork as ipnetwork
37
38 from ifupdown2.ifupdown.utils import utils
39 from ifupdown2.ifupdown.iface import ifaceLinkPrivFlags
40 from ifupdown2.nlmanager.nlpacket import Link
41 except (ImportError, ModuleNotFoundError):
42 from lib.sysfs import Sysfs
43 from lib.base_objects import Cache, Requirements
44
45 import nlmanager.ipnetwork as ipnetwork
46
47 from ifupdown.utils import utils
48 from ifupdown.iface import ifaceLinkPrivFlags
49 from nlmanager.nlpacket import Link
50
51 # WORK AROUND - Tunnel creation should be done via netlink and not iproute2 ####
52 import struct #
53 import socket #
54 #
55 try: #
56 import ifupdown2.nlmanager.nlpacket as nlpacket #
57 except Exception: #
58 import nlmanager.nlpacket as nlpacket #
59 ################################################################################
60
61
62 class IPRoute2(Cache, Requirements):
63
64 VXLAN_UDP_PORT = 4789
65 VXLAN_PEER_REGEX_PATTERN = re.compile("\s+dst\s+(\d+.\d+.\d+.\d+)\s+")
66
67 def __init__(self):
68 Cache.__init__(self)
69 Requirements.__init__(self)
70
71 self.sysfs = Sysfs
72
73 self.__batch = {}
74 self.__batch_mode = False
75
76 # if bridge utils is not installed overrrides specific functions to
77 # avoid constantly checking bridge_utils_is_installed
78 if not Requirements.bridge_utils_is_installed:
79 self.bridge_set_stp = lambda _, __: None
80 self.bridge_del_mcqv4src = lambda _, __: None
81 self.bridge_set_mcqv4src = lambda _, __, ___: None
82
83 ############################################################################
84 # WORK-AROUND
85 ############################################################################
86
87 def __update_cache_after_link_creation(self, ifname, kind):
88 """
89 WORK AROUND - when creating tunnel via iproute2 we still need to fill
90 our internal cache to keep track of this interface until we receive the
91 NEWLINK notification. This code is a copy-paste from:
92 nlcache.tx_nlpacket_get_response_with_error_and_cache_on_ack
93
94 :param ifname:
95 :param kind:
96 :return:
97 """
98 packet = nlpacket.Link(nlpacket.RTM_NEWLINK, False, use_color=False)
99 packet.flags = nlpacket.NLM_F_CREATE | nlpacket.NLM_F_REQUEST | nlpacket.NLM_F_ACK
100 packet.body = struct.pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0)
101 packet.add_attribute(nlpacket.Link.IFLA_IFNAME, ifname)
102 packet.add_attribute(nlpacket.Link.IFLA_LINKINFO, {
103 nlpacket.Link.IFLA_INFO_KIND: kind,
104 nlpacket.Link.IFLA_INFO_DATA: {}
105 })
106 packet.build_message(0, 0)
107 # When creating a new link via netlink, we don't always wait for the kernel
108 # NEWLINK notification to be cached to continue. If our request is ACKed by
109 # the OS we assume that the link was successfully created. Since we aren't
110 # waiting for the kernel notification to continue we need to manually fill
111 # our cache with the packet we just TX'ed. Once the NEWLINK notification
112 # is received it will simply override the previous entry.
113 # We need to keep track of those manually cached packets. We set a private
114 # flag on the objects via the attribute priv_flags
115 packet.priv_flags |= nlpacket.NLM_F_REQUEST
116 try:
117 # we need to decode the service header so all the attribute are properly
118 # filled in the packet object that we are about to store in cache.
119 # i.e.: packet.flags shouldn't contain NLM_F_* values but IFF_* (in case of Link object)
120 # otherwise call to cache.link_is_up() will probably return True
121 packet.decode_service_header()
122 except Exception:
123 # we can ignore all errors
124 pass
125
126 # Then we can use our normal "add_link" API call to cache the packet
127 # and fill up our additional internal data structures.
128 self.cache.add_link(packet)
129
130 ############################################################################
131 # BATCH
132 ############################################################################
133
134 def __add_to_batch(self, prefix, cmd):
135 if prefix in self.__batch:
136 self.__batch[prefix].append(cmd)
137 else:
138 self.__batch[prefix] = [cmd]
139
140 def __execute_or_batch(self, prefix, cmd):
141 if self.__batch_mode:
142 self.__add_to_batch(prefix, cmd)
143 else:
144 utils.exec_command("%s %s" % (prefix, cmd))
145
146 def __execute_or_batch_dry_run(self, prefix, cmd):
147 """
148 The batch function has it's own dryrun handler so we only handle
149 dryrun for non-batch mode. Which will be removed once the "utils"
150 module has it's own dryrun handlers
151 """
152 if self.__batch_mode:
153 self.__add_to_batch(prefix, cmd)
154 else:
155 self.log_info_dry_run("executing: %s %s" % (prefix, cmd))
156
157 def batch_start(self):
158 if not self.__batch_mode:
159 self.__batch_mode = True
160 self.__batch = {}
161
162 def batch_commit(self):
163 try:
164 if not self.__batch_mode or not self.__batch:
165 return
166 for prefix, commands in self.__batch.items():
167 utils.exec_command(
168 "%s -force -batch -" % prefix,
169 stdin="\n".join(commands)
170 )
171 except Exception:
172 raise
173 finally:
174 self.__batch_mode = False
175 del self.__batch
176 self.__batch = None
177
178 ############################################################################
179 # LINK
180 ############################################################################
181
182 def link_up(self, ifname):
183 # TODO: if we already in a batch we shouldn't check the cache as the link might be DOWN during the batch
184 if not self.cache.link_is_up(ifname):
185 self.link_up_force(ifname)
186
187 def link_down(self, ifname):
188 if self.cache.link_is_up(ifname):
189 self.link_down_force(ifname)
190
191 def link_up_dry_run(self, ifname):
192 self.link_up_force(ifname)
193
194 def link_down_dry_run(self, ifname):
195 self.link_down_force(ifname)
196
197 def link_up_force(self, ifname):
198 self.__execute_or_batch(utils.ip_cmd, "link set dev %s up" % ifname)
199
200 def link_down_force(self, ifname):
201 self.__execute_or_batch(utils.ip_cmd, "link set dev %s down" % ifname)
202
203 ###
204
205 def link_set_master(self, ifname, master):
206 if master != self.cache.get_master(ifname):
207 self.__execute_or_batch(
208 utils.ip_cmd,
209 "link set dev %s master %s" % (ifname, master)
210 )
211
212 def link_set_master_dry_run(self, ifname, master):
213 self.__execute_or_batch(
214 utils.ip_cmd,
215 "link set dev %s master %s" % (ifname, master)
216 )
217
218 ###
219
220 def link_set_address(self, ifname, address):
221 if utils.mac_str_to_int(address) != self.cache.get_link_address_raw(ifname):
222 self.link_down(ifname)
223 self.__execute_or_batch(
224 utils.ip_cmd,
225 "link set dev %s address %s" % (ifname, address)
226 )
227 self.link_up(ifname)
228
229 def link_set_address_dry_run(self, ifname, address):
230 self.link_down(ifname)
231 self.__execute_or_batch(
232 utils.ip_cmd,
233 "link set dev %s address %s" % (ifname, address)
234 )
235 self.link_up(ifname)
236
237 def link_set_address_and_keep_down(self, ifname, address, keep_down=False):
238 if utils.mac_str_to_int(address) != self.cache.get_link_address_raw(ifname):
239
240 self.link_down(ifname)
241 self.__execute_or_batch(
242 utils.ip_cmd,
243 "link set dev %s address %s" % (ifname, address)
244 )
245 if not keep_down:
246 self.link_up_force(ifname)
247
248 def link_set_address_and_keep_down_dry_run(self, ifname, address, keep_down=False):
249 self.link_down(ifname)
250 self.__execute_or_batch(
251 utils.ip_cmd,
252 "link set dev %s address %s" % (ifname, address)
253 )
254 if not keep_down:
255 self.link_up(ifname)
256
257 ###
258
259 def link_add_macvlan(self, ifname, macvlan_ifname, macvlan_mode):
260 utils.exec_command(
261 "%s link add link %s name %s type macvlan mode %s"
262 % (utils.ip_cmd, ifname, macvlan_ifname, macvlan_mode)
263 )
264
265 def link_add_macvlan_dry_run(self, ifname, macvlan_ifname, macvlan_mode):
266 # this dryrun method can be removed once dryrun handlers
267 # are added to the utils module
268 self.log_info_ifname_dry_run(ifname, "executing %s link add link %s name %s type macvlan mode %s"
269 % (utils.ip_cmd, ifname, macvlan_ifname, macvlan_mode)
270 )
271
272 ###
273
274 def link_add_veth(self, ifname, peer_name):
275 utils.exec_command(
276 "%s link add %s type veth peer name %s"
277 % (utils.ip_cmd, ifname, peer_name)
278 )
279
280 ###
281
282 def link_add_single_vxlan(self, link_exists, ifname, ip, group, physdev, port, vnifilter="off", ttl=None):
283 self.logger.info("creating single vxlan device: %s" % ifname)
284
285 if link_exists:
286 # When updating an SVD we need to use `ip link set` and we have to
287 # drop the external keyword:
288 # $ ip link set dev vxlan0 type vxlan external local 27.0.0.242 dev ipmr-lo
289 # Error: vxlan: cannot change COLLECT_METADATA flag.
290 cmd = ["link set dev %s type vxlan" % ifname]
291 else:
292 cmd = ["link add dev %s type vxlan external" % ifname]
293
294 # when changing local ip, if we specify vnifilter we get:
295 # Error: vxlan: cannot change flag.
296 # So we are only setting this attribute on vxlan creation
297 if vnifilter and utils.get_boolean_from_string(vnifilter):
298 cmd.append("vnifilter")
299
300 if ip:
301 cmd.append("local %s" % ip)
302
303 if physdev:
304 cmd.append("dev %s" % physdev)
305
306 if group:
307 cmd.append("group %s" % group)
308
309 if port:
310 cmd.append("dstport %s" % port)
311
312 if ttl:
313 cmd.append("ttl %s" % ttl)
314
315 self.__execute_or_batch(utils.ip_cmd, " ".join(cmd))
316 self.__update_cache_after_link_creation(ifname, "vxlan")
317
318 def link_add_l3vxi(self, link_exists, ifname, ip, group, physdev, port, ttl=None):
319 self.logger.info("creating l3vxi device: %s" % ifname)
320
321 if link_exists:
322 # When updating an SVD we need to use `ip link set` and we have to
323 # drop the external keyword:
324 # $ ip link set dev vxlan0 type vxlan external local 27.0.0.242 dev ipmr-lo
325 # Error: vxlan: cannot change COLLECT_METADATA flag.
326 cmd = ["link set dev %s type vxlan" % ifname]
327 else:
328 cmd = ["link add dev %s type vxlan external vnifilter" % ifname]
329 # when changing local ip, if we specify vnifilter we get:
330 # Error: vxlan: cannot change flag.
331 # So we are only setting this attribute on vxlan creation
332
333 if ip:
334 cmd.append("local %s" % ip)
335
336 if physdev:
337 cmd.append("dev %s" % physdev)
338
339 if group:
340 cmd.append("group %s" % group)
341
342 if port:
343 cmd.append("dstport %s" % port)
344
345 if ttl:
346 cmd.append("ttl %s" % ttl)
347
348 self.__execute_or_batch(utils.ip_cmd, " ".join(cmd))
349 self.__update_cache_after_link_creation(ifname, "vxlan")
350
351 def link_create_vxlan(self, name, vxlanid, localtunnelip=None, svcnodeip=None,
352 remoteips=None, learning='on', ageing=None, ttl=None, physdev=None, udp_csum='on', tos = None):
353 if svcnodeip and remoteips:
354 raise Exception("svcnodeip and remoteip are mutually exclusive")
355
356 if self.cache.link_exists(name):
357 cmd = [
358 "link set dev %s type vxlan dstport %d"
359 % (name, self.VXLAN_UDP_PORT)
360 ]
361 else:
362 cmd = [
363 "link add dev %s type vxlan id %s dstport %d"
364 % (name, vxlanid, self.VXLAN_UDP_PORT)
365 ]
366
367 if svcnodeip:
368 if svcnodeip.ip.is_multicast:
369 cmd.append("group %s" % svcnodeip)
370 else:
371 cmd.append("remote %s" % svcnodeip)
372
373 if ageing:
374 cmd.append("ageing %s" % ageing)
375
376 if learning == 'off':
377 cmd.append("nolearning")
378
379 if udp_csum == 'off':
380 cmd.append("noudpcsum")
381
382 if ttl is not None:
383 cmd.append("ttl %s" % ttl)
384
385 if tos is not None:
386 cmd.append("tos %s" % tos)
387
388 if physdev:
389 cmd.append("dev %s" % physdev)
390
391 if localtunnelip:
392 cmd.append("local %s" % localtunnelip)
393
394 self.__execute_or_batch(utils.ip_cmd, " ".join(cmd))
395
396 def get_vxlan_peers(self, dev, svcnodeip):
397 cmd = "%s fdb show brport %s" % (utils.bridge_cmd, dev)
398 cur_peers = []
399 try:
400 ps = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, close_fds=False)
401 utils.enable_subprocess_signal_forwarding(ps, signal.SIGINT)
402 output = subprocess.check_output(("grep", "00:00:00:00:00:00"), stdin=ps.stdout).decode()
403 ps.wait()
404 utils.disable_subprocess_signal_forwarding(signal.SIGINT)
405 try:
406 for l in output.split('\n'):
407 if "src_vni" in l:
408 continue
409 m = self.VXLAN_PEER_REGEX_PATTERN.search(l)
410 if m and m.group(1) != svcnodeip:
411 cur_peers.append(m.group(1))
412 except Exception:
413 self.logger.warning('error parsing ip link output')
414 except subprocess.CalledProcessError as e:
415 if e.returncode != 1:
416 self.logger.error(str(e))
417 finally:
418 utils.disable_subprocess_signal_forwarding(signal.SIGINT)
419 return cur_peers
420
421 ###
422
423 def link_add_xfrm(self, ifname, xfrm_name, xfrm_id):
424 utils.exec_commandl(['ip', 'link', 'add', xfrm_name, 'type', 'xfrm', 'dev', ifname, 'if_id', xfrm_id])
425 self.__update_cache_after_link_creation(xfrm_name, "xfrm")
426
427 def link_add_openvswitch(self, ifname, kind):
428 self.__update_cache_after_link_creation(ifname, kind)
429
430 ############################################################################
431 # TUNNEL
432 ############################################################################
433
434 def tunnel_create(self, tunnelname, mode, attrs=None):
435 if self.cache.link_exists(tunnelname):
436 return
437
438 cmd = []
439 if "6" in mode:
440 cmd.append("-6")
441
442 if mode in ["gretap"]:
443 cmd.append("link add %s type %s" % (tunnelname, mode))
444 else:
445 cmd.append("tunnel add %s mode %s" % (tunnelname, mode))
446
447 if attrs:
448 for k, v in attrs.items():
449 cmd.append(k)
450 if v:
451 cmd.append(v)
452
453 utils.exec_command("%s %s" % (utils.ip_cmd, " ".join(cmd)))
454 self.__update_cache_after_link_creation(tunnelname, mode)
455
456 def tunnel_change(self, tunnelname, attrs=None):
457 """ tunnel change function """
458 if not self.cache.link_exists(tunnelname):
459 return
460 cmd = ["tunnel change %s" % tunnelname]
461 if attrs:
462 for k, v in attrs.items():
463 cmd.append(k)
464 if v:
465 cmd.append(v)
466 self.__execute_or_batch(utils.ip_cmd, " ".join(cmd))
467
468 ############################################################################
469 # ADDRESS
470 ############################################################################
471
472 def addr_flush(self, ifname):
473 if self.cache.link_has_ip(ifname):
474 self.__execute_or_batch(utils.ip_cmd, "addr flush dev %s" % ifname)
475
476 def link_set_ipv6_addrgen_dry_run(self, ifname, addrgen, link_created):
477 addrgen_str = "none" if addrgen else "eui64"
478 self.link_down(ifname)
479 self.__execute_or_batch(utils.ip_cmd, "link set dev %s addrgenmode %s" % (ifname, addrgen_str))
480 self.link_up(ifname)
481
482 def link_set_ipv6_addrgen(self, ifname, addrgen, link_created):
483 """
484 IFLA_INET6_ADDR_GEN_MODE values:
485 0 = eui64
486 1 = none
487
488 :param ifname:
489 :param addrgen:
490 :param link_created:
491 :return:
492 """
493 cached_ipv6_addr_gen_mode = self.cache.get_link_ipv6_addrgen_mode(ifname)
494
495 if cached_ipv6_addr_gen_mode == addrgen:
496 return True
497
498 disabled_ipv6 = self.sysfs.get_ipv6_conf_disable_ipv6(ifname)
499
500 if disabled_ipv6:
501 self.logger.info("%s: cannot set addrgen: ipv6 is disabled on this device" % ifname)
502 return False
503
504 if link_created:
505 link_mtu = self.sysfs.link_get_mtu(ifname)
506 else:
507 link_mtu = self.cache.get_link_mtu(ifname)
508
509 if link_mtu < 1280:
510 self.logger.info("%s: ipv6 addrgen is disabled on device with MTU "
511 "lower than 1280 (current mtu %s): cannot set addrgen %s"
512 % (ifname, link_mtu, "off" if addrgen else "on"))
513 return False
514
515 if not link_created:
516 # When setting addrgenmode it is necessary to flap the macvlan
517 # device. After flapping the device we also need to re-add all
518 # the user configuration. The best way to add the user config
519 # is to flush our internal address cache
520 self.cache.address_flush_link(ifname)
521
522 is_link_up = self.cache.link_is_up(ifname)
523
524 if is_link_up:
525 self.link_down_force(ifname)
526
527 self.__execute_or_batch(
528 utils.ip_cmd,
529 "link set dev %s addrgenmode %s" % (ifname, Link.ifla_inet6_addr_gen_mode_dict.get(addrgen))
530 )
531
532 if is_link_up:
533 self.link_up_force(ifname)
534
535 return True
536
537 @staticmethod
538 def __compare_user_config_vs_running_state(running_addrs, user_addrs):
539 ip4 = []
540 ip6 = []
541
542 for ip in user_addrs or []:
543 if ip.version == 6:
544 ip6.append(ip)
545 else:
546 ip4.append(ip)
547
548 running_ipobj = []
549 for ip in running_addrs or []:
550 running_ipobj.append(ip)
551
552 return running_ipobj == (ip4 + ip6)
553
554 def add_addresses(self, ifacobj, ifname, address_list, purge_existing=False, metric=None, with_address_virtual=False):
555 if purge_existing:
556 running_address_list = self.cache.get_managed_ip_addresses(
557 ifname,
558 [ifacobj],
559 with_address_virtual=with_address_virtual
560 )
561
562 if self.__compare_user_config_vs_running_state(running_address_list, address_list):
563 return
564
565 # if primary address is not same, there is no need to keep any - reset all addresses
566 if running_address_list and address_list and address_list[0] != running_address_list[0]:
567 skip = []
568 else:
569 skip = address_list
570
571 for addr in running_address_list or []:
572 try:
573 if addr in skip:
574 continue
575 self.__execute_or_batch(utils.ip_cmd, "addr del %s dev %s" % (addr, ifname))
576 except Exception as e:
577 self.logger.warning("%s: removing ip address failed: %s" % (ifname, str(e)))
578 for addr in address_list:
579 try:
580 if metric:
581 self.__execute_or_batch(utils.ip_cmd, "addr add %s dev %s metric %s" % (addr, ifname, metric))
582 else:
583 self.__execute_or_batch(utils.ip_cmd, "addr add %s dev %s" % (addr, ifname))
584 except Exception as e:
585 self.logger.error("%s: add_address: %s" % (ifname, str(e)))
586
587 ############################################################################
588 # BRIDGE
589 ############################################################################
590
591 @staticmethod
592 def bridge_set_stp(bridge, stp_state):
593 utils.exec_command("%s stp %s %s" % (utils.brctl_cmd, bridge, stp_state))
594
595 @staticmethod
596 def bridge_fdb_show_dev(dev):
597 try:
598 fdbs = {}
599 output = utils.exec_command("%s fdb show dev %s" % (utils.bridge_cmd, dev))
600 if output:
601 for fdb_entry in output.splitlines():
602 try:
603 entries = fdb_entry.split()
604 fdbs.setdefault(entries[2], []).append(entries[0])
605 except Exception:
606 pass
607 return fdbs
608 except Exception:
609 return None
610
611 @staticmethod
612 def bridge_fdb_show_dev_raw_with_filters(dev, filters):
613 try:
614 output = utils.exec_command("%s fdb show dev %s" % (utils.bridge_cmd, dev)).splitlines()
615 filtered_output = []
616 for l in output:
617 filter_present = True
618 for f in filters:
619 if f not in l:
620 filter_present = False
621 if filter_present:
622 filtered_output.append(l)
623 return filtered_output
624 except Exception:
625 return None
626
627 @staticmethod
628 def bridge_fdb_add(dev, address, vlan=None, bridge=True, remote=None):
629 target = "self" if bridge else ""
630 vlan_str = "vlan %s " % vlan if vlan else ""
631 dst_str = "dst %s " % remote if remote else ""
632
633 utils.exec_command(
634 "%s fdb replace %s dev %s %s %s %s"
635 % (
636 utils.bridge_cmd,
637 address,
638 dev,
639 vlan_str,
640 target,
641 dst_str
642 )
643 )
644
645 @staticmethod
646 def bridge_fdb_add_src_vni(dev, src_vni, dst_ip):
647 """
648 bridge fdb add dev $dev 00:00:00:00:00:00 src_vni $src_vni dst $dst_ip static self
649 """
650 utils.exec_command(
651 "%s fdb add dev %s 00:00:00:00:00:00 src_vni %s dst %s permanent self"
652 % (
653 utils.bridge_cmd,
654 dev,
655 src_vni,
656 dst_ip
657 )
658 )
659
660 @staticmethod
661 def bridge_fdb_append(dev, address, vlan=None, bridge=True, remote=None, src_vni=None):
662 cmd = ["%s fdb append %s dev %s" % (utils.bridge_cmd, address, dev)]
663
664 if bridge:
665 cmd.append("self")
666
667 if vlan:
668 cmd.append("vlan %s" % vlan)
669
670 if remote:
671 cmd.append("dst %s" % remote)
672
673 if src_vni:
674 cmd.append("src_vni %s" % src_vni)
675
676 utils.exec_command(" ".join(cmd))
677
678 @staticmethod
679 def bridge_fdb_del_src_vni(dev, mac, src_vni):
680 utils.exec_command(
681 "%s fdb del %s dev %s src_vni %s"
682 % (
683 utils.bridge_cmd,
684 mac,
685 dev,
686 src_vni
687 )
688 )
689
690 @staticmethod
691 def bridge_fdb_del(dev, address, vlan=None, bridge=True, remote=None):
692 target = "self" if bridge else ""
693 vlan_str = "vlan %s " % vlan if vlan else ""
694 dst_str = "dst %s " % remote if remote else ""
695
696 utils.exec_command(
697 "%s fdb del %s dev %s %s %s %s"
698 % (
699 utils.bridge_cmd,
700 address,
701 dev,
702 vlan_str,
703 target,
704 dst_str
705 )
706 )
707
708 @staticmethod
709 def bridge_fdb_del_raw(dev, args):
710 utils.exec_command("%s fdb del dev %s %s" % (utils.bridge_cmd, dev, args))
711
712 @staticmethod
713 def bridge_vlan_del_vid_list(ifname, vids):
714 if not vids:
715 return
716 for v in vids:
717 utils.exec_command(
718 "%s vlan del vid %s dev %s" % (utils.bridge_cmd, v, ifname)
719 )
720
721 def bridge_vlan_del_vid_list_self(self, ifname, vids, is_bridge=True):
722 target = "self" if is_bridge else ""
723 for v in vids:
724 self.__execute_or_batch(
725 utils.bridge_cmd,
726 "vlan del vid %s dev %s %s" % (v, ifname, target)
727 )
728
729 def bridge_vlan_del_vlan_tunnel_info(self, ifname, vids, vnis):
730 self.__execute_or_batch(
731 utils.bridge_cmd,
732 "vlan del dev %s vid %s tunnel_info id %s" % (
733 ifname, vids, vnis
734 )
735 )
736
737 def bridge_vlan_add_vlan_tunnel_info(self, ifname, vids, vnis):
738 try:
739 self.__execute_or_batch(
740 utils.bridge_cmd,
741 "vlan add dev %s vid %s tunnel_info id %s" % (
742 ifname, vids, vnis
743 )
744 )
745 except Exception as e:
746 if "exists" not in str(e).lower():
747 self.logger.error(e)
748
749 def bridge_vlan_tunnel_show(self, ifname):
750 tunnel_info = {}
751 try:
752 for entry in utils.exec_command("%s vlan tunnel dev %s" % (utils.bridge_cmd, ifname)).splitlines()[1:]:
753
754 if not entry:
755 continue
756
757 entry_list = entry.split()
758 length = len(entry_list)
759
760 if length > 2:
761 # if len == 3, we need to remove the ifname from the list
762 # $ bridge vlan tunnel show dev vxlan42
763 # port vlan ids tunnel id
764 # vxlan42 1042 1542
765 entry_list = entry_list[1:]
766
767 if length < 2:
768 continue
769
770 vnis = utils.ranges_to_ints([entry_list[0]])
771 tunnel_ids = utils.ranges_to_ints([entry_list[1]])
772
773 for vni, tunnel_id in zip(vnis, tunnel_ids):
774 tunnel_info[int(vni)] = int(tunnel_id)
775
776 except Exception as e:
777 self.logger.debug("iproute2: bridge vlan tunnel dev %s: %s" % (ifname, str(e)))
778 return tunnel_info
779
780 @staticmethod
781 def bridge_vlan_add_vid_list(ifname, vids):
782 for v in vids:
783 utils.exec_command(
784 "%s vlan add vid %s dev %s" % (utils.bridge_cmd, v, ifname)
785 )
786
787 def bridge_vlan_add_vid_list_self(self, ifname, vids, is_bridge=True):
788 target = "self" if is_bridge else ""
789 for v in vids:
790 self.__execute_or_batch(
791 utils.bridge_cmd,
792 "vlan add vid %s dev %s %s" % (v, ifname, target)
793 )
794
795 def bridge_vlan_del_vid_list_self(self, ifname, vids, is_bridge=True):
796 target = "self" if is_bridge else ""
797 for v in vids:
798 self.__execute_or_batch(
799 utils.bridge_cmd,
800 "vlan del vid %s dev %s %s" % (v, ifname, target)
801 )
802
803 def bridge_vlan_del_pvid(self, ifname, pvid):
804 self.__execute_or_batch(
805 utils.bridge_cmd,
806 "vlan del vid %s untagged pvid dev %s" % (pvid, ifname)
807 )
808
809 def bridge_vlan_add_pvid(self, ifname, pvid):
810 self.__execute_or_batch(
811 utils.bridge_cmd,
812 "vlan add vid %s untagged pvid dev %s" % (pvid, ifname)
813 )
814
815 def bridge_del_mcqv4src(self, bridge, vlan):
816 try:
817 vlan = int(vlan)
818 except Exception as e:
819 self.logger.info("%s: del mcqv4src vlan: invalid parameter %s: %s"
820 % (bridge, vlan, str(e)))
821 return
822 utils.exec_command("%s delmcqv4src %s %d" % (utils.brctl_cmd, bridge, vlan))
823
824 def bridge_set_mcqv4src(self, bridge, vlan, mcquerier):
825 try:
826 vlan = int(vlan)
827 except Exception as e:
828 self.logger.info("%s: set mcqv4src vlan: invalid parameter %s: %s" % (bridge, vlan, str(e)))
829 return
830 if vlan == 0 or vlan > 4095:
831 self.logger.warning("mcqv4src vlan '%d' invalid range" % vlan)
832 return
833
834 ip = mcquerier.split(".")
835 if len(ip) != 4:
836 self.logger.warning("mcqv4src '%s' invalid IPv4 address" % mcquerier)
837 return
838 for k in ip:
839 if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255:
840 self.logger.warning("mcqv4src '%s' invalid IPv4 address" % mcquerier)
841 return
842
843 utils.exec_command("%s setmcqv4src %s %d %s" % (utils.brctl_cmd, bridge, vlan, mcquerier))
844
845 ############################################################################
846 # ROUTE
847 ############################################################################
848
849 @staticmethod
850 def route_add_gateway(ifname, gateway, vrf=None, metric=None, onlink=True):
851 if not gateway:
852 return
853
854 if not vrf:
855 cmd = "%s route replace default via %s proto kernel" % (utils.ip_cmd, gateway)
856 else:
857 cmd = "%s route replace table %s default via %s proto kernel" % (utils.ip_cmd, vrf, gateway)
858
859 if metric:
860 cmd += " metric %s" % metric
861
862 cmd += " dev %s" % ifname
863
864 if onlink:
865 cmd += " onlink"
866
867 utils.exec_command(cmd)
868
869 @staticmethod
870 def route_del_gateway(ifname, gateway, vrf=None, metric=None):
871 """
872 delete default gw
873 we don't need a DRYRUN handler here as utils.exec_command should have one
874 """
875 if not gateway:
876 return
877
878 if not vrf:
879 cmd = "%s route del default via %s proto kernel" % (utils.ip_cmd, gateway)
880 else:
881 cmd = "%s route del table %s default via %s proto kernel" % (utils.ip_cmd, vrf, gateway)
882
883 if metric:
884 cmd += " metric %s" % metric
885
886 cmd += " dev %s" % ifname
887 utils.exec_command(cmd)
888
889 def fix_ipv6_route_metric(self, ifaceobj, macvlan_ifacename, ips):
890 vrf_table = None
891
892 if ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE:
893 try:
894 for upper_iface in ifaceobj.upperifaces:
895 vrf_table = self.cache.get_vrf_table(upper_iface)
896 if vrf_table:
897 break
898 except Exception:
899 pass
900
901 ip_route_del = []
902 for ip in ips:
903 ip_network_obj = ipaddress.ip_network(ip)
904
905 if ip_network_obj.version == 6:
906 route_prefix = '%s/%d' % (ip_network_obj.network, ip_network_obj.prefixlen)
907
908 if vrf_table:
909 self.__execute_or_batch(
910 utils.ip_cmd,
911 "route del %s table %s dev %s" % (route_prefix, vrf_table, macvlan_ifacename)
912 )
913 else:
914 self.__execute_or_batch(
915 utils.ip_cmd,
916 "route del %s dev %s" % (route_prefix, macvlan_ifacename)
917 )
918
919 ip_route_del.append((route_prefix, vrf_table))
920
921 for ip, vrf_table in ip_route_del:
922 if vrf_table:
923 self.__execute_or_batch(
924 utils.ip_cmd,
925 "route add %s table %s dev %s proto kernel metric 9999" % (ip, vrf_table, macvlan_ifacename)
926 )
927 else:
928 self.__execute_or_batch(
929 utils.ip_cmd,
930 "route add %s dev %s proto kernel metric 9999" % (ip, macvlan_ifacename)
931 )
932
933 def ip_route_get_dev(self, prefix, vrf_master=None):
934 try:
935 if vrf_master:
936 cmd = "%s route get %s vrf %s" % (utils.ip_cmd, prefix, vrf_master)
937 else:
938 cmd = "%s route get %s" % (utils.ip_cmd, prefix)
939
940 output = utils.exec_command(cmd)
941 if output:
942 rline = output.splitlines()[0]
943 if rline:
944 rattrs = rline.split()
945 return rattrs[rattrs.index("dev") + 1]
946 except Exception as e:
947 self.logger.debug("ip_route_get_dev: failed .. %s" % str(e))
948 return None
949
950 def bridge_vni_update(self, vxlandev, vnisd):
951 for vr, g in vnisd.items():
952 cmd_args = "vni add dev %s vni %s" % (vxlandev, vr)
953 if g:
954 cmd_args += ' group %s' %(g)
955 self.__execute_or_batch(utils.bridge_cmd, cmd_args)
956
957 def bridge_vni_add(self, vxlan_device, vni):
958 # bridge vni add understands ranges:
959 # bridge vni add dev vx0 vni 10,11,20-30
960 self.__execute_or_batch(
961 utils.bridge_cmd,
962 "vni add dev %s vni %s" % (vxlan_device, ','.join(vni.split()))
963 )
964
965 def bridge_vni_int_set_del(self, vxlan_device, vni):
966 # bridge vni del understands ranges:
967 # bridge vni del dev vx0 vni 10,11,20-30
968 self.__execute_or_batch(
969 utils.bridge_cmd,
970 "vni del dev %s vni %s" % (vxlan_device, ','.join([str(x) for x in vni]))
971 )
972
973 def bridge_vni_del_list(self, vxlandev, vnis):
974 cmd_args = "vni del dev %s vni %s" % (vxlandev, ','.join(vnis))
975 self.__execute_or_batch(utils.bridge_cmd, cmd_args)
976
977 def compress_vnifilter_into_ranges(self, vnis_ints, vnisd):
978 vbegin = 0
979 vend = 0
980 vnisd_ranges = {}
981 for v, g in vnisd.items():
982 if v not in vnis_ints:
983 continue
984 if vbegin == 0:
985 vbegin = v
986 vend = v
987 lastg = g
988 continue
989 elif ((v - vend) == 1 and g == lastg):
990 vend = v
991 continue
992 else:
993 if vend > vbegin:
994 range = '%d-%d' %(vbegin, vend)
995 vnisd_ranges[range] = lastg
996 else:
997 vnisd_ranges['%s' %vbegin] = lastg
998 vbegin = v
999 vend = v
1000 lastg = g
1001
1002 if vbegin:
1003 if vend > vbegin:
1004 range = '%d-%d' %(vbegin, vend)
1005 vnisd_ranges[range] = lastg
1006 else:
1007 vnisd_ranges['%s' %vbegin] = lastg
1008 return vnisd_ranges
1009
1010 def print_data(self, lprefix, data):
1011 self.logger.info(lprefix)
1012 self.logger.info(data)
1013
1014 def bridge_link_update_vni_filter(self, vxlandev, vnisd):
1015 try:
1016 rvnisd = {}
1017 cmd = 'bridge -j -p vni show dev %s' %( vxlandev )
1018 output = utils.exec_command(cmd)
1019 if output:
1020 vnishow = json.loads(output.strip("\n"))
1021 self.logger.debug(vnishow)
1022 for s in vnishow:
1023 vlist = s.get('vnis')
1024 for v in vlist:
1025 vstart = v.get('vni')
1026 vend = v.get('vniEnd')
1027 group = v.get('group')
1028 if vend:
1029 for tv in range(int(vstart), int(vend)+1):
1030 if group:
1031 rvnisd[tv] = group
1032 else:
1033 rvnisd[tv] = None
1034 else:
1035 if group:
1036 rvnisd[int(vstart)] = group
1037 else:
1038 rvnisd[int(vstart)] = None
1039 vnis_int = vnisd.keys()
1040 rvnis_int = rvnisd.keys()
1041
1042 (vnis_to_del, vnis_to_add) = utils.diff_ids(vnis_int,
1043 rvnis_int)
1044 self.batch_start()
1045 if vnis_to_del:
1046 self.bridge_vni_del_list(vxlandev,
1047 utils.compress_into_ranges(vnis_to_del))
1048 if vnis_to_add:
1049 self.bridge_vni_update(vxlandev,
1050 self.compress_vnifilter_into_ranges(vnis_to_add, vnisd))
1051
1052 # Do any vnis need group update ?
1053 # check remaining vnis
1054 vnis_rem = set(vnis_int)
1055 if vnis_to_add:
1056 vnis_rem = vnis_rem.difference(set(vnis_to_add))
1057 if vnis_to_del:
1058 vnis_rem = vnis_rem.difference(set(vnis_to_del))
1059 vnis_rem = list(vnis_rem)
1060 vnis_to_update = []
1061 if vnis_rem:
1062 for v in vnis_rem:
1063 # check if group is not same
1064 if vnisd.get(v) != rvnisd.get(v):
1065 vnis_to_update.append(v)
1066 if vnis_to_update:
1067 self.bridge_vni_update(vxlandev,
1068 self.compress_vnifilter_into_ranges(vnis_to_update, vnisd))
1069 self.batch_commit()
1070 except Exception as e:
1071 self.logger.error("bridge vni show failed .. %s" % str(e))
1072 return None