From 2f480e3e9d5329eb47548138a7ae57578cd897a1 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 4 Mar 2023 02:17:41 +0100 Subject: [PATCH 1/8] RiseupVPN: Don't do TLS verification if fetching the ca cert failed or the ca was invalid. Instead of early returning the test, we can still gather the necessary configs to run the gateway tests. This change implies that there might occur multiple failures while fetching data from the API, which is why TestKeys.APIFailures becomes a string array. --- internal/experiment/riseupvpn/riseupvpn.go | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index 37061826cd..ce2b7ed5a0 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -61,7 +61,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"` @@ -86,12 +86,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 } } @@ -147,11 +144,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. @@ -208,17 +200,15 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { 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 } } @@ -230,16 +220,19 @@ 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) { From a35d21ecd1e135eac69abb0e44e3cb6f3990b48b Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 6 Mar 2023 15:19:18 +0100 Subject: [PATCH 2/8] RiseupVPN: re-run fetches from the API using Tor+Snowflake, if the API was considered to be blocked --- internal/experiment/riseupvpn/riseupvpn.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index ce2b7ed5a0..12cdb6f46f 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -235,10 +235,20 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { NoTLSVerify: !testkeys.CACertStatus, }}, } + for entry := range multi.CollectOverall(ctx, inputs, 1, 50, "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, 50, "riseupvpn", callbacks) { + testkeys.UpdateProviderAPITestKeys(entry) + } + } + // test gateways now testkeys.TransportStatus = map[string]string{} gateways := parseGateways(testkeys) From ef38cec3a46a6db0be298ef2760476089ee492b9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 6 Mar 2023 15:20:27 +0100 Subject: [PATCH 3/8] Fixing tests after chaning riseupvpn test logic. Gateway tests are no longer skipped, API config data is fetched even if CA cert is invalid or missing --- .../experiment/riseupvpn/riseupvpn_test.go | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn_test.go b/internal/experiment/riseupvpn/riseupvpn_test.go index 9af91c9699..d82b4d2eec 100644 --- a/internal/experiment/riseupvpn/riseupvpn_test.go +++ b/internal/experiment/riseupvpn/riseupvpn_test.go @@ -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 { @@ -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)) } } @@ -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)) } } From cfa4857e3cc4fe6b44a3aae1513e468f529c1fe9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 6 Mar 2023 23:19:45 +0100 Subject: [PATCH 4/8] Filter gateways: just expose a limited set of gateways for network testing, avoid those known to be overloaded. --- internal/experiment/riseupvpn/riseupvpn.go | 103 +++++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index 12cdb6f46f..34b4598f95 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -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 @@ -36,6 +38,7 @@ type GatewayV3 struct { } Host string IPAddress string `json:"ip_address"` + Location string `json:"location"` } // TransportV3 describes a transport. @@ -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 @@ -302,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 @@ -326,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} From 0f273eafdf1c68d660728ad1425801360ed46b75 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 6 Mar 2023 23:48:29 +0100 Subject: [PATCH 5/8] RiseupVPN: reduce multi-collectors (url-getter) overallCount after reducing the amount of tested gateways --- internal/experiment/riseupvpn/riseupvpn.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index 34b4598f95..5adcc243eb 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -217,7 +217,7 @@ 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 { @@ -257,7 +257,7 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { }}, } - 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) } @@ -265,7 +265,7 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { for _, input := range inputs { input.Config.Tunnel = "torsf" } - 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) } } From f6150526cdadd4989b7dba953b00022a32b97725 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Fri, 17 Mar 2023 16:47:33 +0100 Subject: [PATCH 6/8] add null check in gateway filter function --- internal/experiment/riseupvpn/riseupvpn.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index 5adcc243eb..6c1e605c36 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -346,15 +346,17 @@ func parseGateways(testKeys *TestKeys) []GatewayV3 { 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 + if eipService != 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 + } } } From 74239a51e79dd21fbf969adcd0c3a8559c326f99 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Sat, 18 Mar 2023 21:11:10 +0100 Subject: [PATCH 7/8] test only gateways from locations with high redundancy regarding obfs4 bridges, avoid hard-coding locations in the test --- internal/experiment/riseupvpn/riseupvpn.go | 44 ++++++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index 6c1e605c36..d6466a3b16 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -24,8 +24,6 @@ const ( tcpConnect = "tcpconnect://" ) -var locations = []string{"Seattle", "Amsterdam"} - // EipService is the main JSON object of eip-service.json. type EipService struct { Gateways []GatewayV3 @@ -344,12 +342,14 @@ func parseGateways(testKeys *TestKeys) []GatewayV3 { return filterGateways(eipService, geoService) } +// filterGateways selects a subset of available gateways supporting obfs4 func filterGateways(eipService *EipService, geoService *GeoService) []GatewayV3 { var result []GatewayV3 = nil if eipService != nil { + locations := getLocationsUnderTest(eipService, geoService) for _, gateway := range eipService.Gateways { if !gateway.hasTransport("obfs4") || - !gateway.isLocationUnderTest() || + !gateway.isLocationUnderTest(locations) || geoService != nil && !geoService.isHealthyGateway(gateway) { continue } @@ -359,6 +359,42 @@ func filterGateways(eipService *EipService, geoService *GeoService) []GatewayV3 } } } + return result +} + +// getLocationsUnderTest parses all gateways supporting obfs4 and returns the two locations having most obfs4 bridges +func getLocationsUnderTest(eipService *EipService, geoService *GeoService) []string { + var result []string = nil + if eipService != nil { + locationMap := map[string]int{} + locations := []string{} + for _, gateway := range eipService.Gateways { + if !gateway.hasTransport("obfs4") { + continue + } + if _, ok := locationMap[gateway.Location]; !ok { + locations = append(locations, gateway.Location) + } + locationMap[gateway.Location] += 1 + } + + location1 := "" + location2 := "" + for _, location := range locations { + if locationMap[location] > locationMap[location1] { + location2 = location1 + location1 = location + } else if locationMap[location] > locationMap[location2] { + location2 = location + } + } + if location1 != "" { + result = append(result, location1) + } + if location2 != "" { + result = append(result, location2) + } + } return result } @@ -372,7 +408,7 @@ func (gateway *GatewayV3) hasTransport(s string) bool { return false } -func (gateway *GatewayV3) isLocationUnderTest() bool { +func (gateway *GatewayV3) isLocationUnderTest(locations []string) bool { for _, location := range locations { if location == gateway.Location { return true From 36731856014d12773f3855c114397d839cb8060e Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 20 Mar 2023 10:08:53 +0100 Subject: [PATCH 8/8] first draft on obfs4 handshakes in RiseupVPN test --- internal/experiment/riseupvpn/riseupvpn.go | 107 ++++++++++++++++++--- 1 file changed, 91 insertions(+), 16 deletions(-) diff --git a/internal/experiment/riseupvpn/riseupvpn.go b/internal/experiment/riseupvpn/riseupvpn.go index d6466a3b16..ffc95bcd5a 100644 --- a/internal/experiment/riseupvpn/riseupvpn.go +++ b/internal/experiment/riseupvpn/riseupvpn.go @@ -7,9 +7,11 @@ import ( "context" "encoding/json" "errors" + "fmt" "time" "github.com/ooni/probe-cli/v3/internal/experiment/urlgetter" + "github.com/ooni/probe-cli/v3/internal/measurex" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" "github.com/ooni/probe-cli/v3/internal/tracex" @@ -44,7 +46,12 @@ type TransportV3 struct { Type string Protocols []string Ports []string - Options map[string]string + Options OptionsV3 +} + +type OptionsV3 struct { + Cert string `json:"cert"` + IatMode string `json:"iatMode"` } // GatewayConnection describes the connection to a riseupvpn gateway. @@ -77,14 +84,24 @@ type Config struct { urlgetter.Config } +// TargetResults contains the results of measuring a target. +type Obfs4TargetResults struct { + Failure *string `json:"failure"` + NetworkEvents []*measurex.ArchivalNetworkEvent `json:"network_events"` + TargetAddress string `json:"target_address"` + TCPConnect []*measurex.ArchivalTCPConnect `json:"tcp_connect"` + TLSHandshakes []*measurex.ArchivalQUICTLSHandshakeEvent `json:"tls_handshakes"` +} + // TestKeys contains riseupvpn test keys. type TestKeys struct { urlgetter.TestKeys - APIFailure []string `json:"api_failure"` - APIStatus string `json:"api_status"` - CACertStatus bool `json:"ca_cert_status"` - FailingGateways []GatewayConnection `json:"failing_gateways"` - TransportStatus map[string]string `json:"transport_status"` + Obfs4HandshakeResults []Obfs4TargetResults `json:"obfs4_handshake_results"` + APIFailure []string `json:"api_failure"` + APIStatus string `json:"api_status"` + CACertStatus bool `json:"ca_cert_status"` + FailingGateways []GatewayConnection `json:"failing_gateways"` + TransportStatus map[string]string `json:"transport_status"` } // NewTestKeys creates new riseupvpn TestKeys. @@ -126,6 +143,28 @@ func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transport } } +// AddGatewayConnectTestKeys updates the TestKeys using the given MultiOutput +// result of gateway connectivity testing. Sets TransportStatus to "ok" if +// any successful TCP connection could be made +func (tk *TestKeys) AddGatewayObfs4HandshakeTestKeys(targetAddress string, measurement measurex.ArchivalMeasurement, failure *string) { + obfs4TargetResult := Obfs4TargetResults{ + Failure: failure, + NetworkEvents: measurement.NetworkEvents, + TCPConnect: measurement.TCPConnect, + TLSHandshakes: measurement.TLSHandshakes, + TargetAddress: targetAddress, + } + + // TODO: check if failure != nil + tk.Obfs4HandshakeResults = append(tk.Obfs4HandshakeResults, obfs4TargetResult) + for _, tcpConnect := range obfs4TargetResult.TCPConnect { + if !tcpConnect.Status.Success { + gatewayConnection := newObfs4GatewayConnection(*tcpConnect) + tk.FailingGateways = append(tk.FailingGateways, *gatewayConnection) + } + } +} + func (tk *TestKeys) updateTransportStatus(openvpnGatewayCount, obfs4GatewayCount int) { failingOpenvpnGateways, failingObfs4Gateways := 0, 0 for _, gw := range tk.FailingGateways { @@ -156,6 +195,15 @@ func newGatewayConnection( } } +func newObfs4GatewayConnection( + tcpConnect measurex.ArchivalTCPConnect) *GatewayConnection { + return &GatewayConnection{ + IP: tcpConnect.IP, + Port: int(tcpConnect.Port), + TransportType: "obfs4", + } +} + // AddCACertFetchTestKeys adds generic urlgetter.Get() testKeys to riseupvpn specific test keys func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) { tk.NetworkEvents = append(tk.NetworkEvents, testKeys.NetworkEvents...) @@ -272,8 +320,9 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { testkeys.TransportStatus = map[string]string{} gateways := parseGateways(testkeys) openvpnEndpoints := generateMultiInputs(gateways, "openvpn") - obfs4Endpoints := generateMultiInputs(gateways, "obfs4") - overallCount := 1 + len(inputs) + len(openvpnEndpoints) + len(obfs4Endpoints) + //obfs4Endpoints := generateMultiInputs(gateways, "obfs4") + obfs4HandshakeTargets := generateObfs4HandshakeInputs(gateways) + overallCount := 1 + len(inputs) + len(openvpnEndpoints) + len(obfs4HandshakeTargets) // measure openvpn in parallel for entry := range multi.CollectOverall( @@ -281,17 +330,18 @@ func (m Measurer) Run(ctx context.Context, args *model.ExperimentArgs) error { testkeys.AddGatewayConnectTestKeys(entry, "openvpn") } - // measure obfs4 in parallel - // TODO(bassosimone): when urlgetter is able to do obfs4 handshakes, here - // can possibly also test for the obfs4 handshake. - // See https://github.com/ooni/probe/issues/1463. - for entry := range multi.CollectOverall( - ctx, obfs4Endpoints, 1+len(inputs)+len(openvpnEndpoints), overallCount, "riseupvpn", callbacks) { - testkeys.AddGatewayConnectTestKeys(entry, "obfs4") + obfs4Measurer := measurex.NewMeasurerWithDefaultSettings() + obfs4Measurer.Begin = measurement.MeasurementStartTimeSaved + const timeout = 15 * time.Second + + for _, target := range obfs4HandshakeTargets { + meas, failure := obfs4Measurer.EasyOBFS4ConnectAndHandshake( + ctx, timeout, target.Address, sess.TempDir(), target.Params) + testkeys.AddGatewayObfs4HandshakeTestKeys(target.Address, *meas, failure) } // set transport status based on gateway test results - testkeys.updateTransportStatus(len(openvpnEndpoints), len(obfs4Endpoints)) + testkeys.updateTransportStatus(len(openvpnEndpoints), len(obfs4HandshakeTargets)) return nil } @@ -320,6 +370,31 @@ func generateMultiInputs(gateways []GatewayV3, transportType string) []urlgetter return gatewayInputs } +func generateObfs4HandshakeInputs(gateways []GatewayV3) []model.OOAPITorTarget { + var results []model.OOAPITorTarget = nil + for _, gateway := range gateways { + for _, transport := range gateway.Capabilities.Transport { + if transport.Type != "obfs4" { + continue + } + for _, port := range transport.Ports { + target := model.OOAPITorTarget{ + Address: fmt.Sprintf("%s:%s", gateway.IPAddress, port), + Params: map[string][]string{ + "cert": { + transport.Options.Cert, + }, + "iat-mode": {transport.Options.IatMode}, + }, + Protocol: "obfs4", + } + results = append(results, target) + } + } + } + return results +} + func parseGateways(testKeys *TestKeys) []GatewayV3 { var eipService *EipService = nil var geoService *GeoService = nil