ooni-probe-cli/internal/measurex/dialer.go
Simone Basso aa27bbe33f
fix(measurex): use same keys of the OONI data format (#572)
This change should simplify the pipeline's job.

Reference issue: https://github.com/ooni/probe/issues/1817.

I previously dismissed this possibility, but now it seems clear it
is simpler to have a very tabular data format internally and to
convert such a format to OONI's data format when serializing.

The OONI data format is what the pipeline expects, but processing
is easier with a more linear/tabular format.
2021-11-05 10:46:45 +01:00

165 lines
4.0 KiB
Go

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/netxlite"
)
// Conn is a network connection.
type Conn = net.Conn
// Dialer dials network connections.
type Dialer = netxlite.Dialer
// 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 netxlite.Dialer) Dialer {
return WrapDialer(mx.Begin, db, dialer)
}
// WrapDialer wraps a dialer.
func WrapDialer(begin time.Time, db WritableDB, dialer netxlite.Dialer) Dialer {
return &dialerDB{Dialer: dialer, db: db, begin: begin}
}
// NewDialerWithSystemResolver creates a
func (mx *Measurer) NewDialerWithSystemResolver(db WritableDB, logger Logger) 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 Logger) Dialer {
return mx.WrapDialer(db, netxlite.NewDialerWithoutResolver(logger))
}
type dialerDB struct {
netxlite.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
}