Skip to content
Snippets Groups Projects
fsm.go 6.71 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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:
    		}
    	}
    }