]>
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
14 from ifupdown2
.nlmanager
.nlmanager
import Link
16 from ifupdown2
.ifupdown
.iface
import *
17 from ifupdown2
.ifupdown
.utils
import utils
18 from ifupdown2
.ifupdown
.netlink
import netlink
19 from ifupdown2
.ifupdownaddons
.cache
import *
20 from ifupdown2
.ifupdownaddons
.LinkUtils
import LinkUtils
21 from ifupdown2
.ifupdownaddons
.modulebase
import moduleBase
23 import ifupdown
.policymanager
as policymanager
25 from nlmanager
.nlmanager
import Link
27 from ifupdown
.iface
import *
28 from ifupdown
.utils
import utils
29 from ifupdown
.netlink
import netlink
31 from ifupdownaddons
.cache
import *
32 from ifupdownaddons
.LinkUtils
import LinkUtils
33 from ifupdownaddons
.modulebase
import moduleBase
36 class vxlan(moduleBase
):
37 _modinfo
= {'mhelp' : 'vxlan module configures vxlan interfaces.',
41 'validrange' : ['1', '16777214'],
43 'example': ['vxlan-id 100']},
44 'vxlan-local-tunnelip' :
45 {'help' : 'vxlan local tunnel ip',
46 'validvals' : ['<ipv4>'],
47 'example': ['vxlan-local-tunnelip 172.16.20.103']},
50 'validvals' : ['<ipv4>'],
51 'example': ['vxlan-svcnodeip 172.16.22.125']},
53 {'help' : 'vxlan remote ip',
54 'validvals' : ['<ipv4>'],
55 'example': ['vxlan-remoteip 172.16.22.127'],
58 {'help' : 'vxlan learning yes/no',
59 'validvals' : ['yes', 'no', 'on', 'off'],
60 'example': ['vxlan-learning no'],
63 {'help' : 'vxlan aging timer',
64 'validrange' : ['0', '4096'],
65 'example': ['vxlan-ageing 300'],
67 'vxlan-purge-remotes' :
68 {'help' : 'vxlan purge existing remote entries',
69 'validvals' : ['yes', 'no'],
70 'example': ['vxlan-purge-remotes yes'],},
72 'help': 'vxlan UDP port (transmitted to vxlan driver)',
73 'validvals': ['<number>'],
74 'example': 'vxlan-port 4789',
75 'validrange': ['1', '65536'],
79 _clagd_vxlan_anycast_ip
= ""
80 _vxlan_local_tunnelip
= None
82 def __init__(self
, *args
, **kargs
):
83 moduleBase
.__init
__(self
, *args
, **kargs
)
85 purge_remotes
= policymanager
.policymanager_api
.get_module_globals(module_name
=self
.__class
__.__name
__, attr
='vxlan-purge-remotes')
87 self
._purge
_remotes
= utils
.get_boolean_from_string(purge_remotes
)
89 self
._purge
_remotes
= False
91 def syntax_check(self
, ifaceobj
, ifaceobj_getfunc
):
92 if self
._is
_vxlan
_device
(ifaceobj
):
93 if not ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') and not vxlan
._vxlan
_local
_tunnelip
:
94 self
.logger
.warning('%s: missing vxlan-local-tunnelip' % ifaceobj
.name
)
96 return self
.syntax_check_localip_anycastip_equal(
98 ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') or vxlan
._vxlan
_local
_tunnelip
,
99 vxlan
._clagd
_vxlan
_anycast
_ip
103 def syntax_check_localip_anycastip_equal(self
, ifname
, local_ip
, anycast_ip
):
105 if IPNetwork(local_ip
) == IPNetwork(anycast_ip
):
106 self
.logger
.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
107 % (ifname
, local_ip
))
113 def get_dependent_ifacenames(self
, ifaceobj
, ifaceobjs_all
=None):
114 if self
._is
_vxlan
_device
(ifaceobj
):
115 ifaceobj
.link_kind |
= ifaceLinkKind
.VXLAN
116 self
._set
_global
_local
_ip
(ifaceobj
)
117 elif ifaceobj
.name
== 'lo':
118 clagd_vxlan_list
= ifaceobj
.get_attr_value('clagd-vxlan-anycast-ip')
120 if len(clagd_vxlan_list
) != 1:
121 self
.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
123 vxlan
._clagd
_vxlan
_anycast
_ip
= clagd_vxlan_list
[0]
125 self
._set
_global
_local
_ip
(ifaceobj
)
128 def _set_global_local_ip(self
, ifaceobj
):
129 vxlan_local_tunnel_ip
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
130 if vxlan_local_tunnel_ip
and not vxlan
._vxlan
_local
_tunnelip
:
131 vxlan
._vxlan
_local
_tunnelip
= vxlan_local_tunnel_ip
133 def _is_vxlan_device(self
, ifaceobj
):
134 if ifaceobj
.get_attr_value_first('vxlan-id'):
138 def _get_purge_remotes(self
, ifaceobj
):
140 return self
._purge
_remotes
141 purge_remotes
= ifaceobj
.get_attr_value_first('vxlan-purge-remotes')
143 purge_remotes
= utils
.get_boolean_from_string(purge_remotes
)
145 purge_remotes
= self
._purge
_remotes
148 def should_create_set_vxlan(self
, link_exists
, ifname
, vxlan_id
, local
, learning
, ageing
, group
):
150 should we issue a netlink: ip link add dev %ifname type vxlan ...?
151 checking each attribute against the cache
162 for attr_list
, value
in (
163 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_ID
), vxlan_id
),
164 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_AGEING
), ageing
),
165 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LOCAL
), local
),
166 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LEARNING
), learning
),
167 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_GROUP
), group
),
169 if value
and not self
.ipcmd
.cache_check(attr_list
, value
):
173 def _vxlan_create(self
, ifaceobj
):
174 vxlanid
= ifaceobj
.get_attr_value_first('vxlan-id')
176 ifname
= ifaceobj
.name
177 anycastip
= self
._clagd
_vxlan
_anycast
_ip
178 group
= ifaceobj
.get_attr_value_first('vxlan-svcnodeip')
180 local
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
181 if not local
and vxlan
._vxlan
_local
_tunnelip
:
182 local
= vxlan
._vxlan
_local
_tunnelip
184 self
.syntax_check_localip_anycastip_equal(ifname
, local
, anycastip
)
185 # if both local-ip and anycast-ip are identical the function prints a warning
187 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
188 vxlan_port
= ifaceobj
.get_attr_value_first('vxlan-port')
189 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
191 link_exists
= self
.ipcmd
.link_exists(ifname
)
193 if (not link_exists
or
194 not ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.BRIDGE_PORT
):
195 vxlan_learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
196 if not vxlan_learning
:
197 vxlan_learning
= self
.get_attr_default_value('vxlan-learning')
198 learning
= utils
.get_boolean_from_string(vxlan_learning
)
200 learning
= utils
.get_boolean_from_string(
201 self
.ipcmd
.get_vxlandev_learning(ifname
))
204 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifname
)
205 # on ifreload do not overwrite anycast_ip to individual ip
206 # if clagd has modified
208 running_localtunnelip
= vxlanattrs
.get('local')
209 if (anycastip
and running_localtunnelip
and
210 anycastip
== running_localtunnelip
):
211 local
= running_localtunnelip
212 if vxlanattrs
.get('vxlanid') != vxlanid
:
213 self
.log_error('%s: Cannot change running vxlan id: '
214 'Operation not supported' % ifname
, ifaceobj
)
216 vxlanid
= int(vxlanid
)
218 self
.log_error('%s: invalid vxlan-id \'%s\'' % (ifname
, vxlanid
), ifaceobj
)
221 group
= policymanager
.policymanager_api
.get_attr_default(
222 module_name
=self
.__class
__.__name
__,
223 attr
='vxlan-svcnodeip'
228 group
= IPv4Address(group
)
229 except AddressValueError
:
231 group_ip
= IPv4Network(group
).ip
232 self
.logger
.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname
, group
))
235 raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname
, group
))
238 local
= policymanager
.policymanager_api
.get_attr_default(
239 module_name
=self
.__class
__.__name
__,
240 attr
='vxlan-local-tunnelip'
245 local
= IPv4Address(local
)
246 except AddressValueError
:
248 local_ip
= IPv4Network(local
).ip
249 self
.logger
.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname
, local
))
252 raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname
, local
))
255 ageing
= policymanager
.policymanager_api
.get_attr_default(
256 module_name
=self
.__class
__.__name
__,
260 if not ageing
and link_exists
:
261 # if link doesn't exist we let the kernel define ageing
262 ageing
= self
.get_attr_default_value('vxlan-ageing')
265 vxlan_port
= policymanager
.policymanager_api
.get_attr_default(
266 module_name
=self
.__class
__.__name
__,
271 vxlan_port
= int(vxlan_port
)
273 # TypeError means vxlan_port was None
274 # ie: not provided by the user or the policy
275 vxlan_port
= netlink
.VXLAN_UDP_PORT
276 except ValueError as e
:
277 self
.logger
.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname
, netlink
.VXLAN_UDP_PORT
, str(e
)))
278 vxlan_port
= netlink
.VXLAN_UDP_PORT
281 cache_port
= vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
)
282 if vxlan_port
!= cache_port
:
283 self
.logger
.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s'
284 % (ifname
, cache_port
, ifname
, ifname
))
285 vxlan_port
= cache_port
287 if self
.should_create_set_vxlan(link_exists
, ifname
, vxlanid
, local
, learning
, ageing
, group
):
289 netlink
.link_add_vxlan(ifname
, vxlanid
,
295 except Exception as e_netlink
:
296 self
.logger
.debug('%s: vxlan netlink: %s' % (ifname
, str(e_netlink
)))
298 self
.ipcmd
.link_create_vxlan(ifname
, vxlanid
,
301 remoteips
=ifaceobj
.get_attr_value('vxlan-remoteip'),
302 learning
='on' if learning
else 'off',
304 except Exception as e_iproute2
:
305 self
.logger
.warning('%s: vxlan add/set failed: %s' % (ifname
, str(e_iproute2
)))
309 # manually adding an entry to the caching after creating/updating the vxlan
310 if not ifname
in linkCache
.links
:
311 linkCache
.links
[ifname
] = {'linkinfo': {}}
312 linkCache
.links
[ifname
]['linkinfo'].update({
313 'learning': learning
,
314 Link
.IFLA_VXLAN_LEARNING
: learning
,
315 'vxlanid': str(vxlanid
),
316 Link
.IFLA_VXLAN_ID
: vxlanid
319 linkCache
.links
[ifname
]['linkinfo'].update({
321 Link
.IFLA_VXLAN_AGEING
: int(ageing
)
326 self
.logger
.info('%s: vxlan already exists' % ifname
)
327 # if the vxlan already exists it's already cached
329 remoteips
= ifaceobj
.get_attr_value('vxlan-remoteip')
332 for remoteip
in remoteips
:
333 IPv4Address(remoteip
)
334 except Exception as e
:
335 self
.log_error('%s: vxlan-remoteip: %s' %(ifaceobj
.name
, str(e
)))
337 if purge_remotes
or remoteips
:
338 # figure out the diff for remotes and do the bridge fdb updates
339 # only if provisioned by user and not by an vxlan external
341 peers
= self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, group
)
342 if local
and remoteips
and local
in remoteips
:
343 remoteips
.remove(local
)
344 cur_peers
= set(peers
)
346 new_peers
= set(remoteips
)
347 del_list
= cur_peers
.difference(new_peers
)
348 add_list
= new_peers
.difference(cur_peers
)
353 for addr
in del_list
:
355 self
.ipcmd
.bridge_fdb_del(ifaceobj
.name
,
361 for addr
in add_list
:
363 self
.ipcmd
.bridge_fdb_append(ifaceobj
.name
,
369 def _up(self
, ifaceobj
):
370 self
._vxlan
_create
(ifaceobj
)
372 def _down(self
, ifaceobj
):
374 self
.ipcmd
.link_delete(ifaceobj
.name
)
376 self
.log_warn(str(e
))
378 def _query_check_n_update(self
, ifaceobj
, ifaceobjcurr
, attrname
, attrval
,
380 if not ifaceobj
.get_attr_value_first(attrname
):
382 if running_attrval
and attrval
== running_attrval
:
383 ifaceobjcurr
.update_config_with_status(attrname
, attrval
, 0)
385 ifaceobjcurr
.update_config_with_status(attrname
, running_attrval
, 1)
387 def _query_check_n_update_addresses(self
, ifaceobjcurr
, attrname
,
388 addresses
, running_addresses
):
391 if a
in running_addresses
:
392 ifaceobjcurr
.update_config_with_status(attrname
, a
, 0)
394 ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
395 running_addresses
= Set(running_addresses
).difference(
397 [ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
398 for a
in running_addresses
]
400 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
401 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
403 # Update vxlan object
404 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobj
.name
)
406 ifaceobjcurr
.check_n_update_config_with_status_many(ifaceobj
,
407 self
.get_mod_attrs(), -1)
409 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-id',
410 ifaceobj
.get_attr_value_first('vxlan-id'),
411 vxlanattrs
.get('vxlanid'))
413 self
._query
_check
_n
_update
(
417 ifaceobj
.get_attr_value_first('vxlan-port'),
418 str(vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
421 running_attrval
= vxlanattrs
.get('local')
422 attrval
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
424 attrval
= vxlan
._vxlan
_local
_tunnelip
425 ifaceobj
.update_config('vxlan-local-tunnelip', attrval
)
427 if running_attrval
== self
._clagd
_vxlan
_anycast
_ip
:
428 # if local ip is anycast_ip, then let query_check to go through
429 attrval
= self
._clagd
_vxlan
_anycast
_ip
430 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-local-tunnelip',
431 attrval
, running_attrval
)
433 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-svcnodeip',
434 ifaceobj
.get_attr_value_first('vxlan-svcnodeip'),
435 vxlanattrs
.get('svcnode'))
437 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
438 if purge_remotes
or ifaceobj
.get_attr_value('vxlan-remoteip'):
439 # If purge remotes or if vxlan-remoteip's are set
440 # in the config file, we are owners of the installed
441 # remote-ip's, lets check and report any remote ips we don't
443 self
._query
_check
_n
_update
_addresses
(ifaceobjcurr
, 'vxlan-remoteip',
444 ifaceobj
.get_attr_value('vxlan-remoteip'),
445 self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, vxlanattrs
.get('svcnode')))
447 learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
449 running_learning
= vxlanattrs
.get('learning')
450 if learning
== 'yes' and running_learning
== 'on':
451 running_learning
= 'yes'
452 elif learning
== 'no' and running_learning
== 'off':
453 running_learning
= 'no'
454 if learning
== running_learning
:
455 ifaceobjcurr
.update_config_with_status('vxlan-learning',
458 ifaceobjcurr
.update_config_with_status('vxlan-learning',
460 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
462 ageing
= self
.get_mod_subattr('vxlan-ageing', 'default')
463 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-ageing',
464 ageing
, vxlanattrs
.get('ageing'))
466 def _query_running(self
, ifaceobjrunning
):
467 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobjrunning
.name
)
470 attrval
= vxlanattrs
.get('vxlanid')
472 ifaceobjrunning
.update_config('vxlan-id', vxlanattrs
.get('vxlanid'))
474 # if there is no vxlan id, this is not a vxlan port
477 ifaceobjrunning
.update_config('vxlan-port', vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
479 attrval
= vxlanattrs
.get('local')
481 ifaceobjrunning
.update_config('vxlan-local-tunnelip', attrval
)
482 attrval
= vxlanattrs
.get('svcnode')
484 ifaceobjrunning
.update_config('vxlan-svcnode', attrval
)
485 purge_remotes
= self
._get
_purge
_remotes
(None)
487 # if purge_remotes is on, it means we own the
488 # remote ips. Query them and add it to the running config
489 attrval
= self
.ipcmd
.get_vxlan_peers(ifaceobjrunning
.name
, vxlanattrs
.get('svcnode'))
491 [ifaceobjrunning
.update_config('vxlan-remoteip', a
)
493 attrval
= vxlanattrs
.get('learning')
494 if attrval
and attrval
== 'on':
495 ifaceobjrunning
.update_config('vxlan-learning', 'on')
496 attrval
= vxlanattrs
.get('ageing')
498 ifaceobjrunning
.update_config('vxlan-ageing', vxlanattrs
.get('ageing'))
500 _run_ops
= {'pre-up' : _up
,
502 'query-checkcurr' : _query_check
,
503 'query-running' : _query_running
}
506 return self
._run
_ops
.keys()
508 def _init_command_handlers(self
):
510 self
.ipcmd
= LinkUtils()
512 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
513 op_handler
= self
._run
_ops
.get(operation
)
516 if (operation
!= 'query-running' and
517 not self
._is
_vxlan
_device
(ifaceobj
)):
519 self
._init
_command
_handlers
()
520 if operation
== 'query-checkcurr':
521 op_handler(self
, ifaceobj
, query_ifaceobj
)
523 op_handler(self
, ifaceobj
)