func connectProxy(sshc *ssh.Client, h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != "CONNECT" { h.ServeHTTP(w, r) return } host := r.URL.Host if strings.Index(host, ":") < 0 { host += ":80" } sconn, err := sshc.Dial("tcp", host) if err != nil { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusBadGateway) w.Write([]byte(err.Error())) return } w.WriteHeader(http.StatusOK) cconn, _, err := w.(http.Hijacker).Hijack() if err != nil { cconn.Close() sconn.Close() log.Print("CONNECT hijack error: ", err) return } go proxyconn(cconn, sconn) go proxyconn(sconn, cconn) } }
func Hop(through *ssh.Client, toaddr string, c *ssh.ClientConfig) (*ssh.Client, error) { hopconn, err := through.Dial("tcp", toaddr) if err != nil { return nil, err } conn, chans, reqs, err := ssh.NewClientConn(hopconn, toaddr, c) if err != nil { return nil, err } return ssh.NewClient(conn, chans, reqs), nil }
// Forwards the local server listener to the specified target address (format host:port) using the SSH connection as tunnel. // What this method does is the same as "ssh -L $ANY-PORT:jenkins-host:$TARGET-PORT" jenkins-host. func (self *SSHTunnelEstablisher) forwardLocalConnectionsTo(config *util.Config, ssh *ssh.Client, listener net.Listener, targetAddress string) { transfer := func(source io.ReadCloser, target io.Writer) { defer source.Close() _, _ = io.Copy(target, source) } establishBIDITransport := func(source net.Conn, target net.Conn) { go transfer(source, target) go transfer(target, source) } sshAddress := ssh.Conn.RemoteAddr().String() localAddress := listener.Addr().String() util.GOut("ssh-tunnel", "Forwarding local connections on '%v' to '%v' via '%v'.", localAddress, targetAddress, sshAddress) for { if sourceConnection, err := listener.Accept(); err == nil { if targetConnection, err := ssh.Dial("tcp", targetAddress); err == nil { establishBIDITransport(sourceConnection, targetConnection) } else { util.GOut("ssh-tunnel", "ERROR: Failed forwarding incoming local connection on '%v' to '%v' via '%v'.", localAddress, targetAddress, sshAddress) } } else { util.GOut("ssh-tunnel", "Stop forwarding local connections on '%v' to '%v'.", localAddress, targetAddress) return } } }
func drainChildWaitq(waitq []chan net.Conn, address string, client *ssh.Client) ([]chan net.Conn, bool) { for len(waitq) > 0 { reply := waitq[0] conn, err := client.Dial("tcp", address) if err != nil { if err == io.EOF { // Disconnected from the SSH server. return waitq, true } else if err, ok := err.(net.Error); ok && err.Timeout() { log.Print(err) log.Printf("Timeout. Does this mean that we should recycle the connection? %v", address) return waitq, true } else { log.Print(err) log.Printf("Failed to connect to backend server at %s\n", address) } } reply <- conn waitq = waitq[1:] } return waitq, false }
func (tunnel *SSHTunnel) forward(localConn net.Conn, sshServerConn *ssh.Client) { /* serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config) if err != nil { Error.Fatalf("SSH Tunnel: Server dial error: %s\n", err) return }*/ remoteConn, err := sshServerConn.Dial("tcp", tunnel.Remote.String()) if err != nil { Error.Fatalf("SSH Tunnel: Remote dial error: %s\n", err) return } copyConn := func(writer, reader net.Conn) { _, err := io.Copy(writer, reader) if err != nil { Error.Fatalf("SSH Tunnel: Could not forward conenction: %s\n", err) } } go copyConn(localConn, remoteConn) go copyConn(remoteConn, localConn) }
go sshd.HandleConnection(serverNetConn) client = test_helpers.NewClient(clientNetConn, nil) }) AfterEach(func() { client.Close() echoServer.Shutdown() }) Context("when a session is opened", func() { var conn net.Conn JustBeforeEach(func() { var dialErr error conn, dialErr = client.Dial("tcp", echoAddress) Expect(dialErr).NotTo(HaveOccurred()) }) AfterEach(func() { conn.Close() }) It("dials the the target from the remote end", func() { Expect(testDialer.DialCallCount()).To(Equal(1)) net, addr := testDialer.DialArgsForCall(0) Expect(net).To(Equal("tcp")) Expect(addr).To(Equal(echoAddress)) })
}) Context("when a client requests a local port forward", func() { var server *ghttp.Server BeforeEach(func() { server = ghttp.NewServer() server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("GET", "/"), ghttp.RespondWith(http.StatusOK, "hi from jim\n"), ), ) }) It("forwards the local port to the target from the server side", func() { lconn, err := client.Dial("tcp", server.Addr()) Expect(err).NotTo(HaveOccurred()) transport := &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { return lconn, nil }, } client := &http.Client{Transport: transport} resp, err := client.Get("http://127.0.0.1/") Expect(err).NotTo(HaveOccurred()) Expect(resp.StatusCode).To(Equal(http.StatusOK)) reader := bufio.NewReader(resp.Body) line, err := reader.ReadString('\n')
func connectSSH(info PathInfo, resp chan<- *ssh.Client, progress chan<- ProgressCmd) { var err error log.Printf("SSH-connecting to %s\n", info.SSHTunnel.Address) progress <- ProgressCmd{"connection_start", nil} sshKey := []byte(info.SSHTunnel.SSHKeyContents) if info.SSHTunnel.SSHKeyFileName != "" { sshKey, err = ioutil.ReadFile(info.SSHTunnel.SSHKeyFileName) if err != nil { progress <- ProgressCmd{"connection_failed", "Failed to read SSH key"} resp <- nil return } } key, err := ssh.ParsePrivateKey(sshKey) if err != nil { progress <- ProgressCmd{"connection_failed", "Failed to parse SSH key"} resp <- nil return } config := &ssh.ClientConfig{ User: info.SSHTunnel.Username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(key), }, } currentRetriesServer := 0 var sshClientConn *ssh.Client for { progress <- ProgressCmd{"connection_try", nil} if sshClientConn, err = dialSSH(info.SSHTunnel, config, proxyCommand); err == nil { break } currentRetriesServer++ log.Printf("SSH Connection failed %s: %s\n", info.SSHTunnel.Address, err.Error()) if currentRetriesServer < (MAX_RETRIES_SERVER / 1) { log.Println(`Retry...`) progress <- ProgressCmd{"connection_retry", nil} time.Sleep(1 * time.Second) } else { progress <- ProgressCmd{"connection_failed", "Connection retry limit reached"} resp <- nil return } } progress <- ProgressCmd{"connection_established", nil} runBootstrap(sshClientConn, info, progress) if info.SSHTunnel.Run != nil { session, _ := sshClientConn.NewSession() modes := ssh.TerminalModes{ ssh.ECHO: 0, } if err := session.RequestPty("xterm", 80, 40, modes); err != nil { log.Fatalf("request for pseudo terminal failed: %s", err) } session.Start(info.SSHTunnel.Run.Command) time.Sleep(500 * time.Millisecond) } log.Printf("SSH-connection OK. Waiting for %s to be ready...\n", info.Backend.Address) progress <- ProgressCmd{"waiting_backend", nil} currentRetriesClient := 0 for { if conn, err := sshClientConn.Dial("tcp", info.Backend.Address); err == nil { conn.Close() break } currentRetriesClient++ if currentRetriesClient < (MAX_RETRIES_CLIENT / 5) { log.Println(`Retry...`) progress <- ProgressCmd{"waiting_backend_retry", nil} time.Sleep(5 * time.Second) } else { progress <- ProgressCmd{"waiting_backend_timeout", "Connection retry limit reached"} resp <- nil return } } progress <- ProgressCmd{"connection_success", nil} resp <- sshClientConn }
}) AfterEach(func() { client.Close() }) Context("when a client requests the execution of a command", func() { It("runs the command", func() { _, err := client.NewSession() Expect(err).To(MatchError(ContainSubstring("not supported"))) }) }) Context("when a client requests a local port forward", func() { var server *ghttp.Server BeforeEach(func() { server = ghttp.NewServer() }) It("forwards the local port to the target from the server side", func() { _, err := client.Dial("tcp", server.Addr()) Expect(err).To(MatchError(ContainSubstring("unknown channel type"))) }) It("server should not receive any connections", func() { Expect(server.ReceivedRequests()).To(BeEmpty()) }) }) }) })
func forward(localConn net.Conn, config *ssh.ClientConfig, serverAddrString, remoteAddrString string) { defer localConn.Close() currentRetriesServer := 0 currentRetriesRemote := 0 var sshClientConnection *ssh.Client = nil // Loop for retries: for { // Try to connect to the SSH 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 defer sshClientConnection.Close() break } } // 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) defer sshConn.Close() // To be able to close down both transfer threads, we create a channel: quit := make(chan bool) // Create the transfers to/from both sides (two new threads are created for this): go transfer(localConn, sshConn, `Local => Remote`, quit) go transfer(sshConn, localConn, `Remote => Local`, quit) // Wait and look if any of the two transfer theads are down: isRunning := true for isRunning { select { case <-quit: log.Println(`At least one transfer was stopped.`) isRunning = false break } } // Now, close all the channels and therefore, force the other / second thread to go down: log.Println(`Close now all connections.`) return } } }