diff --git a/allexperiments.go b/allexperiments.go
index 893520bf164b4b0185e3b101895a466b93a78869..ce276db190728dc792698d8d8e2f8646d17a6ae9 100644
--- a/allexperiments.go
+++ b/allexperiments.go
@@ -11,6 +11,7 @@ import (
 	"github.com/ooni/probe-engine/experiment/hirl"
 	"github.com/ooni/probe-engine/experiment/ndt7"
 	"github.com/ooni/probe-engine/experiment/psiphon"
+	"github.com/ooni/probe-engine/experiment/riseupvpn"
 	"github.com/ooni/probe-engine/experiment/sniblocking"
 	"github.com/ooni/probe-engine/experiment/stunreachability"
 	"github.com/ooni/probe-engine/experiment/telegram"
@@ -202,6 +203,18 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
 		}
 	},
 
+	"riseupvpn": func(session *Session) *ExperimentBuilder {
+		return &ExperimentBuilder{
+			build: func(config interface{}) *Experiment {
+				return NewExperiment(session, riseupvpn.NewExperimentMeasurer(
+					*config.(*riseupvpn.Config),
+				))
+			},
+			config:      &riseupvpn.Config{},
+			inputPolicy: InputNone,
+		}
+	},
+
 	"telegram": func(session *Session) *ExperimentBuilder {
 		return &ExperimentBuilder{
 			build: func(config interface{}) *Experiment {
diff --git a/experiment/riseupvpn/riseupvpn.go b/experiment/riseupvpn/riseupvpn.go
new file mode 100644
index 0000000000000000000000000000000000000000..61162a3ecd52411b5f023ef11b894aa347e9d6cd
--- /dev/null
+++ b/experiment/riseupvpn/riseupvpn.go
@@ -0,0 +1,280 @@
+// Package riseupvpn contains the RiseupVPN network experiment.
+// API testing based on telegram experiment
+// TODO: write spec
+package riseupvpn
+
+import (
+	"context"
+	"encoding/json"
+	"time"
+
+	"github.com/apex/log"
+	"github.com/ooni/probe-engine/experiment/urlgetter"
+	"github.com/ooni/probe-engine/model"
+	"github.com/ooni/probe-engine/netx"
+	"github.com/ooni/probe-engine/netx/archival"
+)
+
+const (
+	testName      = "riseupvpn"
+	testVersion   = "0.0.2"
+	eipServiceURL = "https://api.black.riseup.net:443/3/config/eip-service.json"
+	providerURL   = "https://riseup.net/provider.json"
+	geoServiceURL = "https://api.black.riseup.net:9001/json"
+	tcpConnect    = "tcpconnect://"
+)
+
+// EipService main json object of eip-service.json
+type EipService struct {
+	Gateways []GatewayV3
+}
+
+//GatewayV3 json obj Version 3
+type GatewayV3 struct {
+	Capabilities struct {
+		Transport []TransportV3
+	}
+	Host      string
+	IPAddress string `json:"ip_address"`
+}
+
+//TransportV3 json obj Version 3
+type TransportV3 struct {
+	Type      string
+	Protocols []string
+	Ports     []string
+	Options   map[string]string
+}
+
+type gatewayConnection struct {
+	IP            string
+	Port          int
+	TransportType string `json:"transport_type"`
+}
+
+// Config contains the riseupvpn experiment config.
+type Config struct {
+	urlgetter.Config
+}
+
+// TestKeys contains riseupvpn test keys.
+type TestKeys struct {
+	urlgetter.TestKeys
+	RiseupVPNApiFailure      *string             `json:"riseupvpn_api_failure"`
+	RiseupVPNApiStatus       string              `json:"riseupvpn_api_status"`
+	RiseupVPNCACertStatus    bool                `json:"riseupvpn_ca_cert_status"`
+	RiseupVPNFailingGateways []gatewayConnection `json:"riseupvpn_failing_gateways"`
+}
+
+// NewTestKeys creates new riseupvpn TestKeys.
+func NewTestKeys() *TestKeys {
+	return &TestKeys{
+		RiseupVPNApiFailure:      nil,
+		RiseupVPNApiStatus:       "ok",
+		RiseupVPNCACertStatus:    true,
+		RiseupVPNFailingGateways: nil,
+	}
+}
+
+// Update updates the TestKeys using the given MultiOutput result.
+func (tk *TestKeys) Update(v urlgetter.MultiOutput) {
+	tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...)
+	tk.Queries = append(tk.Queries, v.TestKeys.Queries...)
+	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.RiseupVPNApiStatus != "ok" {
+		return // we already flipped the state
+	}
+	if v.TestKeys.Failure != nil {
+		tk.RiseupVPNApiStatus = "blocked"
+		tk.RiseupVPNApiFailure = v.TestKeys.Failure
+		return
+	}
+}
+
+// AddGatewayConnectTestKeys updates the TestKeys using the given MultiOutput result of gateway connectivity testing.
+func (tk *TestKeys) AddGatewayConnectTestKeys(v urlgetter.MultiOutput, transportType string) {
+	tk.NetworkEvents = append(tk.NetworkEvents, v.TestKeys.NetworkEvents...)
+	tk.TCPConnect = append(tk.TCPConnect, v.TestKeys.TCPConnect...)
+	for _, tcpConnect := range v.TestKeys.TCPConnect {
+		if !tcpConnect.Status.Success {
+			gatewayConnection := newGatewayConnection(tcpConnect, transportType)
+			tk.RiseupVPNFailingGateways = append(tk.RiseupVPNFailingGateways, *gatewayConnection)
+		}
+	}
+	return
+}
+
+func newGatewayConnection(tcpConnect archival.TCPConnectEntry, transportType string) *gatewayConnection {
+	return &gatewayConnection{
+		IP:            tcpConnect.IP,
+		Port:          tcpConnect.Port,
+		TransportType: transportType,
+	}
+}
+
+// AddCACertFetchTestKeys Adding generic ctx.Get() testKeys to riseupvpn specific test keys
+func (tk *TestKeys) AddCACertFetchTestKeys(testKeys urlgetter.TestKeys) {
+	tk.NetworkEvents = append(tk.NetworkEvents, testKeys.NetworkEvents...)
+	tk.Queries = append(tk.Queries, testKeys.Queries...)
+	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.RiseupVPNApiStatus = "blocked"
+		tk.RiseupVPNApiFailure = tk.Failure
+		tk.RiseupVPNCACertStatus = false
+	}
+}
+
+// Measurer performs the measurement
+type Measurer struct {
+	// Config contains the experiment settings. If empty we
+	// will be using default settings.
+	Config Config
+
+	// Getter is an optional getter to be used for testing.
+	Getter urlgetter.MultiGetter
+}
+
+// ExperimentName implements ExperimentMeasurer.ExperimentName
+func (m Measurer) ExperimentName() string {
+	return testName
+}
+
+// ExperimentVersion implements ExperimentMeasurer.ExperimentVersion
+func (m Measurer) ExperimentVersion() string {
+	return testVersion
+}
+
+// Run implements ExperimentMeasurer.Run
+func (m Measurer) Run(ctx context.Context, sess model.ExperimentSession,
+	measurement *model.Measurement, callbacks model.ExperimentCallbacks) error {
+	ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
+	defer cancel()
+	testkeys := NewTestKeys()
+	measurement.TestKeys = testkeys
+
+	caTarget := "https://black.riseup.net/ca.crt"
+	caGetter := urlgetter.Getter{
+		Config:  m.Config.Config,
+		Session: sess,
+		Target:  caTarget,
+	}
+	log.Info("Getting CA cerificate; please be patient...")
+	tk, err := caGetter.Get(ctx)
+
+	if err != nil {
+		log.Error("Getting CA cerificate failed. Aborting test.")
+		testkeys.AddCACertFetchTestKeys(tk)
+		measurement.TestKeys = testkeys
+		return nil
+	}
+
+	ok := netx.CertPool.AppendCertsFromPEM([]byte(tk.HTTPResponseBody))
+	if !ok {
+		testkeys.RiseupVPNCACertStatus = false
+		testkeys.RiseupVPNApiStatus = "blocked"
+		errorValue := "invalid_ca"
+		testkeys.RiseupVPNApiFailure = &errorValue
+		return nil
+	}
+
+	urlgetter.RegisterExtensions(measurement)
+	inputs := []urlgetter.MultiInput{
+
+		// Here we need to provide the method explicitly. See
+		// https://github.com/ooni/probe-engine/issues/827.
+		{Target: providerURL, Config: urlgetter.Config{
+			Method:          "GET",
+			FailOnHTTPError: true,
+		}},
+		{Target: eipServiceURL, Config: urlgetter.Config{
+			Method:          "GET",
+			FailOnHTTPError: true,
+		}},
+		{Target: geoServiceURL, Config: urlgetter.Config{
+			Method:          "GET",
+			FailOnHTTPError: true,
+		}},
+	}
+	multi := urlgetter.Multi{Begin: time.Now(), Getter: m.Getter, Session: sess}
+
+	for entry := range multi.Collect(ctx, inputs, "riseupvpn", callbacks) {
+		testkeys.Update(entry)
+	}
+
+	//test gateways now
+	gateways := parseGateways(testkeys)
+	openvpnEndpoints := generateMultiInputs(gateways, "openvpn")
+	obfs4Endpoints := generateMultiInputs(gateways, "obfs4")
+
+	// measure openvpn in parallel
+	multi = urlgetter.Multi{Begin: time.Now(), Getter: m.Getter, Session: sess}
+	for entry := range multi.Collect(ctx, openvpnEndpoints, "riseupvpn", callbacks) {
+		testkeys.AddGatewayConnectTestKeys(entry, "openvpn")
+	}
+
+	// measure obfs4 in parallel
+	multi = urlgetter.Multi{Begin: time.Now(), Getter: m.Getter, Session: sess}
+	for entry := range multi.Collect(ctx, obfs4Endpoints, "riseupvpn", callbacks) {
+		testkeys.AddGatewayConnectTestKeys(entry, "obfs4")
+	}
+
+	return nil
+}
+
+func generateMultiInputs(gateways []GatewayV3, transportType string) []urlgetter.MultiInput {
+	var gatewayInputs []urlgetter.MultiInput
+	for _, gateway := range gateways {
+		for _, transport := range gateway.Capabilities.Transport {
+			if transport.Type != transportType {
+				continue
+			}
+			supportsTCP := false
+			for _, protocol := range transport.Protocols {
+				if protocol == "tcp" {
+					supportsTCP = true
+				}
+			}
+			if !supportsTCP {
+				continue
+			}
+			for _, port := range transport.Ports {
+				tcpConnection := tcpConnect + gateway.IPAddress + ":" + port
+				gatewayInputs = append(gatewayInputs, urlgetter.MultiInput{Target: tcpConnection})
+			}
+		}
+	}
+	return gatewayInputs
+}
+
+func parseGateways(testKeys *TestKeys) []GatewayV3 {
+	for _, requestEntry := range testKeys.Requests {
+		if requestEntry.Request.URL == eipServiceURL && requestEntry.Failure == nil {
+			eipService, err := DecodeEIP3(requestEntry.Response.Body.Value)
+			if err == nil {
+				return eipService.Gateways
+			}
+		}
+	}
+
+	return nil
+}
+
+//DecodeEIP3 decodes eip-service.json version 3
+func DecodeEIP3(body string) (*EipService, error) {
+	var eip EipService
+	err := json.Unmarshal([]byte(body), &eip)
+	if err != nil {
+		log.Error(err.Error())
+	}
+
+	return &eip, err
+}
+
+// NewExperimentMeasurer creates a new ExperimentMeasurer.
+func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
+	return Measurer{Config: config}
+}
diff --git a/experiment/riseupvpn/riseupvpn_test.go b/experiment/riseupvpn/riseupvpn_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d5f1e965145394887135a56a901fcd0362ea0b6
--- /dev/null
+++ b/experiment/riseupvpn/riseupvpn_test.go
@@ -0,0 +1,480 @@
+package riseupvpn_test
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"io/ioutil"
+	"math/rand"
+	"net/http"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/apex/log"
+	"github.com/ooni/probe-engine/experiment/riseupvpn"
+	"github.com/ooni/probe-engine/experiment/urlgetter"
+	"github.com/ooni/probe-engine/internal/mockable"
+	"github.com/ooni/probe-engine/model"
+	"github.com/ooni/probe-engine/netx/errorx"
+	"github.com/ooni/probe-engine/netx/selfcensor"
+)
+
+func TestNewExperimentMeasurer(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	if measurer.ExperimentName() != "riseupvpn" {
+		t.Fatal("unexpected name")
+	}
+	if measurer.ExperimentVersion() != "0.0.2" {
+		t.Fatal("unexpected version")
+	}
+}
+
+func TestIntegration(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	measurement := new(model.Measurement)
+	err := measurer.Run(
+		context.Background(),
+		&mockable.Session{
+			MockableLogger: log.Log,
+		},
+		measurement,
+		model.NewPrinterCallbacks(log.Log),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.Agent != "" {
+		t.Fatal("unexpected Agent: " + tk.Agent)
+	}
+	if tk.FailedOperation != nil {
+		t.Fatal("unexpected FailedOperation")
+	}
+	if tk.Failure != nil {
+		t.Fatal("unexpected Failure")
+	}
+	if len(tk.NetworkEvents) <= 0 {
+		t.Fatal("no NetworkEvents?!")
+	}
+	if len(tk.Queries) <= 0 {
+		t.Fatal("no Queries?!")
+	}
+	if len(tk.Requests) <= 0 {
+		t.Fatal("no Requests?!")
+	}
+	if len(tk.TCPConnect) <= 0 {
+		t.Fatal("no TCPConnect?!")
+	}
+	if len(tk.TLSHandshakes) <= 0 {
+		t.Fatal("no TLSHandshakes?!")
+	}
+	if tk.RiseupVPNApiFailure != nil {
+		t.Fatal("unexpected RiseupVPNApiFailure")
+	}
+	if tk.RiseupVPNApiStatus != "ok" {
+		t.Fatal("unexpected RiseupvpnStatus")
+	}
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("unexpected RiseupvPNCaCertStatus")
+	}
+	if tk.RiseupVPNFailingGateways != nil {
+		t.Fatal("unexpected RiseupVPNFailingGateways value")
+	}
+}
+
+// TestUpdateWithMixedResults tests if one operation failed
+// RiseupVPNApiStatus is considered as blocked
+func TestUpdateWithMixedResults(t *testing.T) {
+	tk := riseupvpn.NewTestKeys()
+	tk.Update(urlgetter.MultiOutput{
+		Input: urlgetter.MultiInput{
+			Config: urlgetter.Config{Method: "GET"},
+			Target: "https://api.black.riseup.net:443/3/config/eip-service.json",
+		},
+		TestKeys: urlgetter.TestKeys{
+			HTTPResponseStatus: 200,
+		},
+	})
+	tk.Update(urlgetter.MultiOutput{
+		Input: urlgetter.MultiInput{
+			Config: urlgetter.Config{Method: "GET"},
+			Target: "https://riseup.net/provider.json",
+		},
+		TestKeys: urlgetter.TestKeys{
+			FailedOperation: (func() *string {
+				s := errorx.HTTPRoundTripOperation
+				return &s
+			})(),
+			Failure: (func() *string {
+				s := errorx.FailureEOFError
+				return &s
+			})(),
+		},
+	})
+	tk.Update(urlgetter.MultiOutput{
+		Input: urlgetter.MultiInput{
+			Config: urlgetter.Config{Method: "GET"},
+			Target: "https://api.black.riseup.net:9001/json",
+		},
+		TestKeys: urlgetter.TestKeys{
+			HTTPResponseStatus: 200,
+		},
+	})
+	if tk.RiseupVPNApiStatus != "blocked" {
+		t.Fatal("RiseupVPNApiStatus should be blocked")
+	}
+	if *tk.RiseupVPNApiFailure != errorx.FailureEOFError {
+		t.Fatal("invalid RiseupVPNApiFailure")
+	}
+}
+
+func TestIntegrationFailureCaCertFetch(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+	cancel()
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err := measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.RiseupVPNCACertStatus != false {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+	if tk.RiseupVPNApiStatus != "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure != nil {
+		t.Fatal("RiseupVPNApiFailure should be null")
+	}
+	if len(tk.Requests) > 1 {
+		t.Fatal("Unexpected requests")
+	}
+
+}
+
+func TestIntegrationFailureEipServiceBlocked(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+	selfcensor.Enable(`{"PoisonSystemDNS":{"api.black.riseup.net":["NXDOMAIN"]}}`)
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err := measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+
+	for _, entry := range tk.Requests {
+		if entry.Request.URL == "https://api.black.riseup.net:443/3/config/eip-service.json" {
+			if entry.Failure == nil {
+				t.Fatal("Failure for " + entry.Request.URL + " should not be null")
+			}
+		}
+	}
+
+	if tk.RiseupVPNApiStatus != "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure == nil {
+		t.Fatal("RiseupVPNApiFailure should not be null")
+	}
+
+	cancel()
+}
+
+func TestIntegrationFailureProviderUrlBlocked(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+	selfcensor.Enable(`{"BlockedEndpoints":{"198.252.153.70:443":"REJECT"}}`)
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err := measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+
+	for _, entry := range tk.Requests {
+		if entry.Request.URL == "https://riseup.net/provider.json" {
+			if entry.Failure == nil {
+				t.Fatal("Failure for " + entry.Request.URL + " should not be null")
+			}
+		}
+	}
+
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+	if tk.RiseupVPNApiStatus != "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure == nil {
+		t.Fatal("RiseupVPNApiFailure should not be null")
+	}
+	cancel()
+}
+
+func TestIntegrationFailureGeoIpServiceBlocked(t *testing.T) {
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+	selfcensor.Enable(`{"BlockedEndpoints":{"198.252.153.107:9001":"REJECT"}}`)
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err := measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+
+	for _, entry := range tk.Requests {
+		if entry.Request.URL == "https://api.black.riseup.net:9001/json" {
+			if entry.Failure == nil {
+				t.Fatal("Failure for " + entry.Request.URL + " should not be null")
+			}
+		}
+	}
+
+	if tk.RiseupVPNApiStatus != "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure == nil {
+		t.Fatal("RiseupVPNApiFailure should not be null")
+	}
+
+	cancel()
+}
+
+func TestIntegrationFailureOpenvpnGateway(t *testing.T) {
+	// - fetch client cert and add to certpool
+	caFetchClient := &http.Client{
+		Timeout: time.Second * 30,
+	}
+
+	caCertResponse, err := caFetchClient.Get("https://black.riseup.net/ca.crt")
+	if err != nil {
+		t.SkipNow()
+	}
+	defer caCertResponse.Body.Close()
+
+	var bodyString string
+	if caCertResponse.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(caCertResponse.Body)
+		if err != nil {
+			t.SkipNow()
+		}
+		bodyString = string(bodyBytes)
+	}
+
+	certs := x509.NewCertPool()
+	certs.AppendCertsFromPEM([]byte(bodyString))
+
+	// - fetch and parse eip-service.json
+	client := &http.Client{
+		Timeout: time.Second * 30,
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				RootCAs: certs,
+			},
+		},
+	}
+
+	eipResponse, err := client.Get("https://api.black.riseup.net/3/config/eip-service.json")
+	if err != nil {
+		t.SkipNow()
+	}
+	defer eipResponse.Body.Close()
+
+	if eipResponse.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(eipResponse.Body)
+		if err != nil {
+			return
+		}
+		bodyString = string(bodyBytes)
+	}
+
+	eipService, err := riseupvpn.DecodeEIP3(bodyString)
+
+	// - self censor random gateway
+	gateways := eipService.Gateways
+	if gateways == nil || len(gateways) == 0 {
+		t.SkipNow()
+	}
+	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
+	min := 0
+	max := len(gateways) - 1
+	randomIndex := rnd.Intn(max-min+1) + min
+
+	IP := gateways[randomIndex].IPAddress
+	port := gateways[randomIndex].Capabilities.Transport[0].Ports[0]
+	selfcensor.Enable(`{"BlockedEndpoints":{"` + IP + `:` + port + `":"REJECT"}}`)
+
+	// - run measurement
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err = measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+
+	if tk.RiseupVPNFailingGateways == nil || len(tk.RiseupVPNFailingGateways) != 1 {
+		t.Fatal("unexpected amount of failing gateways")
+	}
+
+	entry := tk.RiseupVPNFailingGateways[0]
+	if entry.IP != IP || fmt.Sprint(entry.Port) != port {
+		t.Fatal("unexpected failed gateway configuration")
+	}
+
+	if tk.RiseupVPNApiStatus == "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure != nil {
+		t.Fatal("RiseupVPNApiFailure should be null")
+	}
+
+	cancel()
+}
+
+func TestIntegrationFailureObfs4Gateway(t *testing.T) {
+	// - fetch client cert and add to certpool
+	caFetchClient := &http.Client{
+		Timeout: time.Second * 30,
+	}
+
+	caCertResponse, err := caFetchClient.Get("https://black.riseup.net/ca.crt")
+	if err != nil {
+		t.SkipNow()
+	}
+	defer caCertResponse.Body.Close()
+
+	var bodyString string
+	if caCertResponse.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(caCertResponse.Body)
+		if err != nil {
+			t.SkipNow()
+		}
+		bodyString = string(bodyBytes)
+	}
+
+	certs := x509.NewCertPool()
+	certs.AppendCertsFromPEM([]byte(bodyString))
+
+	// - fetch and parse eip-service.json
+	client := &http.Client{
+		Timeout: time.Second * 30,
+		Transport: &http.Transport{
+			TLSClientConfig: &tls.Config{
+				RootCAs: certs,
+			},
+		},
+	}
+
+	eipResponse, err := client.Get("https://api.black.riseup.net/3/config/eip-service.json")
+	if err != nil {
+		t.SkipNow()
+	}
+	defer eipResponse.Body.Close()
+
+	if eipResponse.StatusCode == http.StatusOK {
+		bodyBytes, err := ioutil.ReadAll(eipResponse.Body)
+		if err != nil {
+			return
+		}
+		bodyString = string(bodyBytes)
+	}
+
+	eipService, err := riseupvpn.DecodeEIP3(bodyString)
+
+	// - self censor random gateway
+	gateways := eipService.Gateways
+	if gateways == nil || len(gateways) == 0 {
+		t.SkipNow()
+	}
+
+	var selfcensoredGateways []string
+	for _, gateway := range gateways {
+		for _, transport := range gateway.Capabilities.Transport {
+			if transport.Type == "obfs4" {
+				selfcensoredGateways = append(selfcensoredGateways, `{"BlockedEndpoints":{"`+gateway.IPAddress+`:`+transport.Ports[0]+`":"REJECT"}}`)
+			}
+		}
+	}
+
+	if len(selfcensoredGateways) == 0 {
+		t.SkipNow()
+	}
+
+	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
+	min := 0
+	max := len(selfcensoredGateways) - 1
+	randomIndex := rnd.Intn(max-min+1) + min
+
+	selfcensor.Enable(selfcensoredGateways[randomIndex])
+
+	// - run measurement
+	measurer := riseupvpn.NewExperimentMeasurer(riseupvpn.Config{})
+	ctx, cancel := context.WithCancel(context.Background())
+
+	sess := &mockable.Session{MockableLogger: log.Log}
+	measurement := new(model.Measurement)
+	callbacks := model.NewPrinterCallbacks(log.Log)
+	err = measurer.Run(ctx, sess, measurement, callbacks)
+	if err != nil {
+		t.Fatal(err)
+	}
+	tk := measurement.TestKeys.(*riseupvpn.TestKeys)
+	if tk.RiseupVPNCACertStatus != true {
+		t.Fatal("invalid RiseupVPNCACertStatus ")
+	}
+
+	if tk.RiseupVPNFailingGateways == nil || len(tk.RiseupVPNFailingGateways) != 1 {
+		t.Fatal("unexpected amount of failing gateways")
+	}
+
+	entry := tk.RiseupVPNFailingGateways[0]
+	if !strings.Contains(selfcensoredGateways[randomIndex], entry.IP) || !strings.Contains(selfcensoredGateways[randomIndex], strconv.Itoa(entry.Port)) {
+		t.Fatal("unexpected failed gateway configuration")
+	}
+
+	if tk.RiseupVPNApiStatus == "blocked" {
+		t.Fatal("invalid RiseupVPNApiStatus")
+	}
+
+	if tk.RiseupVPNApiFailure != nil {
+		t.Fatal("RiseupVPNApiFailure should be null")
+	}
+
+	cancel()
+}