Skip to content

Commit

Permalink
netx: merge public API with internal (#382)
Browse files Browse the repository at this point in the history
Closes #338, because I have
added code and unit test to check for such case.

Closes #310.

Part of #302.
  • Loading branch information
bassosimone authored Mar 4, 2020
1 parent 54fd287 commit 658a781
Show file tree
Hide file tree
Showing 15 changed files with 1,064 additions and 1,355 deletions.
15 changes: 13 additions & 2 deletions internal/netxlogger/netxlogger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ package netxlogger

import (
"io/ioutil"
"net/http"
"testing"
"time"

"github.com/apex/log"
"github.com/apex/log/handlers/discard"
"github.com/ooni/probe-engine/netx"
"github.com/ooni/probe-engine/netx/modelx"
)

func TestIntegration(t *testing.T) {
log.SetHandler(discard.Default)
client := netx.NewHTTPClient(NewHandler(log.Log))
client := netx.NewHTTPClient()
client.ConfigureDNS("udp", "dns.google.com:53")
resp, err := client.HTTPClient.Get("http://www.facebook.com")
req, err := http.NewRequest("GET", "http://www.facebook.com", nil)
if err != nil {
t.Fatal(err)
}
req = req.WithContext(modelx.WithMeasurementRoot(req.Context(), &modelx.MeasurementRoot{
Beginning: time.Now(),
Handler: NewHandler(log.Log),
}))
resp, err := client.HTTPClient.Do(req)
if err != nil {
t.Fatal(err)
}
Expand Down
21 changes: 7 additions & 14 deletions internal/oonitemplates/oonitemplates.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ type dnsFallback struct {
}

func configureDNS(seed int64, network, address string) (modelx.DNSResolver, error) {
resolver, err := netx.NewResolver(handlers.NoHandler, network, address)
resolver, err := netx.NewResolver(network, address)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -209,10 +209,7 @@ func configureDNS(seed int64, network, address string) (modelx.DNSResolver, erro
continue
}
var fallback modelx.DNSResolver
fallback, err = netx.NewResolver(
handlers.NoHandler, fallbacks[i].network,
fallbacks[i].address,
)
fallback, err = netx.NewResolver(fallbacks[i].network, fallbacks[i].address)
rtx.PanicOnError(err, "porcelain: invalid fallbacks table")
resolver = netx.ChainResolvers(resolver, fallback)
configured++
Expand Down Expand Up @@ -255,11 +252,7 @@ func DNSLookup(
},
}
ctx = modelx.WithMeasurementRoot(ctx, root)
resolver, err := netx.NewResolver(
handlers.NoHandler,
config.ServerNetwork,
config.ServerAddress,
)
resolver, err := netx.NewResolver(config.ServerNetwork, config.ServerAddress)
if err != nil {
results.Error = err
return results
Expand Down Expand Up @@ -331,7 +324,7 @@ func HTTPDo(
MaxBodySnapSize: config.MaxEventsBodySnapSize,
}
ctx := modelx.WithMeasurementRoot(origCtx, root)
client := netx.NewHTTPClientWithProxyFunc(handlers.NoHandler, config.ProxyFunc)
client := netx.NewHTTPClientWithProxyFunc(config.ProxyFunc)
resolver, err := configureDNS(
time.Now().UnixNano(),
config.DNSServerNetwork,
Expand Down Expand Up @@ -422,7 +415,7 @@ func TLSConnect(
},
}
ctx = modelx.WithMeasurementRoot(ctx, root)
dialer := netx.NewDialer(handlers.NoHandler)
dialer := netx.NewDialer()
// TODO(bassosimone): tell dialer to use specific CA bundle?
resolver, err := configureDNS(
time.Now().UnixNano(),
Expand Down Expand Up @@ -485,7 +478,7 @@ func TCPConnect(
},
}
ctx = modelx.WithMeasurementRoot(ctx, root)
dialer := netx.NewDialer(handlers.NoHandler)
dialer := netx.NewDialer()
// TODO(bassosimone): tell dialer to use specific CA bundle?
resolver, err := configureDNS(
time.Now().UnixNano(),
Expand Down Expand Up @@ -553,7 +546,7 @@ func OBFS4Connect(
},
}
ctx = modelx.WithMeasurementRoot(ctx, root)
dialer := netx.NewDialer(handlers.NoHandler)
dialer := netx.NewDialer()
// TODO(bassosimone): tell dialer to use specific CA bundle?
resolver, err := configureDNS(
time.Now().UnixNano(),
Expand Down
160 changes: 160 additions & 0 deletions netx/dialer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Package netx contains OONI's net extensions.
package netx

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
"net"
"time"

"github.com/ooni/probe-engine/netx/handlers"
"github.com/ooni/probe-engine/netx/internal/dialer"
"github.com/ooni/probe-engine/netx/internal/resolver"
"github.com/ooni/probe-engine/netx/modelx"
)

// 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: 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,
})
}

// 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 dialer.New(
d.Resolver, new(net.Dialer),
).DialContext(ctx, network, address)
}

// DialTLS is like Dial, but creates TLS connections.
func (d *Dialer) DialTLS(network, address string) (net.Conn, error) {
ctx := context.Background()
return d.DialTLSContext(ctx, network, address)
}

// 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 dialer.NewTLS(
dialer.New(d.Resolver, new(net.Dialer)),
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
}
115 changes: 115 additions & 0 deletions netx/dialer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package netx_test

import (
"crypto/x509"
"errors"
"testing"

"github.com/ooni/probe-engine/netx"
)

func TestIntegrationDialerDial(t *testing.T) {
dialer := netx.NewDialer()
conn, err := dialer.Dial("tcp", "www.google.com:80")
if err != nil {
t.Fatal(err)
}
conn.Close()
}

func TestIntegrationDialerDialWithCustomResolver(t *testing.T) {
dialer := netx.NewDialer()
resolver, err := netx.NewResolver("tcp", "1.1.1.1:53")
if err != nil {
t.Fatal(err)
}
dialer.SetResolver(resolver)
conn, err := dialer.Dial("tcp", "www.google.com:80")
if err != nil {
t.Fatal(err)
}
conn.Close()
}

func TestIntegrationDialerDialWithConfigureDNS(t *testing.T) {
dialer := netx.NewDialer()
err := dialer.ConfigureDNS("tcp", "1.1.1.1:53")
if err != nil {
t.Fatal(err)
}
conn, err := dialer.Dial("tcp", "www.google.com:80")
if err != nil {
t.Fatal(err)
}
conn.Close()
}

func TestIntegrationDialerDialTLS(t *testing.T) {
dialer := netx.NewDialer()
conn, err := dialer.DialTLS("tcp", "www.google.com:443")
if err != nil {
t.Fatal(err)
}
conn.Close()
}

func TestIntegrationDialerDialTLSForceSkipVerify(t *testing.T) {
dialer := netx.NewDialer()
dialer.ForceSkipVerify()
conn, err := dialer.DialTLS("tcp", "self-signed.badssl.com:443")
if err != nil {
t.Fatal(err)
}
conn.Close()
}

func TestIntegrationDialerSetCABundleNonexisting(t *testing.T) {
dialer := netx.NewDialer()
err := dialer.SetCABundle("testdata/cacert-nonexistent.pem")
if err == nil {
t.Fatal("expected an error here")
}
}

func TestIntegrationDialerSetCABundleInvalid(t *testing.T) {
dialer := netx.NewDialer()
err := dialer.SetCABundle("testdata/cacert-invalid.pem")
if err == nil {
t.Fatal("expected an error here")
}
}

func TestIntegrationDialerSetCABundleWAI(t *testing.T) {
dialer := netx.NewDialer()
err := dialer.SetCABundle("testdata/cacert.pem")
if err != nil {
t.Fatal(err)
}
conn, err := dialer.DialTLS("tcp", "www.google.com:443")
if err == nil {
t.Fatal("expected an error here")
}
var target x509.UnknownAuthorityError
if errors.As(err, &target) == false {
t.Fatal("not the error we expected")
}
if conn != nil {
t.Fatal("expected a nil conn here")
}
}

func TestIntegrationDialerForceSpecificSNI(t *testing.T) {
dialer := netx.NewDialer()
err := dialer.ForceSpecificSNI("www.facebook.com")
conn, err := dialer.DialTLS("tcp", "www.google.com:443")
if err == nil {
t.Fatal("expected an error here")
}
var target x509.HostnameError
if errors.As(err, &target) == false {
t.Fatal("not the error we expected")
}
if conn != nil {
t.Fatal("expected a nil connection here")
}
}
Loading

0 comments on commit 658a781

Please sign in to comment.