Skip to content

Commit 0dbf2bf

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 <[email protected]>
1 parent c65b789 commit 0dbf2bf

File tree

8 files changed

+937
-198
lines changed

8 files changed

+937
-198
lines changed

internal/guest/network/netns.go

Lines changed: 111 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,33 @@ 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+
netlinkRuleAdd = netlink.RuleAdd
28+
netlinkRouteAdd = netlink.RouteAdd
29+
)
30+
31+
const (
32+
ipv4GwDestination = "0.0.0.0/0"
33+
ipv4EmptyGw = "0.0.0.0"
34+
ipv6GwDestination = "::/0"
35+
ipv6EmptyGw = "::"
36+
37+
unreachableErr = "network is unreachable"
38+
)
39+
2340
// MoveInterfaceToNS moves the adapter with interface name `ifStr` to the network namespace
2441
// of `pid`.
2542
func MoveInterfaceToNS(ifStr string, pid int) error {
@@ -67,7 +84,7 @@ func DoInNetNS(ns netns.NsHandle, run func() error) error {
6784
//
6885
// This function MUST be used in tandem with `DoInNetNS` or some other means that ensures that the goroutine
6986
// executing this code stays on the same thread.
70-
func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.NetworkAdapter) error {
87+
func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *guestresource.LCOWNetworkAdapter) error {
7188
ctx, entry := log.S(ctx, logrus.Fields{
7289
"ifname": ifStr,
7390
"pid": nsPid,
@@ -101,29 +118,14 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
101118
}
102119

103120
// 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-
}
121+
if len(adapter.IPConfigs) != 0 {
122+
entry.Debugf("Configuring interface with NAT: %v", adapter)
112123

113124
// Bring the interface up
114125
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
126+
return fmt.Errorf("netlink.LinkSetUp(%#v) failed: %w", link, err)
122127
}
123-
if err := assignIPToLink(ctx, ifStr, nsPid, link,
124-
adapter.AllocatedIPv6Address, adapter.HostIPv6Address, adapter.HostIPv6PrefixLength,
125-
adapter.EnableLowMetric, metric,
126-
); err != nil {
128+
if err := configureLink(ctx, link, adapter); err != nil {
127129
return err
128130
}
129131
} else {
@@ -186,107 +188,104 @@ func NetNSConfig(ctx context.Context, ifStr string, nsPid int, adapter *prot.Net
186188
return nil
187189
}
188190

189-
func assignIPToLink(ctx context.Context,
190-
ifStr string,
191-
nsPid int,
191+
func configureLink(ctx context.Context,
192192
link netlink.Link,
193-
allocatedIP string,
194-
gatewayIP string,
195-
prefixLen uint8,
196-
enableLowMetric bool,
197-
metric int,
193+
adapter *guestresource.LCOWNetworkAdapter,
198194
) 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-
}
195+
for _, ipConfig := range adapter.IPConfigs {
196+
log.G(ctx).WithFields(logrus.Fields{
197+
"link": link.Attrs().Name,
198+
"IP": ipConfig.IPAddress,
199+
"prefixLen": ipConfig.PrefixLength,
200+
}).Debug("assigning IP address")
233201

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)
202+
// Set IP address
203+
ip, addr, err := net.ParseCIDR(ipConfig.IPAddress + "/" + strconv.FormatUint(uint64(ipConfig.PrefixLength), 10))
204+
if err != nil {
205+
return fmt.Errorf("parsing address %s/%d failed: %w", ipConfig.IPAddress, ipConfig.PrefixLength, err)
251206
}
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)
207+
// the IP address field in addr is masked, so replace it with the original ip address
208+
addr.IP = ip
209+
log.G(ctx).WithFields(logrus.Fields{
210+
"allocatedIP": ip,
211+
"IP": addr,
212+
}).Debugf("parsed ip address %s/%d", ipConfig.IPAddress, ipConfig.PrefixLength)
213+
ipAddr := &netlink.Addr{IPNet: addr, Label: ""}
214+
if err := netlinkAddrAdd(link, ipAddr); err != nil {
215+
return fmt.Errorf("netlink.AddrAdd(%#v, %#v) failed: %w", link, ipAddr, err)
258216
}
259217
}
260218

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),
219+
for _, r := range adapter.Routes {
220+
log.G(ctx).WithField("route", r).Debugf("adding a route to interface %s", link.Attrs().Name)
221+
222+
if (r.DestinationPrefix == ipv4GwDestination || r.DestinationPrefix == ipv6GwDestination) &&
223+
(r.NextHop == ipv4EmptyGw || r.NextHop == ipv6EmptyGw) {
224+
// this indicates no default gateway was added for this interface
225+
continue
269226
}
270-
rule := netlink.NewRule()
271-
rule.Table = 101
272-
rule.Src = srcNet
273-
rule.Priority = 5
274227

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

0 commit comments

Comments
 (0)