Skip to content
Snippets Groups Projects
fsm.go 6.71 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"
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
	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

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

	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) 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 == "cease" {
			return
		}

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

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 {
		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 recvMsg(c net.Conn) (msg []byte, err error) {
	buffer := make([]byte, packet.MaxLen)
	_, err = io.ReadFull(c, buffer[0:packet.MinLen])
	if err != nil {
		return nil, fmt.Errorf("Read failed: %v", err)
	}

	l := int(buffer[16])*256 + int(buffer[17])
	toRead := l
	_, err = io.ReadFull(c, buffer[packet.MinLen:toRead])
	if err != nil {
		return nil, fmt.Errorf("Read failed: %v", err)
	}

	return buffer, nil
}

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