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

import (
Oliver Herms's avatar
Oliver Herms committed
	"fmt"
Oliver Herms's avatar
Oliver Herms committed
	"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"
Julian Kornberger's avatar
Julian Kornberger committed
	"github.com/pkg/errors"
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
	stateNameIdle                             = "idle"
	stateNameConnect                          = "connect"
	stateNameActive                           = "active"
	stateNameOpenSent                         = "openSent"
	stateNameOpenConfirm                      = "openConfirm"
	stateNameEstablished                      = "established"
	stateNameCease                            = "cease"
Oliver Herms's avatar
Oliver Herms committed
)

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

Oliver Herms's avatar
Oliver Herms committed
// FSM implements the BGP finite state machine (RFC4271)
type FSM struct {
	counters fsmCounters

	isBMP       bool
Oliver Herms's avatar
Oliver Herms committed
	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

	holdTime              time.Duration
	lastUpdateOrKeepalive time.Time
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{}

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

	ribsInitialized bool
	ipv4Unicast     *fsmAddressFamily
	ipv6Unicast     *fsmAddressFamily
Oliver Herms's avatar
Oliver Herms committed

Oliver Herms's avatar
Oliver Herms committed
	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
	establishedTime time.Time

	connectionCancelFunc context.CancelFunc
Oliver Herms's avatar
Oliver Herms committed
}

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

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

func newFSM(peer *peer) *FSM {
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{}),
		counters:         fsmCounters{},
Oliver Herms's avatar
Oliver Herms committed
	}

	if peer.ipv4 != nil {
		f.ipv4Unicast = newFSMAddressFamily(packet.IPv4AFI, packet.UnicastSAFI, peer.ipv4, f)
	}

	if peer.ipv6 != nil {
		f.ipv6Unicast = newFSMAddressFamily(packet.IPv6AFI, packet.UnicastSAFI, peer.ipv6, f)
Oliver Herms's avatar
Oliver Herms committed
}

func (fsm *FSM) updateLastUpdateOrKeepalive() {
	fsm.lastUpdateOrKeepalive = time.Now()
}

func (fsm *FSM) addressFamily(afi uint16, safi uint8) *fsmAddressFamily {
	if safi != packet.UnicastSAFI {
		return nil
	}

	switch afi {
	case packet.IPv4AFI:
		return fsm.ipv4Unicast
	case packet.IPv6AFI:
		return fsm.ipv6Unicast
	default:
		return nil
	}
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) start() {
	ctx, cancel := context.WithCancel(context.Background())
	fsm.connectionCancelFunc = cancel

Oliver Herms's avatar
Oliver Herms committed
	go fsm.run()
Oliver Herms's avatar
Oliver Herms committed
	return
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) activate() {
Oliver Herms's avatar
Oliver Herms committed
	fsm.eventCh <- AutomaticStart
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) run() {
	defer fsm.cancelRunningGoRoutines()

Oliver Herms's avatar
Oliver Herms committed
	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 == stateNameCease {
Oliver Herms's avatar
Oliver Herms committed
			return
		}

		if oldState != newState && newState == stateNameEstablished {
			fsm.establishedTime = time.Now()
		}

Oliver Herms's avatar
Oliver Herms committed
		fsm.stateMu.Lock()
		fsm.state = next
		fsm.stateMu.Unlock()

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

func (fsm *FSM) cancelRunningGoRoutines() {
	if fsm.connectionCancelFunc != nil {
		fsm.connectionCancelFunc()
	}
}

Oliver Herms's avatar
Oliver Herms committed
func stateName(s state) string {
	switch s.(type) {
	case *idleState:
		return stateNameIdle
Oliver Herms's avatar
Oliver Herms committed
	case *connectState:
		return stateNameConnect
Oliver Herms's avatar
Oliver Herms committed
	case *activeState:
		return stateNameActive
Oliver Herms's avatar
Oliver Herms committed
	case *openSentState:
		return stateNameOpenSent
Oliver Herms's avatar
Oliver Herms committed
	case *openConfirmState:
		return stateNameOpenConfirm
Oliver Herms's avatar
Oliver Herms committed
	case *establishedState:
		return stateNameEstablished
Oliver Herms's avatar
Oliver Herms committed
	case *ceaseState:
		return stateNameCease
Oliver Herms's avatar
Oliver Herms committed
	default:
		panic(fmt.Sprintf("Unknown state: %v", s))
	}
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) cease() {
Oliver Herms's avatar
Oliver Herms committed
	fsm.eventCh <- Cease
}

func (fsm *FSM) tcpConnector(ctx context.Context) {
Oliver Herms's avatar
Oliver Herms committed
	for {
		select {
		case <-fsm.initiateCon:
			c, err := net.DialTCP("tcp", &net.TCPAddr{IP: fsm.local}, &net.TCPAddr{IP: fsm.peer.addr.ToNetIP(), Port: BGPPORT})
Oliver Herms's avatar
Oliver Herms committed
			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 *FSM) tcpConnect() {
Oliver Herms's avatar
Oliver Herms committed
	fsm.initiateCon <- struct{}{}
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) msgReceiver() error {
Oliver Herms's avatar
Oliver Herms committed
	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 *FSM) decodeOptions() *packet.DecodeOptions {
	return &packet.DecodeOptions{
		Use32BitASN: fsm.supports4OctetASN,
	}
}

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

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

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) resetConnectRetryCounter() {
Oliver Herms's avatar
Oliver Herms committed
	fsm.connectRetryCounter = 0
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) sendOpen() error {
	msg := packet.SerializeOpenMsg(fsm.openMessage())
Oliver Herms's avatar
Oliver Herms committed

	_, err := fsm.con.Write(msg)
	if err != nil {
Julian Kornberger's avatar
Julian Kornberger committed
		return errors.Wrap(err, "Unable to send OPEN message")
Oliver Herms's avatar
Oliver Herms committed
	}

	return nil
}

func (fsm *FSM) openMessage() *packet.BGPOpen {
	return &packet.BGPOpen{
		Version:       BGPVersion,
		ASN:           fsm.local16BitASN(),
		HoldTime:      uint16(fsm.peer.holdTime / time.Second),
		BGPIdentifier: fsm.peer.routerID,
		OptParams:     fsm.peer.optOpenParams,
	}
}

func (fsm *FSM) local16BitASN() uint16 {
	if fsm.peer.localASN > uint32(^uint16(0)) {
		return packet.ASTransASN
	}

	return uint16(fsm.peer.localASN)
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) sendNotification(errorCode uint8, errorSubCode uint8) error {
Oliver Herms's avatar
Oliver Herms committed
	msg := packet.SerializeNotificationMsg(&packet.BGPNotification{})

	_, err := fsm.con.Write(msg)
	if err != nil {
Julian Kornberger's avatar
Julian Kornberger committed
		return errors.Wrap(err, "Unable to send NOTIFICATION message")
Oliver Herms's avatar
Oliver Herms committed
	}

	return nil
}

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) sendKeepalive() error {
Oliver Herms's avatar
Oliver Herms committed
	msg := packet.SerializeKeepaliveMsg()

	_, err := fsm.con.Write(msg)
	if err != nil {
Julian Kornberger's avatar
Julian Kornberger committed
		return errors.Wrap(err, "Unable to send KEEPALIVE message")
Oliver Herms's avatar
Oliver Herms committed
	}

	return nil
}

func recvMsg(c net.Conn) (msg []byte, err error) {
	buffer := make([]byte, packet.MaxLen)
	_, err = io.ReadFull(c, buffer[0:packet.MinLen])
	if err != nil {
Julian Kornberger's avatar
Julian Kornberger committed
		return nil, errors.Wrap(err, "Read failed")
	}

	l := int(buffer[16])*256 + int(buffer[17])
	toRead := l
	_, err = io.ReadFull(c, buffer[packet.MinLen:toRead])
	if err != nil {
Julian Kornberger's avatar
Julian Kornberger committed
		return nil, errors.Wrap(err, "Read failed")
Oliver Herms's avatar
Oliver Herms committed
func stopTimer(t *time.Timer) {
	if t == nil {
		return
	}

Oliver Herms's avatar
Oliver Herms committed
	if !t.Stop() {
		select {
		case <-t.C:
		default:
		}
	}
}