summaryrefslogtreecommitdiff
path: root/libpod/networking_machine.go
blob: dce335c0a71e00aa407127b58160269904978fbf (plain)
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
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)
}