Skip to content
Snippets Groups Projects
fsm.go 5.66 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"
Oliver Herms's avatar
Oliver Herms committed
	"github.com/bio-routing/bio-rd/protocols/bgp/types"
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)
}

Oliver Herms's avatar
Oliver Herms committed
// FSM implements the BGP finite state machine (RFC4271)
type FSM struct {
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

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{}

Oliver Herms's avatar
Oliver Herms committed
	options *types.Options
Oliver Herms's avatar
Oliver Herms committed
	local net.IP
Oliver Herms's avatar
Oliver Herms committed

	ribsInitialized bool
	ipv4Unicast     *familyRouting
	ipv6Unicast     *familyRouting
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
}

// NewPassiveFSM2 initiates a new passive FSM
func NewPassiveFSM2(peer *peer, con *net.TCPConn) *FSM {
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) *FSM {
Oliver Herms's avatar
Oliver Herms committed
	fsm := newFSM2(peer)
	fsm.active = true
	fsm.state = newIdleState(fsm)
	return fsm
}

func newFSM2(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{}),
Oliver Herms's avatar
Oliver Herms committed
		options:          &types.Options{},
Oliver Herms's avatar
Oliver Herms committed
	}

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

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

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) start() {
Oliver Herms's avatar
Oliver Herms committed
	go fsm.run()
	go fsm.tcpConnector()
	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() {
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 == "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))
	}
}

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

Oliver Herms's avatar
Oliver Herms committed
func (fsm *FSM) tcpConnector() error {
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
}

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 {
		return fmt.Errorf("Unable to send OPEN message: %v", err)
	}

	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 {
		return fmt.Errorf("Unable to send NOTIFICATION message: %v", err)
	}

	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 {
		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:
		}
	}
}