Skip to content

Commit

Permalink
Refactor errors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
at-wat committed Mar 8, 2020
1 parent 1a60060 commit 3ee958c
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 100 deletions.
25 changes: 13 additions & 12 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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{
{
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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])
Expand Down
59 changes: 59 additions & 0 deletions conn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"testing"
"time"
Expand Down Expand Up @@ -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()
Expand Down
231 changes: 151 additions & 80 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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}
}
Loading

0 comments on commit 3ee958c

Please sign in to comment.