diff options
author | Paul Holzinger <pholzing@redhat.com> | 2021-09-27 13:01:48 +0200 |
---|---|---|
committer | Paul Holzinger <pholzing@redhat.com> | 2021-11-15 15:20:47 +0100 |
commit | 295d87bb0b028e57dc2739791dee4820fe5fcc48 (patch) | |
tree | de3e347beb6dafb6dde8f26bb2b3001d6231d68b /libpod | |
parent | 92da8682b3977a6ef57a6f73dde2f2aef6186d19 (diff) | |
download | podman-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>
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/network/cni/cni_conversion.go | 15 | ||||
-rw-r--r-- | libpod/network/cni/cni_types.go | 15 | ||||
-rw-r--r-- | libpod/network/cni/config_test.go | 13 | ||||
-rw-r--r-- | libpod/network/cni/network.go | 7 | ||||
-rw-r--r-- | libpod/networking_linux.go | 62 | ||||
-rw-r--r-- | libpod/networking_machine.go | 121 | ||||
-rw-r--r-- | libpod/networking_slirp4netns.go | 4 |
7 files changed, 176 insertions, 61 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) |