Skip to content

Commit

Permalink
Merge pull request #12283 from Luap99/machine-ports
Browse files Browse the repository at this point in the history
podman machine improve port forwarding
  • Loading branch information
openshift-merge-robot authored Nov 16, 2021
2 parents be681ab + 295d87b commit 8430ffc
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 8430ffc

Please sign in to comment.