Skip to content

Commit

Permalink
Add single-stack IPv6 support
Browse files Browse the repository at this point in the history
  • Loading branch information
yasminvalim committed Mar 6, 2025
1 parent 1117566 commit 0bb5233
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ nav_order: 9
### Features

- Support partitioning disk with mounted partitions
- Support IPv6 for single-stack OpenStack

### Changes

Expand Down
90 changes: 83 additions & 7 deletions internal/providers/openstack/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package openstack
import (
"context"
"fmt"
"net"
"net/url"
"os"
"os/exec"
Expand All @@ -44,11 +45,16 @@ const (
)

var (
metadataServiceUrl = url.URL{
metadataServiceUrlIPv4 = url.URL{
Scheme: "http",
Host: "169.254.169.254",
Path: "openstack/latest/user_data",
}
metadataServiceUrlIPv6 = url.URL{
Scheme: "http",
Host: "[fe80::a9fe:a9fe%]",
Path: "openstack/latest/user_data",
}
)

func init() {
Expand Down Expand Up @@ -166,14 +172,84 @@ func fetchConfigFromDevice(logger *log.Logger, ctx context.Context, path string)
return os.ReadFile(filepath.Join(mnt, configDriveUserdataPath))
}

func isIPv6Address(ip net.IP) bool {
isIPv6 := ip.To4() == nil
return isIPv6
}

func findZoneID() (string, error) {
fmt.Println("Fetching zone id...")
interfaces, err := net.Interfaces()
if err != nil {
return "", fmt.Errorf("error fetching zone id: %v", err)
}

for _, iface := range interfaces {
fmt.Printf("Checking interface: %s\n", iface.Name)

if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
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) {
return iface.Name, nil
}
}
}
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) {
res, err := f.FetchToBuffer(metadataServiceUrl, resource.FetchOptions{})
var ipv4Res, ipv6Res []byte
var ipv4Err, ipv6Err error

// Attempt to fetch from IPv4
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
interfaceName, err := findZoneID()
if err != nil {
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
return ipv4Res, fmt.Errorf("IPv6 lookup failed, returning IPv4 result")
}
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{})

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
}

// 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
// If IPv4 fails, attempt to fetch from IPv6
interfaceName, err := findZoneID()
if err != nil {
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
return nil, fmt.Errorf("both IPv4 and IPv6 lookup failed")
}

return res, err
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
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")
}
return ipv6Res, fmt.Errorf("IPv4 failed, returning IPv6 result")
}

0 comments on commit 0bb5233

Please sign in to comment.