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)
Example #2
0
    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)
Example #3
0
    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
Example #4
0
    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
Example #5
0
 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)
Example #6
0
    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)
Example #9
0
    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")
Example #10
0
    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 _create_test_ipvpn_nlri(label, nexthop):
    return ipvpn.IPVPNRouteFactory(exa.AFI(exa.AFI.ipv4),
                                   "1.1.1.1/32", label, TEST_RD, nexthop)
Example #12
0
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
                              },
                    }}
    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],
        }
Example #15
0
   - 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()
Example #16
0
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]
Example #17
0
 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)