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