@@ -4,14 +4,11 @@ import (
44 "fmt"
55 "io"
66 "net"
7- "strings"
8- "time"
9-
10- "golang.org/x/crypto/ssh"
117
128 bosherr "github.com/cloudfoundry/bosh-utils/errors"
139 boshlog "github.com/cloudfoundry/bosh-utils/logger"
14- "github.com/pivotal-golang/clock"
10+
11+ boshssh "github.com/cloudfoundry/bosh-cli/ssh"
1512)
1613
1714type SSHTunnel interface {
@@ -20,104 +17,49 @@ type SSHTunnel interface {
2017}
2118
2219type sshTunnel struct {
23- connectionRefusedTimeout time.Duration
24- authFailureTimeout time.Duration
25- timeService clock.Clock
26- startDialDelay time.Duration
27- options Options
28- remoteListener net.Listener
29- logger boshlog.Logger
30- logTag string
31- }
32-
33- func (s * sshTunnel ) Start (readyErrCh chan <- error , errCh chan <- error ) {
34- authMethods := []ssh.AuthMethod {}
35-
36- if s .options .PrivateKey != "" {
37- signer , err := ssh .ParsePrivateKey ([]byte (s .options .PrivateKey ))
38- if err != nil {
39- readyErrCh <- bosherr .WrapErrorf (err , "Parsing private key '%s'" , s .options .PrivateKey )
40- return
41- }
42-
43- authMethods = append (authMethods , ssh .PublicKeys (signer ))
44- }
45-
46- if s .options .Password != "" {
47- s .logger .Debug (s .logTag , "Adding password auth method to ssh tunnel config" )
48-
49- keyboardInteractiveChallenge := func (
50- user ,
51- instruction string ,
52- questions []string ,
53- echos []bool ,
54- ) (answers []string , err error ) {
55- if len (questions ) == 0 {
56- return []string {}, nil
57- }
58- return []string {s .options .Password }, nil
59- }
60- authMethods = append (authMethods , ssh .KeyboardInteractive (keyboardInteractiveChallenge ))
61- authMethods = append (authMethods , ssh .Password (s .options .Password ))
62- }
63-
64- sshConfig := & ssh.ClientConfig {
65- User : s .options .User ,
66- Auth : authMethods ,
67- }
68-
69- s .logger .Debug (s .logTag , "Dialing remote server at %s:%d" , s .options .Host , s .options .Port )
70- remoteAddr := fmt .Sprintf ("%s:%d" , s .options .Host , s .options .Port )
20+ client boshssh.Client
7121
72- retryStrategy := & SSHRetryStrategy {
73- TimeService : s .timeService ,
74- ConnectionRefusedTimeout : s .connectionRefusedTimeout ,
75- AuthFailureTimeout : s .authFailureTimeout ,
76- }
77-
78- var conn * ssh.Client
79- var err error
80- for i := 0 ; ; i ++ {
81- s .logger .Debug (s .logTag , "Making attempt #%d" , i )
82- conn , err = ssh .Dial ("tcp" , remoteAddr , sshConfig )
83-
84- if err == nil {
85- break
86- }
22+ localForwardPort int
23+ remoteForwardPort int
8724
88- if ! retryStrategy .IsRetryable (err ) {
89- readyErrCh <- bosherr .WrapError (err , "Failed to connect to remote server" )
90- return
91- }
25+ remoteListener net.Listener
9226
93- s .logger .Debug (s .logTag , "Attempt failed #%d: Dialing remote server: %s" , i , err .Error ())
27+ logTag string
28+ logger boshlog.Logger
29+ }
9430
95- time .Sleep (s .startDialDelay )
31+ func (s * sshTunnel ) Start (readyErrCh chan <- error , errCh chan <- error ) {
32+ err := s .client .Start ()
33+ if err != nil {
34+ readyErrCh <- bosherr .WrapError (err , "Starting SSH tunnel" )
35+ return
9636 }
9737
98- remoteListenAddr := fmt .Sprintf ("127.0.0.1:%d" , s .options . RemoteForwardPort )
38+ remoteListenAddr := fmt .Sprintf ("127.0.0.1:%d" , s .remoteForwardPort )
9939 s .logger .Debug (s .logTag , "Listening on remote server %s" , remoteListenAddr )
100- s .remoteListener , err = conn .Listen ("tcp" , remoteListenAddr )
40+ s .remoteListener , err = s . client .Listen ("tcp" , remoteListenAddr )
10141 if err != nil {
10242 readyErrCh <- bosherr .WrapError (err , "Listening on remote server" )
10343 return
10444 }
10545
10646 readyErrCh <- nil
47+
10748 for {
10849 remoteConn , err := s .remoteListener .Accept ()
10950 s .logger .Debug (s .logTag , "Received connection" )
11051 if err != nil {
11152 errCh <- bosherr .WrapError (err , "Accepting connection on remote server" )
11253 }
54+
11355 defer func () {
11456 if err = remoteConn .Close (); err != nil {
11557 s .logger .Warn (s .logTag , "Failed to close remote listener connection: %s" , err .Error ())
11658 }
11759 }()
11860
11961 s .logger .Debug (s .logTag , "Dialing local server" )
120- localDialAddr := fmt .Sprintf ("127.0.0.1:%d" , s .options . LocalForwardPort )
62+ localDialAddr := fmt .Sprintf ("127.0.0.1:%d" , s .localForwardPort )
12163 localConn , err := net .Dial ("tcp" , localDialAddr )
12264 if err != nil {
12365 errCh <- bosherr .WrapError (err , "Dialing local server" )
@@ -126,25 +68,31 @@ func (s *sshTunnel) Start(readyErrCh chan<- error, errCh chan<- error) {
12668
12769 go func () {
12870 bytesNum , err := io .Copy (remoteConn , localConn )
71+
12972 defer func () {
13073 if err = localConn .Close (); err != nil {
13174 s .logger .Warn (s .logTag , "Failed to close local dial connection: %s" , err .Error ())
13275 }
13376 }()
77+
13478 s .logger .Debug (s .logTag , "Copying bytes from local to remote %d" , bytesNum )
79+
13580 if err != nil {
13681 errCh <- bosherr .WrapError (err , "Copying bytes from local to remote" )
13782 }
13883 }()
13984
14085 go func () {
14186 bytesNum , err := io .Copy (localConn , remoteConn )
87+
14288 defer func () {
14389 if err = localConn .Close (); err != nil {
14490 s .logger .Warn (s .logTag , "Failed to close local dial connection: %s" , err .Error ())
14591 }
14692 }()
93+
14794 s .logger .Debug (s .logTag , "Copying bytes from remote to local %d" , bytesNum )
95+
14896 if err != nil {
14997 errCh <- bosherr .WrapError (err , "Copying bytes from remote to local" )
15098 }
@@ -153,39 +101,8 @@ func (s *sshTunnel) Start(readyErrCh chan<- error, errCh chan<- error) {
153101}
154102
155103func (s * sshTunnel ) Stop () error {
156- if s .remoteListener = = nil {
157- return nil
104+ if s .remoteListener ! = nil {
105+ return s . remoteListener . Close ()
158106 }
159-
160- return s .remoteListener .Close ()
161- }
162-
163- type SSHRetryStrategy struct {
164- ConnectionRefusedTimeout time.Duration
165- AuthFailureTimeout time.Duration
166- TimeService clock.Clock
167-
168- initialized bool
169- startTime time.Time
170- authStartTime time.Time
171- }
172-
173- func (s * SSHRetryStrategy ) IsRetryable (err error ) bool {
174- now := s .TimeService .Now ()
175- if ! s .initialized {
176- s .startTime = now
177- s .authStartTime = now
178- s .initialized = true
179- }
180-
181- if strings .Contains (err .Error (), "no common algorithms" ) {
182- return false
183- }
184-
185- if strings .Contains (err .Error (), "unable to authenticate" ) {
186- return now .Before (s .authStartTime .Add (s .AuthFailureTimeout ))
187- }
188-
189- s .authStartTime = now
190- return now .Before (s .startTime .Add (s .ConnectionRefusedTimeout ))
107+ return nil
191108}
0 commit comments