Repository: pion/dtls
Pull request #209 (2020-03-05 10:39:35)

Refactor errors
Wrap all errors to implement net.Error.
Caller of Read/Write can check `err.(net.Error).Temporary()` to determine whether the error is fatal or not, as with raw `net.UDPConn`.

Split `errInvalidPacketLength` from `errLengthMismatch`.
`errInvalidPacketLength` is used for errors caused by incoming data, which must be discarded, and `errLengthMismatch` is used for errors due to the data generated by the library, which is a fatal internal error.


codecov[bot]: # [Codecov](https://codecov.io/gh/pion/dtls/pull/209?src=pr&el=h1) Report
> Merging [#209](https://codecov.io/gh/pion/dtls/pull/209?src=pr&el=desc) into [master](https://codecov.io/gh/pion/dtls/commit/3b0a286c52a17f9adab20bf15b493e1c28a9b609?src=pr&el=desc) will **decrease** coverage by `0.63%`.
> The diff coverage is `49.05%`.

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

```diff
@@            Coverage Diff            @@
##           master    #209      +/-   ##
=========================================
- Coverage   77.23%   76.6%   -0.64%     
=========================================
  Files          73      74       +1     
  Lines        3963    3993      +30     
=========================================
- Hits         3061    3059       -2     
- Misses        612     642      +30     
- Partials      290     292       +2
```

| Flag | Coverage Δ | |
|---|---|---|
| #go | `76.6% <49.05%> (-0.64%)` | :arrow_down: |
| #wasm | `?` | |

| [Impacted Files](https://codecov.io/gh/pion/dtls/pull/209?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [errors\_errno.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-ZXJyb3JzX2Vycm5vLmdv) | `0% <0%> (ø)` | |
| [record\_layer.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-cmVjb3JkX2xheWVyLmdv) | `68.42% <100%> (ø)` | :arrow_up: |
| [conn.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-Y29ubi5nbw==) | `84.75% <100%> (-0.13%)` | :arrow_down: |
| [errors.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-ZXJyb3JzLmdv) | `42.42% <34.48%> (-26.81%)` | :arrow_down: |
| [internal/net/deadline/deadline.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbmV0L2RlYWRsaW5lL2RlYWRsaW5lLmdv) | `82.45% <0%> (-3.51%)` | :arrow_down: |
| [internal/net/dpipe/dpipe.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbmV0L2RwaXBlL2RwaXBlLmdv) | `94.11% <0%> (-2.36%)` | :arrow_down: |
| [internal/net/udp/conn.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-aW50ZXJuYWwvbmV0L3VkcC9jb25uLmdv) | `85.15% <0%> (-1.57%)` | :arrow_down: |
| [handshaker.go](https://codecov.io/gh/pion/dtls/pull/209/diff?src=pr&el=tree#diff-aGFuZHNoYWtlci5nbw==) | `73.94% <0%> (-1.41%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/dtls/pull/209?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/209?src=pr&el=footer). Last update [3b0a286...08e9c93](https://codecov.io/gh/pion/dtls/pull/209?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

at-wat: rebased

Patch:
diff --git a/conn.go b/conn.go
index c518c72..b15a4e3 100644
--- a/conn.go
+++ b/conn.go
@@ -259,20 +259,20 @@ func ServerWithContext(ctx context.Context, conn net.Conn, config *Config) (*Con
 
 // Read reads data from the connection.
 func (c *Conn) Read(p []byte) (n int, err error) {
+	if !c.isHandshakeCompletedSuccessfully() {
+		return 0, errHandshakeInProgress
+	}
+
 	select {
 	case <-c.readDeadline.Done():
-		return 0, context.DeadlineExceeded
+		return 0, errDeadlineExceeded
 	default:
 	}
 
-	if !c.isHandshakeCompletedSuccessfully() {
-		return 0, errHandshakeInProgress
-	}
-
 	for {
 		select {
 		case <-c.readDeadline.Done():
-			return 0, context.DeadlineExceeded
+			return 0, errDeadlineExceeded
 		case <-c.closed.Done():
 			return 0, io.EOF
 		case out, ok := <-c.decrypted:
@@ -295,18 +295,19 @@ func (c *Conn) Read(p []byte) (n int, err error) {
 
 // Write writes len(p) bytes from p to the DTLS connection
 func (c *Conn) Write(p []byte) (int, error) {
+	if c.isConnectionClosed() {
+		return 0, ErrConnClosed
+	}
+
 	select {
 	case <-c.writeDeadline.Done():
-		return 0, context.DeadlineExceeded
+		return 0, errDeadlineExceeded
 	default:
 	}
 
 	if !c.isHandshakeCompletedSuccessfully() {
 		return 0, errHandshakeInProgress
 	}
-	if c.isConnectionClosed() {
-		return 0, ErrConnClosed
-	}
 
 	return len(p), c.writePackets(c.writeDeadline, []*packet{
 		{
@@ -421,7 +422,7 @@ func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error {
 
 	for _, compactedRawPackets := range compactedRawPackets {
 		if _, err := c.nextConn.Write(ctx, compactedRawPackets); err != nil {
-			return err
+			return netError(err)
 		}
 	}
 
@@ -569,7 +570,7 @@ func (c *Conn) readAndBuffer(ctx context.Context) error {
 	b := *bufptr
 	i, err := c.nextConn.Read(ctx, b)
 	if err != nil {
-		return err
+		return netError(err)
 	}
 
 	pkts, err := unpackDatagram(b[:i])
diff --git a/errors.go b/errors.go
index aaa281a..aa007bd 100644
--- a/errors.go
+++ b/errors.go
@@ -4,75 +4,145 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
+	"net"
+	"os"
 
 	"golang.org/x/xerrors"
 )
 
 // Typed errors
 var (
-	ErrConnClosed = errors.New("dtls: conn is closed")
-
-	errBufferTooSmall                    = errors.New("dtls: buffer is too small")
-	errClientCertificateRequired         = errors.New("dtls: server required client verification, but got none")
-	errClientCertificateNotVerified      = errors.New("dtls: client sent certificate but did not verify it")
-	errCertificateVerifyNoCertificate    = errors.New("dtls: client sent certificate verify but we have no certificate to verify")
-	errNoCertificates                    = errors.New("dtls: no certificates configured")
-	errCipherSuiteNoIntersection         = errors.New("dtls: Client+Server do not support any shared cipher suites")
-	errCipherSuiteUnset                  = errors.New("dtls: server hello can not be created without a cipher suite")
-	errCompressionMethodUnset            = errors.New("dtls: server hello can not be created without a compression method")
-	errContextUnsupported                = errors.New("dtls: context is not supported for ExportKeyingMaterial")
-	errCookieMismatch                    = errors.New("dtls: Client+Server cookie does not match")
-	errCookieTooLong                     = errors.New("dtls: cookie must not be longer then 255 bytes")
-	errDTLSPacketInvalidLength           = errors.New("dtls: packet is too short")
-	errHandshakeInProgress               = errors.New("dtls: Handshake is in progress")
-	errHandshakeMessageUnset             = errors.New("dtls: handshake message unset, unable to marshal")
-	errInvalidCipherSpec                 = errors.New("dtls: cipher spec invalid")
-	errInvalidCipherSuite                = errors.New("dtls: invalid or unknown cipher suite")
-	errInvalidCompressionMethod          = errors.New("dtls: invalid or unknown compression method")
-	errInvalidContentType                = errors.New("dtls: invalid content type")
-	errInvalidECDSASignature             = errors.New("dtls: ECDSA signature contained zero or negative values")
-	errInvalidEllipticCurveType          = errors.New("dtls: invalid or unknown elliptic curve type")
-	errInvalidExtensionType              = errors.New("dtls: invalid extension type")
-	errInvalidSNIFormat                  = errors.New("dtls: invalid server name format")
-	errInvalidHashAlgorithm              = errors.New("dtls: invalid hash algorithm")
-	errInvalidMAC                        = errors.New("dtls: invalid mac")
-	errInvalidNamedCurve                 = errors.New("dtls: invalid named curve")
-	errInvalidPrivateKey                 = errors.New("dtls: invalid private key type")
-	errInvalidSignatureAlgorithm         = errors.New("dtls: invalid signature algorithm")
-	errInvalidFlight                     = errors.New("dtls: invalid flight number")
-	errKeySignatureGenerateUnimplemented = errors.New("dtls: Unable to generate key signature, unimplemented")
-	errKeySignatureMismatch              = errors.New("dtls: Expected and actual key signature do not match")
-	errKeySignatureVerifyUnimplemented   = errors.New("dtls: Unable to verify key signature, unimplemented")
-	errLengthMismatch                    = errors.New("dtls: data length and declared length do not match")
-	errNilNextConn                       = errors.New("dtls: Conn can not be created with a nil nextConn")
-	errNotEnoughRoomForNonce             = errors.New("dtls: Buffer not long enough to contain nonce")
-	errNotImplemented                    = errors.New("dtls: feature has not been implemented yet")
-	errReservedExportKeyingMaterial      = errors.New("dtls: ExportKeyingMaterial can not be used with a reserved label")
-	errSequenceNumberOverflow            = errors.New("dtls: sequence number overflow")
-	errServerMustHaveCertificate         = errors.New("dtls: Certificate is mandatory for server")
-	errUnableToMarshalFragmented         = errors.New("dtls: unable to marshal fragmented handshakes")
-	errVerifyDataMismatch                = errors.New("dtls: Expected and actual verify data does not match")
-	errNoConfigProvided                  = errors.New("dtls: No config provided")
-	errPSKAndCertificate                 = errors.New("dtls: Certificate and PSK provided")
-	errPSKAndIdentityMustBeSetForClient  = errors.New("dtls: PSK and PSK Identity Hint must both be set for client")
-	errIdentityNoPSK                     = errors.New("dtls: Identity Hint provided but PSK is nil")
-	errNoAvailableCipherSuites           = errors.New("dtls: Connection can not be created, no CipherSuites satisfy this Config")
-	errInvalidClientKeyExchange          = errors.New("dtls: Unable to determine if ClientKeyExchange is a public key or PSK Identity")
-	errNoSupportedEllipticCurves         = errors.New("dtls: Client requested zero or more elliptic curves that are not supported by the server")
-	errRequestedButNoSRTPExtension       = errors.New("dtls: SRTP support was requested but server did not respond with use_srtp extension")
-	errClientNoMatchingSRTPProfile       = errors.New("dtls: Server responded with SRTP Profile we do not support")
-	errServerNoMatchingSRTPProfile       = errors.New("dtls: Client requested SRTP but we have no matching profiles")
-	errServerRequiredButNoClientEMS      = errors.New("dtls: Server requires the Extended Master Secret extension, but the client does not support it")
-	errClientRequiredButNoServerEMS      = errors.New("dtls: Client required Extended Master Secret extension, but server does not support it")
-	errInvalidCertificate                = errors.New("dtls: No certificate provided")
-
-	// Wrapped errors
-	errHandshakeTimeout = newNetError(
-		xerrors.Errorf("dtls: The connection timed out during the handshake: %w", context.DeadlineExceeded),
-		true, false,
-	)
+	ErrConnClosed = &ErrFatal{errors.New("conn is closed")}
+
+	errDeadlineExceeded = &ErrTimeout{xerrors.Errorf("read/write timeout: %w", context.DeadlineExceeded)}
+
+	errBufferTooSmall               = &ErrTemporary{errors.New("buffer is too small")}
+	errContextUnsupported           = &ErrTemporary{errors.New("context is not supported for ExportKeyingMaterial")}
+	errDTLSPacketInvalidLength      = &ErrTemporary{errors.New("packet is too short")}
+	errHandshakeInProgress          = &ErrTemporary{errors.New("handshake is in progress")}
+	errInvalidContentType           = &ErrTemporary{errors.New("invalid content type")}
+	errInvalidMAC                   = &ErrTemporary{errors.New("invalid mac")}
+	errInvalidPacketLength          = &ErrTemporary{errors.New("packet length and declared length do not match")}
+	errReservedExportKeyingMaterial = &ErrTemporary{errors.New("ExportKeyingMaterial can not be used with a reserved label")}
+
+	errCertificateVerifyNoCertificate   = &ErrFatal{errors.New("client sent certificate verify but we have no certificate to verify")}
+	errCipherSuiteNoIntersection        = &ErrFatal{errors.New("client+server do not support any shared cipher suites")}
+	errCipherSuiteUnset                 = &ErrFatal{errors.New("server hello can not be created without a cipher suite")}
+	errClientCertificateNotVerified     = &ErrFatal{errors.New("client sent certificate but did not verify it")}
+	errClientCertificateRequired        = &ErrFatal{errors.New("server required client verification, but got none")}
+	errClientNoMatchingSRTPProfile      = &ErrFatal{errors.New("server responded with SRTP Profile we do not support")}
+	errClientRequiredButNoServerEMS     = &ErrFatal{errors.New("client required Extended Master Secret extension, but server does not support it")}
+	errCompressionMethodUnset           = &ErrFatal{errors.New("server hello can not be created without a compression method")}
+	errCookieMismatch                   = &ErrFatal{errors.New("client+server cookie does not match")}
+	errCookieTooLong                    = &ErrFatal{errors.New("cookie must not be longer then 255 bytes")}
+	errHandshakeTimeout                 = &ErrFatal{xerrors.Errorf("the connection timed out during the handshake: %w", context.DeadlineExceeded)}
+	errIdentityNoPSK                    = &ErrFatal{errors.New("PSK Identity Hint provided but PSK is nil")}
+	errInvalidCertificate               = &ErrFatal{errors.New("no certificate provided")}
+	errInvalidCipherSpec                = &ErrFatal{errors.New("cipher spec invalid")}
+	errInvalidCipherSuite               = &ErrFatal{errors.New("invalid or unknown cipher suite")}
+	errInvalidClientKeyExchange         = &ErrFatal{errors.New("unable to determine if ClientKeyExchange is a public key or PSK Identity")}
+	errInvalidCompressionMethod         = &ErrFatal{errors.New("invalid or unknown compression method")}
+	errInvalidECDSASignature            = &ErrFatal{errors.New("ECDSA signature contained zero or negative values")}
+	errInvalidEllipticCurveType         = &ErrFatal{errors.New("invalid or unknown elliptic curve type")}
+	errInvalidExtensionType             = &ErrFatal{errors.New("invalid extension type")}
+	errInvalidHashAlgorithm             = &ErrFatal{errors.New("invalid hash algorithm")}
+	errInvalidNamedCurve                = &ErrFatal{errors.New("invalid named curve")}
+	errInvalidPrivateKey                = &ErrFatal{errors.New("invalid private key type")}
+	errInvalidSNIFormat                 = &ErrFatal{errors.New("invalid server name format")}
+	errInvalidSignatureAlgorithm        = &ErrFatal{errors.New("invalid signature algorithm")}
+	errKeySignatureMismatch             = &ErrFatal{errors.New("expected and actual key signature do not match")}
+	errNilNextConn                      = &ErrFatal{errors.New("Conn can not be created with a nil nextConn")}
+	errNoAvailableCipherSuites          = &ErrFatal{errors.New("connection can not be created, no CipherSuites satisfy this Config")}
+	errNoCertificates                   = &ErrFatal{errors.New("no certificates configured")}
+	errNoConfigProvided                 = &ErrFatal{errors.New("no config provided")}
+	errNoSupportedEllipticCurves        = &ErrFatal{errors.New("client requested zero or more elliptic curves that are not supported by the server")}
+	errPSKAndCertificate                = &ErrFatal{errors.New("Certificate and PSK provided")} // nolint:stylecheck
+	errPSKAndIdentityMustBeSetForClient = &ErrFatal{errors.New("PSK and PSK Identity Hint must both be set for client")}
+	errRequestedButNoSRTPExtension      = &ErrFatal{errors.New("SRTP support was requested but server did not respond with use_srtp extension")}
+	errServerMustHaveCertificate        = &ErrFatal{errors.New("Certificate is mandatory for server")} // nolint:stylecheck
+	errServerNoMatchingSRTPProfile      = &ErrFatal{errors.New("client requested SRTP but we have no matching profiles")}
+	errServerRequiredButNoClientEMS     = &ErrFatal{errors.New("server requires the Extended Master Secret extension, but the client does not support it")}
+	errVerifyDataMismatch               = &ErrFatal{errors.New("expected and actual verify data does not match")}
+
+	errHandshakeMessageUnset             = &ErrInternal{errors.New("handshake message unset, unable to marshal")}
+	errInvalidFlight                     = &ErrInternal{errors.New("invalid flight number")}
+	errKeySignatureGenerateUnimplemented = &ErrInternal{errors.New("unable to generate key signature, unimplemented")}
+	errKeySignatureVerifyUnimplemented   = &ErrInternal{errors.New("unable to verify key signature, unimplemented")}
+	errLengthMismatch                    = &ErrInternal{errors.New("data length and declared length do not match")}
+	errNotEnoughRoomForNonce             = &ErrInternal{errors.New("buffer not long enough to contain nonce")}
+	errNotImplemented                    = &ErrInternal{errors.New("feature has not been implemented yet")}
+	errSequenceNumberOverflow            = &ErrInternal{errors.New("sequence number overflow")}
+	errUnableToMarshalFragmented         = &ErrInternal{errors.New("unable to marshal fragmented handshakes")}
 )
 
+// ErrFatal indicates that the DTLS connection is no longer available.
+// It is mainly caused by wrong configuration of server or client.
+type ErrFatal struct {
+	Err error
+}
+
+// ErrInternal indicates and internal error caused by the implementation, and the DTLS connection is no longer available.
+// It is mainly caused by bugs or tried to use unimplemented features.
+type ErrInternal struct {
+	Err error
+}
+
+// ErrTemporary indicates that the DTLS connection is still available, but the request was failed temporary.
+type ErrTemporary struct {
+	Err error
+}
+
+// ErrTimeout indicates that the request was timed out.
+type ErrTimeout struct {
+	Err error
+}
+
+// Timeout implements net.Error.Timeout()
+func (*ErrFatal) Timeout() bool { return false }
+
+// Temporary implements net.Error.Temporary()
+func (*ErrFatal) Temporary() bool { return false }
+
+// Unwrap implements Go1.13 error unwrapper.
+func (e *ErrFatal) Unwrap() error { return e.Err }
+
+func (e *ErrFatal) Error() string { return fmt.Sprintf("dtls fatal: %v", e.Err) }
+
+// Timeout implements net.Error.Timeout()
+func (*ErrInternal) Timeout() bool { return false }
+
+// Temporary implements net.Error.Temporary()
+func (*ErrInternal) Temporary() bool { return false }
+
+// Unwrap implements Go1.13 error unwrapper.
+func (e *ErrInternal) Unwrap() error { return e.Err }
+
+func (e *ErrInternal) Error() string { return fmt.Sprintf("dtls internal: %v", e.Err) }
+
+// Timeout implements net.Error.Timeout()
+func (*ErrTemporary) Timeout() bool { return false }
+
+// Temporary implements net.Error.Temporary()
+func (*ErrTemporary) Temporary() bool { return true }
+
+// Unwrap implements Go1.13 error unwrapper.
+func (e *ErrTemporary) Unwrap() error { return e.Err }
+
+func (e *ErrTemporary) Error() string { return fmt.Sprintf("dtls temporary: %v", e.Err) }
+
+// Timeout implements net.Error.Timeout()
+func (*ErrTimeout) Timeout() bool { return true }
+
+// Temporary implements net.Error.Temporary()
+func (*ErrTimeout) Temporary() bool { return true }
+
+// Unwrap implements Go1.13 error unwrapper.
+func (e *ErrTimeout) Unwrap() error { return e.Err }
+
+func (e *ErrTimeout) Error() string { return fmt.Sprintf("dtls timeout: %v", e.Err) }
+
+// errAlert wraps DTLS alert notification as an error
 type errAlert struct {
 	*alert
 }
@@ -85,24 +155,25 @@ func (e *errAlert) IsFatalOrCloseNotify() bool {
 	return e.alertLevel == alertLevelFatal || e.alertDescription == alertCloseNotify
 }
 
-type netError struct {
-	error
-	timeout   bool
-	temporary bool
-}
-
-func newNetError(err error, timeout, temporary bool) error {
-	return &netError{
-		error:     err,
-		timeout:   timeout,
-		temporary: temporary,
+// netError translates an error from underlying Conn to corresponding net.Error.
+func netError(err error) error {
+	switch err {
+	case io.EOF, context.Canceled, context.DeadlineExceeded:
+		// Return io.EOF and context errors as is.
+		return err
 	}
-}
-
-func (e *netError) Timeout() bool {
-	return e.timeout
-}
-
-func (e *netError) Temporary() bool {
-	return e.temporary
+	switch e := err.(type) {
+	case (*net.OpError):
+		if se, ok := e.Err.(*os.SyscallError); ok {
+			if se.Timeout() {
+				return &ErrTimeout{err}
+			}
+			if isOpErrorTemporary(se) {
+				return &ErrTemporary{err}
+			}
+		}
+	case (net.Error):
+		return err
+	}
+	return &ErrFatal{err}
 }
diff --git a/errors_errno.go b/errors_errno.go
new file mode 100644
index 0000000..a9a439b
--- /dev/null
+++ b/errors_errno.go
@@ -0,0 +1,25 @@
+// +build aix darwin dragonfly freebsd linux nacl nacljs netbsd openbsd solaris windows
+
+// For systems having syscall.Errno.
+// Update build targets by following command:
+// $ grep -R ECONN $(go env GOROOT)/src/syscall/zerrors_*.go \
+//     | tr "." "_" | cut -d"_" -f"2" | sort | uniq
+
+package dtls
+
+import (
+	"os"
+	"syscall"
+)
+
+func isOpErrorTemporary(err *os.SyscallError) bool {
+	if ne, ok := err.Err.(syscall.Errno); ok {
+		switch ne {
+		case syscall.ECONNREFUSED:
+			return true
+		default:
+			return false
+		}
+	}
+	return false
+}
diff --git a/errors_noerrno.go b/errors_noerrno.go
new file mode 100644
index 0000000..fcc37ce
--- /dev/null
+++ b/errors_noerrno.go
@@ -0,0 +1,14 @@
+// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!nacl,!nacljs,!netbsd,!openbsd,!solaris,!windows
+
+// For systems without syscall.Errno.
+// Build targets must be inverse of errors_errno.go
+
+package dtls
+
+import (
+	"os"
+)
+
+func isOpErrorTemporary(err *os.SyscallError) bool {
+	return false
+}
diff --git a/examples/util/util.go b/examples/util/util.go
index 06cf02f..bc08688 100644
--- a/examples/util/util.go
+++ b/examples/util/util.go
@@ -5,6 +5,7 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"net"
 	"os"
 	"strings"
 )
@@ -17,9 +18,7 @@ func Chat(conn io.ReadWriter) {
 		b := make([]byte, bufSize)
 		for {
 			n, err := conn.Read(b)
-			if err != nil {
-				return
-			}
+			Check(err)
 			fmt.Printf("Got message: %s\n", string(b[:n]))
 		}
 	}()
@@ -38,7 +37,17 @@ func Chat(conn io.ReadWriter) {
 
 // Check is a helper to throw errors in the examples
 func Check(err error) {
-	if err != nil {
+	switch e := err.(type) {
+	case nil:
+	case (net.Error):
+		if e.Temporary() {
+			fmt.Printf("Warning: %v\n", err)
+			return
+		}
+		fmt.Printf("net.Error: %v\n", err)
+		panic(err)
+	default:
+		fmt.Printf("error: %v\n", err)
 		panic(err)
 	}
 }
diff --git a/go.sum b/go.sum
index 04f6b9e..2aadd94 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 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=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
diff --git a/record_layer.go b/record_layer.go
index 7bd2932..11f32da 100644
--- a/record_layer.go
+++ b/record_layer.go
@@ -77,12 +77,12 @@ func unpackDatagram(buf []byte) ([][]byte, error) {
 
 	for offset := 0; len(buf) != offset; {
 		if len(buf)-offset <= recordLayerHeaderSize {
-			return nil, errDTLSPacketInvalidLength
+			return nil, errInvalidPacketLength
 		}
 
 		pktLen := (recordLayerHeaderSize + int(binary.BigEndian.Uint16(buf[offset+11:])))
 		if offset+pktLen > len(buf) {
-			return nil, errLengthMismatch
+			return nil, errInvalidPacketLength
 		}
 
 		out = append(out, buf[offset:offset+pktLen])


Test patch:
diff --git a/conn_test.go b/conn_test.go
index 457e7c9..4e81c8d 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -8,6 +8,7 @@ import (
 	"crypto/x509"
 	"errors"
 	"fmt"
+	"io"
 	"net"
 	"testing"
 	"time"
@@ -85,6 +86,64 @@ func TestRoutineLeakOnClose(t *testing.T) {
 	// inboundLoop routine should not be leaked.
 }
 
+func TestReadWriteDeadline(t *testing.T) {
+	// Limit runtime in case of deadlocks
+	lim := test.TimeOut(5 * time.Second)
+	defer lim.Stop()
+
+	// Check for leaking routines
+	report := test.CheckRoutines(t)
+	defer report()
+
+	ca, cb, err := pipeMemory()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := ca.SetDeadline(time.Unix(0, 1)); err != nil {
+		t.Fatal(err)
+	}
+	_, werr := ca.Write(make([]byte, 100))
+	if e, ok := werr.(net.Error); ok {
+		if !e.Timeout() {
+			t.Error("Deadline exceeded Write must return Timeout error")
+		}
+		if !e.Temporary() {
+			t.Error("Deadline exceeded Write must return Temporary error")
+		}
+	} else {
+		t.Error("Write must return net.Error error")
+	}
+	_, rerr := ca.Read(make([]byte, 100))
+	if e, ok := rerr.(net.Error); ok {
+		if !e.Timeout() {
+			t.Error("Deadline exceeded Read must return Timeout error")
+		}
+		if !e.Temporary() {
+			t.Error("Deadline exceeded Read must return Temporary error")
+		}
+	} else {
+		t.Error("Read must return net.Error error")
+	}
+	if err := ca.SetDeadline(time.Time{}); err != nil {
+		t.Error(err)
+	}
+
+	if err := ca.Close(); err != nil {
+		t.Error(err)
+	}
+	if err := cb.Close(); err != nil {
+		t.Error(err)
+	}
+
+	if _, err := ca.Write(make([]byte, 100)); err != ErrConnClosed {
+		t.Errorf("Write must return %v after close, got %v", ErrConnClosed, err)
+	}
+	if _, err := ca.Read(make([]byte, 100)); err != io.EOF {
+		t.Errorf("Read must return %v after close, got %v", io.EOF, err)
+	}
+}
+
 func pipeMemory() (*Conn, *Conn, error) {
 	// In memory pipe
 	ca, cb := dpipe.Pipe()
diff --git a/errors_errno_test.go b/errors_errno_test.go
new file mode 100644
index 0000000..7c567ee
--- /dev/null
+++ b/errors_errno_test.go
@@ -0,0 +1,41 @@
+// +build aix darwin dragonfly freebsd linux nacl nacljs netbsd openbsd solaris windows
+
+// For systems having syscall.Errno.
+// The build target must be same as errors_errno.go.
+
+package dtls
+
+import (
+	"net"
+	"testing"
+)
+
+func TestErrorsTemporary(t *testing.T) {
+	addrListen, errListen := net.ResolveUDPAddr("udp", "localhost:0")
+	if errListen != nil {
+		t.Fatalf("Unexpected error: %v", errListen)
+	}
+	// Server is not listening.
+	conn, errDial := net.DialUDP("udp", nil, addrListen)
+	if errDial != nil {
+		t.Fatalf("Unexpected error: %v", errDial)
+	}
+
+	_, _ = conn.Write([]byte{0x00}) // trigger
+	_, err := conn.Read(make([]byte, 10))
+	_ = conn.Close()
+
+	if err == nil {
+		t.Skip("ECONNREFUSED is not set by system")
+	}
+	ne, ok := netError(err).(net.Error)
+	if !ok {
+		t.Fatalf("netError must return net.Error")
+	}
+	if ne.Timeout() {
+		t.Errorf("%v must not be timeout error", err)
+	}
+	if !ne.Temporary() {
+		t.Errorf("%v must be temporary error", err)
+	}
+}
diff --git a/errors_test.go b/errors_test.go
new file mode 100644
index 0000000..a5a511e
--- /dev/null
+++ b/errors_test.go
@@ -0,0 +1,78 @@
+package dtls
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"testing"
+
+	"golang.org/x/xerrors"
+)
+
+func TestErrorUnwrap(t *testing.T) {
+	errExample := errors.New("an example error")
+
+	cases := []struct {
+		err          error
+		errUnwrapped []error
+	}{
+		{
+			&ErrFatal{errExample},
+			[]error{errExample},
+		},
+		{
+			&ErrTemporary{errExample},
+			[]error{errExample},
+		},
+		{
+			&ErrInternal{errExample},
+			[]error{errExample},
+		},
+		{
+			&ErrTimeout{errExample},
+			[]error{errExample},
+		},
+	}
+	for _, c := range cases {
+		c := c
+		t.Run(fmt.Sprintf("%T", c.err), func(t *testing.T) {
+			err := c.err
+			for _, unwrapped := range c.errUnwrapped {
+				e := xerrors.Unwrap(err)
+				if e != unwrapped {
+					t.Errorf("Unwrapped error is expected to be '%v', got '%v'", unwrapped, e)
+				}
+			}
+		})
+	}
+}
+
+func TestErrorNetError(t *testing.T) {
+	errExample := errors.New("an example error")
+
+	cases := []struct {
+		err                error
+		str                string
+		timeout, temporary bool
+	}{
+		{&ErrFatal{errExample}, "dtls fatal: an example error", false, false},
+		{&ErrTemporary{errExample}, "dtls temporary: an example error", false, true},
+		{&ErrInternal{errExample}, "dtls internal: an example error", false, false},
+		{&ErrTimeout{errExample}, "dtls timeout: an example error", true, true},
+	}
+	for _, c := range cases {
+		c := c
+		t.Run(fmt.Sprintf("%T", c.err), func(t *testing.T) {
+			ne, ok := c.err.(net.Error)
+			if !ok {
+				t.Fatalf("%T doesn't implement net.Error", c.err)
+			}
+			if ne.Timeout() != c.timeout {
+				t.Errorf("%T.Timeout() should be %v", c.err, c.timeout)
+			}
+			if ne.Temporary() != c.temporary {
+				t.Errorf("%T.Temporary() should be %v", c.err, c.temporary)
+			}
+		})
+	}
+}
diff --git a/record_layer_test.go b/record_layer_test.go
index f838aa8..5c757ac 100644
--- a/record_layer_test.go
+++ b/record_layer_test.go
@@ -33,12 +33,12 @@ func TestUDPDecode(t *testing.T) {
 		{
 			Name:      "Invalid packet length",
 			Data:      []byte{0x14, 0xfe},
-			WantError: errDTLSPacketInvalidLength,
+			WantError: errInvalidPacketLength,
 		},
 		{
 			Name:      "Packet declared invalid length",
 			Data:      []byte{0x14, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xFF, 0x01},
-			WantError: errLengthMismatch,
+			WantError: errInvalidPacketLength,
 		},
 	} {
 		dtlsPkts, err := unpackDatagram(test.Data)