You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tty-share/proxy/proxy.go

151 lines
3.2 KiB
Go

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
}