Skip to content
Snippets Groups Projects
netlink_writer.go 5.53 KiB
Newer Older
  • Learn to ignore specific revisions
  • cedi's avatar
    cedi committed
    package protocolnetlink
    
    cedi's avatar
    cedi committed
    
    import (
    	"fmt"
    	"sync"
    
    	"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"
    )
    
    
    cedi's avatar
    cedi committed
    // NetlinkWriter is a locRIB subscriber which serializes routes from the locRIB to the Linux Kernel routing stack
    
    cedi's avatar
    cedi committed
    type NetlinkWriter struct {
    	options *config.Netlink
    	filter  *filter.Filter
    
    	// Routingtable for buffering, to ensure no double writes (a.k.a rtnetlink: file exists)
    
    cedi's avatar
    cedi committed
    	mu        sync.RWMutex
    	pathTable map[bnet.Prefix][]*route.Path
    
    cedi's avatar
    cedi committed
    }
    
    
    cedi's avatar
    cedi committed
    // NewNetlinkWriter creates a new NetlinkWriter object and returns the pointer to it
    
    cedi's avatar
    cedi committed
    func NewNetlinkWriter(options *config.Netlink) *NetlinkWriter {
    	return &NetlinkWriter{
    
    cedi's avatar
    cedi committed
    		options:   options,
    		filter:    options.ExportFilter,
    		pathTable: make(map[bnet.Prefix][]*route.Path),
    
    cedi's avatar
    cedi committed
    	}
    }
    
    
    cedi's avatar
    cedi committed
    // UpdateNewClient Not supported for NetlinkWriter, since the writer is not observable
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) UpdateNewClient(routingtable.RouteTableClient) error {
    	return fmt.Errorf("Not supported")
    }
    
    
    cedi's avatar
    cedi committed
    // Register Not supported for NetlinkWriter, since the writer is not observable
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) Register(routingtable.RouteTableClient) {
    	log.Error("Not supported")
    }
    
    
    cedi's avatar
    cedi committed
    // RegisterWithOptions Not supported, since the writer is not observable
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
    	log.Error("Not supported")
    }
    
    
    cedi's avatar
    cedi committed
    // Unregister is not supported, since the writer is not observable
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) Unregister(routingtable.RouteTableClient) {
    	log.Error("Not supported")
    }
    
    // RouteCount returns the number of stored routes
    func (nw *NetlinkWriter) RouteCount() int64 {
    	nw.mu.RLock()
    	defer nw.mu.RUnlock()
    
    cedi's avatar
    cedi committed
    	return int64(len(nw.pathTable))
    
    cedi's avatar
    cedi committed
    }
    
    
    cedi's avatar
    cedi committed
    // AddPath adds a path to the Kernel using netlink. This function is triggered by the loc_rib, cause we are subscribed as client in the loc_rib
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) AddPath(pfx bnet.Prefix, path *route.Path) error {
    	// check if for this prefix already a route is existing
    
    cedi's avatar
    cedi committed
    	existingPaths, ok := nw.pathTable[pfx]
    
    cedi's avatar
    cedi committed
    
    	// if no route exists, add that route
    	if existingPaths == nil || !ok {
    
    cedi's avatar
    cedi committed
    		nw.pathTable[pfx] = []*route.Path{path}
    		return nw.addKernel(pfx)
    
    cedi's avatar
    cedi committed
    	}
    
    	// if the new path is already in, don't do anything
    	for _, ePath := range existingPaths {
    		if ePath.Equal(path) {
    			return nil
    		}
    	}
    
    
    cedi's avatar
    cedi committed
    	// if newly added path is a ecmp path to the existing paths, add it
    	if path.ECMP(existingPaths[0]) {
    		nw.removeKernel(pfx, existingPaths)
    		existingPaths = append(existingPaths, path)
    		nw.pathTable[pfx] = existingPaths
    
    		return nw.addKernel(pfx)
    	}
    
    	// if newly added path is no ecmp path to the existing ones, remove all old and only add the new
    	nw.removeKernel(pfx, existingPaths)
    	nw.pathTable[pfx] = []*route.Path{path}
    	return nw.addKernel(pfx)
    
    cedi's avatar
    cedi committed
    
    }
    
    
    cedi's avatar
    cedi committed
    // RemovePath removes a path from the Kernel using netlink This function is triggered by the loc_rib, cause we are subscribed as client in the loc_rib
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) RemovePath(pfx bnet.Prefix, path *route.Path) bool {
    	// check if for this prefix already a route is existing
    
    cedi's avatar
    cedi committed
    	existingPaths, ok := nw.pathTable[pfx]
    
    cedi's avatar
    cedi committed
    
    	// if no route exists, nothing to do
    	if existingPaths == nil || !ok {
    		return true
    	}
    
    	// if the new path is already in: remove
    	removeIdx := 0
    	remove := false
    	for idx, ePath := range existingPaths {
    		if ePath.Equal(path) {
    			removeIdx = idx
    
    			remove = true
    
    cedi's avatar
    cedi committed
    			err := nw.removeKernel(pfx, []*route.Path{path})
    
    cedi's avatar
    cedi committed
    			if err != nil {
    				log.WithError(err).Errorf("Error while removing path %s for prefix %s", path.String(), pfx.String())
    				remove = false
    			}
    
    			break
    		}
    	}
    
    	if remove {
    		existingPaths = append(existingPaths[:removeIdx], existingPaths[removeIdx+1:]...)
    
    cedi's avatar
    cedi committed
    		nw.pathTable[pfx] = existingPaths
    
    cedi's avatar
    cedi committed
    	}
    
    	return true
    }
    
    // Add pfx/path to kernel
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) addKernel(pfx bnet.Prefix) error {
    
    	route, err := nw.createRoute(pfx, nw.pathTable[pfx])
    	if err != nil {
    		return fmt.Errorf("Could not create Route: %v", err.Error())
    	}
    
    cedi's avatar
    cedi committed
    
    	log.WithFields(log.Fields{
    		"Prefix": pfx.String(),
    		"Table":  route.Table,
    	}).Debug("AddPath to netlink")
    
    
    	err = netlink.RouteAdd(route)
    
    cedi's avatar
    cedi committed
    	if err != nil {
    		log.Errorf("Error while adding route: %v", err)
    		return fmt.Errorf("Error while adding route: %v", err)
    	}
    
    	return nil
    }
    
    // remove pfx/path from kernel
    
    cedi's avatar
    cedi committed
    func (nw *NetlinkWriter) removeKernel(pfx bnet.Prefix, paths []*route.Path) error {
    
    	route, err := nw.createRoute(pfx, nw.pathTable[pfx])
    	if err != nil {
    		return fmt.Errorf("Could not create Route: %v", err.Error())
    	}
    
    cedi's avatar
    cedi committed
    
    
    cedi's avatar
    cedi committed
    	log.WithFields(log.Fields{
    		"Prefix": pfx.String(),
    	}).Debug("Remove from netlink")
    
    
    	err = netlink.RouteDel(route)
    
    cedi's avatar
    cedi committed
    	if err != nil {
    		return fmt.Errorf("Error while removing route: %v", err)
    	}
    
    	return nil
    }
    
    // create a route from a prefix and a path
    
    func (nw *NetlinkWriter) createRoute(pfx bnet.Prefix, paths []*route.Path) (*netlink.Route, error) {
    
    cedi's avatar
    cedi committed
    	route := &netlink.Route{
    		Dst:      pfx.GetIPNet(),
    		Table:    int(nw.options.RoutingTable), // config dependent
    		Protocol: route.ProtoBio,
    
    cedi's avatar
    cedi committed
    	}
    
    
    cedi's avatar
    cedi committed
    	multiPath := make([]*netlink.NexthopInfo, 0)
    
    cedi's avatar
    cedi committed
    
    
    cedi's avatar
    cedi committed
    	for _, path := range paths {
    		nextHop := &netlink.NexthopInfo{
    			Gw: path.NextHop().Bytes(),
    		}
    		multiPath = append(multiPath, nextHop)
    
    cedi's avatar
    cedi committed
    	}
    
    
    	if len(multiPath) == 1 {
    		route.Gw = multiPath[0].Gw
    	} else if len(multiPath) > 1 {
    		route.MultiPath = multiPath
    	} else {
    		return nil, fmt.Errorf("No destination address specified. At least one NextHop has to be specified in path")
    	}
    
    cedi's avatar
    cedi committed
    
    
    cedi's avatar
    cedi committed
    }