1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
package proxy
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"io"
"net"
"github.com/hashicorp/yamux"
log "github.com/sirupsen/logrus"
)
type HelloClient struct {
Version string
Data string
}
type HelloServer struct {
Version string
SessionID string
PublicURL string
Data string
}
type proxyConnection struct {
muxSession *yamux.Session
backConnAddress string
SessionID string
PublicURL string
}
func NewProxyConnection(backConnAddrr, proxyAddr string, noTLS bool) (*proxyConnection, error) {
var conn net.Conn
var err error
if noTLS {
conn, err = net.Dial("tcp", proxyAddr)
if err != nil {
return nil, err
}
} else {
roots, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
conn, err = tls.Dial("tcp", proxyAddr, &tls.Config{RootCAs: roots})
if err != nil {
return nil, err
}
}
// C -> S: HelloCLient
// S -> C: HelloServer {sesionID}
je := json.NewEncoder(conn)
// TODO: extract these strings constants somewhere at some point
helloC := HelloClient{
Version: "1",
Data: "-",
}
err = je.Encode(helloC)
if err != nil {
return nil, err
}
jd := json.NewDecoder(conn)
var helloS HelloServer
err = jd.Decode(&helloS)
if err != nil {
return nil, err
}
log.Debugf("Connected to %s tty-proxy: version=%s, sessionID=%s", helloS.PublicURL, helloS.Version, helloS.SessionID)
session, err := yamux.Server(conn, nil)
return &proxyConnection{
muxSession: session,
backConnAddress: backConnAddrr,
SessionID: helloS.SessionID,
PublicURL: helloS.PublicURL,
}, nil
}
func (p *proxyConnection) RunProxy() {
for {
frontConn, err := p.muxSession.Accept()
if err != nil {
log.Debugf("tty-proxy connection closed: %s", err.Error())
return
}
defer frontConn.Close()
go func() {
backConn, err := net.Dial("tcp", p.backConnAddress)
if err != nil {
log.Errorf("Cannot proxy the connection to the target HTTP server: %s", err.Error())
return
}
defer backConn.Close()
pipeConnectionsAndWait(backConn, frontConn)
}()
}
}
func (p *proxyConnection) Stop() {
p.muxSession.Close()
}
func errToString(err error) string {
if err != nil {
return err.Error()
}
return "nil"
}
func pipeConnectionsAndWait(backConn, frontConn net.Conn) error {
errChan := make(chan error, 2)
backConnAddr := backConn.RemoteAddr().String()
frontConnAddr := frontConn.RemoteAddr().String()
log.Debugf("Piping the two conn %s <-> %s ..", backConnAddr, frontConnAddr)
copyAndNotify := func(dst, src net.Conn, info string) {
n, err := io.Copy(dst, src)
log.Debugf("%s: piping done with %d bytes, and err %s", info, n, errToString(err))
errChan <- err
// Close both connections when done with copying. Yeah, both will beclosed two
// times, but it doesn't matter. By closing them both, we unblock the other copy
// call which would block indefinitely otherwise
dst.Close()
src.Close()
}
go copyAndNotify(backConn, frontConn, "front->back")
go copyAndNotify(frontConn, backConn, "back->front")
err1 := <-errChan
err2 := <-errChan
log.Debugf("Piping finished for %s <-> %s .", backConnAddr, frontConnAddr)
// Return one of the two error that is not nil
if err1 != nil {
return err1
}
return err2
}
|