go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/ifplugin/descriptor/watcher.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  	"fmt"
    20  	"net"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/vishvananda/netlink"
    27  	"go.ligato.io/cn-infra/v2/logging"
    28  	"google.golang.org/protobuf/proto"
    29  	"google.golang.org/protobuf/types/known/emptypb"
    30  
    31  	kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api"
    32  	"go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/linuxcalls"
    33  	ifmodel "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces"
    34  )
    35  
    36  const (
    37  	// InterfaceWatcherName is the name of the descriptor watching Linux interfaces
    38  	// in the default namespace.
    39  	InterfaceWatcherName = "linux-interface-watcher"
    40  
    41  	// Interfaces go down for very very short time (typically <1ms) when changes
    42  	// are being made and we do not want to react to those (would trigger re-creation
    43  	// of everything that depends on it).
    44  	// When interface is found to be UP we react immediately so that other objects
    45  	// that depend on it are created ASAP (e.g. af-packet), but we afford to delay
    46  	// actions that follow from interface going down. The exception is when watched
    47  	// interface is completely removed -- in that case we should react immediately,
    48  	// because for example even only few packets sent over af-packet attached to
    49  	// a removed interface might "break" VPP.
    50  	linkDownDelay = 10 * time.Millisecond
    51  )
    52  
    53  // InterfaceWatcher watches default namespace for newly added/removed Linux interfaces.
    54  type InterfaceWatcher struct {
    55  	// input arguments
    56  	log         logging.Logger
    57  	kvscheduler kvs.KVScheduler
    58  	ifHandler   linuxcalls.NetlinkAPIRead
    59  
    60  	// go routine management
    61  	ctx    context.Context
    62  	cancel context.CancelFunc
    63  	wg     sync.WaitGroup
    64  
    65  	// a set of interfaces present in the default namespace
    66  	ifacesMu sync.Mutex
    67  	ifaces   map[string]hostInterface
    68  
    69  	// conditional variable to check if the list of interfaces is in-sync with
    70  	// Linux network stack
    71  	intfsInSync     bool
    72  	intfsInSyncCond *sync.Cond
    73  
    74  	// Linux notifications
    75  	linkNotifCount     uint64 // counts link notifications across all interfaces
    76  	linkNotifCh        chan netlink.LinkUpdate
    77  	addrNotifCh        chan netlink.AddrUpdate
    78  	delayedLinkNotifCh chan linkNotif
    79  	doneCh             chan struct{}
    80  	notify             func(notification *ifmodel.InterfaceNotification)
    81  }
    82  
    83  type hostInterface struct {
    84  	name    string
    85  	linkRev uint64
    86  	enabled bool
    87  	ipAddrs []string
    88  	vrfName string
    89  }
    90  
    91  type linkNotif struct {
    92  	netlink.LinkUpdate
    93  	rev     uint64
    94  	delayed bool
    95  }
    96  
    97  // NewInterfaceWatcher creates a new instance of the Interface Watcher.
    98  func NewInterfaceWatcher(kvscheduler kvs.KVScheduler, ifHandler linuxcalls.NetlinkAPI, notifyInterface func(*ifmodel.InterfaceNotification), log logging.PluginLogger) *InterfaceWatcher {
    99  	descriptor := &InterfaceWatcher{
   100  		log:                log.NewLogger("if-watcher"),
   101  		kvscheduler:        kvscheduler,
   102  		ifHandler:          ifHandler,
   103  		notify:             notifyInterface,
   104  		ifaces:             make(map[string]hostInterface),
   105  		linkNotifCh:        make(chan netlink.LinkUpdate),
   106  		addrNotifCh:        make(chan netlink.AddrUpdate),
   107  		delayedLinkNotifCh: make(chan linkNotif, 100),
   108  		doneCh:             make(chan struct{}),
   109  	}
   110  	descriptor.intfsInSyncCond = sync.NewCond(&descriptor.ifacesMu)
   111  	descriptor.ctx, descriptor.cancel = context.WithCancel(context.Background())
   112  
   113  	return descriptor
   114  }
   115  
   116  // GetDescriptor returns descriptor suitable for registration with the KVScheduler.
   117  func (w *InterfaceWatcher) GetDescriptor() *kvs.KVDescriptor {
   118  	return &kvs.KVDescriptor{
   119  		Name:        InterfaceWatcherName,
   120  		KeySelector: w.IsLinuxInterfaceNotification,
   121  		Retrieve:    w.Retrieve,
   122  	}
   123  }
   124  
   125  // IsLinuxInterfaceNotification returns <true> for keys representing
   126  // notifications about Linux interfaces in the default network namespace.
   127  func (w *InterfaceWatcher) IsLinuxInterfaceNotification(key string) bool {
   128  	return strings.HasPrefix(key, ifmodel.InterfaceHostNameKeyPrefix)
   129  }
   130  
   131  // Retrieve returns key with empty value for every currently existing Linux interface
   132  // in the default network namespace.
   133  func (w *InterfaceWatcher) Retrieve(correlate []kvs.KVWithMetadata) (values []kvs.KVWithMetadata, err error) {
   134  	// wait until the set of interfaces is in-sync with the Linux network stack
   135  	w.ifacesMu.Lock()
   136  	if !w.intfsInSync {
   137  		w.intfsInSyncCond.Wait()
   138  	}
   139  	defer w.ifacesMu.Unlock()
   140  
   141  	for _, hostIface := range w.ifaces {
   142  		if !hostIface.enabled {
   143  			continue
   144  		}
   145  		values = append(values, kvs.KVWithMetadata{
   146  			Key:    ifmodel.InterfaceHostNameKey(hostIface.name),
   147  			Value:  &emptypb.Empty{},
   148  			Origin: kvs.FromSB,
   149  		})
   150  		for _, ipAddr := range hostIface.ipAddrs {
   151  			values = append(values, kvs.KVWithMetadata{
   152  				Key:    ifmodel.InterfaceHostNameWithAddrKey(hostIface.name, ipAddr),
   153  				Value:  &emptypb.Empty{},
   154  				Origin: kvs.FromSB,
   155  			})
   156  		}
   157  		if hostIface.vrfName != "" {
   158  			values = append(values, kvs.KVWithMetadata{
   159  				Key:    ifmodel.InterfaceHostNameWithVrfKey(hostIface.name, hostIface.vrfName),
   160  				Value:  &emptypb.Empty{},
   161  				Origin: kvs.FromSB,
   162  			})
   163  		}
   164  	}
   165  
   166  	return values, nil
   167  }
   168  
   169  // StartWatching starts interface watching.
   170  func (w *InterfaceWatcher) StartWatching() error {
   171  	// watch default namespace to be aware of interfaces not created by this plugin
   172  	err := w.ifHandler.LinkSubscribe(w.linkNotifCh, w.doneCh)
   173  	if err != nil {
   174  		err = errors.Errorf("failed to subscribe for link notifications: %v", err)
   175  		w.log.Error(err)
   176  		return err
   177  	}
   178  	err = w.ifHandler.AddrSubscribe(w.addrNotifCh, w.doneCh)
   179  	if err != nil {
   180  		err = errors.Errorf("failed to subscribe for address notifications: %v", err)
   181  		w.log.Error(err)
   182  		return err
   183  	}
   184  	w.wg.Add(1)
   185  	go w.watchDefaultNamespace()
   186  	return nil
   187  }
   188  
   189  // StopWatching stops interface watching.
   190  func (w *InterfaceWatcher) StopWatching() {
   191  	w.cancel()
   192  	w.wg.Wait()
   193  }
   194  
   195  // watchDefaultNamespace watches for notification about added/removed interfaces/addresses
   196  // to/from the default namespace.
   197  func (w *InterfaceWatcher) watchDefaultNamespace() {
   198  	defer w.wg.Done()
   199  
   200  	// get the set of interfaces already available in the default namespace
   201  	links, err := w.ifHandler.GetLinkList()
   202  	if err == nil {
   203  		for _, link := range links {
   204  			iface := hostInterface{name: link.Attrs().Name}
   205  			enabled, err := w.ifHandler.IsInterfaceUp(iface.name)
   206  			if err != nil {
   207  				w.log.Warnf("IsInterfaceUp failed for interface %s: %v",
   208  					iface.name, err)
   209  				continue
   210  			}
   211  			iface.enabled = enabled
   212  			if addrs, err := w.ifHandler.GetAddressList(iface.name); err == nil {
   213  				for _, addr := range addrs {
   214  					iface.ipAddrs = append(iface.ipAddrs, addr.IPNet.String())
   215  				}
   216  			} else {
   217  				w.log.Warnf("GetAddressList failed for interface %s: %v",
   218  					iface.name, err)
   219  			}
   220  			iface.vrfName, err = w.getVrfName(link)
   221  			if err != nil {
   222  				w.log.Warn(err)
   223  			}
   224  			w.ifaces[iface.name] = iface
   225  		}
   226  	} else {
   227  		w.log.Warnf("failed to list interfaces in the default namespace: %v", err)
   228  	}
   229  
   230  	// mark the state in-sync with the Linux network stack
   231  	w.ifacesMu.Lock()
   232  	w.intfsInSync = true
   233  	w.ifacesMu.Unlock()
   234  	w.intfsInSyncCond.Broadcast()
   235  
   236  	for {
   237  		select {
   238  		case notif := <-w.linkNotifCh:
   239  			w.linkNotifCount++
   240  			w.processLinkNotification(linkNotif{
   241  				rev:        w.linkNotifCount,
   242  				LinkUpdate: notif,
   243  			})
   244  		case notif := <-w.delayedLinkNotifCh:
   245  			w.processLinkNotification(notif)
   246  		case notif := <-w.addrNotifCh:
   247  			w.processAddrNotification(notif)
   248  		case <-w.ctx.Done():
   249  			close(w.doneCh)
   250  			return
   251  		}
   252  	}
   253  }
   254  
   255  // processLinkNotification processes link notification received from Linux.
   256  func (w *InterfaceWatcher) processLinkNotification(linkNotif linkNotif) {
   257  	var err error
   258  	w.ifacesMu.Lock()
   259  	defer w.ifacesMu.Unlock()
   260  
   261  	ifName := linkNotif.Attrs().Name
   262  	exists, _ := w.ifHandler.InterfaceExists(ifName)
   263  	if !linkNotif.delayed && exists && !isLinkUp(linkNotif.LinkUpdate) {
   264  		// do not react to interface being DOWN immediately, this could be only very temporary
   265  		linkNotif.delayed = true
   266  		time.AfterFunc(linkDownDelay, func() { w.delayedLinkNotifCh <- linkNotif })
   267  		return
   268  	}
   269  
   270  	// send notification to any interface state watcher (e.g. Configurator)
   271  	w.sendStateNotification(linkNotif.LinkUpdate)
   272  
   273  	// push update to the KV Scheduler
   274  	prevState := w.ifaces[ifName]
   275  	if prevState.linkRev > linkNotif.rev {
   276  		// newer notification received in the meantime
   277  		return
   278  	}
   279  	newState := prevState
   280  	newState.name = ifName
   281  	newState.linkRev = linkNotif.rev
   282  	newState.enabled = isLinkUp(linkNotif.LinkUpdate)
   283  	newState.vrfName, err = w.getVrfName(linkNotif.Link)
   284  	if err != nil {
   285  		w.log.Warn(err)
   286  	}
   287  	if prevState.enabled != newState.enabled {
   288  		w.updateLinkKV(ifName, newState.enabled)
   289  		// do not advertise IPs and VRF if interface is disabled
   290  		for _, ipAddr := range newState.ipAddrs {
   291  			w.updateAddrKV(ifName, ipAddr, !newState.enabled)
   292  		}
   293  		if newState.enabled {
   294  			w.updateVrfKV(ifName, newState.vrfName, false)
   295  		} else {
   296  			w.updateVrfKV(ifName, prevState.vrfName, true)
   297  		}
   298  	} else if prevState.vrfName != newState.vrfName {
   299  		w.updateVrfKV(ifName, prevState.vrfName, true)
   300  		w.updateVrfKV(ifName, newState.vrfName, false)
   301  	}
   302  	w.ifaces[ifName] = newState
   303  }
   304  
   305  // processAddrNotification processes address notification received from Linux.
   306  func (w *InterfaceWatcher) processAddrNotification(addrUpdate netlink.AddrUpdate) {
   307  	w.ifacesMu.Lock()
   308  	defer w.ifacesMu.Unlock()
   309  
   310  	link, err := w.ifHandler.GetLinkByIndex(addrUpdate.LinkIndex)
   311  	if err != nil {
   312  		w.log.Warn(err)
   313  		return
   314  	}
   315  
   316  	// push update to the KV Scheduler
   317  	ifName := link.Attrs().Name
   318  	addr := addrUpdate.LinkAddress.String()
   319  	removed := !addrUpdate.NewAddr
   320  	prevState := w.ifaces[ifName]
   321  	addrIdx := -1
   322  	for i, ipAddr := range prevState.ipAddrs {
   323  		if ipAddr == addr {
   324  			addrIdx = i
   325  			break
   326  		}
   327  	}
   328  	knownAddr := addrIdx != -1
   329  	if knownAddr != removed {
   330  		// removed unknown IP or added already known IP
   331  		return
   332  	}
   333  	if prevState.enabled {
   334  		w.updateAddrKV(ifName, addr, removed)
   335  	}
   336  
   337  	// update the internal cache
   338  	newState := prevState
   339  	newState.name = ifName
   340  	if removed {
   341  		lastIdx := len(newState.ipAddrs) - 1
   342  		newState.ipAddrs[addrIdx] = newState.ipAddrs[lastIdx]
   343  		newState.ipAddrs[lastIdx] = ""
   344  		newState.ipAddrs = newState.ipAddrs[:lastIdx]
   345  	} else {
   346  		newState.ipAddrs = append(newState.ipAddrs, addr)
   347  	}
   348  	w.ifaces[ifName] = newState
   349  }
   350  
   351  func linkToInterfaceType(link netlink.Link) ifmodel.Interface_Type {
   352  	switch link.Type() {
   353  	case "veth":
   354  		return ifmodel.Interface_VETH
   355  	case "tuntap", "tun":
   356  		return ifmodel.Interface_TAP_TO_VPP
   357  	case "vrf":
   358  		return ifmodel.Interface_VRF_DEVICE
   359  	case "dummy":
   360  		return ifmodel.Interface_DUMMY
   361  	default:
   362  		if link.Attrs().Name == linuxcalls.DefaultLoopbackName {
   363  			return ifmodel.Interface_LOOPBACK
   364  		}
   365  		return ifmodel.Interface_UNDEFINED
   366  	}
   367  }
   368  
   369  // updateLinkKV updates key-value pair representing the interface latest link status.
   370  func (w *InterfaceWatcher) updateLinkKV(ifName string, enabled bool) {
   371  	var value proto.Message
   372  	if enabled {
   373  		// empty == enabled, nil == disabled
   374  		value = &emptypb.Empty{}
   375  	}
   376  	if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{
   377  		Key:      ifmodel.InterfaceHostNameKey(ifName),
   378  		Value:    value,
   379  		Metadata: nil,
   380  	}); err != nil {
   381  		w.log.Warnf("pushing SB notification failed: %v", err)
   382  	}
   383  }
   384  
   385  // updateAddrKV updates key-value pair representing IP address assigned to a host interface.
   386  func (w *InterfaceWatcher) updateAddrKV(ifName string, address string, removed bool) {
   387  	var value proto.Message
   388  	if !removed {
   389  		// empty == assigned, nil == not assigned
   390  		value = &emptypb.Empty{}
   391  	}
   392  	if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{
   393  		Key:      ifmodel.InterfaceHostNameWithAddrKey(ifName, address),
   394  		Value:    value,
   395  		Metadata: nil,
   396  	}); err != nil {
   397  		w.log.Warnf("pushing SB notification failed: %v", err)
   398  	}
   399  }
   400  
   401  // updateAddrKV updates key-value pair representing association between interface and VRF.
   402  func (w *InterfaceWatcher) updateVrfKV(ifName string, vrf string, removed bool) {
   403  	var value proto.Message
   404  	if vrf == "" {
   405  		return
   406  	}
   407  	if !removed {
   408  		// empty == assigned, nil == not assigned
   409  		value = &emptypb.Empty{}
   410  	}
   411  	if err := w.kvscheduler.PushSBNotification(kvs.KVWithMetadata{
   412  		Key:      ifmodel.InterfaceHostNameWithVrfKey(ifName, vrf),
   413  		Value:    value,
   414  		Metadata: nil,
   415  	}); err != nil {
   416  		w.log.Warnf("pushing SB notification failed: %v", err)
   417  	}
   418  }
   419  
   420  func (w *InterfaceWatcher) sendStateNotification(linkUpdate netlink.LinkUpdate) {
   421  	if w.notify != nil {
   422  		attrs := linkUpdate.Attrs()
   423  		adminStatus := ifmodel.InterfaceState_DOWN
   424  		if isLinkUp(linkUpdate) {
   425  			adminStatus = ifmodel.InterfaceState_UP
   426  		}
   427  		operStatus := ifmodel.InterfaceState_DOWN
   428  		if attrs.OperState != netlink.OperDown && attrs.OperState != netlink.OperNotPresent {
   429  			operStatus = ifmodel.InterfaceState_UP
   430  		}
   431  		w.notify(&ifmodel.InterfaceNotification{
   432  			Type: ifmodel.InterfaceNotification_UPDOWN,
   433  			State: &ifmodel.InterfaceState{
   434  				Name:         attrs.Alias,
   435  				InternalName: attrs.Name,
   436  				Type:         linkToInterfaceType(linkUpdate.Link),
   437  				IfIndex:      int32(attrs.Index),
   438  				AdminStatus:  adminStatus,
   439  				OperStatus:   operStatus,
   440  				LastChange:   time.Now().Unix(),
   441  				PhysAddress:  attrs.HardwareAddr.String(),
   442  				Speed:        0,
   443  				Mtu:          uint32(attrs.MTU),
   444  				Statistics:   nil,
   445  			},
   446  		})
   447  	}
   448  }
   449  
   450  func (w *InterfaceWatcher) getVrfName(link netlink.Link) (string, error) {
   451  	masterIndex := link.Attrs().MasterIndex
   452  	if masterIndex != 0 {
   453  		vrfLink, err := w.ifHandler.GetLinkByIndex(masterIndex)
   454  		if err != nil {
   455  			err = fmt.Errorf("GetLinkByIndex failed for master interface with index %d: %w",
   456  				masterIndex, err)
   457  			return "", err
   458  		}
   459  		if vrfDev, isVrf := vrfLink.(*netlink.Vrf); isVrf {
   460  			return vrfDev.Name, nil
   461  		}
   462  	}
   463  	return "", nil
   464  }
   465  
   466  func isLinkUp(update netlink.LinkUpdate) bool {
   467  	return (update.Attrs().Flags & net.FlagUp) == net.FlagUp
   468  }