go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/l3plugin/descriptor/arp.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  	"net"
    19  	"strings"
    20  
    21  	"github.com/pkg/errors"
    22  	"github.com/vishvananda/netlink"
    23  
    24  	"go.ligato.io/cn-infra/v2/logging"
    25  
    26  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    27  
    28  	"go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin"
    29  	ifdescriptor "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/descriptor"
    30  	"go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/descriptor/adapter"
    31  	l3linuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/l3plugin/linuxcalls"
    32  	"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin"
    33  	nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    34  	"go.ligato.io/vpp-agent/v3/plugins/netalloc"
    35  	netalloc_descr "go.ligato.io/vpp-agent/v3/plugins/netalloc/descriptor"
    36  	ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces"
    37  	l3 "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3"
    38  	netalloc_api "go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    39  )
    40  
    41  const (
    42  	// ARPDescriptorName is the name of the descriptor for Linux ARP entries.
    43  	ARPDescriptorName = "linux-arp"
    44  
    45  	// dependency labels
    46  	arpInterfaceDep   = "interface-is-up"
    47  	arpInterfaceIPDep = "interface-has-ip-address"
    48  )
    49  
    50  // A list of non-retriable errors:
    51  var (
    52  	// ErrARPWithoutInterface is returned when Linux ARP configuration is missing
    53  	// interface reference.
    54  	ErrARPWithoutInterface = errors.New("Linux ARP entry defined without interface reference")
    55  
    56  	// ErrARPWithInvalidIP is returned when Linux ARP configuration contains IP address that cannot be parsed.
    57  	ErrARPWithInvalidIP = errors.New("Linux ARP entry defined with invalid IP address")
    58  
    59  	// ErrARPWithoutHwAddr is returned when Linux ARP configuration is missing
    60  	// MAC address.
    61  	ErrARPWithoutHwAddr = errors.New("Linux ARP entry defined without MAC address")
    62  
    63  	// ErrARPWithInvalidHwAddr is returned when Linux ARP configuration contains MAC address that cannot be parsed.
    64  	ErrARPWithInvalidHwAddr = errors.New("Linux ARP entry defined with invalid MAC address")
    65  )
    66  
    67  // ARPDescriptor teaches KVScheduler how to configure Linux ARP entries.
    68  type ARPDescriptor struct {
    69  	log       logging.Logger
    70  	l3Handler l3linuxcalls.NetlinkAPI
    71  	ifPlugin  ifplugin.API
    72  	nsPlugin  nsplugin.API
    73  	addrAlloc netalloc.AddressAllocator
    74  	scheduler kvs.KVScheduler
    75  
    76  	// parallelization of the Retrieve operation
    77  	goRoutinesCnt int
    78  }
    79  
    80  // NewARPDescriptor creates a new instance of the ARP descriptor.
    81  func NewARPDescriptor(
    82  	scheduler kvs.KVScheduler, ifPlugin ifplugin.API, nsPlugin nsplugin.API, addrAlloc netalloc.AddressAllocator,
    83  	l3Handler l3linuxcalls.NetlinkAPI, log logging.PluginLogger, goRoutinesCnt int) *kvs.KVDescriptor {
    84  
    85  	ctx := &ARPDescriptor{
    86  		scheduler:     scheduler,
    87  		l3Handler:     l3Handler,
    88  		ifPlugin:      ifPlugin,
    89  		nsPlugin:      nsPlugin,
    90  		addrAlloc:     addrAlloc,
    91  		goRoutinesCnt: goRoutinesCnt,
    92  		log:           log.NewLogger("arp-descriptor"),
    93  	}
    94  
    95  	typedDescr := &adapter.ARPDescriptor{
    96  		Name:            ARPDescriptorName,
    97  		NBKeyPrefix:     l3.ModelARPEntry.KeyPrefix(),
    98  		ValueTypeName:   l3.ModelARPEntry.ProtoName(),
    99  		KeySelector:     l3.ModelARPEntry.IsKeyValid,
   100  		KeyLabel:        l3.ModelARPEntry.StripKeyPrefix,
   101  		ValueComparator: ctx.EquivalentARPs,
   102  		Validate:        ctx.Validate,
   103  		Create:          ctx.Create,
   104  		Delete:          ctx.Delete,
   105  		Update:          ctx.Update,
   106  		Retrieve:        ctx.Retrieve,
   107  		Dependencies:    ctx.Dependencies,
   108  		RetrieveDependencies: []string{
   109  			netalloc_descr.IPAllocDescriptorName,
   110  			ifdescriptor.InterfaceDescriptorName},
   111  	}
   112  	return adapter.NewARPDescriptor(typedDescr)
   113  }
   114  
   115  // EquivalentARPs is case-insensitive comparison function for l3.LinuxARPEntry.
   116  // Only MAC addresses are compared - interface and IP address are part of the key
   117  // which is already given to be the same for the two values.
   118  func (d *ARPDescriptor) EquivalentARPs(key string, oldArp, NewArp *l3.ARPEntry) bool {
   119  	// compare MAC addresses case-insensitively
   120  	return strings.EqualFold(oldArp.HwAddress, NewArp.HwAddress)
   121  }
   122  
   123  // Validate validates ARP entry configuration.
   124  func (d *ARPDescriptor) Validate(key string, arp *l3.ARPEntry) (err error) {
   125  	if arp.Interface == "" {
   126  		return kvs.NewInvalidValueError(ErrARPWithoutInterface, "interface")
   127  	}
   128  	if arp.HwAddress == "" {
   129  		return kvs.NewInvalidValueError(ErrARPWithoutHwAddr, "hw_address")
   130  	}
   131  	return d.addrAlloc.ValidateIPAddress(arp.IpAddress, "", "ip_address", netalloc.GWRefAllowed)
   132  }
   133  
   134  // Create creates ARP entry.
   135  func (d *ARPDescriptor) Create(key string, arp *l3.ARPEntry) (metadata interface{}, err error) {
   136  	err = d.updateARPEntry(arp, "add", d.l3Handler.SetARPEntry)
   137  	return nil, err
   138  }
   139  
   140  // Delete removes ARP entry.
   141  func (d *ARPDescriptor) Delete(key string, arp *l3.ARPEntry, metadata interface{}) error {
   142  	return d.updateARPEntry(arp, "delete", d.l3Handler.DelARPEntry)
   143  }
   144  
   145  // Update is able to change MAC address of the ARP entry.
   146  func (d *ARPDescriptor) Update(key string, oldARP, newARP *l3.ARPEntry, oldMetadata interface{}) (newMetadata interface{}, err error) {
   147  	err = d.updateARPEntry(newARP, "modify", d.l3Handler.SetARPEntry)
   148  	return nil, err
   149  }
   150  
   151  // updateARPEntry adds, modifies or deletes an ARP entry.
   152  func (d *ARPDescriptor) updateARPEntry(arp *l3.ARPEntry, actionName string, actionClb func(arpEntry *netlink.Neigh) error) error {
   153  	var err error
   154  
   155  	// Prepare ARP entry object
   156  	neigh := &netlink.Neigh{}
   157  
   158  	// Get interface metadata
   159  	ifMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(arp.Interface)
   160  	if !found || ifMeta == nil {
   161  		err = errors.Errorf("failed to obtain metadata for interface %s", arp.Interface)
   162  		d.log.Error(err)
   163  		return err
   164  	}
   165  
   166  	// set link index
   167  	neigh.LinkIndex = ifMeta.LinuxIfIndex
   168  
   169  	// set IP address
   170  	ipAddr, err := d.addrAlloc.GetOrParseIPAddress(arp.IpAddress, "",
   171  		netalloc_api.IPAddressForm_ADDR_ONLY)
   172  	if err != nil {
   173  		d.log.Error(err)
   174  		return err
   175  	}
   176  	neigh.IP = ipAddr.IP
   177  
   178  	// set MAC address
   179  	mac, err := net.ParseMAC(arp.HwAddress)
   180  	if err != nil {
   181  		err = ErrARPWithInvalidHwAddr
   182  		d.log.Error(err)
   183  		return err
   184  	}
   185  	neigh.HardwareAddr = mac
   186  
   187  	// set ARP entry state (always permanent for static ARPs configured by the agent)
   188  	neigh.State = netlink.NUD_PERMANENT
   189  
   190  	// set ip family based on the IP address
   191  	if neigh.IP.To4() != nil {
   192  		neigh.Family = netlink.FAMILY_V4
   193  	} else {
   194  		neigh.Family = netlink.FAMILY_V6
   195  	}
   196  
   197  	// move to the namespace of the associated interface
   198  	nsCtx := nslinuxcalls.NewNamespaceMgmtCtx()
   199  	revertNs, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace)
   200  	if err != nil {
   201  		err = errors.Errorf("failed to switch namespace: %v", err)
   202  		d.log.Error(err)
   203  		return err
   204  	}
   205  	defer revertNs()
   206  
   207  	// update ARP entry in the interface namespace
   208  	err = actionClb(neigh)
   209  	if err != nil {
   210  		err = errors.Errorf("failed to %s linux ARP entry: %v", actionName, err)
   211  		d.log.Error(err)
   212  		return err
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // Dependencies lists dependencies for a Linux ARP entry.
   219  func (d *ARPDescriptor) Dependencies(key string, arp *l3.ARPEntry) (deps []kvs.Dependency) {
   220  	// the associated interface must exist, but also must be UP and have at least
   221  	// one IP address assigned (to be in the L3 mode)
   222  	if arp.Interface != "" {
   223  		deps = []kvs.Dependency{
   224  			{
   225  				Label: arpInterfaceDep,
   226  				Key:   ifmodel.InterfaceStateKey(arp.Interface, true),
   227  			},
   228  			{
   229  				Label: arpInterfaceIPDep,
   230  				AnyOf: kvs.AnyOfDependency{
   231  					KeyPrefixes: []string{ifmodel.InterfaceAddressPrefix(arp.Interface)},
   232  				},
   233  			},
   234  		}
   235  	}
   236  	// if IP is only a symlink to netalloc address pool, then wait for it to be allocated first
   237  	allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(arp.IpAddress, "", "")
   238  	if hasAllocDep {
   239  		deps = append(deps, allocDep)
   240  	}
   241  	return deps
   242  }
   243  
   244  // Retrieve returns all ARP entries associated with interfaces managed by this agent.
   245  func (d *ARPDescriptor) Retrieve(correlate []adapter.ARPKVWithMetadata) ([]adapter.ARPKVWithMetadata, error) {
   246  	var values []adapter.ARPKVWithMetadata
   247  
   248  	hwLabel := func(arp *l3.ARPEntry) string {
   249  		return arp.Interface + "/" + strings.ToLower(arp.HwAddress)
   250  	}
   251  	expCfg := make(map[string]*l3.ARPEntry) // Interface+MAC -> expected ARP config
   252  	for _, kv := range correlate {
   253  		expCfg[hwLabel(kv.Value)] = kv.Value
   254  	}
   255  
   256  	arpDetails, err := d.l3Handler.DumpARPEntries()
   257  	if err != nil {
   258  		return nil, errors.Errorf("Failed to retrieve linux ARPs: %v", err)
   259  	}
   260  
   261  	for _, arpDetail := range arpDetails {
   262  		// Convert to key-value object with metadata
   263  		arp := adapter.ARPKVWithMetadata{
   264  			Key: l3.ArpKey(arpDetail.ARP.Interface, arpDetail.ARP.IpAddress),
   265  			Value: &l3.ARPEntry{
   266  				Interface: arpDetail.ARP.Interface,
   267  				IpAddress: arpDetail.ARP.IpAddress,
   268  				HwAddress: arpDetail.ARP.HwAddress,
   269  			},
   270  			Origin: kvs.UnknownOrigin, // let the scheduler to determine the origin
   271  		}
   272  		if expCfg, hasExpCfg := expCfg[hwLabel(arp.Value)]; hasExpCfg {
   273  			arp.Value.IpAddress = d.addrAlloc.CorrelateRetrievedIPs(
   274  				[]string{expCfg.IpAddress}, []string{arp.Value.IpAddress},
   275  				"", netalloc_api.IPAddressForm_ADDR_ONLY)[0]
   276  			// recreate key in case the IP address was replaced with a netalloc link
   277  			arp.Key = l3.ArpKey(arp.Value.Interface, arp.Value.IpAddress)
   278  		}
   279  		values = append(values, arp)
   280  	}
   281  
   282  	return values, nil
   283  }