fix(quic): properly unwrap OONI errors from TransportError (#495)
Noticed while playing around with QUIC code. Part of https://github.com/ooni/probe/issues/1544.
This commit is contained in:
parent
50b58672c6
commit
00a85cb7f0
|
@ -179,6 +179,16 @@ func ClassifyQUICHandshakeError(err error) string {
|
||||||
if errCode == quicTLSUnrecognizedName {
|
if errCode == quicTLSUnrecognizedName {
|
||||||
return FailureSSLInvalidHostname
|
return FailureSSLInvalidHostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// quic.TransportError wraps OONI errors using the error
|
||||||
|
// code quic.InternalError. So, if the error code is
|
||||||
|
// an internal error, search for a OONI error and, if
|
||||||
|
// found, just return such an error.
|
||||||
|
if transportError.ErrorCode == quic.InternalError {
|
||||||
|
if s := failuresMap[transportError.ErrorMessage]; s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ClassifyGenericError(err)
|
return ClassifyGenericError(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,16 @@ func TestClassifyQUICHandshakeError(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("for a TransportError wrapping an OONI error", func(t *testing.T) {
|
||||||
|
err := &quic.TransportError{
|
||||||
|
ErrorCode: quic.InternalError,
|
||||||
|
ErrorMessage: FailureHostUnreachable,
|
||||||
|
}
|
||||||
|
if ClassifyQUICHandshakeError(err) != FailureHostUnreachable {
|
||||||
|
t.Fatal("unexpected results")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("for another kind of error", func(t *testing.T) {
|
t.Run("for another kind of error", func(t *testing.T) {
|
||||||
if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
|
if ClassifyQUICHandshakeError(io.EOF) != FailureEOFError {
|
||||||
t.Fatal("unexpected result")
|
t.Fatal("unexpected result")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by go generate; DO NOT EDIT.
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
// Generated: 2021-09-07 20:42:20.172357 +0200 CEST m=+0.135433792
|
// Generated: 2021-09-08 23:09:33.336763 +0200 CEST m=+0.192836793
|
||||||
|
|
||||||
package errorsx
|
package errorsx
|
||||||
|
|
||||||
|
@ -60,6 +60,49 @@ const (
|
||||||
FailureJSONParseError = "json_parse_error"
|
FailureJSONParseError = "json_parse_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// failureMap lists all failures so we can match them
|
||||||
|
// when they are wrapped by quic.TransportError.
|
||||||
|
var failuresMap = map[string]string{
|
||||||
|
"operation_canceled": "operation_canceled",
|
||||||
|
"connection_refused": "connection_refused",
|
||||||
|
"connection_reset": "connection_reset",
|
||||||
|
"host_unreachable": "host_unreachable",
|
||||||
|
"timed_out": "timed_out",
|
||||||
|
"address_family_not_supported": "address_family_not_supported",
|
||||||
|
"address_in_use": "address_in_use",
|
||||||
|
"address_not_available": "address_not_available",
|
||||||
|
"already_connected": "already_connected",
|
||||||
|
"bad_address": "bad_address",
|
||||||
|
"bad_file_descriptor": "bad_file_descriptor",
|
||||||
|
"connection_aborted": "connection_aborted",
|
||||||
|
"connection_already_in_progress": "connection_already_in_progress",
|
||||||
|
"destination_address_required": "destination_address_required",
|
||||||
|
"interrupted": "interrupted",
|
||||||
|
"invalid_argument": "invalid_argument",
|
||||||
|
"message_size": "message_size",
|
||||||
|
"network_down": "network_down",
|
||||||
|
"network_reset": "network_reset",
|
||||||
|
"network_unreachable": "network_unreachable",
|
||||||
|
"no_buffer_space": "no_buffer_space",
|
||||||
|
"no_protocol_option": "no_protocol_option",
|
||||||
|
"not_a_socket": "not_a_socket",
|
||||||
|
"not_connected": "not_connected",
|
||||||
|
"operation_would_block": "operation_would_block",
|
||||||
|
"permission_denied": "permission_denied",
|
||||||
|
"protocol_not_supported": "protocol_not_supported",
|
||||||
|
"wrong_protocol_type": "wrong_protocol_type",
|
||||||
|
"dns_bogon_error": "dns_bogon_error",
|
||||||
|
"dns_nxdomain_error": "dns_nxdomain_error",
|
||||||
|
"eof_error": "eof_error",
|
||||||
|
"generic_timeout_error": "generic_timeout_error",
|
||||||
|
"quic_incompatible_version": "quic_incompatible_version",
|
||||||
|
"ssl_failed_handshake": "ssl_failed_handshake",
|
||||||
|
"ssl_invalid_hostname": "ssl_invalid_hostname",
|
||||||
|
"ssl_unknown_authority": "ssl_unknown_authority",
|
||||||
|
"ssl_invalid_certificate": "ssl_invalid_certificate",
|
||||||
|
"json_parse_error": "json_parse_error",
|
||||||
|
}
|
||||||
|
|
||||||
// classifySyscallError converts a syscall error to the
|
// classifySyscallError converts a syscall error to the
|
||||||
// proper OONI error. Returns the OONI error string
|
// proper OONI error. Returns the OONI error string
|
||||||
// on success, an empty string otherwise.
|
// on success, an empty string otherwise.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by go generate; DO NOT EDIT.
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
// Generated: 2021-09-07 20:42:20.217324 +0200 CEST m=+0.180405501
|
// Generated: 2021-09-08 23:09:33.382965 +0200 CEST m=+0.239039834
|
||||||
|
|
||||||
package errorsx
|
package errorsx
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by go generate; DO NOT EDIT.
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
// Generated: 2021-09-07 20:42:20.037485 +0200 CEST m=+0.000558667
|
// Generated: 2021-09-08 23:09:33.144513 +0200 CEST m=+0.000582543
|
||||||
|
|
||||||
package errorsx
|
package errorsx
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by go generate; DO NOT EDIT.
|
// Code generated by go generate; DO NOT EDIT.
|
||||||
// Generated: 2021-09-07 20:42:20.147482 +0200 CEST m=+0.110557751
|
// Generated: 2021-09-08 23:09:33.310337 +0200 CEST m=+0.166410043
|
||||||
|
|
||||||
package errorsx
|
package errorsx
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,15 @@ func writeGenericFile() {
|
||||||
}
|
}
|
||||||
fileWrite(filep, ")\n\n")
|
fileWrite(filep, ")\n\n")
|
||||||
|
|
||||||
|
fileWrite(filep, "// failureMap lists all failures so we can match them\n")
|
||||||
|
fileWrite(filep, "// when they are wrapped by quic.TransportError.\n")
|
||||||
|
fileWrite(filep, "var failuresMap = map[string]string{\n")
|
||||||
|
for _, spec := range Specs {
|
||||||
|
filePrintf(filep, "\t\"%s\": \"%s\",\n",
|
||||||
|
spec.AsFailureString(), spec.AsFailureString())
|
||||||
|
}
|
||||||
|
fileWrite(filep, "}\n\n")
|
||||||
|
|
||||||
fileWrite(filep, "// classifySyscallError converts a syscall error to the\n")
|
fileWrite(filep, "// classifySyscallError converts a syscall error to the\n")
|
||||||
fileWrite(filep, "// proper OONI error. Returns the OONI error string\n")
|
fileWrite(filep, "// proper OONI error. Returns the OONI error string\n")
|
||||||
fileWrite(filep, "// on success, an empty string otherwise.\n")
|
fileWrite(filep, "// on success, an empty string otherwise.\n")
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||||
utls "gitlab.com/yawning/utls.git"
|
utls "gitlab.com/yawning/utls.git"
|
||||||
)
|
)
|
||||||
|
@ -90,3 +91,45 @@ func TestUTLSHandshaker(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQUICDialer(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skip test in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("works as intended", func(t *testing.T) {
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: "dns.google",
|
||||||
|
}
|
||||||
|
d := netxlite.NewQUICDialerWithoutResolver(
|
||||||
|
netxlite.NewQUICListener(), log.Log,
|
||||||
|
)
|
||||||
|
ctx := context.Background()
|
||||||
|
sess, err := d.DialContext(
|
||||||
|
ctx, "udp", "8.8.8.8:443", tlsConfig, &quic.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("not the error we expected", err)
|
||||||
|
}
|
||||||
|
<-sess.HandshakeComplete().Done()
|
||||||
|
if err := sess.CloseWithError(0, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can guess the SNI and ALPN when using a domain name for web", func(t *testing.T) {
|
||||||
|
d := netxlite.NewQUICDialerWithResolver(
|
||||||
|
netxlite.NewQUICListener(), log.Log,
|
||||||
|
netxlite.NewResolverSystem(log.Log),
|
||||||
|
)
|
||||||
|
ctx := context.Background()
|
||||||
|
sess, err := d.DialContext(
|
||||||
|
ctx, "udp", "dns.google:443", &tls.Config{}, &quic.Config{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("not the error we expected", err)
|
||||||
|
}
|
||||||
|
<-sess.HandshakeComplete().Done()
|
||||||
|
if err := sess.CloseWithError(0, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -67,7 +67,15 @@ type QUICDialer interface {
|
||||||
//
|
//
|
||||||
// 6. if a dialer wraps a resolver, the dialer will forward
|
// 6. if a dialer wraps a resolver, the dialer will forward
|
||||||
// the CloseIdleConnection call to its resolver (which is
|
// the CloseIdleConnection call to its resolver (which is
|
||||||
// instrumental to manage a DoH resolver connections properly).
|
// instrumental to manage a DoH resolver connections properly);
|
||||||
|
//
|
||||||
|
// 7. will use the bundled CA unless you provide another CA;
|
||||||
|
//
|
||||||
|
// 8. will attempt to guess SNI when resolving domain names
|
||||||
|
// and otherwise will not set the SNI;
|
||||||
|
//
|
||||||
|
// 9. will attempt to guess ALPN when the port is known and
|
||||||
|
// otherwise will not set the ALPN.
|
||||||
func NewQUICDialerWithResolver(listener QUICListener,
|
func NewQUICDialerWithResolver(listener QUICListener,
|
||||||
logger Logger, resolver Resolver) QUICDialer {
|
logger Logger, resolver Resolver) QUICDialer {
|
||||||
return &quicDialerLogger{
|
return &quicDialerLogger{
|
||||||
|
|
|
@ -144,28 +144,6 @@ func TestQUICDialerQUICGo(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("works as intended", func(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skip test in short mode")
|
|
||||||
}
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: "dns.google",
|
|
||||||
}
|
|
||||||
systemdialer := quicDialerQUICGo{
|
|
||||||
QUICListener: &quicListenerStdlib{},
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
|
||||||
sess, err := systemdialer.DialContext(
|
|
||||||
ctx, "udp", "8.8.8.8:443", tlsConfig, &quic.Config{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("not the error we expected", err)
|
|
||||||
}
|
|
||||||
<-sess.HandshakeComplete().Done()
|
|
||||||
if err := sess.CloseWithError(0, ""); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("TLS defaults for web", func(t *testing.T) {
|
t.Run("TLS defaults for web", func(t *testing.T) {
|
||||||
expected := errors.New("mocked error")
|
expected := errors.New("mocked error")
|
||||||
var gotTLSConfig *tls.Config
|
var gotTLSConfig *tls.Config
|
||||||
|
|
Loading…
Reference in New Issue
Block a user