blob: 2c49ebd76777838b7de41fde90087202563a5156 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanskife7134b2022-04-01 15:46:29 +02004package socksproxy
5
6import (
7 "context"
8 "errors"
9 "fmt"
10 "io"
11 "net"
12 "net/http"
13 "os"
14 "sync/atomic"
15 "testing"
16
17 "golang.org/x/net/proxy"
18)
19
20// TestE2E implements a happy path test by chaining together an HTTP server, a
21// proxy server, a proxy client (from golang.org/x/net) and an HTTP client into
22// an end-to-end test. It uses HostHandler and the actual host network stack for
23// the test HTTP server and test proxy server.
24func TestE2E(t *testing.T) {
25 ctx, ctxC := context.WithCancel(context.Background())
26 defer ctxC()
27
28 // Start test HTTP server.
29 lisSrv, err := net.Listen("tcp", "127.0.0.1:0")
30 if err != nil {
31 t.Fatalf("could not bind http listener: %v", err)
32 }
33
34 mux := http.NewServeMux()
35 mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
36 fmt.Fprintf(rw, "foo")
37 })
38 go func() {
39 err := http.Serve(lisSrv, mux)
40 if err != nil {
Tim Windelschmidt88049722024-04-11 23:09:23 +020041 t.Errorf("http.Serve: %v", err)
42 return
Serge Bazanskife7134b2022-04-01 15:46:29 +020043 }
44 }()
45
46 // Start proxy server.
47 lisPrx, err := net.Listen("tcp", ":")
48 if err != nil {
49 t.Fatalf("could not bind proxy listener: %v", err)
50 }
51 go func() {
52 err := Serve(ctx, HostHandler, lisPrx)
53 if err != nil && !errors.Is(err, ctx.Err()) {
Tim Windelschmidt88049722024-04-11 23:09:23 +020054 t.Errorf("proxy.Serve: %v", err)
55 return
Serge Bazanskife7134b2022-04-01 15:46:29 +020056 }
57 }()
58
59 // Start proxy client.
60 dialer, err := proxy.SOCKS5("tcp", lisPrx.Addr().String(), nil, proxy.Direct)
61 if err != nil {
62 t.Fatalf("creating SOCKS dialer failed: %v", err)
63 }
64
65 // Create http client.
66 tr := &http.Transport{
67 Dial: dialer.Dial,
68 }
69 cl := &http.Client{
70 Transport: tr,
71 }
72
73 // Perform request and expect 'foo' in response.
74 url := fmt.Sprintf("http://%s/", lisSrv.Addr().String())
75 req, err := http.NewRequest("GET", url, nil)
76 if err != nil {
77 t.Fatalf("creating test request failed: %v", err)
78 }
79 res, err := cl.Do(req)
80 if err != nil {
81 t.Fatalf("test http request failed: %v", err)
82 }
83 defer res.Body.Close()
84 body, _ := io.ReadAll(res.Body)
85 if want, got := "foo", string(body); want != got {
86 t.Errorf("wrong response from HTTP, wanted %q, got %q", want, got)
87 }
88}
89
90// testHandler is a handler which serves /dev/zero and keeps count of the
91// current number of live connections. It's used in TestCancellation to ensure
92// contexts are canceled appropriately.
93type testHandler struct {
94 live int64
95}
96
97func (t *testHandler) Connect(ctx context.Context, req *ConnectRequest) *ConnectResponse {
98 f, _ := os.Open("/dev/zero")
99
100 atomic.AddInt64(&t.live, 1)
101 go func() {
102 <-ctx.Done()
103 atomic.AddInt64(&t.live, -1)
104 f.Close()
105 }()
106
107 return &ConnectResponse{
108 Backend: f,
109 LocalAddress: net.ParseIP("127.0.0.1"),
110 LocalPort: 42123,
111 }
112}
113
114// TestCancellation ensures request contexts are canceled correctly - when an
115// incoming connection is closed and when the entire server is stopped.
116func TestCancellation(t *testing.T) {
117 handler := &testHandler{}
118
119 ctx, ctxC := context.WithCancel(context.Background())
120 defer ctxC()
121
122 // Start proxy server.
123 lisPrx, err := net.Listen("tcp", ":")
124 if err != nil {
125 t.Fatalf("could not bind proxy listener: %v", err)
126 }
127 go func() {
128 err := Serve(ctx, handler, lisPrx)
129 if err != nil && !errors.Is(err, ctx.Err()) {
Tim Windelschmidt88049722024-04-11 23:09:23 +0200130 t.Errorf("proxy.Serve: %v", err)
131 return
Serge Bazanskife7134b2022-04-01 15:46:29 +0200132 }
133 }()
134
135 // Start proxy client.
136 dialer, err := proxy.SOCKS5("tcp", lisPrx.Addr().String(), nil, proxy.Direct)
137 if err != nil {
138 t.Fatalf("creating SOCKS dialer failed: %v", err)
139 }
140
141 // Open two connections.
142 con1, err := dialer.Dial("tcp", "192.2.0.10:1234")
143 if err != nil {
144 t.Fatalf("Dialing first client failed: %v", err)
145 }
146 con2, err := dialer.Dial("tcp", "192.2.0.10:1234")
147 if err != nil {
148 t.Fatalf("Dialing first client failed: %v", err)
149 }
150
151 // Read some data. This makes sure we're ready to check for the liveness of
152 // currently running connections.
153 io.ReadFull(con1, make([]byte, 3))
154 io.ReadFull(con2, make([]byte, 3))
155
156 // Ensure we have two connections.
157 if want, got := int64(2), atomic.LoadInt64(&handler.live); want != got {
158 t.Errorf("wanted %d connections at first, got %d", want, got)
159 }
160
161 // Close one connection. Wait for its context to be canceled.
162 con2.Close()
163 for {
164 if atomic.LoadInt64(&handler.live) == 1 {
165 break
166 }
167 }
168
169 // Cancel the entire server context. Wait for the other connection's context to
170 // be canceled as well.
171 ctxC()
172 for {
173 if atomic.LoadInt64(&handler.live) == 0 {
174 break
175 }
176 }
177}