Enable port forwarding on host

Using the gvproxy application on the host, we can now port forward from
the machine vm on the host.  It requires that 'gvproxy' be installed in
an executable location.  gvproxy can be found in the
containers/gvisor-tap-vsock github repo.

[NO TESTS NEEDED]

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude 2021-05-07 14:14:59 -05:00
parent 7dd463bad1
commit 7ef3981abe
11 changed files with 169 additions and 21 deletions

View File

@ -85,7 +85,10 @@ func DefineNetFlags(cmd *cobra.Command) {
) )
} }
func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) { // NetFlagsToNetOptions parses the network flags for the given cmd.
// The netnsFromConfig bool is used to indicate if the --network flag
// should always be parsed regardless if it was set on the cli.
func NetFlagsToNetOptions(cmd *cobra.Command, netnsFromConfig bool) (*entities.NetOptions, error) {
var ( var (
err error err error
) )
@ -193,7 +196,9 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
return nil, err return nil, err
} }
if cmd.Flags().Changed("network") { // parse the --network value only when the flag is set or we need to use
// the netns config value, e.g. when --pod is not used
if netnsFromConfig || cmd.Flag("network").Changed {
network, err := cmd.Flags().GetString("network") network, err := cmd.Flags().GetString("network")
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -87,7 +87,7 @@ func create(cmd *cobra.Command, args []string) error {
var ( var (
err error err error
) )
cliVals.Net, err = common.NetFlagsToNetOptions(cmd) cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "")
if err != nil { if err != nil {
return err return err
} }

View File

@ -106,7 +106,7 @@ func init() {
func run(cmd *cobra.Command, args []string) error { func run(cmd *cobra.Command, args []string) error {
var err error var err error
cliVals.Net, err = common.NetFlagsToNetOptions(cmd) cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "")
if err != nil { if err != nil {
return err return err
} }

View File

@ -166,10 +166,11 @@ func create(cmd *cobra.Command, args []string) error {
defer errorhandling.SyncQuiet(podIDFD) defer errorhandling.SyncQuiet(podIDFD)
} }
createOptions.Net, err = common.NetFlagsToNetOptions(cmd) createOptions.Net, err = common.NetFlagsToNetOptions(cmd, createOptions.Infra)
if err != nil { if err != nil {
return err return err
} }
if len(createOptions.Net.PublishPorts) > 0 { if len(createOptions.Net.PublishPorts) > 0 {
if !createOptions.Infra { if !createOptions.Infra {
return errors.Errorf("you must have an infra container to publish port bindings to the host") return errors.Errorf("you must have an infra container to publish port bindings to the host")

View File

@ -273,7 +273,6 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating rootless cni network namespace") return nil, errors.Wrap(err, "error creating rootless cni network namespace")
} }
// setup slirp4netns here // setup slirp4netns here
path := r.config.Engine.NetworkCmdPath path := r.config.Engine.NetworkCmdPath
if path == "" { if path == "" {
@ -437,9 +436,32 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
return rootlessCNINS, nil return rootlessCNINS, nil
} }
// setPrimaryMachineIP is used for podman-machine and it sets
// and environment variable with the IP address of the podman-machine
// host.
func setPrimaryMachineIP() error {
// no connection is actually made here
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return err
}
defer func() {
if err := conn.Close(); err != nil {
logrus.Error(err)
}
}()
addr := conn.LocalAddr().(*net.UDPAddr)
return os.Setenv("PODMAN_MACHINE_HOST", addr.IP.String())
}
// setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni // setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni
// networks. If rootless it will join/create the rootless cni namespace. // networks. If rootless it will join/create the rootless cni namespace.
func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) { func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) {
if r.config.MachineEnabled() {
if err := setPrimaryMachineIP(); err != nil {
return nil, err
}
}
rootlessCNINS, err := r.GetRootlessCNINetNs(true) rootlessCNINS, err := r.GetRootlessCNINetNs(true)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -32,6 +32,7 @@ var (
ErrVMAlreadyExists = errors.New("VM already exists") ErrVMAlreadyExists = errors.New("VM already exists")
ErrVMAlreadyRunning = errors.New("VM already running") ErrVMAlreadyRunning = errors.New("VM already running")
ErrMultipleActiveVM = errors.New("only one VM can be active at a time") ErrMultipleActiveVM = errors.New("only one VM can be active at a time")
ForwarderBinaryName = "gvproxy"
) )
type Download struct { type Download struct {

View File

@ -118,6 +118,7 @@ func getDirs(usrName string) []Directory {
// in one swoop, then the leading dirs are creates as root. // in one swoop, then the leading dirs are creates as root.
newDirs := []string{ newDirs := []string{
"/home/" + usrName + "/.config", "/home/" + usrName + "/.config",
"/home/" + usrName + "/.config/containers",
"/home/" + usrName + "/.config/systemd", "/home/" + usrName + "/.config/systemd",
"/home/" + usrName + "/.config/systemd/user", "/home/" + usrName + "/.config/systemd/user",
"/home/" + usrName + "/.config/systemd/user/default.target.wants", "/home/" + usrName + "/.config/systemd/user/default.target.wants",
@ -159,6 +160,22 @@ func getFiles(usrName string) []File {
}, },
}) })
// Set containers.conf up for core user to use cni networks
// by default
files = append(files, File{
Node: Node{
Group: getNodeGrp(usrName),
Path: "/home/" + usrName + "/.config/containers/containers.conf",
User: getNodeUsr(usrName),
},
FileEmbedded1: FileEmbedded1{
Append: nil,
Contents: Resource{
Source: strToPtr("data:,%5Bcontainers%5D%0D%0Anetns%3D%22bridge%22%0D%0Arootless_networking%3D%22cni%22"),
},
Mode: intToPtr(484),
},
})
// Add a file into linger // Add a file into linger
files = append(files, File{ files = append(files, File{
Node: Node{ Node: Node{

View File

@ -13,6 +13,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/utils" "github.com/containers/podman/v3/utils"
"github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/homedir"
@ -82,9 +84,10 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...) cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...)
// Add network // Add network
cmd = append(cmd, "-nic", "user,model=virtio,hostfwd=tcp::"+strconv.Itoa(vm.Port)+"-:22") // Right now the mac address is hardcoded so that the host networking gives it a specific IP address. This is
// why we can only run one vm at a time right now
socketPath, err := getSocketDir() cmd = append(cmd, []string{"-netdev", "socket,id=vlan,fd=3", "-device", "virtio-net-pci,netdev=vlan,mac=5a:94:ef:e4:0c:ee"}...)
socketPath, err := getRuntimeDir()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -235,12 +238,35 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// Start executes the qemu command line and forks it // Start executes the qemu command line and forks it
func (v *MachineVM) Start(name string, _ machine.StartOptions) error { func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
var ( var (
conn net.Conn conn net.Conn
err error err error
wait time.Duration = time.Millisecond * 500 qemuSocketConn net.Conn
wait time.Duration = time.Millisecond * 500
) )
if err := v.startHostNetworking(); err != nil {
return errors.Errorf("unable to start host networking: %q", err)
}
qemuSocketPath, _, err := v.getSocketandPid()
for i := 0; i < 6; i++ {
qemuSocketConn, err = net.Dial("unix", qemuSocketPath)
if err == nil {
break
}
time.Sleep(wait)
wait++
}
if err != nil {
return err
}
fd, err := qemuSocketConn.(*net.UnixConn).File()
if err != nil {
return err
}
attr := new(os.ProcAttr) attr := new(os.ProcAttr)
files := []*os.File{os.Stdin, os.Stdout, os.Stderr} files := []*os.File{os.Stdin, os.Stdout, os.Stderr, fd}
attr.Files = files attr.Files = files
logrus.Debug(v.CmdLine) logrus.Debug(v.CmdLine)
cmd := v.CmdLine cmd := v.CmdLine
@ -256,7 +282,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return err return err
} }
fmt.Println("Waiting for VM ...") fmt.Println("Waiting for VM ...")
socketPath, err := getSocketDir() socketPath, err := getRuntimeDir()
if err != nil { if err != nil {
return err return err
} }
@ -309,16 +335,42 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
logrus.Error(err) logrus.Error(err)
} }
}() }()
_, err = qmpMonitor.Run(input) if _, err = qmpMonitor.Run(input); err != nil {
return err return err
}
_, pidFile, err := v.getSocketandPid()
if err != nil {
return err
}
if _, err := os.Stat(pidFile); os.IsNotExist(err) {
logrus.Infof("pid file %s does not exist", pidFile)
return nil
}
pidString, err := ioutil.ReadFile(pidFile)
if err != nil {
return err
}
pidNum, err := strconv.Atoi(string(pidString))
if err != nil {
return err
}
p, err := os.FindProcess(pidNum)
if p == nil && err != nil {
return err
}
return p.Kill()
} }
// NewQMPMonitor creates the monitor subsection of our vm // NewQMPMonitor creates the monitor subsection of our vm
func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) { func NewQMPMonitor(network, name string, timeout time.Duration) (Monitor, error) {
rtDir, err := getSocketDir() rtDir, err := getRuntimeDir()
if err != nil { if err != nil {
return Monitor{}, err return Monitor{}, err
} }
if !rootless.IsRootless() {
rtDir = "/run"
}
rtDir = filepath.Join(rtDir, "podman") rtDir = filepath.Join(rtDir, "podman")
if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) { if _, err := os.Stat(filepath.Join(rtDir)); os.IsNotExist(err) {
// TODO 0644 is fine on linux but macos is weird // TODO 0644 is fine on linux but macos is weird
@ -533,3 +585,47 @@ func CheckActiveVM() (bool, string, error) {
} }
return false, "", nil return false, "", nil
} }
// startHostNetworking runs a binary on the host system that allows users
// to setup port forwarding to the podman virtual machine
func (v *MachineVM) startHostNetworking() error {
binary, err := exec.LookPath(machine.ForwarderBinaryName)
if err != nil {
return err
}
// Listen on all at port 7777 for setting up and tearing
// down forwarding
listenSocket := "tcp://0.0.0.0:7777"
qemuSocket, pidFile, err := v.getSocketandPid()
if err != nil {
return err
}
attr := new(os.ProcAttr)
// Pass on stdin, stdout, stderr
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
attr.Files = files
cmd := []string{binary}
cmd = append(cmd, []string{"-listen", listenSocket, "-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...)
// Add the ssh port
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
if logrus.GetLevel() == logrus.DebugLevel {
cmd = append(cmd, "--debug")
fmt.Println(cmd)
}
_, err = os.StartProcess(cmd[0], cmd, attr)
return err
}
func (v *MachineVM) getSocketandPid() (string, string, error) {
rtPath, err := getRuntimeDir()
if err != nil {
return "", "", err
}
if !rootless.IsRootless() {
rtPath = "/run"
}
socketDir := filepath.Join(rtPath, "podman")
pidFile := filepath.Join(socketDir, fmt.Sprintf("%s.pid", v.Name))
qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name))
return qemuSocket, pidFile, nil
}

View File

@ -6,7 +6,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func getSocketDir() (string, error) { func getRuntimeDir() (string, error) {
tmpDir, ok := os.LookupEnv("TMPDIR") tmpDir, ok := os.LookupEnv("TMPDIR")
if !ok { if !ok {
return "", errors.New("unable to resolve TMPDIR") return "", errors.New("unable to resolve TMPDIR")

View File

@ -5,7 +5,7 @@ var (
) )
func (v *MachineVM) addArchOptions() []string { func (v *MachineVM) addArchOptions() []string {
opts := []string{"-cpu", "host"} opts := []string{"-machine", "q35,accel=hvf:tcg"}
return opts return opts
} }

View File

@ -1,7 +1,13 @@
package qemu package qemu
import "github.com/containers/podman/v3/pkg/util" import (
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
)
func getSocketDir() (string, error) { func getRuntimeDir() (string, error) {
if !rootless.IsRootless() {
return "/run", nil
}
return util.GetRuntimeDir() return util.GetRuntimeDir()
} }