diff --git a/internal/cmd/qatool/main.go b/internal/cmd/qatool/main.go index 2041608a05..1a71ec1599 100644 --- a/internal/cmd/qatool/main.go +++ b/internal/cmd/qatool/main.go @@ -8,12 +8,12 @@ import ( "regexp" "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivitylte" - "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivityqa" "github.com/ooni/probe-cli/v3/internal/geoipx" "github.com/ooni/probe-cli/v3/internal/minipipeline" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/must" "github.com/ooni/probe-cli/v3/internal/runtimex" + "github.com/ooni/probe-cli/v3/internal/webconnectivityqa" ) var ( diff --git a/internal/experiment/webconnectivity/qa_test.go b/internal/experiment/webconnectivity/qa_test.go index 1e71ec3596..e18521e987 100644 --- a/internal/experiment/webconnectivity/qa_test.go +++ b/internal/experiment/webconnectivity/qa_test.go @@ -3,7 +3,7 @@ package webconnectivity import ( "testing" - "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivityqa" + "github.com/ooni/probe-cli/v3/internal/webconnectivityqa" ) func TestQA(t *testing.T) { diff --git a/internal/experiment/webconnectivitylte/dnsresolvers.go b/internal/experiment/webconnectivitylte/dnsresolvers.go index 8fe6572251..812d471421 100644 --- a/internal/experiment/webconnectivitylte/dnsresolvers.go +++ b/internal/experiment/webconnectivitylte/dnsresolvers.go @@ -9,7 +9,6 @@ package webconnectivitylte import ( "context" "fmt" - "math/rand" "net" "net/http" "net/url" @@ -21,6 +20,7 @@ import ( "github.com/ooni/probe-cli/v3/internal/measurexlite" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" + "github.com/ooni/probe-cli/v3/internal/webconnectivityalgo" ) // Resolves the URL's domain using several resolvers. @@ -95,8 +95,8 @@ func (t *DNSResolvers) run(parentCtx context.Context) []DNSEntry { systemOut := make(chan []string) udpOut := make(chan []string) httpsOut := make(chan []string) - whoamiSystemV4Out := make(chan []DNSWhoamiInfoEntry) - whoamiUDPv4Out := make(chan []DNSWhoamiInfoEntry) + whoamiSystemV4Out := make(chan []webconnectivityalgo.DNSWhoamiInfoEntry) + whoamiUDPv4Out := make(chan []webconnectivityalgo.DNSWhoamiInfoEntry) // TODO(bassosimone): add opportunistic support for detecting // whether DNS queries are answered regardless of dest addr by @@ -188,7 +188,7 @@ func (t *DNSResolvers) Run(parentCtx context.Context) { // whoamiSystemV4 performs a DNS whoami lookup for the system resolver. This function must // always emit an ouput on the [out] channel to synchronize with the caller func. -func (t *DNSResolvers) whoamiSystemV4(parentCtx context.Context, out chan<- []DNSWhoamiInfoEntry) { +func (t *DNSResolvers) whoamiSystemV4(parentCtx context.Context, out chan<- []webconnectivityalgo.DNSWhoamiInfoEntry) { value, _ := DNSWhoamiSingleton.SystemV4(parentCtx) t.Logger.Infof("DNS whoami for system resolver: %+v", value) out <- value @@ -196,7 +196,7 @@ func (t *DNSResolvers) whoamiSystemV4(parentCtx context.Context, out chan<- []DN // whoamiUDPv4 performs a DNS whoami lookup for the given UDP resolver. This function must // always emit an ouput on the [out] channel to synchronize with the caller func. -func (t *DNSResolvers) whoamiUDPv4(parentCtx context.Context, udpAddress string, out chan<- []DNSWhoamiInfoEntry) { +func (t *DNSResolvers) whoamiUDPv4(parentCtx context.Context, udpAddress string, out chan<- []webconnectivityalgo.DNSWhoamiInfoEntry) { value, _ := DNSWhoamiSingleton.UDPv4(parentCtx, udpAddress) t.Logger.Infof("DNS whoami for %s/udp resolver: %+v", udpAddress, value) out <- value @@ -302,62 +302,14 @@ func (t *DNSResolvers) udpAddress() string { return "8.8.4.4:53" } -// OpportunisticDNSOverHTTPS allows to perform opportunistic DNS-over-HTTPS -// measurements as part of Web Connectivity. -type OpportunisticDNSOverHTTPS struct { - // interval is the next interval after which to measure. - interval time.Duration - - // mu provides mutual exclusion - mu *sync.Mutex - - // rnd is the random number generator to use. - rnd *rand.Rand - - // t is when we last run an opportunistic measurement. - t time.Time - - // urls contains the urls of known DoH services. - urls []string -} - -// MaybeNextURL returns the next URL to measure, if any. Our aim is to perform -// periodic, opportunistic DoH measurements as part of Web Connectivity. -func (o *OpportunisticDNSOverHTTPS) MaybeNextURL() (string, bool) { - now := time.Now() - o.mu.Lock() - defer o.mu.Unlock() - if o.t.IsZero() || now.Sub(o.t) > o.interval { - o.rnd.Shuffle(len(o.urls), func(i, j int) { - o.urls[i], o.urls[j] = o.urls[j], o.urls[i] - }) - o.t = now - o.interval = time.Duration(20+o.rnd.Uint32()%20) * time.Second - return o.urls[0], true - } - return "", false -} - -// TODO(bassosimone): consider whether factoring out this code -// and storing the state on disk instead of using memory - -// TODO(bassosimone): consider unifying somehow this code and -// the systemresolver code (or maybe just the list of resolvers) - // OpportunisticDNSOverHTTPSSingleton is the singleton used to keep // track of the opportunistic DNS-over-HTTPS measurements state. -var OpportunisticDNSOverHTTPSSingleton = &OpportunisticDNSOverHTTPS{ - interval: 0, - mu: &sync.Mutex{}, - rnd: rand.New(rand.NewSource(time.Now().UnixNano())), - t: time.Time{}, - urls: []string{ - "https://mozilla.cloudflare-dns.com/dns-query", - "https://dns.nextdns.io/dns-query", - "https://dns.google/dns-query", - "https://dns.quad9.net/dns-query", - }, -} +var OpportunisticDNSOverHTTPSSingleton = webconnectivityalgo.NewOpportunisticDNSOverHTTPSURLProvider( + "https://mozilla.cloudflare-dns.com/dns-query", + "https://dns.nextdns.io/dns-query", + "https://dns.google/dns-query", + "https://dns.quad9.net/dns-query", +) // lookupHostDNSOverHTTPS performs a DNS lookup using a DoH resolver. This function must // always emit an ouput on the [out] channel to synchronize with the caller func. diff --git a/internal/experiment/webconnectivitylte/dnswhoami.go b/internal/experiment/webconnectivitylte/dnswhoami.go index 882fb29c73..f348c9c493 100644 --- a/internal/experiment/webconnectivitylte/dnswhoami.go +++ b/internal/experiment/webconnectivitylte/dnswhoami.go @@ -1,81 +1,8 @@ package webconnectivitylte import ( - "context" - "sync" - "time" - - "github.com/ooni/probe-cli/v3/internal/model" - "github.com/ooni/probe-cli/v3/internal/netxlite" + "github.com/ooni/probe-cli/v3/internal/webconnectivityalgo" ) -// TODO(bassosimone): this code needs refining before we can merge it inside -// master. For one, we already have systemv4 info. Additionally, it would -// be neat to avoid additional AAAA queries. Furthermore, we should also see -// to implement support for IPv6 only clients as well. - -// DNSWhoamiService is a service that performs DNS whoami lookups. -type DNSWhoamiService struct { - // mu provides mutual exclusion - mu *sync.Mutex - - // systemv4 contains systemv4 results - systemv4 []DNSWhoamiInfoEntry - - // udpv4 contains udpv4 results - udpv4 map[string][]DNSWhoamiInfoEntry -} - -// SystemV4 returns the results of querying using the system resolver and IPv4. -func (svc *DNSWhoamiService) SystemV4(ctx context.Context) ([]DNSWhoamiInfoEntry, bool) { - svc.mu.Lock() - defer svc.mu.Unlock() - if len(svc.systemv4) <= 0 { - ctx, cancel := context.WithTimeout(ctx, 4*time.Second) - defer cancel() - netx := &netxlite.Netx{} - reso := netx.NewStdlibResolver(model.DiscardLogger) - addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") - if err != nil || len(addrs) < 1 { - return nil, false - } - svc.systemv4 = []DNSWhoamiInfoEntry{{ - Address: addrs[0], - }} - } - return svc.systemv4, len(svc.systemv4) > 0 -} - -// UDPv4 returns the results of querying a given UDP resolver and IPv4. -func (svc *DNSWhoamiService) UDPv4(ctx context.Context, address string) ([]DNSWhoamiInfoEntry, bool) { - svc.mu.Lock() - defer svc.mu.Unlock() - if len(svc.udpv4[address]) <= 0 { - ctx, cancel := context.WithTimeout(ctx, 4*time.Second) - defer cancel() - netx := &netxlite.Netx{} - dialer := netxlite.NewDialerWithStdlibResolver(model.DiscardLogger) - reso := netx.NewParallelUDPResolver(model.DiscardLogger, dialer, address) - // TODO(bassosimone): this should actually only send an A query. Sending an AAAA - // query is _way_ unnecessary since we know that only A is going to work. - addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") - if err != nil || len(addrs) < 1 { - return nil, false - } - svc.udpv4[address] = []DNSWhoamiInfoEntry{{ - Address: addrs[0], - }} - } - value := svc.udpv4[address] - return value, len(value) > 0 -} - -// TODO(bassosimone): consider factoring this code and keeping state -// on disk rather than on memory. - // DNSWhoamiSingleton is the DNSWhoamiService singleton. -var DNSWhoamiSingleton = &DNSWhoamiService{ - mu: &sync.Mutex{}, - systemv4: []DNSWhoamiInfoEntry{}, - udpv4: map[string][]DNSWhoamiInfoEntry{}, -} +var DNSWhoamiSingleton = webconnectivityalgo.NewDNSWhoamiService() diff --git a/internal/experiment/webconnectivitylte/qa_test.go b/internal/experiment/webconnectivitylte/qa_test.go index 9bc61fcb94..a70f75c05a 100644 --- a/internal/experiment/webconnectivitylte/qa_test.go +++ b/internal/experiment/webconnectivitylte/qa_test.go @@ -3,7 +3,7 @@ package webconnectivitylte import ( "testing" - "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivityqa" + "github.com/ooni/probe-cli/v3/internal/webconnectivityqa" ) func TestQA(t *testing.T) { diff --git a/internal/experiment/webconnectivitylte/testkeys.go b/internal/experiment/webconnectivitylte/testkeys.go index c7b16181b5..52322755e1 100644 --- a/internal/experiment/webconnectivitylte/testkeys.go +++ b/internal/experiment/webconnectivitylte/testkeys.go @@ -15,6 +15,7 @@ import ( "github.com/ooni/probe-cli/v3/internal/legacy/tracex" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/optional" + "github.com/ooni/probe-cli/v3/internal/webconnectivityalgo" ) // TestKeys contains the results produced by web_connectivity. @@ -153,19 +154,13 @@ type ConnPriorityLogEntry struct { T float64 `json:"t"` } -// DNSWhoamiInfoEntry contains an entry for DNSWhoamiInfo. -type DNSWhoamiInfoEntry struct { - // Address is the IP address - Address string `json:"address"` -} - -// DNSWhoamiInfo contains info about DNS whoami. +// DNSWhoamiInfo contains information about a DNS whoami lookup. type DNSWhoamiInfo struct { // SystemV4 contains results related to the system resolver using IPv4. - SystemV4 []DNSWhoamiInfoEntry `json:"system_v4"` + SystemV4 []webconnectivityalgo.DNSWhoamiInfoEntry `json:"system_v4"` // UDPv4 contains results related to an UDP resolver using IPv4. - UDPv4 map[string][]DNSWhoamiInfoEntry `json:"udp_v4"` + UDPv4 map[string][]webconnectivityalgo.DNSWhoamiInfoEntry `json:"udp_v4"` } // TestKeysDoH contains ancillary observations collected using DoH (e.g., the @@ -334,8 +329,8 @@ func NewTestKeys() *TestKeys { SOCKSProxy: nil, NetworkEvents: []*model.ArchivalNetworkEvent{}, DNSWoami: &DNSWhoamiInfo{ - SystemV4: []DNSWhoamiInfoEntry{}, - UDPv4: map[string][]DNSWhoamiInfoEntry{}, + SystemV4: []webconnectivityalgo.DNSWhoamiInfoEntry{}, + UDPv4: map[string][]webconnectivityalgo.DNSWhoamiInfoEntry{}, }, DoH: &TestKeysDoH{ NetworkEvents: []*model.ArchivalNetworkEvent{}, diff --git a/internal/webconnectivityalgo/dnsoverhttps.go b/internal/webconnectivityalgo/dnsoverhttps.go new file mode 100644 index 0000000000..a096ce35a1 --- /dev/null +++ b/internal/webconnectivityalgo/dnsoverhttps.go @@ -0,0 +1,67 @@ +package webconnectivityalgo + +// +// DNS-over-HTTPS +// +// Code to manage DNS-over-HTTPS testing. +// + +import ( + "math/rand" + "sync" + "time" +) + +// TODO(bassosimone): consider whether factoring out this code +// and storing the state on disk instead of using memory + +// TODO(bassosimone): consider unifying somehow this code and +// the systemresolver code (or maybe just the list of resolvers) + +// OpportunisticDNSOverHTTPSURLProvider allows to perform opportunistic DNS-over-HTTPS +// measurements as part of Web Connectivity LTE. The zero value of this struct is not valid, +// please use [NewOpportunisticDNSOverHTTPSURLProvider] to construct. +type OpportunisticDNSOverHTTPSURLProvider struct { + // interval is the next interval after which to measure. + interval time.Duration + + // mu provides mutual exclusion + mu *sync.Mutex + + // rnd is the random number generator to use. + rnd *rand.Rand + + // t is when we last run an opportunistic measurement. + t time.Time + + // urls contains the urls of known DoH services. + urls []string +} + +// NewOpportunisticDNSOverHTTPSURLProvider creates a new [*OpportunisticDNSOverHTTPSURLProvider]. +func NewOpportunisticDNSOverHTTPSURLProvider(urls ...string) *OpportunisticDNSOverHTTPSURLProvider { + return &OpportunisticDNSOverHTTPSURLProvider{ + interval: 0, + mu: &sync.Mutex{}, + rnd: rand.New(rand.NewSource(time.Now().UnixNano())), + t: time.Time{}, + urls: urls, + } +} + +// MaybeNextURL returns the next URL to measure, if any. Our aim is to perform +// periodic, opportunistic DoH measurements as part of Web Connectivity. +func (o *OpportunisticDNSOverHTTPSURLProvider) MaybeNextURL() (string, bool) { + now := time.Now() + o.mu.Lock() + defer o.mu.Unlock() + if o.t.IsZero() || now.Sub(o.t) > o.interval { + o.rnd.Shuffle(len(o.urls), func(i, j int) { + o.urls[i], o.urls[j] = o.urls[j], o.urls[i] + }) + o.t = now + o.interval = time.Duration(20+o.rnd.Uint32()%20) * time.Second + return o.urls[0], true + } + return "", false +} diff --git a/internal/webconnectivityalgo/dnswhoami.go b/internal/webconnectivityalgo/dnswhoami.go new file mode 100644 index 0000000000..9490bb76cb --- /dev/null +++ b/internal/webconnectivityalgo/dnswhoami.go @@ -0,0 +1,99 @@ +package webconnectivityalgo + +// +// DNS whoami lookups +// +// The purpose of this lookups is figuring out who's answering our DNS +// queries so we know whether there's interception ongoing. +// + +import ( + "context" + "sync" + "time" + + "github.com/ooni/probe-cli/v3/internal/model" + "github.com/ooni/probe-cli/v3/internal/netxlite" +) + +// DNSWhoamiInfoEntry contains an entry for DNSWhoamiInfo. +type DNSWhoamiInfoEntry struct { + // Address is the IP address + Address string `json:"address"` +} + +// TODO(bassosimone): this code needs refining before we can merge it inside +// master. For one, we already have systemv4 info. Additionally, it would +// be neat to avoid additional AAAA queries. Furthermore, we should also see +// to implement support for IPv6 only clients as well. + +// TODO(bassosimone): consider factoring this code and keeping state +// on disk rather than on memory. + +// DNSWhoamiService is a service that performs DNS whoami lookups. +// +// The zero value of this struct is invalid. Please, construct using +// the [NewDNSWhoamiService] factory function. +type DNSWhoamiService struct { + // mu provides mutual exclusion + mu *sync.Mutex + + // systemv4 contains systemv4 results + systemv4 []DNSWhoamiInfoEntry + + // udpv4 contains udpv4 results + udpv4 map[string][]DNSWhoamiInfoEntry +} + +// NewDNSWhoamiService constructs a new [*DNSWhoamiService]. +func NewDNSWhoamiService() *DNSWhoamiService { + return &DNSWhoamiService{ + mu: &sync.Mutex{}, + systemv4: []DNSWhoamiInfoEntry{}, + udpv4: map[string][]DNSWhoamiInfoEntry{}, + } +} + +// SystemV4 returns the results of querying using the system resolver and IPv4. +func (svc *DNSWhoamiService) SystemV4(ctx context.Context) ([]DNSWhoamiInfoEntry, bool) { + svc.mu.Lock() + defer svc.mu.Unlock() + if len(svc.systemv4) <= 0 { + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) + defer cancel() + netx := &netxlite.Netx{} + reso := netx.NewStdlibResolver(model.DiscardLogger) + addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") + if err != nil || len(addrs) < 1 { + return nil, false + } + svc.systemv4 = []DNSWhoamiInfoEntry{{ + Address: addrs[0], + }} + } + return svc.systemv4, len(svc.systemv4) > 0 +} + +// UDPv4 returns the results of querying a given UDP resolver and IPv4. +func (svc *DNSWhoamiService) UDPv4(ctx context.Context, address string) ([]DNSWhoamiInfoEntry, bool) { + svc.mu.Lock() + defer svc.mu.Unlock() + if len(svc.udpv4[address]) <= 0 { + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) + defer cancel() + netx := &netxlite.Netx{} + dialer := netxlite.NewDialerWithStdlibResolver(model.DiscardLogger) + reso := netx.NewParallelUDPResolver(model.DiscardLogger, dialer, address) + // TODO(bassosimone): this should actually only send an A query. Sending an AAAA + // query is _way_ unnecessary since we know that only A is going to work. + addrs, err := reso.LookupHost(ctx, "whoami.v4.powerdns.org") + if err != nil || len(addrs) < 1 { + return nil, false + } + svc.udpv4[address] = []DNSWhoamiInfoEntry{{ + Address: addrs[0], + }} + } + value := svc.udpv4[address] + return value, len(value) > 0 +} diff --git a/internal/webconnectivityalgo/doc.go b/internal/webconnectivityalgo/doc.go new file mode 100644 index 0000000000..09ed5026f2 --- /dev/null +++ b/internal/webconnectivityalgo/doc.go @@ -0,0 +1,2 @@ +// Package webconnectivityalgo contains Web Connectivity algorithms. +package webconnectivityalgo diff --git a/internal/experiment/webconnectivityqa/badssl.go b/internal/webconnectivityqa/badssl.go similarity index 100% rename from internal/experiment/webconnectivityqa/badssl.go rename to internal/webconnectivityqa/badssl.go diff --git a/internal/experiment/webconnectivityqa/badssl_test.go b/internal/webconnectivityqa/badssl_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/badssl_test.go rename to internal/webconnectivityqa/badssl_test.go diff --git a/internal/experiment/webconnectivityqa/cloudflare.go b/internal/webconnectivityqa/cloudflare.go similarity index 100% rename from internal/experiment/webconnectivityqa/cloudflare.go rename to internal/webconnectivityqa/cloudflare.go diff --git a/internal/experiment/webconnectivityqa/control.go b/internal/webconnectivityqa/control.go similarity index 100% rename from internal/experiment/webconnectivityqa/control.go rename to internal/webconnectivityqa/control.go diff --git a/internal/experiment/webconnectivityqa/control_test.go b/internal/webconnectivityqa/control_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/control_test.go rename to internal/webconnectivityqa/control_test.go diff --git a/internal/experiment/webconnectivityqa/dnsblocking.go b/internal/webconnectivityqa/dnsblocking.go similarity index 100% rename from internal/experiment/webconnectivityqa/dnsblocking.go rename to internal/webconnectivityqa/dnsblocking.go diff --git a/internal/experiment/webconnectivityqa/dnsblocking_test.go b/internal/webconnectivityqa/dnsblocking_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/dnsblocking_test.go rename to internal/webconnectivityqa/dnsblocking_test.go diff --git a/internal/experiment/webconnectivityqa/dnshijacking.go b/internal/webconnectivityqa/dnshijacking.go similarity index 100% rename from internal/experiment/webconnectivityqa/dnshijacking.go rename to internal/webconnectivityqa/dnshijacking.go diff --git a/internal/experiment/webconnectivityqa/dnshijacking_test.go b/internal/webconnectivityqa/dnshijacking_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/dnshijacking_test.go rename to internal/webconnectivityqa/dnshijacking_test.go diff --git a/internal/experiment/webconnectivityqa/doc.go b/internal/webconnectivityqa/doc.go similarity index 100% rename from internal/experiment/webconnectivityqa/doc.go rename to internal/webconnectivityqa/doc.go diff --git a/internal/experiment/webconnectivityqa/ghost.go b/internal/webconnectivityqa/ghost.go similarity index 100% rename from internal/experiment/webconnectivityqa/ghost.go rename to internal/webconnectivityqa/ghost.go diff --git a/internal/experiment/webconnectivityqa/httpblocking.go b/internal/webconnectivityqa/httpblocking.go similarity index 100% rename from internal/experiment/webconnectivityqa/httpblocking.go rename to internal/webconnectivityqa/httpblocking.go diff --git a/internal/experiment/webconnectivityqa/httpblocking_test.go b/internal/webconnectivityqa/httpblocking_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/httpblocking_test.go rename to internal/webconnectivityqa/httpblocking_test.go diff --git a/internal/experiment/webconnectivityqa/httpdiff.go b/internal/webconnectivityqa/httpdiff.go similarity index 100% rename from internal/experiment/webconnectivityqa/httpdiff.go rename to internal/webconnectivityqa/httpdiff.go diff --git a/internal/experiment/webconnectivityqa/httpdiff_test.go b/internal/webconnectivityqa/httpdiff_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/httpdiff_test.go rename to internal/webconnectivityqa/httpdiff_test.go diff --git a/internal/experiment/webconnectivityqa/idna.go b/internal/webconnectivityqa/idna.go similarity index 100% rename from internal/experiment/webconnectivityqa/idna.go rename to internal/webconnectivityqa/idna.go diff --git a/internal/experiment/webconnectivityqa/largefile.go b/internal/webconnectivityqa/largefile.go similarity index 100% rename from internal/experiment/webconnectivityqa/largefile.go rename to internal/webconnectivityqa/largefile.go diff --git a/internal/experiment/webconnectivityqa/localhost.go b/internal/webconnectivityqa/localhost.go similarity index 100% rename from internal/experiment/webconnectivityqa/localhost.go rename to internal/webconnectivityqa/localhost.go diff --git a/internal/experiment/webconnectivityqa/measurement.go b/internal/webconnectivityqa/measurement.go similarity index 100% rename from internal/experiment/webconnectivityqa/measurement.go rename to internal/webconnectivityqa/measurement.go diff --git a/internal/experiment/webconnectivityqa/redirect.go b/internal/webconnectivityqa/redirect.go similarity index 100% rename from internal/experiment/webconnectivityqa/redirect.go rename to internal/webconnectivityqa/redirect.go diff --git a/internal/experiment/webconnectivityqa/redirect_test.go b/internal/webconnectivityqa/redirect_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/redirect_test.go rename to internal/webconnectivityqa/redirect_test.go diff --git a/internal/experiment/webconnectivityqa/run.go b/internal/webconnectivityqa/run.go similarity index 100% rename from internal/experiment/webconnectivityqa/run.go rename to internal/webconnectivityqa/run.go diff --git a/internal/experiment/webconnectivityqa/run_test.go b/internal/webconnectivityqa/run_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/run_test.go rename to internal/webconnectivityqa/run_test.go diff --git a/internal/experiment/webconnectivityqa/session.go b/internal/webconnectivityqa/session.go similarity index 100% rename from internal/experiment/webconnectivityqa/session.go rename to internal/webconnectivityqa/session.go diff --git a/internal/experiment/webconnectivityqa/session_test.go b/internal/webconnectivityqa/session_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/session_test.go rename to internal/webconnectivityqa/session_test.go diff --git a/internal/experiment/webconnectivityqa/success.go b/internal/webconnectivityqa/success.go similarity index 100% rename from internal/experiment/webconnectivityqa/success.go rename to internal/webconnectivityqa/success.go diff --git a/internal/experiment/webconnectivityqa/tcpblocking.go b/internal/webconnectivityqa/tcpblocking.go similarity index 100% rename from internal/experiment/webconnectivityqa/tcpblocking.go rename to internal/webconnectivityqa/tcpblocking.go diff --git a/internal/experiment/webconnectivityqa/tcpblocking_test.go b/internal/webconnectivityqa/tcpblocking_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/tcpblocking_test.go rename to internal/webconnectivityqa/tcpblocking_test.go diff --git a/internal/experiment/webconnectivityqa/testcase.go b/internal/webconnectivityqa/testcase.go similarity index 100% rename from internal/experiment/webconnectivityqa/testcase.go rename to internal/webconnectivityqa/testcase.go diff --git a/internal/experiment/webconnectivityqa/testcase_test.go b/internal/webconnectivityqa/testcase_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/testcase_test.go rename to internal/webconnectivityqa/testcase_test.go diff --git a/internal/experiment/webconnectivityqa/testkeys.go b/internal/webconnectivityqa/testkeys.go similarity index 100% rename from internal/experiment/webconnectivityqa/testkeys.go rename to internal/webconnectivityqa/testkeys.go diff --git a/internal/experiment/webconnectivityqa/throttling.go b/internal/webconnectivityqa/throttling.go similarity index 100% rename from internal/experiment/webconnectivityqa/throttling.go rename to internal/webconnectivityqa/throttling.go diff --git a/internal/experiment/webconnectivityqa/tlsblocking.go b/internal/webconnectivityqa/tlsblocking.go similarity index 100% rename from internal/experiment/webconnectivityqa/tlsblocking.go rename to internal/webconnectivityqa/tlsblocking.go diff --git a/internal/experiment/webconnectivityqa/tlsblocking_test.go b/internal/webconnectivityqa/tlsblocking_test.go similarity index 100% rename from internal/experiment/webconnectivityqa/tlsblocking_test.go rename to internal/webconnectivityqa/tlsblocking_test.go diff --git a/internal/experiment/webconnectivityqa/websitedown.go b/internal/webconnectivityqa/websitedown.go similarity index 100% rename from internal/experiment/webconnectivityqa/websitedown.go rename to internal/webconnectivityqa/websitedown.go