diff --git a/internal/experiment/openvpn/endpoint.go b/internal/experiment/openvpn/endpoint.go index 6c5157d52..67c359552 100644 --- a/internal/experiment/openvpn/endpoint.go +++ b/internal/experiment/openvpn/endpoint.go @@ -148,11 +148,11 @@ func (e *endpoint) AsInputURI() string { return url.String() } -// APIEnabledProviders is the list of providers that the stable API Endpoint knows about. -// This array will be a subset of the keys in defaultOptionsByProvider, but it might make sense -// to still register info about more providers that the API officially knows about. +// APIEnabledProviders is the list of providers that the stable API Endpoint and/or this +// experiment knows about. var APIEnabledProviders = []string{ "riseupvpn", + "oonivpn", } // isValidProvider returns true if the provider is found as key in the array of [APIEnabledProviders]. diff --git a/internal/experiment/openvpn/openvpn.go b/internal/experiment/openvpn/openvpn.go index bc81da16b..17faa139c 100644 --- a/internal/experiment/openvpn/openvpn.go +++ b/internal/experiment/openvpn/openvpn.go @@ -17,7 +17,7 @@ import ( const ( testName = "openvpn" - testVersion = "0.1.4" + testVersion = "0.1.5" openVPNProtocol = "openvpn" ) diff --git a/internal/experiment/openvpn/richerinput.go b/internal/experiment/openvpn/richerinput.go index 5743865e9..7fb3ad111 100644 --- a/internal/experiment/openvpn/richerinput.go +++ b/internal/experiment/openvpn/richerinput.go @@ -87,13 +87,18 @@ func (tl *targetLoader) Load(ctx context.Context) ([]model.ExperimentTarget, err // If inputs and files are all empty and there are no options, let's use the backend if len(tl.loader.StaticInputs) <= 0 && len(tl.loader.SourceFiles) <= 0 && reflectx.StructOrStructPtrIsZero(tl.options) { - return tl.loadFromBackend(ctx) + targets, err := tl.loadFromBackend(ctx) + if err == nil { + return targets, nil + } } + tl.session.Logger().Warn("Error fetching OpenVPN targets from backend") + // Otherwise, attempt to load the static inputs from CLI and files inputs, err := targetloading.LoadStatic(tl.loader) - // Handle the case where we couldn't load from CLI or files + // Handle the case where we couldn't load from CLI or files: if err != nil { return nil, err } @@ -106,11 +111,34 @@ func (tl *targetLoader) Load(ctx context.Context) ([]model.ExperimentTarget, err URL: input, }) } + if len(targets) > 0 { + return targets, nil + } + + // Return the hardcoded endpoints. + return tl.loadFromDefaultEndpoints() +} + +func (tl *targetLoader) loadFromDefaultEndpoints() ([]model.ExperimentTarget, error) { + tl.session.Logger().Info("Using default OpenVPN endpoints") + targets := []model.ExperimentTarget{} + if udp, err := defaultOONIOpenVPNTargetUDP(); err == nil { + targets = append(targets, + &Target{ + Config: defaultOONIOpenVPNConfig, + URL: udp, + }) + } + if tcp, err := defaultOONIOpenVPNTargetTCP(); err == nil { + targets = append(targets, + &Target{ + Config: defaultOONIOpenVPNConfig, + URL: tcp, + }) + } return targets, nil } -// TODO(https://github.com/ooni/probe/issues/2755): make the code that fetches experiment private -// and let the common code export just the bare minimum to make this possible. func (tl *targetLoader) loadFromBackend(ctx context.Context) ([]model.ExperimentTarget, error) { if tl.options.Provider == "" { tl.options.Provider = defaultProvider diff --git a/internal/experiment/openvpn/targets.go b/internal/experiment/openvpn/targets.go new file mode 100644 index 000000000..d76619897 --- /dev/null +++ b/internal/experiment/openvpn/targets.go @@ -0,0 +1,53 @@ +package openvpn + +import ( + "fmt" + "net" +) + +const defaultOpenVPNEndpoint = "openvpn-server1.ooni.io" + +// this is a safety toggle: it's on purpose that the experiment will receive no +// input if the resolution fails. This also implies that we have no way of knowing if this +// target has been blocked at the level of DNS. +func resolveTarget() (string, error) { + ips, err := net.LookupIP(defaultOpenVPNEndpoint) + if err != nil { + return "", err + } + if len(ips) == 0 { + return "", fmt.Errorf("cannot resolve %v", defaultOpenVPNEndpoint) + } + return ips[0].String(), nil +} + +func defaultOONITargetURL(ip string) string { + return "openvpn://oonivpn.corp/?address=" + ip + ":1194" +} + +func defaultOONIOpenVPNTargetUDP() (string, error) { + ip, err := resolveTarget() + if err != nil { + return "", err + } + return defaultOONITargetURL(ip) + "&transport=udp", nil +} + +func defaultOONIOpenVPNTargetTCP() (string, error) { + ip, err := resolveTarget() + if err != nil { + return "", err + } + return defaultOONITargetURL(ip) + "&transport=tcp", nil +} + +var defaultOONIOpenVPNConfig = &Config{ + Auth: "SHA512", + Cipher: "AES-256-GCM", + Compress: "stub", + Provider: "oonivpn", + Obfuscation: "none", + SafeCA: "-----BEGIN CERTIFICATE-----\nMIIDbzCCAlegAwIBAgIUVZz0MWBrsd26OIEccP08bNv+bScwDQYJKoZIhvcNAQEL\nBQAwIjEgMB4GA1UEAwwXb3BlbnZwbi1zZXJ2ZXIxLm9vbmkuaW8wHhcNMjQwNzI4\nMTE0NzQwWhcNMzQwNzI2MTE0NzQwWjAiMSAwHgYDVQQDDBdvcGVudnBuLXNlcnZl\ncjEub29uaS5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKOkPmCu\nYpAOue+y46rS1dnu+t1JINmvjO+Z2s7nezQMmEgP6o06pjrb8YlLByspGn/y2u7x\nFv2G86kWvRG+cpQgc2xBsEqRq7j5n45j51pat6TKt4c9Ejjnq6kGiqs3W3zDwnn+\nkx3KuvkmnOXkVQfHsUmVaS7fQpf0586ftOC7DenOvJV9LosBEIGyLyFCipaqh0eX\n6UfSGmr8tAqSfoWj8J62JBsP+U0MoLtHpRXLn2jyE/2re2mcsFLG8nRqHpTy8LGM\n6xT7c9JZSR7WiYNXB3UFBUcOpvLZ4ru6sa0BdXJiM8WQhywjVWtnM9UPf09ct57K\nMCdjWhwR81SaVAsCAwEAAaOBnDCBmTAdBgNVHQ4EFgQUOhvQMsDoG1Iw1g/w3sFp\n7BtVqIQwXQYDVR0jBFYwVIAUOhvQMsDoG1Iw1g/w3sFp7BtVqIShJqQkMCIxIDAe\nBgNVBAMMF29wZW52cG4tc2VydmVyMS5vb25pLmlvghRVnPQxYGux3bo4gRxw/Txs\n2/5tJzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOC\nAQEAoiN+S2BSMTYVA5ydWWCmfUDN30QiD2Wb3Of7ReLfG8dN4dMXULbDpzDgMl6/\nGUfOzrnZytm+ctANIQkmwf7iNcDrxLQWuw3HlvXmWLvaXA8aEi608j5dh30b6CZ8\nRSbckRYjo6oPltsmDzUG56K7NLBk9CnmmwzfJ4Rk/gEj5aoo7/AfkYB1zDaV/QBp\n60eKZ6dm6CutGBPZB0rAlBomKrKck51iU1cmJUS/RZZsZuGa6P4T2sZC20g8psOm\ngzQY31eQuTcexEddjrLqZSzVK4ZNDpycfkd1G2u+XWf3W/U2c7BAY0LFt7tieC7p\nHov5K6t2+k9xsw0vFtdDIPm4SA==\n-----END CERTIFICATE-----\n", + SafeCert: "-----BEGIN CERTIFICATE-----\nMIIDcTCCAlmgAwIBAgIRAJU2B64KWje6NlskJbfZomAwDQYJKoZIhvcNAQELBQAw\nIjEgMB4GA1UEAwwXb3BlbnZwbi1zZXJ2ZXIxLm9vbmkuaW8wHhcNMjQwNzI4MTE0\nOTIyWhcNMjYxMDMxMTE0OTIyWjAVMRMwEQYDVQQDDApDTElFTlROQU1FMIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0dHk7p78A4Cz/kNDdZWXF5v/kMQv\nXghZnslZij/4TN8vPZMFVdABXQDLGNm3+ycvJD3PUVmBU1SAJKC3tT6Y+rwiV9Kj\npJYDP4413CP67ZEgFd/JnuthyRnnjzkvB0VtaZ861s3hY06cZBKozqhB4PZSRK0t\nkefXLYN0uJqhVY//On1sVtzHH43zapZzkniHzljNgfqK68cwhn5LVq0B72/5hL8V\nKEYHf6WTZT/ph0pCH+5rtabLt6g+oH67XXLW6bWqgO+sgmWywHOg1nkrBhpplTUk\nGeVfH/sb4TC7syb4zEm7cEfSV2qyuSrOxh+1ATFjge6kgdUAqnXk7z7cYwIDAQAB\no4GuMIGrMAkGA1UdEwQCMAAwHQYDVR0OBBYEFLBzll2vqdxuZuys0G1In9uWmyo0\nMF0GA1UdIwRWMFSAFDob0DLA6BtSMNYP8N7BaewbVaiEoSakJDAiMSAwHgYDVQQD\nDBdvcGVudnBuLXNlcnZlcjEub29uaS5pb4IUVZz0MWBrsd26OIEccP08bNv+bScw\nEwYDVR0lBAwwCgYIKwYBBQUHAwIwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUA\nA4IBAQADqxpvQ1GfmoXsioQZAMDjGe7OSVwLFX0JfYwfTE5b+41Ci1qNMN8MvnUE\nELPbiM4Ka5aJB4UyROml8lO8igqswqPFFSzj5MSzYJsS0oZO8RnCwOvIijxzxRjA\n/ND2tbl3HSv6h6O/9RHpqJrsqxaXIXWdtrpmwal8YNyTU1eQyh7fNnFbxwqy/6oO\n0AU3BjhDWC8GDZXrvzO5utIvE+aZmXQootiSA9AmdxfabaBs6gLKqAtip1wcVJq/\nzd//gBXi8Zu/2Narnxt1wen3wSYK/faA9qvHUtAfHxLNsFWKQiD6Cl+Ksdrj35yt\nRqTncboHHef0xRIvUleoOqQWPLnT\n-----END CERTIFICATE-----\n", + SafeKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR0eTunvwDgLP+\nQ0N1lZcXm/+QxC9eCFmeyVmKP/hM3y89kwVV0AFdAMsY2bf7Jy8kPc9RWYFTVIAk\noLe1Ppj6vCJX0qOklgM/jjXcI/rtkSAV38me62HJGeePOS8HRW1pnzrWzeFjTpxk\nEqjOqEHg9lJErS2R59ctg3S4mqFVj/86fWxW3McfjfNqlnOSeIfOWM2B+orrxzCG\nfktWrQHvb/mEvxUoRgd/pZNlP+mHSkIf7mu1psu3qD6gfrtdctbptaqA76yCZbLA\nc6DWeSsGGmmVNSQZ5V8f+xvhMLuzJvjMSbtwR9JXarK5Ks7GH7UBMWOB7qSB1QCq\ndeTvPtxjAgMBAAECggEBAJn1ky/JNxD26pxjDOgGCSVI0aGPY1ZzeBd8lZhNUkxN\n5GMhM2QBSk7NGzoz372JxhyowixmKfBUa+b0i3iR4zzwuZ6JsIw/i0iieED9oc2a\nlNmYKWDURR+EQ5ajli+WsS80qL8fuQfekgEYdAeYDSced8Vu8aZDYXBDKm2fAU0/\nFOnhhsGPOy6ojmYONnC6ny+thBaOIjNvxV87gvMds7NSfPZ4YkSkyHDe2c/qd/Wx\nq+A70zA1aONOeL/u2Vz0xugiPGqYgqkr4keooumUGtTiH8N7PmJKzQvB0ELc3XeA\nM+pWT2pG2B+ChcwpmpO0/XrZAOdQBdXre35mpqsIHsECgYEA8BHC96sIx0c5J45L\nD3FZpFjhR9ROqNuaUgPTWh2zO6gsOdfD0ENLkiLrkmpEbmg6A63itLPM7ekm+JIV\n/y39jyrg0miDRHcoZZTbBaoiDVTLapbi4o4LZcVp/XwIqm37HSZvNkD02RRFAbUc\nveajh8Dsozffr8wdGPL2BUY/rUMCgYEA375DFvTduK58IY9IMyHt80toZObGZ+W3\nKcHusWmFrnHyeVIwIetNZyJqyX1n204HbNx8EzApd7haJVojNCqn1oFVgmC+O2Rz\n1SUFp3w4AOSKtLZB7116UJ+n0AOipM19sYEkTKg+xN3saMymyshaAoEnVx9izLWr\nNNwWPsKakmECgYB1wwC0pP2FY3ax5KcFSEEE0WSQ66A6TJ8CpEXE6tTE6tXm+eRg\nAOLNKLwN8nrm/dGXhHC0244nFju7q02HA3RiClKGZCYgK6NxUPeva6mQiIvQGXvq\nTmtg3NoFMha+I30O64+aOXriEYNYNxOGQ+Dr8sMhvYLIpYOQfX4ZUEBkKQKBgQC1\n0b5hRGF9d8WF3BLXAoaEhE30WRj4S1OaCm+3GkI5LX3WmzRkC/wdiHlw/YjNTU55\nZ38odKXuFRCkc+hRtywnA3kCdy1/xDThC7HZlfdIunABRG62XqdMJ0HOp3WfKSIw\ngfqGlN5VSuaXj18nQMLscBoREX9PTX4weX1WSPwlYQKBgH7Ga1O5GEaRPDA8XCfW\nB5w1T9X8F9oSYWd8+VZVuxczCW3No68ENJGd1HJgkIp1bvoPT3XQEeYOb5ePyG3c\nR50Deu95AIsjS5U2cKGC2Aw3YyzGNPEkkGuCJqqsnT9nLc3+2lAntbXEkqdbOTfx\nWxxmiOcxGxbRFFuPOxxoXaEa\n-----END PRIVATE KEY-----\n", +}