mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 01:50:50 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| //go:build !remote
 | |
| 
 | |
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/containers/common/libnetwork/types"
 | |
| 	"github.com/containers/common/pkg/machine"
 | |
| 	"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 += "expose"
 | |
| 	} else {
 | |
| 		url += "unexpose"
 | |
| 	}
 | |
| 	ctx := context.Background()
 | |
| 	client := &http.Client{
 | |
| 		Transport: &http.Transport{
 | |
| 			// make sure to not set a proxy here so explicitly ignore the proxy
 | |
| 			// since we want to talk directly to gvproxy
 | |
| 			// https://github.com/containers/podman/issues/13628
 | |
| 			Proxy:                 nil,
 | |
| 			MaxIdleConns:          50,
 | |
| 			IdleConnTimeout:       30 * time.Second,
 | |
| 			TLSHandshakeTimeout:   10 * time.Second,
 | |
| 			ExpectContinueTimeout: 1 * time.Second,
 | |
| 		},
 | |
| 	}
 | |
| 	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 {
 | |
| 	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 := io.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 !machine.IsGvProxyBased() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return requestMachinePorts(true, ports)
 | |
| }
 | |
| 
 | |
| // unexposeMachinePorts closes the ports for podman machine via gvproxy
 | |
| func (r *Runtime) unexposeMachinePorts(ports []types.PortMapping) error {
 | |
| 	if !machine.IsGvProxyBased() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return requestMachinePorts(false, ports)
 | |
| }
 | 
