feat(netxlite): integrate websteps code to use ooni/oohttp (#466)

Part of https://github.com/ooni/probe/issues/1591
This commit is contained in:
Simone Basso 2021-09-06 17:21:34 +02:00 committed by GitHub
parent 6df27d919d
commit 3114d6ca0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 19 deletions

View File

@ -5,6 +5,8 @@ import (
"net" "net"
"net/http" "net/http"
"time" "time"
oohttp "github.com/ooni/oohttp"
) )
// HTTPTransport is an http.Transport-like structure. // HTTPTransport is an http.Transport-like structure.
@ -87,30 +89,58 @@ func (txp *httpTransportConnectionsCloser) CloseIdleConnections() {
// We need a TLS handshaker here, as opposed to a TLSDialer, because we // We need a TLS handshaker here, as opposed to a TLSDialer, because we
// wrap the dialer we'll use to enforce timeouts for HTTP idle // wrap the dialer we'll use to enforce timeouts for HTTP idle
// connections (see https://github.com/ooni/probe/issues/1609 for more info). // connections (see https://github.com/ooni/probe/issues/1609 for more info).
func NewHTTPTransport(dialer Dialer, tlsHandshaker TLSHandshaker) HTTPTransport { //
// TODO(bassosimone): here we should copy code living inside the // The returned transport will use the given Logger for logging.
// websteps prototype to use the oohttp library. //
txp := http.DefaultTransport.(*http.Transport).Clone() // The returned transport will gracefully handle TLS connections
// created using gitlab.com/yawning/utls.git.
//
// The returned transport will not have a configured proxy, not
// even the proxy configurable from the environment.
//
// The returned transport will disable transparent decompression
// of compressed response bodies (and will not automatically
// ask for such compression, though you can always do that manually).
func NewHTTPTransport(logger Logger, dialer Dialer, tlsHandshaker TLSHandshaker) HTTPTransport {
// Using oohttp to support any TLS library.
txp := oohttp.DefaultTransport.(*oohttp.Transport).Clone()
// This wrapping ensures that we always have a timeout when we // This wrapping ensures that we always have a timeout when we
// are using HTTP; see https://github.com/ooni/probe/issues/1609. // are using HTTP; see https://github.com/ooni/probe/issues/1609.
dialer = &httpDialerWithReadTimeout{dialer} dialer = &httpDialerWithReadTimeout{dialer}
txp.DialContext = dialer.DialContext txp.DialContext = dialer.DialContext
tlsDialer := NewTLSDialer(dialer, tlsHandshaker) tlsDialer := NewTLSDialer(dialer, tlsHandshaker)
txp.DialTLSContext = tlsDialer.DialTLSContext txp.DialTLSContext = tlsDialer.DialTLSContext
// We are using a different strategy to implement proxy: we
// use a specific dialer that knows about proxying.
txp.Proxy = nil
// Better for Cloudflare DNS and also better because we have less // Better for Cloudflare DNS and also better because we have less
// noisy events and we can better understand what happened. // noisy events and we can better understand what happened.
//
// UNDOCUMENTED: I am wondering whether we can relax this constraint.
txp.MaxConnsPerHost = 1 txp.MaxConnsPerHost = 1
// The following (1) reduces the number of headers that Go will // The following (1) reduces the number of headers that Go will
// automatically send for us and (2) ensures that we always receive // automatically send for us and (2) ensures that we always receive
// back the true headers, such as Content-Length. This change is // back the true headers, such as Content-Length. This change is
// functional to OONI's goal of observing the network. // functional to OONI's goal of observing the network.
txp.DisableCompression = true txp.DisableCompression = true
// Required to enable using HTTP/2 (which will be anyway forced
// upon us when we are using TLS parroting).
txp.ForceAttemptHTTP2 = true txp.ForceAttemptHTTP2 = true
// Ensure we correctly forward CloseIdleConnections.
return &httpTransportConnectionsCloser{ // Ensure we correctly forward CloseIdleConnections and compose
HTTPTransport: txp, // with a logging transport thus enabling logging.
Dialer: dialer, return &httpTransportLogger{
TLSDialer: tlsDialer, HTTPTransport: &httpTransportConnectionsCloser{
HTTPTransport: &oohttp.StdlibTransport{Transport: txp},
Dialer: dialer,
TLSDialer: tlsDialer,
},
Logger: logger,
} }
} }

View File

@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/apex/log" "github.com/apex/log"
oohttp "github.com/ooni/oohttp"
"github.com/ooni/probe-cli/v3/internal/atomicx" "github.com/ooni/probe-cli/v3/internal/atomicx"
"github.com/ooni/probe-cli/v3/internal/netxlite/iox" "github.com/ooni/probe-cli/v3/internal/netxlite/iox"
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks" "github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
@ -110,7 +111,7 @@ func TestHTTPTransportLoggerCloseIdleConnections(t *testing.T) {
func TestHTTPTransportWorks(t *testing.T) { func TestHTTPTransportWorks(t *testing.T) {
d := NewDialerWithResolver(log.Log, NewResolverSystem(log.Log)) d := NewDialerWithResolver(log.Log, NewResolverSystem(log.Log))
txp := NewHTTPTransport(d, NewTLSHandshakerStdlib(log.Log)) txp := NewHTTPTransport(log.Log, d, NewTLSHandshakerStdlib(log.Log))
client := &http.Client{Transport: txp} client := &http.Client{Transport: txp}
defer client.CloseIdleConnections() defer client.CloseIdleConnections()
resp, err := client.Get("https://www.google.com/robots.txt") resp, err := client.Get("https://www.google.com/robots.txt")
@ -135,7 +136,7 @@ func TestHTTPTransportWithFailingDialer(t *testing.T) {
}, },
Resolver: NewResolverSystem(log.Log), Resolver: NewResolverSystem(log.Log),
} }
txp := NewHTTPTransport(d, NewTLSHandshakerStdlib(log.Log)) txp := NewHTTPTransport(log.Log, d, NewTLSHandshakerStdlib(log.Log))
client := &http.Client{Transport: txp} client := &http.Client{Transport: txp}
resp, err := client.Get("https://www.google.com/robots.txt") resp, err := client.Get("https://www.google.com/robots.txt")
if !errors.Is(err, expected) { if !errors.Is(err, expected) {
@ -153,8 +154,15 @@ func TestHTTPTransportWithFailingDialer(t *testing.T) {
func TestNewHTTPTransport(t *testing.T) { func TestNewHTTPTransport(t *testing.T) {
d := &mocks.Dialer{} d := &mocks.Dialer{}
th := &mocks.TLSHandshaker{} th := &mocks.TLSHandshaker{}
txp := NewHTTPTransport(d, th) txp := NewHTTPTransport(log.Log, d, th)
txpcc, okay := txp.(*httpTransportConnectionsCloser) logtxp, okay := txp.(*httpTransportLogger)
if !okay {
t.Fatal("invalid type")
}
if logtxp.Logger != log.Log {
t.Fatal("invalid logger")
}
txpcc, okay := logtxp.HTTPTransport.(*httpTransportConnectionsCloser)
if !okay { if !okay {
t.Fatal("invalid type") t.Fatal("invalid type")
} }
@ -168,23 +176,23 @@ func TestNewHTTPTransport(t *testing.T) {
if txpcc.TLSDialer.(*tlsDialer).TLSHandshaker != th { if txpcc.TLSDialer.(*tlsDialer).TLSHandshaker != th {
t.Fatal("invalid tls handshaker") t.Fatal("invalid tls handshaker")
} }
htxp, okay := txpcc.HTTPTransport.(*http.Transport) stdwtxp, okay := txpcc.HTTPTransport.(*oohttp.StdlibTransport)
if !okay { if !okay {
t.Fatal("invalid type") t.Fatal("invalid type")
} }
if !htxp.ForceAttemptHTTP2 { if !stdwtxp.Transport.ForceAttemptHTTP2 {
t.Fatal("invalid ForceAttemptHTTP2") t.Fatal("invalid ForceAttemptHTTP2")
} }
if !htxp.DisableCompression { if !stdwtxp.Transport.DisableCompression {
t.Fatal("invalid DisableCompression") t.Fatal("invalid DisableCompression")
} }
if htxp.MaxConnsPerHost != 1 { if stdwtxp.Transport.MaxConnsPerHost != 1 {
t.Fatal("invalid MaxConnPerHost") t.Fatal("invalid MaxConnPerHost")
} }
if htxp.DialTLSContext == nil { if stdwtxp.Transport.DialTLSContext == nil {
t.Fatal("invalid DialTLSContext") t.Fatal("invalid DialTLSContext")
} }
if htxp.DialContext == nil { if stdwtxp.Transport.DialContext == nil {
t.Fatal("invalid DialContext") t.Fatal("invalid DialContext")
} }
} }