def _workerCleanup(self, worker):
        '''
        Consider all routes announced by this worker as withdrawn.
        Consider this worker unsubscribed from all of its current
        subscriptions.
        '''
        log.info("Cleanup for worker %s", worker.name)
        # synthesize withdraw events for all routes from this worker
        if worker in self._source2entries:
            entries = self._source2entries[worker]
            log.info(
                "  Preparing to withdraw %d routes that were advertised "
                "by worker", len(entries))
            for entry in entries:
                log.info("  Enqueue event to Withdraw route %s", entry)
                self.enqueue(RouteEvent(RouteEvent.WITHDRAW, entry))

            del self._source2entries[worker]
        else:
            log.info("(we had no trace of %s in _source2entries)", worker)

        # remove worker from all of its subscriptions
        if worker in self._worker2matches:
            for match in self._worker2matches[worker]:
                assert (match in self._match2workersAndEntries)
                self._match2workers(match).remove(worker)
            del self._worker2matches[worker]
Beispiel #2
0
    def _routeEventUnsubscribe(self, unsubscription):

        self.routeTableManager.enqueue(unsubscription)

        if (self.config['enable_rtc']
                and not isinstance(unsubscription.worker, BGPPeerWorker)):

            wasLastWorkerForSubscription = \
                self._trackedSubscriptionsRemoveWorker(unsubscription)

            # FIXME: not excellent to hardcode this here
            if ((unsubscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and wasLastWorkerForSubscription):
                # synthesize a withdraw RouteEvent for a RouteTarget constraint
                # route
                routeEvent = RouteEvent(
                    RouteEvent.WITHDRAW,
                    self._subscription2RTCRouteEntry(unsubscription), self)
                log.debug(
                    "Based on unsubscription => synthesized withdraw"
                    " for RTC %s", routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug(
                    "No need to synthesize an RTC route "
                    "(wasLastWorkerForSubscription:%s) ",
                    wasLastWorkerForSubscription)
Beispiel #3
0
    def _routeEventSubscribe(self, subscription):

        self.routeTableManager.enqueue(subscription)

        # synthesize a RouteEvent for a RouteTarget constraint route
        if (self.config['enable_rtc']
                and not isinstance(subscription.worker, BGPPeerWorker)):

            firstWorkerForSubscription = self._trackedSubscriptionsAddWorker(
                subscription)

            # FIXME: not excellent to hardcode this here
            if ((subscription.safi in (SAFI.mpls_vpn, SAFI.evpn))
                    and firstWorkerForSubscription):
                routeEvent = RouteEvent(
                    RouteEvent.ADVERTISE,
                    self._subscription2RTCRouteEntry(subscription), self)
                log.debug("Based on subscription => synthesized RTC %s",
                          routeEvent)
                self.routeTableManager.enqueue(routeEvent)
            else:
                log.debug(
                    "No need to synthesize an RTC route "
                    "(firstWorkerForSubscription:%s) ",
                    firstWorkerForSubscription)
Beispiel #4
0
    def _readvertiseStop(self, nlri):
        self.log.debug("Stop re-advertising %s from VRF", nlri.prefix)
        for label in self._getLocalLabels():
            self.log.debug("Stop re-advertising %s from VRF, with label %s",
                           nlri.prefix, label)
            routeEntry = self._routeForReAdvertisement(nlri.prefix, label)
            self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry))

        self.readvertised.remove(nlri.prefix)
    def _workerUnsubscribes(self, sub):
        assert (isinstance(sub.worker, Worker))

        # self._dumpState()

        match = Match(sub.afi, sub.safi, sub.routeTarget)

        # update _worker2matches
        if sub.worker not in self._worker2matches:
            log.warning("worker %s unsubs'd from %s but wasn't tracked yet",
                        sub.worker, match)
        else:
            try:
                self._worker2matches[sub.worker].remove(match)
            except KeyError:
                log.warning(
                    "worker %s unsubs' from %s but this match was"
                    "not tracked for this worker (should not happen,"
                    " this is a bug)", sub.worker, match)

        # synthesize withdraw events
        for entry in self._match2entries(match, emptyListIfNone=True):
            intersect = set(
                self._matchesFor(entry.afi, entry.safi,
                                 entry.routeTargets)).intersection(
                                     self._worker2matches[sub.worker])
            if len(intersect) > 0:
                log.debug(
                    "Will not synthesize withdraw event for %s, because"
                    " worker subscribed to %s", entry, intersect)
            else:
                log.debug("Found a entry for this match: %s", entry)
                event = RouteEvent(RouteEvent.WITHDRAW, entry)
                (shouldDispatch,
                 reason) = self._shouldDispatch(event, sub.worker)
                if shouldDispatch:
                    log.info("Dispatching re-synthesized event for %s", entry)
                    sub.worker.enqueue(event)
                else:
                    log.info(
                        "%s => not dispatching re-synthesized event for %s",
                        reason, entry)

        # update _match2workersAndEntries
        if match not in self._match2workersAndEntries:
            log.warning(
                "worker %s unsubscribed from %s but we had no such"
                " subscription yet", sub.worker, match)
        else:
            try:
                self._match2workers(match).remove(sub.worker)
            except KeyError:
                log.warning(
                    "worker %s unsubscribed from %s but was not"
                    " subscribed yet", sub.worker, match)

        self._checkMatch2workersAndEntriesCleanup(match)
Beispiel #6
0
    def _newRouteEvent(self, eventType, nlri, rts, source, nh, lp=0,
                       replacedRouteEntry=None,
                       afi=AFI(AFI.ipv4), safi=SAFI(SAFI.mpls_vpn)):
        attributes = Attributes()
        attributes.add(NextHop(nh))
        attributes.add(LocalPreference(lp))
        routeEvent = RouteEvent(eventType, RouteEntry(
            afi, safi, rts, nlri, attributes, source), source)
        routeEvent.setReplacedRoute(replacedRouteEntry)

        self.eventTargetWorker.enqueue(routeEvent)

        log.info("Emitting event to %s: %s",
                 self.eventTargetWorker, routeEvent)

        self._wait()

        return routeEvent
Beispiel #7
0
    def vifUnplugged(self, macAddress, ipAddressPrefix, advertiseSubnet):
        label = self.macAddress2LocalPortData[macAddress]['label']
        for prefix in self.readvertised:
            self.log.debug("Stop re-advertising %s with this port as next hop",
                           prefix)
            routeEntry = self._routeForReAdvertisement(prefix, label)
            self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry))

        VPNInstance.vifUnplugged(self, macAddress, ipAddressPrefix,
                                 advertiseSubnet)
Beispiel #8
0
    def _readvertise(self, nlri):
        self.log.debug("Start re-advertising %s from VRF", nlri.prefix)
        for label in self._getLocalLabels():
            self.log.debug("Start re-advertising %s from VRF, with label %s",
                           nlri.prefix, label)
            # need a distinct RD for each route...
            routeEntry = self._routeForReAdvertisement(nlri.prefix, label)
            self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry))

        self.readvertised.add(nlri.prefix)
    def _processReceivedRoute(self, route):
        self.log.info("Received route: %s", route)

        rts = []
        if AttributeID.EXTENDED_COMMUNITY in route.attributes:
            rts = [
                ecom for ecom in route.attributes[
                    AttributeID.EXTENDED_COMMUNITY].communities
                if isinstance(ecom, RouteTarget)
            ]

            if not rts:
                raise Exception("Unable to find any Route Targets"
                                "in the received route")

        routeEntry = self._newRouteEntry(route.nlri.afi, route.nlri.safi, rts,
                                         route.nlri, route.attributes)

        if route.action == "announce":
            self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry))
        else:
            self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry))

        # TODO(tmmorin): move RTC code out-of the peer-specific code
        if (route.nlri.afi, route.nlri.safi) == (AFI(AFI.ipv4),
                                                 SAFI(SAFI.rtc)):
            self.log.info("Received an RTC route")

            if route.nlri.route_target 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._activeFamilies:
                if (afi, safi) != (AFI(AFI.ipv4), SAFI(SAFI.rtc)):
                    if route.action == "announce":
                        self._subscribe(afi, safi, route.nlri.route_target)
                    else:  # withdraw
                        self._unsubscribe(afi, safi, route.nlri.route_target)
    def _workerSubscribes(self, sub):
        # TODO: this function currently will not consider whether or not
        # is already subscribed to set of route events, before considering
        # a subscription.  In particular, multiple identical subscriptions
        # will lead to this code resyntethizing events at each call.
        #
        # Ideally, the code should detect that the worker is already subscribed
        # and skip the subscription. *But* such a change should not be done
        # until the code in ExaBGPPeerWorker is updated to support this.

        assert (isinstance(sub.worker, Worker))
        log.info("workerSubscribes: %s", sub)

        worker = sub.worker

        # self._dumpState()

        match = Match(sub.afi, sub.safi, sub.routeTarget)

        # update match2worker
        self._match2workers(match, createIfNone=True).add(worker)

        # update worker2matches
        if worker not in self._worker2matches:
            self._worker2matches[worker] = set()

        # re-synthesize events
        for entry in self._match2entries(match):
            log.debug("Found a entry for this match: %s", entry)
            event = RouteEvent(RouteEvent.ADVERTISE, entry)
            (shouldDispatch, reason) = self._shouldDispatch(event, worker)

            if shouldDispatch:
                # check if the entry carries a routeTarget to which the worker
                # was already subscribed
                for rt in entry.routeTargets:
                    if Match(entry.afi, entry.safi,
                             rt) in self._worker2matches[worker]:
                        (shouldDispatch, reason) = (
                            False,
                            "worker already had a subscription for this route")
                        break

            if shouldDispatch:
                log.info("Dispatching re-synthesized event for %s", entry)
                worker.enqueue(event)
            else:
                log.info("%s => not dispatching re-synthesized event for %s",
                         reason, entry)

        # update worker2matches
        self._worker2matches[worker].add(match)
Beispiel #11
0
    def updateRouteTargets(self, newImportRTs, newExportRTs):
        added_import_rt = set(newImportRTs) - set(self.importRTs)
        removed_import_rt = set(self.importRTs) - set(newImportRTs)

        self.log.debug("%s %d - Added Import RTs: %s", self.instanceType,
                       self.instanceId, added_import_rt)
        self.log.debug("%s %d - Removed Import RTs: %s", self.instanceType,
                       self.instanceId, removed_import_rt)

        # Register to BGP with these route targets
        for rt in added_import_rt:
            self._subscribe(self.afi, self.safi, rt)

        # Unregister from BGP with these route targets
        for rt in removed_import_rt:
            self._unsubscribe(self.afi, self.safi, rt)

        # Update import and export route targets
        self.importRTs = newImportRTs

        # Re-advertise all routes with new export RTs
        self.log.debug("Exports RTs: %s -> %s", self.exportRTs, newExportRTs)
        if frozenset(newExportRTs) != frozenset(self.exportRTs):
            self.log.debug("Will re-export routes with new RTs")
            self.exportRTs = newExportRTs
            for routeEntry in self.getWorkerRouteEntries():
                self.log.info("Re-advertising route %s with updated RTs (%s)",
                              routeEntry.nlri, newExportRTs)

                updatedAttributes = copy(routeEntry.attributes)
                del updatedAttributes[AttributeID.EXTENDED_COMMUNITY]
                updatedAttributes.add(self._genExtendedCommunities())

                updatedRouteEntry = self._newRouteEntry(
                    routeEntry.afi, routeEntry.safi, self.exportRTs,
                    routeEntry.nlri, updatedAttributes)
                self.log.debug("   updated route: %s", updatedRouteEntry)

                self._pushEvent(
                    RouteEvent(RouteEvent.ADVERTISE, updatedRouteEntry))
Beispiel #12
0
    def __init__(self, *args, **kwargs):

        VPNInstance.__init__(self, *args, **kwargs)

        self.gwPort = None

        # Advertise route to receive multi-destination traffic
        self.log.info("Generating BGP route for broadcast/multicast traffic")

        etag = None
        label = LabelStackEntry(self.instanceLabel)

        route = Route(
            EVPNMulticast(
                RouteDistinguisher(RouteDistinguisher.TYPE_IP_LOC, None,
                                   self.bgpManager.getLocalAddress(),
                                   self.instanceId), etag,
                self.bgpManager.getLocalAddress()))

        route.attributes.add(self._genExtendedCommunities())

        # add PMSI Tunnel Attribute route
        pmsi_tunnel_attribute = PMSITunnelIngressReplication(
            self.dataplaneDriver.getLocalAddress(), label)
        route.attributes.add(pmsi_tunnel_attribute)

        nh = Inet(
            1,
            socket.inet_pton(socket.AF_INET,
                             self.dataplaneDriver.getLocalAddress()))
        route.attributes.add(NextHop(nh))

        self.multicastRouteEntry = self._newRouteEntry(self.afi, self.safi,
                                                       self.exportRTs,
                                                       route.nlri,
                                                       route.attributes)

        self._pushEvent(
            RouteEvent(RouteEvent.ADVERTISE, self.multicastRouteEntry))
    def testF1_testEmptyRT(self):
        # worker advertises a route with no RT

        w1 = self._newworker("Worker1", Worker)

        subscribe = Subscription(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, w1)
        self.routeTableManager.enqueue(subscribe)

        w2 = self._newworker("Worker2", Worker)

        routeEvent = RouteEvent(
            RouteEvent.ADVERTISE,
            RouteEntry(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, NLRI1,
                       Attributes(), w2), w2)

        self.routeTableManager.enqueue(routeEvent)

        self._wait()

        self.assertEqual(
            1, w1.enqueue.call_count,
            "1 route advertised should be synthesized to Worker1")
Beispiel #14
0
    def vifUnplugged(self, macAddress, ipAddressPrefix, advertiseSubnet=False):
        # Verify port and endpoint (MAC address, IP address) tuple consistency
        portData = self.macAddress2LocalPortData.get(macAddress)
        if (not portData or
                self.ipAddress2MacAddress.get(ipAddressPrefix) != macAddress):
            self.log.error(
                "vifUnplugged called for endpoint (%s, %s), but no "
                "consistent informations or was not plugged yet", macAddress,
                ipAddressPrefix)
            raise APIException("No consistent endpoint (%s, %s) informations "
                               "or was not plugged yet, cannot unplug" %
                               (macAddress, ipAddressPrefix))

        # Finding label and local port informations
        label = portData.get('label')
        localPort = portData.get('port_info')
        if (not label or not localPort):
            self.log.error(
                "vifUnplugged called for endpoint (%s, %s), but "
                "port data (%s, %s) is incomplete", macAddress,
                ipAddressPrefix, label, localPort)
            raise Exception("Inconsistent informations for port, bug ?")

        if localPort['linuxif'] in self.localPort2Endpoints:
            # Parse address/mask
            (ipPrefix, prefixLen) = self._parseIPAddressPrefix(ipAddressPrefix)

            lastEndpoint = len(
                self.localPort2Endpoints[localPort['linuxif']]) <= 1

            if not advertiseSubnet:
                self.log.debug("Will advertise as /32 instead of /%d" %
                               prefixLen)
                prefixLen = 32

            self.log.info(
                "Synthesizing and withdrawing BGP route for VIF %s "
                "endpoint (%s, %s/%d)", localPort['linuxif'], macAddress,
                ipPrefix, prefixLen)
            routeEntry = self.synthesizeVifBGPRoute(macAddress, ipPrefix,
                                                    prefixLen, label)
            self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry))

            # Unplug endpoint from data plane
            self.dataplane.vifUnplugged(macAddress, ipPrefix, localPort, label,
                                        lastEndpoint)

            # Forget data for this port if last endpoint
            if lastEndpoint:
                # Free label to the allocator
                self.labelAllocator.release(label)

                del self.localPort2Endpoints[localPort['linuxif']]
            else:
                self.localPort2Endpoints[localPort['linuxif']].remove({
                    'mac':
                    macAddress,
                    'ip':
                    ipAddressPrefix
                })

            if not lastEndpoint:
                if not any([
                        endpoint['mac'] == macAddress for endpoint in
                        self.localPort2Endpoints[localPort['linuxif']]
                ]):
                    del self.macAddress2LocalPortData[macAddress]
            else:
                del self.macAddress2LocalPortData[macAddress]

            del self.ipAddress2MacAddress[ipAddressPrefix]
        else:
            self.log.error(
                "vifUnplugged called for endpoint {%s, %s}, but"
                " port data is incomplete", macAddress, ipAddressPrefix)
            raise Exception("BGP component bug, check its logs")
Beispiel #15
0
    def vifPlugged(self,
                   macAddress,
                   ipAddressPrefix,
                   localPort,
                   advertiseSubnet=False):
        # Check if this port has already been plugged
        # - Verify port informations consistency
        if macAddress in self.macAddress2LocalPortData:
            self.log.debug("MAC address already plugged, checking port "
                           "consistency")
            portData = self.macAddress2LocalPortData[macAddress]

            if (portData.get("port_info") != localPort):
                raise APIException("Port information is not consistent. MAC "
                                   "address cannot be bound to two different"
                                   "ports. Previous plug for port %s "
                                   "(%s != %s)" %
                                   (localPort['linuxif'],
                                    portData.get("port_info"), localPort))

        # - Verify (MAC address, IP address) tuple consistency
        if ipAddressPrefix in self.ipAddress2MacAddress:
            if self.ipAddress2MacAddress.get(ipAddressPrefix) != macAddress:
                raise APIException("Inconsistent endpoint info: %s already "
                                   "bound to a MAC address different from %s" %
                                   (ipAddressPrefix, macAddress))
            else:
                return

        # Else, plug port on dataplane
        try:
            # Parse address/mask
            (ipPrefix, prefixLen) = self._parseIPAddressPrefix(ipAddressPrefix)

            self.log.debug("Plugging port (%s)", ipPrefix)

            portData = self.macAddress2LocalPortData.get(macAddress, dict())
            if not portData:
                portData['label'] = self.labelAllocator.getNewLabel(
                    "Incoming traffic for %s %d, interface %s, endpoint %s/%s"
                    % (self.instanceType, self.instanceId,
                       localPort['linuxif'], macAddress, ipAddressPrefix))
                portData["port_info"] = localPort

            # Call driver to setup the dataplane for incoming traffic
            self.dataplane.vifPlugged(macAddress, ipPrefix, localPort,
                                      portData['label'])

            if not advertiseSubnet:
                self.log.debug("Will advertise as /32 instead of /%d" %
                               prefixLen)
                prefixLen = 32

            self.log.info(
                "Synthesizing and advertising BGP route for VIF %s "
                "endpoint (%s, %s/%d)", localPort['linuxif'], macAddress,
                ipPrefix, prefixLen)
            routeEntry = self.synthesizeVifBGPRoute(macAddress, ipPrefix,
                                                    prefixLen,
                                                    portData['label'])

            self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry))

            if localPort['linuxif'] not in self.localPort2Endpoints:
                self.localPort2Endpoints[localPort['linuxif']] = list()

            self.localPort2Endpoints[localPort['linuxif']].append({
                'mac':
                macAddress,
                'ip':
                ipAddressPrefix,
            })
            self.macAddress2LocalPortData[macAddress] = portData
            self.ipAddress2MacAddress[ipAddressPrefix] = macAddress

        except Exception as e:
            self.log.error("Error in vifPlugged: %s", e)
            if localPort['linuxif'] in self.localPort2Endpoints:
                if len(self.localPort2Endpoints[localPort['linuxif']]) > 1:
                    self.localPort2Endpoints[localPort['linuxif']].remove({
                        'mac':
                        macAddress,
                        'ip':
                        ipAddressPrefix
                    })
                else:
                    del self.localPort2Endpoints[localPort['linuxif']]
            if macAddress in self.macAddress2LocalPortData:
                del self.macAddress2LocalPortData[macAddress]
            if ipAddressPrefix in self.ipAddress2MacAddress:
                del self.ipAddress2MacAddress[ipAddressPrefix]

            raise
    def _receiveRouteEvent(self, routeEvent):
        log.info("receive: %s", routeEvent)

        entry = routeEvent.routeEntry

        log.debug("Try to find a entry from same peer with same nlri")
        try:
            replacedEntry = self._source_nlri2entry[(entry.source, entry.nlri)]
        except KeyError:
            replacedEntry = None

        log.debug("   Result: %s", replacedEntry)

        # replacedEntry should be non-empty for a withdraw
        if replacedEntry is None and (routeEvent.type == RouteEvent.WITHDRAW):
            log.warning("WITHDRAW but found no route that we could remove: %s",
                        routeEvent.routeEntry)
            return

        # Propagate events to interested workers...
        if routeEvent.type == RouteEvent.ADVERTISE:

            if replacedEntry == routeEvent.routeEntry:
                log.warning("The route advertized is the same as the one "
                            "previously advertized by the source, ignoring")
                return

            # propagate event to interested worker
            # and include the info on the route are replaced by this
            # route, if any
            routeEvent.setReplacedRoute(replacedEntry)

            workersAlreadyNotified = self._propagateRouteEvent(routeEvent)
        else:  # WITHDRAW
            workersAlreadyNotified = None

        # Synthesize and dispatch a withdraw event for the route entry that
        # was withdrawn or replaced, except, in the case of a replaced route,
        # to workers that had the ADVERTISE event
        if replacedEntry is not None:
            log.debug("Synthesizing a withdraw event for replaced route %s",
                      replacedEntry)
            removalEvent = RouteEvent(RouteEvent.WITHDRAW, replacedEntry,
                                      routeEvent.source)

            self._propagateRouteEvent(removalEvent, workersAlreadyNotified)

            # Update match2entries and source2entries for the
            # replacedRoute
            for match in self._matchesFor(replacedEntry.afi,
                                          replacedEntry.safi,
                                          replacedEntry.routeTargets):
                try:
                    self._match2entries(match).discard(replacedEntry)
                except KeyError:
                    log.error(
                        "Trying to remove a route from a match, but"
                        " match %s not found - not supposed to happen"
                        " (route: %s)", match, replacedEntry)
                self._checkMatch2workersAndEntriesCleanup(match)

            self._source2entriesRemoveEntry(replacedEntry)

        if routeEvent.type == RouteEvent.ADVERTISE:
            # Update match2entries and source2entries for the newly
            # advertized route
            for match in self._matchesFor(entry.afi, entry.safi,
                                          entry.routeTargets):
                self._match2entries(match, createIfNone=True).add(entry)

            self._source2entriesAddEntry(entry)

            # Update _source_nlri2entry
            self._source_nlri2entry[(entry.source, entry.nlri)] = entry
        else:  # WITHDRAW
            # Update _source_nlri2entry
            try:
                del self._source_nlri2entry[(entry.source, entry.nlri)]
            except KeyError:
                log.error("Withdraw, but nothing removed in "
                          "_sourcenlri2entryRemove")