Skip to content

Commit

Permalink
feat(enginenetx): extend beacons policy for THs (ooni#1318)
Browse files Browse the repository at this point in the history
Provided that we get correct IP addresses, which is a big IF, we can try
to avoid using offending SNIs when using the THs.

Part of ooni/probe#2531
  • Loading branch information
bassosimone authored and Murphy-OrangeMud committed Feb 13, 2024
1 parent 8637821 commit 6a78f5e
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 16 deletions.
74 changes: 64 additions & 10 deletions internal/enginenetx/beaconspolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ func (p *beaconsPolicy) LookupTactics(ctx context.Context, domain, port string)

// emit beacons related tactics first which are empty if there are
// no beacons for the givend domain and port
for tx := range p.tacticsForDomain(domain, port) {
for tx := range p.beaconsTacticsForDomain(domain, port) {
tx.InitialDelay = happyEyeballsDelay(index)
index += 1
out <- tx
}

// now fallback to get more tactics (typically here the fallback
// uses the DNS and obtains some extra tactics)
for tx := range p.Fallback.LookupTactics(ctx, domain, port) {
//
// we wrap whatever the underlying policy returns us with some
// extra logic for better communicating with test helpers
for tx := range p.maybeRewriteTestHelpersTactics(p.Fallback.LookupTactics(ctx, domain, port)) {
tx.InitialDelay = happyEyeballsDelay(index)
index += 1
out <- tx
Expand All @@ -53,7 +56,55 @@ func (p *beaconsPolicy) LookupTactics(ctx context.Context, domain, port string)
return out
}

func (p *beaconsPolicy) tacticsForDomain(domain, port string) <-chan *httpsDialerTactic {
var beaconsPolicyTestHelpersDomains = []string{
"0.th.ooni.org",
"1.th.ooni.org",
"2.th.ooni.org",
"3.th.ooni.org",
"d33d1gs9kpq1c5.cloudfront.net",
}

// TODO(bassosimone): this would be slices.Contains when we'll use go1.21
func beaconsPolicySlicesContains(slice []string, value string) bool {
for _, entry := range slice {
if value == entry {
return true
}
}
return false
}

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

go func() {
defer close(out) // tell the parent when we're done

for tactic := range input {
// When we're not connecting to a TH, pass the policy down the chain unmodified
if !beaconsPolicySlicesContains(beaconsPolicyTestHelpersDomains, 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.beaconsDomainsInRandomOrder() {
out <- &httpsDialerTactic{
Address: tactic.Address,
InitialDelay: 0,
Port: tactic.Port,
SNI: sni,
VerifyHostname: tactic.VerifyHostname,
}
}
}
}()

return out
}

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

go func() {
Expand All @@ -64,14 +115,8 @@ func (p *beaconsPolicy) tacticsForDomain(domain, port string) <-chan *httpsDiale
return
}

snis := p.beaconsDomains()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Shuffle(len(snis), func(i, j int) {
snis[i], snis[j] = snis[j], snis[i]
})

for _, ipAddr := range p.beaconsAddrs() {
for _, sni := range snis {
for _, sni := range p.beaconsDomainsInRandomOrder() {
out <- &httpsDialerTactic{
Address: ipAddr,
InitialDelay: 0,
Expand All @@ -86,6 +131,15 @@ func (p *beaconsPolicy) tacticsForDomain(domain, port string) <-chan *httpsDiale
return out
}

func (p *beaconsPolicy) beaconsDomainsInRandomOrder() (out []string) {
out = p.beaconsDomains()
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 *beaconsPolicy) beaconsAddrs() (out []string) {
return append(
out,
Expand Down
57 changes: 51 additions & 6 deletions internal/enginenetx/beaconspolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func TestBeaconsPolicy(t *testing.T) {
t.Run("for domains for which we don't have beacons and DNS failure", func(t *testing.T) {
expected := errors.New("mocked error")
policy := &beaconsPolicy{
p := &beaconsPolicy{
Fallback: &dnsPolicy{
Logger: model.DiscardLogger,
Resolver: &mocks.Resolver{
Expand All @@ -24,7 +24,7 @@ func TestBeaconsPolicy(t *testing.T) {
}

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

var count int
for range tactics {
Expand All @@ -37,7 +37,7 @@ func TestBeaconsPolicy(t *testing.T) {
})

t.Run("for domains for which we don't have beacons and DNS success", func(t *testing.T) {
policy := &beaconsPolicy{
p := &beaconsPolicy{
Fallback: &dnsPolicy{
Logger: model.DiscardLogger,
Resolver: &mocks.Resolver{
Expand All @@ -49,7 +49,7 @@ func TestBeaconsPolicy(t *testing.T) {
}

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

var count int
for tactic := range tactics {
Expand Down Expand Up @@ -78,7 +78,7 @@ func TestBeaconsPolicy(t *testing.T) {

t.Run("for the api.ooni.io domain", func(t *testing.T) {
expected := errors.New("mocked error")
policy := &beaconsPolicy{
p := &beaconsPolicy{
Fallback: &dnsPolicy{
Logger: model.DiscardLogger,
Resolver: &mocks.Resolver{
Expand All @@ -90,7 +90,7 @@ func TestBeaconsPolicy(t *testing.T) {
}

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

var count int
for tactic := range tactics {
Expand All @@ -116,4 +116,49 @@ func TestBeaconsPolicy(t *testing.T) {
t.Fatal("expected to see at least one tactic")
}
})

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

p := &beaconsPolicy{
Fallback: &dnsPolicy{
Logger: model.DiscardLogger,
Resolver: &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
return expectedAddrs, nil
},
},
},
}

ctx := context.Background()
index := 0
for tactics := range p.LookupTactics(ctx, domain, "443") {

if tactics.Address != "164.92.180.7" {
t.Fatal("unexpected .Address")
}

if tactics.InitialDelay != happyEyeballsDelay(index) {
t.Fatal("unexpected .InitialDelay")
}
index++

if tactics.Port != "443" {
t.Fatal("unexpected .Port")
}

if tactics.SNI == domain {
t.Fatal("unexpected .Domain")
}

if tactics.VerifyHostname != domain {
t.Fatal("unexpected .VerifyHostname")
}
}
})
}
})
}

0 comments on commit 6a78f5e

Please sign in to comment.