-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
netx: merge public API with internal (#382)
Closes #338, because I have added code and unit test to check for such case. Closes #310. Part of #302.
- Loading branch information
1 parent
54fd287
commit 658a781
Showing
15 changed files
with
1,064 additions
and
1,355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
Oops, something went wrong.