|
2 | 2 | package capabilities |
3 | 3 |
|
4 | 4 | import ( |
| 5 | + "bytes" |
5 | 6 | "context" |
6 | 7 | "encoding/base64" |
7 | 8 | "errors" |
8 | 9 | "io" |
9 | 10 | "net" |
| 11 | + "net/http" |
| 12 | + "net/http/httptest" |
| 13 | + "net/url" |
10 | 14 | "strconv" |
11 | 15 | "sync" |
12 | 16 | "testing" |
@@ -80,6 +84,31 @@ func startEchoServer(t *testing.T) (int, func()) { |
80 | 84 | return port, func() { _ = listener.Close() } |
81 | 85 | } |
82 | 86 |
|
| 87 | +func startHTTPServer(t *testing.T) (int, func()) { |
| 88 | + t.Helper() |
| 89 | + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 90 | + w.Header().Set("Content-Type", "text/plain") |
| 91 | + w.Header().Set("Connection", "close") |
| 92 | + _, _ = w.Write([]byte("fritz ok")) |
| 93 | + })) |
| 94 | + u, err := url.Parse(server.URL) |
| 95 | + if err != nil { |
| 96 | + server.Close() |
| 97 | + t.Fatalf("parse server URL: %v", err) |
| 98 | + } |
| 99 | + _, portText, err := net.SplitHostPort(u.Host) |
| 100 | + if err != nil { |
| 101 | + server.Close() |
| 102 | + t.Fatalf("split server host: %v", err) |
| 103 | + } |
| 104 | + port, err := strconv.Atoi(portText) |
| 105 | + if err != nil { |
| 106 | + server.Close() |
| 107 | + t.Fatalf("parse server port: %v", err) |
| 108 | + } |
| 109 | + return port, server.Close |
| 110 | +} |
| 111 | + |
83 | 112 | func TestTunnelManagerOpensAndForwardsBytes(t *testing.T) { |
84 | 113 | withSessionRiskPrompt(t, func(context.Context, riskRequest) (bool, error) { return true, nil }) |
85 | 114 | port, stop := startEchoServer(t) |
@@ -156,6 +185,73 @@ func TestTunnelManagerWaitsForStreamBeforeWritingData(t *testing.T) { |
156 | 185 | } |
157 | 186 | } |
158 | 187 |
|
| 188 | +func TestTunnelManagerHTTPGetCanRaceStreamOpen(t *testing.T) { |
| 189 | + port, stop := startHTTPServer(t) |
| 190 | + defer stop() |
| 191 | + |
| 192 | + bridge := newFakeTunnelBridge() |
| 193 | + mgr := newTunnelMgr(bridge) |
| 194 | + defer mgr.shutdown() |
| 195 | + |
| 196 | + if _, err := mgr.open("tn1", port, "127.0.0.1"); err != nil { |
| 197 | + t.Fatalf("open: %v", err) |
| 198 | + } |
| 199 | + |
| 200 | + request := []byte("GET /login_sid.lua HTTP/1.1\r\nHost: fritz.repeater\r\nConnection: close\r\n\r\n") |
| 201 | + writeDone := make(chan error, 1) |
| 202 | + go func() { |
| 203 | + writeDone <- mgr.writeData("tn1", "s1", request) |
| 204 | + }() |
| 205 | + |
| 206 | + time.Sleep(50 * time.Millisecond) |
| 207 | + if err := mgr.openStream(context.Background(), "tn1", "s1"); err != nil { |
| 208 | + t.Fatalf("openStream: %v", err) |
| 209 | + } |
| 210 | + if err := <-writeDone; err != nil { |
| 211 | + t.Fatalf("writeData: %v", err) |
| 212 | + } |
| 213 | + |
| 214 | + var response []byte |
| 215 | + deadline := time.After(2 * time.Second) |
| 216 | + for !bytes.Contains(response, []byte("fritz ok")) { |
| 217 | + select { |
| 218 | + case rec := <-bridge.dataCh: |
| 219 | + response = append(response, rec.payload...) |
| 220 | + case <-deadline: |
| 221 | + t.Fatalf("HTTP response did not arrive; got %q", response) |
| 222 | + } |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | +func TestTunnelManagerWriteDataStopsWaitingWhenTunnelCloses(t *testing.T) { |
| 227 | + bridge := newFakeTunnelBridge() |
| 228 | + mgr := newTunnelMgr(bridge) |
| 229 | + defer mgr.shutdown() |
| 230 | + |
| 231 | + if _, err := mgr.open("tn1", 5555, "127.0.0.1"); err != nil { |
| 232 | + t.Fatalf("open: %v", err) |
| 233 | + } |
| 234 | + |
| 235 | + writeDone := make(chan error, 1) |
| 236 | + go func() { |
| 237 | + writeDone <- mgr.writeData("tn1", "missing", []byte("payload")) |
| 238 | + }() |
| 239 | + |
| 240 | + time.Sleep(50 * time.Millisecond) |
| 241 | + if ok := mgr.closeTunnel("tn1", "test close"); !ok { |
| 242 | + t.Fatal("closeTunnel returned false") |
| 243 | + } |
| 244 | + |
| 245 | + select { |
| 246 | + case err := <-writeDone: |
| 247 | + if err == nil { |
| 248 | + t.Fatal("writeData unexpectedly succeeded") |
| 249 | + } |
| 250 | + case <-time.After(500 * time.Millisecond): |
| 251 | + t.Fatal("writeData did not unblock after tunnel close") |
| 252 | + } |
| 253 | +} |
| 254 | + |
159 | 255 | func TestTunnelManagerRejectsNonLoopback(t *testing.T) { |
160 | 256 | mgr := newTunnelMgr(newFakeTunnelBridge()) |
161 | 257 | if _, err := mgr.open("tn1", 5555, "8.8.8.8"); err == nil { |
|
0 commit comments