Repository: pion/dtls
Pull request #272 (2020-07-15 04:25:21)

Update CI configs to v0.3.3
Update lint scripts and CI configs.

codecov[bot]: # [Codecov](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=h1) Report
> Merging [#272](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=desc) into [master](https://codecov.io/gh/pion/dtls/commit/66552a52774dd30cbf7cb0e1048106d14d430a3a&el=desc) will **decrease** coverage by `0.12%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/pion/dtls/pull/272/graphs/tree.svg?width=650&height=150&src=pr&token=bcCB2jB88I)](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #272      +/-   ##
==========================================
- Coverage   77.43%   77.30%   -0.13%     
==========================================
  Files          75       75              
  Lines        3944     3944              
==========================================
- Hits         3054     3049       -5     
- Misses        609      613       +4     
- Partials      281      282       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `77.34% <ø> (-0.13%)` | :arrow_down: |
| #wasm | `75.10% <ø> (+0.02%)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [internal/net/connctx/connctx.go](https://codecov.io/gh/pion/dtls/pull/272/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbmV0L2Nvbm5jdHgvY29ubmN0eC5nbw==) | `82.71% <0.00%> (-3.71%)` | :arrow_down: |
| [internal/net/dpipe/dpipe.go](https://codecov.io/gh/pion/dtls/pull/272/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbmV0L2RwaXBlL2RwaXBlLmdv) | `94.11% <0.00%> (-2.36%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=footer). Last update [66552a5...512866a](https://codecov.io/gh/pion/dtls/pull/272?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/assert-contributors.sh b/.github/assert-contributors.sh
index 915f680..b0be2f7 100755
--- a/.github/assert-contributors.sh
+++ b/.github/assert-contributors.sh
@@ -1,10 +1,13 @@
 #!/usr/bin/env bash
 
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 set -e
 
@@ -16,11 +19,24 @@ fi
 
 SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
 
-EXCLUDED_CONTIBUTORS=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot')
-MISSING_CONTIBUTORS=()
+if [ -f ${SCRIPT_PATH}/.ci.conf ]
+then
+  . ${SCRIPT_PATH}/.ci.conf
+fi
+
+#
+# DO NOT EDIT THIS
+#
+EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot')
+# If you want to exclude a name from all repositories, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+# If you want to exclude a name only from this repository,
+# add EXCLUDED_CONTRIBUTORS=('name') to .github/.ci.conf
+
+MISSING_CONTRIBUTORS=()
 
 shouldBeIncluded () {
-	for i in "${EXCLUDED_CONTIBUTORS[@]}"
+	for i in "${EXCLUDED_CONTRIBUTORS[@]}"
 	do
 		if [ "$i" == "$1" ] ; then
 			return 1
@@ -35,15 +51,15 @@ for contributor in $(git log --format='%aN' | sort -u)
 do
 	if shouldBeIncluded $contributor; then
 		if ! grep -q "$contributor" "$SCRIPT_PATH/../README.md"; then
-			MISSING_CONTIBUTORS+=("$contributor")
+			MISSING_CONTRIBUTORS+=("$contributor")
 		fi
 	fi
 done
 unset IFS
 
-if [ ${#MISSING_CONTIBUTORS[@]} -ne 0 ]; then
+if [ ${#MISSING_CONTRIBUTORS[@]} -ne 0 ]; then
     echo "Please add the following contributors to the README"
-    for i in "${MISSING_CONTIBUTORS[@]}"
+    for i in "${MISSING_CONTRIBUTORS[@]}"
     do
 	    echo "$i"
     done
diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh
index 289bdbb..73d20a4 100755
--- a/.github/install-hooks.sh
+++ b/.github/install-hooks.sh
@@ -1,10 +1,13 @@
 #!/bin/bash
 
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
 
diff --git a/.github/lint-commit-message.sh b/.github/lint-commit-message.sh
index 1d1e808..7357c94 100755
--- a/.github/lint-commit-message.sh
+++ b/.github/lint-commit-message.sh
@@ -1,10 +1,13 @@
 #!/usr/bin/env bash
 
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 set -e
 
diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh
index f5c9d87..21e48da 100755
--- a/.github/lint-disallowed-functions-in-library.sh
+++ b/.github/lint-disallowed-functions-in-library.sh
@@ -1,22 +1,47 @@
 #!/usr/bin/env bash
 
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 set -e
 
 # Disallow usages of functions that cause the program to exit in the library code
 SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
-EXCLUDE_DIRECTORIES="--exclude-dir=examples --exclude-dir=.git --exclude-dir=.github "
+if [ -f ${SCRIPT_PATH}/.ci.conf ]
+then
+  . ${SCRIPT_PATH}/.ci.conf
+fi
+
+EXCLUDE_DIRECTORIES=${DISALLOWED_FUNCTIONS_EXCLUDED_DIRECTORIES:-"examples"}
 DISALLOWED_FUNCTIONS=('os.Exit(' 'panic(' 'Fatal(' 'Fatalf(' 'Fatalln(' 'fmt.Println(' 'fmt.Printf(' 'log.Print(' 'log.Println(' 'log.Printf(')
 
+files=$(
+  find "$SCRIPT_PATH/.." -name "*.go" \
+    | grep -v -e '^.*_test.go$' \
+    | while read file
+    do
+      excluded=false
+      for ex in $EXCLUDE_DIRECTORIES
+      do
+        if [[ $file == */$ex/* ]]
+        then
+          excluded=true
+          break
+        fi
+      done
+      $excluded || echo "$file"
+    done
+)
 
 for disallowedFunction in "${DISALLOWED_FUNCTIONS[@]}"
 do
-	if grep -R $EXCLUDE_DIRECTORIES -e "$disallowedFunction" "$SCRIPT_PATH/.." | grep -v -e '_test.go' -e 'nolint'; then
+	if grep -e "$disallowedFunction" $files | grep -v -e 'nolint'; then
 		echo "$disallowedFunction may only be used in example code"
 		exit 1
 	fi
diff --git a/.github/lint-filename.sh b/.github/lint-filename.sh
index 61134af..81b3f14 100755
--- a/.github/lint-filename.sh
+++ b/.github/lint-filename.sh
@@ -1,10 +1,13 @@
 #!/usr/bin/env bash
 
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 set -e
 
diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml
index 96cef4c..451fd7d 100644
--- a/.github/workflows/renovate-go-mod-fix.yaml
+++ b/.github/workflows/renovate-go-mod-fix.yaml
@@ -1,10 +1,13 @@
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 # If this repository should have package specific CI config,
 # remove the repository name from .goassets/.github/workflows/assets-sync.yml.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 name: go-mod-fix
 on:
diff --git a/.travis.yml b/.travis.yml
index e2bb205..9bd21a3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,13 @@
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 # If this repository should have package specific CI config,
 # remove the repository name from .goassets/.github/workflows/assets-sync.yml.
 #
+# If you want to update the shared CI config, send a PR to
+# https://github.com/pion/.goassets instead of this repository.
+#
 
 dist: bionic
 language: go
@@ -28,37 +31,46 @@ cache:
 
 _lint_job: &lint_job
   env: CACHE_NAME=lint
+  before_install:
+    - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi
+  install: skip
   before_script:
     - |
       curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
         | bash -s - -b $GOPATH/bin v${GOLANGCI_LINT_VERSION}
-  install: skip
   script:
     - bash .github/assert-contributors.sh
     - bash .github/lint-disallowed-functions-in-library.sh
     - bash .github/lint-commit-message.sh
     - bash .github/lint-filename.sh
-    - golangci-lint run ./...
+    - golangci-lint run ${GOLANGCI_LINT_EXRA_ARGS:-} ./...
 _test_job: &test_job
   env: CACHE_NAME=test
   before_install:
+    - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi
     - go mod download
   install:
     - go build ./...
   script:
-    - coverpkgs=$(go list ./... | grep -v examples | paste -s -d ',')
+    # If you want to specify repository specific test packages rule,
+    # add `TEST_PACKAGES=$(command to list test target packages)` to .github/.ci.conf
+    - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)}
+    - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',')
     - |
       go test \
         -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \
-        -tags quic \
-        -v -race ./...
+        ${TEST_EXTRA_ARGS:-} \
+        -v -race ${testpkgs}
+    - if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi
   after_success:
     - travis_retry bash <(curl -s https://codecov.io/bash) -c -F go
 _test_i386_job: &test_i386_job
   env: CACHE_NAME=test386
-  language: bash
   services: docker
+  before_install:
+    - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi
   script:
+    - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)}
     - |
       docker run \
         -u $(id -u):$(id -g) \
@@ -70,12 +82,15 @@ _test_i386_job: &test_i386_job
         -w /go/src/github.com/pion/$(basename ${PWD}) \
         -it i386/golang:${GO_VERSION}-alpine \
         /usr/local/go/bin/go test \
-          -tags quic \
-          -v ./...
+          ${TEST_EXTRA_ARGS:-} \
+          -v ${testpkgs}
 _test_wasm_job: &test_wasm_job
   env: CACHE_NAME=wasm
   language: node_js
   node_js: 12
+  before_install:
+    - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi
+    - if ${SKIP_WASM_TEST:-false}; then exit 0; fi
   install:
     # Manually download and install Go instead of using gimme.
     # It looks like gimme Go causes some errors on go-test for Wasm.
@@ -83,15 +98,10 @@ _test_wasm_job: &test_wasm_job
     - export GOROOT=${HOME}/go
     - export PATH=${GOROOT}/bin:${PATH}
     - yarn install
-    - export GO_JS_WASM_EXEC=${GOROOT}/misc/wasm/go_js_wasm_exec
-    # If the repository has wasm_exec hook, use it.
-    - |
-      if [ -f test-wasm/go_js_wasm_exec ]; then
-        export GO_JS_WASM_EXEC=${PWD}/test-wasm/go_js_wasm_exec
-      fi
+    - export GO_JS_WASM_EXEC=${GO_JS_WASM_EXEC:-${GOROOT}/misc/wasm/go_js_wasm_exec}
   script:
-    - testpkgs=$(go list ./... | grep -v examples)
-    - coverpkgs=$(go list ./... | grep -v examples | paste -s -d ',')
+    - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)}
+    - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',')
     - |
       GOOS=js GOARCH=wasm go test \
         -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \
@@ -114,9 +124,13 @@ jobs:
     - <<: *test_i386_job
       name: Test i386 1.13
       env: GO_VERSION=1.13
+      go: 1.14  # Go version for host environment only for `go list`.
+                # All tests are done on the version specified by GO_VERSION.
     - <<: *test_i386_job
       name: Test i386 1.14
       env: GO_VERSION=1.14
+      go: 1.14  # Go version for host environment only for `go list`.
+                # All tests are done on the version specified by GO_VERSION.
     - <<: *test_wasm_job
       name: Test WASM 1.13
       env: GO_VERSION=1.13
diff --git a/codecov.yml b/codecov.yml
index 5941e99..085200a 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,5 +1,5 @@
 #
-# DO NOT EDIT THIS FILE DIRECTLY
+# DO NOT EDIT THIS FILE
 #
 # It is automatically copied from https://github.com/pion/.goassets repository.
 #
diff --git a/flight3handler.go b/flight3handler.go
index fda824a..132c442 100644
--- a/flight3handler.go
+++ b/flight3handler.go
@@ -5,9 +5,24 @@ import (
 )
 
 func flight3Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert, error) {
-	var msgs map[handshakeType]handshakeMessage
-	var ok bool
-	var seq int
+	// Clients may receive multiple HelloVerifyRequest messages with different cookies.
+	// Clients SHOULD handle this by sending a new ClientHello with a cookie in response
+	// to the new HelloVerifyRequest. RFC 6347 Section 4.2.1
+	seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence,
+		handshakeCachePullRule{handshakeTypeHelloVerifyRequest, cfg.initialEpoch, false, true},
+	)
+	if ok {
+		if h, msgOk := msgs[handshakeTypeHelloVerifyRequest].(*handshakeMessageHelloVerifyRequest); msgOk {
+			// DTLS 1.2 clients must not assume that the server will use the protocol version
+			// specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1
+			if !h.version.Equal(protocolVersion1_0) && !h.version.Equal(protocolVersion1_2) {
+				return 0, &alert{alertLevelFatal, alertProtocolVersion}, errUnsupportedProtocolVersion
+			}
+			state.cookie = append([]byte{}, h.cookie...)
+			state.handshakeRecvSequence = seq
+			return flight3, nil, nil
+		}
+	}
 
 	if cfg.localPSKCallback != nil {
 		seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence,
diff --git a/go.mod b/go.mod
index e8eee5c..e992ad1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ require (
 	github.com/pion/logging v0.2.2
 	github.com/pion/transport v0.10.1
 	github.com/pion/udp v0.1.0
-	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
-	golang.org/x/net v0.0.0-20200625001655-4c5254603344
+	golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
+	golang.org/x/net v0.0.0-20200707034311-ab3426394381
 	golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
 )
 
diff --git a/go.sum b/go.sum
index b931301..f6cf626 100644
--- a/go.sum
+++ b/go.sum
@@ -18,11 +18,15 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
+golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
 golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/srtp_protection_profile.go b/srtp_protection_profile.go
index 3ae3c28..7443519 100644
--- a/srtp_protection_profile.go
+++ b/srtp_protection_profile.go
@@ -6,8 +6,14 @@ type SRTPProtectionProfile uint16
 
 const (
 	SRTP_AES128_CM_HMAC_SHA1_80 SRTPProtectionProfile = 0x0001 // nolint
+	SRTP_AES128_CM_HMAC_SHA1_32 SRTPProtectionProfile = 0x0002 // nolint
+	SRTP_AEAD_AES_128_GCM       SRTPProtectionProfile = 0x0007 // nolint
+	SRTP_AEAD_AES_256_GCM       SRTPProtectionProfile = 0x0008 // nolint
 )
 
 var srtpProtectionProfiles = map[SRTPProtectionProfile]bool{
 	SRTP_AES128_CM_HMAC_SHA1_80: true,
+	SRTP_AES128_CM_HMAC_SHA1_32: true,
+	SRTP_AEAD_AES_128_GCM:       true,
+	SRTP_AEAD_AES_256_GCM:       true,
 }


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 4da395a..1edb0b5 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -605,6 +605,22 @@ func TestSRTPConfiguration(t *testing.T) {
 			WantClientError: nil,
 			WantServerError: nil,
 		},
+		{
+			Name:            "Multiple Suites",
+			ClientSRTP:      []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32},
+			ServerSRTP:      []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32},
+			ExpectedProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+			WantClientError: nil,
+			WantServerError: nil,
+		},
+		{
+			Name:            "Multiple Suites, Client Chooses",
+			ClientSRTP:      []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_80, SRTP_AES128_CM_HMAC_SHA1_32},
+			ServerSRTP:      []SRTPProtectionProfile{SRTP_AES128_CM_HMAC_SHA1_32, SRTP_AES128_CM_HMAC_SHA1_80},
+			ExpectedProfile: SRTP_AES128_CM_HMAC_SHA1_80,
+			WantClientError: nil,
+			WantServerError: nil,
+		},
 	} {
 		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 		defer cancel()
@@ -1671,3 +1687,90 @@ func TestProtocolVersionValidation(t *testing.T) {
 		}
 	})
 }
+
+func TestMultipleHelloVerifyRequest(t *testing.T) {
+	// Limit runtime in case of deadlocks
+	lim := test.TimeOut(time.Second * 20)
+	defer lim.Stop()
+
+	// Check for leaking routines
+	report := test.CheckRoutines(t)
+	defer report()
+
+	cookies := [][]byte{
+		// first clientHello contains an empty cookie
+		{},
+	}
+	var packets [][]byte
+	for i := 0; i < 2; i++ {
+		cookie := make([]byte, 20)
+		if _, err := rand.Read(cookie); err != nil {
+			t.Fatal(err)
+		}
+		cookies = append(cookies, cookie)
+
+		record := &recordLayer{
+			recordLayerHeader: recordLayerHeader{
+				sequenceNumber:  uint64(i),
+				protocolVersion: protocolVersion1_2,
+			},
+			content: &handshake{
+				handshakeHeader: handshakeHeader{
+					messageSequence: uint16(i),
+				},
+				handshakeMessage: &handshakeMessageHelloVerifyRequest{
+					version: protocolVersion1_2,
+					cookie:  cookie,
+				}},
+		}
+		packet, err := record.Marshal()
+		if err != nil {
+			t.Fatal(err)
+		}
+		packets = append(packets, packet)
+	}
+
+	ca, cb := dpipe.Pipe()
+	defer func() {
+		err := ca.Close()
+		if err != nil {
+			t.Error(err)
+		}
+	}()
+
+	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+	defer cancel()
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	defer wg.Wait()
+	go func() {
+		defer wg.Done()
+		_, _ = testClient(ctx, ca, &Config{}, false)
+	}()
+
+	for i, cookie := range cookies {
+		// read client hello
+		resp := make([]byte, 1024)
+		n, err := cb.Read(resp)
+		if err != nil {
+			t.Fatal(err)
+		}
+		record := &recordLayer{}
+		if err := record.Unmarshal(resp[:n]); err != nil {
+			t.Fatal(err)
+		}
+		clientHello := record.content.(*handshake).handshakeMessage.(*handshakeMessageClientHello)
+		if !bytes.Equal(clientHello.cookie, cookie) {
+			t.Fatalf("Wrong cookie, expected: %x, got: %x", clientHello.cookie, cookie)
+		}
+		if len(packets) <= i {
+			break
+		}
+		// write hello verify request
+		if _, err := cb.Write(packets[i]); err != nil {
+			t.Fatal(err)
+		}
+	}
+	cancel()
+}