def new_open(self, restarted, asn4, config, enabledFamilies=[]): '''Same as exabgp.Protocol.new_open except that we advertise support for MPLS VPN and RTC''' asn = self.neighbor.local_as # (we don't support ASN4) o = Open(4, asn, self.neighbor.router_id.ip, Capabilities().default(self.neighbor, restarted), self.neighbor.hold_time) o.capabilities[Capabilities.MULTIPROTOCOL_EXTENSIONS].remove( (AFI(AFI.ipv4), SAFI(SAFI.flow_ipv4))) o.capabilities[Capabilities.MULTIPROTOCOL_EXTENSIONS].remove( (AFI(AFI.ipv4), SAFI(SAFI.unicast))) o.capabilities[Capabilities.MULTIPROTOCOL_EXTENSIONS].remove( (AFI(AFI.ipv6), SAFI(SAFI.unicast))) for afi_safi in enabledFamilies: o.capabilities[Capabilities.MULTIPROTOCOL_EXTENSIONS].append( afi_safi) if config['enable_rtc']: o.capabilities[Capabilities.MULTIPROTOCOL_EXTENSIONS].append( (AFI(AFI.ipv4), SAFI(SAFI.rtc))) if not self.connection.write(o.message()): raise Exception("Error while sending open") return o
def __cmp__(self, other): assert isinstance(other, Match) self_afi = self.afi or AFI(0) self_safi = self.safi or SAFI(0) self_rt = self.routeTarget or RouteTarget(0, None, 0) other_afi = other.afi or AFI(0) other_safi = other.safi or SAFI(0) other_rt = other.routeTarget or RouteTarget(0, None, 0) return cmp((self_afi, self_safi, self_rt), (other_afi, other_safi, other_rt))
def _subscription2RTCRouteEntry(self, subscription): route = Route( RouteTargetConstraint(AFI(AFI.ipv4), SAFI(SAFI.rtc), self.config['my_as'], subscription.routeTarget)) nh = Inet( 1, socket.inet_pton(socket.AF_INET, self.config['local_address'])) route.attributes.add(NextHop(nh)) routeEntry = RouteEntry(AFI(AFI.ipv4), SAFI(SAFI.rtc), [], route.nlri, route.attributes, self) return routeEntry
def getLGRoutes(self, pathPrefix): result = {} match_IPVPN = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), Subscription.ANY_RT) match_EVPN = Match(AFI(AFI.l2vpn), SAFI(SAFI.evpn), Subscription.ANY_RT) match_RTC = Match(AFI(AFI.ipv4), SAFI(SAFI.rtc), Subscription.ANY_RT) for match in [match_IPVPN, match_EVPN, match_RTC]: matchResult = [] if match in self._match2workersAndEntries: for entry in self._match2entries(match): matchResult.append(entry.getLookingGlassInfo(pathPrefix)) result[repr(match)] = matchResult return result
def _workerUnsubscriptions(self, worker, rts, afi=AFI(AFI.ipv4), safi=SAFI(SAFI.mpls_vpn)): for rt in rts: unsubscribe = Unsubscription(afi, safi, rt, worker) self.routeTableManager.enqueue(unsubscribe)
def __init__(self,afi,safi,prefix,rd,labelStack): self.afi = AFI(afi) self.safi = SAFI(safi) self.rd = rd self.labelStack = labelStack # an array of LabelStackEntry's if type(labelStack) != list or len(labelStack)==0: raise Exception("Labelstack has to be a non-empty array") self.prefix = prefix
def getAllRoutesButRTC(self): try: return [ re for re in self._match2workersAndEntries[Match( Subscription.ANY_AFI, Subscription.ANY_SAFI, Subscription.ANY_RT)].entries if not (re.afi == AFI(AFI.ipv4) and re.safi == SAFI(SAFI.rtc)) ] except KeyError: return []
def _toEstablished(self): BGPPeerWorker._toEstablished(self) if self.rtc_active: # subscribe to RTC routes, to be able to propagate them from # internal workers to this peer self._subscribe(AFI(AFI.ipv4), SAFI(SAFI.rtc)) else: # 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._activeFamilies: self._subscribe(afi, safi)
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 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")
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
class Subscription(_SubUnsubCommon): """Represents a Subscription to RouteEvents A subscription specifies the AFI, the SAFI, and the Route Target of the RouteEntry for which the subscriber wants to receive events. Any of these (afi, safi or route target) can be replaced by a wildcard: * Subscription.ANY_AFI * Subscription.ANY_SAFI * Subscription.ANY_RT """ ANY_AFI = AFI(0) ANY_SAFI = SAFI(0) ANY_RT = None def __init__(self, afi, safi, routeTarget=None, worker=None): _SubUnsubCommon.__init__(self, afi, safi, routeTarget, worker)
def setUp(self): super(TestVPNInstance, self).setUp() self.labelAllocator = LabelAllocator() self.mockDataplane = mock.Mock() self.mockDataplane.vifPlugged = mock.Mock() self.mockDataplane.vifUnplugged = mock.Mock() self.mockDPDriver = mock.Mock() self.mockDPDriver.initializeDataplaneInstance.returnValue = \ self.mockDataplane VPNInstance.afi = AFI(AFI.ipv4) VPNInstance.safi = SAFI(SAFI.mpls_vpn) self.vpnInstance = TestableVPNInstance(mock.Mock(name='BGPManager'), self.labelAllocator, self.mockDPDriver, 1, 1, [RT1], [RT1], '10.0.0.1', 24, None) self.vpnInstance.synthesizeVifBGPRoute = mock.Mock( return_value=RouteEntry(self.vpnInstance.afi, self.vpnInstance. safi, NLRI1, RT1, Attributes(), None)) self.vpnInstance._pushEvent = mock.Mock() self.vpnInstance._postFirstPlug = mock.Mock() self.vpnInstance.start()
from bagpipe.bgp.engine import RouteEvent from bagpipe.bgp.engine import RouteEntry from bagpipe.bgp.engine import Subscription from bagpipe.bgp.engine import Unsubscription from bagpipe.bgp.engine.worker import Worker from bagpipe.bgp.engine.bgp_peer_worker import BGPPeerWorker from bagpipe.bgp.engine.route_table_manager import RouteTableManager from bagpipe.bgp.engine.route_table_manager import Match from bagpipe.bgp.engine.route_table_manager import WorkerCleanupEvent from bagpipe.exabgp.message.update.attributes import Attributes from bagpipe.exabgp.structure.address import AFI, SAFI log = logging.getLogger() MATCH1 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT1) MATCH2 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT2) MATCH3 = Match(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), RT3) class TestRouteTableManager(TestCase, BaseTestBagPipeBGP): def setUp(self): super(TestRouteTableManager, self).setUp() self.routeTableManager = RouteTableManager() self.routeTableManager.start() self.setEventTargetWorker(self.routeTableManager) def tearDown(self): super(TestRouteTableManager, self).tearDown() self.routeTableManager.stop() self.routeTableManager.join()
class ExaBGPPeerWorker(BGPPeerWorker, LookingGlass): enabledFamilies = [ (AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn)), # (AFI(AFI.ipv6), SAFI(SAFI.mpls_vpn)), (AFI(AFI.l2vpn), SAFI(SAFI.evpn)) ] def __init__(self, bgpManager, name, peerAddress, config): BGPPeerWorker.__init__(self, bgpManager, name, peerAddress) self.config = config self.localAddress = self.config['local_address'] self.peerAddress = peerAddress self.connection = None self.rtc_active = False self._activeFamilies = [] def _toIdle(self): self._activeFamilies = [] def _initiateConnection(self): self.log.debug("Initiate ExaBGP connection to %s from %s", self.peerAddress, self.localAddress) self.rtc_active = False neighbor = Neighbor() neighbor.router_id = RouterID(self.config['local_address']) neighbor.local_as = self.config['my_as'] neighbor.peer_as = self.config['peer_as'] neighbor.local_address = self.config['local_address'] neighbor.peer_address = self.peerAddress neighbor.parse_routes = True # create dummy objects to fake exabgp into talking with us peer = FakePeer(neighbor) local = FakeLocal(self.localAddress) try: self.connection = Connection(peer, local, None, None) except Failure as e: raise InitiateConnectionException(repr(e)) self.log.debug("Instantiate ExaBGP Protocol") self.protocol = MyBGPProtocol(peer, self.connection) self.protocol.connect() # this is highly similar to exabgp.network.peer._run o = self.protocol.new_open(False, False, self.config, ExaBGPPeerWorker.enabledFamilies) self.log.debug("Send open: [%s]", o) self.fsm.state = FSM.OpenSent count = 0 self.log.debug("Wait for open...") while not self.shouldStop: # FIXME: we should time-out here, at some point message = self.protocol.read_open(o, None) count += 1 if isinstance(message, NOP): # TODO(tmmorin): check compliance with BGP specs... if count > 20: self.connection.close() # FIXME: this should be moved to # BGPPeerWorker in a more generic way # (+ send Notify when needed) raise OpenWaitTimeout("%ds" % int(20 * 0.5)) sleep(0.5) continue self.log.debug("Read message: %s", message) if isinstance(message, Open): break else: self.log.error("Received unexpected message: %s", message) # FIXME if self.shouldStop: raise StoppedException() # An Open was received received_open = message self._setHoldTime(received_open.hold_time) # Hack to ease troubleshooting, have the real peer address appear in # the logs when fakerr is used if received_open.router_id.ip != self.peerAddress: self.log.info( "changing thread name from %s to BGP-x%s, based on" " the router-id advertized in Open (different from" " peerAddress == %s)", self.name, received_open.router_id.ip, self.peerAddress) self.name = "BGP-%s:%s" % (self.peerAddress, received_open.router_id.ip) try: mp_capabilities = received_open.capabilities[ Capabilities.MULTIPROTOCOL_EXTENSIONS] except Exception: mp_capabilities = [] # check that our peer advertized at least mpls_vpn and evpn # capabilities self._activeFamilies = [] for (afi, safi) in (ExaBGPPeerWorker.enabledFamilies + [(AFI(AFI.ipv4), SAFI(SAFI.rtc))]): if (afi, safi) not in mp_capabilities: 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.peerAddress) self._activeFamilies.append((afi, safi)) if len(self._activeFamilies) == 0: self.log.error("No family was negotiated for VPN routes") # proceed BGP session self.connection.io.setblocking(1) self.enqueue(SendKeepAlive) self.fsm.state = FSM.OpenConfirm self.rtc_active = False if self.config['enable_rtc']: if (AFI(AFI.ipv4), SAFI(SAFI.rtc)) in mp_capabilities: self.log.info("RTC successfully enabled with peer %s", self.peerAddress) self.rtc_active = True else: self.log.warning( "enable_rtc True but peer not configured for RTC") def _toEstablished(self): BGPPeerWorker._toEstablished(self) if self.rtc_active: # subscribe to RTC routes, to be able to propagate them from # internal workers to this peer self._subscribe(AFI(AFI.ipv4), SAFI(SAFI.rtc)) else: # 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._activeFamilies: self._subscribe(afi, safi) def _receiveLoopFun(self): select.select([self.connection.io], [], [], 5) if not self._queue.empty(): if self._stopLoops.isSet(): self.log.info("stopLoops is set -> Close connection and" " Finish receive Loop") self.connection.close() return 0 try: message = self.protocol.read_message() except Notification as e: self.log.error("Peer notified us about an error: %s", e) return 2 except Failure as e: self.log.warning("Protocol failure: %s", e) return 2 except socket.error as e: self.log.warning("Socket error: %s", e) return 2 except Exception as e: self.log.error("Error while reading BGP message: %s", e) raise if message.TYPE in (NOP.TYPE): # we arrived here because select call timed-out return 1 elif message.TYPE == Update.TYPE: if (self.fsm.state != FSM.Established): raise Exception("Update received but not in Established state") pass # see below elif message.TYPE == KeepAlive.TYPE: if (self.fsm.state == FSM.OpenConfirm): self._toEstablished() self.enqueue(KeepAliveReceived) self.log.debug("Received message: %s", message) else: self.log.warning("Received unexpected message: %s", message) if isinstance(message, Update): self.log.info("Received message: UPDATE...") if message.routes: for route in message.routes: self._processReceivedRoute(route) return 1 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 _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.peerAddress) try: self.connection.write(data) except Exception as e: self.log.error("Was not able to send data: %s", e) def _keepAliveMessageData(self): return KeepAlive().message() def _updateForRouteEvent(self, event): r = Route(event.routeEntry.nlri) if event.type == event.ADVERTISE: self.log.info("Generate UPDATE message: %s", r) r.attributes = event.routeEntry.attributes try: return Update([r]).update(False, self.config['my_as'], self.config['my_as']) 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 '' elif event.type == event.WITHDRAW: self.log.info("Generate WITHDRAW message: %s", r) return Update([r]).withdraw(False, self.config['my_as'], self.config['my_as']) def stop(self): if self.connection is not None: self.connection.close() BGPPeerWorker.stop(self) # Looking Glass ############### def getLookingGlassLocalInfo(self, pathPrefix): return { "peeringAddresses": { "peerAddress": self.peerAddress, "localAddress": self.localAddress }, "as_info": { "local": self.config['my_as'], "peer": self.config['peer_as'] }, "rtc": { "active": self.rtc_active, "enabled": self.config['enable_rtc'] }, "active_families": [repr(f) for f in self._activeFamilies], }
class EVPNNLRI(object): ''' +-----------------------------------+ | Route Type (1 octet) | +-----------------------------------+ | Length (1 octet) | +-----------------------------------+ | Route Type specific (variable) | +-----------------------------------+ ''' afi = AFI(AFI.l2vpn) safi = SAFI(SAFI.evpn) def __init__(self, subtype, packedValue=None): self.subtype = subtype self.packedValue = packedValue def __str__(self): if self.subtype in EVPN_types_to_class: type_string = EVPN_types_to_class[self.subtype].nickname return "EVPN:%s" % type_string else: type_string = "%d" % self.subtype return "EVPN:%s:%s" % (type_string, "..." ) #FIXME: add binascii dump of packedValue def __repr__(self): return self.__str__() def pack(self): if self.packedValue is None: self._computePackedValue() return pack('!BB', self.subtype, len( self.packedValue)) + self.packedValue def _computePackedValue(self): raise Exception("Abstract class, cannot compute packedValue") def __len__(self): if self.packedValue is None: self._computePackedValue() return len(self.packedValue) + 2 def __cmp__(self, other): # # For subtype 2, we will have to ignore a part of the route, so this method will be overridden # if self.packedValue is None: self._computePackedValue() if other.packedValue is None: other._computePackedValue() if (isinstance(other, EVPNNLRI) and self.subtype == other.subtype and self.packedValue == other.packedValue): return 0 else: return -1 def __hash__(self): #FIXME: improve for better performance? # # same as above: need to ignore label and ESI for subtype 2 # if self.packedValue is None: self._computePackedValue() return hash("%s:%s:%s:%s" % (self.afi, self.safi, self.subtype, self.packedValue)) @staticmethod def unpack(data): #subtype typeid = ord(data[0]) data = data[1:] #length length = ord(data[0]) data = data[1:length + 1] if typeid in EVPN_types_to_class: return EVPN_types_to_class[typeid].unpack(data) else: return EVPNNLRI
def __init__ (self,afi,safi): Address.__init__(self,AFI(afi),SAFI(safi))
class EVI(VPNInstance, LookingGlass): ''' Implementation an E-VPN instance (EVI) based on RFC7432 and draft-ietf-bess-evpn-overlay. ''' type = "evpn" afi = AFI(AFI.l2vpn) safi = SAFI(SAFI.evpn) @logDecorator.log 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 generateVifBGPRoute(self, macAddress, ipPrefix, prefixLen, label): # Generate BGP route and advertise it... assert (prefixLen == 32) # label parameter ignored, we want the instance label lse = LabelStackEntry(self.instanceLabel, True) etag = None route = Route( EVPNMACAdvertisement( RouteDistinguisher(RouteDistinguisher.TYPE_IP_LOC, None, self.dataplaneDriver.getLocalAddress(), self.instanceId), EthernetSegmentIdentifier(), etag, MAC(macAddress), lse, ipPrefix)) return self._newRouteEntry(self.afi, self.safi, self.exportRTs, route.nlri, route.attributes) @logDecorator.log def setGatewayPort(self, linuxif, ipvpn): self.dataplane.setGatewayPort(linuxif) self.gwPort = (linuxif, ipvpn) @logDecorator.log def gatewayPortDown(self, linuxif): self.dataplane.gatewayPortDown(linuxif) self.gwPort = None def hasGatewayPort(self): return (self.gwPort is not None) # TrackerWorker callbacks for BGP route updates ########################## def _route2trackedEntry(self, route): if isinstance(route.nlri, EVPNMACAdvertisement): return (EVPNMACAdvertisement, route.nlri.mac) elif isinstance(route.nlri, EVPNMulticast): return (EVPNMulticast, (route.nlri.ip, route.nlri.rd)) elif isinstance(route.nlri, EVPNNLRI): self.log.warning("Received EVPN route of unsupported subtype: %s", route.nlri.subtype) else: raise Exception("EVI %d should not receive routes of type %s" % (self.instanceId, type(route.nlri))) @utils.synchronized @logDecorator.log def _newBestRoute(self, entry, newRoute): (entryClass, info) = entry encaps = self._checkEncaps(newRoute) if not encaps: return if entryClass == EVPNMACAdvertisement: prefix = info nh = newRoute.attributes.get(NextHop.ID) remotePE = nh.next_hop label = newRoute.nlri.label.labelValue self.dataplane.setupDataplaneForRemoteEndpoint( prefix, remotePE, label, newRoute.nlri, encaps) elif entryClass == EVPNMulticast: remote_endpoint = info # check that the route is actually carrying an PMSITunnel of type # ingress replication pmsi_tunnel = newRoute.attributes.get(PMSITunnel.ID) if not isinstance(pmsi_tunnel, PMSITunnelIngressReplication): self.log.warning("Received PMSITunnel of unsupported type: %s", type(pmsi_tunnel)) else: remote_endpoint = pmsi_tunnel.ip label = pmsi_tunnel.label.labelValue self.log.info( "Setting up dataplane for new ingress " "replication destination %s", remote_endpoint) self.dataplane.addDataplaneForBroadcastEndpoint( remote_endpoint, label, newRoute.nlri, encaps) else: self.log.warning("unsupported entryClass: %s", entryClass.__name__) @utils.synchronized @logDecorator.log def _bestRouteRemoved(self, entry, oldRoute, last): (entryClass, info) = entry if entryClass == EVPNMACAdvertisement: if self._skipRouteRemoval(last): self.log.debug("Skipping removal of non-last route because " "dataplane does not want it") return prefix = info nh = oldRoute.attributes.get(NextHop.ID) remotePE = nh.next_hop label = oldRoute.nlri.label.labelValue self.dataplane.removeDataplaneForRemoteEndpoint( prefix, remotePE, label, oldRoute.nlri) elif entryClass == EVPNMulticast: remote_endpoint = info # check that the route is actually carrying an PMSITunnel of type # ingress replication pmsi_tunnel = oldRoute.attributes.get(PMSITunnel.ID) if not isinstance(pmsi_tunnel, PMSITunnelIngressReplication): self.log.warning("PMSITunnel of suppressed route is of" " unsupported type") else: remote_endpoint = pmsi_tunnel.ip label = pmsi_tunnel.label.labelValue self.log.info( "Cleaning up dataplane for ingress replication " "destination %s", remote_endpoint) self.dataplane.removeDataplaneForBroadcastEndpoint( remote_endpoint, label, oldRoute.nlri) else: self.log.warning("unsupported entryClass: %s", entryClass.__name__) # Looking Glass #### def getLookingGlassLocalInfo(self, pathPrefix): if not self.gwPort: return {"gateway_port": None} else: (linuxif, ipvpn) = self.gwPort return { "gateway_port": { "interface": repr(linuxif), "ipvpn": { "href": LookingGlassReferences.getAbsolutePath( "VPN_INSTANCES", pathPrefix, [ipvpn.externalInstanceId]), "id": ipvpn.name, "external_instance_id": ipvpn.externalInstanceId }, } }
def __init__(self, afi, raw): self.afi = AFI(afi) self.raw = raw self.__update()
def CapabilitiesFactory(self, data): capabilities = Capabilities() option_len = ord(data[0]) if option_len: data = data[1:] while data: key, value, data = self._key_values('parameter', data) # Paramaters must only be sent once. if key == Parameter.AUTHENTIFICATION_INFORMATION: raise Notify(2, 5) if key == Parameter.CAPABILITIES: while value: k, capv, value = self._key_values('capability', value) # Multiple Capabilities can be present in a single attribute #if r: # raise Notify(2,0,"Bad length for OPEN %s (size mismatch) %s" % ('capability',hexa(value))) if k == Capabilities.MULTIPROTOCOL_EXTENSIONS: if k not in capabilities: capabilities[k] = MultiProtocol() afi = AFI(unpack('!H', capv[:2])[0]) safi = SAFI(ord(capv[3])) capabilities[k].append((afi, safi)) continue if k == Capabilities.GRACEFUL_RESTART: restart = unpack('!H', capv[:2])[0] restart_flag = restart >> 12 restart_time = restart & Graceful.TIME_MASK value_gr = capv[2:] families = [] while value_gr: afi = AFI(unpack('!H', value_gr[:2])[0]) safi = SAFI(ord(value_gr[2])) flag_family = ord(value_gr[0]) families.append((afi, safi, flag_family)) value_gr = value_gr[4:] capabilities[k] = Graceful(restart_flag, restart_time, families) continue if k == Capabilities.FOUR_BYTES_ASN: capabilities[k] = ASN(unpack('!L', capv[:4])[0]) continue if k == Capabilities.ROUTE_REFRESH: capabilities[k] = RouteRefresh() continue if k == Capabilities.CISCO_ROUTE_REFRESH: capabilities[k] = CiscoRouteRefresh() continue if k == Capabilities.MULTISESSION_BGP: capabilities[k] = MultiSession() continue if k == Capabilities.MULTISESSION_BGP_RFC: capabilities[k] = MultiSession() continue if k not in capabilities: capabilities[k] = Unknown(k, [ord(_) for _ in capv]) else: raise Notify(2, 0, 'Unknown OPEN parameter %s' % hex(key)) return capabilities
def __init__(self, afi, safi, origin_as, route_target): self.afi = AFI(afi) self.safi = SAFI(safi) self.origin_as = origin_as self.route_target = route_target
class VRF(VPNInstance, LookingGlass): # 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 = "ipvpn" afi = AFI(AFI.ipv4) safi = SAFI(SAFI.mpls_vpn) @logDecorator.log def __init__(self, *args, **kwargs): VPNInstance.__init__(self, *args, **kwargs) self.readvertised = set() def _routeFrom(self, prefix, label, rd): return Route( VPNLabelledPrefix(self.afi, self.safi, prefix, rd, [LabelStackEntry(label, True)])) def generateVifBGPRoute(self, macAdress, ipPrefix, prefixLen, label): # Generate BGP route and advertise it... route = self._routeFrom( Prefix(self.afi, ipPrefix, prefixLen), label, RouteDistinguisher(RouteDistinguisher.TYPE_IP_LOC, None, self.bgpManager.getLocalAddress(), self.instanceId)) self.log.debug("route attributes: %s", route.attributes) return self._newRouteEntry(self.afi, self.safi, self.exportRTs, route.nlri, route.attributes) def _getLocalLabels(self): for portData in self.macAddress2LocalPortData.itervalues(): yield portData['label'] def _getRDFromLabel(self, label): # FIXME: this is a crude hack that will break beyond 10000 VRFs return RouteDistinguisher(RouteDistinguisher.TYPE_IP_LOC, None, self.bgpManager.getLocalAddress(), 10000 + label) def _routeForReAdvertisement(self, prefix, label): route = self._routeFrom(prefix, label, self._getRDFromLabel(label)) nh = Inet( 1, socket.inet_pton(socket.AF_INET, self.dataplane.driver.getLocalAddress())) route.attributes.add(NextHop(nh)) route.attributes.add(ECommunities(self.readvertiseToRTs)) routeEntry = self._newRouteEntry(self.afi, self.safi, self.readvertiseToRTs, route.nlri, route.attributes) return routeEntry @logDecorator.log 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) @logDecorator.log 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 vifPlugged(self, macAddress, ipAddressPrefix, localPort, advertiseSubnet): VPNInstance.vifPlugged(self, macAddress, ipAddressPrefix, localPort, advertiseSubnet) label = self.macAddress2LocalPortData[macAddress]['label'] for prefix in self.readvertised: self.log.debug("Re-advertising %s with this port as next hop", prefix) routeEntry = self._routeForReAdvertisement(prefix, label) self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry)) 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) # Callbacks for BGP route updates (TrackerWorker) ######################## def _route2trackedEntry(self, route): if isinstance(route.nlri, VPNLabelledPrefix): return route.nlri.prefix else: self.log.error("We should not receive routes of type %s", type(route.nlri)) return None def _toReadvertise(self, route): return (len( set(route.routeTargets).intersection(set(self.readvertiseFromRTs))) > 0) def _imported(self, route): return (len(set(route.routeTargets).intersection(set(self.importRTs))) > 0) @utils.synchronized @logDecorator.log def _newBestRoute(self, entry, newRoute): prefix = entry if self.readvertise: # check if this is a route we need to re-advertise self.log.debug("route RTs: %s", newRoute.routeTargets) self.log.debug("readv from RTs: %s", self.readvertiseFromRTs) if self._toReadvertise(newRoute): self.log.debug("Need to re-advertise %s", prefix) self._readvertise(newRoute.nlri) if not self._imported(newRoute): self.log.debug("No need to setup dataplane for:%s", prefix) return encaps = self._checkEncaps(newRoute) if not encaps: return self.dataplane.setupDataplaneForRemoteEndpoint( prefix, newRoute.attributes.get(NextHop.ID).next_hop, newRoute.nlri.labelStack[0].labelValue, newRoute.nlri, encaps) @utils.synchronized @logDecorator.log def _bestRouteRemoved(self, entry, oldRoute, last): prefix = entry if self.readvertise and last: # check if this is a route we were re-advertising if self._toReadvertise(oldRoute): self.log.debug("Need to stop re-advertising %s", prefix) self._readvertiseStop(oldRoute.nlri) if not self._imported(oldRoute): self.log.debug("No need to setup dataplane for:%s", prefix) return if self._skipRouteRemoval(last): self.log.debug("Skipping removal of non-last route because " "dataplane does not want it") return self.dataplane.removeDataplaneForRemoteEndpoint( prefix, oldRoute.attributes.get(NextHop.ID).next_hop, oldRoute.nlri.labelStack[0].labelValue, oldRoute.nlri) def getLGMap(self): return { "readvertised": (LGMap.VALUE, [repr(prefix) for prefix in self.readvertised]) }
def _initiateConnection(self): self.log.debug("Initiate ExaBGP connection to %s from %s", self.peerAddress, self.localAddress) self.rtc_active = False neighbor = Neighbor() neighbor.router_id = RouterID(self.config['local_address']) neighbor.local_as = self.config['my_as'] neighbor.peer_as = self.config['peer_as'] neighbor.local_address = self.config['local_address'] neighbor.peer_address = self.peerAddress neighbor.parse_routes = True # create dummy objects to fake exabgp into talking with us peer = FakePeer(neighbor) local = FakeLocal(self.localAddress) try: self.connection = Connection(peer, local, None, None) except Failure as e: raise InitiateConnectionException(repr(e)) self.log.debug("Instantiate ExaBGP Protocol") self.protocol = MyBGPProtocol(peer, self.connection) self.protocol.connect() # this is highly similar to exabgp.network.peer._run o = self.protocol.new_open(False, False, self.config, ExaBGPPeerWorker.enabledFamilies) self.log.debug("Send open: [%s]", o) self.fsm.state = FSM.OpenSent count = 0 self.log.debug("Wait for open...") while not self.shouldStop: # FIXME: we should time-out here, at some point message = self.protocol.read_open(o, None) count += 1 if isinstance(message, NOP): # TODO(tmmorin): check compliance with BGP specs... if count > 20: self.connection.close() # FIXME: this should be moved to # BGPPeerWorker in a more generic way # (+ send Notify when needed) raise OpenWaitTimeout("%ds" % int(20 * 0.5)) sleep(0.5) continue self.log.debug("Read message: %s", message) if isinstance(message, Open): break else: self.log.error("Received unexpected message: %s", message) # FIXME if self.shouldStop: raise StoppedException() # An Open was received received_open = message self._setHoldTime(received_open.hold_time) # Hack to ease troubleshooting, have the real peer address appear in # the logs when fakerr is used if received_open.router_id.ip != self.peerAddress: self.log.info( "changing thread name from %s to BGP-x%s, based on" " the router-id advertized in Open (different from" " peerAddress == %s)", self.name, received_open.router_id.ip, self.peerAddress) self.name = "BGP-%s:%s" % (self.peerAddress, received_open.router_id.ip) try: mp_capabilities = received_open.capabilities[ Capabilities.MULTIPROTOCOL_EXTENSIONS] except Exception: mp_capabilities = [] # check that our peer advertized at least mpls_vpn and evpn # capabilities self._activeFamilies = [] for (afi, safi) in (ExaBGPPeerWorker.enabledFamilies + [(AFI(AFI.ipv4), SAFI(SAFI.rtc))]): if (afi, safi) not in mp_capabilities: 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.peerAddress) self._activeFamilies.append((afi, safi)) if len(self._activeFamilies) == 0: self.log.error("No family was negotiated for VPN routes") # proceed BGP session self.connection.io.setblocking(1) self.enqueue(SendKeepAlive) self.fsm.state = FSM.OpenConfirm self.rtc_active = False if self.config['enable_rtc']: if (AFI(AFI.ipv4), SAFI(SAFI.rtc)) in mp_capabilities: self.log.info("RTC successfully enabled with peer %s", self.peerAddress) self.rtc_active = True else: self.log.warning( "enable_rtc True but peer not configured for RTC")