diff --git a/pkg/platforms/openstack/openstack.go b/pkg/platforms/openstack/openstack.go index 608ba6f876..ae41eaaa14 100644 --- a/pkg/platforms/openstack/openstack.go +++ b/pkg/platforms/openstack/openstack.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + network "net" "os" "os/exec" "path/filepath" @@ -26,11 +27,10 @@ const ( varConfigPath = "/var/config" ospMetaDataBaseDir = "/openstack/2018-08-27" ospMetaDataDir = varConfigPath + ospMetaDataBaseDir - ospMetaDataBaseURL = "http://169.254.169.254" + ospMetaDataBaseDir ospNetworkDataJSON = "network_data.json" ospMetaDataJSON = "meta_data.json" - ospNetworkDataURL = ospMetaDataBaseURL + "/" + ospNetworkDataJSON - ospMetaDataURL = ospMetaDataBaseURL + "/" + ospMetaDataJSON + ospHTTP = "http://" + ospHTTPS = "https://" // Config drive is defined as an iso9660 or vfat (deprecated) drive // with the "config-2" label. //https://docs.openstack.org/nova/latest/user/config-drive.html @@ -40,6 +40,10 @@ const ( var ( ospNetworkDataFile = ospMetaDataDir + "/" + ospNetworkDataJSON ospMetaDataFile = ospMetaDataDir + "/" + ospMetaDataJSON + ospBaseURLS = [2]string{"169.254.169.254", "fe80::a9fe:a9fe"} + ospNetworkDataURL string + ospMetaDataURL string + ospMetaDataBaseURL []string ) //go:generate ../../../bin/mockgen -destination mock/mock_openstack.go -source openstack.go @@ -113,17 +117,85 @@ func New(hostManager host.HostManagerInterface) OpenstackInterface { } } +func getActiveInterfaceName() (string, error) { + interfaces, err := network.Interfaces() + if err != nil { + return "", err + } + + for _, intf := range interfaces { + // Check if the interface is up and not a loopback + if intf.Flags&network.FlagUp != 0 && intf.Flags&network.FlagLoopback == 0 { + addrs, err := intf.Addrs() + if err != nil { + continue + } + + // Check if at least one address is an IPv6 address + for _, addr := range addrs { + if ipNet, ok := addr.(*network.IPNet); ok && ipNet.IP.To4() == nil { + return intf.Name, nil + } + } + } + } + + return "", fmt.Errorf("no active non-loopback interface found with an IPv6 address") +} + +func constructMetaDataURLs(baseURL, activeInterface string, isIPv6 bool) []string { + var urls []string + encodedSign := "%25" + port := "80" + if isIPv6 { + urls = append(urls, ospHTTPS+"["+baseURL+encodedSign+activeInterface+"]:"+port) + urls = append(urls, ospHTTP+"["+baseURL+encodedSign+activeInterface+"]:"+port) + } else { + urls = append(urls, ospHTTPS+baseURL) + urls = append(urls, ospHTTP+baseURL) + } + return urls +} + // GetOpenstackData gets the metadata and network_data func getOpenstackData(mountConfigDrive bool) (metaData *OSPMetaData, networkData *OSPNetworkData, err error) { metaData, networkData, err = getOpenstackDataFromConfigDrive(mountConfigDrive) if err != nil { log.Log.Error(err, "GetOpenStackData(): non-fatal error getting OpenStack data from config drive") - metaData, networkData, err = getOpenstackDataFromMetadataService() - if err != nil { - return metaData, networkData, fmt.Errorf("GetOpenStackData(): error getting OpenStack data: %w", err) + // Attempt to reach MetaData over IPv4 then over IPv6 for both HTTPS and HTTP + reachedMetaData := false + for i, baseURL := range ospBaseURLS { + isIPv6 := i == 1 + if isIPv6 { + activeInterface, err := getActiveInterfaceName() + if err != nil { + log.Log.Error(err, "GetOpenStackData(): non-fatal error retrieving active network interface") + continue + } + ospMetaDataBaseURL = constructMetaDataURLs(baseURL, activeInterface, true) + } else { + ospMetaDataBaseURL = constructMetaDataURLs(baseURL, "", false) + } + // Attempt to reach MetaData over HTTPS then over HTTP + for _, baseMetaURL := range ospMetaDataBaseURL { + ospNetworkDataURL = baseMetaURL + "/" + ospNetworkDataJSON + ospMetaDataURL = baseMetaURL + "/" + ospMetaDataJSON + metaData, networkData, err = getOpenstackDataFromMetadataService() + if err == nil { + reachedMetaData = true + break + } + } + + if reachedMetaData { + break + } } - } + if !reachedMetaData { + return metaData, networkData, fmt.Errorf("GetOpenStackData(): error reaching metadata service both over IPv6 and IPv4: %v", err) + } + } // We can't rely on the PCI address from the metadata so we will lookup the real PCI address // for the NIC that matches the MAC address. //