1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
//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)
}
|