1 # Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Runtime configuration that applies to all bgp sessions, i.e. global settings.
22 from ryu.lib import ip
24 from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4
25 from ryu.services.protocols.bgp.utils.validation import is_valid_asn
27 from ryu.services.protocols.bgp import rtconf
28 from ryu.services.protocols.bgp.rtconf.base import BaseConf
29 from ryu.services.protocols.bgp.rtconf.base import BaseConfListener
30 from ryu.services.protocols.bgp.rtconf.base import compute_optional_conf
31 from ryu.services.protocols.bgp.rtconf.base import ConfigTypeError
32 from ryu.services.protocols.bgp.rtconf.base import ConfigValueError
33 from ryu.services.protocols.bgp.rtconf.base import MissingRequiredConf
34 from ryu.services.protocols.bgp.rtconf.base import validate
36 LOG = logging.getLogger('bgpspeaker.rtconf.common')
39 # Global configuration settings.
41 ROUTER_ID = 'router_id'
42 CLUSTER_ID = 'cluster_id'
43 LABEL_RANGE = 'label_range'
44 LABEL_RANGE_MAX = 'max'
45 LABEL_RANGE_MIN = 'min'
46 LOCAL_PREF = 'local_pref'
48 # Similar to Cisco command 'allowas-in'. Allows the local ASN in the path.
49 # Facilitates auto rd, auto rt import/export
50 # ("rd auto/route-target both auto") and simplified spine/leaf architectures,
51 # sharing an ASN between e.g. leafs.
52 ALLOW_LOCAL_AS_IN_COUNT = 'allow_local_as_in_count'
54 # Configuration that can be set at global level as well as per context
56 # Nested configuration override global or higher level configuration as they
58 # TODO(apgw-dev) Nested configuration overriding higher level configuration is
59 # currently low priority
61 # Similar to Cisco command 'bgp refresh stalepath-time'. To cause the router to
62 # remove stale routes from the BGP table even if the router does not receive a
63 # Route-Refresh EOR message The bgp refresh stalepath-time command is not
64 # needed under normal circumstances.
65 # TODO(PH): Support this feature (currently low priority)
66 REFRESH_STALEPATH_TIME = 'refresh_stalepath_time'
68 # Similar to Cisco command 'bgp refresh max-eor-time'. The bgp refresh max-eor-
69 # time command is not needed under normal circumstances. You might configure
70 # the bgp refresh max-eor-time command in the event of continuous route
71 # flapping, when the router is unable to generate a Route- Refresh EOR message,
72 # in which case a Route-Refresh EOR is generated after the timer expires.
73 # TODO(PH): Support this feature (currently low priority)
74 REFRESH_MAX_EOR_TIME = 'refresh_max_eor_time'
76 BGP_CONN_RETRY_TIME = 'bgp_conn_retry_time'
77 BGP_SERVER_HOSTS = 'bgp_server_hosts'
78 BGP_SERVER_PORT = 'bgp_server_port'
79 TCP_CONN_TIMEOUT = 'tcp_conn_timeout'
80 MAX_PATH_EXT_RTFILTER_ALL = 'maximum_paths_external_rtfilter_all'
83 # Valid default values of some settings.
84 DEFAULT_LABEL_RANGE = (100, 100000)
85 DEFAULT_REFRESH_STALEPATH_TIME = 0
86 DEFAULT_REFRESH_MAX_EOR_TIME = 0
87 DEFAULT_BGP_SERVER_HOSTS = ('0.0.0.0', '::')
88 DEFAULT_BGP_SERVER_PORT = 179
89 DEFAULT_TCP_CONN_TIMEOUT = 30
90 DEFAULT_BGP_CONN_RETRY_TIME = 30
92 DEFAULT_MAX_PATH_EXT_RTFILTER_ALL = True
93 DEFAULT_LOCAL_PREF = 100
96 @validate(name=ALLOW_LOCAL_AS_IN_COUNT)
97 def validate_allow_local_as_in_count(count):
98 if not isinstance(count, numbers.Integral):
99 raise ConfigTypeError(desc=('Configuration value for %s has to be '
100 'integral type' % ALLOW_LOCAL_AS_IN_COUNT))
102 raise ConfigValueError(desc='Invalid local AS count %s' % count)
107 @validate(name=LOCAL_AS)
108 def validate_local_as(asn):
110 raise MissingRequiredConf(conf_name=LOCAL_AS)
112 if not is_valid_asn(asn):
113 raise ConfigValueError(desc='Invalid local_as configuration value: %s'
118 @validate(name=ROUTER_ID)
119 def validate_router_id(router_id):
121 raise MissingRequiredConf(conf_name=ROUTER_ID)
123 if not isinstance(router_id, str):
124 raise ConfigTypeError(conf_name=ROUTER_ID)
125 if not is_valid_ipv4(router_id):
126 raise ConfigValueError(desc='Invalid router id %s' % router_id)
131 @validate(name=CLUSTER_ID)
132 def validate_router_id(cluster_id):
133 if not isinstance(cluster_id, str):
134 raise ConfigTypeError(conf_name=CLUSTER_ID)
135 if not is_valid_ipv4(cluster_id):
136 raise ConfigValueError(desc='Invalid cluster id %s' % cluster_id)
141 @validate(name=REFRESH_STALEPATH_TIME)
142 def validate_refresh_stalepath_time(rst):
143 if not isinstance(rst, numbers.Integral):
144 raise ConfigTypeError(desc=('Configuration value for %s has to be '
145 'integral type' % REFRESH_STALEPATH_TIME))
147 raise ConfigValueError(desc='Invalid refresh stalepath time %s' % rst)
152 @validate(name=REFRESH_MAX_EOR_TIME)
153 def validate_refresh_max_eor_time(rmet):
154 if not isinstance(rmet, numbers.Integral):
155 raise ConfigTypeError(desc=('Configuration value for %s has to be of '
156 'integral type ' % REFRESH_MAX_EOR_TIME))
158 raise ConfigValueError(desc='Invalid refresh stalepath time %s' % rmet)
163 @validate(name=LABEL_RANGE)
164 def validate_label_range(label_range):
165 min_label, max_label = label_range
168 not isinstance(min_label, numbers.Integral) or
169 not isinstance(max_label, numbers.Integral) or min_label < 17 or
170 min_label >= max_label):
171 raise ConfigValueError(desc=('Invalid label_range configuration value:'
172 ' (%s).' % label_range))
177 @validate(name=BGP_SERVER_HOSTS)
178 def validate_bgp_server_hosts(hosts):
180 if not ip.valid_ipv4(host) and not ip.valid_ipv6(host):
181 raise ConfigTypeError(desc=('Invalid bgp sever hosts '
182 'configuration value %s' % hosts))
187 @validate(name=BGP_SERVER_PORT)
188 def validate_bgp_server_port(server_port):
189 if not isinstance(server_port, numbers.Integral):
190 raise ConfigTypeError(desc=('Invalid bgp sever port configuration '
191 'value %s' % server_port))
192 if server_port < 0 or server_port > 65535:
193 raise ConfigValueError(desc='Invalid server port %s' % server_port)
198 @validate(name=TCP_CONN_TIMEOUT)
199 def validate_tcp_conn_timeout(tcp_conn_timeout):
200 # TODO(apgw-dev) made-up some valid values for this settings, check if we
201 # have a standard value in any routers
202 if not isinstance(tcp_conn_timeout, numbers.Integral):
203 raise ConfigTypeError(desc=('Invalid tcp connection timeout '
204 'configuration value %s' %
207 if tcp_conn_timeout < 10:
208 raise ConfigValueError(desc=('Invalid tcp connection timeout'
209 ' configuration value %s' %
212 return tcp_conn_timeout
215 @validate(name=BGP_CONN_RETRY_TIME)
216 def validate_bgp_conn_retry_time(bgp_conn_retry_time):
217 if not isinstance(bgp_conn_retry_time, numbers.Integral):
218 raise ConfigTypeError(desc=('Invalid bgp conn. retry time '
219 'configuration value %s' %
220 bgp_conn_retry_time))
222 if bgp_conn_retry_time < 10:
223 raise ConfigValueError(desc=('Invalid bgp connection retry time'
224 ' configuration value %s' %
225 bgp_conn_retry_time))
226 return bgp_conn_retry_time
229 @validate(name=MAX_PATH_EXT_RTFILTER_ALL)
230 def validate_max_path_ext_rtfilter_all(max_path_ext_rtfilter_all):
231 if not isinstance(max_path_ext_rtfilter_all, bool):
232 raise ConfigTypeError(desc=('Invalid max_path_ext_rtfilter_all'
233 ' configuration value %s' %
234 max_path_ext_rtfilter_all))
235 return max_path_ext_rtfilter_all
238 @validate(name=LOCAL_PREF)
239 def validate_local_pref(local_pref):
240 if not isinstance(local_pref, numbers.Integral):
241 raise ConfigTypeError(desc=('Invalid local_pref'
242 ' configuration value %s' %
247 class CommonConf(BaseConf):
248 """Encapsulates configurations applicable to all peer sessions.
250 Currently if any of these configurations change, it is assumed that current
251 active peer session will be bought down and restarted.
255 VALID_EVT = frozenset([CONF_CHANGED_EVT])
257 REQUIRED_SETTINGS = frozenset([ROUTER_ID, LOCAL_AS])
259 OPTIONAL_SETTINGS = frozenset([REFRESH_STALEPATH_TIME,
260 REFRESH_MAX_EOR_TIME, LABEL_RANGE,
261 BGP_SERVER_HOSTS, BGP_SERVER_PORT,
264 MAX_PATH_EXT_RTFILTER_ALL,
265 ALLOW_LOCAL_AS_IN_COUNT,
269 def __init__(self, **kwargs):
270 super(CommonConf, self).__init__(**kwargs)
272 def _init_opt_settings(self, **kwargs):
273 super(CommonConf, self)._init_opt_settings(**kwargs)
274 self._settings[ALLOW_LOCAL_AS_IN_COUNT] = compute_optional_conf(
275 ALLOW_LOCAL_AS_IN_COUNT, 0, **kwargs)
276 self._settings[LABEL_RANGE] = compute_optional_conf(
277 LABEL_RANGE, DEFAULT_LABEL_RANGE, **kwargs)
278 self._settings[REFRESH_STALEPATH_TIME] = compute_optional_conf(
279 REFRESH_STALEPATH_TIME, DEFAULT_REFRESH_STALEPATH_TIME, **kwargs)
280 self._settings[REFRESH_MAX_EOR_TIME] = compute_optional_conf(
281 REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_MAX_EOR_TIME, **kwargs)
282 self._settings[BGP_SERVER_HOSTS] = compute_optional_conf(
283 BGP_SERVER_HOSTS, DEFAULT_BGP_SERVER_HOSTS, **kwargs)
284 self._settings[BGP_SERVER_PORT] = compute_optional_conf(
285 BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT, **kwargs)
286 self._settings[TCP_CONN_TIMEOUT] = compute_optional_conf(
287 TCP_CONN_TIMEOUT, DEFAULT_TCP_CONN_TIMEOUT, **kwargs)
288 self._settings[BGP_CONN_RETRY_TIME] = compute_optional_conf(
289 BGP_CONN_RETRY_TIME, DEFAULT_BGP_CONN_RETRY_TIME, **kwargs)
290 self._settings[MAX_PATH_EXT_RTFILTER_ALL] = compute_optional_conf(
291 MAX_PATH_EXT_RTFILTER_ALL, DEFAULT_MAX_PATH_EXT_RTFILTER_ALL,
293 self._settings[CLUSTER_ID] = compute_optional_conf(
294 CLUSTER_ID, kwargs[ROUTER_ID], **kwargs)
295 self._settings[LOCAL_PREF] = compute_optional_conf(
296 LOCAL_PREF, DEFAULT_LOCAL_PREF, **kwargs)
298 # =========================================================================
299 # Required attributes
300 # =========================================================================
304 return self._settings[LOCAL_AS]
308 return self._settings[ROUTER_ID]
310 # =========================================================================
311 # Optional attributes with valid defaults.
312 # =========================================================================
314 def cluster_id(self):
315 return self._settings[CLUSTER_ID]
318 def allow_local_as_in_count(self):
319 return self._settings[ALLOW_LOCAL_AS_IN_COUNT]
322 def bgp_conn_retry_time(self):
323 return self._settings[BGP_CONN_RETRY_TIME]
326 def tcp_conn_timeout(self):
327 return self._settings[TCP_CONN_TIMEOUT]
330 def refresh_stalepath_time(self):
331 return self._settings[REFRESH_STALEPATH_TIME]
334 def refresh_max_eor_time(self):
335 return self._settings[REFRESH_MAX_EOR_TIME]
338 def label_range(self):
339 return self._settings[LABEL_RANGE]
342 def bgp_server_hosts(self):
343 return self._settings[BGP_SERVER_HOSTS]
346 def bgp_server_port(self):
347 return self._settings[BGP_SERVER_PORT]
350 def max_path_ext_rtfilter_all(self):
351 return self._settings[MAX_PATH_EXT_RTFILTER_ALL]
354 def local_pref(self):
355 return self._settings[LOCAL_PREF]
358 def get_opt_settings(self):
359 self_confs = super(CommonConf, self).get_opt_settings()
360 self_confs.update(CommonConf.OPTIONAL_SETTINGS)
364 def get_req_settings(self):
365 self_confs = super(CommonConf, self).get_req_settings()
366 self_confs.update(CommonConf.REQUIRED_SETTINGS)
370 def get_valid_evts(self):
371 self_valid_evts = super(CommonConf, self).get_valid_evts()
372 self_valid_evts.update(CommonConf.VALID_EVT)
373 return self_valid_evts
375 def update(self, **kwargs):
376 """Updates global configuration settings with given values.
378 First checks if given configuration values differ from current values.
379 If any of the configuration values changed, generates a change event.
380 Currently we generate change event for any configuration change.
381 Note: This method is idempotent.
383 # Update inherited configurations
384 super(CommonConf, self).update(**kwargs)
387 # Validate given configurations and check if value changed
388 for conf_name, conf_value in kwargs.items():
389 rtconf.base.get_validator(conf_name)(conf_value)
390 item1 = self._settings.get(conf_name, None)
391 item2 = kwargs.get(conf_name, None)
396 # If any configuration changed, we update configuration value and
399 for conf_name, conf_value in kwargs.items():
400 # Since all new values are already validated, we can use them
401 self._settings[conf_name] = conf_value
403 self._notify_listeners(CommonConf.CONF_CHANGED_EVT, self)
406 class CommonConfListener(BaseConfListener):
407 """Base listener for various changes to common configurations."""
409 def __init__(self, global_conf):
410 super(CommonConfListener, self).__init__(global_conf)
411 global_conf.add_listener(CommonConf.CONF_CHANGED_EVT,
412 self.on_update_common_conf)
414 def on_update_common_conf(self, evt):
415 raise NotImplementedError('This method should be overridden.')