def __init__(self, bgp_peering=None, protocol=None): """ please see RFC 4271 page 37 for value meanning """ self.bgp_peering = bgp_peering self.protocol = protocol # Session attributes required (mandatory) for each connection: self.state = bgp_cons.ST_IDLE self.connect_retry_counter = 0 self.connect_retry_time = bgp_cons.CONNECT_RETRY_TIME self.connect_retry_timer = BGPTimer(self.connect_retry_time_event, 'connect retry timer') self.hold_time = bgp_cons.HOLD_TIME self.hold_timer = BGPTimer(self.hold_time_event, 'hold timer') self.keep_alive_time = self.hold_time / 3 self.keep_alive_timer = BGPTimer(self.keep_alive_time_event, 'keep alive timer') self.allow_automatic_start = True self.allow_automatic_stop = False self.delay_open = False self.delay_open_time = bgp_cons.DELAY_OPEN_TIME self.delay_open_timer = BGPTimer(self.delay_open_time_event, 'delay open timer') self.idle_hold_time = bgp_cons.IDLEHOLD_TIME self.idle_hold_timer = BGPTimer(self.idle_hold_time_event, 'idle hold timer') self.uptime = None
def __init__(self): """Create a BGP protocol. """ self.fsm = None self.peer_id = None self.disconnected = False self.receive_buffer = '' self.process_queue_time = 0.5 self.process_queue_timer = BGPTimer(self.process_queue) self.start_process_queue = False self.update_msg_queue = [] # statistic self.msg_sent_stat = { 'Opens': 0, 'Notifications': 0, 'Updates': 0, 'Keepalives': 0, 'Route Refresh': 0 } self.msg_recv_stat = { 'Opens': 0, 'Notifications': 0, 'Updates': 0, 'Keepalives': 0, 'Route Refresh': 0 }
def __init__(self, bgp_peering=None, protocol=None): """ please see RFC 4271 page 37 for value meanning """ self.bgp_peering = bgp_peering self.protocol = protocol # Session attributes required (mandatory) for each connection: self.state = bgp_cons.ST_IDLE self.connect_retry_counter = 0 self.connect_retry_time = bgp_cons.CONNECT_RETRY_TIME self.connect_retry_timer = BGPTimer(self.connect_retry_time_event) self.hold_time = bgp_cons.HOLD_TIME self.hold_timer = BGPTimer(self.hold_time_event) self.keep_alive_time = self.hold_time / 3 self.keep_alive_timer = BGPTimer(self.keep_alive_time_event) self.allow_automatic_start = True self.allow_automatic_stop = False self.delay_open = False self.delay_open_time = bgp_cons.DELAY_OPEN_TIME self.delay_open_timer = BGPTimer(self.delay_open_time_event) self.idle_hold_time = bgp_cons.IDLEHOLD_TIME self.idle_hold_timer = BGPTimer(self.idle_hold_time_event) self.uptime = None self.my_capability = {'AFI_SAFI': self.bgp_peering.afi_safi, '4byteAS': True, 'routeRefresh': True, 'ciscoRouteRefresh': True, 'GracefulRestart': True, 'ciscoMultiSession': True} # neighbor capability self.neighbor_capability = {}
class FSM(object): """ Implements BGP Events described in section 8.1 of RFC 4271 Implements BGP finite state machine described in section 8.2 of RFC 4271 """ protocol = None state = bgp_cons.ST_IDLE large_hold_time = bgp_cons.LARGER_HOLD_TIME def __init__(self, bgp_peering=None, protocol=None): """ please see RFC 4271 page 37 for value meanning """ self.bgp_peering = bgp_peering self.protocol = protocol # Session attributes required (mandatory) for each connection: self.state = bgp_cons.ST_IDLE self.connect_retry_counter = 0 self.connect_retry_time = bgp_cons.CONNECT_RETRY_TIME self.connect_retry_timer = BGPTimer(self.connect_retry_time_event) self.hold_time = bgp_cons.HOLD_TIME self.hold_timer = BGPTimer(self.hold_time_event) self.keep_alive_time = self.hold_time / 3 self.keep_alive_timer = BGPTimer(self.keep_alive_time_event) self.allow_automatic_start = True self.allow_automatic_stop = False self.delay_open = False self.delay_open_time = bgp_cons.DELAY_OPEN_TIME self.delay_open_timer = BGPTimer(self.delay_open_time_event) self.idle_hold_time = bgp_cons.IDLEHOLD_TIME self.idle_hold_timer = BGPTimer(self.idle_hold_time_event) self.uptime = None self.my_capability = {'AFI_SAFI': self.bgp_peering.afi_safi, '4byteAS': True, 'routeRefresh': True, 'ciscoRouteRefresh': True, 'GracefulRestart': True, 'ciscoMultiSession': True} # neighbor capability self.neighbor_capability = {} def __setattr__(self, name, value): if name == 'state' and value != getattr(self, name): LOG.info("[%s]State is now:%s" % (self.bgp_peering.peer_addr, bgp_cons.stateDescr[value])) if value == bgp_cons.ST_ESTABLISHED: self.uptime = time.time() super(FSM, self).__setattr__(name, value) def manual_start(self): """ Event 1: ManualStart Definiton: Should be called when a BGP ManualStart event is requested. Note that a protocol instance does not yet exist at this point, so this method requires some support from BGPPeering.manual_start(). Status: Mandatory """ LOG.info('Manual start.') if self.state == bgp_cons.ST_IDLE: self.connect_retry_counter = 0 self.connect_retry_timer.reset(self.connect_retry_time) def manual_stop(self): """ Event 2: ManualStop Definition: Should be called when a BGP ManualStop event is requested. Status: Mandatory """ LOG.info('Manual stop') if self.state != bgp_cons.ST_IDLE: self.protocol.send_notification(bgp_cons.ERR_CEASE, 0) # Stop all timers LOG.info('Stop all timers') for timer in (self.connect_retry_timer, self.hold_timer, self.keep_alive_timer, self.delay_open_timer, self.idle_hold_timer): timer.cancel() LOG.info('-- Stop timer %s' % timer) self._close_connection() self.connect_retry_counter = 0 self.allow_automatic_start = False self.state = bgp_cons.ST_IDLE def automatic_start(self, idle_hold=False): """ Event 3: AutomaticStart Definition: Should be called when a BGP Automatic Start event is requested. Returns True or False to indicate BGPPeering whether a connection attempt should be initiated. Status: Optional :param idle_hold: BGP Idle Hold or not. """ LOG.info('Automatic start') if self.state in [bgp_cons.ST_IDLE, bgp_cons.ST_CONNECT]: if idle_hold: LOG.info('Idle Hold, please wait time=%s' % self.idle_hold_time) self.idle_hold_timer.reset(self.idle_hold_time) return False elif self.allow_automatic_start: LOG.info('Do not need Idle Hold, start right now.') LOG.info('Connect retry counter: %s' % self.connect_retry_counter) self.connect_retry_counter += 1 self.connect_retry_timer.reset(self.connect_retry_time) LOG.info('Connect retry timer, time=%s' % self.connect_retry_time) self.state = bgp_cons.ST_CONNECT return True else: return False else: return False def connect_retry_time_event(self): """ Event 9: ConnectRetryTimer_Expires Definition: Called when the ConnectRetryTimer expires. Status: Mandatory """ LOG.info('Connect retry timer expires') if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, event 9 self._close_connection() LOG.info('Reset connect retry timer, time=%s' % self.connect_retry_time) self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() LOG.info('Cancel delay open timer') # Initiate TCP connection if self.bgp_peering: LOG.info('Bgp peering connect retry') self.bgp_peering.connect_retry() elif self.state != bgp_cons.ST_IDLE: # State OpenSent, OpenConfirm, Established, event 12 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def hold_time_event(self): """ Event 10: HoldTimer_Expires Definition: Called when the HoldTimer expires. Action: NOTIFICATION message with the Hold Timer Expried Error Code is sent and the BGP connection is closed Status: Mandatory """ LOG.info('Hold Timer expires') if self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): # States OpenSent, OpenConfirm, Established, event 10 self.protocol.send_notification(bgp_cons.ERR_HOLD_TIMER_EXPIRED, 0) self.connect_retry_timer.cancel() self._error_close() self.state = bgp_cons.ST_IDLE elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): self._error_close() def keep_alive_time_event(self): """ Event 11: KeepaliveTimer_Expires Definition: Called when the KeepAliveTimer expires. Action: Send Keepalive messsage if the state of FSM is ST_OPENCONFIRM or ST_ESTABLISHED, close BGP connection if state is others Status: Mandatory """ LOG.info('Keep alive timer expires') if self.state in (bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): # State OpenConfirm, Established, event 11 self.protocol.send_keepalive() if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): self._error_close() def delay_open_time_event(self): """ Event 12: DelayOpenTimer_Expires Definition: Called when the DelayOpenTimer expires. Status: Optional """ LOG.info('Delay open timer expires') # DelayOpen attribute SHOULD be set to TRUE if self.state == bgp_cons.ST_CONNECT: # State Connect, event 12 self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT elif self.state == bgp_cons.ST_ACTIVE: # State Active, event 12 self.connect_retry_timer.cancel() self.delay_open_timer.cancel() self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT elif self.state != bgp_cons.ST_IDLE: # State OpenSent, OpenConfirm, Established, event 12 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def idle_hold_time_event(self): """ Event 13: IdleHoldTimer_Expires Definition: Called when the IdleHoldTimer expires. Status: Optional """ # DampPeerOscillations attribute SHOULD be set to TRUE # assert(self.dampPeerOscillations) LOG.info('Idle Hold Timer expires') if self.state == bgp_cons.ST_IDLE: if self.bgp_peering: self.bgp_peering.automatic_start(idle_hold=False) def connection_made(self): """ Event 16: Tcp_CR_Acked Event 17: TcpConnectionConfirmed Definition: Should be called when a TCP connection has successfully been established with the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, Event 16 or 17 if self.delay_open: self.connect_retry_timer.cancel() LOG.info('Delay open for this peer') self.delay_open_timer.reset(self.delay_open_time) else: self.connect_retry_timer.cancel() self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT def connection_failed(self): """ Event 18: TcpConnectionFails Definition: Should be called when the associated TCP connection failed, or was lost. Status: Mandatory """ if self.state == bgp_cons.ST_CONNECT: # State Connect, event 18 if self.delay_open_timer.active(): self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() self.state = bgp_cons.ST_ACTIVE else: self.connect_retry_timer.cancel() self._close_connection() if self.bgp_peering: self.state = bgp_cons.ST_IDLE self.bgp_peering.connection_closed(self.protocol) elif self.state == bgp_cons.ST_ACTIVE: # State Active, event 18 self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass # TODO: osc damping self.state = bgp_cons.ST_IDLE elif self.state == bgp_cons.ST_OPENSENT: # State OpenSent, event 18 if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass self._close_connection() self.connect_retry_timer.reset(self.connect_retry_time) self.state = bgp_cons.ST_ACTIVE if self.bgp_peering: self.bgp_peering.connection_closed(self.protocol) elif self.state in (bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): self._error_close() def open_received(self): """ Event 19: BGPOpen Event 20: BGPOpen with DelayOpenTimer running Definition: Should be called when a BGP Open message was received from the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): if self.delay_open_timer.active(): # State Connect, event 20 self.connect_retry_timer.cancel() self.delay_open_timer.cancel() self.connect_retry_counter = 0 self.protocol.send_open() self.protocol.send_keepalive() if self.hold_time: self.keep_alive_timer.reset(self.keep_alive_time) self.hold_timer.reset(self.hold_time) else: # holdTime == 0 self.keep_alive_timer.cancel() self.hold_timer.cancel() self.state = bgp_cons.ST_OPENCONFIRM else: # State Connect, event 19 self._error_close() elif self.state == bgp_cons.ST_OPENSENT: # State OpenSent, events 19, 20 self.delay_open_timer.cancel() self.connect_retry_timer.cancel() self.protocol.send_keepalive() if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) self.hold_timer.reset(self.hold_time) self.state = bgp_cons.ST_OPENCONFIRM elif self.state == bgp_cons.ST_OPENCONFIRM: # State OpenConfirm, events 19, 20 # TODO:Perform collision detection pass elif self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 19 or 20 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def header_error(self, suberror, data=''): """ Event 21: BGPHeaderErr Definition: Should be called when an invalid BGP message header was received. Status: Mandatory :param suberror: bgp notification sub error code :param data: bgp notification error data """ self.protocol.send_notification(bgp_cons.ERR_MSG_HDR, suberror, data) # Note: RFC4271 states that we should send ERR_FSM in the # Established state, which contradicts earlier statements. self._error_close() def open_message_error(self, suberror, data=''): """ Event 22: BGPOpenMsgErr Definition: Should be called when an invalid BGP Open message was received. Status: Mandatory :param suberror: bgp notification sub error code :param data: bgp notification error data """ self.protocol.send_notification(bgp_cons.ERR_MSG_OPEN, suberror, data) # Note: RFC4271 states that we should send ERR_FSM in the # Established state, which contradicts earlier statements. self._error_close() def notimsg_version_error(self): """ Event 24: NotifMsgVerErr Definition: Should be called when a BGP Notification Open Version Error message was received from the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM): # State OpenSent, event 24 self.connect_retry_timer.cancel() self._close_connection() self.state = bgp_cons.ST_IDLE elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, event 24 self._error_close() def notification_received(self, error, suberror): """ Event 25: NotifMsg Definition: Should be called when a BGP Notification message was received from the peer. Status: Mandatory :param error: bgp notification error code :param suberror: bgp notification sub error code """ if error == bgp_cons.ERR_MSG_OPEN and suberror == 1: # Event 24 : version error self.notimsg_version_error() else: if self.state != bgp_cons.ST_IDLE: # State != Idle, events 24, 25 self._error_close() def keep_alive_received(self): """ Event 26: KeepAliveMsg Definition: Should be called when a BGP KeepAlive packet was received from the peer. Status: Mandatory """ if self.state == bgp_cons.ST_OPENCONFIRM: # State OpenSent, event 26 self.hold_timer.reset(self.hold_time) self.state = bgp_cons.ST_ESTABLISHED elif self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 26 self.hold_timer.reset(self.hold_time) elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # States Connect, Active, event 26 self._error_close() def update_received(self): """ Event 27: UpdateMsg Definition: Called when a valid BGP Update message was received. Status: Mandatory """ if self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 27 if self.hold_time != 0: self.hold_timer.reset(self.hold_time) elif self.state in (bgp_cons.ST_ACTIVE, bgp_cons.ST_CONNECT): # States Active, Connect, event 27 self._error_close() elif self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM): # States OpenSent, OpenConfirm, event 27 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def update_sent(self): """Called by the protocol instance when it just sent an Update message.""" if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) def _error_close(self): """Internal method that closes a connection and returns the state to IDLE. """ # Stop the timers for timer in (self.connect_retry_timer, self.delay_open_timer, self.hold_timer, self.keep_alive_timer): timer.cancel() self.idle_hold_timer.reset(self.idle_hold_time) # Release BGP resources (routes, etc) if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass self._close_connection() self.connect_retry_counter += 1 self.state = self.bgp_peering.fsm.state = bgp_cons.ST_IDLE def _close_connection(self): """Internal method that close the connection if a valid BGP protocol instance exists. """ if self.protocol is not None: self.protocol.closeConnection() self.connect_retry_counter = 0 LOG.info('Closing protocol connection.')
class FSM(object): """ Implements BGP Events described in section 8.1 of RFC 4271 Implements BGP finite state machine described in section 8.2 of RFC 4271 """ protocol = None state = bgp_cons.ST_IDLE large_hold_time = bgp_cons.LARGER_HOLD_TIME def __init__(self, bgp_peering=None, protocol=None): """ please see RFC 4271 page 37 for value meanning """ self.bgp_peering = bgp_peering self.protocol = protocol # Session attributes required (mandatory) for each connection: self.state = bgp_cons.ST_IDLE self.connect_retry_counter = 0 self.connect_retry_time = bgp_cons.CONNECT_RETRY_TIME self.connect_retry_timer = BGPTimer(self.connect_retry_time_event, 'connect retry timer') self.hold_time = bgp_cons.HOLD_TIME self.hold_timer = BGPTimer(self.hold_time_event, 'hold timer') self.keep_alive_time = self.hold_time / 3 self.keep_alive_timer = BGPTimer(self.keep_alive_time_event, 'keep alive timer') self.allow_automatic_start = True self.allow_automatic_stop = False self.delay_open = False self.delay_open_time = bgp_cons.DELAY_OPEN_TIME self.delay_open_timer = BGPTimer(self.delay_open_time_event, 'delay open timer') self.idle_hold_time = bgp_cons.IDLEHOLD_TIME self.idle_hold_timer = BGPTimer(self.idle_hold_time_event, 'idle hold timer') self.uptime = None def __setattr__(self, name, value): if name == 'state' and value != getattr(self, name): LOG.info("[%s]State is now:%s", self.bgp_peering.peer_addr, bgp_cons.stateDescr[value]) if value == bgp_cons.ST_ESTABLISHED: self.uptime = time.time() self.bgp_peering.handler.on_established(peer=self.bgp_peering.peer_addr, msg=self.uptime) super(FSM, self).__setattr__(name, value) def manual_start(self, idle_hold=False): """ Event 1: ManualStart Definiton: Should be called when a BGP ManualStart event is requested. Note that a protocol instance does not yet exist at this point, so this method requires some support from BGPPeering.manual_start(). Status: Mandatory """ LOG.info('Manual start.') self.allow_automatic_start = True if idle_hold: LOG.info('Idle Hold, please wait time=%s', self.idle_hold_time) self.idle_hold_timer.reset(self.idle_hold_time) return False else: LOG.info('Do not need Idle Hold, start right now.') self.connect_retry_timer.reset(self.connect_retry_time) LOG.info('Connect retry timer, time=%s', self.connect_retry_time) self.state = bgp_cons.ST_CONNECT return True def manual_stop(self): """ Event 2: ManualStop Definition: Should be called when a BGP ManualStop event is requested. Status: Mandatory """ LOG.info('Manual stop') if self.state != bgp_cons.ST_IDLE: self.protocol.send_notification(bgp_cons.ERR_CEASE, 0) # Stop all timers LOG.info('Stop all timers') for timer in (self.connect_retry_timer, self.hold_timer, self.keep_alive_timer, self.delay_open_timer, self.idle_hold_timer): if timer.status: timer.cancel() LOG.info('-- Stop timer %s', timer.name) self._close_connection() self.connect_retry_counter = 0 self.allow_automatic_start = False self.state = bgp_cons.ST_IDLE def automatic_start(self, idle_hold=False): """ Event 3: AutomaticStart Definition: Should be called when a BGP Automatic Start event is requested. Returns True or False to indicate BGPPeering whether a connection attempt should be initiated. Status: Optional :param idle_hold: BGP Idle Hold or not. """ LOG.info('Automatic start') if self.state in [bgp_cons.ST_IDLE, bgp_cons.ST_CONNECT]: if idle_hold: LOG.info('Idle Hold, please wait time=%s', self.idle_hold_time) self.idle_hold_timer.reset(self.idle_hold_time) return False elif self.allow_automatic_start: LOG.info('Do not need Idle Hold, start right now.') LOG.info('Connect retry counter: %s', self.connect_retry_counter) self.connect_retry_counter += 1 self.connect_retry_timer.reset(self.connect_retry_time) LOG.info('Connect retry timer, time=%s', self.connect_retry_time) self.state = bgp_cons.ST_CONNECT return True else: return False else: return False def connect_retry_time_event(self): """ Event 9: ConnectRetryTimer_Expires Definition: Called when the ConnectRetryTimer expires. Status: Mandatory """ LOG.info('Connect retry timer expires') if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, event 9 self._close_connection() LOG.info('Reset connect retry timer, time=%s', self.connect_retry_time) self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() LOG.info('Cancel delay open timer') # Initiate TCP connection if self.bgp_peering: LOG.info('Bgp peering connect retry') self.bgp_peering.connect_retry() elif self.state != bgp_cons.ST_IDLE: # State OpenSent, OpenConfirm, Established, event 12 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def hold_time_event(self): """ Event 10: HoldTimer_Expires Definition: Called when the HoldTimer expires. Action: NOTIFICATION message with the Hold Timer Expried Error Code is sent and the BGP connection is closed Status: Mandatory """ LOG.info('Hold Timer expires') if self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): # States OpenSent, OpenConfirm, Established, event 10 self.protocol.send_notification(bgp_cons.ERR_HOLD_TIMER_EXPIRED, 0) self.connect_retry_timer.cancel() self._error_close() self.state = bgp_cons.ST_IDLE elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): self._error_close() def keep_alive_time_event(self): """ Event 11: KeepaliveTimer_Expires Definition: Called when the KeepAliveTimer expires. Action: Send Keepalive messsage if the state of FSM is ST_OPENCONFIRM or ST_ESTABLISHED, close BGP connection if state is others Status: Mandatory """ LOG.info('Keep alive timer expires') if self.state in (bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): # State OpenConfirm, Established, event 11 self.protocol.send_keepalive() if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): self._error_close() def delay_open_time_event(self): """ Event 12: DelayOpenTimer_Expires Definition: Called when the DelayOpenTimer expires. Status: Optional """ LOG.info('Delay open timer expires') # DelayOpen attribute SHOULD be set to TRUE if self.state == bgp_cons.ST_CONNECT: # State Connect, event 12 self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT elif self.state == bgp_cons.ST_ACTIVE: # State Active, event 12 self.connect_retry_timer.cancel() self.delay_open_timer.cancel() self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT elif self.state != bgp_cons.ST_IDLE: # State OpenSent, OpenConfirm, Established, event 12 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def idle_hold_time_event(self): """ Event 13: IdleHoldTimer_Expires Definition: Called when the IdleHoldTimer expires. Status: Optional """ # DampPeerOscillations attribute SHOULD be set to TRUE # assert(self.dampPeerOscillations) LOG.info('Idle Hold Timer expires') if self.state == bgp_cons.ST_IDLE: if self.bgp_peering: self.bgp_peering.automatic_start(idle_hold=False) def connection_made(self): """ Event 16: Tcp_CR_Acked Event 17: TcpConnectionConfirmed Definition: Should be called when a TCP connection has successfully been established with the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, Event 16 or 17 if self.delay_open: self.connect_retry_timer.cancel() LOG.info('Delay open for this peer') self.delay_open_timer.reset(self.delay_open_time) else: self.connect_retry_timer.cancel() self.idle_hold_timer.cancel() self.protocol.send_open() self.hold_timer.reset(self.large_hold_time) self.state = bgp_cons.ST_OPENSENT def connection_failed(self): """ Event 18: TcpConnectionFails Definition: Should be called when the associated TCP connection failed, or was lost. Status: Mandatory """ if self.state == bgp_cons.ST_CONNECT: # State Connect, event 18 if self.delay_open_timer.active(): self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() self.state = bgp_cons.ST_ACTIVE else: self.connect_retry_timer.cancel() self._close_connection() if self.bgp_peering: self.state = bgp_cons.ST_IDLE self.bgp_peering.connection_closed(self.protocol) elif self.state == bgp_cons.ST_ACTIVE: # State Active, event 18 self.connect_retry_timer.reset(self.connect_retry_time) self.delay_open_timer.cancel() if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass # TODO: osc damping self.state = bgp_cons.ST_IDLE elif self.state == bgp_cons.ST_OPENSENT: # State OpenSent, event 18 if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass self._close_connection() self.connect_retry_timer.reset(self.connect_retry_time) self.state = bgp_cons.ST_ACTIVE if self.bgp_peering: self.bgp_peering.connection_closed(self.protocol) elif self.state in (bgp_cons.ST_OPENCONFIRM, bgp_cons.ST_ESTABLISHED): self._error_close() def open_received(self): """ Event 19: BGPOpen Event 20: BGPOpen with DelayOpenTimer running Definition: Should be called when a BGP Open message was received from the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): if self.delay_open_timer.active(): # State Connect, event 20 self.connect_retry_timer.cancel() self.delay_open_timer.cancel() self.connect_retry_counter = 0 self.protocol.send_open() self.protocol.send_keepalive() if self.hold_time: self.keep_alive_timer.reset(self.keep_alive_time) self.hold_timer.reset(self.hold_time) else: # holdTime == 0 self.keep_alive_timer.cancel() self.hold_timer.cancel() self.state = bgp_cons.ST_OPENCONFIRM else: # State Connect, event 19 self._error_close() elif self.state == bgp_cons.ST_OPENSENT: # State OpenSent, events 19, 20 self.delay_open_timer.cancel() self.connect_retry_timer.cancel() self.protocol.send_keepalive() if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) self.hold_timer.reset(self.hold_time) self.state = bgp_cons.ST_OPENCONFIRM elif self.state == bgp_cons.ST_OPENCONFIRM: # State OpenConfirm, events 19, 20 # TODO:Perform collision detection pass elif self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 19 or 20 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def header_error(self, suberror, data=b''): """ Event 21: BGPHeaderErr Definition: Should be called when an invalid BGP message header was received. Status: Mandatory :param suberror: bgp notification sub error code :param data: bgp notification error data """ self.protocol.send_notification(bgp_cons.ERR_MSG_HDR, suberror, data) # Note: RFC4271 states that we should send ERR_FSM in the # Established state, which contradicts earlier statements. self._error_close() def open_message_error(self, suberror, data=b''): """ Event 22: BGPOpenMsgErr Definition: Should be called when an invalid BGP Open message was received. Status: Mandatory :param suberror: bgp notification sub error code :param data: bgp notification error data """ self.protocol.send_notification(bgp_cons.ERR_MSG_OPEN, suberror, data) # Note: RFC4271 states that we should send ERR_FSM in the # Established state, which contradicts earlier statements. self._error_close() def notimsg_version_error(self): """ Event 24: NotifMsgVerErr Definition: Should be called when a BGP Notification Open Version Error message was received from the peer. Status: Mandatory """ if self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM): # State OpenSent, event 24 self.connect_retry_timer.cancel() self._close_connection() self.state = bgp_cons.ST_IDLE elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # State Connect, event 24 self._error_close() def notification_received(self, error, suberror): """ Event 25: NotifMsg Definition: Should be called when a BGP Notification message was received from the peer. Status: Mandatory :param error: bgp notification error code :param suberror: bgp notification sub error code """ if error == bgp_cons.ERR_MSG_OPEN and suberror == 1: # Event 24 : version error self.notimsg_version_error() else: if self.state != bgp_cons.ST_IDLE: # State != Idle, events 24, 25 self._error_close() def keep_alive_received(self): """ Event 26: KeepAliveMsg Definition: Should be called when a BGP KeepAlive packet was received from the peer. Status: Mandatory """ if self.state == bgp_cons.ST_OPENCONFIRM: # State OpenSent, event 26 self.hold_timer.reset(self.hold_time) self.state = bgp_cons.ST_ESTABLISHED elif self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 26 self.hold_timer.reset(self.hold_time) elif self.state in (bgp_cons.ST_CONNECT, bgp_cons.ST_ACTIVE): # States Connect, Active, event 26 self._error_close() def update_received(self): """ Event 27: UpdateMsg Definition: Called when a valid BGP Update message was received. Status: Mandatory """ if self.state == bgp_cons.ST_ESTABLISHED: # State Established, event 27 if self.hold_time != 0: self.hold_timer.reset(self.hold_time) elif self.state in (bgp_cons.ST_ACTIVE, bgp_cons.ST_CONNECT): # States Active, Connect, event 27 self._error_close() elif self.state in (bgp_cons.ST_OPENSENT, bgp_cons.ST_OPENCONFIRM): # States OpenSent, OpenConfirm, event 27 self.protocol.send_notification(bgp_cons.ERR_FSM, 0) self._error_close() def update_sent(self): """Called by the protocol instance when it just sent an Update message.""" if self.hold_time > 0: self.keep_alive_timer.reset(self.keep_alive_time) def _error_close(self): """Internal method that closes a connection and returns the state to IDLE. """ # Stop the timers for timer in (self.connect_retry_timer, self.delay_open_timer, self.hold_timer, self.keep_alive_timer): timer.cancel() self.idle_hold_timer.reset(self.idle_hold_time) # Release BGP resources (routes, etc) if self.bgp_peering: # self.bgp_peering.releaseResources(self.protocol) pass self._close_connection() self.connect_retry_counter += 1 self.state = self.bgp_peering.fsm.state = bgp_cons.ST_IDLE def _close_connection(self): """Internal method that close the connection if a valid BGP protocol instance exists. """ if self.protocol is not None: self.protocol.closeConnection() self.connect_retry_counter = 0 LOG.info('Closing protocol connection.')
class BGP(protocol.Protocol): """Protocol class for BGP 4""" def __init__(self): """Create a BGP protocol. """ self.fsm = None self.peer_id = None self.disconnected = False self.receive_buffer = '' self.process_queue_time = 0.5 self.process_queue_timer = BGPTimer(self.process_queue) self.start_process_queue = False self.update_msg_queue = [] # statistic self.msg_sent_stat = { 'Opens': 0, 'Notifications': 0, 'Updates': 0, 'Keepalives': 0, 'Route Refresh': 0 } self.msg_recv_stat = { 'Opens': 0, 'Notifications': 0, 'Updates': 0, 'Keepalives': 0, 'Route Refresh': 0 } def connectionMade(self): """ Starts the initial negotiation of the protocol """ # Set transport socket options self.transport.setTcpNoDelay(True) # set tcp option if you want # self.transport.getHandle().setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, md5sig) LOG.info("[%s]TCP Connection established", self.factory.peer_addr) # Set the local BGP id from the local IP address if it's not set if self.factory.bgp_id is None: try: self.factory.bgp_id = int(IPv4Address(self.transport.getHost().host)) except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) self.factory.bgp_id = int(IPv4Address('127.0.0.1')) try: self.fsm.connection_made() except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) def connectionLost(self, reason): """Called when the associated connection was lost. :param reason: the reason of lost connection. """ LOG.debug('Called connectionLost') # Don't do anything if we closed the connection explicitly ourselves if self.disconnected: self.factory.connection_closed(self) LOG.info('Connection lost return and do nothing') return LOG.info("[%s]Connection lost:%s", self.factory.peer_addr, reason.getErrorMessage()) try: # tell FSM that TCP connection is lost. self.fsm.connection_failed() except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) def dataReceived(self, data): """ Appends newly received data to the receive buffer, and then attempts to parse as many BGP messages as possible. :param data: the data received from TCP buffer. """ # Buffer possibly incomplete data first self.receive_buffer += data while self.parse_buffer(): pass # noinspection PyTypeChecker def process_queue(self): """ Process update message queue :return: """ try: self.process_queue_timer.cancel() except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) # if start process queue is True and the queue is not empty if self.start_process_queue and self.update_msg_queue: results = map(Update().parse, self.update_msg_queue) # check results for result in results: # if the result has error. if result['SubError']: # Get address family if result['NLRI'] or result['Withdraw']: afi_safi = (1, 1) else: afi_safi = (0, 0) # write message to file self.factory.write_msg( timestamp=result['Time'], msg_type=6, msg={ 'ATTR': result['Attributes'], 'NLRI': result['NLRI'], 'WITHDRAW': result['Withdraw'] }, afi_safi=afi_safi ) LOG.error('[%s] Update message error: sub error=%s', self.factory.peer_addr, result['SubError']) else: # no error # get address family if result['NLRI'] or result['Withdraw']: afi_safi = (1, 1) elif 14 in result['Attributes']: afi_safi = result['Attributes'][14]['afi_safi'] elif 15 in result['Attributes']: afi_safi = result['Attributes'][15]['afi_safi'] else: afi_safi = (0, 0) self.factory.write_msg( timestamp=result['Time'], msg_type=2, msg={ 'ATTR': result['Attributes'], 'NLRI': result['NLRI'], 'WITHDRAW': result['Withdraw'] }, afi_safi=afi_safi ) if self.factory.flush_and_check_file_size(): # send route fresh if open a new file self.send_route_refresh() self.update_msg_queue = [] self.process_queue_timer.reset(self.process_queue_time) # else the Queue is empty, just go on and reset the timer elif self.start_process_queue and not self.update_msg_queue: self.process_queue_timer.reset(self.process_queue_time) def parse_buffer(self): """ Parse TCP buffer data. :return: True or False """ buf = self.receive_buffer if len(buf) < bgp_cons.HDR_LEN: # Every BGP message is at least 19 octets. Maybe the rest # hasn't arrived yet. return False # Check whether the first 16 octets of the buffer consist of # the BGP marker (all bits one) if buf[:16] != chr(255) * 16: self.fsm.header_error(bgp_cons.ERR_MSG_HDR_CONN_NOT_SYNC) return False # Parse the BGP header try: marker, length, msg_type = struct.unpack('!16sHB', buf[:bgp_cons.HDR_LEN]) except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) self.fsm.header_error(bgp_cons.ERR_MSG_HDR_CONN_NOT_SYNC) return False # Check the length of the message, must be less than 4096, bigger than 19 if length < bgp_cons.HDR_LEN or length > bgp_cons.MAX_LEN: self.fsm.header_error(bgp_cons.ERR_MSG_HDR_BAD_MSG_LEN, struct.pack('!H', length)) # Check whether the entire message is already available if len(buf) < length: return False msg = buf[bgp_cons.HDR_LEN:length] t = time.time() # the time when received that packet. try: if msg_type == bgp_cons.MSG_OPEN: try: self.open_received(timestamp=t, msg=msg) except excep.MessageHeaderError as e: LOG.error(e) self.fsm.header_error(suberror=e.sub_error) return False except excep.OpenMessageError as e: LOG.error(e) self.fsm.open_message_error(suberror=e.sub_error) return False elif msg_type == bgp_cons.MSG_UPDATE: if not self.process_queue_timer.active(): self.process_queue_timer.reset(self.process_queue_time) self.start_process_queue = True # save message to the Queue self.update_msg_queue.append([ t, cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local']['four_bytes_as'], msg]) self.update_received() # Parse Update Message Queue if len(self.update_msg_queue) > 5000: self.process_queue() elif msg_type == bgp_cons.MSG_NOTIFICATION: self.notification_received(Notification().parse(msg)) elif msg_type == bgp_cons.MSG_KEEPALIVE: try: self.keepalive_received(timestamp=t, msg=msg) except excep.MessageHeaderError as e: LOG.error(e) self.fsm.header_error(suberror=e.sub_error) return False elif msg_type == bgp_cons.MSG_ROUTEREFRESH: route_refresh_msg = RouteRefresh().parse(msg) self.route_refresh_received(msg=route_refresh_msg) elif msg_type == bgp_cons.MSG_CISCOROUTEREFRESH: route_refresh_msg = RouteRefresh().parse(msg) self.route_refresh_received(msg=route_refresh_msg) else: # unknown message type self.fsm.header_error(bgp_cons.ERR_MSG_HDR_BAD_MSG_TYPE, struct.pack('!H', msg_type)) except Exception as e: LOG.error(e) error_str = traceback.format_exc() LOG.debug(error_str) self.receive_buffer = self.receive_buffer[length:] return True def closeConnection(self): """Close the connection""" if self.transport.connected: self.transport.loseConnection() self.disconnected = True def update_received(self): """Called when a BGP Update message was received.""" # LOG.debug('[%s] A Update message was received.' % self.factory.peer_addr) self.msg_recv_stat['Updates'] += 1 self.fsm.update_received() def send_update_file(self, update_record): """ send update message from the input file to peer :param update_record: message hex string """ while update_record: marker, length, msg_type = struct.unpack('!16sHB', update_record[:bgp_cons.HDR_LEN]) self.transport.write(update_record[:length]) update_record = update_record[length:] self.msg_sent_stat['Updates'] += 1 return True def send_notification(self, error, sub_error, data=''): """ send BGP notification message :param error: :param sub_error: :param data: :return: """ self.msg_sent_stat['Notifications'] += 1 LOG.info( "[%s]Send a BGP Notification message to the peer [Error: %s, Suberror: %s, Error data: %s ]", self.factory.peer_addr, error, sub_error, [ord(d) for d in data]) # message statistic self.msg_sent_stat['Notifications'] += 1 # construct message msg_notification = Notification().construct(error, sub_error, data) # send message self.transport.write(msg_notification) def notification_received(self, msg): """ BGP notification message received. """ self.msg_recv_stat['Notifications'] += 1 LOG.info( '[%s]Notification message received, error=%s, sub error=%s, data=%s', self.factory.peer_addr, msg[0], msg[1], msg[2]) nofi_msg = {'Error': msg[0], 'Suberror': msg[1], 'Error data': [ord(d) for d in msg[2]]} self.factory.write_msg( timestamp=time.time(), msg_type=3, msg=nofi_msg, afi_safi=(0, 0) ) self.fsm.notification_received(msg[0], msg[1]) LOG.debug('offline') def send_keepalive(self): """ send BGP keepalive message. """ self.msg_sent_stat['Keepalives'] += 1 LOG.info("[%s]Send a BGP KeepAlive message to the peer.", self.factory.peer_addr) # message statistci # self.msg_sent_stat['Keepalives'] += 1 # construct message msg_keepalive = KeepAlive().construct() # send message self.transport.write(msg_keepalive) def keepalive_received(self, timestamp, msg): """ process keepalive message :param timestamp: :param msg: :return: """ self.msg_recv_stat['Keepalives'] += 1 LOG.info("[%s]A BGP KeepAlive message was received from peer.", self.factory.peer_addr) KeepAlive().parse(msg) # write bgp message self.factory.write_msg( timestamp=timestamp, msg_type=4, msg=None, afi_safi=(0, 0), flush=True ) self.fsm.keep_alive_received() def capability_negotiate(self): """ Open message capability negotiation :return: """ # if received open message from remote peer firstly # then copy peer's capability to local according to the # local support. best effort support. if cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['remote']: for capability in cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local']: if capability not in cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['remote']: cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local'].pop(capability) def send_open(self): """ send open message :return: """ # construct Open message self.capability_negotiate() open_msg = Open(version=bgp_cons.VERSION, asn=self.factory.my_asn, hold_time=self.fsm.hold_time, bgp_id=self.factory.bgp_id).\ construct(cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local']) # send message self.transport.write(open_msg) self.msg_sent_stat['Opens'] += 1 LOG.info("[%s]Send a BGP Open message to the peer.", self.factory.peer_addr) LOG.info("[%s]Probe's Capabilities:", self.factory.peer_addr) for key in cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local']: LOG.info("--%s = %s", key, cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['local'][key]) def open_received(self, timestamp, msg): """ porcess open message :param timestamp: timestamp that received this message :param msg: binary raw message data :return: """ self.msg_recv_stat['Opens'] += 1 open_msg = Open() parse_result = open_msg.parse(msg) if self.fsm.bgp_peering.peer_asn != open_msg.asn: raise excep.OpenMessageError(sub_error=bgp_cons.ERR_MSG_OPEN_BAD_PEER_AS) # Open message Capabilities negotiation cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['remote'] = open_msg.capa_dict LOG.info("[%s]A BGP Open message was received", self.factory.peer_addr) LOG.info('--version = %s', open_msg.version) LOG.info('--ASN = %s', open_msg.asn) LOG.info('--hold time = %s', open_msg.hold_time) LOG.info('--id = %s', open_msg.bgp_id) LOG.info("[%s]Neighbor's Capabilities:", self.factory.peer_addr) for key in cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['remote']: LOG.info("--%s = %s", key, cfg.CONF.bgp.running_config[self.factory.peer_addr]['capability']['remote'][key]) # write bgp message self.factory.write_msg( timestamp=timestamp, msg_type=1, msg=parse_result, afi_safi=(0, 0), flush=True ) self.peer_id = open_msg.bgp_id self.bgp_peering.set_peer_id(open_msg.bgp_id) self.negotiate_hold_time(open_msg.hold_time) self.fsm.open_received() def send_route_refresh(self, afi=None, safi=None, res=None): """ Send bgp route refresh message :param afi: :param safi: :param res: """ LOG.info("[%s]Send BGP RouteRefresh message to the peer.", self.factory.peer_addr) if afi and safi and res is not None: if self.fsm.neighbor_capability["ciscoRouteRefresh"]: msg_routerefresh = RouteRefresh(afi, safi, res).construct(bgp_cons.MSG_CISCOROUTEREFRESH) self.transport.write(msg_routerefresh) self.msg_sent_stat['Route Refresh'] += 1 elif self.fsm.neighbor_capability["routeRefresh"]: msg_routerefresh = RouteRefresh(afi, safi, res).construct(bgp_cons.MSG_ROUTEREFRESH) # send message self.transport.write(msg_routerefresh) self.msg_sent_stat['Route Refresh'] += 1 return # construct message for (afi, safi) in self.fsm.neighbor_capability['AFI_SAFI']: if self.fsm.neighbor_capability["ciscoRouteRefresh"]: msg_routerefresh = RouteRefresh(afi, safi).construct(bgp_cons.MSG_CISCOROUTEREFRESH) self.transport.write(msg_routerefresh) self.msg_sent_stat['Route Refresh'] += 1 elif self.fsm.neighbor_capability["routeRefresh"]: msg_routerefresh = RouteRefresh(afi, safi).construct(bgp_cons.MSG_ROUTEREFRESH) # send message self.transport.write(msg_routerefresh) self.msg_sent_stat['Route Refresh'] += 1 def route_refresh_received(self, msg): """ Route Refresh message received. :param msg: msg content """ LOG.info( '[%s]Route Refresh message received, afi=%s, res=%s, safi=%s', self.factory.peer_addr, msg[0], msg[1], msg[2]) def negotiate_hold_time(self, hold_time): """Negotiates the hold time""" self.fsm.hold_time = min(self.fsm.hold_time, hold_time) if self.fsm.hold_time != 0 and self.fsm.hold_time < 3: self.fsm.open_message_error(bgp_cons.ERR_MSG_OPEN_UNACCPT_HOLD_TIME) # Derived times self.fsm.keep_alive_time = self.fsm.hold_time / 3 LOG.info( "[%s]Hold time:%s,Keepalive time:%s", self.factory.peer_addr, self.fsm.hold_time, self.fsm.keep_alive_time)