mirror of
https://github.com/containers/podman.git
synced 2025-06-24 19:42:56 +08:00
Merge pull request #12283 from Luap99/machine-ports
podman machine improve port forwarding
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
121
libpod/networking_machine.go
Normal file
121
libpod/networking_machine.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user