Skip to content
Snippets Groups Projects
fsm2.go 5.37 KiB
Newer Older
Oliver Herms's avatar
Oliver Herms committed
package server

import (
	"fmt"
	"net"
Oliver Herms's avatar
Oliver Herms committed
	"sync"
Oliver Herms's avatar
Oliver Herms committed
	"time"

	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
	"github.com/bio-routing/bio-rd/routingtable"
Oliver Herms's avatar
Oliver Herms committed
	log "github.com/sirupsen/logrus"
)

const (
	// Administrative events
	ManualStart                               = 1
	ManualStop                                = 2
	AutomaticStart                            = 3
	ManualStartWithPassiveTcpEstablishment    = 4
	AutomaticStartWithPassiveTcpEstablishment = 5
	AutomaticStop                             = 8
	Cease                                     = 100
Oliver Herms's avatar
Oliver Herms committed
)

type state interface {
	run() (state, string)
}

// FSM2 implements the BGP finite state machine (RFC4271)
type FSM2 struct {
Oliver Herms's avatar
Oliver Herms committed
	peer        *Peer
	eventCh     chan int
	con         net.Conn
	conCh       chan net.Conn
	initiateCon chan struct{}
	conErrCh    chan error
Oliver Herms's avatar
Oliver Herms committed

	delayOpen      bool
	delayOpenTime  time.Duration
	delayOpenTimer *time.Timer

	connectRetryTime    time.Duration
	connectRetryTimer   *time.Timer
	connectRetryCounter int

Oliver Herms's avatar
Oliver Herms committed
	holdTime  time.Duration
	holdTimer *time.Timer
Oliver Herms's avatar
Oliver Herms committed

	keepaliveTime  time.Duration
	keepaliveTimer *time.Timer

Oliver Herms's avatar
Oliver Herms committed
	msgRecvCh     chan []byte
	msgRecvFailCh chan error
Oliver Herms's avatar
Oliver Herms committed
	stopMsgRecvCh chan struct{}

	capAddPathSend bool
	capAddPathRecv bool

Oliver Herms's avatar
Oliver Herms committed
	local net.IP
	//remote net.IP
Oliver Herms's avatar
Oliver Herms committed

	ribsInitialized bool
Oliver Herms's avatar
Oliver Herms committed
	adjRIBIn        routingtable.RouteTableClient
Oliver Herms's avatar
Oliver Herms committed
	adjRIBOut       routingtable.RouteTableClient
Oliver Herms's avatar
Oliver Herms committed
	rib             routingtable.RouteTableClient
Oliver Herms's avatar
Oliver Herms committed
	updateSender    routingtable.RouteTableClient

	neighborID uint32
	state      state
Oliver Herms's avatar
Oliver Herms committed
	stateMu    sync.RWMutex
Oliver Herms's avatar
Oliver Herms committed
	reason     string
	active     bool
}

// NewPassiveFSM2 initiates a new passive FSM
func NewPassiveFSM2(peer *Peer, con *net.TCPConn) *FSM2 {
Oliver Herms's avatar
Oliver Herms committed
	fsm := newFSM2(peer)
	fsm.con = con
	fsm.state = newIdleState(fsm)
Oliver Herms's avatar
Oliver Herms committed
	return fsm
}

// NewActiveFSM2 initiates a new passive FSM
func NewActiveFSM2(peer *Peer) *FSM2 {
Oliver Herms's avatar
Oliver Herms committed
	fsm := newFSM2(peer)
	fsm.active = true
	fsm.state = newIdleState(fsm)
	return fsm
}

func newFSM2(peer *Peer) *FSM2 {
Oliver Herms's avatar
Oliver Herms committed
	return &FSM2{
Oliver Herms's avatar
Oliver Herms committed
		connectRetryTime: time.Minute,
		peer:             peer,
		eventCh:          make(chan int),
		conCh:            make(chan net.Conn),
		conErrCh:         make(chan error),
		initiateCon:      make(chan struct{}),
		msgRecvCh:        make(chan []byte),
		msgRecvFailCh:    make(chan error),
		stopMsgRecvCh:    make(chan struct{}),
		rib:              peer.rib,
	}
}

func (fsm *FSM2) start() {
	go fsm.run()
	go fsm.tcpConnector()
	return
}

func (fsm *FSM2) activate() {
	fsm.eventCh <- AutomaticStart
}

func (fsm *FSM2) run() {
	next, reason := fsm.state.run()
	for {
		newState := stateName(next)
		oldState := stateName(fsm.state)

		if oldState != newState {
			log.WithFields(log.Fields{
				"peer":       fsm.peer.addr.String(),
				"last_state": oldState,
				"new_state":  newState,
				"reason":     reason,
			}).Info("FSM: Neighbor state change")
		}

		if newState == "cease" {
			return
		}

		fsm.stateMu.Lock()
		fsm.state = next
		fsm.stateMu.Unlock()

		next, reason = fsm.state.run()
Oliver Herms's avatar
Oliver Herms committed
	}
}

Oliver Herms's avatar
Oliver Herms committed
func stateName(s state) string {
	switch s.(type) {
	case *idleState:
		return "idle"
	case *connectState:
		return "connect"
	case *activeState:
		return "active"
	case *openSentState:
		return "openSent"
	case *openConfirmState:
		return "openConfirm"
	case *establishedState:
		return "established"
	case *ceaseState:
		return "cease"
	default:
		panic(fmt.Sprintf("Unknown state: %v", s))
	}
}

func (fsm *FSM2) cease() {
	fsm.eventCh <- Cease
}

func (fsm *FSM2) tcpConnector() error {
	for {
		select {
		case <-fsm.initiateCon:
			c, err := net.DialTCP("tcp", &net.TCPAddr{IP: fsm.local}, &net.TCPAddr{IP: fsm.peer.addr, Port: BGPPORT})
			if err != nil {
				select {
				case fsm.conErrCh <- err:
					continue
				case <-time.NewTimer(time.Second * 30).C:
					continue
				}
			}

			select {
			case fsm.conCh <- c:
				continue
			case <-time.NewTimer(time.Second * 30).C:
				c.Close()
				continue
			}
		}
	}
}
Oliver Herms's avatar
Oliver Herms committed

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM2) tcpConnect() {
	fsm.initiateCon <- struct{}{}
}

func (fsm *FSM2) msgReceiver() error {
	for {
		msg, err := recvMsg(fsm.con)
		if err != nil {
			fsm.msgRecvFailCh <- err
			return nil
		}
		fsm.msgRecvCh <- msg
	}
Oliver Herms's avatar
Oliver Herms committed
}

func (fsm *FSM2) startConnectRetryTimer() {
Oliver Herms's avatar
Oliver Herms committed
	fsm.connectRetryTimer = time.NewTimer(fsm.connectRetryTime)
Oliver Herms's avatar
Oliver Herms committed
}

func (fsm *FSM2) resetConnectRetryTimer() {
Oliver Herms's avatar
Oliver Herms committed
	if !fsm.connectRetryTimer.Reset(fsm.connectRetryTime) {
Oliver Herms's avatar
Oliver Herms committed
		<-fsm.connectRetryTimer.C
	}
}

func (fsm *FSM2) resetConnectRetryCounter() {
	fsm.connectRetryCounter = 0
}

func (fsm *FSM2) sendOpen() error {
	msg := packet.SerializeOpenMsg(&packet.BGPOpen{
		Version:       BGPVersion,
Oliver Herms's avatar
Oliver Herms committed
		AS:            uint16(fsm.peer.localASN),
		HoldTime:      uint16(fsm.peer.holdTime / time.Second),
		BGPIdentifier: fsm.peer.server.routerID,
Oliver Herms's avatar
Oliver Herms committed
		OptParams:     fsm.peer.optOpenParams,
	})

	_, err := fsm.con.Write(msg)
	if err != nil {
		return fmt.Errorf("Unable to send OPEN message: %v", err)
	}

	return nil
}

func (fsm *FSM2) sendNotification(errorCode uint8, errorSubCode uint8) error {
	msg := packet.SerializeNotificationMsg(&packet.BGPNotification{})

	_, err := fsm.con.Write(msg)
	if err != nil {
		return fmt.Errorf("Unable to send NOTIFICATION message: %v", err)
	}

	return nil
}

func (fsm *FSM2) sendKeepalive() error {
	msg := packet.SerializeKeepaliveMsg()

	_, err := fsm.con.Write(msg)
	if err != nil {
		return fmt.Errorf("Unable to send KEEPALIVE message: %v", err)
	}

	return nil
}

func stopTimer(t *time.Timer) {
	if !t.Stop() {
		select {
		case <-t.C:
		default:
		}
	}
}