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
|
// 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
|
||||||
|
}
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
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)
|
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)
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user