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

import (
	"errors"
	"io"
Oliver Herms's avatar
Oliver Herms committed

	"github.com/bio-routing/bio-rd/protocols/bgp/packet"
Oliver Herms's avatar
Oliver Herms committed
	"github.com/bio-routing/bio-rd/route"
	"github.com/bio-routing/bio-rd/routingtable"
Oliver Herms's avatar
Oliver Herms committed

	bnet "github.com/bio-routing/bio-rd/net"
	log "github.com/sirupsen/logrus"
Oliver Herms's avatar
Oliver Herms committed
// UpdateSender converts table changes into BGP update messages
Oliver Herms's avatar
Oliver Herms committed
type UpdateSender struct {
Oliver Herms's avatar
Oliver Herms committed
	routingtable.ClientManager
	afi       uint16
	safi      uint8
	options   *packet.EncodeOptions
	toSendMu  sync.Mutex
	toSend    map[string]*pathPfxs
	destroyCh chan struct{}
}

type pathPfxs struct {
	path *route.Path
	pfxs []bnet.Prefix
}

func newUpdateSender(fsm *FSM, afi uint16, safi uint8) *UpdateSender {
	f := fsm.addressFamily(afi, safi)

Oliver Herms's avatar
Oliver Herms committed
	return &UpdateSender{
		afi:       afi,
		safi:      safi,
		iBGP:      fsm.peer.localASN == fsm.peer.peerASN,
		rrClient:  fsm.peer.routeReflectorClient,
		toSend:    make(map[string]*pathPfxs),
		options: &packet.EncodeOptions{
			Use32BitASN: fsm.supports4OctetASN,
			UseAddPath:  f.addPathRX,
		},
Oliver Herms's avatar
Oliver Herms committed
func (u *UpdateSender) Start(aggrTime time.Duration) {
	go u.sender(aggrTime)
}

// Destroy destroys everything (with greetings to Hatebreed)
func (u *UpdateSender) Destroy() {
	u.destroyCh <- struct{}{}
}

// AddPath adds path p for pfx to toSend queue
func (u *UpdateSender) AddPath(pfx bnet.Prefix, p *route.Path) error {
	u.toSendMu.Lock()

	hash := p.BGPPath.ComputeHash()
	if _, exists := u.toSend[hash]; exists {
		u.toSend[hash].pfxs = append(u.toSend[hash].pfxs, pfx)
		u.toSendMu.Unlock()
		return nil
	u.toSend[p.BGPPath.ComputeHash()] = &pathPfxs{
		path: p,
		pfxs: []bnet.Prefix{
			pfx,
	u.toSendMu.Unlock()
	return nil
}

// sender serializes BGP update messages
Oliver Herms's avatar
Oliver Herms committed
func (u *UpdateSender) sender(aggrTime time.Duration) {
	ticker := time.NewTicker(aggrTime)
	var err error
	var pathAttrs *packet.PathAttribute
	var budget int

	for {
		select {
		case <-u.destroyCh:
			return
		case <-ticker.C:
		}

		u.toSendMu.Lock()

Daniel Czerwonk's avatar
Daniel Czerwonk committed
		overhead := u.updateOverhead()

		for key, pathNLRIs := range u.toSend {
Daniel Czerwonk's avatar
Daniel Czerwonk committed
			budget = packet.MaxLen - packet.HeaderLen - packet.MinUpdateLen - int(pathNLRIs.path.BGPPath.Length()) - overhead
Daniel Czerwonk's avatar
Daniel Czerwonk committed

			pathAttrs, err = packet.PathAttributes(pathNLRIs.path, u.iBGP, u.rrClient)
			if err != nil {
				log.Errorf("Unable to get path attributes: %v", err)
				continue
			}

Oliver Herms's avatar
Oliver Herms committed
			updatesPrefixes := make([][]bnet.Prefix, 0, 1)
			prefixes := make([]bnet.Prefix, 0, 1)
Oliver Herms's avatar
Oliver Herms committed
				budget -= int(packet.BytesInAddr(pfx.Pfxlen())) + 1
				if u.options.UseAddPath {
					budget -= 4
				}

				if budget < 0 {
					updatesPrefixes = append(updatesPrefixes, prefixes)
Oliver Herms's avatar
Oliver Herms committed
					prefixes = make([]bnet.Prefix, 0, 1)
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					budget = packet.MaxLen - int(pathNLRIs.path.BGPPath.Length()) - overhead
Oliver Herms's avatar
Oliver Herms committed
			if len(prefixes) > 0 {
				updatesPrefixes = append(updatesPrefixes, prefixes)
			}

			delete(u.toSend, key)
			u.toSendMu.Unlock()

			u.sendUpdates(pathAttrs, updatesPrefixes, pathNLRIs.path.BGPPath.PathIdentifier)
			u.toSendMu.Lock()
		}
Oliver Herms's avatar
Oliver Herms committed
		u.toSendMu.Unlock()
Daniel Czerwonk's avatar
Daniel Czerwonk committed
func (u *UpdateSender) updateOverhead() int {
	addrLen := packet.IPv4AFI
	if u.afi == packet.IPv6AFI {
		addrLen = packet.IPv6Len
	}

	// since we are replacing the next hop attribute IPv4Len has to be subtracted, we also add another byte for extended length
	return packet.AFILen + packet.SAFILen + 1 + addrLen - packet.IPv4Len + 1
func (u *UpdateSender) sendUpdates(pathAttrs *packet.PathAttribute, updatePrefixes [][]bnet.Prefix, pathID uint32) {
	var err error
Daniel Czerwonk's avatar
Daniel Czerwonk committed
	for _, prefixes := range updatePrefixes {
		update := u.updateMessageForPrefixes(prefixes, pathAttrs, pathID)
		if update == nil {
			log.Errorf("Failed to create update: Neighbor does not support multi protocol.")
			return
		}
		err = serializeAndSendUpdate(u.fsm.con, update, u.options)
Daniel Czerwonk's avatar
Daniel Czerwonk committed
		if err != nil {
			log.Errorf("Failed to serialize and send: %v", err)
Daniel Czerwonk's avatar
Daniel Czerwonk committed
func (u *UpdateSender) updateMessageForPrefixes(pfxs []bnet.Prefix, pa *packet.PathAttribute, pathID uint32) *packet.BGPUpdate {
	if u.afi == packet.IPv4AFI && u.safi == packet.UnicastSAFI {
		return u.bgpUpdate(pfxs, pa, pathID)
	}

Daniel Czerwonk's avatar
Daniel Czerwonk committed
		return u.bgpUpdateMultiProtocol(pfxs, pa, pathID)
	}

Daniel Czerwonk's avatar
Daniel Czerwonk committed
}

func (u *UpdateSender) bgpUpdate(pfxs []bnet.Prefix, pa *packet.PathAttribute, pathID uint32) *packet.BGPUpdate {
	update := &packet.BGPUpdate{
		PathAttributes: pa,
	}

	var nlri *packet.NLRI
	for _, pfx := range pfxs {
		nlri = &packet.NLRI{
			PathIdentifier: pathID,
			IP:             pfx.Addr().ToUint32(),
			Pfxlen:         pfx.Pfxlen(),
			Next:           update.NLRI,
Daniel Czerwonk's avatar
Daniel Czerwonk committed
		update.NLRI = nlri
	}
Daniel Czerwonk's avatar
Daniel Czerwonk committed
	return update
}

func (u *UpdateSender) bgpUpdateMultiProtocol(pfxs []bnet.Prefix, pa *packet.PathAttribute, pathID uint32) *packet.BGPUpdate {
	pa, nextHop := u.copyAttributesWithoutNextHop(pa)

	attrs := &packet.PathAttribute{
		TypeCode: packet.MultiProtocolReachNLRICode,
		Value: packet.MultiProtocolReachNLRI{
			AFI:      u.afi,
			SAFI:     u.safi,
Daniel Czerwonk's avatar
Daniel Czerwonk committed
			NextHop:  nextHop,
			Prefixes: pfxs,
			PathID:   pathID,
Daniel Czerwonk's avatar
Daniel Czerwonk committed
		},
	}
	attrs.Next = pa

	return &packet.BGPUpdate{
		PathAttributes: attrs,
	}
}

func (u *UpdateSender) copyAttributesWithoutNextHop(pa *packet.PathAttribute) (attrs *packet.PathAttribute, nextHop bnet.IP) {
	var curCopy, lastCopy *packet.PathAttribute
Daniel Czerwonk's avatar
Daniel Czerwonk committed
	for cur := pa; cur != nil; cur = cur.Next {
Daniel Czerwonk's avatar
Daniel Czerwonk committed
		if cur.TypeCode == packet.NextHopAttr {
			nextHop = cur.Value.(bnet.IP)
		} else {
			curCopy = cur.Copy()

			if lastCopy == nil {
				attrs = curCopy
			} else {
				lastCopy.Next = curCopy
			}
			lastCopy = curCopy
Daniel Czerwonk's avatar
Daniel Czerwonk committed

	return attrs, nextHop
Oliver Herms's avatar
Oliver Herms committed
// RemovePath withdraws prefix `pfx` from a peer
func (u *UpdateSender) RemovePath(pfx bnet.Prefix, p *route.Path) bool {
	err := u.withdrawPrefix(pfx, p)
Oliver Herms's avatar
Oliver Herms committed
	if err != nil {
		log.Errorf("Unable to withdraw prefix: %v", err)
		return false
	}
	return true
func (u *UpdateSender) withdrawPrefix(pfx bnet.Prefix, p *route.Path) error {
		return u.withDrawPrefixesMultiProtocol(u.fsm.con, pfx, p)
	}

	return u.withDrawPrefixesAddPath(u.fsm.con, pfx, p)
}

// withDrawPrefixes generates a BGPUpdate message and write it to the given
// io.Writer.
func (u *UpdateSender) withDrawPrefixes(out io.Writer, prefixes ...bnet.Prefix) error {
	if len(prefixes) < 1 {
		return nil
	}

	var rootNLRI *packet.NLRI
	var currentNLRI *packet.NLRI
	for _, pfx := range prefixes {
		if rootNLRI == nil {
			rootNLRI = &packet.NLRI{
				IP:     pfx.Addr().ToUint32(),
				Pfxlen: pfx.Pfxlen(),
			}
			currentNLRI = rootNLRI
		} else {
			currentNLRI.Next = &packet.NLRI{
				IP:     pfx.Addr().ToUint32(),
				Pfxlen: pfx.Pfxlen(),
			}
			currentNLRI = currentNLRI.Next
		}
	}

	update := &packet.BGPUpdate{
		WithdrawnRoutes: rootNLRI,
	}

	return serializeAndSendUpdate(out, update, u.options)

}

// withDrawPrefixesAddPath generates a BGPUpdateAddPath message and write it to the given
// io.Writer.
func (u *UpdateSender) withDrawPrefixesAddPath(out io.Writer, pfx bnet.Prefix, p *route.Path) error {
	if p.Type != route.BGPPathType {
		return errors.New("wrong path type, expected BGPPathType")
	}

	if p.BGPPath == nil {
		return errors.New("got nil BGPPath")
	}
	update := &packet.BGPUpdate{
		WithdrawnRoutes: &packet.NLRI{
			PathIdentifier: p.BGPPath.PathIdentifier,
			IP:             pfx.Addr().ToUint32(),
			Pfxlen:         pfx.Pfxlen(),
		},
	}

	return serializeAndSendUpdate(out, update, u.options)
}

func (u *UpdateSender) withDrawPrefixesMultiProtocol(out io.Writer, pfx bnet.Prefix, p *route.Path) error {
	pathID := uint32(0)
	if p.BGPPath != nil {
		pathID = p.BGPPath.PathIdentifier
	}

	update := &packet.BGPUpdate{
		PathAttributes: &packet.PathAttribute{
			TypeCode: packet.MultiProtocolUnreachNLRICode,
			Value: packet.MultiProtocolUnreachNLRI{
				AFI:      u.afi,
				SAFI:     u.safi,
				Prefixes: []bnet.Prefix{pfx},
				PathID:   pathID,
			},
		},
	return serializeAndSendUpdate(out, update, u.options)
Oliver Herms's avatar
Oliver Herms committed
// UpdateNewClient does nothing
Oliver Herms's avatar
Oliver Herms committed
func (u *UpdateSender) UpdateNewClient(client routingtable.RouteTableClient) error {
	log.Warningf("BGP Update Sender: UpdateNewClient not implemented")
Oliver Herms's avatar
Oliver Herms committed
	return nil
// RouteCount returns the number of stored routes
func (u *UpdateSender) RouteCount() int64 {
	log.Warningf("BGP Update Sender: RouteCount not implemented")