@@ -883,6 +883,104 @@ func TestSSH(t *testing.T) {
883
883
require .NoError (t , err )
884
884
})
885
885
886
+ // Test that we can forward a local unix socket to a remote unix socket and
887
+ // that new SSH sessions take over the socket without closing active socket
888
+ // connections.
889
+ t .Run ("RemoteForwardMultipleUnixSockets" , func (t * testing.T ) {
890
+ if runtime .GOOS == "windows" {
891
+ t .Skip ("Test not supported on windows" )
892
+ }
893
+
894
+ t .Parallel ()
895
+
896
+ client , workspace , agentToken := setupWorkspaceForAgent (t )
897
+
898
+ _ = agenttest .New (t , client .URL , agentToken )
899
+ coderdtest .AwaitWorkspaceAgents (t , client , workspace .ID )
900
+
901
+ // Wait super long so this doesn't flake on -race test.
902
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitSuperLong )
903
+ defer cancel ()
904
+
905
+ tmpdir := tempDirUnixSocket (t )
906
+
907
+ type testSocket struct {
908
+ local string
909
+ remote string
910
+ }
911
+
912
+ args := []string {"ssh" , workspace .Name }
913
+ var sockets []testSocket
914
+ for i := 0 ; i < 2 ; i ++ {
915
+ localSock := filepath .Join (tmpdir , fmt .Sprintf ("local-%d.sock" , i ))
916
+ remoteSock := filepath .Join (tmpdir , fmt .Sprintf ("remote-%d.sock" , i ))
917
+ sockets = append (sockets , testSocket {
918
+ local : localSock ,
919
+ remote : remoteSock ,
920
+ })
921
+ args = append (args , "--remote-forward" , fmt .Sprintf ("%s:%s" , remoteSock , localSock ))
922
+ }
923
+
924
+ inv , root := clitest .New (t , args ... )
925
+ clitest .SetupConfig (t , client , root )
926
+ pty := ptytest .New (t ).Attach (inv )
927
+ inv .Stderr = pty .Output ()
928
+
929
+ clitest .Start (t , inv .WithContext (ctx ))
930
+
931
+ // Since something was output, it should be safe to write input.
932
+ // This could show a prompt or "running startup scripts", so it's
933
+ // not indicative of the SSH connection being ready.
934
+ _ = pty .Peek (ctx , 1 )
935
+
936
+ // Ensure the SSH connection is ready by testing the shell
937
+ // input/output.
938
+ pty .WriteLine ("echo ping' 'pong" )
939
+ pty .ExpectMatchContext (ctx , "ping pong" )
940
+
941
+ for i , sock := range sockets {
942
+ i := i
943
+ // Start the listener on the "local machine".
944
+ l , err := net .Listen ("unix" , sock .local )
945
+ require .NoError (t , err )
946
+ defer l .Close () //nolint:revive // Defer is fine in this loop, we only run it twice.
947
+ testutil .Go (t , func () {
948
+ for {
949
+ fd , err := l .Accept ()
950
+ if err != nil {
951
+ if ! errors .Is (err , net .ErrClosed ) {
952
+ assert .NoError (t , err , "listener accept failed" , i )
953
+ }
954
+ return
955
+ }
956
+
957
+ testutil .Go (t , func () {
958
+ defer fd .Close ()
959
+ agentssh .Bicopy (ctx , fd , fd )
960
+ })
961
+ }
962
+ })
963
+
964
+ // Dial the forwarded socket on the "remote machine".
965
+ d := & net.Dialer {}
966
+ fd , err := d .DialContext (ctx , "unix" , sock .remote )
967
+ require .NoError (t , err , i )
968
+ defer fd .Close () //nolint:revive // Defer is fine in this loop, we only run it twice.
969
+
970
+ // Ping / pong to ensure the socket is working.
971
+ _ , err = fd .Write ([]byte ("hello world" ))
972
+ require .NoError (t , err , i )
973
+
974
+ buf := make ([]byte , 11 )
975
+ _ , err = fd .Read (buf )
976
+ require .NoError (t , err , i )
977
+ require .Equal (t , "hello world" , string (buf ), i )
978
+ }
979
+
980
+ // And we're done.
981
+ pty .WriteLine ("exit" )
982
+ })
983
+
886
984
t .Run ("FileLogging" , func (t * testing.T ) {
887
985
t .Parallel ()
888
986
0 commit comments