|
|
|
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
|
|
|
|
}
|