diff --git a/internal/client/TLS.go b/internal/client/TLS.go index bc89e83..7962551 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -63,7 +63,7 @@ type DirectTLS struct { // NewClientTransport handles the TLS handshake for a given conn and returns the sessionKey // if the server proceed with Cloak authentication -func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) { +func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) { payload, sharedSecret := makeAuthenticationPayload(authInfo) chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain)) chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11) diff --git a/internal/client/auth.go b/internal/client/auth.go index 270f65b..4b86887 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -17,7 +17,7 @@ type authenticationPayload struct { // makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and // encrypt the authenticationPayload -func makeAuthenticationPayload(authInfo authInfo) (ret authenticationPayload, sharedSecret [32]byte) { +func makeAuthenticationPayload(authInfo AuthInfo) (ret authenticationPayload, sharedSecret [32]byte) { /* Authentication data: +----------+----------------+---------------------+-------------+--------------+--------+------------+ diff --git a/internal/client/auth_test.go b/internal/client/auth_test.go index 6933b68..006bca9 100644 --- a/internal/client/auth_test.go +++ b/internal/client/auth_test.go @@ -10,12 +10,12 @@ import ( func TestMakeAuthenticationPayload(t *testing.T) { tests := []struct { - authInfo authInfo + authInfo AuthInfo expPayload authenticationPayload expSecret [32]byte }{ { - authInfo{ + AuthInfo{ Unordered: false, SessionId: 3421516597, UID: []byte{ diff --git a/internal/client/connector.go b/internal/client/connector.go index c399274..350d911 100644 --- a/internal/client/connector.go +++ b/internal/client/connector.go @@ -13,7 +13,7 @@ import ( log "github.com/sirupsen/logrus" ) -func MakeSession(connConfig remoteConnConfig, authInfo authInfo, dialer common.Dialer, isAdmin bool) *mux.Session { +func MakeSession(connConfig RemoteConnConfig, authInfo AuthInfo, dialer common.Dialer, isAdmin bool) *mux.Session { log.Info("Attempting to start a new session") //TODO: let caller set this if !isAdmin { diff --git a/internal/client/piper.go b/internal/client/piper.go index 7a3e3dd..1c1a03a 100644 --- a/internal/client/piper.go +++ b/internal/client/piper.go @@ -11,7 +11,7 @@ import ( 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 localUDPAddr, err := net.ResolveUDPAddr("udp", localConfig.LocalAddr) if err != nil { diff --git a/internal/client/state.go b/internal/client/state.go index 66dc330..ce963f5 100644 --- a/internal/client/state.go +++ b/internal/client/state.go @@ -38,19 +38,19 @@ type RawConfig struct { KeepAlive int // nullable } -type remoteConnConfig struct { +type RemoteConnConfig struct { NumConn int KeepAlive time.Duration RemoteAddr string TransportMaker func() Transport } -type localConnConfig struct { +type LocalConnConfig struct { LocalAddr string Timeout time.Duration } -type authInfo struct { +type AuthInfo struct { UID []byte SessionId uint32 ProxyMethod string @@ -120,8 +120,8 @@ func ParseConfig(conf string) (raw *RawConfig, err error) { return } -func (raw *RawConfig) SplitConfigs(worldState common.WorldState) (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) { - nullErr := func(field string) (local localConnConfig, remote remoteConnConfig, auth authInfo, err error) { +func (raw *RawConfig) SplitConfigs(worldState common.WorldState) (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) return } diff --git a/internal/client/transport.go b/internal/client/transport.go index 510e4fd..e86ffd5 100644 --- a/internal/client/transport.go +++ b/internal/client/transport.go @@ -5,6 +5,6 @@ import ( ) type Transport interface { - Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) + Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) net.Conn } diff --git a/internal/client/websocket.go b/internal/client/websocket.go index ed037df..86a1698 100644 --- a/internal/client/websocket.go +++ b/internal/client/websocket.go @@ -18,7 +18,7 @@ type WSOverTLS struct { cdnDomainPort string } -func (ws *WSOverTLS) Handshake(rawConn net.Conn, authInfo authInfo) (sessionKey [32]byte, err error) { +func (ws *WSOverTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) { utlsConfig := &utls.Config{ ServerName: authInfo.MockDomain, InsecureSkipVerify: true, diff --git a/internal/integration_test/integration_test.go b/internal/integration_test/integration_test.go deleted file mode 100644 index 28409ea..0000000 --- a/internal/integration_test/integration_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package integration_test - -import ( - "encoding/base64" - "github.com/cbeuw/Cloak/internal/client" - "github.com/cbeuw/Cloak/internal/server" -) - -var bypassUID = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} -var publicKey, _ = base64.StdEncoding.DecodeString("7f7TuKrs264VNSgMno8PkDlyhGhVuOSR8JHLE6H4Ljc=") -var privateKey, _ = base64.StdEncoding.DecodeString("SMWeC6VuZF8S/id65VuFQFlfa7hTEJBpL6wWhqPP100=") - -var clientConfig = client.RawConfig{ - ServerName: "www.example.com", - ProxyMethod: "test", - EncryptionMethod: "plain", - UID: bypassUID, - PublicKey: publicKey, - NumConn: 3, - UDP: false, - BrowserSig: "chrome", - Transport: "direct", -} - -var serverState = server.State{ - ProxyBook: nil, - ProxyDialer: nil, - AdminUID: nil, - Timeout: 0, - BypassUID: nil, - RedirHost: nil, - RedirPort: "", - RedirDialer: nil, - Panel: nil, - LocalAPIRouter: nil, -} diff --git a/internal/server/auth.go b/internal/server/auth.go index 4de28e6..d6fd6bd 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -78,7 +78,7 @@ func AuthFirstPacket(firstPacket []byte, sta *State) (info ClientInfo, finisher return } - fragments, finisher, err := transport.processFirstPacket(firstPacket, sta.staticPv) + fragments, finisher, err := transport.processFirstPacket(firstPacket, sta.StaticPv) if err != nil { return } diff --git a/internal/server/auth_test.go b/internal/server/auth_test.go index 7eb5775..a0f7b24 100644 --- a/internal/server/auth_test.go +++ b/internal/server/auth_test.go @@ -130,7 +130,7 @@ func TestAuthFirstPacket(t *testing.T) { getNewState := func() *State { sta, _ := InitState(RawConfig{}, common.WorldOfTime(time.Unix(1565998966, 0))) - sta.staticPv = p.(crypto.PrivateKey) + sta.StaticPv = p.(crypto.PrivateKey) sta.ProxyBook["shadowsocks"] = nil return sta } @@ -168,7 +168,7 @@ func TestAuthFirstPacket(t *testing.T) { }) t.Run("Websocket correct", func(t *testing.T) { sta, _ := InitState(RawConfig{}, common.WorldOfTime(time.Unix(1584358419, 0))) - sta.staticPv = p.(crypto.PrivateKey) + sta.StaticPv = p.(crypto.PrivateKey) sta.ProxyBook["shadowsocks"] = nil req := `GET / HTTP/1.1 diff --git a/internal/server/state.go b/internal/server/state.go index e26ad02..ba686b0 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -40,7 +40,7 @@ type State struct { //KeepAlive time.Duration BypassUID map[[16]byte]struct{} - staticPv crypto.PrivateKey + StaticPv crypto.PrivateKey // TODO: this doesn't have to be a net.Addr; resolution is done in Dial automatically RedirHost net.Addr @@ -48,7 +48,7 @@ type State struct { RedirDialer common.Dialer usedRandomM sync.RWMutex - usedRandom map[[32]byte]int64 + UsedRandom map[[32]byte]int64 Panel *userPanel LocalAPIRouter *gmux.Router @@ -148,7 +148,7 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er sta = &State{ BypassUID: make(map[[16]byte]struct{}), ProxyBook: map[string]net.Addr{}, - usedRandom: map[[32]byte]int64{}, + UsedRandom: map[[32]byte]int64{}, RedirDialer: &net.Dialer{}, WorldState: worldState, } @@ -188,7 +188,7 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er var pv [32]byte copy(pv[:], preParse.PrivateKey) - sta.staticPv = &pv + sta.StaticPv = &pv sta.AdminUID = preParse.AdminUID @@ -221,9 +221,9 @@ func (sta *State) UsedRandomCleaner() { for { time.Sleep(CACHE_CLEAN_INTERVAL) sta.usedRandomM.Lock() - for key, t := range sta.usedRandom { + for key, t := range sta.UsedRandom { if time.Unix(t, 0).Before(sta.WorldState.Now().Add(TIMESTAMP_TOLERANCE)) { - delete(sta.usedRandom, key) + delete(sta.UsedRandom, key) } } sta.usedRandomM.Unlock() @@ -232,8 +232,8 @@ func (sta *State) UsedRandomCleaner() { func (sta *State) registerRandom(r [32]byte) bool { sta.usedRandomM.Lock() - _, used := sta.usedRandom[r] - sta.usedRandom[r] = sta.WorldState.Now().Unix() + _, used := sta.UsedRandom[r] + sta.UsedRandom[r] = sta.WorldState.Now().Unix() sta.usedRandomM.Unlock() return used } diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go new file mode 100644 index 0000000..9ef9a90 --- /dev/null +++ b/internal/test/integration_test.go @@ -0,0 +1,174 @@ +package test + +import ( + "bytes" + "encoding/base64" + "github.com/cbeuw/Cloak/internal/client" + "github.com/cbeuw/Cloak/internal/common" + mux "github.com/cbeuw/Cloak/internal/multiplex" + "github.com/cbeuw/Cloak/internal/server" + "github.com/cbeuw/Cloak/internal/server/usermanager" + "github.com/cbeuw/connutil" + "io" + "io/ioutil" + "math/rand" + "net" + "os" + "sync" + "testing" + "time" + + log "github.com/sirupsen/logrus" +) + +func serveEcho(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + // TODO: pass the error back + return + } + go func() { + _, err := io.Copy(conn, conn) + if err != nil { + // TODO: pass the error back + return + } + }() + } +} + +var bypassUID = [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} +var publicKey, _ = base64.StdEncoding.DecodeString("7f7TuKrs264VNSgMno8PkDlyhGhVuOSR8JHLE6H4Ljc=") +var privateKey, _ = base64.StdEncoding.DecodeString("SMWeC6VuZF8S/id65VuFQFlfa7hTEJBpL6wWhqPP100=") + +func basicClientConfigs(state common.WorldState) (client.LocalConnConfig, client.RemoteConnConfig, client.AuthInfo) { + var clientConfig = client.RawConfig{ + ServerName: "www.example.com", + ProxyMethod: "test", + EncryptionMethod: "plain", + UID: bypassUID[:], + PublicKey: publicKey, + NumConn: 4, + UDP: false, + Transport: "direct", + RemoteHost: "fake.com", + RemotePort: "9999", + LocalHost: "127.0.0.1", + LocalPort: "9999", + } + lcl, rmt, auth, _ := clientConfig.SplitConfigs(state) + return lcl, rmt, auth +} + +func basicServerState(ws common.WorldState, db *os.File) *server.State { + manager, _ := usermanager.MakeLocalManager(db.Name()) + var pv [32]byte + copy(pv[:], privateKey) + serverState := &server.State{ + ProxyBook: map[string]net.Addr{"test": &net.TCPAddr{}}, + UsedRandom: map[[32]byte]int64{}, + Timeout: 0, + BypassUID: map[[16]byte]struct{}{bypassUID: {}}, + RedirHost: &net.TCPAddr{}, + RedirPort: "9999", + Panel: server.MakeUserPanel(manager), + LocalAPIRouter: nil, + StaticPv: &pv, + WorldState: ws, + } + return serverState +} + +func establishSession(lcc client.LocalConnConfig, rcc client.RemoteConnConfig, ai client.AuthInfo, serverState *server.State) (common.Dialer, net.Listener, common.Dialer, net.Listener, error) { + // transport + ckClientDialer, ckServerListener := connutil.DialerListener(128) + + clientSeshMaker := func() *mux.Session { + return client.MakeSession(rcc, ai, ckClientDialer, false) + } + + proxyToCkClientD, proxyToCkClientL := connutil.DialerListener(128) + go client.RouteTCP(proxyToCkClientL, lcc.Timeout, clientSeshMaker) + + // set up server + ckServerToProxyD, ckServerToProxyL := connutil.DialerListener(128) + ckServerToWebD, ckServerToWebL := connutil.DialerListener(128) + serverState.ProxyDialer = ckServerToProxyD + serverState.RedirDialer = ckServerToWebD + + go server.Serve(ckServerListener, serverState) + + return proxyToCkClientD, ckServerToProxyL, ckClientDialer, ckServerToWebL, nil +} + +func runEchoTest(t *testing.T, conns []net.Conn) { + const testDataLen = 16384 + var wg sync.WaitGroup + for _, conn := range conns { + wg.Add(1) + go func(conn net.Conn) { + testData := make([]byte, testDataLen) + rand.Read(testData) + + n, err := conn.Write(testData) + if n != testDataLen { + t.Fatalf("written only %v, err %v", n, err) + } + + recvBuf := make([]byte, testDataLen) + _, err = io.ReadFull(conn, recvBuf) + if err != nil { + t.Fatalf("failed to read back: %v", err) + } + + if !bytes.Equal(testData, recvBuf) { + t.Fatalf("echoed data not correct") + } + wg.Done() + }(conn) + } + wg.Wait() +} + +func TestTCP(t *testing.T) { + var tmpDB, _ = ioutil.TempFile("", "ck_user_info") + defer os.Remove(tmpDB.Name()) + log.SetOutput(ioutil.Discard) + + worldState := common.WorldOfTime(time.Unix(10, 0)) + lcc, rcc, ai := basicClientConfigs(worldState) + sta := basicServerState(worldState, tmpDB) + + pxyClientD, pxyServerL, dialerToCkServer, rdirServerL, err := establishSession(lcc, rcc, ai, sta) + if err != nil { + t.Fatal(err) + } + + t.Run("user echo", func(t *testing.T) { + go serveEcho(pxyServerL) + const numConns = 2000 // -race option limits the number of goroutines to 8192 + var conns [numConns]net.Conn + for i := 0; i < numConns; i++ { + conns[i], err = pxyClientD.Dial("", "") + if err != nil { + t.Error(err) + } + } + + runEchoTest(t, conns[:]) + }) + + t.Run("redir echo", func(t *testing.T) { + go serveEcho(rdirServerL) + const numConns = 2000 // -race option limits the number of goroutines to 8192 + var conns [numConns]net.Conn + for i := 0; i < numConns; i++ { + conns[i], err = dialerToCkServer.Dial("", "") + if err != nil { + t.Error(err) + } + } + runEchoTest(t, conns[:]) + }) +} diff --git a/internal/test/test.go b/internal/test/test.go new file mode 100644 index 0000000..aa63f31 --- /dev/null +++ b/internal/test/test.go @@ -0,0 +1,3 @@ +package test + +func blah() {}