diff --git a/plugins/linux/ifplugin/descriptor/interface.go b/plugins/linux/ifplugin/descriptor/interface.go index 58ac380d9a..ab4d271014 100644 --- a/plugins/linux/ifplugin/descriptor/interface.go +++ b/plugins/linux/ifplugin/descriptor/interface.go @@ -58,6 +58,7 @@ const ( // default MTU - expected when MTU is not specified in the config. defaultEthernetMTU = 1500 defaultLoopbackMTU = 65536 + defaultVrfDevMTU = 65536 // dependency labels existingHostInterfaceDep = "host-interface-exists" @@ -114,6 +115,12 @@ var ( // ErrLoopbackNotFound is returned if loopback interface can not be found ErrLoopbackNotFound = errors.New("loopback not found") + + // ErrVRFDevWithMACAddr is returned when VRF device is configured with a MAC address. + ErrVRFDevWithMACAddr = errors.New("it is unsupported to set MAC address to a VRF device") + + // ErrVRFDevInsideVrf is returned when VRF device is configured to be inside another VRF. + ErrVRFDevInsideVrf = errors.New("VRF device cannot be inside another VRF") ) // InterfaceDescriptor teaches KVScheduler how to configure Linux interfaces. @@ -198,10 +205,12 @@ func (d *InterfaceDescriptor) EquivalentInterfaces(key string, oldIntf, newIntf oldIntf.Type != newIntf.Type || oldIntf.Enabled != newIntf.Enabled || oldIntf.LinkOnly != newIntf.LinkOnly || + oldIntf.VrfMasterInterface != newIntf.VrfMasterInterface || getHostIfName(oldIntf) != getHostIfName(newIntf) { return false } - if oldIntf.Type == interfaces.Interface_VETH { + switch oldIntf.Type { + case interfaces.Interface_VETH: if oldIntf.GetVeth().GetPeerIfName() != newIntf.GetVeth().GetPeerIfName() { return false } @@ -210,11 +219,16 @@ func (d *InterfaceDescriptor) EquivalentInterfaces(key string, oldIntf, newIntf getTxChksmOffloading(oldIntf) != getTxChksmOffloading(newIntf) { return false } + case interfaces.Interface_TAP_TO_VPP: + if oldIntf.GetTap().GetVppTapIfName() != newIntf.GetTap().GetVppTapIfName() { + return false + } + case interfaces.Interface_VRF_DEVICE: + if oldIntf.GetVrfDev().GetRoutingTable() != newIntf.GetVrfDev().GetRoutingTable() { + return false + } } - if oldIntf.Type == interfaces.Interface_TAP_TO_VPP && - oldIntf.GetTap().GetVppTapIfName() != newIntf.GetTap().GetVppTapIfName() { - return false - } + if !proto.Equal(oldIntf.Namespace, newIntf.Namespace) { return false } @@ -275,6 +289,13 @@ func (d *InterfaceDescriptor) Validate(key string, linuxIf *interfaces.Interface if d.vppIfPlugin == nil { return ErrTAPRequiresVPPIfPlugin } + case interfaces.Interface_VRF_DEVICE: + if linuxIf.GetPhysAddress() != "" { + return kvs.NewInvalidValueError(ErrVRFDevWithMACAddr, "type", "phys_address") + } + if linuxIf.GetVrfMasterInterface() != "" { + return kvs.NewInvalidValueError(ErrVRFDevInsideVrf, "type", "vrf") + } case interfaces.Interface_UNDEFINED: return kvs.NewInvalidValueError(ErrInterfaceWithoutType, "type") } @@ -300,7 +321,7 @@ func (d *InterfaceDescriptor) Validate(key string, linuxIf *interfaces.Interface return nil } -// Create creates VETH or configures TAP interface. +// Create creates Linux interface. func (d *InterfaceDescriptor) Create(key string, linuxIf *interfaces.Interface) (metadata *ifaceidx.LinuxIfMetadata, err error) { // move to the default namespace nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() @@ -334,6 +355,8 @@ func (d *InterfaceDescriptor) Create(key string, linuxIf *interfaces.Interface) }, nil } metadata, err = getMetadata(linuxIf) + case interfaces.Interface_VRF_DEVICE: + metadata, err = d.createVRF(nsCtx, linuxIf) default: return nil, ErrUnsupportedLinuxInterfaceType } @@ -343,6 +366,7 @@ func (d *InterfaceDescriptor) Create(key string, linuxIf *interfaces.Interface) } metadata.HostIfName = getHostIfName(linuxIf) + metadata.VrfMasterIf = linuxIf.VrfMasterInterface // move to the namespace with the interface revert2, err := d.nsPlugin.SwitchToNamespace(nsCtx, linuxIf.Namespace) @@ -407,7 +431,7 @@ func (d *InterfaceDescriptor) Create(key string, linuxIf *interfaces.Interface) return metadata, nil } -// Delete removes VETH or unconfigures TAP interface. +// Delete removes Linux interface. func (d *InterfaceDescriptor) Delete(key string, linuxIf *interfaces.Interface, metadata *ifaceidx.LinuxIfMetadata) error { // move to the namespace with the interface nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() @@ -429,6 +453,8 @@ func (d *InterfaceDescriptor) Delete(key string, linuxIf *interfaces.Interface, // We only need to unconfigure the interface. // Nothing else needs to be done. return nil + case interfaces.Interface_VRF_DEVICE: + return d.deleteVRF(linuxIf) } err = ErrUnsupportedLinuxInterfaceType @@ -525,6 +551,7 @@ func (d *InterfaceDescriptor) Update(key string, oldLinuxIf, newLinuxIf *interfa } oldMetadata.LinuxIfIndex = link.Attrs().Index oldMetadata.HostIfName = newHostName + oldMetadata.VrfMasterIf = newLinuxIf.VrfMasterInterface return oldMetadata, nil } @@ -545,6 +572,8 @@ func (d *InterfaceDescriptor) UpdateWithRecreate(key string, oldLinuxIf, newLinu return oldLinuxIf.GetVeth().GetPeerIfName() != newLinuxIf.GetVeth().GetPeerIfName() case interfaces.Interface_TAP_TO_VPP: return oldLinuxIf.GetTap().GetVppTapIfName() != newLinuxIf.GetTap().GetVppTapIfName() + case interfaces.Interface_VRF_DEVICE: + return oldLinuxIf.GetVrfDev().GetRoutingTable() != newLinuxIf.GetVrfDev().GetRoutingTable() } return false } @@ -590,19 +619,28 @@ func (d *InterfaceDescriptor) Dependencies(key string, linuxIf *interfaces.Inter return dependencies } -// DerivedValues derives one empty value to represent interface state and also -// one empty value for every IP address assigned to the interface. +// DerivedValues derives: +// - one empty value to represent interface state +// - one empty value to represent assignment of the interface to a (non-default) VRF +// - one empty value for every IP address assigned to the interface. func (d *InterfaceDescriptor) DerivedValues(key string, linuxIf *interfaces.Interface) (derValues []kvs.KeyValuePair) { // interface state derValues = append(derValues, kvs.KeyValuePair{ Key: interfaces.InterfaceStateKey(linuxIf.Name, linuxIf.Enabled), Value: &prototypes.Empty{}, }) + if linuxIf.GetVrfMasterInterface() != "" { + derValues = append(derValues, kvs.KeyValuePair{ + Key: interfaces.InterfaceVrfKey(linuxIf.Name, linuxIf.VrfMasterInterface), + Value: &prototypes.Empty{}, + }) + } if !linuxIf.GetLinkOnly() { // IP addresses for _, ipAddr := range linuxIf.IpAddresses { derValues = append(derValues, kvs.KeyValuePair{ - Key: interfaces.InterfaceAddressKey(linuxIf.Name, ipAddr, netalloc_api.IPAddressSource_STATIC), + Key: interfaces.InterfaceAddressKey( + linuxIf.Name, ipAddr, linuxIf.VrfMasterInterface, netalloc_api.IPAddressSource_STATIC), Value: &prototypes.Empty{}, }) } @@ -663,6 +701,10 @@ func (d *InterfaceDescriptor) Retrieve(correlate []adapter.InterfaceKVWithMetada for _, ifDetail := range ifDetails { // Transform linux interface details to the type-safe value with metadata + var vrfDevRT uint32 + if ifDetail.Interface.Type == interfaces.Interface_VRF_DEVICE { + vrfDevRT = ifDetail.Interface.GetVrfDev().GetRoutingTable() + } kv := adapter.InterfaceKVWithMetadata{ Origin: kvs.FromNB, Value: ifDetail.Interface, @@ -671,6 +713,8 @@ func (d *InterfaceDescriptor) Retrieve(correlate []adapter.InterfaceKVWithMetada Namespace: ifDetail.Interface.GetNamespace(), VPPTapName: ifDetail.Interface.GetTap().GetVppTapIfName(), HostIfName: ifDetail.Interface.HostIfName, + VrfMasterIf: ifDetail.Interface.VrfMasterInterface, + VrfDevRT: vrfDevRT, }, Key: interfaces.InterfaceKey(ifDetail.Interface.Name), } @@ -979,8 +1023,11 @@ func getHostIfName(linuxIf *interfaces.Interface) string { func getInterfaceMTU(linuxIntf *interfaces.Interface) int { mtu := int(linuxIntf.Mtu) if mtu == 0 { - if linuxIntf.Type == interfaces.Interface_LOOPBACK { + switch linuxIntf.Type { + case interfaces.Interface_LOOPBACK: return defaultLoopbackMTU + case interfaces.Interface_VRF_DEVICE: + return defaultVrfDevMTU } return defaultEthernetMTU } diff --git a/plugins/linux/ifplugin/descriptor/interface_address.go b/plugins/linux/ifplugin/descriptor/interface_address.go index e9839c54d1..f929da6cf6 100644 --- a/plugins/linux/ifplugin/descriptor/interface_address.go +++ b/plugins/linux/ifplugin/descriptor/interface_address.go @@ -39,6 +39,9 @@ const ( // DisableIPv6SysctlTemplate is used to enable ipv6 via sysctl. DisableIPv6SysctlTemplate = "net.ipv6.conf.%s.disable_ipv6" + + // dependency labels + interfaceVrfDep = "interface-assigned-to-vrf" ) // InterfaceAddressDescriptor (un)assigns IP address to/from Linux interface. @@ -77,18 +80,18 @@ func (d *InterfaceAddressDescriptor) SetInterfaceIndex(intfIndex ifaceidx.LinuxI d.intfIndex = intfIndex } -// IsInterfaceVrfKey returns true if the key represents assignment of an IP address +// IsInterfaceAddressKey returns true if the key represents assignment of an IP address // to a Linux interface (that needs to be applied). KVs representing addresses // already allocated from netalloc plugin are excluded. func (d *InterfaceAddressDescriptor) IsInterfaceAddressKey(key string) bool { - _, _, source, _, isAddrKey := interfaces.ParseInterfaceAddressKey(key) + _, _, _, source, _, isAddrKey := interfaces.ParseInterfaceAddressKey(key) return isAddrKey && (source == netalloc_api.IPAddressSource_STATIC || source == netalloc_api.IPAddressSource_ALLOC_REF) } // Validate validates IP address to be assigned to an interface. func (d *InterfaceAddressDescriptor) Validate(key string, emptyVal proto.Message) (err error) { - iface, addr, _, invalidKey, _ := interfaces.ParseInterfaceAddressKey(key) + iface, addr, _, _, invalidKey, _ := interfaces.ParseInterfaceAddressKey(key) if invalidKey { return errors.New("invalid key") } @@ -98,7 +101,7 @@ func (d *InterfaceAddressDescriptor) Validate(key string, emptyVal proto.Message // Create assigns IP address to an interface. func (d *InterfaceAddressDescriptor) Create(key string, emptyVal proto.Message) (metadata kvs.Metadata, err error) { - iface, addr, _, _, _ := interfaces.ParseInterfaceAddressKey(key) + iface, addr, _, _, _, _ := interfaces.ParseInterfaceAddressKey(key) ifMeta, found := d.intfIndex.LookupByName(iface) if !found { @@ -157,7 +160,7 @@ func (d *InterfaceAddressDescriptor) Create(key string, emptyVal proto.Message) // Delete unassigns IP address from an interface. func (d *InterfaceAddressDescriptor) Delete(key string, emptyVal proto.Message, metadata kvs.Metadata) (err error) { - iface, addr, _, _, _ := interfaces.ParseInterfaceAddressKey(key) + iface, addr, _, _, _, _ := interfaces.ParseInterfaceAddressKey(key) ifMeta, found := d.intfIndex.LookupByName(iface) if !found { @@ -192,13 +195,18 @@ func (d *InterfaceAddressDescriptor) Delete(key string, emptyVal proto.Message, return err } -// Dependencies mentions potential allocation of the IP address as dependency. +// Dependencies mentions (non-default) VRF and a potential allocation of the IP address as dependencies. func (d *InterfaceAddressDescriptor) Dependencies(key string, emptyVal proto.Message) (deps []kvs.Dependency) { - iface, addr, _, _, _ := interfaces.ParseInterfaceAddressKey(key) + iface, addr, vrf, _, _, _ := interfaces.ParseInterfaceAddressKey(key) + if vrf != "" { + deps = append(deps, kvs.Dependency{ + Label: interfaceVrfDep, + Key: interfaces.InterfaceVrfKey(iface, vrf), + }) + } allocDep, hasAllocDep := d.addrAlloc.GetAddressAllocDep(addr, iface, "") if hasAllocDep { deps = append(deps, allocDep) } - return deps } diff --git a/plugins/linux/ifplugin/descriptor/interface_vrf.go b/plugins/linux/ifplugin/descriptor/interface_vrf.go new file mode 100644 index 0000000000..9a818881ab --- /dev/null +++ b/plugins/linux/ifplugin/descriptor/interface_vrf.go @@ -0,0 +1,171 @@ +// Copyright (c) 2020 Pantheon.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package descriptor + +import ( + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" + "go.ligato.io/cn-infra/v2/logging" + + kvs "go.ligato.io/vpp-agent/v3/plugins/kvscheduler/api" + "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/ifaceidx" + iflinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/linuxcalls" + "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin" + nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" +) + +const ( + // InterfaceVrfDescriptorName is the name of the descriptor for assigning Linux interfaces into a VRF. + InterfaceVrfDescriptorName = "linux-interface-vrf" + + // dependency labels + vrfDeviceDep = "vrf-device-is-created" +) + +// InterfaceVrfDescriptor (un)assigns Linux interface to/from VRF. +type InterfaceVrfDescriptor struct { + log logging.Logger + ifHandler iflinuxcalls.NetlinkAPI + nsPlugin nsplugin.API + intfIndex ifaceidx.LinuxIfMetadataIndex +} + +// NewInterfaceVrfDescriptor creates a new instance of InterfaceVrfDescriptor. +func NewInterfaceVrfDescriptor(nsPlugin nsplugin.API, + ifHandler iflinuxcalls.NetlinkAPI, log logging.PluginLogger) (descr *kvs.KVDescriptor, ctx *InterfaceVrfDescriptor) { + + ctx = &InterfaceVrfDescriptor{ + ifHandler: ifHandler, + nsPlugin: nsPlugin, + log: log.NewLogger("interface-vrf-descriptor"), + } + descr = &kvs.KVDescriptor{ + Name: InterfaceVrfDescriptorName, + KeySelector: ctx.IsInterfaceVrfKey, + Create: ctx.Create, + Delete: ctx.Delete, + Dependencies: ctx.Dependencies, + } + return +} + +// SetInterfaceIndex should be used to provide interface index immediately after +// the descriptor registration. +func (d *InterfaceVrfDescriptor) SetInterfaceIndex(intfIndex ifaceidx.LinuxIfMetadataIndex) { + d.intfIndex = intfIndex +} + +// IsInterfaceVrfKey returns true if the key represents assignment of a Linux interface into a VRF. +func (d *InterfaceVrfDescriptor) IsInterfaceVrfKey(key string) bool { + _, _, _, isVrfKey := interfaces.ParseInterfaceVrfKey(key) + return isVrfKey +} + +// Validate validates derived key. +func (d *InterfaceVrfDescriptor) Validate(key string, emptyVal proto.Message) (err error) { + _, _, invalidKey, _ := interfaces.ParseInterfaceVrfKey(key) + if invalidKey { + return errors.New("invalid key") + } + return nil +} + +// Create puts interface into a VRF. +func (d *InterfaceVrfDescriptor) Create(key string, emptyVal proto.Message) (metadata kvs.Metadata, err error) { + iface, vrf, _, _ := interfaces.ParseInterfaceVrfKey(key) + + ifMeta, found := d.intfIndex.LookupByName(iface) + if !found { + err = errors.Errorf("failed to find interface %s", iface) + d.log.Error(err) + return nil, err + } + vrfMeta, found := d.intfIndex.LookupByName(vrf) + if !found { + err = errors.Errorf("failed to find VRF device %s", vrf) + d.log.Error(err) + return nil, err + } + + // switch to the namespace with the interface + nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() + revert, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace) + if err != nil { + d.log.Error(err) + return nil, err + } + defer revert() + + err = d.ifHandler.PutInterfaceIntoVRF(ifMeta.HostIfName, vrfMeta.HostIfName) + if err != nil { + err = errors.WithMessagef(err, "failed to put interface '%s' into VRF '%s'", + ifMeta.HostIfName, vrfMeta.HostIfName) + } + return nil, err +} + +// Delete removes interface from VRF. +func (d *InterfaceVrfDescriptor) Delete(key string, emptyVal proto.Message, metadata kvs.Metadata) (err error) { + iface, vrf, _, _ := interfaces.ParseInterfaceVrfKey(key) + + ifMeta, found := d.intfIndex.LookupByName(iface) + if !found { + err = errors.Errorf("failed to find interface %s", iface) + d.log.Error(err) + return err + } + vrfMeta, found := d.intfIndex.LookupByName(vrf) + if !found { + err = errors.Errorf("failed to find VRF device %s", vrf) + d.log.Error(err) + return err + } + + // switch to the namespace with the interface + nsCtx := nslinuxcalls.NewNamespaceMgmtCtx() + revert, err := d.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace) + if err != nil { + if _, ok := err.(*nsplugin.UnavailableMicroserviceErr); ok { + // Assume that the delete was called by scheduler because the namespace + // was removed. Do not return error in this case. + d.log.Debugf("Interface %s assumed to be unassigned from VRF %s, required namespace %+v does not exist", + iface, vrf, ifMeta.Namespace) + return nil + } + d.log.Error(err) + return err + } + defer revert() + + err = d.ifHandler.RemoveInterfaceFromVRF(ifMeta.HostIfName, vrfMeta.HostIfName) + if err != nil { + err = errors.WithMessagef(err, "failed to remove interface '%s' from VRF '%s'", + ifMeta.HostIfName, vrfMeta.HostIfName) + } + return err +} + +// Dependencies lists the VRF device as the only dependency. +func (d *InterfaceVrfDescriptor) Dependencies(key string, emptyVal proto.Message) (deps []kvs.Dependency) { + _, vrf, _, _ := interfaces.ParseInterfaceVrfKey(key) + if vrf != "" { + deps = append(deps, kvs.Dependency{ + Label: vrfDeviceDep, + Key: interfaces.InterfaceKey(vrf), + }) + } + return deps +} \ No newline at end of file diff --git a/plugins/linux/ifplugin/descriptor/interface_vrfdev.go b/plugins/linux/ifplugin/descriptor/interface_vrfdev.go new file mode 100644 index 0000000000..e94599b6da --- /dev/null +++ b/plugins/linux/ifplugin/descriptor/interface_vrfdev.go @@ -0,0 +1,107 @@ +// Copyright (c) 2020 Pantheon.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package descriptor + +import ( + "fmt" + "github.com/pkg/errors" + "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/linuxcalls" + + "go.ligato.io/vpp-agent/v3/plugins/linux/ifplugin/ifaceidx" + nslinuxcalls "go.ligato.io/vpp-agent/v3/plugins/linux/nsplugin/linuxcalls" + interfaces "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" +) + +const ( + // Enabling this option allows a “global” listen socket to work across L3 master domains (e.g., VRFs). + sysctlL3MDevVar = "net.ipv4.tcp_l3mdev_accept" +) + +// createVRF creates a new VRF network device. +func (d *InterfaceDescriptor) createVRF( + nsCtx nslinuxcalls.NamespaceMgmtCtx, linuxIf *interfaces.Interface, +) (md *ifaceidx.LinuxIfMetadata, err error) { + hostName := getHostIfName(linuxIf) + rt := linuxIf.GetVrfDev().GetRoutingTable() + agentPrefix := d.serviceLabel.GetAgentPrefix() + + // move to the namespace with the interface + revert, err := d.nsPlugin.SwitchToNamespace(nsCtx, linuxIf.Namespace) + if err != nil { + d.log.Error("switch to namespace failed:", err) + return nil, err + } + defer revert() + + // Enable child sockets to inherit the L3 master device index. + // Without this VRF cannot really be used. + err = d.enableL3MasterDev() + if err != nil { + return nil, err + } + + // create a new VRF device + err = d.ifHandler.AddVRFDevice(hostName, rt) + if err != nil { + return nil, errors.WithMessagef(err, "failed to add VRF device %s (rt: %d)", hostName, rt) + } + + // add alias + err = d.ifHandler.SetInterfaceAlias(hostName, agentPrefix+linuxcalls.GetVRFAlias(linuxIf)) + if err != nil { + return nil, errors.WithMessagef(err, "error setting VRF %s alias", hostName) + } + + // build metadata + link, err := d.ifHandler.GetLinkByName(hostName) + if err != nil { + return nil, errors.WithMessagef(err, "error getting link %s", hostName) + } + + return &ifaceidx.LinuxIfMetadata{ + Namespace: linuxIf.Namespace, + LinuxIfIndex: link.Attrs().Index, + HostIfName: hostName, + VrfDevRT: rt, + }, nil +} + +// deleteVRF removes VRF network device. +func (d *InterfaceDescriptor) deleteVRF(linuxIf *interfaces.Interface) error { + hostName := getHostIfName(linuxIf) + err := d.ifHandler.DeleteInterface(hostName) + if err != nil { + d.log.Error(err) + return err + } + return nil +} + +func (d *InterfaceDescriptor) enableL3MasterDev() error { + value, err := getSysctl(sysctlL3MDevVar) + if err != nil { + err = fmt.Errorf("could not read sysctl value for %s: %w", + sysctlL3MDevVar, err) + return err + } + if value == "0" { + _, err = setSysctl(sysctlL3MDevVar, "1") + if err != nil { + err = fmt.Errorf("failed to enable %s: %w", sysctlL3MDevVar, err) + return err + } + } + return nil +} \ No newline at end of file diff --git a/plugins/linux/ifplugin/ifaceidx/ifaceidx.go b/plugins/linux/ifplugin/ifaceidx/ifaceidx.go index 035e9942d8..d90e0addff 100644 --- a/plugins/linux/ifplugin/ifaceidx/ifaceidx.go +++ b/plugins/linux/ifplugin/ifaceidx/ifaceidx.go @@ -68,6 +68,8 @@ type LinuxIfMetadata struct { VPPTapName string // empty for VETHs Namespace *linux_namespace.NetNamespace HostIfName string + VrfMasterIf string + VrfDevRT uint32 // only set for VRF_DEVICE } // LinuxIfMetadataIndexDto represents an item sent through watch channel in LinuxIfMetadataIndex. diff --git a/plugins/linux/ifplugin/ifplugin.go b/plugins/linux/ifplugin/ifplugin.go index 48fb6ab74f..6d8e64498e 100644 --- a/plugins/linux/ifplugin/ifplugin.go +++ b/plugins/linux/ifplugin/ifplugin.go @@ -50,6 +50,7 @@ type IfPlugin struct { ifDescriptor *descriptor.InterfaceDescriptor ifWatcher *descriptor.InterfaceWatcher ifAddrDescriptor *descriptor.InterfaceAddressDescriptor + ifVrfDescriptor *descriptor.InterfaceVrfDescriptor // index map ifIndex ifaceidx.LinuxIfMetadataIndex @@ -108,10 +109,11 @@ func (p *IfPlugin) Init() error { config.GoRoutinesCnt, p.Log) p.ifDescriptor.SetInterfaceHandler(p.ifHandler) - var addrDescriptor *kvs.KVDescriptor + var addrDescriptor, vrfDescriptor *kvs.KVDescriptor addrDescriptor, p.ifAddrDescriptor = descriptor.NewInterfaceAddressDescriptor(p.NsPlugin, p.AddrAlloc, p.ifHandler, p.Log) - err = p.Deps.KVScheduler.RegisterKVDescriptor(addrDescriptor) + vrfDescriptor, p.ifVrfDescriptor = descriptor.NewInterfaceVrfDescriptor(p.NsPlugin, p.ifHandler, p.Log) + err = p.Deps.KVScheduler.RegisterKVDescriptor(addrDescriptor, vrfDescriptor) if err != nil { return err } @@ -125,6 +127,7 @@ func (p *IfPlugin) Init() error { // pass read-only index map to descriptors p.ifDescriptor.SetInterfaceIndex(p.ifIndex) p.ifAddrDescriptor.SetInterfaceIndex(p.ifIndex) + p.ifVrfDescriptor.SetInterfaceIndex(p.ifIndex) // start interface watching if err = p.ifWatcher.StartWatching(); err != nil { diff --git a/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.go b/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.go index 141a080754..a04054d540 100644 --- a/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.go +++ b/plugins/linux/ifplugin/linuxcalls/dump_interface_linuxcalls.go @@ -198,6 +198,17 @@ func ParseTapAlias(alias string) (linuxTapName, vppTapName, origHostIfName strin return } +// GetVRFAlias returns alias for Linux VRF devices managed by the agent. +func GetVRFAlias(linuxIf *interfaces.Interface) string { + return linuxIf.Name +} + +// ParseVRFAlias parses out logical name of a VRF devices from the alias. +// Currently there are no other logical information stored in the alias so it is very straightforward. +func ParseVRFAlias(alias string) (vrfName string) { + return alias +} + // retrieveInterfaces is run by a separate go routine to retrieve all interfaces // present in every -th network namespace from the list. func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, goRoutineIdx, goRoutinesCnt int, ch chan<- retrievedInterfaces) { @@ -224,6 +235,8 @@ func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, g } // retrieve every interface managed by this agent + var ifaces []*InterfaceDetails + vrfDevs := make(map[int]string) // vrf index -> vrf name for _, link := range links { iface := &interfaces.Interface{ Namespace: nsRef, @@ -258,6 +271,23 @@ func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, g VppTapIfName: vppTapIfName, }, } + } else if link.Type() == "vrf" { + vrfDev, isVrf := link.(*netlink.Vrf) + if !isVrf { + h.log.WithFields(logging.Fields{ + "if-host-name": link.Attrs().Name, + "namespace": nsRef, + }).Warnf("Unable to retrieve VRF-specific attributes") + continue + } + iface.Type = interfaces.Interface_VRF_DEVICE + iface.Name = ParseVRFAlias(alias) + iface.Link = &interfaces.Interface_VrfDev{ + VrfDev: &interfaces.VrfDevLink{ + RoutingTable: vrfDev.Table, + }, + } + vrfDevs[link.Attrs().Index] = iface.Name } else if link.Attrs().Name == DefaultLoopbackName { iface.Type = interfaces.Interface_LOOPBACK iface.Name = alias @@ -279,7 +309,7 @@ func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, g h.retrieveLinkDetails(link, iface, nsRef) // build interface details - retrieved.interfaces = append(retrieved.interfaces, &InterfaceDetails{ + ifaces = append(ifaces, &InterfaceDetails{ Interface: iface, Meta: &InterfaceMeta{ LinuxIfIndex: link.Attrs().Index, @@ -310,6 +340,14 @@ func (h *NetLinkHandler) retrieveInterfaces(nsList []*namespaces.NetNamespace, g }) } + // fill VRF names + for _, iface := range ifaces { + if vrfDev, inVrf := vrfDevs[iface.Meta.MasterIndex]; inVrf { + iface.Interface.VrfMasterInterface = vrfDev + } + } + retrieved.interfaces = append(retrieved.interfaces, ifaces...) + // switch back to the default namespace revert() } diff --git a/plugins/linux/ifplugin/linuxcalls/link_linuxcalls.go b/plugins/linux/ifplugin/linuxcalls/link_linuxcalls.go index fbf8d84e4e..e8786ae194 100644 --- a/plugins/linux/ifplugin/linuxcalls/link_linuxcalls.go +++ b/plugins/linux/ifplugin/linuxcalls/link_linuxcalls.go @@ -232,3 +232,48 @@ func (h *NetLinkHandler) AddVethInterfacePair(ifName, peerIfName string) error { } return nil } + +// AddVRFDevice configures new VRF network device. +func (h *NetLinkHandler) AddVRFDevice(vrfDevName string, routingTable uint32) error { + attrs := netlink.NewLinkAttrs() + attrs.Name = vrfDevName + link := &netlink.Vrf{ + LinkAttrs: attrs, + Table: routingTable, + } + if err := netlink.LinkAdd(link); err != nil { + return errors.Wrapf(err, "LinkAdd (vrf=%s, rt=%d)", + vrfDevName, routingTable) + } + return nil +} + +// PutInterfaceIntoVRF assigns Linux interface into a given VRF. +func (h *NetLinkHandler) PutInterfaceIntoVRF(ifName, vrfDevName string) error { + ifLink, err := h.GetLinkByName(ifName) + if err != nil { + return err + } + vrfLink, err := h.GetLinkByName(vrfDevName) + if err != nil { + return err + } + if err := netlink.LinkSetMasterByIndex(ifLink, vrfLink.Attrs().Index); err != nil { + return errors.Wrapf(err, "LinkSetMasterByIndex (interface=%s, vrf=%s, vrf-index=%d)", + ifName, vrfDevName, vrfLink.Attrs().Index) + } + return nil +} + +// RemoveInterfaceFromVRF un-assigns Linux interface from a given VRF. +func (h *NetLinkHandler) RemoveInterfaceFromVRF(ifName, vrfDevName string) error { + ifLink, err := h.GetLinkByName(ifName) + if err != nil { + return err + } + if err := netlink.LinkSetNoMaster(ifLink); err != nil { + return errors.Wrapf(err, "LinkSetNoMaster (interface=%s, vrf=%s)", + ifName, vrfDevName) + } + return nil +} diff --git a/plugins/linux/ifplugin/linuxcalls/netlink_api.go b/plugins/linux/ifplugin/linuxcalls/netlink_api.go index 107544edae..a430ecc3d1 100644 --- a/plugins/linux/ifplugin/linuxcalls/netlink_api.go +++ b/plugins/linux/ifplugin/linuxcalls/netlink_api.go @@ -72,6 +72,12 @@ type NetlinkAPI interface { // AddVethInterfacePair configures two connected VETH interfaces AddVethInterfacePair(ifName, peerIfName string) error + // AddVRFDevice configures new VRF network device. + AddVRFDevice(vrfDevName string, routingTable uint32) error + // PutInterfaceIntoVRF assigns Linux interface into a given VRF. + PutInterfaceIntoVRF(ifName, vrfDevName string) error + // RemoveInterfaceFromVRF un-assigns Linux interface from a given VRF. + RemoveInterfaceFromVRF(ifName, vrfDevName string) error // DeleteInterface removes the given interface. DeleteInterface(ifName string) error // SetInterfaceUp sets interface state to 'up' diff --git a/plugins/linux/l3plugin/descriptor/route.go b/plugins/linux/l3plugin/descriptor/route.go index 5ed49e2400..a73c8eadce 100644 --- a/plugins/linux/l3plugin/descriptor/route.go +++ b/plugins/linux/l3plugin/descriptor/route.go @@ -183,6 +183,21 @@ func (d *RouteDescriptor) updateRoute(route *linux_l3.Route, actionName string, // set link index netlinkRoute.LinkIndex = ifMeta.LinuxIfIndex + // set routing table + if ifMeta.VrfMasterIf != "" { + // - route depends on interface having an IP address + // - IP address depends on the interface already being in the VRF + // - VRF assignment depends on the VRF device being configured + // => conclusion: VRF device is configured at this point + vrfMeta, found := d.ifPlugin.GetInterfaceIndex().LookupByName(ifMeta.VrfMasterIf) + if !found || vrfMeta == nil { + err = errors.Errorf("failed to obtain metadata for VRF device %s", ifMeta.VrfMasterIf) + d.log.Error(err) + return err + } + netlinkRoute.Table = int(vrfMeta.VrfDevRT) + } + // set destination network dstNet, err := d.addrAlloc.GetOrParseIPAddress(route.DstNetwork, "", netalloc_api.IPAddressForm_ADDR_NET) @@ -274,9 +289,14 @@ func (d *RouteDescriptor) Dependencies(key string, route *linux_l3.Route) []kvs. }) dependencies = append(dependencies, kvs.Dependency{ Label: allocatedAddrAttached, - Key: ifmodel.InterfaceAddressKey( - route.OutgoingInterface, d.addrAlloc.CreateAddressAllocRef(network, "", false), - netalloc_api.IPAddressSource_ALLOC_REF), + AnyOf: kvs.AnyOfDependency{ + // match IP address assignment regardless of VRF (hence key prefix omitting suffix with vrf from the key) + KeyPrefixes: []string{ + ifmodel.InterfaceAddressKey( + route.OutgoingInterface, d.addrAlloc.CreateAddressAllocRef(network, "", false), + "", netalloc_api.IPAddressSource_ALLOC_REF), + }, + }, }) } else if gwAddr := net.ParseIP(getGwAddr(route)); gwAddr != nil && !gwAddr.IsUnspecified() { // GW is not netalloc reference but an actual IP @@ -296,7 +316,7 @@ func (d *RouteDescriptor) Dependencies(key string, route *linux_l3.Route) []kvs. } return false } - ifName, address, source, _, isAddrKey := ifmodel.ParseInterfaceAddressKey(key) + ifName, address, _, source, _, isAddrKey := ifmodel.ParseInterfaceAddressKey(key) if isAddrKey && source != netalloc_api.IPAddressSource_ALLOC_REF { if _, network, err := net.ParseCIDR(address); err == nil && network.Contains(gwAddr) { // GW address is inside the local network of the outgoing interface @@ -308,10 +328,9 @@ func (d *RouteDescriptor) Dependencies(key string, route *linux_l3.Route) []kvs. }, }, }) - } else if route.OutgoingInterface != "" { - // route also requires the interface to be in the L3 mode (have at least one - // IP address assigned) - we set this only for routes without GW and other - // routes will inherit this dependency transitively through GW-reachability dep. + } + if route.OutgoingInterface != "" { + // route also requires the interface to be in the L3 mode (have at least one IP address assigned) dependencies = append(dependencies, kvs.Dependency{ Label: routeOutInterfaceIPAddrDep, AnyOf: kvs.AnyOfDependency{ diff --git a/plugins/linux/l3plugin/linuxcalls/dump_route_linuxcalls.go b/plugins/linux/l3plugin/linuxcalls/dump_route_linuxcalls.go index 27bdc690b5..a81dd7bd5b 100644 --- a/plugins/linux/l3plugin/linuxcalls/dump_route_linuxcalls.go +++ b/plugins/linux/l3plugin/linuxcalls/dump_route_linuxcalls.go @@ -45,18 +45,26 @@ type retrievedRoutes struct { // interface. // works as filter, if set to zero, all routes in the namespace // are returned. -func (h *NetLinkHandler) GetRoutes(interfaceIdx int) (v4Routes, v6Routes []netlink.Route, err error) { - var link netlink.Link - if interfaceIdx != 0 { - // netlink.RouteList reads only link index - link = &netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Index: interfaceIdx}} +func (h *NetLinkHandler) GetRoutes(interfaceIdx, table int) (v4Routes, v6Routes []netlink.Route, err error) { + var routeFilter *netlink.Route + var filterMask uint64 + if interfaceIdx != 0 || table != 0 { + routeFilter = &netlink.Route{ + LinkIndex: interfaceIdx, + Table: table, + } + if interfaceIdx != 0 { + filterMask |= netlink.RT_FILTER_OIF + } + if table != 0 { + filterMask |= netlink.RT_FILTER_TABLE + } } - - v4Routes, err = netlink.RouteList(link, netlink.FAMILY_V4) + v4Routes, err = netlink.RouteListFiltered(netlink.FAMILY_V4, routeFilter, filterMask) if err != nil { return } - v6Routes, err = netlink.RouteList(link, netlink.FAMILY_V6) + v6Routes, err = netlink.RouteListFiltered(netlink.FAMILY_V6, routeFilter, filterMask) return } @@ -112,6 +120,15 @@ func (h *NetLinkHandler) retrieveRoutes(interfaces []string, goRoutineIdx, goRou break } + // obtain the associated routing table + var table int + if ifMeta.VrfMasterIf != "" { + vrfMeta, found := h.ifIndexes.LookupByName(ifMeta.VrfMasterIf) + if found { + table = int(vrfMeta.VrfDevRT) + } + } + // switch to the namespace of the interface revertNs, err := h.nsPlugin.SwitchToNamespace(nsCtx, ifMeta.Namespace) if err != nil { @@ -124,7 +141,7 @@ func (h *NetLinkHandler) retrieveRoutes(interfaces []string, goRoutineIdx, goRou } // get routes assigned to this interface - v4Routes, v6Routes, err := h.GetRoutes(ifMeta.LinuxIfIndex) + v4Routes, v6Routes, err := h.GetRoutes(ifMeta.LinuxIfIndex, table) revertNs() if err != nil { retrieved.err = err @@ -163,6 +180,7 @@ func (h *NetLinkHandler) retrieveRoutes(interfaces []string, goRoutineIdx, goRou NetlinkScope: route.Scope, Protocol: uint32(route.Protocol), MTU: uint32(route.MTU), + Table: uint32(route.Table), }, }) } diff --git a/plugins/linux/l3plugin/linuxcalls/netlink_api.go b/plugins/linux/l3plugin/linuxcalls/netlink_api.go index cf0151d94d..f1519a1407 100644 --- a/plugins/linux/l3plugin/linuxcalls/netlink_api.go +++ b/plugins/linux/l3plugin/linuxcalls/netlink_api.go @@ -50,6 +50,7 @@ type RouteMeta struct { NetlinkScope netlink.Scope `json:"link_scope"` Protocol uint32 `json:"protocol"` MTU uint32 `json:"mtu"` + Table uint32 `json:"table"` } // NetlinkAPI interface covers all methods inside linux calls package needed @@ -89,11 +90,12 @@ type NetlinkAPIRead interface { // with proto-modeled ARP data and additional metadata DumpARPEntries() ([]*ArpDetails, error) - // GetRoutes reads all configured static routes with the given outgoing - // interface. + // GetRoutes reads all configured static routes inside the given table + // and with the given outgoing interface. // works as filter, if set to zero, all routes in the namespace // are returned. - GetRoutes(interfaceIdx int) (v4Routes, v6Routes []netlink.Route, err error) + // Zero represents the main routing table. + GetRoutes(interfaceIdx, table int) (v4Routes, v6Routes []netlink.Route, err error) // DumpRoutes reads all route entries and returns them as details // with proto-modeled route data and additional metadata diff --git a/plugins/linux/l3plugin/linuxcalls/route_linuxcalls.go b/plugins/linux/l3plugin/linuxcalls/route_linuxcalls.go index e58d71edfa..55f533142d 100644 --- a/plugins/linux/l3plugin/linuxcalls/route_linuxcalls.go +++ b/plugins/linux/l3plugin/linuxcalls/route_linuxcalls.go @@ -25,7 +25,7 @@ func (h *NetLinkHandler) AddRoute(route *netlink.Route) error { return netlink.RouteAdd(route) } -// ReplaceRoute removes the static route +// ReplaceRoute replaces the static route func (h *NetLinkHandler) ReplaceRoute(route *netlink.Route) error { return netlink.RouteReplace(route) } diff --git a/proto/ligato/linux/interfaces/interface.pb.go b/proto/ligato/linux/interfaces/interface.pb.go index 244ad65c1d..8f351485a0 100644 --- a/proto/ligato/linux/interfaces/interface.pb.go +++ b/proto/ligato/linux/interfaces/interface.pb.go @@ -34,6 +34,10 @@ const ( Interface_TAP_TO_VPP Interface_Type = 2 // TAP created by VPP to have the Linux-side further configured Interface_LOOPBACK Interface_Type = 3 Interface_EXISTING Interface_Type = 4 + // In Linux, VRF is implemented as yet another type of netdevice (i.e. listed with `ip link show`). + // Network interfaces are then assigned to VRF simply by enslaving them to the VRF device. + // For more information, visit: https://www.kernel.org/doc/Documentation/networking/vrf.txt + Interface_VRF_DEVICE Interface_Type = 5 ) // Enum value maps for Interface_Type. @@ -44,6 +48,7 @@ var ( 2: "TAP_TO_VPP", 3: "LOOPBACK", 4: "EXISTING", + 5: "VRF_DEVICE", } Interface_Type_value = map[string]int32{ "UNDEFINED": 0, @@ -51,6 +56,7 @@ var ( "TAP_TO_VPP": 2, "LOOPBACK": 3, "EXISTING": 4, + "VRF_DEVICE": 5, } ) @@ -155,16 +161,24 @@ type Interface struct { IpAddresses []string `protobuf:"bytes,6,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` // PhysAddress represents physical address (MAC) of the interface. // Random address will be assigned if left empty. + // Not used (and not supported) by VRF devices. PhysAddress string `protobuf:"bytes,7,opt,name=phys_address,json=physAddress,proto3" json:"phys_address,omitempty"` // MTU is the maximum transmission unit value. Mtu uint32 `protobuf:"varint,8,opt,name=mtu,proto3" json:"mtu,omitempty"` // Types that are assignable to Link: // *Interface_Veth // *Interface_Tap + // *Interface_VrfDev Link isInterface_Link `protobuf_oneof:"link"` // Configure/Resync link only. IP/MAC addresses are expected to be configured // externally - i.e. by a different agent or manually via CLI. LinkOnly bool `protobuf:"varint,9,opt,name=link_only,json=linkOnly,proto3" json:"link_only,omitempty"` + // Reference to the logical name of a VRF_DEVICE interface. + // If defined, this interface will be enslaved to the VRF device and will thus become + // part of the VRF (L3-level separation) that the device represents. + // Interfaces enslaved to the same VRF_DEVICE master interface therefore + // comprise single VRF with a separate routing table. + VrfMasterInterface string `protobuf:"bytes,10,opt,name=vrf_master_interface,json=vrfMasterInterface,proto3" json:"vrf_master_interface,omitempty"` } func (x *Interface) Reset() { @@ -276,6 +290,13 @@ func (x *Interface) GetTap() *TapLink { return nil } +func (x *Interface) GetVrfDev() *VrfDevLink { + if x, ok := x.GetLink().(*Interface_VrfDev); ok { + return x.VrfDev + } + return nil +} + func (x *Interface) GetLinkOnly() bool { if x != nil { return x.LinkOnly @@ -283,6 +304,13 @@ func (x *Interface) GetLinkOnly() bool { return false } +func (x *Interface) GetVrfMasterInterface() string { + if x != nil { + return x.VrfMasterInterface + } + return "" +} + type isInterface_Link interface { isInterface_Link() } @@ -297,10 +325,17 @@ type Interface_Tap struct { Tap *TapLink `protobuf:"bytes,21,opt,name=tap,proto3,oneof"` } +type Interface_VrfDev struct { + // VRF_DEVICE-specific configuration + VrfDev *VrfDevLink `protobuf:"bytes,22,opt,name=vrf_dev,json=vrfDev,proto3,oneof"` +} + func (*Interface_Veth) isInterface_Link() {} func (*Interface_Tap) isInterface_Link() {} +func (*Interface_VrfDev) isInterface_Link() {} + type VethLink struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -415,6 +450,58 @@ func (x *TapLink) GetVppTapIfName() string { return "" } +type VrfDevLink struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Routing table associated with the VRF. + // Table ID is an 8-bit unsigned integer value. Please note that 253, 254 and 255 are reserved values + // for special routing tables (main, default, local). + // Multiple VRFs inside the same network namespace should each use a different routing table. + // For more information, visit: http://linux-ip.net/html/routing-tables.html + RoutingTable uint32 `protobuf:"varint,1,opt,name=routing_table,json=routingTable,proto3" json:"routing_table,omitempty"` +} + +func (x *VrfDevLink) Reset() { + *x = VrfDevLink{} + if protoimpl.UnsafeEnabled { + mi := &file_ligato_linux_interfaces_interface_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VrfDevLink) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VrfDevLink) ProtoMessage() {} + +func (x *VrfDevLink) ProtoReflect() protoreflect.Message { + mi := &file_ligato_linux_interfaces_interface_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VrfDevLink.ProtoReflect.Descriptor instead. +func (*VrfDevLink) Descriptor() ([]byte, []int) { + return file_ligato_linux_interfaces_interface_proto_rawDescGZIP(), []int{3} +} + +func (x *VrfDevLink) GetRoutingTable() uint32 { + if x != nil { + return x.RoutingTable + } + return 0 +} + var File_ligato_linux_interfaces_interface_proto protoreflect.FileDescriptor var file_ligato_linux_interfaces_interface_proto_rawDesc = []byte{ @@ -424,7 +511,7 @@ var file_ligato_linux_interfaces_interface_proto_rawDesc = []byte{ 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x1a, 0x26, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x95, 0x04, 0x0a, 0x09, 0x49, + 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x05, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x6c, 0x69, 0x67, @@ -451,45 +538,56 @@ var file_ligato_linux_interfaces_interface_proto_rawDesc = []byte{ 0x70, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x54, 0x61, 0x70, 0x4c, 0x69, 0x6e, 0x6b, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x70, + 0x12, 0x3e, 0x0a, 0x07, 0x76, 0x72, 0x66, 0x5f, 0x64, 0x65, 0x76, 0x18, 0x16, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x23, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x56, 0x72, 0x66, 0x44, + 0x65, 0x76, 0x4c, 0x69, 0x6e, 0x6b, 0x48, 0x00, 0x52, 0x06, 0x76, 0x72, 0x66, 0x44, 0x65, 0x76, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x4b, 0x0a, - 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x56, 0x45, 0x54, 0x48, 0x10, 0x01, 0x12, 0x0e, - 0x0a, 0x0a, 0x54, 0x41, 0x50, 0x5f, 0x54, 0x4f, 0x5f, 0x56, 0x50, 0x50, 0x10, 0x02, 0x12, 0x0c, - 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, - 0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x42, 0x06, 0x0a, 0x04, 0x6c, 0x69, - 0x6e, 0x6b, 0x22, 0xec, 0x02, 0x0a, 0x08, 0x56, 0x65, 0x74, 0x68, 0x4c, 0x69, 0x6e, 0x6b, 0x12, - 0x20, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x49, 0x66, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x6a, 0x0a, 0x16, 0x72, 0x78, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, - 0x5f, 0x6f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x34, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x56, 0x65, 0x74, 0x68, - 0x4c, 0x69, 0x6e, 0x6b, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, - 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x14, 0x72, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x6a, 0x0a, - 0x16, 0x74, 0x78, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x66, - 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, - 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x56, 0x65, 0x74, 0x68, 0x4c, 0x69, 0x6e, 0x6b, - 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, - 0x69, 0x6e, 0x67, 0x52, 0x14, 0x74, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, - 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x66, 0x0a, 0x12, 0x43, 0x68, 0x65, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x6c, 0x69, 0x6e, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x30, 0x0a, + 0x14, 0x76, 0x72, 0x66, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x76, 0x72, 0x66, + 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x22, + 0x5b, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, + 0x49, 0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x56, 0x45, 0x54, 0x48, 0x10, 0x01, + 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x41, 0x50, 0x5f, 0x54, 0x4f, 0x5f, 0x56, 0x50, 0x50, 0x10, 0x02, + 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x4f, 0x50, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0c, + 0x0a, 0x08, 0x45, 0x58, 0x49, 0x53, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, + 0x56, 0x52, 0x46, 0x5f, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x10, 0x05, 0x42, 0x06, 0x0a, 0x04, + 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0xec, 0x02, 0x0a, 0x08, 0x56, 0x65, 0x74, 0x68, 0x4c, 0x69, 0x6e, + 0x6b, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x49, 0x66, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x6a, 0x0a, 0x16, 0x72, 0x78, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, + 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x34, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, + 0x75, 0x78, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x56, 0x65, + 0x74, 0x68, 0x4c, 0x69, 0x6e, 0x6b, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, + 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x14, 0x72, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x12, - 0x19, 0x0a, 0x15, 0x43, 0x48, 0x4b, 0x53, 0x4d, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x4f, 0x41, 0x44, - 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x48, - 0x4b, 0x53, 0x4d, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x45, 0x4e, 0x41, 0x42, - 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x48, 0x4b, 0x53, 0x4d, 0x5f, 0x4f, - 0x46, 0x46, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, - 0x02, 0x22, 0x30, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x25, 0x0a, 0x0f, - 0x76, 0x70, 0x70, 0x5f, 0x74, 0x61, 0x70, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x70, 0x70, 0x54, 0x61, 0x70, 0x49, 0x66, 0x4e, - 0x61, 0x6d, 0x65, 0x42, 0x4a, 0x5a, 0x48, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, - 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x70, 0x70, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x76, 0x33, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2f, 0x6c, 0x69, - 0x6e, 0x75, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x3b, 0x6c, - 0x69, 0x6e, 0x75, 0x78, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6a, 0x0a, 0x16, 0x74, 0x78, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x5f, 0x6f, + 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x34, 0x2e, 0x6c, 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x2e, 0x56, 0x65, 0x74, 0x68, 0x4c, 0x69, + 0x6e, 0x6b, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, + 0x61, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x14, 0x74, 0x78, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, + 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x66, 0x0a, 0x12, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x4f, 0x66, 0x66, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, + 0x67, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x48, 0x4b, 0x53, 0x4d, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x4f, + 0x41, 0x44, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, + 0x43, 0x48, 0x4b, 0x53, 0x4d, 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x45, 0x4e, + 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x48, 0x4b, 0x53, 0x4d, + 0x5f, 0x4f, 0x46, 0x46, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, + 0x44, 0x10, 0x02, 0x22, 0x30, 0x0a, 0x07, 0x54, 0x61, 0x70, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x25, + 0x0a, 0x0f, 0x76, 0x70, 0x70, 0x5f, 0x74, 0x61, 0x70, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x70, 0x70, 0x54, 0x61, 0x70, 0x49, + 0x66, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x0a, 0x56, 0x72, 0x66, 0x44, 0x65, 0x76, 0x4c, + 0x69, 0x6e, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x4a, 0x5a, 0x48, 0x67, 0x6f, 0x2e, 0x6c, + 0x69, 0x67, 0x61, 0x74, 0x6f, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x70, 0x70, 0x2d, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x69, 0x67, 0x61, + 0x74, 0x6f, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, + 0x63, 0x65, 0x73, 0x3b, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, + 0x61, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -505,27 +603,29 @@ func file_ligato_linux_interfaces_interface_proto_rawDescGZIP() []byte { } var file_ligato_linux_interfaces_interface_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_ligato_linux_interfaces_interface_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_ligato_linux_interfaces_interface_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_ligato_linux_interfaces_interface_proto_goTypes = []interface{}{ (Interface_Type)(0), // 0: ligato.linux.interfaces.Interface.Type (VethLink_ChecksumOffloading)(0), // 1: ligato.linux.interfaces.VethLink.ChecksumOffloading (*Interface)(nil), // 2: ligato.linux.interfaces.Interface (*VethLink)(nil), // 3: ligato.linux.interfaces.VethLink (*TapLink)(nil), // 4: ligato.linux.interfaces.TapLink - (*namespace.NetNamespace)(nil), // 5: ligato.linux.namespace.NetNamespace + (*VrfDevLink)(nil), // 5: ligato.linux.interfaces.VrfDevLink + (*namespace.NetNamespace)(nil), // 6: ligato.linux.namespace.NetNamespace } var file_ligato_linux_interfaces_interface_proto_depIdxs = []int32{ 0, // 0: ligato.linux.interfaces.Interface.type:type_name -> ligato.linux.interfaces.Interface.Type - 5, // 1: ligato.linux.interfaces.Interface.namespace:type_name -> ligato.linux.namespace.NetNamespace + 6, // 1: ligato.linux.interfaces.Interface.namespace:type_name -> ligato.linux.namespace.NetNamespace 3, // 2: ligato.linux.interfaces.Interface.veth:type_name -> ligato.linux.interfaces.VethLink 4, // 3: ligato.linux.interfaces.Interface.tap:type_name -> ligato.linux.interfaces.TapLink - 1, // 4: ligato.linux.interfaces.VethLink.rx_checksum_offloading:type_name -> ligato.linux.interfaces.VethLink.ChecksumOffloading - 1, // 5: ligato.linux.interfaces.VethLink.tx_checksum_offloading:type_name -> ligato.linux.interfaces.VethLink.ChecksumOffloading - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 5, // 4: ligato.linux.interfaces.Interface.vrf_dev:type_name -> ligato.linux.interfaces.VrfDevLink + 1, // 5: ligato.linux.interfaces.VethLink.rx_checksum_offloading:type_name -> ligato.linux.interfaces.VethLink.ChecksumOffloading + 1, // 6: ligato.linux.interfaces.VethLink.tx_checksum_offloading:type_name -> ligato.linux.interfaces.VethLink.ChecksumOffloading + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_ligato_linux_interfaces_interface_proto_init() } @@ -570,10 +670,23 @@ func file_ligato_linux_interfaces_interface_proto_init() { return nil } } + file_ligato_linux_interfaces_interface_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VrfDevLink); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_ligato_linux_interfaces_interface_proto_msgTypes[0].OneofWrappers = []interface{}{ (*Interface_Veth)(nil), (*Interface_Tap)(nil), + (*Interface_VrfDev)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -581,7 +694,7 @@ func file_ligato_linux_interfaces_interface_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ligato_linux_interfaces_interface_proto_rawDesc, NumEnums: 2, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/ligato/linux/interfaces/interface.proto b/proto/ligato/linux/interfaces/interface.proto index deda749a1c..8d346a2e97 100644 --- a/proto/ligato/linux/interfaces/interface.proto +++ b/proto/ligato/linux/interfaces/interface.proto @@ -13,6 +13,11 @@ message Interface { TAP_TO_VPP = 2; // TAP created by VPP to have the Linux-side further configured LOOPBACK = 3; EXISTING = 4; + + // In Linux, VRF is implemented as yet another type of netdevice (i.e. listed with `ip link show`). + // Network interfaces are then assigned to VRF simply by enslaving them to the VRF device. + // For more information, visit: https://www.kernel.org/doc/Documentation/networking/vrf.txt + VRF_DEVICE = 5; }; // Name is mandatory field representing logical name for the interface. @@ -41,9 +46,10 @@ message Interface { // PhysAddress represents physical address (MAC) of the interface. // Random address will be assigned if left empty. + // Not used (and not supported) by VRF devices. string phys_address = 7; - /* MTU is the maximum transmission unit value. */ + // MTU is the maximum transmission unit value. uint32 mtu = 8; oneof link { @@ -52,11 +58,21 @@ message Interface { // TAP_TO_VPP-specific configuration TapLink tap = 21; + + // VRF_DEVICE-specific configuration + VrfDevLink vrf_dev = 22; }; // Configure/Resync link only. IP/MAC addresses are expected to be configured // externally - i.e. by a different agent or manually via CLI. bool link_only = 9; + + // Reference to the logical name of a VRF_DEVICE interface. + // If defined, this interface will be enslaved to the VRF device and will thus become + // part of the VRF (L3-level separation) that the device represents. + // Interfaces enslaved to the same VRF_DEVICE master interface therefore + // comprise single VRF with a separate routing table. + string vrf_master_interface = 10; }; message VethLink { @@ -80,3 +96,14 @@ message TapLink { // Logical name of the VPP TAP interface (mandatory for TAP_TO_VPP) string vpp_tap_if_name = 1; }; + + +message VrfDevLink { + // Routing table associated with the VRF. + // Table ID is an 8-bit unsigned integer value. Please note that 253, 254 and 255 are reserved values + // for special routing tables (main, default, local). + // Multiple VRFs inside the same network namespace should each use a different routing table. + // For more information, visit: http://linux-ip.net/html/routing-tables.html + uint32 routing_table = 1; +}; + diff --git a/proto/ligato/linux/interfaces/models.go b/proto/ligato/linux/interfaces/models.go index 0f150ded4f..8a24ead51d 100644 --- a/proto/ligato/linux/interfaces/models.go +++ b/proto/ligato/linux/interfaces/models.go @@ -66,11 +66,21 @@ const ( // interfaceAddressKeyPrefix is used as a common prefix for keys derived from // interfaces to represent assigned IP addresses. - interfaceAddressKeyPrefix = "linux/interface/{iface}/address/" + interfaceAddrKeyPrefix = "linux/interface/{iface}/address/" - // interfaceAddressKeyTemplate is a template for (derived) key representing IP address + // interfaceAddrKeyTmpl is a template for (derived) key representing IP address // (incl. mask) assigned to a Linux interface (referenced by the logical name). - interfaceAddressKeyTemplate = interfaceAddressKeyPrefix + "{address-source}/{address}" + interfaceAddrKeyTmpl = interfaceAddrKeyPrefix + "{address-source}/{address}" + + // interfaceAddrKeyTmplWithVrf is extended template for keys of derived + // interface addresses which also includes the VRF name. + interfaceAddrKeyTmplWithVrf = interfaceAddrKeyTmpl + "/vrf/{vrf}" + + /* Interface VRF (derived) */ + + // interfaceVrfKeyTmpl is a template for (derived) key representing assignment + // of a Linux interface into a VRF. + interfaceVrfKeyTmpl = "linux/interface/{iface}/vrf/{vrf}" ) const ( @@ -125,11 +135,13 @@ func InterfaceAddressPrefix(iface string) string { if iface == "" { iface = InvalidKeyPart } - return strings.Replace(interfaceAddressKeyPrefix, "{iface}", iface, 1) + return strings.Replace(interfaceAddrKeyPrefix, "{iface}", iface, 1) } // InterfaceAddressKey returns key representing IP address assigned to Linux interface. -func InterfaceAddressKey(iface string, address string, source netalloc.IPAddressSource) string { +// With undefined vrf the returned key can be also used as a key prefix, matching derived +// interface address key regardless of the VRF to which it belongs. +func InterfaceAddressKey(iface, address, vrf string, source netalloc.IPAddressSource) string { if iface == "" { iface = InvalidKeyPart } @@ -144,25 +156,35 @@ func InterfaceAddressKey(iface string, address string, source netalloc.IPAddress src = strings.ToLower(src) // construct key without validating the IP address - key := strings.Replace(interfaceAddressKeyTemplate, "{iface}", iface, 1) + tmpl := interfaceAddrKeyTmpl + if vrf != "" { + tmpl = interfaceAddrKeyTmplWithVrf + } + key := strings.Replace(tmpl, "{iface}", iface, 1) key = strings.Replace(key, "{address-source}", src, 1) key = strings.Replace(key, "{address}", address, 1) + if vrf != "" { + key = strings.Replace(key, "{vrf}", vrf, 1) + } return key } // ParseInterfaceAddressKey parses interface address from key derived // from interface by InterfaceAddressKey(). -func ParseInterfaceAddressKey(key string) (iface, address string, source netalloc.IPAddressSource, invalidKey, isAddrKey bool) { +func ParseInterfaceAddressKey(key string) (iface, address, vrf string, source netalloc.IPAddressSource, invalidKey, isAddrKey bool) { parts := strings.Split(key, "/") if len(parts) < 4 || parts[0] != "linux" || parts[1] != "interface" { return } addrIdx := -1 + vrfIdx := len(parts) for idx, part := range parts { - if part == "address" { + switch part { + case "address": addrIdx = idx - break + case "vrf": + vrfIdx = idx } } if addrIdx == -1 { @@ -193,10 +215,73 @@ func ParseInterfaceAddressKey(key string) (iface, address string, source netallo source = netalloc.IPAddressSource(srcInt) // return address as is (not parsed - this is done by the netalloc plugin) - address = strings.Join(parts[addrIdx+2:], "/") + address = strings.Join(parts[addrIdx+2:vrfIdx], "/") if address == "" { invalidKey = true } + + // parse vrf + if vrfIdx < len(parts) { + if vrfIdx == len(parts)-1 { + invalidKey = true + return + } + vrf = parts[vrfIdx+1] + } + return +} + +// InterfaceVrfKey returns key representing assignment of a Linux interface into a VRF. +func InterfaceVrfKey(iface, vrf string) string { + if iface == "" { + iface = InvalidKeyPart + } + if vrf == "" { + vrf = InvalidKeyPart + } + + key := strings.Replace(interfaceVrfKeyTmpl, "{iface}", iface, 1) + key = strings.Replace(key, "{vrf}", vrf, 1) + return key +} + +// ParseInterfaceVrfKey parses interface VRF from key derived +// from interface by InterfaceVrfKey(). +func ParseInterfaceVrfKey(key string) (iface, vrf string, invalidKey, isVrfKey bool) { + parts := strings.Split(key, "/") + if len(parts) < 4 || parts[0] != "linux" || parts[1] != "interface" { + return + } + + vrfIdx := -1 + for idx, part := range parts { + switch part { + case "address": + // avoid collision with InterfaceAddressKey + return + case "vrf": + vrfIdx = idx + } + } + if vrfIdx == -1 { + return + } + isVrfKey = true + + // parse interface name + iface = strings.Join(parts[2:vrfIdx], "/") + if iface == "" { + iface = InvalidKeyPart + invalidKey = true + } + + // parse VRF + if vrfIdx == len(parts)-1 { + invalidKey = true + vrf = InvalidKeyPart + return + } + vrf = parts[vrfIdx+1] return } diff --git a/proto/ligato/linux/interfaces/models_test.go b/proto/ligato/linux/interfaces/models_test.go index 89db05ba13..a16b26e225 100644 --- a/proto/ligato/linux/interfaces/models_test.go +++ b/proto/ligato/linux/interfaces/models_test.go @@ -25,6 +25,7 @@ func TestInterfaceAddressKey(t *testing.T) { name string iface string address string + vrf string source netalloc.IPAddressSource expectedKey string }{ @@ -104,14 +105,30 @@ func TestInterfaceAddressKey(t *testing.T) { source: netalloc.IPAddressSource_STATIC, expectedKey: "linux/interface/memif0/address/alloc_ref/alloc:net1/IPV6_ADDR", }, + { + name: "IPv4 address inside VRF", + iface: "memif0", + address: "192.168.1.12/24", + vrf: "blue", + source: netalloc.IPAddressSource_STATIC, + expectedKey: "linux/interface/memif0/address/static/192.168.1.12/24/vrf/blue", + }, + { + name: "IPv6 address inside VRF", + iface: "memif0", + address: "2001:db8::/32", + vrf: "red", + source: netalloc.IPAddressSource_STATIC, + expectedKey: "linux/interface/memif0/address/static/2001:db8::/32/vrf/red", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - key := InterfaceAddressKey(test.iface, test.address, test.source) + key := InterfaceAddressKey(test.iface, test.address, test.vrf, test.source) if key != test.expectedKey { - t.Errorf("failed for: iface=%s address=%s source=%s\n"+ + t.Errorf("failed for: iface=%s address=%s vrf=%s source=%s\n"+ "expected key:\n\t%q\ngot key:\n\t%q", - test.iface, test.address, string(test.source), test.expectedKey, key) + test.iface, test.address, test.vrf, string(test.source), test.expectedKey, key) } }) } @@ -123,6 +140,7 @@ func TestParseInterfaceAddressKey(t *testing.T) { key string expectedIface string expectedIfaceAddr string + expectedIfaceVrf string expectedSource netalloc.IPAddressSource expectedInvalidKey bool expectedIsAddrKey bool @@ -175,6 +193,24 @@ func TestParseInterfaceAddressKey(t *testing.T) { expectedSource: netalloc.IPAddressSource_FROM_DHCP, expectedIsAddrKey: true, }, + { + name: "IPv4 address inside VRF", + key: "linux/interface/memif0/address/static/192.168.1.12/24/vrf/blue", + expectedIface: "memif0", + expectedIfaceAddr: "192.168.1.12/24", + expectedIfaceVrf: "blue", + expectedSource: netalloc.IPAddressSource_STATIC, + expectedIsAddrKey: true, + }, + { + name: "IPv6 address", + key: "linux/interface/tap1/address/static/2001:db8:85a3::8a2e:370:7334/48/vrf/red", + expectedIface: "tap1", + expectedIfaceAddr: "2001:db8:85a3::8a2e:370:7334/48", + expectedIfaceVrf: "red", + expectedSource: netalloc.IPAddressSource_STATIC, + expectedIsAddrKey: true, + }, { name: "invalid interface", key: "linux/interface//address/static/10.10.10.10/30", @@ -243,6 +279,13 @@ func TestParseInterfaceAddressKey(t *testing.T) { expectedIfaceAddr: "", expectedIsAddrKey: false, }, + { + name: "not interface address key #2", + key: "linux/interface/veth0/vrf/blue", + expectedIface: "", + expectedIfaceAddr: "", + expectedIsAddrKey: false, + }, { name: "invalid address source", key: "linux/interface/memif0/address//192.168.1.12/24", @@ -267,7 +310,7 @@ func TestParseInterfaceAddressKey(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - iface, ipAddr, source, invalidKey, isAddrKey := ParseInterfaceAddressKey(test.key) + iface, ipAddr, vrf, source, invalidKey, isAddrKey := ParseInterfaceAddressKey(test.key) if isAddrKey != test.expectedIsAddrKey { t.Errorf("expected isAddrKey: %v\tgot: %v", test.expectedIsAddrKey, isAddrKey) } @@ -283,6 +326,122 @@ func TestParseInterfaceAddressKey(t *testing.T) { if ipAddr != test.expectedIfaceAddr { t.Errorf("expected ipAddr: %s\tgot: %s", test.expectedIfaceAddr, ipAddr) } + if vrf != test.expectedIfaceVrf { + t.Errorf("expected vrf: %s\tgot: %s", test.expectedIfaceVrf, vrf) + } + }) + } +} + +func TestInterfaceVrfKey(t *testing.T) { + tests := []struct { + name string + iface string + vrf string + expectedKey string + }{ + { + name: "VRF 'blue'", + iface: "veth0", + vrf: "blue", + expectedKey: "linux/interface/veth0/vrf/blue", + }, + { + name: "VRF 'red'", + iface: "veth0", + vrf: "red", + expectedKey: "linux/interface/veth0/vrf/red", + }, + { + name: "invalid interface", + iface: "", + vrf: "blue", + expectedKey: "linux/interface//vrf/blue", + }, + { + name: "invalid VRF", + iface: "veth0", + vrf: "", + expectedKey: "linux/interface/veth0/vrf/", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + key := InterfaceVrfKey(test.iface, test.vrf) + if key != test.expectedKey { + t.Errorf("failed for: iface=%s vrf=%s\n"+ + "expected key:\n\t%q\ngot key:\n\t%q", + test.iface, test.vrf, test.expectedKey, key) + } }) } } + +func TestParseInterfaceVrfKey(t *testing.T) { + tests := []struct { + name string + key string + expectedIface string + expectedIfaceVrf string + expectedInvalidKey bool + expectedIsVrfKey bool + }{ + { + name: "VRF 'blue'", + key: "linux/interface/veth0/vrf/blue", + expectedIface: "veth0", + expectedIfaceVrf: "blue", + expectedIsVrfKey: true, + }, + { + name: "VRF 'red'", + key: "linux/interface/veth0/vrf/red", + expectedIface: "veth0", + expectedIfaceVrf: "red", + expectedIsVrfKey: true, + }, + { + name: "missing interface", + key: "linux/interface//vrf/blue", + expectedIface: "", + expectedIfaceVrf: "blue", + expectedInvalidKey: true, + expectedIsVrfKey: true, + }, + { + name: "missing VRF", + key: "linux/interface/veth0/vrf", + expectedIface: "veth0", + expectedIfaceVrf: "", + expectedInvalidKey: true, + expectedIsVrfKey: true, + }, + { + name: "not interface VRF key", + key: "linux/interface/tap1/address/static/2001:db8:85a3::8a2e:370:7334/48", + expectedIsVrfKey: false, + }, + { + name: "not interface VRF key #2", + key: "linux/interface/tap1/address/static/192.168.1.1/32/vrf/blue", + expectedIsVrfKey: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + iface, vrf, invalidKey, isVrfKey := ParseInterfaceVrfKey(test.key) + if isVrfKey != test.expectedIsVrfKey { + t.Errorf("expected isVrfKey: %v\tgot: %v", test.expectedIsVrfKey, isVrfKey) + } + if invalidKey != test.expectedInvalidKey { + t.Errorf("expected invalidKey: %v\tgot: %v", test.expectedInvalidKey, invalidKey) + } + if iface != test.expectedIface { + t.Errorf("expected iface: %s\tgot: %s", test.expectedIface, iface) + } + if vrf != test.expectedIfaceVrf { + t.Errorf("expected vrf: %s\tgot: %s", test.expectedIfaceVrf, vrf) + } + }) + } +} \ No newline at end of file diff --git a/tests/e2e/080_vrf_test.go b/tests/e2e/080_vrf_test.go new file mode 100644 index 0000000000..7e35bffdf3 --- /dev/null +++ b/tests/e2e/080_vrf_test.go @@ -0,0 +1,444 @@ +// Copyright (c) 2020 Pantheon.tech +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + + "go.ligato.io/vpp-agent/v3/proto/ligato/kvscheduler" + "go.ligato.io/vpp-agent/v3/proto/ligato/linux/interfaces" + "go.ligato.io/vpp-agent/v3/proto/ligato/linux/l3" + "go.ligato.io/vpp-agent/v3/proto/ligato/linux/namespace" + "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/interfaces" + "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3" +) + +// +// +----------------------------------------------------------------------+ +// | VPP | +// | +-------------------------------+ +-------------------------------+ | +// | | VRF 1 | | VRF 2 | | +// | | +-------------------+ | | +-------------------+ | | +// | | | 192.168.1.1/24 | | | | 192.168.1.1./24 | | | +// | | +---------+---------+ | | +----------+--------+ | | +// | +------------------|------------+ +-------------------|-----------+ | +// +--------------------|-----------------------------------|-------------+ +// Linux | (TAP) | (TAP) +// +------------------|------------+ +-------------------|-----------+ +// | VRF_1 | | | VRF_2 | | +// | +---------+---------+ | | +---------+--------+ | +// | | 192.168.1.2/24 | | | | 192.168.1.2/24 | | +// | +-------------------+ | | +------------------+ | +// +-------------------------------+ +-------------------------------+ +// +func TestVRFsWithSameSubnets(t *testing.T) { + ctx := setupE2E(t) + defer ctx.teardownE2E() + + const ( + vrf1ID = 1 + vrf2ID = 2 + vrf1Label = "vrf-1" + vrf2Label = "vrf-2" + vrfVppIP = "192.168.1.1" + vrfLinuxIP = "192.168.1.2" + vrfSubnetMask = "/24" + tapNameSuffix = "-tap" + msName = "microservice1" + ) + + // TAP interfaces + vrf1VppTap := &vpp_interfaces.Interface{ + Name: vrf1Label + tapNameSuffix, + Type: vpp_interfaces.Interface_TAP, + Enabled: true, + Vrf: vrf1ID, + IpAddresses: []string{vrfVppIP + vrfSubnetMask}, + Link: &vpp_interfaces.Interface_Tap{ + Tap: &vpp_interfaces.TapLink{ + Version: 2, + ToMicroservice: msNamePrefix + msName, + }, + }, + } + vrf1LinuxTap := &linux_interfaces.Interface{ + Name: vrf1Label + tapNameSuffix, + Type: linux_interfaces.Interface_TAP_TO_VPP, + Enabled: true, + VrfMasterInterface: vrf1Label, + IpAddresses: []string{vrfLinuxIP + vrfSubnetMask}, + Link: &linux_interfaces.Interface_Tap{ + Tap: &linux_interfaces.TapLink{ + VppTapIfName: vrf1Label + tapNameSuffix, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + vrf2VppTap := &vpp_interfaces.Interface{ + Name: vrf2Label + tapNameSuffix, + Type: vpp_interfaces.Interface_TAP, + Enabled: true, + Vrf: vrf2ID, + IpAddresses: []string{vrfVppIP + vrfSubnetMask}, + Link: &vpp_interfaces.Interface_Tap{ + Tap: &vpp_interfaces.TapLink{ + Version: 2, + ToMicroservice: msNamePrefix + msName, + }, + }, + } + vrf2LinuxTap := &linux_interfaces.Interface{ + Name: vrf2Label + tapNameSuffix, + Type: linux_interfaces.Interface_TAP_TO_VPP, + VrfMasterInterface: vrf2Label, + Enabled: true, + IpAddresses: []string{vrfLinuxIP + vrfSubnetMask}, + Link: &linux_interfaces.Interface_Tap{ + Tap: &linux_interfaces.TapLink{ + VppTapIfName: vrf2Label + tapNameSuffix, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + + // VRFs + vppVrf1 := &vpp_l3.VrfTable{ + Id: vrf1ID, + Label: vrf1Label, + Protocol: vpp_l3.VrfTable_IPV4, + } + vppVrf2 := &vpp_l3.VrfTable{ + Id: vrf2ID, + Label: vrf2Label, + Protocol: vpp_l3.VrfTable_IPV4, + } + linuxVrf1 := &linux_interfaces.Interface{ + Name: vrf1Label, + Type: linux_interfaces.Interface_VRF_DEVICE, + Enabled: true, + Link: &linux_interfaces.Interface_VrfDev{ + VrfDev: &linux_interfaces.VrfDevLink{ + RoutingTable: vrf1ID, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + linuxVrf2 := &linux_interfaces.Interface{ + Name: vrf2Label, + Type: linux_interfaces.Interface_VRF_DEVICE, + Enabled: true, + Link: &linux_interfaces.Interface_VrfDev{ + VrfDev: &linux_interfaces.VrfDevLink{ + RoutingTable: vrf2ID, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + + ctx.startMicroservice(msName) + + // configure everything in one resync + err := ctx.grpcClient.ResyncConfig( + vppVrf1, vppVrf2, + linuxVrf1, linuxVrf2, + vrf1VppTap, vrf1LinuxTap, + vrf2VppTap, vrf2LinuxTap, + ) + Expect(err).ToNot(HaveOccurred()) + + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf1VppTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2LinuxTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2VppTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(linuxVrf1)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(linuxVrf2)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vppVrf1)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vppVrf2)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + + // try to ping in both VRFs + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + + Expect(ctx.agentInSync()).To(BeTrue()) + + // restart microservice + ctx.stopMicroservice(msName) + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_PENDING)) + Eventually(ctx.getValueStateClb(vrf2LinuxTap)).Should(Equal(kvscheduler.ValueState_PENDING)) + Expect(ctx.agentInSync()).To(BeTrue()) + + ctx.startMicroservice(msName) + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Eventually(ctx.getValueStateClb(vrf2LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.agentInSync()).To(BeTrue()) + + // re-create Linux VRF1 + err = ctx.grpcClient.ChangeRequest(). + Delete(linuxVrf1).Send(context.Background()) + Expect(err).ToNot(HaveOccurred()) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).ToNot(Succeed()) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + + err = ctx.grpcClient.ChangeRequest().Update( + linuxVrf1, + ).Send(context.Background()) + Expect(err).ToNot(HaveOccurred()) + + Eventually(ctx.pingFromMsClb(msName, vrfVppIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).Should(Succeed()) + Expect(ctx.pingFromMs(msName, vrfVppIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.agentInSync()).To(BeTrue()) +} + +// +// +--------------------------------------------------------------------------------+ +// | VPP inter-VRF RT: | +// | +-------------------------------+ .2.0/24 +-------------------------------+ | +// | | VRF 1 | ---------> | VRF 2 | | +// | | +-------------------+ | | +-------------------+ | | +// | | | 192.168.1.1/24 | | .1.0/24 | | 192.168.2.1./24 | | | +// | | +---------+---------+ | <--------- | +----------+--------+ | | +// | +------------------|------------+ +-------------------|-----------+ | +// +--------------------|---------------------------------------------|-------------+ +// Linux | (TAP) | (TAP) +// +------------------|------------+ +-------------------|-----------+ +// | VRF_1 | | | VRF_2 | | +// | +---------+---------+ | | +---------+--------+ | +// | | 192.168.1.2/24 | | | | 192.168.2.2/24 | | +// | +-------------------+ | | +------------------+ | +// | ^ | | ^ | +// | | | | | | +// | RT: 192.168.2.0/24 --+ | | RT: 192.168.1.0/24 --+ | +// +-------------------------------+ +-------------------------------+ +// +func TestVRFRoutes(t *testing.T) { + ctx := setupE2E(t) + defer ctx.teardownE2E() + + const ( + vrf1ID = 1 + vrf2ID = 2 + vrf1Label = "vrf-1" + vrf2Label = "vrf-2" + vrfSubnetMask = "/24" + vrf1Subnet = "192.168.1.0" + vrfSubnetMask + vrf1VppIP = "192.168.1.1" + vrf1LinuxIP = "192.168.1.2" + vrf2Subnet = "192.168.2.0" + vrfSubnetMask + vrf2VppIP = "192.168.2.1" + vrf2LinuxIP = "192.168.2.2" + tapNameSuffix = "-tap" + msName = "microservice1" + ) + + // TAP interfaces + vrf1VppTap := &vpp_interfaces.Interface{ + Name: vrf1Label + tapNameSuffix, + Type: vpp_interfaces.Interface_TAP, + Enabled: true, + Vrf: vrf1ID, + IpAddresses: []string{vrf1VppIP + vrfSubnetMask}, + Link: &vpp_interfaces.Interface_Tap{ + Tap: &vpp_interfaces.TapLink{ + Version: 2, + ToMicroservice: msNamePrefix + msName, + }, + }, + } + vrf1LinuxTap := &linux_interfaces.Interface{ + Name: vrf1Label + tapNameSuffix, + Type: linux_interfaces.Interface_TAP_TO_VPP, + Enabled: true, + VrfMasterInterface: vrf1Label, + IpAddresses: []string{vrf1LinuxIP + vrfSubnetMask}, + Link: &linux_interfaces.Interface_Tap{ + Tap: &linux_interfaces.TapLink{ + VppTapIfName: vrf1Label + tapNameSuffix, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + vrf2VppTap := &vpp_interfaces.Interface{ + Name: vrf2Label + tapNameSuffix, + Type: vpp_interfaces.Interface_TAP, + Enabled: true, + Vrf: vrf2ID, + IpAddresses: []string{vrf2VppIP + vrfSubnetMask}, + Link: &vpp_interfaces.Interface_Tap{ + Tap: &vpp_interfaces.TapLink{ + Version: 2, + ToMicroservice: msNamePrefix + msName, + }, + }, + } + vrf2LinuxTap := &linux_interfaces.Interface{ + Name: vrf2Label + tapNameSuffix, + Type: linux_interfaces.Interface_TAP_TO_VPP, + VrfMasterInterface: vrf2Label, + Enabled: true, + IpAddresses: []string{vrf2LinuxIP + vrfSubnetMask}, + Link: &linux_interfaces.Interface_Tap{ + Tap: &linux_interfaces.TapLink{ + VppTapIfName: vrf2Label + tapNameSuffix, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + + // VRFs + vppVrf1 := &vpp_l3.VrfTable{ + Id: vrf1ID, + Label: vrf1Label, + Protocol: vpp_l3.VrfTable_IPV4, + } + vppVrf2 := &vpp_l3.VrfTable{ + Id: vrf2ID, + Label: vrf2Label, + Protocol: vpp_l3.VrfTable_IPV4, + } + linuxVrf1 := &linux_interfaces.Interface{ + Name: vrf1Label, + Type: linux_interfaces.Interface_VRF_DEVICE, + Enabled: true, + Link: &linux_interfaces.Interface_VrfDev{ + VrfDev: &linux_interfaces.VrfDevLink{ + RoutingTable: vrf1ID, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + linuxVrf2 := &linux_interfaces.Interface{ + Name: vrf2Label, + Type: linux_interfaces.Interface_VRF_DEVICE, + Enabled: true, + Link: &linux_interfaces.Interface_VrfDev{ + VrfDev: &linux_interfaces.VrfDevLink{ + RoutingTable: vrf2ID, + }, + }, + Namespace: &linux_namespace.NetNamespace{ + Type: linux_namespace.NetNamespace_MICROSERVICE, + Reference: msNamePrefix + msName, + }, + } + + // Routes + vrf1VppRoute := &vpp_l3.Route{ + Type: vpp_l3.Route_INTER_VRF, + VrfId: vrf1ID, + DstNetwork: vrf2Subnet, + NextHopAddr: vrf2LinuxIP, + ViaVrfId: vrf2ID, + } + vrf2VppRoute := &vpp_l3.Route{ + Type: vpp_l3.Route_INTER_VRF, + VrfId: vrf2ID, + DstNetwork: vrf1Subnet, + NextHopAddr: vrf1LinuxIP, + ViaVrfId: vrf1ID, + } + vrf1LinuxRoute := &linux_l3.Route{ + OutgoingInterface: vrf1Label + tapNameSuffix, + Scope: linux_l3.Route_GLOBAL, + DstNetwork: vrf2Subnet, + GwAddr: vrf1VppIP, + } + vrf2LinuxRoute := &linux_l3.Route{ + OutgoingInterface: vrf2Label + tapNameSuffix, + Scope: linux_l3.Route_GLOBAL, + DstNetwork: vrf1Subnet, + GwAddr: vrf2VppIP, + } + + ctx.startMicroservice(msName) + + // configure everything in one resync + err := ctx.grpcClient.ResyncConfig( + vppVrf1, vppVrf2, + linuxVrf1, linuxVrf2, + vrf1VppTap, vrf1LinuxTap, + vrf2VppTap, vrf2LinuxTap, + vrf1VppRoute, vrf2VppRoute, + vrf1LinuxRoute, vrf2LinuxRoute, + ) + Expect(err).ToNot(HaveOccurred()) + + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf1VppTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2LinuxTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2VppTap)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf1VppRoute)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2VppRoute)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf1LinuxRoute)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.getValueState(vrf2LinuxRoute)).To(Equal(kvscheduler.ValueState_CONFIGURED)) + + // try to ping across VRFs + Expect(ctx.pingFromMs(msName, vrf2LinuxIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.pingFromMs(msName, vrf1LinuxIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.agentInSync()).To(BeTrue()) + + // restart microservice + ctx.stopMicroservice(msName) + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_PENDING)) + Eventually(ctx.getValueStateClb(vrf2LinuxTap)).Should(Equal(kvscheduler.ValueState_PENDING)) + Expect(ctx.agentInSync()).To(BeTrue()) + + ctx.startMicroservice(msName) + Eventually(ctx.getValueStateClb(vrf1LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Eventually(ctx.getValueStateClb(vrf2LinuxTap)).Should(Equal(kvscheduler.ValueState_CONFIGURED)) + Expect(ctx.pingFromMs(msName, vrf2LinuxIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.pingFromMs(msName, vrf1LinuxIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.agentInSync()).To(BeTrue()) + + // re-create Linux VRF1 + err = ctx.grpcClient.ChangeRequest(). + Delete(linuxVrf1).Send(context.Background()) + Expect(err).ToNot(HaveOccurred()) + Expect(ctx.pingFromMs(msName, vrf2LinuxIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).ToNot(Succeed()) + Expect(ctx.pingFromMs(msName, vrf1LinuxIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).ToNot(Succeed()) + + err = ctx.grpcClient.ChangeRequest().Update( + linuxVrf1, + ).Send(context.Background()) + Expect(err).ToNot(HaveOccurred()) + Eventually(ctx.pingFromMsClb(msName, vrf2LinuxIP, pingWithOutInterface(vrf1Label+tapNameSuffix))).Should(Succeed()) + Expect(ctx.pingFromMs(msName, vrf1LinuxIP, pingWithOutInterface(vrf2Label+tapNameSuffix))).To(Succeed()) + Expect(ctx.agentInSync()).To(BeTrue()) +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index f25ba3ed1e..10ea9f0bc2 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -364,21 +364,21 @@ func (ctx *TestCtx) stopMicroservice(msName string) { } // pingFromMs pings from the microservice -func (ctx *TestCtx) pingFromMs(msName, dstAddress string) error { +func (ctx *TestCtx) pingFromMs(msName, dstAddress string, opts... pingOpt) error { ctx.t.Helper() ms, found := ctx.microservices[msName] if !found { // bug inside a test ctx.t.Fatalf("cannot ping from unknown microservice '%s'", msName) } - return ms.ping(dstAddress) + return ms.ping(dstAddress, opts...) } // pingFromMsClb can be used to ping repeatedly inside the assertions "Eventually" // and "Consistently" from Omega. -func (ctx *TestCtx) pingFromMsClb(msName, dstAddress string) func() error { +func (ctx *TestCtx) pingFromMsClb(msName, dstAddress string, opts... pingOpt) func() error { return func() error { - return ctx.pingFromMs(msName, dstAddress) + return ctx.pingFromMs(msName, dstAddress, opts...) } } diff --git a/tests/e2e/microservice_test.go b/tests/e2e/microservice_test.go index 956c063c1e..5079f7372a 100644 --- a/tests/e2e/microservice_test.go +++ b/tests/e2e/microservice_test.go @@ -34,6 +34,13 @@ type microservice struct { nsCalls nslinuxcalls.NetworkNamespaceAPI } +type pingOpts struct { + allowedLoss int + outIface string +} + +type pingOpt func(opts *pingOpts) + func createMicroservice(ctx *TestCtx, msName string, dockerClient *docker.Client, nsCalls nslinuxcalls.NetworkNamespaceAPI) *microservice { container, err := dockerClient.CreateContainer(docker.CreateContainerOptions{ Name: msNamePrefix + msName, @@ -162,11 +169,34 @@ func (ms *microservice) enterNetNs() (exitNetNs func()) { } } +func pingWithAllowedLoss(maxLoss int) pingOpt { + return func(opts *pingOpts) { + opts.allowedLoss = maxLoss + } +} + +func pingWithOutInterface(iface string) pingOpt { + return func(opts *pingOpts) { + opts.outIface = iface + } +} + // ping from inside of the microservice. -func (ms *microservice) ping(destAddress string, allowedLoss ...int) error { +func (ms *microservice) ping(destAddress string, opts... pingOpt) error { ms.ctx.t.Helper() + params := &pingOpts{ + allowedLoss: 49, // by default at least half of the packets should get through + } + for _, o := range opts { + o(params) + } - stdout, err := ms.exec("ping", "-w", "4", destAddress) + args := []string{"-w", "4"} + if params.outIface != "" { + args = append(args, "-I", params.outIface) + } + args = append(args, destAddress) + stdout, err := ms.exec("ping", args...) if err != nil { return err } @@ -179,11 +209,7 @@ func (ms *microservice) ping(destAddress string, allowedLoss ...int) error { ms.ctx.logger.Printf("Linux ping %s: sent=%d, received=%d, loss=%d%%", destAddress, sent, recv, loss) - maxLoss := 49 // by default at least half of the packets should ge through - if len(allowedLoss) > 0 { - maxLoss = allowedLoss[0] - } - if sent == 0 || loss > maxLoss { + if sent == 0 || loss > params.allowedLoss { return fmt.Errorf("failed to ping '%s': %s", destAddress, matches[0]) } return nil