ooni-probe-cli/internal/measurex/quic.go
Simone Basso 9ffa124511
chore: upgrade deps and attempt to enable using go1.19 (#869)
* upgrade to our go.mod enabled of psiphon-tunnel-core such that
we're now using v2.0.24 of the tunnel-core;

* upgrade to the latest lucas-clemente/quic-go release;

* upgrade to the latest ooni/oohttp release (which is based on go1.19
but the diff seems good enough to continue using go1.18.x as well);

* upgrade to the latest ooni/oocrypto release (for which we can make the
same remarks regarding using go1.18.x);

* deal with changes in lucas-clemente/quic-go API as well as changes
in what a go1.19 *tls.Conn compatible type should look like.

Unfortunately, we cannot switch to go1.19 because psiphon forks quic-go
and their fork's still not building using such a version of go.

Part of ooni/probe#2211.
2022-08-19 11:26:50 +02:00

160 lines
4.2 KiB
Go

package measurex
//
// QUIC
//
// Wrappers for QUIC to store events into a WritableDB.
//
import (
"context"
"crypto/tls"
"net"
"time"
"github.com/lucas-clemente/quic-go"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)
type quicListenerDB struct {
model.QUICListener
begin time.Time
db WritableDB
}
func (ql *quicListenerDB) Listen(addr *net.UDPAddr) (model.UDPLikeConn, error) {
pconn, err := ql.QUICListener.Listen(addr)
if err != nil {
return nil, err
}
return &udpLikeConnDB{
UDPLikeConn: pconn,
begin: ql.begin,
db: ql.db,
}, nil
}
type udpLikeConnDB struct {
model.UDPLikeConn
begin time.Time
db WritableDB
}
func (c *udpLikeConnDB) WriteTo(p []byte, addr net.Addr) (int, error) {
started := time.Since(c.begin).Seconds()
count, err := c.UDPLikeConn.WriteTo(p, addr)
finished := time.Since(c.begin).Seconds()
c.db.InsertIntoReadWrite(&NetworkEvent{
Operation: "write_to",
Network: "quic",
RemoteAddr: addr.String(),
Started: started,
Finished: finished,
Failure: NewFailure(err),
Count: count,
})
return count, err
}
func (c *udpLikeConnDB) ReadFrom(b []byte) (int, net.Addr, error) {
started := time.Since(c.begin).Seconds()
count, addr, err := c.UDPLikeConn.ReadFrom(b)
finished := time.Since(c.begin).Seconds()
c.db.InsertIntoReadWrite(&NetworkEvent{
Operation: "read_from",
Network: "quic",
RemoteAddr: addrStringIfNotNil(addr),
Started: started,
Finished: finished,
Failure: NewFailure(err),
Count: count,
})
return count, addr, err
}
func (c *udpLikeConnDB) Close() error {
started := time.Since(c.begin).Seconds()
err := c.UDPLikeConn.Close()
finished := time.Since(c.begin).Seconds()
c.db.InsertIntoClose(&NetworkEvent{
Operation: "close",
Network: "quic",
RemoteAddr: "",
Started: started,
Finished: finished,
Failure: NewFailure(err),
Count: 0,
})
return err
}
// NewQUICDialerWithoutResolver creates a new QUICDialer that is not
// attached to any resolver. This means that every attempt to dial any
// address containing a domain name will fail. This QUICDialer will
// save any event into the WritableDB. Any QUICConn created by it will
// likewise save any event into the WritableDB.
func (mx *Measurer) NewQUICDialerWithoutResolver(db WritableDB, logger model.Logger) model.QUICDialer {
return &quicDialerDB{db: db, logger: logger, begin: mx.Begin}
}
type quicDialerDB struct {
model.QUICDialer
begin time.Time
db WritableDB
logger model.Logger
}
func (qh *quicDialerDB) DialContext(ctx context.Context, address string,
tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
started := time.Since(qh.begin).Seconds()
var state tls.ConnectionState
listener := &quicListenerDB{
QUICListener: netxlite.NewQUICListener(),
begin: qh.begin,
db: qh.db,
}
dialer := netxlite.NewQUICDialerWithoutResolver(listener, qh.logger)
defer dialer.CloseIdleConnections()
sess, err := dialer.DialContext(ctx, address, tlsConfig, quicConfig)
if err == nil {
<-sess.HandshakeComplete().Done() // robustness (the dialer already does that)
state = sess.ConnectionState().TLS.ConnectionState
}
finished := time.Since(qh.begin).Seconds()
qh.db.InsertIntoQUICHandshake(&QUICTLSHandshakeEvent{
Network: "quic",
RemoteAddr: address,
SNI: tlsConfig.ServerName,
ALPN: tlsConfig.NextProtos,
SkipVerify: tlsConfig.InsecureSkipVerify,
Started: started,
Finished: finished,
Failure: NewFailure(err),
Oddity: qh.computeOddity(err),
TLSVersion: netxlite.TLSVersionString(state.Version),
CipherSuite: netxlite.TLSCipherSuiteString(state.CipherSuite),
NegotiatedProto: state.NegotiatedProtocol,
PeerCerts: peerCerts(nil, &state),
})
return sess, err
}
func (qh *quicDialerDB) computeOddity(err error) Oddity {
if err == nil {
return ""
}
switch err.Error() {
case netxlite.FailureGenericTimeoutError:
return OddityQUICHandshakeTimeout
case netxlite.FailureHostUnreachable:
return OddityQUICHandshakeHostUnreachable
default:
return OddityQUICHandshakeOther
}
}
func (qh *quicDialerDB) CloseIdleConnections() {
// nothing to do
}