Skip to content

Commit 18f03e7

Browse files
committed
Add single-stack IPv6 support
1 parent 3b3a27b commit 18f03e7

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

docs/release-notes.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ nav_order: 9
1212

1313
- Support partitioning disk with mounted partitions
1414
- Support Proxmox VE
15+
- Support IPv6 for single-stack OpenStack
1516

1617
### Changes
1718

internal/providers/openstack/openstack.go

+83-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package openstack
2222
import (
2323
"context"
2424
"fmt"
25+
"net"
2526
"net/url"
2627
"os"
2728
"os/exec"
@@ -44,11 +45,16 @@ const (
4445
)
4546

4647
var (
47-
metadataServiceUrl = url.URL{
48+
metadataServiceUrlIPv4 = url.URL{
4849
Scheme: "http",
4950
Host: "169.254.169.254",
5051
Path: "openstack/latest/user_data",
5152
}
53+
metadataServiceUrlIPv6 = url.URL{
54+
Scheme: "http",
55+
Host: "[fe80::a9fe:a9fe%]",
56+
Path: "openstack/latest/user_data",
57+
}
5258
)
5359

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

175+
func isIPv6Address(ip net.IP) bool {
176+
isIPv6 := ip.To4() == nil
177+
return isIPv6
178+
}
179+
180+
func findZoneID() (string, error) {
181+
fmt.Println("Fetching zone id...")
182+
interfaces, err := net.Interfaces()
183+
if err != nil {
184+
return "", fmt.Errorf("error fetching zone id: %v", err)
185+
}
186+
187+
for _, iface := range interfaces {
188+
fmt.Printf("Checking interface: %s\n", iface.Name)
189+
190+
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
191+
continue
192+
}
193+
194+
addrs, err := iface.Addrs()
195+
if err != nil {
196+
fmt.Printf("Error fetching addresses for interface %s: %v\n", iface.Name, err)
197+
continue
198+
}
199+
200+
for _, addr := range addrs {
201+
if ipnet, ok := addr.(*net.IPNet); ok && isIPv6Address(ipnet.IP) {
202+
return iface.Name, nil
203+
}
204+
}
205+
}
206+
return "", fmt.Errorf("no active IPv6 network interface found")
207+
}
208+
209+
// Fetches configuration from both IPv4 and IPv6 metadata services
169210
func fetchConfigFromMetadataService(f *resource.Fetcher) ([]byte, error) {
170-
res, err := f.FetchToBuffer(metadataServiceUrl, resource.FetchOptions{})
211+
var ipv4Res, ipv6Res []byte
212+
var ipv4Err, ipv6Err error
213+
214+
// Attempt to fetch from IPv4
215+
ipv4Res, ipv4Err = f.FetchToBuffer(metadataServiceUrlIPv4, resource.FetchOptions{})
216+
if ipv4Err == nil {
217+
fmt.Println("Successfully fetched configuration from IPv4.")
218+
219+
// If IPv4 succeeds, attempt to fetch from IPv6 as well
220+
interfaceName, err := findZoneID()
221+
if err != nil {
222+
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
223+
return ipv4Res, fmt.Errorf("IPv6 lookup failed, returning IPv4 result")
224+
}
225+
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
226+
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
227+
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
228+
ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{})
229+
230+
if ipv6Err != nil {
231+
fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err)
232+
return ipv4Res, fmt.Errorf("IPv4 succeeded, but IPv6 failed: %v", ipv6Err)
233+
}
234+
fmt.Println("Successfully fetched configuration from both IPv4 and IPv6.")
235+
return append(ipv4Res, ipv6Res...), nil
236+
}
171237

172-
// the metadata server exists but doesn't contain any actual metadata,
173-
// assume that there is no config specified
174-
if err == resource.ErrNotFound {
175-
return nil, nil
238+
// If IPv4 fails, attempt to fetch from IPv6
239+
interfaceName, err := findZoneID()
240+
if err != nil {
241+
fmt.Printf("IPv6 metadata service lookup failed: %v\n", err)
242+
return nil, fmt.Errorf("both IPv4 and IPv6 lookup failed")
176243
}
177244

178-
return res, err
245+
metadataServiceUrlIPv6.Host = fmt.Sprintf("[%s%s]", "fe80::a9fe:a9fe%", interfaceName)
246+
metadataServiceUrlIPv6Str := fmt.Sprintf("http://[%s%s]/openstack/latest/user_data", metadataServiceUrlIPv6.Host, interfaceName)
247+
fmt.Printf("Fetching from IPv6 metadata service at %s...\n", metadataServiceUrlIPv6Str)
248+
ipv6Res, ipv6Err = f.FetchToBuffer(metadataServiceUrlIPv6, resource.FetchOptions{})
249+
250+
if ipv6Err != nil {
251+
fmt.Printf("IPv6 metadata service failed: %v\n", ipv6Err)
252+
return nil, fmt.Errorf("both IPv4 and IPv6 services failed")
253+
}
254+
return ipv6Res, fmt.Errorf("IPv4 failed, returning IPv6 result")
179255
}

0 commit comments

Comments
 (0)