]>
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 {'help': 'vxlan physical device',
80 'example': ['vxlan-physdev eth1']},
83 _clagd_vxlan_anycast_ip
= ""
84 _vxlan_local_tunnelip
= None
86 def __init__(self
, *args
, **kargs
):
87 moduleBase
.__init
__(self
, *args
, **kargs
)
89 purge_remotes
= policymanager
.policymanager_api
.get_module_globals(module_name
=self
.__class
__.__name
__, attr
='vxlan-purge-remotes')
91 self
._purge
_remotes
= utils
.get_boolean_from_string(purge_remotes
)
93 self
._purge
_remotes
= False
95 def syntax_check(self
, ifaceobj
, ifaceobj_getfunc
):
96 if self
._is
_vxlan
_device
(ifaceobj
):
97 if not ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') and not vxlan
._vxlan
_local
_tunnelip
:
98 self
.logger
.warning('%s: missing vxlan-local-tunnelip' % ifaceobj
.name
)
100 return self
.syntax_check_localip_anycastip_equal(
102 ifaceobj
.get_attr_value_first('vxlan-local-tunnelip') or vxlan
._vxlan
_local
_tunnelip
,
103 vxlan
._clagd
_vxlan
_anycast
_ip
107 def syntax_check_localip_anycastip_equal(self
, ifname
, local_ip
, anycast_ip
):
109 if IPNetwork(local_ip
) == IPNetwork(anycast_ip
):
110 self
.logger
.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
111 % (ifname
, local_ip
))
117 def get_dependent_ifacenames(self
, ifaceobj
, ifaceobjs_all
=None):
118 if self
._is
_vxlan
_device
(ifaceobj
):
119 ifaceobj
.link_kind |
= ifaceLinkKind
.VXLAN
120 self
._set
_global
_local
_ip
(ifaceobj
)
121 elif ifaceobj
.name
== 'lo':
122 clagd_vxlan_list
= ifaceobj
.get_attr_value('clagd-vxlan-anycast-ip')
124 if len(clagd_vxlan_list
) != 1:
125 self
.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
127 vxlan
._clagd
_vxlan
_anycast
_ip
= clagd_vxlan_list
[0]
129 self
._set
_global
_local
_ip
(ifaceobj
)
131 # If we should use a specific underlay device for the VXLAN
132 # tunnel make sure this device is set up before the VXLAN iface.
133 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
140 def _set_global_local_ip(self
, ifaceobj
):
141 vxlan_local_tunnel_ip
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
142 if vxlan_local_tunnel_ip
and not vxlan
._vxlan
_local
_tunnelip
:
143 vxlan
._vxlan
_local
_tunnelip
= vxlan_local_tunnel_ip
145 def _is_vxlan_device(self
, ifaceobj
):
146 if ifaceobj
.get_attr_value_first('vxlan-id'):
150 def _get_purge_remotes(self
, ifaceobj
):
152 return self
._purge
_remotes
153 purge_remotes
= ifaceobj
.get_attr_value_first('vxlan-purge-remotes')
155 purge_remotes
= utils
.get_boolean_from_string(purge_remotes
)
157 purge_remotes
= self
._purge
_remotes
160 def should_create_set_vxlan(self
, link_exists
, ifname
, vxlan_id
, local
, learning
, ageing
, group
):
162 should we issue a netlink: ip link add dev %ifname type vxlan ...?
163 checking each attribute against the cache
174 for attr_list
, value
in (
175 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_ID
), vxlan_id
),
176 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_AGEING
), ageing
),
177 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LOCAL
), local
),
178 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_LEARNING
), learning
),
179 ((ifname
, 'linkinfo', Link
.IFLA_VXLAN_GROUP
), group
),
181 if value
and not self
.ipcmd
.cache_check(attr_list
, value
):
185 def _vxlan_create(self
, ifaceobj
):
186 vxlanid
= ifaceobj
.get_attr_value_first('vxlan-id')
188 ifname
= ifaceobj
.name
189 anycastip
= self
._clagd
_vxlan
_anycast
_ip
190 group
= ifaceobj
.get_attr_value_first('vxlan-svcnodeip')
192 local
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
193 if not local
and vxlan
._vxlan
_local
_tunnelip
:
194 local
= vxlan
._vxlan
_local
_tunnelip
196 self
.syntax_check_localip_anycastip_equal(ifname
, local
, anycastip
)
197 # if both local-ip and anycast-ip are identical the function prints a warning
199 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
200 vxlan_port
= ifaceobj
.get_attr_value_first('vxlan-port')
201 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
202 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
204 link_exists
= self
.ipcmd
.link_exists(ifname
)
206 if (not link_exists
or
207 not ifaceobj
.link_privflags
& ifaceLinkPrivFlags
.BRIDGE_PORT
):
208 vxlan_learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
209 if not vxlan_learning
:
210 vxlan_learning
= self
.get_attr_default_value('vxlan-learning')
211 learning
= utils
.get_boolean_from_string(vxlan_learning
)
213 learning
= utils
.get_boolean_from_string(
214 self
.ipcmd
.get_vxlandev_learning(ifname
))
217 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifname
)
218 # on ifreload do not overwrite anycast_ip to individual ip
219 # if clagd has modified
221 running_localtunnelip
= vxlanattrs
.get('local')
222 if (anycastip
and running_localtunnelip
and
223 anycastip
== running_localtunnelip
):
224 local
= running_localtunnelip
225 if vxlanattrs
.get('vxlanid') != vxlanid
:
226 self
.log_error('%s: Cannot change running vxlan id: '
227 'Operation not supported' % ifname
, ifaceobj
)
229 vxlanid
= int(vxlanid
)
231 self
.log_error('%s: invalid vxlan-id \'%s\'' % (ifname
, vxlanid
), ifaceobj
)
234 group
= policymanager
.policymanager_api
.get_attr_default(
235 module_name
=self
.__class
__.__name
__,
236 attr
='vxlan-svcnodeip'
241 group
= IPv4Address(group
)
242 except AddressValueError
:
244 group_ip
= IPv4Network(group
).ip
245 self
.logger
.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname
, group
))
248 raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname
, group
))
251 local
= policymanager
.policymanager_api
.get_attr_default(
252 module_name
=self
.__class
__.__name
__,
253 attr
='vxlan-local-tunnelip'
258 local
= IPv4Address(local
)
259 except AddressValueError
:
261 local_ip
= IPv4Network(local
).ip
262 self
.logger
.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname
, local
))
265 raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname
, local
))
268 ageing
= policymanager
.policymanager_api
.get_attr_default(
269 module_name
=self
.__class
__.__name
__,
273 if not ageing
and link_exists
:
274 # if link doesn't exist we let the kernel define ageing
275 ageing
= self
.get_attr_default_value('vxlan-ageing')
278 vxlan_port
= policymanager
.policymanager_api
.get_attr_default(
279 module_name
=self
.__class
__.__name
__,
284 vxlan_port
= int(vxlan_port
)
286 # TypeError means vxlan_port was None
287 # ie: not provided by the user or the policy
288 vxlan_port
= netlink
.VXLAN_UDP_PORT
289 except ValueError as e
:
290 self
.logger
.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname
, netlink
.VXLAN_UDP_PORT
, str(e
)))
291 vxlan_port
= netlink
.VXLAN_UDP_PORT
294 cache_port
= vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
)
295 if vxlan_port
!= cache_port
:
296 self
.logger
.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s'
297 % (ifname
, cache_port
, ifname
, ifname
))
298 vxlan_port
= cache_port
300 if self
.should_create_set_vxlan(link_exists
, ifname
, vxlanid
, local
, learning
, ageing
, group
):
302 netlink
.link_add_vxlan(ifname
, vxlanid
,
309 except Exception as e_netlink
:
310 self
.logger
.debug('%s: vxlan netlink: %s' % (ifname
, str(e_netlink
)))
312 self
.ipcmd
.link_create_vxlan(ifname
, vxlanid
,
315 remoteips
=ifaceobj
.get_attr_value('vxlan-remoteip'),
316 learning
='on' if learning
else 'off',
318 except Exception as e_iproute2
:
319 self
.logger
.warning('%s: vxlan add/set failed: %s' % (ifname
, str(e_iproute2
)))
323 # manually adding an entry to the caching after creating/updating the vxlan
324 if not ifname
in linkCache
.links
:
325 linkCache
.links
[ifname
] = {'linkinfo': {}}
326 linkCache
.links
[ifname
]['linkinfo'].update({
327 'learning': learning
,
328 Link
.IFLA_VXLAN_LEARNING
: learning
,
329 'vxlanid': str(vxlanid
),
330 Link
.IFLA_VXLAN_ID
: vxlanid
333 linkCache
.links
[ifname
]['linkinfo'].update({
335 Link
.IFLA_VXLAN_AGEING
: int(ageing
)
340 self
.logger
.info('%s: vxlan already exists' % ifname
)
341 # if the vxlan already exists it's already cached
343 remoteips
= ifaceobj
.get_attr_value('vxlan-remoteip')
346 for remoteip
in remoteips
:
347 IPv4Address(remoteip
)
348 except Exception as e
:
349 self
.log_error('%s: vxlan-remoteip: %s' %(ifaceobj
.name
, str(e
)))
351 if purge_remotes
or remoteips
:
352 # figure out the diff for remotes and do the bridge fdb updates
353 # only if provisioned by user and not by an vxlan external
355 peers
= self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, group
)
356 if local
and remoteips
and local
in remoteips
:
357 remoteips
.remove(local
)
358 cur_peers
= set(peers
)
360 new_peers
= set(remoteips
)
361 del_list
= cur_peers
.difference(new_peers
)
362 add_list
= new_peers
.difference(cur_peers
)
367 for addr
in del_list
:
369 self
.ipcmd
.bridge_fdb_del(ifaceobj
.name
,
375 for addr
in add_list
:
377 self
.ipcmd
.bridge_fdb_append(ifaceobj
.name
,
383 def _up(self
, ifaceobj
):
384 self
._vxlan
_create
(ifaceobj
)
386 def _down(self
, ifaceobj
):
388 self
.ipcmd
.link_delete(ifaceobj
.name
)
390 self
.log_warn(str(e
))
392 def _query_check_n_update(self
, ifaceobj
, ifaceobjcurr
, attrname
, attrval
,
394 if not ifaceobj
.get_attr_value_first(attrname
):
396 if running_attrval
and attrval
== running_attrval
:
397 ifaceobjcurr
.update_config_with_status(attrname
, attrval
, 0)
399 ifaceobjcurr
.update_config_with_status(attrname
, running_attrval
, 1)
401 def _query_check_n_update_addresses(self
, ifaceobjcurr
, attrname
,
402 addresses
, running_addresses
):
405 if a
in running_addresses
:
406 ifaceobjcurr
.update_config_with_status(attrname
, a
, 0)
408 ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
409 running_addresses
= Set(running_addresses
).difference(
411 [ifaceobjcurr
.update_config_with_status(attrname
, a
, 1)
412 for a
in running_addresses
]
414 def _query_check(self
, ifaceobj
, ifaceobjcurr
):
415 if not self
.ipcmd
.link_exists(ifaceobj
.name
):
417 # Update vxlan object
418 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobj
.name
)
420 ifaceobjcurr
.check_n_update_config_with_status_many(ifaceobj
,
421 self
.get_mod_attrs(), -1)
423 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-id',
424 ifaceobj
.get_attr_value_first('vxlan-id'),
425 vxlanattrs
.get('vxlanid'))
427 self
._query
_check
_n
_update
(
431 ifaceobj
.get_attr_value_first('vxlan-port'),
432 str(vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
435 running_attrval
= vxlanattrs
.get('local')
436 attrval
= ifaceobj
.get_attr_value_first('vxlan-local-tunnelip')
438 attrval
= vxlan
._vxlan
_local
_tunnelip
439 ifaceobj
.update_config('vxlan-local-tunnelip', attrval
)
441 if running_attrval
== self
._clagd
_vxlan
_anycast
_ip
:
442 # if local ip is anycast_ip, then let query_check to go through
443 attrval
= self
._clagd
_vxlan
_anycast
_ip
444 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-local-tunnelip',
445 attrval
, running_attrval
)
447 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-svcnodeip',
448 ifaceobj
.get_attr_value_first('vxlan-svcnodeip'),
449 vxlanattrs
.get('svcnode'))
451 purge_remotes
= self
._get
_purge
_remotes
(ifaceobj
)
452 if purge_remotes
or ifaceobj
.get_attr_value('vxlan-remoteip'):
453 # If purge remotes or if vxlan-remoteip's are set
454 # in the config file, we are owners of the installed
455 # remote-ip's, lets check and report any remote ips we don't
457 self
._query
_check
_n
_update
_addresses
(ifaceobjcurr
, 'vxlan-remoteip',
458 ifaceobj
.get_attr_value('vxlan-remoteip'),
459 self
.ipcmd
.get_vxlan_peers(ifaceobj
.name
, vxlanattrs
.get('svcnode')))
461 learning
= ifaceobj
.get_attr_value_first('vxlan-learning')
463 running_learning
= vxlanattrs
.get('learning')
464 if learning
== 'yes' and running_learning
== 'on':
465 running_learning
= 'yes'
466 elif learning
== 'no' and running_learning
== 'off':
467 running_learning
= 'no'
468 if learning
== running_learning
:
469 ifaceobjcurr
.update_config_with_status('vxlan-learning',
472 ifaceobjcurr
.update_config_with_status('vxlan-learning',
474 ageing
= ifaceobj
.get_attr_value_first('vxlan-ageing')
476 ageing
= self
.get_mod_subattr('vxlan-ageing', 'default')
477 self
._query
_check
_n
_update
(ifaceobj
, ifaceobjcurr
, 'vxlan-ageing',
478 ageing
, vxlanattrs
.get('ageing'))
480 physdev
= ifaceobj
.get_attr_value_first('vxlan-physdev')
483 ifla_vxlan_link
= vxlanattrs
.get(Link
.IFLA_VXLAN_LINK
)
486 self
._query
_check
_n
_update
(
491 netlink
.get_iface_name(ifla_vxlan_link
)
494 ifaceobjcurr
.update_config_with_status('vxlan-physdev', physdev
, 1)
497 def _query_running(self
, ifaceobjrunning
):
498 vxlanattrs
= self
.ipcmd
.get_vxlandev_attrs(ifaceobjrunning
.name
)
501 attrval
= vxlanattrs
.get('vxlanid')
503 ifaceobjrunning
.update_config('vxlan-id', vxlanattrs
.get('vxlanid'))
505 # if there is no vxlan id, this is not a vxlan port
508 ifaceobjrunning
.update_config('vxlan-port', vxlanattrs
.get(Link
.IFLA_VXLAN_PORT
))
510 attrval
= vxlanattrs
.get('local')
512 ifaceobjrunning
.update_config('vxlan-local-tunnelip', attrval
)
513 attrval
= vxlanattrs
.get('svcnode')
515 ifaceobjrunning
.update_config('vxlan-svcnode', attrval
)
516 purge_remotes
= self
._get
_purge
_remotes
(None)
518 # if purge_remotes is on, it means we own the
519 # remote ips. Query them and add it to the running config
520 attrval
= self
.ipcmd
.get_vxlan_peers(ifaceobjrunning
.name
, vxlanattrs
.get('svcnode'))
522 [ifaceobjrunning
.update_config('vxlan-remoteip', a
)
524 attrval
= vxlanattrs
.get('learning')
525 if attrval
and attrval
== 'on':
526 ifaceobjrunning
.update_config('vxlan-learning', 'on')
527 attrval
= vxlanattrs
.get('ageing')
529 ifaceobjrunning
.update_config('vxlan-ageing', vxlanattrs
.get('ageing'))
531 ifla_vxlan_link
= vxlanattrs
.get(Link
.IFLA_VXLAN_LINK
)
533 ifaceobjrunning
.update_config(
535 netlink
.get_iface_name(ifla_vxlan_link
)
538 _run_ops
= {'pre-up' : _up
,
540 'query-checkcurr' : _query_check
,
541 'query-running' : _query_running
}
544 return self
._run
_ops
.keys()
546 def _init_command_handlers(self
):
548 self
.ipcmd
= LinkUtils()
550 def run(self
, ifaceobj
, operation
, query_ifaceobj
=None, **extra_args
):
551 op_handler
= self
._run
_ops
.get(operation
)
554 if (operation
!= 'query-running' and
555 not self
._is
_vxlan
_device
(ifaceobj
)):
557 self
._init
_command
_handlers
()
558 if operation
== 'query-checkcurr':
559 op_handler(self
, ifaceobj
, query_ifaceobj
)
561 op_handler(self
, ifaceobj
)