Skip to content

Commit 3aa7b20

Browse files
add ipv6 default route to dualstack windows nodes (#2508)
* add ipv6 default route to dualstack windows nodes * fix comments * add UT for adding ipv6 default route * add UT for adding ipv6 default route * fix linter issue * fix comments * fix comments * fix an issue * fix comments and UT * fix the UT test * fix the subnet family afINET * fix the UT test * fix an UT test * fix Ramiro's comments * fix latest comments * fix a linter issue * add context to failure of UT test * Update network/network_windows.go Co-authored-by: tamilmani1989 <[email protected]> Signed-off-by: Paul Yu <[email protected]> * add an UT to cover failure case * change the UT name * add UT to mock powershell command * fix UT * fix linter issue --------- Signed-off-by: Paul Yu <[email protected]> Co-authored-by: tamilmani1989 <[email protected]>
1 parent d5f687b commit 3aa7b20

File tree

2 files changed

+218
-1
lines changed

2 files changed

+218
-1
lines changed

network/network_windows.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ package network
55

66
import (
77
"encoding/json"
8-
"errors"
98
"fmt"
109
"strconv"
1110
"strings"
1211
"time"
1312

1413
"github.com/Azure/azure-container-networking/network/hnswrapper"
1514
"github.com/Azure/azure-container-networking/network/policy"
15+
"github.com/Azure/azure-container-networking/platform"
1616
"github.com/Microsoft/hcsshim"
1717
"github.com/Microsoft/hcsshim/hcn"
1818
"github.com/google/uuid"
19+
"github.com/pkg/errors"
1920
"go.uber.org/zap"
2021
)
2122

@@ -38,6 +39,10 @@ const (
3839
routeCmd = "netsh interface ipv6 %s route \"%s\" \"%s\" \"%s\" store=persistent"
3940
// add/delete ipv4 and ipv6 route rules to/from windows node
4041
netRouteCmd = "netsh interface %s %s route \"%s\" \"%s\" \"%s\""
42+
// Default IPv6 Route
43+
defaultIPv6Route = "::/0"
44+
// Default IPv6 nextHop
45+
defaultIPv6NextHop = "fe80::1234:5678:9abc"
4146
)
4247

4348
// Windows implementation of route.
@@ -318,6 +323,34 @@ func (nm *networkManager) configureHcnNetwork(nwInfo *NetworkInfo, extIf *extern
318323
return hcnNetwork, nil
319324
}
320325

326+
func (nm *networkManager) addIPv6DefaultRoute() error {
327+
// add ipv6 default route if it does not exist in dualstack overlay windows node from persistent store
328+
// persistent store setting is only read during the adapter restarts or reboots to re-populate the active store
329+
330+
// get interface index to add ipv6 default route and only consider there is one vEthernet interface for now
331+
getIpv6IfIndexCmd := `((Get-NetIPInterface | where InterfaceAlias -Like "vEthernet*").IfIndex)[0]`
332+
ifIndex, err := nm.plClient.ExecutePowershellCommand(getIpv6IfIndexCmd)
333+
if err != nil {
334+
return errors.Wrap(err, "error while executing powershell command to get ipv6 Hyper-V interface")
335+
}
336+
337+
getIPv6RoutePersistentCmd := fmt.Sprintf("Get-NetRoute -DestinationPrefix %s -PolicyStore Persistentstore", defaultIPv6Route)
338+
if out, err := nm.plClient.ExecutePowershellCommand(getIPv6RoutePersistentCmd); err != nil {
339+
logger.Info("ipv6 default route is not found from persistentstore, adding default ipv6 route to the windows node", zap.String("out", out), zap.Error(err))
340+
// run powershell cmd to add ipv6 default route
341+
// if there is an ipv6 default route in active store but not persistent store; to add ipv6 default route to persistent store
342+
// need to remove ipv6 default route from active store and then use this command to add default route entry to both active and persistent store
343+
addCmd := fmt.Sprintf("Remove-NetRoute -DestinationPrefix %s -InterfaceIndex %s -NextHop %s -confirm:$false;New-NetRoute -DestinationPrefix %s -InterfaceIndex %s -NextHop %s -confirm:$false",
344+
defaultIPv6Route, ifIndex, defaultIPv6NextHop, defaultIPv6Route, ifIndex, defaultIPv6NextHop)
345+
346+
if _, err := nm.plClient.ExecutePowershellCommand(addCmd); err != nil {
347+
return errors.Wrap(err, "Failed to add ipv6 default route to both persistent and active store")
348+
}
349+
}
350+
351+
return nil
352+
}
353+
321354
// newNetworkImplHnsV2 creates a new container network for HNSv2.
322355
func (nm *networkManager) newNetworkImplHnsV2(nwInfo *NetworkInfo, extIf *externalInterface) (*network, error) {
323356
hcnNetwork, err := nm.configureHcnNetwork(nwInfo, extIf)
@@ -347,6 +380,17 @@ func (nm *networkManager) newNetworkImplHnsV2(nwInfo *NetworkInfo, extIf *extern
347380
logger.Info("Network with name already exists", zap.String("name", hcnNetwork.Name))
348381
}
349382

383+
// check if ipv6 default gateway route is missing before windows endpoint creation
384+
for i := range nwInfo.Subnets {
385+
if nwInfo.Subnets[i].Family == platform.AfINET6 {
386+
if err = nm.addIPv6DefaultRoute(); err != nil {
387+
// should not block network creation but remind user that it's failed to add ipv6 default route to windows node
388+
logger.Error("failed to add missing ipv6 default route to windows node active/persistent store", zap.Error(err))
389+
}
390+
break
391+
}
392+
}
393+
350394
var vlanid int
351395
opt, _ := nwInfo.Options[genericData].(map[string]interface{})
352396
if opt != nil && opt[VlanIDKey] != nil {

network/network_windows_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@
77
package network
88

99
import (
10+
"errors"
1011
"fmt"
12+
"net"
13+
"strings"
1114
"testing"
1215
"time"
1316

1417
"github.com/Azure/azure-container-networking/network/hnswrapper"
18+
"github.com/Azure/azure-container-networking/platform"
1519
"github.com/Microsoft/hcsshim/hcn"
1620
)
1721

22+
var (
23+
errTestFailure = errors.New("test failure")
24+
failedCaseReturn = "false"
25+
succededCaseReturn = "true"
26+
)
27+
1828
func TestNewAndDeleteNetworkImplHnsV2(t *testing.T) {
1929
nm := &networkManager{
2030
ExternalInterfaces: map[string]*externalInterface{},
@@ -227,3 +237,166 @@ func TestDeleteNetworkImplHnsV1WithTimeout(t *testing.T) {
227237
t.Fatal("Failed to timeout HNS calls for deleting network")
228238
}
229239
}
240+
241+
func TestAddIPv6DefaultRoute(t *testing.T) {
242+
_, ipnetv4, _ := net.ParseCIDR("10.240.0.0/12")
243+
_, ipnetv6, _ := net.ParseCIDR("fc00::/64")
244+
245+
nm := &networkManager{
246+
ExternalInterfaces: map[string]*externalInterface{},
247+
plClient: platform.NewMockExecClient(false),
248+
}
249+
250+
networkSubnetInfo := []SubnetInfo{
251+
{
252+
Family: platform.AfINET,
253+
Gateway: net.ParseIP("10.240.0.1"),
254+
Prefix: *ipnetv4,
255+
},
256+
{
257+
Family: platform.AfINET6,
258+
Gateway: net.ParseIP("fc00::1"),
259+
Prefix: *ipnetv6,
260+
},
261+
}
262+
263+
nwInfo := &NetworkInfo{
264+
Id: "d3f97a83-ba4c-45d5-ba88-dc56757ece28",
265+
MasterIfName: "eth0",
266+
Mode: "bridge",
267+
Subnets: networkSubnetInfo,
268+
}
269+
270+
extInterface := &externalInterface{
271+
Name: "eth0",
272+
Subnets: []string{"subnet1", "subnet2"},
273+
}
274+
275+
Hnsv2 = hnswrapper.NewHnsv2wrapperFake()
276+
277+
// check if network can be successfully created
278+
_, err := nm.newNetworkImplHnsV2(nwInfo, extInterface)
279+
if err != nil {
280+
t.Fatalf("Failed to create network due to error:%+v", err)
281+
}
282+
}
283+
284+
func TestFailToAddIPv6DefaultRoute(t *testing.T) {
285+
_, ipnetv4, _ := net.ParseCIDR("10.240.0.0/12")
286+
_, ipnetv6, _ := net.ParseCIDR("fc00::/64")
287+
288+
nm := &networkManager{
289+
ExternalInterfaces: map[string]*externalInterface{},
290+
plClient: platform.NewMockExecClient(true), // return mock exec error
291+
}
292+
293+
networkSubnetInfo := []SubnetInfo{
294+
{
295+
Family: platform.AfINET,
296+
Gateway: net.ParseIP("10.240.0.1"),
297+
Prefix: *ipnetv4,
298+
},
299+
{
300+
Family: platform.AfINET6,
301+
Gateway: net.ParseIP("fc00::1"),
302+
Prefix: *ipnetv6,
303+
},
304+
}
305+
306+
nwInfo := &NetworkInfo{
307+
Id: "d3f97a83-ba4c-45d5-ba88-dc56757ece28",
308+
MasterIfName: "eth0",
309+
Mode: "bridge",
310+
Subnets: networkSubnetInfo,
311+
}
312+
313+
extInterface := &externalInterface{
314+
Name: "eth0",
315+
Subnets: []string{"subnet1", "subnet2"},
316+
}
317+
318+
Hnsv2 = hnswrapper.NewHnsv2wrapperFake()
319+
320+
// check if network is failed to create
321+
_, err := nm.newNetworkImplHnsV2(nwInfo, extInterface)
322+
if err == nil {
323+
t.Fatal("Network should not be created")
324+
}
325+
}
326+
327+
func TestAddIPv6DefaultRouteHappyPath(t *testing.T) {
328+
mockExecClient := platform.NewMockExecClient(false)
329+
330+
nm := &networkManager{
331+
plClient: mockExecClient,
332+
}
333+
334+
// happy path
335+
mockExecClient.SetPowershellCommandResponder(func(cmd string) (string, error) {
336+
if strings.Contains(cmd, "Get-NetIPInterface") || strings.Contains(cmd, "Remove-NetRoute") {
337+
return succededCaseReturn, nil
338+
}
339+
340+
// fail secondary command execution and successfully execute remove-netRoute command
341+
if strings.Contains(cmd, "Get-NetRoute") {
342+
return failedCaseReturn, errTestFailure
343+
}
344+
345+
return "", nil
346+
})
347+
348+
err := nm.addIPv6DefaultRoute()
349+
if err != nil {
350+
t.Fatal("Failed to test happy path")
351+
}
352+
}
353+
354+
func TestAddIPv6DefaultRouteUnhappyPathGetNetInterface(t *testing.T) {
355+
mockExecClient := platform.NewMockExecClient(false)
356+
357+
nm := &networkManager{
358+
plClient: mockExecClient,
359+
}
360+
361+
// failed to execute Get-NetIPInterface command to find interface index
362+
mockExecClient.SetPowershellCommandResponder(func(cmd string) (string, error) {
363+
if strings.Contains(cmd, "Get-NetIPInterface") {
364+
return failedCaseReturn, errTestFailure
365+
}
366+
return "", nil
367+
})
368+
369+
err := nm.addIPv6DefaultRoute()
370+
if err == nil {
371+
t.Fatal("Failed to test unhappy path with failing to execute get-netIPInterface command")
372+
}
373+
}
374+
375+
func TestAddIPv6DefaultRouteUnhappyPathAddRoute(t *testing.T) {
376+
mockExecClient := platform.NewMockExecClient(false)
377+
378+
nm := &networkManager{
379+
plClient: mockExecClient,
380+
}
381+
382+
mockExecClient.SetPowershellCommandResponder(func(cmd string) (string, error) {
383+
if strings.Contains(cmd, "Get-NetIPInterface") {
384+
return succededCaseReturn, nil
385+
}
386+
387+
// fail secondary command execution and failed to execute remove-netRoute command
388+
if strings.Contains(cmd, "Get-NetRoute") {
389+
return failedCaseReturn, errTestFailure
390+
}
391+
392+
if strings.Contains(cmd, "Remove-NetRoute") {
393+
return failedCaseReturn, errTestFailure
394+
}
395+
return "", nil
396+
})
397+
398+
err := nm.addIPv6DefaultRoute()
399+
if err == nil {
400+
t.Fatal("Failed to test unhappy path with failing to add default route command")
401+
}
402+
}

0 commit comments

Comments
 (0)