Merge pull request #16789 from spowelljr/detectBlock

QEMU: Unblock bootpd if start fails to due to blocking
pull/16867/head
Steven Powell 2023-07-11 15:40:52 -07:00 committed by GitHub
commit 926cfe4469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 64 deletions

View File

@ -47,6 +47,7 @@ import (
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
"k8s.io/minikube/pkg/minikube/command" "k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/firewall"
netutil "k8s.io/minikube/pkg/network" netutil "k8s.io/minikube/pkg/network"
"k8s.io/klog/v2" "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") 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" { 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.") 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) { func validateBuiltImageVersion(r command.Runner, driverName string) {
if driver.IsNone(driverName) { if driver.IsNone(driverName) {
return return

View File

@ -40,9 +40,13 @@ import (
"github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/state"
"github.com/pkg/errors" "github.com/pkg/errors"
"k8s.io/klog/v2"
pkgdrivers "k8s.io/minikube/pkg/drivers" pkgdrivers "k8s.io/minikube/pkg/drivers"
"k8s.io/minikube/pkg/minikube/exit" "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/reason"
"k8s.io/minikube/pkg/minikube/style"
"k8s.io/minikube/pkg/network" "k8s.io/minikube/pkg/network"
) )
@ -515,13 +519,19 @@ func (d *Driver) Start() error {
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
} }
if err != nil { if err == nil {
if isBootpdError(err) { log.Debugf("IP: %s", d.IPAddress)
exit.Error(reason.IfBootpdFirewall, "ip not found", err) break
} }
if !isBootpdError(err) {
return errors.Wrap(err, "IP address never found in dhcp leases file") 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) log.Infof("Waiting for VM to start (ssh -p %d docker@%s)...", d.SSHPort, d.IPAddress)

View File

@ -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
}