diff --git a/go.mod b/go.mod index 715dccb9b6..f5b336d688 100644 --- a/go.mod +++ b/go.mod @@ -104,7 +104,8 @@ require ( github.com/subosito/gotenv v1.3.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect - github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect + github.com/vishvananda/netlink v1.2.1-beta.2 + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect diff --git a/go.sum b/go.sum index 9ad21a839e..92c01e753c 100644 --- a/go.sum +++ b/go.sum @@ -830,13 +830,14 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52 github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 h1:cPXZWzzG0NllBLdjWoD1nDfaqu98YMv+OneaKc8sPOA= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/netns/netns.go b/netns/netns.go new file mode 100644 index 0000000000..dba8f30f80 --- /dev/null +++ b/netns/netns.go @@ -0,0 +1,38 @@ +//go:build linux +// +build linux + +package netns + +import ( + "github.com/pkg/errors" + "github.com/vishvananda/netns" +) + +type Netns struct{} + +func New() *Netns { + return &Netns{} +} + +func (f *Netns) Get() (int, error) { + nsHandle, err := netns.Get() + return int(nsHandle), errors.Wrap(err, "netns impl") +} + +func (f *Netns) GetFromName(name string) (int, error) { + nsHandle, err := netns.GetFromName(name) + return int(nsHandle), errors.Wrap(err, "netns impl") +} + +func (f *Netns) Set(fileDescriptor int) error { + return errors.Wrap(netns.Set(netns.NsHandle(fileDescriptor)), "netns impl") +} + +func (f *Netns) NewNamed(name string) (int, error) { + nsHandle, err := netns.NewNamed(name) + return int(nsHandle), errors.Wrap(err, "netns impl") +} + +func (f *Netns) DeleteNamed(name string) error { + return errors.Wrap(netns.DeleteNamed(name), "netns impl") +} diff --git a/network/endpoint_linux.go b/network/endpoint_linux.go index b084895097..0c21b6c0d5 100644 --- a/network/endpoint_linux.go +++ b/network/endpoint_linux.go @@ -13,6 +13,7 @@ import ( "github.com/Azure/azure-container-networking/log" "github.com/Azure/azure-container-networking/netio" "github.com/Azure/azure-container-networking/netlink" + "github.com/Azure/azure-container-networking/netns" "github.com/Azure/azure-container-networking/network/networkutils" "github.com/Azure/azure-container-networking/ovsctl" "github.com/Azure/azure-container-networking/platform" @@ -89,21 +90,42 @@ func (nw *network) newEndpointImpl(_ apipaClient, nl netlink.NetlinkInterface, p } if vlanid != 0 { - log.Printf("OVS client") - if _, ok := epInfo.Data[SnatBridgeIPKey]; ok { - nw.SnatBridgeIP = epInfo.Data[SnatBridgeIPKey].(string) - } + if nw.Mode == opModeNative { + log.Printf("Native client") + vlanVethName := fmt.Sprintf("%s.%d", nw.extIf.Name, vlanid) + vnetNSName := fmt.Sprintf("az_ns_%d", vlanid) + + epClient = &NativeEndpointClient{ + primaryHostIfName: nw.extIf.Name, + vlanIfName: vlanVethName, + vnetVethName: hostIfName, + containerVethName: contIfName, + vnetNSName: vnetNSName, + nw: nw, + vlanID: vlanid, + netnsClient: netns.New(), + netlink: nl, + netioshim: &netio.NetIO{}, + plClient: plc, + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + } + } else { + log.Printf("OVS client") + if _, ok := epInfo.Data[SnatBridgeIPKey]; ok { + nw.SnatBridgeIP = epInfo.Data[SnatBridgeIPKey].(string) + } - epClient = NewOVSEndpointClient( - nw, - epInfo, - hostIfName, - contIfName, - vlanid, - localIP, - nl, - ovsctl.NewOvsctl(), - plc) + epClient = NewOVSEndpointClient( + nw, + epInfo, + hostIfName, + contIfName, + vlanid, + localIP, + nl, + ovsctl.NewOvsctl(), + plc) + } } else if nw.Mode != opModeTransparent { log.Printf("Bridge client") epClient = NewLinuxBridgeEndpointClient(nw.extIf, hostIfName, contIfName, nw.Mode, nl, plc) @@ -239,7 +261,28 @@ func (nw *network) deleteEndpointImpl(nl netlink.NetlinkInterface, plc platform. // entering the container netns and hence works both for CNI and CNM. if ep.VlanID != 0 { epInfo := ep.getInfo() - epClient = NewOVSEndpointClient(nw, epInfo, ep.HostIfName, "", ep.VlanID, ep.LocalIP, nl, ovsctl.NewOvsctl(), plc) + if nw.Mode == opModeNative { + log.Printf("Native client") + vlanVethName := fmt.Sprintf("%s.%d", nw.extIf.Name, ep.VlanID) + vnetNSName := fmt.Sprintf("az_ns_%d", ep.VlanID) + + epClient = &NativeEndpointClient{ + primaryHostIfName: nw.extIf.Name, + vlanIfName: vlanVethName, + vnetVethName: ep.HostIfName, + containerVethName: "", + vnetNSName: vnetNSName, + nw: nw, + vlanID: ep.VlanID, + netnsClient: netns.New(), + netlink: nl, + netioshim: &netio.NetIO{}, + plClient: plc, + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + } + } else { + epClient = NewOVSEndpointClient(nw, epInfo, ep.HostIfName, "", ep.VlanID, ep.LocalIP, nl, ovsctl.NewOvsctl(), plc) + } } else if nw.Mode != opModeTransparent { epClient = NewLinuxBridgeEndpointClient(nw.extIf, ep.HostIfName, "", nw.Mode, nl, plc) } else { diff --git a/network/hnswrapper/hnsv1wrapper.go b/network/hnswrapper/hnsv1wrapper.go index 247024220e..55c117347f 100644 --- a/network/hnswrapper/hnsv1wrapper.go +++ b/network/hnswrapper/hnsv1wrapper.go @@ -1,5 +1,5 @@ //go:build windows -//+build windows +// +build windows package hnswrapper diff --git a/network/hnswrapper/hnsv1wrapperinterface.go b/network/hnswrapper/hnsv1wrapperinterface.go index 6f7a3bda45..6367bead28 100644 --- a/network/hnswrapper/hnsv1wrapperinterface.go +++ b/network/hnswrapper/hnsv1wrapperinterface.go @@ -19,5 +19,5 @@ type HnsV1WrapperInterface interface { GetHNSEndpointByID(endpointID string) (*hcsshim.HNSEndpoint, error) HotAttachEndpoint(containerID string, endpointID string) error IsAttached(hnsep *hcsshim.HNSEndpoint, containerID string) (bool, error) - GetHNSGlobals() (*hcsshim.HNSGlobals, error) + GetHNSGlobals() (*hcsshim.HNSGlobals, error) } diff --git a/network/hnswrapper/hnsv2wrapperfake.go b/network/hnswrapper/hnsv2wrapperfake.go index 8e887812c4..4f59aa5aec 100644 --- a/network/hnswrapper/hnsv2wrapperfake.go +++ b/network/hnswrapper/hnsv2wrapperfake.go @@ -42,7 +42,7 @@ func NewHnsv2wrapperFake() *Hnsv2wrapperFake { } } -func delayHnsCall(delay time.Duration){ +func delayHnsCall(delay time.Duration) { time.Sleep(delay) } diff --git a/network/native_endpointclient_linux.go b/network/native_endpointclient_linux.go new file mode 100644 index 0000000000..8ddd4b6bec --- /dev/null +++ b/network/native_endpointclient_linux.go @@ -0,0 +1,407 @@ +package network + +import ( + "fmt" + "net" + + "github.com/Azure/azure-container-networking/log" + "github.com/Azure/azure-container-networking/netio" + "github.com/Azure/azure-container-networking/netlink" + "github.com/Azure/azure-container-networking/network/networkutils" + "github.com/Azure/azure-container-networking/platform" + "github.com/pkg/errors" + vishnetlink "github.com/vishvananda/netlink" +) + +const ( + azureMac = "12:34:56:78:9a:bc" // Packets leaving the VM should have this MAC + loopbackIf = "lo" // The name of the loopback interface + numDefaultRoutes = 2 // VNET NS, when no containers use it, has this many routes +) + +type netnsClient interface { + Get() (fileDescriptor int, err error) + GetFromName(name string) (fileDescriptor int, err error) + Set(fileDescriptor int) (err error) + NewNamed(name string) (fileDescriptor int, err error) + DeleteNamed(name string) (err error) +} +type NativeEndpointClient struct { + primaryHostIfName string // So like eth0 + vlanIfName string // So like eth0.1 + vnetVethName string // Peer is containerVethName + containerVethName string // Peer is vnetVethName + + vnetMac net.HardwareAddr + containerMac net.HardwareAddr + + vnetNSName string + vnetNSFileDescriptor int + + nw *network + vlanID int + netnsClient netnsClient + netlink netlink.NetlinkInterface + netioshim netio.NetIOInterface + plClient platform.ExecClient + netUtilsClient networkutils.NetworkUtils +} + +// Adds interfaces to the vnet (created if not existing) and vm namespace +func (client *NativeEndpointClient) AddEndpoints(epInfo *EndpointInfo) error { + // VM Namespace + err := client.PopulateVM(epInfo) + if err != nil { + return err + } + // VNET Namespace + return ExecuteInNS(client.vnetNSName, func() error { + return client.PopulateVnet(epInfo) + }) +} + +// Called from AddEndpoints, Namespace: VM +func (client *NativeEndpointClient) PopulateVM(epInfo *EndpointInfo) error { + vmNS, err := client.netnsClient.Get() + if err != nil { + return errors.Wrap(err, "failed to get vm ns handle") + } + + log.Printf("[native] Checking if NS exists...") + vnetNS, existingErr := client.netnsClient.GetFromName(client.vnetNSName) + // If the ns does not exist, the below code will trigger to create it + // This will also (we assume) mean the vlan veth does not exist + if existingErr != nil { + // We assume the only possible error is that the namespace doesn't exist + log.Printf("[native] No existing NS detected. Creating the vnet namespace and switching to it") + vnetNS, err = client.netnsClient.NewNamed(client.vnetNSName) + if err != nil { + return errors.Wrap(err, "failed to create vnet ns") + } + client.vnetNSFileDescriptor = vnetNS + deleteNSIfNotNilErr := client.netnsClient.Set(vmNS) + // Any failure will trigger removing the namespace created + defer func() { + if deleteNSIfNotNilErr != nil { + log.Logf("[native] Removing vnet ns due to failure...") + err = client.netnsClient.DeleteNamed(client.vnetNSName) + if err != nil { + log.Errorf("failed to cleanup/delete ns after failing to create vlan veth") + } + } + }() + if deleteNSIfNotNilErr != nil { + return errors.Wrap(deleteNSIfNotNilErr, "failed to set current ns to vm") + } + + // Now create vlan veth + log.Printf("[native] Create the host vlan link after getting eth0: %s", client.primaryHostIfName) + // Get parent interface index. Index is consistent across libraries. + eth0, deleteNSIfNotNilErr := client.netioshim.GetNetworkInterfaceByName(client.primaryHostIfName) + if deleteNSIfNotNilErr != nil { + return errors.Wrap(deleteNSIfNotNilErr, "failed to get eth0 interface") + } + linkAttrs := vishnetlink.NewLinkAttrs() + linkAttrs.Name = client.vlanIfName + // Set the peer + linkAttrs.ParentIndex = eth0.Index + link := &vishnetlink.Vlan{ + LinkAttrs: linkAttrs, + VlanId: client.vlanID, + } + log.Printf("[native] Attempting to create %s link in VM NS", client.vlanIfName) + // Create vlan veth + deleteNSIfNotNilErr = vishnetlink.LinkAdd(link) + if deleteNSIfNotNilErr != nil { + // Any failure to add the link should error (auto delete NS) + return errors.Wrap(deleteNSIfNotNilErr, "failed to create vlan vnet link after making new ns") + } + // vlan veth was created successfully, so move the vlan veth you created + log.Printf("[native] Move vlan link (%s) to vnet NS: %d", client.vlanIfName, uintptr(client.vnetNSFileDescriptor)) + deleteNSIfNotNilErr = client.netlink.SetLinkNetNs(client.vlanIfName, uintptr(client.vnetNSFileDescriptor)) + if deleteNSIfNotNilErr != nil { + if delErr := client.netlink.DeleteLink(client.vlanIfName); delErr != nil { + log.Errorf("deleting vlan veth failed on addendpoint failure") + } + return errors.Wrap(deleteNSIfNotNilErr, "deleting vlan veth in vm ns due to addendpoint failure") + } + } else { + log.Printf("[native] Existing NS (%s) detected. Assuming %s exists too", client.vnetNSName, client.vlanIfName) + } + client.vnetNSFileDescriptor = vnetNS + + if err = client.netUtilsClient.CreateEndpoint(client.vnetVethName, client.containerVethName); err != nil { + return errors.Wrap(err, "failed to create veth pair") + } + + if err = client.netlink.SetLinkNetNs(client.vnetVethName, uintptr(client.vnetNSFileDescriptor)); err != nil { + if delErr := client.netlink.DeleteLink(client.vnetVethName); delErr != nil { + log.Errorf("Deleting vnet veth failed on addendpoint failure:%v", delErr) + } + return errors.Wrap(err, "failed to move vnetVethName into vnet ns, deleting") + } + + containerIf, err := client.netioshim.GetNetworkInterfaceByName(client.containerVethName) + if err != nil { + return errors.Wrap(err, "container veth does not exist") + } + client.containerMac = containerIf.HardwareAddr + return nil +} + +// Called from AddEndpoints, Namespace: Vnet +func (client *NativeEndpointClient) PopulateVnet(epInfo *EndpointInfo) error { + _, err := client.netioshim.GetNetworkInterfaceByName(client.vlanIfName) + if err != nil { + return errors.Wrap(err, "vlan veth doesn't exist") + } + vnetVethIf, err := client.netioshim.GetNetworkInterfaceByName(client.vnetVethName) + if err != nil { + return errors.Wrap(err, "vnet veth doesn't exist") + } + client.vnetMac = vnetVethIf.HardwareAddr + return nil +} + +func (client *NativeEndpointClient) AddEndpointRules(epInfo *EndpointInfo) error { + // There are no rules to add here + // Described as rules on ip addresses on the container interface + + return nil +} + +func (client *NativeEndpointClient) DeleteEndpointRules(ep *endpoint) { + // Never added any endpoint rules +} + +func (client *NativeEndpointClient) MoveEndpointsToContainerNS(epInfo *EndpointInfo, nsID uintptr) error { + if err := client.netlink.SetLinkNetNs(client.containerVethName, nsID); err != nil { + return errors.Wrap(err, "failed to move endpoint to container ns") + } + return nil +} + +func (client *NativeEndpointClient) SetupContainerInterfaces(epInfo *EndpointInfo) error { + if err := client.netUtilsClient.SetupContainerInterface(client.containerVethName, epInfo.IfName); err != nil { + return errors.Wrap(err, "failed to setup container interface") + } + client.containerVethName = epInfo.IfName + + return nil +} + +// Adds routes, arp entries, etc. to the vnet and container namespaces +func (client *NativeEndpointClient) ConfigureContainerInterfacesAndRoutes(epInfo *EndpointInfo) error { + // Container NS + err := client.ConfigureContainerInterfacesAndRoutesImpl(epInfo) + if err != nil { + return err + } + + // Switch to vnet NS and call ConfigureVnetInterfacesAndRoutes + return ExecuteInNS(client.vnetNSName, func() error { + return client.ConfigureVnetInterfacesAndRoutesImpl(epInfo) + }) +} + +// Called from ConfigureContainerInterfacesAndRoutes, Namespace: Container +func (client *NativeEndpointClient) ConfigureContainerInterfacesAndRoutesImpl(epInfo *EndpointInfo) error { + if err := client.netUtilsClient.AssignIPToInterface(client.containerVethName, epInfo.IPAddresses); err != nil { + return errors.Wrap(err, "failed to assign ips to container veth interface") + } + // kernel subnet route auto added by above call must be removed + for _, ipAddr := range epInfo.IPAddresses { + _, ipnet, _ := net.ParseCIDR(ipAddr.String()) + routeInfo := RouteInfo{ + Dst: *ipnet, + Scope: netlink.RT_SCOPE_LINK, + Protocol: netlink.RTPROT_KERNEL, + } + if err := deleteRoutes(client.netlink, client.netioshim, client.containerVethName, []RouteInfo{routeInfo}); err != nil { + return errors.Wrap(err, "failed to remove kernel subnet route") + } + } + + if err := client.AddDefaultRoutes(client.containerVethName); err != nil { + return errors.Wrap(err, "failed container ns add default routes") + } + if err := client.AddDefaultArp(client.containerVethName, client.vnetMac.String()); err != nil { + return errors.Wrap(err, "failed container ns add default arp") + } + return nil +} + +// Called from ConfigureContainerInterfacesAndRoutes, Namespace: Vnet +func (client *NativeEndpointClient) ConfigureVnetInterfacesAndRoutesImpl(epInfo *EndpointInfo) error { + err := client.netlink.SetLinkState(loopbackIf, true) + if err != nil { + return errors.Wrap(err, "failed to set loopback link state to up") + } + + // Add route specifying which device the pod ip(s) are on + routeInfoList := client.GetVnetRoutes(epInfo.IPAddresses) + + if err = client.AddDefaultRoutes(client.vlanIfName); err != nil { + return errors.Wrap(err, "failed vnet ns add default/gateway routes (idempotent)") + } + if err = client.AddDefaultArp(client.vlanIfName, azureMac); err != nil { + return errors.Wrap(err, "failed vnet ns add default arp entry (idempotent)") + } + if err = addRoutes(client.netlink, client.netioshim, client.vnetVethName, routeInfoList); err != nil { + return errors.Wrap(err, "failed adding routes to vnet specific to this container") + } + // Return to ConfigureContainerInterfacesAndRoutes + return err +} + +// Helper that gets the routes in the vnet NS for a particular list of IP addresses +// Example: 192.168.0.4 dev proto static +func (client *NativeEndpointClient) GetVnetRoutes(ipAddresses []net.IPNet) []RouteInfo { + routeInfoList := make([]RouteInfo, 0, len(ipAddresses)) + // Add route specifying which device the pod ip(s) are on + for _, ipAddr := range ipAddresses { + var ( + routeInfo RouteInfo + ipNet net.IPNet + ) + + if ipAddr.IP.To4() != nil { + ipNet = net.IPNet{IP: ipAddr.IP, Mask: net.CIDRMask(ipv4FullMask, ipv4Bits)} + } else { + ipNet = net.IPNet{IP: ipAddr.IP, Mask: net.CIDRMask(ipv6FullMask, ipv6Bits)} + } + log.Printf("[net] Native client adding route for the ip %v", ipNet.String()) + routeInfo.Dst = ipNet + routeInfoList = append(routeInfoList, routeInfo) + + } + return routeInfoList +} + +// Helper that creates routing rules for the current NS which direct packets +// to the virtual gateway ip on linkToName device interface +// Route 1: 169.254.1.1 dev +// Route 2: default via 169.254.1.1 dev +func (client *NativeEndpointClient) AddDefaultRoutes(linkToName string) error { + // Add route for virtualgwip (ip route add 169.254.1.1/32 dev eth0) + virtualGwIP, virtualGwNet, _ := net.ParseCIDR(virtualGwIPString) + routeInfo := RouteInfo{ + Dst: *virtualGwNet, + Scope: netlink.RT_SCOPE_LINK, + } + // Difference between interface name in addRoutes and DevName: in RouteInfo? + if err := addRoutes(client.netlink, client.netioshim, linkToName, []RouteInfo{routeInfo}); err != nil { + return err + } + + // Add default route (ip route add default via 169.254.1.1 dev eth0) + _, defaultIPNet, _ := net.ParseCIDR(defaultGwCidr) + dstIP := net.IPNet{IP: net.ParseIP(defaultGw), Mask: defaultIPNet.Mask} + routeInfo = RouteInfo{ + Dst: dstIP, + Gw: virtualGwIP, + } + + if err := addRoutes(client.netlink, client.netioshim, linkToName, []RouteInfo{routeInfo}); err != nil { + return err + } + return nil +} + +// Helper that creates arp entry for the current NS which maps the virtual +// gateway (169.254.1.1) to destMac on a particular interfaceName +// Example: (169.254.1.1) at 12:34:56:78:9a:bc [ether] PERM on +func (client *NativeEndpointClient) AddDefaultArp(interfaceName, destMac string) error { + _, virtualGwNet, _ := net.ParseCIDR(virtualGwIPString) + log.Printf("[net] Adding static arp for IP address %v and MAC %v in namespace", + virtualGwNet.String(), destMac) + hardwareAddr, err := net.ParseMAC(destMac) + if err != nil { + return errors.Wrap(err, "unable to parse mac") + } + if err := client.netlink.AddOrRemoveStaticArp(netlink.ADD, interfaceName, virtualGwNet.IP, hardwareAddr, false); err != nil { + return fmt.Errorf("adding arp entry failed: %w", err) + } + return nil +} + +func (client *NativeEndpointClient) DeleteEndpoints(ep *endpoint) error { + return ExecuteInNS(client.vnetNSName, func() error { + // Passing in functionality to get number of routes after deletion + getNumRoutesLeft := func() (int, error) { + routes, err := vishnetlink.RouteList(nil, vishnetlink.FAMILY_V4) + if err != nil { + return 0, errors.Wrap(err, "failed to get num routes left") + } + return len(routes), nil + } + + return client.DeleteEndpointsImpl(ep, getNumRoutesLeft) + }) +} + +// getNumRoutesLeft is a function which gets the current number of routes in the namespace. Namespace: Vnet +func (client *NativeEndpointClient) DeleteEndpointsImpl(ep *endpoint, getNumRoutesLeft func() (int, error)) error { + routeInfoList := client.GetVnetRoutes(ep.IPAddresses) + if err := deleteRoutes(client.netlink, client.netioshim, client.vnetVethName, routeInfoList); err != nil { + return errors.Wrap(err, "failed to remove routes") + } + + routesLeft, err := getNumRoutesLeft() + if err != nil { + return err + } + + log.Printf("[native] There are %d routes remaining after deletion", routesLeft) + + if routesLeft <= numDefaultRoutes { + // Deletes default arp, default routes, vlan veth; there are two default routes + // so when we have <= numDefaultRoutes routes left, no containers use this namespace + log.Printf("[native] Deleting namespace %s as no containers occupy it", client.vnetNSName) + delErr := client.netnsClient.DeleteNamed(client.vnetNSName) + if delErr != nil { + return errors.Wrap(delErr, "failed to delete namespace") + } + } + return nil +} + +// Helper function that allows executing a function in a VM namespace +// Does not work for process namespaces +func ExecuteInNS(nsName string, f func() error) error { + // Current namespace + returnedTo, err := GetCurrentThreadNamespace() + if err != nil { + log.Errorf("[ExecuteInNS] Could not get NS we are in: %v", err) + } else { + log.Printf("[ExecuteInNS] In NS before switch: %s", returnedTo.file.Name()) + } + + // Open the network namespace + log.Printf("[ExecuteInNS] Opening ns %v.", fmt.Sprintf("/var/run/netns/%s", nsName)) + ns, err := OpenNamespace(fmt.Sprintf("/var/run/netns/%s", nsName)) + if err != nil { + return err + } + defer ns.Close() + // Enter the network namespace + log.Printf("[ExecuteInNS] Entering ns %s.", ns.file.Name()) + if err := ns.Enter(); err != nil { + return err + } + + // Exit network namespace + defer func() { + log.Printf("[ExecuteInNS] Exiting ns %s.", ns.file.Name()) + if err := ns.Exit(); err != nil { + log.Errorf("[ExecuteInNS] Could not exit ns, err:%v.", err) + } + returnedTo, err := GetCurrentThreadNamespace() + if err != nil { + log.Errorf("[ExecuteInNS] Could not get NS we returned to: %v", err) + } else { + log.Printf("[ExecuteInNS] Returned to NS: %s", returnedTo.file.Name()) + } + }() + return f() +} diff --git a/network/native_endpointclient_linux_test.go b/network/native_endpointclient_linux_test.go new file mode 100644 index 0000000000..ab81ca6041 --- /dev/null +++ b/network/native_endpointclient_linux_test.go @@ -0,0 +1,665 @@ +//go:build linux +// +build linux + +package network + +import ( + "net" + "testing" + + "github.com/Azure/azure-container-networking/netio" + "github.com/Azure/azure-container-networking/netlink" + "github.com/Azure/azure-container-networking/network/networkutils" + "github.com/Azure/azure-container-networking/platform" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +var errNetnsMock = errors.New("mock netns error") + +func newNetnsErrorMock(errStr string) error { + return errors.Wrap(errNetnsMock, errStr) +} + +type mockNetns struct { + get func() (fileDescriptor int, err error) + getFromName func(name string) (fileDescriptor int, err error) + set func(fileDescriptor int) (err error) + newNamed func(name string) (fileDescriptor int, err error) + deleteNamed func(name string) (err error) +} + +func (netns *mockNetns) Get() (fileDescriptor int, err error) { + return netns.get() +} + +func (netns *mockNetns) GetFromName(name string) (fileDescriptor int, err error) { + return netns.getFromName(name) +} + +func (netns *mockNetns) Set(fileDescriptor int) (err error) { + return netns.set(fileDescriptor) +} + +func (netns *mockNetns) NewNamed(name string) (fileDescriptor int, err error) { + return netns.newNamed(name) +} + +func (netns *mockNetns) DeleteNamed(name string) (err error) { + return netns.deleteNamed(name) +} + +func defaultGet() (int, error) { + return 1, nil +} + +func defaultGetFromName(name string) (int, error) { + return 1, nil +} + +func defaultSet(handle int) error { + return nil +} + +func defaultNewNamed(name string) (int, error) { + return 1, nil +} + +func defaultDeleteNamed(name string) error { + return nil +} + +func TestNativeAddEndpoints(t *testing.T) { + nl := netlink.NewMockNetlink(false, "") + plc := platform.NewMockExecClient(false) + + tests := []struct { + name string + client *NativeEndpointClient + epInfo *EndpointInfo + wantErr bool + wantErrMsg string + }{ + // Populating VM with data and creating interfaces/links + { + name: "Add endpoints create vnet ns failure", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: func(name string) (fileDescriptor int, err error) { + return 0, newNetnsErrorMock("netns failure") + }, + newNamed: func(name string) (fileDescriptor int, err error) { + return 0, newNetnsErrorMock("netns failure") + }, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "failed to create vnet ns: netns failure: " + errNetnsMock.Error(), + }, + { + name: "Add endpoints with existing vnet ns", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: defaultGetFromName, + newNamed: defaultNewNamed, + set: defaultSet, + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: false, + }, + { + name: "Add endpoints netlink fail", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: defaultGetFromName, + newNamed: defaultNewNamed, + set: defaultSet, + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(true, "netlink fail"), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "failed to move vnetVethName into vnet ns, deleting: " + netlink.ErrorMockNetlink.Error() + " : netlink fail", + }, + { + name: "Add endpoints get interface fail for primary interface (eth0)", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: func(name string) (fileDescriptor int, err error) { + return 0, newNetnsErrorMock("netns failure") + }, + newNamed: defaultNewNamed, + set: defaultSet, + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 1), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "failed to get eth0 interface: " + netio.ErrMockNetIOFail.Error() + ":eth0", + }, + { + name: "Add endpoints get interface fail for getting container veth", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: defaultGetFromName, + newNamed: defaultNewNamed, + set: defaultSet, + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 1), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "container veth does not exist: " + netio.ErrMockNetIOFail.Error() + ":B1veth0", + }, + { + name: "Add endpoints NetNS Get fail", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: func() (fileDescriptor int, err error) { + return 0, newNetnsErrorMock("netns failure") + }, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "failed to get vm ns handle: netns failure: " + errNetnsMock.Error(), + }, + { + name: "Add endpoints NetNS Set fail", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + get: defaultGet, + getFromName: func(name string) (fileDescriptor int, err error) { + return 0, newNetnsErrorMock("do not fail on this error") + }, + newNamed: defaultNewNamed, + set: func(fileDescriptor int) (err error) { + return newNetnsErrorMock("netns failure") + }, + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "failed to set current ns to vm: netns failure: " + errNetnsMock.Error(), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.client.PopulateVM(tt.epInfo) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error()) + } else { + require.NoError(t, err) + } + }) + } + + tests = []struct { + name string + client *NativeEndpointClient + epInfo *EndpointInfo + wantErr bool + wantErrMsg string + }{ + // Populate the client with information from the vnet and set up vnet + { + name: "Add endpoints get vnet veth mac address", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{}, + wantErr: false, + }, + { + name: "Add endpoints fail check vlan veth exists", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 1), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "vlan veth doesn't exist: " + netio.ErrMockNetIOFail.Error() + ":eth0.1", + }, + { + name: "Add endpoints fail check vnet veth exists", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 2), + }, + epInfo: &EndpointInfo{}, + wantErr: true, + wantErrMsg: "vnet veth doesn't exist: " + netio.ErrMockNetIOFail.Error() + ":A1veth0", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.client.PopulateVnet(tt.epInfo) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNativeDeleteEndpoints(t *testing.T) { + nl := netlink.NewMockNetlink(false, "") + plc := platform.NewMockExecClient(false) + IPAddresses := []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + { + IP: net.ParseIP("192.168.0.6"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + } + + tests := []struct { + name string + client *NativeEndpointClient + ep *endpoint + wantErr bool + wantErrMsg string + routesLeft func() (int, error) + }{ + { + name: "Delete endpoint delete vnet ns", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + deleteNamed: defaultDeleteNamed, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + ep: &endpoint{ + IPAddresses: IPAddresses, + }, + routesLeft: func() (int, error) { + return numDefaultRoutes, nil + }, + wantErr: false, + }, + { + name: "Delete endpoint do not delete vnet ns it is still in use", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + deleteNamed: func(name string) (err error) { + return newNetnsErrorMock("netns failure") + }, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + ep: &endpoint{ + IPAddresses: IPAddresses, + }, + routesLeft: func() (int, error) { + return numDefaultRoutes + 1, nil + }, + wantErr: false, + }, + { + name: "Delete endpoint fail to delete namespace", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + netnsClient: &mockNetns{ + deleteNamed: func(name string) (err error) { + return newNetnsErrorMock("netns failure") + }, + }, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + ep: &endpoint{ + IPAddresses: IPAddresses, + }, + routesLeft: func() (int, error) { + return numDefaultRoutes, nil + }, + wantErr: true, + wantErrMsg: "failed to delete namespace: netns failure: " + errNetnsMock.Error(), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.client.DeleteEndpointsImpl(tt.ep, tt.routesLeft) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNativeConfigureContainerInterfacesAndRoutes(t *testing.T) { + nl := netlink.NewMockNetlink(false, "") + plc := platform.NewMockExecClient(false) + + vnetMac, _ := net.ParseMAC("ab:cd:ef:12:34:56") + + tests := []struct { + name string + client *NativeEndpointClient + epInfo *EndpointInfo + wantErr bool + wantErrMsg string + }{ + { + name: "Configure interface and routes good path for container", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: false, + }, + { + name: "Configure interface and routes multiple IPs", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + { + IP: net.ParseIP("192.168.0.6"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + { + IP: net.ParseIP("192.168.0.8"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: false, + }, + { + name: "Configure interface and routes assign ip fail", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(true, "netlink fail"), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: true, + wantErrMsg: "netlink fail", + }, + { + name: "Configure interface and routes container 2nd default route added fail", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 3), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: true, + wantErrMsg: "failed container ns add default routes: addRoutes failed: " + netio.ErrMockNetIOFail.Error() + ":B1veth0", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.client.ConfigureContainerInterfacesAndRoutesImpl(tt.epInfo) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error()) + } else { + require.NoError(t, err) + } + }) + } + tests = []struct { + name string + client *NativeEndpointClient + epInfo *EndpointInfo + wantErr bool + wantErrMsg string + }{ + { + name: "Configure interface and routes good path for vnet", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(false, 0), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: false, + }, + { + // fail route that tells which device container ip is on for vnet + name: "Configure interface and routes fail final routes for vnet", + client: &NativeEndpointClient{ + primaryHostIfName: "eth0", + vlanIfName: "eth0.1", + vnetVethName: "A1veth0", + containerVethName: "B1veth0", + vnetNSName: "az_ns_1", + vnetMac: vnetMac, + netlink: netlink.NewMockNetlink(false, ""), + plClient: platform.NewMockExecClient(false), + netUtilsClient: networkutils.NewNetworkUtils(nl, plc), + netioshim: netio.NewMockNetIO(true, 3), + }, + epInfo: &EndpointInfo{ + IPAddresses: []net.IPNet{ + { + IP: net.ParseIP("192.168.0.4"), + Mask: net.CIDRMask(subnetv4Mask, ipv4Bits), + }, + }, + }, + wantErr: true, + wantErrMsg: "failed adding routes to vnet specific to this container: addRoutes failed: " + netio.ErrMockNetIOFail.Error() + ":A1veth0", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := tt.client.ConfigureVnetInterfacesAndRoutesImpl(tt.epInfo) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErrMsg, "Expected:%v actual:%v", tt.wantErrMsg, err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/network/network.go b/network/network.go index 16979d7414..a28d0a7244 100644 --- a/network/network.go +++ b/network/network.go @@ -18,6 +18,7 @@ const ( opModeBridge = "bridge" opModeTunnel = "tunnel" opModeTransparent = "transparent" + opModeNative = "native" opModeDefault = opModeTunnel ) diff --git a/network/network_linux.go b/network/network_linux.go index 2afb4a5a0f..5ced57c8c2 100644 --- a/network/network_linux.go +++ b/network/network_linux.go @@ -88,6 +88,9 @@ func (nm *networkManager) newNetworkImpl(nwInfo *NetworkInfo, extIf *externalInt return nil, fmt.Errorf("Ipv6 forwarding failed: %w", err) } } + case opModeNative: + log.Printf("Native mode") + ifName = extIf.Name default: return nil, errNetworkModeInvalid }