From 6b8a11e3b9c3f05b1d36ee76c2d763134261ee1f Mon Sep 17 00:00:00 2001 From: Yasmin Valim Date: Mon, 15 Jul 2024 18:01:05 -0300 Subject: [PATCH 1/2] Add single-stack IPv6 support --- docs/release-notes.md | 1 + internal/providers/openstack/openstack.go | 93 +++++++++++++++++++---- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 8e842f0a2..8be710cfe 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ nav_order: 9 ### Features - Support partitioning disk with mounted partitions +- Support IPv6 for single-stack OpenStack ### Changes diff --git a/internal/providers/openstack/openstack.go b/internal/providers/openstack/openstack.go index 4ef352cfe..c6da56422 100644 --- a/internal/providers/openstack/openstack.go +++ b/internal/providers/openstack/openstack.go @@ -22,6 +22,7 @@ package openstack import ( "context" "fmt" + "net" "net/url" "os" "os/exec" @@ -43,14 +44,6 @@ const ( configDriveUserdataPath = "/openstack/latest/user_data" ) -var ( - metadataServiceUrl = url.URL{ - Scheme: "http", - Host: "169.254.169.254", - Path: "openstack/latest/user_data", - } -) - func init() { platform.Register(platform.Provider{ Name: "openstack", @@ -166,14 +159,86 @@ func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string) return os.ReadFile(filepath.Join(mnt, configDriveUserdataPath)) } +func FindIPv6InterfaceName() (string, error) { + interfaces, err := net.Interfaces() + if err != nil { + return "", err + } + + for _, iface := range interfaces { + // Check if the interface is up and not loopback + if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 { + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + // Check if the address is an IPv6 + if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() == nil && !ipnet.IP.IsLinkLocalUnicast() { + return iface.Name, nil + } + } + } + } + + return "", fmt.Errorf("no IPv6 interface name found") +} + func fetchConfigFromMetadataService(f *resource.Fetcher) ([]byte, error) { - res, err := f.FetchToBuffer(metadataServiceUrl, resource.FetchOptions{}) - // the metadata server exists but doesn't contain any actual metadata, - // assume that there is no config specified - if err == resource.ErrNotFound { - return nil, nil + // Find IPv6 name + iface, err := FindIPv6InterfaceName() + if err != nil { + return nil, err + } + + // Construct URL + var ( + ipv4MetadataServiceUrl = url.URL{ + Scheme: "http", + Host: "169.254.169.254", + Path: "openstack/latest/user_data", + } + ipv6MetadataServiceUrl = url.URL{ + Scheme: "http", + Host: fmt.Sprintf("[fe80::a9fe:a9fe%%%s]", url.PathEscape(iface)), + Path: "openstack/latest/user_data", + } + ) + + var resIPv4, resIPv6 []byte + var errIPv4, errIPv6 error + + // Try IPv4 endpoint + resIPv4, errIPv4 = f.FetchToBuffer(ipv4MetadataServiceUrl, resource.FetchOptions{}) + if errIPv4 != nil && errIPv4 != resource.ErrNotFound { + f.Logger.Err("Failed to fetch config from IPv4: %v", errIPv4) + } + + // Try IPv6 endpoint + resIPv6, errIPv6 = f.FetchToBuffer(ipv6MetadataServiceUrl, resource.FetchOptions{}) + if errIPv6 != nil && errIPv6 != resource.ErrNotFound { + f.Logger.Err("Failed to fetch config from IPv6: %v", errIPv6) + } + + // If both IPv4 and IPv6 have valid data, combine them + if resIPv4 != nil && resIPv6 != nil { + return append(resIPv4, resIPv6...), nil + } else if resIPv4 != nil { + return resIPv4, nil + } else if resIPv6 != nil { + return resIPv6, nil + } + + // If both endpoints fail, return the appropriate error + if errIPv4 != nil { + return nil, errIPv4 + } + if errIPv6 != nil { + return nil, errIPv6 } - return res, err + // If both endpoints return ErrNotFound + return nil, nil } From ee0d2afbcc3c1071d7b2aed60f206977ed76474e Mon Sep 17 00:00:00 2001 From: Yasmin Valim Date: Thu, 12 Sep 2024 16:05:53 -0300 Subject: [PATCH 2/2] debuggers: add some print to debug, will reset this commit later --- internal/providers/openstack/openstack.go | 140 ++++++++++++---------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/internal/providers/openstack/openstack.go b/internal/providers/openstack/openstack.go index c6da56422..a4d0d539d 100644 --- a/internal/providers/openstack/openstack.go +++ b/internal/providers/openstack/openstack.go @@ -44,6 +44,18 @@ const ( configDriveUserdataPath = "/openstack/latest/user_data" ) +var ( + metadataServiceUrlIPv4 = url.URL{ + Scheme: "http", + Host: "169.254.169.254", + Path: "openstack/latest/user_data", + } + metadataServiceUrlIPv6 = url.URL{ + Scheme: "http", + Path: "openstack/latest/user_data", + } +) + func init() { platform.Register(platform.Provider{ Name: "openstack", @@ -159,86 +171,94 @@ func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string) return os.ReadFile(filepath.Join(mnt, configDriveUserdataPath)) } -func FindIPv6InterfaceName() (string, error) { +// Checks if an IP is an IPv6 address +func isIPv6Address(ip net.IP) bool { + isIPv6 := ip.To4() == nil + fmt.Printf("Verificando se o IP é IPv6: %s, resultado: %v\n", ip.String(), isIPv6) + return isIPv6 +} + +// Finds the name of an active network interface +func findNetworkInterfaceName() (string, error) { + fmt.Println("Fetching network interfaces...") interfaces, err := net.Interfaces() if err != nil { - return "", err + return "", fmt.Errorf("error fetching network interfaces: %v", err) } for _, iface := range interfaces { - // Check if the interface is up and not loopback - if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 { - addrs, err := iface.Addrs() - if err != nil { - continue - } + fmt.Printf("Checking interface: %s\n", iface.Name) - for _, addr := range addrs { - // Check if the address is an IPv6 - if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() == nil && !ipnet.IP.IsLinkLocalUnicast() { - return iface.Name, nil - } + // Skip inactive or loopback interfaces + if iface.Flags&net.FlagUp != 1 && iface.Flags&net.FlagLoopback == 1 { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + fmt.Printf("Error fetching addresses for interface %s: %v\n", iface.Name, err) + continue + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && isIPv6Address(ipnet.IP) { + fmt.Printf("Active interface found: %s\n", iface.Name) + return iface.Name, nil } } } - - return "", fmt.Errorf("no IPv6 interface name found") + return "", fmt.Errorf("no active IPv6 network interface found") } +// Fetches configuration from both IPv4 and IPv6 metadata services func fetchConfigFromMetadataService(f *resource.Fetcher) ([]byte, error) { - - // Find IPv6 name - iface, err := FindIPv6InterfaceName() - if err != nil { - return nil, err - } - - // Construct URL - var ( - ipv4MetadataServiceUrl = url.URL{ - Scheme: "http", - Host: "169.254.169.254", - Path: "openstack/latest/user_data", - } - ipv6MetadataServiceUrl = url.URL{ - Scheme: "http", - Host: fmt.Sprintf("[fe80::a9fe:a9fe%%%s]", url.PathEscape(iface)), - Path: "openstack/latest/user_data", + var ipv4Res, ipv6Res []byte + var ipv4Err, ipv6Err error + + // Attempt to fetch from IPv4 + fmt.Println("Fetching from IPv4 metadata service...") + ipv4Res, ipv4Err = f.FetchToBuffer(metadataServiceUrlIPv4, resource.FetchOptions{}) + if ipv4Err == nil { + fmt.Println("Successfully fetched configuration from IPv4.") + + // If IPv4 succeeds, attempt to fetch from IPv6 as well + fmt.Println("Fetching IPv6 address for metadata service...") + interfaceName, err := findNetworkInterfaceName() + if err != nil { + fmt.Printf("IPv6 metadata service lookup failed: %v\n", err) + return ipv4Res, fmt.Errorf("IPv6 lookup failed, returning IPv4 result") } - ) - var resIPv4, resIPv6 []byte - var errIPv4, errIPv6 error + metadataServiceUrlIPv6.Host = fmt.Sprintf("fe80::a9fe:a9fe%%%s", interfaceName) + fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6.String()) + ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{}) - // Try IPv4 endpoint - resIPv4, errIPv4 = f.FetchToBuffer(ipv4MetadataServiceUrl, resource.FetchOptions{}) - if errIPv4 != nil && errIPv4 != resource.ErrNotFound { - f.Logger.Err("Failed to fetch config from IPv4: %v", errIPv4) + if ipv6Err != nil { + fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err) + return ipv4Res, fmt.Errorf("IPv4 succeeded, but IPv6 failed: %v", ipv6Err) + } + fmt.Println("Successfully fetched configuration from both IPv4 and IPv6.") + return append(ipv4Res, ipv6Res...), nil } - // Try IPv6 endpoint - resIPv6, errIPv6 = f.FetchToBuffer(ipv6MetadataServiceUrl, resource.FetchOptions{}) - if errIPv6 != nil && errIPv6 != resource.ErrNotFound { - f.Logger.Err("Failed to fetch config from IPv6: %v", errIPv6) - } + // If IPv4 fails, attempt to fetch from IPv6 + fmt.Printf("IPv4 metadata service failed: %v\n", ipv4Err) + fmt.Println("Trying to fetch from IPv6 metadata service...") - // If both IPv4 and IPv6 have valid data, combine them - if resIPv4 != nil && resIPv6 != nil { - return append(resIPv4, resIPv6...), nil - } else if resIPv4 != nil { - return resIPv4, nil - } else if resIPv6 != nil { - return resIPv6, nil + interfaceName, err := findNetworkInterfaceName() + if err != nil { + fmt.Printf("IPv6 metadata service lookup failed: %v\n", err) + return nil, fmt.Errorf("both IPv4 and IPv6 lookup failed") } - // If both endpoints fail, return the appropriate error - if errIPv4 != nil { - return nil, errIPv4 - } - if errIPv6 != nil { - return nil, errIPv6 + metadataServiceUrlIPv6.Host = fmt.Sprintf("fe80::a9fe:a9fe%%%s", interfaceName) + fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6.String()) + ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{}) + if ipv6Err != nil { + fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err) + return nil, fmt.Errorf("both IPv4 and IPv6 services failed") } - // If both endpoints return ErrNotFound - return nil, nil + fmt.Println("Successfully fetched configuration from IPv6 metadata service.") + return ipv6Res, fmt.Errorf("IPv4 failed, returning IPv6 result") }