def _backlog(self, maximum=0): backlog = self._messages.get(self.neighbor.peer_as, []) if backlog: if not self._frozen: self._frozen = time.time() if self._frozen and self._frozen + ( self.neighbor.hold_time) < time.time(): raise Failure( 'peer %s not reading on socket - killing session' % self.neighbor.peer_as) logger.message( self.me( "unable to send route for %d second (maximum allowed %d)" % (time.time() - self._frozen, self.neighbor.hold_time))) nb_backlog = len(backlog) if nb_backlog > MAX_BACKLOG: raise Failure( 'over %d routes buffered for peer %s - killing session' % (MAX_BACKLOG, self.neighbor.peer_as)) logger.message( self.me("backlog of %d/%d routes" % (nb_backlog, MAX_BACKLOG))) count = 0 while backlog: count += 1 name, update = backlog[0] written = self.connection.write(update) if not written: break logger.message(self.me(">> DEBUFFERED %s" % name)) backlog.pop(0) self._frozen = 0 yield count if maximum and count >= maximum: break self._messages[self.neighbor.peer_as] = backlog
def read (self,number): if not self.io: raise Failure('Trying to read on a closed TCP connection') if number == 0: return '' try: r = self.io.recv(number) self.last_read = time.time() logger.wire(LazyFormat("%15s RECV " % self.peer,hexa,r)) return r except socket.timeout,e: self.close() raise Failure('Timeout while reading data from the network: %s ' % str(e))
class Connection (object): def __init__ (self,peer,local,md5,ttl): self.io = None self.last_read = 0 self.last_write = 0 self.peer = peer self._loop_start = None self._buffer = [] logger.wire("Opening connection to %s" % self.peer) if peer.afi != local.afi: raise Failure('The local IP and peer IP must be of the same family (both IPv4 or both IPv6)') try: if peer.afi == AFI.ipv4: self.io = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if peer.afi == AFI.ipv6: self.io = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP) try: self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except AttributeError: pass try: self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except AttributeError: pass try: # diable Nagle's algorithm (no grouping of packets) self.io.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) except AttributeError: logger.warning("wire","Could not disable nagle's algorithm for %s" % self.peer) pass self.io.settimeout(1) if peer.afi == AFI.ipv4: self.io.bind((local.ip,0)) if peer.afi == AFI.ipv6: self.io.bind((local.ip,0,0,0)) except socket.error,e: self.close() raise Failure('Could not bind to local ip %s - %s' % (local.ip,str(e))) if md5: try: TCP_MD5SIG = 14 TCP_MD5SIG_MAXKEYLEN = 80 SS_PADSIZE = 120 n_addr = socket.inet_aton(peer.ip) n_port = socket.htons(179) tcp_md5sig = 'HH4s%dx2xH4x%ds' % (SS_PADSIZE, TCP_MD5SIG_MAXKEYLEN) md5sig = struct.pack(tcp_md5sig, socket.AF_INET, n_port, n_addr, len(md5), md5) self.io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, md5sig) except socket.error,e: self.close() raise Failure('This OS does not support TCP_MD5SIG, you can not use MD5 : %s' % str(e))
def __init__ (self,peer,local,md5,ttl): self.io = None self.last_read = 0 self.last_write = 0 self.peer = peer self._loop_start = None self._buffer = [] logger.wire("Opening connection to %s" % self.peer) if peer.afi != local.afi: raise Failure('The local IP and peer IP must be of the same family (both IPv4 or both IPv6)') try: if peer.afi == AFI.ipv4: self.io = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) if peer.afi == AFI.ipv6: self.io = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP) try: self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) except AttributeError: pass try: self.io.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except AttributeError: pass try: # diable Nagle's algorithm (no grouping of packets) self.io.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) except AttributeError: logger.warning("wire","Could not disable nagle's algorithm for %s" % self.peer) pass self.io.settimeout(1) if peer.afi == AFI.ipv4: self.io.bind((local.ip,0)) if peer.afi == AFI.ipv6: self.io.bind((local.ip,0,0,0)) except socket.error,e: self.close() raise Failure('Could not bind to local ip %s - %s' % (local.ip,str(e)))
def new_open(self, restarted, asn4): if asn4: asn = self.neighbor.local_as else: asn = AS_TRANS o = Open(4, asn, self.neighbor.router_id.ip, Capabilities().default(self.neighbor, restarted), self.neighbor.hold_time) if not self.connection.write(o.message()): raise Failure('Could not send open') return o
def chunked(generator, size): chunk = '' for data in generator: if len(data) > size: raise Failure( 'Can not send BGP update larger than %d bytes on this connection.' % size) if len(chunk) + len(data) <= size: chunk += data continue yield chunk chunk = data if chunk: yield chunk
def pending (self,reset=False): if reset: self._loop_start = None else: if not self._loop_start: self._loop_start = time.time() else: if self._loop_start + READ_TIMEOUT < time.time(): raise Failure('Waited for data on a socket for more than %d second(s)' % READ_TIMEOUT) try: r,_,_ = select.select([self.io,],[],[],0) except select.error,e: errno,message = e.args if errno in errno_block: return False raise
def close(self): #self._delta.last = 0 if self.connection: # must be first otherwise we could have a loop caused by the raise in the below self.connection.close() self.connection = None message = 'neighbor %s down\n' % self.peer.neighbor.peer_address try: proc = self.peer.supervisor.processes for name in proc.notify(self.neighbor.peer_address): proc.write(name, message) except ProcessError: raise Failure( 'Could not send message(s) to helper program(s) : %s' % message)
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 = Connection(peer, local, md5, ttl) message = 'neighbor %s connected\n' % self.peer.neighbor.peer_address try: proc = self.peer.supervisor.processes for name in proc.notify(self.neighbor.peer_address): proc.write(name, message) except ProcessError: raise Failure( 'Could not send message(s) to helper program(s) : %s' % message)
def write (self,data): if not self.io: # We alrady returned a Failure # It must be a write attempted during the closing of the peering session # Make sure it does not hold the cleanup. return True if not self.ready(): return False try: logger.wire(LazyFormat("%15s SENT " % self.peer,hexa,data)) self.io.sendall(data) self.last_write = time.time() return True except socket.error, e: # Must never happen as we are performing a select before the write #failure = getattr(e,'errno',None) #if failure in errno_block: # return False self.close() logger.wire("%15s %s" % (self.peer,trace())) raise Failure('Problem while writing data to the network: %s' % str(e))
n_addr = socket.inet_aton(peer.ip) n_port = socket.htons(179) tcp_md5sig = 'HH4s%dx2xH4x%ds' % (SS_PADSIZE, TCP_MD5SIG_MAXKEYLEN) md5sig = struct.pack(tcp_md5sig, socket.AF_INET, n_port, n_addr, len(md5), md5) self.io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, md5sig) except socket.error,e: self.close() raise Failure('This OS does not support TCP_MD5SIG, you can not use MD5 : %s' % str(e)) # None (ttl-security unset) or zero (maximum TTL) is the same thing if ttl: try: self.io.setsockopt(socket.IPPROTO_IP,socket.IP_TTL, 20) except socket.error,e: self.close() raise Failure('This OS does not support IP_TTL (ttl-security), you can not use MD5 : %s' % str(e)) try: if peer.afi == AFI.ipv4: self.io.connect((peer.ip,179)) if peer.afi == AFI.ipv6: self.io.connect((peer.ip,179,0,0)) self.io.setblocking(0) except socket.error, e: self.close() raise Failure('Could not connect to peer: %s' % str(e)) try: try: # Linux / Windows self.message_size = self.io.getsockopt(socket.SOL_SOCKET, socket.SO_MAX_MSG_SIZE)
def read_message(self): # This call reset the time for the timeout in if not self.connection.pending(True): return NOP('') length = 19 data = '' while length: if self.connection.pending(): delta = self.connection.read(length) data += delta length -= len(delta) # The socket is closed if not data: raise Failure('The TCP connection is closed') if data[:16] != Message.MARKER: # We are speaking BGP - send us a valid Marker raise Notify(1, 1, 'The packet received does not contain a BGP marker') raw_length = data[16:18] length = unpack('!H', raw_length)[0] msg = data[18] if (length < 19 or length > 4096): # BAD Message Length raise Notify(1, 2) if ((msg == Open.TYPE and length < 29) or (msg == Update.TYPE and length < 23) or (msg == Notification.TYPE and length < 21) or (msg == KeepAlive.TYPE and length != 19)): # MUST send the faulty length back raise Notify(1, 2, raw_length) #(msg == RouteRefresh.TYPE and length != 23) length -= 19 data = '' while length: if self.connection.pending(): delta = self.connection.read(length) data += delta length -= len(delta) # The socket is closed if not data: raise Failure('The TCP connection is closed') if msg == Notification.TYPE: raise Notification(ord(data[0]), ord(data[1])) if msg == KeepAlive.TYPE: return self.KeepAliveFactory(data) if msg == Open.TYPE: return self.OpenFactory(data) if msg == Update.TYPE: if self.neighbor.parse_routes: update = self.UpdateFactory(data) return update else: return NOP('') if self.strict: raise Notify(1, 3, msg) return NOP(data)
def _run(self, max_wait_open=10.0): try: if self.supervisor.processes.broken(self.neighbor.peer_address): # XXX: we should perhaps try to restart the process ?? raise Failure( 'ExaBGP lost the helper process for this peer - peer down') self.bgp = Protocol(self) self.bgp.connect() self._reset_skip() _open = self.bgp.new_open(self._restarted, self._asn4) logger.message(self.me('>> %s' % _open)) yield None start = time.time() while True: self.open = self.bgp.read_open(_open, self.neighbor.peer_address.ip) if time.time() - start > max_wait_open: logger.message( self. me('Waited for an OPEN for too long - killing the session' )) raise Notify( 1, 1, 'The client took over %s seconds to send the OPEN, closing' % str(max_wait_open)) # OPEN or NOP if self.open.TYPE == NOP.TYPE: yield None continue # This test is already done in read_open #if self.open.TYPE != Open.TYPE: # raise Notify(5,1,'We are expecting an OPEN message') logger.message(self.me('<< %s' % self.open)) if not self.open.capabilities.announced( Capabilities.FOUR_BYTES_ASN) and _open.asn.asn4(): self._asn4 = False raise Notify( 2, 0, 'peer does not speak ASN4 - restarting in compatibility mode' ) if _open.capabilities.announced(Capabilities.MULTISESSION_BGP): if not self.open.capabilities.announced( Capabilities.MULTISESSION_BGP): raise Notify(2, 7, 'peer does not support MULTISESSION') local_sessionid = set( _open.capabilities[Capabilities.MULTISESSION_BGP]) remote_sessionid = self.open.capabilities[ Capabilities.MULTISESSION_BGP] # Empty capability is the same as MultiProtocol (which is what we send) if not remote_sessionid: remote_sessionid.append( Capabilities.MULTIPROTOCOL_EXTENSIONS) remote_sessionid = set(remote_sessionid) # As we only send one MP per session, if the matching fails, we have nothing in common if local_sessionid.intersection( remote_sessionid) != local_sessionid: raise Notify( 2, 8, 'peer did not reply with the sessionid we sent') # We can not collide due to the way we generate the configuration yield None break message = self.bgp.new_keepalive(force=True) logger.message(self.me('>> KEEPALIVE (OPENCONFIRM)')) yield True while True: message = self.bgp.read_keepalive() # KEEPALIVE or NOP if message.TYPE == KeepAlive.TYPE: logger.message(self.me('<< KEEPALIVE (ESTABLISHED)')) break yield None try: for name in self.supervisor.processes.notify( self.neighbor.peer_address): self.supervisor.processes.write( name, 'neighbor %s up\n' % self.neighbor.peer_address) except ProcessError: # Can not find any better error code that 6,0 ! raise Notify(6, 0, 'ExaBGP Internal error, sorry.') count = 0 for count in self.bgp.new_announce(): yield True self._updates = self.bgp.buffered() if count: logger.message(self.me('>> %d UPDATE(s)' % count)) eor = False if self.neighbor.graceful_restart and \ self.open.capabilities.announced(Capabilities.MULTIPROTOCOL_EXTENSIONS) and \ self.open.capabilities.announced(Capabilities.GRACEFUL_RESTART): families = [] for family in self.open.capabilities[ Capabilities.GRACEFUL_RESTART].families(): if family in self.neighbor.families(): families.append(family) self.bgp.new_eors(families) if families: eor = True logger.message( self.me('>> EOR %s' % ', '.join([ '%s %s' % (str(afi), str(safi)) for (afi, safi) in families ]))) if not eor: # 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 ??) c, k = self.bgp.new_keepalive(True) if k: logger.message( self.me('>> KEEPALIVE (no more UPDATE and no EOR)')) seen_update = False while self._running: self._now = time.time() if self._now > self._next_info: self._next_info = self._now + self.update_time display_update = True else: display_update = False c, k = self.bgp.new_keepalive() if k: logger.message(self.me('>> KEEPALIVE')) if display_update: logger.timers( self.me('Sending Timer %d second(s) left' % c)) message = self.bgp.read_message() # let's read if we have keepalive before doing the timer check c = self.bgp.check_keepalive() if display_update: logger.timers( self.me('Receive Timer %d second(s) left' % c)) if message.TYPE == KeepAlive.TYPE: logger.message(self.me('<< KEEPALIVE')) elif message.TYPE == Update.TYPE: seen_update = True self._received_routes.extend(message.routes) if message.routes: logger.message(self.me('<< UPDATE')) self._route_parsed += len(message.routes) if self._route_parsed: for route in message.routes: logger.routes( LazyFormat(self.me(''), str, route)) else: logger.message(self.me('<< UPDATE (not parsed)')) elif message.TYPE not in (NOP.TYPE, ): logger.message(self.me('<< %d' % ord(message.TYPE))) if seen_update and display_update: logger.supervisor( self.me('processed %d routes' % self._route_parsed)) seen_update = False if self._updates: count = 0 for count in self.bgp.new_update(): yield True logger.message(self.me('>> UPDATE (%d)' % count)) self._updates = self.bgp.buffered() yield None if self.neighbor.graceful_restart and self.open.capabilities.announced( Capabilities.GRACEFUL_RESTART): logger.warning('Closing the connection without notification') self.bgp.close() return # User closing the connection raise Notify(6, 3) except NotConnected, e: logger.warning('we can not connect to the peer %s' % str(e)) self._more_skip() try: self.bgp.close() except Failure: pass return