Merge pull request #12283 from Luap99/machine-ports

podman machine improve port forwarding
This commit is contained in:
OpenShift Merge Robot
2021-11-16 14:53:40 +01:00
committed by GitHub
8 changed files with 177 additions and 65 deletions

View File

@ -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 // Note: in the future we might like to allow for dynamic domain names
plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName)) 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: case types.MacVLANNetworkDriver:
plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, ipamConf)) plugins = append(plugins, newVLANPlugin(types.MacVLANNetworkDriver, network.NetworkInterface, vlanPluginMode, mtu, ipamConf))
@ -369,3 +365,14 @@ func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry
} }
return cniPorts, nil 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
}

View File

@ -110,12 +110,6 @@ type dnsNameConfig struct {
Capabilities map[string]bool `json:"capabilities"` 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 // ncList describes a generic map
type ncList map[string]interface{} type ncList map[string]interface{}
@ -285,12 +279,3 @@ func newVLANPlugin(pluginType, device, mode string, mtu int, ipam ipamConfig) VL
} }
return m return m
} }
func newPodmanMachinePlugin() podmanMachineConfig {
caps := make(map[string]bool, 1)
caps["portMappings"] = true
return podmanMachineConfig{
PluginType: "podman-machine",
Capabilities: caps,
}
}

View File

@ -965,19 +965,6 @@ var _ = Describe("Config", func() {
Expect(logString).To(ContainSubstring("dnsname and internal networks are incompatible")) 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() { It("network inspect partial ID", func() {
network := types.Network{Name: "net4"} network := types.Network{Name: "net4"}
network1, err := libpodNet.NetworkCreate(network) network1, err := libpodNet.NetworkCreate(network)

View File

@ -150,6 +150,13 @@ func (n *cniNetwork) loadNetworks() error {
continue 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 { if _, err := n.cniConf.ValidateNetworkList(context.Background(), conf); err != nil {
logrus.Warnf("Error validating CNI config file %s: %v", file, err) logrus.Warnf("Error validating CNI config file %s: %v", file, err)
continue continue

View File

@ -87,12 +87,28 @@ func (c *Container) GetNetworkAliases(netName string) ([]string, error) {
return aliases, nil 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) { func (c *Container) getNetworkOptions() (types.NetworkOptions, error) {
opts := types.NetworkOptions{ opts := types.NetworkOptions{
ContainerID: c.config.ID, ContainerID: c.config.ID,
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.config.PortMappings opts.PortMappings = c.convertPortMappings()
networks, _, err := c.networks() networks, _, err := c.networks()
if err != nil { if err != nil {
return opts, err return opts, err
@ -591,32 +607,9 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) {
return rootlessNetNS, nil 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 // 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. // 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) { 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) rootlessNetNS, err := r.GetRootlessNetNs(true)
if err != nil { if err != nil {
return nil, err return nil, err
@ -650,7 +643,18 @@ func getCNIPodName(c *Container) string {
} }
// Create and configure a new network namespace for a container // 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() { if ctr.config.NetMode.IsSlirp4netns() {
return nil, r.setupSlirp4netns(ctr, ctrNS) 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. // Tear down a network namespace, undoing all state associated with it.
func (r *Runtime) teardownNetNS(ctr *Container) error { 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 { if err := r.teardownCNI(ctr); err != nil {
return err return err
} }
@ -1206,7 +1214,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
ContainerID: c.config.ID, ContainerID: c.config.ID,
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.config.PortMappings opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists { if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) 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, ContainerID: c.config.ID,
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.config.PortMappings opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists { if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)

View File

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

View File

@ -509,7 +509,7 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath strin
childIP := getRootlessPortChildIP(ctr, netStatus) childIP := getRootlessPortChildIP(ctr, netStatus)
cfg := rootlessport.Config{ cfg := rootlessport.Config{
Mappings: ctr.config.PortMappings, Mappings: ctr.convertPortMappings(),
NetNSPath: netnsPath, NetNSPath: netnsPath,
ExitFD: 3, ExitFD: 3,
ReadyFD: 4, 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 // for each port we want to add we need to open a connection to the slirp4netns control socket
// and send the add_hostfwd command. // and send the add_hostfwd command.
for _, i := range ctr.config.PortMappings { for _, i := range ctr.convertPortMappings() {
conn, err := net.Dial("unix", apiSocket) conn, err := net.Dial("unix", apiSocket)
if err != nil { if err != nil {
return errors.Wrapf(err, "cannot open connection to %s", apiSocket) return errors.Wrapf(err, "cannot open connection to %s", apiSocket)

View File

@ -664,9 +664,6 @@ func (v *MachineVM) startHostNetworking() error {
return err 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() qemuSocket, pidFile, err := v.getSocketandPid()
if err != nil { if err != nil {
return err return err
@ -676,7 +673,7 @@ func (v *MachineVM) startHostNetworking() error {
files := []*os.File{os.Stdin, os.Stdout, os.Stderr} files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
attr.Files = files attr.Files = files
cmd := []string{binary} 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 // Add the ssh port
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
if logrus.GetLevel() == logrus.DebugLevel { if logrus.GetLevel() == logrus.DebugLevel {