Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/supervisor/pkg/ports/tunnel_test.go
2500 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package ports
6
7
import (
8
"context"
9
"fmt"
10
"io"
11
"io/ioutil"
12
"net"
13
"net/http"
14
"strconv"
15
"strings"
16
"sync"
17
"testing"
18
19
"github.com/google/go-cmp/cmp"
20
"golang.org/x/sync/errgroup"
21
22
"github.com/gitpod-io/gitpod/supervisor/api"
23
)
24
25
// TODO(ak) add reverse test.
26
func TestLocalPortTunneling(t *testing.T) {
27
updates := make(chan []PortTunnelState, 4)
28
assertUpdate := func(expectation []PortTunnelState) {
29
update := <-updates
30
if diff := cmp.Diff(expectation, update); diff != "" {
31
t.Errorf("unexpected exposures (-want +got):\n%s", diff)
32
}
33
}
34
35
doneCtx, done := context.WithCancel(context.Background())
36
eg, ctx := errgroup.WithContext(context.Background())
37
service := NewTunneledPortsService(false)
38
tunneled, errors := service.Observe(ctx)
39
eg.Go(func() error {
40
for {
41
select {
42
case <-doneCtx.Done():
43
return nil
44
case ports := <-tunneled:
45
if ports == nil {
46
close(updates)
47
return nil
48
}
49
updates <- ports
50
case err := <-errors:
51
return err
52
}
53
}
54
})
55
assertUpdate([]PortTunnelState{})
56
57
localPort, err := availablePort()
58
if err != nil {
59
t.Fatal(err)
60
}
61
localListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.FormatInt(int64(localPort), 10))
62
if err != nil {
63
t.Fatal(err)
64
}
65
fmt.Printf("local service is listening on %d\n", localPort)
66
eg.Go(func() error {
67
go func() {
68
<-doneCtx.Done()
69
localListener.Close()
70
}()
71
localServer := http.Server{
72
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73
b, _ := ioutil.ReadAll(r.Body)
74
_, _ = w.Write(append(b, '!'))
75
}),
76
}
77
_ = localServer.Serve(localListener)
78
return nil
79
})
80
81
targetPort, err := availablePort()
82
if err != nil {
83
t.Fatal(err)
84
}
85
desc := PortTunnelDescription{
86
LocalPort: localPort,
87
TargetPort: targetPort,
88
Visibility: api.TunnelVisiblity_host,
89
}
90
_, err = service.Tunnel(ctx, &TunnelOptions{
91
SkipIfExists: false,
92
}, &PortTunnelDescription{
93
LocalPort: localPort,
94
TargetPort: targetPort,
95
Visibility: api.TunnelVisiblity_host,
96
})
97
if err != nil {
98
t.Fatal(err)
99
}
100
fmt.Printf("%d:%d tunnel has been created\n", localPort, targetPort)
101
assertUpdate([]PortTunnelState{{Desc: desc, Clients: map[string]uint32{}}})
102
103
targetAddr := "127.0.0.1:" + strconv.FormatInt(int64(targetPort), 10)
104
proxyAddr, err := net.ResolveTCPAddr("tcp", targetAddr)
105
if err != nil {
106
t.Fatal(err)
107
}
108
proxyListener, err := net.ListenTCP("tcp", proxyAddr)
109
if err != nil {
110
t.Fatal(err)
111
}
112
fmt.Printf("target proxy is listening on %d\n", targetPort)
113
eg.Go(func() error {
114
defer proxyListener.Close()
115
116
src, err := proxyListener.Accept()
117
if err != nil {
118
return err
119
}
120
defer src.Close()
121
122
dst, err := service.EstablishTunnel(ctx, "test", localPort, targetPort)
123
if err != nil {
124
return err
125
}
126
defer dst.Close()
127
128
done := make(chan struct{})
129
var once sync.Once
130
go func() {
131
_, _ = io.Copy(src, dst)
132
once.Do(func() { close(done) })
133
}()
134
go func() {
135
_, _ = io.Copy(dst, src)
136
once.Do(func() { close(done) })
137
}()
138
<-done
139
return nil
140
})
141
142
// actually open ssh channel
143
resp, err := http.Post("http://"+targetAddr, "text/plain", strings.NewReader("Hello World"))
144
if err != nil {
145
t.Fatal(err)
146
}
147
body, err := ioutil.ReadAll(resp.Body)
148
if err != nil {
149
t.Fatal(err)
150
}
151
if string(body) != ("Hello World!") {
152
t.Fatal("wrong resp")
153
}
154
assertUpdate([]PortTunnelState{{Desc: desc, Clients: map[string]uint32{"test": targetPort}}})
155
156
_, err = service.CloseTunnel(ctx, localPort)
157
if err != nil {
158
t.Fatal(err)
159
}
160
assertUpdate([]PortTunnelState{})
161
162
done()
163
164
err = eg.Wait()
165
if err != nil && err != context.Canceled {
166
t.Error(err)
167
}
168
}
169
170
func availablePort() (uint32, error) {
171
l, err := net.Listen("tcp", "127.0.0.1:0")
172
if err != nil {
173
return 0, err
174
}
175
l.Close()
176
_, parsed, err := net.SplitHostPort(l.Addr().String())
177
if err != nil {
178
return 0, err
179
}
180
port, err := strconv.Atoi(parsed)
181
if err != nil {
182
return 0, err
183
}
184
return uint32(port), nil
185
}
186
187