diff --git a/internal/cmd/miniooni/libminiooni.go b/internal/cmd/miniooni/libminiooni.go index 9106883..e78f132 100644 --- a/internal/cmd/miniooni/libminiooni.go +++ b/internal/cmd/miniooni/libminiooni.go @@ -19,7 +19,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/engine" "github.com/ooni/probe-cli/v3/internal/engine/legacy/assetsdir" "github.com/ooni/probe-cli/v3/internal/engine/model" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" "github.com/ooni/probe-cli/v3/internal/humanize" "github.com/ooni/probe-cli/v3/internal/kvstore" "github.com/ooni/probe-cli/v3/internal/version" @@ -41,7 +40,6 @@ type Options struct { Proxy string Random bool ReportFile string - SelfCensorSpec string TorArgs []string TorBinary string Tunnel string @@ -108,10 +106,6 @@ func init() { &globalOptions.ReportFile, "reportfile", 'o', "Set the report file path", "PATH", ) - getopt.FlagLong( - &globalOptions.SelfCensorSpec, "self-censor-spec", 0, - "Enable and configure self censorship", "JSON", - ) getopt.FlagLong( &globalOptions.TorArgs, "tor-args", 0, "Extra args for tor binary (may be specified multiple times)", @@ -305,9 +299,6 @@ func MainWithConfiguration(experimentName string, currentOptions Options) { extraOptions := mustMakeMap(currentOptions.ExtraOptions) annotations := mustMakeMap(currentOptions.Annotations) - err := selfcensor.MaybeEnable(currentOptions.SelfCensorSpec) - fatalOnError(err, "cannot parse --self-censor-spec argument") - logger := &log.Logger{Level: log.InfoLevel, Handler: &logHandler{Writer: os.Stderr}} if currentOptions.Verbose { logger.Level = log.DebugLevel @@ -323,7 +314,7 @@ func MainWithConfiguration(experimentName string, currentOptions Options) { homeDir := gethomedir(currentOptions.HomeDir) fatalIfFalse(homeDir != "", "home directory is empty") miniooniDir := path.Join(homeDir, ".miniooni") - err = os.MkdirAll(miniooniDir, 0700) + err := os.MkdirAll(miniooniDir, 0700) fatalOnError(err, "cannot create $HOME/.miniooni directory") // We cleanup the assets files used by versions of ooniprobe diff --git a/internal/engine/experiment/hhfm/hhfm.go b/internal/engine/experiment/hhfm/hhfm.go index 10eef48..1d07dd0 100644 --- a/internal/engine/experiment/hhfm/hhfm.go +++ b/internal/engine/experiment/hhfm/hhfm.go @@ -20,8 +20,8 @@ import ( "github.com/ooni/probe-cli/v3/internal/engine/model" "github.com/ooni/probe-cli/v3/internal/engine/netx" "github.com/ooni/probe-cli/v3/internal/engine/netx/archival" + "github.com/ooni/probe-cli/v3/internal/engine/netx/dialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/errorx" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" "github.com/ooni/probe-cli/v3/internal/randx" ) @@ -319,11 +319,11 @@ type Dialer struct { // DialContext dials a specific connection and arranges such that // headers in the outgoing request are transformed. func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - dialer := d.Dialer - if dialer == nil { - dialer = selfcensor.DefaultDialer + child := d.Dialer + if child == nil { + child = dialer.Default } - conn, err := dialer.DialContext(ctx, network, address) + conn, err := child.DialContext(ctx, network, address) if err != nil { return nil, err } diff --git a/internal/engine/experiment/ndt7/dial.go b/internal/engine/experiment/ndt7/dial.go index cff75f2..18d22d5 100644 --- a/internal/engine/experiment/ndt7/dial.go +++ b/internal/engine/experiment/ndt7/dial.go @@ -10,7 +10,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/engine/model" "github.com/ooni/probe-cli/v3/internal/engine/netx/dialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/resolver" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" ) type dialManager struct { @@ -36,7 +35,7 @@ func newDialManager(ndt7URL string, logger model.Logger, userAgent string) dialM func (mgr dialManager) dialWithTestName(ctx context.Context, testName string) (*websocket.Conn, error) { var reso resolver.Resolver = resolver.SystemResolver{} reso = resolver.LoggingResolver{Resolver: reso, Logger: mgr.logger} - var dlr dialer.Dialer = selfcensor.SystemDialer{} + var dlr dialer.Dialer = dialer.Default dlr = dialer.TimeoutDialer{Dialer: dlr} dlr = dialer.ErrorWrapperDialer{Dialer: dlr} dlr = dialer.LoggingDialer{Dialer: dlr, Logger: mgr.logger} diff --git a/internal/engine/netx/dialer/system.go b/internal/engine/netx/dialer/system.go new file mode 100644 index 0000000..cb8f0ad --- /dev/null +++ b/internal/engine/netx/dialer/system.go @@ -0,0 +1,24 @@ +package dialer + +import ( + "context" + "net" + "time" +) + +// defaultNetDialer is the net.Dialer we use by default. +var defaultNetDialer = &net.Dialer{ + Timeout: 15 * time.Second, + KeepAlive: 15 * time.Second, +} + +// SystemDialer is the system dialer. +type SystemDialer struct{} + +// DialContext implements Dialer.DialContext +func (d SystemDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + return defaultNetDialer.DialContext(ctx, network, address) +} + +// Default is the dialer we use by default. +var Default = SystemDialer{} diff --git a/internal/engine/netx/dialer/system_test.go b/internal/engine/netx/dialer/system_test.go new file mode 100644 index 0000000..25f6e7e --- /dev/null +++ b/internal/engine/netx/dialer/system_test.go @@ -0,0 +1,20 @@ +package dialer + +import ( + "strings" + "testing" + + "github.com/ooni/psiphon/oopsi/golang.org/x/net/context" +) + +func TestSystemDialer(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() // fail immediately + conn, err := Default.DialContext(ctx, "tcp", "8.8.8.8:853") + if err == nil || !strings.HasSuffix(err.Error(), "operation was canceled") { + t.Fatal("not the error we expected", err) + } + if conn != nil { + t.Fatal("expected nil conn here") + } +} diff --git a/internal/engine/netx/httptransport/http3transport_test.go b/internal/engine/netx/httptransport/http3transport_test.go index 8e53aa9..7042385 100644 --- a/internal/engine/netx/httptransport/http3transport_test.go +++ b/internal/engine/netx/httptransport/http3transport_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/lucas-clemente/quic-go" + "github.com/ooni/probe-cli/v3/internal/engine/netx/dialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/httptransport" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" ) type MockQUICDialer struct{} @@ -42,7 +42,7 @@ func TestHTTP3TransportSNI(t *testing.T) { namech := make(chan string, 1) sni := "sni.org" txp := httptransport.NewHTTP3Transport(httptransport.Config{ - Dialer: selfcensor.SystemDialer{}, QUICDialer: MockSNIQUICDialer{namech: namech}, TLSConfig: &tls.Config{ServerName: sni}}) + Dialer: dialer.Default, QUICDialer: MockSNIQUICDialer{namech: namech}, TLSConfig: &tls.Config{ServerName: sni}}) req, err := http.NewRequest("GET", "https://www.google.com", nil) if err != nil { t.Fatal(err) @@ -67,7 +67,7 @@ func TestHTTP3TransportSNINoVerify(t *testing.T) { namech := make(chan string, 1) sni := "sni.org" txp := httptransport.NewHTTP3Transport(httptransport.Config{ - Dialer: selfcensor.SystemDialer{}, QUICDialer: MockSNIQUICDialer{namech: namech}, TLSConfig: &tls.Config{ServerName: sni, InsecureSkipVerify: true}}) + Dialer: dialer.Default, QUICDialer: MockSNIQUICDialer{namech: namech}, TLSConfig: &tls.Config{ServerName: sni, InsecureSkipVerify: true}}) req, err := http.NewRequest("GET", "https://www.google.com", nil) if err != nil { t.Fatal(err) @@ -89,7 +89,7 @@ func TestHTTP3TransportCABundle(t *testing.T) { certch := make(chan *x509.CertPool, 1) certpool := x509.NewCertPool() txp := httptransport.NewHTTP3Transport(httptransport.Config{ - Dialer: selfcensor.SystemDialer{}, QUICDialer: MockCertQUICDialer{certch: certch}, TLSConfig: &tls.Config{RootCAs: certpool}}) + Dialer: dialer.Default, QUICDialer: MockCertQUICDialer{certch: certch}, TLSConfig: &tls.Config{RootCAs: certpool}}) req, err := http.NewRequest("GET", "https://www.google.com", nil) if err != nil { t.Fatal(err) @@ -114,7 +114,7 @@ func TestHTTP3TransportCABundle(t *testing.T) { func TestUnitHTTP3TransportSuccess(t *testing.T) { txp := httptransport.NewHTTP3Transport(httptransport.Config{ - Dialer: selfcensor.SystemDialer{}, QUICDialer: MockQUICDialer{}}) + Dialer: dialer.Default, QUICDialer: MockQUICDialer{}}) req, err := http.NewRequest("GET", "https://www.google.com", nil) if err != nil { @@ -134,7 +134,7 @@ func TestUnitHTTP3TransportSuccess(t *testing.T) { func TestUnitHTTP3TransportFailure(t *testing.T) { txp := httptransport.NewHTTP3Transport(httptransport.Config{ - Dialer: selfcensor.SystemDialer{}, QUICDialer: MockQUICDialer{}}) + Dialer: dialer.Default, QUICDialer: MockQUICDialer{}}) ctx, cancel := context.WithCancel(context.Background()) cancel() // so that the request immediately fails diff --git a/internal/engine/netx/netx.go b/internal/engine/netx/netx.go index 9c6b740..2d38ce0 100644 --- a/internal/engine/netx/netx.go +++ b/internal/engine/netx/netx.go @@ -36,7 +36,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/engine/netx/httptransport" "github.com/ooni/probe-cli/v3/internal/engine/netx/quicdialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/resolver" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" "github.com/ooni/probe-cli/v3/internal/engine/netx/tlsdialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/tlsx" "github.com/ooni/probe-cli/v3/internal/engine/netx/trace" @@ -151,7 +150,7 @@ func NewDialer(config Config) Dialer { if config.FullResolver == nil { config.FullResolver = NewResolver(config) } - var d Dialer = selfcensor.SystemDialer{} + var d Dialer = dialer.Default d = dialer.TimeoutDialer{Dialer: d} d = dialer.ErrorWrapperDialer{Dialer: d} if config.Logger != nil { diff --git a/internal/engine/netx/netx_test.go b/internal/engine/netx/netx_test.go index 0a0be5d..82105d9 100644 --- a/internal/engine/netx/netx_test.go +++ b/internal/engine/netx/netx_test.go @@ -13,7 +13,6 @@ import ( "github.com/ooni/probe-cli/v3/internal/engine/netx/dialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/httptransport" "github.com/ooni/probe-cli/v3/internal/engine/netx/resolver" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" "github.com/ooni/probe-cli/v3/internal/engine/netx/tlsdialer" "github.com/ooni/probe-cli/v3/internal/engine/netx/trace" ) @@ -245,7 +244,7 @@ func TestNewDialerVanilla(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } @@ -285,7 +284,7 @@ func TestNewDialerWithResolver(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } @@ -334,7 +333,7 @@ func TestNewDialerWithLogger(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } @@ -384,7 +383,7 @@ func TestNewDialerWithDialSaver(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } @@ -434,7 +433,7 @@ func TestNewDialerWithReadWriteSaver(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } @@ -480,7 +479,7 @@ func TestNewDialerWithContextByteCounting(t *testing.T) { if !ok { t.Fatal("not the dialer we expected") } - if _, ok := td.Dialer.(selfcensor.SystemDialer); !ok { + if _, ok := td.Dialer.(dialer.SystemDialer); !ok { t.Fatal("not the dialer we expected") } } diff --git a/internal/engine/netx/resolver/system.go b/internal/engine/netx/resolver/system.go index 784bc42..6c0c049 100644 --- a/internal/engine/netx/resolver/system.go +++ b/internal/engine/netx/resolver/system.go @@ -1,10 +1,29 @@ package resolver -import "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" +import ( + "context" + "net" +) -// SystemResolver is the system resolver. It is implemented using -// selfcensor.SystemResolver so that we can perform integration testing -// by forcing the code to return specific responses. -type SystemResolver = selfcensor.SystemResolver +// SystemResolver is the system resolver. +type SystemResolver struct{} + +// LookupHost implements Resolver.LookupHost. +func (r SystemResolver) LookupHost(ctx context.Context, hostname string) ([]string, error) { + return net.DefaultResolver.LookupHost(ctx, hostname) +} + +// Network implements Resolver.Network. +func (r SystemResolver) Network() string { + return "system" +} + +// Address implements Resolver.Address. +func (r SystemResolver) Address() string { + return "" +} + +// Default is the resolver we use by default. +var Default = SystemResolver{} var _ Resolver = SystemResolver{} diff --git a/internal/engine/netx/selfcensor/selfcensor.go b/internal/engine/netx/selfcensor/selfcensor.go deleted file mode 100644 index bbe0147..0000000 --- a/internal/engine/netx/selfcensor/selfcensor.go +++ /dev/null @@ -1,230 +0,0 @@ -// Package selfcensor contains code that triggers censorship. We use -// this functionality to implement integration tests. -// -// The self censoring functionality is disabled by default. To enable it, -// call Enable with a JSON-serialized Spec structure as its argument. -// -// The following example causes NXDOMAIN to be returned for `dns.google`: -// -// selfcensor.Enable(`{"PoisonSystemDNS":{"dns.google":["NXDOMAIN"]}}`) -// -// The following example blocks connecting to `8.8.8.8:443`: -// -// selfcensor.Enable(`{"BlockedEndpoints":{"8.8.8.8:443":"REJECT"}}`) -// -// The following example blocks packets containing dns.google: -// -// selfcensor.Enable(`{"BlockedFingerprints":{"dns.google":"RST"}}`) -// -// The documentation of the Spec structure contains further information on -// how to populate the JSON. Miniooni uses the `--self-censor-spec flag` to -// which you are supposed to pass a serialized JSON. -package selfcensor - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "log" - "net" - "sync" - "time" - - "github.com/ooni/probe-cli/v3/internal/atomicx" -) - -// Spec indicates what self censorship techniques to implement. -type Spec struct { - // PoisonSystemDNS allows you to change the behaviour of the system - // DNS regarding specific domains. They keys are the domains and the - // values are the IP addresses to return. If you set the values for - // a domain to `[]string{"NXDOMAIN"}`, the system resolver will return - // an NXDOMAIN response. If you set the values for a domain to - // `[]string{"TIMEOUT"}` the system resolver will return "i/o timeout". - PoisonSystemDNS map[string][]string - - // BlockedEndpoints allows you to block specific IP endpoints. The key is - // `IP:port` to block. The format is the same of net.JoinHostPort. If - // the value is "REJECT", then the connection attempt will fail with - // ECONNREFUSED. If the value is "TIMEOUT", then the connector will return - // claiming "i/o timeout". If the value is anything else, we will - // perform a "REJECT". - BlockedEndpoints map[string]string - - // BlockedFingerprints allows you to block packets whose body contains - // specific fingerprints. Of course, the key is the fingerprint. If - // the value is "RST", then the connection will be reset. If the value - // is "TIMEOUT", then the code will return claiming "i/o timeout". If - // the value is anything else, we will perform a "RST". - BlockedFingerprints map[string]string -} - -var ( - attempts *atomicx.Int64 = &atomicx.Int64{} - enabled *atomicx.Int64 = &atomicx.Int64{} - mu sync.Mutex - spec *Spec -) - -// Enabled returns whether self censorship is enabled -func Enabled() bool { - return enabled.Load() != 0 -} - -// Attempts returns the number of self censorship attempts so far. A self -// censorship attempt is defined as the code entering into the branch that -// _may_ perform self censorship. We expected to see this counter being -// equal to zero when Enabled() returns false. -func Attempts() int64 { - return attempts.Load() -} - -// Enable turns on the self censorship engine. This function returns -// an error if we cannot parse a Spec from the serialized JSON inside -// data. Each time you call Enable you overwrite the previous spec. -func Enable(data string) error { - mu.Lock() - defer mu.Unlock() - s := new(Spec) - if err := json.Unmarshal([]byte(data), s); err != nil { - return err - } - spec = s - enabled.Add(1) - log.Printf("selfcensor: spec %+v", *spec) - return nil -} - -// MaybeEnable is like enable except that it does nothing in case -// the string provided as argument is an empty string. -func MaybeEnable(data string) (err error) { - if data != "" { - err = Enable(data) - } - return -} - -// SystemResolver is a self-censoring system resolver. This resolver does -// not censor anything unless you call selfcensor.Enable(). -type SystemResolver struct{} - -// errTimeout indicates that a timeout error has occurred. -var errTimeout = errors.New("i/o timeout") - -// LookupHost implements Resolver.LookupHost -func (r SystemResolver) LookupHost(ctx context.Context, hostname string) ([]string, error) { - if enabled.Load() != 0 { // jumps not taken by default - mu.Lock() - defer mu.Unlock() - attempts.Add(1) - if spec.PoisonSystemDNS != nil { - values := spec.PoisonSystemDNS[hostname] - if len(values) == 1 && values[0] == "NXDOMAIN" { - return nil, errors.New("no such host") - } - if len(values) == 1 && values[0] == "TIMEOUT" { - return nil, errTimeout - } - if len(values) > 0 { - return values, nil - } - } - // FALLTHROUGH - } - return net.DefaultResolver.LookupHost(ctx, hostname) -} - -// Network implements Resolver.Network -func (r SystemResolver) Network() string { - return "system" -} - -// Address implements Resolver.Address -func (r SystemResolver) Address() string { - return "" -} - -// SystemDialer is a self-censoring system dialer. This dialer does -// not censor anything unless you call selfcensor.Enable(). -type SystemDialer struct{} - -// defaultNetDialer is the dialer we use by default. -var defaultNetDialer = &net.Dialer{ - Timeout: 15 * time.Second, - KeepAlive: 15 * time.Second, -} - -// DefaultDialer is the dialer you should use in code that wants -// to take advantage of selfcensor capabilities. -var DefaultDialer = SystemDialer{} - -// DialContext implements Dialer.DialContext -func (d SystemDialer) DialContext( - ctx context.Context, network, address string) (net.Conn, error) { - if enabled.Load() != 0 { // jumps not taken by default - mu.Lock() - defer mu.Unlock() - attempts.Add(1) - if spec.BlockedEndpoints != nil { - action, ok := spec.BlockedEndpoints[address] - if ok && action == "TIMEOUT" { - return nil, errTimeout - } - if ok { - switch network { - case "tcp", "tcp4", "tcp6": - return nil, errors.New("connection refused") - default: - // not applicable - } - } - } - if spec.BlockedFingerprints != nil { - conn, err := defaultNetDialer.DialContext(ctx, network, address) - if err != nil { - return nil, err - } - return connWrapper{Conn: conn, closed: make(chan interface{}, 128), - fingerprints: spec.BlockedFingerprints}, nil - } - // FALLTHROUGH - } - return defaultNetDialer.DialContext(ctx, network, address) -} - -type connWrapper struct { - net.Conn - closed chan interface{} - fingerprints map[string]string -} - -func (c connWrapper) Write(p []byte) (int, error) { - // TODO(bassosimone): implement reassembly to workaround the - // splitting of the ClientHello message. - if _, err := c.match(p, len(p)); err != nil { - return 0, err - } - return c.Conn.Write(p) -} - -func (c connWrapper) match(p []byte, n int) (int, error) { - p = p[:n] // trim - for key, value := range c.fingerprints { - if bytes.Index(p, []byte(key)) != -1 { - if value == "TIMEOUT" { - return 0, errTimeout - } - return 0, errors.New("connection reset by peer") - } - } - return n, nil -} - -func (c connWrapper) Close() error { - // Implementation note: we will block here if we attempt to close - // too many times and noone's reading. Because we have a large buffer, - // and because this is integration testing code, that's fine. - c.closed <- true - return c.Conn.Close() -} diff --git a/internal/engine/netx/selfcensor/selfcensor_test.go b/internal/engine/netx/selfcensor/selfcensor_test.go deleted file mode 100644 index 9938fda..0000000 --- a/internal/engine/netx/selfcensor/selfcensor_test.go +++ /dev/null @@ -1,271 +0,0 @@ -package selfcensor_test - -import ( - "context" - "strings" - "testing" - "time" - - "github.com/ooni/probe-cli/v3/internal/engine/netx" - "github.com/ooni/probe-cli/v3/internal/engine/netx/selfcensor" -) - -// TestDisabled MUST be the first test in this file. -func TestDisabled(t *testing.T) { - if selfcensor.Enabled() != false { - t.Fatal("self censorship should be disabled by default") - } - if selfcensor.Attempts() != 0 { - t.Fatal("we expect no self censorship attempts at the beginning") - } - t.Run("the system resolver does not trigger selfcensor events", func(t *testing.T) { - addrs, err := selfcensor.SystemResolver{}.LookupHost( - context.Background(), "dns.google", - ) - if err != nil { - t.Fatal(err) - } - if addrs == nil { - t.Fatal("expected non-nil addrs here") - } - if selfcensor.Attempts() != 0 { - t.Fatal("we expect no self censorship attempts by default") - } - }) - t.Run("the system dialer does not trigger selfcensor events", func(t *testing.T) { - conn, err := selfcensor.SystemDialer{}.DialContext( - context.Background(), "tcp", "8.8.8.8:443", - ) - if err != nil { - t.Fatal(err) - } - if conn == nil { - t.Fatal("expected non-nil conn here") - } - conn.Close() - if selfcensor.Attempts() != 0 { - t.Fatal("we expect no self censorship attempts by default") - } - }) -} - -// TestDisabled MUST be the second test in this file. -func TestEnableInvalidJSON(t *testing.T) { - if selfcensor.Enabled() != false { - t.Fatal("we need to start with self censorship not enabled") - } - err := selfcensor.Enable("{") - if err == nil || !strings.HasSuffix(err.Error(), "unexpected end of JSON input") { - t.Fatal("not the error we expectd") - } - if selfcensor.Enabled() != false { - t.Fatal("we expected self censorship to still be not enabled") - } -} - -// TestMaybeEnableWorksAsIntended MUST be the second test in this file. -func TestMaybeEnableWorksAsIntended(t *testing.T) { - if selfcensor.Enabled() != false { - t.Fatal("we need to start with self censorship not enabled") - } - err := selfcensor.MaybeEnable("") - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != false { - t.Fatal("we expected self censorship to still be not enabled") - } -} - -func TestResolveCauseNXDOMAIN(t *testing.T) { - err := selfcensor.MaybeEnable(`{"PoisonSystemDNS":{"dns.google":["NXDOMAIN"]}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemResolver{}.LookupHost( - context.Background(), "dns.google", - ) - if err == nil || !strings.HasSuffix(err.Error(), "no such host") { - t.Fatal("not the error we expected") - } - if addrs != nil { - t.Fatal("expected nil addrs here") - } -} - -func TestResolveCauseTimeout(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - err := selfcensor.MaybeEnable(`{"PoisonSystemDNS":{"dns.google":["TIMEOUT"]}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemResolver{}.LookupHost(ctx, "dns.google") - if err == nil || err.Error() != "i/o timeout" { - t.Fatal("not the error we expected") - } - if addrs != nil { - t.Fatal("expected nil addrs here") - } -} - -func TestResolveCauseBogon(t *testing.T) { - err := selfcensor.MaybeEnable(`{"PoisonSystemDNS":{"dns.google":["10.0.0.7"]}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemResolver{}.LookupHost( - context.Background(), "dns.google") - if err != nil { - t.Fatal(err) - } - if len(addrs) != 1 || addrs[0] != "10.0.0.7" { - t.Fatal("not the addrs we expected") - } -} - -func TestResolveCheckNetworkAndAddress(t *testing.T) { - err := selfcensor.MaybeEnable(`{"PoisonSystemDNS":{"dns.google":["10.0.0.7"]}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - reso := selfcensor.SystemResolver{} - if reso.Network() != "system" { - t.Fatal("invalid Network") - } - if reso.Address() != "" { - t.Fatal("invalid Address") - } -} - -func TestDialHandlesErrorsWithBlockedFingerprints(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - cancel() // so we should fail immediately! - err := selfcensor.MaybeEnable(`{"BlockedFingerprints":{"dns.google":"TIMEOUT"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemDialer{}.DialContext(ctx, "tcp", "8.8.8.8:443") - if err == nil || !strings.HasSuffix(err.Error(), "operation was canceled") { - t.Fatal("not the error we expected") - } - if addrs != nil { - t.Fatal("expected nil addrs here") - } -} - -func TestDialCauseTimeout(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - err := selfcensor.MaybeEnable(`{"BlockedEndpoints":{"8.8.8.8:443":"TIMEOUT"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemDialer{}.DialContext(ctx, "tcp", "8.8.8.8:443") - if err == nil || err.Error() != "i/o timeout" { - t.Fatal("not the error we expected") - } - if addrs != nil { - t.Fatal("expected nil addrs here") - } -} - -func TestDialCauseConnectionRefused(t *testing.T) { - err := selfcensor.MaybeEnable(`{"BlockedEndpoints":{"8.8.8.8:443":"REJECT"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - addrs, err := selfcensor.SystemDialer{}.DialContext( - context.Background(), "tcp", "8.8.8.8:443") - if err == nil || !strings.HasSuffix(err.Error(), "connection refused") { - t.Fatal("not the error we expected") - } - if addrs != nil { - t.Fatal("expected nil addrs here") - } -} - -func TestBlockedFingerprintsTimeout(t *testing.T) { - err := selfcensor.MaybeEnable(`{"BlockedFingerprints":{"dns.google":"TIMEOUT"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - tlsDialer := netx.NewTLSDialer(netx.Config{ - Dialer: selfcensor.SystemDialer{}, - }) - conn, err := tlsDialer.DialTLSContext( - context.Background(), "tcp", "dns.google:443") - if err == nil || err.Error() != "generic_timeout_error" { - t.Fatal("not the error expected") - } - if conn != nil { - t.Fatal("expected nil conn here") - } -} - -func TestBlockedFingerprintsNoMatch(t *testing.T) { - err := selfcensor.MaybeEnable(`{"BlockedFingerprints":{"ooni.io":"TIMEOUT"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - tlsDialer := netx.NewTLSDialer(netx.Config{ - Dialer: selfcensor.SystemDialer{}, - }) - conn, err := tlsDialer.DialTLSContext( - context.Background(), "tcp", "dns.google:443") - if err != nil { - t.Fatal(err) - } - if conn == nil { - t.Fatal("expected non-nil conn here") - } - conn.Close() -} - -func TestBlockedFingerprintsConnectionReset(t *testing.T) { - err := selfcensor.MaybeEnable(`{"BlockedFingerprints":{"dns.google":"RST"}}`) - if err != nil { - t.Fatal(err) - } - if selfcensor.Enabled() != true { - t.Fatal("we expected self censorship to be enabled now") - } - tlsDialer := netx.NewTLSDialer(netx.Config{ - Dialer: selfcensor.SystemDialer{}, - }) - conn, err := tlsDialer.DialTLSContext( - context.Background(), "tcp", "dns.google:443") - if err == nil || err.Error() != "connection_reset" { - t.Fatal("not the error we expected") - } - if conn != nil { - t.Fatal("expected nil conn here") - } -}