示例#1
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, '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
示例#2
0
文件: fsm.py 项目: c0ns0le/yabgp
    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
示例#3
0
文件: protocol.py 项目: jumeng/yabgp
    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
        }
示例#4
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 = {}
示例#5
0
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.')
示例#6
0
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.')
示例#7
0
文件: protocol.py 项目: jumeng/yabgp
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)