diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 116336d0f9..a371d74f32 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -47,6 +47,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" "k8s.io/minikube/pkg/minikube/command" + "k8s.io/minikube/pkg/minikube/firewall" netutil "k8s.io/minikube/pkg/network" "k8s.io/klog/v2" @@ -339,7 +340,11 @@ func provisionWithDriver(cmd *cobra.Command, ds registry.DriverState, existing * return node.Starter{}, errors.Wrap(err, "Failed to generate config") } - unblockBootpdFirewall(cc) + if firewall.IsBootpdBlocked(cc) { + if err := firewall.UnblockBootpd(); err != nil { + klog.Warningf("failed unblocking bootpd from firewall: %v", err) + } + } if driver.IsVM(cc.Driver) && runtime.GOARCH == "arm64" && cc.KubernetesConfig.ContainerRuntime == "crio" { exit.Message(reason.Unimplemented, "arm64 VM drivers do not currently support the crio container runtime. See https://github.com/kubernetes/minikube/issues/14146 for details.") @@ -416,64 +421,6 @@ func vmwareUnsupported(driverName string) { `) } -// isBootpdBlocked returns true if the built-in macOS firewall is on and bootpd is not unblocked -func isBootpdBlocked(cc config.ClusterConfig) bool { - // only applies to qemu, on macOS, with socket_vmnet - if cc.Driver != driver.QEMU2 || runtime.GOOS != "darwin" || cc.Network != "socket_vmnet" { - return false - } - out, err := exec.Command("/usr/libexec/ApplicationFirewall/socketfilterfw", "--getglobalstate").Output() - if err != nil { - klog.Warningf("failed to get firewall state: %v", err) - return false - } - if regexp.MustCompile(`Firewall is disabled`).Match(out) { - return false - } - out, err = exec.Command("/usr/libexec/ApplicationFirewall/socketfilterfw", "--listapps").Output() - if err != nil { - klog.Warningf("failed to list firewall apps: %v", err) - return false - } - return !regexp.MustCompile(`\/usr\/libexec\/bootpd.*\n.*\( Allow`).Match(out) -} - -// unblockBootpdFirewall adds bootpd to the built-in macOS firewall and then unblocks it -func unblockBootpdFirewall(cc config.ClusterConfig) { - if !isBootpdBlocked(cc) { - return - } - - cmds := []*exec.Cmd{ - exec.Command("sudo", "/usr/libexec/ApplicationFirewall/socketfilterfw", "--add", "/usr/libexec/bootpd"), - exec.Command("sudo", "/usr/libexec/ApplicationFirewall/socketfilterfw", "--unblock", "/usr/libexec/bootpd"), - } - - var cmdString strings.Builder - for _, c := range cmds { - cmdString.WriteString(fmt.Sprintf(" $ %s \n", strings.Join(c.Args, " "))) - } - - out.Styled(style.Permissions, "Your firewall is blocking bootpd which is required for socket_vmnet. The following commands will be executed to unblock bootpd:\n\n{{.commands}}\n", out.V{"commands": cmdString.String()}) - - for _, c := range cmds { - testArgs := append([]string{"-n"}, c.Args[1:]...) - test := exec.Command("sudo", testArgs...) - klog.Infof("testing: %s", test.Args) - if err := test.Run(); err != nil { - klog.Infof("%v may require a password: %v", c.Args, err) - if !viper.GetBool("interactive") { - klog.Warningf("%s requires a password, and --interactive=false", c.Args) - } - } - klog.Infof("running: %s", c.Args) - err := c.Run() - if err != nil { - klog.Warningf("running %s failed: %v", c.Args, err) - } - } -} - func validateBuiltImageVersion(r command.Runner, driverName string) { if driver.IsNone(driverName) { return diff --git a/pkg/drivers/qemu/qemu.go b/pkg/drivers/qemu/qemu.go index 0bd533ab96..f9c1059f88 100644 --- a/pkg/drivers/qemu/qemu.go +++ b/pkg/drivers/qemu/qemu.go @@ -40,9 +40,13 @@ import ( "github.com/docker/machine/libmachine/state" "github.com/pkg/errors" + "k8s.io/klog/v2" pkgdrivers "k8s.io/minikube/pkg/drivers" "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/firewall" + "k8s.io/minikube/pkg/minikube/out" "k8s.io/minikube/pkg/minikube/reason" + "k8s.io/minikube/pkg/minikube/style" "k8s.io/minikube/pkg/network" ) @@ -515,13 +519,19 @@ func (d *Driver) Start() error { time.Sleep(2 * time.Second) } - if err != nil { - if isBootpdError(err) { - exit.Error(reason.IfBootpdFirewall, "ip not found", err) - } + if err == nil { + log.Debugf("IP: %s", d.IPAddress) + break + } + if !isBootpdError(err) { return errors.Wrap(err, "IP address never found in dhcp leases file") } - log.Debugf("IP: %s", d.IPAddress) + if unblockErr := firewall.UnblockBootpd(); unblockErr != nil { + klog.Errorf("failed unblocking bootpd from firewall: %v", unblockErr) + exit.Error(reason.IfBootpdFirewall, "ip not found", err) + } + out.Styled(style.Restarting, "Successfully unblocked bootpd process from firewall, retrying") + return fmt.Errorf("ip not found: %v", err) } log.Infof("Waiting for VM to start (ssh -p %d docker@%s)...", d.SSHPort, d.IPAddress) diff --git a/pkg/minikube/firewall/firewall.go b/pkg/minikube/firewall/firewall.go new file mode 100644 index 0000000000..f62b311996 --- /dev/null +++ b/pkg/minikube/firewall/firewall.go @@ -0,0 +1,87 @@ +/* +Copyright 2023 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package firewall + +import ( + "fmt" + "os/exec" + "regexp" + "runtime" + "strings" + + "github.com/spf13/viper" + "k8s.io/klog/v2" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/driver" + "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/style" +) + +// IsBootpdBlocked checks if the bootpd process is blocked by the macOS builtin firewall +func IsBootpdBlocked(cc config.ClusterConfig) bool { + // only applies to qemu, on macOS, with socket_vmnet + if cc.Driver != driver.QEMU2 || runtime.GOOS != "darwin" || cc.Network != "socket_vmnet" { + return false + } + out, err := exec.Command("/usr/libexec/ApplicationFirewall/socketfilterfw", "--getglobalstate").Output() + if err != nil { + klog.Warningf("failed to get firewall state: %v", err) + return false + } + if regexp.MustCompile(`Firewall is disabled`).Match(out) { + return false + } + out, err = exec.Command("/usr/libexec/ApplicationFirewall/socketfilterfw", "--listapps").Output() + if err != nil { + klog.Warningf("failed to list firewall apps: %v", err) + return false + } + return !regexp.MustCompile(`\/usr\/libexec\/bootpd.*\n.*\( Allow`).Match(out) +} + +// UnblockBootpd adds bootpd to the built-in macOS firewall and then unblocks it +func UnblockBootpd() error { + cmds := []*exec.Cmd{ + exec.Command("sudo", "/usr/libexec/ApplicationFirewall/socketfilterfw", "--add", "/usr/libexec/bootpd"), + exec.Command("sudo", "/usr/libexec/ApplicationFirewall/socketfilterfw", "--unblock", "/usr/libexec/bootpd"), + } + + var cmdString strings.Builder + for _, c := range cmds { + cmdString.WriteString(fmt.Sprintf(" $ %s \n", strings.Join(c.Args, " "))) + } + + out.Styled(style.Permissions, "Your firewall is blocking bootpd which is required for socket_vmnet. The following commands will be executed to unblock bootpd:\n\n{{.commands}}\n", out.V{"commands": cmdString.String()}) + + for _, c := range cmds { + testArgs := append([]string{"-n"}, c.Args[1:]...) + test := exec.Command("sudo", testArgs...) + klog.Infof("testing: %s", test.Args) + if err := test.Run(); err != nil { + klog.Infof("%v may require a password: %v", c.Args, err) + if !viper.GetBool("interactive") { + klog.Warningf("%s requires a password, and --interactive=false", c.Args) + } + } + klog.Infof("running: %s", c.Args) + err := c.Run() + if err != nil { + return fmt.Errorf("running %s failed: %v", c.Args, err) + } + } + return nil +}