def _reader (self, number): # The function must not be called if it does not return with no data with a smaller size as parameter if not self.io: self.close() raise NotConnected('Trying to read on a closed TCP connection') if number == 0: yield b'' return while not self.reading(): yield b'' data = b'' reported = '' while True: try: while True: if self.defensive and random.randint(0,2): raise socket.error(errno.EAGAIN,'raising network error on purpose') read = self.io.recv(number) if not read: self.close() self.logger.wire("%s %s lost TCP session with peer" % (self.name(),self.peer),source=self.session()) raise LostConnection('the TCP connection was closed by the remote end') data += read number -= len(read) if not number: self.logger.wire( LazyFormat( "%s %-32s RECEIVED " % ( self.name(), '%s / %s' % (self.local,self.peer) ), read ), source=self.session() ) yield data return yield b'' except socket.timeout as exc: self.close() self.logger.wire("%s %s peer is too slow" % (self.name(),self.peer),source=self.session()) raise TooSlowError('Timeout while reading data from the network (%s)' % errstr(exc)) except socket.error as exc: if exc.args[0] in error.block: message = "%s %s blocking io problem mid-way through reading a message %s, trying to complete" % (self.name(),self.peer,errstr(exc)) if message != reported: reported = message self.logger.wire(message,'debug',self.session()) yield b'' elif exc.args[0] in error.fatal: self.close() raise LostConnection('issue reading on the socket: %s' % errstr(exc)) # what error could it be ! else: self.logger.wire("%s %s undefined error reading on socket" % (self.name(),self.peer),source=self.session()) raise NetworkError('Problem while reading data from the network (%s)' % errstr(exc))
def parse(self, data, negotiated): if not data: return self # We do not care if the attribute are transitive or not as we do not redistribute flag = Attribute.Flag(ord(data[0])) aid = Attribute.CODE(ord(data[1])) if flag & Attribute.Flag.EXTENDED_LENGTH: length = unpack('!H', data[2:4])[0] offset = 4 else: length = ord(data[2]) offset = 3 data = data[offset:] left = data[length:] attribute = data[:length] logger = Logger() logger.parser( LazyFormat( "parsing flag %x type %02x (%s) len %02x %s" % (flag, int(aid), aid, length, 'payload ' if length else ''), data[:length])) # remove the PARTIAL bit before comparaison if the attribute is optional if aid in Attribute.attributes_optional: aid &= Attribute.Flag.MASK_PARTIAL & 0xFF # aid &= ~Attribute.Flag.PARTIAL & 0xFF # cleaner than above (python use signed integer for ~) # handle the attribute if we know it if Attribute.registered(aid, flag): self.add(Attribute.unpack(aid, flag, attribute, negotiated)) return self.parse(left, negotiated) # XXX: FIXME: we could use a fallback function here like capability # if we know the attribute but the flag is not what the RFC says. ignore it. if aid in Attribute.attributes_known: logger.parser( 'invalid flag for attribute %s (flag 0x%02X, aid 0x%02X)' % (Attribute.CODE.names.get(aid, 'unset'), flag, aid)) return self.parse(left, negotiated) # it is an unknown transitive attribute we need to pass on if flag & Attribute.Flag.TRANSITIVE: logger.parser( 'unknown transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid)) self.add( GenericAttribute(aid, flag | Attribute.Flag.PARTIAL, attribute), attribute) return self.parse(left, negotiated) # it is an unknown non-transitive attribute we can ignore. logger.parser( 'ignoring unknown non-transitive attribute (flag 0x%02X, aid 0x%02X)' % (flag, aid)) return self.parse(left, negotiated)
def writer(self, data): if not self.io: # XXX: FIXME: Make sure it does not hold the cleanup during the closing of the peering session yield True return while not self.writing(): yield False self.logger.debug(LazyFormat('sending TCP payload', data), self.session()) # The first while is here to setup the try/catch block once as it is very expensive while True: try: while True: if self.defensive and random.randint(0, 2): raise socket.error(errno.EAGAIN, 'raising network error on purpose') # we can not use sendall as in case of network buffer filling # it does raise and does not let you know how much was sent number = self.io.send(data) if not number: self.close() self.logger.warning( '%s %s lost TCP connection with peer' % (self.name(), self.peer), self.session()) raise LostConnection('lost the TCP connection') data = data[number:] if not data: yield True return yield False except socket.error as exc: if exc.args[0] in error.block: self.logger.debug( '%s %s blocking io problem mid-way through writing a message %s, trying to complete' % (self.name(), self.peer, errstr(exc)), self.session()) yield False elif exc.errno == errno.EPIPE: # The TCP connection is gone. self.close() raise NetworkError('Broken TCP connection') elif exc.args[0] in error.fatal: self.close() self.logger.critical( '%s %s problem sending message (%s)' % (self.name(), self.peer, errstr(exc)), self.session()) raise NetworkError( 'Problem while writing data to the network (%s)' % errstr(exc)) # what error could it be ! else: self.logger.critical( '%s %s undefined error writing on socket' % (self.name(), self.peer), self.session()) yield False
def unpack (cls,afi,safi,data,addpath,nexthop,action): if not cls.logger: cls.logger = Logger() cls.logger.parser(LazyFormat("parsing %s/%s nlri payload " % (afi,safi),data)) key = '%d/%d' % (afi,safi) if key in cls.registered_nlri: return cls.registered_nlri[key].unpack(afi,safi,data,addpath,nexthop,action) raise Notify(3,0,'trying to decode unknown family %s/%s' % (AFI(afi),SAFI(safi)))
def _reader (self,number): # The function must not be called if it does not return with no data with a smaller size as parameter if not self.io: self.close() raise NotConnected('Trying to read on a close TCP conncetion') if number == 0: yield '' return # XXX: one of the socket option is to recover the size of the buffer # XXX: we could use it to not have to put together the string with multiple reads # XXX: and get rid of the self.read_timeout option while not self.reading(): yield '' data = '' while True: try: while True: if self._reading is None: self._reading = time.time() elif time.time() > self._reading + self.read_timeout: self.close() self.logger.wire("%s %s peer is too slow (we were told there was data on the socket but we can not read up to what should be there)" % (self.name(),self.peer)) raise TooSlowError('Waited to read for data on a socket for more than %d second(s)' % self.read_timeout) if self.defensive and random.randint(0,2): raise socket.error(errno.EAGAIN,'raising network error in purpose') read = self.io.recv(number) if not read: self.close() self.logger.wire("%s %s lost TCP session with peer" % (self.name(),self.peer)) raise LostConnection('the TCP connection was closed by the remote end') data += read number -= len(read) if not number: self.logger.wire(LazyFormat("%s %-32s RECEIVED " % (self.name(),'%s / %s' % (self.local,self.peer)),od,read)) self._reading = None yield data return except socket.timeout,e: self.close() self.logger.wire("%s %s peer is too slow" % (self.name(),self.peer)) raise TooSlowError('Timeout while reading data from the network (%s)' % errstr(e)) except socket.error,e: if e.args[0] in error.block: self.logger.wire("%s %s blocking io problem mid-way through reading a message %s, trying to complete" % (self.name(),self.peer,errstr(e)),'debug') elif e.args[0] in error.fatal: self.close() raise LostConnection('issue reading on the socket: %s' % errstr(e)) # what error could it be ! else: self.logger.wire("%s %s undefined error reading on socket" % (self.name(),self.peer)) raise NetworkError('Problem while reading data from the network (%s)' % errstr(e))
def writer (self,data): if not self.io: # XXX: FIXME: Make sure it does not hold the cleanup during the closing of the peering session yield True return if not self.writing(): yield False return self.logger.wire(LazyFormat("%s %-32s SENDING " % (self.name(),'%s / %s' % (self.local,self.peer)),od,data)) # The first while is here to setup the try/catch block once as it is very expensive while True: try: while True: if self._writing is None: self._writing = time.time() elif time.time() > self._writing + self.read_timeout: self.close() self.logger.wire("%s %s peer is too slow" % (self.name(),self.peer)) raise TooSlowError('Waited to write for data on a socket for more than %d second(s)' % self.read_timeout) if self.defensive and random.randint(0,2): raise socket.error(errno.EAGAIN,'raising network error in purpose') # we can not use sendall as in case of network buffer filling # it does raise and does not let you know how much was sent nb = self.io.send(data) if not nb: self.close() self.logger.wire("%s %s lost TCP connection with peer" % (self.name(),self.peer)) raise LostConnection('lost the TCP connection') data = data[nb:] if not data: self._writing = None yield True return yield False except socket.error,e: if e.args[0] in error.block: self.logger.wire("%s %s blocking io problem mid-way through writing a message %s, trying to complete" % (self.name(),self.peer,errstr(e)),'debug') elif e.errno == errno.EPIPE: # The TCP connection is gone. self.close() raise NetworkError('Broken TCP connection') elif e.args[0] in error.fatal: self.close() self.logger.wire("%s %s problem sending message (%s)" % (self.name(),self.peer,errstr(e))) raise NetworkError('Problem while writing data to the network (%s)' % errstr(e)) # what error could it be ! else: self.logger.wire("%s %s undefined error writing on socket" % (self.name(),self.peer)) yield False
def _main (self, direction): """yield True if we want to come back to it asap, None if nothing urgent, and False if stopped""" if self._teardown: raise Notify(6,3) proto = self._[direction]['proto'] # Announce to the process BGP is up self.logger.network('Connected to peer %s (%s)' % (self.neighbor.name(),direction)) if self.neighbor.api['neighbor-changes']: try: self.reactor.processes.up(self) except ProcessError: # Can not find any better error code than 6,0 ! # XXX: We can not restart the program so this will come back again and again - FIX # XXX: In the main loop we do exit on this kind of error raise Notify(6,0,'ExaBGP Internal error, sorry.') send_eor = not self.neighbor.manual_eor new_routes = None self._resend_routes = SEND.NORMAL send_families = [] # Every last asm message should be re-announced on restart for family in self.neighbor.asm: if family in self.neighbor.families(): self.neighbor.messages.appendleft(self.neighbor.asm[family]) operational = None refresh = None command_eor = None number = 0 refresh_enhanced = True if proto.negotiated.refresh == REFRESH.ENHANCED else False self.send_ka = KA(self.me,proto) while not self._teardown: for message in proto.read_message(): self.recv_timer.check_ka(message) if self.send_ka() is not False: # we need and will send a keepalive while self.send_ka() is None: yield ACTION.NOW # Received update if message.TYPE == Update.TYPE: number += 1 self.logger.routes(LazyFormat(self.me('<< UPDATE (%d)' % number),message.attributes,lambda _: "%s%s" % (' attributes' if _ else '',_))) for nlri in message.nlris: self.neighbor.rib.incoming.insert_received(Change(nlri,message.attributes)) self.logger.routes(LazyFormat(self.me('<< UPDATE (%d) nlri ' % number),nlri,str)) elif message.TYPE == RouteRefresh.TYPE: if message.reserved == RouteRefresh.request: self._resend_routes = SEND.REFRESH send_families.append((message.afi,message.safi)) # SEND OPERATIONAL if self.neighbor.operational: if not operational: new_operational = self.neighbor.messages.popleft() if self.neighbor.messages else None if new_operational: operational = proto.new_operational(new_operational,proto.negotiated) if operational: try: operational.next() except StopIteration: operational = None # SEND REFRESH if self.neighbor.route_refresh: if not refresh: new_refresh = self.neighbor.refresh.popleft() if self.neighbor.refresh else None if new_refresh: refresh = proto.new_refresh(new_refresh) if refresh: try: refresh.next() except StopIteration: refresh = None # Take the routes already sent to that peer and resend them if self._reconfigure: self._reconfigure = False # we are here following a configuration change if self._neighbor: # see what changed in the configuration self.neighbor.rib.outgoing.replace(self._neighbor.backup_changes,self._neighbor.changes) # do not keep the previous routes in memory as they are not useful anymore self._neighbor.backup_changes = [] self._have_routes = True # Take the routes already sent to that peer and resend them if self._resend_routes != SEND.DONE: enhanced = True if refresh_enhanced and self._resend_routes == SEND.REFRESH else False self._resend_routes = SEND.DONE self.neighbor.rib.outgoing.resend(send_families,enhanced) self._have_routes = True send_families = [] # Need to send update if self._have_routes and not new_routes: self._have_routes = False # XXX: in proto really. hum to think about ? new_routes = proto.new_update() if new_routes: try: count = 20 while count: # This can raise a NetworkError new_routes.next() count -= 1 except StopIteration: new_routes = None elif send_eor: send_eor = False for _ in proto.new_eors(): yield ACTION.NOW self.logger.message(self.me('>> EOR(s)')) # SEND MANUAL KEEPALIVE (only if we have no more routes to send) elif not command_eor and self.neighbor.eor: new_eor = self.neighbor.eor.popleft() command_eor = proto.new_eors(new_eor.afi,new_eor.safi) if command_eor: try: command_eor.next() except StopIteration: command_eor = None if new_routes or message.TYPE != NOP.TYPE: yield ACTION.NOW elif self.neighbor.messages or operational: yield ACTION.NOW elif self.neighbor.eor or command_eor: yield ACTION.NOW else: yield ACTION.LATER # read_message will loop until new message arrives with NOP if self._teardown: break # If graceful restart, silent shutdown if self.neighbor.graceful_restart and proto.negotiated.sent_open.capabilities.announced(Capability.CODE.GRACEFUL_RESTART): self.logger.network('Closing the session without notification','error') proto.close('graceful restarted negotiated, closing without sending any notification') raise NetworkError('closing') # notify our peer of the shutdown raise Notify(6,self._teardown)
def unpack_message(cls, data, negotiated): logger = Logger() logger.debug(LazyFormat('parsing UPDATE', data), 'parser') length = len(data) # This could be speed up massively by changing the order of the IF if length == 4 and data == b'\x00\x00\x00\x00': return EOR(AFI.ipv4, SAFI.unicast) # pylint: disable=E1101 if length == 11 and data.startswith(EOR.NLRI.PREFIX): return EOR.unpack_message(data, negotiated) withdrawn, _attributes, announced = cls.split(data) if not withdrawn: logger.debug('withdrawn NLRI none', 'routes') attributes = Attributes.unpack(_attributes, negotiated) if not announced: logger.debug('announced NLRI none', 'routes') # Is the peer going to send us some Path Information with the route (AddPath) addpath = negotiated.addpath.receive(AFI.ipv4, SAFI.unicast) # empty string for NoNextHop, the packed IP otherwise (without the 3/4 bytes of attributes headers) nexthop = attributes.get(Attribute.CODE.NEXT_HOP, NoNextHop) # nexthop = NextHop.unpack(_nexthop.ton()) # XXX: NEXTHOP MUST NOT be the IP address of the receiving speaker. nlris = [] while withdrawn: nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, withdrawn, IN.WITHDRAWN, addpath) logger.debug('withdrawn NLRI %s' % nlri, 'routes') withdrawn = left nlris.append(nlri) while announced: nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, announced, IN.ANNOUNCED, addpath) nlri.nexthop = nexthop logger.debug('announced NLRI %s' % nlri, 'routes') announced = left nlris.append(nlri) unreach = attributes.pop(MPURNLRI.ID, None) reach = attributes.pop(MPRNLRI.ID, None) if unreach is not None: nlris.extend(unreach.nlris) if reach is not None: nlris.extend(reach.nlris) if not attributes and not nlris: # Careful do not use == or != as the comparaison does not work if unreach is None and reach is None: return EOR(AFI.ipv4, SAFI.unicast) if unreach is not None: return EOR(unreach.afi, unreach.safi) if reach is not None: return EOR(reach.afi, reach.safi) raise RuntimeError('This was not expected') update = Update(nlris, attributes) def parsed(_): # we need the import in the function as otherwise we have an cyclic loop # as this function currently uses Update.. from exabgp.reactor.api.response import Response from exabgp.version import json as json_version return 'json %s' % Response.JSON(json_version).update( negotiated.neighbor, 'in', update, None, '', '') logger.debug(LazyFormat('decoded UPDATE', '', parsed), 'parser') return update
def unpack_message(cls, data, negotiated): logger = Logger() logger.parser(LazyFormat("parsing UPDATE", data)) length = len(data) # This could be speed up massively by changing the order of the IF if length == 4 and data == b'\x00\x00\x00\x00': return EOR(AFI(AFI.ipv4), SAFI(SAFI.unicast)) # pylint: disable=E1101 if length == 11 and data.startswith(EOR.NLRI.PREFIX): return EOR.unpack_message(data, negotiated) withdrawn, _attributes, announced = cls.split(data) if not withdrawn: logger.parser("withdrawn NLRI none") attributes = Attributes.unpack(_attributes, negotiated) if not announced: logger.parser("announced NLRI none") # Is the peer going to send us some Path Information with the route (AddPath) addpath = negotiated.addpath.receive(AFI(AFI.ipv4), SAFI(SAFI.unicast)) # empty string for NoNextHop, the packed IP otherwise (without the 3/4 bytes of attributes headers) nexthop = attributes.get(Attribute.CODE.NEXT_HOP, NoNextHop) # nexthop = NextHop.unpack(_nexthop.ton()) # XXX: NEXTHOP MUST NOT be the IP address of the receiving speaker. nlris = [] while withdrawn: nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, withdrawn, IN.WITHDRAWN, addpath) logger.parser("withdrawn NLRI %s" % nlri) withdrawn = left nlris.append(nlri) while announced: nlri, left = NLRI.unpack_nlri(AFI.ipv4, SAFI.unicast, announced, IN.ANNOUNCED, addpath) nlri.nexthop = nexthop logger.parser("announced NLRI %s" % nlri) announced = left nlris.append(nlri) unreach = attributes.pop(MPURNLRI.ID, None) reach = attributes.pop(MPRNLRI.ID, None) if unreach is not None: nlris.extend(unreach.nlris) if reach is not None: nlris.extend(reach.nlris) if not attributes and not nlris: # Careful do not use == or != as the comparaison does not work if unreach is None and reach is None: return EOR(AFI(AFI.ipv4), SAFI(SAFI.unicast)) if unreach is not None: return EOR(unreach.afi, unreach.safi) if reach is not None: return EOR(reach.afi, reach.safi) raise RuntimeError('This was not expected') return Update(nlris, attributes)
def _factory(self, data): if not data: return self # We do not care if the attribute are transitive or not as we do not redistribute flag = Flag(ord(data[0])) code = AID(ord(data[1])) if flag & Flag.EXTENDED_LENGTH: length = unpack('!H', data[2:4])[0] offset = 4 else: length = ord(data[2]) offset = 3 if self.hasmp: if code not in (AID.MP_REACH_NLRI, AID.MP_UNREACH_NLRI): self.cacheable = False self.prefix = '' else: self.prefix += data[:offset + length] data = data[offset:] next = data[length:] attribute = data[:length] logger = Logger() logger.parser( LazyFormat( "parsing flag %x type %02x (%s) len %02x %s" % (flag, int(code), code, length, 'payload ' if length else ''), od, data[:length])) if code == AID.ORIGIN and flag.matches(Origin.FLAG): # This if block should never be called anymore ... if not self.add_from_cache(code, attribute): self.add(Origin(ord(attribute)), attribute) return self.factory(next) # only 2-4% of duplicated data - is it worth to cache ? if code == AID.AS_PATH and flag.matches(ASPath.FLAG): if length: # we store the AS4_PATH as AS_PATH, do not over-write if not self.has(code): if not self.add_from_cache(code, attribute): self.add(self.__new_ASPath(attribute), attribute) return self.factory(next) if code == AID.AS4_PATH and flag.matches(AS4Path.FLAG): if length: # ignore the AS4_PATH on new spekers as required by RFC 4893 section 4.1 if not self.negotiated.asn4: # This replace the old AS_PATH if not self.add_from_cache(code, attribute): self.add(self.__new_ASPath4(attribute), attribute) return self.factory(next) if code == AID.NEXT_HOP and flag.matches(NextHop.FLAG): # XXX: FIXME: we are double caching the NH (once in the class, once here) if not self.add_from_cache(code, attribute): self.add(cachedNextHop(attribute), attribute) return self.factory(next) if code == AID.MED and flag.matches(MED.FLAG): if not self.add_from_cache(code, attribute): self.add(MED(attribute), attribute) return self.factory(next) if code == AID.LOCAL_PREF and flag.matches(LocalPreference.FLAG): if not self.add_from_cache(code, attribute): self.add(LocalPreference(attribute), attribute) return self.factory(next) if code == AID.ATOMIC_AGGREGATE and flag.matches(AtomicAggregate.FLAG): if not self.add_from_cache(code, attribute): raise Notify( 3, 2, 'invalid ATOMIC_AGGREGATE %s' % [hex(ord(_)) for _ in attribute]) return self.factory(next) if code == AID.AGGREGATOR and flag.matches(Aggregator.FLAG): # AS4_AGGREGATOR are stored as AGGREGATOR - so do not overwrite if exists if not self.has(code): if not self.add_from_cache(AID.AGGREGATOR, attribute): self.add(Aggregator(attribute), attribute) return self.factory(next) if code == AID.AS4_AGGREGATOR and flag.matches(Aggregator.FLAG): if not self.add_from_cache(AID.AGGREGATOR, attribute): self.add(Aggregator(attribute), attribute) return self.factory(next) if code == AID.COMMUNITY and flag.matches(Communities.FLAG): if not self.add_from_cache(code, attribute): self.add(self.__new_communities(attribute), attribute) return self.factory(next) if code == AID.ORIGINATOR_ID and flag.matches(OriginatorID.FLAG): if not self.add_from_cache(code, attribute): self.add(OriginatorID(AFI.ipv4, SAFI.unicast, data[:4]), attribute) return self.factory(next) if code == AID.CLUSTER_LIST and flag.matches(ClusterList.FLAG): if not self.add_from_cache(code, attribute): self.add(ClusterList(attribute), attribute) return self.factory(next) if code == AID.EXTENDED_COMMUNITY and flag.matches(ECommunities.FLAG): if not self.add_from_cache(code, attribute): self.add(self.__new_extended_communities(attribute), attribute) return self.factory(next) if code == AID.AIGP and flag.matches(AIGP.FLAG): if self.negotiated.neighbor.aigp: if not self.add_from_cache(code, attribute): self.add(AIGP(attribute), attribute) return self.factory(next) if code == AID.MP_UNREACH_NLRI and flag.matches(MPURNLRI.FLAG): self.hasmp = True # -- Reading AFI/SAFI data = data[:length] afi, safi = unpack('!HB', data[:3]) offset = 3 data = data[offset:] if (afi, safi) not in self.negotiated.families: raise Notify( 3, 0, 'presented a non-negotiated family %d/%d' % (afi, safi)) # Is the peer going to send us some Path Information with the route (AddPath) addpath = self.negotiated.addpath.receive(afi, safi) # XXX: we do assume that it is an EOR. most likely harmless if not data: self.mp_withdraw.append(NLRIEOR(afi, safi, IN.announced)) return self.factory(next) while data: length, nlri = self.nlriFactory(afi, safi, data, addpath, None, IN.withdrawn) self.mp_withdraw.append(nlri) data = data[length:] logger.parser( LazyFormat("parsed withdraw mp nlri %s payload " % nlri, od, data[:length])) return self.factory(next) if code == AID.MP_REACH_NLRI and flag.matches(MPRNLRI.FLAG): self.hasmp = True data = data[:length] # -- Reading AFI/SAFI afi, safi = unpack('!HB', data[:3]) offset = 3 # we do not want to accept unknown families if (afi, safi) not in self.negotiated.families: raise Notify( 3, 0, 'presented a non-negotiated family %d/%d' % (afi, safi)) # -- Reading length of next-hop len_nh = ord(data[offset]) offset += 1 rd = 0 # check next-hope size if afi == AFI.ipv4: if safi in (SAFI.unicast, SAFI.multicast): if len_nh != 4: raise Notify( 3, 0, 'invalid ipv4 unicast/multicast next-hop length %d expected 4' % len_nh) elif safi in (SAFI.mpls_vpn, ): if len_nh != 12: raise Notify( 3, 0, 'invalid ipv4 mpls_vpn next-hop length %d expected 12' % len_nh) rd = 8 elif safi in (SAFI.flow_ip, ): if len_nh not in (0, 4): raise Notify( 3, 0, 'invalid ipv4 flow_ip next-hop length %d expected 4' % len_nh) elif safi in (SAFI.flow_vpn, ): if len_nh not in (0, 4): raise Notify( 3, 0, 'invalid ipv4 flow_vpn next-hop length %d expected 4' % len_nh) elif afi == AFI.ipv6: if safi in (SAFI.unicast, ): if len_nh not in (16, 32): raise Notify( 3, 0, 'invalid ipv6 unicast next-hop length %d expected 16 or 32' % len_nh) elif safi in (SAFI.mpls_vpn, ): if len_nh not in (24, 40): raise Notify( 3, 0, 'invalid ipv6 mpls_vpn next-hop length %d expected 24 or 40' % len_nh) rd = 8 elif safi in (SAFI.flow_ip, ): if len_nh not in (0, 16, 32): raise Notify( 3, 0, 'invalid ipv6 flow_ip next-hop length %d expected 0, 16 or 32' % len_nh) elif safi in (SAFI.flow_vpn, ): if len_nh not in (0, 16, 32): raise Notify( 3, 0, 'invalid ipv6 flow_vpn next-hop length %d expected 0, 16 or 32' % len_nh) size = len_nh - rd # XXX: FIXME: GET IT FROM CACHE HERE ? nh = data[offset + rd:offset + rd + size] # chech the RD is well zero if rd and sum([int(ord(_)) for _ in data[offset:8]]) != 0: raise Notify( 3, 0, "MP_REACH_NLRI next-hop's route-distinguisher must be zero" ) offset += len_nh # Skip a reserved bit as somone had to bug us ! reserved = ord(data[offset]) offset += 1 if reserved != 0: raise Notify(3, 0, 'the reserved bit of MP_REACH_NLRI is not zero') # Is the peer going to send us some Path Information with the route (AddPath) addpath = self.negotiated.addpath.receive(afi, safi) # Reading the NLRIs data = data[offset:] while data: length, nlri = self.nlriFactory(afi, safi, data, addpath, nh, IN.announced) self.mp_announce.append(nlri) logger.parser( LazyFormat("parsed announce mp nlri %s payload " % nlri, od, data[:length])) data = data[length:] return self.factory(next) if flag & Flag.TRANSITIVE: if code in self.known_attributes: # XXX: FIXME: we should really close the session logger.parser( 'ignoring implemented invalid transitive attribute (code 0x%02X, flag 0x%02X)' % (code, flag)) return self.factory(next) if not self.add_from_cache(code, attribute): self.add(UnknownAttribute(code, flag, attribute), attribute) return self.factory(next) logger.parser( 'ignoring non-transitive attribute (code 0x%02X, flag 0x%02X)' % (code, flag)) return self.factory(next)
def unpack_message(cls, data, negotiated): logger = Logger() length = len(data) # This could be speed up massively by changing the order of the IF if length == 23: return EOR(AFI.ipv4, SAFI.unicast, IN.announced) if length == 30 and data.startswith(EOR.NLRI.PREFIX): return EOR.unpack_message(data) withdrawn, _attributes, announced = cls.split(data) attributes = Attributes.unpack(_attributes, negotiated) if not withdrawn: logger.parser("no withdrawn NLRI") if not announced: logger.parser("no announced NLRI") # Is the peer going to send us some Path Information with the route (AddPath) addpath = negotiated.addpath.receive(AFI(AFI.ipv4), SAFI(SAFI.unicast)) # empty string for NoIP, the packed IP otherwise (without the 3/4 bytes of attributes headers) nexthop = attributes.get(Attribute.ID.NEXT_HOP, NoIP).packed nlris = [] while withdrawn: length, nlri = NLRI.unpack(AFI.ipv4, SAFI.unicast, withdrawn, addpath, nexthop, IN.withdrawn) logger.parser( LazyFormat("parsed withdraw nlri %s payload " % nlri, od, withdrawn[:len(nlri)])) withdrawn = withdrawn[length:] nlris.append(nlri) while announced: length, nlri = NLRI.unpack(AFI.ipv4, SAFI.unicast, announced, addpath, nexthop, IN.announced) logger.parser( LazyFormat("parsed announce nlri %s payload " % nlri, od, announced[:len(nlri)])) announced = announced[length:] nlris.append(nlri) # required for 'is' comparaison UNREACH = [ EMPTY_MPURNLRI, ] REACH = [ EMPTY_MPRNLRI, ] unreach = attributes.pop(MPURNLRI.ID, UNREACH) reach = attributes.pop(MPRNLRI.ID, REACH) for mpr in unreach: nlris.extend(mpr.nlris) for mpr in reach: nlris.extend(mpr.nlris) if not attributes and not nlris: # Careful do not use == or != as the comparaison does not work if unreach is UNREACH and reach is REACH: return EOR(AFI(AFI.ipv4), SAFI(SAFI.unicast)) if unreach is not UNREACH: return EOR(unreach[0].afi, unreach[0].safi) if reach is not REACH: return EOR(reach[0].afi, reach[0].safi) raise RuntimeError('This was not expected') return Update(nlris, attributes)
def _main (self,direction): """yield True if we want to come back to it asap, None if nothing urgent, and False if stopped""" if self._teardown: raise Notify(6,3) proto = self._[direction]['proto'] # Announce to the process BGP is up self.logger.network('Connected to peer %s (%s)' % (self.neighbor.name(),direction)) if self.neighbor.api['neighbor-changes']: try: self.reactor.processes.up(self) except ProcessError: # Can not find any better error code than 6,0 ! # XXX: We can not restart the program so this will come back again and again - FIX # XXX: In the main loop we do exit on this kind of error raise Notify(6,0,'ExaBGP Internal error, sorry.') send_eor = True new_routes = None self._resend_routes = SEND.normal send_families = [] # Every last asm message should be re-announced on restart for family in self.neighbor.asm: if family in self.neighbor.families(): self.neighbor.messages.appendleft(self.neighbor.asm[family]) counter = Counter(self.logger,self.me) operational = None refresh = None number = 0 self.send_ka = KA(self.me,proto) while not self._teardown: for message in proto.read_message(): self.recv_timer.check_ka(message) if self.send_ka() is not False: # we need and will send a keepalive while self.send_ka() is None: yield ACTION.immediate # Give information on the number of routes seen counter.display() # Received update if message.TYPE == Update.TYPE: counter.increment(len(message.nlris)) number += 1 self.logger.routes(LazyFormat(self.me('<< UPDATE (%d)' % number),lambda _: "%s%s" % (' attributes' if _ else '',_),message.attributes)) for nlri in message.nlris: self.neighbor.rib.incoming.insert_received(Change(nlri,message.attributes)) self.logger.routes(LazyFormat(self.me('<< UPDATE (%d) nlri ' % number),str,nlri)) elif message.TYPE == RouteRefresh.TYPE: if message.reserved == RouteRefresh.request: self._resend_routes = SEND.refresh send_families.append((message.afi,message.safi)) # SEND OPERATIONAL if self.neighbor.operational: if not operational: new_operational = self.neighbor.messages.popleft() if self.neighbor.messages else None if new_operational: operational = proto.new_operational(new_operational,proto.negotiated) if operational: try: operational.next() except StopIteration: operational = None # SEND REFRESH if self.neighbor.route_refresh: if not refresh: new_refresh = self.neighbor.refresh.popleft() if self.neighbor.refresh else None if new_refresh: enhanced_negotiated = True if proto.negotiated.refresh == REFRESH.enhanced else False refresh = proto.new_refresh(new_refresh,enhanced_negotiated) if refresh: try: refresh.next() except StopIteration: refresh = None # Take the routes already sent to that peer and resend them if self._resend_routes != SEND.done: enhanced_refresh = True if self._resend_routes == SEND.refresh and proto.negotiated.refresh == REFRESH.enhanced else False self._resend_routes = SEND.done self.neighbor.rib.outgoing.resend(send_families,enhanced_refresh) self._have_routes = True send_families = [] # Need to send update if self._have_routes and not new_routes: self._have_routes = False # XXX: in proto really. hum to think about ? new_routes = proto.new_update() if new_routes: try: count = 20 while count: # This can raise a NetworkError new_routes.next() count -= 1 except StopIteration: new_routes = None elif send_eor: send_eor = False for eor in proto.new_eors(): yield ACTION.immediate self.logger.message(self.me('>> EOR(s)')) # Go to other Peers yield ACTION.immediate if new_routes or message.TYPE != NOP.TYPE or self.neighbor.messages else ACTION.later # read_message will loop until new message arrives with NOP if self._teardown: break # If graceful restart, silent shutdown if self.neighbor.graceful_restart and proto.negotiated.sent_open.capabilities.announced(Capability.ID.GRACEFUL_RESTART): self.logger.network('Closing the session without notification','error') proto.close('graceful restarted negotiated, closing without sending any notification') raise NetworkError('closing') # notify our peer of the shutdown raise Notify(6,self._teardown)
def _main(self): """yield True if we want to come back to it asap, None if nothing urgent, and False if stopped""" if self._teardown: raise Notify(6, 3) self.neighbor.rib.incoming.clear() include_withdraw = False # Announce to the process BGP is up log.notice( 'connected to %s with %s' % (self.id(), self.proto.connection.name()), 'reactor') self.stats['up'] = self.stats.get('up', 0) + 1 if self.neighbor.api['neighbor-changes']: try: self.reactor.processes.up(self.neighbor) except ProcessError: # Can not find any better error code than 6,0 ! # XXX: We can not restart the program so this will come back again and again - FIX # XXX: In the main loop we do exit on this kind of error raise Notify(6, 0, 'ExaBGP Internal error, sorry.') send_eor = not self.neighbor.manual_eor new_routes = None self._resend_routes = SEND.NORMAL send_families = [] # Every last asm message should be re-announced on restart for family in self.neighbor.asm: if family in self.neighbor.families(): self.neighbor.messages.appendleft(self.neighbor.asm[family]) operational = None refresh = None command_eor = None number = 0 refresh_enhanced = True if self.proto.negotiated.refresh == REFRESH.ENHANCED else False send_ka = KA(self.proto.connection.session, self.proto) while not self._teardown: for message in self.proto.read_message(): self.recv_timer.check_ka(message) if send_ka() is not False: # we need and will send a keepalive while send_ka() is None: yield ACTION.NOW # Received update if message.TYPE == Update.TYPE: number += 1 log.debug('<< UPDATE #%d' % number, self.id()) for nlri in message.nlris: self.neighbor.rib.incoming.update_cache( Change(nlri, message.attributes)) log.debug( LazyFormat(' UPDATE #%d nlri ' % number, nlri, str), self.id()) elif message.TYPE == RouteRefresh.TYPE: if message.reserved == RouteRefresh.request: self._resend_routes = SEND.REFRESH send_families.append((message.afi, message.safi)) # SEND OPERATIONAL if self.neighbor.operational: if not operational: new_operational = self.neighbor.messages.popleft( ) if self.neighbor.messages else None if new_operational: operational = self.proto.new_operational( new_operational, self.proto.negotiated) if operational: try: next(operational) except StopIteration: operational = None # make sure that if some operational message are received via the API # that we do not eat memory for nothing elif self.neighbor.messages: self.neighbor.messages.popleft() # SEND REFRESH if self.neighbor.route_refresh: if not refresh: new_refresh = self.neighbor.refresh.popleft( ) if self.neighbor.refresh else None if new_refresh: refresh = self.proto.new_refresh(new_refresh) if refresh: try: next(refresh) except StopIteration: refresh = None # Take the routes already sent to that peer and resend them if self._reconfigure: self._reconfigure = False # we are here following a configuration change if self._neighbor: # see what changed in the configuration self.neighbor.rib.outgoing.replace( self._neighbor.backup_changes, self._neighbor.changes) # do not keep the previous routes in memory as they are not useful anymore self._neighbor.backup_changes = [] # Take the routes already sent to that peer and resend them if self._resend_routes != SEND.DONE: enhanced = True if refresh_enhanced and self._resend_routes == SEND.REFRESH else False self._resend_routes = SEND.DONE self.neighbor.rib.outgoing.resend(send_families, enhanced) send_families = [] # Need to send update if not new_routes and self.neighbor.rib.outgoing.pending(): # XXX: in proto really. hum to think about ? new_routes = self.proto.new_update(include_withdraw) if new_routes: count = 1 if self.neighbor.rate_limit > 0 else 25 try: for _ in range(count): # This can raise a NetworkError next(new_routes) except StopIteration: new_routes = None include_withdraw = True elif send_eor: send_eor = False for _ in self.proto.new_eors(): yield ACTION.NOW log.debug('>> EOR(s)', self.id()) # SEND MANUAL KEEPALIVE (only if we have no more routes to send) elif not command_eor and self.neighbor.eor: new_eor = self.neighbor.eor.popleft() command_eor = self.proto.new_eors(new_eor.afi, new_eor.safi) if command_eor: try: next(command_eor) except StopIteration: command_eor = None if new_routes or message.TYPE != NOP.TYPE: yield ACTION.NOW elif self.neighbor.messages or operational: yield ACTION.NOW elif self.neighbor.eor or command_eor: yield ACTION.NOW else: yield ACTION.LATER # read_message will loop until new message arrives with NOP if self._teardown: break # If graceful restart, silent shutdown if self.neighbor.graceful_restart and self.proto.negotiated.sent_open.capabilities.announced( Capability.CODE.GRACEFUL_RESTART): log.error('closing the session without notification', self.id()) self.proto.close( 'graceful restarted negotiated, closing without sending any notification' ) raise NetworkError('closing') # notify our peer of the shutdown raise Notify(6, self._teardown)
def _main(self, direction): "yield True if we want to come back to it asap, None if nothing urgent, and False if stopped" if self._teardown: raise Notify(6, 3) proto = self._[direction]['proto'] # Announce to the process BGP is up self.logger.network('Connected to peer %s (%s)' % (self.neighbor.name(), direction)) if self.neighbor.api.neighbor_changes: try: self.reactor.processes.up(self.neighbor.peer_address) except ProcessError: # Can not find any better error code than 6,0 ! # XXX: We can not restart the program so this will come back again and again - FIX # XXX: In the main loop we do exit on this kind of error raise Notify(6, 0, 'ExaBGP Internal error, sorry.') send_eor = True new_routes = None self._resend_routes = True # Every last asm message should be re-announced on restart for family in self.neighbor.asm: if family in self.neighbor.families(): self.neighbor.messages.appendleft(self.neighbor.asm[family]) counter = Counter(self.logger, self._log(direction)) need_keepalive = False keepalive = None operational = None while not self._teardown: for message in proto.read_message(): # Update timer self.timer.tick(message) # Give information on the number of routes seen counter.display() # Received update if message.TYPE == Update.TYPE: counter.increment(len(message.nlris)) for nlri in message.nlris: self.neighbor.rib.incoming.insert_received( Change(nlri, message.attributes)) self.logger.routes(LazyFormat(self.me(''), str, nlri)) # SEND KEEPALIVES need_keepalive |= self.timer.keepalive() if need_keepalive and not keepalive: keepalive = proto.new_keepalive() need_keepalive = False if keepalive: try: keepalive.next() except StopIteration: keepalive = None # SEND OPERATIONAL if self.neighbor.operational: if not operational: new_operational = self.neighbor.messages.popleft( ) if self.neighbor.messages else None if new_operational: operational = proto.new_operational( new_operational) if operational: try: operational.next() except StopIteration: operational = None # Take the routes already sent to that peer and resend them if self._resend_routes: self._resend_routes = False self.neighbor.rib.outgoing.resend_known() self._have_routes = True # Need to send update if self._have_routes and not new_routes: self._have_routes = False # XXX: in proto really. hum to think about ? new_routes = proto.new_update() if new_routes: try: count = 20 while count: # This can raise a NetworkError new_routes.next() count -= 1 except StopIteration: new_routes = None elif send_eor: send_eor = False for eor in proto.new_eors(): yield ACTION.immediate self.logger.message(self.me('>> EOR(s)')) # Go to other Peers yield ACTION.immediate if new_routes or message.TYPE != NOP.TYPE else ACTION.later # read_message will loop until new message arrives with NOP if self._teardown: break # If graceful restart, silent shutdown if self.neighbor.graceful_restart and proto.negotiated.sent_open.capabilities.announced( CapabilityID.GRACEFUL_RESTART): self.logger.network('Closing the session without notification', 'error') proto.close( 'graceful restarted negotiated, closing without sending any notification' ) raise NetworkError('closing') # notify our peer of the shutdown raise Notify(6, self._teardown)
def _FlowNLRIFactory(afi, safi, nexthop, bgp, action): logger = Logger() logger.parser(LazyFormat("parsing flow nlri payload ", od, bgp)) total = len(bgp) length, bgp = ord(bgp[0]), bgp[1:] if length & 0xF0 == 0xF0: # bigger than 240 extra, bgp = ord(bgp[0]), bgp[1:] length = ((length & 0x0F) << 16) + extra if length > len(bgp): raise Notify(3, 10, 'invalid length at the start of the the flow') bgp = bgp[:length] nlri = FlowNLRI(afi, safi) nlri.action = action if nexthop: nlri.nexthop = cachedNextHop(nexthop) if safi == SAFI.flow_vpn: nlri.rd = RouteDistinguisher(bgp[:8]) bgp = bgp[8:] seen = [] while bgp: what, bgp = ord(bgp[0]), bgp[1:] if what not in decode.get(afi, {}): raise Notify( 3, 10, 'unknown flowspec component received for address family %d' % what) seen.append(what) if sorted(seen) != seen: raise Notify( 3, 10, 'components are not sent in the right order %s' % seen) decoder = decode[afi][what] klass = factory[afi][what] if decoder == 'prefix': if afi == AFI.ipv4: _, rd, mask, size, prefix, left = _nlrifactory( afi, safi, bgp, action) adding = klass(prefix, mask) if not nlri.add(adding): raise Notify( 3, 10, 'components are incompatible (two sources, two destinations, mix ipv4/ipv6) %s' % seen) logger.parser( LazyFormat( "added flow %s (%s) payload " % (klass.NAME, adding), od, bgp[:-len(left)])) bgp = left else: byte, bgp = bgp[1], bgp[0] + bgp[2:] offset = ord(byte) _, rd, mask, size, prefix, left = _nlrifactory( afi, safi, bgp, action) adding = klass(prefix, mask, offset) if not nlri.add(adding): raise Notify( 3, 10, 'components are incompatible (two sources, two destinations, mix ipv4/ipv6) %s' % seen) logger.parser( LazyFormat( "added flow %s (%s) payload " % (klass.NAME, adding), od, bgp[:-len(left)])) bgp = left else: end = False while not end: byte, bgp = ord(bgp[0]), bgp[1:] end = CommonOperator.eol(byte) operator = CommonOperator.operator(byte) length = CommonOperator.length(byte) value, bgp = bgp[:length], bgp[length:] adding = klass.decoder(value) nlri.add(klass(operator, adding)) logger.parser( LazyFormat( "added flow %s (%s) operator %d len %d payload " % (klass.NAME, adding, byte, length), od, value)) return total - len(bgp), nlri
def UpdateFactory(negotiated, data): logger = Logger() length = len(data) lw, withdrawn, data = defix(data) if len(withdrawn) != lw: raise Notify( 3, 1, 'invalid withdrawn routes length, not enough data available') la, attribute, announced = defix(data) if len(attribute) != la: raise Notify( 3, 1, 'invalid total path attribute length, not enough data available') if 2 + lw + 2 + la + len(announced) != length: raise Notify( 3, 1, 'error in BGP message length, not enough data for the size announced' ) attributes = AttributesFactory(NLRIFactory, negotiated, attribute) # Is the peer going to send us some Path Information with the route (AddPath) addpath = negotiated.addpath.receive(AFI(AFI.ipv4), SAFI(SAFI.unicast)) nho = attributes.get(AID.NEXT_HOP, None) nh = nho.packed if nho else None if not withdrawn: logger.parser(LazyFormat("parsed no withdraw nlri", od, '')) nlris = [] while withdrawn: length, nlri = NLRIFactory(AFI.ipv4, SAFI.unicast_multicast, withdrawn, addpath, nh, IN.withdrawn) logger.parser( LazyFormat("parsed withdraw nlri %s payload " % nlri, od, withdrawn[:len(nlri)])) withdrawn = withdrawn[length:] nlris.append(nlri) if not announced: logger.parser(LazyFormat("parsed no announced nlri", od, '')) while announced: length, nlri = NLRIFactory(AFI.ipv4, SAFI.unicast_multicast, announced, addpath, nh, IN.announced) logger.parser( LazyFormat("parsed announce nlri %s payload " % nlri, od, announced[:len(nlri)])) announced = announced[length:] nlris.append(nlri) for nlri in attributes.mp_withdraw: nlris.append(nlri) for nlri in attributes.mp_announce: nlris.append(nlri) return Update(nlris, attributes)