Refactor client side transport (breaks server)

This commit is contained in:
Andy Wang 2020-04-08 20:53:09 +01:00
parent 96845700f8
commit 7bfae8accd
13 changed files with 148 additions and 123 deletions

View File

@ -46,17 +46,6 @@ func addExtRec(typ []byte, data []byte) []byte {
return ret return ret
} }
func addRecordLayer(input []byte, typ []byte, ver []byte) []byte {
length := make([]byte, 2)
binary.BigEndian.PutUint16(length, uint16(len(input)))
ret := make([]byte, 5+len(input))
copy(ret[0:1], typ)
copy(ret[1:3], ver)
copy(ret[3:5], length)
copy(ret[5:], input)
return ret
}
func genStegClientHello(ai authenticationPayload, serverName string) (ret clientHelloFields) { func genStegClientHello(ai authenticationPayload, serverName string) (ret clientHelloFields) {
// random is marshalled ephemeral pub key 32 bytes // random is marshalled ephemeral pub key 32 bytes
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare // The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
@ -68,33 +57,31 @@ func genStegClientHello(ai authenticationPayload, serverName string) (ret client
} }
type DirectTLS struct { type DirectTLS struct {
browser *util.TLSConn
browser browser
} }
func (DirectTLS) HasRecordLayer() bool { return true } // NewClientTransport handles the TLS handshake for a given conn and returns the sessionKey
func (DirectTLS) UnitReadFunc() func(net.Conn, []byte) (int, error) { return util.ReadTLS }
// PrepareConnection handles the TLS handshake for a given conn and returns the sessionKey
// if the server proceed with Cloak authentication // if the server proceed with Cloak authentication
func (tls DirectTLS) PrepareConnection(authInfo *authInfo, conn net.Conn) (preparedConn net.Conn, sessionKey [32]byte, err error) { func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) {
preparedConn = conn
payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now()) payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now())
chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain)) chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain))
chWithRecordLayer := addRecordLayer(chOnly, []byte{0x16}, []byte{0x03, 0x01}) chWithRecordLayer := util.AddRecordLayer(chOnly, util.Handshake, util.VersionTLS11)
_, err = preparedConn.Write(chWithRecordLayer) _, err = rawConn.Write(chWithRecordLayer)
if err != nil { if err != nil {
return return
} }
log.Trace("client hello sent successfully") log.Trace("client hello sent successfully")
tls.TLSConn = &util.TLSConn{Conn: rawConn}
buf := make([]byte, 1024) buf := make([]byte, 1024)
log.Trace("waiting for ServerHello") log.Trace("waiting for ServerHello")
_, err = util.ReadTLS(preparedConn, buf) _, err = tls.Read(buf)
if err != nil { if err != nil {
return return
} }
encrypted := append(buf[11:43], buf[89:121]...) encrypted := append(buf[6:38], buf[84:116]...)
nonce := encrypted[0:12] nonce := encrypted[0:12]
ciphertextWithTag := encrypted[12:60] ciphertextWithTag := encrypted[12:60]
sessionKeySlice, err := util.AESGCMDecrypt(nonce, sharedSecret[:], ciphertextWithTag) sessionKeySlice, err := util.AESGCMDecrypt(nonce, sharedSecret[:], ciphertextWithTag)
@ -105,12 +92,11 @@ func (tls DirectTLS) PrepareConnection(authInfo *authInfo, conn net.Conn) (prepa
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
// ChangeCipherSpec and EncryptedCert (in the format of application data) // ChangeCipherSpec and EncryptedCert (in the format of application data)
_, err = util.ReadTLS(preparedConn, buf) _, err = tls.Read(buf)
if err != nil { if err != nil {
return return
} }
} }
return sessionKey, nil
return preparedConn, sessionKey, nil
} }

View File

@ -30,7 +30,7 @@ type authInfo struct {
// makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and // makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and
// encrypt the authenticationPayload // encrypt the authenticationPayload
func makeAuthenticationPayload(authInfo *authInfo, randReader io.Reader, time time.Time) (ret authenticationPayload, sharedSecret [32]byte) { func makeAuthenticationPayload(authInfo authInfo, randReader io.Reader, time time.Time) (ret authenticationPayload, sharedSecret [32]byte) {
/* /*
Authentication data: Authentication data:
+----------+----------------+---------------------+-------------+--------------+--------+------------+ +----------+----------------+---------------------+-------------+--------------+--------+------------+

View File

@ -10,14 +10,14 @@ import (
func TestMakeAuthenticationPayload(t *testing.T) { func TestMakeAuthenticationPayload(t *testing.T) {
tests := []struct { tests := []struct {
authInfo *authInfo authInfo authInfo
seed io.Reader seed io.Reader
time time.Time time time.Time
expPayload authenticationPayload expPayload authenticationPayload
expSecret [32]byte expSecret [32]byte
}{ }{
{ {
&authInfo{ authInfo{
Unordered: false, Unordered: false,
SessionId: 3421516597, SessionId: 3421516597,
UID: []byte{ UID: []byte{

View File

@ -18,10 +18,10 @@ type remoteConnConfig struct {
KeepAlive time.Duration KeepAlive time.Duration
Protector func(string, string, syscall.RawConn) error Protector func(string, string, syscall.RawConn) error
RemoteAddr string RemoteAddr string
Transport Transport TransportMaker func() Transport
} }
func MakeSession(connConfig *remoteConnConfig, authInfo *authInfo, isAdmin bool) *mux.Session { func MakeSession(connConfig remoteConnConfig, authInfo authInfo, isAdmin bool) *mux.Session {
log.Info("Attempting to start a new session") log.Info("Attempting to start a new session")
if !isAdmin { if !isAdmin {
// sessionID is usergenerated. There shouldn't be a security concern because the scope of // sessionID is usergenerated. There shouldn't be a security concern because the scope of
@ -48,16 +48,17 @@ func MakeSession(connConfig *remoteConnConfig, authInfo *authInfo, isAdmin bool)
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
goto makeconn goto makeconn
} }
var sk [32]byte
remoteConn, sk, err = connConfig.Transport.PrepareConnection(authInfo, remoteConn) transportConn := connConfig.TransportMaker()
sk, err := transportConn.Handshake(remoteConn, authInfo)
if err != nil { if err != nil {
remoteConn.Close() transportConn.Close()
log.Errorf("Failed to prepare connection to remote: %v", err) log.Errorf("Failed to prepare connection to remote: %v", err)
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
goto makeconn goto makeconn
} }
_sessionKey.Store(sk) _sessionKey.Store(sk)
connsCh <- remoteConn connsCh <- transportConn
wg.Done() wg.Done()
}() }()
} }
@ -65,7 +66,7 @@ func MakeSession(connConfig *remoteConnConfig, authInfo *authInfo, isAdmin bool)
log.Debug("All underlying connections established") log.Debug("All underlying connections established")
sessionKey := _sessionKey.Load().([32]byte) sessionKey := _sessionKey.Load().([32]byte)
obfuscator, err := mux.MakeObfuscator(authInfo.EncryptionMethod, sessionKey, connConfig.Transport.HasRecordLayer()) obfuscator, err := mux.MakeObfuscator(authInfo.EncryptionMethod, sessionKey)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -73,7 +74,6 @@ func MakeSession(connConfig *remoteConnConfig, authInfo *authInfo, isAdmin bool)
seshConfig := mux.SessionConfig{ seshConfig := mux.SessionConfig{
Obfuscator: obfuscator, Obfuscator: obfuscator,
Valve: nil, Valve: nil,
UnitRead: connConfig.Transport.UnitReadFunc(),
Unordered: authInfo.Unordered, Unordered: authInfo.Unordered,
} }
sesh := mux.MakeSession(authInfo.SessionId, seshConfig) sesh := mux.MakeSession(authInfo.SessionId, seshConfig)

View File

@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func RouteUDP(localConfig *localConnConfig, newSeshFunc func() *mux.Session) { func RouteUDP(localConfig localConnConfig, newSeshFunc func() *mux.Session) {
var sesh *mux.Session var sesh *mux.Session
localUDPAddr, err := net.ResolveUDPAddr("udp", localConfig.LocalAddr) localUDPAddr, err := net.ResolveUDPAddr("udp", localConfig.LocalAddr)
if err != nil { if err != nil {
@ -100,7 +100,7 @@ start:
} }
func RouteTCP(localConfig *localConnConfig, newSeshFunc func() *mux.Session) { func RouteTCP(localConfig localConnConfig, newSeshFunc func() *mux.Session) {
tcpListener, err := net.Listen("tcp", localConfig.LocalAddr) tcpListener, err := net.Listen("tcp", localConfig.LocalAddr)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -100,13 +100,12 @@ func ParseConfig(conf string) (raw *rawConfig, err error) {
return return
} }
func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConnConfig, auth *authInfo, err error) { func (raw *rawConfig) SplitConfigs() (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) {
nullErr := func(field string) (local *localConnConfig, remote *remoteConnConfig, auth *authInfo, err error) { nullErr := func(field string) (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) {
err = fmt.Errorf("%v cannot be empty", field) err = fmt.Errorf("%v cannot be empty", field)
return return
} }
auth = new(authInfo)
auth.UID = raw.UID auth.UID = raw.UID
auth.Unordered = raw.UDP auth.Unordered = raw.UDP
if raw.ServerName == "" { if raw.ServerName == "" {
@ -145,7 +144,6 @@ func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConn
return return
} }
remote = new(remoteConnConfig)
if raw.RemoteHost == "" { if raw.RemoteHost == "" {
return nullErr("RemoteHost") return nullErr("RemoteHost")
} }
@ -161,7 +159,11 @@ func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConn
// Transport and (if TLS mode), browser // Transport and (if TLS mode), browser
switch strings.ToLower(raw.Transport) { switch strings.ToLower(raw.Transport) {
case "cdn": case "cdn":
remote.Transport = WSOverTLS{remote.RemoteAddr} remote.TransportMaker = func() Transport {
return &WSOverTLS{
cdnDomainPort: remote.RemoteAddr,
}
}
case "direct": case "direct":
fallthrough fallthrough
default: default:
@ -174,7 +176,11 @@ func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConn
default: default:
browser = &Chrome{} browser = &Chrome{}
} }
remote.Transport = DirectTLS{browser} remote.TransportMaker = func() Transport {
return &DirectTLS{
browser: browser,
}
}
} }
// KeepAlive // KeepAlive
@ -184,8 +190,6 @@ func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConn
remote.KeepAlive = remote.KeepAlive * time.Second remote.KeepAlive = remote.KeepAlive * time.Second
} }
local = new(localConnConfig)
if raw.LocalHost == "" { if raw.LocalHost == "" {
return nullErr("LocalHost") return nullErr("LocalHost")
} }

View File

@ -3,7 +3,6 @@ package client
import "net" import "net"
type Transport interface { type Transport interface {
PrepareConnection(*authInfo, net.Conn) (net.Conn, [32]byte, error) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error)
HasRecordLayer() bool net.Conn
UnitReadFunc() func(net.Conn, []byte) (int, error)
} }

View File

@ -16,47 +16,44 @@ import (
) )
type WSOverTLS struct { type WSOverTLS struct {
*util.WebSocketConn
cdnDomainPort string cdnDomainPort string
} }
func (WSOverTLS) HasRecordLayer() bool { return false } func (ws *WSOverTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) {
func (WSOverTLS) UnitReadFunc() func(net.Conn, []byte) (int, error) { return util.ReadWebSocket }
func (ws WSOverTLS) PrepareConnection(authInfo *authInfo, cdnConn net.Conn) (preparedConn net.Conn, sessionKey [32]byte, err error) {
utlsConfig := &utls.Config{ utlsConfig := &utls.Config{
ServerName: authInfo.MockDomain, ServerName: authInfo.MockDomain,
InsecureSkipVerify: true, InsecureSkipVerify: true,
} }
uconn := utls.UClient(cdnConn, utlsConfig, utls.HelloChrome_Auto) uconn := utls.UClient(rawConn, utlsConfig, utls.HelloChrome_Auto)
err = uconn.Handshake() err = uconn.Handshake()
preparedConn = uconn
if err != nil { if err != nil {
return return
} }
u, err := url.Parse("ws://" + ws.cdnDomainPort) u, err := url.Parse("ws://" + ws.cdnDomainPort)
if err != nil { if err != nil {
return preparedConn, sessionKey, fmt.Errorf("failed to parse ws url: %v", err) return sessionKey, fmt.Errorf("failed to parse ws url: %v", err)
} }
payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now()) payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now())
header := http.Header{} header := http.Header{}
header.Add("hidden", base64.StdEncoding.EncodeToString(append(payload.randPubKey[:], payload.ciphertextWithTag[:]...))) header.Add("hidden", base64.StdEncoding.EncodeToString(append(payload.randPubKey[:], payload.ciphertextWithTag[:]...)))
c, _, err := websocket.NewClient(preparedConn, u, header, 16480, 16480) c, _, err := websocket.NewClient(uconn, u, header, 16480, 16480)
if err != nil { if err != nil {
return preparedConn, sessionKey, fmt.Errorf("failed to handshake: %v", err) return sessionKey, fmt.Errorf("failed to handshake: %v", err)
} }
preparedConn = &util.WebSocketConn{Conn: c} ws.WebSocketConn = &util.WebSocketConn{Conn: c}
buf := make([]byte, 128) buf := make([]byte, 128)
n, err := preparedConn.Read(buf) n, err := ws.Read(buf)
if err != nil { if err != nil {
return preparedConn, sessionKey, fmt.Errorf("failed to read reply: %v", err) return sessionKey, fmt.Errorf("failed to read reply: %v", err)
} }
if n != 60 { if n != 60 {
return preparedConn, sessionKey, errors.New("reply must be 60 bytes") return sessionKey, errors.New("reply must be 60 bytes")
} }
reply := buf[:60] reply := buf[:60]

View File

@ -37,11 +37,7 @@ type Obfuscator struct {
SessionKey [32]byte SessionKey [32]byte
} }
func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD, hasRecordLayer bool) Obfser { func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
var rlLen int
if hasRecordLayer {
rlLen = 5
}
obfs := func(f *Frame, buf []byte) (int, error) { obfs := func(f *Frame, buf []byte) (int, error) {
// we need the encrypted data to be at least 8 bytes to be used as nonce for salsa20 stream header encryption // we need the encrypted data to be at least 8 bytes to be used as nonce for salsa20 stream header encryption
// this will be the case if the encryption method is an AEAD cipher, however for plain, it's well possible // this will be the case if the encryption method is an AEAD cipher, however for plain, it's well possible
@ -56,15 +52,15 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD, hasRecordLayer bool)
} }
// usefulLen is the amount of bytes that will be eventually sent off // usefulLen is the amount of bytes that will be eventually sent off
usefulLen := rlLen + HEADER_LEN + len(f.Payload) + int(extraLen) usefulLen := HEADER_LEN + len(f.Payload) + int(extraLen)
if len(buf) < usefulLen { if len(buf) < usefulLen {
return 0, io.ErrShortBuffer return 0, io.ErrShortBuffer
} }
// we do as much in-place as possible to save allocation // we do as much in-place as possible to save allocation
useful := buf[:usefulLen] // (tls header) + payload + potential overhead useful := buf[:usefulLen] // stream header + payload + potential overhead
header := useful[rlLen : rlLen+HEADER_LEN] header := useful[:HEADER_LEN]
encryptedPayloadWithExtra := useful[rlLen+HEADER_LEN:] encryptedPayloadWithExtra := useful[HEADER_LEN:]
putU32(header[0:4], f.StreamID) putU32(header[0:4], f.StreamID)
putU64(header[4:12], f.Seq) putU64(header[4:12], f.Seq)
@ -84,39 +80,23 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD, hasRecordLayer bool)
nonce := encryptedPayloadWithExtra[len(encryptedPayloadWithExtra)-8:] nonce := encryptedPayloadWithExtra[len(encryptedPayloadWithExtra)-8:]
salsa20.XORKeyStream(header, header, nonce, &salsaKey) salsa20.XORKeyStream(header, header, nonce, &salsaKey)
if hasRecordLayer {
recordLayer := useful[0:5]
// We don't use util.AddRecordLayer here to avoid unnecessary malloc
recordLayer[0] = 0x17
recordLayer[1] = 0x03
recordLayer[2] = 0x03
binary.BigEndian.PutUint16(recordLayer[3:5], uint16(HEADER_LEN+len(encryptedPayloadWithExtra)))
}
// Composing final obfsed message
return usefulLen, nil return usefulLen, nil
} }
return obfs return obfs
} }
func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD, hasRecordLayer bool) Deobfser { func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser {
var rlLen int // stream header length + minimum data size (i.e. nonce size of salsa20)
if hasRecordLayer { minInputLen := HEADER_LEN + 8
rlLen = 5
}
// record layer length + stream header length + minimum data size (i.e. nonce size of salsa20)
minInputLen := rlLen + HEADER_LEN + 8
deobfs := func(in []byte) (*Frame, error) { deobfs := func(in []byte) (*Frame, error) {
if len(in) < minInputLen { if len(in) < minInputLen {
return nil, fmt.Errorf("input size %v, but it cannot be shorter than %v bytes", len(in), minInputLen) return nil, fmt.Errorf("input size %v, but it cannot be shorter than %v bytes", len(in), minInputLen)
} }
peeled := make([]byte, len(in)-rlLen) header := in[:HEADER_LEN]
copy(peeled, in[rlLen:]) pldWithOverHead := in[HEADER_LEN:] // payload + potential overhead
header := peeled[:HEADER_LEN] nonce := in[len(in)-8:]
pldWithOverHead := peeled[HEADER_LEN:] // payload + potential overhead
nonce := peeled[len(peeled)-8:]
salsa20.XORKeyStream(header, header, nonce, &salsaKey) salsa20.XORKeyStream(header, header, nonce, &salsaKey)
streamID := u32(header[0:4]) streamID := u32(header[0:4])
@ -156,7 +136,7 @@ func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD, hasRecordLayer boo
return deobfs return deobfs
} }
func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte, hasRecordLayer bool) (obfuscator *Obfuscator, err error) { func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator *Obfuscator, err error) {
var payloadCipher cipher.AEAD var payloadCipher cipher.AEAD
switch encryptionMethod { switch encryptionMethod {
case E_METHOD_PLAIN: case E_METHOD_PLAIN:
@ -181,8 +161,8 @@ func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte, hasRecordLayer b
} }
obfuscator = &Obfuscator{ obfuscator = &Obfuscator{
MakeObfs(sessionKey, payloadCipher, hasRecordLayer), MakeObfs(sessionKey, payloadCipher),
MakeDeobfs(sessionKey, payloadCipher, hasRecordLayer), MakeDeobfs(sessionKey, payloadCipher),
sessionKey, sessionKey,
} }
return return

View File

@ -27,9 +27,6 @@ type SessionConfig struct {
Valve Valve
// This is supposed to read one TLS message.
UnitRead func(net.Conn, []byte) (int, error)
Unordered bool Unordered bool
SendBufferSize int SendBufferSize int

View File

@ -156,7 +156,7 @@ func (sb *switchboard) deplex(connId uint32, conn net.Conn) {
defer conn.Close() defer conn.Close()
buf := make([]byte, sb.recvBufferSize) buf := make([]byte, sb.recvBufferSize)
for { for {
n, err := sb.session.UnitRead(conn, buf) n, err := conn.Read(buf)
sb.valve.rxWait(n) sb.valve.rxWait(n)
sb.valve.AddRx(int64(n)) sb.valve.AddRx(int64(n))
if err != nil { if err != nil {

79
internal/util/tls.go Normal file
View File

@ -0,0 +1,79 @@
package util
import (
"encoding/binary"
"io"
"net"
"time"
)
const (
VersionTLS11 = 0x0301
VersionTLS13 = 0x0303
Handshake = 22
ApplicationData = 23
)
func AddRecordLayer(input []byte, typ byte, ver uint16) []byte {
length := make([]byte, 2)
binary.BigEndian.PutUint16(length, uint16(len(input)))
ret := make([]byte, 5+len(input))
ret[0] = typ
binary.BigEndian.PutUint16(ret[1:3], ver)
copy(ret[3:5], length)
copy(ret[5:], input)
return ret
}
type TLSConn struct {
net.Conn
}
func (tls *TLSConn) LocalAddr() net.Addr {
return tls.Conn.LocalAddr()
}
func (tls *TLSConn) RemoteAddr() net.Addr {
return tls.Conn.RemoteAddr()
}
func (tls *TLSConn) SetDeadline(t time.Time) error {
return tls.Conn.SetDeadline(t)
}
func (tls *TLSConn) SetReadDeadline(t time.Time) error {
return tls.Conn.SetReadDeadline(t)
}
func (tls *TLSConn) SetWriteDeadline(t time.Time) error {
return tls.Conn.SetWriteDeadline(t)
}
func (tls *TLSConn) Read(buffer []byte) (n int, err error) {
// TCP is a stream. Multiple TLS messages can arrive at the same time,
// a single message can also be segmented due to MTU of the IP layer.
// This function guareentees a single TLS message to be read and everything
// else is left in the buffer.
_, err = io.ReadFull(tls.Conn, buffer[:5])
if err != nil {
return
}
dataLength := int(binary.BigEndian.Uint16(buffer[3:5]))
if dataLength > len(buffer) {
err = io.ErrShortBuffer
return
}
return io.ReadFull(tls.Conn, buffer[:dataLength])
}
func (tls *TLSConn) Write(in []byte) (n int, err error) {
// TODO: write record layer directly first?
toWrite := AddRecordLayer(in, ApplicationData, VersionTLS13)
return tls.Conn.Write(toWrite)
}
func (tls *TLSConn) Close() error {
return tls.Conn.Close()
}

View File

@ -4,7 +4,6 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/binary"
"io" "io"
"net" "net"
"time" "time"
@ -60,24 +59,8 @@ func CryptoRandRead(buf []byte) {
} }
// ReadTLS reads TLS data according to its record layer // ReadTLS reads TLS data according to its record layer
func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) { //func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) {
// TCP is a stream. Multiple TLS messages can arrive at the same time, //}
// a single message can also be segmented due to MTU of the IP layer.
// This function guareentees a single TLS message to be read and everything
// else is left in the buffer.
_, err = io.ReadFull(conn, buffer[:5])
if err != nil {
return
}
dataLength := int(binary.BigEndian.Uint16(buffer[3:5]))
if dataLength > len(buffer) {
err = io.ErrShortBuffer
return
}
n, err = io.ReadFull(conn, buffer[5:dataLength+5])
return n + 5, err
}
func Pipe(dst net.Conn, src net.Conn, srcReadTimeout time.Duration) { func Pipe(dst net.Conn, src net.Conn, srcReadTimeout time.Duration) {
// The maximum size of TLS message will be 16380+14+16. 14 because of the stream header and 16 // The maximum size of TLS message will be 16380+14+16. 14 because of the stream header and 16