Skip to content

Commit cfb90c9

Browse files
author
Kathryn Baldauf
committed
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 <kabaldau@microsoft.com>
1 parent c65b789 commit cfb90c9

File tree

8 files changed

+905
-198
lines changed

8 files changed

+905
-198
lines changed

internal/guest/network/netns.go

Lines changed: 110 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,32 @@ import (
1010
"os/exec"
1111
"runtime"
1212
"strconv"
13+
"strings"
1314
"time"
1415

15-
"github.com/Microsoft/hcsshim/internal/guest/prot"
1616
"github.com/Microsoft/hcsshim/internal/log"
17+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
1718
"github.com/pkg/errors"
1819
"github.com/sirupsen/logrus"
1920
"github.com/vishvananda/netlink"
2021
"github.com/vishvananda/netns"
2122
)
2223

24+
var (
25+
// function definitions for mocking configureLink
26+
netlinkAddrAdd = netlink.AddrAdd
27+
netlinkRouteAdd = netlink.RouteAdd
28+
)
29+
30+
const (
31+
ipv4GwDestination = "0.0.0.0/0"
32+
ipv4EmptyGw = "0.0.0.0"
33+
ipv6GwDestination = "::/0"
34+
ipv6EmptyGw = "::"
35+
36+
unreachableErr = "network is unreachable"
37+
)
38+
2339
// MoveInterfaceToNS moves the adapter with interface name `ifStr` to the network namespace
2440
// of `pid`.
2541
func MoveInterfaceToNS(ifStr string, pid int) error {
@@ -67,7 +83,7 @@ func DoInNetNS(ns netns.NsHandle, run func() error) error {
6783
//
6884
// This function MUST be used in tandem with `DoInNetNS` or some other means that ensures that the goroutine
6985
// executing this code stays on the same thread.
70-
func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.NetworkAdapter) error {
86+
func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *guestresource.LCOWNetworkAdapter) error {
7187
ctx, entry := log.S(ctx, logrus.Fields{
7288
"ifname": ifStr,
7389
"pid": nsPid,
@@ -101,29 +117,14 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
101117
}
102118

103119
// Configure the interface
104-
if adapter.NatEnabled {
105-
entry.Tracef("Configuring interface with NAT: %s/%d gw=%s",
106-
adapter.AllocatedIPAddress,
107-
adapter.HostIPPrefixLength, adapter.HostIPAddress)
108-
metric := 1
109-
if adapter.EnableLowMetric {
110-
metric = 500
111-
}
120+
if len(adapter.IPConfigs) != 0 {
121+
entry.Debugf("Configuring interface with NAT: %v", adapter)
112122

113123
// Bring the interface up
114124
if err := netlink.LinkSetUp(link); err != nil {
115-
return errors.Wrapf(err, "netlink.LinkSetUp(%#v) failed", link)
116-
}
117-
if err := assignIPToLink(ctx, ifStr, nsPid, link,
118-
adapter.AllocatedIPAddress, adapter.HostIPAddress, adapter.HostIPPrefixLength,
119-
adapter.EnableLowMetric, metric,
120-
); err != nil {
121-
return err
125+
return fmt.Errorf("netlink.LinkSetUp(%#v) failed: %w", link, err)
122126
}
123-
if err := assignIPToLink(ctx, ifStr, nsPid, link,
124-
adapter.AllocatedIPv6Address, adapter.HostIPv6Address, adapter.HostIPv6PrefixLength,
125-
adapter.EnableLowMetric, metric,
126-
); err != nil {
127+
if err := configureLink(ctx, link, adapter); err != nil {
127128
return err
128129
}
129130
} else {
@@ -186,107 +187,104 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
186187
return nil
187188
}
188189

189-
func assignIPToLink(ctx context.Context,
190-
ifStr string,
191-
nsPid int,
190+
func configureLink(ctx context.Context,
192191
link netlink.Link,
193-
allocatedIP string,
194-
gatewayIP string,
195-
prefixLen uint8,
196-
enableLowMetric bool,
197-
metric int,
192+
adapter *guestresource.LCOWNetworkAdapter,
198193
) error {
199-
entry := log.G(ctx)
200-
entry.WithFields(logrus.Fields{
201-
"link": link.Attrs().Name,
202-
"IP": allocatedIP,
203-
"prefixLen": prefixLen,
204-
"gateway": gatewayIP,
205-
"metric": metric,
206-
}).Trace("assigning IP address")
207-
if allocatedIP == "" {
208-
return nil
209-
}
210-
// Set IP address
211-
ip, addr, err := net.ParseCIDR(allocatedIP + "/" + strconv.FormatUint(uint64(prefixLen), 10))
212-
if err != nil {
213-
return errors.Wrapf(err, "parsing address %s/%d failed", allocatedIP, prefixLen)
214-
}
215-
// the IP address field in addr is masked, so replace it with the original ip address
216-
addr.IP = ip
217-
entry.WithFields(logrus.Fields{
218-
"allocatedIP": ip,
219-
"IP": addr,
220-
}).Debugf("parsed ip address %s/%d", allocatedIP, prefixLen)
221-
ipAddr := &netlink.Addr{IPNet: addr, Label: ""}
222-
if err := netlink.AddrAdd(link, ipAddr); err != nil {
223-
return errors.Wrapf(err, "netlink.AddrAdd(%#v, %#v) failed", link, ipAddr)
224-
}
225-
if gatewayIP == "" {
226-
return nil
227-
}
228-
// Set gateway
229-
gw := net.ParseIP(gatewayIP)
230-
if gw == nil {
231-
return errors.Wrapf(err, "parsing gateway address %s failed", gatewayIP)
232-
}
194+
for _, ipConfig := range adapter.IPConfigs {
195+
log.G(ctx).WithFields(logrus.Fields{
196+
"link": link.Attrs().Name,
197+
"IP": ipConfig.IPAddress,
198+
"prefixLen": ipConfig.PrefixLength,
199+
}).Debug("assigning IP address")
233200

234-
if !addr.Contains(gw) {
235-
// In the case that a gw is not part of the subnet we are setting gw for,
236-
// a new addr containing this gw address need to be added into the link to avoid getting
237-
// unreachable error when adding this out-of-subnet gw route
238-
entry.Debugf("gw is outside of the subnet: Configure %s in %d with: %s/%d gw=%s\n",
239-
ifStr, nsPid, allocatedIP, prefixLen, gatewayIP)
240-
241-
// net library's ParseIP call always returns an array of length 16, so we
242-
// need to first check if the address is IPv4 or IPv6 before calculating
243-
// the mask length. See https://pkg.go.dev/net#ParseIP.
244-
ml := 8
245-
if gw.To4() != nil {
246-
ml *= net.IPv4len
247-
} else if gw.To16() != nil {
248-
ml *= net.IPv6len
249-
} else {
250-
return fmt.Errorf("gw IP is neither IPv4 nor IPv6: %v", gw)
201+
// Set IP address
202+
ip, addr, err := net.ParseCIDR(ipConfig.IPAddress + "/" + strconv.FormatUint(uint64(ipConfig.PrefixLength), 10))
203+
if err != nil {
204+
return fmt.Errorf("parsing address %s/%d failed: %w", ipConfig.IPAddress, ipConfig.PrefixLength, err)
251205
}
252-
addr2 := &net.IPNet{
253-
IP: gw,
254-
Mask: net.CIDRMask(ml, ml)}
255-
ipAddr2 := &netlink.Addr{IPNet: addr2, Label: ""}
256-
if err := netlink.AddrAdd(link, ipAddr2); err != nil {
257-
return errors.Wrapf(err, "netlink.AddrAdd(%#v, %#v) failed", link, ipAddr2)
206+
// the IP address field in addr is masked, so replace it with the original ip address
207+
addr.IP = ip
208+
log.G(ctx).WithFields(logrus.Fields{
209+
"allocatedIP": ip,
210+
"IP": addr,
211+
}).Debugf("parsed ip address %s/%d", ipConfig.IPAddress, ipConfig.PrefixLength)
212+
ipAddr := &netlink.Addr{IPNet: addr, Label: ""}
213+
if err := netlinkAddrAdd(link, ipAddr); err != nil {
214+
return fmt.Errorf("netlink.AddrAdd(%#v, %#v) failed: %w", link, ipAddr, err)
258215
}
259216
}
260217

261-
var table int
262-
if enableLowMetric {
263-
// add a route rule for the new interface so packets coming on this interface
264-
// always go out the same interface
265-
_, ml := addr.Mask.Size()
266-
srcNet := &net.IPNet{
267-
IP: net.ParseIP(allocatedIP),
268-
Mask: net.CIDRMask(ml, ml),
218+
for _, r := range adapter.Routes {
219+
log.G(ctx).WithField("route", r).Debugf("adding a route to interface %s", link.Attrs().Name)
220+
221+
if (r.DestinationPrefix == ipv4GwDestination || r.DestinationPrefix == ipv6GwDestination) &&
222+
(r.NextHop == ipv4EmptyGw || r.NextHop == ipv6EmptyGw) {
223+
// this indicates no default gateway was added for this interface
224+
continue
269225
}
270-
rule := netlink.NewRule()
271-
rule.Table = 101
272-
rule.Src = srcNet
273-
rule.Priority = 5
274226

275-
if err := netlink.RuleAdd(rule); err != nil {
276-
return errors.Wrapf(err, "netlink.RuleAdd(%#v) failed", rule)
227+
// dst will be nil when setting default gateways
228+
var dst *net.IPNet
229+
if !(r.DestinationPrefix == ipv4GwDestination || r.DestinationPrefix == ipv6GwDestination) {
230+
dstIP, dstAddr, err := net.ParseCIDR(r.DestinationPrefix)
231+
if err != nil {
232+
return fmt.Errorf("parsing route dst address %s failed: %w", r.DestinationPrefix, err)
233+
}
234+
dstAddr.IP = dstIP
235+
dst = dstAddr
236+
}
237+
238+
// gw can be nil when setting something like
239+
// ip route add 10.0.0.0/16 dev eth0
240+
gw := net.ParseIP(r.NextHop)
241+
if gw == nil && dst == nil {
242+
return fmt.Errorf("gw and destination cannot both be nil")
243+
}
244+
245+
route := netlink.Route{
246+
Scope: netlink.SCOPE_UNIVERSE,
247+
LinkIndex: link.Attrs().Index,
248+
Gw: gw,
249+
Dst: dst,
250+
Priority: int(r.Metric),
251+
}
252+
if err := netlinkRouteAdd(&route); err != nil {
253+
// unfortunately, netlink library doesn't have great error handling,
254+
// so we have to rely on the string error here
255+
if strings.Contains(err.Error(), unreachableErr) && gw != nil {
256+
// In the case that a gw is not part of the subnet we are setting gw for,
257+
// a new addr containing this gw address needs to be added into the link to avoid getting
258+
// unreachable error when adding this out-of-subnet gw route
259+
log.G(ctx).Infof("gw is outside of the subnet: %v", gw)
260+
261+
// net library's ParseIP call always returns an array of length 16, so we
262+
// need to first check if the address is IPv4 or IPv6 before calculating
263+
// the mask length. See https://pkg.go.dev/net#ParseIP.
264+
ml := 8
265+
if gw.To4() != nil {
266+
ml *= net.IPv4len
267+
} else if gw.To16() != nil {
268+
ml *= net.IPv6len
269+
} else {
270+
return fmt.Errorf("gw IP is neither IPv4 nor IPv6: %v", gw)
271+
}
272+
addr2 := &net.IPNet{
273+
IP: gw,
274+
Mask: net.CIDRMask(ml, ml)}
275+
ipAddr2 := &netlink.Addr{IPNet: addr2, Label: ""}
276+
if err := netlinkAddrAdd(link, ipAddr2); err != nil {
277+
return fmt.Errorf("netlink.AddrAdd(%#v, %#v) failed: %w", link, ipAddr2, err)
278+
}
279+
280+
// try adding the route again
281+
if err := netlinkRouteAdd(&route); err != nil {
282+
return fmt.Errorf("netlink.RouteAdd(%#v) failed: %w", route, err)
283+
}
284+
} else {
285+
return fmt.Errorf("netlink.RouteAdd(%#v) failed: %w", route, err)
286+
}
277287
}
278-
table = rule.Table
279-
}
280-
// add the default route in that interface specific table
281-
route := netlink.Route{
282-
Scope: netlink.SCOPE_UNIVERSE,
283-
LinkIndex: link.Attrs().Index,
284-
Gw: gw,
285-
Table: table,
286-
Priority: metric,
287-
}
288-
if err := netlink.RouteAdd(&route); err != nil {
289-
return errors.Wrapf(err, "netlink.RouteAdd(%#v) failed", route)
290288
}
291289
return nil
292290
}

0 commit comments

Comments
 (0)