fix(sessionresolver): honour the proxy (#250)
In reality, we are not going to use the sessionresolver when we're using a proxy (I just tested). But, it nonetheless feels a lot more robust to write a correct sessionresolver that handles the proxy in the most correct way. That is, the sessionresolver will now skip all the entries that cannot use a socks5 proxy (including among them also the system resolver). What's more, it will construct a child resolver that propagates the proxy. We have confidence that this holds true because we have added a test ensuring that we are really using the configured proxy. See https://github.com/ooni/probe/issues/1381
This commit is contained in:
parent
4da372a84d
commit
f0110fe85a
@ -88,6 +88,7 @@ func (r *Resolver) newresolver(URL string) (childResolver, error) {
|
|||||||
ByteCounter: r.byteCounter(),
|
ByteCounter: r.byteCounter(),
|
||||||
HTTP3Enabled: h3,
|
HTTP3Enabled: h3,
|
||||||
Logger: r.logger(),
|
Logger: r.logger(),
|
||||||
|
ProxyURL: r.ProxyURL,
|
||||||
}, URL)
|
}, URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ type Resolver struct {
|
|||||||
ByteCounter *bytecounter.Counter // optional
|
ByteCounter *bytecounter.Counter // optional
|
||||||
KVStore KVStore // optional
|
KVStore KVStore // optional
|
||||||
Logger Logger // optional
|
Logger Logger // optional
|
||||||
|
ProxyURL *url.URL // optional
|
||||||
codec codec
|
codec codec
|
||||||
dnsClientMaker dnsclientmaker
|
dnsClientMaker dnsclientmaker
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@ -71,6 +73,9 @@ func (r *Resolver) LookupHost(ctx context.Context, hostname string) ([]string, e
|
|||||||
defer r.writestate(state)
|
defer r.writestate(state)
|
||||||
me := multierror.New(ErrLookupHost)
|
me := multierror.New(ErrLookupHost)
|
||||||
for _, e := range state {
|
for _, e := range state {
|
||||||
|
if r.shouldSkipWithProxy(e) {
|
||||||
|
continue // we cannot proxy this URL so ignore it
|
||||||
|
}
|
||||||
addrs, err := r.lookupHost(ctx, e, hostname)
|
addrs, err := r.lookupHost(ctx, e, hostname)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
@ -80,6 +85,19 @@ func (r *Resolver) LookupHost(ctx context.Context, hostname string) ([]string, e
|
|||||||
return nil, me
|
return nil, me
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) shouldSkipWithProxy(e *resolverinfo) bool {
|
||||||
|
URL, err := url.Parse(e.URL)
|
||||||
|
if err != nil {
|
||||||
|
return true // please skip
|
||||||
|
}
|
||||||
|
switch URL.Scheme {
|
||||||
|
case "https", "dot", "tcp":
|
||||||
|
return false // we can handle this
|
||||||
|
default:
|
||||||
|
return true // please skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) lookupHost(ctx context.Context, ri *resolverinfo, hostname string) ([]string, error) {
|
func (r *Resolver) lookupHost(ctx context.Context, ri *resolverinfo, hostname string) ([]string, error) {
|
||||||
const ewma = 0.9 // the last sample is very important
|
const ewma = 0.9 // the last sample is very important
|
||||||
re, err := r.getresolver(ri.URL)
|
re, err := r.getresolver(ri.URL)
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -247,3 +249,92 @@ func TestMaybeConfusionManyEntries(t *testing.T) {
|
|||||||
t.Fatal("unexpected state[3].URL")
|
t.Fatal("unexpected state[3].URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolverWorksWithProxy(t *testing.T) {
|
||||||
|
var (
|
||||||
|
works int32
|
||||||
|
startuperr = make(chan error)
|
||||||
|
listench = make(chan net.Listener)
|
||||||
|
done = make(chan interface{})
|
||||||
|
)
|
||||||
|
// proxy implementation
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
lconn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
startuperr <- err
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listench <- lconn
|
||||||
|
for {
|
||||||
|
conn, err := lconn.Accept()
|
||||||
|
if err != nil {
|
||||||
|
// We assume this is when we were told to
|
||||||
|
// shutdown by the main goroutine.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.AddInt32(&works, 1)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// make sure we could start the proxy
|
||||||
|
if err := <-startuperr; err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
listener := <-listench
|
||||||
|
// use the proxy
|
||||||
|
reso := &Resolver{ProxyURL: &url.URL{
|
||||||
|
Scheme: "socks5",
|
||||||
|
Host: listener.Addr().String(),
|
||||||
|
}}
|
||||||
|
ctx := context.Background()
|
||||||
|
addrs, err := reso.LookupHost(ctx, "ooni.org")
|
||||||
|
// cleanly shutdown the listener
|
||||||
|
listener.Close()
|
||||||
|
<-done
|
||||||
|
// check results
|
||||||
|
if !errors.Is(err, ErrLookupHost) {
|
||||||
|
t.Fatal("not the error we expected")
|
||||||
|
}
|
||||||
|
if addrs != nil {
|
||||||
|
t.Fatal("expected nil addrs")
|
||||||
|
}
|
||||||
|
if works < 1 {
|
||||||
|
t.Fatal("expected to see a positive number of entries here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldSkipWithProxyWorks(t *testing.T) {
|
||||||
|
expect := []struct {
|
||||||
|
url string
|
||||||
|
result bool
|
||||||
|
}{{
|
||||||
|
url: "\t",
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
url: "https://dns.google/dns-query",
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
url: "dot://dns.google/",
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
url: "http3://dns.google/dns-query",
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
url: "tcp://dns.google/",
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
url: "udp://dns.google/",
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
url: "system:///",
|
||||||
|
result: true,
|
||||||
|
}}
|
||||||
|
reso := &Resolver{}
|
||||||
|
for _, e := range expect {
|
||||||
|
out := reso.shouldSkipWithProxy(&resolverinfo{URL: e.url})
|
||||||
|
if out != e.result {
|
||||||
|
t.Fatal("unexpected result for", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -108,14 +108,15 @@ func NewSession(config SessionConfig) (*Session, error) {
|
|||||||
ByteCounter: sess.byteCounter,
|
ByteCounter: sess.byteCounter,
|
||||||
BogonIsError: true,
|
BogonIsError: true,
|
||||||
Logger: sess.logger,
|
Logger: sess.logger,
|
||||||
|
ProxyURL: config.ProxyURL,
|
||||||
}
|
}
|
||||||
sess.resolver = &sessionresolver.Resolver{
|
sess.resolver = &sessionresolver.Resolver{
|
||||||
ByteCounter: sess.byteCounter,
|
ByteCounter: sess.byteCounter,
|
||||||
KVStore: config.KVStore,
|
KVStore: config.KVStore,
|
||||||
Logger: sess.logger,
|
Logger: sess.logger,
|
||||||
|
ProxyURL: config.ProxyURL,
|
||||||
}
|
}
|
||||||
httpConfig.FullResolver = sess.resolver
|
httpConfig.FullResolver = sess.resolver
|
||||||
httpConfig.ProxyURL = config.ProxyURL // no need to proxy the resolver
|
|
||||||
sess.httpDefaultTransport = netx.NewHTTPTransport(httpConfig)
|
sess.httpDefaultTransport = netx.NewHTTPTransport(httpConfig)
|
||||||
return sess, nil
|
return sess, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user