go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.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  //go:build !windows && !darwin
    16  
    17  package linuxcalls
    18  
    19  import (
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/vishvananda/netlink"
    24  	"go.ligato.io/cn-infra/v2/logging"
    25  	"golang.org/x/sys/unix"
    26  	"google.golang.org/protobuf/proto"
    27  
    28  	"go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls"
    29  	interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces"
    30  	namespaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace"
    31  )
    32  
    33  const (
    34  	// defaultLoopbackName is the name used to access loopback interface in linux
    35  	// host_if_name field in config is effectively ignored
    36  	DefaultLoopbackName = "lo"
    37  
    38  	// minimum number of namespaces to be given to a single Go routine for processing
    39  	// in the Retrieve operation
    40  	minWorkForGoRoutine = 3
    41  )
    42  
    43  // retrievedIfaces is used as the return value sent via channel by retrieveInterfaces().
    44  type retrievedInterfaces struct {
    45  	interfaces []*InterfaceDetails
    46  	stats      []*InterfaceStatistics
    47  	err        error
    48  }
    49  
    50  // DumpInterfaces retrieves all linux interfaces from default namespace and from all
    51  // the other namespaces based on known linux interfaces from the index map.
    52  func (h *NetLinkHandler) DumpInterfaces() ([]*InterfaceDetails, error) {
    53  	return h.DumpInterfacesFromNamespaces(h.getKnownNamespaces())
    54  }
    55  
    56  // DumpInterfaceStats retrieves statistics for all linux interfaces from default namespace
    57  // and from all the other namespaces based on known linux interfaces from the index map.
    58  func (h *NetLinkHandler) DumpInterfaceStats() ([]*InterfaceStatistics, error) {
    59  	return h.DumpInterfaceStatsFromNamespaces(h.getKnownNamespaces())
    60  }
    61  
    62  // DumpInterfacesFromNamespaces requires context in form of the namespace list of which linux interfaces
    63  // will be retrieved. If no context is provided, interfaces only from the default namespace are retrieved.
    64  func (h *NetLinkHandler) DumpInterfacesFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceDetails, error) {
    65  	// Always retrieve from the default namespace
    66  	if len(nsList) == 0 {
    67  		nsList = []*namespaces.NetNamespace{nil}
    68  	}
    69  	// Determine the number of go routines to invoke
    70  	goRoutinesCnt := len(nsList) / minWorkForGoRoutine
    71  	if goRoutinesCnt == 0 {
    72  		goRoutinesCnt = 1
    73  	}
    74  	if goRoutinesCnt > h.goRoutineCount {
    75  		goRoutinesCnt = h.goRoutineCount
    76  	}
    77  	ch := make(chan retrievedInterfaces, goRoutinesCnt)
    78  
    79  	// Invoke multiple go routines for more efficient parallel interface retrieval
    80  	for idx := 0; idx < goRoutinesCnt; idx++ {
    81  		if goRoutinesCnt > 1 {
    82  			go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
    83  		} else {
    84  			h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
    85  		}
    86  	}
    87  
    88  	// receive results from the go routines
    89  	var linuxIfs []*InterfaceDetails
    90  	for idx := 0; idx < goRoutinesCnt; idx++ {
    91  		retrieved := <-ch
    92  		if retrieved.err != nil {
    93  			return nil, retrieved.err
    94  		}
    95  		linuxIfs = append(linuxIfs, retrieved.interfaces...)
    96  	}
    97  	return linuxIfs, nil
    98  }
    99  
   100  // DumpInterfaceStatsFromNamespaces requires context in form of the namespace list of which linux interface stats
   101  // will be retrieved. If no context is provided, interface stats only from the default namespace interfaces
   102  // are retrieved.
   103  func (h *NetLinkHandler) DumpInterfaceStatsFromNamespaces(nsList []*namespaces.NetNamespace) ([]*InterfaceStatistics, error) {
   104  	// Always retrieve from the default namespace
   105  	if len(nsList) == 0 {
   106  		nsList = []*namespaces.NetNamespace{nil}
   107  	}
   108  	// Determine the number of go routines to invoke
   109  	goRoutinesCnt := len(nsList) / minWorkForGoRoutine
   110  	if goRoutinesCnt == 0 {
   111  		goRoutinesCnt = 1
   112  	}
   113  	if goRoutinesCnt > h.goRoutineCount {
   114  		goRoutinesCnt = h.goRoutineCount
   115  	}
   116  	ch := make(chan retrievedInterfaces, goRoutinesCnt)
   117  
   118  	// Invoke multiple go routines for more efficient parallel interface retrieval
   119  	for idx := 0; idx < goRoutinesCnt; idx++ {
   120  		if goRoutinesCnt > 1 {
   121  			go h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
   122  		} else {
   123  			h.retrieveInterfaces(nsList, idx, goRoutinesCnt, ch)
   124  		}
   125  	}
   126  
   127  	// receive results from the go routines
   128  	var linuxStats []*InterfaceStatistics
   129  	for idx := 0; idx < goRoutinesCnt; idx++ {
   130  		retrieved := <-ch
   131  		if retrieved.err != nil {
   132  			return nil, retrieved.err
   133  		}
   134  		linuxStats = append(linuxStats, retrieved.stats...)
   135  	}
   136  	return linuxStats, nil
   137  }
   138  
   139  // Obtain all linux namespaces known to the Linux plugin
   140  func (h *NetLinkHandler) getKnownNamespaces() []*namespaces.NetNamespace {
   141  	// Add default namespace
   142  	nsList := []*namespaces.NetNamespace{nil}
   143  	for _, ifName := range h.ifIndexes.ListAllInterfaces() {
   144  		if metadata, exists := h.ifIndexes.LookupByName(ifName); exists {
   145  			if metadata == nil {
   146  				h.log.Warnf("metadata for %s are nil", ifName)
   147  				continue
   148  			}
   149  			nsListed := false
   150  			for _, ns := range nsList {
   151  				if proto.Equal(ns, metadata.Namespace) {
   152  					nsListed = true
   153  					break
   154  				}
   155  			}
   156  			if !nsListed {
   157  				nsList = append(nsList, metadata.Namespace)
   158  			}
   159  		}
   160  	}
   161  	return nsList
   162  }
   163  
   164  // GetVethAlias returns alias for Linux VETH interface managed by the agent.
   165  // The alias stores the VETH logical name together with the peer (logical) name.
   166  func GetVethAlias(vethName, peerName string) string {
   167  	return vethName + "/" + peerName
   168  }
   169  
   170  // ParseVethAlias parses out VETH logical name together with the peer name from the alias.
   171  func ParseVethAlias(alias string) (vethName, peerName string) {
   172  	aliasParts := strings.Split(alias, "/")
   173  	vethName = aliasParts[0]
   174  	if len(aliasParts) > 1 {
   175  		peerName = aliasParts[1]
   176  	}
   177  	return
   178  }
   179  
   180  // GetTapAlias returns alias for Linux TAP interface managed by the agent.
   181  // The alias stores the TAP_TO_VPP logical name together with VPP-TAP logical name
   182  // and the host interface name as originally set by VPP side.
   183  func GetTapAlias(linuxIf *interfaces.Interface, origHostIfName string) string {
   184  	return linuxIf.Name + "/" + linuxIf.GetTap().GetVppTapIfName() + "/" + origHostIfName
   185  }
   186  
   187  // ParseTapAlias parses out TAP_TO_VPP logical name together with the name of the
   188  // linked VPP-TAP and the original TAP host interface name.
   189  func ParseTapAlias(alias string) (linuxTapName, vppTapName, origHostIfName string) {
   190  	aliasParts := strings.Split(alias, "/")
   191  	linuxTapName = aliasParts[0]
   192  	if len(aliasParts) > 1 {
   193  		vppTapName = aliasParts[1]
   194  	}
   195  	if len(aliasParts) > 2 {
   196  		origHostIfName = aliasParts[2]
   197  	}
   198  	return
   199  }
   200  
   201  // GetDummyIfAlias returns alias for Linux Dummy interface managed by the agent.
   202  func GetDummyIfAlias(linuxIf *interfaces.Interface) string {
   203  	return linuxIf.Name
   204  }
   205  
   206  // ParseDummyIfAlias parses out logical name of a Dummy interface from the alias.
   207  // Currently there are no other logical information stored in the alias so it is very straightforward.
   208  func ParseDummyIfAlias(alias string) (ifName string) {
   209  	return alias
   210  }
   211  
   212  // GetVRFAlias returns alias for Linux VRF devices managed by the agent.
   213  func GetVRFAlias(linuxIf *interfaces.Interface) string {
   214  	return linuxIf.Name
   215  }
   216  
   217  // ParseVRFAlias parses out logical name of a VRF devices from the alias.
   218  // Currently there are no other logical information stored in the alias so it is very straightforward.
   219  func ParseVRFAlias(alias string) (vrfName string) {
   220  	return alias
   221  }
   222  
   223  // retrieveInterfaces is run by a separate go routine to retrieve all interfaces
   224  // present in every <goRoutineIdx>-th network namespace from the list.
   225  func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedInterfaces) {
   226  	var retrieved retrievedInterfaces
   227  
   228  	nsCtx := linuxcalls.NewNamespaceMgmtCtx()
   229  	for i := goRoutineIdx; i < len(nsList); i += goRoutinesCnt {
   230  		nsRef := nsList[i]
   231  		// switch to the namespace
   232  		revert, err := h.nsPlugin.SwitchToNamespace(nsCtx, nsRef)
   233  		if err != nil {
   234  			h.log.WithField("namespace", nsRef).Warn("Failed to switch namespace:", err)
   235  			continue // continue with the next namespace
   236  		}
   237  
   238  		// get all links in the namespace
   239  		links, err := h.GetLinkList()
   240  		if err != nil {
   241  			h.log.Error("Failed to get link list:", err)
   242  			// switch back to the default namespace before returning error
   243  			revert()
   244  			retrieved.err = err
   245  			break
   246  		}
   247  
   248  		// retrieve every interface managed by this agent
   249  		var ifaces []*InterfaceDetails
   250  		vrfDevs := make(map[int]string) // vrf index -> vrf name
   251  		for _, link := range links {
   252  			iface := &interfaces.Interface{
   253  				Namespace:   nsRef,
   254  				HostIfName:  link.Attrs().Name,
   255  				PhysAddress: link.Attrs().HardwareAddr.String(),
   256  				Mtu:         uint32(link.Attrs().MTU),
   257  			}
   258  
   259  			alias := link.Attrs().Alias
   260  			if !strings.HasPrefix(alias, h.agentPrefix) {
   261  				// skip interface not configured by this agent
   262  				continue
   263  			}
   264  			alias = strings.TrimPrefix(alias, h.agentPrefix)
   265  
   266  			// parse alias to obtain logical references
   267  			if link.Type() == "veth" {
   268  				iface.Type = interfaces.Interface_VETH
   269  				var vethPeerIfName string
   270  				iface.Name, vethPeerIfName = ParseVethAlias(alias)
   271  				iface.Link = &interfaces.Interface_Veth{
   272  					Veth: &interfaces.VethLink{
   273  						PeerIfName: vethPeerIfName,
   274  					},
   275  				}
   276  			} else if link.Type() == "dummy" {
   277  				iface.Type = interfaces.Interface_DUMMY
   278  				iface.Name = ParseDummyIfAlias(alias)
   279  			} else if link.Type() == "tuntap" || link.Type() == "tun" /* not defined in vishvananda */ {
   280  				iface.Type = interfaces.Interface_TAP_TO_VPP
   281  				var vppTapIfName string
   282  				iface.Name, vppTapIfName, _ = ParseTapAlias(alias)
   283  				iface.Link = &interfaces.Interface_Tap{
   284  					Tap: &interfaces.TapLink{
   285  						VppTapIfName: vppTapIfName,
   286  					},
   287  				}
   288  			} else if link.Type() == "vrf" {
   289  				vrfDev, isVrf := link.(*netlink.Vrf)
   290  				if !isVrf {
   291  					h.log.WithFields(logging.Fields{
   292  						"if-host-name": link.Attrs().Name,
   293  						"namespace":    nsRef,
   294  					}).Warnf("Unable to retrieve VRF-specific attributes")
   295  					continue
   296  				}
   297  				iface.Type = interfaces.Interface_VRF_DEVICE
   298  				iface.Name = ParseVRFAlias(alias)
   299  				iface.Link = &interfaces.Interface_VrfDev{
   300  					VrfDev: &interfaces.VrfDevLink{
   301  						RoutingTable: vrfDev.Table,
   302  					},
   303  				}
   304  				vrfDevs[link.Attrs().Index] = iface.Name
   305  			} else if link.Attrs().Name == DefaultLoopbackName {
   306  				iface.Type = interfaces.Interface_LOOPBACK
   307  				iface.Name = alias
   308  			} else {
   309  				// unsupported interface type supposedly configured by agent => print warning
   310  				h.log.WithFields(logging.Fields{
   311  					"if-host-name": link.Attrs().Name,
   312  					"namespace":    nsRef,
   313  				}).Warnf("Managed interface of unsupported type: %s", link.Type())
   314  				continue
   315  			}
   316  
   317  			// skip interfaces with invalid aliases
   318  			if iface.Name == "" {
   319  				continue
   320  			}
   321  
   322  			// retrieve addresses, MTU, etc.
   323  			h.retrieveLinkDetails(link, iface, nsRef)
   324  
   325  			// build interface details
   326  			ifaces = append(ifaces, &InterfaceDetails{
   327  				Interface: iface,
   328  				Meta: &InterfaceMeta{
   329  					LinuxIfIndex:  link.Attrs().Index,
   330  					ParentIndex:   link.Attrs().ParentIndex,
   331  					MasterIndex:   link.Attrs().MasterIndex,
   332  					OperState:     uint8(link.Attrs().OperState),
   333  					Flags:         link.Attrs().RawFlags,
   334  					Encapsulation: link.Attrs().EncapType,
   335  					NumRxQueues:   link.Attrs().NumRxQueues,
   336  					NumTxQueues:   link.Attrs().NumTxQueues,
   337  					TxQueueLen:    link.Attrs().TxQLen,
   338  				},
   339  			})
   340  
   341  			// build interface statistics
   342  			retrieved.stats = append(retrieved.stats, &InterfaceStatistics{
   343  				Name:         iface.Name,
   344  				Type:         iface.Type,
   345  				LinuxIfIndex: link.Attrs().Index,
   346  				RxPackets:    link.Attrs().Statistics.RxPackets,
   347  				TxPackets:    link.Attrs().Statistics.TxPackets,
   348  				RxBytes:      link.Attrs().Statistics.RxBytes,
   349  				TxBytes:      link.Attrs().Statistics.TxBytes,
   350  				RxErrors:     link.Attrs().Statistics.RxErrors,
   351  				TxErrors:     link.Attrs().Statistics.TxErrors,
   352  				RxDropped:    link.Attrs().Statistics.TxDropped,
   353  				TxDropped:    link.Attrs().Statistics.RxDropped,
   354  			})
   355  		}
   356  
   357  		// fill VRF names
   358  		for _, iface := range ifaces {
   359  			if vrfDev, inVrf := vrfDevs[iface.Meta.MasterIndex]; inVrf {
   360  				iface.Interface.VrfMasterInterface = vrfDev
   361  			}
   362  		}
   363  		retrieved.interfaces = append(retrieved.interfaces, ifaces...)
   364  
   365  		// switch back to the default namespace
   366  		revert()
   367  	}
   368  
   369  	ch <- retrieved
   370  }
   371  
   372  // retrieveLinkDetails retrieves link details common to all interface types (e.g. addresses).
   373  func (h *NetLinkHandler) retrieveLinkDetails(link netlink.Link, iface *interfaces.Interface, nsRef *namespaces.NetNamespace) {
   374  	var err error
   375  	// read interface status
   376  	iface.Enabled, err = h.IsInterfaceUp(link.Attrs().Name)
   377  	if err != nil {
   378  		h.log.WithFields(logging.Fields{
   379  			"if-host-name": link.Attrs().Name,
   380  			"namespace":    nsRef,
   381  		}).Warn("Failed to read interface status:", err)
   382  	}
   383  
   384  	// read assigned IP addresses
   385  	addressList, err := h.GetAddressList(link.Attrs().Name)
   386  	if err != nil {
   387  		h.log.WithFields(logging.Fields{
   388  			"if-host-name": link.Attrs().Name,
   389  			"namespace":    nsRef,
   390  		}).Warn("Failed to read address list:", err)
   391  	}
   392  	for _, address := range addressList {
   393  		if address.Scope == unix.RT_SCOPE_LINK {
   394  			// ignore link-local IPv6 addresses
   395  			continue
   396  		}
   397  		mask, _ := address.Mask.Size()
   398  		addrStr := address.IP.String() + "/" + strconv.Itoa(mask)
   399  		iface.IpAddresses = append(iface.IpAddresses, addrStr)
   400  	}
   401  
   402  	// read checksum offloading
   403  	if iface.Type == interfaces.Interface_VETH {
   404  		rxOn, txOn, err := h.GetChecksumOffloading(link.Attrs().Name)
   405  		if err != nil {
   406  			h.log.WithFields(logging.Fields{
   407  				"if-host-name": link.Attrs().Name,
   408  				"namespace":    nsRef,
   409  			}).Warn("Failed to read checksum offloading:", err)
   410  		} else {
   411  			if !rxOn {
   412  				iface.GetVeth().RxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
   413  			}
   414  			if !txOn {
   415  				iface.GetVeth().TxChecksumOffloading = interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
   416  			}
   417  		}
   418  	}
   419  }