Skip to content

Commit 792dee2

Browse files
committed
kic: plumb ip-family and ipv6 for docker bridge
1 parent 1c88f6d commit 792dee2

File tree

9 files changed

+533
-98
lines changed

9 files changed

+533
-98
lines changed

pkg/drivers/kic/kic.go

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ func (d *Driver) Create() error {
8989
OCIBinary: d.NodeConfig.OCIBinary,
9090
APIServerPort: d.NodeConfig.APIServerPort,
9191
GPUs: d.NodeConfig.GPUs,
92+
IPFamily: strings.ToLower(d.NodeConfig.IPFamily),
93+
IPv6: d.NodeConfig.StaticIPv6,
9294
}
9395
if params.Memory != "0" {
9496
params.Memory += "mb"
@@ -98,41 +100,102 @@ func (d *Driver) Create() error {
98100
if networkName == "" {
99101
networkName = d.NodeConfig.ClusterName
100102
}
103+
101104
staticIP := d.NodeConfig.StaticIP
102-
if gateway, err := oci.CreateNetwork(d.OCIBinary, networkName, d.NodeConfig.Subnet, staticIP); err != nil {
105+
106+
gateway, err := oci.CreateNetworkWithIPFamily(
107+
d.OCIBinary,
108+
networkName,
109+
d.NodeConfig.Subnet,
110+
d.NodeConfig.Subnetv6,
111+
staticIP,
112+
d.NodeConfig.StaticIPv6,
113+
params.IPFamily,
114+
)
115+
if err != nil {
103116
msg := "Unable to create dedicated network, this might result in cluster IP change after restart: {{.error}}"
104117
args := out.V{"error": err}
105-
if staticIP != "" {
118+
if staticIP != "" || d.NodeConfig.StaticIPv6 != "" {
119+
// If the user requested a static IP on either family, failing
120+
// to create the dedicated network should be fatal.
106121
exit.Message(reason.IfDedicatedNetwork, msg, args)
107122
}
108123
out.WarningT(msg, args)
109-
} else if gateway != nil && staticIP != "" {
110-
params.Network = networkName
111-
params.IP = staticIP
112-
} else if gateway != nil {
124+
} else {
125+
// Only attach to the user-defined network when creation/reuse
126+
// succeeded. For IPv6-only networks, gateway may legitimately be nil.
113127
params.Network = networkName
114-
ip := gateway.To4()
115-
// calculate the container IP based on guessing the machine index
116-
index := driver.IndexFromMachineName(d.NodeConfig.MachineName)
117-
if int(ip[3])+index > 253 { // reserve last client ip address for multi-control-plane loadbalancer vip address in ha cluster
118-
return fmt.Errorf("too many machines to calculate an IP")
128+
}
129+
130+
// Now decide static IPs per family based on the gateway (if any).
131+
switch params.IPFamily {
132+
case "ipv6":
133+
if d.NodeConfig.StaticIPv6 != "" {
134+
params.IPv6 = d.NodeConfig.StaticIPv6
135+
}
136+
137+
case "dual":
138+
// IPv4 part (only if Docker reported a v4 gateway)
139+
if g4 := gateway.To4(); g4 != nil {
140+
if staticIP != "" {
141+
params.IP = staticIP
142+
} else {
143+
ip := make(net.IP, len(g4))
144+
copy(ip, g4)
145+
index := driver.IndexFromMachineName(d.NodeConfig.MachineName)
146+
if int(ip[3])+index > 253 {
147+
return fmt.Errorf("too many machines to calculate an IPv4")
148+
}
149+
ip[3] += byte(index)
150+
klog.Infof("calculated static IPv4 %q for the %q container", ip.String(), d.NodeConfig.MachineName)
151+
params.IP = ip.String()
152+
}
153+
}
154+
if d.NodeConfig.StaticIPv6 != "" {
155+
params.IPv6 = d.NodeConfig.StaticIPv6
156+
}
157+
158+
default: // ipv4
159+
if staticIP != "" {
160+
params.IP = staticIP
161+
} else if gateway != nil {
162+
if g4 := gateway.To4(); g4 != nil {
163+
ip := make(net.IP, len(g4))
164+
copy(ip, g4)
165+
index := driver.IndexFromMachineName(d.NodeConfig.MachineName)
166+
if int(ip[3])+index > 253 {
167+
return fmt.Errorf("too many machines to calculate an IP")
168+
}
169+
ip[3] += byte(index)
170+
klog.Infof("calculated static IP %q for the %q container", ip.String(), d.NodeConfig.MachineName)
171+
params.IP = ip.String()
172+
}
119173
}
120-
ip[3] += byte(index)
121-
klog.Infof("calculated static IP %q for the %q container", ip.String(), d.NodeConfig.MachineName)
122-
params.IP = ip.String()
123174
}
124-
drv := d.DriverName()
125175

176+
drv := d.DriverName()
177+
// Default listen address: v4 localhost for ipv4, v6 localhost for ipv6-only
126178
listAddr := oci.DefaultBindIPV4
179+
// IPv6-only clusters must publish on IPv6 loopback so the host can reach them
180+
if params.IPFamily == "ipv6" {
181+
listAddr = "::1"
182+
}
183+
127184
if d.NodeConfig.ListenAddress != "" && d.NodeConfig.ListenAddress != listAddr {
128185
out.Step(style.Tip, "minikube is not meant for production use. You are opening non-local traffic")
129186
out.WarningT("Listening to {{.listenAddr}}. This is not recommended and can cause a security vulnerability. Use at your own risk",
130187
out.V{"listenAddr": d.NodeConfig.ListenAddress})
131188
listAddr = d.NodeConfig.ListenAddress
132189
} else if oci.IsExternalDaemonHost(drv) {
133-
out.WarningT("Listening to 0.0.0.0 on external docker host {{.host}}. Please be advised",
134-
out.V{"host": oci.DaemonHost(drv)})
135-
listAddr = "0.0.0.0"
190+
if params.IPFamily == "ipv6" {
191+
out.WarningT("Listening to :: on external docker host {{.host}}. Please be advised",
192+
out.V{"host": oci.DaemonHost(drv)})
193+
listAddr = "::"
194+
} else {
195+
out.WarningT("Listening to 0.0.0.0 on external docker host {{.host}}. Please be advised",
196+
out.V{"host": oci.DaemonHost(drv)})
197+
listAddr = "0.0.0.0"
198+
}
136199
}
137200

138201
// control plane specific options
@@ -293,18 +356,38 @@ func (d *Driver) DriverName() string {
293356

294357
// GetIP returns an IP or hostname that this host is available at
295358
func (d *Driver) GetIP() (string, error) {
296-
ip, _, err := oci.ContainerIPs(d.OCIBinary, d.MachineName)
297-
return ip, err
359+
ip4, ip6, err := oci.ContainerIPs(d.OCIBinary, d.MachineName)
360+
if err != nil {
361+
return "", err
362+
}
363+
switch strings.ToLower(d.NodeConfig.IPFamily) {
364+
case "ipv6":
365+
if ip6 != "" {
366+
return ip6, nil
367+
}
368+
}
369+
// default / dual prefers IPv4 for backward compat
370+
return ip4, nil
298371
}
299372

300373
// GetExternalIP returns an IP which is accessible from outside
301374
func (d *Driver) GetExternalIP() (string, error) {
302-
return oci.DaemonHost(d.DriverName()), nil
375+
host := oci.DaemonHost(d.DriverName())
376+
// For local daemons and IPv6-only clusters, ports are published on ::1
377+
if strings.ToLower(d.NodeConfig.IPFamily) == "ipv6" && !oci.IsExternalDaemonHost(d.DriverName()) {
378+
return "::1", nil
379+
}
380+
return host, nil
303381
}
304382

305383
// GetSSHHostname returns hostname for use with ssh
306384
func (d *Driver) GetSSHHostname() (string, error) {
307-
return oci.DaemonHost(d.DriverName()), nil
385+
host := oci.DaemonHost(d.DriverName())
386+
// For local daemons and IPv6-only clusters, ports are published on ::1
387+
if strings.ToLower(d.NodeConfig.IPFamily) == "ipv6" && !oci.IsExternalDaemonHost(d.DriverName()) {
388+
return "::1", nil
389+
}
390+
return host, nil
308391
}
309392

310393
// GetSSHPort returns port for use with ssh

pkg/drivers/kic/oci/network.go

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,41 @@ func RoutableHostIPFromInside(ociBin string, clusterName string, containerName s
8888
// digDNS will get the IP record for a dns
8989
func digDNS(ociBin, containerName, dns string) (net.IP, error) {
9090
rr, err := runCmd(exec.Command(ociBin, "exec", "-t", containerName, "dig", "+short", dns))
91-
ip := net.ParseIP(strings.TrimSpace(rr.Stdout.String()))
9291
if err != nil {
93-
return ip, errors.Wrapf(err, "resolve dns to ip")
92+
// still try to parse whatever output we got
93+
klog.Infof("dig returned error, attempting to parse output anyway: %v", err)
9494
}
95-
96-
klog.Infof("got host ip for mount in container by digging dns: %s", ip.String())
97-
return ip, nil
95+
out := strings.TrimSpace(rr.Stdout.String())
96+
if out == "" {
97+
return nil, errors.Wrapf(err, "resolve dns to ip")
98+
}
99+
// Parse line-by-line. On non-Linux (Docker Desktop), prefer IPv4 for better routability.
100+
var firstIP net.IP
101+
for _, line := range strings.Split(out, "\n") {
102+
s := strings.TrimSpace(line)
103+
if s == "" {
104+
continue
105+
}
106+
ip := net.ParseIP(s)
107+
if ip == nil {
108+
continue
109+
}
110+
if runtime.GOOS != "linux" && ip.To4() == nil {
111+
// Prefer IPv4 on Desktop; keep looking for an A record
112+
if firstIP == nil {
113+
firstIP = ip
114+
}
115+
continue
116+
}
117+
klog.Infof("got host ip for mount in container by digging dns: %s", ip.String())
118+
return ip, nil
119+
}
120+
// Fallback: return first valid IP if only AAAA answers were present
121+
if firstIP != nil {
122+
klog.Infof("got host ip for mount in container by digging dns (first match): %s", firstIP.String())
123+
return firstIP, nil
124+
}
125+
return nil, errors.New("no A/AAAA answers returned by dig")
98126
}
99127

100128
// gatewayIP inspects oci container to find a gateway IP string
@@ -107,6 +135,14 @@ func gatewayIP(ociBin, containerName string) (string, error) {
107135
return gatewayIP, nil
108136
}
109137

138+
// Fallback to IPv6 gateway (needed for IPv6-only / dual-stack)
139+
rr6, err6 := runCmd(exec.Command(ociBin, "container", "inspect", "--format", "{{.NetworkSettings.IPv6Gateway}}", containerName))
140+
if err6 == nil {
141+
if gatewayIP6 := strings.TrimSpace(rr6.Stdout.String()); gatewayIP6 != "" {
142+
return gatewayIP6, nil
143+
}
144+
}
145+
110146
// https://github.com/kubernetes/minikube/issues/11293
111147
// need to check nested network
112148
// check .NetworkSettings.Networks["cluster-name"].Gateway and then
@@ -126,16 +162,24 @@ func gatewayIP(ociBin, containerName string) (string, error) {
126162
}
127163

128164
func networkGateway(ociBin, container, network string) (string, error) {
129-
format := fmt.Sprintf(`
130-
{{ if index .NetworkSettings.Networks %q}}
131-
{{(index .NetworkSettings.Networks %q).Gateway}}
132-
{{ end }}
133-
`, network, network)
134-
rr, err := runCmd(exec.Command(ociBin, "container", "inspect", "--format", format, container))
165+
// First try IPv4 gateway on the specific network
166+
format4 := fmt.Sprintf(`{{ if index .NetworkSettings.Networks %q}}{{(index .NetworkSettings.Networks %q).Gateway}}{{ end }}`, network, network)
167+
rr, err := runCmd(exec.Command(ociBin, "container", "inspect", "--format", format4, container))
135168
if err != nil {
136169
return "", errors.Wrapf(err, "inspect gateway")
137170
}
138-
return strings.TrimSpace(rr.Stdout.String()), nil
171+
172+
gw := strings.TrimSpace(rr.Stdout.String())
173+
if gw != "" {
174+
return gw, nil
175+
}
176+
// Fallback to IPv6 gateway
177+
format6 := fmt.Sprintf(`{{ if index .NetworkSettings.Networks %q}}{{(index .NetworkSettings.Networks %q).IPv6Gateway}}{{ end }}`, network, network)
178+
rr6, err := runCmd(exec.Command(ociBin, "container", "inspect", "--format", format6, container))
179+
if err != nil {
180+
return "", errors.Wrapf(err, "inspect ipv6 gateway")
181+
}
182+
return strings.TrimSpace(rr6.Stdout.String()), nil
139183
}
140184

141185
// containerGatewayIP gets the default gateway ip for the container
@@ -188,9 +232,8 @@ func ForwardedPort(ociBin string, ociID string, contPort int) (int, error) {
188232
o := strings.TrimSpace(rr.Stdout.String())
189233
o = strings.Trim(o, "'")
190234
p, err := strconv.Atoi(o)
191-
192235
if err != nil {
193-
return p, errors.Wrapf(err, "convert host-port %q to number", p)
236+
return 0, errors.Wrapf(err, "convert host-port %q to number", o)
194237
}
195238

196239
return p, nil

0 commit comments

Comments
 (0)