@@ -102,3 +102,97 @@ func TestForwardMultipleConnections(t *testing.T) {
102102 }
103103 }
104104}
105+
106+ func TestForwardPortConflict (t * testing.T ) {
107+ echoAddr := startEchoServer (t )
108+ ctx , cancel := context .WithCancel (context .Background ())
109+ defer cancel ()
110+
111+ // Start the first forwarder on an ephemeral port.
112+ f1 := New ("127.0.0.1:0" , echoAddr )
113+ errCh1 := make (chan error , 1 )
114+ go func () { errCh1 <- f1 .Start (ctx ) }()
115+ time .Sleep (50 * time .Millisecond )
116+
117+ // Sanity-check: the first forwarder should be working.
118+ boundAddr := f1 .ListenAddr ()
119+ conn , err := net .Dial ("tcp" , boundAddr )
120+ if err != nil {
121+ t .Fatalf ("first forwarder unreachable: %v" , err )
122+ }
123+ conn .Close ()
124+
125+ // Start a second forwarder on the SAME address — this must fail.
126+ f2 := New (boundAddr , echoAddr )
127+ err = f2 .Start (ctx )
128+ if err == nil {
129+ t .Fatal ("expected error when binding to an already-used port, got nil" )
130+ }
131+ t .Logf ("second forwarder correctly failed: %v" , err )
132+ }
133+
134+ func TestForwardHalfClose (t * testing.T ) {
135+ // Server that reads ALL client data, then sends a response after
136+ // the client has already closed its write side.
137+ ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
138+ if err != nil {
139+ t .Fatal (err )
140+ }
141+ t .Cleanup (func () { ln .Close () })
142+
143+ const serverReply = "server-says-hello"
144+
145+ go func () {
146+ for {
147+ conn , err := ln .Accept ()
148+ if err != nil {
149+ return
150+ }
151+ go func () {
152+ defer conn .Close ()
153+ // Read everything the client sends (blocks until client CloseWrite).
154+ data , _ := io .ReadAll (conn )
155+ // Now send back: the echoed data + a fixed suffix.
156+ conn .Write (data )
157+ conn .Write ([]byte (serverReply ))
158+ // Close write so the proxy's copy loop terminates.
159+ if tc , ok := conn .(* net.TCPConn ); ok {
160+ tc .CloseWrite ()
161+ }
162+ }()
163+ }
164+ }()
165+
166+ serverAddr := ln .Addr ().String ()
167+
168+ ctx , cancel := context .WithCancel (context .Background ())
169+ defer cancel ()
170+
171+ f := New ("127.0.0.1:0" , serverAddr )
172+ go f .Start (ctx )
173+ time .Sleep (50 * time .Millisecond )
174+
175+ conn , err := net .Dial ("tcp" , f .ListenAddr ())
176+ if err != nil {
177+ t .Fatal (err )
178+ }
179+ defer conn .Close ()
180+
181+ // Client sends data, then closes its write side.
182+ clientMsg := "half-close-test"
183+ conn .Write ([]byte (clientMsg ))
184+ conn .(* net.TCPConn ).CloseWrite ()
185+
186+ // The server only starts writing AFTER it has read everything, so
187+ // if half-close propagation is broken we would hang or get no data.
188+ conn .SetReadDeadline (time .Now ().Add (2 * time .Second ))
189+ buf , err := io .ReadAll (conn )
190+ if err != nil {
191+ t .Fatalf ("reading server response: %v" , err )
192+ }
193+
194+ expected := clientMsg + serverReply
195+ if string (buf ) != expected {
196+ t .Errorf ("expected %q, got %q" , expected , string (buf ))
197+ }
198+ }
0 commit comments