Skip to content

Commit

Permalink
podman machine improve port forwarding
Browse files Browse the repository at this point in the history
This commits adds port forwarding logic directly into podman. The
podman-machine cni plugin is no longer needed.

The following new features are supported:
 - works with cni, netavark and slirp4netns
 - ports can use the hostIP to bind instead of hard coding 0.0.0.0
 - gvproxy no longer listens on 0.0.0.0:7777 (requires a new gvproxy
   version)
 - support the udp protocol

With this we no longer need podman-machine-cni and should remove it from
the packaging. There is also a change to make sure we are backwards
compatible with old config which include this plugin.

Fixes #11528
Fixes #11728

[NO NEW TESTS NEEDED] We have no podman machine test at the moment.
Please test this manually on your system.

Signed-off-by: Paul Holzinger <[email protected]>
  • Loading branch information
Luap99 committed Nov 15, 2021
1 parent 92da868 commit 295d87b
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 65 deletions.
15 changes: 11 additions & 4 deletions libpod/network/cni/cni_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,6 @@ func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writ
// Note: in the future we might like to allow for dynamic domain names
plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName))
}
// Add the podman-machine CNI plugin if we are in a machine
if n.isMachine {
plugins = append(plugins, newPodmanMachinePlugin())
}

case types.MacVLANNetworkDriver:
plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, ipamConf))
Expand Down Expand Up @@ -369,3 +365,14 @@ func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry
}
return cniPorts, nil
}

func removeMachinePlugin(conf *libcni.NetworkConfigList) *libcni.NetworkConfigList {
plugins := make([]*libcni.NetworkConfig, 0, len(conf.Plugins))
for _, net := range conf.Plugins {
if net.Network.Type != "podman-machine" {
plugins = append(plugins, net)
}
}
conf.Plugins = plugins
return conf
}
15 changes: 0 additions & 15 deletions libpod/network/cni/cni_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ type dnsNameConfig struct {
Capabilities map[string]bool `json:"capabilities"`
}

// podmanMachineConfig enables port handling on the host OS
type podmanMachineConfig struct {
PluginType string `json:"type"`
Capabilities map[string]bool `json:"capabilities"`
}

// ncList describes a generic map
type ncList map[string]interface{}

Expand Down Expand Up @@ -285,12 +279,3 @@ func newVLANPlugin(pluginType, device, mode string, mtu int, ipam ipamConfig) VL
}
return m
}

func newPodmanMachinePlugin() podmanMachineConfig {
caps := make(map[string]bool, 1)
caps["portMappings"] = true
return podmanMachineConfig{
PluginType: "podman-machine",
Capabilities: caps,
}
}
13 changes: 0 additions & 13 deletions libpod/network/cni/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,19 +965,6 @@ var _ = Describe("Config", func() {
Expect(logString).To(ContainSubstring("dnsname and internal networks are incompatible"))
})

It("create config with podman machine plugin", func() {
libpodNet, err := getNetworkInterface(cniConfDir, true)
Expect(err).To(BeNil())

network := types.Network{}
network1, err := libpodNet.NetworkCreate(network)
Expect(err).To(BeNil())
Expect(network1.Driver).To(Equal("bridge"))
path := filepath.Join(cniConfDir, network1.Name+".conflist")
Expect(path).To(BeARegularFile())
grepInFile(path, `"type": "podman-machine",`)
})

It("network inspect partial ID", func() {
network := types.Network{Name: "net4"}
network1, err := libpodNet.NetworkCreate(network)
Expand Down
7 changes: 7 additions & 0 deletions libpod/network/cni/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ func (n *cniNetwork) loadNetworks() error {
continue
}

// podman < v4.0 used the podman-machine cni plugin for podman machine port forwarding
// since this is now build into podman we no longer use the plugin
// old configs may still contain it so we just remove it here
if n.isMachine {
conf = removeMachinePlugin(conf)
}

if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil {
logrus.Warnf("Error validating CNI config file %s: %v", file, err)
continue
Expand Down
62 changes: 35 additions & 27 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,28 @@ func (c *Container) GetNetworkAliases(netName string) ([]string, error) {
return aliases, nil
}

// convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
// This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
// For machine the HostIP must only be used by gvproxy and never in the VM.
func (c *Container) convertPortMappings() []types.PortMapping {
if !c.runtime.config.Engine.MachineEnabled || len(c.config.PortMappings) == 0 {
return c.config.PortMappings
}
// if we run in a machine VM we have to ignore the host IP part
newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings))
for _, port := range c.config.PortMappings {
port.HostIP = ""
newPorts = append(newPorts, port)
}
return newPorts
}

func (c *Container) getNetworkOptions() (types.NetworkOptions, error) {
opts := types.NetworkOptions{
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
opts.PortMappings = c.config.PortMappings
opts.PortMappings = c.convertPortMappings()
networks, _, err := c.networks()
if err != nil {
return opts, err
Expand Down Expand Up @@ -591,32 +607,9 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) {
return rootlessNetNS, 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())
}

// setUpNetwork will set up the the networks, on error it will also tear down the cni
// networks. If rootless it will join/create the rootless network namespace.
func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) {
if r.config.MachineEnabled() {
if err := setPrimaryMachineIP(); err != nil {
return nil, err
}
}
rootlessNetNS, err := r.GetRootlessNetNs(true)
if err != nil {
return nil, err
Expand Down Expand Up @@ -650,7 +643,18 @@ func getCNIPodName(c *Container) string {
}

// Create and configure a new network namespace for a container
func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (map[string]types.StatusBlock, error) {
func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[string]types.StatusBlock, rerr error) {
if err := r.exposeMachinePorts(ctr.config.PortMappings); err != nil {
return nil, err
}
defer func() {
// make sure to unexpose the gvproxy ports when an error happens
if rerr != nil {
if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil {
logrus.Errorf("failed to free gvproxy machine ports: %v", err)
}
}
}()
if ctr.config.NetMode.IsSlirp4netns() {
return nil, r.setupSlirp4netns(ctr, ctrNS)
}
Expand Down Expand Up @@ -836,6 +840,10 @@ func (r *Runtime) teardownCNI(ctr *Container) error {

// Tear down a network namespace, undoing all state associated with it.
func (r *Runtime) teardownNetNS(ctr *Container) error {
if err := r.unexposeMachinePorts(ctr.config.PortMappings); err != nil {
// do not return an error otherwise we would prevent network cleanup
logrus.Errorf("failed to free gvproxy machine ports: %v", err)
}
if err := r.teardownCNI(ctr); err != nil {
return err
}
Expand Down Expand Up @@ -1206,7 +1214,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
opts.PortMappings = c.config.PortMappings
opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
Expand Down Expand Up @@ -1298,7 +1306,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
ContainerID: c.config.ID,
ContainerName: getCNIPodName(c),
}
opts.PortMappings = c.config.PortMappings
opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
Expand Down
121 changes: 121 additions & 0 deletions libpod/networking_machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package libpod

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"

"github.com/containers/podman/v3/libpod/network/types"
"github.com/sirupsen/logrus"
)

const machineGvproxyEndpoint = "gateway.containers.internal"

// machineExpose is the struct for the gvproxy port forwarding api send via json
type machineExpose struct {
// Local is the local address on the vm host, format is ip:port
Local string `json:"local"`
// Remote is used to specify the vm ip:port
Remote string `json:"remote,omitempty"`
// Protocol to forward, tcp or udp
Protocol string `json:"protocol"`
}

func requestMachinePorts(expose bool, ports []types.PortMapping) error {
url := "http://" + machineGvproxyEndpoint + "/services/forwarder/"
if expose {
url = url + "expose"
} else {
url = url + "unexpose"
}
ctx := context.Background()
client := &http.Client{}
buf := new(bytes.Buffer)
for num, port := range ports {
protocols := strings.Split(port.Protocol, ",")
for _, protocol := range protocols {
for i := uint16(0); i < port.Range; i++ {
machinePort := machineExpose{
Local: net.JoinHostPort(port.HostIP, strconv.FormatInt(int64(port.HostPort+i), 10)),
Protocol: protocol,
}
if expose {
// only set the remote port the ip will be automatically be set by gvproxy
machinePort.Remote = ":" + strconv.FormatInt(int64(port.HostPort+i), 10)
}

// post request
if err := json.NewEncoder(buf).Encode(machinePort); err != nil {
if expose {
// in case of an error make sure to unexpose the other ports
if cerr := requestMachinePorts(false, ports[:num]); cerr != nil {
logrus.Errorf("failed to free gvproxy machine ports: %v", cerr)
}
}
return err
}
if err := makeMachineRequest(ctx, client, url, buf); err != nil {
if expose {
// in case of an error make sure to unexpose the other ports
if cerr := requestMachinePorts(false, ports[:num]); cerr != nil {
logrus.Errorf("failed to free gvproxy machine ports: %v", cerr)
}
}
return err
}
buf.Reset()
}
}
}
return nil
}

func makeMachineRequest(ctx context.Context, client *http.Client, url string, buf io.Reader) error {
//var buf io.ReadWriter
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf)
if err != nil {
return err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return annotateGvproxyResponseError(resp.Body)
}
return nil
}

func annotateGvproxyResponseError(r io.Reader) error {
b, err := ioutil.ReadAll(r)
if err == nil && len(b) > 0 {
return fmt.Errorf("something went wrong with the request: %q", string(b))
}
return errors.New("something went wrong with the request, could not read response")
}

// exposeMachinePorts exposes the ports for podman machine via gvproxy
func (r *Runtime) exposeMachinePorts(ports []types.PortMapping) error {
if !r.config.Engine.MachineEnabled {
return nil
}
return requestMachinePorts(true, ports)
}

// unexposeMachinePorts closes the ports for podman machine via gvproxy
func (r *Runtime) unexposeMachinePorts(ports []types.PortMapping) error {
if !r.config.Engine.MachineEnabled {
return nil
}
return requestMachinePorts(false, ports)
}
4 changes: 2 additions & 2 deletions libpod/networking_slirp4netns.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin

childIP := getRootlessPortChildIP(ctr, netStatus)
cfg := rootlessport.Config{
Mappings: ctr.config.PortMappings,
Mappings: ctr.convertPortMappings(),
NetNSPath: netnsPath,
ExitFD: 3,
ReadyFD: 4,
Expand Down Expand Up @@ -594,7 +594,7 @@ func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd

// for each port we want to add we need to open a connection to the slirp4netns control socket
// and send the add_hostfwd command.
for _, i := range ctr.config.PortMappings {
for _, i := range ctr.convertPortMappings() {
conn, err := net.Dial("unix", apiSocket)
if err != nil {
return errors.Wrapf(err, "cannot open connection to %s", apiSocket)
Expand Down
5 changes: 1 addition & 4 deletions pkg/machine/qemu/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,9 +664,6 @@ func (v *MachineVM) startHostNetworking() error {
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
Expand All @@ -676,7 +673,7 @@ func (v *MachineVM) startHostNetworking() error {
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}...)
cmd = append(cmd, []string{"-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 {
Expand Down

0 comments on commit 295d87b

Please sign in to comment.