@@ -2,6 +2,7 @@ package proxy
2
2
3
3
import (
4
4
"bufio"
5
+ "context"
5
6
"crypto/tls"
6
7
"errors"
7
8
"fmt"
@@ -13,9 +14,11 @@ import (
13
14
"strings"
14
15
"sync"
15
16
"sync/atomic"
17
+ "time"
16
18
17
19
"github.com/coder/boundary/audit"
18
20
"github.com/coder/boundary/rules"
21
+ "golang.org/x/sync/errgroup"
19
22
)
20
23
21
24
// Server handles HTTP and HTTPS requests with rule-based filtering
@@ -658,6 +661,11 @@ func (p *Server) streamRequestToTarget(clientConn *tls.Conn, bufReader *bufio.Re
658
661
}
659
662
}()
660
663
664
+ // Set connection deadlines to prevent indefinite blocking
665
+ deadline := time .Now ().Add (5 * time .Minute )
666
+ _ = clientConn .SetDeadline (deadline )
667
+ _ = targetConn .SetDeadline (deadline )
668
+
661
669
// Send HTTP request headers to target
662
670
reqLine := fmt .Sprintf ("%s %s %s\r \n " , req .Method , req .URL .RequestURI (), req .Proto )
663
671
_ , err = targetConn .Write ([]byte (reqLine ))
@@ -680,20 +688,40 @@ func (p *Server) streamRequestToTarget(clientConn *tls.Conn, bufReader *bufio.Re
680
688
return fmt .Errorf ("failed to write headers to target: %v" , err )
681
689
}
682
690
683
- // Stream request body and response bidirectionally
684
- go func () {
685
- // Stream request body: client -> target
691
+ // Use errgroup to manage bidirectional streaming and ensure cleanup
692
+ g , ctx := errgroup .WithContext (context .Background ())
693
+
694
+ // Stream request body: client -> target
695
+ g .Go (func () error {
686
696
_ , err := io .Copy (targetConn , bufReader )
687
- if err != nil {
688
- p .logger .Error ("Error copying request body to target" , "error" , err )
697
+ if err != nil && ! errors . Is ( err , io . EOF ) && ! errors . Is ( err , net . ErrClosed ) {
698
+ p .logger .Debug ("Error copying request body to target" , "error" , err )
689
699
}
690
- }()
700
+ // Close write side to signal EOF to target
701
+ _ = targetConn .CloseWrite ()
702
+ return nil
703
+ })
691
704
692
705
// Stream response: target -> client
693
- _ , err = io .Copy (clientConn , targetConn )
694
- if err != nil {
695
- p .logger .Error ("Error copying response from target to client" , "error" , err )
696
- }
706
+ g .Go (func () error {
707
+ _ , err := io .Copy (clientConn , targetConn )
708
+ if err != nil && ! errors .Is (err , io .EOF ) && ! errors .Is (err , net .ErrClosed ) {
709
+ p .logger .Debug ("Error copying response from target to client" , "error" , err )
710
+ }
711
+ return nil
712
+ })
713
+
714
+ // Monitor context cancellation to ensure both goroutines exit
715
+ g .Go (func () error {
716
+ <- ctx .Done ()
717
+ // Force close connections to unblock any hanging io.Copy
718
+ _ = clientConn .Close ()
719
+ _ = targetConn .Close ()
720
+ return nil
721
+ })
722
+
723
+ // Wait for all goroutines to complete
724
+ _ = g .Wait ()
697
725
698
726
return nil
699
727
}
@@ -729,16 +757,46 @@ func (p *Server) handleConnectStreaming(tlsConn *tls.Conn, req *http.Request, ho
729
757
}
730
758
defer func () { _ = targetConn .Close () }()
731
759
732
- // Bidirectional copy
733
- go func () {
760
+ // Set connection deadlines to prevent indefinite blocking
761
+ deadline := time .Now ().Add (5 * time .Minute )
762
+ _ = tlsConn .SetDeadline (deadline )
763
+ _ = targetConn .SetDeadline (deadline )
764
+
765
+ // Use errgroup for bidirectional copy with proper cleanup
766
+ g , ctx := errgroup .WithContext (context .Background ())
767
+
768
+ // Client to target
769
+ g .Go (func () error {
734
770
_ , err := io .Copy (targetConn , tlsConn )
735
- if err != nil {
736
- p .logger .Error ("Error copying from client to target" , "error" , err )
771
+ if err != nil && ! errors . Is ( err , io . EOF ) && ! errors . Is ( err , net . ErrClosed ) {
772
+ p .logger .Debug ("Error copying from client to target" , "error" , err )
737
773
}
738
- }()
739
- _ , err = io .Copy (tlsConn , targetConn )
740
- if err != nil {
741
- p .logger .Error ("Error copying from target to client" , "error" , err )
742
- }
774
+ // Close write side to signal EOF
775
+ if tc , ok := targetConn .(* net.TCPConn ); ok {
776
+ _ = tc .CloseWrite ()
777
+ }
778
+ return nil
779
+ })
780
+
781
+ // Target to client
782
+ g .Go (func () error {
783
+ _ , err := io .Copy (tlsConn , targetConn )
784
+ if err != nil && ! errors .Is (err , io .EOF ) && ! errors .Is (err , net .ErrClosed ) {
785
+ p .logger .Debug ("Error copying from target to client" , "error" , err )
786
+ }
787
+ return nil
788
+ })
789
+
790
+ // Monitor context cancellation to ensure cleanup
791
+ g .Go (func () error {
792
+ <- ctx .Done ()
793
+ // Force close connections to unblock any hanging io.Copy
794
+ _ = tlsConn .Close ()
795
+ _ = targetConn .Close ()
796
+ return nil
797
+ })
798
+
799
+ // Wait for all goroutines to complete
800
+ _ = g .Wait ()
743
801
p .logger .Debug ("CONNECT tunnel closed" , "hostname" , hostname )
744
802
}
0 commit comments