b78b9aca51
This diff changes the data format to prefer "udp" to "quic" everywhere we were previously using "quic". Previously, the code inconsistently used "quic" for operations where we knew we were using "quic" and "udp" otherwise (e.g., for generic operations like ReadFrom). While it would be more correct to say that a specific HTTP request used "quic" rather than "udp", using "udp" consistently allows one to see how distinct events such as ReadFrom and an handshake all refer to the same address, port, and protocol triple. Therefore, this change makes it easier to programmatically unpack a single measurement and create endpoint stats. Before implementing this change, I discussed the problem with @hellais who mentioned that ooni/data is not currently using the "quic" string anywhere. I know that ooni/pipeline also doesn't rely on this string. The only users of this feature have been research-oriented experiments such as urlgetter, for which such a change would actually be acceptable. See https://github.com/ooni/probe/issues/2238 and https://github.com/ooni/spec/pull/262.
160 lines
4.2 KiB
Go
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: "udp",
|
|
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: "udp",
|
|
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: "udp",
|
|
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: "udp",
|
|
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
|
|
}
|