summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-09-27 13:01:48 +0200
committerPaul Holzinger <pholzing@redhat.com>2021-11-15 15:20:47 +0100
commit295d87bb0b028e57dc2739791dee4820fe5fcc48 (patch)
treede3e347beb6dafb6dde8f26bb2b3001d6231d68b
parent92da8682b3977a6ef57a6f73dde2f2aef6186d19 (diff)
downloadpodman-295d87bb0b028e57dc2739791dee4820fe5fcc48.tar.gz
podman-295d87bb0b028e57dc2739791dee4820fe5fcc48.tar.bz2
podman-295d87bb0b028e57dc2739791dee4820fe5fcc48.zip
podman machine improve port forwarding
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 <pholzing@redhat.com>
-rw-r--r--libpod/network/cni/cni_conversion.go15
-rw-r--r--libpod/network/cni/cni_types.go15
-rw-r--r--libpod/network/cni/config_test.go13
-rw-r--r--libpod/network/cni/network.go7
-rw-r--r--libpod/networking_linux.go62
-rw-r--r--libpod/networking_machine.go121
-rw-r--r--libpod/networking_slirp4netns.go4
-rw-r--r--pkg/machine/qemu/machine.go5
8 files changed, 177 insertions, 65 deletions
diff --git a/libpod/network/cni/cni_conversion.go b/libpod/network/cni/cni_conversion.go
index 70d259b60..788165b5e 100644
--- a/libpod/network/cni/cni_conversion.go
+++ b/libpod/network/cni/cni_conversion.go
@@ -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))
@@ -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
+}
diff --git a/libpod/network/cni/cni_types.go b/libpod/network/cni/cni_types.go
index c70cb92b6..e5eb777de 100644
--- a/libpod/network/cni/cni_types.go
+++ b/libpod/network/cni/cni_types.go
@@ -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{}
@@ -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,
- }
-}
diff --git a/libpod/network/cni/config_test.go b/libpod/network/cni/config_test.go
index 0dfc6173c..c2e5fc985 100644
--- a/libpod/network/cni/config_test.go
+++ b/libpod/network/cni/config_test.go
@@ -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)
diff --git a/libpod/network/cni/network.go b/libpod/network/cni/network.go
index 3e9cdaa47..41e3e414e 100644
--- a/libpod/network/cni/network.go
+++ b/libpod/network/cni/network.go
@@ -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
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index fc91155fa..8ce435efd 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -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
@@ -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
@@ -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)
}
@@ -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
}
@@ -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)
@@ -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)
diff --git a/libpod/networking_machine.go b/libpod/networking_machine.go
new file mode 100644
index 000000000..7cb2a00f7
--- /dev/null
+++ b/libpod/networking_machine.go
@@ -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)
+}
diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go
index 674075e23..67ea31c1c 100644
--- a/libpod/networking_slirp4netns.go
+++ b/libpod/networking_slirp4netns.go
@@ -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,
@@ -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)
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index a7174aac3..57c32bf74 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -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
@@ -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 {