1package probeservices 2 3import ( 4 "context" 5 "time" 6 7 "github.com/ooni/probe-cli/v3/internal/engine/model" 8) 9 10// Default returns the default probe services 11func Default() []model.Service { 12 return []model.Service{{ 13 Address: "https://ps1.ooni.io", 14 Type: "https", 15 }, { 16 Address: "https://ps2.ooni.io", 17 Type: "https", 18 }, { 19 Front: "dkyhjv0wpi2dk.cloudfront.net", 20 Type: "cloudfront", 21 Address: "https://dkyhjv0wpi2dk.cloudfront.net", 22 }} 23} 24 25// SortEndpoints gives priority to https, then cloudfronted, then onion. 26func SortEndpoints(in []model.Service) (out []model.Service) { 27 for _, entry := range in { 28 if entry.Type == "https" { 29 out = append(out, entry) 30 } 31 } 32 for _, entry := range in { 33 if entry.Type == "cloudfront" { 34 out = append(out, entry) 35 } 36 } 37 for _, entry := range in { 38 if entry.Type == "onion" { 39 out = append(out, entry) 40 } 41 } 42 return 43} 44 45// OnlyHTTPS returns the HTTPS endpoints only. 46func OnlyHTTPS(in []model.Service) (out []model.Service) { 47 for _, entry := range in { 48 if entry.Type == "https" { 49 out = append(out, entry) 50 } 51 } 52 return 53} 54 55// OnlyFallbacks returns the fallback endpoints only. 56func OnlyFallbacks(in []model.Service) (out []model.Service) { 57 for _, entry := range SortEndpoints(in) { 58 if entry.Type != "https" { 59 out = append(out, entry) 60 } 61 } 62 return 63} 64 65// Candidate is a candidate probe service. 66type Candidate struct { 67 // Duration is the time it took to access the service. 68 Duration time.Duration 69 70 // Err indicates whether the service works. 71 Err error 72 73 // Endpoint is the service endpoint. 74 Endpoint model.Service 75 76 // TestHelpers contains the data returned by the endpoint. 77 TestHelpers map[string][]model.Service 78} 79 80func (c *Candidate) try(ctx context.Context, sess Session) { 81 client, err := NewClient(sess, c.Endpoint) 82 if err != nil { 83 c.Err = err 84 return 85 } 86 start := time.Now() 87 testhelpers, err := client.GetTestHelpers(ctx) 88 c.Duration = time.Since(start) 89 c.Err = err 90 c.TestHelpers = testhelpers 91 sess.Logger().Debugf("probe services: %+v: %+v %s", c.Endpoint, err, c.Duration) 92} 93 94func try(ctx context.Context, sess Session, svc model.Service) *Candidate { 95 candidate := &Candidate{Endpoint: svc} 96 candidate.try(ctx, sess) 97 return candidate 98} 99 100// TryAll tries all the input services using the provided context and session. It 101// returns a list containing information on each candidate that was tried. We will 102// try all the HTTPS candidates first. So, the beginning of the list will contain 103// all of them, and for each of them you will know whether it worked (by checking the 104// Err field) and how fast it was (by checking the Duration field). You should pick 105// the fastest one that worked. If none of them works, then TryAll will subsequently 106// attempt with all the available fallbacks, and return at the first success. In 107// such case, you will see a list of N failing HTTPS candidates, followed by a single 108// successful fallback candidate (e.g. cloudfronted). If all candidates fail, you 109// see in output a list containing all entries where Err is not nil. 110func TryAll(ctx context.Context, sess Session, in []model.Service) (out []*Candidate) { 111 var found bool 112 for _, svc := range OnlyHTTPS(in) { 113 candidate := try(ctx, sess, svc) 114 out = append(out, candidate) 115 if candidate.Err == nil { 116 found = true 117 } 118 } 119 if !found { 120 for _, svc := range OnlyFallbacks(in) { 121 candidate := try(ctx, sess, svc) 122 out = append(out, candidate) 123 if candidate.Err == nil { 124 return 125 } 126 } 127 } 128 return 129} 130 131// SelectBest selects the best among the candidates. If there is no 132// suitable candidate, then this function returns nil. 133func SelectBest(candidates []*Candidate) (selected *Candidate) { 134 for _, e := range candidates { 135 if e.Err != nil { 136 continue 137 } 138 if selected == nil { 139 selected = e 140 continue 141 } 142 if selected.Duration > e.Duration { 143 selected = e 144 continue 145 } 146 } 147 return 148} 149