def _open(self, direction, message): self.seen_open[direction] = message if all(self.seen_open.values()): self.negotiated = Negotiated(None) self.negotiated.sent(self.seen_open['send']) self.negotiated.received(self.seen_open['receive'])
def check_update (neighbor, raw): from exabgp.logger import Logger logger = Logger() logger._parser = True logger.parser('\ndecoding routes in configuration') n = neighbor[neighbor.keys()[0]] p = Peer(n,None) path = {} for f in known_families(): if n.add_path: path[f] = n.add_path capa = Capabilities().new(n,False) capa[Capability.CODE.ADD_PATH] = path capa[Capability.CODE.MULTIPROTOCOL] = n.families() routerid_1 = str(n.router_id) routerid_2 = '.'.join(str((int(_)+1) % 250) for _ in str(n.router_id).split('.',-1)) o1 = Open(4,n.local_as,routerid_1,capa,180) o2 = Open(4,n.peer_as,routerid_2,capa,180) negotiated = Negotiated(n) negotiated.sent(o1) negotiated.received(o2) # grouped = False while raw: if raw.startswith('\xff'*16): kind = ord(raw[18]) size = (ord(raw[16]) << 16) + (ord(raw[17])) injected,raw = raw[19:size],raw[size:] if kind == 2: logger.parser('the message is an update') decoding = 'update' else: logger.parser('the message is not an update (%d) - aborting' % kind) return False else: logger.parser('header missing, assuming this message is ONE update') decoding = 'update' injected,raw = raw,'' try: # This does not take the BGP header - let's assume we will not break that :) update = Update.unpack_message(injected,negotiated) except KeyboardInterrupt: raise except Notify,exc: logger.parser('could not parse the message') logger.parser(str(exc)) return False except Exception,exc: logger.parser('could not parse the message') logger.parser(str(exc)) return False
def setUp (self): # env.log.all = True self.negotiated = {} for asn4 in (True,False): neighbor = FakeNeighbor() neighbor.asn4 = asn4 capa = Capabilities().new(neighbor,False) capa[Capability.CODE.MULTIPROTOCOL] = neighbor.families() # path = {} # for f in NLRI.known_families(): # if neighbor.add_path: # path[f] = neighbor.add_path # capa[Capability.CODE.ADD_PATH] = path o1 = Open(4,neighbor.local_as,str(neighbor.local_address),capa,180) o2 = Open(4,neighbor.peer_as,str(neighbor.peer_address),capa,180) negotiated = Negotiated(neighbor) negotiated.sent(o1) negotiated.received(o2) self.negotiated[asn4] = negotiated
def check_update(neighbor, raw): from exabgp.logger import Logger logger = Logger() logger._parser = True logger.parser("\ndecoding routes in configuration") n = neighbor[neighbor.keys()[0]] p = Peer(n, None) path = {} for f in known_families(): if n.add_path: path[f] = n.add_path capa = Capabilities().new(n, False) capa[Capability.ID.ADD_PATH] = path capa[Capability.ID.MULTIPROTOCOL_EXTENSIONS] = n.families() o1 = Open(4, n.local_as, str(n.local_address), capa, 180) o2 = Open(4, n.peer_as, str(n.peer_address), capa, 180) negotiated = Negotiated(n) negotiated.sent(o1) negotiated.received(o2) # grouped = False while raw: if raw.startswith("\xff" * 16): kind = ord(raw[18]) size = (ord(raw[16]) << 16) + (ord(raw[17])) injected, raw = raw[19:size], raw[size:] if kind == 2: logger.parser("the message is an update") decoding = "update" else: logger.parser("the message is not an update (%d) - aborting" % kind) return False else: logger.parser("header missing, assuming this message is ONE update") decoding = "update" injected, raw = raw, "" try: # This does not take the BGP header - let's assume we will not break that :) update = Update.unpack_message(injected, negotiated) except KeyboardInterrupt: raise except Notify, e: logger.parser("could not parse the message") logger.parser(str(e)) return False except Exception, e: logger.parser("could not parse the message") logger.parser(str(e)) return False
def _open (self,direction,message): self.seen_open[direction] = message if all(self.seen_open.values()): self.negotiated = Negotiated(None) self.negotiated.sent(self.seen_open['send']) self.negotiated.received(self.seen_open['receive'])
def __init__(self, peer): try: self.logger = Logger() except RuntimeError: self.logger = FakeLogger() self.peer = peer self.neighbor = peer.neighbor self.negotiated = Negotiated(self.neighbor) self.connection = None if self.neighbor.connect: self.port = self.neighbor.connect elif os.environ.get('exabgp.tcp.port', '').isdigit(): self.port = int(os.environ.get('exabgp.tcp.port')) elif os.environ.get('exabgp_tcp_port', '').isdigit(): self.port = int(os.environ.get('exabgp_tcp_port')) else: self.port = 179 # XXX: FIXME: check the the -19 is correct (but it is harmless) # The message size is the whole BGP message _without_ headers self.message_size = Message.MAX_LEN - Message.HEADER_LEN from exabgp.configuration.environment import environment self.log_routes = environment.settings().log.routes
def setUp(self): #env.log.all = True self.negotiated = {} for asn4 in (True, False): neighbor = Neighbor() neighbor.asn4 = asn4 capa = Capabilities().new(neighbor, False) capa[Capability.ID.MULTIPROTOCOL] = neighbor.families() # path = {} # for f in known_families(): # if neighbor.add_path: # path[f] = neighbor.add_path # capa[Capability.ID.ADD_PATH] = path o1 = Open(4, neighbor.local_as, str(neighbor.local_address), capa, 180) o2 = Open(4, neighbor.peer_as, str(neighbor.peer_address), capa, 180) negotiated = Negotiated(neighbor) negotiated.sent(o1) negotiated.received(o2) self.negotiated[asn4] = negotiated
def check_update (neighbor, raw): from exabgp.logger import Logger logger = Logger() logger._parser = True logger.parser('\ndecoding routes in configuration') n = neighbor[neighbor.keys()[0]] p = Peer(n,None) path = {} for f in known_families(): if n.add_path: path[f] = n.add_path capa = Capabilities().new(n,False) capa[Capability.CODE.ADD_PATH] = path capa[Capability.CODE.MULTIPROTOCOL] = n.families() routerid_1 = str(n.router_id) routerid_2 = '.'.join(str((int(_)+1) % 250) for _ in str(n.router_id).split('.',-1)) o1 = Open(4,n.local_as,routerid_1,capa,180) o2 = Open(4,n.peer_as,routerid_2,capa,180) negotiated = Negotiated(n) negotiated.sent(o1) negotiated.received(o2) # grouped = False while raw: if raw.startswith('\xff'*16): kind = ord(raw[18]) size = (ord(raw[16]) << 16) + (ord(raw[17])) injected,raw = raw[19:size],raw[size:] if kind == 2: logger.parser('the message is an update') decoding = 'update' else: logger.parser('the message is not an update (%d) - aborting' % kind) return False else: logger.parser('header missing, assuming this message is ONE update') decoding = 'update' injected,raw = raw,'' try: # This does not take the BGP header - let's assume we will not break that :) update = Update.unpack_message(injected,negotiated) except KeyboardInterrupt: raise except Notify,exc: logger.parser('could not parse the message','error') logger.parser(str(exc),'error') return False except Exception,exc: logger.parser('could not parse the message','error') logger.parser(str(exc),'error') return False
def __init__ (self,peer): try: self.logger = Logger() except RuntimeError: self.logger = FakeLogger() self.peer = peer self.neighbor = peer.neighbor self.negotiated = Negotiated(self.neighbor) self.connection = None port = os.environ.get('exabgp.tcp.port','') self.port = int(port) if port.isdigit() else 179 # XXX: FIXME: check the the -19 is correct (but it is harmless) # The message size is the whole BGP message _without_ headers self.message_size = Message.MAX_LEN-Message.HEADER_LEN
class Transcoder (object): seen_open = { 'send': None, 'receive': None, } negotiated = None json = JSON(json_version) def __init__ (self, src='json', dst='json'): if src != 'json': raise RuntimeError('left as an exercise to the reader') if dst != 'json': raise RuntimeError('left as an exercise to the reader') self.convert = self._from_json self.encoder = self.json def _state (self): self.seen_open['send'] = None self.seen_open['receive'] = None self.negotiated = None def _open (self,direction,message): self.seen_open[direction] = message if all(self.seen_open.values()): self.negotiated = Negotiated(None) self.negotiated.sent(self.seen_open['send']) self.negotiated.received(self.seen_open['receive']) def _from_json (self, string): try: parsed = json.loads(string) except ValueError: print >> sys.stderr, 'invalid JSON message' sys.exit(1) if parsed.get('exabgp','0.0.0') != json_version: print >> sys.stderr, 'invalid json version', string sys.exit(1) content = parsed.get('type','') if not content: print >> sys.stderr, 'invalid json content', string sys.exit(1) neighbor = _FakeNeighbor( parsed['neighbor']['address']['local'], parsed['neighbor']['address']['peer'], parsed['neighbor']['asn']['local'], parsed['neighbor']['asn']['peer'], ) if content == 'state': self._state() return string direction = parsed['neighbor']['direction'] category = parsed['neighbor']['message']['category'] header = parsed['neighbor']['message']['header'] body = parsed['neighbor']['message']['body'] raw = ''.join(chr(int(body[_:_+2],16)) for _ in range(0,len(body),2)) if content == 'open': message = Open.unpack_message(raw) self._open(direction,message) return self.encoder.open(neighbor,direction,message,header,body) if content == 'keapalive': return self.encoder.keepalive(neighbor,direction,header,body) if content == 'notification': return self.encoder.notification(neighbor,direction,ord(message[0]),ord(message[1]),message[2:],header,body) if not self.negotiated: print >> sys.stderr, 'invalid message sequence, open not exchange not complete', string sys.exit(1) message = Message.unpack(category,raw,self.negotiated) if content == 'update': return self.encoder.update(neighbor, direction, message, header,body) if content == 'eor': # XXX: Should not be required return self.encoder.update(neighbor, direction, message, header,body) if content == 'refresh': return self.json.refresh(neighbor, direction, message, header,body) if content == 'operational': return self.json.refresh(neighbor, direction, message, header,body) raise RuntimeError('the programer is a monkey and forgot a JSON message type')
class Protocol (object): decode = True def __init__ (self,peer): try: self.logger = Logger() except RuntimeError: self.logger = FakeLogger() self.peer = peer self.neighbor = peer.neighbor self.negotiated = Negotiated(self.neighbor) self.connection = None port = os.environ.get('exabgp.tcp.port','') or os.environ.get('exabgp_tcp_port','') self.port = int(port) if port.isdigit() else 179 # XXX: FIXME: check the the -19 is correct (but it is harmless) # The message size is the whole BGP message _without_ headers self.message_size = Message.MAX_LEN-Message.HEADER_LEN from exabgp.configuration.environment import environment self.log_routes = environment.settings().log.routes # XXX: we use self.peer.neighbor.peer_address when we could use self.neighbor.peer_address def __del__ (self): self.close('automatic protocol cleanup') def me (self,message): return "Peer %15s ASN %-7s %s" % (self.peer.neighbor.peer_address,self.peer.neighbor.peer_as,message) def accept (self,incoming): self.connection = incoming self.peer.reactor.processes.reset(self.peer) if self.peer.neighbor.api.neighbor_changes: self.peer.reactor.processes.connected(self.peer) # very important - as we use this function on __init__ return self def connect (self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: peer = self.neighbor.peer_address local = self.neighbor.local_address md5 = self.neighbor.md5 ttl = self.neighbor.ttl self.connection = Outgoing(peer.afi,peer.ip,local.ip,self.port,md5,ttl) connected = False try: generator = self.connection.establish() while True: connected = generator.next() if not connected: yield False continue self.peer.reactor.processes.reset(self.peer) if self.peer.neighbor.api.neighbor_changes: self.peer.reactor.processes.connected(self.peer) yield True return except StopIteration: # close called by the caller # self.close('could not connect to remote end') yield False return def close (self,reason='protocol closed, reason unspecified'): if self.connection: self.logger.network(self.me(reason)) # must be first otherwise we could have a loop caused by the raise in the below self.connection.close() self.connection = None try: if self.peer.neighbor.api.neighbor_changes: self.peer.reactor.processes.down(self.peer,reason) except ProcessError: self.logger.message(self.me('could not send notification of neighbor close to API')) def write (self,message): if self.neighbor.api.send_packets: self.peer.reactor.processes.send(self.peer,ord(message[18]),message[:19],message[19:]) for boolean in self.connection.writer(message): yield boolean # Read from network ....................................................... def read_message (self,comment=''): self.peer.reactor.processes.increase(self.peer) for length,msg,header,body,notify in self.connection.reader(): if notify: if self.neighbor.api.receive_packets: self.peer.reactor.processes.receive(self.peer,msg,header,body) if self.neighbor.api.receive_notifications: self.peer.reactor.processes.notification(self.peer,notify.code,notify.subcode,str(notify)) # XXX: is notify not already Notify class ? raise Notify(notify.code,notify.subcode,str(notify)) if not length: yield _NOP if self.neighbor.api.receive_packets: self.peer.reactor.processes.receive(self.peer,msg,header,body) if msg == Message.Type.UPDATE: self.logger.message(self.me('<< UPDATE')) # This could be speed up massively by changing the order of the IF if length == 23: update = EORFactory() if self.neighbor.api.receive_updates: if self.neighbor.api.consolidate: self.peer.reactor.processes.update(self.peer,update,header,body) else: self.peer.reactor.processes.update(self.peer,update,'','') elif length == 30 and body.startswith(EOR.MP): update = EORFactory(body) if self.neighbor.api.receive_updates: if self.neighbor.api.consolidate: self.peer.reactor.processes.update(self.peer,update,header,body) else: self.peer.reactor.processes.update(self.peer,update,'','') elif self.neighbor.api.receive_updates: update = UpdateFactory(self.negotiated,body) if self.neighbor.api.consolidate: self.peer.reactor.processes.update(self.peer,update,header,body) else: self.peer.reactor.processes.update(self.peer,update,'','') elif self.log_routes: update = UpdateFactory(self.negotiated,body) else: update = _UPDATE yield update elif msg == Message.Type.KEEPALIVE: self.logger.message(self.me('<< KEEPALIVE%s' % (' (%s)' % comment if comment else ''))) if self.neighbor.api.receive_keepalives: if self.neighbor.api.consolidate: self.peer.reactor.processes.keepalive(self.peer,msg,header,body) else: self.peer.reactor.processes.keepalive(self.peer,msg,'','') yield KeepAlive() elif msg == Message.Type.NOTIFICATION: self.logger.message(self.me('<< NOTIFICATION')) yield NotificationFactory(body) elif msg == Message.Type.ROUTE_REFRESH: if self.negotiated.refresh != REFRESH.absent: self.logger.message(self.me('<< ROUTE-REFRESH')) refresh = RouteRefreshFactory(body) if self.neighbor.api.receive_refresh: if refresh.reserved in (RouteRefresh.start,RouteRefresh.end): if self.neighbor.api.consolidate: self.peer.reactor.process.refresh(self.peer,refresh,header,body) else: self.peer.reactor.processes.refresh(self.peer,refresh,'','') else: # XXX: FIXME: really should raise, we are too nice self.logger.message(self.me('<< NOP (un-negotiated type %d)' % msg)) refresh = UnknownMessageFactory(body) yield refresh elif msg == Message.Type.OPERATIONAL: if self.peer.neighbor.operational: operational = OperationalFactory(body) what = OperationalGroup[operational.what][0] if self.neighbor.api.consolidate: self.peer.reactor.processes.operational(self.peer,what,operational,header,body) else: self.peer.reactor.processes.operational(self.peer,what,operational,'','') else: operational = _OPERATIONAL yield operational elif msg == Message.Type.OPEN: if self.neighbor.api.receive_opens: open_message = OpenFactory(body) if self.neighbor.api.consolidate: self.peer.reactor.processes.open(self.peer,'received',open_message,header,body) else: self.peer.reactor.processes.open(self.peer,'received',open_message,'','') yield OpenFactory(body) else: # XXX: FIXME: really should raise, we are too nice self.logger.message(self.me('<< NOP (unknow type %d)' % msg)) yield UnknownMessageFactory(msg) def validate_open (self): error = self.negotiated.validate(self.neighbor) if error is not None: raise Notify(*error) def read_open (self,ip): for received_open in self.read_message(): if received_open.TYPE == NOP.TYPE: yield received_open else: break if received_open.TYPE == Notification.TYPE: raise received_open if received_open.TYPE != Open.TYPE: raise Notify(5,1,'The first packet recevied is not an open message (%s)' % received_open) self.logger.message(self.me('<< %s' % received_open)) yield received_open def read_keepalive (self,comment=''): for message in self.read_message(comment): if message.TYPE == NOP.TYPE: yield message else: break if message.TYPE == Notification.TYPE: raise message if message.TYPE != KeepAlive.TYPE: raise Notify(5,2) yield message # # Sending message to peer # def new_open (self,restarted): sent_open = Open( 4, self.neighbor.local_as, self.neighbor.router_id.ip, Capabilities().new(self.neighbor,restarted), self.neighbor.hold_time ) # we do not buffer open message in purpose msg_send = sent_open.message() for _ in self.write(msg_send): yield _NOP self.logger.message(self.me('>> %s' % sent_open)) if self.neighbor.api.receive_opens: if self.neighbor.api.consolidate: header = msg_send[0:38] body = msg_send[38:] self.peer.reactor.processes.open(self.peer,'sent',sent_open,header,body) else: self.peer.reactor.processes.open(self.peer,'sent',sent_open,'','') yield sent_open def new_keepalive (self,comment=''): keepalive = KeepAlive() for _ in self.write(keepalive.message()): yield _NOP self.logger.message(self.me('>> KEEPALIVE%s' % (' (%s)' % comment if comment else ''))) yield keepalive def new_notification (self,notification): for _ in self.write(notification.message()): yield _NOP self.logger.message(self.me('>> NOTIFICATION (%d,%d,"%s")' % (notification.code,notification.subcode,notification.data))) yield notification def new_update (self): updates = self.neighbor.rib.outgoing.updates(self.neighbor.group_updates) number = 0 for update in updates: for message in update.messages(self.negotiated): number += 1 for boolean in self.write(message): # boolean is a transient network error we already announced yield _NOP if number: self.logger.message(self.me('>> %d UPDATE(s)' % number)) yield _UPDATE def new_eors (self): # Send EOR to let our peer know he can perform a RIB update if self.negotiated.families: for afi,safi in self.negotiated.families: eor = EOR(afi,safi).message() for _ in self.write(eor): yield _NOP yield _UPDATE else: # If we are not sending an EOR, send a keepalive as soon as when finished # So the other routers knows that we have no (more) routes to send ... # (is that behaviour documented somewhere ??) for eor in self.new_keepalive('EOR'): yield _NOP yield _UPDATE def new_operational (self,operational,negotiated): for _ in self.write(operational.message(negotiated)): yield _NOP self.logger.message(self.me('>> OPERATIONAL %s' % str(operational))) yield operational def new_refresh (self,refresh,negotiated): for refresh in refresh.messages(negotiated): for _ in self.write(refresh): yield _NOP self.logger.message(self.me('>> REFRESH %s' % str(refresh))) yield refresh
def check_neighbor (neighbors): from exabgp.logger import Logger logger = Logger() logger._option.parser = True if not neighbors: logger.parser('\ncould not find neighbor(s) to check') return False logger.parser('\ndecoding routes in configuration') for name in neighbors.keys(): neighbor = neighbors[name] path = {} for f in known_families(): if neighbor.add_path: path[f] = neighbor.add_path capa = Capabilities().new(neighbor,False) if path: capa[Capability.CODE.ADD_PATH] = path capa[Capability.CODE.MULTIPROTOCOL] = neighbor.families() o1 = Open(4,neighbor.local_as,'127.0.0.2',capa,180) o2 = Open(4,neighbor.peer_as,'127.0.0.3',capa,180) negotiated = Negotiated(neighbor) negotiated.sent(o1) negotiated.received(o2) # grouped = False for message in neighbor.rib.outgoing.updates(False): pass for change1 in neighbor.rib.outgoing.sent_changes(): str1 = change1.extensive() packed = list(Update([change1.nlri],change1.attributes).messages(negotiated)) pack1 = packed[0] logger.parser('parsed route requires %d updates' % len(packed)) logger.parser('parsed route requires %d updates' % len(packed)) logger.parser('update size is %d' % len(pack1)) logger.parser('parsed route %s' % str1) logger.parser('parsed hex %s' % od(pack1)) # This does not take the BGP header - let's assume we will not break that :) try: logger.parser('') # new line pack1s = pack1[19:] if pack1.startswith('\xFF'*16) else pack1 update = Update.unpack_message(pack1s,negotiated) change2 = Change(update.nlris[0],update.attributes) str2 = change2.extensive() pack2 = list(Update([update.nlris[0]],update.attributes).messages(negotiated))[0] logger.parser('recoded route %s' % str2) logger.parser('recoded hex %s' % od(pack2)) str1r = str1.replace(' med 100','').replace(' local-preference 100','').replace(' origin igp','') str2r = str2.replace(' med 100','').replace(' local-preference 100','').replace(' origin igp','') if ' name ' in str1r: parts = str1r.split(' ') pos = parts.index('name') str1r = ' '.join(parts[:pos] + parts[pos+2:]) skip = False if str1r != str2r: if 'attribute [' in str1r and ' 0x00 ' in str1r: # we do not decode non-transitive attributes logger.parser('skipping string check on update with non-transitive attribute(s)') skip = True else: logger.parser('strings are different:') logger.parser('[%s]' % (str1r)) logger.parser('[%s]' % (str2r)) return False else: logger.parser('strings are fine') if skip: logger.parser('skipping encoding for update with non-transitive attribute(s)') elif pack1 != pack2: logger.parser('encoding are different') logger.parser('[%s]' % (od(pack1))) logger.parser('[%s]' % (od(pack2))) return False else: logger.parser('encoding is fine') logger.parser('----------------------------------------') logger.parser('JSON nlri %s' % change1.nlri.json()) logger.parser('JSON attr %s' % change1.attributes.json()) except Notify,exc: logger.parser('----------------------------------------') logger.parser(str(exc)) logger.parser('----------------------------------------') return False neighbor.rib.clear()
def check_neighbor(neighbor): from exabgp.logger import Logger logger = Logger() logger._parser = True logger.parser("\ndecoding routes in configuration") n = neighbor[neighbor.keys()[0]] path = {} for f in known_families(): if n.add_path: path[f] = n.add_path capa = Capabilities().new(n, False) capa[Capability.ID.ADD_PATH] = path capa[Capability.ID.MULTIPROTOCOL_EXTENSIONS] = n.families() o1 = Open(4, n.local_as, str(n.local_address), capa, 180) o2 = Open(4, n.peer_as, str(n.peer_address), capa, 180) negotiated = Negotiated(n) negotiated.sent(o1) negotiated.received(o2) # grouped = False for nei in neighbor.keys(): for message in neighbor[nei].rib.outgoing.updates(False): pass for change1 in neighbor[nei].rib.outgoing.sent_changes(): str1 = change1.extensive() packed = list(Update([change1.nlri], change1.attributes).messages(negotiated)) pack1 = packed[0] logger.parser("parsed route requires %d updates" % len(packed)) logger.parser("update size is %d" % len(pack1)) logger.parser("parsed route %s" % str1) logger.parser("parsed hex %s" % od(pack1)) # This does not take the BGP header - let's assume we will not break that :) try: logger.parser("") # new line pack1s = pack1[19:] if pack1.startswith("\xFF" * 16) else pack1 update = Update.unpack_message(pack1s, negotiated) change2 = Change(update.nlris[0], update.attributes) str2 = change2.extensive() pack2 = list(Update([update.nlris[0]], update.attributes).messages(negotiated))[0] logger.parser("recoded route %s" % str2) logger.parser("recoded hex %s" % od(pack2)) str1r = str1.replace(" med 100", "").replace(" local-preference 100", "").replace(" origin igp", "") str2r = str2.replace(" med 100", "").replace(" local-preference 100", "").replace(" origin igp", "") skip = False if str1r != str2r: if "attribute [" in str1r and " 0x00 " in str1r: # we do not decode non-transitive attributes logger.parser("skipping string check on update with non-transitive attribute(s)") skip = True else: logger.parser("strings are different:") logger.parser("[%s]" % str1r) logger.parser("[%s]" % str2r) return False else: logger.parser("strings are fine") if skip: logger.parser("skipping encoding for update with non-transitive attribute(s)") elif pack1 != pack2: logger.parser("encoding are different") logger.parser("[%s]" % od(pack1)) logger.parser("[%s]" % od(pack2)) return False else: logger.parser("encoding is fine") logger.parser("----------------------------------------") except Notify, e: logger.parser("----------------------------------------") logger.parser(str(e)) logger.parser("----------------------------------------") return False
class Protocol(object): decode = True def __init__(self, peer): try: self.logger = Logger() except RuntimeError: self.logger = FakeLogger() self.peer = peer self.neighbor = peer.neighbor self.negotiated = Negotiated(self.neighbor) self.connection = None port = os.environ.get('exabgp.tcp.port', '') or os.environ.get( 'exabgp_tcp_port', '') self.port = int(port) if port.isdigit() else 179 # XXX: FIXME: check the the -19 is correct (but it is harmless) # The message size is the whole BGP message _without_ headers self.message_size = Message.MAX_LEN - Message.HEADER_LEN from exabgp.configuration.environment import environment self.log_routes = environment.settings().log.routes # XXX: we use self.peer.neighbor.peer_address when we could use self.neighbor.peer_address def __del__(self): self.close('automatic protocol cleanup') def me(self, message): return "Peer %15s ASN %-7s %s" % (self.peer.neighbor.peer_address, self.peer.neighbor.peer_as, message) def accept(self, incoming): self.connection = incoming self.peer.reactor.processes.reset(self.peer) if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer) # very important - as we use this function on __init__ return self def connect(self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: peer = self.neighbor.peer_address local = self.neighbor.local_address md5 = self.neighbor.md5 ttl = self.neighbor.ttl self.connection = Outgoing(peer.afi, peer.ip, local.ip, self.port, md5, ttl) connected = False try: generator = self.connection.establish() while True: connected = generator.next() if not connected: yield False continue self.peer.reactor.processes.reset(self.peer) if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer) yield True return except StopIteration: # close called by the caller # self.close('could not connect to remote end') yield False return def close(self, reason='protocol closed, reason unspecified'): if self.connection: self.logger.network(self.me(reason)) # must be first otherwise we could have a loop caused by the raise in the below self.connection.close() self.connection = None try: if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.down(self.peer, reason) except ProcessError: self.logger.message( self. me('could not send notification of neighbor close to API')) def write(self, message): if self.neighbor.api[ 'send-packets'] and not self.neighbor.api['consolidate']: self.peer.reactor.processes.send(self.peer, ord(message[18]), message[:19], message[19:]) for boolean in self.connection.writer(message): yield boolean # Read from network ....................................................... def read_message(self, comment=''): for length, msg, header, body, notify in self.connection.reader(): if notify: if self.neighbor.api['receive-packets']: self.peer.reactor.processes.receive( self.peer, msg, header, body) if self.neighbor.api[Message.ID.NOTIFICATION]: self.peer.reactor.processes.notification( self.peer, notify.code, notify.subcode, str(notify)) # XXX: is notify not already Notify class ? raise Notify(notify.code, notify.subcode, str(notify)) if not length: yield _NOP if self.neighbor.api[ 'receive-packets'] and not self.neighbor.api['consolidate']: self.peer.reactor.processes.receive(self.peer, msg, header, body) if msg == Message.ID.UPDATE and not self.neighbor.api[ 'receive-parsed'] and not self.log_routes: yield _UPDATE return message = Message.unpack_message(msg, body, self.negotiated) self.logger.message(self.me('<< %s' % Message.ID.name(msg))) if message.TYPE == Notification.TYPE: raise message if self.neighbor.api[msg]: if self.neighbor.api['receive-parsed']: if self.neighbor.api['consolidate'] and self.neighbor.api[ 'receive-packets']: self.peer.reactor.processes.message( msg, self.peer, message, header, body) else: self.peer.reactor.processes.message( msg, self.peer, message, '', '') yield message return # XXX: FIXME: check it is well 2,4 raise Notify(2, 4, 'unknown message received') # elif msg == Message.ID.ROUTE_REFRESH: # if self.negotiated.refresh != REFRESH.absent: # self.logger.message(self.me('<< ROUTE-REFRESH')) # refresh = RouteRefresh.unpack_message(body,self.negotiated) # if self.neighbor.api.receive_refresh: # if refresh.reserved in (RouteRefresh.start,RouteRefresh.end): # if self.neighbor.api.consolidate: # self.peer.reactor.process.refresh(self.peer,refresh,header,body) # else: # self.peer.reactor.processes.refresh(self.peer,refresh,'','') # else: # # XXX: FIXME: really should raise, we are too nice # self.logger.message(self.me('<< NOP (un-negotiated type %d)' % msg)) # refresh = UnknownMessage.unpack_message(body,self.negotiated) # yield refresh def validate_open(self): error = self.negotiated.validate(self.neighbor) if error is not None: raise Notify(*error) def read_open(self, ip): for received_open in self.read_message(): if received_open.TYPE == NOP.TYPE: yield received_open else: break if received_open.TYPE != Open.TYPE: raise Notify( 5, 1, 'The first packet recevied is not an open message (%s)' % received_open) self.logger.message(self.me('<< %s' % received_open)) yield received_open def read_keepalive(self, comment=''): for message in self.read_message(comment): if message.TYPE == NOP.TYPE: yield message else: break if message.TYPE != KeepAlive.TYPE: raise Notify(5, 2) yield message # # Sending message to peer # def new_open(self, restarted): sent_open = Open(4, self.neighbor.local_as, self.neighbor.router_id.ip, Capabilities().new(self.neighbor, restarted), self.neighbor.hold_time) # we do not buffer open message in purpose msg_send = sent_open.message() for _ in self.write(msg_send): yield _NOP self.logger.message(self.me('>> %s' % sent_open)) if self.neighbor.api[Message.ID.OPEN]: if self.neighbor.api['consolidate']: header = msg_send[0:38] body = msg_send[38:] self.peer.reactor.processes.message(Message.ID.OPEN, self.peer, sent_open, header, body, 'sent') else: self.peer.reactor.processes.message(Message.ID.OPEN, self.peer, sent_open, '', '', 'sent') yield sent_open def new_keepalive(self, comment=''): keepalive = KeepAlive() for _ in self.write(keepalive.message()): yield _NOP self.logger.message( self.me('>> KEEPALIVE%s' % (' (%s)' % comment if comment else ''))) yield keepalive def new_notification(self, notification): for _ in self.write(notification.message()): yield _NOP self.logger.message( self.me( '>> NOTIFICATION (%d,%d,"%s")' % (notification.code, notification.subcode, notification.data))) yield notification def new_update(self): updates = self.neighbor.rib.outgoing.updates( self.neighbor.group_updates) number = 0 for update in updates: for message in update.messages(self.negotiated): number += 1 for boolean in self.write(message): # boolean is a transient network error we already announced yield _NOP if number: self.logger.message(self.me('>> %d UPDATE(s)' % number)) yield _UPDATE def new_eors(self): # Send EOR to let our peer know he can perform a RIB update if self.negotiated.families: for afi, safi in self.negotiated.families: eor = EOR(afi, safi).message() for _ in self.write(eor): yield _NOP yield _UPDATE else: # If we are not sending an EOR, send a keepalive as soon as when finished # So the other routers knows that we have no (more) routes to send ... # (is that behaviour documented somewhere ??) for eor in self.new_keepalive('EOR'): yield _NOP yield _UPDATE def new_operational(self, operational, negotiated): for _ in self.write(operational.message(negotiated)): yield _NOP self.logger.message(self.me('>> OPERATIONAL %s' % str(operational))) yield operational def new_refresh(self, refresh, negotiated): for refresh in refresh.messages(negotiated): for _ in self.write(refresh): yield _NOP self.logger.message(self.me('>> REFRESH %s' % str(refresh))) yield refresh
def check_neighbor(neighbor): from exabgp.logger import Logger logger = Logger() logger._parser = True logger.parser('\ndecoding routes in configuration') n = neighbor[neighbor.keys()[0]] path = {} for f in known_families(): if n.add_path: path[f] = n.add_path capa = Capabilities().new(n, False) capa[Capability.ID.ADD_PATH] = path capa[Capability.ID.MULTIPROTOCOL] = n.families() o1 = Open(4, n.local_as, str(n.local_address), capa, 180) o2 = Open(4, n.peer_as, str(n.peer_address), capa, 180) negotiated = Negotiated(n) negotiated.sent(o1) negotiated.received(o2) #grouped = False for nei in neighbor.keys(): for message in neighbor[nei].rib.outgoing.updates(False): pass for change1 in neighbor[nei].rib.outgoing.sent_changes(): str1 = change1.extensive() packed = list( Update([change1.nlri], change1.attributes).messages(negotiated)) pack1 = packed[0] logger.parser('parsed route requires %d updates' % len(packed)) logger.parser('update size is %d' % len(pack1)) logger.parser('parsed route %s' % str1) logger.parser('parsed hex %s' % od(pack1)) # This does not take the BGP header - let's assume we will not break that :) try: logger.parser('') # new line pack1s = pack1[19:] if pack1.startswith('\xFF' * 16) else pack1 update = Update.unpack_message(pack1s, negotiated) change2 = Change(update.nlris[0], update.attributes) str2 = change2.extensive() pack2 = list( Update([update.nlris[0]], update.attributes).messages(negotiated))[0] logger.parser('recoded route %s' % str2) logger.parser('recoded hex %s' % od(pack2)) str1r = str1.replace(' med 100', '').replace( ' local-preference 100', '').replace(' origin igp', '') str2r = str2.replace(' med 100', '').replace( ' local-preference 100', '').replace(' origin igp', '') skip = False if str1r != str2r: if 'attribute [' in str1r and ' 0x00 ' in str1r: # we do not decode non-transitive attributes logger.parser( 'skipping string check on update with non-transitive attribute(s)' ) skip = True else: logger.parser('strings are different:') logger.parser('[%s]' % str1r) logger.parser('[%s]' % str2r) return False else: logger.parser('strings are fine') if skip: logger.parser( 'skipping encoding for update with non-transitive attribute(s)' ) elif pack1 != pack2: logger.parser('encoding are different') logger.parser('[%s]' % od(pack1)) logger.parser('[%s]' % od(pack2)) return False else: logger.parser('encoding is fine') logger.parser('----------------------------------------') except Notify, e: logger.parser('----------------------------------------') logger.parser(str(e)) logger.parser('----------------------------------------') return False
def check_neighbor (neighbors): from exabgp.logger import Logger logger = Logger() logger._parser = True if not neighbors: logger.parser('\ncould not find neighbor(s) to check') return False logger.parser('\ndecoding routes in configuration') for name in neighbors.keys(): neighbor = neighbors[name] neighbor.rib.clear() path = {} for f in known_families(): if neighbor.add_path: path[f] = neighbor.add_path capa = Capabilities().new(neighbor,False) capa[Capability.CODE.ADD_PATH] = path capa[Capability.CODE.MULTIPROTOCOL] = neighbor.families() o1 = Open(4,neighbor.local_as,str(neighbor.local_address),capa,180) o2 = Open(4,neighbor.peer_as,str(neighbor.peer_address),capa,180) negotiated = Negotiated(neighbor) negotiated.sent(o1) negotiated.received(o2) # grouped = False for message in neighbor.rib.outgoing.updates(False): pass for change1 in neighbor.rib.outgoing.sent_changes(): str1 = change1.extensive() packed = list(Update([change1.nlri],change1.attributes).messages(negotiated)) pack1 = packed[0] logger.parser('parsed route requires %d updates' % len(packed)) logger.parser('update size is %d' % len(pack1)) logger.parser('parsed route %s' % str1) logger.parser('parsed hex %s' % od(pack1)) # This does not take the BGP header - let's assume we will not break that :) try: logger.parser('') # new line pack1s = pack1[19:] if pack1.startswith('\xFF'*16) else pack1 update = Update.unpack_message(pack1s,negotiated) change2 = Change(update.nlris[0],update.attributes) str2 = change2.extensive() pack2 = list(Update([update.nlris[0]],update.attributes).messages(negotiated))[0] logger.parser('recoded route %s' % str2) logger.parser('recoded hex %s' % od(pack2)) str1r = str1.replace(' med 100','').replace(' local-preference 100','').replace(' origin igp','') str2r = str2.replace(' med 100','').replace(' local-preference 100','').replace(' origin igp','') if ' name ' in str1r: parts = str1r.split(' ') pos = parts.index('name') str1r = ' '.join(parts[:pos] + parts[pos+2:]) skip = False if str1r != str2r: if 'attribute [' in str1r and ' 0x00 ' in str1r: # we do not decode non-transitive attributes logger.parser('skipping string check on update with non-transitive attribute(s)') skip = True else: logger.parser('strings are different:') logger.parser('[%s]' % (str1r)) logger.parser('[%s]' % (str2r)) return False else: logger.parser('strings are fine') if skip: logger.parser('skipping encoding for update with non-transitive attribute(s)') elif pack1 != pack2: logger.parser('encoding are different') logger.parser('[%s]' % (od(pack1))) logger.parser('[%s]' % (od(pack2))) return False else: logger.parser('encoding is fine') logger.parser('----------------------------------------') except Notify,exc: logger.parser('----------------------------------------') logger.parser(str(exc)) logger.parser('----------------------------------------') return False
class Transcoder(object): seen_open = { 'send': None, 'receive': None, } negotiated = None json = JSON(json_version) def __init__(self, src='json', dst='json'): if src != 'json': raise RuntimeError('left as an exercise to the reader') if dst != 'json': raise RuntimeError('left as an exercise to the reader') self.convert = self._from_json self.encoder = self.json def _state(self): self.seen_open['send'] = None self.seen_open['receive'] = None self.negotiated = None def _open(self, direction, message): self.seen_open[direction] = message if all(self.seen_open.values()): self.negotiated = Negotiated(None) self.negotiated.sent(self.seen_open['send']) self.negotiated.received(self.seen_open['receive']) def _from_json(self, string): try: parsed = json.loads(string) except ValueError: print >> sys.stderr, 'invalid JSON message' sys.exit(1) if parsed.get('exabgp', '0.0.0') != json_version: print >> sys.stderr, 'invalid json version', string sys.exit(1) content = parsed.get('type', '') if not content: print >> sys.stderr, 'invalid json content', string sys.exit(1) neighbor = _FakeNeighbor( parsed['neighbor']['address']['local'], parsed['neighbor']['address']['peer'], parsed['neighbor']['asn']['local'], parsed['neighbor']['asn']['peer'], ) if content == 'state': self._state() return string direction = parsed['neighbor']['direction'] category = parsed['neighbor']['message']['category'] header = parsed['neighbor']['message']['header'] body = parsed['neighbor']['message']['body'] raw = ''.join( chr(int(body[_:_ + 2], 16)) for _ in range(0, len(body), 2)) if content == 'open': message = Open.unpack_message(raw) self._open(direction, message) return self.encoder.open(neighbor, direction, message, header, body) if content == 'keapalive': return self.encoder.keepalive(neighbor, direction, header, body) if content == 'notification': return self.encoder.notification(neighbor, direction, ord(message[0]), ord(message[1]), message[2:], header, body) if not self.negotiated: print >> sys.stderr, 'invalid message sequence, open not exchange not complete', string sys.exit(1) message = Message.unpack(category, raw, self.negotiated) if content == 'update': return self.encoder.update(neighbor, direction, message, header, body) if content == 'eor': # XXX: Should not be required return self.encoder.update(neighbor, direction, message, header, body) if content == 'refresh': return self.json.refresh(neighbor, direction, message, header, body) if content == 'operational': return self.json.refresh(neighbor, direction, message, header, body) raise RuntimeError( 'the programer is a monkey and forgot a JSON message type')