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.
  • Loading branch information
at-wat committed Mar 7, 2020
1 parent 45cabe8 commit aa4ca69
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 @@ -243,20 +243,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 @@ -279,18 +279,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 @@ -405,7 +406,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 @@ -553,7 +554,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 aa4ca69

Please sign in to comment.