go.ligato.io/vpp-agent/v3@v3.5.0/plugins/vpp/ifplugin/descriptor/dhcp.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  	"context"
    19  	"strings"
    20  	"sync"
    21  
    22  	"github.com/go-errors/errors"
    23  	"go.ligato.io/cn-infra/v2/logging"
    24  	"google.golang.org/protobuf/proto"
    25  	"google.golang.org/protobuf/types/known/emptypb"
    26  
    27  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    28  	"go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/ifaceidx"
    29  	"go.ligato.io/vpp-agent/v3/plugins/vpp/ifplugin/vppcalls"
    30  	"go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    31  	interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces"
    32  )
    33  
    34  const (
    35  	// DHCPDescriptorName is the name of the descriptor configuring DHCP for VPP
    36  	// interfaces.
    37  	DHCPDescriptorName = "vpp-dhcp"
    38  )
    39  
    40  // DHCPDescriptor enables/disables DHCP for VPP interfaces and notifies about
    41  // new DHCP leases.
    42  type DHCPDescriptor struct {
    43  	// provided by the plugin
    44  	log         logging.Logger
    45  	ifHandler   vppcalls.InterfaceVppAPI
    46  	kvscheduler kvs.KVScheduler
    47  	ifIndex     ifaceidx.IfaceMetadataIndex
    48  
    49  	// DHCP notification watching
    50  	cancel context.CancelFunc
    51  	wg     sync.WaitGroup
    52  }
    53  
    54  // NewDHCPDescriptor creates a new instance of DHCPDescriptor.
    55  func NewDHCPDescriptor(kvscheduler kvs.KVScheduler, ifHandler vppcalls.InterfaceVppAPI,
    56  	ifIndex ifaceidx.IfaceMetadataIndex, log logging.PluginLogger,
    57  ) (*kvs.KVDescriptor, *DHCPDescriptor) {
    58  	ctx := &DHCPDescriptor{
    59  		kvscheduler: kvscheduler,
    60  		ifHandler:   ifHandler,
    61  		ifIndex:     ifIndex,
    62  		log:         log.NewLogger("dhcp-descriptor"),
    63  	}
    64  	descr := &kvs.KVDescriptor{
    65  		Name:                 DHCPDescriptorName,
    66  		KeySelector:          ctx.IsDHCPRelatedKey,
    67  		KeyLabel:             ctx.InterfaceNameFromKey,
    68  		WithMetadata:         true,              // DHCP leases
    69  		Create:               ctx.Create,        // DHCP client
    70  		Delete:               ctx.Delete,        // DHCP client
    71  		Retrieve:             ctx.Retrieve,      // DHCP leases
    72  		DerivedValues:        ctx.DerivedValues, // IP address from DHCP lease
    73  		RetrieveDependencies: []string{InterfaceDescriptorName},
    74  	}
    75  	return descr, ctx
    76  }
    77  
    78  // WatchDHCPNotifications starts watching for DHCP notifications.
    79  func (d *DHCPDescriptor) WatchDHCPNotifications(ctx context.Context) {
    80  	// Create child context
    81  	var childCtx context.Context
    82  	childCtx, d.cancel = context.WithCancel(ctx)
    83  
    84  	d.wg.Add(1)
    85  	go d.watchDHCPNotifications(childCtx)
    86  }
    87  
    88  // Close stops watching of DHCP notifications.
    89  func (d *DHCPDescriptor) Close() error {
    90  	d.cancel()
    91  	d.wg.Wait()
    92  	return nil
    93  }
    94  
    95  // IsDHCPRelatedKey returns true if the key is identifying DHCP client (derived value)
    96  // or DHCP lease (notification).
    97  func (d *DHCPDescriptor) IsDHCPRelatedKey(key string) bool {
    98  	if _, isValid := interfaces.ParseNameFromDHCPClientKey(key); isValid {
    99  		return true
   100  	}
   101  	if _, isValid := interfaces.ParseNameFromDHCPLeaseKey(key); isValid {
   102  		return true
   103  	}
   104  	return false
   105  }
   106  
   107  // InterfaceNameFromKey returns interface name from DHCP-related key.
   108  func (d *DHCPDescriptor) InterfaceNameFromKey(key string) string {
   109  	if iface, isValid := interfaces.ParseNameFromDHCPClientKey(key); isValid {
   110  		return iface
   111  	}
   112  	if iface, isValid := interfaces.ParseNameFromDHCPLeaseKey(key); isValid {
   113  		return iface
   114  	}
   115  	return key
   116  }
   117  
   118  // Create enables DHCP client.
   119  func (d *DHCPDescriptor) Create(key string, emptyVal proto.Message) (metadata kvs.Metadata, err error) {
   120  	ifName, _ := interfaces.ParseNameFromDHCPClientKey(key)
   121  	ifMeta, found := d.ifIndex.LookupByName(ifName)
   122  	if !found {
   123  		err = errors.Errorf("failed to find DHCP-enabled interface %s", ifName)
   124  		d.log.Error(err)
   125  		return nil, err
   126  	}
   127  
   128  	if err := d.ifHandler.SetInterfaceAsDHCPClient(ifMeta.SwIfIndex, ifName); err != nil {
   129  		err = errors.Errorf("failed to enable DHCP client for interface %s", ifName)
   130  		d.log.Error(err)
   131  		return nil, err
   132  	}
   133  
   134  	return nil, err
   135  }
   136  
   137  // Delete disables DHCP client.
   138  func (d *DHCPDescriptor) Delete(key string, emptyVal proto.Message, metadata kvs.Metadata) error {
   139  	ifName, _ := interfaces.ParseNameFromDHCPClientKey(key)
   140  	ifMeta, found := d.ifIndex.LookupByName(ifName)
   141  	if !found {
   142  		err := errors.Errorf("failed to find DHCP-enabled interface %s", ifName)
   143  		d.log.Error(err)
   144  		return err
   145  	}
   146  
   147  	if err := d.ifHandler.UnsetInterfaceAsDHCPClient(ifMeta.SwIfIndex, ifName); err != nil {
   148  		err = errors.Errorf("failed to disable DHCP client for interface %s", ifName)
   149  		d.log.Error(err)
   150  		return err
   151  	}
   152  
   153  	// notify about the unconfigured client by removing the lease notification
   154  	return d.kvscheduler.PushSBNotification(kvs.KVWithMetadata{
   155  		Key:      interfaces.DHCPLeaseKey(ifName),
   156  		Value:    nil,
   157  		Metadata: nil,
   158  	})
   159  }
   160  
   161  // Retrieve returns all existing DHCP leases.
   162  func (d *DHCPDescriptor) Retrieve(correlate []kvs.KVWithMetadata) (
   163  	leases []kvs.KVWithMetadata, err error,
   164  ) {
   165  	// Retrieve VPP configuration.
   166  	dhcpDump, err := d.ifHandler.DumpDhcpClients()
   167  	if err != nil {
   168  		d.log.Error(err)
   169  		return leases, err
   170  	}
   171  
   172  	for ifIdx, dhcpData := range dhcpDump {
   173  		ifName, _, found := d.ifIndex.LookupBySwIfIndex(ifIdx)
   174  		if !found {
   175  			d.log.Warnf("failed to find interface sw_if_index=%d with DHCP lease", ifIdx)
   176  			return leases, err
   177  		}
   178  		// Store lease under both value (for visibility & to derive interface IP address)
   179  		// and metadata (for watching).
   180  		lease := &interfaces.DHCPLease{
   181  			InterfaceName:   ifName,
   182  			HostName:        dhcpData.Lease.Hostname,
   183  			HostPhysAddress: dhcpData.Lease.HostMac,
   184  			IsIpv6:          dhcpData.Lease.IsIPv6,
   185  			HostIpAddress:   dhcpData.Lease.HostAddress,
   186  			RouterIpAddress: dhcpData.Lease.RouterAddress,
   187  		}
   188  		leases = append(leases, kvs.KVWithMetadata{
   189  			Key:      interfaces.DHCPLeaseKey(ifName),
   190  			Value:    lease,
   191  			Metadata: lease,
   192  			Origin:   kvs.FromSB,
   193  		})
   194  	}
   195  
   196  	return leases, nil
   197  }
   198  
   199  // DerivedValues derives empty value for leased IP address.
   200  func (d *DHCPDescriptor) DerivedValues(key string, dhcpData proto.Message) (derValues []kvs.KeyValuePair) {
   201  	if strings.HasPrefix(key, interfaces.DHCPLeaseKeyPrefix) {
   202  		dhcpLease, ok := dhcpData.(*interfaces.DHCPLease)
   203  		if ok && dhcpLease.HostIpAddress != "" {
   204  			return []kvs.KeyValuePair{
   205  				{
   206  					Key: interfaces.InterfaceAddressKey(dhcpLease.InterfaceName, dhcpLease.HostIpAddress,
   207  						netalloc.IPAddressSource_FROM_DHCP),
   208  					Value: &emptypb.Empty{},
   209  				},
   210  			}
   211  		}
   212  	}
   213  	return derValues
   214  }
   215  
   216  // watchDHCPNotifications watches and processes DHCP notifications.
   217  func (d *DHCPDescriptor) watchDHCPNotifications(ctx context.Context) {
   218  	defer d.wg.Done()
   219  	d.log.Debug("Started watcher on DHCP notifications")
   220  
   221  	dhcpChan := make(chan *vppcalls.Lease, 10)
   222  	if err := d.ifHandler.WatchDHCPLeases(ctx, dhcpChan); err != nil {
   223  		d.log.Errorf("watching dhcp leases failed: %v", err)
   224  		return
   225  	}
   226  
   227  	for {
   228  		select {
   229  		case lease := <-dhcpChan:
   230  			// Get interface logical name
   231  			ifName, _, found := d.ifIndex.LookupBySwIfIndex(lease.SwIfIndex)
   232  			if !found {
   233  				d.log.Warnf("Interface sw_if_index=%d with DHCP lease was not found in the mapping", lease.SwIfIndex)
   234  				continue
   235  			}
   236  
   237  			d.log.Debugf("DHCP assigned %v to interface %q (router address %v)", lease.HostAddress, ifName, lease.RouterAddress)
   238  
   239  			// notify about the new lease
   240  			dhcpLease := &interfaces.DHCPLease{
   241  				InterfaceName:   ifName,
   242  				HostName:        lease.Hostname,
   243  				HostPhysAddress: lease.HostMac,
   244  				HostIpAddress:   lease.HostAddress,
   245  				RouterIpAddress: lease.RouterAddress,
   246  			}
   247  			if err := d.kvscheduler.PushSBNotification(kvs.KVWithMetadata{
   248  				Key:      interfaces.DHCPLeaseKey(ifName),
   249  				Value:    dhcpLease,
   250  				Metadata: dhcpLease,
   251  			}); err != nil {
   252  				d.log.Error(err)
   253  			}
   254  		case <-ctx.Done():
   255  			return
   256  		}
   257  	}
   258  }