package measurex // // Dialer // // Wrappers for Dialer and Conn to store events into a WritableDB. // import ( "context" "net" "time" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" ) // Conn is a network connection. type Conn = net.Conn // WrapDialer creates a new dialer that writes events // into the given WritableDB. The net.Conns created by // a wrapped dialer also write into the WritableDB. func (mx *Measurer) WrapDialer(db WritableDB, dialer model.Dialer) model.Dialer { return WrapDialer(mx.Begin, db, dialer) } // WrapDialer wraps a dialer. func WrapDialer(begin time.Time, db WritableDB, dialer model.Dialer) model.Dialer { return &dialerDB{Dialer: dialer, db: db, begin: begin} } // NewDialerWithSystemResolver creates a func (mx *Measurer) NewDialerWithSystemResolver(db WritableDB, logger model.Logger) model.Dialer { r := mx.NewResolverSystem(db, logger) return mx.WrapDialer(db, netxlite.NewDialerWithResolver(logger, r)) } // NewDialerWithoutResolver is a convenience factory for creating // a dialer that saves measurements into the DB and that is not attached // to any resolver (hence only works when passed IP addresses). func (mx *Measurer) NewDialerWithoutResolver(db WritableDB, logger model.Logger) model.Dialer { return mx.WrapDialer(db, netxlite.NewDialerWithoutResolver(logger)) } type dialerDB struct { model.Dialer begin time.Time db WritableDB } // NetworkEvent contains a network event. This kind of events // are generated by Dialer, QUICDialer, Conn, QUICConn. type NetworkEvent struct { RemoteAddr string Failure *string Count int Operation string Network string Oddity Oddity Finished float64 Started float64 } func (d *dialerDB) DialContext( ctx context.Context, network, address string) (Conn, error) { started := time.Since(d.begin).Seconds() conn, err := d.Dialer.DialContext(ctx, network, address) finished := time.Since(d.begin).Seconds() d.db.InsertIntoDial(&NetworkEvent{ Operation: "connect", Network: network, RemoteAddr: address, Started: started, Finished: finished, Failure: NewFailure(err), Oddity: d.computeOddity(err), Count: 0, }) if err != nil { return nil, err } return &connDB{ Conn: conn, begin: d.begin, db: d.db, network: network, remoteAddr: address, }, nil } func (c *dialerDB) computeOddity(err error) Oddity { if err == nil { return "" } switch err.Error() { case netxlite.FailureGenericTimeoutError: return OddityTCPConnectTimeout case netxlite.FailureConnectionRefused: return OddityTCPConnectRefused case netxlite.FailureHostUnreachable: return OddityTCPConnectHostUnreachable default: return OddityTCPConnectOher } } type connDB struct { net.Conn begin time.Time db WritableDB network string remoteAddr string } func (c *connDB) Read(b []byte) (int, error) { started := time.Since(c.begin).Seconds() count, err := c.Conn.Read(b) finished := time.Since(c.begin).Seconds() c.db.InsertIntoReadWrite(&NetworkEvent{ Operation: "read", Network: c.network, RemoteAddr: c.remoteAddr, Started: started, Finished: finished, Failure: NewFailure(err), Count: count, }) return count, err } func (c *connDB) Write(b []byte) (int, error) { started := time.Since(c.begin).Seconds() count, err := c.Conn.Write(b) finished := time.Since(c.begin).Seconds() c.db.InsertIntoReadWrite(&NetworkEvent{ Operation: "write", Network: c.network, RemoteAddr: c.remoteAddr, Started: started, Finished: finished, Failure: NewFailure(err), Count: count, }) return count, err } func (c *connDB) Close() error { started := time.Since(c.begin).Seconds() err := c.Conn.Close() finished := time.Since(c.begin).Seconds() c.db.InsertIntoClose(&NetworkEvent{ Operation: "close", Network: c.network, RemoteAddr: c.remoteAddr, Started: started, Finished: finished, Failure: NewFailure(err), Count: 0, }) return err }