Skip to content

Commit

Permalink
Merge pull request #16789 from spowelljr/detectBlock
Browse files Browse the repository at this point in the history
QEMU: Unblock bootpd if start fails to due to blocking
  • Loading branch information
spowelljr committed Jul 11, 2023
2 parents 46eebd4 + 233e5b6 commit 926cfe4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 64 deletions.
65 changes: 6 additions & 59 deletions cmd/minikube/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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
Expand Down
20 changes: 15 additions & 5 deletions pkg/drivers/qemu/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand Down
87 changes: 87 additions & 0 deletions pkg/minikube/firewall/firewall.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 926cfe4

Please sign in to comment.