go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/l3plugin/descriptor/route.go (about)

     1  // Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at:
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package descriptor
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"net"
    22  	"strings"
    23  
    24  	"github.com/pkg/errors"
    25  	"go.ligato.io/cn-infra/v2/logging"
    26  	"go.ligato.io/cn-infra/v2/utils/addrs"
    27  	"google.golang.org/protobuf/proto"
    28  
    29  	"go.ligato.io/vpp-agent/v3/pkg/models"
    30  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    31  	"go.ligato.io/vpp-agent/v3/plugins/netalloc"
    32  	netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor"
    33  	ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/descriptor"
    34  	"go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/descriptor/adapter"
    35  	"go.ligato.io/vpp-agent/v3/plugins/vpp/l3plugin/vppcalls"
    36  	netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    37  	interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    38  	l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
    39  )
    40  
    41  const (
    42  	// RouteDescriptorName is the name of the descriptor for static routes.
    43  	RouteDescriptorName = "vpp-route"
    44  
    45  	// dependency labels
    46  	routeOutInterfaceDep = "interface-exists"
    47  	vrfTableDep          = "vrf-table-exists"
    48  	viaVrfTableDep       = "via-vrf-table-exists"
    49  
    50  	// static route weight by default
    51  	defaultWeight = 1
    52  )
    53  
    54  // RouteDescriptor teaches KVScheduler how to configure VPP routes.
    55  type RouteDescriptor struct {
    56  	log          logging.Logger
    57  	routeHandler vppcalls.RouteVppAPI
    58  	addrAlloc    netalloc.AddressAllocator
    59  }
    60  
    61  // NewRouteDescriptor creates a new instance of the Route descriptor.
    62  func NewRouteDescriptor(
    63  	routeHandler vppcalls.RouteVppAPI, addrAlloc netalloc.AddressAllocator,
    64  	log logging.PluginLogger) *kvs.KVDescriptor {
    65  
    66  	ctx := &RouteDescriptor{
    67  		routeHandler: routeHandler,
    68  		addrAlloc:    addrAlloc,
    69  		log:          log.NewLogger("static-route-descriptor"),
    70  	}
    71  
    72  	typedDescr := &adapter.RouteDescriptor{
    73  		Name:            RouteDescriptorName,
    74  		NBKeyPrefix:     l3.ModelRoute.KeyPrefix(),
    75  		ValueTypeName:   l3.ModelRoute.ProtoName(),
    76  		KeySelector:     l3.ModelRoute.IsKeyValid,
    77  		ValueComparator: ctx.EquivalentRoutes,
    78  		Validate:        ctx.Validate,
    79  		Create:          ctx.Create,
    80  		Delete:          ctx.Delete,
    81  		Retrieve:        ctx.Retrieve,
    82  		Dependencies:    ctx.Dependencies,
    83  		RetrieveDependencies: []string{
    84  			netalloc_descr.IPAllocDescriptorName,
    85  			ifdescriptor.InterfaceDescriptorName,
    86  			VrfTableDescriptorName},
    87  	}
    88  	return adapter.NewRouteDescriptor(typedDescr)
    89  }
    90  
    91  // EquivalentRoutes is case-insensitive comparison function for l3.Route.
    92  func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *l3.Route) bool {
    93  	if oldRoute.GetType() != newRoute.GetType() ||
    94  		oldRoute.GetVrfId() != newRoute.GetVrfId() ||
    95  		oldRoute.GetViaVrfId() != newRoute.GetViaVrfId() ||
    96  		oldRoute.GetOutgoingInterface() != newRoute.GetOutgoingInterface() ||
    97  		getWeight(oldRoute) != getWeight(newRoute) ||
    98  		oldRoute.GetPreference() != newRoute.GetPreference() {
    99  		return false
   100  	}
   101  
   102  	// compare dst networks
   103  	if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) {
   104  		return false
   105  	}
   106  
   107  	// compare gw addresses (next hop)
   108  	if !equalAddrs(getGwAddr(oldRoute), getGwAddr(newRoute)) {
   109  		return false
   110  	}
   111  
   112  	return true
   113  }
   114  
   115  // Validate validates VPP static route configuration.
   116  func (d *RouteDescriptor) Validate(key string, route *l3.Route) (err error) {
   117  	// validate destination network
   118  	err = d.addrAlloc.ValidateIPAddress(route.DstNetwork, "", "dst_network",
   119  		netalloc.GWRefAllowed)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// validate next hop address (GW)
   125  	err = d.addrAlloc.ValidateIPAddress(getGwAddr(route), route.OutgoingInterface,
   126  		"gw_addr", netalloc.GWRefRequired)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// validate IP network implied by the IP and prefix length
   132  	if !strings.HasPrefix(route.DstNetwork, netalloc_api.AllocRefPrefix) {
   133  		_, ipNet, _ := net.ParseCIDR(route.DstNetwork)
   134  		if !strings.EqualFold(ipNet.String(), route.DstNetwork) {
   135  			e := fmt.Errorf("DstNetwork (%s) must represent IP network (%s)",
   136  				route.DstNetwork, ipNet.String())
   137  			return kvs.NewInvalidValueError(e, "dst_network")
   138  		}
   139  	}
   140  
   141  	// TODO: validate mix of IP versions?
   142  
   143  	return nil
   144  }
   145  
   146  // Create adds VPP static route.
   147  func (d *RouteDescriptor) Create(key string, route *l3.Route) (metadata interface{}, err error) {
   148  	err = d.routeHandler.VppAddRoute(context.TODO(), route)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	return nil, nil
   154  }
   155  
   156  // Delete removes VPP static route.
   157  func (d *RouteDescriptor) Delete(key string, route *l3.Route, metadata interface{}) error {
   158  	err := d.routeHandler.VppDelRoute(context.TODO(), route)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  // Retrieve returns all routes associated with interfaces managed by this agent.
   167  func (d *RouteDescriptor) Retrieve(correlate []adapter.RouteKVWithMetadata) (
   168  	retrieved []adapter.RouteKVWithMetadata, err error,
   169  ) {
   170  	// prepare expected configuration with de-referenced netalloc links
   171  	nbCfg := make(map[string]*l3.Route)
   172  	expCfg := make(map[string]*l3.Route)
   173  	for _, kv := range correlate {
   174  		dstNetwork := kv.Value.DstNetwork
   175  		parsed, err := d.addrAlloc.GetOrParseIPAddress(kv.Value.DstNetwork,
   176  			"", netalloc_api.IPAddressForm_ADDR_NET)
   177  		if err == nil {
   178  			dstNetwork = parsed.String()
   179  		}
   180  		nextHop := kv.Value.NextHopAddr
   181  		parsed, err = d.addrAlloc.GetOrParseIPAddress(getGwAddr(kv.Value),
   182  			kv.Value.OutgoingInterface, netalloc_api.IPAddressForm_ADDR_ONLY)
   183  		if err == nil {
   184  			nextHop = parsed.IP.String()
   185  		}
   186  		route := proto.Clone(kv.Value).(*l3.Route)
   187  		route.DstNetwork = dstNetwork
   188  		route.NextHopAddr = nextHop
   189  		key := models.Key(route)
   190  		expCfg[key] = route
   191  		nbCfg[key] = kv.Value
   192  	}
   193  
   194  	// Retrieve VPP route configuration
   195  	routes, err := d.routeHandler.DumpRoutes()
   196  	if err != nil {
   197  		return nil, errors.Errorf("failed to dump VPP routes: %v", err)
   198  	}
   199  
   200  	for _, route := range routes {
   201  		key := models.Key(route.Route)
   202  		value := route.Route
   203  		origin := kvs.UnknownOrigin
   204  
   205  		// correlate with the expected configuration
   206  		if expCfg, hasExpCfg := expCfg[key]; hasExpCfg {
   207  			if d.EquivalentRoutes(key, value, expCfg) {
   208  				value = nbCfg[key]
   209  				// recreate the key in case the dest. IP or GW IP were replaced with netalloc link
   210  				key = models.Key(value)
   211  				origin = kvs.FromNB
   212  			}
   213  		}
   214  
   215  		retrieved = append(retrieved, adapter.RouteKVWithMetadata{
   216  			Key:    key,
   217  			Value:  value,
   218  			Origin: origin,
   219  		})
   220  	}
   221  
   222  	return retrieved, nil
   223  }
   224  
   225  // Dependencies lists dependencies for a VPP route.
   226  func (d *RouteDescriptor) Dependencies(key string, route *l3.Route) []kvs.Dependency {
   227  	var dependencies []kvs.Dependency
   228  	// the outgoing interface must exist and be UP
   229  	if route.OutgoingInterface != "" {
   230  		dependencies = append(dependencies, kvs.Dependency{
   231  			Label: routeOutInterfaceDep,
   232  			Key:   interfaces.InterfaceKey(route.OutgoingInterface),
   233  		})
   234  	}
   235  
   236  	// non-zero VRFs
   237  	var protocol l3.VrfTable_Protocol
   238  	_, isIPv6, _ := addrs.ParseIPWithPrefix(route.DstNetwork)
   239  	if isIPv6 {
   240  		protocol = l3.VrfTable_IPV6
   241  	}
   242  	if route.VrfId != 0 {
   243  		dependencies = append(dependencies, kvs.Dependency{
   244  			Label: vrfTableDep,
   245  			Key:   l3.VrfTableKey(route.VrfId, protocol),
   246  		})
   247  	}
   248  	if route.Type == l3.Route_INTER_VRF && route.ViaVrfId != 0 {
   249  		dependencies = append(dependencies, kvs.Dependency{
   250  			Label: viaVrfTableDep,
   251  			Key:   l3.VrfTableKey(route.ViaVrfId, protocol),
   252  		})
   253  	}
   254  
   255  	// if destination network is netalloc reference, then the address must be allocated first
   256  	allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(route.DstNetwork,
   257  		"", "dst_network-")
   258  	if hasAllocDep {
   259  		dependencies = append(dependencies, allocDep)
   260  	}
   261  	// if GW is netalloc reference, then the address must be allocated first
   262  	allocDep, hasAllocDep = d.addrAlloc.GetAddressAllocDep(route.NextHopAddr,
   263  		route.OutgoingInterface, "gw_addr-")
   264  	if hasAllocDep {
   265  		dependencies = append(dependencies, allocDep)
   266  	}
   267  
   268  	// TODO: perhaps check GW routability
   269  	return dependencies
   270  }
   271  
   272  // equalAddrs compares two IP addresses for equality.
   273  func equalAddrs(addr1, addr2 string) bool {
   274  	if strings.HasPrefix(addr1, netalloc_api.AllocRefPrefix) ||
   275  		strings.HasPrefix(addr2, netalloc_api.AllocRefPrefix) {
   276  		return addr1 == addr2
   277  	}
   278  	a1 := net.ParseIP(addr1)
   279  	a2 := net.ParseIP(addr2)
   280  	if a1 == nil || a2 == nil {
   281  		// if parsing fails, compare as strings
   282  		return strings.EqualFold(addr1, addr2)
   283  	}
   284  	return a1.Equal(a2)
   285  }
   286  
   287  // getGwAddr returns the GW address chosen in the given route, handling the cases
   288  // when it is left undefined.
   289  func getGwAddr(route *l3.Route) string {
   290  	if route.GetNextHopAddr() != "" {
   291  		return route.GetNextHopAddr()
   292  	}
   293  	// return zero address
   294  	// - with netalloc'd destination network, just assume it is for IPv4
   295  	if !strings.HasPrefix(route.GetDstNetwork(), netalloc_api.AllocRefPrefix) {
   296  		_, dstIPNet, err := net.ParseCIDR(route.GetDstNetwork())
   297  		if err != nil {
   298  			return ""
   299  		}
   300  		if dstIPNet.IP.To4() == nil {
   301  			return net.IPv6zero.String()
   302  		}
   303  	}
   304  	return net.IPv4zero.String()
   305  }
   306  
   307  // getWeight returns static route weight, handling the cases when it is left undefined.
   308  func getWeight(route *l3.Route) uint32 {
   309  	if route.Weight == 0 {
   310  		return defaultWeight
   311  	}
   312  	return route.Weight
   313  }
   314  
   315  // equalNetworks compares two IP networks for equality.
   316  func equalNetworks(net1, net2 string) bool {
   317  	if strings.HasPrefix(net1, netalloc_api.AllocRefPrefix) ||
   318  		strings.HasPrefix(net2, netalloc_api.AllocRefPrefix) {
   319  		return net1 == net2
   320  	}
   321  	_, n1, err1 := net.ParseCIDR(net1)
   322  	_, n2, err2 := net.ParseCIDR(net2)
   323  	if err1 != nil || err2 != nil {
   324  		// if parsing fails, compare as strings
   325  		return strings.EqualFold(net1, net2)
   326  	}
   327  	return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask)
   328  }