Skip to content

Riseup tests improvements #1

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 120 additions & 28 deletions internal/experiment/riseupvpn/riseupvpn.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const (
tcpConnect = "tcpconnect://"
)

var locations = []string{"Seattle", "Amsterdam"}

// EipService is the main JSON object of eip-service.json.
type EipService struct {
Gateways []GatewayV3
Expand All @@ -36,6 +38,7 @@ type GatewayV3 struct {
}
Host string
IPAddress string `json:"ip_address"`
Location string `json:"location"`
}

// TransportV3 describes a transport.
Expand All @@ -53,6 +56,24 @@ type GatewayConnection struct {
TransportType string `json:"transport_type"`
}

// GatewayLoad describes the load of a single Gateway.
type GatewayLoad struct {
Host string `json:"host"`
Fullness float64 `json:"fullness"`
Overload bool `json:"overload"`
}

// GeoService represents the geoService API (also known as menshen) json response
type GeoService struct {
IPAddress string `json:"ip"`
Country string `json:"cc"`
City string `json:"city"`
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
Gateways []string `json:"gateways"`
SortedGateways []GatewayLoad `json:"sortedGateways"`
}

// Config contains the riseupvpn experiment config.
type Config struct {
urlgetter.Config
Expand All @@ -61,7 +82,7 @@ type Config struct {
// TestKeys contains riseupvpn test keys.
type TestKeys struct {
urlgetter.TestKeys
APIFailure *string `json:"api_failure"`
APIFailure []string `json:"api_failure"`
APIStatus string `json:"api_status"`
CACertStatus bool `json:"ca_cert_status"`
FailingGateways []GatewayConnection `json:"failing_gateways"`
Expand All @@ -86,12 +107,9 @@ func (tk *TestKeys) UpdateProviderAPITestKeys(v urlgetter.MultiOutput) {
tk.Requests = append(tk.Requests, v.TestKeys.Requests...)
tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...)
tk.TLSHandshakes = append(tk.TLSHandshakes, v.TestKeys.TLSHandshakes...)
if tk.APIStatus != "ok" {
return // we already flipped the state
}
if v.TestKeys.Failure != nil {
tk.APIStatus = "blocked"
tk.APIFailure = v.TestKeys.Failure
tk.APIFailure = append(tk.APIFailure, *v.TestKeys.Failure)
return
}
}
Expand Down Expand Up @@ -147,11 +165,6 @@ func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) {
tk.Requests = append(tk.Requests, testKeys.Requests...)
tk.TCPConnect = append(tk.TCPConnect, testKeys.TCPConnect...)
tk.TLSHandshakes = append(tk.TLSHandshakes, testKeys.TLSHandshakes...)
if testKeys.Failure != nil {
tk.APIStatus = "blocked"
tk.APIFailure = tk.Failure
tk.CACertStatus = false
}
}

// Measurer performs the measurement.
Expand Down Expand Up @@ -204,21 +217,19 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error {
FailOnHTTPError: true,
}},
}
for entry := range multi.CollectOverall(ctx, inputs, 0, 50, "riseupvpn", callbacks) {
for entry := range multi.CollectOverall(ctx, inputs, 0, 20, "riseupvpn", callbacks) {
tk := entry.TestKeys
testkeys.AddCACertFetchTestKeys(tk)
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
}
if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok {
testkeys.CACertStatus = false
testkeys.APIStatus = "blocked"
errorValue := "invalid_ca"
testkeys.APIFailure = &errorValue
return nil
testkeys.APIFailure = append(testkeys.APIFailure, *tk.Failure)
certPool = nil
} else if ok := certPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody)); !ok {
testkeys.CACertStatus = false
testkeys.APIStatus = "blocked"
testkeys.APIFailure = append(testkeys.APIFailure, "invalid_ca")
certPool = nil
}
}

Expand All @@ -230,22 +241,35 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error {
CertPool: certPool,
Method: "GET",
FailOnHTTPError: true,
NoTLSVerify: !testkeys.CACertStatus,
}},
{Target: eipServiceURL, Config: urlgetter.Config{
CertPool: certPool,
Method: "GET",
FailOnHTTPError: true,
NoTLSVerify: !testkeys.CACertStatus,
}},
{Target: geoServiceURL, Config: urlgetter.Config{
CertPool: certPool,
Method: "GET",
FailOnHTTPError: true,
NoTLSVerify: !testkeys.CACertStatus,
}},
}
for entry := range multi.CollectOverall(ctx, inputs, 1, 50, "riseupvpn", callbacks) {

for entry := range multi.CollectOverall(ctx, inputs, 1, 20, "riseupvpn", callbacks) {
testkeys.UpdateProviderAPITestKeys(entry)
}

if testkeys.APIStatus == "blocked" {
for _, input := range inputs {
input.Config.Tunnel = "torsf"
}
for entry := range multi.CollectOverall(ctx, inputs, 1, 20, "riseupvpn", callbacks) {
testkeys.UpdateProviderAPITestKeys(entry)
}
}

// test gateways now
testkeys.TransportStatus = map[string]string{}
gateways := parseGateways(testkeys)
Expand Down Expand Up @@ -299,18 +323,76 @@ func generateMultiInputs(gateways []GatewayV3, transportType string) []urlgetter
}

func parseGateways(testKeys *TestKeys) []GatewayV3 {
var eipService *EipService = nil
var geoService *GeoService = nil
for _, requestEntry := range testKeys.Requests {
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)
if err == nil {
return eipService.Gateways
var err error = nil
eipService, err = DecodeEIP3(requestEntry.Response.Body.Value)
if err != nil {
testKeys.APIFailure = append(testKeys.APIFailure, "invalid_eipservice_response")
return nil
}
} else if requestEntry.Request.URL == geoServiceURL && requestEntry.Failure == nil {
var err error = nil
geoService, err = DecodeGeoService(requestEntry.Response.Body.Value)
if err != nil {
testKeys.APIFailure = append(testKeys.APIFailure, "invalid_geoservice_response")
}
}
}
return nil
return filterGateways(eipService, geoService)
}

func filterGateways(eipService *EipService, geoService *GeoService) []GatewayV3 {
var result []GatewayV3 = nil
for _, gateway := range eipService.Gateways {
if !gateway.hasTransport("obfs4") ||
!gateway.isLocationUnderTest() ||
geoService != nil && !geoService.isHealthyGateway(gateway) {
continue
}
result = append(result, gateway)
if len(result) == 3 {
return result
}
}

return result
}

func (gateway *GatewayV3) hasTransport(s string) bool {
for _, transport := range gateway.Capabilities.Transport {
if s == transport.Type {
return true
}
}
return false
}

func (gateway *GatewayV3) isLocationUnderTest() bool {
for _, location := range locations {
if location == gateway.Location {
return true
}
}
return false
}

func (geoService *GeoService) isHealthyGateway(gateway GatewayV3) bool {
if geoService.SortedGateways == nil {
// Earlier versions of the geoservice don't include the sorted gateway list containing the load info,
// so we can't say anything about the load of a gateway in that case.
// We assume it's an healthy location. Riseup will switch to the updated API soon *fingers crossed*
return true
}
for _, gatewayLoad := range geoService.SortedGateways {
if gatewayLoad.Host == gateway.Host {
return !gatewayLoad.Overload
}
}

return false
}

// DecodeEIP3 decodes eip-service.json version 3
Expand All @@ -323,6 +405,16 @@ func DecodeEIP3(body string) (*EipService, error) {
return &eip, nil
}

// DecodeGeoService decodes geoService json
func DecodeGeoService(body string) (*GeoService, error) {
var gs GeoService
err := json.Unmarshal([]byte(body), &gs)
if err != nil {
return nil, err
}
return &gs, nil
}

// NewExperimentMeasurer creates a new ExperimentMeasurer.
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
return Measurer{Config: config}
Expand Down
35 changes: 24 additions & 11 deletions internal/experiment/riseupvpn/riseupvpn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func TestUpdateWithMixedResults(t *testing.T) {
if tk.APIStatus != "blocked" {
t.Fatal("ApiStatus should be blocked")
}
if *tk.APIFailure != netxlite.FailureEOFError {
if len(tk.APIFailure) > 0 && tk.APIFailure[0] != netxlite.FailureEOFError {
t.Fatal("invalid ApiFailure")
}
if tk.FailingGateways != nil {
Expand Down Expand Up @@ -344,11 +344,24 @@ func TestInvalidCaCert(t *testing.T) {
if tk.APIStatus != "blocked" {
t.Fatal("ApiStatus should be blocked")
}
if tk.FailingGateways != nil {
t.Fatal("invalid FailingGateways")

if tk.FailingGateways == nil || len(tk.FailingGateways) != 1 {
t.Fatal("invalid length of FailingGateways")
}
if tk.TransportStatus != nil {
t.Fatal("invalid TransportStatus")

gw := tk.FailingGateways[0]
if gw.IP != "234.345.234.345" {
t.Fatal("invalid failed gateway ip: " + fmt.Sprint(gw.IP))
}
if gw.Port != 443 {
t.Fatal("invalid failed gateway port: " + fmt.Sprint(gw.Port))
}
if gw.TransportType != "openvpn" {
t.Fatal("invalid failed transport type: " + fmt.Sprint(gw.TransportType))
}

if tk.TransportStatus == nil || tk.TransportStatus["openvpn"] != "ok" {
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
}
}

Expand All @@ -371,17 +384,17 @@ func TestFailureCaCertFetch(t *testing.T) {
t.Fatal("invalid ApiStatus")
}

if tk.APIFailure != nil {
t.Fatal("ApiFailure should be null")
if tk.APIFailure == nil || len(tk.APIFailure) != 1 || tk.APIFailure[0] != io.EOF.Error() {
t.Fatal("ApiFailure should not be null" + fmt.Sprint(tk.APIFailure))
}
if len(tk.Requests) > 1 {
t.Fatal("Unexpected requests")
if len(tk.Requests) == 1 {
t.Fatal("Too less requests, expected to run all API requests")
}
if tk.FailingGateways != nil {
t.Fatal("invalid FailingGateways")
}
if tk.TransportStatus != nil {
t.Fatal("invalid TransportStatus")
if tk.TransportStatus == nil || tk.TransportStatus["openvpn"] != "ok" || tk.TransportStatus["obfs4"] != "ok" {
t.Fatal("invalid TransportStatus: " + fmt.Sprint(tk.TransportStatus))
}
}

Expand Down