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 Module for RT Filter related functionality.
21 from ryu.lib.packet.bgp import RF_RTC_UC
22 from ryu.lib.packet.bgp import RouteTargetMembershipNLRI
23 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
24 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
25 from ryu.lib.packet.bgp import BGPPathAttributeAsPath
26 from ryu.lib.packet.bgp import BGPPathAttributeOrigin
27 from ryu.services.protocols.bgp.base import OrderedDict
28 from ryu.services.protocols.bgp.info_base.rtc import RtcPath
30 LOG = logging.getLogger('bgpspeaker.util.rtfilter')
33 class RouteTargetManager(object):
34 def __init__(self, core_service, neighbors_conf, vrfs_conf):
35 self._core_service = core_service
36 # TODO(PH): Consider extending VrfsConfListener and
37 # NeighborsConfListener
38 self._neighbors_conf = neighbors_conf
39 self._vrfs_conf = vrfs_conf
41 # Peer to its current RT filter map
42 # <key>/value = <peer_ip>/<rt filter set>
43 self._peer_to_rtfilter_map = {}
45 # Collection of import RTs of all configured VRFs
46 self._all_vrfs_import_rts_set = set()
48 # Collection of current RTC AS for all configured Neighbors
49 self._all_rtc_as_set = set()
50 # Interested RTs according to current entries in RTC global table
51 self._global_interested_rts = set()
54 def peer_to_rtfilter_map(self):
55 return self._peer_to_rtfilter_map.copy()
57 @peer_to_rtfilter_map.setter
58 def peer_to_rtfilter_map(self, new_map):
59 self._peer_to_rtfilter_map = new_map.copy()
62 def global_interested_rts(self):
63 return set(self._global_interested_rts)
65 def add_rt_nlri(self, route_target, is_withdraw=False):
67 # Since we allow RTC AS setting for each neighbor, we collect all we
68 # collect allRTC AS settings and add a RT NLRI using each AS number
70 # Add RT NLRI with local AS
71 rtc_as_set.add(self._core_service.asn)
72 # Collect RTC AS from neighbor settings
73 rtc_as_set.update(self._neighbors_conf.rtc_as_set)
74 # Add RT NLRI path (withdraw) for each RTC AS
75 for rtc_as in rtc_as_set:
76 self._add_rt_nlri_for_as(rtc_as, route_target, is_withdraw)
78 def _add_rt_nlri_for_as(self, rtc_as, route_target, is_withdraw=False):
79 from ryu.services.protocols.bgp.core import EXPECTED_ORIGIN
80 rt_nlri = RouteTargetMembershipNLRI(rtc_as, route_target)
81 # Create a dictionary for path-attrs.
82 pattrs = OrderedDict()
84 # MpReachNlri and/or MpUnReachNlri attribute info. is contained
85 # in the path. Hence we do not add these attributes here.
86 pattrs[BGP_ATTR_TYPE_ORIGIN] = BGPPathAttributeOrigin(
88 pattrs[BGP_ATTR_TYPE_AS_PATH] = BGPPathAttributeAsPath([])
90 # Create Path instance and initialize appropriately.
91 path = RtcPath(None, rt_nlri, 0, is_withdraw=is_withdraw,
93 tm = self._core_service.table_manager
96 def update_rtc_as_set(self):
97 """Syncs RT NLRIs for new and removed RTC_ASes.
99 This method should be called when a neighbor is added or removed.
101 # Compute the diffs in RTC_ASes
102 curr_rtc_as_set = self._neighbors_conf.rtc_as_set
103 # Always add local AS to RTC AS set
104 curr_rtc_as_set.add(self._core_service.asn)
105 removed_rtc_as_set = self._all_rtc_as_set - curr_rtc_as_set
106 new_rtc_as_set = curr_rtc_as_set - self._all_rtc_as_set
108 # Update to new RTC_AS set
109 self._all_rtc_as_set = curr_rtc_as_set
111 # Sync RT NLRI by adding/withdrawing as appropriate
112 for new_rtc_as in new_rtc_as_set:
113 for import_rt in self._all_vrfs_import_rts_set:
114 self._add_rt_nlri_for_as(new_rtc_as, import_rt)
115 for removed_rtc_as in removed_rtc_as_set:
116 for import_rt in self._all_vrfs_import_rts_set:
117 self._add_rt_nlri_for_as(removed_rtc_as, import_rt,
120 def update_local_rt_nlris(self):
121 """Does book-keeping of local RT NLRIs based on all configured VRFs.
123 Syncs all import RTs and RT NLRIs.
124 The method should be called when any VRFs are added/removed/changed.
126 current_conf_import_rts = set()
127 for vrf in self._vrfs_conf.vrf_confs:
128 current_conf_import_rts.update(vrf.import_rts)
130 removed_rts = self._all_vrfs_import_rts_set - current_conf_import_rts
131 new_rts = current_conf_import_rts - self._all_vrfs_import_rts_set
132 self._all_vrfs_import_rts_set = current_conf_import_rts
134 # Add new and withdraw removed local RtNlris
135 for new_rt in new_rts:
136 self.add_rt_nlri(new_rt)
137 for removed_rt in removed_rts:
138 self.add_rt_nlri(removed_rt, is_withdraw=True)
140 def on_rt_filter_chg_sync_peer(self, peer, new_rts, old_rts, table):
141 LOG.debug('RT Filter changed for peer %s, new_rts %s, old_rts %s ',
142 peer, new_rts, old_rts)
143 for dest in table.values():
144 # If this destination does not have best path, we ignore it
145 if not dest.best_path:
148 desired_rts = set(dest.best_path.get_rts())
150 # If this path was sent to peer and if all path RTs are now not of
151 # interest, we need to send withdraw for this path to this peer
152 if dest.was_sent_to(peer):
153 if not (desired_rts - old_rts):
154 dest.withdraw_if_sent_to(peer)
156 # New RT could be Default RT, which means we need to share this
158 desired_rts.add(RouteTargetMembershipNLRI.DEFAULT_RT)
159 # If we have RT filter has new RTs that are common with path
160 # RTs, then we send this path to peer
161 if desired_rts.intersection(new_rts):
162 peer.communicate_path(dest.best_path)
164 def _compute_global_interested_rts(self):
165 """Computes current global interested RTs for global tables.
167 Computes interested RTs based on current RT filters for peers. This
168 filter should be used to check if for RTs on a path that is installed
169 in any global table (expect RT Table).
171 interested_rts = set()
172 for rtfilter in self._peer_to_rtfilter_map.values():
173 interested_rts.update(rtfilter)
175 interested_rts.update(self._vrfs_conf.vrf_interested_rts)
176 # Remove default RT as it is not a valid RT for paths
177 # TODO(PH): Check if we have better alternative than add and remove
178 interested_rts.add(RouteTargetMembershipNLRI.DEFAULT_RT)
179 interested_rts.remove(RouteTargetMembershipNLRI.DEFAULT_RT)
180 return interested_rts
182 def update_interested_rts(self):
183 """Updates interested RT list.
185 Check if interested RTs have changes from previous check.
186 Takes appropriate action for new interesting RTs and removal of un-
189 prev_global_rts = self._global_interested_rts
190 curr_global_rts = self._compute_global_interested_rts()
192 new_global_rts = curr_global_rts - prev_global_rts
193 removed_global_rts = prev_global_rts - curr_global_rts
195 # Update current interested RTs for next iteration
196 self._global_interested_rts = curr_global_rts
198 LOG.debug('Global Interested RT changed, new RTs %s, removed RTs %s',
199 new_global_rts, removed_global_rts)
200 tm = self._core_service.table_manager
201 tm.on_interesting_rts_change(new_global_rts, removed_global_rts)
203 def filter_by_origin_as(self, new_best_path, qualified_peers):
204 path_rf = new_best_path.route_family
206 # We need not filter any peer if this is not a RT NLRI path or if
207 # source of this path is remote peer (i.e. if this is not a local path)
208 if path_rf != RF_RTC_UC or new_best_path.source is not None:
209 return qualified_peers
211 filtered_qualified_peers = []
212 rt_origin_as = new_best_path.nlri.origin_as
213 # Collect peers whose RTC_AS setting match paths RT Origin AS
214 for qualified_peer in qualified_peers:
216 self._neighbors_conf.get_neighbor_conf(
217 qualified_peer.ip_address)
218 if neigh_conf.rtc_as == rt_origin_as:
219 filtered_qualified_peers.append(qualified_peer)
221 # Update qualified peers to include only filtered peers
222 return filtered_qualified_peers