github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nettools/nettools_test.go (about)

     1  /*
     2  Copyright 2016 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package nettools
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"net"
    23  	"os/exec"
    24  	"reflect"
    25  	"testing"
    26  
    27  	"github.com/containernetworking/cni/pkg/ns"
    28  	cnitypes "github.com/containernetworking/cni/pkg/types"
    29  	cnicurrent "github.com/containernetworking/cni/pkg/types/current"
    30  	"github.com/davecgh/go-spew/spew"
    31  	"github.com/vishvananda/netlink"
    32  )
    33  
    34  const (
    35  	innerHwAddr       = "42:a4:a6:22:80:2e"
    36  	outerHwAddr       = "42:b5:b7:33:91:3f"
    37  	secondInnerHwAddr = "42:a4:a6:22:80:2f"
    38  	secondOuterHwAddr = "42:b5:b7:33:91:3e"
    39  	dummyInnerHwAddr  = "42:a4:a6:22:80:40"
    40  	dummyOuterHwAddr  = "42:b5:b7:33:91:4f"
    41  )
    42  
    43  func expectedExtractedLinkInfo(contNsPath string) *cnicurrent.Result {
    44  	return &cnicurrent.Result{
    45  		Interfaces: []*cnicurrent.Interface{
    46  			{
    47  				Name:    "eth0",
    48  				Mac:     innerHwAddr,
    49  				Sandbox: contNsPath,
    50  			},
    51  		},
    52  		IPs: []*cnicurrent.IPConfig{
    53  			{
    54  				Version:   "4",
    55  				Interface: 0,
    56  				Address: net.IPNet{
    57  					IP:   net.IP{10, 1, 90, 5},
    58  					Mask: net.IPMask{255, 255, 255, 0},
    59  				},
    60  				Gateway: net.IP{10, 1, 90, 1},
    61  			},
    62  		},
    63  		Routes: []*cnitypes.Route{
    64  			{
    65  				Dst: net.IPNet{
    66  					IP:   net.IP{0, 0, 0, 0},
    67  					Mask: net.IPMask{0, 0, 0, 0},
    68  				},
    69  				GW: net.IP{10, 1, 90, 1},
    70  			},
    71  		},
    72  	}
    73  }
    74  
    75  // withTemporaryNSAvailable creates a new network namespace and
    76  // passes is as an argument to the specified function.
    77  // It does NOT change current network namespace.
    78  func withTempNetNS(t *testing.T, toRun func(ns ns.NetNS)) {
    79  	ns, err := ns.NewNS()
    80  	if err != nil {
    81  		t.Errorf("Error creating network namespace: %v", err)
    82  		return
    83  	}
    84  	defer func() {
    85  		// We use log.Panicf() instead of t.Fatalf() in these tests
    86  		// because ns.Do() uses separate goroutine
    87  		if r := recover(); r != nil {
    88  			t.Fatal(r)
    89  		}
    90  		if err = ns.Close(); err != nil {
    91  			t.Fatalf("Error closing network namespace: %v", err)
    92  		}
    93  	}()
    94  	toRun(ns)
    95  }
    96  
    97  func inNS(netNS ns.NetNS, name string, toRun func()) {
    98  	var r interface{}
    99  	if err := netNS.Do(func(ns.NetNS) error {
   100  		defer func() {
   101  			//r = recover()
   102  		}()
   103  		toRun()
   104  		return nil
   105  	}); err != nil {
   106  		log.Fatalf("failed to enter %s: %v", name, err)
   107  	}
   108  
   109  	// re-panic in the original goroutine
   110  	if r != nil {
   111  		log.Panic(r)
   112  	}
   113  }
   114  
   115  // withHostAndContNS creates two namespaces, one serving as 'host'
   116  // namespace and one serving as 'container' one, and calls
   117  // the specified function in the 'host' namespace, passing both
   118  // namespaces to it.
   119  func withHostAndContNS(t *testing.T, toRun func(hostNS, contNS ns.NetNS)) {
   120  	withTempNetNS(t, func(hostNS ns.NetNS) {
   121  		withTempNetNS(t, func(contNS ns.NetNS) {
   122  			inNS(hostNS, "hostNS", func() { toRun(hostNS, contNS) })
   123  		})
   124  	})
   125  }
   126  
   127  func verifyLinkUp(t *testing.T, name, title string) netlink.Link {
   128  	link, err := netlink.LinkByName(name)
   129  	if err != nil {
   130  		log.Panicf("cannot locate link: %s", title)
   131  	}
   132  	if link.Attrs().Flags&net.FlagUp == 0 {
   133  		t.Errorf("link should be up, but it's down: %s", title)
   134  	}
   135  	return link
   136  }
   137  
   138  func verifyNoLink(t *testing.T, name, title string) {
   139  	if _, err := netlink.LinkByName(name); err == nil {
   140  		t.Errorf("link should not be present: %s", title)
   141  	}
   142  }
   143  
   144  func verifyBridgeMember(t *testing.T, name, title string, bridge netlink.Link) netlink.Link {
   145  	if bridge.Type() != "bridge" {
   146  		log.Panicf("link %q is not a bridge", bridge.Attrs().Name)
   147  	}
   148  	link := verifyLinkUp(t, name, title)
   149  	if link.Attrs().MasterIndex != bridge.Attrs().Index {
   150  		t.Errorf("link %q doesn't belong to bridge %q", name, bridge.Attrs().Name)
   151  	}
   152  	return link
   153  }
   154  
   155  func TestEscapePair(t *testing.T) {
   156  	withHostAndContNS(t, func(hostNS, contNS ns.NetNS) {
   157  		hostVeth, contVeth, err := CreateEscapeVethPair(contNS, "esc0", 1500)
   158  		if err != nil {
   159  			log.Panicf("Error creating escape veth pair: %v", err)
   160  		}
   161  		// need to force hostNS here because of side effects of NetNS.Do()
   162  		// See https://github.com/vishvananda/netns/issues/17
   163  		inNS(hostNS, "hostNS", func() {
   164  			verifyLinkUp(t, hostVeth.Attrs().Name, "host veth")
   165  			verifyNoLink(t, contVeth.Attrs().Name, "container veth in host namespace")
   166  		})
   167  		inNS(contNS, "contNS", func() {
   168  			verifyLinkUp(t, contVeth.Attrs().Name, "container veth")
   169  			verifyNoLink(t, hostVeth.Attrs().Name, "host veth in container namespace")
   170  		})
   171  	})
   172  }
   173  
   174  func makeTestVeth(t *testing.T, base string, index int) netlink.Link {
   175  	name := fmt.Sprintf("%s%d", base, index)
   176  	veth := &netlink.Veth{
   177  		LinkAttrs: netlink.LinkAttrs{
   178  			Name:  name,
   179  			Flags: net.FlagUp,
   180  			MTU:   1500,
   181  		},
   182  		PeerName: "p" + name,
   183  	}
   184  	if err := netlink.LinkAdd(veth); err != nil {
   185  		log.Panicf("failed to create veth: %v", err)
   186  	}
   187  
   188  	return veth
   189  }
   190  
   191  func makeTestBridge(t *testing.T, name string, links []netlink.Link) *netlink.Bridge {
   192  	br, err := SetupBridge(name, links)
   193  	if err != nil {
   194  		log.Panicf("failed to create first bridge: %v", err)
   195  	}
   196  	if br.Attrs().Name != name {
   197  		log.Panicf("bad bridge name: %q instead of %q", br.Attrs().Name, name)
   198  	}
   199  	verifyLinkUp(t, name, "bridge")
   200  	return br
   201  }
   202  
   203  func TestSetupBridge(t *testing.T) {
   204  	withTempNetNS(t, func(hostNS ns.NetNS) {
   205  		inNS(hostNS, "hostNS", func() {
   206  			var links []netlink.Link
   207  			for i := 0; i < 4; i++ {
   208  				links = append(links, makeTestVeth(t, "veth", i))
   209  			}
   210  
   211  			brs := []*netlink.Bridge{
   212  				makeTestBridge(t, "testbr0", links[0:2]),
   213  				makeTestBridge(t, "testbr1", links[2:4]),
   214  			}
   215  			if brs[0].Attrs().Name == brs[1].Attrs().Name {
   216  				t.Errorf("bridges have identical name %q", brs[0].Attrs().Name)
   217  			}
   218  			if brs[0].Attrs().Index == brs[1].Attrs().Index {
   219  				t.Errorf("bridges have the same index %d", brs[0].Attrs().Index)
   220  			}
   221  
   222  			for i := 0; i < 4; i++ {
   223  				name := links[i].Attrs().Name
   224  				verifyBridgeMember(t, name, name, brs[i/2])
   225  			}
   226  		})
   227  	})
   228  }
   229  
   230  func parseAddr(addr string) *netlink.Addr {
   231  	r, err := netlink.ParseAddr(addr)
   232  	if err != nil {
   233  		log.Panicf("failed to parse addr: %v", err)
   234  	}
   235  	return r
   236  }
   237  
   238  func addTestRoute(t *testing.T, route *netlink.Route) {
   239  	if err := netlink.RouteAdd(route); err != nil {
   240  		log.Panicf("Failed to add route %#v: %v", route, err)
   241  	}
   242  }
   243  
   244  func setupLink(hwAddrAsText string, link netlink.Link) netlink.Link {
   245  	hwAddr, err := net.ParseMAC(hwAddrAsText)
   246  	if err != nil {
   247  		log.Panicf("Error parsing hwaddr %q: %v", hwAddrAsText, err)
   248  	}
   249  	if err := SetHardwareAddr(link, hwAddr); err != nil {
   250  		log.Panicf("Error setting hardware address: %v", err)
   251  	}
   252  
   253  	// re-query attrs (including new mac)
   254  	link, err = netlink.LinkByName(link.Attrs().Name)
   255  	if err != nil {
   256  		log.Panicf("cannot locate link: %s", link.Attrs().Name)
   257  	}
   258  
   259  	return link
   260  }
   261  
   262  func withFakeCNIVeth(t *testing.T, mtu int, toRun func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link)) {
   263  	withHostAndContNS(t, func(hostNS, contNS ns.NetNS) {
   264  		origHostVeth, origContVeth, err := CreateEscapeVethPair(contNS, "eth0", mtu)
   265  		if err != nil {
   266  			log.Panicf("failed to create veth pair: %v", err)
   267  		}
   268  		// need to force hostNS here because of side effects of NetNS.Do()
   269  		// See https://github.com/vishvananda/netns/issues/17
   270  		inNS(hostNS, "hostNS", func() {
   271  			origHostVeth = setupLink(outerHwAddr, origHostVeth)
   272  		})
   273  		inNS(contNS, "contNS", func() {
   274  			origContVeth = setupLink(innerHwAddr, origContVeth)
   275  
   276  			if err = netlink.AddrAdd(origContVeth, parseAddr("10.1.90.5/24")); err != nil {
   277  				log.Panicf("failed to add addr for origContVeth: %v", err)
   278  			}
   279  
   280  			toRun(hostNS, contNS, origHostVeth, origContVeth)
   281  		})
   282  	})
   283  }
   284  
   285  func withFakeCNIVethAndGateway(t *testing.T, mtu int, toRun func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link)) {
   286  	withFakeCNIVeth(t, mtu, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   287  		addTestRoute(t, &netlink.Route{
   288  			Gw:    parseAddr("10.1.90.1/24").IPNet.IP,
   289  			Scope: SCOPE_UNIVERSE,
   290  		})
   291  
   292  		toRun(hostNS, contNS, origHostVeth, origContVeth)
   293  	})
   294  }
   295  
   296  func verifyNoAddressAndRoutes(t *testing.T, link netlink.Link) {
   297  	if routes, err := netlink.RouteList(link, FAMILY_V4); err != nil {
   298  		t.Errorf("failed to get route list: %v", err)
   299  	} else if len(routes) != 0 {
   300  		t.Errorf("unexpected routes remain on the interface: %s", spew.Sdump(routes))
   301  	}
   302  
   303  	if addrs, err := netlink.AddrList(link, FAMILY_V4); err != nil {
   304  		t.Errorf("failed to get addresses for veth: %v", err)
   305  	} else if len(addrs) != 0 {
   306  		t.Errorf("unexpected addresses remain on the interface: %s", spew.Sdump(addrs))
   307  	}
   308  }
   309  
   310  func TestFindVeth(t *testing.T) {
   311  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   312  		allLinks, err := netlink.LinkList()
   313  		if err != nil {
   314  			log.Panicf("LinkList() failed: %v", err)
   315  		}
   316  		contVeth, err := FindVeth(allLinks)
   317  		if err != nil {
   318  			log.Panicf("FindVeth() failed: %v", err)
   319  		}
   320  
   321  		if contVeth.Attrs().Index != origContVeth.Attrs().Index {
   322  			t.Errorf("GrabInterfaceInfo() didn't return original cont veth. Interface returned: %q", origContVeth.Attrs().Name)
   323  		}
   324  	})
   325  }
   326  
   327  func TestStripLink(t *testing.T) {
   328  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   329  		if err := StripLink(origContVeth); err != nil {
   330  			log.Panicf("StripLink() failed: %v", err)
   331  		}
   332  		verifyNoAddressAndRoutes(t, origContVeth)
   333  	})
   334  }
   335  
   336  func TestExtractLinkInfo(t *testing.T) {
   337  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   338  		info, err := ExtractLinkInfo(origContVeth, contNS.Path())
   339  		if err != nil {
   340  			log.Panicf("failed to grab interface info: %v", err)
   341  		}
   342  		expectedInfo := expectedExtractedLinkInfo(contNS.Path())
   343  		if !reflect.DeepEqual(info, expectedInfo) {
   344  			t.Errorf("interface info mismatch. Expected:\n%s\nActual:\n%s",
   345  				spew.Sdump(expectedInfo), spew.Sdump(*info))
   346  		}
   347  	})
   348  }
   349  
   350  func verifyContainerSideNetwork(t *testing.T, origContVeth netlink.Link, contNsPath string, hostNS ns.NetNS, mtu int) {
   351  	allLinks, err := netlink.LinkList()
   352  	if err != nil {
   353  		log.Panicf("error listing links: %v", err)
   354  	}
   355  
   356  	origHwAddr := origContVeth.Attrs().HardwareAddr
   357  	expectedInfo := expectedExtractedLinkInfo(contNsPath)
   358  	csn, err := SetupContainerSideNetwork(expectedInfo, contNsPath, allLinks, false, hostNS)
   359  	if err != nil {
   360  		log.Panicf("failed to set up container side network: %v", err)
   361  	}
   362  	expectedInfo = expectedExtractedLinkInfo(contNsPath)
   363  	if !reflect.DeepEqual(csn.Result, expectedInfo) {
   364  		t.Errorf("interface info mismatch. Expected:\n%s\nActual:\n%s",
   365  			spew.Sdump(expectedInfo), spew.Sdump(*csn.Result))
   366  	}
   367  	if !reflect.DeepEqual(origHwAddr, csn.Interfaces[0].HardwareAddr) {
   368  		t.Errorf("bad hwaddr returned from SetupContainerSideNetwork: %v instead of %v", csn.Interfaces[0].HardwareAddr, origHwAddr)
   369  	}
   370  	// re-query origContVeth attrs
   371  	origContVeth, err = netlink.LinkByName(origContVeth.Attrs().Name)
   372  	if err != nil {
   373  		log.Panicf("the original cni veth is gone")
   374  	}
   375  	if reflect.DeepEqual(origContVeth.Attrs().HardwareAddr, origHwAddr) {
   376  		t.Errorf("cni veth hardware address didn't change")
   377  	}
   378  	if origContVeth.Attrs().MTU != mtu {
   379  		t.Errorf("bad veth MTU: %d instead of %d", origContVeth.Attrs().MTU, mtu)
   380  	}
   381  
   382  	verifyNoAddressAndRoutes(t, origContVeth)
   383  
   384  	bridge := verifyLinkUp(t, "br0", "in-container bridge")
   385  	verifyBridgeMember(t, origContVeth.Attrs().Name, "origContVeth", bridge)
   386  	tap := verifyBridgeMember(t, "tap0", "tap0", bridge)
   387  	if tap.Type() != "tun" {
   388  		t.Errorf("tap0 interface must have type tun, but has %q instead", tap.Type())
   389  	}
   390  	if tap.Attrs().MTU != mtu {
   391  		t.Errorf("bad tap MTU: %d instead of %d", tap.Attrs().MTU, mtu)
   392  	}
   393  
   394  	addrs, err := netlink.AddrList(bridge, FAMILY_V4)
   395  	if err != nil {
   396  		t.Errorf("failed to get addresses for dhcp-side veth: %v", err)
   397  	}
   398  	expectedAddr := "169.254.254.2/24 br0"
   399  	if len(addrs) != 1 {
   400  		t.Errorf("br0 should have exactly one address, but got this instead: %v", spew.Sdump(addrs))
   401  	} else if addrs[0].String() != expectedAddr {
   402  		t.Errorf("bad br0 address %q (expected %q)", addrs[0].String(), expectedAddr)
   403  	}
   404  
   405  	if bridge.Attrs().MTU != mtu {
   406  		t.Errorf("bad bridge MTU: %d instead of %d", bridge.Attrs().MTU, mtu)
   407  	}
   408  }
   409  
   410  func TestSetUpContainerSideNetworkWithInfo(t *testing.T) {
   411  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   412  		if err := StripLink(origContVeth); err != nil {
   413  			log.Panicf("StripLink() failed: %v", err)
   414  		}
   415  		verifyContainerSideNetwork(t, origContVeth, contNS.Path(), hostNS, defaultMTU)
   416  	})
   417  }
   418  
   419  func TestSetUpContainerSideNetworkMTU(t *testing.T) {
   420  	withFakeCNIVethAndGateway(t, 9000, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   421  		if err := StripLink(origContVeth); err != nil {
   422  			log.Panicf("StripLink() failed: %v", err)
   423  		}
   424  		verifyContainerSideNetwork(t, origContVeth, contNS.Path(), hostNS, 9000)
   425  	})
   426  }
   427  
   428  func TestLoopbackInterface(t *testing.T) {
   429  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   430  		verifyContainerSideNetwork(t, origContVeth, contNS.Path(), hostNS, defaultMTU)
   431  		if out, err := exec.Command("ping", "-c", "1", "127.0.0.1").CombinedOutput(); err != nil {
   432  			log.Panicf("ping 127.0.0.1 failed:\n%s", out)
   433  		}
   434  	})
   435  }
   436  
   437  func stringInList(expected string, list []string) bool {
   438  	for _, element := range list {
   439  		if element == expected {
   440  			return true
   441  		}
   442  	}
   443  
   444  	return false
   445  }
   446  
   447  func verifyNoLinks(t *testing.T, linkNames []string) {
   448  	links, err := netlink.LinkList()
   449  	if err != nil {
   450  		log.Panicf("netlink.LinkList failed: %v", err)
   451  	}
   452  
   453  	for _, link := range links {
   454  		linkName := link.Attrs().Name
   455  		if stringInList(linkName, linkNames) {
   456  			t.Errorf("there should not be interface called %s in container namespace", linkName)
   457  		}
   458  	}
   459  }
   460  
   461  func verifyVethHaveConfiguration(t *testing.T, info *cnicurrent.Result) {
   462  	allLinks, err := netlink.LinkList()
   463  	if err != nil {
   464  		log.Panicf("LinkList() failed: %v", err)
   465  	}
   466  	contVeth, err := FindVeth(allLinks)
   467  	if err != nil {
   468  		log.Panicf("FindVeth() failed: %v", err)
   469  	}
   470  
   471  	addrList, err := netlink.AddrList(contVeth, FAMILY_V4)
   472  	if err != nil {
   473  		log.Panicf("AddrList() failed: %v", err)
   474  	}
   475  
   476  	if len(addrList) != 1 {
   477  		t.Errorf("veth should have single address but have: %d", len(addrList))
   478  	}
   479  	if !addrList[0].IP.Equal(info.IPs[0].Address.IP) {
   480  		t.Errorf("veth has ip %s wherever expected is %s", addrList[0].IP.String(), info.IPs[0].Address.IP.String())
   481  	}
   482  	addrMaskSize := addrList[0].Mask.String()
   483  	desiredMaskSize := info.IPs[0].Address.Mask.String()
   484  	if addrMaskSize != desiredMaskSize {
   485  		t.Errorf("veth has ipmask %s wherever expected is %s", addrMaskSize, desiredMaskSize)
   486  	}
   487  
   488  	routeList, err := netlink.RouteList(contVeth, FAMILY_V4)
   489  	if err != nil {
   490  		log.Panicf("RouteList() failed: %v", err)
   491  	}
   492  
   493  	for _, route := range routeList {
   494  		if route.Gw != nil {
   495  			if route.Gw.String() == info.Routes[0].GW.String() {
   496  				return
   497  			}
   498  		}
   499  	}
   500  
   501  	t.Errorf("not found desired route to: %s", info.Routes[0].GW.String())
   502  }
   503  
   504  func TestTeardownContainerSideNetwork(t *testing.T) {
   505  	withFakeCNIVethAndGateway(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   506  		if err := StripLink(origContVeth); err != nil {
   507  			log.Panicf("StripLink() failed: %v", err)
   508  		}
   509  		allLinks, err := netlink.LinkList()
   510  		if err != nil {
   511  			log.Panicf("error listing links: %v", err)
   512  		}
   513  
   514  		csn, err := SetupContainerSideNetwork(expectedExtractedLinkInfo(contNS.Path()), contNS.Path(), allLinks, false, hostNS)
   515  		if err != nil {
   516  			log.Panicf("failed to set up container side network: %v", err)
   517  		}
   518  
   519  		if err := Teardown(csn); err != nil {
   520  			log.Panicf("failed to tear down container side network: %v", err)
   521  		}
   522  
   523  		verifyNoLinks(t, []string{"br0", "tap0"})
   524  		verifyVethHaveConfiguration(t, expectedExtractedLinkInfo(contNS.Path()))
   525  
   526  		// re-quiry origContVeth attrs
   527  		origContVeth, err = netlink.LinkByName(origContVeth.Attrs().Name)
   528  		if err != nil {
   529  			log.Panicf("the original cni veth is gone")
   530  		}
   531  		if !reflect.DeepEqual(origContVeth.Attrs().HardwareAddr, csn.Interfaces[0].HardwareAddr) {
   532  			t.Errorf("cni veth hardware address wasn't restored")
   533  		}
   534  	})
   535  }
   536  
   537  func TestFindingLinkByAddress(t *testing.T) {
   538  	withFakeCNIVeth(t, defaultMTU, func(hostNS, contNS ns.NetNS, origHostVeth, origContVeth netlink.Link) {
   539  		expectedInfo := expectedExtractedLinkInfo(contNS.Path())
   540  		allLinks, err := netlink.LinkList()
   541  		if err != nil {
   542  			log.Panicf("LinkList() failed: %v", err)
   543  		}
   544  
   545  		link, err := findLinkByAddress(allLinks, expectedInfo.IPs[0].Address)
   546  		if err != nil {
   547  			t.Errorf("didn't found preconfigured link: %v", err)
   548  		}
   549  		if link == nil {
   550  			t.Errorf("<nil> where configured link was expected")
   551  		}
   552  
   553  		link, err = findLinkByAddress(allLinks, *parseAddr("1.2.3.4/8").IPNet)
   554  		if link != nil {
   555  			t.Errorf("found link with dummy address")
   556  		}
   557  		if err == nil {
   558  			t.Errorf("expected error but received <nil>")
   559  		}
   560  	})
   561  }
   562  
   563  func withMultipleInterfacesConfigured(t *testing.T, toRun func(contNS ns.NetNS, innerLinks []netlink.Link)) {
   564  	withHostAndContNS(t, func(hostNS, contNS ns.NetNS) {
   565  		var origContVeths [2]netlink.Link
   566  		for n, vp := range []struct {
   567  			name        string
   568  			outerHwAddr string
   569  			innerHwAddr string
   570  			ip          string
   571  		}{
   572  			{"eth0", outerHwAddr, innerHwAddr, "10.1.90.5/24"},
   573  			{"eth1", secondOuterHwAddr, secondInnerHwAddr, "192.168.37.8/16"},
   574  		} {
   575  			origHostVeth, origContVeth, err := CreateEscapeVethPair(contNS, vp.name, 1500)
   576  			if err != nil {
   577  				log.Panicf("failed to create veth pair %q: %v", vp.name, err)
   578  			}
   579  			inNS(hostNS, "hostNS", func() { setupLink(vp.outerHwAddr, origHostVeth) })
   580  			inNS(contNS, "contNS", func() {
   581  				origContVeths[n] = setupLink(vp.innerHwAddr, origContVeth)
   582  				if err = netlink.AddrAdd(origContVeths[n], parseAddr(vp.ip)); err != nil {
   583  					log.Panicf("failed to add addr for %q: %v", vp.name, err)
   584  				}
   585  			})
   586  		}
   587  		inNS(contNS, "contNS", func() {
   588  			gwAddr := parseAddr("10.1.90.1/24")
   589  			addTestRoute(t, &netlink.Route{
   590  				Gw:    gwAddr.IPNet.IP,
   591  				Scope: SCOPE_UNIVERSE,
   592  			})
   593  
   594  			toRun(contNS, origContVeths[:])
   595  		})
   596  	})
   597  }
   598  
   599  func expectedExtractedLinkInfoForMultipleInterfaces(contNsPath string) *cnicurrent.Result {
   600  	expectedInfo := expectedExtractedLinkInfo(contNsPath)
   601  	expectedInfo.IPs = append(expectedInfo.IPs, &cnicurrent.IPConfig{
   602  		Version:   "4",
   603  		Interface: 1,
   604  		Address: net.IPNet{
   605  			IP:   net.IP{192, 168, 37, 8},
   606  			Mask: net.IPMask{255, 255, 0, 0},
   607  		},
   608  	})
   609  	expectedInfo.Interfaces = append(expectedInfo.Interfaces, &cnicurrent.Interface{
   610  		Name:    "eth1",
   611  		Mac:     secondInnerHwAddr,
   612  		Sandbox: contNsPath,
   613  	})
   614  	return expectedInfo
   615  }
   616  
   617  func expectedExtractedLinkInfoWithMissingInterface(contNsPath string) *cnicurrent.Result {
   618  	expectedInfo := expectedExtractedLinkInfo(contNsPath)
   619  	expectedInfo.IPs = append(expectedInfo.IPs, &cnicurrent.IPConfig{
   620  		Version:   "4",
   621  		Interface: -1,
   622  		Address: net.IPNet{
   623  			IP:   net.IP{192, 168, 37, 8},
   624  			Mask: net.IPMask{255, 255, 0, 0},
   625  		},
   626  	})
   627  	return expectedInfo
   628  }
   629  
   630  func TestMultiInterfaces(t *testing.T) {
   631  	withMultipleInterfacesConfigured(t, func(contNS ns.NetNS, innerLinks []netlink.Link) {
   632  		expectedInfo := expectedExtractedLinkInfoForMultipleInterfaces(contNS.Path())
   633  		result, err := ValidateAndFixCNIResult(expectedInfo, contNS.Path(), innerLinks)
   634  		if err != nil {
   635  			t.Errorf("error during validate/fix cni result: %v", err)
   636  		}
   637  		if !reflect.DeepEqual(result, expectedInfo) {
   638  			t.Errorf("result different than expected:\nActual:\n%s\nExpected:\n%s",
   639  				spew.Sdump(result), spew.Sdump(expectedInfo))
   640  		}
   641  	})
   642  }
   643  
   644  func TestMultiInterfacesWithMissingInterface(t *testing.T) {
   645  	withMultipleInterfacesConfigured(t, func(contNS ns.NetNS, innerLinks []netlink.Link) {
   646  		infoToFix := expectedExtractedLinkInfoForMultipleInterfaces(contNS.Path())
   647  		expectedInfo := expectedExtractedLinkInfoForMultipleInterfaces(contNS.Path())
   648  		result, err := ValidateAndFixCNIResult(infoToFix, contNS.Path(), innerLinks)
   649  		if err != nil {
   650  			t.Errorf("error during validate/fix cni result: %v", err)
   651  		}
   652  		if !reflect.DeepEqual(result, expectedInfo) {
   653  			t.Errorf("result different than expected:\nActual:\n%s\nExpected:\n%s",
   654  				spew.Sdump(result), spew.Sdump(expectedInfo))
   655  		}
   656  	})
   657  }