]>
git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/addons/vxlan.py
3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
9 from ipaddr
import IPNetwork
, IPv4Address
, IPv4Network
, AddressValueError
12 import ifupdown2
.ifupdown
.policymanager
as policymanager
13 import ifupdown2
.ifupdown
.ifupdownflags
as ifupdownflags
15 from ifupdown2
.nlmanager
.nlmanager
import Link
17 from ifupdown2
.ifupdown
.iface
import *
18 from ifupdown2
.ifupdown
.utils
import utils
19 from ifupdown2
.ifupdown
.netlink
import netlink
20 from ifupdown2
.ifupdownaddons
.cache
import *
21 from ifupdown2
.ifupdownaddons
.LinkUtils
import LinkUtils
22 from ifupdown2
.ifupdownaddons
.modulebase
import moduleBase
24 import ifupdown
.policymanager
as policymanager
25 import ifupdown
.ifupdownflags
as ifupdownflags
27 from nlmanager
.nlmanager
import Link
29 from ifupdown
.iface
import *
30 from ifupdown
.utils
import utils
31 from ifupdown
.netlink
import netlink
33 from ifupdownaddons
.cache
import *
34 from ifupdownaddons
.LinkUtils
import LinkUtils
35 from ifupdownaddons
.modulebase
import moduleBase
38 class vxlan(moduleBase
):
39 _modinfo
= {'mhelp' : 'vxlan module configures vxlan interfaces.',
43 'validrange' : ['1', '16777214'],
45 'example': ['vxlan-id 100']},
46 'vxlan-local-tunnelip' :
47 {'help' : 'vxlan local tunnel ip',
48 'validvals' : ['<ipv4>'],
49 'example': ['vxlan-local-tunnelip 172.16.20.103']},
52 'validvals' : ['<ipv4>'],
53 'example': ['vxlan-svcnodeip 172.16.22.125']},
55 {'help' : 'vxlan remote ip',
56 'validvals' : ['<ipv4>'],
57 'example': ['vxlan-remoteip 172.16.22.127'],
60 {'help' : 'vxlan learning yes/no',
61 'validvals' : ['yes', 'no', 'on', 'off'],
62 'example': ['vxlan-learning no'],
65 {'help' : 'vxlan aging timer',
66 'validrange' : ['0', '4096'],
67 'example': ['vxlan-ageing 300'],
69 'vxlan-purge-remotes' :
70 {'help' : 'vxlan purge existing remote entries',
71 'validvals' : ['yes', 'no'],
72 'example': ['vxlan-purge-remotes yes'],},
74 'help': 'vxlan UDP port (transmitted to vxlan driver)',
75 'example': ['vxlan-port 4789'],
76 'validrange': ['1', '65536'],
80 {'help': 'vxlan physical device',
81 'example': ['vxlan-physdev eth1']},
84 _clagd_vxlan_anycast_ip
= ""
85 _vxlan_local_tunnelip
= None
87 def __init__(self
, *args
, **kargs
):
88 moduleBase
.__init
__(self
, *args
, **kargs
)
90 purge_remotes
= policymanager
.policymanager_api
.get_module_globals(module_name
=self
.__class
__.__name
__, attr
='vxlan-purge-remotes')
92 self
._purge
_remotes
= utils
.get_boolean_from_string(purge_remotes
)
94 self
._purge
_remotes
= False
96 def syntax_check(self
, ifaceobj
, ifaceobj_getfunc
):
97 if self
._is
_vxlan
_device
(ifaceobj
):
98 if not ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') and not vxlan
._vxlan
_local
_tunnelip
:
99 self
.logger
.warning('%s: missing vxlan-local-tunnelip' % ifaceobj
.name
)
101 return self
.syntax_check_localip_anycastip_equal(
103 ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') or vxlan
._vxlan
_local
_tunnelip
,
104 vxlan
._clagd
_vxlan
_anycast
_ip
108 def syntax_check_localip_anycastip_equal(self
, ifname
, local_ip
, anycast_ip
):
110 if IPNetwork(local_ip
) == IPNetwork(anycast_ip
):
111 self
.logger
.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
112 % (ifname
, local_ip
))
118 def get_dependent_ifacenames(self
, ifaceobj
, ifaceobjs_all
=None):
119 if self
._is
_vxlan
_device
(ifaceobj
):
120 ifaceobj
.link_kind |
= ifaceLinkKind
.VXLAN
121 self
._set
_global
_local
_ip
(ifaceobj
)
122 elif ifaceobj
.name
== 'lo':
123 clagd_vxlan_list
= ifaceobj
.get_attr_value('clagd-vxlan-anycast-ip')
125 if len(clagd_vxlan_list
) != 1:
126 self
.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
128 vxlan
._clagd
_vxlan
_anycast
_ip
= clagd_vxlan_list
[0]
130 self
._set
_global
_local
_ip
(ifaceobj
)
132 # If we should use a specific underlay device for the VXLAN
133 # tunnel make sure this device is set up before the VXLAN iface.
134 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
141 def _set_global_local_ip(self
, ifaceobj
):
142 vxlan_local_tunnel_ip
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
143 if vxlan_local_tunnel_ip
and not vxlan
._vxlan
_local
_tunnelip
:
144 vxlan
._vxlan
_local
_tunnelip
= vxlan_local_tunnel_ip
146 def _is_vxlan_device(self
, ifaceobj
):
147 if ifaceobj
.get_attr_value_first('vxlan-id'):
151 def _get_purge_remotes(self
, ifaceobj
):
153 return self
._purge
_remotes
154 purge_remotes
= ifaceobj
.get_attr_value_first('vxlan-purge-remotes')
156 purge_remotes
= utils
.get_boolean_from_string(purge_remotes
)
158 purge_remotes
= self
._purge
_remotes
161 def should_create_set_vxlan(self
, link_exists
, ifname
, vxlan_id
, local
, learning
, ageing
, group
):
163 should we issue a netlink: ip link add dev %ifname type vxlan ...?
164 checking each attribute against the cache
175 for attr_list
, value
in (
176 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_ID
), vxlan_id
),
177 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_AGEING
), ageing
),
178 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LOCAL
), local
),
179 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LEARNING
), learning
),
180 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_GROUP
), group
),
182 if value
and not self
.ipcmd
.cache_check(attr_list
, value
):
186 def _vxlan_create(self
, ifaceobj
):
187 vxlanid
= ifaceobj
.get_attr_value_first('vxlan-id')
189 ifname
= ifaceobj
.name
190 anycastip
= self
._clagd
_vxlan
_anycast
_ip
191 group
= ifaceobj
.get_attr_value_first('vxlan-svcnodeip')
193 local
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
194 if not local
and vxlan
._vxlan
_local
_tunnelip
:
195 local
= vxlan
._vxlan
_local
_tunnelip
197 self
.syntax_check_localip_anycastip_equal(ifname
, local
, anycastip
)
198 # if both local-ip and anycast-ip are identical the function prints a warning
200 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
201 vxlan_port
= ifaceobj
.get_attr_value_first('vxlan-port')
202 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
203 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
205 link_exists
= self
.ipcmd
.link_exists(ifname
)
207 if (not link_exists
or
208 not ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.BRIDGE_PORT
):
209 vxlan_learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
210 if not vxlan_learning
:
211 vxlan_learning
= self
.get_attr_default_value('vxlan-learning')
212 learning
= utils
.get_boolean_from_string(vxlan_learning
)
214 learning
= utils
.get_boolean_from_string(
215 self
.ipcmd
.get_vxlandev_learning(ifname
))
218 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifname
)
219 # on ifreload do not overwrite anycast_ip to individual ip
220 # if clagd has modified
222 running_localtunnelip
= vxlanattrs
.get('local')
223 if (anycastip
and running_localtunnelip
and
224 anycastip
== running_localtunnelip
):
225 local
= running_localtunnelip
226 if vxlanattrs
.get('vxlanid') != vxlanid
:
227 self
.log_error('%s: Cannot change running vxlan id: '
228 'Operation not supported' % ifname
, ifaceobj
)
230 vxlanid
= int(vxlanid
)
232 self
.log_error('%s: invalid vxlan-id \'%s\'' % (ifname
, vxlanid
), ifaceobj
)
235 group
= policymanager
.policymanager_api
.get_attr_default(
236 module_name
=self
.__class
__.__name
__,
237 attr
='vxlan-svcnodeip'
242 group
= IPv4Address(group
)
243 except AddressValueError
:
245 group_ip
= IPv4Network(group
).ip
246 self
.logger
.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname
, group
))
249 raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname
, group
))
252 local
= policymanager
.policymanager_api
.get_attr_default(
253 module_name
=self
.__class
__.__name
__,
254 attr
='vxlan-local-tunnelip'
259 local
= IPv4Address(local
)
260 except AddressValueError
:
262 local_ip
= IPv4Network(local
).ip
263 self
.logger
.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname
, local
))
266 raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname
, local
))
269 ageing
= policymanager
.policymanager_api
.get_attr_default(
270 module_name
=self
.__class
__.__name
__,
274 if not ageing
and link_exists
:
275 # if link doesn't exist we let the kernel define ageing
276 ageing
= self
.get_attr_default_value('vxlan-ageing')
279 vxlan_port
= policymanager
.policymanager_api
.get_attr_default(
280 module_name
=self
.__class
__.__name
__,
285 vxlan_port
= int(vxlan_port
)
287 # TypeError means vxlan_port was None
288 # ie: not provided by the user or the policy
289 vxlan_port
= netlink
.VXLAN_UDP_PORT
290 except ValueError as e
:
291 self
.logger
.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname
, netlink
.VXLAN_UDP_PORT
, str(e
)))
292 vxlan_port
= netlink
.VXLAN_UDP_PORT
294 if link_exists
and not ifupdownflags
.flags
.DRYRUN
:
295 cache_port
= vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
)
296 if vxlan_port
!= cache_port
:
297 self
.logger
.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s'
298 % (ifname
, cache_port
, ifname
, ifname
))
299 vxlan_port
= cache_port
301 if self
.should_create_set_vxlan(link_exists
, ifname
, vxlanid
, local
, learning
, ageing
, group
):
303 netlink
.link_add_vxlan(ifname
, vxlanid
,
310 except Exception as e_netlink
:
311 self
.logger
.debug('%s: vxlan netlink: %s' % (ifname
, str(e_netlink
)))
313 self
.ipcmd
.link_create_vxlan(ifname
, vxlanid
,
316 remoteips
=ifaceobj
.get_attr_value('vxlan-remoteip'),
317 learning
='on' if learning
else 'off',
319 except Exception as e_iproute2
:
320 self
.logger
.warning('%s: vxlan add/set failed: %s' % (ifname
, str(e_iproute2
)))
324 # manually adding an entry to the caching after creating/updating the vxlan
325 if not ifname
in linkCache
.links
:
326 linkCache
.links
[ifname
] = {'linkinfo': {}}
327 linkCache
.links
[ifname
]['linkinfo'].update({
328 'learning': learning
,
329 Link
.IFLA_VXLAN_LEARNING
: learning
,
330 'vxlanid': str(vxlanid
),
331 Link
.IFLA_VXLAN_ID
: vxlanid
334 linkCache
.links
[ifname
]['linkinfo'].update({
336 Link
.IFLA_VXLAN_AGEING
: int(ageing
)
341 self
.logger
.info('%s: vxlan already exists' % ifname
)
342 # if the vxlan already exists it's already cached
344 remoteips
= ifaceobj
.get_attr_value('vxlan-remoteip')
347 for remoteip
in remoteips
:
348 IPv4Address(remoteip
)
349 except Exception as e
:
350 self
.log_error('%s: vxlan-remoteip: %s' %(ifaceobj
.name
, str(e
)))
352 if purge_remotes
or remoteips
:
353 # figure out the diff for remotes and do the bridge fdb updates
354 # only if provisioned by user and not by an vxlan external
356 peers
= self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, group
)
357 if local
and remoteips
and local
in remoteips
:
358 remoteips
.remove(local
)
359 cur_peers
= set(peers
)
361 new_peers
= set(remoteips
)
362 del_list
= cur_peers
.difference(new_peers
)
363 add_list
= new_peers
.difference(cur_peers
)
368 for addr
in del_list
:
370 self
.ipcmd
.bridge_fdb_del(ifaceobj
.name
,
376 for addr
in add_list
:
378 self
.ipcmd
.bridge_fdb_append(ifaceobj
.name
,
384 def _up(self
, ifaceobj
):
385 self
._vxlan
_create
(ifaceobj
)
387 def _down(self
, ifaceobj
):
389 self
.ipcmd
.link_delete(ifaceobj
.name
)
391 self
.log_warn(str(e
))
393 def _query_check_n_update(self
, ifaceobj
, ifaceobjcurr
, attrname
, attrval
,
395 if not ifaceobj
.get_attr_value_first(attrname
):
397 if running_attrval
and attrval
== running_attrval
:
398 ifaceobjcurr
.update_config_with_status(attrname
, attrval
, 0)
400 ifaceobjcurr
.update_config_with_status(attrname
, running_attrval
, 1)
402 def _query_check_n_update_addresses(self
, ifaceobjcurr
, attrname
,
403 addresses
, running_addresses
):
406 if a
in running_addresses
:
407 ifaceobjcurr
.update_config_with_status(attrname
, a
, 0)
409 ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
410 running_addresses
= Set(running_addresses
).difference(
412 [ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
413 for a
in running_addresses
]
415 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
416 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
418 # Update vxlan object
419 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobj
.name
)
421 ifaceobjcurr
.check_n_update_config_with_status_many(ifaceobj
,
422 self
.get_mod_attrs(), -1)
424 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-id',
425 ifaceobj
.get_attr_value_first('vxlan-id'),
426 vxlanattrs
.get('vxlanid'))
428 self
._query
_check
_n
_update
(
432 ifaceobj
.get_attr_value_first('vxlan-port'),
433 str(vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
436 running_attrval
= vxlanattrs
.get('local')
437 attrval
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
439 attrval
= vxlan
._vxlan
_local
_tunnelip
440 ifaceobj
.update_config('vxlan-local-tunnelip', attrval
)
442 if running_attrval
== self
._clagd
_vxlan
_anycast
_ip
:
443 # if local ip is anycast_ip, then let query_check to go through
444 attrval
= self
._clagd
_vxlan
_anycast
_ip
445 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-local-tunnelip',
446 attrval
, running_attrval
)
448 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-svcnodeip',
449 ifaceobj
.get_attr_value_first('vxlan-svcnodeip'),
450 vxlanattrs
.get('svcnode'))
452 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
453 if purge_remotes
or ifaceobj
.get_attr_value('vxlan-remoteip'):
454 # If purge remotes or if vxlan-remoteip's are set
455 # in the config file, we are owners of the installed
456 # remote-ip's, lets check and report any remote ips we don't
458 self
._query
_check
_n
_update
_addresses
(ifaceobjcurr
, 'vxlan-remoteip',
459 ifaceobj
.get_attr_value('vxlan-remoteip'),
460 self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, vxlanattrs
.get('svcnode')))
462 learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
464 running_learning
= vxlanattrs
.get('learning')
465 if learning
== 'yes' and running_learning
== 'on':
466 running_learning
= 'yes'
467 elif learning
== 'no' and running_learning
== 'off':
468 running_learning
= 'no'
469 if learning
== running_learning
:
470 ifaceobjcurr
.update_config_with_status('vxlan-learning',
473 ifaceobjcurr
.update_config_with_status('vxlan-learning',
475 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
477 ageing
= self
.get_mod_subattr('vxlan-ageing', 'default')
478 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-ageing',
479 ageing
, vxlanattrs
.get('ageing'))
481 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
484 ifla_vxlan_link
= vxlanattrs
.get(Link
.IFLA_VXLAN_LINK
)
487 self
._query
_check
_n
_update
(
492 netlink
.get_iface_name(ifla_vxlan_link
)
495 ifaceobjcurr
.update_config_with_status('vxlan-physdev', physdev
, 1)
498 def _query_running(self
, ifaceobjrunning
):
499 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobjrunning
.name
)
502 attrval
= vxlanattrs
.get('vxlanid')
504 ifaceobjrunning
.update_config('vxlan-id', vxlanattrs
.get('vxlanid'))
506 # if there is no vxlan id, this is not a vxlan port
509 ifaceobjrunning
.update_config('vxlan-port', vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
511 attrval
= vxlanattrs
.get('local')
513 ifaceobjrunning
.update_config('vxlan-local-tunnelip', attrval
)
514 attrval
= vxlanattrs
.get('svcnode')
516 ifaceobjrunning
.update_config('vxlan-svcnode', attrval
)
517 purge_remotes
= self
._get
_purge
_remotes
(None)
519 # if purge_remotes is on, it means we own the
520 # remote ips. Query them and add it to the running config
521 attrval
= self
.ipcmd
.get_vxlan_peers(ifaceobjrunning
.name
, vxlanattrs
.get('svcnode'))
523 [ifaceobjrunning
.update_config('vxlan-remoteip', a
)
525 attrval
= vxlanattrs
.get('learning')
526 if attrval
and attrval
== 'on':
527 ifaceobjrunning
.update_config('vxlan-learning', 'on')
528 attrval
= vxlanattrs
.get('ageing')
530 ifaceobjrunning
.update_config('vxlan-ageing', vxlanattrs
.get('ageing'))
532 ifla_vxlan_link
= vxlanattrs
.get(Link
.IFLA_VXLAN_LINK
)
534 ifaceobjrunning
.update_config(
536 netlink
.get_iface_name(ifla_vxlan_link
)
539 _run_ops
= {'pre-up' : _up
,
541 'query-checkcurr' : _query_check
,
542 'query-running' : _query_running
}
545 return self
._run
_ops
.keys()
547 def _init_command_handlers(self
):
549 self
.ipcmd
= LinkUtils()
551 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
552 op_handler
= self
._run
_ops
.get(operation
)
555 if (operation
!= 'query-running' and
556 not self
._is
_vxlan
_device
(ifaceobj
)):
558 self
._init
_command
_handlers
()
559 if operation
== 'query-checkcurr':
560 op_handler(self
, ifaceobj
, query_ifaceobj
)
562 op_handler(self
, ifaceobj
)