def _process_received_route(self, action, nlri, attributes): self.log.info("Received route: %s, %s", nlri, attributes) route_entry = engine.RouteEntry(nlri, None, attributes) if action == exa_message.IN.ANNOUNCED: self._advertise_route(route_entry) elif action == exa_message.IN.WITHDRAWN: self._withdraw_route(route_entry) else: raise Exception("unsupported action ??? (%s)" % action) # TODO(tmmorin): move RTC code out-of the peer-specific code if (nlri.afi, nlri.safi) == (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)): self.log.info("Received an RTC route") if nlri.rt is None: self.log.info("Received RTC is a wildcard") # the semantic of RTC routes does not distinguish between AFI/SAFIs # if our peer subscribed to a Route Target, it means that we needs # to send him all routes of any AFI/SAFI carrying this RouteTarget. for (afi, safi) in self._active_families: if (afi, safi) != (exa.AFI(exa.AFI.ipv4), exa.SAFI( exa.SAFI.rtc)): if action == exa_message.IN.ANNOUNCED: self._subscribe(afi, safi, nlri.rt) elif action == exa_message.IN.WITHDRAWN: self._unsubscribe(afi, safi, nlri.rt) else: raise Exception("unsupported action ??? (%s)" % action)
def test_7_matches(self): m1a = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), exa.RouteTarget(64512, 1)) m1b = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), exa.RouteTarget(64512, 1)) m1c = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), exa.RouteTarget(64512, 1, False)) m2 = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), exa.RouteTarget(64512, 2)) m3 = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), exa.RouteTarget(64513, 1)) self.assertEqual(hash(m1a), hash(m1b)) self.assertEqual(hash(m1a), hash(m1c)) self.assertNotEqual(hash(m1a), hash(m2)) self.assertNotEqual(hash(m1a), hash(m3)) self.assertEqual(m1a, m1b) self.assertEqual(m1a, m1c) self.assertNotEqual(m1a, m2) self.assertNotEqual(m1a, m3)
def _new_flow_event(self, event_type, nlri, to_rts, attract_rts, source, afi=exa.AFI(exa.AFI.ipv4), safi=exa.SAFI(exa.SAFI.flow_vpn), **kwargs): attributes = exa.Attributes() ecommunities = exa.ExtendedCommunities() ecommunities.communities.append( exa.TrafficRedirect(exa.ASN(int(to_rts[0].asn)), int(to_rts[0].number)) ) attributes.add(ecommunities) flow_event = engine.RouteEvent(event_type, engine.RouteEntry(nlri, attract_rts, attributes, source), source) self.event_target_worker.enqueue(flow_event) LOG.info("*** Emitting FlowSpec event to %s: %s", self.event_target_worker, flow_event) self._wait() return flow_event
def _new_route_event(self, event_type, nlri, rts, source, nh, lp=0, replaced_route_entry=None, afi=exa.AFI(exa.AFI.ipv4), safi=exa.SAFI(exa.SAFI.mpls_vpn), **kwargs): attributes = exa.Attributes() attributes.add(exa.NextHop(nh)) attributes.add(exa.LocalPreference(lp)) if 'rtrecords' in kwargs: ecoms = exa.ExtendedCommunities() ecoms.communities += kwargs['rtrecords'] attributes.add(ecoms) route_event = engine.RouteEvent(event_type, engine.RouteEntry(nlri, rts, attributes, source), source) route_event.set_replaced_route(replaced_route_entry) LOG.info("*** Emitting event to %s: %s", self.event_target_worker, route_event) self.event_target_worker._on_event(route_event) return route_event
def _worker_subscriptions(self, worker, rts, afi=exa.AFI(exa.AFI.ipv4), safi=exa.SAFI(exa.SAFI.mpls_vpn)): for rt in rts: subscribe = engine.Subscription(afi, safi, rt, worker) self.rtm._on_event(subscribe)
def update_route_targets(self, new_import_rts, new_export_rts): added_import_rt = set(new_import_rts) - set(self.import_rts) removed_import_rt = set(self.import_rts) - set(new_import_rts) self.log.debug("%s %d - Added Import RTs: %s", self.instance_type, self.instance_id, added_import_rt) self.log.debug("%s %d - Removed Import RTs: %s", self.instance_type, self.instance_id, removed_import_rt) # Register to BGP with these route targets for rt in added_import_rt: self._subscribe(self.afi, self.safi, rt) self._subscribe(self.afi, exa.SAFI(exa.SAFI.flow_vpn), rt) # Unregister from BGP with these route targets for rt in removed_import_rt: self._unsubscribe(self.afi, self.safi, rt) self._unsubscribe(self.afi, exa.SAFI(exa.SAFI.flow_vpn), rt) # Update import and export route targets self.import_rts = new_import_rts # Re-advertise all routes with new export RTs self.log.debug("Exports RTs: %s -> %s", self.export_rts, new_export_rts) if frozenset(new_export_rts) != frozenset(self.export_rts): self.log.debug("Will re-export routes with new RTs") self.export_rts = new_export_rts # FIXME: we should only update the routes that # are routes of ports plugged to the VPN instance, # not all routes which would wrongly include # routes that we re-advertise between RTs for route_entry in self.get_route_entries(): self.log.info("Re-advertising route %s with updated RTs (%s)", route_entry.nlri, new_export_rts) updated_route_entry = engine.RouteEntry( route_entry.nlri, None, copy.copy(route_entry.attributes)) # reset the route_targets # will RTs originally present in route_entry.attributes updated_route_entry.set_route_targets(self.export_rts) self.log.debug(" updated route: %s", updated_route_entry) self._advertise_route(updated_route_entry)
def _subscription_2_rtc_route_entry(self, subscription): nlri = exa.RTC.new(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc), cfg.CONF.BGP.my_as, subscription.route_target, exa.IP.create(self.get_local_address())) route_entry = engine.RouteEntry(nlri) return route_entry
def _worker_subscriptions(self, worker, rts, wait=True, afi=exa.AFI(exa.AFI.ipv4), safi=exa.SAFI(exa.SAFI.mpls_vpn)): for rt in rts: subscribe = engine.Subscription(afi, safi, rt, worker) self.rtm.enqueue(subscribe) if wait: self._wait()
def _to_established(self): super(ExaBGPPeerWorker, self)._to_established() if self.rtc_active: self.log.debug("RTC active, subscribing to all RTC routes") # subscribe to RTC routes, to be able to propagate them from # internal workers to this peer self._subscribe(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)) else: self.log.debug("RTC inactive, subscribing to all active families") # if we don't use RTC with our peer, then we need to see events for # all routes of all active families, to be able to send them to him for (afi, safi) in self._active_families: self._subscribe(afi, safi)
def test_f1_test_empty_rt(self): # worker advertises a route with no RT w1 = self._new_worker("Worker1", worker.Worker) subscribe = engine.Subscription(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), None, w1) self.rtm.enqueue(subscribe) w2 = self._new_worker("Worker2", worker.Worker) route_event = engine.RouteEvent( engine.RouteEvent.ADVERTISE, engine.RouteEntry(t.NLRI1, None, exa.Attributes()), w2) self.rtm.enqueue(route_event) self._wait() self.assertEqual( 1, w1.enqueue.call_count, "1 route advertised should be synthesized to Worker1")
def _new_flow_event(self, event_type, nlri, to_rts, attract_rts, source, afi=exa.AFI(exa.AFI.ipv4), safi=exa.SAFI(exa.SAFI.flow_vpn), **kwargs): attributes = exa.Attributes() ecommunities = exa.ExtendedCommunities() ecommunities.communities.append( exa.TrafficRedirect(exa.ASN(int(to_rts[0].asn)), int(to_rts[0].number)) ) attributes.add(ecommunities) flow_event = engine.RouteEvent(event_type, engine.RouteEntry(nlri, attract_rts, attributes, source), source) self.event_target_worker._on_event(flow_event) return flow_event
def __init__(self, desc): self.desc = desc self.action = None self.afi = exa.AFI(exa.AFI.ipv4) self.safi = exa.SAFI(exa.SAFI.mpls_vpn)
class VRF(vpn_instance.VPNInstance, lg.LookingGlassMixin): # component managing a VRF: # - calling a driver to instantiate the dataplane # - registering to receive routes for the needed route targets # - calling the driver to setup/update/remove routes in the dataplane # - cleanup: calling the driver, unregistering for BGP routes type = constants.IPVPN afi = exa.AFI(exa.AFI.ipv4) safi = exa.SAFI(exa.SAFI.mpls_vpn) @log_decorator.log def __init__(self, *args, **kwargs): vpn_instance.VPNInstance.__init__(self, *args, **kwargs) self.readvertised = set() @classmethod def validate_convert_attach_params(cls, params): super(VRF, cls).validate_convert_attach_params(params) if 'gateway_ip' not in params: raise exc.APIMissingParameterException('gateway_ip') def _nlri_from(self, prefix, label, rd): assert rd is not None return ipvpn_routes.IPVPNRouteFactory( self.afi, prefix, label, rd, self.dp_driver.get_local_address()) def generate_vif_bgp_route(self, mac_address, ip_prefix, plen, label, rd): # Generate BGP route and advertise it... nlri = self._nlri_from("%s/%s" % (ip_prefix, plen), label, rd) return engine.RouteEntry(nlri) def _get_local_labels(self): for port_data in self.mac_2_localport_data.values(): yield port_data['label'] def _imported(self, route): return len(set(route.route_targets).intersection(set(self.import_rts)) ) > 0 def _to_readvertise(self, route): # Only re-advertise IP VPN routes (e.g. not Flowspec routes) if not isinstance(route.nlri, ipvpn_routes.IPVPN): return False rt_records = route.ecoms(exa.RTRecord) self.log.debug("RTRecords: %s (readvertise_to_rts:%s)", rt_records, self.readvertise_to_rts) readvertise_targets_as_records = [exa.RTRecord.from_rt(rt) for rt in self.readvertise_to_rts] if self.attract_traffic: readvertise_targets_as_records += [exa.RTRecord.from_rt(rt) for rt in self.attract_rts] if set(readvertise_targets_as_records).intersection(set(rt_records)): self.log.debug("not to re-advertise because one of the readvertise" " or attract-redirect RTs is in RTRecords: %s", set(readvertise_targets_as_records) .intersection(set(rt_records))) return False return len(set(route.route_targets).intersection( set(self.readvertise_from_rts) )) > 0 def _route_for_readvertisement(self, route, label, rd, lb_consistent_hash_order, do_default=False): prefix = "0.0.0.0/0" if do_default else route.nlri.cidr.prefix() nlri = self._nlri_from(prefix, label, rd) attributes = exa.Attributes() # new RTRecord = original RTRecord (if any) + orig RTs converted into # RTRecord attributes orig_rtrecords = route.ecoms(exa.RTRecord) rts = route.ecoms(exa.RTExtCom) add_rtrecords = [exa.RTRecord.from_rt(rt) for rt in rts] final_rtrecords = list(set(orig_rtrecords) | set(add_rtrecords)) ecoms = self._gen_encap_extended_communities() ecoms.communities += final_rtrecords ecoms.communities.append( exa.ConsistentHashSortOrder(lb_consistent_hash_order)) attributes.add(ecoms) entry = engine.RouteEntry(nlri, self.readvertise_to_rts, attributes) self.log.debug("RouteEntry for (re-)advertisement: %s", entry) return entry @log_decorator.log def _route_for_redirect_prefix(self, prefix): prefix_classifier = utils.dict_camelcase_to_underscore( self.attract_classifier) prefix_classifier['destination_prefix'] = prefix traffic_classifier = vpn_instance.TrafficClassifier( **prefix_classifier) self.log.debug("Advertising prefix %s for redirection based on " "traffic classifier %s", prefix, traffic_classifier) rules = traffic_classifier.map_traffic_classifier_2_redirect_rules() return self.synthesize_redirect_bgp_route(rules) def _advertise_route_or_default(self, route, label, rd, lb_consistent_hash_order=0): if self.attract_traffic: self.log.debug("Advertising default route from VRF %d to " "redirection VRF", self.instance_id) route_entry = self._route_for_readvertisement( route, label, rd, lb_consistent_hash_order, do_default=self.attract_traffic ) self._advertise_route(route_entry) def _withdraw_route_or_default(self, route, label, rd, lb_consistent_hash_order=0): if self.attract_traffic: self.log.debug("Stop advertising default route from VRF to " "redirection VRF") route_entry = self._route_for_readvertisement( route, label, rd, lb_consistent_hash_order, do_default=self.attract_traffic ) self._withdraw_route(route_entry) @log_decorator.log def _readvertise(self, route): nlri = route.nlri self.log.debug("Start re-advertising %s from VRF", nlri.cidr.prefix()) for _, endpoints in self.localport_2_endpoints.items(): for endpoint in endpoints: port_data = self.mac_2_localport_data[endpoint['mac']] label = port_data['label'] lb_consistent_hash_order = port_data[ 'lb_consistent_hash_order'] rd = self.endpoint_2_rd[(endpoint['mac'], endpoint['ip'])] self.log.debug("Start re-advertising %s from VRF, with label " "%s and route distinguisher %s", nlri, label, rd) # need a distinct RD for each route... self._advertise_route_or_default(route, label, rd, lb_consistent_hash_order) if self.attract_traffic: flow_route = self._route_for_redirect_prefix(nlri.cidr.prefix()) self._advertise_route(flow_route) self.readvertised.add(route) @log_decorator.log def _readvertise_stop(self, route): nlri = route.nlri self.log.debug("Stop re-advertising %s from VRF", nlri.cidr.prefix()) for _, endpoints in self.localport_2_endpoints.items(): for endpoint in endpoints: port_data = self.mac_2_localport_data[endpoint['mac']] label = port_data['label'] lb_consistent_hash_order = port_data[ 'lb_consistent_hash_order'] rd = self.endpoint_2_rd[(endpoint['mac'], endpoint['ip'])] self.log.debug("Stop re-advertising %s from VRF, with label %s" "and route distinguisher %s", nlri, label, rd) self._withdraw_route_or_default(route, label, rd, lb_consistent_hash_order) if self.attract_traffic: flow_route = self._route_for_redirect_prefix(nlri.cidr.prefix()) self._withdraw_route(flow_route) self.readvertised.remove(route) def vif_plugged(self, mac_address, ip_address_prefix, localport, advertise_subnet=False, lb_consistent_hash_order=0): vpn_instance.VPNInstance.vif_plugged(self, mac_address, ip_address_prefix, localport, advertise_subnet, lb_consistent_hash_order) label = self.mac_2_localport_data[mac_address]['label'] rd = self.endpoint_2_rd[(mac_address, ip_address_prefix)] for route in self.readvertised: self.log.debug("Re-advertising %s with this port as next hop", route.nlri) self._advertise_route_or_default(route, label, rd, lb_consistent_hash_order) if self.attract_traffic: flow_route = self._route_for_redirect_prefix( route.nlri.cidr.prefix()) self._advertise_route(flow_route) def vif_unplugged(self, mac_address, ip_address_prefix, advertise_subnet=False, lb_consistent_hash_order=0): label = self.mac_2_localport_data[mac_address]['label'] lb_consistent_hash_order = (self.mac_2_localport_data[mac_address] ["lb_consistent_hash_order"]) rd = self.endpoint_2_rd[(mac_address, ip_address_prefix)] for route in self.readvertised: self.log.debug("Stop re-advertising %s with this port as next hop", route.nlri) self._withdraw_route_or_default(route, label, rd, lb_consistent_hash_order) if self.attract_traffic and self.has_only_one_endpoint(): flow_route = self._route_for_redirect_prefix( route.nlri.cidr.prefix()) self._withdraw_route(flow_route) vpn_instance.VPNInstance.vif_unplugged(self, mac_address, ip_address_prefix, advertise_subnet, lb_consistent_hash_order) # Callbacks for BGP route updates (TrackerWorker) ######################## def _route_2_tracked_entry(self, route): if isinstance(route.nlri, ipvpn_routes.IPVPN): return route.nlri.cidr.prefix() elif isinstance(route.nlri, flowspec.Flow): return (flowspec.Flow, route.nlri._rules()) else: self.log.error("We should not receive routes of type %s", type(route.nlri)) return None @utils.synchronized @log_decorator.log def _new_best_route(self, entry, new_route): if isinstance(new_route.nlri, flowspec.Flow): if len(new_route.ecoms(exa.TrafficRedirect)) == 1: traffic_redirect = new_route.ecoms(exa.TrafficRedirect) redirect_rt = "%s:%s" % (traffic_redirect[0].asn, traffic_redirect[0].target) self.start_redirect_traffic(redirect_rt, new_route.nlri.rules) else: self.log.warning("FlowSpec action or multiple traffic redirect" " actions not supported: %s", new_route.ecoms()) else: prefix = entry if self.readvertise: # check if this is a route we need to re-advertise self.log.debug("route RTs: %s", new_route.route_targets) self.log.debug("readv from RTs: %s", self.readvertise_from_rts) if self._to_readvertise(new_route): self.log.debug("Need to re-advertise %s", prefix) self._readvertise(new_route) if not self._imported(new_route): self.log.debug("No need to setup dataplane for:%s", prefix) return encaps = self._check_encaps(new_route) if not encaps: return assert len(new_route.nlri.labels.labels) == 1 lb_consistent_hash_order = 0 if new_route.ecoms(exa.ConsistentHashSortOrder): lb_consistent_hash_order = new_route.ecoms( exa.ConsistentHashSortOrder)[0].order self.dataplane.setup_dataplane_for_remote_endpoint( prefix, new_route.nexthop, new_route.nlri.labels.labels[0], new_route.nlri, encaps, lb_consistent_hash_order) @utils.synchronized @log_decorator.log def _best_route_removed(self, entry, old_route, last): if isinstance(old_route.nlri, flowspec.Flow): if len(old_route.ecoms(exa.TrafficRedirect)) == 1: if last: traffic_redirect = old_route.ecoms( exa.TrafficRedirect) redirect_rt = "%s:%s" % (traffic_redirect[0].asn, traffic_redirect[0].target) self.stop_redirect_traffic(redirect_rt, old_route.nlri.rules) else: self.log.warning("FlowSpec action or multiple traffic redirect" " actions not supported: %s", old_route.ecoms()) else: prefix = entry if self.readvertise and last: # check if this is a route we were re-advertising if self._to_readvertise(old_route): self.log.debug("Need to stop re-advertising %s", prefix) self._readvertise_stop(old_route) if not self._imported(old_route): self.log.debug("No need to update dataplane for:%s", prefix) return if self._skip_route_removal(last): self.log.debug("Skipping removal of non-last route because " "dataplane does not want it") return encaps = self._check_encaps(old_route) if not encaps: return assert len(old_route.nlri.labels.labels) == 1 lb_consistent_hash_order = 0 if old_route.ecoms(exa.ConsistentHashSortOrder): lb_consistent_hash_order = old_route.ecoms( exa.ConsistentHashSortOrder)[0].order self.dataplane.remove_dataplane_for_remote_endpoint( prefix, old_route.nexthop, old_route.nlri.labels.labels[0], old_route.nlri, encaps, lb_consistent_hash_order) # Looking glass ### def get_lg_map(self): return { "readvertised": (lg.SUBTREE, self.get_lg_readvertised_routes), } def get_lg_readvertised_routes(self, path_prefix): return [route.get_lg_local_info(path_prefix) for route in self.readvertised]
def _initiate_connection(self): self.log.debug("Initiate ExaBGP connection to %s:%s from %s", self.peer_address, cfg.CONF.BGP.bgp_port, self.local_address) self.rtc_active = False neighbor = exa_neighbor.Neighbor() neighbor.make_rib() neighbor.router_id = exa_open.RouterID(self.local_address) neighbor.local_as = exa.ASN(cfg.CONF.BGP.my_as) # no support for eBGP yet: neighbor.peer_as = exa.ASN(cfg.CONF.BGP.my_as) neighbor.local_address = exa.IP.create(self.local_address) neighbor.md5_ip = exa.IP.create(self.local_address) neighbor.peer_address = exa.IP.create(self.peer_address) neighbor.hold_time = exa_open.HoldTime( bgp_peer_worker.DEFAULT_HOLDTIME) neighbor.connect = cfg.CONF.BGP.bgp_port neighbor.api = collections.defaultdict(list) for afi_safi in self.enabled_families: neighbor.add_family(afi_safi) if cfg.CONF.BGP.enable_rtc: neighbor.add_family( (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))) self.log.debug("Instantiate ExaBGP Peer") self.peer = exa_peer.Peer(neighbor, None) try: for action in self.peer._connect(): self.fsm.state = TRANSLATE_EXABGP_STATE[ self.peer._outgoing.fsm.state] if action == exa_peer.ACTION.LATER: time.sleep(2) elif action == exa_peer.ACTION.NOW: time.sleep(0.1) if self.should_stop: self.log.debug("We're closing, raise StoppedException") raise bgp_peer_worker.StoppedException() if action == exa_peer.ACTION.CLOSE: self.log.debug("Socket status is CLOSE, " "raise InitiateConnectionException") raise bgp_peer_worker.InitiateConnectionException( "Socket is closed") except exa_peer.Interrupted: self.log.debug("Connect was interrupted, " "raise InitiateConnectionException") raise bgp_peer_worker.InitiateConnectionException( "Connect was interrupted") except exa_message.Notify as e: self.log.debug("Notify: %s", e) if (e.code, e.subcode) == (1, 1): raise bgp_peer_worker.OpenWaitTimeout(str(e)) else: raise Exception("Notify received: %s" % e) except exa_reactor.network.error.LostConnection as e: raise # check the capabilities of the session just established... self.protocol = self.peer._outgoing.proto received_open = self.protocol.negotiated.received_open self._set_hold_time(self.protocol.negotiated.holdtime) mp_capabilities = received_open.capabilities.get( exa_open.capability.Capability.CODE.MULTIPROTOCOL, []) # check that our peer advertized at least mpls_vpn and evpn # capabilities self._active_families = [] for (afi, safi) in (self.__class__.enabled_families + [(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))]): if (afi, safi) not in mp_capabilities: if (((afi, safi) != (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))) or cfg.CONF.BGP.enable_rtc): self.log.warning( "Peer does not advertise (%s,%s) " "capability", afi, safi) else: self.log.info( "Family (%s,%s) successfully negotiated with peer %s", afi, safi, self.peer_address) self._active_families.append((afi, safi)) if len(self._active_families) == 0: self.log.error("No family was negotiated for VPN routes") self.rtc_active = False if cfg.CONF.BGP.enable_rtc: if (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)) in mp_capabilities: self.log.info("RTC successfully enabled with peer %s", self.peer_address) self.rtc_active = True else: self.log.warning( "enable_rtc True but peer not configured for RTC")
class ExaBGPPeerWorker(bgp_peer_worker.BGPPeerWorker, lg.LookingGlassMixin): enabled_families = [ (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn)), # (exa.AFI(exa.exa.AFI.ipv6), # exa.SAFI(exa.SAFI.mpls_vpn)), (exa.AFI(exa.AFI.l2vpn), exa.SAFI(exa.SAFI.evpn)), (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.flow_vpn)) ] def __init__(self, bgp_manager, peer_address): bgp_peer_worker.BGPPeerWorker.__init__(self, bgp_manager, peer_address) self.local_address = cfg.CONF.BGP.local_address self.peer_address = peer_address self.peer = None self.rtc_active = False self._active_families = [] # hooks into BGPPeerWorker state changes def _stop_and_clean(self): super(ExaBGPPeerWorker, self)._stop_and_clean() self._active_families = [] if self.peer is not None: self.log.info("Clearing peer") if self.peer._outgoing.proto: self.peer._outgoing.proto.close() self.peer.stop() self.peer = None def _to_established(self): super(ExaBGPPeerWorker, self)._to_established() if self.rtc_active: self.log.debug("RTC active, subscribing to all RTC routes") # subscribe to RTC routes, to be able to propagate them from # internal workers to this peer self._subscribe(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)) else: self.log.debug("RTC inactive, subscribing to all active families") # if we don't use RTC with our peer, then we need to see events for # all routes of all active families, to be able to send them to him for (afi, safi) in self._active_families: self._subscribe(afi, safi) # implementation of BGPPeerWorker abstract methods def _initiate_connection(self): self.log.debug("Initiate ExaBGP connection to %s:%s from %s", self.peer_address, cfg.CONF.BGP.bgp_port, self.local_address) self.rtc_active = False neighbor = exa_neighbor.Neighbor() neighbor.make_rib() neighbor.router_id = exa_open.RouterID(self.local_address) neighbor.local_as = exa.ASN(cfg.CONF.BGP.my_as) # no support for eBGP yet: neighbor.peer_as = exa.ASN(cfg.CONF.BGP.my_as) neighbor.local_address = exa.IP.create(self.local_address) neighbor.md5_ip = exa.IP.create(self.local_address) neighbor.peer_address = exa.IP.create(self.peer_address) neighbor.hold_time = exa_open.HoldTime( bgp_peer_worker.DEFAULT_HOLDTIME) neighbor.connect = cfg.CONF.BGP.bgp_port neighbor.api = collections.defaultdict(list) for afi_safi in self.enabled_families: neighbor.add_family(afi_safi) if cfg.CONF.BGP.enable_rtc: neighbor.add_family( (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))) self.log.debug("Instantiate ExaBGP Peer") self.peer = exa_peer.Peer(neighbor, None) try: for action in self.peer._connect(): self.fsm.state = TRANSLATE_EXABGP_STATE[ self.peer._outgoing.fsm.state] if action == exa_peer.ACTION.LATER: time.sleep(2) elif action == exa_peer.ACTION.NOW: time.sleep(0.1) if self.should_stop: self.log.debug("We're closing, raise StoppedException") raise bgp_peer_worker.StoppedException() if action == exa_peer.ACTION.CLOSE: self.log.debug("Socket status is CLOSE, " "raise InitiateConnectionException") raise bgp_peer_worker.InitiateConnectionException( "Socket is closed") except exa_peer.Interrupted: self.log.debug("Connect was interrupted, " "raise InitiateConnectionException") raise bgp_peer_worker.InitiateConnectionException( "Connect was interrupted") except exa_message.Notify as e: self.log.debug("Notify: %s", e) if (e.code, e.subcode) == (1, 1): raise bgp_peer_worker.OpenWaitTimeout(str(e)) else: raise Exception("Notify received: %s" % e) except exa_reactor.network.error.LostConnection as e: raise # check the capabilities of the session just established... self.protocol = self.peer._outgoing.proto received_open = self.protocol.negotiated.received_open self._set_hold_time(self.protocol.negotiated.holdtime) mp_capabilities = received_open.capabilities.get( exa_open.capability.Capability.CODE.MULTIPROTOCOL, []) # check that our peer advertized at least mpls_vpn and evpn # capabilities self._active_families = [] for (afi, safi) in (self.__class__.enabled_families + [(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))]): if (afi, safi) not in mp_capabilities: if (((afi, safi) != (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc))) or cfg.CONF.BGP.enable_rtc): self.log.warning( "Peer does not advertise (%s,%s) " "capability", afi, safi) else: self.log.info( "Family (%s,%s) successfully negotiated with peer %s", afi, safi, self.peer_address) self._active_families.append((afi, safi)) if len(self._active_families) == 0: self.log.error("No family was negotiated for VPN routes") self.rtc_active = False if cfg.CONF.BGP.enable_rtc: if (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)) in mp_capabilities: self.log.info("RTC successfully enabled with peer %s", self.peer_address) self.rtc_active = True else: self.log.warning( "enable_rtc True but peer not configured for RTC") def _receive_loop_fun(self): try: select.select([self.protocol.connection.io], [], [], 2) if not self.protocol.connection: raise Exception("lost connection") message = six.next(self.protocol.read_message()) if message.ID != exa_message.NOP.ID: self.log.debug("protocol read message: %s", message) except exa_message.Notification as e: self.log.error("Notification: %s", e) return 2 except exa_reactor.network.error.LostConnection as e: self.log.warning("Lost connection while waiting for message: %s", e) return 2 except TypeError as e: self.log.error("Error while reading BGP message: %s", e) return 2 except Exception as e: self.log.error("Error while reading BGP message: %s", e) raise if message.ID == exa_message.NOP.ID: return 1 if message.ID == exa_message.Update.ID: if self.fsm.state != bgp_peer_worker.FSM.Established: raise Exception("Update received but not in Established state") # more below elif message.ID == exa_message.KeepAlive.ID: self.enqueue(bgp_peer_worker.KEEP_ALIVE_RECEIVED) self.log.debug("Received message: %s", message) else: self.log.warning("Received unexpected message: %s", message) if isinstance(message, exa_message.Update): if message.nlris: for nlri in message.nlris: if nlri.action == exa_message.IN.ANNOUNCED: action = engine.RouteEvent.ADVERTISE elif nlri.action == exa_message.IN.WITHDRAWN: action = engine.RouteEvent.WITHDRAW else: raise Exception("should not be reached (action:%s)", nlri.action) self._process_received_route(action, nlri, message.attributes) return 1 def _process_received_route(self, action, nlri, attributes): self.log.info("Received route: %s, %s", nlri, attributes) route_entry = engine.RouteEntry(nlri, None, attributes) if action == exa_message.IN.ANNOUNCED: self._advertise_route(route_entry) elif action == exa_message.IN.WITHDRAWN: self._withdraw_route(route_entry) else: raise Exception("unsupported action ??? (%s)" % action) # TODO(tmmorin): move RTC code out-of the peer-specific code if (nlri.afi, nlri.safi) == (exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.rtc)): self.log.info("Received an RTC route") if nlri.rt is None: self.log.info("Received RTC is a wildcard") # the semantic of RTC routes does not distinguish between AFI/SAFIs # if our peer subscribed to a Route Target, it means that we needs # to send him all routes of any AFI/SAFI carrying this RouteTarget. for (afi, safi) in self._active_families: if (afi, safi) != (exa.AFI(exa.AFI.ipv4), exa.SAFI( exa.SAFI.rtc)): if action == exa_message.IN.ANNOUNCED: self._subscribe(afi, safi, nlri.rt) elif action == exa_message.IN.WITHDRAWN: self._unsubscribe(afi, safi, nlri.rt) else: raise Exception("unsupported action ??? (%s)" % action) def _send(self, data): # (error if state not the right one for sending updates) self.log.debug("Sending %d bytes on socket to peer %s", len(data), self.peer_address) try: for _ in self.protocol.connection.writer(data): pass except Exception as e: self.log.error("Was not able to send data: %s", e) self.log.warning("%s", traceback.format_exc()) def _keep_alive_message_data(self): return exa_message.KeepAlive().message() def _update_for_route_event(self, event): try: r = exa_message.Update([event.route_entry.nlri], event.route_entry.attributes) return ''.join(r.messages(self.protocol.negotiated)) except Exception as e: self.log.error( "Exception while generating message for " "route %s: %s", r, e) self.log.warning("%s", traceback.format_exc()) return '' # Looking Glass ############### def get_lg_local_info(self, path_prefix): return { "peeringAddresses": { "peer_address": self.peer_address, "local_address": self.local_address }, "as_info": { "local": cfg.CONF.BGP.my_as, "peer": cfg.CONF.BGP.my_as }, "rtc": { "active": self.rtc_active, "enabled": cfg.CONF.BGP.enable_rtc }, "active_families": [repr(f) for f in self._active_families], }
class EVI(vpn_instance.VPNInstance, lg.LookingGlassMixin): '''Implementation an E-VPN MAC-VRF instance (EVI) based on RFC7432 and draft-ietf-bess-evpn-overlay. ''' type = constants.EVPN afi = exa.AFI(exa.AFI.l2vpn) safi = exa.SAFI(exa.SAFI.evpn) @log_decorator.log def __init__(self, *args, **kwargs): vpn_instance.VPNInstance.__init__(self, *args, **kwargs) self.gw_port = None # Advertise route to receive multi-destination traffic self.log.info("Generating BGP route for broadcast/multicast traffic") nlri = exa.EVPNMulticast( self.instance_rd, exa.EthernetTag(), exa.IP.create(self.bgp_manager.get_local_address()), None, exa.IP.create(self.bgp_manager.get_local_address())) attributes = exa.Attributes() attributes.add(self._gen_encap_extended_communities()) # add PMSI Tunnel Attribute route attributes.add( exa.PMSIIngressReplication(self.dp_driver.get_local_address(), self.instance_label)) self.multicast_route_entry = engine.RouteEntry(nlri, self.export_rts, attributes) self._advertise_route(self.multicast_route_entry) def generate_vif_bgp_route(self, mac_address, ip_prefix, plen, label, rd): # Generate BGP route and advertise it... assert(plen == 32) # label parameter ignored, we need to use instance label nlri = exa.EVPNMAC( rd, exa.ESI(), exa.EthernetTag(), exa.MAC(mac_address), 6*8, exa.Labels([self.instance_label]), exa.IP.create(ip_prefix), None, exa.IP.create(self.dp_driver.get_local_address())) return engine.RouteEntry(nlri) @log_decorator.log def set_gateway_port(self, linuxif, ipvpn): self.dataplane.set_gateway_port(linuxif, ipvpn.gateway_ip) self.gw_port = (linuxif, ipvpn) @log_decorator.log def gateway_port_down(self, linuxif): self.dataplane.gateway_port_down(linuxif) self.gw_port = None def has_gateway_port(self): return (self.gw_port is not None) # TrackerWorker callbacks for BGP route updates ########################## def _route_2_tracked_entry(self, route): if isinstance(route.nlri, exa.EVPNMAC): return (exa.EVPNMAC, route.nlri.mac) elif isinstance(route.nlri, exa.EVPNMulticast): return (exa.EVPNMulticast, (route.nlri.ip, route.nlri.rd)) elif isinstance(route.nlri, exa.EVPN): self.log.warning("Received EVPN route of unsupported subtype: %s", route.nlri.CODE) return None else: raise Exception("EVI %d should not receive routes of type %s" % (self.instance_id, type(route.nlri))) @utils.synchronized @log_decorator.log def _new_best_route(self, entry, new_route): (entry_class, info) = entry encaps = self._check_encaps(new_route) if not encaps: return if entry_class == exa.EVPNMAC: prefix = info remote_pe = new_route.nexthop label = new_route.nlri.label.labels[0] self.dataplane.setup_dataplane_for_remote_endpoint( prefix, remote_pe, label, new_route.nlri, encaps) elif entry_class == exa.EVPNMulticast: remote_endpoint = info # check that the route is actually carrying an PMSITunnel of type # ingress replication pmsi_tunnel = new_route.attributes.get(exa.PMSI.ID) if not isinstance(pmsi_tunnel, exa.PMSIIngressReplication): self.log.warning("Received PMSITunnel of unsupported type: %s", type(pmsi_tunnel)) else: remote_endpoint = pmsi_tunnel.ip label = pmsi_tunnel.label self.log.info("Setting up dataplane for new ingress " "replication destination %s", remote_endpoint) self.dataplane.add_dataplane_for_bum_endpoint( remote_endpoint, label, new_route.nlri, encaps) else: self.log.warning("unsupported entry_class: %s", entry_class.__name__) @utils.synchronized @log_decorator.log def _best_route_removed(self, entry, old_route, last): (entry_class, info) = entry if entry_class == exa.EVPNMAC: if self._skip_route_removal(last): self.log.debug("Skipping removal of non-last route because " "dataplane does not want it") return prefix = info remote_pe = old_route.nexthop label = old_route.nlri.label.labels[0] self.dataplane.remove_dataplane_for_remote_endpoint( prefix, remote_pe, label, old_route.nlri) elif entry_class == exa.EVPNMulticast: remote_endpoint = info # check that the route is actually carrying an PMSITunnel of type # ingress replication pmsi_tunnel = old_route.attributes.get(exa.PMSI.ID) if not isinstance(pmsi_tunnel, exa.PMSIIngressReplication): self.log.warning("PMSITunnel of suppressed route is of" " unsupported type") else: remote_endpoint = pmsi_tunnel.ip label = pmsi_tunnel.label self.log.info("Cleaning up dataplane for ingress replication " "destination %s", remote_endpoint) self.dataplane.remove_dataplane_for_bum_endpoint( remote_endpoint, label, old_route.nlri) else: self.log.warning("unsupported entry_class: %s", entry_class.__name__) # Looking Glass #### def get_lg_local_info(self, path_prefix): if not self.gw_port: return {"gateway_port": None} else: (linuxif, ipvpn) = self.gw_port return {"gateway_port": { "interface": repr(linuxif), "ipvpn": {"href": lg.get_absolute_path( "VPN_INSTANCES", path_prefix, [ipvpn.external_instance_id]), "id": ipvpn.name, "external_instance_id": ipvpn.external_instance_id }, }}
- testDx : to test worker cleanup - testEx : to test dumpState """ import testtools from unittest import mock from networking_bagpipe.bagpipe_bgp import engine from networking_bagpipe.bagpipe_bgp.engine import bgp_peer_worker as bpw from networking_bagpipe.bagpipe_bgp.engine import exa from networking_bagpipe.bagpipe_bgp.engine import route_table_manager as rtm from networking_bagpipe.bagpipe_bgp.engine import worker from networking_bagpipe.tests.unit.bagpipe_bgp import base as t MATCH1 = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), t.RT1) MATCH2 = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), t.RT2) MATCH3 = rtm.Match(exa.AFI(exa.AFI.ipv4), exa.SAFI(exa.SAFI.mpls_vpn), t.RT3) class TestRouteTableManager(testtools.TestCase, t.BaseTestBagPipeBGP): def setUp(self): super(TestRouteTableManager, self).setUp() self.rtm = rtm.RouteTableManager(mock.Mock(), mock.Mock()) self.rtm.start() self.set_event_target_worker(self.rtm) def tearDown(self): super(TestRouteTableManager, self).tearDown() self.rtm.stop() self.rtm.join()
def IPVPNRouteFactory(afi, prefix, label, rd, nexthop): packed_prefix, mask = prefix_to_packed_ip_mask(prefix) return IPVPN.new(afi, exa.SAFI(exa.SAFI.mpls_vpn), packed_prefix, mask, exa.Labels([label], True), rd, nexthop)
def __init__(self, vpn_manager, dataplane_driver, external_instance_id, instance_id, import_rts, export_rts, gateway_ip, mask, readvertise, attract_traffic, fallback=None, **kwargs): self.manager = vpn_manager self.instance_type = self.__class__.__name__ self.instance_id = instance_id threading.Thread.__init__(self) self.setDaemon(True) if dataplane_driver.ecmp_support: compare_routes = tracker_worker.compare_ecmp else: compare_routes = tracker_worker.compare_no_ecmp tracker_worker.TrackerWorker.__init__( self, self.manager.bgp_manager, "%s-%d" % (self.instance_type, self.instance_id), compare_routes) lg.LookingGlassLocalLogger.__init__( self, "%s-%d" % (self.instance_type, self.instance_id)) self.lock = threading.Lock() self.import_rts = import_rts self.export_rts = export_rts self.external_instance_id = external_instance_id self.gateway_ip = gateway_ip self.mask = mask self.fallback = None self.afi = self.__class__.afi self.safi = self.__class__.safi assert isinstance(self.afi, exa.AFI) assert isinstance(self.safi, exa.SAFI) self.dp_driver = dataplane_driver if 'vni' in kwargs: self.instance_label = kwargs.pop('vni') self.forced_vni = True else: self.instance_label = self.manager.label_allocator.get_new_label( "Incoming traffic for %s %d" % (self.instance_type, self.instance_id)) self.forced_vni = False self.instance_rd = self.manager.rd_allocator.get_new_rd( "Default route distinguisher for %s %d" % (self.instance_type, self.instance_id)) self.localport_data = dict() # One local port -> List of endpoints (MAC and IP addresses tuple) self.localport_2_endpoints = dict() # One endpoint (MAC and IP addresses tuple) -> One route distinguisher self.endpoint_2_rd = dict() # One MAC address -> One local port self.mac_2_localport_data = dict() # One IP address -> Multiple MAC address self.ip_address_2_mac = dict() # Redirected instances list from which traffic is attracted (based on # FlowSpec 5-tuple classification) self.redirected_instances = set() self.dataplane = self.dp_driver.initialize_dataplane_instance( self.instance_id, self.external_instance_id, self.gateway_ip, self.mask, self.instance_label, **kwargs) for rt in self.import_rts: self._subscribe(self.afi, self.safi, rt) # Subscribe to FlowSpec routes # FIXME(tmorin): this maybe isn't applicable yet to E-VPN yet self._subscribe(self.afi, exa.SAFI(exa.SAFI.flow_vpn), rt) if readvertise: self.readvertise = True try: self.readvertise_to_rts = readvertise['to_rt'] except KeyError: raise exc.APIException("'readvertise' specified with no " "'to_rt'") self.readvertise_from_rts = readvertise.get('from_rt', []) self.log.debug("readvertise enabled, from RT:%s, to %s", self.readvertise_from_rts, self.readvertise_to_rts) for rt in self.readvertise_from_rts: self._subscribe(self.afi, self.safi, rt) else: self.log.debug("readvertise not enabled") self.readvertise = False if self.readvertise and attract_traffic: if len(self.readvertise_to_rts) != 1: raise exc.APIException("attract_traffic requires exactly one " "RT to be provided in " "readvertise/to_rt") self.attract_traffic = True self.attract_rts = attract_traffic['redirect_rts'] try: self.attract_classifier = attract_traffic['classifier'] except KeyError: raise exc.APIException("'attract_traffic' specified with no " "'classifier'") self.log.debug( "Attract traffic enabled with RT: %s and " "classifier: %s", self.attract_rts, self.attract_classifier) else: self.log.debug("attract traffic not enabled") self.attract_traffic = False self.dataplane.update_fallback(fallback)