• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ooni / probe-cli / 8664534558

12 Apr 2024 03:22PM UTC coverage: 82.775% (-0.03%) from 82.801%
8664534558

Pull #1551

github

bassosimone
fix(measurexlite): add robust RemoteAddr accessors

Closes https://github.com/ooni/probe/issues/2707
Pull Request #1551: fix(measurexlite): add robust RemoteAddr accessors

14 of 14 new or added lines in 1 file covered. (100.0%)

10 existing lines in 3 files now uncovered.

26291 of 31762 relevant lines covered (82.78%)

53.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.2
/internal/experiment/webconnectivitylte/priority.go
1
package webconnectivitylte
2

3
//
4
// Determine which connection(s) are allowed to fetch the webpage
5
// by giving higher priority to the system resolver, then to the
6
// UDP resolver, then to the DoH resolver, then to the TH.
7
//
8
// This sorting reflects the likelyhood that we will se a blockpage
9
// because the system resolver is the most likely to be blocked
10
// (e.g., in Italy). The UDP resolver is also blocked in countries
11
// with more censorship (e.g., in China). The DoH resolver and
12
// the TH have more or less the same priority here, but we needed
13
// to choose one of them to have higher priority.
14
//
15
// Note that this functionality is where Web Connectivity LTE
16
// diverges from websteps, which will instead fetch all the available
17
// webpages. To adhere to the Web Connectivity model, we need to
18
// have a single fetch per redirect. However, by allowing all the
19
// resolvers plus the TH to provide us with addresses, we increase
20
// our chances of detecting more kinds of censorship.
21
//
22
// Also note that this implementation basically makes the
23
// https://github.com/ooni/probe/issues/2258 issue obsolete,
24
// since now we're considering all resolvers.
25
//
26
// See https://github.com/ooni/probe/issues/2276.
27
//
28

29
import (
30
        "context"
31
        "errors"
32
        "fmt"
33
        "net"
34
        "time"
35

36
        "github.com/ooni/probe-cli/v3/internal/model"
37
        "github.com/ooni/probe-cli/v3/internal/runtimex"
38
)
39

40
// errNotPermittedToFetch indicates we're not permitted to fetch a webpage
41
// because another goroutine have already fetched that webpage.
42
var errNotPermittedToFetch = errors.New("webconnectivity: not permitted to fetch")
43

44
// prioritySelector selects the connection with the highest priority.
45
type prioritySelector struct {
46
        // ch is the channel used to ask for priority
47
        ch chan *priorityRequest
48

49
        // logger is the logger to use
50
        logger model.Logger
51

52
        // m contains a map from known addresses to their flags
53
        m map[string]int64
54

55
        // nhttps is the number of addrs resolved using DoH
56
        nhttps int
57

58
        // nsystem is the number of addrs resolved using the system resolver
59
        nsystem int
60

61
        // nudp is the nunber of addrs resolver using UDP
62
        nudp int
63

64
        // tk contains the TestKeys.
65
        tk *TestKeys
66

67
        // zeroTime is the zero time of the current measurement
68
        zeroTime time.Time
69
}
70

71
// priorityRequest is a request to get priority for fetching the webpage
72
// over other concurrent connections that are doing the same.
73
type priorityRequest struct {
74
        // addr is the address we're using
75
        addr string
76

77
        // resp is the buffered channel where the response will appear
78
        resp chan bool
79
}
80

81
// newPrioritySelector creates a new prioritySelector instance.
82
func newPrioritySelector(
83
        ctx context.Context,
84
        zeroTime time.Time,
85
        tk *TestKeys,
86
        logger model.Logger,
87
        addrs []DNSEntry,
88
) *prioritySelector {
54✔
89
        ps := &prioritySelector{
54✔
90
                ch:       make(chan *priorityRequest),
54✔
91
                logger:   logger,
54✔
92
                m:        map[string]int64{},
54✔
93
                nhttps:   0,
54✔
94
                nsystem:  0,
54✔
95
                nudp:     0,
54✔
96
                tk:       tk,
54✔
97
                zeroTime: zeroTime,
54✔
98
        }
54✔
99
        ps.log("create with %+v", addrs)
54✔
100
        for _, addr := range addrs {
114✔
101
                flags := addr.Flags
60✔
102
                ps.m[addr.Addr] = flags
60✔
103
                if (flags & DNSAddrFlagSystemResolver) != 0 {
109✔
104
                        ps.nsystem++
49✔
105
                }
49✔
106
                if (flags & DNSAddrFlagUDP) != 0 {
111✔
107
                        ps.nudp++
51✔
108
                }
51✔
109
                if (flags & DNSAddrFlagHTTPS) != 0 {
99✔
110
                        ps.nhttps++
39✔
111
                }
39✔
112
        }
113
        go ps.selector(ctx)
54✔
114
        return ps
54✔
115
}
116

117
// log formats and emits a ConnPriorityLogEntry
118
func (ps *prioritySelector) log(format string, v ...any) {
94✔
119
        ps.tk.AppendConnPriorityLogEntry(&ConnPriorityLogEntry{
94✔
120
                Msg: fmt.Sprintf(format, v...),
94✔
121
                T:   time.Since(ps.zeroTime).Seconds(),
94✔
122
        })
94✔
123
        ps.logger.Infof("prioritySelector: "+format, v...)
94✔
124
}
94✔
125

126
// permissionToFetch returns whether this ready-to-use connection
127
// is permitted to perform a round trip and fetch the webpage.
128
func (ps *prioritySelector) permissionToFetch(address string) bool {
40✔
129
        ipAddr, _, err := net.SplitHostPort(address)
40✔
130
        runtimex.PanicOnError(err, "net.SplitHostPort failed")
40✔
131
        r := &priorityRequest{
40✔
132
                addr: ipAddr,
40✔
133
                resp: make(chan bool, 1), // buffer to simplify selector() implementation
40✔
134
        }
40✔
135
        select {
40✔
UNCOV
136
        case <-time.After(10 * time.Millisecond):
×
UNCOV
137
                ps.log("conn %s: denied permission: timed out sending", address)
×
UNCOV
138
                return false
×
139
        case ps.ch <- r:
40✔
140
                select {
40✔
141
                case <-time.After(time.Second):
×
142
                        ps.log("conn %s: denied permission: timed out receiving", address)
×
143
                        return false
×
144
                case v := <-r.resp:
40✔
145
                        ps.log("conn %s: granted permission: %+v", address, v)
40✔
146
                        return v
40✔
147
                }
148
        }
149
}
150

151
// selector grants permission to the highest priority request that
152
// arrives within a reasonable time frame. This function runs into the
153
// background goroutine and terminates when [ctx] is done.
154
//
155
// This function implements https://github.com/ooni/probe/issues/2276.
156
func (ps *prioritySelector) selector(ctx context.Context) {
54✔
157
        // Implementation note: setting an arbitrary timeout here would
54✔
158
        // be ~an issue because we want this goroutine to be available in
54✔
159
        // case the only connections from which we could fetch a webpage
54✔
160
        // are the ones using TH addresses. However, we know the TH could
54✔
161
        // require a long time to complete due to timeouts caused by IP
54✔
162
        // addresses provided by the probe.
54✔
163
        //
54✔
164
        // See https://explorer.ooni.org/measurement/20220911T105037Z_webconnectivity_IT_30722_n1_ruzuQ219SmIO9SrT?input=http%3A%2F%2Festrenosli.org%2F
54✔
165
        // for a measurement where a too-short timeout prevented us from
54✔
166
        // attempting to fetch a webpage from TH-resolved addrs.
54✔
167
        //
54✔
168
        // See https://explorer.ooni.org/measurement/20220911T194527Z_webconnectivity_IT_30722_n1_jufRZGay0Db9Ge4v?input=http%3A%2F%2Festrenosli.org%2F
54✔
169
        // for a measurement where this issue was fixed.
54✔
170

54✔
171
        // await the first priority request
54✔
172
        var first *priorityRequest
54✔
173
        select {
54✔
174
        case <-ctx.Done():
17✔
175
                return
17✔
176
        case first = <-ps.ch:
37✔
177
        }
178

179
        // if this request is highest priority, grant permission
180
        if ps.isHighestPriority(first) {
66✔
181
                first.resp <- true // buffered channel
29✔
182
                return
29✔
183
        }
29✔
184

185
        // collect additional requests for up to extraTime, thus giving
186
        // a possibly higher priority connection time to establish
187
        const extraTime = 500 * time.Millisecond
8✔
188
        expired := time.NewTimer(extraTime)
8✔
189
        defer expired.Stop()
8✔
190
        requests := []*priorityRequest{first}
8✔
191
Loop:
8✔
192
        for {
19✔
193
                select {
11✔
194
                case <-expired.C:
8✔
195
                        break Loop
8✔
196
                case r := <-ps.ch:
3✔
197
                        requests = append(requests, r)
3✔
198
                case <-ctx.Done():
×
199
                        return
×
200
                }
201
        }
202

203
        // grant permission to the highest priority request
204
        highPrio := ps.findHighestPriority(requests)
8✔
205
        highPrio.resp <- true // buffered channel
8✔
206

8✔
207
        // deny permission to all the other inflight requests
8✔
208
        for _, r := range requests {
19✔
209
                if highPrio != r {
14✔
210
                        r.resp <- false // buffered channel
3✔
211
                }
3✔
212
        }
213
}
214

215
// findHighestPriority returns the highest priority request
216
func (ps *prioritySelector) findHighestPriority(reqs []*priorityRequest) *priorityRequest {
8✔
217
        runtimex.Assert(len(reqs) > 0, "findHighestPriority wants a non-empty reqs slice")
8✔
218
        for _, r := range reqs {
19✔
219
                if ps.isHighestPriority(r) {
14✔
220
                        return r
3✔
221
                }
3✔
222
        }
223
        return reqs[0]
5✔
224
}
225

226
// isHighestPriority returns whether this request is highest priority
227
func (ps *prioritySelector) isHighestPriority(r *priorityRequest) bool {
48✔
228
        // See https://github.com/ooni/probe/issues/2276
48✔
229
        flags := ps.m[r.addr]
48✔
230
        if ps.nsystem > 0 {
94✔
231
                if (flags & DNSAddrFlagSystemResolver) != 0 {
76✔
232
                        return true
30✔
233
                }
30✔
234
        } else if ps.nudp > 0 {
4✔
235
                if (flags & DNSAddrFlagUDP) != 0 {
4✔
236
                        return true
2✔
237
                }
2✔
238
        } else if ps.nhttps > 0 {
×
239
                if (flags & DNSAddrFlagHTTPS) != 0 {
×
240
                        return true
×
241
                }
×
242
        } else {
×
243
                // Happens when we only have addresses from the TH
×
244
                return true
×
245
        }
×
246
        return false
16✔
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc