Skip to content

Commit

Permalink
sample a max of n targets
Browse files Browse the repository at this point in the history
  • Loading branch information
ainghazal committed Oct 17, 2024
1 parent dd895e9 commit 86202ed
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 46 deletions.
51 changes: 5 additions & 46 deletions internal/experiment/openvpn/richerinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,13 @@ package openvpn

import (
"context"
"slices"
"time"

"github.com/ooni/probe-cli/v3/internal/experimentconfig"
"github.com/ooni/probe-cli/v3/internal/legacy/netx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/targetloading"
)

// defaultOONIHostnames is the array of hostnames that will return valid
// endpoints to be probed. Do note that this is a workaround for the lack
// of a backend service.
var defaultOONIEndpoints = []string{
"a.composer-presenter.com",
"a.goodyear2dumpster.com",
}

// maxDefaultOONIAddresses is how many IPs to use from the
// set of resolved IPs.
var maxDefaultOONIAddresses = 3

// providerAuthentication is a map so that we know which kind of credentials we
// need to fill in the openvpn options for each known provider.
var providerAuthentication = map[string]AuthMethod{
Expand Down Expand Up @@ -125,41 +110,15 @@ func lookupHost(ctx context.Context, hostname string, r model.Resolver) ([]strin
}

func (tl *targetLoader) loadFromDefaultEndpoints() ([]model.ExperimentTarget, error) {
resolver := netx.NewResolver(netx.Config{
BogonIsError: false,
Logger: tl.session.Logger(),
Saver: nil,
})

addrs := []string{}

// get the set of all IPs for all the hostnames we have.
for _, hostname := range defaultOONIEndpoints {
resolved, err := lookupHost(context.Background(), hostname, resolver)
if err != nil {
tl.loader.Logger.Warnf("Cannot resolve %s", hostname)
continue
}
for _, ipaddr := range resolved {
if !slices.Contains(addrs, ipaddr) {
addrs = append(addrs, ipaddr)
}
}
}

// Remove the bogons

validAddrs := []string{}
targets := []model.ExperimentTarget{}

for _, addr := range addrs {
if !netxlite.IsBogon(addr) {
validAddrs = append(validAddrs, addr)
}
addrs, err := resolveOONIAddresses(tl.session.Logger())
if err != nil {
return targets, err
}

tl.loader.Logger.Warnf("Picking from default OpenVPN endpoints")
targets := []model.ExperimentTarget{}
if inputs, err := pickOONIOpenVPNTargets(validAddrs); err == nil {
if inputs, err := pickOONIOpenVPNTargets(addrs); err == nil {
for _, url := range inputs {
targets = append(targets,
&Target{
Expand Down
93 changes: 93 additions & 0 deletions internal/experiment/openvpn/targets.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,103 @@
package openvpn

import (
"context"
"fmt"
"math/rand"
"slices"
"time"

"github.com/ooni/probe-cli/v3/internal/legacy/netx"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)

// defaultOONIHostnames is the array of hostnames that will return valid
// endpoints to be probed. Do note that this is a workaround for the lack
// of a backend service.
var defaultOONIEndpoints = []string{
"a.composer-presenter.com",
"a.goodyear2dumpster.com",
}

// maxDefaultOONIAddresses is how many IPs to use from the
// set of resolved IPs.
var maxDefaultOONIAddresses = 3

// sampleN takes max n elements sampled ramdonly from the array a.
func sampleN(a []string, n int) []string {
if n > len(a) {
n = len(a)
}

rand.Seed(time.Now().UnixNano())
sampled := make([]string, 0)

// Use a map to track indices we've already selected to avoid duplicates
picked := make(map[int]struct{})

for len(sampled) < n {
idx := rand.Intn(len(a)) // Random index
if _, exists := picked[idx]; !exists {
sampled = append(sampled, a[idx])
picked[idx] = struct{}{} // Mark index as used
}
}

return sampled
}

// resolveOONIAddresses returns a max of maxDefaultOONIAddresses after
// performing DNS resolution. The returned IP addreses exclude possible
// bogons.
func resolveOONIAddresses(logger model.Logger) ([]string, error) {
resolver := netx.NewResolver(netx.Config{
BogonIsError: false,
Logger: logger,
Saver: nil,
})

addrs := []string{}

var lastErr error

// get the set of all IPs for all the hostnames we have.
for _, hostname := range defaultOONIEndpoints {
resolved, err := lookupHost(context.Background(), hostname, resolver)
if err != nil {
lastErr = err
continue
}
for _, ipaddr := range resolved {
if !slices.Contains(addrs, ipaddr) {
addrs = append(addrs, ipaddr)
}
}
}

// Sample a max of maxDefaultOONIAddresses

sampled := sampleN(addrs, maxDefaultOONIAddresses)

// Remove the bogons

valid := []string{}

for _, addr := range sampled {
if !netxlite.IsBogon(addr) {
valid = append(valid, addr)
}
}

// We only return error if the filtered list is zero len.

if (len(valid) == 0) && (lastErr != nil) {
return valid, lastErr
}

return valid, nil
}

// pickOONIOpenVPNTargets returns an array of input URIs from the list of available endpoints, up to max,
// for the given transport. By default, we use the first endpoint that resolves to an IP. If reverseOrder
// is specified, we reverse the list before attempting resolution.
Expand Down

0 comments on commit 86202ed

Please sign in to comment.