Skip to content

Commit 1939182

Browse files
committed
kic: plumb ip-family and ipv6 for docker bridge
1 parent 40967e8 commit 1939182

File tree

8 files changed

+487
-103
lines changed

8 files changed

+487
-103
lines changed

pkg/drivers/kic/kic.go

Lines changed: 97 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"
@@ -99,40 +101,93 @@ func (d *Driver) Create() error {
99101
networkName = d.NodeConfig.ClusterName
100102
}
101103
staticIP := d.NodeConfig.StaticIP
102-
if gateway, err := oci.CreateNetwork(d.OCIBinary, networkName, d.NodeConfig.Subnet, staticIP); err != nil {
104+
// NEW: create network with IPv6/dual awareness
105+
gateway, err := oci.CreateNetworkWithIPFamily(
106+
d.OCIBinary,
107+
networkName,
108+
d.NodeConfig.Subnet,
109+
d.NodeConfig.Subnetv6, // NEW
110+
staticIP,
111+
d.NodeConfig.StaticIPv6, // NEW
112+
params.IPFamily, // NEW
113+
)
114+
if err != nil {
103115
msg := "Unable to create dedicated network, this might result in cluster IP change after restart: {{.error}}"
104116
args := out.V{"error": err}
105117
if staticIP != "" {
106118
exit.Message(reason.IfDedicatedNetwork, msg, args)
107119
}
108120
out.WarningT(msg, args)
109-
} else if gateway != nil && staticIP != "" {
110-
params.Network = networkName
111-
params.IP = staticIP
112-
} else if gateway != nil {
113-
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")
121+
}
122+
// Always attach to the created user network (even if gateway is nil in IPv6-only)
123+
params.Network = networkName
124+
125+
// Now decide static IPs per family
126+
switch params.IPFamily {
127+
case "ipv6":
128+
if d.NodeConfig.StaticIPv6 != "" {
129+
params.IPv6 = d.NodeConfig.StaticIPv6
130+
}
131+
case "dual":
132+
// IPv4 part (only if Docker reported a v4 gateway)
133+
if g4 := gateway.To4(); g4 != nil {
134+
if staticIP != "" {
135+
params.IP = staticIP
136+
} else {
137+
ip := make(net.IP, len(g4))
138+
copy(ip, g4)
139+
index := driver.IndexFromMachineName(d.NodeConfig.MachineName)
140+
if int(ip[3])+index > 253 {
141+
return fmt.Errorf("too many machines to calculate an IPv4")
142+
}
143+
ip[3] += byte(index)
144+
klog.Infof("calculated static IPv4 %q for the %q container", ip.String(), d.NodeConfig.MachineName)
145+
params.IP = ip.String()
146+
}
147+
}
148+
if d.NodeConfig.StaticIPv6 != "" {
149+
params.IPv6 = d.NodeConfig.StaticIPv6
150+
}
151+
default: // ipv4
152+
if staticIP != "" {
153+
params.IP = staticIP
154+
} else if gateway != nil {
155+
if g4 := gateway.To4(); g4 != nil {
156+
ip := make(net.IP, len(g4))
157+
copy(ip, g4)
158+
index := driver.IndexFromMachineName(d.NodeConfig.MachineName)
159+
if int(ip[3])+index > 253 {
160+
return fmt.Errorf("too many machines to calculate an IP")
161+
}
162+
ip[3] += byte(index)
163+
klog.Infof("calculated static IP %q for the %q container", ip.String(), d.NodeConfig.MachineName)
164+
params.IP = ip.String()
165+
}
119166
}
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()
123167
}
124168
drv := d.DriverName()
125-
169+
// Default listen address: v4 localhost for ipv4, v6 localhost for ipv6-only
126170
listAddr := oci.DefaultBindIPV4
171+
// IPv6-only clusters must publish on IPv6 loopback so the host can reach them
172+
if params.IPFamily == "ipv6" {
173+
listAddr = "::1"
174+
}
175+
127176
if d.NodeConfig.ListenAddress != "" && d.NodeConfig.ListenAddress != listAddr {
128177
out.Step(style.Tip, "minikube is not meant for production use. You are opening non-local traffic")
129178
out.WarningT("Listening to {{.listenAddr}}. This is not recommended and can cause a security vulnerability. Use at your own risk",
130179
out.V{"listenAddr": d.NodeConfig.ListenAddress})
131180
listAddr = d.NodeConfig.ListenAddress
132181
} 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"
182+
if params.IPFamily == "ipv6" {
183+
out.WarningT("Listening to :: on external docker host {{.host}}. Please be advised",
184+
out.V{"host": oci.DaemonHost(drv)})
185+
listAddr = "::"
186+
} else {
187+
out.WarningT("Listening to 0.0.0.0 on external docker host {{.host}}. Please be advised",
188+
out.V{"host": oci.DaemonHost(drv)})
189+
listAddr = "0.0.0.0"
190+
}
136191
}
137192

138193
// control plane specific options
@@ -293,18 +348,38 @@ func (d *Driver) DriverName() string {
293348

294349
// GetIP returns an IP or hostname that this host is available at
295350
func (d *Driver) GetIP() (string, error) {
296-
ip, _, err := oci.ContainerIPs(d.OCIBinary, d.MachineName)
297-
return ip, err
351+
ip4, ip6, err := oci.ContainerIPs(d.OCIBinary, d.MachineName)
352+
if err != nil {
353+
return "", err
354+
}
355+
switch strings.ToLower(d.NodeConfig.IPFamily) {
356+
case "ipv6":
357+
if ip6 != "" {
358+
return ip6, nil
359+
}
360+
}
361+
// default / dual prefers IPv4 for backward compat
362+
return ip4, nil
298363
}
299364

300365
// GetExternalIP returns an IP which is accessible from outside
301366
func (d *Driver) GetExternalIP() (string, error) {
302-
return oci.DaemonHost(d.DriverName()), nil
367+
host := oci.DaemonHost(d.DriverName())
368+
// For local daemons and IPv6-only clusters, ports are published on ::1
369+
if strings.ToLower(d.NodeConfig.IPFamily) == "ipv6" && !oci.IsExternalDaemonHost(d.DriverName()) {
370+
return "::1", nil
371+
}
372+
return host, nil
303373
}
304374

305375
// GetSSHHostname returns hostname for use with ssh
306376
func (d *Driver) GetSSHHostname() (string, error) {
307-
return oci.DaemonHost(d.DriverName()), nil
377+
host := oci.DaemonHost(d.DriverName())
378+
// For local daemons and IPv6-only clusters, ports are published on ::1
379+
if strings.ToLower(d.NodeConfig.IPFamily) == "ipv6" && !oci.IsExternalDaemonHost(d.DriverName()) {
380+
return "::1", nil
381+
}
382+
return host, nil
308383
}
309384

310385
// 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)