Skip to content
Snippets Groups Projects
netlink_reader.go 6.01 KiB
Newer Older
cedi's avatar
cedi committed
package protocolnetlink
cedi's avatar
cedi committed

import (
	"fmt"
	"sync"
	"time"

	"github.com/bio-routing/bio-rd/config"
	bnet "github.com/bio-routing/bio-rd/net"
	"github.com/bio-routing/bio-rd/route"
	"github.com/bio-routing/bio-rd/routingtable"
	"github.com/bio-routing/bio-rd/routingtable/filter"
	log "github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
)

// Constants for IP family
const (
cedi's avatar
cedi committed
	IPFamily4 int = 4 // IPv4
	IPFamily6 int = 6 // IPv6
cedi's avatar
cedi committed
// NetlinkReader read routes from the Linux Kernel and propagates it to the locRIB
cedi's avatar
cedi committed
type NetlinkReader struct {
	options *config.Netlink
	routingtable.ClientManager
	filter *filter.Filter

	mu     sync.RWMutex
	routes []netlink.Route
}

cedi's avatar
cedi committed
// NewNetlinkReader creates a new reader object and returns the pointer to it
cedi's avatar
cedi committed
func NewNetlinkReader(options *config.Netlink) *NetlinkReader {
	nr := &NetlinkReader{
		options: options,
		filter:  options.ImportFilter,
	}

	nr.ClientManager = routingtable.NewClientManager(nr)

	return nr
}

cedi's avatar
cedi committed
// Read reads routes from the kernel
cedi's avatar
cedi committed
func (nr *NetlinkReader) Read() {
	// Start fetching the kernel routes after the hold time
	time.Sleep(nr.options.HoldTime)

	for {
		// Family doesn't matter. I only filter by the rt_table here
cedi's avatar
cedi committed
		routes, err := netlink.RouteListFiltered(IPFamily4, &netlink.Route{Table: int(nr.options.RoutingTable)}, netlink.RT_FILTER_TABLE)
cedi's avatar
cedi committed
		if err != nil {
			log.WithError(err).Panic("Failed to read routes from kernel")
		}

		nr.propagateChanges(routes)

		nr.mu.Lock()
		nr.routes = routes

		log.Debugf("NetlinkRouteDiff: %d", len(route.NetlinkRouteDiff(nr.routes, routes)))
		nr.mu.Unlock()

		time.Sleep(nr.options.UpdateInterval)
	}
}

// propagate changes to all subscribed clients
func (nr *NetlinkReader) propagateChanges(routes []netlink.Route) {
	nr.removePathsFromClients(routes)
	nr.addPathsToClients(routes)
}

// Add given paths to clients
func (nr *NetlinkReader) addPathsToClients(routes []netlink.Route) {
cedi's avatar
cedi committed
	// If there where no routes yet, just skip this funktion. There's nothing to delete
	if len(routes) == 0 {
		nr.mu.RUnlock()
		return
	}

	// only advertise changed routes
	nr.mu.RLock()
	advertise := route.NetlinkRouteDiff(routes, nr.routes)
	nr.mu.RUnlock()

cedi's avatar
cedi committed
	for _, client := range nr.ClientManager.Clients() {
		for _, r := range advertise {
			if isBioRoute(r) {
				log.WithFields(routeLogFields(r)).Debug("Skipping bio route")
				continue
			}
cedi's avatar
cedi committed

cedi's avatar
cedi committed
			// create pfx and path from route
			pfx, paths, err := route.NewPathsFromNlRoute(r, true)
			if err != nil {
				log.WithError(err).WithFields(log.Fields{
					"prefix": pfx.String(),
cedi's avatar
cedi committed
				}).Error("Unable to create path")
cedi's avatar
cedi committed
				continue
			}

cedi's avatar
cedi committed
			for _, path := range paths {
				var p *route.Path
				if nr.filter != nil {
					var reject bool
					p, reject := nr.filter.ProcessTerms(pfx, path)
					if reject {
						log.WithError(err).WithFields(log.Fields{
							"prefix": pfx.String(),
							"path":   p.String(),
						}).Debug("Skipping route due to filter")
						continue
					}
				}

				log.WithFields(log.Fields{
					"pfx":  pfx,
					"path": p,
				}).Debug("NetlinkReader - client.AddPath")
				client.AddPath(pfx, p)
			}
cedi's avatar
cedi committed
		}
	}
}

// Remove given paths from clients
func (nr *NetlinkReader) removePathsFromClients(routes []netlink.Route) {
cedi's avatar
cedi committed

	// If there where no routes yet, just skip this funktion. There's nothing to delete
cedi's avatar
cedi committed
	if len(nr.routes) == 0 {
cedi's avatar
cedi committed
		nr.mu.RUnlock()
cedi's avatar
cedi committed

	// only withdraw changed routes
	withdraw := route.NetlinkRouteDiff(nr.routes, routes)
	nr.mu.RUnlock()
cedi's avatar
cedi committed

cedi's avatar
cedi committed
	for _, client := range nr.ClientManager.Clients() {
		for _, r := range withdraw {
			if isBioRoute(r) {
				log.WithFields(routeLogFields(r)).Debug("Skipping bio route")
				continue
			}
cedi's avatar
cedi committed

cedi's avatar
cedi committed
			// create pfx and path from route
			pfx, paths, err := route.NewPathsFromNlRoute(r, true)
			if err != nil {
				log.WithError(err).WithFields(log.Fields{
					"prefix": pfx.String(),
				}).Error("Unable to create path")
cedi's avatar
cedi committed
			}

cedi's avatar
cedi committed
			for _, path := range paths {
				var p *route.Path
				if nr.filter != nil {
					var reject bool
					p, reject = nr.filter.ProcessTerms(pfx, path)
					if reject {
						log.WithError(err).WithFields(log.Fields{
							"prefix": pfx.String(),
							"path":   p.String(),
						}).Debug("Skipping route due to filter")
						continue
					}
				}

				log.WithFields(log.Fields{
					"pfx":  pfx,
					"path": p,
				}).Debug("NetlinkReader - client.RemovePath")
				client.RemovePath(pfx, p)
			}
cedi's avatar
cedi committed
// Is route a BIO-Written route?
func isBioRoute(r netlink.Route) bool {
	return uint32(r.Protocol) == route.ProtoBio
}

cedi's avatar
cedi committed
func routeLogFields(route netlink.Route) log.Fields {
	return log.Fields{
		"LinkIndex":  route.LinkIndex,
		"ILinkIndex": route.ILinkIndex,
		"Scope":      route.Scope,
		"Dst":        route.Dst,
		"Src":        route.Src,
		"Gw":         route.Gw,
		"MultiPath":  route.MultiPath,
		"Protocol":   route.Protocol,
		"Priority":   route.Priority,
		"Table":      route.Table,
		"Type":       route.Type,
		"Tos":        route.Tos,
		"Flags":      route.Flags,
		"MPLSDst":    route.MPLSDst,
		"NewDst":     route.NewDst,
		"Encap":      route.Encap,
		"MTU":        route.MTU,
		"AdvMSS":     route.AdvMSS,
	}
}

cedi's avatar
cedi committed
// AddPath is Not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) AddPath(bnet.Prefix, *route.Path) error {
	return fmt.Errorf("Not supported")
}

cedi's avatar
cedi committed
// RemovePath is Not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) RemovePath(bnet.Prefix, *route.Path) bool {
	return false
}

cedi's avatar
cedi committed
// UpdateNewClient is currently not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) UpdateNewClient(routingtable.RouteTableClient) error {
	return fmt.Errorf("Not supported")
}

cedi's avatar
cedi committed
// Register is currently not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) Register(routingtable.RouteTableClient) {
}

cedi's avatar
cedi committed
// RegisterWithOptions is Not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
}

cedi's avatar
cedi committed
// Unregister is Not supported
cedi's avatar
cedi committed
func (nr *NetlinkReader) Unregister(routingtable.RouteTableClient) {
}

cedi's avatar
cedi committed
// RouteCount retuns the number of routes stored in the internal routing table
cedi's avatar
cedi committed
func (nr *NetlinkReader) RouteCount() int64 {
	nr.mu.RLock()
	defer nr.mu.RUnlock()

	return int64(len(nr.routes))
}