Skip to content
Snippets Groups Projects
netlink_reader.go 6.11 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 (
	IPFamily4 = 4 // IPv4
	IPFamily6 = 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(int(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)
	}
}

// create a path from a route
func createPathFromRoute(r *netlink.Route) (*route.Path, error) {
	nlPath, err := route.NewNlPathFromRoute(r, true)

	if err != nil {
cedi's avatar
cedi committed
		return nil, fmt.Errorf("Error while creating path object from route object: %v", err)
cedi's avatar
cedi committed
	}

	return &route.Path{
		Type:        route.NetlinkPathType,
		NetlinkPath: nlPath,
	}, nil
}

// 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) {
	// only advertise changed routes
	nr.mu.RLock()
	advertise := route.NetlinkRouteDiff(routes, nr.routes)
	nr.mu.RUnlock()

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

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

		if nr.filter != nil {
			var reject bool
			// TODO: Implement filter that cann handle netlinkRoute objects
			path, reject = nr.filter.ProcessTerms(pfx, path)
			if reject {
				log.WithError(err).WithFields(log.Fields{
					"prefix": pfx.String(),
					"path":   path.String(),
				}).Debug("Skipping route due to filter")
cedi's avatar
cedi committed

				continue
			}
cedi's avatar
cedi committed

		for _, client := range nr.ClientManager.Clients() {
cedi's avatar
cedi committed
			log.WithFields(log.Fields{
				"pfx":  pfx,
				"path": path,
			}).Debug("NetlinkReader - client.AddPath")
			client.AddPath(pfx, path)
		}
	}
}

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
// Remove given paths from clients
func (nr *NetlinkReader) removePathsFromClients(routes []netlink.Route) {
cedi's avatar
cedi committed

	// get the number of routes
	routeLength := len(nr.routes)

	// If there where no routes yet, just skip this funktion. There's nothing to delete
	if routeLength == 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

	for _, r := range withdraw {
		// Is it a BIO-Written route? if so, skip it, dont advertise it
		if r.Protocol == route.ProtoBio {
			continue
		}

		// create pfx and path from route
		pfx := bnet.NewPfxFromIPNet(r.Dst)
		path, err := createPathFromRoute(&r)
		if err != nil {
			log.WithError(err).Error("Unable to create path")
			continue
		}
cedi's avatar
cedi committed

		if nr.filter != nil {
			var reject bool
			// TODO: Implement filter that cann handle netlinkRoute objects
			path, reject = nr.filter.ProcessTerms(pfx, path)
			if reject {
				continue
cedi's avatar
cedi committed
			}
cedi's avatar
cedi committed

		for _, client := range nr.ClientManager.Clients() {
cedi's avatar
cedi committed
			log.WithFields(log.Fields{
				"pfx":  pfx,
				"path": path,
			}).Debug("NetlinkReader - client.RemovePath")
			client.RemovePath(pfx, path)
		}
	}
}

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