58adb68b2c
* refactor: move tracex outside of engine/netx Consistently with https://github.com/ooni/probe/issues/2121 and https://github.com/ooni/probe/issues/2115, we can now move tracex outside of engine/netx. The main reason why this makes sense now is that the package is now changed significantly from the one that we imported from ooni/probe-engine. We have improved its implementation, which had not been touched significantly for quite some time, and converted it to unit testing. I will document tomorrow some extra work I'd like to do with this package but likely could not do $soon. * go fmt * regen tutorials
148 lines
4.3 KiB
Go
148 lines
4.3 KiB
Go
package urlgetter
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
"github.com/ooni/probe-cli/v3/internal/tracex"
|
|
"github.com/ooni/probe-cli/v3/internal/tunnel"
|
|
)
|
|
|
|
// The Getter gets the specified target in the context of the
|
|
// given session and with the specified config.
|
|
//
|
|
// Other OONI experiment should use the Getter to factor code when
|
|
// the Getter implements the operations they wanna perform.
|
|
type Getter struct {
|
|
// Begin is the time when the experiment begun. If you do not
|
|
// set this field, every target is measured independently.
|
|
Begin time.Time
|
|
|
|
// Config contains settings for this run. If not set, then
|
|
// we will use the default config.
|
|
Config Config
|
|
|
|
// Session is the session for this run. This field must
|
|
// be set otherwise the code will panic.
|
|
Session model.ExperimentSession
|
|
|
|
// Target is the thing to measure in this run. This field must
|
|
// be set otherwise the code won't know what to do.
|
|
Target string
|
|
|
|
// testIOUtilTempDir allows us to mock ioutil.TempDir
|
|
testIOUtilTempDir func(dir, pattern string) (string, error)
|
|
}
|
|
|
|
// Get performs the action described by g using the given context
|
|
// and returning the test keys and eventually an error
|
|
func (g Getter) Get(ctx context.Context) (TestKeys, error) {
|
|
if g.Config.Timeout > 0 {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, g.Config.Timeout)
|
|
defer cancel()
|
|
}
|
|
if g.Begin.IsZero() {
|
|
g.Begin = time.Now()
|
|
}
|
|
saver := new(tracex.Saver)
|
|
tk, err := g.get(ctx, saver)
|
|
// Make sure we have an operation in cases where we fail before
|
|
// hitting our httptransport that does error wrapping.
|
|
if err != nil {
|
|
err = netxlite.NewTopLevelGenericErrWrapper(err)
|
|
}
|
|
tk.FailedOperation = tracex.NewFailedOperation(err)
|
|
tk.Failure = tracex.NewFailure(err)
|
|
events := saver.Read()
|
|
tk.Queries = append(tk.Queries, tracex.NewDNSQueriesList(g.Begin, events)...)
|
|
tk.NetworkEvents = append(
|
|
tk.NetworkEvents, tracex.NewNetworkEventsList(g.Begin, events)...,
|
|
)
|
|
tk.Requests = append(
|
|
tk.Requests, tracex.NewRequestList(g.Begin, events)...,
|
|
)
|
|
if len(tk.Requests) > 0 {
|
|
// OONI's convention is that the last request appears first
|
|
tk.HTTPResponseStatus = tk.Requests[0].Response.Code
|
|
tk.HTTPResponseBody = tk.Requests[0].Response.Body.Value
|
|
tk.HTTPResponseLocations = tk.Requests[0].Response.Locations
|
|
}
|
|
tk.TCPConnect = append(
|
|
tk.TCPConnect, tracex.NewTCPConnectList(g.Begin, events)...,
|
|
)
|
|
tk.TLSHandshakes = append(
|
|
tk.TLSHandshakes, tracex.NewTLSHandshakesList(g.Begin, events)...,
|
|
)
|
|
return tk, err
|
|
}
|
|
|
|
// ioutilTempDir calls either g.testIOUtilTempDir or ioutil.TempDir
|
|
func (g Getter) ioutilTempDir(dir, pattern string) (string, error) {
|
|
if g.testIOUtilTempDir != nil {
|
|
return g.testIOUtilTempDir(dir, pattern)
|
|
}
|
|
return ioutil.TempDir(dir, pattern)
|
|
}
|
|
|
|
func (g Getter) get(ctx context.Context, saver *tracex.Saver) (TestKeys, error) {
|
|
tk := TestKeys{
|
|
Agent: "redirect",
|
|
Tunnel: g.Config.Tunnel,
|
|
}
|
|
if g.Config.DNSCache != "" {
|
|
tk.DNSCache = []string{g.Config.DNSCache}
|
|
}
|
|
if g.Config.NoFollowRedirects {
|
|
tk.Agent = "agent"
|
|
}
|
|
// start tunnel
|
|
var proxyURL *url.URL
|
|
if g.Config.Tunnel != "" {
|
|
// Every new instance of the tunnel goes into a separate
|
|
// directory within the temporary directory. Calling
|
|
// Session.Close will delete such a directory.
|
|
tundir, err := g.ioutilTempDir(g.Session.TempDir(), "urlgetter-tunnel-")
|
|
if err != nil {
|
|
return tk, err
|
|
}
|
|
tun, _, err := tunnel.Start(ctx, &tunnel.Config{
|
|
Name: g.Config.Tunnel,
|
|
Session: g.Session,
|
|
TorArgs: g.Session.TorArgs(),
|
|
TorBinary: g.Session.TorBinary(),
|
|
TunnelDir: tundir,
|
|
})
|
|
if err != nil {
|
|
return tk, err
|
|
}
|
|
tk.BootstrapTime = tun.BootstrapTime().Seconds()
|
|
proxyURL = tun.SOCKS5ProxyURL()
|
|
tk.SOCKSProxy = proxyURL.String()
|
|
defer tun.Stop()
|
|
}
|
|
// create configuration
|
|
configurer := Configurer{
|
|
Config: g.Config,
|
|
Logger: g.Session.Logger(),
|
|
ProxyURL: proxyURL,
|
|
Saver: saver,
|
|
}
|
|
configuration, err := configurer.NewConfiguration()
|
|
if err != nil {
|
|
return tk, err
|
|
}
|
|
defer configuration.CloseIdleConnections()
|
|
// run the measurement
|
|
runner := Runner{
|
|
Config: g.Config,
|
|
HTTPConfig: configuration.HTTPConfig,
|
|
Target: g.Target,
|
|
}
|
|
return tk, runner.Run(ctx)
|
|
}
|