refactor(riseupvpn): minor changes and annotations (#275)

This commit is contained in:
Simone Basso 2021-04-02 17:58:36 +02:00 committed by GitHub
parent c89ecce3e0
commit d7cd1ebcaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 43 deletions

View File

@ -24,12 +24,12 @@ const (
tcpConnect = "tcpconnect://" tcpConnect = "tcpconnect://"
) )
// EipService main json object of eip-service.json // EipService is the main JSON object of eip-service.json.
type EipService struct { type EipService struct {
Gateways []GatewayV3 Gateways []GatewayV3
} }
// GatewayV3 json obj Version 3 // GatewayV3 describes a gateway.
type GatewayV3 struct { type GatewayV3 struct {
Capabilities struct { Capabilities struct {
Transport []TransportV3 Transport []TransportV3
@ -38,7 +38,7 @@ type GatewayV3 struct {
IPAddress string `json:"ip_address"` IPAddress string `json:"ip_address"`
} }
// TransportV3 json obj Version 3 // TransportV3 describes a transport.
type TransportV3 struct { type TransportV3 struct {
Type string Type string
Protocols []string Protocols []string
@ -46,7 +46,7 @@ type TransportV3 struct {
Options map[string]string Options map[string]string
} }
// GatewayConnection describes the connection to a riseupvpn gateway // GatewayConnection describes the connection to a riseupvpn gateway.
type GatewayConnection struct { type GatewayConnection struct {
IP string `json:"ip"` IP string `json:"ip"`
Port int `json:"port"` Port int `json:"port"`
@ -96,8 +96,9 @@ func (tk *TestKeys) UpdateProviderAPITestKeys(v urlgetter.MultiOutput) {
} }
} }
// AddGatewayConnectTestKeys updates the TestKeys using the given MultiOutput result of gateway connectivity testing. // AddGatewayConnectTestKeys updates the TestKeys using the given MultiOutput
// Sets TransportStatus to "ok" if any successful TCP connection could be made // result of gateway connectivity testing. Sets TransportStatus to "ok" if
// any successful TCP connection could be made
func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transportType string) { func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transportType string) {
tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...) tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...)
tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...) tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...)
@ -107,10 +108,9 @@ func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transport
tk.FailingGateways = append(tk.FailingGateways, *gatewayConnection) tk.FailingGateways = append(tk.FailingGateways, *gatewayConnection)
} }
} }
return
} }
func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount int, obfs4GatewayCount int) { func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount, obfs4GatewayCount int) {
failingOpenvpnGateways, failingObfs4Gateways := 0, 0 failingOpenvpnGateways, failingObfs4Gateways := 0, 0
for _, gw := range tk.FailingGateways { for _, gw := range tk.FailingGateways {
if gw.TransportType == "openvpn" { if gw.TransportType == "openvpn" {
@ -119,13 +119,11 @@ func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount int, obfs4GatewayC
failingObfs4Gateways++ failingObfs4Gateways++
} }
} }
if failingOpenvpnGateways < openvpnGatewayCount { if failingOpenvpnGateways < openvpnGatewayCount {
tk.TransportStatus["openvpn"] = "ok" tk.TransportStatus["openvpn"] = "ok"
} else { } else {
tk.TransportStatus["openvpn"] = "blocked" tk.TransportStatus["openvpn"] = "blocked"
} }
if failingObfs4Gateways < obfs4GatewayCount { if failingObfs4Gateways < obfs4GatewayCount {
tk.TransportStatus["obfs4"] = "ok" tk.TransportStatus["obfs4"] = "ok"
} else { } else {
@ -133,7 +131,8 @@ func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount int, obfs4GatewayC
} }
} }
func newGatewayConnection(tcpConnect archival.TCPConnectEntry, transportType string) *GatewayConnection { func newGatewayConnection(
tcpConnect archival.TCPConnectEntry, transportType string) *GatewayConnection {
return &GatewayConnection{ return &GatewayConnection{
IP: tcpConnect.IP, IP: tcpConnect.IP,
Port: tcpConnect.Port, Port: tcpConnect.Port,
@ -141,7 +140,7 @@ func newGatewayConnection(tcpConnect archival.TCPConnectEntry, transportType str
} }
} }
// AddCACertFetchTestKeys Adding generic urlgetter.Get() testKeys to riseupvpn specific test keys // AddCACertFetchTestKeys adds generic urlgetter.Get() testKeys to riseupvpn specific test keys
func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) { func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) {
tk.NetworkEvents = append(tk.NetworkEvents, testKeys.NetworkEvents...) tk.NetworkEvents = append(tk.NetworkEvents, testKeys.NetworkEvents...)
tk.Queries = append(tk.Queries, testKeys.Queries...) tk.Queries = append(tk.Queries, testKeys.Queries...)
@ -155,7 +154,7 @@ func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) {
} }
} }
// Measurer performs the measurement // Measurer performs the measurement.
type Measurer struct { type Measurer struct {
// Config contains the experiment settings. If empty we // Config contains the experiment settings. If empty we
// will be using default settings. // will be using default settings.
@ -165,17 +164,17 @@ type Measurer struct {
Getter urlgetter.MultiGetter Getter urlgetter.MultiGetter
} }
// ExperimentName implements ExperimentMeasurer.ExperimentName // ExperimentName implements ExperimentMeasurer.ExperimentName.
func (m Measurer) ExperimentName() string { func (m Measurer) ExperimentName() string {
return testName return testName
} }
// ExperimentVersion implements ExperimentMeasurer.ExperimentVersion // ExperimentVersion implements ExperimentMeasurer.ExperimentVersion.
func (m Measurer) ExperimentVersion() string { func (m Measurer) ExperimentVersion() string {
return testVersion return testVersion
} }
// Run implements ExperimentMeasurer.Run // Run implements ExperimentMeasurer.Run.
func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession, func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
measurement *model.Measurement, callbacks model.ExperimentCallbacks) error { measurement *model.Measurement, callbacks model.ExperimentCallbacks) error {
ctx, cancel := context.WithTimeout(ctx, 90*time.Second) ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
@ -184,12 +183,20 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
measurement.TestKeys = testkeys measurement.TestKeys = testkeys
urlgetter.RegisterExtensions(measurement) urlgetter.RegisterExtensions(measurement)
caTarget := "https://black.riseup.net/ca.crt"
certPool := netx.NewDefaultCertPool() certPool := netx.NewDefaultCertPool()
multi := urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess} // used multiple times below
inputs := []urlgetter.MultiInput{ multi := urlgetter.Multi{
{Target: caTarget, Config: urlgetter.Config{ Begin: measurement.MeasurementStartTimeSaved,
Getter: m.Getter,
Session: sess,
}
// See if we can get the certificate first
caTarget := "https://black.riseup.net/ca.crt"
inputs := []urlgetter.MultiInput{{
Target: caTarget,
Config: urlgetter.Config{
Method: "GET", Method: "GET",
FailOnHTTPError: true, FailOnHTTPError: true,
}}, }},
@ -198,9 +205,11 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
tk := entry.TestKeys tk := entry.TestKeys
testkeys.AddCACertFetchTestKeys(tk) testkeys.AddCACertFetchTestKeys(tk)
if tk.Failure != nil { if tk.Failure != nil {
// TODO(bassosimone,cyberta): should we update the testkeys
// in this case (e.g., APIFailure?)
// See https://github.com/ooni/probe/issues/1432.
return nil return nil
} }
if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok { if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok {
testkeys.CACertStatus = false testkeys.CACertStatus = false
testkeys.APIStatus = "blocked" testkeys.APIStatus = "blocked"
@ -210,8 +219,8 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
} }
} }
// Now test the service endpoints using the above-fetched CA
inputs = []urlgetter.MultiInput{ inputs = []urlgetter.MultiInput{
// Here we need to provide the method explicitly. See // Here we need to provide the method explicitly. See
// https://github.com/ooni/probe-engine/issues/827. // https://github.com/ooni/probe-engine/issues/827.
{Target: providerURL, Config: urlgetter.Config{ {Target: providerURL, Config: urlgetter.Config{
@ -230,8 +239,6 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
FailOnHTTPError: true, FailOnHTTPError: true,
}}, }},
} }
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess}
for entry := range multi.CollectOverall(ctx, inputs, 1, 50, "riseupvpn", callbacks) { for entry := range multi.CollectOverall(ctx, inputs, 1, 50, "riseupvpn", callbacks) {
testkeys.UpdateProviderAPITestKeys(entry) testkeys.UpdateProviderAPITestKeys(entry)
} }
@ -244,20 +251,21 @@ func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
overallCount := 1 + len(inputs) + len(openvpnEndpoints) + len(obfs4Endpoints) overallCount := 1 + len(inputs) + len(openvpnEndpoints) + len(obfs4Endpoints)
// measure openvpn in parallel // measure openvpn in parallel
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess} for entry := range multi.CollectOverall(
for entry := range multi.CollectOverall(ctx, openvpnEndpoints, 1+len(inputs), overallCount, "riseupvpn", callbacks) { ctx, openvpnEndpoints, 1+len(inputs), overallCount, "riseupvpn", callbacks) {
testkeys.AddGatewayConnectTestKeys(entry, "openvpn") testkeys.AddGatewayConnectTestKeys(entry, "openvpn")
} }
// measure obfs4 in parallel // measure obfs4 in parallel
multi = urlgetter.Multi{Begin: measurement.MeasurementStartTimeSaved, Getter: m.Getter, Session: sess} // TODO(bassosimone): when urlgetter is able to do obfs4 handshakes, here
for entry := range multi.CollectOverall(ctx, obfs4Endpoints, 1+len(inputs)+len(openvpnEndpoints), overallCount, "riseupvpn", callbacks) { // can possibly also test for the obfs4 handshake.
for entry := range multi.CollectOverall(
ctx, obfs4Endpoints, 1+len(inputs)+len(openvpnEndpoints), overallCount, "riseupvpn", callbacks) {
testkeys.AddGatewayConnectTestKeys(entry, "obfs4") testkeys.AddGatewayConnectTestKeys(entry, "obfs4")
} }
// set transport status based on gateway test results // set transport status based on gateway test results
testkeys.updateTransportStatus(len(openvpnEndpoints), len(obfs4Endpoints)) testkeys.updateTransportStatus(len(openvpnEndpoints), len(obfs4Endpoints))
return nil return nil
} }
@ -289,6 +297,9 @@ func generateMultiInputs(gateways []GatewayV3, transportType string) []urlgetter
func parseGateways(testKeys *TestKeys) []GatewayV3 { func parseGateways(testKeys *TestKeys) []GatewayV3 {
for _, requestEntry := range testKeys.Requests { for _, requestEntry := range testKeys.Requests {
if requestEntry.Request.URL == eipServiceURL && requestEntry.Failure == nil { if requestEntry.Request.URL == eipServiceURL && requestEntry.Failure == nil {
// TODO(bassosimone,cyberta): is it reasonable that we discard
// the error when the JSON we fetched cannot be parsed?
// See https://github.com/ooni/probe/issues/1432
eipService, err := DecodeEIP3(requestEntry.Response.Body.Value) eipService, err := DecodeEIP3(requestEntry.Response.Body.Value)
if err == nil { if err == nil {
return eipService.Gateways return eipService.Gateways
@ -336,7 +347,10 @@ func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, e
sk.ValidCACert = tk.CACertStatus sk.ValidCACert = tk.CACertStatus
sk.FailingGateways = len(tk.FailingGateways) sk.FailingGateways = len(tk.FailingGateways)
sk.TransportStatus = tk.TransportStatus sk.TransportStatus = tk.TransportStatus
sk.IsAnomaly = (sk.APIBlocked == true || tk.CACertStatus == false || // Note: the order in the following OR chains matter: TransportStatus
tk.TransportStatus["openvpn"] == "blocked" || tk.TransportStatus["obfs4"] == "blocked") // is nil if APIBlocked or !CACertStatus
sk.IsAnomaly = (sk.APIBlocked || !tk.CACertStatus ||
tk.TransportStatus["openvpn"] == "blocked" ||
tk.TransportStatus["obfs4"] == "blocked")
return sk, nil return sk, nil
} }

View File

@ -3,20 +3,20 @@ package riseupvpn_test
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
"github.com/apex/log" "github.com/apex/log"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/riseupvpn" "github.com/ooni/probe-cli/v3/internal/engine/experiment/riseupvpn"
"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter" "github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
"github.com/ooni/probe-cli/v3/internal/engine/internal/mockable" "github.com/ooni/probe-cli/v3/internal/engine/internal/mockable"
"github.com/ooni/probe-cli/v3/internal/engine/model" "github.com/ooni/probe-cli/v3/internal/engine/model"
"github.com/ooni/probe-cli/v3/internal/engine/netx/archival"
"github.com/ooni/probe-cli/v3/internal/engine/netx/errorx" "github.com/ooni/probe-cli/v3/internal/engine/netx/errorx"
) )
@ -176,6 +176,10 @@ UN9SaWRlWKSdP4haujnzCoJbM7dU9bjvlGZNyXEekgeT0W2qFeGGp+yyUWw8tNsp
0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO 0BuC1b7uW/bBn/xKm319wXVDvBgZgcktMolak39V7DVO
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
// TODO(bassosimone): maybe we can switch this test to internal
// testing (since now it's all unit tested!) and just use the
// same constants that are used in riseupvpn.go.
eipserviceurl = "https://api.black.riseup.net:443/3/config/eip-service.json" eipserviceurl = "https://api.black.riseup.net:443/3/config/eip-service.json"
providerurl = "https://riseup.net/provider.json" providerurl = "https://riseup.net/provider.json"
geoserviceurl = "https://api.black.riseup.net:9001/json" geoserviceurl = "https://api.black.riseup.net:9001/json"
@ -320,10 +324,7 @@ func TestInvalidCaCert(t *testing.T) {
obfs4url1: true, obfs4url1: true,
}), }),
} }
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sess := &mockable.Session{MockableLogger: log.Log} sess := &mockable.Session{MockableLogger: log.Log}
measurement := new(model.Measurement) measurement := new(model.Measurement)
callbacks := model.NewPrinterCallbacks(log.Log) callbacks := model.NewPrinterCallbacks(log.Log)
@ -338,6 +339,12 @@ func TestInvalidCaCert(t *testing.T) {
if tk.APIStatus != "blocked" { if tk.APIStatus != "blocked" {
t.Fatal("ApiStatus should be blocked") t.Fatal("ApiStatus should be blocked")
} }
if tk.FailingGateways != nil {
t.Fatal("invalid FailingGateways")
}
if tk.TransportStatus != nil {
t.Fatal("invalid TransportStatus")
}
} }
func TestFailureCaCertFetch(t *testing.T) { func TestFailureCaCertFetch(t *testing.T) {
@ -365,6 +372,12 @@ func TestFailureCaCertFetch(t *testing.T) {
if len(tk.Requests) > 1 { if len(tk.Requests) > 1 {
t.Fatal("Unexpected requests") t.Fatal("Unexpected requests")
} }
if tk.FailingGateways != nil {
t.Fatal("invalid FailingGateways")
}
if tk.TransportStatus != nil {
t.Fatal("invalid TransportStatus")
}
} }
func TestFailureEipServiceBlocked(t *testing.T) { func TestFailureEipServiceBlocked(t *testing.T) {
@ -379,7 +392,7 @@ func TestFailureEipServiceBlocked(t *testing.T) {
})) }))
tk := measurement.TestKeys.(*riseupvpn.TestKeys) tk := measurement.TestKeys.(*riseupvpn.TestKeys)
if tk.CACertStatus != true { if tk.CACertStatus != true {
t.Fatal("invalid CACertStatus ") t.Fatal("invalid CACertStatus")
} }
for _, entry := range tk.Requests { for _, entry := range tk.Requests {
@ -582,9 +595,7 @@ func TestMissingTransport(t *testing.T) {
}), }),
} }
ctx, cancel := context.WithCancel(context.Background()) ctx := context.Background()
defer cancel()
sess := &mockable.Session{MockableLogger: log.Log} sess := &mockable.Session{MockableLogger: log.Log}
measurement := new(model.Measurement) measurement := new(model.Measurement)
callbacks := model.NewPrinterCallbacks(log.Log) callbacks := model.NewPrinterCallbacks(log.Log)
@ -600,7 +611,6 @@ func TestMissingTransport(t *testing.T) {
if _, found := tk.TransportStatus["obfs"]; found { if _, found := tk.TransportStatus["obfs"]; found {
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus)) t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
} }
} }
func TestSummaryKeysInvalidType(t *testing.T) { func TestSummaryKeysInvalidType(t *testing.T) {
@ -708,7 +718,7 @@ func generateMockGetter(requestResponse map[string]string, responseStatus map[st
responseBody, foundRequest := requestResponse[url] responseBody, foundRequest := requestResponse[url]
isSuccessStatus, foundStatus := responseStatus[url] isSuccessStatus, foundStatus := responseStatus[url]
if !foundRequest || !foundStatus { if !foundRequest || !foundStatus {
return urlgetter.DefaultMultiGetter(ctx, g) return urlgetter.TestKeys{}, errors.New("request or status not found")
} }
var failure *string var failure *string
@ -768,6 +778,7 @@ func generateMockGetter(requestResponse map[string]string, responseStatus map[st
return tk, nil return tk, nil
} }
} }
func generateDefaultMockGetter(responseStatuses map[string]bool) urlgetter.MultiGetter { func generateDefaultMockGetter(responseStatuses map[string]bool) urlgetter.MultiGetter {
return generateMockGetter(RequestResponse, responseStatuses) return generateMockGetter(RequestResponse, responseStatuses)
} }