Skip to content

Commit

Permalink
Add DHCP IP Retries in PrepareHNSNetwork
Browse files Browse the repository at this point in the history
To address the potential race condition issue where acquiring a DHCP IP address may fail after CreateHNSNetwork,
we added a retry mechanism to wait for an available IP. If the DHCP IP cannot be acquired within three seconds,
a warning message will be returned.

Signed-off-by: Shuyang Xin <gavinx@vmware.com>
  • Loading branch information
XinShuYang committed Dec 21, 2023
1 parent 0c6d471 commit 44ba719
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 15 deletions.
7 changes: 6 additions & 1 deletion pkg/agent/agent_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ func (i *Initializer) prepareHNSNetworkAndOVSExtension() error {
return err
}
i.nodeConfig.UplinkNetConfig.DNSServers = dnsServers
Dhcp, err := util.GetDHCPByInterfaceIndex(adapter.Index)
if err != nil {
return err
}
i.nodeConfig.UplinkNetConfig.DHCP = Dhcp
// Save routes which are configured on the uplink interface, and configure them on the management virtual adapter
// if Windows host doesn't move the configuration automatically.
if err = i.saveHostRoutes(); err != nil {
Expand All @@ -117,7 +122,7 @@ func (i *Initializer) prepareHNSNetworkAndOVSExtension() error {
if subnetCIDR == nil {
return fmt.Errorf("failed to find valid IPv4 PodCIDR")
}
return util.PrepareHNSNetwork(subnetCIDR, i.nodeConfig.NodeTransportIPv4Addr, adapter, i.nodeConfig.UplinkNetConfig.Gateway, dnsServers, i.nodeConfig.UplinkNetConfig.Routes, i.ovsBridge)
return util.PrepareHNSNetwork(subnetCIDR, i.nodeConfig.NodeTransportIPv4Addr, adapter, i.nodeConfig.UplinkNetConfig.Gateway, dnsServers, i.nodeConfig.UplinkNetConfig.Routes, i.ovsBridge, i.nodeConfig.UplinkNetConfig.DHCP)
}

func (i *Initializer) prepareVMNetworkAndOVSExtension() error {
Expand Down
1 change: 1 addition & 0 deletions pkg/agent/config/node_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type AdapterNetConfig struct {
Routes []interface{}
// OFPort is the OpenFlow port number of the uplink interface allocated by OVS.
OFPort uint32
DHCP bool
}

type WireGuardConfig struct {
Expand Down
57 changes: 44 additions & 13 deletions pkg/agent/util/net_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ func ConfigureLinkAddresses(idx int, ipNets []*net.IPNet) error {
}

// PrepareHNSNetwork creates HNS Network for containers.
func PrepareHNSNetwork(subnetCIDR *net.IPNet, nodeIPNet *net.IPNet, uplinkAdapter *net.Interface, nodeGateway string, dnsServers string, routes []interface{}, newName string) error {
func PrepareHNSNetwork(subnetCIDR *net.IPNet, nodeIPNet *net.IPNet, uplinkAdapter *net.Interface, nodeGateway string, dnsServers string, routes []interface{}, newName string, dhcp bool) error {
klog.InfoS("Creating HNSNetwork", "name", LocalHNSNetwork, "subnet", subnetCIDR, "nodeIP", nodeIPNet, "adapter", uplinkAdapter)
hnsNet, err := CreateHNSNetwork(LocalHNSNetwork, subnetCIDR, nodeIPNet, uplinkAdapter)
if err != nil {
Expand All @@ -461,7 +461,7 @@ func PrepareHNSNetwork(subnetCIDR *net.IPNet, nodeIPNet *net.IPNet, uplinkAdapte
}
}()

adapter, ipFound, err := adapterIPExists(nodeIPNet.IP, uplinkAdapter.HardwareAddr, ContainerVNICPrefix)
adapter, ipFound, err := adapterIPExists(nodeIPNet.IP, uplinkAdapter.HardwareAddr, ContainerVNICPrefix, dhcp)
if err != nil {
return err
}
Expand Down Expand Up @@ -521,29 +521,44 @@ func PrepareHNSNetwork(subnetCIDR *net.IPNet, nodeIPNet *net.IPNet, uplinkAdapte

// adapterIPExists finds the network adapter configured with the provided IP, MAC and its name has the given prefix.
// If "namePrefix" is empty, it returns the first network adapter with the provided IP and MAC.
// If DHCP is enabled on the original adapter, it will attempt to acquire an IP multiple times.
// It returns true if the IP is found on the adapter, otherwise it returns false.
func adapterIPExists(ip net.IP, mac net.HardwareAddr, namePrefix string) (*net.Interface, bool, error) {
func adapterIPExists(ip net.IP, mac net.HardwareAddr, namePrefix string, dhcp bool) (*net.Interface, bool, error) {
adapters, err := netInterfaces()
if err != nil {
return nil, false, err
}
// To avoid the issue of insufficient time for the Windows adapter to obtain an dhcp IP after the creation of the HNS network,
// wait for up to three seconds and retry IP acquisition in case of failures.
ipExists := false
for idx, adapter := range adapters {
if bytes.Equal(adapter.HardwareAddr, mac) {
if namePrefix == "" || strings.Contains(adapter.Name, namePrefix) {
addrList, err := netInterfaceAddrs(&adapters[idx])
if err != nil {
return nil, false, err
}
for _, addr := range addrList {
if ipNet, ok := addr.(*net.IPNet); ok {
if ipNet.IP.Equal(ip) {
ipExists = true
break
for retry := 0; retry < 4; retry++ {
addrList, err := netInterfaceAddrs(&adapters[idx])
if err != nil {
return nil, false, err
}
for _, addr := range addrList {
if ipNet, ok := addr.(*net.IPNet); ok {
if ipNet.IP.Equal(ip) {
ipExists = true
break
}
}
}
if ipExists {
return &adapter, true, nil
}
if dhcp == false {
return &adapter, false, nil
} else if !ipExists && retry < 4 {
time.Sleep(1 * time.Second)
} else {
klog.Warning("Unable to find an available DHCP IP with MAC %s, IP %s, and name prefix %s after retries", mac.String(), ip.String(), namePrefix)
return &adapter, false, nil
}
}
return &adapter, ipExists, nil
}
}
}
Expand Down Expand Up @@ -619,6 +634,22 @@ func SetAdapterDNSServers(adapterName, dnsServers string) error {
return nil
}

// GetDHCPByInterfaceIndex returns the DHCP status on the specified interface.
func GetDHCPByInterfaceIndex(ifIndex int) (bool, error) {
cmd := fmt.Sprintf("$(Get-NetIPInterface -InterfaceIndex %d -AddressFamily IPv4).Dhcp", ifIndex)
Dhcp, err := runCommand(cmd)
if err != nil {
return false, err
}
Dhcp = strings.TrimSpace(Dhcp)
if Dhcp == "Enabled" {
klog.Infof("IPv4 DHCP is Enabled for Interface %d", ifIndex)
return true, nil
}
klog.Infof("IPv4 DHCP is Disabled for Interface%d", ifIndex)
return false, nil
}

// ListenLocalSocket creates a listener on a Unix domain socket or a Windows named pipe.
// - If the specified address starts with "\\.\pipe\", create a listener on the a Windows named pipe path.
// - Else create a listener on a local Unix domain socket.
Expand Down
2 changes: 1 addition & 1 deletion pkg/agent/util/net_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ func TestPrepareHNSNetwork(t *testing.T) {
defer mockHNSNetworkCreate(tc.hnsNetworkCreateErr)()
defer mockHNSNetworkDelete(nil)()
defer mockAntreaNetIO(&antreasyscalltest.MockNetIO{CreateIPForwardEntryErr: tc.createRowErr})()
gotErr := PrepareHNSNetwork(testSubnetCIDR, tc.nodeIPNet, testUplinkAdapter, "testGateway", tc.dnsServers, testRoutes, tc.newName)
gotErr := PrepareHNSNetwork(testSubnetCIDR, tc.nodeIPNet, testUplinkAdapter, "testGateway", tc.dnsServers, testRoutes, tc.newName, false)
assert.Equal(t, tc.wantErr, gotErr)
})
}
Expand Down

0 comments on commit 44ba719

Please sign in to comment.