Skip to content
141 changes: 139 additions & 2 deletions cmd/minikube/cmd/start_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cmd

import (
"fmt"
"net"
"runtime"
"strings"
"time"
Expand Down Expand Up @@ -61,6 +62,7 @@ const (
kubernetesVersion = "kubernetes-version"
noKubernetes = "no-kubernetes"
hostOnlyCIDR = "host-only-cidr"
hostOnlyCIDRv6 = "host-only-cidr-v6"
containerRuntime = "container-runtime"
criSocket = "cri-socket"
networkPlugin = "network-plugin" // deprecated, use --cni instead
Expand All @@ -84,6 +86,8 @@ const (
apiServerPort = "apiserver-port"
dnsDomain = "dns-domain"
serviceCIDR = "service-cluster-ip-range"
serviceCIDRv6 = "service-cluster-ip-range-v6"
ipFamily = "ip-family"
imageRepository = "image-repository"
imageMirrorCountry = "image-mirror-country"
mountString = "mount-string"
Expand Down Expand Up @@ -144,8 +148,12 @@ const (
socketVMnetClientPath = "socket-vmnet-client-path"
socketVMnetPath = "socket-vmnet-path"
staticIP = "static-ip"
staticIPv6 = "static-ipv6"
gpus = "gpus"
autoPauseInterval = "auto-pause-interval"
subnetv6 = "subnet-v6"
podCIDR = "pod-cidr"
podCIDRv6 = "pod-cidr-v6"
)

var (
Expand Down Expand Up @@ -209,6 +217,7 @@ func initMinikubeFlags() {
startCmd.Flags().Bool(disableMetrics, false, "If set, disables metrics reporting (CPU and memory usage), this can improve CPU usage. Defaults to false.")
startCmd.Flags().Bool(disableCoreDNSLog, false, "If set, disable CoreDNS verbose logging. Defaults to false.")
startCmd.Flags().String(staticIP, "", "Set a static IP for the minikube cluster, the IP must be: private, IPv4, and the last octet must be between 2 and 254, for example 192.168.200.200 (Docker and Podman drivers only)")
startCmd.Flags().String(staticIPv6, "", "Set a static IPv6 address for the minikube cluster, for example fd00::100 (Docker and Podman drivers only)")
startCmd.Flags().StringP(gpus, "g", "", "Allow pods to use your GPUs. Options include: [all,nvidia,amd] (Docker driver with Docker container-runtime only)")
startCmd.Flags().Duration(autoPauseInterval, time.Minute*1, "Duration of inactivity before the minikube VM is paused (default 1m0s)")
}
Expand Down Expand Up @@ -260,6 +269,7 @@ func initDriverFlags() {

// virtualbox
startCmd.Flags().String(hostOnlyCIDR, "192.168.59.1/24", "The CIDR to be used for the minikube VM (virtualbox driver only)")
startCmd.Flags().String(hostOnlyCIDRv6, "fd00::1/64", "The IPv6 CIDR to be used for the minikube VM (virtualbox driver only)")
startCmd.Flags().Bool(dnsProxy, false, "Enable proxy for NAT DNS requests (virtualbox driver only)")
startCmd.Flags().Bool(hostDNSResolver, true, "Enable host resolver for NAT DNS requests (virtualbox driver only)")
startCmd.Flags().Bool(noVTXCheck, false, "Disable checking for the availability of hardware virtualization before the vm is started (virtualbox driver only)")
Expand All @@ -282,6 +292,9 @@ func initDriverFlags() {
startCmd.Flags().String(listenAddress, "", "IP Address to use to expose ports (docker and podman driver only)")
startCmd.Flags().StringSlice(ports, []string{}, "List of ports that should be exposed (docker and podman driver only)")
startCmd.Flags().String(subnet, "", "Subnet to be used on kic cluster. If left empty, minikube will choose subnet address, beginning from 192.168.49.0. (docker and podman driver only)")
startCmd.Flags().String(subnetv6, "", "IPv6 subnet (CIDR) for the Docker/Podman network. If empty, minikube picks an internal ULA. (docker and podman driver only)")
startCmd.Flags().String(podCIDR, "", "IPv4 CIDR to use for pod IPs (bridge CNI).")
startCmd.Flags().String(podCIDRv6, "", "IPv6 CIDR to use for pod IPs (bridge CNI).")

// qemu
startCmd.Flags().String(qemuFirmwarePath, "", "Path to the qemu firmware file. Defaults: For Linux, the default firmware location. For macOS, the brew installation location. For Windows, C:\\Program Files\\qemu\\share")
Expand All @@ -293,7 +306,9 @@ func initNetworkingFlags() {
startCmd.Flags().StringSliceVar(&registryMirror, "registry-mirror", nil, "Registry mirrors to pass to the Docker daemon")
startCmd.Flags().String(imageRepository, "", "Alternative image repository to pull docker images from. This can be used when you have limited access to gcr.io. Set it to \"auto\" to let minikube decide one for you. For Chinese mainland users, you may use local gcr.io mirrors such as registry.cn-hangzhou.aliyuncs.com/google_containers")
startCmd.Flags().String(imageMirrorCountry, "", "Country code of the image mirror to be used. Leave empty to use the global one. For Chinese mainland users, set it to cn.")
startCmd.Flags().String(serviceCIDR, constants.DefaultServiceCIDR, "The CIDR to be used for service cluster IPs.")
startCmd.Flags().String(serviceCIDR, constants.DefaultServiceCIDR, "The IPv4 CIDR to be used for service cluster IPs.")
startCmd.Flags().String(serviceCIDRv6, constants.DefaultServiceCIDRv6, "The IPv6 CIDR to be used for service cluster IPs.")
startCmd.Flags().String(ipFamily, "ipv4", "Cluster IP family mode: one of 'ipv4' (default), 'ipv6', or 'dual'.")
startCmd.Flags().StringArrayVar(&config.DockerEnv, "docker-env", nil, "Environment variables to pass to the Docker daemon. (format: key=value)")
startCmd.Flags().StringArrayVar(&config.DockerOpt, "docker-opt", nil, "Specify arbitrary flags to pass to the Docker daemon. (format: key=value)")

Expand Down Expand Up @@ -494,6 +509,112 @@ func getNetwork(driverName string, options *run.CommandOptions) string {
return n
}

// normalizeAndValidateIPFamily sets defaults, validates CIDRs, and adds
// guardrails for IPv6/dual-stack on Docker.
func normalizeAndValidateIPFamily(cc *config.ClusterConfig) {
fam := strings.ToLower(strings.TrimSpace(cc.KubernetesConfig.IPFamily))
switch fam {
case "", "ipv4", "ipv6", "dual":
// ok
default:
exit.Message(reason.Usage,
"Invalid --ip-family {{.fam}}. Must be one of: ipv4, ipv6, dual.",
out.V{"fam": cc.KubernetesConfig.IPFamily})
}

// Default to ipv4 to keep existing behavior for old configs.
if fam == "" {
fam = "ipv4"
cc.KubernetesConfig.IPFamily = fam
}

// IPv6-capable families: ensure v6 defaults.
if fam != "ipv4" {
if cc.KubernetesConfig.ServiceCIDRv6 == "" {
cc.KubernetesConfig.ServiceCIDRv6 = constants.DefaultServiceCIDRv6
}
if cc.KubernetesConfig.PodCIDRv6 == "" {
cc.KubernetesConfig.PodCIDRv6 = constants.DefaultPodCIDRv6
}
}

// For ipv4 or dual-stack, keep the usual IPv4 pod CIDR default if none was set.
if fam != "ipv6" && cc.KubernetesConfig.PodCIDR == "" {
cc.KubernetesConfig.PodCIDR = cni.DefaultPodCIDR
}

// --- CIDR / address validation ---

// Helper: ensure a CIDR is v4 or v6 as expected.
checkCIDR := func(flag string, cidr string, wantV6 bool) {
ip, _, err := net.ParseCIDR(cidr)
if err != nil {
exit.Message(reason.Usage, "{{.flag}} must be a valid CIDR: {{.e}}",
out.V{"flag": flag, "e": err})
}
isV4 := ip.To4() != nil
if wantV6 && isV4 {
exit.Message(reason.Usage, "{{.flag}} must be a valid IPv6 CIDR: {{.e}}",
out.V{"flag": flag, "e": err})
}
if !wantV6 && !isV4 {
exit.Message(reason.Usage, "{{.flag}} must be a valid IPv4 CIDR: {{.e}}",
out.V{"flag": flag, "e": err})
}
}

// IPv6-only CIDR flags.
if cidr := cc.Subnetv6; cidr != "" {
checkCIDR("--subnet-v6", cidr, true)
}
if cidr := cc.KubernetesConfig.ServiceCIDRv6; cidr != "" {
checkCIDR("--service-cluster-ip-range-v6", cidr, true)
}
if cidr := cc.KubernetesConfig.PodCIDRv6; cidr != "" {
checkCIDR("--pod-cidr-v6", cidr, true)
}

// IPv4-only pod CIDR.
if cidr := cc.KubernetesConfig.PodCIDR; cidr != "" {
checkCIDR("--pod-cidr", cidr, false)
}

// validate static IPv6 if provided
if s := cc.StaticIPv6; s != "" {
ip := net.ParseIP(s)
if ip == nil || ip.To4() != nil {
exit.Message(reason.Usage, "--static-ipv6 must be a valid IPv6 address")
}
}

// Docker driver guardrails: Linux daemon for IPv6, warn on Desktop.
if driver.IsDocker(cc.Driver) && fam != "ipv4" {
// Desktop vs Linux daemon hint (we can't reliably detect IPv6 enabled here).
si, err := oci.CachedDaemonInfo(cc.Driver)
if err != nil {
si, err = oci.DaemonInfo(cc.Driver)
if err != nil {
exit.Message(reason.Usage, "Failed to query Docker daemon info: {{.e}}", out.V{"e": err})
}
}
// On non-Linux hosts we assume Docker Desktop; on Linux it's a native Engine
// unless DockerOS explicitly says "Docker Desktop".
isLinuxDaemon := runtime.GOOS == "linux" && si.DockerOS != "Docker Desktop"
if !isLinuxDaemon {
if fam == "ipv6" {
exit.Message(reason.Usage,
"IPv6 clusters require a Linux Docker daemon (Desktop is not supported). "+
"Use a Linux/WSL2 daemon or set --ip-family=ipv4.")
}
out.WarningT("Dual-stack on Docker Desktop may be limited. For full IPv6 support, use a Linux Docker daemon.")
}
// Friendly reminder about enabling daemon IPv6 (actual failure will occur during
// network create if the daemon/network really blocks IPv6 bridge networks).
out.Styled(style.Tip,
"If your Docker daemon/network blocks IPv6 bridge networks, enable IPv6 in /etc/docker/daemon.json and restart:\n {\"ipv6\": true, \"fixed-cidr-v6\": \"fd00:55:66::/64\"}")
}
}

func validateQemuNetwork(n string) string {
switch n {
case "socket_vmnet":
Expand Down Expand Up @@ -576,6 +697,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
KicBaseImage: viper.GetString(kicBaseImage),
Network: getNetwork(drvName, options),
Subnet: viper.GetString(subnet),
Subnetv6: viper.GetString(subnetv6),
Memory: getMemorySize(cmd, drvName),
CPUs: getCPUCount(drvName),
DiskSize: getDiskSize(),
Expand All @@ -590,6 +712,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
InsecureRegistry: insecureRegistry,
RegistryMirror: registryMirror,
HostOnlyCIDR: viper.GetString(hostOnlyCIDR),
HostOnlyCIDRv6: viper.GetString(hostOnlyCIDRv6),
HypervVirtualSwitch: viper.GetString(hypervVirtualSwitch),
HypervUseExternalSwitch: viper.GetBool(hypervUseExternalSwitch),
HypervExternalAdapter: viper.GetString(hypervExternalAdapter),
Expand Down Expand Up @@ -631,6 +754,7 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
SocketVMnetClientPath: detect.SocketVMNetClientPath(),
SocketVMnetPath: detect.SocketVMNetPath(),
StaticIP: viper.GetString(staticIP),
StaticIPv6: viper.GetString(staticIPv6),
KubernetesConfig: config.KubernetesConfig{
KubernetesVersion: k8sVersion,
ClusterName: ClusterFlagValue(),
Expand All @@ -644,6 +768,10 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
CRISocket: viper.GetString(criSocket),
NetworkPlugin: chosenNetworkPlugin,
ServiceCIDR: viper.GetString(serviceCIDR),
ServiceCIDRv6: viper.GetString(serviceCIDRv6),
PodCIDR: viper.GetString(podCIDR),
PodCIDRv6: viper.GetString(podCIDRv6),
IPFamily: viper.GetString(ipFamily),
ImageRepository: getRepository(cmd, k8sVersion),
ExtraOptions: getExtraOptions(),
ShouldLoadCachedImages: viper.GetBool(cacheImages),
Expand Down Expand Up @@ -697,6 +825,8 @@ func generateNewConfigFromFlags(cmd *cobra.Command, k8sVersion string, rtime str
}
}

normalizeAndValidateIPFamily(&cc)

return cc
}

Expand Down Expand Up @@ -831,11 +961,15 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC
updateStringFromFlag(cmd, &cc.MinikubeISO, isoURL)
updateStringFromFlag(cmd, &cc.KicBaseImage, kicBaseImage)
updateStringFromFlag(cmd, &cc.Network, network)
updateStringFromFlag(cmd, &cc.Subnetv6, subnetv6)
updateStringFromFlag(cmd, &cc.KubernetesConfig.PodCIDR, podCIDR)
updateStringFromFlag(cmd, &cc.KubernetesConfig.PodCIDRv6, podCIDRv6)
updateStringFromFlag(cmd, &cc.HyperkitVpnKitSock, vpnkitSock)
updateStringSliceFromFlag(cmd, &cc.HyperkitVSockPorts, vsockPorts)
updateStringSliceFromFlag(cmd, &cc.NFSShare, nfsShare)
updateStringFromFlag(cmd, &cc.NFSSharesRoot, nfsSharesRoot)
updateStringFromFlag(cmd, &cc.HostOnlyCIDR, hostOnlyCIDR)
updateStringFromFlag(cmd, &cc.HostOnlyCIDRv6, hostOnlyCIDRv6)
updateStringFromFlag(cmd, &cc.HypervVirtualSwitch, hypervVirtualSwitch)
updateBoolFromFlag(cmd, &cc.HypervUseExternalSwitch, hypervUseExternalSwitch)
updateStringFromFlag(cmd, &cc.HypervExternalAdapter, hypervExternalAdapter)
Expand All @@ -853,6 +987,7 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC
updateDurationFromFlag(cmd, &cc.StartHostTimeout, waitTimeout)
updateStringSliceFromFlag(cmd, &cc.ExposedPorts, ports)
updateStringFromFlag(cmd, &cc.SSHIPAddress, sshIPAddress)
updateStringFromFlag(cmd, &cc.StaticIPv6, staticIPv6)
updateStringFromFlag(cmd, &cc.SSHUser, sshSSHUser)
updateStringFromFlag(cmd, &cc.SSHKey, sshSSHKey)
updateIntFromFlag(cmd, &cc.SSHPort, sshSSHPort)
Expand All @@ -865,6 +1000,8 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC
updateStringFromFlag(cmd, &cc.KubernetesConfig.CRISocket, criSocket)
updateStringFromFlag(cmd, &cc.KubernetesConfig.NetworkPlugin, networkPlugin)
updateStringFromFlag(cmd, &cc.KubernetesConfig.ServiceCIDR, serviceCIDR)
updateStringFromFlag(cmd, &cc.KubernetesConfig.ServiceCIDRv6, serviceCIDRv6)
updateStringFromFlag(cmd, &cc.KubernetesConfig.IPFamily, ipFamily)
updateBoolFromFlag(cmd, &cc.KubernetesConfig.ShouldLoadCachedImages, cacheImages)
updateDurationFromFlag(cmd, &cc.CertExpiration, certExpiration)
updateStringFromFlag(cmd, &cc.MountString, mountString)
Expand Down Expand Up @@ -921,7 +1058,7 @@ func updateExistingConfigFromFlags(cmd *cobra.Command, existing *config.ClusterC
if cc.ScheduledStop != nil && time.Until(time.Unix(cc.ScheduledStop.InitiationTime, 0).Add(cc.ScheduledStop.Duration)) <= 0 {
cc.ScheduledStop = nil
}

normalizeAndValidateIPFamily(&cc)
return cc
}

Expand Down
Loading