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 Aug 8, 2024
1 parent 1117566 commit 6b8a11e
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 14 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
93 changes: 79 additions & 14 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 @@ -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",
Expand Down Expand Up @@ -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
}

0 comments on commit 6b8a11e

Please sign in to comment.