From f9369ee34bf4077f811f3e29cab2bcadc5ebf373 Mon Sep 17 00:00:00 2001 From: paulyu Date: Thu, 20 Nov 2025 16:19:29 +0000 Subject: [PATCH 01/24] fix dualnic windows duplicated issue --- cni/network/multitenancy.go | 40 +++++++++++++++++----- cni/network/multitenancy_test.go | 59 +++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index e617afb7d6..171a55eac7 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -159,6 +159,26 @@ func (m *Multitenancy) DetermineSnatFeatureOnHost(snatFile, nmAgentSupportedApis return snatConfig.EnableSnatForDns, snatConfig.EnableSnatOnHost, nil } +// addDefaultRouteToGateway appends a default route +// to both epInfo and result. Returns error if gwStr is not a valid IP. +func (m *Multitenancy) addDefaultRoute(gwStr string, epInfo *network.EndpointInfo, result *network.InterfaceInfo) error { + gw := net.ParseIP(gwStr) + if gw == nil { + return fmt.Errorf("invalid gateway IP: %s", gwStr) //nolint + } + + var dst net.IPNet + if gw.To4() != nil { + _, defaultIPNet, _ := net.ParseCIDR("0.0.0.0/0") + dst = net.IPNet{IP: net.IPv4zero, Mask: defaultIPNet.Mask} + } + + ri := network.RouteInfo{Dst: dst, Gw: gw} + epInfo.Routes = append(epInfo.Routes, ri) + result.Routes = append(result.Routes, ri) + return nil +} + func (m *Multitenancy) SetupRoutingForMultitenancy( nwCfg *cni.NetworkConfig, cnsNetworkConfig *cns.GetNetworkContainerResponse, @@ -172,15 +192,17 @@ func (m *Multitenancy) SetupRoutingForMultitenancy( logger.Info("add default route for multitenancy.snat on host enabled") addDefaultRoute(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) } else { - _, defaultIPNet, _ := net.ParseCIDR("0.0.0.0/0") - dstIP := net.IPNet{IP: net.ParseIP("0.0.0.0"), Mask: defaultIPNet.Mask} - gwIP := net.ParseIP(cnsNetworkConfig.IPConfiguration.GatewayIPAddress) - epInfo.Routes = append(epInfo.Routes, network.RouteInfo{Dst: dstIP, Gw: gwIP}) - result.Routes = append(result.Routes, network.RouteInfo{Dst: dstIP, Gw: gwIP}) - - if epInfo.EnableSnatForDns { - logger.Info("add SNAT for DNS enabled") - addSnatForDNS(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) + // only set default route when skipDefaultRoutes is false to avoid duplicated default routes given to HNS + if !epInfo.SkipDefaultRoutes { + if err := m.addDefaultRoute( + cnsNetworkConfig.IPConfiguration.GatewayIPAddress, + epInfo, result, + ); err != nil { + logger.Error("failed adding default route", + zap.String("gateway", cnsNetworkConfig.IPConfiguration.GatewayIPAddress), + zap.Error(err), + ) + } } } diff --git a/cni/network/multitenancy_test.go b/cni/network/multitenancy_test.go index b6dbb9e19a..5a2a24479d 100644 --- a/cni/network/multitenancy_test.go +++ b/cni/network/multitenancy_test.go @@ -189,6 +189,7 @@ func getIPNetWithString(ipaddrwithcidr string) *net.IPNet { func TestSetupRoutingForMultitenancy(t *testing.T) { require := require.New(t) //nolint:gocritic + type args struct { nwCfg *cni.NetworkConfig cnsNetworkConfig *cns.GetNetworkContainerResponse @@ -204,7 +205,7 @@ func TestSetupRoutingForMultitenancy(t *testing.T) { expected args }{ { - name: "test happy path", + name: "adds default v4 route when SNAT disabled and SkipDefaultRoutes=false", args: args{ nwCfg: &cni.NetworkConfig{ MultiTenancy: true, @@ -212,14 +213,13 @@ func TestSetupRoutingForMultitenancy(t *testing.T) { }, cnsNetworkConfig: &cns.GetNetworkContainerResponse{ IPConfiguration: cns.IPConfiguration{ - IPSubnet: cns.IPSubnet{}, - DNSServers: nil, GatewayIPAddress: "10.0.0.1", }, }, - epInfo: &network.EndpointInfo{}, + epInfo: &network.EndpointInfo{}, // SkipDefaultRoutes defaults to false result: &network.InterfaceInfo{}, }, + multitenancyClient: &Multitenancy{}, expected: args{ nwCfg: &cni.NetworkConfig{ MultiTenancy: true, @@ -227,8 +227,6 @@ func TestSetupRoutingForMultitenancy(t *testing.T) { }, cnsNetworkConfig: &cns.GetNetworkContainerResponse{ IPConfiguration: cns.IPConfiguration{ - IPSubnet: cns.IPSubnet{}, - DNSServers: nil, GatewayIPAddress: "10.0.0.1", }, }, @@ -250,11 +248,56 @@ func TestSetupRoutingForMultitenancy(t *testing.T) { }, }, }, + { + name: "does not add default route when SkipDefaultRoutes=true", + args: args{ + nwCfg: &cni.NetworkConfig{ + MultiTenancy: true, + EnableSnatOnHost: false, + }, + cnsNetworkConfig: &cns.GetNetworkContainerResponse{ + IPConfiguration: cns.IPConfiguration{ + GatewayIPAddress: "10.0.0.1", + }, + }, + epInfo: &network.EndpointInfo{ + SkipDefaultRoutes: true, + }, + result: &network.InterfaceInfo{}, + }, + multitenancyClient: &Multitenancy{}, + expected: args{ + nwCfg: &cni.NetworkConfig{ + MultiTenancy: true, + EnableSnatOnHost: false, + }, + cnsNetworkConfig: &cns.GetNetworkContainerResponse{ + IPConfiguration: cns.IPConfiguration{ + GatewayIPAddress: "10.0.0.1", + }, + }, + epInfo: &network.EndpointInfo{ + SkipDefaultRoutes: true, + Routes: nil, // unchanged + }, + result: &network.InterfaceInfo{ + Routes: nil, // unchanged + }, + }, + }, } + for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - tt.multitenancyClient.SetupRoutingForMultitenancy(tt.args.nwCfg, tt.args.cnsNetworkConfig, tt.args.azIpamResult, tt.args.epInfo, tt.args.result) + tt.multitenancyClient.SetupRoutingForMultitenancy( + tt.args.nwCfg, + tt.args.cnsNetworkConfig, + tt.args.azIpamResult, + tt.args.epInfo, + tt.args.result, + ) + require.Exactly(tt.expected.nwCfg, tt.args.nwCfg) require.Exactly(tt.expected.cnsNetworkConfig, tt.args.cnsNetworkConfig) require.Exactly(tt.expected.azIpamResult, tt.args.azIpamResult) @@ -861,4 +904,4 @@ func TestGetMultiTenancyCNIResultNotFound(t *testing.T) { } }) } -} +} \ No newline at end of file From 9277b6226fa015dc81e6d89e6ef39c44b8ec1de1 Mon Sep 17 00:00:00 2001 From: paulyu Date: Thu, 20 Nov 2025 16:21:04 +0000 Subject: [PATCH 02/24] fix linter issue --- cni/network/multitenancy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cni/network/multitenancy_test.go b/cni/network/multitenancy_test.go index 5a2a24479d..6b23d84e56 100644 --- a/cni/network/multitenancy_test.go +++ b/cni/network/multitenancy_test.go @@ -904,4 +904,4 @@ func TestGetMultiTenancyCNIResultNotFound(t *testing.T) { } }) } -} \ No newline at end of file +} From 85345029308d940090e1d932e35db73dd65f1598 Mon Sep 17 00:00:00 2001 From: Paul Yu <129891899+paulyufan2@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:37:51 -0500 Subject: [PATCH 03/24] Update cni/network/multitenancy.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Paul Yu <129891899+paulyufan2@users.noreply.github.com> --- cni/network/multitenancy.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index 171a55eac7..dd45abb104 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -204,6 +204,14 @@ func (m *Multitenancy) SetupRoutingForMultitenancy( ) } } + // Restore DNS SNAT routing if enabled + if epInfo.EnableSnatForDns { + if err := m.addSnatForDNS(epInfo, result); err != nil { + logger.Error("failed adding SNAT for DNS", + zap.Error(err), + ) + } + } } setupInfraVnetRoutingForMultitenancy(nwCfg, azIpamResult, epInfo) From 0414d922a2421d8f7637cfc5c0627041dbe9ca5e Mon Sep 17 00:00:00 2001 From: Paul Yu <129891899+paulyufan2@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:42:34 -0500 Subject: [PATCH 04/24] Update cni/network/multitenancy.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Paul Yu <129891899+paulyufan2@users.noreply.github.com> --- cni/network/multitenancy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index dd45abb104..117e704b06 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -159,7 +159,7 @@ func (m *Multitenancy) DetermineSnatFeatureOnHost(snatFile, nmAgentSupportedApis return snatConfig.EnableSnatForDns, snatConfig.EnableSnatOnHost, nil } -// addDefaultRouteToGateway appends a default route +// addDefaultRoute appends a default route // to both epInfo and result. Returns error if gwStr is not a valid IP. func (m *Multitenancy) addDefaultRoute(gwStr string, epInfo *network.EndpointInfo, result *network.InterfaceInfo) error { gw := net.ParseIP(gwStr) From 3c8ab62425b5f6bc82d4b11f1af6f812bffb2822 Mon Sep 17 00:00:00 2001 From: paulyu Date: Mon, 24 Nov 2025 17:48:55 +0000 Subject: [PATCH 05/24] get original codes back --- cni/network/multitenancy.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index 117e704b06..44a14b9189 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -204,13 +204,9 @@ func (m *Multitenancy) SetupRoutingForMultitenancy( ) } } - // Restore DNS SNAT routing if enabled if epInfo.EnableSnatForDns { - if err := m.addSnatForDNS(epInfo, result); err != nil { - logger.Error("failed adding SNAT for DNS", - zap.Error(err), - ) - } + logger.Info("add SNAT for DNS enabled") + addSnatForDNS(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) } } From b31c5579bfbba310036774e7b559dc4393d1f089 Mon Sep 17 00:00:00 2001 From: paulyu Date: Mon, 24 Nov 2025 18:34:22 +0000 Subject: [PATCH 06/24] fix a bug --- cni/network/multitenancy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index 44a14b9189..7e84129d34 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -190,7 +190,7 @@ func (m *Multitenancy) SetupRoutingForMultitenancy( // if snat enabled, add 169.254.128.1 as default gateway if nwCfg.EnableSnatOnHost { logger.Info("add default route for multitenancy.snat on host enabled") - addDefaultRoute(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) + m.addDefaultRoute(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) } else { // only set default route when skipDefaultRoutes is false to avoid duplicated default routes given to HNS if !epInfo.SkipDefaultRoutes { From 9188d2c7d650e095bbbfba8a63c8662fd9a99d24 Mon Sep 17 00:00:00 2001 From: paulyu Date: Mon, 24 Nov 2025 18:36:15 +0000 Subject: [PATCH 07/24] remove preivious change --- cni/network/multitenancy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cni/network/multitenancy.go b/cni/network/multitenancy.go index 7e84129d34..44a14b9189 100644 --- a/cni/network/multitenancy.go +++ b/cni/network/multitenancy.go @@ -190,7 +190,7 @@ func (m *Multitenancy) SetupRoutingForMultitenancy( // if snat enabled, add 169.254.128.1 as default gateway if nwCfg.EnableSnatOnHost { logger.Info("add default route for multitenancy.snat on host enabled") - m.addDefaultRoute(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) + addDefaultRoute(cnsNetworkConfig.LocalIPConfiguration.GatewayIPAddress, epInfo, result) } else { // only set default route when skipDefaultRoutes is false to avoid duplicated default routes given to HNS if !epInfo.SkipDefaultRoutes { From 3cfabd1744751edd65db3eee71be6b1d1c34c7d7 Mon Sep 17 00:00:00 2001 From: paulyu Date: Tue, 6 Jan 2026 21:42:44 +0000 Subject: [PATCH 08/24] set a dummy route when SkipDefaultRoutes is set to true --- cni/network/network_windows.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index 255fad470f..87da3e0b14 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -132,7 +132,25 @@ func (plugin *NetPlugin) getNetworkName(netNs string, interfaceInfo *network.Int func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, - _ *network.EndpointInfo) { + epInfo *network.EndpointInfo) { + // as a workaround, HNS will not set this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) + // on interface when SkipDefaultRoutes is set to true. + // the only usage for this dummy default is to bypass HNS setting default route + // TODO: Remove this once HNS fix is ready + if !epInfo.SkipDefaultRoutes { + return + } + + _, defaultIPNet, err := net.ParseCIDR("0.0.0.0/0") + if err != nil { + return + } + + dummyRoute := network.RouteInfo{ + Dst: *defaultIPNet, + Gw: net.IPv4zero, + } + epInfo.Routes = append(epInfo.Routes, dummyRoute) } func getNetworkDNSSettings(nwCfg *cni.NetworkConfig, _ network.DNSInfo) (network.DNSInfo, error) { From c09a81bcca8b4686cf881ca032f36c5e93b1f858 Mon Sep 17 00:00:00 2001 From: paulyu Date: Tue, 6 Jan 2026 21:51:43 +0000 Subject: [PATCH 09/24] fix linter issue --- cni/network/network_windows.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index 87da3e0b14..93429ab3b2 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -133,24 +133,24 @@ func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, epInfo *network.EndpointInfo) { - // as a workaround, HNS will not set this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) - // on interface when SkipDefaultRoutes is set to true. - // the only usage for this dummy default is to bypass HNS setting default route - // TODO: Remove this once HNS fix is ready - if !epInfo.SkipDefaultRoutes { - return - } - - _, defaultIPNet, err := net.ParseCIDR("0.0.0.0/0") - if err != nil { - return - } + // as a workaround, HNS will not set this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) + // on interface when SkipDefaultRoutes is set to true. + // the only usage for this dummy default is to bypass HNS setting default route + // TODO: Remove this once HNS fix is ready + if !epInfo.SkipDefaultRoutes { + return + } - dummyRoute := network.RouteInfo{ - Dst: *defaultIPNet, - Gw: net.IPv4zero, - } - epInfo.Routes = append(epInfo.Routes, dummyRoute) + _, defaultIPNet, err := net.ParseCIDR("0.0.0.0/0") + if err != nil { + return + } + + dummyRoute := network.RouteInfo{ + Dst: *defaultIPNet, + Gw: net.IPv4zero, + } + epInfo.Routes = append(epInfo.Routes, dummyRoute) } func getNetworkDNSSettings(nwCfg *cni.NetworkConfig, _ network.DNSInfo) (network.DNSInfo, error) { From da51de4f0d8cfe7dd7b5c83e24fb08005e6df9c4 Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 9 Jan 2026 18:08:21 +0000 Subject: [PATCH 10/24] fix linter issue --- cni/network/network_windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index 93429ab3b2..b2ba4cd926 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -132,7 +132,8 @@ func (plugin *NetPlugin) getNetworkName(netNs string, interfaceInfo *network.Int func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, - epInfo *network.EndpointInfo) { + epInfo *network.EndpointInfo + ) { // as a workaround, HNS will not set this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) // on interface when SkipDefaultRoutes is set to true. // the only usage for this dummy default is to bypass HNS setting default route From 5be4670f2a838e5eee9b3deb7122b4d1ac706ff9 Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 9 Jan 2026 18:16:47 +0000 Subject: [PATCH 11/24] add an UT for InfraVnetRoutingForMultitenancy --- cni/network/network_windows.go | 5 +-- cni/network/network_windows_test.go | 66 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index b2ba4cd926..7b0217ad3c 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -134,9 +134,8 @@ func setupInfraVnetRoutingForMultitenancy( _ *cniTypesCurr.Result, epInfo *network.EndpointInfo ) { - // as a workaround, HNS will not set this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) - // on interface when SkipDefaultRoutes is set to true. - // the only usage for this dummy default is to bypass HNS setting default route + // as a workaround, CNI needs to pass this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) to HNS + // the only usage for this dummy default is to bypass HNS setting default route, causing duplicated routes issue // TODO: Remove this once HNS fix is ready if !epInfo.SkipDefaultRoutes { return diff --git a/cni/network/network_windows_test.go b/cni/network/network_windows_test.go index 5d1189a350..d093ca376d 100644 --- a/cni/network/network_windows_test.go +++ b/cni/network/network_windows_test.go @@ -1381,3 +1381,69 @@ func TestPluginWindowsAdd(t *testing.T) { }) } } + +// When SkipDefaultRoutes == false => Function returns early. Route list is unchanged (still 1 route, and no dummy 0.0.0.0/0 via 0.0.0.0). +// When SkipDefaultRoutes == true, the Function appends a new route. So the total routes becomes 2. +// One of the routes is exactly Dst=0.0.0.0/0, Gw=0.0.0.0. The original route (1.1.1.1/24 via 10.0.0.1) is preserved. +func TestSetupInfraVnetRoutingForMultitenancy(t *testing.T) { + tests := []struct { + name string + epInfo *network.EndpointInfo + wantRouteCount int + expectDummyRoute bool + }{ + { + name: "SkipDefaultRoutes=false — do not add dummy default route", + epInfo: &network.EndpointInfo{ + SkipDefaultRoutes: false, + Routes: []network.RouteInfo{ + { + Dst: *getCIDRNotationForAddress("1.1.1.1/24"), + Gw: net.ParseIP("10.0.0.1"), + }, + }, + }, + wantRouteCount: 1, + expectDummyRoute: false, + }, + { + name: "SkipDefaultRoutes=true — append dummy default route 0.0.0.0/0 via 0.0.0.0", + epInfo: &network.EndpointInfo{ + SkipDefaultRoutes: true, + Routes: []network.RouteInfo{ + { + Dst: *getCIDRNotationForAddress("1.1.1.1/24"), + Gw: net.ParseIP("10.0.0.1"), + }, + }, + }, + wantRouteCount: 2, + expectDummyRoute: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + setupInfraVnetRoutingForMultitenancy(nil, nil, tt.epInfo) + + require.Len(t, tt.epInfo.Routes, tt.wantRouteCount) + + require.True(t, tt.epInfo.Routes[0].Gw.Equal(net.ParseIP("10.0.0.1"))) + + hasDummy := false + for _, r := range tt.epInfo.Routes { + if r.Gw.Equal(net.IPv4zero) && r.Dst.String() == "0.0.0.0/0" { + hasDummy = true + break + } + } + + if tt.expectDummyRoute { + require.True(t, hasDummy, "expected dummy default route 0.0.0.0/0 via 0.0.0.0 to be added") + } else { + require.False(t, hasDummy, "did not expect dummy default route to be added") + } + }) + } +} \ No newline at end of file From 55da91a5878c130ffcff202cb5ff12a3589c81aa Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 9 Jan 2026 18:18:17 +0000 Subject: [PATCH 12/24] fix linter issue --- cni/network/network_windows_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cni/network/network_windows_test.go b/cni/network/network_windows_test.go index d093ca376d..f0cfe842b5 100644 --- a/cni/network/network_windows_test.go +++ b/cni/network/network_windows_test.go @@ -1387,10 +1387,10 @@ func TestPluginWindowsAdd(t *testing.T) { // One of the routes is exactly Dst=0.0.0.0/0, Gw=0.0.0.0. The original route (1.1.1.1/24 via 10.0.0.1) is preserved. func TestSetupInfraVnetRoutingForMultitenancy(t *testing.T) { tests := []struct { - name string - epInfo *network.EndpointInfo - wantRouteCount int - expectDummyRoute bool + name string + epInfo *network.EndpointInfo + wantRouteCount int + expectDummyRoute bool }{ { name: "SkipDefaultRoutes=false — do not add dummy default route", @@ -1446,4 +1446,4 @@ func TestSetupInfraVnetRoutingForMultitenancy(t *testing.T) { } }) } -} \ No newline at end of file +} From 44fd481aff410599545369f51257c0bff914ea18 Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 9 Jan 2026 20:11:02 +0000 Subject: [PATCH 13/24] fix missing , --- cni/network/network_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index 7b0217ad3c..0163d4473b 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -132,7 +132,7 @@ func (plugin *NetPlugin) getNetworkName(netNs string, interfaceInfo *network.Int func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, - epInfo *network.EndpointInfo + epInfo *network.EndpointInfo, ) { // as a workaround, CNI needs to pass this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) to HNS // the only usage for this dummy default is to bypass HNS setting default route, causing duplicated routes issue From a27d5e5a7348b2f7bea83de425ee087a71b53968 Mon Sep 17 00:00:00 2001 From: paulyu Date: Wed, 14 Jan 2026 18:29:49 +0000 Subject: [PATCH 14/24] format codes --- cni/network/network_windows.go | 2 +- cni/network/network_windows_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cni/network/network_windows.go b/cni/network/network_windows.go index 0163d4473b..1acfcbbccc 100644 --- a/cni/network/network_windows.go +++ b/cni/network/network_windows.go @@ -133,7 +133,7 @@ func setupInfraVnetRoutingForMultitenancy( _ *cni.NetworkConfig, _ *cniTypesCurr.Result, epInfo *network.EndpointInfo, - ) { +) { // as a workaround, CNI needs to pass this dummy default route (0.0.0.0/0, nexthop: 0.0.0.0) to HNS // the only usage for this dummy default is to bypass HNS setting default route, causing duplicated routes issue // TODO: Remove this once HNS fix is ready diff --git a/cni/network/network_windows_test.go b/cni/network/network_windows_test.go index f0cfe842b5..c6b98a55ef 100644 --- a/cni/network/network_windows_test.go +++ b/cni/network/network_windows_test.go @@ -1423,7 +1423,6 @@ func TestSetupInfraVnetRoutingForMultitenancy(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { setupInfraVnetRoutingForMultitenancy(nil, nil, tt.epInfo) From 9efa18ef5e04bccc6d259539b66cbdab775409c7 Mon Sep 17 00:00:00 2001 From: paulyu Date: Wed, 14 Jan 2026 21:47:45 +0000 Subject: [PATCH 15/24] use dummy default route for apipa endpoint --- cns/hnsclient/hnsclient_windows.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index b97781a17a..5d4db8dd4b 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -537,9 +537,10 @@ func configureHostNCApipaEndpoint( endpoint.Policies = append(endpoint.Policies, endpointPolicy) } - // keep Apipa Endpoint gw as 169.254.128.1 to make sure NC to host connectivity work for both Linux and Windows containers + // Use a dummy default route on the APIPA endpoint to avoid conflicting with host default routes. + // Host↔NC connectivity still relies on being on-link in the APIPA subnet plus ACLs. hcnRoute := hcn.Route{ - NextHop: hnsLoopbackAdapterIPAddress, + NextHop: "0.0.0.0", DestinationPrefix: "0.0.0.0/0", } From 61e4fec562b9eb903738f390525f412243280cc2 Mon Sep 17 00:00:00 2001 From: paulyu Date: Mon, 19 Jan 2026 15:02:09 +0000 Subject: [PATCH 16/24] fix apipa endpoint destinationprefix --- cns/hnsclient/hnsclient_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 5d4db8dd4b..1fee749801 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -541,7 +541,7 @@ func configureHostNCApipaEndpoint( // Host↔NC connectivity still relies on being on-link in the APIPA subnet plus ACLs. hcnRoute := hcn.Route{ NextHop: "0.0.0.0", - DestinationPrefix: "0.0.0.0/0", + DestinationPrefix: "169.254.128.0/17", } endpoint.Routes = append(endpoint.Routes, hcnRoute) From 8d3b277ac6119ba76969e5c8fd450b6c78c9919a Mon Sep 17 00:00:00 2001 From: paulyu Date: Mon, 19 Jan 2026 16:49:33 +0000 Subject: [PATCH 17/24] rollback hexthop --- cns/hnsclient/hnsclient_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 1fee749801..3bad6625c2 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -540,7 +540,7 @@ func configureHostNCApipaEndpoint( // Use a dummy default route on the APIPA endpoint to avoid conflicting with host default routes. // Host↔NC connectivity still relies on being on-link in the APIPA subnet plus ACLs. hcnRoute := hcn.Route{ - NextHop: "0.0.0.0", + NextHop: hnsLoopbackAdapterIPAddress, DestinationPrefix: "169.254.128.0/17", } From ce7de6c5c2b0865ea410659a79fcdcda158008ee Mon Sep 17 00:00:00 2001 From: paulyu Date: Tue, 20 Jan 2026 18:44:17 +0000 Subject: [PATCH 18/24] fix hns network destination prefix --- cns/hnsclient/hnsclient_windows.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 3bad6625c2..c36aa38c9e 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -269,7 +269,7 @@ func configureHostNCApipaNetwork(localIPConfiguration cns.IPConfiguration) (*hcn Routes: []hcn.Route{ { NextHop: localIPConfiguration.GatewayIPAddress, - DestinationPrefix: "0.0.0.0/0", + DestinationPrefix: "169.254.128.0/17", }, }, } @@ -537,8 +537,7 @@ func configureHostNCApipaEndpoint( endpoint.Policies = append(endpoint.Policies, endpointPolicy) } - // Use a dummy default route on the APIPA endpoint to avoid conflicting with host default routes. - // Host↔NC connectivity still relies on being on-link in the APIPA subnet plus ACLs. + hcnRoute := hcn.Route{ NextHop: hnsLoopbackAdapterIPAddress, DestinationPrefix: "169.254.128.0/17", From 515561418c33213b937efb8697e69f9a9bc23776 Mon Sep 17 00:00:00 2001 From: paulyu Date: Thu, 22 Jan 2026 14:51:08 +0000 Subject: [PATCH 19/24] add a new comment --- cns/hnsclient/hnsclient_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index c36aa38c9e..3b33c66b62 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -537,7 +537,7 @@ func configureHostNCApipaEndpoint( endpoint.Policies = append(endpoint.Policies, endpointPolicy) } - + // keep Apipa Endpoint gw as 169.254.128.1 to make sure NC to host connectivity work for both Linux and Windows containers hcnRoute := hcn.Route{ NextHop: hnsLoopbackAdapterIPAddress, DestinationPrefix: "169.254.128.0/17", From b2c2d587a38967a32da2cd44493dd922886cdc03 Mon Sep 17 00:00:00 2001 From: paulyu Date: Thu, 22 Jan 2026 15:12:31 +0000 Subject: [PATCH 20/24] make sure hnc netowrk default gateway --- cns/hnsclient/hnsclient_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 3b33c66b62..8646d5c59f 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -269,7 +269,7 @@ func configureHostNCApipaNetwork(localIPConfiguration cns.IPConfiguration) (*hcn Routes: []hcn.Route{ { NextHop: localIPConfiguration.GatewayIPAddress, - DestinationPrefix: "169.254.128.0/17", + DestinationPrefix: "0.0.0.0/0", }, }, } From 43ec0c1217eba53ddc23589b0e84a5aa25bc46de Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 23 Jan 2026 00:06:27 +0000 Subject: [PATCH 21/24] add apipa network metric to 50 --- cns/hnsclient/hnsclient_windows.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 8646d5c59f..30a01e92e9 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -270,6 +270,7 @@ func configureHostNCApipaNetwork(localIPConfiguration cns.IPConfiguration) (*hcn { NextHop: localIPConfiguration.GatewayIPAddress, DestinationPrefix: "0.0.0.0/0", + Metric: 50, }, }, } From 05a9d3b8f78e5ca3a4d9d3ae63dd83dd2451718a Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 23 Jan 2026 00:28:27 +0000 Subject: [PATCH 22/24] add metric to 50 --- cns/hnsclient/hnsclient_windows.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index 30a01e92e9..cd80e1a19f 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -542,6 +542,7 @@ func configureHostNCApipaEndpoint( hcnRoute := hcn.Route{ NextHop: hnsLoopbackAdapterIPAddress, DestinationPrefix: "169.254.128.0/17", + Metric: 50, } endpoint.Routes = append(endpoint.Routes, hcnRoute) From 21d51f0fa33beef496cc9272f7e5d0ef24cc8b80 Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 23 Jan 2026 00:39:55 +0000 Subject: [PATCH 23/24] keep destination prefix to 0.0.0.0/0 --- cns/hnsclient/hnsclient_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index cd80e1a19f..c80b1e2acb 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -541,7 +541,7 @@ func configureHostNCApipaEndpoint( // keep Apipa Endpoint gw as 169.254.128.1 to make sure NC to host connectivity work for both Linux and Windows containers hcnRoute := hcn.Route{ NextHop: hnsLoopbackAdapterIPAddress, - DestinationPrefix: "169.254.128.0/17", + DestinationPrefix: "0.0.0.0/0", Metric: 50, } From 3ff1257fd4738e9697034834c6aa2fe181701440 Mon Sep 17 00:00:00 2001 From: paulyu Date: Fri, 23 Jan 2026 19:29:13 +0000 Subject: [PATCH 24/24] fix destinationprefix --- cns/hnsclient/hnsclient_windows.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cns/hnsclient/hnsclient_windows.go b/cns/hnsclient/hnsclient_windows.go index c80b1e2acb..30a01e92e9 100644 --- a/cns/hnsclient/hnsclient_windows.go +++ b/cns/hnsclient/hnsclient_windows.go @@ -541,8 +541,7 @@ func configureHostNCApipaEndpoint( // keep Apipa Endpoint gw as 169.254.128.1 to make sure NC to host connectivity work for both Linux and Windows containers hcnRoute := hcn.Route{ NextHop: hnsLoopbackAdapterIPAddress, - DestinationPrefix: "0.0.0.0/0", - Metric: 50, + DestinationPrefix: "169.254.128.0/17", } endpoint.Routes = append(endpoint.Routes, hcnRoute)