go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/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  	"net"
    20  	"strings"
    21  
    22  	"github.com/pkg/errors"
    23  	"github.com/vishvananda/netlink"
    24  	"go.ligato.io/cn-infra/v2/logging"
    25  	"google.golang.org/protobuf/proto"
    26  	prototypes "google.golang.org/protobuf/types/known/emptypb"
    27  
    28  	"go.ligato.io/vpp-agent/v3/pkg/models"
    29  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    30  	"go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin"
    31  	ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor"
    32  	"go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/descriptor/adapter"
    33  	l3linuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/linuxcalls"
    34  	"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin"
    35  	nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    36  	"go.ligato.io/vpp-agent/v3/plugins/netalloc"
    37  	netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor"
    38  	ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces"
    39  	linux_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3"
    40  	netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    41  )
    42  
    43  const (
    44  	// RouteDescriptorName is the name of the descriptor for Linux routes.
    45  	RouteDescriptorName = "linux-route"
    46  
    47  	// dependency labels
    48  	routeOutInterfaceDep       = "outgoing-interface-is-up"
    49  	routeOutInterfaceIPAddrDep = "outgoing-interface-has-ip-address"
    50  	routeGwReachabilityDep     = "gw-reachable"
    51  	allocatedAddrAttached      = "allocated-addr-attached"
    52  
    53  	// default metric of the IPv6 route
    54  	ipv6DefaultMetric = 1024
    55  )
    56  
    57  // A list of non-retriable errors:
    58  var (
    59  	// ErrRouteWithoutInterface is returned when Linux Route configuration is missing
    60  	// outgoing interface reference.
    61  	ErrRouteWithoutInterface = errors.New("Linux Route defined without outgoing interface reference")
    62  
    63  	// ErrRouteWithUndefinedScope is returned when Linux Route is configured without scope.
    64  	ErrRouteWithUndefinedScope = errors.New("Linux Route defined without scope")
    65  
    66  	// ErrRouteLinkWithGw is returned when link-local Linux route has gateway address
    67  	// specified - it shouldn't be since destination is already neighbour by definition.
    68  	ErrRouteLinkWithGw = errors.New("Link-local Linux Route was defined with non-empty GW address")
    69  )
    70  
    71  // RouteDescriptor teaches KVScheduler how to configure Linux routes.
    72  type RouteDescriptor struct {
    73  	log       logging.Logger
    74  	l3Handler l3linuxcalls.NetlinkAPI
    75  	ifPlugin  ifplugin.API
    76  	nsPlugin  nsplugin.API
    77  	addrAlloc netalloc.AddressAllocator
    78  	scheduler kvs.KVScheduler
    79  
    80  	// parallelization of the Retrieve operation
    81  	goRoutinesCnt int
    82  }
    83  
    84  // NewRouteDescriptor creates a new instance of the Route descriptor.
    85  func NewRouteDescriptor(
    86  	scheduler kvs.KVScheduler, ifPlugin ifplugin.API, nsPlugin nsplugin.API, addrAlloc netalloc.AddressAllocator,
    87  	l3Handler l3linuxcalls.NetlinkAPI, log logging.PluginLogger, goRoutinesCnt int) *kvs.KVDescriptor {
    88  
    89  	ctx := &RouteDescriptor{
    90  		scheduler:     scheduler,
    91  		l3Handler:     l3Handler,
    92  		ifPlugin:      ifPlugin,
    93  		nsPlugin:      nsPlugin,
    94  		addrAlloc:     addrAlloc,
    95  		goRoutinesCnt: goRoutinesCnt,
    96  		log:           log.NewLogger("route-descriptor"),
    97  	}
    98  	typedDescr := &adapter.RouteDescriptor{
    99  		Name:               RouteDescriptorName,
   100  		NBKeyPrefix:        linux_l3.ModelRoute.KeyPrefix(),
   101  		ValueTypeName:      linux_l3.ModelRoute.ProtoName(),
   102  		KeySelector:        linux_l3.ModelRoute.IsKeyValid,
   103  		KeyLabel:           linux_l3.ModelRoute.StripKeyPrefix,
   104  		ValueComparator:    ctx.EquivalentRoutes,
   105  		Validate:           ctx.Validate,
   106  		Create:             ctx.Create,
   107  		Delete:             ctx.Delete,
   108  		Update:             ctx.Update,
   109  		UpdateWithRecreate: ctx.UpdateWithRecreate,
   110  		Retrieve:           ctx.Retrieve,
   111  		DerivedValues:      ctx.DerivedValues,
   112  		Dependencies:       ctx.Dependencies,
   113  		RetrieveDependencies: []string{
   114  			netalloc_descr.IPAllocDescriptorName,
   115  			ifdescriptor.InterfaceDescriptorName},
   116  	}
   117  	return adapter.NewRouteDescriptor(typedDescr)
   118  }
   119  
   120  // EquivalentRoutes is case-insensitive comparison function for l3.LinuxRoute.
   121  func (d *RouteDescriptor) EquivalentRoutes(key string, oldRoute, newRoute *linux_l3.Route) bool {
   122  	// attributes compared as usually:
   123  	if oldRoute.OutgoingInterface != newRoute.OutgoingInterface {
   124  		return false
   125  	}
   126  	// compare scopes for IPv4 routes
   127  	if d.isIPv4Route(newRoute) && oldRoute.Scope != newRoute.Scope {
   128  		return false
   129  	}
   130  	// compare metrics
   131  	if !d.isRouteMetricEqual(oldRoute, newRoute) {
   132  		return false
   133  	}
   134  
   135  	// compare IP addresses converted to net.IP(Net)
   136  	if !equalNetworks(oldRoute.DstNetwork, newRoute.DstNetwork) {
   137  		return false
   138  	}
   139  	return equalAddrs(d.getGwAddr(oldRoute), d.getGwAddr(newRoute))
   140  }
   141  
   142  // Validate validates static route configuration.
   143  func (d *RouteDescriptor) Validate(key string, route *linux_l3.Route) (err error) {
   144  	if route.OutgoingInterface == "" {
   145  		return kvs.NewInvalidValueError(ErrRouteWithoutInterface, "outgoing_interface")
   146  	}
   147  	if route.Scope == linux_l3.Route_LINK && route.GwAddr != "" {
   148  		return kvs.NewInvalidValueError(ErrRouteLinkWithGw, "scope", "gw_addr")
   149  	}
   150  	err = d.addrAlloc.ValidateIPAddress(route.DstNetwork, "", "dst_network",
   151  		netalloc.GWRefAllowed)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	return d.addrAlloc.ValidateIPAddress(d.getGwAddr(route), route.OutgoingInterface,
   156  		"gw_addr", netalloc.GWRefRequired)
   157  }
   158  
   159  // Create adds Linux route.
   160  func (d *RouteDescriptor) Create(key string, route *linux_l3.Route) (metadata interface{}, err error) {
   161  	err = d.updateRoute(route, "add", d.l3Handler.AddRoute)
   162  	return nil, err
   163  }
   164  
   165  // Delete removes Linux route.
   166  func (d *RouteDescriptor) Delete(key string, route *linux_l3.Route, metadata interface{}) error {
   167  	return d.updateRoute(route, "delete", d.l3Handler.DelRoute)
   168  }
   169  
   170  // Update is able to change route scope and GW address.
   171  func (d *RouteDescriptor) Update(key string, oldRoute, newRoute *linux_l3.Route, oldMetadata interface{}) (newMetadata interface{}, err error) {
   172  	err = d.updateRoute(newRoute, "modify", d.l3Handler.ReplaceRoute)
   173  	return nil, err
   174  }
   175  
   176  // UpdateWithRecreate in case the metric was changed
   177  func (d *RouteDescriptor) UpdateWithRecreate(_ string, oldRoute, newRoute *linux_l3.Route, _ interface{}) bool {
   178  	return !d.isRouteMetricEqual(oldRoute, newRoute)
   179  }
   180  
   181  // updateRoute adds, modifies or deletes a Linux route.
   182  func (d *RouteDescriptor) updateRoute(route *linux_l3.Route, actionName string, actionClb func(route *netlink.Route) error) error {
   183  	var err error
   184  
   185  	// Prepare Netlink Route object
   186  	netlinkRoute := &netlink.Route{}
   187  
   188  	// Get interface metadata
   189  	ifMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(route.OutgoingInterface)
   190  	if !found || ifMeta == nil {
   191  		err = errors.Errorf("failed to obtain metadata for interface %s", route.OutgoingInterface)
   192  		d.log.Error(err)
   193  		return err
   194  	}
   195  
   196  	// set link index
   197  	netlinkRoute.LinkIndex = ifMeta.LinuxIfIndex
   198  
   199  	// set routing table
   200  	if ifMeta.VrfMasterIf != "" {
   201  		// - route depends on interface having an IP address
   202  		// - IP address depends on the interface already being in the VRF
   203  		// - VRF assignment depends on the VRF device being configured
   204  		// => conclusion: VRF device is configured at this point
   205  		vrfMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(ifMeta.VrfMasterIf)
   206  		if !found || vrfMeta == nil {
   207  			err = errors.Errorf("failed to obtain metadata for VRF device %s", ifMeta.VrfMasterIf)
   208  			d.log.Error(err)
   209  			return err
   210  		}
   211  		netlinkRoute.Table = int(vrfMeta.VrfDevRT)
   212  	}
   213  
   214  	// set destination network
   215  	dstNet, err := d.addrAlloc.GetOrParseIPAddress(route.DstNetwork, "",
   216  		netalloc_api.IPAddressForm_ADDR_NET)
   217  	if err != nil {
   218  		d.log.Error(err)
   219  		return err
   220  	}
   221  	netlinkRoute.Dst = dstNet
   222  
   223  	// set gateway address
   224  	if route.GwAddr != "" {
   225  		gwAddr, err := d.addrAlloc.GetOrParseIPAddress(route.GwAddr, route.OutgoingInterface,
   226  			netalloc_api.IPAddressForm_ADDR_ONLY)
   227  		if err != nil {
   228  			d.log.Error(err)
   229  			return err
   230  		}
   231  		netlinkRoute.Gw = gwAddr.IP
   232  	}
   233  
   234  	// set route scope for IPv4
   235  	if d.isIPv4Route(route) {
   236  		scope, err := rtScopeFromNBToNetlink(route.Scope)
   237  		if err != nil {
   238  			d.log.Error(err)
   239  			return err
   240  		}
   241  		netlinkRoute.Scope = scope
   242  	}
   243  
   244  	// set route metric
   245  	netlinkRoute.Priority = int(route.Metric)
   246  
   247  	// move to the namespace of the associated interface
   248  	nsCtx := nslinuxcalls.NewNamespaceMgmtCtx()
   249  	revertNs, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace)
   250  	if err != nil {
   251  		err = errors.Errorf("failed to switch namespace: %v", err)
   252  		d.log.Error(err)
   253  		return err
   254  	}
   255  	defer revertNs()
   256  
   257  	// update route in the interface namespace
   258  	err = actionClb(netlinkRoute)
   259  	if err != nil {
   260  		err = errors.Errorf("failed to %s linux route: %v", actionName, err)
   261  		d.log.Error(err)
   262  		return err
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  // Dependencies lists dependencies for a Linux route.
   269  func (d *RouteDescriptor) Dependencies(key string, route *linux_l3.Route) []kvs.Dependency {
   270  	var dependencies []kvs.Dependency
   271  	// the outgoing interface must exist and be UP
   272  	if route.OutgoingInterface != "" {
   273  		dependencies = append(dependencies, kvs.Dependency{
   274  			Label: routeOutInterfaceDep,
   275  			Key:   ifmodel.InterfaceStateKey(route.OutgoingInterface, true),
   276  		})
   277  	}
   278  	// if destination network is netalloc reference, then the address must be allocated first
   279  	allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(route.DstNetwork, "",
   280  		"dst_network-")
   281  	if hasAllocDep {
   282  		dependencies = append(dependencies, allocDep)
   283  	}
   284  	// if GW is netalloc reference, then the address must be allocated first
   285  	allocDep, hasAllocDep = d.addrAlloc.GetAddressAllocDep(route.GwAddr, route.OutgoingInterface,
   286  		"gw_addr-")
   287  	if hasAllocDep {
   288  		dependencies = append(dependencies, allocDep)
   289  	}
   290  	// GW must be routable
   291  	network, iface, _, isRef, _ := d.addrAlloc.ParseAddressAllocRef(route.GwAddr, route.OutgoingInterface)
   292  	if isRef {
   293  		// GW is netalloc reference
   294  		dependencies = append(dependencies, kvs.Dependency{
   295  			Label: routeGwReachabilityDep,
   296  			AnyOf: kvs.AnyOfDependency{
   297  				KeyPrefixes: []string{
   298  					netalloc_api.NeighGwKey(network, iface),
   299  					linux_l3.StaticLinkLocalRouteKey(
   300  						d.addrAlloc.CreateAddressAllocRef(network, iface, true),
   301  						route.OutgoingInterface),
   302  				},
   303  			},
   304  		})
   305  		dependencies = append(dependencies, kvs.Dependency{
   306  			Label: allocatedAddrAttached,
   307  			Key: ifmodel.InterfaceAddressKey(
   308  				route.OutgoingInterface, d.addrAlloc.CreateAddressAllocRef(network, "", false),
   309  				netalloc_api.IPAddressSource_ALLOC_REF),
   310  		})
   311  	} else if gwAddr := net.ParseIP(d.getGwAddr(route)); gwAddr != nil && !gwAddr.IsUnspecified() {
   312  		// GW is not netalloc reference but an actual IP
   313  		dependencies = append(dependencies, kvs.Dependency{
   314  			Label: routeGwReachabilityDep,
   315  			AnyOf: kvs.AnyOfDependency{
   316  				KeyPrefixes: []string{
   317  					ifmodel.InterfaceAddressPrefix(route.OutgoingInterface),
   318  					linux_l3.StaticLinkLocalRoutePrefix(route.OutgoingInterface),
   319  				},
   320  				KeySelector: func(key string) bool {
   321  					dstAddr, ifName, isRouteKey := linux_l3.ParseStaticLinkLocalRouteKey(key)
   322  					if isRouteKey && ifName == route.OutgoingInterface {
   323  						if _, dstNet, err := net.ParseCIDR(dstAddr); err == nil && dstNet.Contains(gwAddr) {
   324  							// GW address is neighbour as told by another link-local route
   325  							return true
   326  						}
   327  						return false
   328  					}
   329  					_, address, source, _, isAddrKey := ifmodel.ParseInterfaceAddressKey(key)
   330  					if isAddrKey && source != netalloc_api.IPAddressSource_ALLOC_REF {
   331  						if _, network, err := net.ParseCIDR(address); err == nil && network.Contains(gwAddr) {
   332  							// GW address is inside the local network of the outgoing interface
   333  							// as given by the assigned IP address
   334  							return true
   335  						}
   336  					}
   337  					return false
   338  				},
   339  			},
   340  		})
   341  	}
   342  	if route.OutgoingInterface != "" {
   343  		// route also requires the interface to be in the L3 mode (have at least one IP address assigned)
   344  		dependencies = append(dependencies, kvs.Dependency{
   345  			Label: routeOutInterfaceIPAddrDep,
   346  			AnyOf: kvs.AnyOfDependency{
   347  				KeyPrefixes: []string{
   348  					ifmodel.InterfaceAddressPrefix(route.OutgoingInterface),
   349  				},
   350  			},
   351  		})
   352  	}
   353  	return dependencies
   354  }
   355  
   356  // DerivedValues derives empty value under StaticLinkLocalRouteKey if route is link-local.
   357  // It is used in dependencies for network reachability of a route gateway (see above).
   358  func (d *RouteDescriptor) DerivedValues(key string, route *linux_l3.Route) (derValues []kvs.KeyValuePair) {
   359  	if route.Scope == linux_l3.Route_LINK {
   360  		derValues = append(derValues, kvs.KeyValuePair{
   361  			Key:   linux_l3.StaticLinkLocalRouteKey(route.DstNetwork, route.OutgoingInterface),
   362  			Value: &prototypes.Empty{},
   363  		})
   364  	}
   365  	return derValues
   366  }
   367  
   368  // Retrieve returns all routes associated with interfaces managed by this agent.
   369  func (d *RouteDescriptor) Retrieve(correlate []adapter.RouteKVWithMetadata) ([]adapter.RouteKVWithMetadata, error) {
   370  	var values []adapter.RouteKVWithMetadata
   371  
   372  	// prepare expected configuration with de-referenced netalloc links
   373  	nbCfg := make(map[string]*linux_l3.Route)
   374  	expCfg := make(map[string]*linux_l3.Route)
   375  	for _, kv := range correlate {
   376  		dstNetwork := kv.Value.DstNetwork
   377  		parsed, err := d.addrAlloc.GetOrParseIPAddress(kv.Value.DstNetwork,
   378  			"", netalloc_api.IPAddressForm_ADDR_NET)
   379  		if err == nil {
   380  			dstNetwork = parsed.String()
   381  		}
   382  		gwAddr := kv.Value.GwAddr
   383  		parsed, err = d.addrAlloc.GetOrParseIPAddress(d.getGwAddr(kv.Value),
   384  			kv.Value.OutgoingInterface, netalloc_api.IPAddressForm_ADDR_ONLY)
   385  		if err == nil {
   386  			gwAddr = parsed.IP.String()
   387  		}
   388  		route := proto.Clone(kv.Value).(*linux_l3.Route)
   389  		route.DstNetwork = dstNetwork
   390  		route.GwAddr = gwAddr
   391  		key := models.Key(route)
   392  		expCfg[key] = route
   393  		nbCfg[key] = kv.Value
   394  	}
   395  
   396  	routeDetails, err := d.l3Handler.DumpRoutes()
   397  	if err != nil {
   398  		return nil, errors.Errorf("Failed to retrieve linux ARPs: %v", err)
   399  	}
   400  
   401  	// correlate with the expected configuration
   402  	for _, routeDetails := range routeDetails {
   403  		// convert to key-value object with metadata
   404  		// resolve scope for IPv4. Note that IPv6 route scope always returns zero value.
   405  		var scope linux_l3.Route_Scope
   406  		if d.isIPv4Route(routeDetails.Route) {
   407  			scope, err = rtScopeFromNetlinkToNB(routeDetails.Meta.NetlinkScope)
   408  			if err != nil {
   409  				// route not configured by the agent
   410  				continue
   411  			}
   412  		}
   413  		route := adapter.RouteKVWithMetadata{
   414  			Key: linux_l3.RouteKey(routeDetails.Route.DstNetwork, routeDetails.Route.OutgoingInterface),
   415  			Value: &linux_l3.Route{
   416  				OutgoingInterface: routeDetails.Route.OutgoingInterface,
   417  				Scope:             scope,
   418  				DstNetwork:        routeDetails.Route.DstNetwork,
   419  				GwAddr:            routeDetails.Route.GwAddr,
   420  				Metric:            routeDetails.Route.Metric,
   421  			},
   422  			Origin: kvs.UnknownOrigin, // let the scheduler to determine the origin
   423  		}
   424  
   425  		key := linux_l3.RouteKey(routeDetails.Route.DstNetwork, routeDetails.Route.OutgoingInterface)
   426  		if expCfg, hasExpCfg := expCfg[key]; hasExpCfg {
   427  			if d.EquivalentRoutes(key, route.Value, expCfg) {
   428  				route.Value = nbCfg[key]
   429  				// recreate the key in case the dest. IP was replaced with netalloc link
   430  				route.Key = models.Key(route.Value)
   431  			}
   432  		}
   433  		values = append(values, route)
   434  	}
   435  
   436  	return values, nil
   437  }
   438  
   439  // compares route metrics. For IPv6, Metric 0 & 1024 are considered the same value
   440  func (d *RouteDescriptor) isRouteMetricEqual(oldRoute, newRoute *linux_l3.Route) bool {
   441  	if oldRoute.Metric != newRoute.Metric {
   442  		if d.isIPv4Route(newRoute) {
   443  			return false
   444  		}
   445  		return (oldRoute.Metric == 0 && newRoute.Metric == ipv6DefaultMetric) ||
   446  			(oldRoute.Metric == ipv6DefaultMetric && newRoute.Metric == 0)
   447  	}
   448  	return true
   449  }
   450  
   451  // checks the destination network to determine whether the route is an IPv4 route
   452  func (d *RouteDescriptor) isIPv4Route(r *linux_l3.Route) bool {
   453  	addr, err := d.addrAlloc.GetOrParseIPAddress(r.DstNetwork, "", netalloc_api.IPAddressForm_ADDR_ONLY)
   454  	if err != nil {
   455  		d.log.Error(err)
   456  	}
   457  	return addr != nil && addr.IP != nil && addr.IP.To4() != nil
   458  }
   459  
   460  // getGwAddr returns the GW address chosen in the given route, handling the cases
   461  // when it is left undefined.
   462  func (d *RouteDescriptor) getGwAddr(route *linux_l3.Route) string {
   463  	if route.GwAddr == "" {
   464  		if d.isIPv4Route(route) {
   465  			return l3linuxcalls.IPv4AddrAny
   466  		}
   467  		return l3linuxcalls.IPv6AddrAny
   468  	}
   469  	return route.GwAddr
   470  }
   471  
   472  // rtScopeFromNBToNetlink convert Route scope from NB configuration
   473  // to the corresponding Netlink constant.
   474  func rtScopeFromNBToNetlink(scope linux_l3.Route_Scope) (netlink.Scope, error) {
   475  	switch scope {
   476  	case linux_l3.Route_GLOBAL:
   477  		return netlink.SCOPE_UNIVERSE, nil
   478  	case linux_l3.Route_HOST:
   479  		return netlink.SCOPE_HOST, nil
   480  	case linux_l3.Route_LINK:
   481  		return netlink.SCOPE_LINK, nil
   482  	case linux_l3.Route_SITE:
   483  		return netlink.SCOPE_SITE, nil
   484  	}
   485  	return 0, ErrRouteWithUndefinedScope
   486  }
   487  
   488  // rtScopeFromNetlinkToNB converts Route scope from Netlink constant
   489  // to the corresponding NB constant.
   490  func rtScopeFromNetlinkToNB(scope netlink.Scope) (linux_l3.Route_Scope, error) {
   491  	switch scope {
   492  	case netlink.SCOPE_UNIVERSE:
   493  		return linux_l3.Route_GLOBAL, nil
   494  	case netlink.SCOPE_HOST:
   495  		return linux_l3.Route_HOST, nil
   496  	case netlink.SCOPE_LINK:
   497  		return linux_l3.Route_LINK, nil
   498  	case netlink.SCOPE_SITE:
   499  		return linux_l3.Route_SITE, nil
   500  	}
   501  	return 0, ErrRouteWithUndefinedScope
   502  }
   503  
   504  // equalAddrs compares two IP addresses for equality.
   505  func equalAddrs(addr1, addr2 string) bool {
   506  	if strings.HasPrefix(addr1, netalloc_api.AllocRefPrefix) {
   507  		return addr1 == addr2
   508  	}
   509  	a1 := net.ParseIP(addr1)
   510  	a2 := net.ParseIP(addr2)
   511  	if a1 == nil || a2 == nil {
   512  		// if parsing fails, compare as strings
   513  		return strings.EqualFold(addr1, addr2)
   514  	}
   515  	return a1.Equal(a2)
   516  }
   517  
   518  // equalNetworks compares two IP networks for equality.
   519  func equalNetworks(net1, net2 string) bool {
   520  	if strings.HasPrefix(net1, netalloc_api.AllocRefPrefix) {
   521  		return net1 == net2
   522  	}
   523  	_, n1, err1 := net.ParseCIDR(net1)
   524  	_, n2, err2 := net.ParseCIDR(net2)
   525  	if err1 != nil || err2 != nil {
   526  		// if parsing fails, compare as strings
   527  		return strings.EqualFold(net1, net2)
   528  	}
   529  	return n1.IP.Equal(n2.IP) && bytes.Equal(n1.Mask, n2.Mask)
   530  }