1 # Copyright (C) 2017, 2018, 2019 Cumulus Networks, Inc. all rights reserved
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.
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.
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
17 # https://www.gnu.org/licenses/gpl-2.0-standalone.html
20 # Julien Fortin, julien@cumulusnetworks.com
22 # iproute2 -- contains all iproute2 related operation
33 from ifupdown2
.lib
.sysfs
import Sysfs
34 from ifupdown2
.lib
.base_objects
import Cache
, Requirements
36 import ifupdown2
.nlmanager
.ipnetwork
as ipnetwork
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
45 import nlmanager
.ipnetwork
as ipnetwork
47 from ifupdown
.utils
import utils
48 from ifupdown
.iface
import ifaceLinkPrivFlags
49 from nlmanager
.nlpacket
import Link
51 # WORK AROUND - Tunnel creation should be done via netlink and not iproute2 ####
56 import ifupdown2
.nlmanager
.nlpacket
as nlpacket
#
58 import nlmanager
.nlpacket
as nlpacket
#
59 ################################################################################
62 class IPRoute2(Cache
, Requirements
):
65 VXLAN_PEER_REGEX_PATTERN
= re
.compile("\s+dst\s+(\d+.\d+.\d+.\d+)\s+")
69 Requirements
.__init
__(self
)
74 self
.__batch
_mode
= False
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
83 ############################################################################
85 ############################################################################
87 def __update_cache_after_link_creation(self
, ifname
, kind
):
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
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
: {}
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
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()
123 # we can ignore all errors
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
)
130 ############################################################################
132 ############################################################################
134 def __add_to_batch(self
, prefix
, cmd
):
135 if prefix
in self
.__batch
:
136 self
.__batch
[prefix
].append(cmd
)
138 self
.__batch
[prefix
] = [cmd
]
140 def __execute_or_batch(self
, prefix
, cmd
):
141 if self
.__batch
_mode
:
142 self
.__add
_to
_batch
(prefix
, cmd
)
144 utils
.exec_command("%s %s" % (prefix
, cmd
))
146 def __execute_or_batch_dry_run(self
, prefix
, cmd
):
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
152 if self
.__batch
_mode
:
153 self
.__add
_to
_batch
(prefix
, cmd
)
155 self
.log_info_dry_run("executing: %s %s" % (prefix
, cmd
))
157 def batch_start(self
):
158 if not self
.__batch
_mode
:
159 self
.__batch
_mode
= True
162 def batch_commit(self
):
164 if not self
.__batch
_mode
or not self
.__batch
:
166 for prefix
, commands
in self
.__batch
.items():
168 "%s -force -batch -" % prefix
,
169 stdin
="\n".join(commands
)
174 self
.__batch
_mode
= False
178 ############################################################################
180 ############################################################################
182 def link_up(self
, ifname
):
183 if not self
.cache
.link_is_up(ifname
):
184 self
.link_up_force(ifname
)
186 def link_down(self
, ifname
):
187 if self
.cache
.link_is_up(ifname
):
188 self
.link_down_force(ifname
)
190 def link_up_dry_run(self
, ifname
):
191 self
.link_up_force(ifname
)
193 def link_down_dry_run(self
, ifname
):
194 self
.link_down_force(ifname
)
196 def link_up_force(self
, ifname
):
197 self
.__execute
_or
_batch
(utils
.ip_cmd
, "link set dev %s up" % ifname
)
199 def link_down_force(self
, ifname
):
200 self
.__execute
_or
_batch
(utils
.ip_cmd
, "link set dev %s down" % ifname
)
204 def link_set_master(self
, ifname
, master
):
205 if master
!= self
.cache
.get_master(ifname
):
206 self
.__execute
_or
_batch
(
208 "link set dev %s master %s" % (ifname
, master
)
211 def link_set_master_dry_run(self
, ifname
, master
):
212 self
.__execute
_or
_batch
(
214 "link set dev %s master %s" % (ifname
, master
)
219 def link_set_address(self
, ifname
, address
):
220 if utils
.mac_str_to_int(address
) != self
.cache
.get_link_address_raw(ifname
):
221 self
.link_down(ifname
)
222 self
.__execute
_or
_batch
(
224 "link set dev %s address %s" % (ifname
, address
)
228 def link_set_address_dry_run(self
, ifname
, address
):
229 self
.link_down(ifname
)
230 self
.__execute
_or
_batch
(
232 "link set dev %s address %s" % (ifname
, address
)
236 def link_set_address_and_keep_down(self
, ifname
, address
, keep_down
=False):
237 if utils
.mac_str_to_int(address
) != self
.cache
.get_link_address_raw(ifname
):
238 self
.link_down(ifname
)
239 self
.__execute
_or
_batch
(
241 "link set dev %s address %s" % (ifname
, address
)
246 def link_set_address_and_keep_down_dry_run(self
, ifname
, address
, keep_down
=False):
247 self
.link_down(ifname
)
248 self
.__execute
_or
_batch
(
250 "link set dev %s address %s" % (ifname
, address
)
257 def link_add_macvlan(self
, ifname
, macvlan_ifname
, macvlan_mode
):
259 "%s link add link %s name %s type macvlan mode %s"
260 % (utils
.ip_cmd
, ifname
, macvlan_ifname
, macvlan_mode
)
263 def link_add_macvlan_dry_run(self
, ifname
, macvlan_ifname
, macvlan_mode
):
264 # this dryrun method can be removed once dryrun handlers
265 # are added to the utils module
266 self
.log_info_ifname_dry_run(ifname
, "executing %s link add link %s name %s type macvlan mode %s"
267 % (utils
.ip_cmd
, ifname
, macvlan_ifname
, macvlan_mode
)
272 def link_create_vxlan(self
, name
, vxlanid
, localtunnelip
=None, svcnodeip
=None,
273 remoteips
=None, learning
='on', ageing
=None, ttl
=None, physdev
=None):
274 if svcnodeip
and remoteips
:
275 raise Exception("svcnodeip and remoteip are mutually exclusive")
277 if self
.cache
.link_exists(name
):
279 "link set dev %s type vxlan dstport %d"
280 % (name
, self
.VXLAN_UDP_PORT
)
284 "link add dev %s type vxlan id %s dstport %d"
285 % (name
, vxlanid
, self
.VXLAN_UDP_PORT
)
289 if svcnodeip
.ip
.is_multicast
:
290 cmd
.append("group %s" % svcnodeip
)
292 cmd
.append("remote %s" % svcnodeip
)
295 cmd
.append("ageing %s" % ageing
)
297 if learning
== 'off':
298 cmd
.append("nolearning")
301 cmd
.append("ttl %s" % ttl
)
304 cmd
.append("dev %s" % physdev
)
307 cmd
.append("local %s" % localtunnelip
)
309 self
.__execute
_or
_batch
(utils
.ip_cmd
, " ".join(cmd
))
311 def get_vxlan_peers(self
, dev
, svcnodeip
):
312 cmd
= "%s fdb show brport %s" % (utils
.bridge_cmd
, dev
)
315 ps
= subprocess
.Popen(shlex
.split(cmd
), stdout
=subprocess
.PIPE
, close_fds
=False)
316 utils
.enable_subprocess_signal_forwarding(ps
, signal
.SIGINT
)
317 output
= subprocess
.check_output(("grep", "00:00:00:00:00:00"), stdin
=ps
.stdout
).decode()
319 utils
.disable_subprocess_signal_forwarding(signal
.SIGINT
)
321 for l
in output
.split('\n'):
322 m
= self
.VXLAN_PEER_REGEX_PATTERN
.search(l
)
323 if m
and m
.group(1) != svcnodeip
:
324 cur_peers
.append(m
.group(1))
326 self
.logger
.warning('error parsing ip link output')
327 except subprocess
.CalledProcessError
as e
:
328 if e
.returncode
!= 1:
329 self
.logger
.error(str(e
))
331 utils
.disable_subprocess_signal_forwarding(signal
.SIGINT
)
336 def link_add_xfrm(self
, ifname
, xfrm_name
, xfrm_id
):
337 utils
.exec_commandl(['ip', 'link', 'add', xfrm_name
, 'type', 'xfrm', 'dev', ifname
, 'if_id', xfrm_id
])
338 self
.__update
_cache
_after
_link
_creation
(xfrm_name
, "xfrm")
340 ############################################################################
342 ############################################################################
344 def tunnel_create(self
, tunnelname
, mode
, attrs
=None):
345 if self
.cache
.link_exists(tunnelname
):
352 if mode
in ["gretap"]:
353 cmd
.append("link add %s type %s" % (tunnelname
, mode
))
355 cmd
.append("tunnel add %s mode %s" % (tunnelname
, mode
))
358 for k
, v
in attrs
.items():
363 utils
.exec_command("%s %s" % (utils
.ip_cmd
, " ".join(cmd
)))
364 self
.__update
_cache
_after
_link
_creation
(tunnelname
, mode
)
366 def tunnel_change(self
, tunnelname
, attrs
=None):
367 """ tunnel change function """
368 if not self
.cache
.link_exists(tunnelname
):
370 cmd
= ["tunnel change %s" % tunnelname
]
372 for k
, v
in attrs
.items():
376 self
.__execute
_or
_batch
(utils
.ip_cmd
, " ".join(cmd
))
378 ############################################################################
380 ############################################################################
382 def addr_flush(self
, ifname
):
383 if self
.cache
.link_has_ip(ifname
):
384 self
.__execute
_or
_batch
(utils
.ip_cmd
, "addr flush dev %s" % ifname
)
386 def link_set_ipv6_addrgen_dry_run(self
, ifname
, addrgen
, link_created
):
387 addrgen_str
= "none" if addrgen
else "eui64"
388 self
.link_down(ifname
)
389 self
.__execute
_or
_batch
(utils
.ip_cmd
, "link set dev %s addrgenmode %s" % (ifname
, addrgen_str
))
392 def link_set_ipv6_addrgen(self
, ifname
, addrgen
, link_created
):
394 IFLA_INET6_ADDR_GEN_MODE values:
403 cached_ipv6_addr_gen_mode
= self
.cache
.get_link_ipv6_addrgen_mode(ifname
)
405 if cached_ipv6_addr_gen_mode
== addrgen
:
408 disabled_ipv6
= self
.sysfs
.get_ipv6_conf_disable_ipv6(ifname
)
411 self
.logger
.info("%s: cannot set addrgen: ipv6 is disabled on this device" % ifname
)
415 link_mtu
= self
.sysfs
.link_get_mtu(ifname
)
417 link_mtu
= self
.cache
.get_link_mtu(ifname
)
420 self
.logger
.info("%s: ipv6 addrgen is disabled on device with MTU "
421 "lower than 1280 (current mtu %s): cannot set addrgen %s"
422 % (ifname
, link_mtu
, "off" if addrgen
else "on"))
426 # When setting addrgenmode it is necessary to flap the macvlan
427 # device. After flapping the device we also need to re-add all
428 # the user configuration. The best way to add the user config
429 # is to flush our internal address cache
430 self
.cache
.address_flush_link(ifname
)
432 is_link_up
= self
.cache
.link_is_up(ifname
)
435 self
.link_down_force(ifname
)
437 self
.__execute
_or
_batch
(
439 "link set dev %s addrgenmode %s" % (ifname
, Link
.ifla_inet6_addr_gen_mode_dict
.get(addrgen
))
443 self
.link_up_force(ifname
)
448 def __compare_user_config_vs_running_state(running_addrs
, user_addrs
):
452 for ip
in user_addrs
or []:
459 for ip
in running_addrs
or []:
460 running_ipobj
.append(ip
)
462 return running_ipobj
== (ip4
+ ip6
)
464 def add_addresses(self
, ifacobj
, ifname
, address_list
, purge_existing
=False, metric
=None, with_address_virtual
=False):
466 running_address_list
= self
.cache
.get_managed_ip_addresses(
469 with_address_virtual
=with_address_virtual
472 if self
.__compare
_user
_config
_vs
_running
_state
(running_address_list
, address_list
):
476 self
.__execute
_or
_batch
(utils
.ip_cmd
, "addr flush dev %s" % ifname
)
477 except Exception as e
:
478 self
.logger
.warning("%s: flushing all ip address failed: %s" % (ifname
, str(e
)))
479 for addr
in address_list
:
482 self
.__execute
_or
_batch
(utils
.ip_cmd
, "addr add %s dev %s metric %s" % (addr
, ifname
, metric
))
484 self
.__execute
_or
_batch
(utils
.ip_cmd
, "addr add %s dev %s" % (addr
, ifname
))
485 except Exception as e
:
486 self
.logger
.error("%s: add_address: %s" % (ifname
, str(e
)))
488 ############################################################################
490 ############################################################################
493 def bridge_set_stp(bridge
, stp_state
):
494 utils
.exec_command("%s stp %s %s" % (utils
.brctl_cmd
, bridge
, stp_state
))
497 def bridge_fdb_show_dev(dev
):
500 output
= utils
.exec_command("%s fdb show dev %s" % (utils
.bridge_cmd
, dev
))
502 for fdb_entry
in output
.splitlines():
504 entries
= fdb_entry
.split()
505 fdbs
.setdefault(entries
[2], []).append(entries
[0])
513 def bridge_fdb_add(dev
, address
, vlan
=None, bridge
=True, remote
=None):
514 target
= "self" if bridge
else ""
515 vlan_str
= "vlan %s " % vlan
if vlan
else ""
516 dst_str
= "dst %s " % remote
if remote
else ""
519 "%s fdb replace %s dev %s %s %s %s"
531 def bridge_fdb_append(dev
, address
, vlan
=None, bridge
=True, remote
=None):
532 target
= "self" if bridge
else ""
533 vlan_str
= "vlan %s " % vlan
if vlan
else ""
534 dst_str
= "dst %s " % remote
if remote
else ""
537 "%s fdb append %s dev %s %s %s %s"
549 def bridge_fdb_del(dev
, address
, vlan
=None, bridge
=True, remote
=None):
550 target
= "self" if bridge
else ""
551 vlan_str
= "vlan %s " % vlan
if vlan
else ""
552 dst_str
= "dst %s " % remote
if remote
else ""
555 "%s fdb del %s dev %s %s %s %s"
567 def bridge_vlan_del_vid_list(ifname
, vids
):
572 "%s vlan del vid %s dev %s" % (utils
.bridge_cmd
, v
, ifname
)
575 def bridge_vlan_del_vid_list_self(self
, ifname
, vids
, is_bridge
=True):
576 target
= "self" if is_bridge
else ""
578 self
.__execute
_or
_batch
(
580 "vlan del vid %s dev %s %s" % (v
, ifname
, target
)
584 def bridge_vlan_add_vid_list(ifname
, vids
):
587 "%s vlan add vid %s dev %s" % (utils
.bridge_cmd
, v
, ifname
)
590 def bridge_vlan_add_vid_list_self(self
, ifname
, vids
, is_bridge
=True):
591 target
= "self" if is_bridge
else ""
593 self
.__execute
_or
_batch
(
595 "vlan add vid %s dev %s %s" % (v
, ifname
, target
)
598 def bridge_vlan_del_pvid(self
, ifname
, pvid
):
599 self
.__execute
_or
_batch
(
601 "vlan del vid %s untagged pvid dev %s" % (pvid
, ifname
)
604 def bridge_vlan_add_pvid(self
, ifname
, pvid
):
605 self
.__execute
_or
_batch
(
607 "vlan add vid %s untagged pvid dev %s" % (pvid
, ifname
)
610 def bridge_del_mcqv4src(self
, bridge
, vlan
):
613 except Exception as e
:
614 self
.logger
.info("%s: del mcqv4src vlan: invalid parameter %s: %s"
615 % (bridge
, vlan
, str(e
)))
617 utils
.exec_command("%s delmcqv4src %s %d" % (utils
.brctl_cmd
, bridge
, vlan
))
619 def bridge_set_mcqv4src(self
, bridge
, vlan
, mcquerier
):
622 except Exception as e
:
623 self
.logger
.info("%s: set mcqv4src vlan: invalid parameter %s: %s" % (bridge
, vlan
, str(e
)))
625 if vlan
== 0 or vlan
> 4095:
626 self
.logger
.warning("mcqv4src vlan '%d' invalid range" % vlan
)
629 ip
= mcquerier
.split(".")
631 self
.logger
.warning("mcqv4src '%s' invalid IPv4 address" % mcquerier
)
634 if not k
.isdigit() or int(k
, 10) < 0 or int(k
, 10) > 255:
635 self
.logger
.warning("mcqv4src '%s' invalid IPv4 address" % mcquerier
)
638 utils
.exec_command("%s setmcqv4src %s %d %s" % (utils
.brctl_cmd
, bridge
, vlan
, mcquerier
))
640 ############################################################################
642 ############################################################################
645 def route_add_gateway(ifname
, gateway
, vrf
=None, metric
=None, onlink
=True):
650 cmd
= "%s route add default via %s proto kernel" % (utils
.ip_cmd
, gateway
)
652 cmd
= "%s route add table %s default via %s proto kernel" % (utils
.ip_cmd
, vrf
, gateway
)
655 cmd
+= " metric %s" % metric
657 cmd
+= " dev %s" % ifname
662 utils
.exec_command(cmd
)
665 def route_del_gateway(ifname
, gateway
, vrf
=None, metric
=None):
668 we don't need a DRYRUN handler here as utils.exec_command should have one
674 cmd
= "%s route del default via %s proto kernel" % (utils
.ip_cmd
, gateway
)
676 cmd
= "%s route del table %s default via %s proto kernel" % (utils
.ip_cmd
, vrf
, gateway
)
679 cmd
+= " metric %s" % metric
681 cmd
+= " dev %s" % ifname
682 utils
.exec_command(cmd
)
684 def fix_ipv6_route_metric(self
, ifaceobj
, macvlan_ifacename
, ips
):
687 if ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.VRF_SLAVE
:
689 for upper_iface
in ifaceobj
.upperifaces
:
690 vrf_table
= self
.cache
.get_vrf_table(upper_iface
)
698 ip_network_obj
= ipaddress
.ip_network(ip
)
700 if ip_network_obj
.version
== 6:
701 route_prefix
= '%s/%d' % (ip_network_obj
.network
, ip_network_obj
.prefixlen
)
704 self
.__execute
_or
_batch
(
706 "route del %s table %s dev %s" % (route_prefix
, vrf_table
, macvlan_ifacename
)
709 self
.__execute
_or
_batch
(
711 "route del %s dev %s" % (route_prefix
, macvlan_ifacename
)
714 ip_route_del
.append((route_prefix
, vrf_table
))
716 for ip
, vrf_table
in ip_route_del
:
718 self
.__execute
_or
_batch
(
720 "route add %s table %s dev %s proto kernel metric 9999" % (ip
, vrf_table
, macvlan_ifacename
)
723 self
.__execute
_or
_batch
(
725 "route add %s dev %s proto kernel metric 9999" % (ip
, macvlan_ifacename
)
728 def ip_route_get_dev(self
, prefix
, vrf_master
=None):
731 cmd
= "%s route get %s vrf %s" % (utils
.ip_cmd
, prefix
, vrf_master
)
733 cmd
= "%s route get %s" % (utils
.ip_cmd
, prefix
)
735 output
= utils
.exec_command(cmd
)
737 rline
= output
.splitlines()[0]
739 rattrs
= rline
.split()
740 return rattrs
[rattrs
.index("dev") + 1]
741 except Exception as e
:
742 self
.logger
.debug("ip_route_get_dev: failed .. %s" % str(e
))