diff --git a/internal/client/TLS.go b/internal/client/TLS.go index bffe2aa..942bffa 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -11,6 +11,8 @@ import ( log "github.com/sirupsen/logrus" ) +const appDataMaxLength = 16401 + type clientHelloFields struct { random []byte sessionId []byte diff --git a/internal/client/connector.go b/internal/client/connector.go index 0083476..420675e 100644 --- a/internal/client/connector.go +++ b/internal/client/connector.go @@ -63,9 +63,10 @@ func MakeSession(connConfig remoteConnConfig, authInfo authInfo, dialer common.D } seshConfig := mux.SessionConfig{ - Obfuscator: obfuscator, - Valve: nil, - Unordered: authInfo.Unordered, + Obfuscator: obfuscator, + Valve: nil, + Unordered: authInfo.Unordered, + MaxFrameSize: appDataMaxLength, } sesh := mux.MakeSession(authInfo.SessionId, seshConfig) diff --git a/internal/client/piper.go b/internal/client/piper.go index 44dfab7..6c62eae 100644 --- a/internal/client/piper.go +++ b/internal/client/piper.go @@ -1,13 +1,13 @@ package client import ( + "github.com/cbeuw/Cloak/internal/common" "io" "net" "sync/atomic" "time" mux "github.com/cbeuw/Cloak/internal/multiplex" - "github.com/cbeuw/Cloak/internal/util" log "github.com/sirupsen/logrus" ) @@ -136,8 +136,15 @@ func RouteTCP(localConfig localConnConfig, newSeshFunc func() *mux.Session) { stream.Close() return } - go util.Pipe(localConn, stream, 0) - util.Pipe(stream, localConn, localConfig.Timeout) + go func() { + if _, err := common.Copy(localConn, stream, 0); err != nil { + log.Debugf("copying stream to proxy client: %v", err) + } + }() + //util.Pipe(stream, localConn, localConfig.Timeout) + if _, err = common.Copy(stream, localConn, localConfig.Timeout); err != nil { + log.Debugf("copying proxy client to stream: %v", err) + } }() } diff --git a/internal/common/copy.go b/internal/common/copy.go new file mode 100644 index 0000000..20fcc09 --- /dev/null +++ b/internal/common/copy.go @@ -0,0 +1,104 @@ +/* +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* +Forked from https://golang.org/src/io/io.go +*/ +package common + +import ( + "io" + "net" + "time" +) + +// copyBuffer is the actual implementation of Copy and CopyBuffer. +// if buf is nil, one is allocated. +func Copy(dst net.Conn, src net.Conn, srcReadTimeout time.Duration) (written int64, err error) { + /* + // If the reader has a WriteTo method, use it to do the copy. + // Avoids an allocation and a copy. + if wt, ok := src.(WriterTo); ok { + return wt.WriteTo(dst) + } + // Similarly, if the writer has a ReadFrom method, use it to do the copy. + if rt, ok := dst.(ReaderFrom); ok { + return rt.ReadFrom(src) + } + + */ + //if buf == nil { + size := 32 * 1024 + /* + if l, ok := src.(*LimitedReader); ok && int64(size) > l.N { + if l.N < 1 { + size = 1 + } else { + size = int(l.N) + } + } + + */ + buf := make([]byte, size) + //} + for { + if srcReadTimeout != 0 { + src.SetReadDeadline(time.Now().Add(srcReadTimeout)) + /* + err = + if err != nil { + break + } + + */ + } + nr, er := src.Read(buf) + if nr > 0 { + var offset int + for offset < nr { + nw, ew := dst.Write(buf[offset:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + offset += nw + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return written, err +} diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index 67c464f..0f91999 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -33,8 +33,9 @@ type Obfuscator struct { // Used in Stream.Write. Add multiplexing headers, encrypt and add TLS header Obfs Obfser // Remove TLS header, decrypt and unmarshall frames - Deobfs Deobfser - SessionKey [32]byte + Deobfs Deobfser + SessionKey [32]byte + minOverhead int } func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser { @@ -137,10 +138,14 @@ func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser { } func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator *Obfuscator, err error) { + obfuscator = &Obfuscator{ + SessionKey: sessionKey, + } var payloadCipher cipher.AEAD switch encryptionMethod { case E_METHOD_PLAIN: payloadCipher = nil + obfuscator.minOverhead = 0 case E_METHOD_AES_GCM: var c cipher.Block c, err = aes.NewCipher(sessionKey[:]) @@ -151,19 +156,18 @@ func MakeObfuscator(encryptionMethod byte, sessionKey [32]byte) (obfuscator *Obf if err != nil { return } + obfuscator.minOverhead = payloadCipher.Overhead() case E_METHOD_CHACHA20_POLY1305: payloadCipher, err = chacha20poly1305.New(sessionKey[:]) if err != nil { return } + obfuscator.minOverhead = payloadCipher.Overhead() default: return nil, errors.New("Unknown encryption method") } - obfuscator = &Obfuscator{ - MakeObfs(sessionKey, payloadCipher), - MakeDeobfs(sessionKey, payloadCipher), - sessionKey, - } + obfuscator.Obfs = MakeObfs(sessionKey, payloadCipher) + obfuscator.Deobfs = MakeDeobfs(sessionKey, payloadCipher) return } diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index cfc12f6..1d6b1cb 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -13,7 +13,8 @@ import ( ) const ( - acceptBacklog = 1024 + acceptBacklog = 1024 + // TODO: will this be a signature? defaultSendRecvBufSize = 20480 ) @@ -29,6 +30,7 @@ type SessionConfig struct { Unordered bool + MaxFrameSize int // maximum size of the frame, including the header SendBufferSize int ReceiveBufferSize int } @@ -77,6 +79,9 @@ func MakeSession(id uint32, config SessionConfig) *Session { if config.ReceiveBufferSize <= 0 { sesh.ReceiveBufferSize = defaultSendRecvBufSize } + if config.MaxFrameSize <= 0 { + sesh.MaxFrameSize = defaultSendRecvBufSize - 1024 + } sbConfig := switchboardConfig{ valve: sesh.Valve, diff --git a/internal/multiplex/stream.go b/internal/multiplex/stream.go index b302f95..2826e18 100644 --- a/internal/multiplex/stream.go +++ b/internal/multiplex/stream.go @@ -96,11 +96,19 @@ func (s *Stream) Write(in []byte) (n int, err error) { return 0, ErrBrokenStream } + var payload []byte + maxDataLen := s.session.MaxFrameSize - HEADER_LEN - s.session.minOverhead + if len(in) <= maxDataLen { + payload = in + } else { + payload = in[:maxDataLen] + } + f := &Frame{ StreamID: s.id, Seq: atomic.AddUint64(&s.nextSendSeq, 1) - 1, Closing: C_NOOP, - Payload: in, + Payload: payload, } i, err := s.session.Obfs(f, s.obfsBuf) @@ -108,7 +116,7 @@ func (s *Stream) Write(in []byte) (n int, err error) { return i, err } n, err = s.session.sb.send(s.obfsBuf[:i], &s.assignedConnId) - log.Tracef("%v sent to remote through stream %v with err %v", len(in), s.id, err) + log.Tracef("%v sent to remote through stream %v with err %v", len(payload), s.id, err) if err != nil { if err == errBrokenSwitchboard { s.session.SetTerminalMsg(err.Error()) @@ -116,7 +124,7 @@ func (s *Stream) Write(in []byte) (n int, err error) { } return } - return len(in), nil + return len(payload), nil } diff --git a/internal/server/TLS.go b/internal/server/TLS.go index 2ec9ac0..c8d38ab 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -11,6 +11,8 @@ import ( log "github.com/sirupsen/logrus" ) +const appDataMaxLength = 16401 + type TLS struct{} var ErrBadClientHello = errors.New("non (or malformed) ClientHello") diff --git a/internal/server/dispatcher.go b/internal/server/dispatcher.go index 6721dec..46bd8fd 100644 --- a/internal/server/dispatcher.go +++ b/internal/server/dispatcher.go @@ -3,6 +3,7 @@ package server import ( "bytes" "encoding/base64" + "github.com/cbeuw/Cloak/internal/common" "github.com/cbeuw/Cloak/internal/util" "io" "net" @@ -72,6 +73,13 @@ func DispatchConnection(conn net.Conn, sta *State) { return } + seshConfig := mux.SessionConfig{ + Obfuscator: obfuscator, + Valve: nil, + Unordered: ci.Unordered, + MaxFrameSize: appDataMaxLength, + } + // adminUID can use the server as normal with unlimited QoS credits. The adminUID is not // added to the userinfo database. The distinction between going into the admin mode // and normal proxy mode is that sessionID needs == 0 for admin mode @@ -82,10 +90,6 @@ func DispatchConnection(conn net.Conn, sta *State) { return } log.Trace("finished handshake") - seshConfig := mux.SessionConfig{ - Obfuscator: obfuscator, - Valve: nil, - } sesh := mux.MakeSession(0, seshConfig) sesh.AddConnection(preparedConn) //TODO: Router could be nil in cnc mode @@ -113,11 +117,7 @@ func DispatchConnection(conn net.Conn, sta *State) { return } - sesh, existing, err := user.GetSession(ci.SessionId, mux.SessionConfig{ - Obfuscator: obfuscator, - Valve: nil, - Unordered: ci.Unordered, - }) + sesh, existing, err := user.GetSession(ci.SessionId, seshConfig) if err != nil { user.CloseSession(ci.SessionId, "") log.Error(err) @@ -173,9 +173,17 @@ func DispatchConnection(conn net.Conn, sta *State) { } log.Tracef("%v endpoint has been successfully connected", ci.ProxyMethod) - go util.Pipe(localConn, newStream, sta.Timeout) - go util.Pipe(newStream, localConn, 0) - + //TODO: stream timeout + go func() { + if _, err := common.Copy(localConn, newStream, sta.Timeout); err != nil { + log.Debugf("copying stream to proxy client: %v", err) + } + }() + go func() { + if _, err := common.Copy(newStream, localConn, 0); err != nil { + log.Debugf("copying proxy client to stream: %v", err) + } + }() } } diff --git a/internal/util/util.go b/internal/util/util.go index 1c6c54e..9a74c74 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -4,8 +4,6 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "io" - "net" "time" log "github.com/sirupsen/logrus" @@ -58,18 +56,12 @@ func CryptoRandRead(buf []byte) { log.Fatal("Cannot get cryptographic random bytes after 10 retries") } -// ReadTLS reads TLS data according to its record layer -//func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) { -//} - +/* 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 // because of the salt/mac // 16408 is the max TLS message size on Firefox buf := make([]byte, 16378) - if srcReadTimeout != 0 { - src.SetReadDeadline(time.Now().Add(srcReadTimeout)) - } for { if srcReadTimeout != 0 { src.SetReadDeadline(time.Now().Add(srcReadTimeout)) @@ -88,3 +80,5 @@ func Pipe(dst net.Conn, src net.Conn, srcReadTimeout time.Duration) { } } } + +*/