go.ligato.io/vpp-agent/v3@v3.5.0/proto/ligato/vpp/interfaces/models.go (about)

     1  // Copyright (c) 2017 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 vpp_interfaces
    16  
    17  import (
    18  	"strconv"
    19  	"strings"
    20  
    21  	"go.ligato.io/vpp-agent/v3/pkg/models"
    22  	"go.ligato.io/vpp-agent/v3/proto/ligato/netalloc"
    23  )
    24  
    25  // ModuleName is the module name used for models.
    26  const ModuleName = "vpp"
    27  
    28  var (
    29  	ModelInterface = models.Register(&Interface{}, models.Spec{
    30  		Module:  ModuleName,
    31  		Version: "v2",
    32  		Type:    "interfaces",
    33  	})
    34  
    35  	ModelSpan = models.Register(&Span{}, models.Spec{
    36  		Module:  ModuleName,
    37  		Version: "v2",
    38  		Type:    "span",
    39  	}, models.WithNameTemplate("{{.InterfaceFrom}}/to/{{.InterfaceTo}}"))
    40  )
    41  
    42  // InterfaceKey returns the key used in NB DB to store the configuration of the
    43  // given vpp interface.
    44  func InterfaceKey(name string) string {
    45  	return models.Key(&Interface{
    46  		Name: name,
    47  	})
    48  }
    49  
    50  // SpanKey returns the key used in NB DB to store the configuration of the
    51  // given vpp span.
    52  func SpanKey(ifaceFrom, ifaceTo string) string {
    53  	return models.Key(&Span{
    54  		InterfaceFrom: ifaceFrom,
    55  		InterfaceTo:   ifaceTo,
    56  	})
    57  }
    58  
    59  /* Interface State */
    60  const (
    61  	// StatePrefix is a key prefix used in NB DB to store interface states.
    62  	StatePrefix = "vpp/status/v2/interface/"
    63  )
    64  
    65  /* Interface Error */
    66  const (
    67  	// ErrorPrefix is a key prefix used in NB DB to store interface errors.
    68  	ErrorPrefix = "vpp/status/v2/interface/error/"
    69  )
    70  
    71  /* Interface Address (derived) */
    72  const (
    73  	addressKeyPrefix = "vpp/interface/{iface}/address/"
    74  
    75  	// addressKeyTemplate is a template for (derived) key representing assigned
    76  	// IP addresses to an interface.
    77  	addressKeyTemplate = addressKeyPrefix + "{address-source}/{address}"
    78  )
    79  
    80  /* Interface VRF (derived) */
    81  const (
    82  	// vrfTKeyTemplatePrefix is a prefix of the template used to construct
    83  	// key representing assignment of an interface into a VRF table.
    84  	// The prefix includes the source interface name, but not details about the
    85  	// target VRF.
    86  	vrfTKeyTemplatePrefix = "vpp/interface/{iface}/vrf/"
    87  
    88  	// vrfKeyTemplate is a template for (derived) key representing assignment
    89  	// of a VPP interface into a VRF table.
    90  	vrfKeyTemplate = vrfTKeyTemplatePrefix + "{vrf}/ip-version/{ip-version}"
    91  
    92  	// inheritedVrfKeyTemplate is a template for (derived) key representing
    93  	// assignment of an (unnumbered) VPP interface into a VRF table, ID of which
    94  	// is given by the configuration of a referenced (numbered) interface.
    95  	inheritedVrfKeyTemplate = vrfTKeyTemplatePrefix + "from-interface/{from-iface}"
    96  
    97  	vrfIPv4 = "v4"
    98  	vrfIPv6 = "v6"
    99  	vrfBoth = "both"
   100  )
   101  
   102  /* Unnumbered interface (derived) */
   103  const (
   104  	// UnnumberedKeyPrefix is used as a common prefix for keys derived from
   105  	// interfaces to represent unnumbered interfaces.
   106  	UnnumberedKeyPrefix = "vpp/interface/unnumbered/"
   107  )
   108  
   109  /* Bond interface enslavement (derived) */
   110  const (
   111  	// BondedInterfacePrefix is used as a common prefix for keys derived from
   112  	// interfaces to represent interface slaves for bond interface.
   113  	BondedInterfacePrefix = "vpp/bond/{bond}/interface/{iface}/"
   114  )
   115  
   116  /* DHCP (client - derived, lease - notification) */
   117  const (
   118  	// IP6NDKeyPrefix is used as a common prefix for keys derived from
   119  	// interfaces to represent enabled IP6 ND.
   120  	IP6NDKeyPrefix = "vpp/interface/ip6nd/"
   121  )
   122  
   123  /* DHCP (client - derived, lease - notification) */
   124  const (
   125  	// DHCPClientKeyPrefix is used as a common prefix for keys derived from
   126  	// interfaces to represent enabled DHCP clients.
   127  	DHCPClientKeyPrefix = "vpp/interface/dhcp-client/"
   128  
   129  	// DHCPLeaseKeyPrefix is used as a common prefix for keys representing
   130  	// notifications with DHCP leases.
   131  	DHCPLeaseKeyPrefix = "vpp/interface/dhcp-lease/"
   132  )
   133  
   134  /* Interface Link State */
   135  
   136  const (
   137  	// interface link states as described in the keys
   138  	linkUpState   = "UP"
   139  	linkDownState = "DOWN"
   140  
   141  	// linkStateKeyTemplate is a template for keys representing
   142  	// the link state of VPP interfaces (up/down).
   143  	linkStateKeyTemplate = "vpp/interface/{ifName}/link-state/{linkState}"
   144  )
   145  
   146  /* Interface Rx-placement (derived) */
   147  const (
   148  	// rxPlacementKeyTemplate is a template for (derived) key representing
   149  	// rx-placement configured for a given interface queue.
   150  	rxPlacementKeyTemplate = "vpp/interface/{iface}/rx-placement/queue/{queue}"
   151  )
   152  
   153  /* Interface Rx-modes (derived) */
   154  const (
   155  	// rxModeKeyTemplate is a template for (derived) key representing
   156  	// rx-mode configuration for all queues of a given interface.
   157  	rxModesKeyTemplate = "vpp/interface/{iface}/rx-modes"
   158  )
   159  
   160  /* Interface with IP address (derived, property) */
   161  const (
   162  	// interfaceWithIPKeyTemplate is a template for keys derived from all interfaces
   163  	// but created only after at least one IP address is assigned.
   164  	interfaceWithIPKeyTemplate = "vpp/interface/{iface}/has-IP-address"
   165  )
   166  
   167  const (
   168  	// InvalidKeyPart is used in key for parts which are invalid
   169  	InvalidKeyPart = "<invalid>"
   170  )
   171  
   172  /* Interface Error */
   173  
   174  // InterfaceErrorKey returns the key used in NB DB to store the interface errors.
   175  func InterfaceErrorKey(iface string) string {
   176  	if iface == "" {
   177  		iface = InvalidKeyPart
   178  	}
   179  	return ErrorPrefix + iface
   180  }
   181  
   182  /* Interface State */
   183  
   184  // InterfaceStateKey returns the key used in NB DB to store the state data of the
   185  // given vpp interface.
   186  func InterfaceStateKey(iface string) string {
   187  	if iface == "" {
   188  		iface = InvalidKeyPart
   189  	}
   190  	return StatePrefix + iface
   191  }
   192  
   193  /* Interface Address (derived) */
   194  
   195  // InterfaceAddressPrefix returns longest-common prefix of keys representing
   196  // assigned IP addresses to a specific VPP interface.
   197  func InterfaceAddressPrefix(iface string) string {
   198  	if iface == "" {
   199  		iface = InvalidKeyPart
   200  	}
   201  	return strings.Replace(addressKeyPrefix, "{iface}", iface, 1)
   202  }
   203  
   204  // InterfaceAddressKey returns key representing IP address assigned to VPP interface.
   205  func InterfaceAddressKey(iface string, address string, source netalloc.IPAddressSource) string {
   206  	if iface == "" {
   207  		iface = InvalidKeyPart
   208  	}
   209  
   210  	src := source.String()
   211  	if src == "" {
   212  		src = InvalidKeyPart
   213  	}
   214  	if strings.HasPrefix(address, netalloc.AllocRefPrefix) {
   215  		src = netalloc.IPAddressSource_ALLOC_REF.String()
   216  	}
   217  	src = strings.ToLower(src)
   218  
   219  	// construct key without validating the IP address
   220  	key := strings.Replace(addressKeyTemplate, "{iface}", iface, 1)
   221  	key = strings.Replace(key, "{address-source}", src, 1)
   222  	key = strings.Replace(key, "{address}", address, 1)
   223  	return key
   224  }
   225  
   226  // ParseInterfaceAddressKey parses interface address from key derived
   227  // from interface by InterfaceAddressKey().
   228  func ParseInterfaceAddressKey(key string) (iface, address string, source netalloc.IPAddressSource, invalidKey, isAddrKey bool) {
   229  	parts := strings.Split(key, "/")
   230  	if len(parts) < 4 || parts[0] != "vpp" || parts[1] != "interface" {
   231  		return
   232  	}
   233  
   234  	addrIdx := -1
   235  	for idx, part := range parts {
   236  		if part == "address" {
   237  			addrIdx = idx
   238  			break
   239  		}
   240  	}
   241  	if addrIdx == -1 {
   242  		return
   243  	}
   244  	isAddrKey = true
   245  
   246  	// parse interface name
   247  	iface = strings.Join(parts[2:addrIdx], "/")
   248  	if iface == "" {
   249  		iface = InvalidKeyPart
   250  		invalidKey = true
   251  	}
   252  
   253  	// parse address type
   254  	if addrIdx == len(parts)-1 {
   255  		invalidKey = true
   256  		return
   257  	}
   258  
   259  	// parse address source
   260  	src := strings.ToUpper(parts[addrIdx+1])
   261  	srcInt, validSrc := netalloc.IPAddressSource_value[src]
   262  	if !validSrc {
   263  		invalidKey = true
   264  		return
   265  	}
   266  	source = netalloc.IPAddressSource(srcInt)
   267  
   268  	// return address as is (not parsed - this is done by the netalloc plugin)
   269  	address = strings.Join(parts[addrIdx+2:], "/")
   270  	if address == "" {
   271  		invalidKey = true
   272  	}
   273  	return
   274  }
   275  
   276  /* Interface VRF (derived) */
   277  
   278  // InterfaceVrfKeyPrefix returns prefix of the key representing assignment
   279  // of the given interface into unspecified VRF table.
   280  func InterfaceVrfKeyPrefix(iface string) string {
   281  	return strings.Replace(vrfTKeyTemplatePrefix, "{iface}", iface, 1)
   282  }
   283  
   284  // InterfaceVrfKey returns key representing assignment of the given interface
   285  // into the given VRF.
   286  func InterfaceVrfKey(iface string, vrf int, ipv4, ipv6 bool) string {
   287  	if iface == "" {
   288  		iface = InvalidKeyPart
   289  	}
   290  	ipVer := InvalidKeyPart
   291  	if ipv4 && ipv6 {
   292  		ipVer = vrfBoth
   293  	} else if ipv4 {
   294  		ipVer = vrfIPv4
   295  	} else if ipv6 {
   296  		ipVer = vrfIPv6
   297  	}
   298  
   299  	vrfTableID := InvalidKeyPart
   300  	if vrf >= 0 {
   301  		vrfTableID = strconv.Itoa(vrf)
   302  	}
   303  
   304  	key := strings.Replace(vrfKeyTemplate, "{iface}", iface, 1)
   305  	key = strings.Replace(key, "{vrf}", vrfTableID, 1)
   306  	key = strings.Replace(key, "{ip-version}", ipVer, 1)
   307  	return key
   308  }
   309  
   310  // ParseInterfaceVrfKey parses details from key derived from interface by
   311  // InterfaceVrfKey().
   312  func ParseInterfaceVrfKey(key string) (iface string, vrf int, ipv4, ipv6, isIfaceVrfKey bool) {
   313  	parts := strings.Split(key, "/")
   314  	if len(parts) < 7 || parts[0] != "vpp" || parts[1] != "interface" {
   315  		return
   316  	}
   317  
   318  	vrfIdx := -1
   319  	ipVerIdx := -1
   320  	for idx, part := range parts {
   321  		if part == "vrf" {
   322  			vrfIdx = idx
   323  		}
   324  		if part == "ip-version" {
   325  			ipVerIdx = idx
   326  		}
   327  	}
   328  	if vrfIdx == -1 || ipVerIdx != len(parts)-2 {
   329  		return
   330  	}
   331  	isIfaceVrfKey = true
   332  
   333  	// parse interface name
   334  	iface = strings.Join(parts[2:vrfIdx], "/")
   335  	if iface == "" {
   336  		iface = InvalidKeyPart
   337  	}
   338  
   339  	// parse VRF table ID
   340  	var err error
   341  	vrf, err = strconv.Atoi(parts[vrfIdx+1])
   342  	if err != nil {
   343  		vrf = -1
   344  	}
   345  
   346  	// parse IP version
   347  	switch parts[ipVerIdx+1] {
   348  	case vrfBoth:
   349  		ipv4 = true
   350  		ipv6 = true
   351  	case vrfIPv4:
   352  		ipv4 = true
   353  	case vrfIPv6:
   354  		ipv6 = true
   355  	}
   356  	return
   357  }
   358  
   359  // InterfaceInheritedVrfKey returns key representing assignment of the given
   360  // interface into a VRF inherited from another interface.
   361  // Used by unnumbered interfaces.
   362  func InterfaceInheritedVrfKey(iface string, fromIface string) string {
   363  	if iface == "" {
   364  		iface = InvalidKeyPart
   365  	}
   366  	if fromIface == "" {
   367  		fromIface = InvalidKeyPart
   368  	}
   369  	key := strings.Replace(inheritedVrfKeyTemplate, "{iface}", iface, 1)
   370  	key = strings.Replace(key, "{from-iface}", fromIface, 1)
   371  	return key
   372  }
   373  
   374  // ParseInterfaceInheritedVrfKey parses details from key derived from interface by
   375  // InterfaceInheritedVrfKey().
   376  func ParseInterfaceInheritedVrfKey(key string) (iface, fromIface string, isIfaceInherVrfKey bool) {
   377  	parts := strings.Split(key, "/")
   378  	if len(parts) < 6 || parts[0] != "vpp" || parts[1] != "interface" {
   379  		return
   380  	}
   381  
   382  	vrfIdx := -1
   383  	for idx, part := range parts {
   384  		if part == "vrf" {
   385  			vrfIdx = idx
   386  			break
   387  		}
   388  	}
   389  	if vrfIdx == -1 || vrfIdx+2 >= len(parts) || parts[vrfIdx+1] != "from-interface" {
   390  		return
   391  	}
   392  	isIfaceInherVrfKey = true
   393  
   394  	// parse interface name
   395  	iface = strings.Join(parts[2:vrfIdx], "/")
   396  	if iface == "" {
   397  		iface = InvalidKeyPart
   398  	}
   399  
   400  	// parse from-interface name
   401  	fromIface = strings.Join(parts[vrfIdx+2:], "/")
   402  	if fromIface == "" {
   403  		fromIface = InvalidKeyPart
   404  	}
   405  	return
   406  }
   407  
   408  /* Unnumbered interface (derived) */
   409  
   410  // UnnumberedKey returns key representing unnumbered interface.
   411  func UnnumberedKey(iface string) string {
   412  	if iface == "" {
   413  		iface = InvalidKeyPart
   414  	}
   415  	return UnnumberedKeyPrefix + iface
   416  }
   417  
   418  // ParseNameFromUnnumberedKey returns suffix of the key.
   419  func ParseNameFromUnnumberedKey(key string) (iface string, isUnnumberedKey bool) {
   420  	suffix := strings.TrimPrefix(key, UnnumberedKeyPrefix)
   421  	if suffix != key && suffix != "" {
   422  		return suffix, true
   423  	}
   424  	return
   425  }
   426  
   427  /* Bond slave interface (derived) */
   428  
   429  // BondedInterfaceKey returns a key with bond and slave interface set
   430  func BondedInterfaceKey(bondIf, slaveIf string) string {
   431  	if bondIf == "" {
   432  		bondIf = InvalidKeyPart
   433  	}
   434  	if slaveIf == "" {
   435  		slaveIf = InvalidKeyPart
   436  	}
   437  	key := strings.Replace(BondedInterfacePrefix, "{bond}", bondIf, 1)
   438  	key = strings.Replace(key, "{iface}", slaveIf, 1)
   439  	return key
   440  }
   441  
   442  // ParseBondedInterfaceKey returns names of interfaces of the key.
   443  func ParseBondedInterfaceKey(key string) (bondIf, slaveIf string, isBondSlaveInterfaceKey bool) {
   444  	keyComps := strings.Split(key, "/")
   445  	if len(keyComps) >= 5 && keyComps[0] == "vpp" && keyComps[1] == "bond" && keyComps[3] == "interface" {
   446  		slaveIf = strings.Join(keyComps[4:], "/")
   447  		return keyComps[2], slaveIf, true
   448  	}
   449  	return "", "", false
   450  }
   451  
   452  // IP6NDKey returns a (derived) key used to represent enabled IP6 ND.
   453  func IP6NDKey(iface string) string {
   454  	if iface == "" {
   455  		iface = InvalidKeyPart
   456  	}
   457  	return IP6NDKeyPrefix + iface
   458  }
   459  
   460  // ParseNameFromIP6NDKey returns suffix of the key.
   461  func ParseNameFromIP6NDKey(key string) (iface string, isIP6NDKey bool) {
   462  	if suffix := strings.TrimPrefix(key, IP6NDKeyPrefix); suffix != key && suffix != "" {
   463  		return suffix, true
   464  	}
   465  	return
   466  }
   467  
   468  /* DHCP (client - derived, lease - notification) */
   469  
   470  // DHCPClientKey returns a (derived) key used to represent enabled DHCP lease.
   471  func DHCPClientKey(iface string) string {
   472  	if iface == "" {
   473  		iface = InvalidKeyPart
   474  	}
   475  	return DHCPClientKeyPrefix + iface
   476  }
   477  
   478  // ParseNameFromDHCPClientKey returns suffix of the key.
   479  func ParseNameFromDHCPClientKey(key string) (iface string, isDHCPClientKey bool) {
   480  	if suffix := strings.TrimPrefix(key, DHCPClientKeyPrefix); suffix != key && suffix != "" {
   481  		return suffix, true
   482  	}
   483  	return
   484  }
   485  
   486  // DHCPLeaseKey returns a key used to represent DHCP lease for the given interface.
   487  func DHCPLeaseKey(iface string) string {
   488  	if iface == "" {
   489  		iface = InvalidKeyPart
   490  	}
   491  	return DHCPLeaseKeyPrefix + iface
   492  }
   493  
   494  // ParseNameFromDHCPLeaseKey returns suffix of the key.
   495  func ParseNameFromDHCPLeaseKey(key string) (iface string, isDHCPLeaseKey bool) {
   496  	if suffix := strings.TrimPrefix(key, DHCPLeaseKeyPrefix); suffix != key && suffix != "" {
   497  		return suffix, true
   498  	}
   499  	return
   500  }
   501  
   502  /* Link State (notification) */
   503  
   504  // LinkStateKey returns key representing link state of a VPP interface.
   505  func LinkStateKey(ifaceName string, linkIsUp bool) string {
   506  	if ifaceName == "" {
   507  		ifaceName = InvalidKeyPart
   508  	}
   509  	linkState := linkDownState
   510  	if linkIsUp {
   511  		linkState = linkUpState
   512  	}
   513  	key := strings.Replace(linkStateKeyTemplate, "{ifName}", ifaceName, 1)
   514  	key = strings.Replace(key, "{linkState}", linkState, 1)
   515  	return key
   516  }
   517  
   518  // ParseLinkStateKey parses key representing link state of a VPP interface.
   519  func ParseLinkStateKey(key string) (ifaceName string, isLinkUp bool, isLinkStateKey bool) {
   520  	if suffix := strings.TrimPrefix(key, "vpp/interface/"); suffix != key {
   521  		parts := strings.Split(suffix, "/")
   522  		linkState := -1
   523  		for i, part := range parts {
   524  			if part == "link-state" {
   525  				linkState = i
   526  			}
   527  		}
   528  		if linkState != len(parts)-2 {
   529  			return
   530  		}
   531  
   532  		switch parts[len(parts)-1] {
   533  		case linkDownState:
   534  		case linkUpState:
   535  			isLinkUp = true
   536  		default:
   537  			return
   538  		}
   539  
   540  		// beware: interface name may contain forward slashes
   541  		ifaceName = strings.Join(parts[:linkState], "/")
   542  		if ifaceName == InvalidKeyPart {
   543  			isLinkUp = false
   544  			ifaceName = ""
   545  			return
   546  		}
   547  		isLinkStateKey = true
   548  	}
   549  	return
   550  }
   551  
   552  /* Interface with IP address (derived property) */
   553  
   554  // InterfaceWithIPKey returns key derived from every VPP interface but created only
   555  // after at least one IP address was assigned to it.
   556  func InterfaceWithIPKey(ifaceName string) string {
   557  	if ifaceName == "" {
   558  		ifaceName = InvalidKeyPart
   559  	}
   560  	return strings.Replace(interfaceWithIPKeyTemplate, "{iface}", ifaceName, 1)
   561  }
   562  
   563  // ParseInterfaceWithIPKey parses key derived from every VPP interface but created only
   564  // after at least one IP address was assigned to it
   565  func ParseInterfaceWithIPKey(key string) (ifaceName string, isInterfaceWithIPKey bool) {
   566  	if suffix := strings.TrimPrefix(key, "vpp/interface/"); suffix != key {
   567  		if prefix := strings.TrimSuffix(suffix, "/has-IP-address"); prefix != suffix {
   568  			if prefix != InvalidKeyPart {
   569  				ifaceName = prefix
   570  				isInterfaceWithIPKey = true
   571  			}
   572  		}
   573  	}
   574  	return
   575  }
   576  
   577  /* Rx placement (derived) */
   578  
   579  // RxPlacementKey returns a key representing rx-placement configured for a given
   580  // interface queue.
   581  func RxPlacementKey(ifaceName string, queue uint32) string {
   582  	if ifaceName == "" {
   583  		ifaceName = InvalidKeyPart
   584  	}
   585  	key := strings.Replace(rxPlacementKeyTemplate, "{iface}", ifaceName, 1)
   586  	key = strings.Replace(key, "{queue}", strconv.Itoa(int(queue)), 1)
   587  	return key
   588  }
   589  
   590  // ParseRxPlacementKey parses key representing rx-placement configured for a given
   591  // interface queue.
   592  func ParseRxPlacementKey(key string) (ifaceName string, queue uint32, isRxPlacementKey bool) {
   593  	if suffix := strings.TrimPrefix(key, "vpp/interface/"); suffix != key {
   594  		parts := strings.Split(suffix, "/")
   595  		rxPlacement := -1
   596  		for i, part := range parts {
   597  			if part == "rx-placement" {
   598  				rxPlacement = i
   599  			}
   600  		}
   601  		if rxPlacement != len(parts)-3 {
   602  			return
   603  		}
   604  
   605  		if parts[len(parts)-2] != "queue" {
   606  			return
   607  		}
   608  
   609  		queueID, err := strconv.Atoi(parts[len(parts)-1])
   610  		if err != nil || queueID < 0 {
   611  			return
   612  		}
   613  
   614  		// beware: interface name may contain forward slashes
   615  		ifaceName = strings.Join(parts[:rxPlacement], "/")
   616  		if ifaceName == InvalidKeyPart {
   617  			ifaceName = ""
   618  			return
   619  		}
   620  
   621  		queue = uint32(queueID)
   622  		isRxPlacementKey = true
   623  	}
   624  	return
   625  }
   626  
   627  /* Rx modes (derived) */
   628  
   629  // RxModesKey returns a key representing rx-mode configuration for all queues
   630  // of a given interface.
   631  func RxModesKey(ifaceName string) string {
   632  	if ifaceName == "" {
   633  		ifaceName = InvalidKeyPart
   634  	}
   635  	return strings.Replace(rxModesKeyTemplate, "{iface}", ifaceName, 1)
   636  }
   637  
   638  // ParseRxModesKey parses key representing rx-mode configuration for all queues
   639  // of a given interface.
   640  func ParseRxModesKey(key string) (ifaceName string, isRxModesKey bool) {
   641  	if suffix := strings.TrimPrefix(key, "vpp/interface/"); suffix != key {
   642  		parts := strings.Split(suffix, "/")
   643  		if len(parts) == 0 || parts[len(parts)-1] != "rx-modes" {
   644  			return
   645  		}
   646  
   647  		// beware: interface name may contain forward slashes
   648  		ifaceName = strings.Join(parts[:len(parts)-1], "/")
   649  		if ifaceName == InvalidKeyPart {
   650  			ifaceName = ""
   651  			return
   652  		}
   653  
   654  		isRxModesKey = true
   655  	}
   656  	return
   657  }