├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── routine-sprint-releases.md └── workflows │ ├── add_issues_to_project.yml │ ├── alltests.yml │ ├── android.yml │ ├── build_docs.yml │ ├── checks.yml │ ├── codeql-analysis.yml │ ├── coverage.yml │ ├── debianrepo.yml │ ├── generate.yml │ ├── go1.22.yml │ ├── gobash.yml │ ├── gosec.yml │ ├── ios.yml │ ├── libtorlinux.yml │ ├── linux.yml │ ├── macos.yml │ ├── netxlite.yml │ ├── oohelperd.yml │ ├── tarball.yml │ └── windows.yml ├── .gitignore ├── .vscode ├── .gitignore └── settings.json ├── CDEPS ├── README.md ├── libevent │ ├── 000.patch │ ├── 001.patch │ └── 002.patch ├── openssl │ ├── 000.patch │ └── 001.patch ├── tor │ ├── 000.patch │ ├── 001.patch │ ├── 002.patch │ ├── 003.patch │ └── 004.patch └── zlib │ └── 000.patch ├── CLI ├── .gitignore ├── README.md └── check-go-version ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DESIGN.md ├── Dockerfile.oonith ├── E2E ├── .gitignore ├── README.md ├── debian.bash ├── miniooni.bash └── ooniprobe.bash ├── GOCACHE ├── .gitignore └── README.md ├── GOVERSION ├── LICENSE ├── MOBILE ├── README.md ├── android │ ├── .gitignore │ ├── README.md │ ├── adbinstall │ ├── createpom │ ├── ensure │ ├── home │ ├── newkeystore │ ├── setup │ ├── sign │ └── template.pom └── ios │ ├── .gitignore │ ├── README.md │ ├── check-xcode-version │ ├── createpodspecs │ ├── createpodspecs_test │ ├── libcrypto-template.podspec │ ├── libevent-template.podspec │ ├── libssl-template.podspec │ ├── libtor-template.podspec │ ├── libz-template.podspec │ ├── make-extra-frameworks │ ├── oonimkall-template.podspec │ └── zipframeworks ├── MONOREPO ├── README.md ├── repo │ └── .gitignore ├── tools │ ├── .gitignore │ ├── gitconfig.bash │ ├── gitx │ ├── info │ ├── libcore.bash │ ├── libgit.bash │ ├── local.example.bash │ ├── setupandroid │ ├── setupshfmt │ └── vscode └── w │ ├── build-android-stable.bash │ ├── build-android-with-cli.bash │ └── run-desktop-with-cli.bash ├── Makefile ├── NDKVERSION ├── NOTICE.md ├── PULL_REQUEST_TEMPLATE.md ├── Readme.md ├── cmd ├── README.md └── ooniprobe │ ├── internal │ ├── autorun │ │ ├── autorun.go │ │ └── autorun_darwin.go │ ├── cli │ │ ├── app │ │ │ └── app.go │ │ ├── autorun │ │ │ └── autorun.go │ │ ├── geoip │ │ │ ├── geoip.go │ │ │ └── geoip_test.go │ │ ├── info │ │ │ ├── info.go │ │ │ └── info_test.go │ │ ├── list │ │ │ └── list.go │ │ ├── onboard │ │ │ └── onboard.go │ │ ├── reset │ │ │ └── reset.go │ │ ├── rm │ │ │ └── rm.go │ │ ├── root │ │ │ └── root.go │ │ ├── run │ │ │ └── run.go │ │ ├── show │ │ │ └── show.go │ │ ├── upload │ │ │ └── upload.go │ │ └── version │ │ │ └── version.go │ ├── config │ │ ├── parser.go │ │ ├── parser_test.go │ │ ├── settings.go │ │ └── testdata │ │ │ ├── config-v0.json │ │ │ └── valid-config.json │ ├── log │ │ └── handlers │ │ │ ├── batch │ │ │ └── batch.go │ │ │ ├── cli │ │ │ ├── cli.go │ │ │ ├── measurements.go │ │ │ ├── progress │ │ │ │ └── progress.go │ │ │ └── results.go │ │ │ └── syslog │ │ │ ├── syslog.c │ │ │ └── syslog.go │ ├── nettests │ │ ├── dash.go │ │ ├── dnscheck.go │ │ ├── echcheck.go │ │ ├── facebook_messenger.go │ │ ├── groups.go │ │ ├── http_header_field_manipulation.go │ │ ├── http_invalid_request_line.go │ │ ├── ndt.go │ │ ├── nettests.go │ │ ├── nettests_test.go │ │ ├── openvpn.go │ │ ├── psiphon.go │ │ ├── riseupvpn.go │ │ ├── run.go │ │ ├── signal.go │ │ ├── stunreachability.go │ │ ├── telegram.go │ │ ├── tor.go │ │ ├── torsf.go │ │ ├── vanillator.go │ │ ├── web_connectivity.go │ │ └── whatsapp.go │ ├── ooni │ │ ├── default-config.json │ │ ├── ooni.go │ │ └── ooni_test.go │ ├── oonitest │ │ └── oonitest.go │ ├── output │ │ └── output.go │ └── utils │ │ ├── homedir │ │ └── homedir.go │ │ ├── paths.go │ │ ├── util_test.go │ │ └── utils.go │ ├── main.go │ └── testdata │ └── testing-config.json ├── docs ├── OONIProbeLegacyCompatibility.md ├── README.md ├── design │ ├── README.md │ ├── dd-001-oonimkall.md │ ├── dd-002-netx.md │ ├── dd-003-step-by-step.md │ ├── dd-004-minioonirunv2.md │ ├── dd-005-dslx.md │ ├── dd-006-probeservices.md │ ├── dd-007-throttling.md │ ├── dd-008-richer-input.md │ └── img │ │ ├── git-probe-cli-change-histogram.png │ │ └── git-probe-cli-netx-deps.png ├── logo.png └── releasing.md ├── go.mod ├── go.sum ├── internal ├── README.md ├── bytecounter │ ├── conn.go │ ├── conn_test.go │ ├── context.go │ ├── context_test.go │ ├── counter.go │ ├── counter_test.go │ ├── dialer.go │ ├── dialer_test.go │ ├── doc.go │ ├── http.go │ ├── http_test.go │ ├── resolver.go │ └── resolver_test.go ├── checkincache │ ├── checkincache.go │ └── checkincache_test.go ├── cmd │ ├── README.md │ ├── apitool │ │ ├── README.md │ │ ├── main.go │ │ └── main_test.go │ ├── buildtool │ │ ├── android.go │ │ ├── android_test.go │ │ ├── builddeps.go │ │ ├── cbuildenv.go │ │ ├── cbuildenv_test.go │ │ ├── cdeps.go │ │ ├── cdepslibevent.go │ │ ├── cdepsopenssl.go │ │ ├── cdepstor.go │ │ ├── cdepszlib.go │ │ ├── darwin.go │ │ ├── darwin_test.go │ │ ├── generic.go │ │ ├── generic_test.go │ │ ├── golang.go │ │ ├── golang_test.go │ │ ├── gomobile.go │ │ ├── internal │ │ │ ├── buildtoolmodel │ │ │ │ └── buildtoolmodel.go │ │ │ └── buildtooltest │ │ │ │ ├── buildtooltest.go │ │ │ │ └── buildtooltest_test.go │ │ ├── ios.go │ │ ├── ios_test.go │ │ ├── linux.go │ │ ├── linuxcdeps.go │ │ ├── linuxcdeps_test.go │ │ ├── linuxdocker.go │ │ ├── linuxdocker_test.go │ │ ├── linuxstatic.go │ │ ├── linuxstatic_test.go │ │ ├── main.go │ │ ├── oohelperd.go │ │ ├── oohelperd_test.go │ │ ├── product.go │ │ ├── psiphon.go │ │ ├── testdata │ │ │ └── GOVERSION │ │ ├── utils.go │ │ ├── windows.go │ │ └── windows_test.go │ ├── e2epostprocess │ │ └── main.go │ ├── gardener │ │ ├── ARCHITECTURE.md │ │ ├── DESIGN.md │ │ ├── README.md │ │ ├── internal │ │ │ ├── aggregationapi │ │ │ │ ├── aggregationapi.go │ │ │ │ └── aggregationapi_test.go │ │ │ ├── dnsfix │ │ │ │ ├── dnsfix.go │ │ │ │ ├── dnsfix_test.go │ │ │ │ └── testdata │ │ │ │ │ ├── dnsreport.csv │ │ │ │ │ └── lists │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── it-expected.csv │ │ │ │ │ └── it.csv │ │ │ ├── dnsreport │ │ │ │ ├── dnsreport.go │ │ │ │ ├── dnsreport_test.go │ │ │ │ └── testdata │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── dnsreport-expected.csv │ │ │ │ │ └── repo │ │ │ │ │ └── lists │ │ │ │ │ └── it.csv │ │ │ ├── sync │ │ │ │ ├── sync.go │ │ │ │ ├── sync_test.go │ │ │ │ └── testdata │ │ │ │ │ └── .gitignore │ │ │ └── testlists │ │ │ │ ├── testdata │ │ │ │ ├── .gitignore │ │ │ │ ├── 00-LEGEND-category_codes.csv │ │ │ │ ├── global.csv │ │ │ │ ├── it-expected.csv │ │ │ │ ├── it.csv │ │ │ │ └── official │ │ │ │ │ └── it │ │ │ │ │ ├── aams.csv │ │ │ │ │ └── bofh.csv │ │ │ │ ├── testlists.go │ │ │ │ └── testlists_test.go │ │ └── main.go │ ├── getresources │ │ └── getresources.go │ ├── ghgen │ │ ├── android.go │ │ ├── config.go │ │ ├── ios.go │ │ ├── linux.go │ │ ├── macos.go │ │ ├── main.go │ │ ├── utils.go │ │ └── windows.go │ ├── miniooni │ │ ├── .gitignore │ │ ├── README.md │ │ ├── consent.go │ │ ├── javascript.go │ │ ├── main.go │ │ ├── main_test.go │ │ ├── oonirun.go │ │ ├── runx.go │ │ ├── session.go │ │ └── utils.go │ ├── minipipeline │ │ ├── main.go │ │ ├── main_test.go │ │ └── testdata │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ ├── oohelper │ │ ├── README.md │ │ ├── internal │ │ │ ├── client.go │ │ │ ├── client_test.go │ │ │ └── fake_test.go │ │ ├── oohelper.go │ │ └── oohelper_test.go │ ├── oohelperd │ │ ├── README.md │ │ ├── main.go │ │ ├── main_test.go │ │ ├── replace.go │ │ └── replace_test.go │ ├── oonireport │ │ ├── oonireport.go │ │ ├── oonireport_test.go │ │ └── testdata │ │ │ ├── noentries.json │ │ │ └── testmeasurement.json │ ├── ooporthelper │ │ ├── README.md │ │ ├── main.go │ │ ├── main_test.go │ │ └── ports.go │ ├── printversion │ │ └── main.go │ ├── qatool │ │ ├── main.go │ │ └── main_test.go │ └── tinyjafar │ │ ├── README.md │ │ ├── main.go │ │ └── main_test.go ├── database │ ├── actions.go │ ├── actions_test.go │ ├── database.go │ ├── database_test.go │ ├── migrations │ │ ├── 1_create_msmt_results.sql │ │ ├── 2_single_msmt_file.sql │ │ └── 3_results_is_uploaded.sql │ ├── models.go │ └── utils.go ├── engine │ ├── .gitignore │ ├── allexperiments.go │ ├── doc.go │ ├── experiment.go │ ├── experiment_integration_test.go │ ├── experiment_test.go │ ├── experimentbuilder.go │ ├── experimentbuilder_test.go │ ├── inputloader_integration_test.go │ ├── savemeasurement.go │ ├── savemeasurement_test.go │ ├── session.go │ ├── session_integration_test.go │ ├── session_internal_test.go │ ├── session_nopsiphon.go │ ├── session_nopsiphon_test.go │ ├── session_psiphon.go │ ├── session_psiphon_test.go │ └── testdata │ │ ├── .gitignore │ │ └── collector-expected.jsonl ├── enginelocate │ ├── cloudflare.go │ ├── cloudflare_test.go │ ├── geolocate.go │ ├── geolocate_test.go │ ├── invalid_test.go │ ├── iplookup.go │ ├── iplookup_test.go │ ├── mmdblookup.go │ ├── resolverlookup.go │ ├── resolverlookup_test.go │ ├── stun.go │ ├── stun_test.go │ ├── ubuntu.go │ └── ubuntu_test.go ├── enginenetx │ ├── DESIGN.md │ ├── bridgespolicy.go │ ├── bridgespolicy_test.go │ ├── dnspolicy.go │ ├── dnspolicy_test.go │ ├── doc.go │ ├── filter.go │ ├── filter_test.go │ ├── happyeyeballs.go │ ├── happyeyeballs_test.go │ ├── httpsdialer.go │ ├── httpsdialer_test.go │ ├── mixpolicy.go │ ├── mixpolicy_test.go │ ├── mockspolicy_test.go │ ├── network.go │ ├── network_internal_test.go │ ├── network_test.go │ ├── nullpolicy.go │ ├── nullpolicy_test.go │ ├── statsmanager.go │ ├── statsmanager_test.go │ ├── statspolicy.go │ ├── statspolicy_test.go │ ├── stream.go │ ├── stream_test.go │ ├── testhelperspolicy.go │ ├── testhelperspolicy_test.go │ ├── userpolicy.go │ └── userpolicy_test.go ├── engineresolver │ ├── doc.go │ ├── errwrapper.go │ ├── errwrapper_test.go │ ├── factory.go │ ├── factory_test.go │ ├── integration_test.go │ ├── jsoncodec.go │ ├── jsoncodec_test.go │ ├── lookup.go │ ├── lookup_test.go │ ├── resolver.go │ ├── resolver_test.go │ ├── resolvermaker.go │ ├── resolvermaker_test.go │ ├── state.go │ └── state_test.go ├── erroror │ └── erroror.go ├── experiment │ ├── README.md │ ├── dash │ │ ├── collect.go │ │ ├── collect_test.go │ │ ├── dependencies.go │ │ ├── dependencies_test.go │ │ ├── doc.go │ │ ├── download.go │ │ ├── download_test.go │ │ ├── locate.go │ │ ├── measurer.go │ │ ├── measurer_test.go │ │ ├── model.go │ │ ├── negotiate.go │ │ ├── negotiate_test.go │ │ ├── runner.go │ │ └── runner_test.go │ ├── dnscheck │ │ ├── dnscheck.go │ │ ├── dnscheck_test.go │ │ ├── richerinput.go │ │ ├── richerinput_test.go │ │ └── testdata │ │ │ └── input.txt │ ├── dnsping │ │ ├── dnsping.go │ │ ├── dnsping_test.go │ │ ├── summarize.go │ │ ├── summarize_test.go │ │ └── testkeys.go │ ├── echcheck │ │ ├── config.go │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── generate.go │ │ ├── generate_test.go │ │ ├── handshake.go │ │ ├── handshake_test.go │ │ ├── measure.go │ │ ├── measure_test.go │ │ └── verbatim.go │ ├── example │ │ ├── example.go │ │ └── example_test.go │ ├── fbmessenger │ │ ├── fbmessenger.go │ │ └── fbmessenger_test.go │ ├── hhfm │ │ ├── fake_test.go │ │ ├── hhfm.go │ │ ├── hhfm_internal_test.go │ │ └── hhfm_test.go │ ├── hirl │ │ ├── fake_test.go │ │ ├── hirl.go │ │ └── hirl_test.go │ ├── httphostheader │ │ ├── httphostheader.go │ │ └── httphostheader_test.go │ ├── ndt7 │ │ ├── callback.go │ │ ├── dial.go │ │ ├── dial_test.go │ │ ├── doc.go │ │ ├── download.go │ │ ├── download_test.go │ │ ├── ndt7.go │ │ ├── ndt7_test.go │ │ ├── param.go │ │ ├── spec.go │ │ ├── upload.go │ │ ├── upload_test.go │ │ ├── wsconn.go │ │ └── wsconn_test.go │ ├── openvpn │ │ ├── endpoint.go │ │ ├── endpoint_test.go │ │ ├── openvpn.go │ │ ├── openvpn_test.go │ │ ├── richerinput.go │ │ ├── richerinput_test.go │ │ ├── targets.go │ │ └── targets_test.go │ ├── portfiltering │ │ ├── config.go │ │ ├── config_test.go │ │ ├── doc.go │ │ ├── measurer.go │ │ ├── measurer_test.go │ │ ├── ports.go │ │ ├── tcpconnect.go │ │ └── testkeys.go │ ├── psiphon │ │ ├── psiphon.go │ │ └── psiphon_test.go │ ├── quicping │ │ ├── crypto.go │ │ ├── quic.go │ │ ├── quicping.go │ │ └── quicping_test.go │ ├── riseupvpn │ │ ├── riseupvpn.go │ │ └── riseupvpn_test.go │ ├── signal │ │ ├── signal.go │ │ └── signal_test.go │ ├── simplequicping │ │ ├── simplequicping.go │ │ └── simplequicping_test.go │ ├── sniblocking │ │ ├── sniblocking.go │ │ └── sniblocking_test.go │ ├── stunreachability │ │ ├── fake_test.go │ │ ├── stunreachability.go │ │ └── stunreachability_test.go │ ├── tcpping │ │ ├── tcpping.go │ │ └── tcpping_test.go │ ├── telegram │ │ ├── telegram.go │ │ └── telegram_test.go │ ├── tlsmiddlebox │ │ ├── config.go │ │ ├── config_test.go │ │ ├── conn.go │ │ ├── conn_test.go │ │ ├── connect.go │ │ ├── dialer.go │ │ ├── dialer_test.go │ │ ├── dns.go │ │ ├── doc.go │ │ ├── measurer.go │ │ ├── measurer_test.go │ │ ├── syscall_cgo.go │ │ ├── syscall_otherwise.go │ │ ├── syscall_unix.go │ │ ├── syscall_windows.go │ │ ├── testkeys.go │ │ ├── trace.go │ │ ├── tracing.go │ │ ├── tracing_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── tlsping │ │ ├── tlsping.go │ │ └── tlsping_test.go │ ├── tlstool │ │ ├── internal │ │ │ ├── dialer.go │ │ │ ├── dialer_test.go │ │ │ ├── fake_test.go │ │ │ ├── internal.go │ │ │ ├── internal_test.go │ │ │ ├── splitter.go │ │ │ ├── splitter_test.go │ │ │ ├── writer.go │ │ │ └── writer_test.go │ │ ├── tlstool.go │ │ └── tlstool_test.go │ ├── tor │ │ ├── tor.go │ │ └── tor_test.go │ ├── torsf │ │ ├── .gitignore │ │ ├── integration_test.go │ │ ├── testdata │ │ │ ├── partial.log │ │ │ └── tor.log │ │ ├── torsf.go │ │ └── torsf_test.go │ ├── urlgetter │ │ ├── .gitignore │ │ ├── configurer.go │ │ ├── configurer_test.go │ │ ├── getter.go │ │ ├── getter_integration_test.go │ │ ├── getter_test.go │ │ ├── multi.go │ │ ├── multi_test.go │ │ ├── runner.go │ │ ├── runner_test.go │ │ ├── urlgetter.go │ │ └── urlgetter_test.go │ ├── vanillator │ │ ├── integration_test.go │ │ ├── testdata │ │ │ ├── partial.log │ │ │ └── tor.log │ │ ├── vanillator.go │ │ └── vanillator_test.go │ ├── webconnectivity │ │ ├── connects.go │ │ ├── connects_test.go │ │ ├── control.go │ │ ├── control_test.go │ │ ├── dnsanalysis.go │ │ ├── dnsanalysis_test.go │ │ ├── dnslookup.go │ │ ├── dnslookup_test.go │ │ ├── doc.go │ │ ├── endpoints.go │ │ ├── endpoints_test.go │ │ ├── httpanalysis.go │ │ ├── httpanalysis_test.go │ │ ├── httpget.go │ │ ├── httpget_test.go │ │ ├── internal │ │ │ ├── internal.go │ │ │ └── internal_test.go │ │ ├── qa_test.go │ │ ├── summary.go │ │ ├── summary_test.go │ │ ├── webconnectivity.go │ │ └── webconnectivity_test.go │ ├── webconnectivitylte │ │ ├── README.md │ │ ├── analysisclassic.go │ │ ├── analysisclassic_test.go │ │ ├── analysiscore.go │ │ ├── analysisext.go │ │ ├── analysishttpdiff.go │ │ ├── cleartextflow.go │ │ ├── cleartextflow_test.go │ │ ├── config.go │ │ ├── control.go │ │ ├── dnscache.go │ │ ├── dnsresolvers.go │ │ ├── dnswhoami.go │ │ ├── doc.go │ │ ├── httpredirect.go │ │ ├── httpredirect_test.go │ │ ├── idgenerator.go │ │ ├── ipfiltering.go │ │ ├── ipfiltering_test.go │ │ ├── measurer.go │ │ ├── priority.go │ │ ├── qa_test.go │ │ ├── redirects.go │ │ ├── redirects_test.go │ │ ├── secureflow.go │ │ ├── secureflow_test.go │ │ ├── summary.go │ │ ├── summary_test.go │ │ ├── tags.go │ │ ├── testdata │ │ │ └── 20230706183840.201925_PK_webconnectivity_19f5e0d803cbaea7.jsonc │ │ └── testkeys.go │ └── whatsapp │ │ ├── whatsapp.go │ │ └── whatsapp_test.go ├── experimentconfig │ ├── experimentconfig.go │ └── experimentconfig_test.go ├── experimentname │ ├── experimentname.go │ └── experimentname_test.go ├── feature │ └── psiphonfeat │ │ ├── doc.go │ │ ├── psiphon_enabled.go │ │ ├── psiphon_otherwise.go │ │ └── tunnel.go ├── flagx │ ├── stringarray.go │ └── stringarray_test.go ├── fsx │ ├── example_test.go │ ├── fsx.go │ ├── fsx_test.go │ └── testdata │ │ ├── .gitignore │ │ └── testfile.txt ├── geoipx │ ├── geoipx.go │ └── geoipx_test.go ├── httpclientx │ ├── DESIGN.md │ ├── config.go │ ├── config_test.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── getjson.go │ ├── getjson_test.go │ ├── getraw.go │ ├── getraw_test.go │ ├── getxml.go │ ├── getxml_test.go │ ├── httpclientx.go │ ├── httpclientx_test.go │ ├── nilsafety.go │ ├── nilsafety_test.go │ ├── overlapped.go │ ├── overlapped_test.go │ ├── postjson.go │ └── postjson_test.go ├── hujsonx │ ├── hujsonx.go │ └── hujsonx_test.go ├── humanize │ ├── humanize.go │ └── humanize_test.go ├── idnax │ ├── idnax.go │ └── idnax_test.go ├── inputparser │ ├── inputparser.go │ └── inputparser_test.go ├── kvstore │ ├── doc.go │ ├── example_test.go │ ├── fs.go │ ├── fs_test.go │ ├── memory.go │ └── memory_test.go ├── legacy │ ├── assetsdir │ │ ├── assetsdir.go │ │ └── assetsdir_test.go │ ├── kvstore2dir │ │ ├── kvstore2dir.go │ │ └── kvstore2dir_test.go │ ├── legacymodel │ │ ├── archival.go │ │ ├── archival_test.go │ │ └── doc.go │ ├── measurex │ │ ├── archival.go │ │ ├── db.go │ │ ├── dialer.go │ │ ├── dnsx.go │ │ ├── dnsx_test.go │ │ ├── doc.go │ │ ├── easy.go │ │ ├── endpoint.go │ │ ├── failure.go │ │ ├── http.go │ │ ├── logger.go │ │ ├── measurement.go │ │ ├── measurer.go │ │ ├── oddity.go │ │ ├── quic.go │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ ├── tls.go │ │ ├── tracing.go │ │ └── utils.go │ ├── mockable │ │ └── mockable.go │ ├── multierror │ │ ├── example_test.go │ │ ├── multierror.go │ │ └── multierror_test.go │ ├── netx │ │ ├── config.go │ │ ├── dialer.go │ │ ├── dnstransport.go │ │ ├── dnstransport_test.go │ │ ├── doc.go │ │ ├── http.go │ │ ├── http_test.go │ │ ├── integration_test.go │ │ ├── quic.go │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ ├── tls.go │ │ └── tls_test.go │ └── tracex │ │ ├── archival.go │ │ ├── archival_test.go │ │ ├── dialer.go │ │ ├── dialer_test.go │ │ ├── doc.go │ │ ├── event.go │ │ ├── event_test.go │ │ ├── http.go │ │ ├── http_test.go │ │ ├── quic.go │ │ ├── quic_test.go │ │ ├── resolver.go │ │ ├── resolver_test.go │ │ ├── saver.go │ │ ├── saver_test.go │ │ ├── tls.go │ │ └── tls_test.go ├── libooniengine │ ├── engine.go │ └── engine.h ├── libtor │ ├── android │ │ ├── 386 │ │ │ └── .gitignore │ │ ├── amd64 │ │ │ └── .gitignore │ │ ├── arm │ │ │ └── .gitignore │ │ └── arm64 │ │ │ └── .gitignore │ ├── enabled.go │ ├── enabled_test.go │ ├── fallback.go │ ├── iphoneos │ │ └── arm64 │ │ │ └── .gitignore │ ├── iphonesimulator │ │ ├── amd64 │ │ │ └── .gitignore │ │ └── arm64 │ │ │ └── .gitignore │ ├── linux │ │ └── amd64 │ │ │ └── .gitignore │ └── testdata │ │ └── .gitignore ├── logmodel │ └── logmodel.go ├── logx │ ├── doc.go │ ├── handler.go │ ├── handler_test.go │ ├── operation.go │ ├── operation_test.go │ ├── prefix.go │ ├── prefix_test.go │ ├── scrubber.go │ └── scrubber_test.go ├── measurexlite │ ├── bytecounting_test.go │ ├── conn.go │ ├── conn_test.go │ ├── dialer.go │ ├── dialer_test.go │ ├── dns.go │ ├── dns_test.go │ ├── doc.go │ ├── failure.go │ ├── failure_test.go │ ├── http.go │ ├── http_test.go │ ├── quic.go │ ├── quic_test.go │ ├── tls.go │ ├── tls_test.go │ ├── trace.go │ ├── trace_test.go │ ├── udp.go │ ├── udp_test.go │ ├── utls.go │ ├── utls_test.go │ ├── web.go │ └── web_test.go ├── memoryless │ ├── memoryless.go │ └── memoryless_test.go ├── minipipeline │ ├── analysis.go │ ├── analysis_test.go │ ├── classic.go │ ├── dnsdiff.go │ ├── doc.go │ ├── httpdiff.go │ ├── httpdiff_test.go │ ├── measurement.go │ ├── normalize.go │ ├── normalize_test.go │ ├── observation.go │ ├── observation_test.go │ ├── qa_test.go │ ├── set.go │ ├── set_test.go │ ├── sorting.go │ ├── sorting_test.go │ ├── testdata │ │ └── webconnectivity │ │ │ ├── generated │ │ │ ├── README.md │ │ │ ├── badSSLWithExpiredCertificate │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── badSSLWithUnknownAuthorityWithConsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── badSSLWithUnknownAuthorityWithInconsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── badSSLWithWrongServerName │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── cloudflareCAPTCHAWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── cloudflareCAPTCHAWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── controlFailureWithSuccessfulHTTPSWebsite │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── controlFailureWithSuccessfulHTTPWebsite │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsBlockingAndroidDNSCacheNoData │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsBlockingBOGON │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsBlockingNXDOMAIN │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsHijackingToLocalhostWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsHijackingToLocalhostWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsHijackingToProxyWithHTTPSURL │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── dnsHijackingToProxyWithHTTPURL │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── ghostDNSBlockingWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── ghostDNSBlockingWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── httpBlockingConnectionReset │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── httpDiffWithConsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── httpDiffWithInconsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── idnaWithoutCensorshipLowercase │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── idnaWithoutCensorshipWithFirstLetterUppercase │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── largeFileWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── largeFileWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── localhostWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── localhostWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithBrokenLocationForHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithBrokenLocationForHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenConnectionRefusedForHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenConnectionRefusedForHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenConnectionResetForHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenConnectionResetForHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenEOFForHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenEOFForHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenNXDOMAIN │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenTimeoutForHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithConsistentDNSAndThenTimeoutForHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithMoreThanTenRedirectsAndHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── redirectWithMoreThanTenRedirectsAndHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── successWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── successWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── tcpBlockingConnectTimeout │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── tcpBlockingConnectionRefusedWithInconsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── throttlingWithHTTP │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── throttlingWithHTTPS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── tlsBlockingConnectionResetWithConsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── tlsBlockingConnectionResetWithInconsistentDNS │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── websiteDownNXDOMAIN │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ ├── websiteDownNoAddrs │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ └── websiteDownTCPConnect │ │ │ │ ├── analysis.json │ │ │ │ ├── analysis_classic.json │ │ │ │ ├── measurement.json │ │ │ │ ├── observations.json │ │ │ │ └── observations_classic.json │ │ │ └── manual │ │ │ ├── 8844 │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ │ │ ├── README.md │ │ │ ├── dnsgoogle80 │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── command.txt │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ │ │ ├── firefoxcom │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ │ │ ├── issue-2456 │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ │ │ ├── noipv6 │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── command.txt │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ │ │ └── youtube │ │ │ ├── analysis.json │ │ │ ├── analysis_classic.json │ │ │ ├── command.txt │ │ │ ├── measurement.json │ │ │ ├── observations.json │ │ │ └── observations_classic.json │ ├── utils.go │ └── utils_test.go ├── mlablocatev2 │ ├── mlablocatev2.go │ └── mlablocatev2_test.go ├── mocks │ ├── addr.go │ ├── addr_test.go │ ├── database.go │ ├── database_test.go │ ├── dialer.go │ ├── dialer_test.go │ ├── dnsdecoder.go │ ├── dnsdecoder_test.go │ ├── dnsencoder.go │ ├── dnsencoder_test.go │ ├── dnsquery.go │ ├── dnsquery_test.go │ ├── dnsresponse.go │ ├── dnsresponse_test.go │ ├── dnstransport.go │ ├── dnstransport_test.go │ ├── doc.go │ ├── experiment.go │ ├── experiment_test.go │ ├── experimentbuilder.go │ ├── experimentbuilder_test.go │ ├── experimentinputprocessor.go │ ├── experimentinputprocessor_test.go │ ├── experimentmeasurer.go │ ├── experimentmeasurer_test.go │ ├── experimenttargetloader.go │ ├── experimenttargetloader_test.go │ ├── geoip.go │ ├── geoip_test.go │ ├── http.go │ ├── http3.go │ ├── http3_test.go │ ├── http_test.go │ ├── keyvaluestore.go │ ├── keyvaluestore_test.go │ ├── listener.go │ ├── listener_test.go │ ├── location.go │ ├── location_test.go │ ├── logger.go │ ├── logger_test.go │ ├── measuringnetwork.go │ ├── measuringnetwork_test.go │ ├── quic.go │ ├── quic_test.go │ ├── reader.go │ ├── reader_test.go │ ├── resolver.go │ ├── resolver_test.go │ ├── saver.go │ ├── saver_test.go │ ├── session.go │ ├── session_test.go │ ├── submitter.go │ ├── submitter_test.go │ ├── tls.go │ ├── tls_test.go │ ├── trace.go │ ├── trace_test.go │ ├── udplistener.go │ ├── udplistener_test.go │ ├── underlyingnetwork.go │ ├── underlyingnetwork_test.go │ ├── writer.go │ └── writer_test.go ├── model │ ├── README.md │ ├── archival.go │ ├── archival_test.go │ ├── database.go │ ├── doc.go │ ├── experiment.go │ ├── experiment_test.go │ ├── geoip.go │ ├── geoip_test.go │ ├── http.go │ ├── keyvaluestore.go │ ├── location.go │ ├── logger.go │ ├── logger_test.go │ ├── measurement.go │ ├── measurement_test.go │ ├── netx.go │ ├── ooapi.go │ ├── ooapi_test.go │ ├── runtype.go │ └── th.go ├── must │ ├── must.go │ ├── must_test.go │ └── testdata │ │ └── .gitignore ├── netemx │ ├── adapter.go │ ├── address.go │ ├── android.go │ ├── android_test.go │ ├── badssl.go │ ├── badssl_test.go │ ├── cloudflare.go │ ├── cloudflare_test.go │ ├── dnsoverhttps.go │ ├── dnsoverudp.go │ ├── dnsoverudp_test.go │ ├── doc.go │ ├── example_test.go │ ├── geoip.go │ ├── http.go │ ├── http3.go │ ├── http3_test.go │ ├── http_test.go │ ├── httpbin.go │ ├── httpbin_test.go │ ├── https.go │ ├── https_test.go │ ├── largefile.go │ ├── largetfile_test.go │ ├── netstack.go │ ├── ooapi.go │ ├── ooapi_test.go │ ├── oohelperd.go │ ├── oohelperd_test.go │ ├── qaenv.go │ ├── qaenv_test.go │ ├── scenario.go │ ├── tcpecho.go │ ├── tlsproxy.go │ ├── web.go │ ├── web_test.go │ ├── yandex.go │ └── yandex_test.go ├── netxlite │ ├── bogon.go │ ├── bogon_test.go │ ├── certifi.go │ ├── certifi_test.go │ ├── classify.go │ ├── classify_test.go │ ├── dialer.go │ ├── dialer_test.go │ ├── dnsdecoder.go │ ├── dnsdecoder_test.go │ ├── dnsencoder.go │ ├── dnsencoder_test.go │ ├── dnsovergetaddrinfo.go │ ├── dnsovergetaddrinfo_test.go │ ├── dnsoverhttps.go │ ├── dnsoverhttps_test.go │ ├── dnsovertcp.go │ ├── dnsovertcp_test.go │ ├── dnsoverudp.go │ ├── dnsoverudp_test.go │ ├── dnstransport.go │ ├── dnstransport_test.go │ ├── doc.go │ ├── errno.go │ ├── errno_darwin.go │ ├── errno_darwin_test.go │ ├── errno_freebsd.go │ ├── errno_freebsd_test.go │ ├── errno_linux.go │ ├── errno_linux_test.go │ ├── errno_openbsd.go │ ├── errno_openbsd_test.go │ ├── errno_windows.go │ ├── errno_windows_test.go │ ├── errwrapper.go │ ├── errwrapper_test.go │ ├── getaddrinfo.go │ ├── getaddrinfo_bsd.go │ ├── getaddrinfo_bsd_test.go │ ├── getaddrinfo_cgo.go │ ├── getaddrinfo_linux.go │ ├── getaddrinfo_linux_test.go │ ├── getaddrinfo_otherwise.go │ ├── getaddrinfo_test.go │ ├── getaddrinfo_windows.go │ ├── getaddrinfo_windows_test.go │ ├── http3.go │ ├── http3_test.go │ ├── httpcloser.go │ ├── httpcloser_test.go │ ├── httperrwrap.go │ ├── httperrwrap_test.go │ ├── httpfactory.go │ ├── httpfactory_test.go │ ├── httplogger.go │ ├── httplogger_test.go │ ├── httpquirks.go │ ├── httpquirks_test.go │ ├── httpstdlib.go │ ├── httptimeout.go │ ├── httptimeout_test.go │ ├── httpwrap.go │ ├── httpwrap_test.go │ ├── integration_test.go │ ├── internal │ │ ├── gencertifi │ │ │ └── main.go │ │ └── generrno │ │ │ └── main.go │ ├── iox.go │ ├── iox_test.go │ ├── maybeproxy.go │ ├── maybeproxy_test.go │ ├── netem.go │ ├── netem_test.go │ ├── netx.go │ ├── netx_test.go │ ├── operations.go │ ├── quic.go │ ├── quic_test.go │ ├── quirks.go │ ├── quirks_test.go │ ├── resolvercache.go │ ├── resolvercache_test.go │ ├── resolvercore.go │ ├── resolvercore_test.go │ ├── resolverparallel.go │ ├── resolverparallel_test.go │ ├── resolverserial.go │ ├── resolverserial_test.go │ ├── shaping.go │ ├── shaping_otherwise.go │ ├── shaping_otherwise_test.go │ ├── shaping_shaping.go │ ├── shaping_shaping_test.go │ ├── tls.go │ ├── tls_test.go │ ├── tproxy.go │ ├── tproxy_test.go │ ├── trace.go │ ├── trace_test.go │ ├── udp.go │ ├── udp_test.go │ ├── utls.go │ └── utls_test.go ├── oohelperd │ ├── dns.go │ ├── dns_test.go │ ├── handler.go │ ├── handler_test.go │ ├── http.go │ ├── http_test.go │ ├── ipinfo.go │ ├── ipinfo_test.go │ ├── measure.go │ ├── metrics.go │ ├── qa_test.go │ ├── quic.go │ ├── tcptls.go │ └── tcptls_test.go ├── oonirun │ ├── doc.go │ ├── experiment.go │ ├── experiment_test.go │ ├── inputprocessor.go │ ├── inputprocessor_test.go │ ├── link.go │ ├── saver.go │ ├── saver_test.go │ ├── session.go │ ├── submitter.go │ ├── submitter_test.go │ ├── v1.go │ ├── v1_test.go │ ├── v2.go │ └── v2_test.go ├── optional │ ├── optional.go │ └── optional_test.go ├── platform │ ├── platform.go │ └── platform_test.go ├── probeservices │ ├── benchselect.go │ ├── bouncer.go │ ├── bouncer_test.go │ ├── checkin.go │ ├── checkin_test.go │ ├── collector.go │ ├── collector_test.go │ ├── login.go │ ├── login_test.go │ ├── measurementmeta.go │ ├── measurementmeta_test.go │ ├── openvpn.go │ ├── openvpn_test.go │ ├── orchestra_test.go │ ├── probeservices.go │ ├── probeservices_test.go │ ├── psiphon.go │ ├── psiphon_test.go │ ├── register.go │ ├── register_test.go │ ├── statefile.go │ ├── statefile_test.go │ ├── tor.go │ └── tor_test.go ├── progress │ ├── progress.go │ └── progress_test.go ├── ptx │ ├── doc.go │ ├── fake.go │ ├── fake_test.go │ ├── obfs4.go │ ├── obfs4_test.go │ ├── ptx.go │ ├── ptx_test.go │ ├── snowflake.go │ ├── snowflake_test.go │ └── testdata │ │ └── .gitignore ├── randx │ ├── randx.go │ └── randx_test.go ├── reflectx │ ├── reflectx.go │ └── reflectx_test.go ├── registry │ ├── allexperiments.go │ ├── dash.go │ ├── dnscheck.go │ ├── dnsping.go │ ├── doc.go │ ├── dslxtutorial.go │ ├── echcheck.go │ ├── example.go │ ├── factory.go │ ├── factory_test.go │ ├── fbmessenger.go │ ├── hhfm.go │ ├── hirl.go │ ├── httphostheader.go │ ├── ndt.go │ ├── openvpn.go │ ├── portfiltering.go │ ├── psiphon.go │ ├── quicping.go │ ├── riseupvpn.go │ ├── signal.go │ ├── simplequicping.go │ ├── sniblocking.go │ ├── stunreachability.go │ ├── tcpping.go │ ├── telegram.go │ ├── tlsmiddlebox.go │ ├── tlsping.go │ ├── tlstool.go │ ├── tor.go │ ├── torsf.go │ ├── urlgetter.go │ ├── vanillator.go │ ├── webconnectivity.go │ ├── webconnectivityv05.go │ └── whatsapp.go ├── runtimex │ ├── runtimex.go │ └── runtimex_test.go ├── scrubber │ ├── scrubber.go │ └── scrubber_test.go ├── shellx │ ├── shellx.go │ ├── shellx_test.go │ ├── shellxtesting │ │ ├── shellxtesting.go │ │ └── shellxtesting_test.go │ └── testdata │ │ └── checkenv.go ├── strcasex │ ├── acronyms.go │ ├── camel.go │ ├── camel_test.go │ ├── doc.go │ ├── snake.go │ └── snake_test.go ├── stuninput │ ├── stuninput.go │ └── stuninput_test.go ├── targetloading │ ├── targetloading.go │ ├── targetloading_test.go │ └── testdata │ │ ├── loader1.txt │ │ ├── loader2.txt │ │ └── loader3.txt ├── testingproxy │ ├── dialer.go │ ├── doc.go │ ├── hosthttp.go │ ├── hosthttps.go │ ├── httputils.go │ ├── httputils_test.go │ ├── netemhttp.go │ ├── netemhttps.go │ ├── qa_test.go │ ├── sockshost.go │ ├── socksnetem.go │ └── testcase.go ├── testingquic │ ├── testingquic.go │ └── testingquic_test.go ├── testingsocks5 │ ├── LICENSE │ ├── auth.go │ ├── client.go │ ├── client_test.go │ ├── doc.go │ ├── internal_test.go │ ├── qa_test.go │ ├── request.go │ ├── request_test.go │ └── server.go ├── testingx │ ├── closeverify.go │ ├── closeverify_test.go │ ├── dnscore.go │ ├── dnscore_test.go │ ├── dnsoverhttps.go │ ├── dnsoverhttps_test.go │ ├── dnsoverudp.go │ ├── dnsoverudp_test.go │ ├── dnssimulategfw.go │ ├── dnssimulategfw_test.go │ ├── doc.go │ ├── fakefill.go │ ├── fakefill_test.go │ ├── geoip.go │ ├── geoip_test.go │ ├── httpproxy.go │ ├── httpproxy_test.go │ ├── httptestx.go │ ├── httptestx_test.go │ ├── logger.go │ ├── logger_test.go │ ├── oonibackendwithlogin.go │ ├── oonibackendwithlogin_test.go │ ├── oonicollector.go │ ├── oonicollector_test.go │ ├── tcpx.go │ ├── time.go │ ├── time_test.go │ ├── tlssniproxy.go │ ├── tlssniproxy_test.go │ ├── tlsx.go │ ├── tlsx_internal_test.go │ └── tlsx_test.go ├── throttling │ ├── throttling.go │ └── throttling_test.go ├── torlogs │ ├── testdata │ │ ├── empty.log │ │ └── tor.log │ ├── torlogs.go │ └── torlogs_test.go ├── tunnel │ ├── config.go │ ├── config_test.go │ ├── fake.go │ ├── fake_integration_test.go │ ├── fake_test.go │ ├── mocks │ │ ├── mocks.go │ │ └── mocks_test.go │ ├── psiphon.go │ ├── psiphon_integration_test.go │ ├── psiphon_test.go │ ├── session_test.go │ ├── testdata │ │ └── .gitignore │ ├── tor.go │ ├── tor_integration_test.go │ ├── tor_test.go │ ├── tordesktop.go │ ├── torembed.go │ ├── torsf.go │ ├── torsf_test.go │ ├── tunnel.go │ └── tunnel_test.go ├── tutorial │ ├── README.md │ ├── dslx │ │ ├── README.md │ │ ├── chapter01 │ │ │ └── README.md │ │ ├── chapter02 │ │ │ ├── README.md │ │ │ └── main.go │ │ └── chapter03 │ │ │ └── README.md │ ├── experiment │ │ └── torsf │ │ │ ├── README.md │ │ │ ├── chapter01 │ │ │ ├── README.md │ │ │ └── main.go │ │ │ ├── chapter02 │ │ │ ├── README.md │ │ │ ├── main.go │ │ │ └── torsf.go │ │ │ ├── chapter03 │ │ │ ├── README.md │ │ │ ├── main.go │ │ │ └── torsf.go │ │ │ └── chapter04 │ │ │ ├── README.md │ │ │ ├── main.go │ │ │ └── torsf.go │ ├── generator │ │ └── main.go │ ├── measurex │ │ ├── README.md │ │ ├── chapter01 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter02 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter03 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter04 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter05 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter06 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter07 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter08 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter09 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter10 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter11 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter12 │ │ │ ├── README.md │ │ │ └── main.go │ │ ├── chapter13 │ │ │ ├── README.md │ │ │ └── main.go │ │ └── chapter14 │ │ │ ├── README.md │ │ │ └── main.go │ └── netxlite │ │ ├── README.md │ │ ├── chapter01 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter02 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter03 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter04 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter05 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter06 │ │ ├── README.md │ │ └── main.go │ │ ├── chapter07 │ │ ├── README.md │ │ └── main.go │ │ └── chapter08 │ │ ├── README.md │ │ └── main.go ├── urlx │ ├── DESIGN.md │ ├── urlx.go │ └── urlx_test.go ├── version │ └── version.go ├── webconnectivityalgo │ ├── calltesthelpers.go │ ├── calltesthelpers_test.go │ ├── dnsoverhttps.go │ ├── dnsoverhttps_test.go │ ├── dnsoverudp.go │ ├── dnsoverudp_test.go │ ├── dnswhoami.go │ ├── dnswhoami_test.go │ └── doc.go ├── webconnectivityqa │ ├── badssl.go │ ├── badssl_test.go │ ├── checker.go │ ├── checker_test.go │ ├── cloudflare.go │ ├── control.go │ ├── control_test.go │ ├── dnsblocking.go │ ├── dnsblocking_test.go │ ├── dnshijacking.go │ ├── dnshijacking_test.go │ ├── doc.go │ ├── ghost.go │ ├── ghost_test.go │ ├── httpblocking.go │ ├── httpblocking_test.go │ ├── httpdiff.go │ ├── httpdiff_test.go │ ├── idna.go │ ├── largefile.go │ ├── localhost.go │ ├── localhost_test.go │ ├── measurement.go │ ├── redirect.go │ ├── redirect_test.go │ ├── run.go │ ├── run_test.go │ ├── session.go │ ├── session_test.go │ ├── success.go │ ├── tcpblocking.go │ ├── tcpblocking_test.go │ ├── testcase.go │ ├── testcase_test.go │ ├── testkeys.go │ ├── throttling.go │ ├── tlsblocking.go │ ├── tlsblocking_test.go │ ├── websitedown.go │ └── websitedown_test.go └── x │ ├── doc.go │ ├── dslengine │ ├── measurexlite.go │ ├── minimal.go │ └── options.go │ ├── dsljavascript │ ├── consolemodule.go │ ├── doc.go │ ├── golangmodule.go │ ├── oonimodule.go │ └── vm.go │ ├── dsljson │ ├── dedupaddrs.go │ ├── dnslookupudp.go │ ├── doc.go │ ├── drop.go │ ├── getaddrinfo.go │ ├── http.go │ ├── loader.go │ ├── makeendpoints.go │ ├── quic.go │ ├── register.go │ ├── rootnode.go │ ├── run.go │ ├── stagenode.go │ ├── taken.go │ ├── tcp.go │ ├── teeaddrs.go │ └── tls.go │ ├── dslvm │ ├── closer.go │ ├── dedupaddrs.go │ ├── dnslookupudp.go │ ├── doc.go │ ├── done.go │ ├── drop.go │ ├── getaddrinfo.go │ ├── http.go │ ├── makeendpoints.go │ ├── observations.go │ ├── quic.go │ ├── runtime.go │ ├── semaphore.go │ ├── stage.go │ ├── start.go │ ├── taken.go │ ├── tcp.go │ ├── teeaddrs.go │ ├── tls.go │ ├── trace.go │ └── wait.go │ └── dslx │ ├── address.go │ ├── address_test.go │ ├── dns.go │ ├── dns_test.go │ ├── doc.go │ ├── endpoint.go │ ├── endpoint_test.go │ ├── fxasync.go │ ├── fxasync_test.go │ ├── fxcore.go │ ├── fxcore_test.go │ ├── fxgen.go │ ├── fxstream.go │ ├── fxstream_test.go │ ├── http_test.go │ ├── httpcore.go │ ├── httpquic.go │ ├── httptcp.go │ ├── httptls.go │ ├── integration_test.go │ ├── observations.go │ ├── qa_test.go │ ├── quic.go │ ├── quic_test.go │ ├── runtimecore.go │ ├── runtimemeasurex.go │ ├── runtimemeasurex_test.go │ ├── runtimeminimal.go │ ├── runtimeminimal_test.go │ ├── tcp.go │ ├── tcp_test.go │ ├── tls.go │ ├── tls_test.go │ └── trace.go ├── oonith ├── Makefile └── buildspec.yml ├── pkg ├── .gitignore ├── README.md ├── gobash │ ├── .gitignore │ ├── go.mod │ ├── main.go │ ├── signal_notunix.go │ ├── signal_unix.go │ ├── version.go │ └── version_test.go └── oonimkall │ ├── .gitignore │ ├── README.md │ ├── doc.go │ ├── experiment.go │ ├── experiment_test.go │ ├── httpx.go │ ├── httpx_test.go │ ├── session.go │ ├── session_integration_test.go │ ├── session_test.go │ ├── sessioncontext.go │ ├── sessioncontext_test.go │ ├── sessionlogger.go │ ├── sessionlogger_test.go │ ├── task.go │ ├── task_test.go │ ├── taskemitter.go │ ├── taskemitter_test.go │ ├── tasklogger.go │ ├── tasklogger_test.go │ ├── taskmodel.go │ ├── taskrunner.go │ ├── taskrunner_test.go │ ├── taskutils_test.go │ ├── uuid.go │ ├── uuid_test.go │ ├── webconnectivity.go │ ├── webconnectivity_integration_test.go │ └── webconnectivity_test.go └── script ├── README.md ├── build_docs.sh ├── ghpublish-branch.out.txt ├── ghpublish-pr.out.txt ├── ghpublish-prerelease.out.txt ├── ghpublish-release.out.txt ├── ghpublish.bash ├── ghpublish_test.bash ├── go.bash ├── internal └── go.bash ├── linuxcoverage.bash ├── linuxcoveragerun.bash ├── maketarball.bash ├── nocopyreadall.bash └── updateminipipeline.bash /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [*.py] 11 | indent_style = space 12 | 13 | [*.{yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.java] 18 | indent_style = space 19 | -------------------------------------------------------------------------------- /.github/workflows/alltests.yml: -------------------------------------------------------------------------------- 1 | # Runs the whole test suite 2 | name: alltests 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "release/**" 8 | - "fullbuild" 9 | - "alltestsbuild" 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Get GOVERSION content 18 | id: goversion 19 | run: echo "version=$(cat GOVERSION)" >> "$GITHUB_OUTPUT" 20 | 21 | - uses: magnetikonline/action-golang-cache@v4 22 | with: 23 | go-version: "${{ steps.goversion.outputs.version }}" 24 | cache-key-suffix: "-alltests-${{ steps.goversion.outputs.version }}" 25 | 26 | - run: go test -race -tags shaping ./... 27 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | # Performs miscellaneous quick checks 2 | name: checks 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | perform_code_quality_checks: 9 | runs-on: "${{ matrix.os }}" 10 | strategy: 11 | matrix: 12 | os: [ "ubuntu-22.04" ] 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - run: ./MOBILE/ios/createpodspecs_test 19 | 20 | - run: ./script/nocopyreadall.bash 21 | 22 | - run: ./script/ghpublish_test.bash 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # Runs a CodeQL scan. 2 | name: "CodeQL" 3 | 4 | on: 5 | push: 6 | branches: 7 | - "master" 8 | - "release/**" 9 | - "fullbuild" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-22.04 15 | permissions: # See https://github.com/ooni/probe/issues/2154 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ "go" ] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Initialize CodeQL 28 | uses: github/codeql-action/init@v2 29 | with: 30 | languages: ${{ matrix.language }} 31 | 32 | - name: Autobuild 33 | uses: github/codeql-action/autobuild@v2 34 | 35 | - name: Perform CodeQL Analysis 36 | uses: github/codeql-action/analyze@v2 37 | -------------------------------------------------------------------------------- /.github/workflows/generate.yml: -------------------------------------------------------------------------------- 1 | # Verifies that `go generate ./...` is not broken 2 | name: generate 3 | on: 4 | push: 5 | branches: 6 | - "release/**" 7 | - "fullbuild" 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Get GOVERSION content 16 | id: goversion 17 | run: echo "version=$(cat GOVERSION)" >> "$GITHUB_OUTPUT" 18 | 19 | - uses: magnetikonline/action-golang-cache@v4 20 | with: 21 | go-version: "${{ steps.goversion.outputs.version }}" 22 | cache-key-suffix: "-generate-${{ steps.goversion.outputs.version }}" 23 | 24 | - run: go generate ./... 25 | -------------------------------------------------------------------------------- /.github/workflows/go1.22.yml: -------------------------------------------------------------------------------- 1 | # Runs the whole test suite using go1.22 2 | name: alltests-go1.22 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "release/**" 8 | - "fullbuild" 9 | - "alltestsbuild" 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: magnetikonline/action-golang-cache@v4 18 | with: 19 | go-version: ~1.22 20 | cache-key-suffix: "-alltests-go1.22" 21 | 22 | # We cannot run buildtool tests using an unexpected version of Go because the 23 | # tests check whether we're using the expected version of Go 😂😂😂😂. 24 | - run: go test -race -tags shaping $(go list ./...|grep -v 'internal/cmd/buildtool') 25 | -------------------------------------------------------------------------------- /.github/workflows/gosec.yml: -------------------------------------------------------------------------------- 1 | # Runs the gosec security scanner 2 | name: gosec 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "release/**" 8 | - "fullbuild" 9 | 10 | jobs: 11 | gosec: 12 | runs-on: ubuntu-22.04 13 | env: 14 | GO111MODULE: on 15 | steps: 16 | - name: Checkout Source 17 | uses: actions/checkout@v4 18 | 19 | - name: Get GOVERSION content 20 | id: goversion 21 | run: echo "version=$(cat GOVERSION)" >> "$GITHUB_OUTPUT" 22 | 23 | - uses: magnetikonline/action-golang-cache@v4 24 | with: 25 | go-version: "${{ steps.goversion.outputs.version }}" 26 | cache-key-suffix: "-gosec-${{ steps.goversion.outputs.version }}" 27 | 28 | - name: Run Gosec security scanner 29 | continue-on-error: true # TODO(https://github.com/ooni/probe/issues/2180) 30 | uses: securego/gosec@master 31 | with: 32 | args: ./... 33 | -------------------------------------------------------------------------------- /.github/workflows/libtorlinux.yml: -------------------------------------------------------------------------------- 1 | # Runs tests for internal/libtor with -tags=ooni_libtor 2 | name: libtorlinux 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "release/**" 8 | - "fullbuild" 9 | 10 | jobs: 11 | test_ooni_libtor: 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Get GOVERSION content 18 | id: goversion 19 | run: echo "version=$(cat GOVERSION)" >> "$GITHUB_OUTPUT" 20 | 21 | - uses: magnetikonline/action-golang-cache@v4 22 | with: 23 | go-version: "${{ steps.goversion.outputs.version }}" 24 | cache-key-suffix: "-libtorlinux-${{ steps.goversion.outputs.version }}" 25 | 26 | - run: go run ./internal/cmd/buildtool linux cdeps zlib openssl libevent tor 27 | 28 | - run: go test -count 1 -v -cover -tags ooni_libtor -race ./internal/libtor/... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pcap 2 | .DS_Store 3 | /*.asc 4 | /*.csv 5 | /*.deb 6 | /*.exe 7 | /*.json 8 | /*.jsonl 9 | /*.pprof 10 | /*.sqlite3 11 | /*.tar.gz 12 | /*.zip 13 | /DEBIAN_INSTALLED_PACKAGE.txt 14 | /apitool 15 | /badproxy.pem 16 | /buildtool 17 | /citizenlab-test-lists 18 | /gardener 19 | /ghpublish.out.txt 20 | /libooniengine.* 21 | /measurement.json 22 | /miniooni 23 | /oohelper 24 | /oohelperd 25 | /ooniprobe 26 | /oonireport 27 | /ooporthelper 28 | /probe-cli.cov 29 | /tinyjafar 30 | /tmp-* 31 | /dist 32 | -------------------------------------------------------------------------------- /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | /*.log 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black", 3 | "gopls": { 4 | "build.directoryFilters": [ 5 | "-GOCACHE", 6 | "-GOPATH" 7 | ] 8 | }, 9 | "C_Cpp.default.configurationProvider": "ms-vscode.makefile-tools" 10 | } 11 | -------------------------------------------------------------------------------- /CDEPS/README.md: -------------------------------------------------------------------------------- 1 | # Code to build C dependencies 2 | 3 | Directory used to compile C dependencies. 4 | -------------------------------------------------------------------------------- /CDEPS/libevent/000.patch: -------------------------------------------------------------------------------- 1 | diff --git a/bufferevent_openssl.c b/bufferevent_openssl.c 2 | index b51b834..06b219e 100644 3 | --- a/bufferevent_openssl.c 4 | +++ b/bufferevent_openssl.c 5 | @@ -67,6 +67,11 @@ 6 | #include 7 | #include "openssl-compat.h" 8 | 9 | +#include 10 | +#ifndef OPENSSL_OONI 11 | +#error "We're not including the correct openssl/opensslv.h file" 12 | +#endif 13 | + 14 | /* 15 | * Define an OpenSSL bio that targets a bufferevent. 16 | */ 17 | -------------------------------------------------------------------------------- /CDEPS/libevent/001.patch: -------------------------------------------------------------------------------- 1 | diff --git a/test/regress_zlib.c b/test/regress_zlib.c 2 | index 5fe7749..558155c 100644 3 | --- a/test/regress_zlib.c 4 | +++ b/test/regress_zlib.c 5 | @@ -80,6 +80,10 @@ 6 | 7 | #include 8 | 9 | +#ifndef ZLIB_OONI 10 | +#error "We're not including the correct zlib.h file" 11 | +#endif 12 | + 13 | static int infilter_calls; 14 | static int outfilter_calls; 15 | static int readcb_finished; 16 | -------------------------------------------------------------------------------- /CDEPS/libevent/002.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/event2/event.h b/include/event2/event.h 2 | index a6b6144..6abb474 100644 3 | --- a/include/event2/event.h 4 | +++ b/include/event2/event.h 5 | @@ -1665,6 +1665,10 @@ int event_base_update_cache_time(struct event_base *base); 6 | EVENT2_EXPORT_SYMBOL 7 | void libevent_global_shutdown(void); 8 | 9 | +/* EVENT_OONI is used by dependencies to ensure they are using the 10 | + correct event.h header and not some other header. */ 11 | +#define EVENT_OONI 1 12 | + 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /CDEPS/openssl/000.patch: -------------------------------------------------------------------------------- 1 | diff --git a/crypto/comp/c_zlib.c b/crypto/comp/c_zlib.c 2 | index 0fbab8f014..0dc8ff53d4 100644 3 | --- a/crypto/comp/c_zlib.c 4 | +++ b/crypto/comp/c_zlib.c 5 | @@ -26,6 +26,10 @@ COMP_METHOD *COMP_zlib(void); 6 | 7 | # include 8 | 9 | +#ifndef ZLIB_OONI 10 | +# error "We're not including the correct zlib.h file" 11 | +#endif 12 | + 13 | static int zlib_stateful_init(COMP_CTX *ctx); 14 | static void zlib_stateful_finish(COMP_CTX *ctx); 15 | static ossl_ssize_t zlib_stateful_compress_block(COMP_CTX *ctx, unsigned char *out, 16 | -------------------------------------------------------------------------------- /CDEPS/openssl/001.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/openssl/opensslv.h.in b/include/openssl/opensslv.h.in 2 | index 3f47a2ac08..b413461ca7 100644 3 | --- a/include/openssl/opensslv.h.in 4 | +++ b/include/openssl/opensslv.h.in 5 | @@ -13,6 +13,10 @@ 6 | # define OPENSSL_OPENSSLV_H 7 | # pragma once 8 | 9 | +/* OPENSSL_OONI is used by dependencies to ensure they are using the 10 | + correct OpenSSL headers and not some other headers. */ 11 | +#define OPENSSL_OONI 1 12 | + 13 | # ifdef __cplusplus 14 | extern "C" { 15 | # endif 16 | -------------------------------------------------------------------------------- /CDEPS/tor/000.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/lib/tls/tortls_openssl.c b/src/lib/tls/tortls_openssl.c 2 | index 77de2d6..ce46554 100644 3 | --- a/src/lib/tls/tortls_openssl.c 4 | +++ b/src/lib/tls/tortls_openssl.c 5 | @@ -45,6 +45,10 @@ DISABLE_GCC_WARNING("-Wredundant-decls") 6 | #error "We require OpenSSL with ECC support" 7 | #endif 8 | 9 | +#ifndef OPENSSL_OONI 10 | +#error "We're not including the correct openssl/opensslv.h file" 11 | +#endif 12 | + 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /CDEPS/tor/001.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/lib/compress/compress_zlib.c b/src/lib/compress/compress_zlib.c 2 | index 52f9509..fb7e39e 100644 3 | --- a/src/lib/compress/compress_zlib.c 4 | +++ b/src/lib/compress/compress_zlib.c 5 | @@ -45,6 +45,10 @@ 6 | #error "We require zlib version 1.2 or later." 7 | #endif 8 | 9 | +#ifndef ZLIB_OONI 10 | +#error "We're not including the correct zlib.h file" 11 | +#endif 12 | + 13 | static size_t tor_zlib_state_size_precalc(int inflate, 14 | int windowbits, int memlevel); 15 | 16 | -------------------------------------------------------------------------------- /CDEPS/tor/002.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/lib/evloop/compat_libevent.c b/src/lib/evloop/compat_libevent.c 2 | index fd840f8..2ec37e7 100644 3 | --- a/src/lib/evloop/compat_libevent.c 4 | +++ b/src/lib/evloop/compat_libevent.c 5 | @@ -19,6 +19,10 @@ 6 | #include 7 | #include 8 | 9 | +#ifndef EVENT_OONI 10 | +#error "We're not including the correct event2/event.h file" 11 | +#endif 12 | + 13 | /** A string which, if it appears in a libevent log, should be ignored. */ 14 | static const char *suppress_msg = NULL; 15 | /** Callback function passed to event_set_log() so we can intercept 16 | -------------------------------------------------------------------------------- /CDEPS/tor/003.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/feature/api/tor_api.c b/src/feature/api/tor_api.c 2 | index 88e91ebfd5..2773949264 100644 3 | --- a/src/feature/api/tor_api.c 4 | +++ b/src/feature/api/tor_api.c 5 | @@ -131,9 +131,13 @@ tor_main_configuration_free(tor_main_configuration_t *cfg) 6 | } 7 | raw_free(cfg->argv_owned); 8 | } 9 | + /* See https://gitlab.torproject.org/tpo/core/tor/-/issues/40747 to 10 | + understand why we're not closing the socket here. */ 11 | + /* 12 | if (SOCKET_OK(cfg->owning_controller_socket)) { 13 | raw_closesocket(cfg->owning_controller_socket); 14 | } 15 | + */ 16 | raw_free(cfg); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /CDEPS/tor/004.patch: -------------------------------------------------------------------------------- 1 | diff --git a/configure.ac b/configure.ac 2 | index b218a59ce2..b87adec9fc 100644 3 | --- a/configure.ac 4 | +++ b/configure.ac 5 | @@ -1294,10 +1294,10 @@ tor_cap_pkg_redhat="libcap" 6 | tor_cap_devpkg_debian="libcap-dev" 7 | tor_cap_devpkg_redhat="libcap-devel" 8 | 9 | -AC_CHECK_LIB([cap], [cap_init], [], 10 | - AC_MSG_NOTICE([Libcap was not found. Capabilities will not be usable.]) 11 | -) 12 | -AC_CHECK_FUNCS(cap_set_proc) 13 | +dnl AC_CHECK_LIB([cap], [cap_init], [], 14 | +dnl AC_MSG_NOTICE([Libcap was not found. Capabilities will not be usable.]) 15 | +dnl ) 16 | +dnl AC_CHECK_FUNCS(cap_set_proc) 17 | 18 | dnl --------------------------------------------------------------------- 19 | dnl Now that we know about our major libraries, we can check for compiler 20 | -------------------------------------------------------------------------------- /CDEPS/zlib/000.patch: -------------------------------------------------------------------------------- 1 | diff --git a/zlib.h b/zlib.h 2 | index 953cb50..ec2e2f8 100644 3 | --- a/zlib.h 4 | +++ b/zlib.h 5 | @@ -1928,6 +1928,10 @@ ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, 6 | # endif 7 | #endif 8 | 9 | +/* ZLIB_OONI is used by dependencies to ensure they are using the 10 | + correct zlib.h header and not some other header. */ 11 | +#define ZLIB_OONI 1 12 | + 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /CLI/.gitignore: -------------------------------------------------------------------------------- 1 | /Dockerfile 2 | /miniooni-* 3 | /oohelperd-* 4 | /ooniprobe-* 5 | -------------------------------------------------------------------------------- /CLI/README.md: -------------------------------------------------------------------------------- 1 | # Building CLI tools 2 | 3 | We use this directory for building CLI tools (e.g., `ooniprobe`). 4 | 5 | -------------------------------------------------------------------------------- /CLI/check-go-version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | EXPECTED_GOLANG_VERSION=go$(cat GOVERSION) 6 | 7 | printf "checking for go... " 8 | if ! command -v go; then 9 | echo "not found" 10 | exit 1 11 | fi 12 | 13 | printf "checking for go version... " 14 | GOLANG_VERSION=$(go version | awk '{print $3}') 15 | echo $GOLANG_VERSION 16 | if [[ $GOLANG_VERSION != $EXPECTED_GOLANG_VERSION ]]; then 17 | echo "FATAL: go version must be $EXPECTED_GOLANG_VERSION instead of $GOLANG_VERSION" 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hellais @DecFox 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # OONI Code of Conduct 2 | 3 | Please, refer to https://ooni.org/get-involved/code-of-conduct/. 4 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design documents 2 | 3 | Please, see the [docs/design](docs/design) directory for a collection 4 | of important design documents related to this repo. 5 | -------------------------------------------------------------------------------- /Dockerfile.oonith: -------------------------------------------------------------------------------- 1 | # This dockerfile is used to build the oohelperd binary 2 | # To make use of it, see the Makefile located inside of oonith/Makefile. 3 | # 4 | # Note: The Dockerfile needs to reside in the root of the repo, so that we can 5 | # copy files into the docker build context. 6 | FROM golang:1.22.3-bullseye as builder 7 | ARG BRANCH_NAME=master 8 | 9 | WORKDIR /build 10 | 11 | COPY . . 12 | 13 | RUN go run ./internal/cmd/buildtool oohelperd build 14 | 15 | ## Image running on the host 16 | FROM golang:1.22.3-bullseye as runner 17 | 18 | WORKDIR /app 19 | 20 | COPY --from=builder /build/CLI/oohelperd-* /app 21 | RUN mv oohelperd-* oohelperd 22 | 23 | # oohelperd service 24 | EXPOSE 80 25 | 26 | # Run 27 | CMD ["/app/oohelperd", "-api-endpoint", "0.0.0.0:80"] 28 | -------------------------------------------------------------------------------- /E2E/.gitignore: -------------------------------------------------------------------------------- 1 | /*.jsonl 2 | -------------------------------------------------------------------------------- /E2E/README.md: -------------------------------------------------------------------------------- 1 | # End-to-end tests 2 | 3 | This directory is used to run end-to-end tests where we run specific 4 | measurements, we fetch them back from the API, and check them. 5 | -------------------------------------------------------------------------------- /E2E/ooniprobe.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This test for now uses --no-collector and we just ensure that the OONI 4 | # instance is not exploding. We are confident that, if miniooni submits 5 | # measurements, also ooniprobe should be able to do that. However, it would 6 | # actually be nice if someone could enhance this script to also make sure 7 | # that we can actually fetch the measurements we submit. 8 | 9 | set -euxo pipefail 10 | 11 | if [ "$#" != 1 ]; then 12 | echo "Usage: $0 " 1>&2 13 | exit 1 14 | fi 15 | 16 | $1 onboard --yes 17 | 18 | # Important! DO NOT run performance from CI b/c it will overload m-lab servers 19 | $1 run websites --config cmd/ooniprobe/testdata/testing-config.json --no-collector 20 | -------------------------------------------------------------------------------- /GOCACHE/.gitignore: -------------------------------------------------------------------------------- 1 | /oonibuild 2 | -------------------------------------------------------------------------------- /GOCACHE/README.md: -------------------------------------------------------------------------------- 1 | # GOCACHE for Docker based builds 2 | 3 | This directory contains the GOCACHE and GOMODCACHE we use when 4 | statically compiling Linux binaries using Docker. 5 | 6 | If you keep the content of this directory, subsequent builds will be 7 | faster. You will notice this especially for builds using qemu-user-static 8 | to build for different architectures. 9 | -------------------------------------------------------------------------------- /GOVERSION: -------------------------------------------------------------------------------- 1 | 1.23.4 2 | -------------------------------------------------------------------------------- /MOBILE/README.md: -------------------------------------------------------------------------------- 1 | # Scripts to build the OONI Probe mobile library 2 | 3 | This directory is used for building for Android and iOS. 4 | 5 | The [android](android) directory contains Android specific code. We will put 6 | Android build artifacts inside this directory as well. 7 | 8 | The [ios](ios) directory contains iOS specific code. We will put iOS 9 | build artifacts inside this directory as well. 10 | -------------------------------------------------------------------------------- /MOBILE/android/.gitignore: -------------------------------------------------------------------------------- 1 | /*.apk 2 | /*.aar 3 | /*.asc 4 | /*.idsig 5 | /*.jar 6 | /*.jks 7 | /*.pom 8 | -------------------------------------------------------------------------------- /MOBILE/android/adbinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 4 | android_home=$(cd $reporoot && ./MOBILE/android/home) 5 | adb=$android_home/platform-tools/adb 6 | $adb install $reporoot/MOBILE/android/app.apk 7 | -------------------------------------------------------------------------------- /MOBILE/android/createpom: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | __version=$(date -u +%Y.%m.%d-%H%M%S) 4 | cat ./MOBILE/android/template.pom | sed -e "s/@VERSION@/$__version/g" > ./MOBILE/android/oonimkall.pom 5 | -------------------------------------------------------------------------------- /MOBILE/android/ensure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | __install_extra="build-tools;34.0.0 platforms;android-35" 6 | 7 | __ndk_version=$(cat ./NDKVERSION) 8 | 9 | ANDROID_HOME=$(./MOBILE/android/home) 10 | 11 | __sdkmanager=$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager 12 | if [[ ! -x $__sdkmanager ]]; then 13 | echo "FATAL: expected to find sdkmanager at $__sdkmanager, but found nothing" 1>&2 14 | echo "HINT: run ./MOBILE/android/setup to (re)install the SDK" 1>&2 15 | exit 1 16 | fi 17 | 18 | set -x 19 | echo "Yes" | $__sdkmanager --install $__install_extra "ndk;$__ndk_version" 20 | -------------------------------------------------------------------------------- /MOBILE/android/home: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | nocheck=0 6 | if [[ $# -eq 1 && $1 == "--no-check" ]]; then 7 | nocheck=1 8 | fi 9 | 10 | GOOS=$(go env GOOS) 11 | case $GOOS in 12 | linux) 13 | __sdk_dir=$HOME/Android/Sdk 14 | ;; 15 | darwin) 16 | __sdk_dir=$HOME/Library/Android/sdk 17 | ;; 18 | *) 19 | echo "FATAL: unsupported operating system" 1>&2 20 | exit 1 21 | ;; 22 | esac 23 | 24 | ANDROID_HOME=${ANDROID_HOME:-$__sdk_dir} 25 | if [[ $nocheck == 0 && ! -d $ANDROID_HOME ]]; then 26 | echo "FATAL: expected to find android SDK at $ANDROID_HOME, but found nothing" 1>&2 27 | echo "HINT: run ./MOBILE/android/setup to (re)install the SDK" 1>&2 28 | exit 1 29 | fi 30 | echo $ANDROID_HOME 31 | -------------------------------------------------------------------------------- /MOBILE/android/newkeystore: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 4 | rm -rf $reporoot/MOBILE/android/keystore.jks 5 | keystorepassword=ooniprobe 6 | printf "${keystorepassword}\n${keystorepassword}\n\n\n\n\n\n\nyes\n" | 7 | keytool -genkey -v -keystore $reporoot/MOBILE/android/keystore.jks -keyalg RSA \ 8 | -keysize 2048 -validity 7 -alias key0 9 | -------------------------------------------------------------------------------- /MOBILE/android/sign: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 4 | android_home=$(cd $reporoot && ./MOBILE/android/home) 5 | apksigner=$(find $android_home/build-tools -type f -name apksigner | head -n1) 6 | zipalign=$(find $android_home/build-tools -type f -name zipalign | head -n1) 7 | ( 8 | cd $reporoot/MOBILE/android 9 | rm -f app-unsigned-aligned.apk 10 | $zipalign -p 4 app-unsigned.apk app-unsigned-aligned.apk 11 | rm -f app.apk 12 | $apksigner sign --ks keystore.jks --out app.apk \ 13 | --ks-pass pass:ooniprobe app-unsigned-aligned.apk 14 | ) 15 | -------------------------------------------------------------------------------- /MOBILE/ios/.gitignore: -------------------------------------------------------------------------------- 1 | /*.xcframework/ 2 | /*.zip 3 | /*.podspec 4 | -------------------------------------------------------------------------------- /MOBILE/ios/README.md: -------------------------------------------------------------------------------- 1 | # Directory MOBILE/ios 2 | 3 | This directory contains iOS specific code. We will put 4 | iOS build artifacts inside it as well. 5 | -------------------------------------------------------------------------------- /MOBILE/ios/check-xcode-version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | EXPECTED_XCODE_VERSION=${EXPECTED_XCODE_VERSION:-15.1} 6 | 7 | printf "checking for xcodebuild... " 8 | command -v xcodebuild || { 9 | echo "not found" 10 | exit 1 11 | } 12 | 13 | printf "checking for Xcode version... " 14 | __XCODEVERSION_REAL=$(xcodebuild -version | grep ^Xcode | awk '{print $2}') 15 | echo $__XCODEVERSION_REAL 16 | [[ "$EXPECTED_XCODE_VERSION" = "$__XCODEVERSION_REAL" ]] || { 17 | echo "fatal: Xcode version must be $EXPECTED_XCODE_VERSION instead of $__XCODEVERSION_REAL" 18 | exit 1 19 | } 20 | -------------------------------------------------------------------------------- /MOBILE/ios/createpodspecs_test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | export CREATE_POD_SPECS_DEBUG=1 5 | 6 | function check() { 7 | local expect=$1 8 | local got=$2 9 | echo -n "checking whether '$expect' equals '$got'... " 1>&2 10 | if [[ "$got" != "$expect" ]]; then 11 | echo "NO" 1>&2 12 | exit 1 13 | fi 14 | echo "yes" 15 | } 16 | 17 | expect="v3.10.9-beta.116-44-g1777474 -> rolling" 18 | got=$(./MOBILE/ios/createpodspecs v3.10.9-beta.116-44-g1777474) 19 | check "$expect" "$got" 20 | 21 | expect="v3.10.0 -> v3.10.0" 22 | got=$(./MOBILE/ios/createpodspecs v3.10.0) 23 | check "$expect" "$got" 24 | 25 | expect="v3.10.0-alpha -> v3.10.0-alpha" 26 | got=$(./MOBILE/ios/createpodspecs v3.10.0-alpha) 27 | check "$expect" "$got" 28 | 29 | expect="v3.10.0-alpha.1 -> v3.10.0-alpha.1" 30 | got=$(./MOBILE/ios/createpodspecs v3.10.0-alpha.1) 31 | check "$expect" "$got" 32 | 33 | -------------------------------------------------------------------------------- /MOBILE/ios/libcrypto-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "libcrypto" 3 | s.version = "@VERSION@" 4 | s.summary = "OpenSSL libcrypto compiled for OONI Probe iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "Apache" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/libcrypto.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "libcrypto.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/libevent-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "libevent" 3 | s.version = "@VERSION@" 4 | s.summary = "Libevent compiled for OONI Probe iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "BSD" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/libevent.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "libevent.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/libssl-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "libssl" 3 | s.version = "@VERSION@" 4 | s.summary = "OpenSSL libssl compiled for OONI Probe iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "Apache" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/libssl.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "libssl.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/libtor-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "libtor" 3 | s.version = "@VERSION@" 4 | s.summary = "tor compiled for OONI Probe iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "BSD" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/libtor.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "libtor.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/libz-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "libz" 3 | s.version = "@VERSION@" 4 | s.summary = "zlib compiled for OONI Probe iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "zlib" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/libz.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "libz.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/oonimkall-template.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "oonimkall" 3 | s.version = "@VERSION@" 4 | s.summary = "OONI Probe Library for iOS" 5 | s.author = "Simone Basso" 6 | s.homepage = "https://github.com/ooni/probe-cli" 7 | s.license = { :type => "GPL" } 8 | s.source = { 9 | :http => "https://github.com/ooni/probe-cli/releases/download/@RELEASE@/oonimkall.xcframework.zip" 10 | } 11 | s.platform = :ios, "9.0" 12 | s.ios.vendored_frameworks = "oonimkall.xcframework" 13 | end 14 | -------------------------------------------------------------------------------- /MOBILE/ios/zipframeworks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | ( 4 | cd ./MOBILE/ios 5 | for name in libcrypto libevent libssl libtor libz oonimkall; do 6 | rm -rf ${name}.xcframework.zip 7 | zip -yr ${name}.xcframework.zip ${name}.xcframework 8 | done 9 | ) 10 | -------------------------------------------------------------------------------- /MONOREPO/repo/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /MONOREPO/tools/.gitignore: -------------------------------------------------------------------------------- 1 | /local.bash 2 | -------------------------------------------------------------------------------- /MONOREPO/tools/info: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #doc: 4 | #doc: # MONOREPO/tools/info 5 | #doc: 6 | #doc: Shows inline documentation. 7 | 8 | set -euo pipefail 9 | 10 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 11 | 12 | source $reporoot/MONOREPO/tools/libcore.bash 13 | 14 | document() { 15 | echo "" 16 | cat $1 | grep '^#doc:' | sed -e 's/^#doc: //g' -e 's/^#doc://g' 17 | } 18 | 19 | document $reporoot/MONOREPO/tools/info 20 | document $reporoot/MONOREPO/tools/gitconfig.bash 21 | document $reporoot/MONOREPO/tools/libcore.bash 22 | document $reporoot/MONOREPO/tools/libgit.bash 23 | document $reporoot/MONOREPO/tools/gitx 24 | document $reporoot/MONOREPO/tools/setupandroid 25 | document $reporoot/MONOREPO/tools/setupgo 26 | document $reporoot/MONOREPO/tools/setupshfmt 27 | -------------------------------------------------------------------------------- /MONOREPO/tools/local.example.bash: -------------------------------------------------------------------------------- 1 | #doc: 2 | #doc: # MONOREPO/tools/local.example.bash 3 | #doc: 4 | #doc: Example of a possible local.bash file. By copying this file 5 | #doc: to MONOREPO/tools/local.bash, you are able to override the 6 | #doc: repositories tracked by the monorepo scripts. 7 | 8 | repositories=( 9 | . # the dot is git@github.com:ooni/probe-cli and MUST be first 10 | git@github.com:ooni/probe-android 11 | git@github.com:ooni/probe-desktop 12 | ) 13 | -------------------------------------------------------------------------------- /MONOREPO/tools/setupandroid: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #doc: 4 | #doc: # MONOREPO/tools/setupsandroid 5 | #doc: 6 | #doc: Installs the required android tools. 7 | 8 | set -euo pipefail 9 | 10 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 11 | 12 | source $reporoot/MONOREPO/tools/libcore.bash 13 | 14 | run ./MOBILE/android/setup 15 | run ./MOBILE/android/ensure 16 | -------------------------------------------------------------------------------- /MONOREPO/tools/setupshfmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #doc: 4 | #doc: # MONOREPO/tools/setupshfmt 5 | #doc: 6 | #doc: Installs the shfmt shell formatter. 7 | 8 | set -euo pipefail 9 | 10 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 11 | 12 | source $reporoot/MONOREPO/tools/libcore.bash 13 | 14 | $reporoot/MONOREPO/tools/setupgo 15 | 16 | run go install mvdan.cc/sh/v3/cmd/shfmt@latest 17 | -------------------------------------------------------------------------------- /MONOREPO/w/build-android-stable.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | reporoot=$(dirname $(dirname $(dirname $(realpath $0)))) 5 | cd $reporoot 6 | 7 | source $reporoot/MONOREPO/tools/libgit.bash 8 | for_each_repo fail_if_dirty 9 | for_each_repo fail_if_not_main 10 | 11 | run ./MOBILE/android/newkeystore 12 | 13 | ( 14 | run export ANDROID_HOME=$(./MOBILE/android/home) 15 | run cd ./MONOREPO/repo/probe-android 16 | 17 | run ./gradlew assembleStableFullRelease 18 | 19 | apkdir=./app/build/outputs/apk/stableFull/release 20 | run cp -v $apkdir/app-stable-full-release-unsigned.apk $reporoot/MOBILE/android/app-unsigned.apk 21 | ) 22 | 23 | run ./MOBILE/android/sign 24 | -------------------------------------------------------------------------------- /NDKVERSION: -------------------------------------------------------------------------------- 1 | 27.2.12479018 2 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | This product includes [IP Geolocation by DB-IP](https://db-ip.com). 2 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Checklist 2 | 3 | - [ ] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) 4 | - [ ] reference issue for this pull request: 5 | - [ ] 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: 6 | - [ ] if you changed code inside an experiment, make sure you bump its version number 7 | 8 | 9 | 10 | ## Description 11 | 12 | Please, insert here a more detailed description. 13 | -------------------------------------------------------------------------------- /cmd/README.md: -------------------------------------------------------------------------------- 1 | # Commands implemented in Go 2 | 3 | This directory contains the public binaries that you 4 | can build from this repository. 5 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/cli/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/apex/log" 7 | "github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root" 8 | "github.com/ooni/probe-cli/v3/internal/version" 9 | ) 10 | 11 | // Run the app. This is the main app entry point 12 | func Run() { 13 | root.Cmd.Version(version.Version) 14 | _, err := root.Cmd.Parse(os.Args[1:]) 15 | if err != nil { 16 | log.WithError(err).Error("failure in main command") 17 | os.Exit(2) 18 | } 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/cli/show/show.go: -------------------------------------------------------------------------------- 1 | package nettest 2 | 3 | import ( 4 | "github.com/alecthomas/kingpin/v2" 5 | "github.com/apex/log" 6 | "github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root" 7 | "github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/output" 8 | ) 9 | 10 | func init() { 11 | cmd := root.Command("show", "Show a specific measurement") 12 | msmtID := cmd.Arg("id", "the id of the measurement to show").Required().Int64() 13 | cmd.Action(func(_ *kingpin.ParseContext) error { 14 | ctx, err := root.Init() 15 | if err != nil { 16 | log.WithError(err).Error("failed to initialize root context") 17 | return err 18 | } 19 | msmt, err := ctx.DB().GetMeasurementJSON(*msmtID) 20 | if err != nil { 21 | log.Errorf("error: %v", err) 22 | return err 23 | } 24 | output.MeasurementJSON(msmt) 25 | return nil 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/cli/upload/upload.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "github.com/alecthomas/kingpin/v2" 5 | "github.com/apex/log" 6 | "github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root" 7 | ) 8 | 9 | func init() { 10 | cmd := root.Command("upload", "Upload a specific measurement") 11 | 12 | cmd.Action(func(_ *kingpin.ParseContext) error { 13 | log.Info("Uploading") 14 | log.Error("this function is not implemented") 15 | return nil 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/cli/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/alecthomas/kingpin/v2" 7 | "github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root" 8 | "github.com/ooni/probe-cli/v3/internal/version" 9 | ) 10 | 11 | func init() { 12 | cmd := root.Command("version", "Show version.") 13 | cmd.Action(func(_ *kingpin.ParseContext) error { 14 | fmt.Println(version.Version) 15 | return nil 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/config/settings.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // Sharing settings 4 | type Sharing struct { 5 | UploadResults bool `json:"upload_results"` 6 | } 7 | 8 | // Advanced settings 9 | type Advanced struct{} 10 | 11 | // Nettests related settings 12 | type Nettests struct { 13 | WebsitesMaxRuntime int64 `json:"websites_max_runtime"` 14 | WebsitesURLLimit int64 `json:"websites_url_limit"` 15 | WebsitesEnabledCategoryCodes []string `json:"websites_enabled_category_codes"` 16 | } 17 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/config/testdata/valid-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": 1, 3 | "_informed_consent": false, 4 | "sharing": { 5 | "upload_results": true 6 | }, 7 | "nettests": { 8 | "websites_max_runtime": 0 9 | }, 10 | "advanced": { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/dash.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Dash test implementation 6 | type Dash struct { 7 | } 8 | 9 | // Run starts the test 10 | func (d Dash) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder("dash") 12 | if err != nil { 13 | return err 14 | } 15 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 16 | } 17 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/echcheck.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // ECHCheck nettest implementation. 6 | type ECHCheck struct{} 7 | 8 | // Run starts the nettest. 9 | func (n ECHCheck) Run(ctl *Controller) error { 10 | builder, err := ctl.Session.NewExperimentBuilder("echcheck") 11 | if err != nil { 12 | return err 13 | } 14 | // providing an input containing an empty string causes the experiment 15 | // to recognize the empty string and use the default URL 16 | return ctl.Run(builder, []model.ExperimentTarget{ 17 | model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("https://cloudflare-ech.com/cdn-cgi/trace"), 18 | // Use ECH on a non-standard port. 19 | model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("https://min-ng.test.defo.ie:15443"), 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/facebook_messenger.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // FacebookMessenger test implementation 6 | type FacebookMessenger struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h FacebookMessenger) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "facebook_messenger", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/http_header_field_manipulation.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // HTTPHeaderFieldManipulation test implementation 6 | type HTTPHeaderFieldManipulation struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h HTTPHeaderFieldManipulation) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "http_header_field_manipulation", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/http_invalid_request_line.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // HTTPInvalidRequestLine test implementation 6 | type HTTPInvalidRequestLine struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h HTTPInvalidRequestLine) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "http_invalid_request_line", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/ndt.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // NDT test implementation. We use v7 of NDT since 2020-03-12. 6 | type NDT struct { 7 | } 8 | 9 | // Run starts the test 10 | func (n NDT) Run(ctl *Controller) error { 11 | // Since 2020-03-18 probe-engine exports v7 as "ndt". 12 | builder, err := ctl.Session.NewExperimentBuilder("ndt") 13 | if err != nil { 14 | return err 15 | } 16 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/psiphon.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Psiphon test implementation 6 | type Psiphon struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h Psiphon) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "psiphon", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/riseupvpn.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // RiseupVPN test implementation 6 | type RiseupVPN struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h RiseupVPN) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "riseupvpn", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/signal.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Signal nettest implementation. 6 | type Signal struct{} 7 | 8 | // Run starts the nettest. 9 | func (h Signal) Run(ctl *Controller) error { 10 | builder, err := ctl.Session.NewExperimentBuilder( 11 | "signal", 12 | ) 13 | if err != nil { 14 | return err 15 | } 16 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/telegram.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Telegram test implementation 6 | type Telegram struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h Telegram) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "telegram", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/tor.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Tor test implementation 6 | type Tor struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h Tor) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "tor", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/torsf.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // TorSf test implementation 6 | type TorSf struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h TorSf) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder("torsf") 12 | if err != nil { 13 | return err 14 | } 15 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 16 | } 17 | 18 | func (h TorSf) onlyBackground() {} 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/vanillator.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // VanillaTor test implementation 6 | type VanillaTor struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h VanillaTor) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder("vanilla_tor") 12 | if err != nil { 13 | return err 14 | } 15 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 16 | } 17 | 18 | func (h VanillaTor) onlyBackground() {} 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/nettests/whatsapp.go: -------------------------------------------------------------------------------- 1 | package nettests 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // WhatsApp test implementation 6 | type WhatsApp struct { 7 | } 8 | 9 | // Run starts the test 10 | func (h WhatsApp) Run(ctl *Controller) error { 11 | builder, err := ctl.Session.NewExperimentBuilder( 12 | "whatsapp", 13 | ) 14 | if err != nil { 15 | return err 16 | } 17 | return ctl.Run(builder, []model.ExperimentTarget{model.NewOOAPIURLInfoWithDefaultCategoryAndCountry("")}) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/ooni/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": 1, 3 | "_informed_consent": false, 4 | "sharing": { 5 | "upload_results": true 6 | }, 7 | "nettests": { 8 | "websites_max_runtime": 0 9 | }, 10 | "advanced": {} 11 | } 12 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/ooni/ooni_test.go: -------------------------------------------------------------------------------- 1 | package ooni 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | "testing" 8 | ) 9 | 10 | func TestInit(t *testing.T) { 11 | ooniHome, err := ioutil.TempDir("", "oonihome") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | defer os.RemoveAll(ooniHome) 16 | 17 | probe := NewProbe("", ooniHome) 18 | swName := "ooniprobe-cli-tests" 19 | swVersion := "3.0.0-alpha" 20 | if err := probe.Init(swName, swVersion, ""); err != nil { 21 | t.Error(err) 22 | t.Fatal("failed to init the context") 23 | } 24 | 25 | configPath := path.Join(ooniHome, "config.json") 26 | if _, err := os.Stat(configPath); os.IsNotExist(err) { 27 | t.Fatal("config file was not created") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmd/ooniprobe/internal/utils/util_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/fatih/color" 7 | ) 8 | 9 | func TestEscapeAwareRuneCountInString(t *testing.T) { 10 | var bold = color.New(color.Bold) 11 | var myColor = color.New(color.FgBlue) 12 | 13 | s := myColor.Sprintf("•ABC%s%s", bold.Sprintf("DEF"), "\x1B[00;38;5;244m\x1B[m\x1B[00;38;5;33mGHI\x1B[0m") 14 | count := EscapeAwareRuneCountInString(s) 15 | if count != 10 { 16 | t.Errorf("Count was incorrect, got: %d, want: %d.", count, 10) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ooniprobe/testdata/testing-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": 1, 3 | "_informed_consent": true, 4 | "sharing": { 5 | "upload_results": true 6 | }, 7 | "nettests": { 8 | "websites_max_runtime": 15 9 | }, 10 | "advanced": {} 11 | } 12 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This directory contains documentation. 4 | -------------------------------------------------------------------------------- /docs/design/img/git-probe-cli-change-histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooni/probe-cli/76bfcf657ac5034c21819a9d780e8ed81a94f750/docs/design/img/git-probe-cli-change-histogram.png -------------------------------------------------------------------------------- /docs/design/img/git-probe-cli-netx-deps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooni/probe-cli/76bfcf657ac5034c21819a9d780e8ed81a94f750/docs/design/img/git-probe-cli-netx-deps.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooni/probe-cli/76bfcf657ac5034c21819a9d780e8ed81a94f750/docs/logo.png -------------------------------------------------------------------------------- /internal/bytecounter/counter_test.go: -------------------------------------------------------------------------------- 1 | package bytecounter 2 | 3 | import "testing" 4 | 5 | func TestCounter(t *testing.T) { 6 | counter := New() 7 | counter.CountBytesReceived(16384) 8 | counter.CountKibiBytesReceived(10) 9 | counter.CountBytesSent(2048) 10 | counter.CountKibiBytesSent(10) 11 | if counter.BytesSent() != 12288 { 12 | t.Fatal("invalid bytes sent") 13 | } 14 | if counter.BytesReceived() != 26624 { 15 | t.Fatal("invalid bytes received") 16 | } 17 | if v := counter.KibiBytesSent(); v < 11.9 || v > 12.1 { 18 | t.Fatal("invalid kibibytes sent") 19 | } 20 | if v := counter.KibiBytesReceived(); v < 25.9 || v > 26.1 { 21 | t.Fatal("invalid kibibytes received") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/bytecounter/doc.go: -------------------------------------------------------------------------------- 1 | // Package bytecounter contains code to track the number of 2 | // bytes sent and received by a probe. 3 | package bytecounter 4 | -------------------------------------------------------------------------------- /internal/cmd/README.md: -------------------------------------------------------------------------------- 1 | # Directory github.com/ooni/probe-cli/internal/cmd 2 | 3 | This directory contains the source code for the CLI tools we build 4 | but we don't want to expose to the outside world. That is, you 5 | can only build these tools if you have cloned this repo. 6 | -------------------------------------------------------------------------------- /internal/cmd/apitool/README.md: -------------------------------------------------------------------------------- 1 | # apitool 2 | 3 | This directory contains a tool to fetch measurements. This tool is 4 | intended to sporadically fetch measurements, not for batch downloading. 5 | 6 | Please, see https://ooni.org/data for information pertaining how to 7 | access OONI data in bulk. Please see https://explorer.ooni.org if your 8 | intent is to navigate and explore OONI data. 9 | -------------------------------------------------------------------------------- /internal/cmd/apitool/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func init() { 9 | *reportid = `20201209T052225Z_urlgetter_IT_30722_n1_E1VUhMz08SEkgYFU` 10 | *input = `https://www.example.org` 11 | } 12 | 13 | func TestRaw(t *testing.T) { 14 | if testing.Short() { 15 | t.Skip("skip test in short mode") 16 | } 17 | *mode = "raw" 18 | main() 19 | } 20 | 21 | func TestMeta(t *testing.T) { 22 | if testing.Short() { 23 | t.Skip("skip test in short mode") 24 | } 25 | *mode = "meta" 26 | main() 27 | } 28 | 29 | func TestInvalidMode(t *testing.T) { 30 | defer func() { 31 | if recover() == nil { 32 | t.Fatal("the code did not panic") 33 | } 34 | }() 35 | osExit = func(code int) { 36 | panic(fmt.Errorf("%d", code)) 37 | } 38 | *mode = "antani" 39 | main() 40 | } 41 | -------------------------------------------------------------------------------- /internal/cmd/buildtool/golang_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | func TestGolangCheck(t *testing.T) { 9 | t.Run("successful case using the correct go version", func(t *testing.T) { 10 | golangCheck(filepath.Join("..", "..", "..", "GOVERSION")) 11 | }) 12 | 13 | t.Run("invalid Go version where we expect a panic", func(t *testing.T) { 14 | var panicked bool 15 | func() { 16 | defer func() { 17 | panicked = recover() != nil 18 | }() 19 | golangCheck(filepath.Join("testdata", "GOVERSION")) 20 | }() 21 | if !panicked { 22 | t.Fatal("should have panicked") 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /internal/cmd/buildtool/linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Linux builds entry point 5 | // 6 | 7 | import "github.com/spf13/cobra" 8 | 9 | // linuxSubcommand returns the linux [cobra.Command]. 10 | func linuxSubcommand() *cobra.Command { 11 | cmd := &cobra.Command{ 12 | Use: "linux", 13 | Short: "Builds ooniprobe and miniooni for linux", 14 | } 15 | cmd.AddCommand(linuxCdepsSubcommand()) 16 | cmd.AddCommand(linuxDockerSubcommand()) 17 | cmd.AddCommand(linuxStaticSubcommand()) 18 | return cmd 19 | } 20 | -------------------------------------------------------------------------------- /internal/cmd/buildtool/testdata/GOVERSION: -------------------------------------------------------------------------------- 1 | 1.17.11 2 | -------------------------------------------------------------------------------- /internal/cmd/buildtool/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var errInvalidGOOSValue = errors.New("cannot build for runtime.GOOS value") 9 | 10 | // generateLibrary generates the suitable library extension for the given GOOS. 11 | func generateLibrary(prefix string, os string) (string, error) { 12 | switch os { 13 | case "windows": 14 | return fmt.Sprintf("%s.dll", prefix), nil 15 | case "linux": 16 | return fmt.Sprintf("%s.so", prefix), nil 17 | case "darwin": 18 | return fmt.Sprintf("%s.dylib", prefix), nil 19 | default: 20 | return "", errInvalidGOOSValue 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/dnsfix/testdata/dnsreport.csv: -------------------------------------------------------------------------------- 1 | file,line,url,failure,measurement_count,anomaly_count,confirmed_count,ok_count,failure_count 2 | testdata/lists/it-copy.csv,2,http://www.torrentdownload.ws/,dns_nxdomain_error,30,10,4,9,7 3 | testdata/lists/it-copy.csv,3,http://www.torrentroom.com/,dns_nxdomain_error,0,0,0,0,0 4 | testdata/lists/it-copy.csv,6,http://www.torrentvia.com/,dns_nxdomain_error,10,0,0,0,10 5 | testdata/lists/it-copy.csv,10,http://btdigg.org/,dns_nxdomain_error,10,0,0,10,0 6 | testdata/lists/it-copy.csv,12,http://demonoid.ph/,dns_servfail_error,10,0,0,10,0 7 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/dnsfix/testdata/lists/.gitignore: -------------------------------------------------------------------------------- 1 | /it-copy.csv 2 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/dnsreport/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | /*.sqlite3 2 | /dnsreport.csv 3 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/dnsreport/testdata/dnsreport-expected.csv: -------------------------------------------------------------------------------- 1 | file,line,url,failure,measurement_count,anomaly_count,confirmed_count,ok_count,failure_count 2 | testdata/repo/lists/it.csv,2,http://www.torrentdownload.ws/,dns_nxdomain_error,30,10,4,9,7 3 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/dnsreport/testdata/repo/lists/it.csv: -------------------------------------------------------------------------------- 1 | url,category_code,category_description,date_added,source,notes 2 | http://www.torrentdownload.ws/,FILE,File-sharing,2017-04-12,, 3 | http://130.192.91.211/,FILE,File-sharing,2017-04-12,, 4 | http://torrentroom.com/,FILE,File-sharing,2017-04-12,, 5 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/sync/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/testlists/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | /it-copy.csv 2 | -------------------------------------------------------------------------------- /internal/cmd/gardener/internal/testlists/testdata/official/it/aams.csv: -------------------------------------------------------------------------------- 1 | url,name,category_code,date_added,date_published,data_format_version,source,authority,notes 2 | .cbmsport.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 3 | bet.myblog.it,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 4 | betcalcio.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 5 | betcalcio1.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 6 | betcalcio2.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 7 | betscommesse.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 8 | bet-scommesse.com,official/it/aams,GMB,,2015-04-05,0.1,AAMS,AAMS, 9 | -------------------------------------------------------------------------------- /internal/cmd/getresources/getresources.go: -------------------------------------------------------------------------------- 1 | // Command getresources downloads the resources 2 | package main 3 | 4 | import ( 5 | "log" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | log.Printf("This command is no longer needed. We will keep it into") 11 | log.Printf("the repository until the end of 2021, so you have\n") 12 | log.Printf("time to adjust your build workflow.\n") 13 | time.Sleep(5 * time.Second) 14 | } 15 | -------------------------------------------------------------------------------- /internal/cmd/ghgen/main.go: -------------------------------------------------------------------------------- 1 | // Command ghgen regenerates selected GitHub actions. 2 | package main 3 | 4 | import ( 5 | _ "embed" 6 | ) 7 | 8 | func main() { 9 | for name, jobs := range Config { 10 | generateWorkflowFile(name, jobs) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /internal/cmd/miniooni/.gitignore: -------------------------------------------------------------------------------- 1 | /report.jsonl 2 | -------------------------------------------------------------------------------- /internal/cmd/miniooni/README.md: -------------------------------------------------------------------------------- 1 | # miniooni 2 | 3 | This directory contains the source code of a simple CLI client that we 4 | use for research as well as for running QA scripts. We designed this tool 5 | to have a CLI similar to MK and OONI Probe v2.x to ease A/B testing. Perfect backwards 6 | compatibility was not a design goal for miniooni. Rather, we aimed to 7 | have as little conflict as possible, such that we can run side-by-side 8 | QA checks. 9 | -------------------------------------------------------------------------------- /internal/cmd/miniooni/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/version" 7 | ) 8 | 9 | func TestSimple(t *testing.T) { 10 | if testing.Short() { 11 | t.Skip("skip test in short mode") 12 | } 13 | MainWithConfiguration("example", &Options{ 14 | SoftwareName: "miniooni", 15 | SoftwareVersion: version.Version, 16 | Yes: true, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /internal/cmd/oohelper/README.md: -------------------------------------------------------------------------------- 1 | # oohelper 2 | 3 | This directory contains the source code of a simple client 4 | for the Web Connectivity test helper. 5 | -------------------------------------------------------------------------------- /internal/cmd/oohelper/oohelper_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestSmoke(t *testing.T) { 6 | if testing.Short() { 7 | t.Skip("skip test in short mode") 8 | } 9 | *target = "http://www.example.com" 10 | main() 11 | } 12 | -------------------------------------------------------------------------------- /internal/cmd/oohelperd/README.md: -------------------------------------------------------------------------------- 1 | # oohelperd 2 | 3 | This directory contains the source code of the Web 4 | Connectivity test helper written in Go. 5 | -------------------------------------------------------------------------------- /internal/cmd/oonireport/testdata/noentries.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooni/probe-cli/76bfcf657ac5034c21819a9d780e8ed81a94f750/internal/cmd/oonireport/testdata/noentries.json -------------------------------------------------------------------------------- /internal/cmd/ooporthelper/README.md: -------------------------------------------------------------------------------- 1 | # ooporthelper 2 | 3 | This directory contains the source code of the Port- 4 | Filtering test helper written in go -------------------------------------------------------------------------------- /internal/cmd/ooporthelper/ports.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // List of ports we want to use for running integration tests 5 | // 6 | 7 | // Ports for testing the testhelper 8 | // Note: we must only use unprivileged ports here to ensure tests run successfully 9 | var TestPorts = []string{ 10 | "8080", // tcp 11 | "5050", // tcp 12 | } 13 | -------------------------------------------------------------------------------- /internal/cmd/printversion/main.go: -------------------------------------------------------------------------------- 1 | // Command printversion prints the current version of this repository. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/version" 8 | ) 9 | 10 | func main() { 11 | fmt.Println(version.Version) 12 | } 13 | -------------------------------------------------------------------------------- /internal/database/database_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/apex/log" 9 | ) 10 | 11 | func TestConnect(t *testing.T) { 12 | tmpfile, err := ioutil.TempFile("", "dbtest") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | defer os.Remove(tmpfile.Name()) 17 | 18 | sess, err := Connect(tmpfile.Name()) 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | 23 | colls, err := sess.Collections() 24 | if err != nil { 25 | t.Error(err) 26 | } 27 | 28 | if len(colls) < 1 { 29 | log.Fatal("missing tables") 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /internal/database/migrations/3_results_is_uploaded.sql: -------------------------------------------------------------------------------- 1 | -- +migrate Down 2 | -- +migrate StatementBegin 3 | 4 | ALTER TABLE `results` 5 | DROP COLUMN result_is_uploaded; 6 | 7 | -- +migrate StatementEnd 8 | 9 | -- +migrate Up 10 | -- +migrate StatementBegin 11 | 12 | ALTER TABLE `results` 13 | ADD COLUMN result_is_uploaded TINYINT(1) DEFAULT 1 NOT NULL; 14 | 15 | -- +migrate StatementEnd -------------------------------------------------------------------------------- /internal/database/utils.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "time" 9 | ) 10 | 11 | // resultTimestamp is a windows friendly timestamp 12 | const resultTimestamp = "2006-01-02T150405.999999999Z0700" 13 | 14 | // makeResultsDir creates and returns a directory for the result 15 | func makeResultsDir(home string, name string, ts time.Time) (string, error) { 16 | p := filepath.Join(home, "msmts", 17 | fmt.Sprintf("%s-%s", name, ts.Format(resultTimestamp))) 18 | 19 | // If the path already exists, this is a problem. It should not clash, because 20 | // we are using nanosecond precision for the starttime. 21 | if _, e := os.Stat(p); e == nil { 22 | return "", errors.New("results path already exists") 23 | } 24 | err := os.MkdirAll(p, 0700) 25 | if err != nil { 26 | return "", err 27 | } 28 | return p, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/engine/.gitignore: -------------------------------------------------------------------------------- 1 | /example.org 2 | /oonipsiphon/ 3 | /psiphon-config.json.age 4 | /psiphon-config.key 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /internal/engine/allexperiments.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | // 4 | // List of all implemented experiments. 5 | // 6 | // Note: if you're looking for a way to register a new experiment, we 7 | // now use the internal/registry package for this purpose. 8 | // 9 | // (This comment will eventually autodestruct.) 10 | // 11 | 12 | import "github.com/ooni/probe-cli/v3/internal/registry" 13 | 14 | // AllExperiments returns the name of all experiments 15 | func AllExperiments() []string { 16 | return registry.ExperimentNames() 17 | } 18 | -------------------------------------------------------------------------------- /internal/engine/doc.go: -------------------------------------------------------------------------------- 1 | // Package engine contains the engine API. 2 | package engine 3 | -------------------------------------------------------------------------------- /internal/engine/session_nopsiphon_test.go: -------------------------------------------------------------------------------- 1 | //go:build !ooni_psiphon_config 2 | 3 | package engine 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "testing" 9 | ) 10 | 11 | func TestEarlySessionNoPsiphonFetchPsiphonConfig(t *testing.T) { 12 | s := &sessionTunnelEarlySession{} 13 | out, err := s.FetchPsiphonConfig(context.Background()) 14 | if !errors.Is(err, errPsiphonNoEmbeddedConfig) { 15 | t.Fatal("not the error we expected", err) 16 | } 17 | if out != nil { 18 | t.Fatal("expected nil here") 19 | } 20 | } 21 | 22 | func TestCheckEmbeddedPsiphonConfig(t *testing.T) { 23 | if err := CheckEmbeddedPsiphonConfig(); err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/engine/session_psiphon_test.go: -------------------------------------------------------------------------------- 1 | //go:build ooni_psiphon_config 2 | 3 | package engine 4 | 5 | import ( 6 | "context" 7 | "testing" 8 | ) 9 | 10 | func TestSessionEmbeddedPsiphonConfig(t *testing.T) { 11 | s := &Session{} 12 | data, err := s.FetchPsiphonConfig(context.Background()) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | if data == nil { 17 | t.Fatal("expected non-nil data here") 18 | } 19 | } 20 | 21 | func TestCheckEmbeddedPsiphonConfig(t *testing.T) { 22 | if err := CheckEmbeddedPsiphonConfig(); err != nil { 23 | t.Fatal(err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/engine/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | /asn.mmdb 2 | /ca-bundle.pem 3 | /country.mmdb 4 | /enginetests*/ 5 | /kvstore2/ 6 | /oonimkall 7 | /oonipsiphon 8 | /psiphon_config.json 9 | /psiphon_unit_tests/ 10 | /test-download-resources-idempotent*/ 11 | -------------------------------------------------------------------------------- /internal/engine/testdata/collector-expected.jsonl: -------------------------------------------------------------------------------- 1 | {"format":"json","content":{"data_format_version":"0.2.0","id":"bdd20d7a-bba5-40dd-a111-9863d7908572","input":null,"measurement_start_time":"2018-11-01 15:33:20","probe_asn":"AS0","probe_cc":"ZZ","probe_ip":"1.2.3.4","probe_network_name":"","report_id":"_id","resolver_asn":"AS15169","resolver_ip":"8.8.8.8","resolver_network_name":"Google LLC","software_name":"ooniprobe-engine","software_version":"0.1.0","test_keys":{"failure":null},"test_name":"dummy","test_runtime":5.0565230846405,"test_start_time":"2018-11-01 15:33:17","test_version":"0.1.0"}} -------------------------------------------------------------------------------- /internal/enginelocate/invalid_test.go: -------------------------------------------------------------------------------- 1 | package enginelocate 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | func invalidIPLookup( 10 | ctx context.Context, 11 | httpClient model.HTTPClient, 12 | logger model.Logger, 13 | userAgent string, 14 | resolver model.Resolver, 15 | ) (string, error) { 16 | return "invalid IP", nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/enginelocate/mmdblookup.go: -------------------------------------------------------------------------------- 1 | package enginelocate 2 | 3 | import ( 4 | "github.com/ooni/probe-cli/v3/internal/geoipx" 5 | ) 6 | 7 | type mmdbLookupper struct{} 8 | 9 | func (mmdbLookupper) LookupASN(ip string) (uint, string, error) { 10 | return geoipx.LookupASN(ip) 11 | } 12 | 13 | func (mmdbLookupper) LookupCC(ip string) (string, error) { 14 | return geoipx.LookupCC(ip) 15 | } 16 | -------------------------------------------------------------------------------- /internal/enginenetx/doc.go: -------------------------------------------------------------------------------- 1 | // Package enginenetx contains engine-specific network-extensions. 2 | package enginenetx 3 | -------------------------------------------------------------------------------- /internal/enginenetx/nullpolicy.go: -------------------------------------------------------------------------------- 1 | package enginenetx 2 | 3 | // 4 | // A policy that never returns any tactic. 5 | // 6 | 7 | import "context" 8 | 9 | // nullPolicy is a policy that never returns any tactics. 10 | // 11 | // You can use this policy to terminate the policy chain and 12 | // ensure ane existing policy has a "null" fallback. 13 | // 14 | // The zero value is ready to use. 15 | type nullPolicy struct{} 16 | 17 | var _ httpsDialerPolicy = &nullPolicy{} 18 | 19 | // LookupTactics implements httpsDialerPolicy. 20 | // 21 | // This policy returns a closed channel such that it won't 22 | // be possible to read policies from it. 23 | func (n *nullPolicy) LookupTactics(ctx context.Context, domain string, port string) <-chan *httpsDialerTactic { 24 | output := make(chan *httpsDialerTactic) 25 | close(output) 26 | return output 27 | } 28 | -------------------------------------------------------------------------------- /internal/enginenetx/nullpolicy_test.go: -------------------------------------------------------------------------------- 1 | package enginenetx 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestNullPolicy(t *testing.T) { 9 | p := &nullPolicy{} 10 | var count int 11 | for range p.LookupTactics(context.Background(), "api.ooni.io", "443") { 12 | count++ 13 | } 14 | if count != 0 { 15 | t.Fatal("should have not returned any policy") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/enginenetx/stream.go: -------------------------------------------------------------------------------- 1 | package enginenetx 2 | 3 | // streamTacticsFromSlice streams tactics from a given slice. 4 | // 5 | // This function returns a channel where we emit the edited 6 | // tactics, and which we clone when we're done. 7 | func streamTacticsFromSlice(input []*httpsDialerTactic) <-chan *httpsDialerTactic { 8 | output := make(chan *httpsDialerTactic) 9 | go func() { 10 | defer close(output) 11 | for _, tx := range input { 12 | output <- tx 13 | } 14 | }() 15 | return output 16 | } 17 | -------------------------------------------------------------------------------- /internal/enginenetx/stream_test.go: -------------------------------------------------------------------------------- 1 | package enginenetx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/ooni/probe-cli/v3/internal/testingx" 8 | ) 9 | 10 | func TestStreamTacticsFromSlice(t *testing.T) { 11 | input := []*httpsDialerTactic{} 12 | ff := &testingx.FakeFiller{} 13 | ff.Fill(&input) 14 | 15 | var output []*httpsDialerTactic 16 | for tx := range streamTacticsFromSlice(input) { 17 | output = append(output, tx) 18 | } 19 | 20 | if diff := cmp.Diff(input, output); diff != "" { 21 | t.Fatal(diff) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/engineresolver/errwrapper_test.go: -------------------------------------------------------------------------------- 1 | package engineresolver 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | ) 10 | 11 | func TestErrWrapper(t *testing.T) { 12 | ew := newErrWrapper(io.EOF, "https://dns.quad9.net/dns-query") 13 | o := ew.Error() 14 | expect := " EOF" 15 | if diff := cmp.Diff(expect, o); diff != "" { 16 | t.Fatal(diff) 17 | } 18 | if !errors.Is(ew, io.EOF) { 19 | t.Fatal("not the sub-error we expected") 20 | } 21 | if errors.Unwrap(ew) != io.EOF { 22 | t.Fatal("unwrap failed") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/engineresolver/integration_test.go: -------------------------------------------------------------------------------- 1 | package engineresolver_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/engineresolver" 8 | "github.com/ooni/probe-cli/v3/internal/kvstore" 9 | ) 10 | 11 | func TestSessionResolverGood(t *testing.T) { 12 | if testing.Short() { 13 | t.Skip("skip test in short mode") 14 | } 15 | reso := &engineresolver.Resolver{ 16 | KVStore: &kvstore.Memory{}, 17 | } 18 | defer reso.CloseIdleConnections() 19 | if reso.Network() != "sessionresolver" { 20 | t.Fatal("unexpected Network") 21 | } 22 | if reso.Address() != "" { 23 | t.Fatal("unexpected Address") 24 | } 25 | addrs, err := reso.LookupHost(context.Background(), "google.com") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if len(addrs) < 1 { 30 | t.Fatal("expected some addrs here") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/erroror/erroror.go: -------------------------------------------------------------------------------- 1 | // Package erroror contains code to represent an error or a value. 2 | package erroror 3 | 4 | // Value represents an error or a value. 5 | type Value[Type any] struct { 6 | Err error 7 | Value Type 8 | } 9 | -------------------------------------------------------------------------------- /internal/experiment/dash/dependencies.go: -------------------------------------------------------------------------------- 1 | package dash 2 | 3 | // 4 | // Dependencies for unit testing. 5 | // 6 | 7 | import ( 8 | "context" 9 | "io" 10 | "net/http" 11 | 12 | "github.com/ooni/probe-cli/v3/internal/model" 13 | ) 14 | 15 | // dependencies allows unit testing each phase of the experiment. 16 | type dependencies interface { 17 | // HTTPClient returns the HTTP client to use. 18 | HTTPClient() model.HTTPClient 19 | 20 | // Logger returns the logger we should use. 21 | Logger() model.Logger 22 | 23 | // NewHTTPRequestWithContext allows to mock the [http.NewRequestWithContext] function. 24 | NewHTTPRequestWithContext( 25 | context context.Context, method string, url string, body io.Reader) (*http.Request, error) 26 | 27 | // UserAgent returns the user agent we should use. 28 | UserAgent() string 29 | } 30 | -------------------------------------------------------------------------------- /internal/experiment/dash/doc.go: -------------------------------------------------------------------------------- 1 | // Package dash implements the DASH network experiment. 2 | // 3 | // Spec: https://github.com/ooni/spec/blob/master/nettests/ts-021-dash.md 4 | package dash 5 | -------------------------------------------------------------------------------- /internal/experiment/dash/locate.go: -------------------------------------------------------------------------------- 1 | package dash 2 | 3 | // 4 | // Code to invoke m-lab's locate API. 5 | // 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/ooni/probe-cli/v3/internal/mlablocatev2" 11 | "github.com/ooni/probe-cli/v3/internal/runtimex" 12 | ) 13 | 14 | // locate issues a query to m-lab's locate services to obtain the 15 | // m-lab server with which to perform a DASH experiment. 16 | func locate(ctx context.Context, deps dependencies) (*mlablocatev2.DashResult, error) { 17 | client := mlablocatev2.NewClient(deps.HTTPClient(), deps.Logger(), deps.UserAgent()) 18 | result, err := client.QueryDash(ctx) 19 | if err != nil { 20 | return nil, err 21 | } 22 | runtimex.Assert(len(result) >= 1, "too few entries") 23 | return result[0], nil // ~same as with locate services v1 24 | } 25 | -------------------------------------------------------------------------------- /internal/experiment/dnscheck/testdata/input.txt: -------------------------------------------------------------------------------- 1 | https://1dot1dot1dot1dot.com/dns-query 2 | https://dns.cloudflare/dns-query 3 | -------------------------------------------------------------------------------- /internal/experiment/echcheck/config.go: -------------------------------------------------------------------------------- 1 | package echcheck 2 | 3 | const ( 4 | defaultResolver = "https://mozilla.cloudflare-dns.com/dns-query" 5 | ) 6 | 7 | // Config contains the experiment config. 8 | type Config struct { 9 | // ResolverURL is the default DoH resolver 10 | ResolverURL string `ooni:"URL for DoH resolver"` 11 | } 12 | 13 | func (c Config) resolverURL() string { 14 | if c.ResolverURL != "" { 15 | return c.ResolverURL 16 | } 17 | return defaultResolver 18 | } 19 | -------------------------------------------------------------------------------- /internal/experiment/echcheck/config_test.go: -------------------------------------------------------------------------------- 1 | package echcheck 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConfig(t *testing.T) { 8 | 9 | c := Config{ 10 | ResolverURL: "", 11 | } 12 | s1 := c.resolverURL() 13 | if s1 != defaultResolver { 14 | t.Fatalf("expected: %s, got %s", defaultResolver, s1) 15 | } 16 | 17 | testResolver := "testResolver" 18 | 19 | c = Config{ 20 | ResolverURL: testResolver, 21 | } 22 | s1 = c.resolverURL() 23 | if s1 != testResolver { 24 | t.Fatalf("expected: %s, got %s", testResolver, s1) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/experiment/echcheck/doc.go: -------------------------------------------------------------------------------- 1 | // Package echcheck contains the ECH blocking network experiment. 2 | // 3 | // https://github.com/ooni/spec/pull/263 4 | package echcheck 5 | -------------------------------------------------------------------------------- /internal/experiment/echcheck/generate_test.go: -------------------------------------------------------------------------------- 1 | package echcheck 2 | 3 | import ( 4 | "crypto/rand" 5 | "testing" 6 | ) 7 | 8 | func TestParseableGREASEConfigList(t *testing.T) { 9 | // A GREASE extension that can't be parsed is invalid. 10 | grease, err := generateGreaseyECHConfigList(rand.Reader, "example.com") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if _, err := parseECHConfigList(grease); err != nil { 15 | t.Fatal(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/experiment/ndt7/callback.go: -------------------------------------------------------------------------------- 1 | package ndt7 2 | 3 | import "time" 4 | 5 | type ( 6 | callbackJSON func(data []byte) error 7 | callbackPerformance func(elapsed time.Duration, count int64) 8 | ) 9 | -------------------------------------------------------------------------------- /internal/experiment/ndt7/doc.go: -------------------------------------------------------------------------------- 1 | // Package ndt7 contains the ndt7 network experiment. 2 | // 3 | // See https://github.com/ooni/spec/blob/master/nettests/ts-022-ndt.md 4 | package ndt7 5 | -------------------------------------------------------------------------------- /internal/experiment/ndt7/param.go: -------------------------------------------------------------------------------- 1 | package ndt7 2 | 3 | import "time" 4 | 5 | const ( 6 | paramFractionForScaling = 16 7 | paramMinMessageSize = 1 << 10 8 | paramMaxBufferSize = 1 << 20 9 | paramMaxScaledMessageSize = 1 << 20 10 | paramMaxMessageSize = 1 << 24 11 | paramMaxRuntimeUpperBound = 15.0 // seconds 12 | paramMaxRuntime = 10 * time.Second 13 | paramMeasureInterval = 250 * time.Millisecond 14 | ) 15 | -------------------------------------------------------------------------------- /internal/experiment/ndt7/wsconn.go: -------------------------------------------------------------------------------- 1 | package ndt7 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | // wsConn is the interface of gorilla/websocket.Conn 11 | type wsConn interface { 12 | NextReader() (int, io.Reader, error) 13 | SetReadDeadline(time.Time) error 14 | SetReadLimit(int64) 15 | SetWriteDeadline(time.Time) error 16 | WritePreparedMessage(*websocket.PreparedMessage) error 17 | } 18 | -------------------------------------------------------------------------------- /internal/experiment/portfiltering/config.go: -------------------------------------------------------------------------------- 1 | package portfiltering 2 | 3 | // 4 | // Config for the port-filtering experiment 5 | // 6 | 7 | import "time" 8 | 9 | // Config contains the experiment configuration. 10 | type Config struct { 11 | // Delay is the delay between each repetition (in milliseconds). 12 | Delay int64 `ooni:"number of milliseconds to wait before testing each port"` 13 | } 14 | 15 | func (c *Config) delay() time.Duration { 16 | if c.Delay > 0 { 17 | return time.Duration(c.Delay) * time.Millisecond 18 | } 19 | return 100 * time.Millisecond 20 | } 21 | -------------------------------------------------------------------------------- /internal/experiment/portfiltering/config_test.go: -------------------------------------------------------------------------------- 1 | package portfiltering 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestConfig_delay(t *testing.T) { 9 | c := Config{} 10 | if c.delay() != 100*time.Millisecond { 11 | t.Fatal("invalid default delay") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /internal/experiment/portfiltering/doc.go: -------------------------------------------------------------------------------- 1 | // Package portfiltering implements the portfiltering experiment 2 | // 3 | // Spec: https://github.com/ooni/spec/blob/master/nettests/ts-038-port-filtering.md. 4 | package portfiltering 5 | -------------------------------------------------------------------------------- /internal/experiment/portfiltering/testkeys.go: -------------------------------------------------------------------------------- 1 | package portfiltering 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // TestKeys contains the experiment results. 6 | type TestKeys struct { 7 | TCPConnect []*model.ArchivalTCPConnectResult `json:"tcp_connect"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/experiment/tlsmiddlebox/doc.go: -------------------------------------------------------------------------------- 1 | // Package tlsmiddlebox implements the tlsmiddlebox experiment 2 | // 3 | // Spec: https://github.com/ooni/spec/blob/master/nettests/ts-037-tlsmiddlebox.md. 4 | package tlsmiddlebox 5 | -------------------------------------------------------------------------------- /internal/experiment/tlsmiddlebox/syscall_cgo.go: -------------------------------------------------------------------------------- 1 | //go:build cgo && windows 2 | 3 | package tlsmiddlebox 4 | 5 | // 6 | // CGO support for SO_ERROR 7 | // 8 | 9 | /* 10 | #cgo windows LDFLAGS: -lws2_32 11 | 12 | #ifdef _WIN32 13 | #include 14 | #endif 15 | */ 16 | import "C" 17 | 18 | import "unsafe" 19 | 20 | // getErrFromSockOpt returns the errno of the SO_ERROR 21 | // 22 | // This is the CGO_ENABLED=1 implementation of this function, which 23 | // returns the errno obtained from the getsockopt call 24 | func getErrFromSockOpt(fd uintptr) int { 25 | var cErrno C.int 26 | szInt := C.sizeof_int 27 | C.getsockopt((C.SOCKET)(fd), (C.int)(C.SOL_SOCKET), (C.int)(C.SO_ERROR), (*C.char)(unsafe.Pointer(&cErrno)), (*C.int)(unsafe.Pointer(&szInt))) 28 | return int(cErrno) 29 | } 30 | -------------------------------------------------------------------------------- /internal/experiment/tlsmiddlebox/syscall_otherwise.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package tlsmiddlebox 4 | 5 | // 6 | // Disabled CGO for SO_ERROR 7 | // 8 | 9 | // getErrFromSockOpt returns the errno of the SO_ERROR 10 | // 11 | // This is the CGO_ENABLED=0 implementation of this function, which 12 | // always returns errno=0 for SO_ERROR 13 | func getErrFromSockOpt(fd uintptr) int { 14 | return 0 15 | } 16 | -------------------------------------------------------------------------------- /internal/experiment/tlsmiddlebox/utils.go: -------------------------------------------------------------------------------- 1 | package tlsmiddlebox 2 | 3 | // 4 | // Utility functions for tlsmiddlebox 5 | // 6 | 7 | import ( 8 | "net" 9 | ) 10 | 11 | // prepareAddrs prepares the resolved IP addresses by 12 | // adding the configured port as a prefix 13 | func prepareAddrs(addrs []string, port string) (out []string) { 14 | if port == "" { 15 | port = "443" 16 | } 17 | for _, addr := range addrs { 18 | if net.ParseIP(addr) == nil { 19 | continue 20 | } 21 | addr = net.JoinHostPort(addr, port) 22 | out = append(out, addr) 23 | } 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /internal/experiment/tlstool/internal/dialer.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | // Dialer creates net.Conn instances where (1) we delay writes if 12 | // a delay is configured and (2) we split outgoing buffers if there 13 | // is a configured splitter function. 14 | type Dialer struct { 15 | model.Dialer 16 | Delay time.Duration 17 | Splitter func([]byte) [][]byte 18 | } 19 | 20 | // DialContext implements netx.Dialer.DialContext. 21 | func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 22 | conn, err := d.Dialer.DialContext(ctx, network, address) 23 | if err != nil { 24 | return nil, err 25 | } 26 | conn = SleeperWriter{Conn: conn, Delay: d.Delay} 27 | conn = SplitterWriter{Conn: conn, Splitter: d.Splitter} 28 | return conn, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/experiment/torsf/.gitignore: -------------------------------------------------------------------------------- 1 | /torsf 2 | -------------------------------------------------------------------------------- /internal/experiment/urlgetter/.gitignore: -------------------------------------------------------------------------------- 1 | /urlgetter-tunnel 2 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivity/control_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivity 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | ) 8 | 9 | func TestFillASNsEmpty(t *testing.T) { 10 | dns := new(ControlDNSResult) 11 | fillASNs(dns) 12 | if diff := cmp.Diff(dns.ASNs, []int64{}); diff != "" { 13 | t.Fatal(diff) 14 | } 15 | } 16 | 17 | func TestFillASNsSuccess(t *testing.T) { 18 | dns := new(ControlDNSResult) 19 | dns.Addrs = []string{"8.8.8.8", "1.1.1.1"} 20 | fillASNs(dns) 21 | if diff := cmp.Diff(dns.ASNs, []int64{15169, 13335}); diff != "" { 22 | t.Fatal(diff) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivity/doc.go: -------------------------------------------------------------------------------- 1 | // Package webconnectivity implements OONI's Web Connectivity experiment. 2 | // 3 | // See https://github.com/ooni/spec/blob/master/nettests/ts-017-web-connectivity.md 4 | package webconnectivity 5 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivity/internal/internal.go: -------------------------------------------------------------------------------- 1 | // Package internal contains internal code. 2 | package internal 3 | 4 | import "fmt" 5 | 6 | // StringPointerToString converts a string pointer to a string. When the 7 | // pointer is null, we return the "nil" string. 8 | func StringPointerToString(v *string) (out string) { 9 | out = "nil" 10 | if v != nil { 11 | out = fmt.Sprintf("%+v", *v) 12 | } 13 | return 14 | } 15 | 16 | // BoolPointerToString is like StringPointerToString but for bool. 17 | func BoolPointerToString(v *bool) (out string) { 18 | out = "nil" 19 | if v != nil { 20 | out = fmt.Sprintf("%+v", *v) 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivity/internal/internal_test.go: -------------------------------------------------------------------------------- 1 | package internal_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivity/internal" 7 | ) 8 | 9 | func TestStringPointerToString(t *testing.T) { 10 | s := "ANTANI" 11 | if internal.StringPointerToString(&s) != s { 12 | t.Fatal("unexpected result") 13 | } 14 | if internal.StringPointerToString(nil) != "nil" { 15 | t.Fatal("unexpected result") 16 | } 17 | } 18 | 19 | func TestBoolPointerToString(t *testing.T) { 20 | v := true 21 | if internal.BoolPointerToString(&v) != "true" { 22 | t.Fatal("unexpected result") 23 | } 24 | v = false 25 | if internal.BoolPointerToString(&v) != "false" { 26 | t.Fatal("unexpected result") 27 | } 28 | if internal.BoolPointerToString(nil) != "nil" { 29 | t.Fatal("unexpected result") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivity/qa_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivity 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/webconnectivityqa" 7 | ) 8 | 9 | func TestQA(t *testing.T) { 10 | for _, tc := range webconnectivityqa.AllTestCases() { 11 | t.Run(tc.Name, func(t *testing.T) { 12 | if (tc.Flags & webconnectivityqa.TestCaseFlagNoV04) != 0 { 13 | t.Skip("this test case cannot run on Web Connectivity v0.4") 14 | } 15 | if testing.Short() && tc.LongTest { 16 | t.Skip("skip test in short mode") 17 | } 18 | measurer := NewExperimentMeasurer(Config{}) 19 | if err := webconnectivityqa.RunTestCase(measurer, tc); err != nil { 20 | t.Fatal(err) 21 | } 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/config.go: -------------------------------------------------------------------------------- 1 | package webconnectivitylte 2 | 3 | // 4 | // Config 5 | // 6 | 7 | // Config contains webconnectivity experiment configuration. 8 | type Config struct { 9 | DNSOverUDPResolver string 10 | } 11 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/dnswhoami.go: -------------------------------------------------------------------------------- 1 | package webconnectivitylte 2 | 3 | import ( 4 | "github.com/ooni/probe-cli/v3/internal/model" 5 | "github.com/ooni/probe-cli/v3/internal/webconnectivityalgo" 6 | ) 7 | 8 | // DNSWhoamiSingleton is the DNSWhoamiService singleton. 9 | var DNSWhoamiSingleton = webconnectivityalgo.NewDNSWhoamiService(model.DiscardLogger) 10 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/doc.go: -------------------------------------------------------------------------------- 1 | // Package webconnectivitylte implements the web_connectivity experiment. 2 | // 3 | // Spec: https://github.com/ooni/spec/blob/master/nettests/ts-017-web-connectivity.md. 4 | // 5 | // This implementation, in particular, contains extensions over the original model, 6 | // which we document at https://github.com/ooni/probe/issues/2237. 7 | package webconnectivitylte 8 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/qa_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivitylte 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/webconnectivityqa" 7 | ) 8 | 9 | func TestQA(t *testing.T) { 10 | for _, tc := range webconnectivityqa.AllTestCases() { 11 | t.Run(tc.Name, func(t *testing.T) { 12 | if (tc.Flags & webconnectivityqa.TestCaseFlagNoLTE) != 0 { 13 | t.Skip("this test case cannot run on Web Connectivity LTE") 14 | } 15 | if testing.Short() && tc.LongTest { 16 | t.Skip("skip test in short mode") 17 | } 18 | measurer := NewExperimentMeasurer(&Config{}) 19 | if err := webconnectivityqa.RunTestCase(measurer, tc); err != nil { 20 | t.Fatal(err) 21 | } 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/redirects.go: -------------------------------------------------------------------------------- 1 | package webconnectivitylte 2 | 3 | import "sync/atomic" 4 | 5 | // NumRedirects counts the number of redirects left. 6 | type NumRedirects struct { 7 | count *atomic.Int64 8 | } 9 | 10 | // NewNumRedirects creates a new NumRedirects instance. 11 | func NewNumRedirects(n int64) *NumRedirects { 12 | count := &atomic.Int64{} 13 | count.Add(n) 14 | return &NumRedirects{ 15 | count: count, 16 | } 17 | } 18 | 19 | // CanFollowOneMoreRedirect returns true if we are 20 | // allowed to follow one more redirect. 21 | func (nr *NumRedirects) CanFollowOneMoreRedirect() bool { 22 | return nr.count.Add(-1) >= 0 23 | } 24 | -------------------------------------------------------------------------------- /internal/experiment/webconnectivitylte/redirects_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivitylte 2 | 3 | import "testing" 4 | 5 | func TestNumRedirects(t *testing.T) { 6 | const count = 10 7 | nr := NewNumRedirects(count) 8 | for idx := 0; idx < count; idx++ { 9 | if !nr.CanFollowOneMoreRedirect() { 10 | t.Fatal("got false with idx=", idx) 11 | } 12 | } 13 | if nr.CanFollowOneMoreRedirect() { 14 | t.Fatal("got true after the loop") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/experimentname/experimentname.go: -------------------------------------------------------------------------------- 1 | // Package experimentname contains code to manipulate experiment names. 2 | package experimentname 3 | 4 | import "github.com/ooni/probe-cli/v3/internal/strcasex" 5 | 6 | // Canonicalize allows code to provide experiment names 7 | // in a more flexible way, where we have aliases. 8 | // 9 | // Because we allow for uppercase experiment names for backwards 10 | // compatibility with MK, we need to add some exceptions here when 11 | // mapping (e.g., DNSCheck => dnscheck). 12 | func Canonicalize(name string) string { 13 | switch name = strcasex.ToSnake(name); name { 14 | case "ndt_7": 15 | name = "ndt" // since 2020-03-18, we use ndt7 to implement ndt by default 16 | case "dns_check": 17 | name = "dnscheck" 18 | case "stun_reachability": 19 | name = "stunreachability" 20 | case "web_connectivity@v_0_5": 21 | name = "web_connectivity@v0.5" 22 | default: 23 | } 24 | return name 25 | } 26 | -------------------------------------------------------------------------------- /internal/feature/psiphonfeat/doc.go: -------------------------------------------------------------------------------- 1 | // Package psiphonfeat implements the psiphon feature. When it is possible 2 | // to enable this feature, we are able to start psiphon tunnels. Otherwise, it's 3 | // not possible to start psiphon tunnels. 4 | package psiphonfeat 5 | -------------------------------------------------------------------------------- /internal/feature/psiphonfeat/psiphon_enabled.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.24 && !ooni_feature_disable_psiphon 2 | 3 | package psiphonfeat 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/Psiphon-Labs/psiphon-tunnel-core/ClientLibrary/clientlib" 9 | ) 10 | 11 | // Enabled indicates whether this feature is enabled. 12 | const Enabled = true 13 | 14 | // Start attempts to start the Psiphon tunnel and returns either a Tunnel or an error. 15 | func Start(ctx context.Context, config []byte, workdir string) (Tunnel, error) { 16 | tun, err := clientlib.StartTunnel(ctx, config, "", clientlib.Parameters{ 17 | DataRootDirectory: &workdir}, nil, nil) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return &tunnel{tun}, nil 22 | } 23 | 24 | type tunnel struct { 25 | *clientlib.PsiphonTunnel 26 | } 27 | 28 | // GetSOCKSProxyPort implements Tunnel. 29 | func (t *tunnel) GetSOCKSProxyPort() int { 30 | return t.SOCKSProxyPort 31 | } 32 | -------------------------------------------------------------------------------- /internal/feature/psiphonfeat/psiphon_otherwise.go: -------------------------------------------------------------------------------- 1 | //go:build go1.24 || ooni_feature_disable_psiphon 2 | 3 | package psiphonfeat 4 | 5 | import "context" 6 | 7 | // Enabled indicates whether this feature is enabled. 8 | const Enabled = false 9 | 10 | // Start attempts to start the Psiphon tunnel and returns either a Tunnel or an error. 11 | func Start(ctx context.Context, config []byte, workdir string) (Tunnel, error) { 12 | return nil, ErrFeatureNotEnabled 13 | } 14 | -------------------------------------------------------------------------------- /internal/feature/psiphonfeat/tunnel.go: -------------------------------------------------------------------------------- 1 | package psiphonfeat 2 | 3 | import "errors" 4 | 5 | // Tunnel is the interface implementing the Psiphon tunnel. 6 | type Tunnel interface { 7 | // Stop stops a running Psiphon tunnel. 8 | Stop() 9 | 10 | // GetSOCKSProxyPort returns the SOCKS5 port used by the tunnel. 11 | GetSOCKSProxyPort() int 12 | } 13 | 14 | // ErrFeatureNotEnabled indicates that the Psiphon feature is not enabled in this build. 15 | var ErrFeatureNotEnabled = errors.New("psiphonfeat: not enabled") 16 | -------------------------------------------------------------------------------- /internal/fsx/example_test.go: -------------------------------------------------------------------------------- 1 | package fsx_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "path/filepath" 9 | "syscall" 10 | 11 | "github.com/ooni/probe-cli/v3/internal/fsx" 12 | "github.com/ooni/probe-cli/v3/internal/netxlite" 13 | ) 14 | 15 | func ExampleOpenFile_openingDir() { 16 | filep, err := fsx.OpenFile("testdata") 17 | if !errors.Is(err, syscall.ENOENT) { 18 | log.Fatal("unexpected error", err) 19 | } 20 | if filep != nil { 21 | log.Fatal("expected nil fp") 22 | } 23 | } 24 | 25 | func ExampleOpenFile_openingFile() { 26 | filep, err := fsx.OpenFile(filepath.Join("testdata", "testfile.txt")) 27 | if err != nil { 28 | log.Fatal("unexpected error", err) 29 | } 30 | data, err := netxlite.ReadAllContext(context.Background(), filep) 31 | if err != nil { 32 | log.Fatal("unexpected error", err) 33 | } 34 | fmt.Printf("%d\n", len(data)) 35 | } 36 | -------------------------------------------------------------------------------- /internal/fsx/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /internal/fsx/testdata/testfile.txt: -------------------------------------------------------------------------------- 1 | my test input -------------------------------------------------------------------------------- /internal/httpclientx/config_test.go: -------------------------------------------------------------------------------- 1 | package httpclientx 2 | 3 | import "testing" 4 | 5 | func TestConfigMaxResponseBodySize(t *testing.T) { 6 | t.Run("the default returned value corresponds to the constant default", func(t *testing.T) { 7 | config := &Config{} 8 | if value := config.maxResponseBodySize(); value != DefaultMaxResponseBodySize { 9 | t.Fatal("unexpected maxResponseBodySize()", value) 10 | } 11 | }) 12 | 13 | t.Run("we can override the default", func(t *testing.T) { 14 | config := &Config{} 15 | const expectedValue = DefaultMaxResponseBodySize / 2 16 | config.MaxResponseBodySize = expectedValue 17 | if value := config.maxResponseBodySize(); value != expectedValue { 18 | t.Fatal("unexpected maxResponseBodySize()", value) 19 | } 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /internal/hujsonx/hujsonx.go: -------------------------------------------------------------------------------- 1 | // Package hujsonx contains github.com/tailscale/hujson extensions. 2 | package hujsonx 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | "github.com/tailscale/hujson" 8 | ) 9 | 10 | // Unmarshal is like [json.Unmarshal] except that it first removes comments and 11 | // extra commas using the [hujson.Standardize] function. 12 | func Unmarshal(data []byte, v any) error { 13 | data, err := hujson.Standardize(data) 14 | if err != nil { 15 | return err 16 | } 17 | return json.Unmarshal(data, v) 18 | } 19 | -------------------------------------------------------------------------------- /internal/hujsonx/hujsonx_test.go: -------------------------------------------------------------------------------- 1 | package hujsonx 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | type user struct { 10 | Name string 11 | Age int 12 | } 13 | 14 | func TestHuJSONXWorkingAsIntended(t *testing.T) { 15 | t.Run("for invalid input", func(t *testing.T) { 16 | input := []byte("{") 17 | var v user 18 | err := Unmarshal(input, &v) 19 | if !errors.Is(err, io.ErrUnexpectedEOF) { 20 | t.Fatal("unexpected error", err) 21 | } 22 | }) 23 | 24 | t.Run("for valid JSON we cannot map to a real struct", func(t *testing.T) { 25 | input := []byte(`{"Name": {}, "Age": []}`) 26 | var v user 27 | err := Unmarshal(input, &v) 28 | expected := "json: cannot unmarshal object into Go struct field user.Name of type string" 29 | if err == nil || err.Error() != expected { 30 | t.Fatal("unexpected error", err) 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /internal/humanize/humanize.go: -------------------------------------------------------------------------------- 1 | // Package humanize is like dustin/go-humanize. 2 | package humanize 3 | 4 | import "fmt" 5 | 6 | // SI is like dustin/go-humanize.SI but its implementation is 7 | // specially tailored for printing download speeds. 8 | func SI(value float64, unit string) string { 9 | value, prefix := reduce(value) 10 | return fmt.Sprintf("%6.2f %s%s", value, prefix, unit) 11 | } 12 | 13 | // reduce reduces value to a base value and a unit prefix. For 14 | // example, reduce(1055) returns (1.055, "k"). 15 | func reduce(value float64) (float64, string) { 16 | if value < 1e03 { 17 | return value, " " 18 | } 19 | value /= 1e03 20 | if value < 1e03 { 21 | return value, "k" 22 | } 23 | value /= 1e03 24 | if value < 1e03 { 25 | return value, "M" 26 | } 27 | value /= 1e03 28 | return value, "G" 29 | } 30 | -------------------------------------------------------------------------------- /internal/idnax/idnax.go: -------------------------------------------------------------------------------- 1 | // Package idnax contains IDNA extensions. 2 | package idnax 3 | 4 | import "golang.org/x/net/idna" 5 | 6 | // ToASCII converts an IDNA to ASCII using the [idna.Lookup] profile. 7 | func ToASCII(domain string) (string, error) { 8 | return idna.Lookup.ToASCII(domain) 9 | } 10 | -------------------------------------------------------------------------------- /internal/kvstore/doc.go: -------------------------------------------------------------------------------- 1 | // Package kvstore implements model.KeyValueStore. 2 | package kvstore 3 | -------------------------------------------------------------------------------- /internal/kvstore/example_test.go: -------------------------------------------------------------------------------- 1 | package kvstore_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "reflect" 8 | 9 | "github.com/ooni/probe-cli/v3/internal/kvstore" 10 | ) 11 | 12 | func ExampleMemory() { 13 | kvs := &kvstore.Memory{} 14 | if _, err := kvs.Get("akey"); !errors.Is(err, kvstore.ErrNoSuchKey) { 15 | log.Fatal("unexpected error", err) 16 | } 17 | val := []byte("value") 18 | if err := kvs.Set("akey", val); err != nil { 19 | log.Fatal("unexpected error", err) 20 | } 21 | out, err := kvs.Get("akey") 22 | if err != nil { 23 | log.Fatal("unexpected error", err) 24 | } 25 | fmt.Printf("%+v\n", reflect.DeepEqual(val, out)) 26 | // Output: true 27 | } 28 | -------------------------------------------------------------------------------- /internal/kvstore/memory_test.go: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestNoSuchKey(t *testing.T) { 9 | kvs := &Memory{} 10 | value, err := kvs.Get("nonexistent") 11 | if !errors.Is(err, ErrNoSuchKey) { 12 | t.Fatal("expected an error here") 13 | } 14 | if value != nil { 15 | t.Fatal("expected empty string here") 16 | } 17 | } 18 | 19 | func TestExistingKey(t *testing.T) { 20 | kvs := &Memory{} 21 | if err := kvs.Set("antani", []byte("mascetti")); err != nil { 22 | t.Fatal(err) 23 | } 24 | value, err := kvs.Get("antani") 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if string(value) != "mascetti" { 29 | t.Fatal("not the result we expected") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/legacy/legacymodel/doc.go: -------------------------------------------------------------------------------- 1 | // Package legacymodel contains legacy content that used to be in internal/model 2 | package legacymodel 3 | -------------------------------------------------------------------------------- /internal/legacy/measurex/doc.go: -------------------------------------------------------------------------------- 1 | // Package measurex contains measurement extensions. 2 | // 3 | // This package is now frozen. Please, use measurexlite for new code. See 4 | // https://github.com/ooni/probe-cli/blob/master/docs/design/dd-003-step-by-step.md 5 | // for details about this. 6 | package measurex 7 | -------------------------------------------------------------------------------- /internal/legacy/measurex/failure.go: -------------------------------------------------------------------------------- 1 | package measurex 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/measurexlite" 4 | 5 | // NewFailure is an alias for measurexlite.NewFailure 6 | var NewFailure = measurexlite.NewFailure 7 | -------------------------------------------------------------------------------- /internal/legacy/measurex/logger.go: -------------------------------------------------------------------------------- 1 | package measurex 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/logx" 4 | 5 | // 6 | // Logger 7 | // 8 | // Code for logging 9 | // 10 | 11 | // NewOperationLogger is an alias for measurex.NewOperationLogger. 12 | var NewOperationLogger = logx.NewOperationLogger 13 | 14 | // OperationLogger is an alias for measurex.OperationLogger. 15 | type OperationLogger = logx.OperationLogger 16 | -------------------------------------------------------------------------------- /internal/legacy/multierror/example_test.go: -------------------------------------------------------------------------------- 1 | package multierror_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/legacy/multierror" 8 | ) 9 | 10 | func ExampleUnion() { 11 | root := errors.New("some error") 12 | me := multierror.New(root) 13 | check := errors.Is(me, root) 14 | fmt.Printf("%+v\n", check) 15 | // Output: true 16 | } 17 | -------------------------------------------------------------------------------- /internal/legacy/netx/quic.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | // 4 | // QUICDialer from Config. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | "github.com/ooni/probe-cli/v3/internal/netxlite" 10 | ) 11 | 12 | // NewQUICDialer creates a new QUICDialer using the given Config. 13 | func NewQUICDialer(config Config) model.QUICDialer { 14 | if config.FullResolver == nil { 15 | config.FullResolver = NewResolver(config) 16 | } 17 | // TODO(https://github.com/ooni/probe/issues/2121#issuecomment-1147424810): we 18 | // should count the bytes consumed by this QUIC dialer 19 | netx := &netxlite.Netx{} 20 | ql := config.ReadWriteSaver.WrapUDPListener(netx.NewUDPListener()) 21 | logger := model.ValidLoggerOrDefault(config.Logger) 22 | return netx.NewQUICDialerWithResolver(ql, logger, config.FullResolver, config.Saver) 23 | } 24 | -------------------------------------------------------------------------------- /internal/legacy/netx/resolver.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | // 4 | // Resolver from Config. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | "github.com/ooni/probe-cli/v3/internal/netxlite" 10 | ) 11 | 12 | // NewResolver creates a new resolver from the specified config. 13 | func NewResolver(config Config) model.Resolver { 14 | if config.BaseResolver == nil { 15 | config.BaseResolver = netxlite.NewUnwrappedStdlibResolver() 16 | } 17 | r := netxlite.WrapResolver( 18 | model.ValidLoggerOrDefault(config.Logger), 19 | config.BaseResolver, 20 | ) 21 | r = netxlite.MaybeWrapWithCachingResolver(config.CacheResolutions, r) 22 | r = netxlite.MaybeWrapWithStaticDNSCache(config.DNSCache, r) 23 | r = netxlite.MaybeWrapWithBogonResolver(config.BogonIsError, r) 24 | return config.Saver.WrapResolver(r) // WAI when config.Saver==nil 25 | } 26 | -------------------------------------------------------------------------------- /internal/legacy/netx/resolver_test.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/apex/log" 9 | "github.com/ooni/probe-cli/v3/internal/legacy/tracex" 10 | "github.com/ooni/probe-cli/v3/internal/netxlite" 11 | ) 12 | 13 | func TestNewResolverBogonResolutionNotBroken(t *testing.T) { 14 | saver := new(tracex.Saver) 15 | r := NewResolver(Config{ 16 | BogonIsError: true, 17 | DNSCache: map[string][]string{ 18 | "www.google.com": {"127.0.0.1"}, 19 | }, 20 | Saver: saver, 21 | Logger: log.Log, 22 | }) 23 | addrs, err := r.LookupHost(context.Background(), "www.google.com") 24 | if !errors.Is(err, netxlite.ErrDNSBogon) { 25 | t.Fatal("not the error we expected") 26 | } 27 | if err.Error() != netxlite.FailureDNSBogonError { 28 | t.Fatal("error not correctly wrapped") 29 | } 30 | if len(addrs) > 0 { 31 | t.Fatal("expected no addresses here") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/legacy/netx/tls.go: -------------------------------------------------------------------------------- 1 | package netx 2 | 3 | // 4 | // TLSDialer from Config. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | "github.com/ooni/probe-cli/v3/internal/netxlite" 10 | ) 11 | 12 | // NewTLSDialer creates a new TLSDialer from the specified config. 13 | func NewTLSDialer(config Config) model.TLSDialer { 14 | if config.Dialer == nil { 15 | config.Dialer = NewDialer(config) 16 | } 17 | netx := &netxlite.Netx{} 18 | logger := model.ValidLoggerOrDefault(config.Logger) 19 | thx := netx.NewTLSHandshakerStdlib(logger) 20 | thx = config.Saver.WrapTLSHandshaker(thx) // WAI even when config.Saver is nil 21 | tlsConfig := netxlite.ClonedTLSConfigOrNewEmptyConfig(config.TLSConfig) 22 | return netxlite.NewTLSDialerWithConfig(config.Dialer, thx, tlsConfig) 23 | } 24 | -------------------------------------------------------------------------------- /internal/legacy/tracex/doc.go: -------------------------------------------------------------------------------- 1 | // Package tracex performs measurements using tracing. To use tracing means 2 | // that we'll wrap netx data types (e.g., a Dialer) with equivalent data types 3 | // saving events into a Saver data struture. Then we will use the data types 4 | // normally (e.g., call the Dialer's DialContet method and then use the 5 | // resulting connection). When done, we will extract the trace containing 6 | // all the events that occurred from the saver and process it to determine 7 | // what happened during the measurement itself. 8 | // 9 | // This package is now frozen. Please, use measurexlite for new code. See 10 | // https://github.com/ooni/probe-cli/blob/master/docs/design/dd-003-step-by-step.md 11 | // for details about this. 12 | package tracex 13 | -------------------------------------------------------------------------------- /internal/legacy/tracex/saver.go: -------------------------------------------------------------------------------- 1 | package tracex 2 | 3 | // 4 | // Saver implementation 5 | // 6 | 7 | import "sync" 8 | 9 | // The Saver saves a trace. The zero value of this type 10 | // is valid and can be used without initialization. 11 | type Saver struct { 12 | // ops contains the saved events. 13 | ops []Event 14 | 15 | // mu provides mutual exclusion. 16 | mu sync.Mutex 17 | } 18 | 19 | // Read reads and returns events inside the trace. It advances 20 | // the read pointer so you won't see such events again. 21 | func (s *Saver) Read() []Event { 22 | s.mu.Lock() 23 | defer s.mu.Unlock() 24 | v := s.ops 25 | s.ops = nil 26 | return v 27 | } 28 | 29 | // Write adds the given event to the trace. A subsequent call 30 | // to Read will read this event. 31 | func (s *Saver) Write(ev Event) { 32 | s.mu.Lock() 33 | defer s.mu.Unlock() 34 | s.ops = append(s.ops, ev) 35 | } 36 | -------------------------------------------------------------------------------- /internal/libooniengine/engine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // C API 5 | // 6 | 7 | //#include 8 | // 9 | //#include "engine.h" 10 | import "C" 11 | 12 | import ( 13 | "unsafe" 14 | 15 | "github.com/ooni/probe-cli/v3/internal/version" 16 | ) 17 | 18 | //export OONIEngineVersion 19 | func OONIEngineVersion() *C.char { 20 | return C.CString(version.Version) 21 | } 22 | 23 | //export OONIEngineFreeMemory 24 | func OONIEngineFreeMemory(ptr *C.void) { 25 | C.free(unsafe.Pointer(ptr)) 26 | } 27 | 28 | func main() { 29 | // do nothing 30 | } 31 | -------------------------------------------------------------------------------- /internal/libooniengine/engine.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | 3 | #ifndef OONI_ENGINE_H 4 | #define OONI_ENGINE_H 5 | 6 | /// 7 | /// C API for using the OONI engine. 8 | /// 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | /// OONIEngineVersion return the current engine version. 15 | /// 16 | /// @return A char pointer with the current version string. 17 | char *OONIEngineVersion(void); 18 | 19 | /// OONIEngineFreeMemory frees the memory allocated by the engine. 20 | /// 21 | /// @param ptr a void pointer refering to the memory to be freed. 22 | void OONIENgineFreeMemory(void *ptr); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | #endif /* OONI_ENGINE_H */ 28 | -------------------------------------------------------------------------------- /internal/libtor/android/386/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/libtor/android/amd64/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/libtor/android/arm/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/libtor/android/arm64/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/libtor/fallback.go: -------------------------------------------------------------------------------- 1 | //go:build !ooni_libtor 2 | 3 | package libtor 4 | 5 | import "github.com/cretz/bine/process" 6 | 7 | // MaybeCreator returns a valid [process.Creator], if possible, otherwise false. 8 | func MaybeCreator() (process.Creator, bool) { 9 | return nil, false 10 | } 11 | -------------------------------------------------------------------------------- /internal/libtor/iphoneos/arm64/.gitignore: -------------------------------------------------------------------------------- 1 | /include 2 | /lib 3 | -------------------------------------------------------------------------------- /internal/libtor/iphonesimulator/amd64/.gitignore: -------------------------------------------------------------------------------- 1 | /include 2 | /lib 3 | -------------------------------------------------------------------------------- /internal/libtor/iphonesimulator/arm64/.gitignore: -------------------------------------------------------------------------------- 1 | /include 2 | /lib 3 | -------------------------------------------------------------------------------- /internal/libtor/linux/amd64/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/libtor/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/logx/doc.go: -------------------------------------------------------------------------------- 1 | // Package logx contains logging extensions. 2 | package logx 3 | -------------------------------------------------------------------------------- /internal/measurexlite/udp.go: -------------------------------------------------------------------------------- 1 | package measurexlite 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // NewUDPListener implements model.Measuring Network. 6 | func (tx *Trace) NewUDPListener() model.UDPListener { 7 | return tx.Netx.NewUDPListener() 8 | } 9 | -------------------------------------------------------------------------------- /internal/measurexlite/udp_test.go: -------------------------------------------------------------------------------- 1 | package measurexlite 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/mocks" 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | func TestNewUDPListener(t *testing.T) { 12 | // Make sure that we're forwarding the call to the measuring network. 13 | expectListener := &mocks.UDPListener{} 14 | trace := NewTrace(0, time.Now()) 15 | trace.Netx = &mocks.MeasuringNetwork{ 16 | MockNewUDPListener: func() model.UDPListener { 17 | return expectListener 18 | }, 19 | } 20 | listener := trace.NewUDPListener() 21 | if listener != expectListener { 22 | t.Fatal("unexpected listener") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /internal/measurexlite/utls.go: -------------------------------------------------------------------------------- 1 | package measurexlite 2 | 3 | // 4 | // Support for using utls 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | utls "gitlab.com/yawning/utls.git" 10 | ) 11 | 12 | // NewTLSHandshakerUTLS is equivalent to netxlite.Netx.NewTLSHandshakerUTLS 13 | // except that it returns a model.TLSHandshaker that uses this trace. 14 | func (tx *Trace) NewTLSHandshakerUTLS(dl model.DebugLogger, id *utls.ClientHelloID) model.TLSHandshaker { 15 | return &tlsHandshakerTrace{ 16 | thx: tx.Netx.NewTLSHandshakerUTLS(dl, id), 17 | tx: tx, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/measurexlite/web.go: -------------------------------------------------------------------------------- 1 | package measurexlite 2 | 3 | // 4 | // Code to process web results (e.g., from web connectivity) 5 | // 6 | 7 | import "regexp" 8 | 9 | // webTitleRegexp is the regexp to extract the title 10 | // 11 | // MK used {1,128} but we're making it larger here to get longer titles 12 | // e.g. 's one 13 | var webTitleRegexp = regexp.MustCompile(`(?i)([^<]{1,512})`) 14 | 15 | // WebGetTitle returns the title or an empty string. 16 | func WebGetTitle(measurementBody string) string { 17 | v := webTitleRegexp.FindStringSubmatch(measurementBody) 18 | if len(v) < 2 { 19 | return "" 20 | } 21 | return v[1] 22 | } 23 | -------------------------------------------------------------------------------- /internal/minipipeline/testdata/webconnectivity/generated/README.md: -------------------------------------------------------------------------------- 1 | Test cases automatically generated from webconnectivityqa. 2 | -------------------------------------------------------------------------------- /internal/minipipeline/testdata/webconnectivity/manual/README.md: -------------------------------------------------------------------------------- 1 | Manually curated test cases. 2 | -------------------------------------------------------------------------------- /internal/minipipeline/testdata/webconnectivity/manual/dnsgoogle80/command.txt: -------------------------------------------------------------------------------- 1 | ./miniooni -i http://dns.google web_connectivity@v0.5 2 | -------------------------------------------------------------------------------- /internal/minipipeline/testdata/webconnectivity/manual/noipv6/command.txt: -------------------------------------------------------------------------------- 1 | # From a network without IPv6 2 | ./miniooni -i https://www.youtube.com/ web_connectivity@v0.5 3 | -------------------------------------------------------------------------------- /internal/minipipeline/testdata/webconnectivity/manual/youtube/command.txt: -------------------------------------------------------------------------------- 1 | ./miniooni -i https://www.youtube.com/ web_connectivity@v0.5 2 | -------------------------------------------------------------------------------- /internal/mocks/addr.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "net" 4 | 5 | // Addr allows mocking net.Addr. 6 | type Addr struct { 7 | MockString func() string 8 | MockNetwork func() string 9 | } 10 | 11 | var _ net.Addr = &Addr{} 12 | 13 | // String calls MockString. 14 | func (a *Addr) String() string { 15 | return a.MockString() 16 | } 17 | 18 | // Network calls MockNetwork. 19 | func (a *Addr) Network() string { 20 | return a.MockNetwork() 21 | } 22 | -------------------------------------------------------------------------------- /internal/mocks/addr_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "testing" 4 | 5 | func TestAddr(t *testing.T) { 6 | t.Run("String", func(t *testing.T) { 7 | a := &Addr{ 8 | MockString: func() string { 9 | return "antani" 10 | }, 11 | } 12 | if a.String() != "antani" { 13 | t.Fatal("invalid result for String") 14 | } 15 | }) 16 | 17 | t.Run("Network", func(t *testing.T) { 18 | a := &Addr{ 19 | MockNetwork: func() string { 20 | return "mascetti" 21 | }, 22 | } 23 | if a.Network() != "mascetti" { 24 | t.Fatal("invalid result for Network") 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /internal/mocks/dnsdecoder.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | // 4 | // Mocks for model.DNSDecoder 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | // DNSDecoder allows mocking model.DNSDecoder. 12 | type DNSDecoder struct { 13 | MockDecodeResponse func(data []byte, query model.DNSQuery) (model.DNSResponse, error) 14 | } 15 | 16 | var _ model.DNSDecoder = &DNSDecoder{} 17 | 18 | func (e *DNSDecoder) DecodeResponse(data []byte, query model.DNSQuery) (model.DNSResponse, error) { 19 | return e.MockDecodeResponse(data, query) 20 | } 21 | -------------------------------------------------------------------------------- /internal/mocks/dnsdecoder_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/model" 8 | ) 9 | 10 | func TestDNSDecoder(t *testing.T) { 11 | t.Run("DecodeResponse", func(t *testing.T) { 12 | expected := errors.New("mocked error") 13 | e := &DNSDecoder{ 14 | MockDecodeResponse: func(reply []byte, query model.DNSQuery) (model.DNSResponse, error) { 15 | return nil, expected 16 | }, 17 | } 18 | out, err := e.DecodeResponse(make([]byte, 17), &DNSQuery{}) 19 | if !errors.Is(err, expected) { 20 | t.Fatal("unexpected err", err) 21 | } 22 | if out != nil { 23 | t.Fatal("unexpected out") 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /internal/mocks/dnsencoder.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | // 4 | // Mocks for model.DNSEncoder. 5 | // 6 | 7 | import "github.com/ooni/probe-cli/v3/internal/model" 8 | 9 | // DNSEncoder allows mocking model.DNSEncoder. 10 | type DNSEncoder struct { 11 | MockEncode func(domain string, qtype uint16, padding bool) model.DNSQuery 12 | } 13 | 14 | var _ model.DNSEncoder = &DNSEncoder{} 15 | 16 | // Encode calls MockEncode. 17 | func (e *DNSEncoder) Encode(domain string, qtype uint16, padding bool) model.DNSQuery { 18 | return e.MockEncode(domain, qtype, padding) 19 | } 20 | -------------------------------------------------------------------------------- /internal/mocks/dnsquery.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | // 4 | // Mocks for model.DNSQuery. 5 | // 6 | 7 | import "github.com/ooni/probe-cli/v3/internal/model" 8 | 9 | // DNSQuery allocks mocking model.DNSQuery. 10 | type DNSQuery struct { 11 | MockDomain func() string 12 | MockType func() uint16 13 | MockBytes func() ([]byte, error) 14 | MockID func() uint16 15 | } 16 | 17 | func (q *DNSQuery) Domain() string { 18 | return q.MockDomain() 19 | } 20 | 21 | func (q *DNSQuery) Type() uint16 { 22 | return q.MockType() 23 | } 24 | 25 | func (q *DNSQuery) Bytes() ([]byte, error) { 26 | return q.MockBytes() 27 | } 28 | 29 | func (q *DNSQuery) ID() uint16 { 30 | return q.MockID() 31 | } 32 | 33 | var _ model.DNSQuery = &DNSQuery{} 34 | -------------------------------------------------------------------------------- /internal/mocks/doc.go: -------------------------------------------------------------------------------- 1 | // Package mocks contains mocks for implementing unit tests. 2 | package mocks 3 | -------------------------------------------------------------------------------- /internal/mocks/experimentinputprocessor.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "context" 4 | 5 | // ExperimentInputProcessor processes inputs running the given experiment. 6 | type ExperimentInputProcessor struct { 7 | MockRun func(ctx context.Context) error 8 | } 9 | 10 | func (eip *ExperimentInputProcessor) Run(ctx context.Context) error { 11 | return eip.MockRun(ctx) 12 | } 13 | -------------------------------------------------------------------------------- /internal/mocks/experimentinputprocessor_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | ) 8 | 9 | func TestExperimentInputProcessor(t *testing.T) { 10 | t.Run("Run", func(t *testing.T) { 11 | expected := errors.New("mocked error") 12 | eip := &ExperimentInputProcessor{ 13 | MockRun: func(ctx context.Context) error { 14 | return expected 15 | }, 16 | } 17 | err := eip.Run(context.Background()) 18 | if !errors.Is(err, expected) { 19 | t.Fatal("unexpected result") 20 | } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /internal/mocks/experimenttargetloader.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | // ExperimentTargetLoader mocks model.ExperimentTargetLoader 10 | type ExperimentTargetLoader struct { 11 | MockLoad func(ctx context.Context) ([]model.ExperimentTarget, error) 12 | } 13 | 14 | var _ model.ExperimentTargetLoader = &ExperimentTargetLoader{} 15 | 16 | // Load calls MockLoad 17 | func (eil *ExperimentTargetLoader) Load(ctx context.Context) ([]model.ExperimentTarget, error) { 18 | return eil.MockLoad(ctx) 19 | } 20 | -------------------------------------------------------------------------------- /internal/mocks/experimenttargetloader_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | func TestExperimentInputLoader(t *testing.T) { 12 | t.Run("Load", func(t *testing.T) { 13 | expected := errors.New("mocked error") 14 | eil := &ExperimentTargetLoader{ 15 | MockLoad: func(ctx context.Context) ([]model.ExperimentTarget, error) { 16 | return nil, expected 17 | }, 18 | } 19 | out, err := eil.Load(context.Background()) 20 | if !errors.Is(err, expected) { 21 | t.Fatal("unexpected err", err) 22 | } 23 | if len(out) > 0 { 24 | t.Fatal("unexpected length") 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /internal/mocks/http3.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "net/http" 4 | 5 | // HTTP3RoundTripper allows mocking http3.RoundTripper. 6 | type HTTP3RoundTripper struct { 7 | MockRoundTrip func(req *http.Request) (*http.Response, error) 8 | MockClose func() error 9 | } 10 | 11 | // RoundTrip calls MockRoundTrip. 12 | func (txp *HTTP3RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 13 | return txp.MockRoundTrip(req) 14 | } 15 | 16 | // Close calls MockClose. 17 | func (txp *HTTP3RoundTripper) Close() error { 18 | return txp.MockClose() 19 | } 20 | -------------------------------------------------------------------------------- /internal/mocks/http3_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestHTTP3RoundTripper(t *testing.T) { 10 | t.Run("RoundTrip", func(t *testing.T) { 11 | expected := errors.New("mocked error") 12 | txp := &HTTP3RoundTripper{ 13 | MockRoundTrip: func(req *http.Request) (*http.Response, error) { 14 | return nil, expected 15 | }, 16 | } 17 | resp, err := txp.RoundTrip(&http.Request{}) 18 | if !errors.Is(err, expected) { 19 | t.Fatal("unexpected err", err) 20 | } 21 | if resp != nil { 22 | t.Fatal("unexpected resp") 23 | } 24 | }) 25 | 26 | t.Run("Close", func(t *testing.T) { 27 | expected := errors.New("mocked error") 28 | txp := &HTTP3RoundTripper{ 29 | MockClose: func() error { 30 | return expected 31 | }, 32 | } 33 | if err := txp.Close(); !errors.Is(err, expected) { 34 | t.Fatal("unexpected err", err) 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /internal/mocks/keyvaluestore.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // KeyValueStore allows mocking model.KeyValueStore. 6 | type KeyValueStore struct { 7 | MockGet func(key string) (value []byte, err error) 8 | 9 | MockSet func(key string, value []byte) (err error) 10 | } 11 | 12 | var _ model.KeyValueStore = &KeyValueStore{} 13 | 14 | func (kvs *KeyValueStore) Get(key string) (value []byte, err error) { 15 | return kvs.MockGet(key) 16 | } 17 | 18 | func (kvs *KeyValueStore) Set(key string, value []byte) (err error) { 19 | return kvs.MockSet(key, value) 20 | } 21 | -------------------------------------------------------------------------------- /internal/mocks/keyvaluestore_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestKeyValueStore(t *testing.T) { 9 | t.Run("Get", func(t *testing.T) { 10 | expect := errors.New("mocked error") 11 | kvs := &KeyValueStore{ 12 | MockGet: func(key string) (value []byte, err error) { 13 | return nil, expect 14 | }, 15 | } 16 | out, err := kvs.Get("antani") 17 | if !errors.Is(err, expect) { 18 | t.Fatal("unexpected err", err) 19 | } 20 | if out != nil { 21 | t.Fatal("unexpected out") 22 | } 23 | }) 24 | 25 | t.Run("Set", func(t *testing.T) { 26 | expect := errors.New("mocked error") 27 | kvs := &KeyValueStore{ 28 | MockSet: func(key string, value []byte) (err error) { 29 | return expect 30 | }, 31 | } 32 | err := kvs.Set("antani", nil) 33 | if !errors.Is(err, expect) { 34 | t.Fatal("unexpected err", err) 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /internal/mocks/listener.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "net" 4 | 5 | // Listener allows mocking a net.Listener. 6 | type Listener struct { 7 | // Accept allows mocking Accept. 8 | MockAccept func() (net.Conn, error) 9 | 10 | // Close allows mocking Close. 11 | MockClose func() error 12 | 13 | // Addr allows mocking Addr. 14 | MockAddr func() net.Addr 15 | } 16 | 17 | var _ net.Listener = &Listener{} 18 | 19 | // Accept implements net.Listener.Accept 20 | func (li *Listener) Accept() (net.Conn, error) { 21 | return li.MockAccept() 22 | } 23 | 24 | // Close implements net.Listener.Closer. 25 | func (li *Listener) Close() error { 26 | return li.MockClose() 27 | } 28 | 29 | // Addr implements net.Listener.Addr 30 | func (li *Listener) Addr() net.Addr { 31 | return li.MockAddr() 32 | } 33 | -------------------------------------------------------------------------------- /internal/mocks/reader.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "io" 4 | 5 | // Reader allows to mock any io.Reader. 6 | type Reader struct { 7 | MockRead func(b []byte) (int, error) 8 | } 9 | 10 | // MockableReader implements an io.Reader. 11 | var _ io.Reader = &Reader{} 12 | 13 | // Read implements io.Reader.Read. 14 | func (r *Reader) Read(b []byte) (int, error) { 15 | return r.MockRead(b) 16 | } 17 | -------------------------------------------------------------------------------- /internal/mocks/reader_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestReader(t *testing.T) { 9 | t.Run("Read", func(t *testing.T) { 10 | expected := errors.New("mocked error") 11 | r := &Reader{ 12 | MockRead: func(b []byte) (int, error) { 13 | return 0, expected 14 | }, 15 | } 16 | b := make([]byte, 128) 17 | count, err := r.Read(b) 18 | if !errors.Is(err, expected) { 19 | t.Fatal("unexpected error", err) 20 | } 21 | if count != 0 { 22 | t.Fatal("unexpected count", count) 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /internal/mocks/saver.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Saver saves a measurement on some persistent storage. 6 | type Saver struct { 7 | MockSaveMeasurement func(m *model.Measurement) error 8 | } 9 | 10 | func (s *Saver) SaveMeasurement(m *model.Measurement) error { 11 | return s.MockSaveMeasurement(m) 12 | } 13 | -------------------------------------------------------------------------------- /internal/mocks/saver_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/model" 8 | ) 9 | 10 | func TestSaver(t *testing.T) { 11 | t.Run("SaveMeasurement", func(t *testing.T) { 12 | expected := errors.New("mocked error") 13 | s := &Saver{ 14 | MockSaveMeasurement: func(m *model.Measurement) error { 15 | return expected 16 | }, 17 | } 18 | err := s.SaveMeasurement(&model.Measurement{}) 19 | if !errors.Is(err, expected) { 20 | t.Fatal("unexpected err", err) 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /internal/mocks/submitter.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | // Submitter mocks model.Submitter. 10 | type Submitter struct { 11 | MockSubmit func(ctx context.Context, m *model.Measurement) (string, error) 12 | } 13 | 14 | // Submit calls MockSubmit 15 | func (s *Submitter) Submit(ctx context.Context, m *model.Measurement) (string, error) { 16 | return s.MockSubmit(ctx, m) 17 | } 18 | -------------------------------------------------------------------------------- /internal/mocks/submitter_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | func TestSubmitter(t *testing.T) { 12 | t.Run("Submit", func(t *testing.T) { 13 | expect := errors.New("mocked error") 14 | s := &Submitter{ 15 | MockSubmit: func(ctx context.Context, m *model.Measurement) (string, error) { 16 | return "", expect 17 | }, 18 | } 19 | _, err := s.Submit(context.Background(), &model.Measurement{}) 20 | if !errors.Is(err, expect) { 21 | t.Fatal("unexpected err", err) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /internal/mocks/udplistener.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | // UDPListener is a mockable netxlite.UDPListener. 10 | type UDPListener struct { 11 | MockListen func(addr *net.UDPAddr) (model.UDPLikeConn, error) 12 | } 13 | 14 | // Listen calls MockListen. 15 | func (ql *UDPListener) Listen(addr *net.UDPAddr) (model.UDPLikeConn, error) { 16 | return ql.MockListen(addr) 17 | } 18 | -------------------------------------------------------------------------------- /internal/mocks/udplistener_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "testing" 7 | 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | func TestUDPListener(t *testing.T) { 12 | t.Run("Listen", func(t *testing.T) { 13 | expected := errors.New("mocked error") 14 | ql := &UDPListener{ 15 | MockListen: func(addr *net.UDPAddr) (model.UDPLikeConn, error) { 16 | return nil, expected 17 | }, 18 | } 19 | pconn, err := ql.Listen(&net.UDPAddr{}) 20 | if !errors.Is(err, expected) { 21 | t.Fatal("not the error we expected", expected) 22 | } 23 | if pconn != nil { 24 | t.Fatal("expected nil conn here") 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /internal/mocks/writer.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "io" 4 | 5 | // Writer allows to mock any io.Writer. 6 | type Writer struct { 7 | MockWrite func(b []byte) (int, error) 8 | } 9 | 10 | // Writer implements an io.Writer. 11 | var _ io.Writer = &Writer{} 12 | 13 | // Write implements io.Writer.Write. 14 | func (r *Writer) Write(b []byte) (int, error) { 15 | return r.MockWrite(b) 16 | } 17 | -------------------------------------------------------------------------------- /internal/mocks/writer_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | func TestWriter(t *testing.T) { 9 | t.Run("Write", func(t *testing.T) { 10 | expected := errors.New("mocked error") 11 | r := &Writer{ 12 | MockWrite: func(b []byte) (int, error) { 13 | return 0, expected 14 | }, 15 | } 16 | b := make([]byte, 128) 17 | count, err := r.Write(b) 18 | if !errors.Is(err, expected) { 19 | t.Fatal("unexpected error", err) 20 | } 21 | if count != 0 { 22 | t.Fatal("unexpected count", count) 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /internal/model/README.md: -------------------------------------------------------------------------------- 1 | # Package github.com/ooni/probe-cli/v3/internal/model 2 | 3 | Shared data structures and interfaces. We include in this 4 | package the most fundamental types. Use `go doc` to get 5 | more thorough documentation about what is inside this package 6 | and when to put a type inside this package. 7 | -------------------------------------------------------------------------------- /internal/model/experiment_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/apex/log" 7 | ) 8 | 9 | func TestPrinterCallbacksCallbacks(t *testing.T) { 10 | printer := NewPrinterCallbacks(log.Log) 11 | printer.OnProgress(0.4, "progress") 12 | } 13 | -------------------------------------------------------------------------------- /internal/model/geoip.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // GeoIPASNlookupper performs ASN lookups. 4 | type GeoIPASNLookupper interface { 5 | LookupASN(ip string) (asn uint, org string, err error) 6 | } 7 | 8 | // GeoIPASNLookupperFunc transforms a func into a [GeoIPASNLookupper]. 9 | type GeoIPASNLookupperFunc func(ip string) (asn uint, org string, err error) 10 | 11 | var _ GeoIPASNLookupper = GeoIPASNLookupperFunc(nil) 12 | 13 | // LookupASN implements GeoIPASNLookupper. 14 | func (fx GeoIPASNLookupperFunc) LookupASN(ip string) (asn uint, org string, err error) { 15 | return fx(ip) 16 | } 17 | -------------------------------------------------------------------------------- /internal/model/geoip_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "testing" 4 | 5 | func TestGeoIPLookupperFunc(t *testing.T) { 6 | fx := func(ip string) (asn uint, org string, err error) { 7 | return 137, "Consortium GARR", nil 8 | } 9 | lookupper := GeoIPASNLookupperFunc(fx) 10 | asn, org, err := lookupper.LookupASN("130.192.91.211") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | if asn != 137 { 15 | t.Fatal("invalid asn", asn) 16 | } 17 | if org != "Consortium GARR" { 18 | t.Fatal("invalid org", org) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/model/keyvaluestore.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // 4 | // Definition of a key-value store. 5 | // 6 | 7 | // KeyValueStore is a generic key-value store. 8 | type KeyValueStore interface { 9 | // Get gets the value of the given key or returns an 10 | // error if there is no such key or we cannot read 11 | // from the key-value store. 12 | Get(key string) (value []byte, err error) 13 | 14 | // Set sets the value of the given key and returns 15 | // whether the operation was successful or not. 16 | Set(key string, value []byte) (err error) 17 | } 18 | -------------------------------------------------------------------------------- /internal/model/location.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // LocationProvider is an interface that returns the current location. The 4 | // [engine.Session] struct implements this interface. 5 | type LocationProvider interface { 6 | ProbeASN() uint 7 | ProbeASNString() string 8 | ProbeCC() string 9 | ProbeIP() string 10 | ProbeNetworkName() string 11 | ResolverIP() string 12 | } 13 | 14 | // LocationASN contains ASN information related to a location. 15 | type LocationASN struct { 16 | ASNumber uint 17 | Organization string 18 | } 19 | -------------------------------------------------------------------------------- /internal/model/runtype.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // RunType describes the type of a ooniprobe run. 4 | type RunType string 5 | 6 | const ( 7 | // RunTypeManual indicates that the user manually run `ooniprobe run`. Command 8 | // line tools such as miniooni should always use this run type. 9 | RunTypeManual = RunType("manual") 10 | 11 | // RunTypeTimed indicates that the user run `ooniprobe run unattended`, which 12 | // is the correct way to run ooniprobe from scripts and cronjobs. 13 | RunTypeTimed = RunType("timed") 14 | ) 15 | -------------------------------------------------------------------------------- /internal/must/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/netemx/android.go: -------------------------------------------------------------------------------- 1 | package netemx 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ooni/netem" 7 | "github.com/ooni/probe-cli/v3/internal/netxlite" 8 | ) 9 | 10 | // androidStack wraps [netem.UnderlyingNetwork] to simulate what our getaddrinfo 11 | // wrapper does on Android when it sees the EAI_NODATA return value. 12 | type androidStack struct { 13 | netem.UnderlyingNetwork 14 | } 15 | 16 | // GetaddrinfoLookupANY implements [netem.UnderlyingNetwork] 17 | func (as *androidStack) GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error) { 18 | addrs, cname, err := as.UnderlyingNetwork.GetaddrinfoLookupANY(ctx, domain) 19 | if err != nil { 20 | err = netxlite.NewErrGetaddrinfo(0, netxlite.ErrAndroidDNSCacheNoData) 21 | } 22 | return addrs, cname, err 23 | } 24 | -------------------------------------------------------------------------------- /internal/netemx/android_test.go: -------------------------------------------------------------------------------- 1 | package netemx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/apex/log" 9 | "github.com/ooni/probe-cli/v3/internal/netxlite" 10 | ) 11 | 12 | // Make sure we can emulate Android's getaddrinfo behavior. 13 | func TestEmulateAndroidGetaddrinfo(t *testing.T) { 14 | env := MustNewScenario(InternetScenario) 15 | defer env.Close() 16 | 17 | env.EmulateAndroidGetaddrinfo(true) 18 | defer env.EmulateAndroidGetaddrinfo(false) 19 | 20 | env.Do(func() { 21 | netx := &netxlite.Netx{} 22 | reso := netx.NewStdlibResolver(log.Log) 23 | addrs, err := reso.LookupHost(context.Background(), "www.nonexistent.xyz") 24 | if !errors.Is(err, netxlite.ErrAndroidDNSCacheNoData) { 25 | t.Fatal("unexpected error") 26 | } 27 | if len(addrs) != 0 { 28 | t.Fatal("expected zero-length addresses") 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /internal/netemx/dnsoverhttps.go: -------------------------------------------------------------------------------- 1 | package netemx 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/ooni/netem" 7 | "github.com/ooni/probe-cli/v3/internal/testingx" 8 | ) 9 | 10 | // DNSOverHTTPSHandlerFactory is a [QAEnvHTTPHandlerFactory] for [testingx.GeoIPHandlerUbuntu]. 11 | type DNSOverHTTPSHandlerFactory struct{} 12 | 13 | var _ HTTPHandlerFactory = &DNSOverHTTPSHandlerFactory{} 14 | 15 | // NewHandler implements QAEnvHTTPHandlerFactory. 16 | func (f *DNSOverHTTPSHandlerFactory) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler { 17 | return &testingx.DNSOverHTTPSHandler{ 18 | RoundTripper: testingx.NewDNSRoundTripperWithDNSConfig(env.OtherResolversConfig()), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/netemx/doc.go: -------------------------------------------------------------------------------- 1 | // Package netemx contains code to run integration tests using [github.com/ooni/netem]. 2 | package netemx 3 | -------------------------------------------------------------------------------- /internal/netemx/geoip.go: -------------------------------------------------------------------------------- 1 | package netemx 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/ooni/netem" 7 | "github.com/ooni/probe-cli/v3/internal/testingx" 8 | ) 9 | 10 | // GeoIPHandlerFactoryUbuntu is a [QAEnvHTTPHandlerFactory] for [testingx.GeoIPHandlerUbuntu]. 11 | type GeoIPHandlerFactoryUbuntu struct { 12 | ProbeIP string 13 | } 14 | 15 | var _ HTTPHandlerFactory = &GeoIPHandlerFactoryUbuntu{} 16 | 17 | // NewHandler implements QAEnvHTTPHandlerFactory. 18 | func (f *GeoIPHandlerFactoryUbuntu) NewHandler(env NetStackServerFactoryEnv, stack *netem.UNetStack) http.Handler { 19 | return &testingx.GeoIPHandlerUbuntu{ProbeIP: f.ProbeIP} 20 | } 21 | -------------------------------------------------------------------------------- /internal/netxlite/certifi_test.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import ( 4 | "crypto/x509" 5 | "testing" 6 | ) 7 | 8 | func TestPEMCerts(t *testing.T) { 9 | t.Run("we can successfully load the bundled certificates", func(t *testing.T) { 10 | pool := x509.NewCertPool() 11 | if !pool.AppendCertsFromPEM([]byte(pemcerts)) { 12 | t.Fatal("cannot load pemcerts") 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /internal/netxlite/getaddrinfo_windows.go: -------------------------------------------------------------------------------- 1 | //go:build cgo && windows 2 | 3 | package netxlite 4 | 5 | //#include 6 | import "C" 7 | 8 | import "syscall" 9 | 10 | const getaddrinfoAIFlags = C.AI_CANONNAME 11 | 12 | // Making constants available to Go code so we can run tests (it seems 13 | // it's not possible to import C directly in tests, sadly). 14 | const ( 15 | aiCanonname = C.AI_CANONNAME 16 | ) 17 | 18 | // toError is the function that converts the return value from 19 | // the getaddrinfo function into a proper Go error. 20 | func (state *getaddrinfoState) toError(code int64, err error, goos string) error { 21 | if err == nil { 22 | // Implementation note: on Windows getaddrinfo directly 23 | // returns what is basically a winsock2 error. So if there 24 | // is no other error, just cast code to a syscall err. 25 | err = syscall.Errno(code) 26 | } 27 | return NewErrGetaddrinfo(int64(code), err) 28 | } 29 | -------------------------------------------------------------------------------- /internal/netxlite/httpstdlib.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | // stdlibTransport wraps a httpTransportStdlib to add .Network() 10 | type httpTransportStdlib struct { 11 | StdlibTransport *http.Transport 12 | } 13 | 14 | var _ model.HTTPTransport = &httpTransportStdlib{} 15 | 16 | func (txp *httpTransportStdlib) CloseIdleConnections() { 17 | txp.StdlibTransport.CloseIdleConnections() 18 | } 19 | 20 | func (txp *httpTransportStdlib) RoundTrip(req *http.Request) (*http.Response, error) { 21 | return txp.StdlibTransport.RoundTrip(req) 22 | } 23 | 24 | // Network implements HTTPTransport.Network. 25 | func (txp *httpTransportStdlib) Network() string { 26 | return "tcp" 27 | } 28 | -------------------------------------------------------------------------------- /internal/netxlite/httpwrap.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | // 4 | // Wrappers for already constructed types. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | ) 10 | 11 | // WrapHTTPTransport creates an HTTPTransport using the given logger 12 | // and guarantees that returned errors are wrapped. 13 | // 14 | // This is a low level factory. Consider not using it directly. 15 | func WrapHTTPTransport(logger model.DebugLogger, txp model.HTTPTransport) model.HTTPTransport { 16 | return &httpTransportLogger{ 17 | HTTPTransport: &httpTransportErrWrapper{txp}, 18 | Logger: logger, 19 | } 20 | } 21 | 22 | // WrapHTTPClient wraps an HTTP client to add error wrapping capabilities. 23 | // 24 | // This is a low level factory. Consider not using it directly. 25 | func WrapHTTPClient(clnt model.HTTPClient) model.HTTPClient { 26 | return &httpClientErrWrapper{clnt} 27 | } 28 | -------------------------------------------------------------------------------- /internal/netxlite/httpwrap_test.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestWrapHTTPClient(t *testing.T) { 9 | origClient := &http.Client{} 10 | wrapped := WrapHTTPClient(origClient) 11 | errWrapper := wrapped.(*httpClientErrWrapper) 12 | innerClient := errWrapper.HTTPClient.(*http.Client) 13 | if innerClient != origClient { 14 | t.Fatal("not the inner client we expected") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/netxlite/shaping.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // NewMaybeShapingDialer takes in input a model.Dialer and returns in output another 6 | // model.Dialer that MAY dial connections with I/O shaping, depending on whether 7 | // the user builds with or without the `-tags shaping` CLI flag. 8 | // 9 | // We typically use `-tags shaping` when running integration tests for dash and ndt7 to 10 | // avoiod hammering m-lab servers from the very-fast GitHub CI servers. 11 | // 12 | // See https://github.com/ooni/probe/issues/2112 for extra context. 13 | func NewMaybeShapingDialer(dialer model.Dialer) model.Dialer { 14 | return newMaybeShapingDialer(dialer) 15 | } 16 | -------------------------------------------------------------------------------- /internal/netxlite/shaping_otherwise.go: -------------------------------------------------------------------------------- 1 | //go:build !shaping 2 | 3 | package netxlite 4 | 5 | import ( 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | func newMaybeShapingDialer(dialer model.Dialer) model.Dialer { 10 | return dialer 11 | } 12 | -------------------------------------------------------------------------------- /internal/netxlite/shaping_otherwise_test.go: -------------------------------------------------------------------------------- 1 | //go:build !shaping 2 | 3 | package netxlite 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/ooni/probe-cli/v3/internal/mocks" 9 | ) 10 | 11 | func TestNewShapingDialer(t *testing.T) { 12 | in := &mocks.Dialer{} 13 | out := NewMaybeShapingDialer(in) 14 | if in != out { 15 | t.Fatal("expected to see the same pointer") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/netxlite/udp.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/model" 7 | ) 8 | 9 | // NewUDPListener creates a new UDPListener using the underlying 10 | // [*Netx] structure to create listening UDP sockets. 11 | func (netx *Netx) NewUDPListener() model.UDPListener { 12 | return &udpListenerErrWrapper{&udpListenerStdlib{provider: netx.MaybeCustomUnderlyingNetwork()}} 13 | } 14 | 15 | // udpListenerStdlib is a UDPListener using the standard library. 16 | type udpListenerStdlib struct { 17 | // provider is the OPTIONAL nil-safe [model.UnderlyingNetwork] provider. 18 | provider *MaybeCustomUnderlyingNetwork 19 | } 20 | 21 | var _ model.UDPListener = &udpListenerStdlib{} 22 | 23 | // Listen implements UDPListener.Listen. 24 | func (qls *udpListenerStdlib) Listen(addr *net.UDPAddr) (model.UDPLikeConn, error) { 25 | return qls.provider.Get().ListenUDP("udp", addr) 26 | } 27 | -------------------------------------------------------------------------------- /internal/netxlite/udp_test.go: -------------------------------------------------------------------------------- 1 | package netxlite 2 | 3 | import "testing" 4 | 5 | func TestNewUDPListener(t *testing.T) { 6 | netx := &Netx{} 7 | ql := netx.NewUDPListener() 8 | qew := ql.(*udpListenerErrWrapper) 9 | _ = qew.UDPListener.(*udpListenerStdlib) 10 | } 11 | -------------------------------------------------------------------------------- /internal/oonirun/doc.go: -------------------------------------------------------------------------------- 1 | // Package oonirun contains code to run OONI experiments. 2 | // 3 | // This package supports OONI Run v1 and v2 as well as the direct 4 | // creation and instantiation of OONI experiments. 5 | // 6 | // See https://github.com/ooni/probe-cli/blob/master/docs/design/dd-004-minioonirunv2.md 7 | // for more information on the subset of OONI Run v2 implemented by this package. 8 | package oonirun 9 | -------------------------------------------------------------------------------- /internal/probeservices/orchestra_test.go: -------------------------------------------------------------------------------- 1 | package probeservices 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // MetadataFixture returns a valid metadata struct. This is mostly 6 | // useful for testing. (We should see if we can make this private.) 7 | func MetadataFixture() model.OOAPIProbeMetadata { 8 | return model.OOAPIProbeMetadata{ 9 | Platform: "linux", 10 | ProbeASN: "AS15169", 11 | ProbeCC: "US", 12 | SoftwareName: "miniooni", 13 | SoftwareVersion: "0.1.0-dev", 14 | SupportedTests: []string{ 15 | "web_connectivity", 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/ptx/doc.go: -------------------------------------------------------------------------------- 1 | // Package ptx contains code to use pluggable transports. 2 | package ptx 3 | -------------------------------------------------------------------------------- /internal/ptx/fake.go: -------------------------------------------------------------------------------- 1 | package ptx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | // FakeDialer is a fake pluggable transport dialer. It actually 10 | // just creates a TCP connection with the given address. 11 | type FakeDialer struct { 12 | // Address is the real destination address. 13 | Address string 14 | } 15 | 16 | var _ PTDialer = &FakeDialer{} 17 | 18 | // DialContext establishes a TCP connection with d.Address. 19 | func (d *FakeDialer) DialContext(ctx context.Context) (net.Conn, error) { 20 | return (&net.Dialer{}).DialContext(ctx, "tcp", d.Address) 21 | } 22 | 23 | // AsBridgeArgument returns the argument to be passed to 24 | // the tor command line to declare this bridge. 25 | func (d *FakeDialer) AsBridgeArgument() string { 26 | return fmt.Sprintf("fake %s", d.Address) 27 | } 28 | 29 | // Name returns the pluggable transport name. 30 | func (d *FakeDialer) Name() string { 31 | return "fake" 32 | } 33 | -------------------------------------------------------------------------------- /internal/ptx/fake_test.go: -------------------------------------------------------------------------------- 1 | package ptx 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestFakeDialerWorks(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skip test in short mode") 11 | } 12 | 13 | fd := &FakeDialer{Address: "8.8.8.8:53"} 14 | conn, err := fd.DialContext(context.Background()) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | if fd.Name() != "fake" { 19 | t.Fatal("invalid value returned by fd.Name") 20 | } 21 | if fd.AsBridgeArgument() != "fake 8.8.8.8:53" { 22 | t.Fatal("invalid value returned by fd.AsBridgeArgument") 23 | } 24 | conn.Close() 25 | } 26 | -------------------------------------------------------------------------------- /internal/ptx/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/randx/randx_test.go: -------------------------------------------------------------------------------- 1 | package randx 2 | 3 | import "testing" 4 | 5 | func TestLetters(t *testing.T) { 6 | str := Letters(1024) 7 | for _, chr := range str { 8 | if (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') { 9 | continue 10 | } 11 | t.Fatal("invalid input char") 12 | } 13 | } 14 | 15 | func TestLettersUppercase(t *testing.T) { 16 | str := LettersUppercase(1024) 17 | for _, chr := range str { 18 | if chr >= 'A' && chr <= 'Z' { 19 | continue 20 | } 21 | t.Fatal("invalid input char") 22 | } 23 | } 24 | 25 | func TestChangeCapitalization(t *testing.T) { 26 | str := Letters(2048) 27 | if ChangeCapitalization(str) == str { 28 | t.Fatal("capitalization not changed") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/registry/allexperiments.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import "sort" 4 | 5 | // Where we register all the available experiments. 6 | var AllExperiments = map[string]func() *Factory{} 7 | 8 | // ExperimentNames returns the name of all experiments 9 | func ExperimentNames() (names []string) { 10 | for key := range AllExperiments { 11 | names = append(names, key) 12 | } 13 | sort.Strings(names) // sort by name to always provide predictable output 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /internal/registry/dash.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `dash' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/dash" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "dash" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return dash.NewExperimentMeasurer( 18 | *config.(*dash.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &dash.Config{}, 23 | enabledByDefault: true, 24 | interruptible: true, 25 | inputPolicy: model.InputNone, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/dnscheck.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `dnscheck' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/dnscheck" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "dnscheck" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | // TODO(bassosimone,DecFox): for now, we MUST keep the InputOrStaticDefault 16 | // policy because otherwise ./pkg/oonimkall should break. 17 | return &Factory{ 18 | build: func(config interface{}) model.ExperimentMeasurer { 19 | return dnscheck.NewExperimentMeasurer() 20 | }, 21 | canonicalName: canonicalName, 22 | config: &dnscheck.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrStaticDefault, 25 | newLoader: dnscheck.NewLoader, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/dnsping.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `dnsping' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/dnsping" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "dnsping" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return dnsping.NewExperimentMeasurer( 18 | *config.(*dnsping.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &dnsping.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrStaticDefault, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/doc.go: -------------------------------------------------------------------------------- 1 | // Package registry contains a registry of all the available experiments. 2 | package registry 3 | -------------------------------------------------------------------------------- /internal/registry/dslxtutorial.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `simple sni' experiment from the dslx tutorial. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/model" 9 | "github.com/ooni/probe-cli/v3/internal/tutorial/dslx/chapter02" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "simple_sni" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return chapter02.NewExperimentMeasurer( 18 | *config.(*chapter02.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &chapter02.Config{}, 23 | inputPolicy: model.InputOrQueryBackend, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/registry/echcheck.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `echcheck' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/echcheck" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "echcheck" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return echcheck.NewExperimentMeasurer( 18 | *config.(*echcheck.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &echcheck.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOptional, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/fbmessenger.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `fbmessenger' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/fbmessenger" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "facebook_messenger" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return fbmessenger.NewExperimentMeasurer( 18 | *config.(*fbmessenger.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &fbmessenger.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/hhfm.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `hhfm' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/hhfm" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "http_header_field_manipulation" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return hhfm.NewExperimentMeasurer( 18 | *config.(*hhfm.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &hhfm.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/hirl.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `hirl' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/hirl" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "http_invalid_request_line" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return hirl.NewExperimentMeasurer( 18 | *config.(*hirl.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &hirl.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/httphostheader.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `httphostheader' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/httphostheader" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "http_host_header" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return httphostheader.NewExperimentMeasurer( 18 | *config.(*httphostheader.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &httphostheader.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrQueryBackend, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/ndt.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `ndt' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/ndt7" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "ndt" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return ndt7.NewExperimentMeasurer( 18 | *config.(*ndt7.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &ndt7.Config{}, 23 | enabledByDefault: true, 24 | interruptible: true, 25 | inputPolicy: model.InputNone, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/openvpn.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `openvpn' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/openvpn" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "openvpn" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return openvpn.NewExperimentMeasurer() 18 | }, 19 | canonicalName: canonicalName, 20 | config: &openvpn.Config{}, 21 | enabledByDefault: true, 22 | interruptible: true, 23 | inputPolicy: model.InputOrQueryBackend, 24 | newLoader: openvpn.NewLoader, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/portfiltering.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the 'portfiltering' experiment 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/portfiltering" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "portfiltering" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config any) model.ExperimentMeasurer { 17 | return portfiltering.NewExperimentMeasurer( 18 | *config.(*portfiltering.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &portfiltering.Config{}, 23 | enabledByDefault: true, 24 | interruptible: false, 25 | inputPolicy: model.InputNone, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/psiphon.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `psiphon' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/psiphon" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "psiphon" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return psiphon.NewExperimentMeasurer( 18 | *config.(*psiphon.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &psiphon.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOptional, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/quicping.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `quicping' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/quicping" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "quicping" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return quicping.NewExperimentMeasurer( 18 | *config.(*quicping.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &quicping.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/riseupvpn.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `riseupvpn' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/riseupvpn" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "riseupvpn" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return riseupvpn.NewExperimentMeasurer( 18 | *config.(*riseupvpn.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &riseupvpn.Config{}, 23 | inputPolicy: model.InputNone, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/registry/signal.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `signal' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/signal" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "signal" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return signal.NewExperimentMeasurer( 18 | *config.(*signal.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &signal.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/simplequicping.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `simplequicping' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/simplequicping" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "simplequicping" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return simplequicping.NewExperimentMeasurer( 18 | *config.(*simplequicping.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &simplequicping.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/sniblocking.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `sniblocking' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/sniblocking" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "sni_blocking" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return sniblocking.NewExperimentMeasurer( 18 | *config.(*sniblocking.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &sniblocking.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrQueryBackend, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/stunreachability.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `stunreachability' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/stunreachability" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "stunreachability" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return stunreachability.NewExperimentMeasurer( 18 | *config.(*stunreachability.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &stunreachability.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrStaticDefault, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/tcpping.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `tcpping' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/tcpping" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "tcpping" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return tcpping.NewExperimentMeasurer( 18 | *config.(*tcpping.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &tcpping.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/telegram.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `telegram' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/telegram" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "telegram" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config any) model.ExperimentMeasurer { 17 | return telegram.NewExperimentMeasurer( 18 | *config.(*telegram.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &telegram.Config{}, 23 | enabledByDefault: true, 24 | interruptible: false, 25 | inputPolicy: model.InputNone, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/tlsmiddlebox.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `tlsmiddlebox' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/tlsmiddlebox" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "tlsmiddlebox" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return tlsmiddlebox.NewExperimentMeasurer( 18 | *config.(*tlsmiddlebox.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &tlsmiddlebox.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/tlsping.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `tlsping' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/tlsping" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "tlsping" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return tlsping.NewExperimentMeasurer( 18 | *config.(*tlsping.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &tlsping.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/tlstool.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `tlstool' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/tlstool" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "tlstool" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return tlstool.NewExperimentMeasurer( 18 | *config.(*tlstool.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &tlstool.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputOrQueryBackend, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/tor.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `tor' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/tor" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "tor" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return tor.NewExperimentMeasurer( 18 | *config.(*tor.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &tor.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/torsf.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `torsf' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/torsf" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "torsf" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return torsf.NewExperimentMeasurer( 18 | *config.(*torsf.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &torsf.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/urlgetter.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `urlgetter' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/urlgetter" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "urlgetter" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return urlgetter.NewExperimentMeasurer( 18 | *config.(*urlgetter.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &urlgetter.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputStrictlyRequired, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/registry/webconnectivity.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `web_connectivity' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivity" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "web_connectivity" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config any) model.ExperimentMeasurer { 17 | return webconnectivity.NewExperimentMeasurer( 18 | *config.(*webconnectivity.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &webconnectivity.Config{}, 23 | enabledByDefault: true, 24 | interruptible: false, 25 | inputPolicy: model.InputOrQueryBackend, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/registry/webconnectivityv05.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `web_connectivity@v0.5' experiment. 5 | // 6 | // See https://github.com/ooni/probe/issues/2237 7 | // 8 | 9 | import ( 10 | "github.com/ooni/probe-cli/v3/internal/experiment/webconnectivitylte" 11 | "github.com/ooni/probe-cli/v3/internal/model" 12 | ) 13 | 14 | func init() { 15 | const canonicalName = "web_connectivity@v0.5" 16 | AllExperiments[canonicalName] = func() *Factory { 17 | return &Factory{ 18 | build: func(config any) model.ExperimentMeasurer { 19 | return webconnectivitylte.NewExperimentMeasurer( 20 | config.(*webconnectivitylte.Config), 21 | ) 22 | }, 23 | canonicalName: canonicalName, 24 | config: &webconnectivitylte.Config{}, 25 | enabledByDefault: true, 26 | interruptible: false, 27 | inputPolicy: model.InputOrQueryBackend, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/registry/whatsapp.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // 4 | // Registers the `whatsapp' experiment. 5 | // 6 | 7 | import ( 8 | "github.com/ooni/probe-cli/v3/internal/experiment/whatsapp" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func init() { 13 | const canonicalName = "whatsapp" 14 | AllExperiments[canonicalName] = func() *Factory { 15 | return &Factory{ 16 | build: func(config interface{}) model.ExperimentMeasurer { 17 | return whatsapp.NewExperimentMeasurer( 18 | *config.(*whatsapp.Config), 19 | ) 20 | }, 21 | canonicalName: canonicalName, 22 | config: &whatsapp.Config{}, 23 | enabledByDefault: true, 24 | inputPolicy: model.InputNone, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/shellx/testdata/checkenv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | 5 | func main() { 6 | antani := os.Getenv("ANTANI") 7 | mascetti := os.Getenv("MASCETTI") 8 | stuzzica := os.Getenv("STUZZICA") 9 | if antani != "antani" { 10 | os.Exit(1) 11 | } 12 | if mascetti != "mascetti" { 13 | os.Exit(2) 14 | } 15 | if stuzzica != "stuzzica" { 16 | os.Exit(3) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/strcasex/acronyms.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | package strcasex 4 | 5 | var uppercaseAcronym = map[string]string{ 6 | "ID": "id", 7 | } 8 | 9 | // ConfigureAcronym allows you to add additional words which will be considered acronyms 10 | func ConfigureAcronym(key, val string) { 11 | uppercaseAcronym[key] = val 12 | } 13 | -------------------------------------------------------------------------------- /internal/strcasex/doc.go: -------------------------------------------------------------------------------- 1 | // Package strcasex converts strings to various cases. 2 | // 3 | // This package forks https://github.com/iancoleman/strcase at v0.2.0. 4 | // 5 | // See the conversion table below: 6 | // 7 | // | Function | Result | 8 | // |---------------------------------|--------------------| 9 | // | ToSnake(s) | any_kind_of_string | 10 | // | ToScreamingSnake(s) | ANY_KIND_OF_STRING | 11 | // | ToKebab(s) | any-kind-of-string | 12 | // | ToScreamingKebab(s) | ANY-KIND-OF-STRING | 13 | // | ToDelimited(s, '.') | any.kind.of.string | 14 | // | ToScreamingDelimited(s, '.') | ANY.KIND.OF.STRING | 15 | // | ToCamel(s) | AnyKindOfString | 16 | // | ToLowerCamel(s) | anyKindOfString | 17 | package strcasex 18 | -------------------------------------------------------------------------------- /internal/stuninput/stuninput_test.go: -------------------------------------------------------------------------------- 1 | package stuninput 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestAsSnowflakeInput(t *testing.T) { 9 | outputs := AsSnowflakeInput() 10 | if len(outputs) != len(inputs) { 11 | t.Fatal("unexpected number of entries") 12 | } 13 | for _, output := range outputs { 14 | output = strings.TrimPrefix(output, "stun:") 15 | if !inputs[output] { 16 | t.Fatal("not found in inputs", output) 17 | } 18 | } 19 | } 20 | 21 | func TestAsStunReachabilityInput(t *testing.T) { 22 | outputs := AsnStunReachabilityInput() 23 | if len(outputs) != len(inputs) { 24 | t.Fatal("unexpected number of entries") 25 | } 26 | for _, output := range outputs { 27 | output = strings.TrimPrefix(output, "stun://") 28 | if !inputs[output] { 29 | t.Fatal("not found in inputs", output) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/targetloading/testdata/loader1.txt: -------------------------------------------------------------------------------- 1 | https://www.x.org/ 2 | https://www.slashdot.org/ 3 | https://abc.xyz/ 4 | -------------------------------------------------------------------------------- /internal/targetloading/testdata/loader2.txt: -------------------------------------------------------------------------------- 1 | https://run.ooni.io/ 2 | -------------------------------------------------------------------------------- /internal/targetloading/testdata/loader3.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /internal/testingproxy/doc.go: -------------------------------------------------------------------------------- 1 | // Package testingproxy contains shared test cases for the proxies. 2 | package testingproxy 3 | -------------------------------------------------------------------------------- /internal/testingproxy/qa_test.go: -------------------------------------------------------------------------------- 1 | package testingproxy_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/testingproxy" 7 | ) 8 | 9 | func TestHTTPProxy(t *testing.T) { 10 | for _, testCase := range testingproxy.HTTPTestCases { 11 | t.Run(testCase.Name(), func(t *testing.T) { 12 | short := testCase.Short() 13 | if !short && testing.Short() { 14 | t.Skip("skip test in short mode") 15 | } 16 | testCase.Run(t) 17 | }) 18 | } 19 | } 20 | 21 | func TestSOCKSProxy(t *testing.T) { 22 | for _, testCase := range testingproxy.SOCKSTestCases { 23 | t.Run(testCase.Name(), func(t *testing.T) { 24 | short := testCase.Short() 25 | if !short && testing.Short() { 26 | t.Skip("skip test in short mode") 27 | } 28 | testCase.Run(t) 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/testingquic/testingquic_test.go: -------------------------------------------------------------------------------- 1 | package testingquic 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func TestWorksAsIntended(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("skip test in short mode") 11 | } 12 | 13 | endpoint := MustEndpoint("443") 14 | addr, port, err := net.SplitHostPort(endpoint) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | if addr != address { 20 | t.Fatal("invalid addr") 21 | } 22 | if port != "443" { 23 | t.Fatal("invalid port") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/testingsocks5/doc.go: -------------------------------------------------------------------------------- 1 | // Package testingsock5 is a netem-aware fork of https://github.com/armon/go-socks5. 2 | package testingsocks5 3 | -------------------------------------------------------------------------------- /internal/testingx/dnscore_test.go: -------------------------------------------------------------------------------- 1 | package testingx 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestNewDNSRoundTripSimulateTimeout(t *testing.T) { 11 | t.Run("when the context has already been cancelled", func(t *testing.T) { 12 | rtx := NewDNSRoundTripperSimulateTimeout(time.Second, errors.New("mocked error")) 13 | ctx, cancel := context.WithCancel(context.Background()) 14 | cancel() // immediately cancel 15 | resp, err := rtx.RoundTrip(ctx, make([]byte, 128)) 16 | if !errors.Is(err, context.Canceled) { 17 | t.Fatal("unexpected err", err) 18 | } 19 | if len(resp) != 0 { 20 | t.Fatal("expected zero-byte resp") 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /internal/testingx/doc.go: -------------------------------------------------------------------------------- 1 | // Package testingx contains code useful for testing. 2 | package testingx 3 | -------------------------------------------------------------------------------- /internal/testingx/geoip.go: -------------------------------------------------------------------------------- 1 | package testingx 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // GeoIPHandlerUbuntu is an [http.Handler] implementing Ubuntu's GeoIP lookup service. 9 | type GeoIPHandlerUbuntu struct { 10 | // ProbeIP is the MANDATORY probe IP to return. 11 | ProbeIP string 12 | } 13 | 14 | var _ http.Handler = &GeoIPHandlerUbuntu{} 15 | 16 | // ServeHTTP implements [http.Handler]. 17 | func (p *GeoIPHandlerUbuntu) ServeHTTP(w http.ResponseWriter, r *http.Request) { 18 | resp := fmt.Sprintf( 19 | `%s`, 20 | p.ProbeIP, 21 | ) 22 | w.Header().Add("Content-Type", "text/xml") 23 | _, _ = w.Write([]byte(resp)) 24 | } 25 | -------------------------------------------------------------------------------- /internal/testingx/geoip_test.go: -------------------------------------------------------------------------------- 1 | package testingx 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestGeoIPHandlerUbuntu(t *testing.T) { 13 | handler := &GeoIPHandlerUbuntu{ 14 | ProbeIP: "1.2.3.4", 15 | } 16 | server := httptest.NewServer(handler) 17 | defer server.Close() 18 | 19 | expectBody := []byte( 20 | `1.2.3.4`, 21 | ) 22 | 23 | resp, err := http.Get(server.URL) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | defer resp.Body.Close() 28 | 29 | if resp.StatusCode != 200 { 30 | t.Fatal("unexpected status code", resp.StatusCode) 31 | } 32 | 33 | body, err := io.ReadAll(resp.Body) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | if diff := cmp.Diff(expectBody, body); diff != "" { 39 | t.Fatal(diff) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/testingx/time_test.go: -------------------------------------------------------------------------------- 1 | package testingx 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestNewTimeDeterministic(t *testing.T) { 9 | zero := time.Date(2023, 8, 24, 11, 45, 00, 0, time.UTC) 10 | td := NewTimeDeterministic(zero) 11 | if !td.zeroTime.Equal(zero) { 12 | t.Fatal("unexpected zero time") 13 | } 14 | if td.counter != 0 { 15 | t.Fatal("unexpected counter") 16 | } 17 | } 18 | 19 | func TestTimeDeterministic(t *testing.T) { 20 | td := &TimeDeterministic{} 21 | t0 := td.Now() 22 | if !t0.Equal(td.zeroTime) { 23 | t.Fatal("invalid t0 value") 24 | } 25 | t1 := td.Now() 26 | if t1.Sub(t0) != time.Second { 27 | t.Fatal("invalid t1 value") 28 | } 29 | t2 := td.Now() 30 | if t2.Sub(t1) != time.Second { 31 | t.Fatal("invalid t2 value") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/torlogs/testdata/empty.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ooni/probe-cli/76bfcf657ac5034c21819a9d780e8ed81a94f750/internal/torlogs/testdata/empty.log -------------------------------------------------------------------------------- /internal/tunnel/session_test.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import "context" 4 | 5 | // MockableSession is a mockable session. 6 | type MockableSession struct { 7 | // Result contains the bytes of the psiphon config. 8 | Result []byte 9 | 10 | // Err is the error, if any. 11 | Err error 12 | } 13 | 14 | // FetchPsiphonConfig implements ExperimentSession.FetchPsiphonConfig 15 | func (sess *MockableSession) FetchPsiphonConfig(ctx context.Context) ([]byte, error) { 16 | return sess.Result, sess.Err 17 | } 18 | -------------------------------------------------------------------------------- /internal/tunnel/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /internal/urlx/urlx.go: -------------------------------------------------------------------------------- 1 | // Package urlx contains URL extensions. 2 | package urlx 3 | 4 | import ( 5 | "net/url" 6 | ) 7 | 8 | // ResolveReference constructs a new URL consisting of the given base URL with 9 | // the path appended to the given path and the optional query. 10 | // 11 | // For example, given: 12 | // 13 | // URL := "https://api.ooni.io/api/v1" 14 | // path := "/measurement_meta" 15 | // rawQuery := "full=true" 16 | // 17 | // This function will return: 18 | // 19 | // result := "https://api.ooni.io/api/v1/measurement_meta?full=true" 20 | // 21 | // This function fails when we cannot parse URL as a [*net.URL]. 22 | func ResolveReference(baseURL, path, rawQuery string) (string, error) { 23 | parsedBase, err := url.Parse(baseURL) 24 | if err != nil { 25 | return "", err 26 | } 27 | ref := &url.URL{ 28 | Path: path, 29 | RawQuery: rawQuery, 30 | } 31 | return parsedBase.ResolveReference(ref).String(), nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Package version contains version information. 2 | package version 3 | 4 | // Version is the ooniprobe version. 5 | const Version = "3.26.0-alpha" 6 | -------------------------------------------------------------------------------- /internal/webconnectivityalgo/dnsoverudp.go: -------------------------------------------------------------------------------- 1 | package webconnectivityalgo 2 | 3 | import ( 4 | "math/rand" 5 | "net" 6 | ) 7 | 8 | // dnsOverUDPResolverAddressIPv4 is the list of DNS-over-UDP IPv4 addresses. 9 | var dnsOverUDPResolverAddressIPv4 = []string{ 10 | // dns.google 11 | "8.8.8.8", 12 | "8.8.4.4", 13 | 14 | // dns.quad9.net 15 | "9.9.9.9", 16 | "149.112.112.112", 17 | 18 | // cloudflare-dns.com 19 | "1.1.1.1", 20 | "1.0.0.1", 21 | 22 | // doh.opendns.com 23 | "208.67.222.222", 24 | "208.67.220.220", 25 | } 26 | 27 | // RandomDNSOverUDPResolverEndpointIPv4 returns a random DNS-over-UDP resolver endpoint using IPv4. 28 | func RandomDNSOverUDPResolverEndpointIPv4() string { 29 | idx := rand.Intn(len(dnsOverUDPResolverAddressIPv4)) // #nosec G404 -- not really important 30 | return net.JoinHostPort(dnsOverUDPResolverAddressIPv4[idx], "53") 31 | } 32 | -------------------------------------------------------------------------------- /internal/webconnectivityalgo/dnsoverudp_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivityalgo 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func TestRandomDNSOverUDPResolverEndpointIPv4(t *testing.T) { 9 | results := make(map[string]int64) 10 | const maxruns = 1024 11 | for idx := 0; idx < maxruns; idx++ { 12 | endpoint := RandomDNSOverUDPResolverEndpointIPv4() 13 | results[endpoint]++ 14 | if _, _, err := net.SplitHostPort(endpoint); err != nil { 15 | t.Fatal(err) 16 | } 17 | } 18 | t.Log(results) 19 | if len(results) < 3 { 20 | t.Fatal("expected to see at least three different results out of 1024 runs") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/webconnectivityalgo/doc.go: -------------------------------------------------------------------------------- 1 | // Package webconnectivityalgo contains Web Connectivity algorithms. 2 | package webconnectivityalgo 3 | -------------------------------------------------------------------------------- /internal/webconnectivityqa/doc.go: -------------------------------------------------------------------------------- 1 | // Package webconnectivityqa contains code to perform Web Connectivity QA. This package 2 | // is separate from [webconnectivity] and [webconnectivitylte] and works with both. 3 | package webconnectivityqa 4 | -------------------------------------------------------------------------------- /internal/webconnectivityqa/testcase_test.go: -------------------------------------------------------------------------------- 1 | package webconnectivityqa 2 | 3 | import "testing" 4 | 5 | func TestAllTestCases(t *testing.T) { 6 | t.Run("we have at least one test case to run", func(t *testing.T) { 7 | if len(AllTestCases()) < 1 { 8 | t.Fatal("expected at least a single test case") 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /internal/x/doc.go: -------------------------------------------------------------------------------- 1 | // Package x contains highly experimental packages. 2 | package x 3 | -------------------------------------------------------------------------------- /internal/x/dsljavascript/doc.go: -------------------------------------------------------------------------------- 1 | // Package dsljavascript allows running experiments written in JavaScript. 2 | package dsljavascript 3 | -------------------------------------------------------------------------------- /internal/x/dsljavascript/golangmodule.go: -------------------------------------------------------------------------------- 1 | package dsljavascript 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/dop251/goja" 7 | "github.com/ooni/probe-cli/v3/internal/runtimex" 8 | ) 9 | 10 | // newModuleGolang creates the _golang module in JavaScript 11 | func (vm *VM) newModuleGolang(gojaVM *goja.Runtime, mod *goja.Object) { 12 | runtimex.Assert(vm.vm == gojaVM, "dsljavascript: unexpected gojaVM pointer value") 13 | exports := mod.Get("exports").(*goja.Object) 14 | runtimex.Try0(exports.Set("timeNow", vm.golangTimeNow)) 15 | } 16 | 17 | // golangTimeNow returns the current time using golang [time.Now] 18 | func (vm *VM) golangTimeNow(call goja.FunctionCall) goja.Value { 19 | runtimex.Assert(len(call.Arguments) == 0, "dsljavascript: _golang.timeNow expects zero arguments") 20 | return vm.vm.ToValue(time.Now()) 21 | } 22 | -------------------------------------------------------------------------------- /internal/x/dsljson/doc.go: -------------------------------------------------------------------------------- 1 | // Package dsljson allows expressing the measurement DSL using JSON. 2 | package dsljson 3 | -------------------------------------------------------------------------------- /internal/x/dsljson/rootnode.go: -------------------------------------------------------------------------------- 1 | package dsljson 2 | 3 | // RootNode is the root node of the DSL. 4 | type RootNode struct { 5 | Stages []StageNode `json:"stages"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/x/dsljson/run.go: -------------------------------------------------------------------------------- 1 | package dsljson 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/ooni/probe-cli/v3/internal/x/dslvm" 7 | ) 8 | 9 | // Run runs the DSL represented by the given [*RootNode]. 10 | func Run(ctx context.Context, rtx dslvm.Runtime, root *RootNode) error { 11 | lx := newLoader() 12 | if err := lx.load(rtx.Logger(), root); err != nil { 13 | return err 14 | } 15 | for _, stage := range lx.stages { 16 | go stage.Run(ctx, rtx) 17 | } 18 | dslvm.Wait(lx.toWait...) 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /internal/x/dsljson/stagenode.go: -------------------------------------------------------------------------------- 1 | package dsljson 2 | 3 | import "encoding/json" 4 | 5 | // StageNode describes a stage node in the DSL. 6 | type StageNode struct { 7 | Name string `json:"name"` 8 | Value json.RawMessage `json:"value"` 9 | } 10 | -------------------------------------------------------------------------------- /internal/x/dslvm/closer.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | import "github.com/ooni/probe-cli/v3/internal/model" 4 | 5 | // Closer is something that [Drop] should explicitly close. 6 | type Closer interface { 7 | Close(logger model.Logger) error 8 | } 9 | -------------------------------------------------------------------------------- /internal/x/dslvm/doc.go: -------------------------------------------------------------------------------- 1 | // Package dslvm contains low-level code for implementing the measurements DSL. 2 | package dslvm 3 | -------------------------------------------------------------------------------- /internal/x/dslvm/done.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | // Done indicates that a DSL pipeline terminated. 4 | type Done struct{} 5 | -------------------------------------------------------------------------------- /internal/x/dslvm/makeendpoints.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | // MakeEndpointsStage is a [Stage] that transforms IP addresses to TCP/UDP endpoints. 9 | type MakeEndpointsStage struct { 10 | // Input contains the MANDATORY channel from which to read IP addresses. We 11 | // assume that this channel will be closed when done. 12 | Input <-chan string 13 | 14 | // Output is the MANDATORY channel emitting endpoints. We will close this 15 | // channel when the Input channel has been closed. 16 | Output chan<- string 17 | 18 | // Port is the MANDATORY port. 19 | Port string 20 | } 21 | 22 | var _ Stage = &MakeEndpointsStage{} 23 | 24 | // Run transforms IP addresses to endpoints. 25 | func (sx *MakeEndpointsStage) Run(ctx context.Context, rtx Runtime) { 26 | defer close(sx.Output) 27 | for addr := range sx.Input { 28 | sx.Output <- net.JoinHostPort(addr, sx.Port) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/x/dslvm/stage.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | import "context" 4 | 5 | // Stage is a stage in the DSL graph. 6 | type Stage interface { 7 | Run(ctx context.Context, rtx Runtime) 8 | } 9 | -------------------------------------------------------------------------------- /internal/x/dslvm/start.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | import "context" 4 | 5 | // Start starts all the given [Stage] instances. 6 | func Start(ctx context.Context, rtx Runtime, stages ...Stage) { 7 | for _, stage := range stages { 8 | go stage.Run(ctx, rtx) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /internal/x/dslvm/wait.go: -------------------------------------------------------------------------------- 1 | package dslvm 2 | 3 | import "sync" 4 | 5 | // Wait waits until all the given channels are done. 6 | func Wait(channels ...<-chan Done) { 7 | waitGroup := &sync.WaitGroup{} 8 | for _, channel := range channels { 9 | waitGroup.Add(1) 10 | go func(channel <-chan Done) { 11 | defer waitGroup.Done() 12 | for range channel { 13 | // drain! 14 | } 15 | }(channel) 16 | } 17 | waitGroup.Wait() 18 | } 19 | -------------------------------------------------------------------------------- /internal/x/dslx/doc.go: -------------------------------------------------------------------------------- 1 | // Package dslx implements a domain specific language for writing network experiments. 2 | // 3 | // See https://github.com/ooni/probe-cli/blob/master/docs/design/dd-005-dslx.md for the 4 | // design document explaining why we introduced this package. 5 | package dslx 6 | -------------------------------------------------------------------------------- /internal/x/dslx/endpoint_test.go: -------------------------------------------------------------------------------- 1 | package dslx 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | func TestEndpoint(t *testing.T) { 11 | idGen := &atomic.Int64{} 12 | idGen.Add(42) 13 | 14 | t.Run("Create new endpoint", func(t *testing.T) { 15 | testEndpoint := NewEndpoint( 16 | "network", 17 | "10.9.8.76", 18 | EndpointOptionDomain("www.example.com"), 19 | EndpointOptionTags("antani"), 20 | ) 21 | if testEndpoint.Network != "network" { 22 | t.Fatalf("unexpected network") 23 | } 24 | if testEndpoint.Address != "10.9.8.76" { 25 | t.Fatalf("unexpected address") 26 | } 27 | if testEndpoint.Domain != "www.example.com" { 28 | t.Fatalf("unexpected domain") 29 | } 30 | if diff := cmp.Diff([]string{"antani"}, testEndpoint.Tags); diff != "" { 31 | t.Fatal(diff) 32 | } 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /internal/x/dslx/runtimemeasurex_test.go: -------------------------------------------------------------------------------- 1 | package dslx 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/ooni/probe-cli/v3/internal/measurexlite" 8 | "github.com/ooni/probe-cli/v3/internal/mocks" 9 | "github.com/ooni/probe-cli/v3/internal/model" 10 | ) 11 | 12 | func TestMeasurexLiteRuntime(t *testing.T) { 13 | t.Run("we can configure a custom model.MeasuringNetwork", func(t *testing.T) { 14 | netx := &mocks.MeasuringNetwork{} 15 | rt := NewRuntimeMeasurexLite(model.DiscardLogger, time.Now(), RuntimeMeasurexLiteOptionMeasuringNetwork(netx)) 16 | if rt.netx != netx { 17 | t.Fatal("did not set the measuring network") 18 | } 19 | trace := rt.NewTrace(rt.IDGenerator().Add(1), rt.ZeroTime()).(*measurexlite.Trace) 20 | if trace.Netx != netx { 21 | t.Fatal("did not set the measuring network") 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/.gitignore: -------------------------------------------------------------------------------- 1 | /testdata 2 | -------------------------------------------------------------------------------- /pkg/README.md: -------------------------------------------------------------------------------- 1 | # Public Go packages 2 | 3 | This directory contains public Go packages. 4 | -------------------------------------------------------------------------------- /pkg/gobash/.gitignore: -------------------------------------------------------------------------------- 1 | /gobash.exe 2 | -------------------------------------------------------------------------------- /pkg/gobash/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ooni/probe-cli/pkg/gobash 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /pkg/gobash/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | // read the content of GOVERSION 10 | data, err := ioutil.ReadFile("GOVERSION") 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | 15 | // strip trailing newlines 16 | for len(data) > 0 && data[len(data)-1] == '\r' || data[len(data)-1] == '\n' { 17 | data = data[:len(data)-1] 18 | } 19 | 20 | // run the specified version of go 21 | Run("go" + string(data)) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/gobash/signal_notunix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | // +build windows 7 | 8 | package main 9 | 10 | import ( 11 | "os" 12 | ) 13 | 14 | var signalsToIgnore = []os.Signal{os.Interrupt} 15 | -------------------------------------------------------------------------------- /pkg/gobash/signal_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build darwin || freebsd || linux || openbsd 6 | // +build darwin freebsd linux openbsd 7 | 8 | package main 9 | 10 | import ( 11 | "os" 12 | "syscall" 13 | ) 14 | 15 | var signalsToIgnore = []os.Signal{os.Interrupt, syscall.SIGQUIT} 16 | -------------------------------------------------------------------------------- /pkg/oonimkall/.gitignore: -------------------------------------------------------------------------------- 1 | /testdata 2 | -------------------------------------------------------------------------------- /pkg/oonimkall/session_test.go: -------------------------------------------------------------------------------- 1 | package oonimkall 2 | 3 | import "testing" 4 | 5 | func TestNewCheckInInfoWebConnectivityNilPointer(t *testing.T) { 6 | out := newCheckInInfoWebConnectivity(nil) 7 | if out != nil { 8 | t.Fatal("expected nil pointer") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg/oonimkall/sessioncontext.go: -------------------------------------------------------------------------------- 1 | package oonimkall 2 | 3 | import ( 4 | "context" 5 | "math" 6 | "time" 7 | ) 8 | 9 | const maxTimeout = int64(time.Duration(math.MaxInt64) / time.Second) 10 | 11 | func clampTimeout(timeout, max int64) int64 { 12 | if timeout > max { 13 | timeout = max 14 | } 15 | return timeout 16 | } 17 | 18 | func newContext(timeout int64) (context.Context, context.CancelFunc) { 19 | return newContextEx(timeout, maxTimeout) 20 | } 21 | 22 | func newContextEx(timeout, max int64) (context.Context, context.CancelFunc) { 23 | if timeout > 0 { 24 | timeout = clampTimeout(timeout, max) 25 | return context.WithTimeout( 26 | context.Background(), time.Duration(timeout)*time.Second) 27 | } 28 | return context.WithCancel(context.Background()) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/oonimkall/uuid.go: -------------------------------------------------------------------------------- 1 | package oonimkall 2 | 3 | import "github.com/google/uuid" 4 | 5 | // NewUUID4 generates a new UUID4 string. This functionality is typically 6 | // used by mobile apps to generate random unique identifiers. 7 | func NewUUID4() string { 8 | return uuid.Must(uuid.NewRandom()).String() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/oonimkall/uuid_test.go: -------------------------------------------------------------------------------- 1 | package oonimkall_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/pkg/oonimkall" 7 | ) 8 | 9 | func TestNewUUID4(t *testing.T) { 10 | if out := oonimkall.NewUUID4(); len(out) != 36 { 11 | t.Fatal("not the expected output") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/oonimkall/webconnectivity_integration_test.go: -------------------------------------------------------------------------------- 1 | package oonimkall_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ooni/probe-cli/v3/pkg/oonimkall" 7 | ) 8 | 9 | func TestSessionWebConnectivity(t *testing.T) { 10 | if testing.Short() { 11 | t.Skip("skip test in short mode") 12 | } 13 | sess, err := NewSessionForTesting() 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | ctx := sess.NewContext() 18 | config := &oonimkall.WebConnectivityConfig{ 19 | Input: "https://www.google.com", 20 | } 21 | results, err := sess.WebConnectivity(ctx, config) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | t.Logf("bytes received: %f", results.KibiBytesReceived) 26 | t.Logf("bytes sent: %f", results.KibiBytesSent) 27 | t.Logf("measurement: %d bytes", len(results.Measurement)) 28 | } 29 | -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | # Misc scripts 2 | 3 | This directory contains miscellaneous scripts. 4 | -------------------------------------------------------------------------------- /script/ghpublish-branch.out.txt: -------------------------------------------------------------------------------- 1 | gh release create -p rolling --target 7327e1ff7f0cfdc5ff0335574b85dc8ceb9465b6 2 | gh release upload rolling --clobber ABC 3 | -------------------------------------------------------------------------------- /script/ghpublish-pr.out.txt: -------------------------------------------------------------------------------- 1 | gh release create -p rolling --target 7327e1ff7f0cfdc5ff0335574b85dc8ceb9465b6 2 | gh release upload rolling --clobber ABC 3 | -------------------------------------------------------------------------------- /script/ghpublish-prerelease.out.txt: -------------------------------------------------------------------------------- 1 | gh release create -p v0.0.0-alpha --target 7327e1ff7f0cfdc5ff0335574b85dc8ceb9465b6 2 | gh release upload v0.0.0-alpha --clobber ABC 3 | -------------------------------------------------------------------------------- /script/ghpublish-release.out.txt: -------------------------------------------------------------------------------- 1 | gh release create v0.0.0 --target 7327e1ff7f0cfdc5ff0335574b85dc8ceb9465b6 2 | gh release upload v0.0.0 --clobber ABC 3 | -------------------------------------------------------------------------------- /script/ghpublish.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # 1. obtain the github ref of this action run 5 | __ref=${GITHUB_REF:-} 6 | 7 | if [[ $__ref == "" ]]; then 8 | echo "FATAL: missing github ref" 1>&2 9 | exit 1 10 | fi 11 | 12 | # 2. determine whether to publish to a release or to rolling 13 | if [[ $__ref =~ ^refs/tags/v ]]; then 14 | __tag=${__ref#refs/tags/} 15 | else 16 | __tag=rolling 17 | fi 18 | 19 | # 3. determine whether this is a pre-release 20 | prerelease="-p" 21 | if [[ $__tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 22 | prerelease="" 23 | fi 24 | 25 | gh=${gh:-gh} 26 | 27 | set -x 28 | 29 | # 4. create the release unless it already exists 30 | $gh release create $prerelease $__tag --target $GITHUB_SHA || true 31 | 32 | # 5. publish all the assets passed as arguments to the target release 33 | $gh release upload $__tag --clobber "$@" 34 | -------------------------------------------------------------------------------- /script/go.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | # We use ./pkg/gobash to ensure we execute the correct version of go. 5 | # 6 | # See https://github.com/ooni/probe/issues/2664 for context. 7 | 8 | # Build the gobash.exe wrapper 9 | (cd ./pkg/gobash && go build -v -o gobash.exe .) 10 | 11 | # Download the exact version of Go we need 12 | ./pkg/gobash/gobash.exe download 13 | 14 | # Make sure we're using the exact toolchain we've just downloaded 15 | # See https://github.com/ooni/probe/issues/2695 16 | export GOTOOLCHAIN=local 17 | 18 | # Execute commands using such an exact version of go 19 | ./pkg/gobash/gobash.exe "$@" 20 | -------------------------------------------------------------------------------- /script/internal/go.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | # If this script is invoked by ./script/go.bash, then go is 4 | # the correct version of go expected by the buildtool. 5 | # 6 | # See https://github.com/ooni/probe/issues/2664 7 | go "$@" 8 | -------------------------------------------------------------------------------- /script/linuxcoverage.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Computes coverage inside an environment where we unshared the network namespace 5 | # to ensure unit tests don't depend on the network. 6 | # 7 | 8 | set -euxo pipefail 9 | 10 | # obtain the full path of the go executable 11 | go=$(which go) 12 | 13 | # populate the vendor directory so we don't need the network in `go test` 14 | go mod vendor 15 | 16 | # run tests using a different network namespace 17 | sudo unshare --net ./script/linuxcoveragerun.bash $go 18 | 19 | -------------------------------------------------------------------------------- /script/linuxcoveragerun.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Script invoked by ./script/linuxcoverage.bash to run tests with coverage 5 | # using a separate network namespace with only loopback support. 6 | # 7 | # The first an unique argument is the path to the go binary to use. 8 | # 9 | 10 | set -euxo pipefail 11 | 12 | # make sure we have access to loopback since we have many ~unit 13 | # tests using the loopback interface 14 | ip link set lo up 15 | 16 | # make sure we run all the "unit" tests (where "unit" means proper unit 17 | # tests or tests using localhost or tests using netemx). 18 | $1 test -short -race -count 1 -coverprofile=probe-cli.cov ./... 19 | -------------------------------------------------------------------------------- /script/maketarball.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # 1. obtain the github ref of this action run 6 | __ref=${GITHUB_REF:-} 7 | 8 | if [[ $__ref == "" ]]; then 9 | echo "FATAL: missing github ref" 1>&2 10 | exit 1 11 | fi 12 | 13 | # 2. determine whether to use a release tag name or just "rolling" 14 | if [[ $__ref =~ ^refs/tags/v ]]; then 15 | __version=${__ref#refs/tags/v} 16 | else 17 | __version=rolling 18 | fi 19 | 20 | set -x 21 | 22 | # 3. make sure we're using the correct go version 23 | ./CLI/check-go-version 24 | 25 | # 4. generate the actual tarball 26 | go mod vendor 27 | tar -czf ooni-probe-cli-${__version}.tar.gz --transform "s,^,ooni-probe-cli-${__version}/," * 28 | --------------------------------------------------------------------------------