telebit/relay/mplexy/listener.go

362 lines
9.9 KiB
Go
Raw Normal View History

2020-05-01 06:12:16 +00:00
package mplexy
import (
2017-03-22 21:43:36 +00:00
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/hex"
2020-04-30 10:43:36 +00:00
"fmt"
"net"
2017-03-22 21:43:36 +00:00
"net/http"
"strconv"
"strings"
"time"
2020-04-30 05:52:44 +00:00
"git.coolaj86.com/coolaj86/go-telebitd/packer"
2020-05-01 05:47:46 +00:00
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
2020-05-01 07:06:14 +00:00
"git.coolaj86.com/coolaj86/go-telebitd/relay/tunnel"
2020-04-30 05:52:44 +00:00
"git.coolaj86.com/coolaj86/go-telebitd/sni"
)
type contextKey string
//CtxConnectionTrack
const (
2020-05-01 05:47:46 +00:00
ctxServerStatus contextKey = "serverstatus"
2020-04-30 10:43:36 +00:00
ctxConfig contextKey = "tlsConfig"
ctxListenerRegistration contextKey = "listenerRegistration"
ctxConnectionTrack contextKey = "connectionTrack"
ctxWssHostName contextKey = "wsshostname"
ctxCancelCheck contextKey = "cancelcheck"
ctxLoadbalanceDefaultMethod contextKey = "lbdefaultmethod"
2020-05-01 05:47:46 +00:00
//ctxConnectionTable contextKey = "connectionTable"
)
2020-04-30 10:43:36 +00:00
// TODO isn't this restriction in the TLS lib?
// or are we just pre-checking for remote hosts?
type tlsScheme int
const (
2020-04-30 10:43:36 +00:00
encryptNone tlsScheme = iota
encryptSSLV2
encryptSSLV3
encryptTLS10
encryptTLS11
encryptTLS12
2020-04-30 10:43:36 +00:00
encryptTLS13
)
2020-04-30 10:43:36 +00:00
// multiListenAndServe -- used to lisen for any https traffic on 443 (8443)
// - setup generic TCP listener, unencrypted TCP, with a Deadtime out
// - leaverage the wedgeConn to peek into the buffer.
// - if TLS, consume connection with TLS certbundle, pass to request identifier
// - else, just pass to the request identififer
2020-04-30 10:43:36 +00:00
func (mx *MPlexy) multiListenAndServe(ctx context.Context, listenerRegistration *ListenerRegistration) {
2020-05-01 07:06:14 +00:00
loginfo.Println(":" + string(listenerRegistration.port))
cancelCheck := ctx.Value(ctxCancelCheck).(int)
listenAddr, err := net.ResolveTCPAddr("tcp", ":"+strconv.Itoa(listenerRegistration.port))
if nil != err {
2020-05-01 07:06:14 +00:00
loginfo.Println(err)
return
}
ln, err := net.ListenTCP("tcp", listenAddr)
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to bind", err)
listenerRegistration.status = listenerFault
listenerRegistration.err = err
listenerRegistration.commCh <- listenerRegistration
return
}
listenerRegistration.status = listenerAdded
listenerRegistration.commCh <- listenerRegistration
for {
select {
case <-ctx.Done():
2020-05-01 07:06:14 +00:00
loginfo.Println("Cancel signal hit")
return
default:
ln.SetDeadline(time.Now().Add(time.Duration(cancelCheck) * time.Second))
conn, err := ln.Accept()
if nil != err {
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
continue
}
2020-05-01 07:06:14 +00:00
loginfo.Println(err)
return
}
2020-04-30 10:43:36 +00:00
fmt.Println("New connection from %v on %v", conn.LocalAddr(), conn.RemoteAddr())
// TODO maybe put these into something like mx.newConnCh and have an mx.Accept()?
2020-05-01 05:47:46 +00:00
wedgeConn := tunnel.NewWedgeConn(conn)
2020-04-30 10:43:36 +00:00
go mx.accept(ctx, wedgeConn)
}
}
}
2020-04-30 10:43:36 +00:00
//accept -
// - accept a wedgeConnection along with all the other required attritvues
// - peek into the buffer, determine TLS or unencrypted
2020-05-01 05:47:46 +00:00
// - if TSL, then terminate with a TLS endpoint, pass to acceptEcryptedStream
// - if clearText, pass to acceptPlainStream
func (mx *MPlexy) accept(ctx context.Context, wConn *tunnel.WedgeConn) {
peekCnt := 10
encryptMode := encryptNone
2020-05-01 07:06:14 +00:00
loginfo.Println("new conn", wConn, wConn.LocalAddr().String(), wConn.RemoteAddr().String())
peek, err := wConn.Peek(peekCnt)
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("error while peeking")
2020-05-01 05:47:46 +00:00
wConn.Close()
return
}
//take a look for a TLS header.
if bytes.Contains(peek[0:0], []byte{0x80}) && bytes.Contains(peek[2:4], []byte{0x01, 0x03}) {
encryptMode = encryptSSLV2
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x00}) {
encryptMode = encryptSSLV3
2020-05-05 04:49:38 +00:00
loginfo.Println("SSLV3")
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x01}) {
encryptMode = encryptTLS10
2020-05-01 07:06:14 +00:00
loginfo.Println("TLS10")
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x02}) {
encryptMode = encryptTLS11
2020-05-05 04:49:38 +00:00
loginfo.Println("TLS11")
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x03}) {
encryptMode = encryptTLS12
2020-05-05 04:49:38 +00:00
loginfo.Println("TLS12")
2020-04-30 10:43:36 +00:00
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x04}) {
encryptMode = encryptTLS13
2020-05-05 04:49:38 +00:00
loginfo.Println("TLS13")
2020-04-30 10:43:36 +00:00
}
if encryptMode == encryptSSLV2 {
2020-05-01 07:06:14 +00:00
loginfo.Println("<= SSLv2 is not accepted")
2020-05-01 05:47:46 +00:00
wConn.Close()
return
2020-04-30 05:49:09 +00:00
}
2020-04-30 05:49:09 +00:00
if encryptMode == encryptNone {
2020-05-01 07:06:14 +00:00
loginfo.Println("Handle Unencrypted")
2020-05-01 05:47:46 +00:00
mx.acceptPlainStream(ctx, wConn, false)
2020-04-30 05:49:09 +00:00
return
}
2020-05-01 07:06:14 +00:00
loginfo.Println("Handle Encryption")
2020-05-01 05:47:46 +00:00
mx.acceptEncryptedStream(ctx, wConn)
}
2020-05-01 05:47:46 +00:00
func (mx *MPlexy) acceptEncryptedStream(ctx context.Context, wConn *tunnel.WedgeConn) {
// Peek at SNI (ServerName) from TLS Hello header
2020-05-01 05:47:46 +00:00
peek, err := wConn.PeekAll()
2020-04-30 05:49:09 +00:00
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("Bad socket: read error from", wConn.RemoteAddr(), err)
loginfo.Println(hex.Dump(peek[0:]))
2020-05-01 05:47:46 +00:00
wConn.Close()
2020-04-30 05:49:09 +00:00
return
}
2020-04-30 05:49:09 +00:00
sniHostName, err := sni.GetHostname(peek)
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("Bad socket: no SNI from", wConn.RemoteAddr(), err)
loginfo.Println(err)
2020-05-01 05:47:46 +00:00
wConn.Close()
2020-04-30 05:49:09 +00:00
return
}
2020-05-01 07:06:14 +00:00
loginfo.Println("SNI:", sniHostName)
2020-05-01 05:47:46 +00:00
if sniHostName == mx.wssHostName || sniHostName == mx.adminHostName {
// The TLS should be terminated and handled internally
tlsConfig := ctx.Value(ctxConfig).(*tls.Config)
2020-05-05 04:49:38 +00:00
conn := tls.Server(wConn, tlsConfig)
2020-05-01 05:47:46 +00:00
tlsWedgeConn := tunnel.NewWedgeConn(conn)
mx.acceptPlainStream(ctx, tlsWedgeConn, true)
2020-04-30 05:49:09 +00:00
return
2020-04-30 10:43:36 +00:00
}
2020-05-01 05:47:46 +00:00
//oneConn := &oneConnListener{wConn}
2020-05-01 05:47:46 +00:00
// TLS remains intact and shall be routed downstream, wholesale
2020-05-01 07:06:14 +00:00
loginfo.Println("processing non terminating traffic", mx.wssHostName, sniHostName)
2020-05-01 05:47:46 +00:00
go mx.routeToTarget(ctx, wConn, sniHostName, "https")
}
2020-05-01 05:47:46 +00:00
//acceptPlainStream --
// - we have an unencrypted stream connection with the ability to peek
// - attempt to identify HTTP
// - handle http
// - attempt to identify as WSS session
// - attempt to identify as ADMIN/API session
// - else handle as raw http
// - handle other?
2020-05-01 05:47:46 +00:00
func (mx *MPlexy) acceptPlainStream(ctx context.Context, wConn *tunnel.WedgeConn, encrypted bool) {
2020-05-01 07:06:14 +00:00
loginfo.Println("Plain Conn", wConn.LocalAddr().String(), wConn.RemoteAddr().String())
2020-05-01 05:47:46 +00:00
// TODO couldn't reading everything be dangerous? Or is it limited to a single packet?
peek, err := wConn.PeekAll()
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("error while peeking", err)
loginfo.Println(hex.Dump(peek[0:]))
2020-05-01 05:47:46 +00:00
wConn.Close()
return
}
2020-04-30 10:43:36 +00:00
// TODO handle by TCP port as well
// (which needs a short read timeout since servers expect clients to say hello)
2020-04-30 05:49:09 +00:00
// HTTP Identifcation // CRLF
if !bytes.Contains(peek[:], []byte{0x0d, 0x0a}) {
2020-05-01 05:47:46 +00:00
wConn.Close()
2020-04-30 05:49:09 +00:00
return
}
2020-04-30 05:49:09 +00:00
//string protocol
if !bytes.ContainsAny(peek[:], "HTTP/") {
2020-05-01 05:47:46 +00:00
wConn.Close()
2020-04-30 05:49:09 +00:00
return
}
2020-05-01 07:06:14 +00:00
loginfo.Println("identified HTTP")
2020-04-30 05:49:09 +00:00
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(peek)))
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("identified as HTTP, failed request parsing", err)
2020-05-01 05:47:46 +00:00
wConn.Close()
2020-04-30 05:49:09 +00:00
return
}
2020-05-01 07:06:14 +00:00
var hostname string
host := strings.Split(r.Host, ":")
if len(host) > 0 {
hostname = host[0]
}
if hostname == InvalidAdminDomain {
loginfo.Println("admin.invalid")
2020-05-01 05:47:46 +00:00
// TODO mx.Admin.CheckRemoteIP(conn) here
// handle admin path
mx.AcceptAdminClient(wConn)
return
}
2020-05-05 06:44:21 +00:00
if hostname == mx.wssHostName &&
("Upgrade" == r.Header.Get("Connection") || "WebSocket" == r.Header.Get("Upgrade")) {
loginfo.Println("WebSocket Upgrade is in order...")
mx.AcceptTargetServer(wConn)
return
}
2020-05-01 07:06:14 +00:00
if hostname == mx.adminHostName {
2020-05-05 06:44:21 +00:00
loginfo.Println("matched admin hostname")
2020-05-01 07:06:14 +00:00
// TODO mx.Admin.CheckRemoteIP(conn) here
// handle admin path
mx.AcceptAdminClient(wConn)
return
}
2020-05-01 05:47:46 +00:00
// TODO sniHostName is the key to the route, which could also be a port or hostname
//traffic not terminating on the rvpn do not decrypt
2020-05-01 07:06:14 +00:00
loginfo.Println("processing non terminating traffic", mx.wssHostName, r.Host)
loginfo.Println(hex.Dump(peek))
2020-05-01 05:47:46 +00:00
if !encrypted {
// TODO request and cache http resources as a feature??
2020-05-01 08:29:36 +00:00
go mx.routeToTarget(ctx, wConn, hostname, "http")
2020-04-30 05:49:09 +00:00
return
}
2020-05-01 05:47:46 +00:00
// This is not presently possible
2020-05-01 07:06:14 +00:00
loginfo.Println("impossible condition: local decryption of routable client", mx.wssHostName, r.Host)
2020-05-01 05:47:46 +00:00
go mx.routeToTarget(ctx, wConn, r.Host, "https")
}
2020-05-01 05:47:46 +00:00
//routeToTarget -
// - get a wConn and start processing requests
2020-05-01 05:47:46 +00:00
func (mx *MPlexy) routeToTarget(ctx context.Context, extConn *tunnel.WedgeConn, hostname, service string) {
// TODO is this the right place to do this?
defer extConn.Close()
//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
2020-05-01 05:47:46 +00:00
serverStatus := ctx.Value(ctxServerStatus).(*api.Status)
defer func() {
serverStatus.ExtConnectionUnregister(extConn)
extConn.Close()
}()
//find the connection by domain name
conn, ok := serverStatus.ConnectionTable.ConnByDomain(hostname)
if !ok {
//matching connection can not be found based on ConnByDomain
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to match ", hostname, " to an existing connection")
//http.Error(, "Domain not supported", http.StatusBadRequest)
return
}
2020-05-01 05:47:46 +00:00
track := api.NewTrack(extConn, hostname)
serverStatus.ExtConnectionRegister(track)
remoteStr := extConn.RemoteAddr().String()
2020-05-01 07:06:14 +00:00
loginfo.Println("Domain Accepted", hostname, remoteStr)
var header *packer.Header
if rAddr, rPort, err := net.SplitHostPort(remoteStr); err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to decode hostport", remoteStr, err)
} else if port, err := strconv.Atoi(rPort); err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Printf("unable to parse port string %q: %v\n", rPort, err)
} else if header, err = packer.NewHeader(rAddr, port, service); err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to create packer header", err)
}
if header == nil {
return
}
for {
2020-05-06 17:11:13 +00:00
fmt.Println("xxyyzz buffer")
buffer, err := extConn.PeekAll()
if err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to peekAll", err)
return
}
2020-05-01 07:06:14 +00:00
loginfo.Println("Before Packer", hex.Dump(buffer))
p := packer.NewPacker(header)
p.Data.AppendBytes(buffer)
buf := p.PackV1()
2020-05-01 07:06:14 +00:00
//loginfo.Println(hex.Dump(buf.Bytes()))
//Bundle up the send request and dispatch
2020-05-01 05:47:46 +00:00
sendTrack := api.NewSendTrack(buf.Bytes(), hostname)
serverStatus.SendExtRequest(conn, sendTrack)
cnt := len(buffer)
if _, err = extConn.Discard(cnt); err != nil {
2020-05-01 07:06:14 +00:00
loginfo.Println("unable to discard", cnt, err)
return
}
}
}