From cfbf0dfcb6c849c844e64dc1e82ef2b6af58e191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=A1=AE=E5=AE=9A?= <35424927+notsure2@users.noreply.github.com> Date: Sat, 19 Dec 2020 16:42:10 +0200 Subject: [PATCH 01/11] Fix critical bugs in session opening for TCP and UDP in case of Singleplex mode. (#145) * Fix critical bugs in session opening for TCP and UDP in case of Singleplex mode. - In case of TCP, don't open the session in the listener accept thread. This causes resource exhaustion of the tcp listener backlog queue in case of internet connection disruption or timeout. - In case of UDP, don't create a new session for every UDP packet. * Fix race in integration test. * Fix race condition in session maker * Code style improvements * Explicit session.Close() call is indeed needed Co-authored-by: Andy Wang --- cmd/ck-client/ck-client.go | 5 +++-- internal/client/piper.go | 23 +++++++++++++++-------- internal/test/integration_test.go | 6 ++++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index d48f788..efe51c2 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -169,6 +169,7 @@ func main() { } log.Infof("Listening on %v %v for %v client", network, localConfig.LocalAddr, authInfo.ProxyMethod) seshMaker = func() *mux.Session { + authInfo := authInfo // copy the struct because we are overwriting SessionId // sessionID is usergenerated. There shouldn't be a security concern because the scope of // sessionID is limited to its UID. quad := make([]byte, 4) @@ -184,12 +185,12 @@ func main() { return net.ListenUDP("udp", udpAddr) } - client.RouteUDP(acceptor, localConfig.Timeout, seshMaker) + client.RouteUDP(acceptor, localConfig.Timeout, remoteConfig.Singleplex, seshMaker) } else { listener, err := net.Listen("tcp", localConfig.LocalAddr) if err != nil { log.Fatal(err) } - client.RouteTCP(listener, localConfig.Timeout, seshMaker) + client.RouteTCP(listener, localConfig.Timeout, remoteConfig.Singleplex, seshMaker) } } diff --git a/internal/client/piper.go b/internal/client/piper.go index 295e3e8..92b92a4 100644 --- a/internal/client/piper.go +++ b/internal/client/piper.go @@ -10,7 +10,7 @@ import ( log "github.com/sirupsen/logrus" ) -func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration, newSeshFunc func() *mux.Session) { +func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration, singleplex bool, newSeshFunc func() *mux.Session) { var sesh *mux.Session localConn, err := bindFunc() if err != nil { @@ -27,18 +27,22 @@ func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration continue } - if sesh == nil || sesh.IsClosed() || sesh.Singleplex { + if !singleplex && (sesh == nil || sesh.IsClosed()) { sesh = newSeshFunc() } stream, ok := streams[addr.String()] if !ok { + if singleplex { + sesh = newSeshFunc() + } + stream, err = sesh.OpenStream() if err != nil { - log.Errorf("Failed to open stream: %v", err) - if sesh.Singleplex { + if singleplex { sesh.Close() } + log.Errorf("Failed to open stream: %v", err) continue } @@ -74,7 +78,7 @@ func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration } } -func RouteTCP(listener net.Listener, streamTimeout time.Duration, newSeshFunc func() *mux.Session) { +func RouteTCP(listener net.Listener, streamTimeout time.Duration, singleplex bool, newSeshFunc func() *mux.Session) { var sesh *mux.Session for { localConn, err := listener.Accept() @@ -82,10 +86,14 @@ func RouteTCP(listener net.Listener, streamTimeout time.Duration, newSeshFunc fu log.Fatal(err) continue } - if sesh == nil || sesh.IsClosed() || sesh.Singleplex { + if !singleplex && (sesh == nil || sesh.IsClosed()) { sesh = newSeshFunc() } go func(sesh *mux.Session, localConn net.Conn, timeout time.Duration) { + if singleplex { + sesh = newSeshFunc() + } + data := make([]byte, 10240) _ = localConn.SetReadDeadline(time.Now().Add(streamTimeout)) i, err := io.ReadAtLeast(localConn, data, 1) @@ -101,7 +109,7 @@ func RouteTCP(listener net.Listener, streamTimeout time.Duration, newSeshFunc fu if err != nil { log.Errorf("Failed to open stream: %v", err) localConn.Close() - if sesh.Singleplex { + if singleplex { sesh.Close() } return @@ -125,5 +133,4 @@ func RouteTCP(listener net.Listener, streamTimeout time.Duration, newSeshFunc fu } }(sesh, localConn, streamTimeout) } - } diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go index 9f086c1..1a3d0fb 100644 --- a/internal/test/integration_test.go +++ b/internal/test/integration_test.go @@ -185,7 +185,9 @@ func establishSession(lcc client.LocalConnConfig, rcc client.RemoteConnConfig, a // whatever connection initiator (including a proper ck-client) netToCkServerD, ckServerListener := connutil.DialerListener(10 * 1024) + clientSeshMaker := func() *mux.Session { + ai := ai quad := make([]byte, 4) common.RandRead(ai.WorldState.Rand, quad) ai.SessionId = binary.BigEndian.Uint32(quad) @@ -206,12 +208,12 @@ func establishSession(lcc client.LocalConnConfig, rcc client.RemoteConnConfig, a addrCh <- conn.LocalAddr().(*net.UDPAddr) return conn, err } - go client.RouteUDP(acceptor, lcc.Timeout, clientSeshMaker) + go client.RouteUDP(acceptor, lcc.Timeout, rcc.Singleplex, clientSeshMaker) proxyToCkClientD = mDialer } else { var proxyToCkClientL *connutil.PipeListener proxyToCkClientD, proxyToCkClientL = connutil.DialerListener(10 * 1024) - go client.RouteTCP(proxyToCkClientL, lcc.Timeout, clientSeshMaker) + go client.RouteTCP(proxyToCkClientL, lcc.Timeout, rcc.Singleplex, clientSeshMaker) } // set up server From a803d209709f8094ed02407a822e4a1374f61dee Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 14:45:55 +0000 Subject: [PATCH 02/11] Remove sensitive keys from example configs to prevent people from using them --- example_config/ckclient.json | 4 ++-- example_config/ckserver.json | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example_config/ckclient.json b/example_config/ckclient.json index 1df5b86..a8b4243 100644 --- a/example_config/ckclient.json +++ b/example_config/ckclient.json @@ -2,8 +2,8 @@ "Transport": "direct", "ProxyMethod": "shadowsocks", "EncryptionMethod": "plain", - "UID": "5nneblJy6lniPJfr81LuYQ==", - "PublicKey": "IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=", + "UID": "---Your UID here---", + "PublicKey": "---Public key here---", "ServerName": "www.bing.com", "NumConn": 4, "BrowserSig": "chrome", diff --git a/example_config/ckserver.json b/example_config/ckserver.json index aa0566a..c7a977c 100644 --- a/example_config/ckserver.json +++ b/example_config/ckserver.json @@ -18,10 +18,10 @@ ":80" ], "BypassUID": [ - "1rmq6Ag1jZJCImLBIL5wzQ==" + "---Bypass UID here---" ], - "RedirAddr": "204.79.197.200:443", - "PrivateKey": "EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=", - "AdminUID": "5nneblJy6lniPJfr81LuYQ==", + "RedirAddr": "cloudflare.com", + "PrivateKey": "---Private key here---", + "AdminUID": "---Admin UID here---", "DatabasePath": "userinfo.db" } From 21bcb530623ff35b62538b173407b6ad4a8bfe85 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 15:24:25 +0000 Subject: [PATCH 03/11] Human friendly key and uid generators --- README.md | 7 +++---- cmd/ck-server/ck-server.go | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cd85a0d..044c144 100644 --- a/README.md +++ b/README.md @@ -165,9 +165,8 @@ is established. 0. Install at least one underlying proxy server (e.g. OpenVPN, Shadowsocks). 1. Download [the latest release](https://github.com/cbeuw/Cloak/releases) or clone and build this repo. -2. Run `ck-server -k`. The base64 string before the comma is the **public** key to be given to users, the one after the - comma is the **private** key to be kept secret. -3. Run `ck-server -u`. This will be used as the `AdminUID`. +2. Run `ck-server -key`. The **public** should be given to users, the **private** key should be kept secret. +3. Run `ck-server -uid`. The new UID will be used as `AdminUID`. 4. Copy example_config/ckserver.json into a desired location. Change `PrivateKey` to the private key you just obtained; change `AdminUID` to the UID you just obtained. 5. Configure your underlying proxy server so that they all listen on localhost. Edit `ProxyBook` in the configuration @@ -181,7 +180,7 @@ is established. ##### Unrestricted users -Run `ck-server -u` and add the UID into the `BypassUID` field in `ckserver.json` +Run `ck-server -uid` and add the UID into the `BypassUID` field in `ckserver.json` ##### Users subject to bandwidth and credit controls diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index e741149..b646232 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -80,8 +80,11 @@ func main() { askVersion := flag.Bool("v", false, "Print the version number") printUsage := flag.Bool("h", false, "Print this message") - genUID := flag.Bool("u", false, "Generate a UID") - genKeyPair := flag.Bool("k", false, "Generate a pair of public and private key, output in the format of pubkey,pvkey") + genUIDScript := flag.Bool("u", false, "Generate a UID to STDOUT") + genKeyPairScript := flag.Bool("k", false, "Generate a pair of public and private key and output to STDOUT in the format of ,") + + genUIDHuman := flag.Bool("uid", false, "Generate and print out a UID") + genKeyPairHuman := flag.Bool("key", false, "Generate and print out a public-private key pair") pprofAddr := flag.String("d", "", "debug use: ip:port to be listened by pprof profiler") verbosity := flag.String("verbosity", "info", "verbosity level") @@ -96,13 +99,23 @@ func main() { flag.Usage() return } - if *genUID { - fmt.Println(generateUID()) + if *genUIDScript || *genUIDHuman { + uid := generateUID() + if *genUIDScript { + fmt.Println(uid) + } else { + fmt.Printf("\x1B[35mYour UID is:\u001B[0m %s\n", uid) + } return } - if *genKeyPair { + if *genKeyPairScript || *genKeyPairHuman { pub, pv := generateKeyPair() - fmt.Printf("%v,%v", pub, pv) + if *genKeyPairScript { + fmt.Printf("%v,%v\n", pub, pv) + } else { + fmt.Printf("\x1B[36mYour PUBLIC key is:\x1B[0m %65s\n", pub) + fmt.Printf("\x1B[33mYour PRIVATE key is (keep it secret):\x1B[0m %47s\n", pv) + } return } From 2e36627a121676504fb1633957358685e65899d4 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 15:34:17 +0000 Subject: [PATCH 04/11] Make AdminUID optional and implement better validation on empty config fields --- README.md | 2 +- example_config/ckserver.json | 2 +- internal/server/dispatcher.go | 2 +- internal/server/state.go | 10 ++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 044c144..6571453 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ is established. 0. Install at least one underlying proxy server (e.g. OpenVPN, Shadowsocks). 1. Download [the latest release](https://github.com/cbeuw/Cloak/releases) or clone and build this repo. 2. Run `ck-server -key`. The **public** should be given to users, the **private** key should be kept secret. -3. Run `ck-server -uid`. The new UID will be used as `AdminUID`. +3. (Skip if you only want to add unrestricted users) Run `ck-server -uid`. The new UID will be used as `AdminUID`. 4. Copy example_config/ckserver.json into a desired location. Change `PrivateKey` to the private key you just obtained; change `AdminUID` to the UID you just obtained. 5. Configure your underlying proxy server so that they all listen on localhost. Edit `ProxyBook` in the configuration diff --git a/example_config/ckserver.json b/example_config/ckserver.json index c7a977c..589868b 100644 --- a/example_config/ckserver.json +++ b/example_config/ckserver.json @@ -22,6 +22,6 @@ ], "RedirAddr": "cloudflare.com", "PrivateKey": "---Private key here---", - "AdminUID": "---Admin UID here---", + "AdminUID": "---Admin UID here (optional)---", "DatabasePath": "userinfo.db" } diff --git a/internal/server/dispatcher.go b/internal/server/dispatcher.go index 4fa0698..80ee28c 100644 --- a/internal/server/dispatcher.go +++ b/internal/server/dispatcher.go @@ -190,7 +190,7 @@ func dispatchConnection(conn net.Conn, sta *State) { // 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 - if bytes.Equal(ci.UID, sta.AdminUID) && ci.SessionId == 0 { + if len(sta.AdminUID) != 0 && bytes.Equal(ci.UID, sta.AdminUID) && ci.SessionId == 0 { sesh := mux.MakeSession(0, seshConfig) preparedConn, err := finishHandshake(conn, sessionKey, sta.WorldState.Rand) if err != nil { diff --git a/internal/server/state.go b/internal/server/state.go index 66541f5..576e326 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -168,6 +168,10 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er return } + if len(preParse.PrivateKey) == 0 { + err = fmt.Errorf("must have a valid private key. Run `ck-server -key` to generate one") + return + } var pv [32]byte copy(pv[:], preParse.PrivateKey) sta.StaticPv = &pv @@ -179,8 +183,10 @@ func InitState(preParse RawConfig, worldState common.WorldState) (sta *State, er copy(arrUID[:], UID) sta.BypassUID[arrUID] = struct{}{} } - copy(arrUID[:], sta.AdminUID) - sta.BypassUID[arrUID] = struct{}{} + if len(sta.AdminUID) != 0 { + copy(arrUID[:], sta.AdminUID) + sta.BypassUID[arrUID] = struct{}{} + } go sta.UsedRandomCleaner() return sta, nil From 3728622f482cf45215843a9fe51c4f06f8fbfc81 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 19:17:18 +0000 Subject: [PATCH 05/11] Add github actions --- .github/workflows/build.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a8dd660 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,17 @@ +name: Build and test +on: [ push ] +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '^1.15' # The Go version to download (if necessary) and use. + - run: go test -race -coverprofile=coverage.txt -coverpkg=./... -covermode=atomic ./... + - uses: codecov/codecov-action@v1 + with: + file: coverage.txt From 275540a793f010cbe0a7f40fb56770052642af2f Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 19:21:36 +0000 Subject: [PATCH 06/11] Fix commandline syntax --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8dd660..03cdf87 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/setup-go@v2 with: go-version: '^1.15' # The Go version to download (if necessary) and use. - - run: go test -race -coverprofile=coverage.txt -coverpkg=./... -covermode=atomic ./... + - run: go test -race -coverprofile coverage.txt -coverpkg ./... -covermode atomic ./... - uses: codecov/codecov-action@v1 with: file: coverage.txt From 005da456c01c321fbd86e728b2424a1ad688b03e Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 19:30:53 +0000 Subject: [PATCH 07/11] Fix timing sensitive tests --- .travis.yml | 10 ---------- internal/multiplex/datagramBufferedPipe_test.go | 2 +- internal/multiplex/mux_test.go | 6 +++++- internal/multiplex/session_test.go | 2 +- internal/multiplex/streamBufferedPipe_test.go | 4 +++- internal/multiplex/stream_test.go | 2 +- internal/multiplex/switchboard_test.go | 4 ++-- 7 files changed, 13 insertions(+), 17 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6fbe39a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go - -go: - - "1.15" - -script: - - go test -race -coverprofile=coverage.txt -coverpkg=./... -covermode=atomic ./... - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/internal/multiplex/datagramBufferedPipe_test.go b/internal/multiplex/datagramBufferedPipe_test.go index f03cff0..4a5d4e2 100644 --- a/internal/multiplex/datagramBufferedPipe_test.go +++ b/internal/multiplex/datagramBufferedPipe_test.go @@ -76,7 +76,7 @@ func TestDatagramBuffer_BlockingRead(t *testing.T) { pipe := NewDatagramBufferedPipe() b := []byte{0x01, 0x02, 0x03} go func() { - time.Sleep(100 * time.Millisecond) + time.Sleep(readBlockTime) pipe.Write(Frame{Payload: b}) }() b2 := make([]byte, len(b)) diff --git a/internal/multiplex/mux_test.go b/internal/multiplex/mux_test.go index 436c407..d3deaf9 100644 --- a/internal/multiplex/mux_test.go +++ b/internal/multiplex/mux_test.go @@ -12,6 +12,8 @@ import ( "time" ) +const eventualConsistencyTolerance = 500 * time.Millisecond + func serveEcho(l net.Listener) { for { conn, err := l.Accept() @@ -111,6 +113,8 @@ func TestMultiplex(t *testing.T) { //test echo runEchoTest(t, streams, maxMsgLen) + + time.Sleep(eventualConsistencyTolerance) if clientSession.streamCount() != numStreams { t.Errorf("client stream count is wrong: %v", clientSession.streamCount()) } @@ -148,7 +152,7 @@ func TestMux_StreamClosing(t *testing.T) { t.Errorf("can't write to stream: %v", err) } - time.Sleep(500 * time.Millisecond) + time.Sleep(eventualConsistencyTolerance) _ = toBeClosed.Close() _, err = io.ReadFull(toBeClosed, recvBuf) if err != nil { diff --git a/internal/multiplex/session_test.go b/internal/multiplex/session_test.go index 52fe6a5..79d62b5 100644 --- a/internal/multiplex/session_test.go +++ b/internal/multiplex/session_test.go @@ -408,7 +408,7 @@ func TestSession_timeoutAfter(t *testing.T) { seshConfigOrdered.Obfuscator = obfuscator seshConfigOrdered.InactivityTimeout = 100 * time.Millisecond sesh := MakeSession(0, seshConfigOrdered) - time.Sleep(200 * time.Millisecond) + time.Sleep(2 * seshConfigOrdered.InactivityTimeout) if !sesh.IsClosed() { t.Error("session should have timed out") } diff --git a/internal/multiplex/streamBufferedPipe_test.go b/internal/multiplex/streamBufferedPipe_test.go index dcaad29..ff0ec24 100644 --- a/internal/multiplex/streamBufferedPipe_test.go +++ b/internal/multiplex/streamBufferedPipe_test.go @@ -7,6 +7,8 @@ import ( "time" ) +const readBlockTime = 500 * time.Millisecond + func TestPipeRW(t *testing.T) { pipe := NewStreamBufferedPipe() b := []byte{0x01, 0x02, 0x03} @@ -60,7 +62,7 @@ func TestReadBlock(t *testing.T) { pipe := NewStreamBufferedPipe() b := []byte{0x01, 0x02, 0x03} go func() { - time.Sleep(100 * time.Millisecond) + time.Sleep(readBlockTime) pipe.Write(b) }() b2 := make([]byte, len(b)) diff --git a/internal/multiplex/stream_test.go b/internal/multiplex/stream_test.go index 278082b..9527034 100644 --- a/internal/multiplex/stream_test.go +++ b/internal/multiplex/stream_test.go @@ -230,7 +230,7 @@ func TestStream_Close(t *testing.T) { if err != nil { t.Errorf("can't read residual data %v", err) } - time.Sleep(100 * time.Millisecond) + time.Sleep(eventualConsistencyTolerance) if sI, _ := sesh.streams.Load(stream.(*Stream).id); sI != nil { t.Error("stream still exists") return diff --git a/internal/multiplex/switchboard_test.go b/internal/multiplex/switchboard_test.go index e052b55..01d55ae 100644 --- a/internal/multiplex/switchboard_test.go +++ b/internal/multiplex/switchboard_test.go @@ -145,7 +145,7 @@ func TestSwitchboard_CloseOnOneDisconn(t *testing.T) { sesh.AddConnection(conn1client) conn0server.Close() - time.Sleep(500 * time.Millisecond) + time.Sleep(eventualConsistencyTolerance) if !sesh.IsClosed() { t.Error("session not closed after one conn is disconnected") return @@ -178,7 +178,7 @@ func TestSwitchboard_ConnsCount(t *testing.T) { sesh.sb.closeAll() - time.Sleep(500 * time.Millisecond) + time.Sleep(eventualConsistencyTolerance) if sesh.sb.connsCount() != 0 { t.Error("connsCount incorrect") } From 8d146582d245e3f6a77aae6596080480aedde287 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 19:42:31 +0000 Subject: [PATCH 08/11] Add release action --- .github/workflows/release.yml | 20 ++++++++++++++++++++ internal/multiplex/session_test.go | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..03bc0af --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +on: + push: + tags: + - 'v*' + +name: Create Release + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build + run: v=${{ github.ref }} ./release.sh + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: release/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/internal/multiplex/session_test.go b/internal/multiplex/session_test.go index 79d62b5..5e9ee16 100644 --- a/internal/multiplex/session_test.go +++ b/internal/multiplex/session_test.go @@ -408,7 +408,7 @@ func TestSession_timeoutAfter(t *testing.T) { seshConfigOrdered.Obfuscator = obfuscator seshConfigOrdered.InactivityTimeout = 100 * time.Millisecond sesh := MakeSession(0, seshConfigOrdered) - time.Sleep(2 * seshConfigOrdered.InactivityTimeout) + time.Sleep(5 * seshConfigOrdered.InactivityTimeout) if !sesh.IsClosed() { t.Error("session should have timed out") } From 4a37449d33b0a2b725cbf44e054760a310cf4ba8 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 20:14:43 +0000 Subject: [PATCH 09/11] Replace all time-sensitive tests with assert.Eventually --- go.mod | 2 +- go.sum | 5 +++ internal/multiplex/mux_test.go | 24 +++++++------- internal/multiplex/session_test.go | 9 +++--- internal/multiplex/stream_test.go | 11 ++++--- internal/multiplex/switchboard_test.go | 19 ++++++----- internal/test/integration_test.go | 45 +++++++------------------- 7 files changed, 51 insertions(+), 64 deletions(-) diff --git a/go.mod b/go.mod index b3ebe02..e950ab7 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57 github.com/sirupsen/logrus v1.5.0 github.com/stephens2424/writerset v1.0.2 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.6.1 go.etcd.io/bbolt v1.3.4 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect diff --git a/go.sum b/go.sum index d24d520..150adcb 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -76,5 +78,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/multiplex/mux_test.go b/internal/multiplex/mux_test.go index d3deaf9..76344ca 100644 --- a/internal/multiplex/mux_test.go +++ b/internal/multiplex/mux_test.go @@ -4,6 +4,7 @@ import ( "bytes" "github.com/cbeuw/Cloak/internal/common" "github.com/cbeuw/connutil" + "github.com/stretchr/testify/assert" "io" "math/rand" "net" @@ -12,8 +13,6 @@ import ( "time" ) -const eventualConsistencyTolerance = 500 * time.Millisecond - func serveEcho(l net.Listener) { for { conn, err := l.Accept() @@ -114,13 +113,13 @@ func TestMultiplex(t *testing.T) { //test echo runEchoTest(t, streams, maxMsgLen) - time.Sleep(eventualConsistencyTolerance) - if clientSession.streamCount() != numStreams { - t.Errorf("client stream count is wrong: %v", clientSession.streamCount()) - } - if serverSession.streamCount() != numStreams { - t.Errorf("server stream count is wrong: %v", serverSession.streamCount()) - } + assert.Eventuallyf(t, func() bool { + return clientSession.streamCount() == numStreams + }, time.Second, 10*time.Millisecond, "client stream count is wrong: %v", clientSession.streamCount()) + + assert.Eventuallyf(t, func() bool { + return serverSession.streamCount() == numStreams + }, time.Second, 10*time.Millisecond, "server stream count is wrong: %v", serverSession.streamCount()) // close one stream closing, streams := streams[0], streams[1:] @@ -152,9 +151,12 @@ func TestMux_StreamClosing(t *testing.T) { t.Errorf("can't write to stream: %v", err) } - time.Sleep(eventualConsistencyTolerance) + _, err = io.ReadFull(toBeClosed, recvBuf[:1]) + if err != nil { + t.Errorf("can't read anything before stream closed: %v", err) + } _ = toBeClosed.Close() - _, err = io.ReadFull(toBeClosed, recvBuf) + _, err = io.ReadFull(toBeClosed, recvBuf[1:]) if err != nil { t.Errorf("can't read residual data on stream: %v", err) } diff --git a/internal/multiplex/session_test.go b/internal/multiplex/session_test.go index 5e9ee16..b280895 100644 --- a/internal/multiplex/session_test.go +++ b/internal/multiplex/session_test.go @@ -3,6 +3,7 @@ package multiplex import ( "bytes" "github.com/cbeuw/connutil" + "github.com/stretchr/testify/assert" "math/rand" "strconv" "sync" @@ -408,10 +409,10 @@ func TestSession_timeoutAfter(t *testing.T) { seshConfigOrdered.Obfuscator = obfuscator seshConfigOrdered.InactivityTimeout = 100 * time.Millisecond sesh := MakeSession(0, seshConfigOrdered) - time.Sleep(5 * seshConfigOrdered.InactivityTimeout) - if !sesh.IsClosed() { - t.Error("session should have timed out") - } + + assert.Eventually(t, func() bool { + return sesh.IsClosed() + }, 5*seshConfigOrdered.InactivityTimeout, seshConfigOrdered.InactivityTimeout, "session should have timed out") } func BenchmarkRecvDataFromRemote_Ordered(b *testing.B) { diff --git a/internal/multiplex/stream_test.go b/internal/multiplex/stream_test.go index 9527034..6ce16d9 100644 --- a/internal/multiplex/stream_test.go +++ b/internal/multiplex/stream_test.go @@ -3,6 +3,7 @@ package multiplex import ( "bytes" "github.com/cbeuw/Cloak/internal/common" + "github.com/stretchr/testify/assert" "io" "io/ioutil" "math/rand" @@ -230,11 +231,11 @@ func TestStream_Close(t *testing.T) { if err != nil { t.Errorf("can't read residual data %v", err) } - time.Sleep(eventualConsistencyTolerance) - if sI, _ := sesh.streams.Load(stream.(*Stream).id); sI != nil { - t.Error("stream still exists") - return - } + + assert.Eventually(t, func() bool { + sI, _ := sesh.streams.Load(stream.(*Stream).id) + return sI == nil + }, time.Second, 10*time.Millisecond, "streams still exists") }) } diff --git a/internal/multiplex/switchboard_test.go b/internal/multiplex/switchboard_test.go index 01d55ae..2c3f36f 100644 --- a/internal/multiplex/switchboard_test.go +++ b/internal/multiplex/switchboard_test.go @@ -2,6 +2,7 @@ package multiplex import ( "github.com/cbeuw/connutil" + "github.com/stretchr/testify/assert" "math/rand" "sync" "testing" @@ -145,11 +146,11 @@ func TestSwitchboard_CloseOnOneDisconn(t *testing.T) { sesh.AddConnection(conn1client) conn0server.Close() - time.Sleep(eventualConsistencyTolerance) - if !sesh.IsClosed() { - t.Error("session not closed after one conn is disconnected") - return - } + + assert.Eventually(t, func() bool { + return sesh.IsClosed() + }, time.Second, 10*time.Millisecond, "session not closed after one conn is disconnected") + if _, err := conn1client.Write([]byte{0x00}); err == nil { t.Error("the other conn is still connected") return @@ -178,9 +179,7 @@ func TestSwitchboard_ConnsCount(t *testing.T) { sesh.sb.closeAll() - time.Sleep(eventualConsistencyTolerance) - if sesh.sb.connsCount() != 0 { - t.Error("connsCount incorrect") - } - + assert.Eventuallyf(t, func() bool { + return sesh.sb.connsCount() == 0 + }, time.Second, 10*time.Millisecond, "connsCount incorrect: %v", sesh.sb.connsCount()) } diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go index 1a3d0fb..db58b39 100644 --- a/internal/test/integration_test.go +++ b/internal/test/integration_test.go @@ -10,6 +10,7 @@ import ( mux "github.com/cbeuw/Cloak/internal/multiplex" "github.com/cbeuw/Cloak/internal/server" "github.com/cbeuw/connutil" + "github.com/stretchr/testify/assert" "io" "io/ioutil" "math/rand" @@ -356,17 +357,9 @@ func TestTCPSingleplex(t *testing.T) { proxyConn1.Close() - retries := 0 -retry: - time.Sleep(delayBeforeTestingConnClose) - if user.NumSession() != 1 { - retries++ - if retries > connCloseRetries { - t.Error("first session was not closed on connection close") - } else { - goto retry - } - } + assert.Eventually(t, func() bool { + return user.NumSession() == 1 + }, time.Second, 10*time.Millisecond, "first session was not closed on connection close") // conn2 should still work runEchoTest(t, []net.Conn{proxyConn2}, 65536) @@ -479,17 +472,10 @@ func TestClosingStreamsFromProxy(t *testing.T) { serverConn, _ := proxyFromCkServerL.Accept() serverConn.Close() - retries := 0 - retry: - time.Sleep(delayBeforeTestingConnClose) - if _, err := clientConn.Read(make([]byte, 16)); err == nil { - retries++ - if retries > connCloseRetries { - t.Errorf("closing stream on server side is not reflected to the client: %v", err) - } else { - goto retry - } - } + assert.Eventually(t, func() bool { + _, err := clientConn.Read(make([]byte, 16)) + return err != nil + }, time.Second, 10*time.Millisecond, "closing stream on server side is not reflected to the client") }) t.Run("closing from client", func(t *testing.T) { @@ -499,17 +485,10 @@ func TestClosingStreamsFromProxy(t *testing.T) { serverConn, _ := proxyFromCkServerL.Accept() clientConn.Close() - retries := 0 - retry: - time.Sleep(delayBeforeTestingConnClose) - if _, err := serverConn.Read(make([]byte, 16)); err == nil { - retries++ - if retries > 3 { - t.Errorf("closing stream on client side is not reflected to the server: %v", err) - } else { - goto retry - } - } + assert.Eventually(t, func() bool { + _, err := serverConn.Read(make([]byte, 16)) + return err != nil + }, time.Second, 10*time.Millisecond, "closing stream on client side is not reflected to the server") }) t.Run("send then close", func(t *testing.T) { From fa4ec77d71edf6e2efb90b5c58cf0baa1110fc77 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 20:21:34 +0000 Subject: [PATCH 10/11] Remove Travis CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6571453..9b50dfc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/cbeuw/Cloak.svg?branch=master)](https://travis-ci.org/cbeuw/Cloak) +[![Build Status](https://github.com/cbeuw/Cloak/workflows/Build%20and%20test/badge.svg)](https://github.com/cbeuw/Cloak/actions) [![codecov](https://codecov.io/gh/cbeuw/Cloak/branch/master/graph/badge.svg)](https://codecov.io/gh/cbeuw/Cloak) [![Go Report Card](https://goreportcard.com/badge/github.com/cbeuw/Cloak)](https://goreportcard.com/report/github.com/cbeuw/Cloak) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SAUYKGSREP8GL&source=url) From c293cd37892974e94f261d308d5a31fa258a1815 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 19 Dec 2020 20:25:38 +0000 Subject: [PATCH 11/11] Migrate off azure --- .github/workflows/release.yml | 4 ++- azure-pipelines.yml | 64 ----------------------------------- 2 files changed, 3 insertions(+), 65 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03bc0af..316ea63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Build - run: v=${{ github.ref }} ./release.sh + run: | + export PATH=${PATH}:`go env GOPATH`/bin + v=${{ github.ref }} ./release.sh - name: Release uses: softprops/action-gh-release@v1 with: diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 75509bd..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,64 +0,0 @@ -# Go -# Build your Go project. -# Add steps that test, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/go - -trigger: - tags: - include: - - refs/tags/v* - branches: - exclude: - - master - -pool: - vmImage: 'ubuntu-latest' - -variables: - GOBIN: '$(GOPATH)/bin' # Go binaries path - GOROOT: '$(Agent.BuildDirectory)/go' # Go installation path - GOPATH: '$(Agent.BuildDirectory)/gopath' # Go workspace path - modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)' # Path to the module's code - -steps: -- script: | - mkdir -p '$(GOBIN)' - mkdir -p '$(GOPATH)/pkg' - mkdir -p '$(modulePath)' - shopt -s extglob - shopt -s dotglob - mv !(gopath) '$(modulePath)' - echo '##vso[task.prependpath]$(GOBIN)' - echo '##vso[task.prependpath]$(GOROOT)/bin' - wget "https://golang.org/dl/go1.15.2.linux-amd64.tar.gz" --output-document "$(Agent.BuildDirectory)/go1.15.2.tar.gz" - tar -C '$(Agent.BuildDirectory)' -xzf "$(Agent.BuildDirectory)/go1.15.2.tar.gz" - displayName: 'Set up the Go workspace' - -- script: | - v="$(git describe --tags)" ./release.sh - mv ./release/* $(Build.ArtifactStagingDirectory)/ - workingDirectory: '$(modulePath)' - displayName: 'Get dependencies, then build' - -# GitHub Release -# Create, edit, or delete a GitHub release -- task: GitHubRelease@0 - inputs: - gitHubConnection: github.com_cbeuw - repositoryName: '$(Build.Repository.Name)' - action: 'create' # Options: create, edit, delete - target: '$(Build.SourceVersion)' # Required when action == Create || Action == Edit - tagSource: 'auto' # Required when action == Create# Options: auto, manual - #tagPattern: # Optional - #tag: "$(git describe --tags)" # Required when action == Edit || Action == Delete || TagSource == Manual - #title: # Optional - #releaseNotesSource: 'file' # Optional. Options: file, input - #releaseNotesFile: # Optional - #releaseNotes: # Optional - #assets: '$(Build.ArtifactStagingDirectory)/*' # Optional - #assetUploadMode: 'delete' # Optional. Options: delete, replace - #isDraft: false # Optional - #isPreRelease: false # Optional - addChangeLog: false # Optional - #compareWith: 'lastFullRelease' # Required when addChangeLog == True. Options: lastFullRelease, lastRelease, lastReleaseByTag - #releaseTag: # Required when compareWith == LastReleaseByTag