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