Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(enginenetx): split generation and mixing #1591

Merged
merged 146 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
146 commits
Select commit Hold shift + click to select a range
80b5e1f
doc(enginenetx): improve documentation
bassosimone Apr 15, 2024
62c3917
fix(enginenetx): mix bridges and DNS tactics
bassosimone Apr 15, 2024
0d2b0f2
fix: update test name and add comment
bassosimone Apr 15, 2024
aee17cf
feat: test for the mixed policies case
bassosimone Apr 15, 2024
7576fc7
feat(enginenetx): add support for filtering tactics
bassosimone Apr 15, 2024
8120c06
refactor: rename function
bassosimone Apr 15, 2024
3207255
feat: improve TCP connect statistics
bassosimone Apr 15, 2024
5618b72
fix previous
bassosimone Apr 15, 2024
2153e36
feat: start to prepare for filtering endpoints
bassosimone Apr 16, 2024
e5cdbb0
ongoing work while documenting and clarifying
bassosimone Apr 16, 2024
089f70b
x
bassosimone Apr 16, 2024
20e71e8
[ci skip]
bassosimone Apr 16, 2024
b5b2e49
[ci skip]
bassosimone Apr 16, 2024
94eb284
[ci skip]
bassosimone Apr 16, 2024
90601c6
[ci skip]
bassosimone Apr 16, 2024
ef8fdfe
[ci skip]
bassosimone Apr 16, 2024
aa65cb7
[ci skip]
bassosimone Apr 16, 2024
28a6265
[ci skip]
bassosimone Apr 16, 2024
f452208
[ci skip]
bassosimone Apr 16, 2024
119e610
[ci skip]
bassosimone Apr 16, 2024
f3fb1dd
[ci skip]
bassosimone Apr 16, 2024
ce6ec84
[ci skip]
bassosimone Apr 16, 2024
4f63b60
[ci skip]
bassosimone Apr 16, 2024
45e655c
x
bassosimone Apr 16, 2024
8e2a1f3
x
bassosimone Apr 16, 2024
e2aed07
x
bassosimone Apr 16, 2024
08fbf48
[ci skip]
bassosimone Apr 16, 2024
7c6ab4b
[ci skip]
bassosimone Apr 16, 2024
c843241
[ci skip]
bassosimone Apr 16, 2024
7f16577
[ci skip]
bassosimone Apr 16, 2024
4b0c768
[ci skip]
bassosimone Apr 16, 2024
7ea130d
[ci skip]
bassosimone Apr 16, 2024
1cbc109
[ci skip]
bassosimone Apr 16, 2024
7edfbb8
[ci skip]
bassosimone Apr 16, 2024
21f9b90
[ci skip]
bassosimone Apr 16, 2024
8e5fee9
[ci skip]
bassosimone Apr 16, 2024
4f8cf91
[ci skip]
bassosimone Apr 16, 2024
436fb50
[ci skip]
bassosimone Apr 16, 2024
b6aebc2
[ci skip]
bassosimone Apr 16, 2024
77b03bd
[ci skip]
bassosimone Apr 16, 2024
f565e68
[ci skip]
bassosimone Apr 16, 2024
e9eee04
[ci skip]
bassosimone Apr 16, 2024
393968d
[ci skip]
bassosimone Apr 16, 2024
665b961
[ci skip]
bassosimone Apr 16, 2024
fb651c7
[ci skip]
bassosimone Apr 16, 2024
cb0dbfc
[ci skip]
bassosimone Apr 16, 2024
bfc0a1d
[ci skip]
bassosimone Apr 16, 2024
02660de
[ci skip]
bassosimone Apr 16, 2024
f707616
[ci skip]
bassosimone Apr 16, 2024
492ab69
[ci skip]
bassosimone Apr 16, 2024
3b63fbd
[ci skip]
bassosimone Apr 16, 2024
0c06b53
[ci skip]
bassosimone Apr 16, 2024
8691606
[ci skip]
bassosimone Apr 16, 2024
20f800a
[ci skip]
bassosimone Apr 16, 2024
e02c5d4
[ci skip]
bassosimone Apr 16, 2024
dd128d8
[ci skip]
bassosimone Apr 16, 2024
8c5bc60
[ci skip]
bassosimone Apr 16, 2024
6e47258
[ci skip]
bassosimone Apr 16, 2024
018fec4
[ci skip]
bassosimone Apr 16, 2024
c834599
[ci skip]
bassosimone Apr 16, 2024
ccd26c4
[ci skip]
bassosimone Apr 16, 2024
0b32b47
[ci skip]
bassosimone Apr 16, 2024
6066a7f
[ci skip]
bassosimone Apr 16, 2024
6c83c25
[ci skip]
bassosimone Apr 16, 2024
19fc4b5
[ci skip]
bassosimone Apr 16, 2024
856d261
[ci skip]
bassosimone Apr 16, 2024
2b7a881
[ci skip]
bassosimone Apr 16, 2024
b7327e2
[ci skip]
bassosimone Apr 16, 2024
3399fec
[ci skip]
bassosimone Apr 16, 2024
baef14f
[ci skip]
bassosimone Apr 16, 2024
15d28f2
[ci skip]
bassosimone Apr 16, 2024
0baaf9b
[ci skip]
bassosimone Apr 16, 2024
49bbf25
[ci skip]
bassosimone Apr 16, 2024
d739ddd
[ci skip]
bassosimone Apr 16, 2024
4896f68
[ci skip]
bassosimone Apr 16, 2024
8bdbbaf
x
bassosimone Apr 16, 2024
4e3a8af
the design document should now be good
bassosimone Apr 16, 2024
fd81cf7
x
bassosimone Apr 16, 2024
8a93533
x
bassosimone Apr 16, 2024
2949035
remove more code that we probably don't need
bassosimone Apr 16, 2024
c075625
[ci skip]
bassosimone Apr 17, 2024
8d14587
[ci skip]
bassosimone Apr 17, 2024
7a2a360
[ci skip]
bassosimone Apr 17, 2024
dfed510
[ci skip]
bassosimone Apr 17, 2024
be41fa9
[ci skip]
bassosimone Apr 17, 2024
5f7302a
[ci skip]
bassosimone Apr 17, 2024
7443710
[ci skip]
bassosimone Apr 17, 2024
2944ea0
[ci skip]
bassosimone Apr 17, 2024
43c1e7c
[ci skip]
bassosimone Apr 17, 2024
d6159d6
[ci skip]
bassosimone Apr 17, 2024
dcf4a03
[ci skip]
bassosimone Apr 17, 2024
f037eb3
[ci skip]
bassosimone Apr 17, 2024
586c450
[ci skip]
bassosimone Apr 17, 2024
de31fd5
[ci skip]
bassosimone Apr 17, 2024
8a3eef7
[ci skip]
bassosimone Apr 17, 2024
697f6b3
[ci skip]
bassosimone Apr 17, 2024
e652147
[ci skip]
bassosimone Apr 17, 2024
4baca70
[ci skip]
bassosimone Apr 17, 2024
fa1c237
[ci skip]
bassosimone Apr 17, 2024
75eb2df
Merge branch 'master' into issue/2704
bassosimone Apr 17, 2024
fd9c568
Merge branch 'master' into issue/2704
bassosimone Apr 17, 2024
d0f721a
x
bassosimone Apr 17, 2024
7058b7d
x
bassosimone Apr 17, 2024
4e628db
x
bassosimone Apr 17, 2024
3b0e110
x
bassosimone Apr 17, 2024
72e1336
Merge branch 'master' into issue/2704
bassosimone Apr 17, 2024
32178c6
x
bassosimone Apr 17, 2024
e7c7541
x
bassosimone Apr 17, 2024
f16784c
x
bassosimone Apr 17, 2024
28bcd5f
[ci skip]
bassosimone Apr 17, 2024
da06bbb
[ci skip]
bassosimone Apr 17, 2024
67ae4d9
[ci skip]
bassosimone Apr 17, 2024
02ee95d
[ci skip]
bassosimone Apr 17, 2024
3877575
fix: adapt test after remix change
bassosimone Apr 18, 2024
af87c6e
doc: add table of contents
bassosimone Apr 18, 2024
1a50266
doc: rename the goals section to be more clear
bassosimone Apr 18, 2024
139f10c
Update internal/enginenetx/DESIGN.md
bassosimone Apr 26, 2024
1d8a786
Update internal/enginenetx/DESIGN.md
bassosimone Apr 26, 2024
a161fa1
Merge branch 'master' into issue/2704
bassosimone Apr 26, 2024
bffc811
Apply suggestions from @hellais
bassosimone May 7, 2024
e9765df
Merge branch 'master' into issue/2704
bassosimone May 7, 2024
9d2c47f
feat: allow chaining policies after the DNS
bassosimone May 8, 2024
f7509d9
feat: implement the mix-with-interleaving policy
bassosimone May 8, 2024
ab3926e
fix: we actually need to get rid of fallbacks
bassosimone May 8, 2024
d4f60b1
refactor(bridgespolicy.go): use free functions
bassosimone May 8, 2024
b0f88bd
feat: introduce fallback-less v2 policies
bassosimone May 8, 2024
35d667f
Shove all the changes I have together
bassosimone May 8, 2024
2582614
chore: add more debugging info
bassosimone May 9, 2024
957ce1d
x
bassosimone May 9, 2024
e2b02aa
x
bassosimone May 9, 2024
7291b9b
feat: write tests for bridgespolicy
bassosimone May 9, 2024
adf00d0
x
bassosimone May 9, 2024
3b406ff
feat: write tests for mixpolicy
bassosimone May 9, 2024
563f22b
x
bassosimone May 9, 2024
d0727ed
feat: write tests for statsPolicyV2
bassosimone May 9, 2024
124d0f9
x
bassosimone May 9, 2024
e4b1826
x
bassosimone May 9, 2024
8d92599
x
bassosimone May 9, 2024
f28bd50
x
bassosimone May 9, 2024
37f4004
x
bassosimone May 9, 2024
288d500
chore: write tests for userPolicyV2
bassosimone May 9, 2024
8b9856a
x
bassosimone May 9, 2024
3cb8ab5
chore: test the mocks policy
bassosimone May 9, 2024
78fa51f
x
bassosimone May 9, 2024
7f1201c
chore: write tests for test helpers policy
bassosimone May 9, 2024
c6cbc8b
x
bassosimone May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 30 additions & 18 deletions internal/enginenetx/bridgespolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ import (
"time"
)

// bridgesPolicyV2 is a policy where we use bridges for communicating
// with the OONI backend, i.e., api.ooni.io.
//
// A bridge is an IP address that can route traffic from and to
// the OONI backend and accepts any SNI.
//
// The zero value is invalid; please, init MANDATORY fields.
//
// This is v2 of the bridgesPolicy because the previous implementation
// incorporated mixing logic, while now the mixing happens outside
// of this policy, thus giving us much more flexibility.
type bridgesPolicyV2 struct{}

var _ httpsDialerPolicy = &bridgesPolicyV2{}

// LookupTactics implements httpsDialerPolicy.
func (p *bridgesPolicyV2) LookupTactics(ctx context.Context, domain, port string) <-chan *httpsDialerTactic {
return bridgesTacticsForDomain(domain, port)
}

// bridgesPolicy is a policy where we use bridges for communicating
// with the OONI backend, i.e., api.ooni.io.
//
Expand All @@ -31,7 +51,7 @@ func (p *bridgesPolicy) LookupTactics(ctx context.Context, domain, port string)
return mixSequentially(
// emit bridges related tactics first which are empty if there are
// no bridges for the givend domain and port
p.bridgesTacticsForDomain(domain, port),
bridgesTacticsForDomain(domain, port),

// now fallback to get more tactics (typically here the fallback
// uses the DNS and obtains some extra tactics)
Expand All @@ -42,14 +62,6 @@ func (p *bridgesPolicy) LookupTactics(ctx context.Context, domain, port string)
)
}

var bridgesPolicyTestHelpersDomains = []string{
"0.th.ooni.org",
"1.th.ooni.org",
"2.th.ooni.org",
"3.th.ooni.org",
"d33d1gs9kpq1c5.cloudfront.net",
}

func (p *bridgesPolicy) maybeRewriteTestHelpersTactics(input <-chan *httpsDialerTactic) <-chan *httpsDialerTactic {
out := make(chan *httpsDialerTactic)

Expand All @@ -58,14 +70,14 @@ func (p *bridgesPolicy) maybeRewriteTestHelpersTactics(input <-chan *httpsDialer

for tactic := range input {
// When we're not connecting to a TH, pass the policy down the chain unmodified
if !slices.Contains(bridgesPolicyTestHelpersDomains, tactic.VerifyHostname) {
if !slices.Contains(testHelpersDomains, tactic.VerifyHostname) {
out <- tactic
continue
}

// This is the case where we're connecting to a test helper. Let's try
// to produce policies hiding the SNI to censoring middleboxes.
for _, sni := range p.bridgesDomainsInRandomOrder() {
for _, sni := range bridgesDomainsInRandomOrder() {
out <- &httpsDialerTactic{
Address: tactic.Address,
InitialDelay: 0, // set when dialing
Expand All @@ -80,7 +92,7 @@ func (p *bridgesPolicy) maybeRewriteTestHelpersTactics(input <-chan *httpsDialer
return out
}

func (p *bridgesPolicy) bridgesTacticsForDomain(domain, port string) <-chan *httpsDialerTactic {
func bridgesTacticsForDomain(domain, port string) <-chan *httpsDialerTactic {
out := make(chan *httpsDialerTactic)

go func() {
Expand All @@ -91,8 +103,8 @@ func (p *bridgesPolicy) bridgesTacticsForDomain(domain, port string) <-chan *htt
return
}

for _, ipAddr := range p.bridgesAddrs() {
for _, sni := range p.bridgesDomainsInRandomOrder() {
for _, ipAddr := range bridgesAddrs() {
for _, sni := range bridgesDomainsInRandomOrder() {
out <- &httpsDialerTactic{
Address: ipAddr,
InitialDelay: 0, // set when dialing
Expand All @@ -107,23 +119,23 @@ func (p *bridgesPolicy) bridgesTacticsForDomain(domain, port string) <-chan *htt
return out
}

func (p *bridgesPolicy) bridgesDomainsInRandomOrder() (out []string) {
out = p.bridgesDomains()
func bridgesDomainsInRandomOrder() (out []string) {
out = bridgesDomains()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Shuffle(len(out), func(i, j int) {
out[i], out[j] = out[j], out[i]
})
return
}

func (p *bridgesPolicy) bridgesAddrs() (out []string) {
func bridgesAddrs() (out []string) {
return append(
out,
"162.55.247.208",
)
}

func (p *bridgesPolicy) bridgesDomains() (out []string) {
func bridgesDomains() (out []string) {
// See https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40273
return append(
out,
Expand Down
57 changes: 56 additions & 1 deletion internal/enginenetx/bridgespolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,61 @@ import (
"github.com/ooni/probe-cli/v3/internal/model"
)

func TestBridgesPolicyV2(t *testing.T) {
t.Run("for domains for which we don't have bridges", func(t *testing.T) {
p := &bridgesPolicyV2{}

tactics := p.LookupTactics(context.Background(), "www.example.com", "443")

var count int
for range tactics {
count++
}

if count != 0 {
t.Fatal("expected to see zero tactics")
}
})

t.Run("for the api.ooni.io domain", func(t *testing.T) {
p := &bridgesPolicyV2{}

tactics := p.LookupTactics(context.Background(), "api.ooni.io", "443")

var count int
for tactic := range tactics {
count++

// for each generated tactic, make sure we're getting the
// expected value for each of the fields

if tactic.Port != "443" {
t.Fatal("the port should always be 443")
}

if tactic.Address != "162.55.247.208" {
t.Fatal("the host should always be 162.55.247.208")
}

if tactic.InitialDelay != 0 {
t.Fatal("unexpected InitialDelay")
}

if tactic.SNI == "api.ooni.io" {
t.Fatal("we should not see the `api.ooni.io` SNI on the wire")
}

if tactic.VerifyHostname != "api.ooni.io" {
t.Fatal("the VerifyHostname field should always be like `api.ooni.io`")
}
}

if count <= 0 {
t.Fatal("expected to see at least one tactic")
}
})
}

func TestBridgesPolicy(t *testing.T) {
t.Run("for domains for which we don't have bridges and DNS failure", func(t *testing.T) {
expected := errors.New("mocked error")
Expand Down Expand Up @@ -202,7 +257,7 @@ func TestBridgesPolicy(t *testing.T) {
})

t.Run("for test helper domains", func(t *testing.T) {
for _, domain := range bridgesPolicyTestHelpersDomains {
for _, domain := range testHelpersDomains {
t.Run(domain, func(t *testing.T) {
expectedAddrs := []string{"164.92.180.7"}

Expand Down
2 changes: 0 additions & 2 deletions internal/enginenetx/dnspolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import (
// given resolver and the domain as the SNI.
//
// The zero value is invalid; please, init all MANDATORY fields.
//
// This policy uses an Happy-Eyeballs-like algorithm.
type dnsPolicy struct {
// Logger is the MANDATORY logger.
Logger model.Logger
Expand Down
13 changes: 13 additions & 0 deletions internal/enginenetx/httpsdialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ func (hd *httpsDialer) DialTLSContext(ctx context.Context, network string, endpo
return nil, err
}

// TODO(bassosimone): this code should be refactored using the same
// pattern used by `./internal/httpclientx` to perform attempts faster
// in case there is an initial early failure.

// TODO(bassosimone): the algorithm to filter and assign initial
// delays is broken because, if the DNS runs for more than one
// second, then several policies will immediately be due. We should
// probably use a better strategy that takes as the zero the time
// when the first dialing policy becomes available.

// We need a cancellable context to interrupt the tactics emitter early when we
// immediately get a valid response and we don't need to use other tactics.
ctx, cancel := context.WithCancel(ctx)
Expand Down Expand Up @@ -319,6 +329,9 @@ func (hd *httpsDialer) dialTLS(
return nil, err
}

// for debugging let the user know which tactic is ready
logger.Infof("tactic '%+v' is ready", tactic)

// tell the observer that we're starting
hd.stats.OnStarting(tactic)

Expand Down
133 changes: 133 additions & 0 deletions internal/enginenetx/mixpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package enginenetx

//
// Mix policies - ability of mixing from a primary policy and a fallback policy
// in a more flexible way than strictly falling back
//

import (
"context"

"github.com/ooni/probe-cli/v3/internal/optional"
)

// mixPolicyEitherOr reads from primary and only if primary does
// not return any tactic, then it reads from fallback.
type mixPolicyEitherOr struct {
// Primary is the primary policy.
Primary httpsDialerPolicy

// Fallback is the fallback policy.
Fallback httpsDialerPolicy
}

var _ httpsDialerPolicy = &mixPolicyEitherOr{}

// LookupTactics implements httpsDialerPolicy.
func (m *mixPolicyEitherOr) LookupTactics(ctx context.Context, domain string, port string) <-chan *httpsDialerTactic {
// create the output channel
output := make(chan *httpsDialerTactic)

go func() {
// make sure we eventually close the output channel
defer close(output)

// drain the primary policy
var count int
for tx := range m.Primary.LookupTactics(ctx, domain, port) {
output <- tx
count++
}

// if the primary worked, we're good
if count > 0 {
return
}

// drain the fallback policy
for tx := range m.Fallback.LookupTactics(ctx, domain, port) {
output <- tx
}
}()

return output
}

// mixPolicyInterleave interleaves policies by a given interleaving
// factor. Say the interleave factor is N, then we first read N tactics
// from the primary policy, then N from the fallback one, and we keep
// going on like this until we've read all the tactics from both.
type mixPolicyInterleave struct {
// Primary is the primary policy. We will read N from this
// policy first, then N from fallback, and so on.
Primary httpsDialerPolicy

// Fallback is the fallback policy.
Fallback httpsDialerPolicy

// Factor is the interleaving factor to use. If this value is
// zero, we behave like it was set to one.
Factor uint8
}

var _ httpsDialerPolicy = &mixPolicyInterleave{}

// LookupTactics implements httpsDialerPolicy.
func (p *mixPolicyInterleave) LookupTactics(ctx context.Context, domain string, port string) <-chan *httpsDialerTactic {
// create the output channel
output := make(chan *httpsDialerTactic)

go func() {
// make sure we eventually close the output channel
defer close(output)

// obtain the primary channel
primary := optional.Some(p.Primary.LookupTactics(ctx, domain, port))

// obtain the fallback channel
fallback := optional.Some(p.Fallback.LookupTactics(ctx, domain, port))

// loop until both channels are drained
for !primary.IsNone() || !fallback.IsNone() {
// take N from primary if possible
primary = p.maybeTakeN(primary, output)

// take N from secondary if possible
fallback = p.maybeTakeN(fallback, output)
}
}()

return output
}

// maybeTakeN takes N entries from input if it's not none. When input is not
// none and reading from it indicates EOF, this function returns none. Otherwise,
// it returns the same value given as input.
func (p *mixPolicyInterleave) maybeTakeN(
input optional.Value[<-chan *httpsDialerTactic],
output chan<- *httpsDialerTactic,
) optional.Value[<-chan *httpsDialerTactic] {
// make sure we've not already drained this channel
if !input.IsNone() {

// obtain the underlying channel
ch := input.Unwrap()

// take N entries from the channel
for idx := uint8(0); idx < max(1, p.Factor); idx++ {

// attempt to get the next tactic
tactic, good := <-ch

// handle the case where the channel has been drained
if !good {
return optional.None[<-chan *httpsDialerTactic]()
}

// emit the tactic
output <- tactic
}
}

return input
}
Loading
Loading