ooni-probe-cli/internal/engine/legacy/netx/dialer.go
Simone Basso 06ee0e55a9
refactor(netx/dialer): hide implementation complexity (#372)
* refactor(netx/dialer): hide implementation complexity

This follows the blueprint of `module.Config` and `nodule.New`
described at https://github.com/ooni/probe/issues/1591.

* fix: ndt7 bug where we were not using the right resolver

* fix(legacy/netx): clarify irrelevant implementation change

* fix: improve comments

* fix(hhfm): do not use dialer.New b/c it breaks it

Unclear to me why this is happening. Still, improve upon the
previous situation by adding a timeout.

It does not seem a priority to look into this issue now.
2021-06-09 09:42:31 +02:00

204 lines
6.0 KiB
Go

// Package netx contains OONI's net extensions.
package netx
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
"net"
"time"
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/handlers"
"github.com/ooni/probe-cli/v3/internal/engine/legacy/netx/modelx"
"github.com/ooni/probe-cli/v3/internal/engine/netx/dialer"
"github.com/ooni/probe-cli/v3/internal/engine/netx/tlsdialer"
)
// Dialer performs measurements while dialing.
type Dialer struct {
Beginning time.Time
Handler modelx.Handler
Resolver modelx.DNSResolver
TLSConfig *tls.Config
}
func newDialer(beginning time.Time, handler modelx.Handler) *Dialer {
return &Dialer{
Beginning: beginning,
Handler: handler,
Resolver: newResolverSystem(),
TLSConfig: new(tls.Config),
}
}
// NewDialer creates a new Dialer instance.
func NewDialer() *Dialer {
return newDialer(time.Now(), handlers.NoHandler)
}
// Dial creates a TCP or UDP connection. See net.Dial docs.
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
return d.DialContext(context.Background(), network, address)
}
func maybeWithMeasurementRoot(
ctx context.Context, beginning time.Time, handler modelx.Handler,
) context.Context {
if modelx.ContextMeasurementRoot(ctx) != nil {
return ctx
}
return modelx.WithMeasurementRoot(ctx, &modelx.MeasurementRoot{
Beginning: beginning,
Handler: handler,
})
}
// newDNSDialer creates a new DNS dialer using the following chain:
//
// - DNSDialer (topmost)
// - EmitterDialer
// - ErrorWrapperDialer
// - ByteCountingDialer
// - dialer.Default
//
// If you have others needs, manually build the chain you need.
func newDNSDialer(resolver dialer.Resolver) dialer.Dialer {
// Implementation note: we're wrapping the result of dialer.New
// on the outside, while previously we were puttting the
// EmitterDialer before the DNSDialer (see the above comment).
//
// Yet, this is fine because the only experiment which is
// using this code is tor, for which it doesn't matter.
//
// Also (and I am always scared to write this kind of
// comments), we should rewrite tor soon.
return &EmitterDialer{dialer.New(&dialer.Config{
ContextByteCounting: true,
}, resolver)}
}
// DialContext is like Dial but the context allows to interrupt a
// pending connection attempt at any time.
func (d *Dialer) DialContext(
ctx context.Context, network, address string,
) (conn net.Conn, err error) {
ctx = maybeWithMeasurementRoot(ctx, d.Beginning, d.Handler)
return newDNSDialer(d.Resolver).DialContext(ctx, network, address)
}
// DialTLS is like Dial, but creates TLS connections.
func (d *Dialer) DialTLS(network, address string) (net.Conn, error) {
return d.DialTLSContext(context.Background(), network, address)
}
// newTLSDialer creates a new TLSDialer using:
//
// - EmitterTLSHandshaker (topmost)
// - ErrorWrapperTLSHandshaker
// - TimeoutTLSHandshaker
// - SystemTLSHandshaker
//
// If you have others needs, manually build the chain you need.
func newTLSDialer(d dialer.Dialer, config *tls.Config) tlsdialer.TLSDialer {
return tlsdialer.TLSDialer{
Config: config,
Dialer: d,
TLSHandshaker: tlsdialer.EmitterTLSHandshaker{
TLSHandshaker: tlsdialer.ErrorWrapperTLSHandshaker{
TLSHandshaker: tlsdialer.TimeoutTLSHandshaker{
TLSHandshaker: tlsdialer.SystemTLSHandshaker{},
},
},
},
}
}
// DialTLSContext is like DialTLS, but with context
func (d *Dialer) DialTLSContext(
ctx context.Context, network, address string,
) (net.Conn, error) {
ctx = maybeWithMeasurementRoot(ctx, d.Beginning, d.Handler)
return newTLSDialer(
newDNSDialer(d.Resolver),
d.TLSConfig,
).DialTLSContext(ctx, network, address)
}
// SetCABundle configures the dialer to use a specific CA bundle. This
// function is not goroutine safe. Make sure you call it before starting
// to use this specific dialer.
func (d *Dialer) SetCABundle(path string) error {
cert, err := ioutil.ReadFile(path)
if err != nil {
return err
}
pool := x509.NewCertPool()
if pool.AppendCertsFromPEM(cert) == false {
return errors.New("AppendCertsFromPEM failed")
}
d.TLSConfig.RootCAs = pool
return nil
}
// ForceSpecificSNI forces using a specific SNI.
func (d *Dialer) ForceSpecificSNI(sni string) error {
d.TLSConfig.ServerName = sni
return nil
}
// ForceSkipVerify forces to skip certificate verification
func (d *Dialer) ForceSkipVerify() error {
d.TLSConfig.InsecureSkipVerify = true
return nil
}
// ConfigureDNS configures the DNS resolver. The network argument
// selects the type of resolver. The address argument indicates the
// resolver address and depends on the network.
//
// This functionality is not goroutine safe. You should only change
// the DNS settings before starting to use the Dialer.
//
// The following is a list of all the possible network values:
//
// - "": behaves exactly like "system"
//
// - "system": this indicates that Go should use the system resolver
// and prevents us from seeing any DNS packet. The value of the
// address parameter is ignored when using "system". If you do
// not ConfigureDNS, this is the default resolver used.
//
// - "udp": indicates that we should send queries using UDP. In this
// case the address is a host, port UDP endpoint.
//
// - "tcp": like "udp" but we use TCP.
//
// - "dot": we use DNS over TLS (DoT). In this case the address is
// the domain name of the DoT server.
//
// - "doh": we use DNS over HTTPS (DoH). In this case the address is
// the URL of the DoH server.
//
// For example:
//
// d.ConfigureDNS("system", "")
// d.ConfigureDNS("udp", "8.8.8.8:53")
// d.ConfigureDNS("tcp", "8.8.8.8:53")
// d.ConfigureDNS("dot", "dns.quad9.net")
// d.ConfigureDNS("doh", "https://cloudflare-dns.com/dns-query")
func (d *Dialer) ConfigureDNS(network, address string) error {
r, err := newResolver(d.Beginning, d.Handler, network, address)
if err == nil {
d.Resolver = r
}
return err
}
// SetResolver is a more flexible way of configuring a resolver
// that should perhaps be used instead of ConfigureDNS.
func (d *Dialer) SetResolver(r modelx.DNSResolver) {
d.Resolver = r
}