From cfb90c9d0de377bf564b397f673d312f99e04515 Mon Sep 17 00:00:00 2001 From: Kathryn Baldauf Date: Wed, 6 Nov 2024 08:53:33 -0800 Subject: [PATCH] Add support for HCN v2 endpoint and add unit tests * switch to HCN v2 endpoint API instead of HNS v1 endpoint API * Support parsing routes in GCS when we setup the network interfaces * [breaking] update gcs bridge LCOW network adapter type with new fields that better align with v2 endpoint * [breaking] REMOVE support for policy based routing and EnableLowMetric in the GCS network config code * Add unit tests for new GCS side changes Signed-off-by: Kathryn Baldauf --- internal/guest/network/netns.go | 222 +++---- internal/guest/network/netns_test.go | 663 +++++++++++++++++++ internal/guest/runtime/hcsv2/network.go | 22 +- internal/protocol/guestresource/resources.go | 34 +- internal/uvm/computeagent.go | 35 +- internal/uvm/network.go | 117 ++-- internal/uvm/network_test.go | 6 +- internal/uvm/types.go | 4 +- 8 files changed, 905 insertions(+), 198 deletions(-) create mode 100644 internal/guest/network/netns_test.go diff --git a/internal/guest/network/netns.go b/internal/guest/network/netns.go index bbafa0f505..d8f84efbc5 100644 --- a/internal/guest/network/netns.go +++ b/internal/guest/network/netns.go @@ -10,16 +10,32 @@ import ( "os/exec" "runtime" "strconv" + "strings" "time" - "github.com/Microsoft/hcsshim/internal/guest/prot" "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" ) +var ( + // function definitions for mocking configureLink + netlinkAddrAdd = netlink.AddrAdd + netlinkRouteAdd = netlink.RouteAdd +) + +const ( + ipv4GwDestination = "0.0.0.0/0" + ipv4EmptyGw = "0.0.0.0" + ipv6GwDestination = "::/0" + ipv6EmptyGw = "::" + + unreachableErr = "network is unreachable" +) + // MoveInterfaceToNS moves the adapter with interface name `ifStr` to the network namespace // of `pid`. func MoveInterfaceToNS(ifStr string, pid int) error { @@ -67,7 +83,7 @@ func DoInNetNS(ns netns.NsHandle, run func() error) error { // // This function MUST be used in tandem with `DoInNetNS` or some other means that ensures that the goroutine // executing this code stays on the same thread. -func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.NetworkAdapter) error { +func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *guestresource.LCOWNetworkAdapter) error { ctx, entry := log.S(ctx, logrus.Fields{ "ifname": ifStr, "pid": nsPid, @@ -101,29 +117,14 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net } // Configure the interface - if adapter.NatEnabled { - entry.Tracef("Configuring interface with NAT: %s/%d gw=%s", - adapter.AllocatedIPAddress, - adapter.HostIPPrefixLength, adapter.HostIPAddress) - metric := 1 - if adapter.EnableLowMetric { - metric = 500 - } + if len(adapter.IPConfigs) != 0 { + entry.Debugf("Configuring interface with NAT: %v", adapter) // Bring the interface up if err := netlink.LinkSetUp(link); err != nil { - return errors.Wrapf(err, "netlink.LinkSetUp(%#v) failed", link) - } - if err := assignIPToLink(ctx, ifStr, nsPid, link, - adapter.AllocatedIPAddress, adapter.HostIPAddress, adapter.HostIPPrefixLength, - adapter.EnableLowMetric, metric, - ); err != nil { - return err + return fmt.Errorf("netlink.LinkSetUp(%#v) failed: %w", link, err) } - if err := assignIPToLink(ctx, ifStr, nsPid, link, - adapter.AllocatedIPv6Address, adapter.HostIPv6Address, adapter.HostIPv6PrefixLength, - adapter.EnableLowMetric, metric, - ); err != nil { + if err := configureLink(ctx, link, adapter); err != nil { return err } } else { @@ -186,107 +187,104 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net return nil } -func assignIPToLink(ctx context.Context, - ifStr string, - nsPid int, +func configureLink(ctx context.Context, link netlink.Link, - allocatedIP string, - gatewayIP string, - prefixLen uint8, - enableLowMetric bool, - metric int, + adapter *guestresource.LCOWNetworkAdapter, ) error { - entry := log.G(ctx) - entry.WithFields(logrus.Fields{ - "link": link.Attrs().Name, - "IP": allocatedIP, - "prefixLen": prefixLen, - "gateway": gatewayIP, - "metric": metric, - }).Trace("assigning IP address") - if allocatedIP == "" { - return nil - } - // Set IP address - ip, addr, err := net.ParseCIDR(allocatedIP + "/" + strconv.FormatUint(uint64(prefixLen), 10)) - if err != nil { - return errors.Wrapf(err, "parsing address %s/%d failed", allocatedIP, prefixLen) - } - // the IP address field in addr is masked, so replace it with the original ip address - addr.IP = ip - entry.WithFields(logrus.Fields{ - "allocatedIP": ip, - "IP": addr, - }).Debugf("parsed ip address %s/%d", allocatedIP, prefixLen) - ipAddr := &netlink.Addr{IPNet: addr, Label: ""} - if err := netlink.AddrAdd(link, ipAddr); err != nil { - return errors.Wrapf(err, "netlink.AddrAdd(%#v, %#v) failed", link, ipAddr) - } - if gatewayIP == "" { - return nil - } - // Set gateway - gw := net.ParseIP(gatewayIP) - if gw == nil { - return errors.Wrapf(err, "parsing gateway address %s failed", gatewayIP) - } + for _, ipConfig := range adapter.IPConfigs { + log.G(ctx).WithFields(logrus.Fields{ + "link": link.Attrs().Name, + "IP": ipConfig.IPAddress, + "prefixLen": ipConfig.PrefixLength, + }).Debug("assigning IP address") - if !addr.Contains(gw) { - // In the case that a gw is not part of the subnet we are setting gw for, - // a new addr containing this gw address need to be added into the link to avoid getting - // unreachable error when adding this out-of-subnet gw route - entry.Debugf("gw is outside of the subnet: Configure %s in %d with: %s/%d gw=%s\n", - ifStr, nsPid, allocatedIP, prefixLen, gatewayIP) - - // net library's ParseIP call always returns an array of length 16, so we - // need to first check if the address is IPv4 or IPv6 before calculating - // the mask length. See https://pkg.go.dev/net#ParseIP. - ml := 8 - if gw.To4() != nil { - ml *= net.IPv4len - } else if gw.To16() != nil { - ml *= net.IPv6len - } else { - return fmt.Errorf("gw IP is neither IPv4 nor IPv6: %v", gw) + // Set IP address + ip, addr, err := net.ParseCIDR(ipConfig.IPAddress + "/" + strconv.FormatUint(uint64(ipConfig.PrefixLength), 10)) + if err != nil { + return fmt.Errorf("parsing address %s/%d failed: %w", ipConfig.IPAddress, ipConfig.PrefixLength, err) } - addr2 := &net.IPNet{ - IP: gw, - Mask: net.CIDRMask(ml, ml)} - ipAddr2 := &netlink.Addr{IPNet: addr2, Label: ""} - if err := netlink.AddrAdd(link, ipAddr2); err != nil { - return errors.Wrapf(err, "netlink.AddrAdd(%#v, %#v) failed", link, ipAddr2) + // the IP address field in addr is masked, so replace it with the original ip address + addr.IP = ip + log.G(ctx).WithFields(logrus.Fields{ + "allocatedIP": ip, + "IP": addr, + }).Debugf("parsed ip address %s/%d", ipConfig.IPAddress, ipConfig.PrefixLength) + ipAddr := &netlink.Addr{IPNet: addr, Label: ""} + if err := netlinkAddrAdd(link, ipAddr); err != nil { + return fmt.Errorf("netlink.AddrAdd(%#v, %#v) failed: %w", link, ipAddr, err) } } - var table int - if enableLowMetric { - // add a route rule for the new interface so packets coming on this interface - // always go out the same interface - _, ml := addr.Mask.Size() - srcNet := &net.IPNet{ - IP: net.ParseIP(allocatedIP), - Mask: net.CIDRMask(ml, ml), + for _, r := range adapter.Routes { + log.G(ctx).WithField("route", r).Debugf("adding a route to interface %s", link.Attrs().Name) + + if (r.DestinationPrefix == ipv4GwDestination || r.DestinationPrefix == ipv6GwDestination) && + (r.NextHop == ipv4EmptyGw || r.NextHop == ipv6EmptyGw) { + // this indicates no default gateway was added for this interface + continue } - rule := netlink.NewRule() - rule.Table = 101 - rule.Src = srcNet - rule.Priority = 5 - if err := netlink.RuleAdd(rule); err != nil { - return errors.Wrapf(err, "netlink.RuleAdd(%#v) failed", rule) + // dst will be nil when setting default gateways + var dst *net.IPNet + if !(r.DestinationPrefix == ipv4GwDestination || r.DestinationPrefix == ipv6GwDestination) { + dstIP, dstAddr, err := net.ParseCIDR(r.DestinationPrefix) + if err != nil { + return fmt.Errorf("parsing route dst address %s failed: %w", r.DestinationPrefix, err) + } + dstAddr.IP = dstIP + dst = dstAddr + } + + // gw can be nil when setting something like + // ip route add 10.0.0.0/16 dev eth0 + gw := net.ParseIP(r.NextHop) + if gw == nil && dst == nil { + return fmt.Errorf("gw and destination cannot both be nil") + } + + route := netlink.Route{ + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: link.Attrs().Index, + Gw: gw, + Dst: dst, + Priority: int(r.Metric), + } + if err := netlinkRouteAdd(&route); err != nil { + // unfortunately, netlink library doesn't have great error handling, + // so we have to rely on the string error here + if strings.Contains(err.Error(), unreachableErr) && gw != nil { + // In the case that a gw is not part of the subnet we are setting gw for, + // a new addr containing this gw address needs to be added into the link to avoid getting + // unreachable error when adding this out-of-subnet gw route + log.G(ctx).Infof("gw is outside of the subnet: %v", gw) + + // net library's ParseIP call always returns an array of length 16, so we + // need to first check if the address is IPv4 or IPv6 before calculating + // the mask length. See https://pkg.go.dev/net#ParseIP. + ml := 8 + if gw.To4() != nil { + ml *= net.IPv4len + } else if gw.To16() != nil { + ml *= net.IPv6len + } else { + return fmt.Errorf("gw IP is neither IPv4 nor IPv6: %v", gw) + } + addr2 := &net.IPNet{ + IP: gw, + Mask: net.CIDRMask(ml, ml)} + ipAddr2 := &netlink.Addr{IPNet: addr2, Label: ""} + if err := netlinkAddrAdd(link, ipAddr2); err != nil { + return fmt.Errorf("netlink.AddrAdd(%#v, %#v) failed: %w", link, ipAddr2, err) + } + + // try adding the route again + if err := netlinkRouteAdd(&route); err != nil { + return fmt.Errorf("netlink.RouteAdd(%#v) failed: %w", route, err) + } + } else { + return fmt.Errorf("netlink.RouteAdd(%#v) failed: %w", route, err) + } } - table = rule.Table - } - // add the default route in that interface specific table - route := netlink.Route{ - Scope: netlink.SCOPE_UNIVERSE, - LinkIndex: link.Attrs().Index, - Gw: gw, - Table: table, - Priority: metric, - } - if err := netlink.RouteAdd(&route); err != nil { - return errors.Wrapf(err, "netlink.RouteAdd(%#v) failed", route) } return nil } diff --git a/internal/guest/network/netns_test.go b/internal/guest/network/netns_test.go new file mode 100644 index 0000000000..93393c8e9c --- /dev/null +++ b/internal/guest/network/netns_test.go @@ -0,0 +1,663 @@ +//go:build linux +// +build linux + +package network + +import ( + "bytes" + "context" + "fmt" + "net" + "strings" + "testing" + + "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/vishvananda/netlink" +) + +const ( + ipv4TotalMaskLength = 32 + ipv6TotalMaskLength = 128 +) + +type testRoute struct { + scope netlink.Scope + dstIP string + gw string + priority int +} + +type testAddr struct { + ip string + prefixLen int + maskLength int +} + +type fakeLink struct { + attr *netlink.LinkAttrs +} + +func (l *fakeLink) Attrs() *netlink.LinkAttrs { + return l.attr +} + +func (l *fakeLink) Type() string { + return "" +} + +func newFakeLink(name string, index int) *fakeLink { + attr := &netlink.LinkAttrs{ + Name: name, + Index: index, + } + return &fakeLink{ + attr: attr, + } +} + +var _ = (netlink.Link)(&fakeLink{}) + +// unreachableNetlinkRouteAdd is a helper function that will always return that the +// network is unreachable +func unreachableNetlinkRouteAdd(count *int, link netlink.Link, expected []*testRoute) func(_ *netlink.Route) error { + return func(route *netlink.Route) error { + f := standardNetlinkRouteAdd(count, link, expected) + if err := f(route); err != nil { + return err + } + return fmt.Errorf(unreachableErr) + } +} + +func standardNetlinkRouteAdd(count *int, link netlink.Link, expected []*testRoute) func(_ *netlink.Route) error { + return func(route *netlink.Route) error { + if *count >= len(expected) { + return fmt.Errorf("expected to call route add %d times, instead got %d", len(expected), *count) + } + exp := expected[*count] + + if exp.scope != route.Scope { + return fmt.Errorf("expected scope %s, instead got %s", exp.scope, route.Scope) + } + + if route.Gw == nil && exp.gw != "" { + return fmt.Errorf("expected to have gw set %s", exp.gw) + } else if route.Gw != nil && (exp.gw != route.Gw.String()) { + return fmt.Errorf("expected gw %s, instead got %s", exp.gw, route.Gw.String()) + } + + if route.Dst == nil && exp.dstIP != "" { + return fmt.Errorf("expected to have dst set %s", exp.dstIP) + } else if route.Dst != nil && (exp.dstIP != route.Dst.String()) { + return fmt.Errorf("expected dst %s, instead got %s", exp.dstIP, route.Dst.String()) + } + + if route.Priority != exp.priority { + return fmt.Errorf("expected to use metric %d, instead used %d", exp.priority, route.Priority) + } + + if link.Attrs().Index != route.LinkIndex { + return fmt.Errorf("expected to get link index %d, instead got %d", link.Attrs().Index, route.LinkIndex) + } + + *count++ + return nil + } +} + +func standardNetlinkAddrAdd(count *int, expected []*testAddr) func(_ netlink.Link, _ *netlink.Addr) error { + return func(link netlink.Link, addr *netlink.Addr) error { + if *count >= len(expected) { + return fmt.Errorf("expected to call addr add %d times, instead got %d", len(expected), *count) + } + exp := expected[*count] + + if addr.IP.String() != exp.ip { + return fmt.Errorf("expected to add address %s, instead got %s", exp.ip, addr.IP.String()) + } + expectedMask := net.CIDRMask(exp.prefixLen, exp.maskLength) + if !bytes.Equal(addr.Mask, expectedMask) { + return fmt.Errorf("expected mask to be %s, instead got %s", expectedMask, addr.Mask) + } + *count++ + return nil + } +} + +func Test_configureLink_IPv4(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "192.168.0.100", + DestinationPrefix: "0.0.0.0/0", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "192.168.0.100", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "192.168.0.5", + prefixLen: 24, + maskLength: ipv4TotalMaskLength, + }, + } + + routeAddCount := 0 + netlinkRouteAdd = standardNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_IPv6(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + PrefixLength: 64, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + DestinationPrefix: "::/0", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + prefixLen: 64, + maskLength: ipv6TotalMaskLength, + }, + } + + routeAddCount := 0 + netlinkRouteAdd = standardNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_IPv4AndIPv6(t *testing.T) { + ctx := context.Background() + + link1 := newFakeLink("eth0", 1) + + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + { + IPAddress: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + PrefixLength: 64, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "192.168.0.100", + DestinationPrefix: "0.0.0.0/0", + }, + { + NextHop: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + DestinationPrefix: "::/0", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "192.168.0.100", + priority: 0, + }, + { + scope: netlink.SCOPE_UNIVERSE, + gw: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "192.168.0.5", + prefixLen: 24, + maskLength: ipv4TotalMaskLength, + }, + { + ip: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + prefixLen: 64, + maskLength: ipv6TotalMaskLength, + }, + } + + routeAddCount := 0 + netlinkRouteAdd = standardNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_No_Gateway_IPv4(t *testing.T) { + ctx := context.Background() + + link1 := newFakeLink("eth0", 0) + + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + }, + } + expectedAddr := []*testAddr{ + { + ip: "192.168.0.5", + prefixLen: 24, + maskLength: ipv4TotalMaskLength, + }, + } + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_No_Gateway_IPv6(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + PrefixLength: 64, + }, + }, + } + expectedAddr := []*testAddr{ + { + ip: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + prefixLen: 64, + maskLength: ipv6TotalMaskLength, + }, + } + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_GatewayOutsideSubnet_IPv4(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "10.0.0.2", + DestinationPrefix: "0.0.0.0/0", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "10.0.0.2", + priority: 0, + }, + { + // routeAdd should be called twice with the same content in this test + scope: netlink.SCOPE_UNIVERSE, + gw: "10.0.0.2", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "192.168.0.5", + prefixLen: 24, + maskLength: ipv4TotalMaskLength, + }, + { + ip: "10.0.0.2", + prefixLen: ipv4TotalMaskLength, + maskLength: ipv4TotalMaskLength, + }, + } + + // since it isn't easy to change the definition of the netlinkRouteAdd per call, + // instead of checking for a success for this test case, we just check that the + // behavior we expect happens when we get the error message we expect. + routeAddCount := 0 + netlinkRouteAdd = unreachableNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + err := configureLink(ctx, link1, adapter) + if err == nil || !strings.Contains(err.Error(), unreachableErr) { + t.Fatalf("expected an error from configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_GatewayOutsideSubnet_IPv6(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + PrefixLength: 64, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "9999:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + DestinationPrefix: "::/0", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "9999:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + priority: 0, + }, + { + // routeAdd should be called twice with the same content in this test + scope: netlink.SCOPE_UNIVERSE, + gw: "9999:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + prefixLen: 64, + maskLength: ipv6TotalMaskLength, + }, + { + ip: "9999:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + prefixLen: ipv6TotalMaskLength, + maskLength: ipv6TotalMaskLength, + }, + } + + // since it isn't easy to change the definition of the netlinkRouteAdd per call, + // instead of checking for a success for this test case, we just check that the + // behavior we expect happens when we get the error message we expect. + routeAddCount := 0 + netlinkRouteAdd = unreachableNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + err := configureLink(ctx, link1, adapter) + if err == nil || !strings.Contains(err.Error(), unreachableErr) { + t.Fatalf("expected an error from configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_MultiRoute_IPv4(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "0.0.0.0", + DestinationPrefix: "0.0.0.0/0", + }, + { + NextHop: "192.168.0.100", + DestinationPrefix: "0.0.0.0/0", + }, + { + NextHop: "192.168.0.5", + DestinationPrefix: "10.10.0.0/16", + }, + { + DestinationPrefix: "10.0.0.0/16", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "192.168.0.100", + priority: 0, + }, + { + scope: netlink.SCOPE_UNIVERSE, + gw: "192.168.0.5", + dstIP: "10.10.0.0/16", + priority: 0, + }, + { + scope: netlink.SCOPE_UNIVERSE, + dstIP: "10.0.0.0/16", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "192.168.0.5", + prefixLen: 24, + maskLength: ipv4TotalMaskLength, + }, + } + + routeAddCount := 0 + netlinkRouteAdd = standardNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_MultiRoute_IPv6(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + PrefixLength: 64, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + NextHop: "::", + DestinationPrefix: "::/0", + }, + { + NextHop: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + DestinationPrefix: "::/0", + }, + { + NextHop: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:cccc", + DestinationPrefix: "fc49:65e9:61e8:5aa7:9680:dd25:8ce8:c4f0/64", + }, + { + DestinationPrefix: "25a6:6c50:5564:4a67:d7d3:6aa3:7e1f:9786/64", + }, + }, + } + expectedRoutes := []*testRoute{ + { + scope: netlink.SCOPE_UNIVERSE, + gw: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:aaaa", + priority: 0, + }, + { + scope: netlink.SCOPE_UNIVERSE, + gw: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:cccc", + dstIP: "fc49:65e9:61e8:5aa7:9680:dd25:8ce8:c4f0/64", + priority: 0, + }, + { + scope: netlink.SCOPE_UNIVERSE, + dstIP: "25a6:6c50:5564:4a67:d7d3:6aa3:7e1f:9786/64", + priority: 0, + }, + } + expectedAddr := []*testAddr{ + { + ip: "9541:a2d4:f0f3:18ff:c868:26ce:e9c4:30a6", + prefixLen: 64, + maskLength: ipv6TotalMaskLength, + }, + } + + routeAddCount := 0 + netlinkRouteAdd = standardNetlinkRouteAdd(&routeAddCount, link1, expectedRoutes) + + addrAddCount := 0 + netlinkAddrAdd = standardNetlinkAddrAdd(&addrAddCount, expectedAddr) + + if err := configureLink(ctx, link1, adapter); err != nil { + t.Fatalf("configureLink: %s", err) + } + + if routeAddCount != len(expectedRoutes) { + t.Fatalf("expected to call routeAdd %d times, instead called it %d times", len(expectedRoutes), routeAddCount) + } + + if addrAddCount != len(expectedAddr) { + t.Fatalf("expected to call addrAdd %d times, instead called it %d times", len(expectedAddr), addrAddCount) + } +} + +func Test_configureLink_Bad_Route_IPv4(t *testing.T) { + ctx := context.Background() + link1 := newFakeLink("eth0", 0) + adapter := &guestresource.LCOWNetworkAdapter{ + IPConfigs: []guestresource.LCOWIPConfig{ + { + IPAddress: "192.168.0.5", + PrefixLength: 24, + }, + }, + Routes: []guestresource.LCOWRoute{ + { + // this is a bad route, destination prefix cannot be empty. + // Default gateways should have a destination prefix of "0.0.0.0/0" + NextHop: "192.168.0.100", + }, + }, + } + + err := configureLink(ctx, link1, adapter) + if err == nil { + t.Fatal("configureLink expected error due to badly formed route") + } +} diff --git a/internal/guest/runtime/hcsv2/network.go b/internal/guest/runtime/hcsv2/network.go index e8b0ebec17..1cfe5dd354 100644 --- a/internal/guest/runtime/hcsv2/network.go +++ b/internal/guest/runtime/hcsv2/network.go @@ -16,7 +16,6 @@ import ( "github.com/Microsoft/hcsshim/internal/guest/gcserr" "github.com/Microsoft/hcsshim/internal/guest/network" - "github.com/Microsoft/hcsshim/internal/guest/prot" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) @@ -215,12 +214,7 @@ func (n *namespace) Sync(ctx context.Context) (err error) { defer n.m.Unlock() if n.pid != 0 { - for i, a := range n.nics { - // Lower the metric for anything but the first adapter - // TODO: remove when we correctly support assigning metrics to the default GWs - if i > 0 { - a.adapter.EnableLowMetric = true - } + for _, a := range n.nics { err = a.assignToPid(ctx, n.pid) if err != nil { return err @@ -252,18 +246,6 @@ func (nin *nicInNamespace) assignToPid(ctx context.Context, pid int) (err error) trace.StringAttribute("ifname", nin.ifname), trace.Int64Attribute("pid", int64(pid))) - v1Adapter := &prot.NetworkAdapter{ - NatEnabled: (nin.adapter.IPAddress != "") || (nin.adapter.IPv6Address != ""), - AllocatedIPAddress: nin.adapter.IPAddress, - HostIPAddress: nin.adapter.GatewayAddress, - HostIPPrefixLength: nin.adapter.PrefixLength, - AllocatedIPv6Address: nin.adapter.IPv6Address, - HostIPv6Address: nin.adapter.IPv6GatewayAddress, - HostIPv6PrefixLength: nin.adapter.IPv6PrefixLength, - EnableLowMetric: nin.adapter.EnableLowMetric, - EncapOverhead: nin.adapter.EncapOverhead, - } - if err := network.MoveInterfaceToNS(nin.ifname, pid); err != nil { return errors.Wrapf(err, "failed to move interface %s to network namespace", nin.ifname) } @@ -276,7 +258,7 @@ func (nin *nicInNamespace) assignToPid(ctx context.Context, pid int) (err error) defer ns.Close() netNSCfg := func() error { - return network.NetNSConfig(ctx, nin.ifname, pid, v1Adapter) + return network.NetNSConfig(ctx, nin.ifname, pid, nin.adapter) } if err := network.DoInNetNS(ns, netNSCfg); err != nil { diff --git a/internal/protocol/guestresource/resources.go b/internal/protocol/guestresource/resources.go index 1eb695e801..c789ec3243 100644 --- a/internal/protocol/guestresource/resources.go +++ b/internal/protocol/guestresource/resources.go @@ -151,20 +151,26 @@ type LCOWMappedVPCIDevice struct { // LCOWNetworkAdapter represents a network interface and its associated // configuration in a namespace. type LCOWNetworkAdapter struct { - NamespaceID string `json:",omitempty"` - ID string `json:",omitempty"` - MacAddress string `json:",omitempty"` - IPAddress string `json:",omitempty"` - PrefixLength uint8 `json:",omitempty"` - GatewayAddress string `json:",omitempty"` - IPv6Address string `json:",omitempty"` - IPv6PrefixLength uint8 `json:",omitempty"` - IPv6GatewayAddress string `json:",omitempty"` - DNSSuffix string `json:",omitempty"` - DNSServerList string `json:",omitempty"` - EnableLowMetric bool `json:",omitempty"` - EncapOverhead uint16 `json:",omitempty"` - VPCIAssigned bool `json:",omitempty"` + NamespaceID string `json:",omitempty"` + ID string `json:",omitempty"` + MacAddress string `json:",omitempty"` + DNSSuffix string `json:",omitempty"` + DNSServerList string `json:",omitempty"` + EncapOverhead uint16 `json:",omitempty"` + VPCIAssigned bool `json:",omitempty"` + IPConfigs []LCOWIPConfig `json:",omitempty"` + Routes []LCOWRoute `json:",omitempty"` +} + +type LCOWIPConfig struct { + IPAddress string `json:",omitempty"` + PrefixLength uint8 `json:",omitempty"` +} + +type LCOWRoute struct { + NextHop string `json:",omitempty"` + DestinationPrefix string `json:",omitempty"` + Metric uint16 `json:",omitempty"` } type LCOWContainerConstraints struct { diff --git a/internal/uvm/computeagent.go b/internal/uvm/computeagent.go index 7516cdf6ea..3bfd68cfa3 100644 --- a/internal/uvm/computeagent.go +++ b/internal/uvm/computeagent.go @@ -4,6 +4,8 @@ package uvm import ( "context" + "fmt" + "net" "strings" "github.com/Microsoft/go-winio" @@ -112,13 +114,34 @@ func (ca *computeAgent) AddNIC(ctx context.Context, req *computeagent.AddNICInte switch endpt := endpoint.(type) { case *ncproxynetworking.Endpoint: + ipConfig := guestresource.LCOWIPConfig{ + IPAddress: endpt.Settings.IPAddress, + PrefixLength: uint8(endpt.Settings.IPAddressPrefixLength), + } + cfg := &guestresource.LCOWNetworkAdapter{ - NamespaceID: endpt.NamespaceID, - ID: req.NicID, - IPAddress: endpt.Settings.IPAddress, - PrefixLength: uint8(endpt.Settings.IPAddressPrefixLength), - GatewayAddress: endpt.Settings.DefaultGateway, - VPCIAssigned: true, + NamespaceID: endpt.NamespaceID, + ID: req.NicID, + VPCIAssigned: true, + IPConfigs: []guestresource.LCOWIPConfig{ipConfig}, + } + + if endpt.Settings.DefaultGateway != "" { + gw := net.ParseIP(endpt.Settings.DefaultGateway) + if gw == nil { + return nil, fmt.Errorf("invalid gateway address: %s", endpt.Settings.DefaultGateway) + } + route := guestresource.LCOWRoute{ + NextHop: endpt.Settings.DefaultGateway, + } + if gw.To4() != nil { + route.DestinationPrefix = "0.0.0.0/0" + } else if gw.To16() != nil { + route.DestinationPrefix = "::/0" + } else { + return nil, fmt.Errorf("gateway address is neither IPv4 nor IPv6: %s", endpt.Settings.DefaultGateway) + } + cfg.Routes = []guestresource.LCOWRoute{route} } if err := ca.uvm.AddNICInGuest(ctx, cfg); err != nil { return nil, err diff --git a/internal/uvm/network.go b/internal/uvm/network.go index 18090f1ec5..2f20bf4364 100644 --- a/internal/uvm/network.go +++ b/internal/uvm/network.go @@ -4,6 +4,7 @@ package uvm import ( "context" + "encoding/json" "fmt" "os" "slices" @@ -38,8 +39,8 @@ var ( ErrNICNotFound = errors.New("NIC not found in network namespace") ) -func sortEndpoints(endpoints []*hns.HNSEndpoint) { - cmp := func(a, b *hns.HNSEndpoint) int { +func sortEndpoints(endpoints []*hcn.HostComputeEndpoint) { + cmp := func(a, b *hcn.HostComputeEndpoint) int { if strings.HasSuffix(a.Name, "eth0") { return -1 } @@ -59,7 +60,7 @@ func (uvm *UtilityVM) SetupNetworkNamespace(ctx context.Context, nsid string) er nsidInsideUVM := nsid // Query endpoints with actual nsid - endpoints, err := GetNamespaceEndpoints(ctx, nsid) + endpoints, err := GetHCNNamespaceEndpoints(ctx, nsid) if err != nil { return err } @@ -92,22 +93,22 @@ func (uvm *UtilityVM) SetupNetworkNamespace(ctx context.Context, nsid string) er return nil } -// GetNamespaceEndpoints gets all endpoints in `netNS` -func GetNamespaceEndpoints(ctx context.Context, netNS string) ([]*hns.HNSEndpoint, error) { - op := "uvm::GetNamespaceEndpoints" +// GetHCNNamespaceEndpoints gets all endpoints in `netNS` with the HCN v2 API +func GetHCNNamespaceEndpoints(ctx context.Context, netNS string) ([]*hcn.HostComputeEndpoint, error) { + op := "uvm::GetHCNNamespaceEndpoints" l := log.G(ctx).WithField("netns-id", netNS) l.Debug(op + " - Begin") defer func() { l.Debug(op + " - End") }() - ids, err := hns.GetNamespaceEndpoints(netNS) + ids, err := hcn.GetNamespaceEndpointIds(netNS) if err != nil { return nil, err } - var endpoints []*hns.HNSEndpoint + var endpoints []*hcn.HostComputeEndpoint for _, id := range ids { - endpoint, err := hns.GetHNSEndpointByID(id) + endpoint, err := hcn.GetEndpointByID(id) if err != nil { return nil, err } @@ -382,13 +383,18 @@ func (uvm *UtilityVM) AddNetNSByID(ctx context.Context, id string) error { // // If no network namespace matches `id` returns `ErrNetNSNotFound`. func (uvm *UtilityVM) AddEndpointToNSWithID(ctx context.Context, nsID, nicID string, endpoint *hns.HNSEndpoint) error { + // get the v2 endpoint + endpointV2, err := hcn.GetEndpointByID(endpoint.Id) + if err != nil { + return err + } uvm.m.Lock() defer uvm.m.Unlock() ns, ok := uvm.namespaces[nsID] if !ok { return ErrNetNSNotFound } - if _, ok := ns.nics[endpoint.Id]; !ok { + if _, ok := ns.nics[endpointV2.Id]; !ok { if nicID == "" { id, err := guid.NewV4() if err != nil { @@ -396,12 +402,12 @@ func (uvm *UtilityVM) AddEndpointToNSWithID(ctx context.Context, nsID, nicID str } nicID = id.String() } - if err := uvm.addNIC(ctx, nicID, endpoint); err != nil { + if err := uvm.addNIC(ctx, nicID, endpointV2); err != nil { return err } - ns.nics[endpoint.Id] = &nicInfo{ + ns.nics[endpointV2.Id] = &nicInfo{ ID: nicID, - Endpoint: endpoint, + Endpoint: endpointV2, } } return nil @@ -412,7 +418,7 @@ func (uvm *UtilityVM) AddEndpointToNSWithID(ctx context.Context, nsID, nicID str // added endpoints. // // If no network namespace matches `id` returns `ErrNetNSNotFound`. -func (uvm *UtilityVM) AddEndpointsToNS(ctx context.Context, id string, endpoints []*hns.HNSEndpoint) error { +func (uvm *UtilityVM) AddEndpointsToNS(ctx context.Context, id string, endpoints []*hcn.HostComputeEndpoint) error { uvm.m.Lock() defer uvm.m.Unlock() @@ -547,8 +553,56 @@ func getNetworkModifyRequest(adapterID string, requestType guestrequest.RequestT } } +// convertToLCOWReq converts the HCN endpoint type to the guestresource.LCOWNetworkAdapter type that is +// passed to the GCS for a request. +func convertToLCOWReq(id string, endpoint *hcn.HostComputeEndpoint) (*guestresource.LCOWNetworkAdapter, error) { + req := &guestresource.LCOWNetworkAdapter{ + NamespaceID: endpoint.HostComputeNamespace, + ID: id, + MacAddress: endpoint.MacAddress, + } + + for _, i := range endpoint.IpConfigurations { + ipConfig := guestresource.LCOWIPConfig{ + IPAddress: i.IpAddress, + PrefixLength: i.PrefixLength, + } + if req.IPConfigs == nil { + req.IPConfigs = make([]guestresource.LCOWIPConfig, 0) + } + req.IPConfigs = append(req.IPConfigs, ipConfig) + } + + for _, r := range endpoint.Routes { + if req.Routes == nil { + req.Routes = make([]guestresource.LCOWRoute, 0) + } + newRoute := guestresource.LCOWRoute{ + DestinationPrefix: r.DestinationPrefix, + NextHop: r.NextHop, + Metric: r.Metric, + } + req.Routes = append(req.Routes, newRoute) + } + + req.DNSSuffix = endpoint.Dns.Domain + req.DNSServerList = strings.Join(endpoint.Dns.ServerList, ",") + + for _, p := range endpoint.Policies { + if p.Type == hcn.EncapOverhead { + var settings hcn.EncapOverheadEndpointPolicySetting + if err := json.Unmarshal(p.Settings, &settings); err != nil { + return nil, fmt.Errorf("unmarshal encap overhead policy setting: %w", err) + } + req.EncapOverhead = settings.Overhead + } + } + + return req, nil +} + // addNIC adds a nic to the Utility VM. -func (uvm *UtilityVM) addNIC(ctx context.Context, id string, endpoint *hns.HNSEndpoint) error { +func (uvm *UtilityVM) addNIC(ctx context.Context, id string, endpoint *hcn.HostComputeEndpoint) error { // First a pre-add. This is a guest-only request and is only done on Windows. if uvm.operatingSystem == "windows" { preAddRequest := hcsschema.ModifySettingRequest{ @@ -586,31 +640,12 @@ func (uvm *UtilityVM) addNIC(ctx context.Context, id string, endpoint *hns.HNSEn nil), } } else { - // Verify this version of LCOW supports Network HotAdd - s := &guestresource.LCOWNetworkAdapter{ - NamespaceID: endpoint.Namespace.ID, - ID: id, - MacAddress: endpoint.MacAddress, - IPAddress: endpoint.IPAddress.String(), - PrefixLength: endpoint.PrefixLength, - GatewayAddress: endpoint.GatewayAddress, - DNSSuffix: endpoint.DNSSuffix, - DNSServerList: endpoint.DNSServerList, - EnableLowMetric: endpoint.EnableLowMetric, - EncapOverhead: endpoint.EncapOverhead, - } - if v6 := endpoint.IPv6Address.To16(); v6 != nil && !v6.IsUnspecified() { - // if the address is empty or invalid, endpoint.IPv6Address.String will be "" - // since we should be guranteed an IPv4, but not a v6, skip invalid v6 address - s.IPv6Address = v6.String() - s.IPv6PrefixLength = endpoint.IPv6PrefixLength - s.IPv6GatewayAddress = endpoint.GatewayAddressV6 - log.G(ctx).WithFields(logrus.Fields{ - "ip": s.IPAddress, - "prefixLength": s.IPv6PrefixLength, - "gateway": s.IPv6GatewayAddress, - }).Debug("adding IPv6 settings") + s, err := convertToLCOWReq(id, endpoint) + if err != nil { + return err } + + // Verify this version of LCOW supports Network HotAdd if uvm.isNetworkNamespaceSupported() { request.GuestRequest = guestrequest.ModificationRequest{ ResourceType: guestresource.ResourceTypeNetwork, @@ -627,7 +662,7 @@ func (uvm *UtilityVM) addNIC(ctx context.Context, id string, endpoint *hns.HNSEn return nil } -func (uvm *UtilityVM) removeNIC(ctx context.Context, id string, endpoint *hns.HNSEndpoint) error { +func (uvm *UtilityVM) removeNIC(ctx context.Context, id string, endpoint *hcn.HostComputeEndpoint) error { request := hcsschema.ModifySettingRequest{ RequestType: guestrequest.RequestTypeRemove, ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, id), @@ -652,7 +687,7 @@ func (uvm *UtilityVM) removeNIC(ctx context.Context, id string, endpoint *hns.HN ResourceType: guestresource.ResourceTypeNetwork, RequestType: guestrequest.RequestTypeRemove, Settings: &guestresource.LCOWNetworkAdapter{ - NamespaceID: endpoint.Namespace.ID, + NamespaceID: endpoint.HostComputeNamespace, ID: endpoint.Id, }, } diff --git a/internal/uvm/network_test.go b/internal/uvm/network_test.go index a6fa22a7ea..84f77af364 100644 --- a/internal/uvm/network_test.go +++ b/internal/uvm/network_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/Microsoft/hcsshim/internal/hns" + "github.com/Microsoft/hcsshim/hcn" ) func Test_SortEndpoints(t *testing.T) { @@ -50,9 +50,9 @@ func Test_SortEndpoints(t *testing.T) { } for i, test := range tests { t.Run(fmt.Sprint(t.Name(), i), func(st *testing.T) { - endpoints := []*hns.HNSEndpoint{} + endpoints := []*hcn.HostComputeEndpoint{} for _, n := range test.endpointNames { - e := &hns.HNSEndpoint{ + e := &hcn.HostComputeEndpoint{ Name: n, } endpoints = append(endpoints, e) diff --git a/internal/uvm/types.go b/internal/uvm/types.go index 74edfb2dff..8e31276924 100644 --- a/internal/uvm/types.go +++ b/internal/uvm/types.go @@ -11,9 +11,9 @@ import ( "github.com/Microsoft/go-winio/pkg/guid" "golang.org/x/sys/windows" + "github.com/Microsoft/hcsshim/hcn" "github.com/Microsoft/hcsshim/internal/gcs" "github.com/Microsoft/hcsshim/internal/hcs" - "github.com/Microsoft/hcsshim/internal/hns" "github.com/Microsoft/hcsshim/internal/uvm/scsi" ) @@ -25,7 +25,7 @@ import ( type nicInfo struct { ID string - Endpoint *hns.HNSEndpoint + Endpoint *hcn.HostComputeEndpoint } type namespaceInfo struct {