From 6ae3b46fc1c74e7f4e0a4e3d1fb75049a3f65190 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 2 Jan 2015 18:01:52 +0100 Subject: [PATCH] Retry, Refactoring & Doc + Refactoring of some functions + Added the retrying of all three connections + Added the documentation to the code + Improved the error messages --- AcceptClients.go | 28 +++++++++++++ Constants.go | 7 ++++ CreateLocalEndPoint.go | 35 ++++++++++++++++ Forward.go | 72 ++++++++++++++++++++++++++++----- KeyboardInteractiveChallenge.go | 14 ++++++- Main.go | 28 +++++-------- PasswordCallback.go | 1 + Transfer.go | 16 ++++++-- Variables.go | 11 ++--- 9 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 AcceptClients.go create mode 100644 Constants.go create mode 100644 CreateLocalEndPoint.go diff --git a/AcceptClients.go b/AcceptClients.go new file mode 100644 index 0000000..aea2628 --- /dev/null +++ b/AcceptClients.go @@ -0,0 +1,28 @@ +package main + +import ( + "golang.org/x/crypto/ssh" + "log" + "net" +) + +func acceptClients(connection net.Listener, config *ssh.ClientConfig) { + + // Endless loop + for { + + // Accept (another) client connection: + if localConn, err := connection.Accept(); err != nil { + + // Fail + log.Printf("Accepting a client failed: %s\n", err.Error()) + } else { + + // Success + log.Println(`Client accepted.`) + + // Start the forwarding: + go forward(localConn, config) + } + } +} diff --git a/Constants.go b/Constants.go new file mode 100644 index 0000000..27b1bcf --- /dev/null +++ b/Constants.go @@ -0,0 +1,7 @@ +package main + +const ( + maxRetriesLocal = 16 // How many retries are allowed to create the local end-point? + maxRetriesRemote = 16 // How many retries are allowed to create the remote end-point? + maxRetriesServer = 16 // How many retries are allowed to create the SSH server's connection? +) diff --git a/CreateLocalEndPoint.go b/CreateLocalEndPoint.go new file mode 100644 index 0000000..c779bd5 --- /dev/null +++ b/CreateLocalEndPoint.go @@ -0,0 +1,35 @@ +package main + +import ( + "log" + "net" + "time" +) + +func createLocalEndPoint() (localListener net.Listener) { + + // Loop for the necessary retries + for { + + // Try to create the local end-point + if localListenerObj, err := net.Listen(`tcp`, localAddrString); err != nil { + + // It was not able to create the end-point: + currentRetriesLocal++ + log.Printf("Was not able to create the local end-point %s: %s\n", localAddrString, err.Error()) + + // Is another retry possible? + if currentRetriesLocal < maxRetriesLocal { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + log.Fatalln(`No more retries for the local end-point: ` + localAddrString) // => Exit + } + } else { + // Success! + log.Println(`Listen to local address ` + localAddrString) + localListener = localListenerObj + return + } + } +} diff --git a/Forward.go b/Forward.go index a21a78f..a0b437a 100644 --- a/Forward.go +++ b/Forward.go @@ -4,20 +4,74 @@ import ( "golang.org/x/crypto/ssh" "log" "net" + "time" ) func forward(localConn net.Conn, config *ssh.ClientConfig) { - sshClientConn, err := ssh.Dial("tcp", serverAddrString, config) - if err != nil { - log.Printf("ssh.Dial failed: %s\n", err) - return + currentRetriesServer := 0 + currentRetriesRemote := 0 + var sshClientConnection *ssh.Client = nil + + // Loop for retries: + for { + + // Try to connect to the SSD server: + if sshClientConn, err := ssh.Dial(`tcp`, serverAddrString, config); err != nil { + + // Failed: + currentRetriesServer++ + log.Printf("Was not able to connect with the SSH server %s: %s\n", serverAddrString, err.Error()) + + // Is a retry alowed? + if currentRetriesServer < maxRetriesServer { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + + // After the return, this thread is closed down. The client can try it again... + log.Println(`No more retries for connecting the SSH server.`) + return + } + + } else { + + // Success: + log.Println(`Connected to the SSH server ` + serverAddrString) + sshClientConnection = sshClientConn + break + } } - if sshConn, err := sshClientConn.Dial("tcp", remoteAddrString); err != nil { - log.Println(`Was not able to create the tunnel: ` + err.Error()) - } else { - go transfer(localConn, sshConn) - go transfer(sshConn, localConn) + // Loop for retries: + for { + + // Try to create the remote end-point: + if sshConn, err := sshClientConnection.Dial(`tcp`, remoteAddrString); err != nil { + + // Failed: + currentRetriesRemote++ + log.Printf("Was not able to create the remote end-point %s: %s\n", remoteAddrString, err.Error()) + + // Is another retry allowed? + if currentRetriesRemote < maxRetriesRemote { + log.Println(`Retry...`) + time.Sleep(1 * time.Second) + } else { + + // After the return, this thread is closed down. The client can try it again... + log.Println(`No more retries for connecting the remote end-point.`) + return + } + } else { + + // Fine, the connections are up and ready :-) + log.Printf("The remote end-point %s is connected.\n", remoteAddrString) + + // Create the transfers to/from both sides (two new threads are created for this): + go transfer(localConn, sshConn, `Local => Remote`) + go transfer(sshConn, localConn, `Remote => Local`) + return + } } } diff --git a/KeyboardInteractiveChallenge.go b/KeyboardInteractiveChallenge.go index 7292e53..570b979 100644 --- a/KeyboardInteractiveChallenge.go +++ b/KeyboardInteractiveChallenge.go @@ -4,8 +4,10 @@ import ( "log" ) +// Another auth. method. func keyboardInteractiveChallenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + // Log all the provided data: log.Println(`User: ` + user) log.Println(`Instruction: ` + instruction) log.Println(`Questions:`) @@ -13,11 +15,19 @@ func keyboardInteractiveChallenge(user, instruction string, questions []string, log.Println(q) } + // How many questions are asked? countQuestions := len(questions) - answers = make([]string, countQuestions, countQuestions) - if countQuestions > 0 { + if countQuestions == 1 { + + // We expect that in this case (only one question is asked), that the server want to know the password ;-) + answers = make([]string, countQuestions, countQuestions) answers[0] = password + + } else if countQuestions > 1 { + + // After logging, this call will exit the whole program: + log.Fatalln(`The SSH server is asking multiple questions! This program cannot handle this case.`) } err = nil diff --git a/Main.go b/Main.go index 961d9f3..ee702f1 100644 --- a/Main.go +++ b/Main.go @@ -2,13 +2,18 @@ package main import ( "golang.org/x/crypto/ssh" - "log" - "net" + "runtime" ) func main() { + // Allow Go to use all CPUs: + runtime.GOMAXPROCS(runtime.NumCPU()) + + // Read the configuration from the command-line args: readFlags() + + // Create the SSH configuration: config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ @@ -18,20 +23,9 @@ func main() { }, } - localListener, err := net.Listen(`tcp`, localAddrString) - if err != nil { - log.Printf("net.Listen failed: %v\n", err) - } else { - log.Println(`Listen to local address.`) - } + // Create the local end-point: + localListener := createLocalEndPoint() - for { - localConn, err := localListener.Accept() - if err != nil { - log.Printf("listen.Accept failed: %v\n", err) - } else { - log.Println(`Accepted a client.`) - go forward(localConn, config) - } - } + // Accept client connections (will block forever): + acceptClients(localListener, config) } diff --git a/PasswordCallback.go b/PasswordCallback.go index f7bef9d..17b56d5 100644 --- a/PasswordCallback.go +++ b/PasswordCallback.go @@ -1,5 +1,6 @@ package main +// Just a callback function for the password request. func passwordCallback() (string, error) { return password, nil } diff --git a/Transfer.go b/Transfer.go index e5f9912..119c40a 100644 --- a/Transfer.go +++ b/Transfer.go @@ -5,11 +5,21 @@ import ( "log" ) -func transfer(fromReader io.Reader, toWriter io.Writer) { +// The transfer function. +func transfer(fromReader io.Reader, toWriter io.Writer, name string) { + log.Printf("%s transfer started.", name) + + // This call blocks until the client or service will close the connection. + // Therefore, this call maybe takes hours or even longer. Concern, may this + // program will be used to connect multiple servers to make e.g. a database + // available... if _, err := io.Copy(toWriter, fromReader); err != nil { - log.Printf("io.Copy failed: %v\n", err) + + // In this case, we do not fail the whole program: Regarding how the client + // or the service was e.g. shut down, the error may only means 'client has been closed'. + log.Printf("%s transfer failed: %s\n", name, err.Error()) } else { - log.Println(`Transfer closed.`) + log.Printf("%s transfer closed.\n", name) } } diff --git a/Variables.go b/Variables.go index 9bb1c67..237d757 100644 --- a/Variables.go +++ b/Variables.go @@ -1,9 +1,10 @@ package main var ( - username = "name" - password = "pwd" - serverAddrString = "server:22" - localAddrString = "localhost:53001" - remoteAddrString = "localhost:27017" + username = `` // The SSH user's name + password = `` // The user's password + serverAddrString = `` // The SSH server address + localAddrString = `` // The local end-point + remoteAddrString = `` // The remote end-point (on the SSH server's side) + currentRetriesLocal = 0 // Check how many retries are occur for creating the local end-point )