Skip to content

Commit

Permalink
feat(webconnectivityqa): start adapting test cases with redirects (#1240
Browse files Browse the repository at this point in the history
)

## Checklist

- [x] I have read the [contribution
guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md)
- [x] reference issue for this pull request:
ooni/probe#1803
- [x] if you changed anything related to how experiments work and you
need to reflect these changes in the ooni/spec repository, please link
to the related ooni/spec pull request: N/A
- [x] if you changed code inside an experiment, make sure you bump its
version number: N/A

## Description

This diff starts adapting from QA/webconnectivity.py some of the test
cases involding errors happening during HTTP redirects.

I am pleased to see that we've discovered LTE bugs thanks to these new
test cases... well, let's say "pleased".
  • Loading branch information
bassosimone committed Sep 5, 2023
1 parent a379ecd commit 39ee1ca
Show file tree
Hide file tree
Showing 11 changed files with 490 additions and 102 deletions.
99 changes: 0 additions & 99 deletions QA/webconnectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,102 +47,6 @@ def assert_status_flags_are(ooni_exe, tk, desired):
assert tk["x_status"] == desired


def webconnectivity_http_connection_refused_with_consistent_dns(ooni_exe, outfile):
"""Test case where there's TCP/IP blocking w/ consistent DNS that occurs
while we're following the chain of redirects."""
# We use a bit.ly link redirecting to nexa.polito.it. We block the IP address
# used by nexa.polito.it. So the error should happen in the redirect chain.
ip = socket.gethostbyname("nexa.polito.it")
args = [
"-iptables-reset-ip",
ip,
]
tk = execute_jafar_and_return_validated_test_keys(
ooni_exe,
outfile,
"-i https://bit.ly/3h9EJR3 web_connectivity",
"webconnectivity_http_connection_refused_with_consistent_dns",
args,
)
assert tk["dns_experiment_failure"] == None
assert tk["dns_consistency"] == "consistent"
assert tk["control_failure"] == None
assert tk["http_experiment_failure"] == "connection_refused"
assert tk["body_length_match"] == None
assert tk["body_proportion"] == 0
assert tk["status_code_match"] == None
assert tk["headers_match"] == None
assert tk["title_match"] == None
assert tk["blocking"] == "http-failure"
assert tk["accessible"] == False
assert_status_flags_are(ooni_exe, tk, 8320)


def webconnectivity_http_connection_reset_with_consistent_dns(ooni_exe, outfile):
"""Test case where there's RST-based blocking blocking w/ consistent DNS that
occurs while we're following the chain of redirects."""
# We use a bit.ly link redirecting to nexa.polito.it. We block the Host header
# used for nexa.polito.it. So the error should happen in the redirect chain.
args = [
"-iptables-reset-keyword",
"Host: nexa",
]
tk = execute_jafar_and_return_validated_test_keys(
ooni_exe,
outfile,
"-i https://bit.ly/3h9EJR3 web_connectivity",
"webconnectivity_http_connection_reset_with_consistent_dns",
args,
)
assert tk["dns_experiment_failure"] == None
assert tk["dns_consistency"] == "consistent"
assert tk["control_failure"] == None
assert tk["http_experiment_failure"] == "connection_reset"
assert tk["body_length_match"] == None
assert tk["body_proportion"] == 0
assert tk["status_code_match"] == None
assert tk["headers_match"] == None
assert tk["title_match"] == None
assert tk["blocking"] == "http-failure"
assert tk["accessible"] == False
assert_status_flags_are(ooni_exe, tk, 8448)


def webconnectivity_http_nxdomain_with_consistent_dns(ooni_exe, outfile):
"""Test case where there's a redirection and the redirected request cannot
continue because a NXDOMAIN error occurs."""
# We use a bit.ly link redirecting to nexa.polito.it. We block the DNS request
# for nexa.polito.it. So the error should happen in the redirect chain.
args = [
"-iptables-hijack-dns-to",
"127.0.0.1:53",
"-dns-proxy-block",
"nexa.polito.it",
]
tk = execute_jafar_and_return_validated_test_keys(
ooni_exe,
outfile,
"-i https://bit.ly/3h9EJR3 web_connectivity",
"webconnectivity_http_nxdomain_with_consistent_dns",
args,
)
assert tk["dns_experiment_failure"] == None
assert tk["dns_consistency"] == "consistent"
assert tk["control_failure"] == None
assert (
tk["http_experiment_failure"] == "dns_nxdomain_error" # miniooni
or tk["http_experiment_failure"] == "dns_lookup_error" # MK
)
assert tk["body_length_match"] == None
assert tk["body_proportion"] == 0
assert tk["status_code_match"] == None
assert tk["headers_match"] == None
assert tk["title_match"] == None
assert tk["blocking"] == "dns"
assert tk["accessible"] == False
assert_status_flags_are(ooni_exe, tk, 8224)


def webconnectivity_http_eof_error_with_consistent_dns(ooni_exe, outfile):
"""Test case where there's a redirection and the redirected request cannot
continue because an eof_error error occurs."""
Expand Down Expand Up @@ -496,9 +400,6 @@ def main():
outfile = "webconnectivity.jsonl"
ooni_exe = sys.argv[1]
tests = [
webconnectivity_http_connection_refused_with_consistent_dns,
webconnectivity_http_connection_reset_with_consistent_dns,
webconnectivity_http_nxdomain_with_consistent_dns,
webconnectivity_http_eof_error_with_consistent_dns,
webconnectivity_http_generic_timeout_error_with_consistent_dns,
webconnectivity_http_connection_reset_with_inconsistent_dns,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1
github.com/montanaflynn/stats v0.7.1
github.com/ooni/go-libtor v1.1.8
github.com/ooni/netem v0.0.0-20230825114658-42ca83ac5cbc
github.com/ooni/netem v0.0.0-20230905212743-4889f9501fcf
github.com/ooni/oocrypto v0.5.3
github.com/ooni/oohttp v0.6.3
github.com/ooni/probe-assets v0.18.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
github.com/ooni/netem v0.0.0-20230825114658-42ca83ac5cbc h1:SPkk1q5A/3n0KmVVp1h0BcMZCPYqlgQOANAOnzuNeqI=
github.com/ooni/netem v0.0.0-20230825114658-42ca83ac5cbc/go.mod h1:3LJOzTIu2O4ADDJN2ILG4ViJOqyH/u9fKY8QT2Rma8Y=
github.com/ooni/netem v0.0.0-20230905212743-4889f9501fcf h1:dkrCLZASi2HCcAT1TLNFX+EpONGEeYt5VIZKj88aVZU=
github.com/ooni/netem v0.0.0-20230905212743-4889f9501fcf/go.mod h1:3LJOzTIu2O4ADDJN2ILG4ViJOqyH/u9fKY8QT2Rma8Y=
github.com/ooni/oocrypto v0.5.3 h1:CAb0Ze6q/EWD1PRGl9KqpzMfkut4O3XMaiKYsyxrWOs=
github.com/ooni/oocrypto v0.5.3/go.mod h1:HjEQ5pQBl6btcWgAsKKq1tFo8CfBrZu63C/vPAUGIDk=
github.com/ooni/oohttp v0.6.3 h1:MHydpeAPU/LSDSI/hIFJwZm4afBhd2Yo+rNxxFdeMCY=
Expand Down
52 changes: 52 additions & 0 deletions internal/experiment/webconnectivityqa/dnshijacking_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package webconnectivityqa

import (
"context"
"testing"

"github.com/apex/log"
"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/netemx"
"github.com/ooni/probe-cli/v3/internal/netxlite"
)

func TestDNSHijackingTestCases(t *testing.T) {
testcases := []*TestCase{
dnsHijackingToProxyWithHTTPURL(),
dnsHijackingToProxyWithHTTPSURL(),
}

for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
env := netemx.MustNewScenario(netemx.InternetScenario)
tc.Configure(env)

env.Do(func() {
expect := []string{netemx.ISPProxyAddress}

t.Run("with stdlib resolver", func(t *testing.T) {
reso := netxlite.NewStdlibResolver(log.Log)
addrs, err := reso.LookupHost(context.Background(), "www.example.com")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expect, addrs); diff != "" {
t.Fatal(diff)
}
})

t.Run("with UDP resolver", func(t *testing.T) {
d := netxlite.NewDialerWithoutResolver(log.Log)
reso := netxlite.NewParallelUDPResolver(log.Log, d, "8.8.8.8:53")
addrs, err := reso.LookupHost(context.Background(), "www.example.com")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expect, addrs); diff != "" {
t.Fatal(diff)
}
})
})
})
}
}
190 changes: 190 additions & 0 deletions internal/experiment/webconnectivityqa/redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package webconnectivityqa

import (
"github.com/apex/log"
"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/netemx"
)

// redirectWithConsistentDNSAndThenConnectionRefusedForHTTP is a scenario where the redirect
// works but then there's connection refused for an HTTP URL.
func redirectWithConsistentDNSAndThenConnectionRefusedForHTTP() *TestCase {
return &TestCase{
Name: "redirectWithConsistentDNSAndThenConnectionRefusedForHTTP",
Flags: TestCaseFlagNoLTE, // BUG: LTE thinks this website is accessible (WTF?!)
Input: "https://bit.ly/32447",
Configure: func(env *netemx.QAEnv) {

// make sure we cannot connect to the example domain on port 80
env.DPIEngine().AddRule(&netem.DPICloseConnectionForServerEndpoint{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 80,
})

// make sure we cannot connect to the example domain on port 443
env.DPIEngine().AddRule(&netem.DPICloseConnectionForServerEndpoint{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 443,
})

},
ExpectErr: false,
ExpectTestKeys: &testKeys{
DNSExperimentFailure: nil,
DNSConsistency: "consistent",
HTTPExperimentFailure: "connection_refused",
XStatus: 8320, // StatusExperimentHTTP | StatusAnomalyConnect
XDNSFlags: 0,
XBlockingFlags: 32, // analysisFlagSuccess
Accessible: false,
Blocking: "http-failure",
},
}
}

// redirectWithConsistentDNSAndThenConnectionRefusedForHTTPS is a scenario where the redirect
// works but then there's connection refused for an HTTPS URL.
func redirectWithConsistentDNSAndThenConnectionRefusedForHTTPS() *TestCase {
return &TestCase{
Name: "redirectWithConsistentDNSAndThenConnectionRefusedForHTTPS",
Flags: TestCaseFlagNoLTE, // BUG: LTE thinks this website is accessible (WTF?!)
Input: "https://bit.ly/21645",
Configure: func(env *netemx.QAEnv) {

// make sure we cannot connect to the example domain on port 80
env.DPIEngine().AddRule(&netem.DPICloseConnectionForServerEndpoint{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 80,
})

// make sure we cannot connect to the example domain on port 443
env.DPIEngine().AddRule(&netem.DPICloseConnectionForServerEndpoint{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 443,
})

},
ExpectErr: false,
ExpectTestKeys: &testKeys{
DNSExperimentFailure: nil,
DNSConsistency: "consistent",
HTTPExperimentFailure: "connection_refused",
XStatus: 8320, // StatusExperimentHTTP | StatusAnomalyConnect
XDNSFlags: 0,
XBlockingFlags: 32, // analysisFlagSuccess
Accessible: false,
Blocking: "http-failure",
},
}
}

// redirectWithConsistentDNSAndThenConnectionResetForHTTP is a scenario where the redirect
// works but then there's connection refused for an HTTP URL.
func redirectWithConsistentDNSAndThenConnectionResetForHTTP() *TestCase {
return &TestCase{
Name: "redirectWithConsistentDNSAndThenConnectionResetForHTTP",
Flags: 0,
Input: "https://bit.ly/32447",
Configure: func(env *netemx.QAEnv) {

// make sure we cannot HTTP round trip
env.DPIEngine().AddRule(&netem.DPIResetTrafficForString{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 80,
String: "www.example.com",
})

// make sure we cannot TLS handshake
env.DPIEngine().AddRule(&netem.DPIResetTrafficForTLSSNI{
Logger: log.Log,
SNI: "www.example.com",
})

},
ExpectErr: false,
ExpectTestKeys: &testKeys{
DNSExperimentFailure: nil,
DNSConsistency: "consistent",
HTTPExperimentFailure: "connection_reset",
XStatus: 8448, // StatusExperimentHTTP | StatusAnomalyReadWrite
XDNSFlags: 0,
XBlockingFlags: 8, // analysisFlagHTTPBlocking
Accessible: false,
Blocking: "http-failure",
},
}
}

// redirectWithConsistentDNSAndThenConnectionResetForHTTPS is a scenario where the redirect
// works but then there's connection refused for an HTTPS URL.
func redirectWithConsistentDNSAndThenConnectionResetForHTTPS() *TestCase {
return &TestCase{
Name: "redirectWithConsistentDNSAndThenConnectionResetForHTTPS",
Flags: TestCaseFlagNoLTE, // BUG: LTE thinks this website is accessible (WTF?!)
Input: "https://bit.ly/21645",
Configure: func(env *netemx.QAEnv) {

// make sure we cannot HTTP round trip
env.DPIEngine().AddRule(&netem.DPIResetTrafficForString{
Logger: log.Log,
ServerIPAddress: netemx.AddressWwwExampleCom,
ServerPort: 80,
String: "www.example.com",
})

// make sure we cannot TLS handshake
env.DPIEngine().AddRule(&netem.DPIResetTrafficForTLSSNI{
Logger: log.Log,
SNI: "www.example.com",
})

},
ExpectErr: false,
ExpectTestKeys: &testKeys{
DNSExperimentFailure: nil,
DNSConsistency: "consistent",
HTTPExperimentFailure: "connection_reset",
XStatus: 8448, // StatusExperimentHTTP | StatusAnomalyReadWrite
XDNSFlags: 0,
XBlockingFlags: 8, // analysisFlagHTTPBlocking
Accessible: false,
Blocking: "http-failure",
},
}
}

// redirectWithConsistentDNSAndThenNXDOMAIN is a scenario where the redirect
// works but then there's NXDOMAIN for the URL's domain
func redirectWithConsistentDNSAndThenNXDOMAIN() *TestCase {
return &TestCase{
Name: "redirectWithConsistentDNSAndThenNXDOMAIN",
Flags: TestCaseFlagNoLTE, // BUG: LTE thinks this website is accessible (WTF?!)
Input: "https://bit.ly/21645",
Configure: func(env *netemx.QAEnv) {

// Empty addresses cause NXDOMAIN
env.DPIEngine().AddRule(&netem.DPISpoofDNSResponse{
Addresses: []string{},
Logger: env.Logger(),
Domain: "www.example.com",
})

},
ExpectErr: false,
ExpectTestKeys: &testKeys{
DNSExperimentFailure: nil,
DNSConsistency: "consistent",
HTTPExperimentFailure: "dns_nxdomain_error",
XStatus: 8224, // StatusExperimentHTTP | StatusAnomalyDNS
XDNSFlags: 0,
XBlockingFlags: 8, // analysisFlagHTTPBlocking
Accessible: false,
Blocking: "dns",
},
}
}
Loading

0 comments on commit 39ee1ca

Please sign in to comment.