feat: port-filtering experiment (#891)
Part of https://github.com/ooni/probe/issues/2005
This commit is contained in:
parent
cb632ea0f3
commit
d6a362d96f
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -14,8 +14,10 @@
|
||||||
/miniooni.exe
|
/miniooni.exe
|
||||||
/oohelper
|
/oohelper
|
||||||
/oohelperd
|
/oohelperd
|
||||||
|
/ooporthelper
|
||||||
/oohelperd.exe
|
/oohelperd.exe
|
||||||
/oohelper.exe
|
/oohelper.exe
|
||||||
|
/ooporthelper.exe
|
||||||
/ooniprobe
|
/ooniprobe
|
||||||
/ooniprobe_checksums.txt
|
/ooniprobe_checksums.txt
|
||||||
/ooniprobe_checksums.txt.asc
|
/ooniprobe_checksums.txt.asc
|
||||||
|
|
4
internal/cmd/ooporthelper/README.md
Normal file
4
internal/cmd/ooporthelper/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# ooporthelper
|
||||||
|
|
||||||
|
This directory contains the source code of the Port-
|
||||||
|
Filtering test helper written in go
|
79
internal/cmd/ooporthelper/main.go
Normal file
79
internal/cmd/ooporthelper/main.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// command ooporthelper implements the Port Filtering test helper
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/engine/experiment/portfiltering"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
srvCtx context.Context
|
||||||
|
srvCancel context.CancelFunc
|
||||||
|
srvWg = new(sync.WaitGroup)
|
||||||
|
srvTestChan = make(chan string, len(TestPorts)) // buffered channel for testing
|
||||||
|
srvTest bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
srvCtx, srvCancel = context.WithCancel(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdown(ctx context.Context, l net.Listener) {
|
||||||
|
<-ctx.Done()
|
||||||
|
l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(DecFox): Add the ability of an echo service to generate some traffic
|
||||||
|
func handleConnetion(ctx context.Context, conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenTCP(ctx context.Context, port string) {
|
||||||
|
defer srvWg.Done()
|
||||||
|
address := net.JoinHostPort("127.0.0.1", port)
|
||||||
|
listener, err := net.Listen("tcp", address)
|
||||||
|
runtimex.PanicOnError(err, "net.Listen failed")
|
||||||
|
go shutdown(ctx, listener)
|
||||||
|
srvTestChan <- port // send to channel to imply server will start listening on port
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("listener unable to accept connections on port%s", port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go handleConnetion(ctx, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logmap := map[bool]log.Level{
|
||||||
|
true: log.DebugLevel,
|
||||||
|
false: log.InfoLevel,
|
||||||
|
}
|
||||||
|
debug := flag.Bool("debug", false, "Toggle debug mode")
|
||||||
|
flag.Parse()
|
||||||
|
log.SetLevel(logmap[*debug])
|
||||||
|
defer srvCancel()
|
||||||
|
ports := portfiltering.Ports
|
||||||
|
if srvTest {
|
||||||
|
ports = TestPorts
|
||||||
|
}
|
||||||
|
for _, port := range ports {
|
||||||
|
srvWg.Add(1)
|
||||||
|
ctx, cancel := context.WithCancel(srvCtx)
|
||||||
|
defer cancel()
|
||||||
|
go listenTCP(ctx, port)
|
||||||
|
}
|
||||||
|
<-srvCtx.Done()
|
||||||
|
srvWg.Wait() // wait for listeners on all ports to close
|
||||||
|
}
|
31
internal/cmd/ooporthelper/main_test.go
Normal file
31
internal/cmd/ooporthelper/main_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMainWorkingAsIntended(t *testing.T) {
|
||||||
|
srvTest = true // toggle to imply that we are running in test mode
|
||||||
|
go main()
|
||||||
|
dialer := netxlite.NewDialerWithoutResolver(model.DiscardLogger)
|
||||||
|
for _, port := range TestPorts {
|
||||||
|
<-srvTestChan
|
||||||
|
addr := net.JoinHostPort("127.0.0.1", port)
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
t.Fatal("expected non-nil conn")
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
srvCancel() // shutdown server
|
||||||
|
srvWg.Wait() // wait for listeners on all ports to close
|
||||||
|
}
|
12
internal/cmd/ooporthelper/ports.go
Normal file
12
internal/cmd/ooporthelper/ports.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
//
|
||||||
|
// List of ports we want to use for running integration tests
|
||||||
|
//
|
||||||
|
|
||||||
|
// Ports for testing the testhelper
|
||||||
|
// Note: we must only use unprivileged ports here to ensure tests run successfully
|
||||||
|
var TestPorts = []string{
|
||||||
|
"8080", // tcp
|
||||||
|
"5050", // tcp
|
||||||
|
}
|
20
internal/engine/experiment/portfiltering/config.go
Normal file
20
internal/engine/experiment/portfiltering/config.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
//
|
||||||
|
// Config for the port-filtering experiment
|
||||||
|
//
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Config contains the experiment configuration.
|
||||||
|
type Config struct {
|
||||||
|
// Delay is the delay between each repetition (in milliseconds).
|
||||||
|
Delay int64 `ooni:"number of milliseconds to wait before testing each port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) delay() time.Duration {
|
||||||
|
if c.Delay > 0 {
|
||||||
|
return time.Duration(c.Delay) * time.Millisecond
|
||||||
|
}
|
||||||
|
return 100 * time.Millisecond
|
||||||
|
}
|
13
internal/engine/experiment/portfiltering/config_test.go
Normal file
13
internal/engine/experiment/portfiltering/config_test.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig_delay(t *testing.T) {
|
||||||
|
c := Config{}
|
||||||
|
if c.delay() != 100*time.Millisecond {
|
||||||
|
t.Fatal("invalid default delay")
|
||||||
|
}
|
||||||
|
}
|
4
internal/engine/experiment/portfiltering/doc.go
Normal file
4
internal/engine/experiment/portfiltering/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Package portfiltering implements the portfiltering experiment
|
||||||
|
//
|
||||||
|
// Spec: https://github.com/ooni/spec/blob/master/nettests/ts-038-port-filtering.md.
|
||||||
|
package portfiltering
|
67
internal/engine/experiment/portfiltering/measurer.go
Normal file
67
internal/engine/experiment/portfiltering/measurer.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
//
|
||||||
|
// Measurer for the port-filtering experiment
|
||||||
|
//
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testName = "portfiltering"
|
||||||
|
testVersion = "0.1.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Measurer performs the measurement.
|
||||||
|
type Measurer struct {
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExperimentName implements ExperimentMeasurer.ExperiExperimentName.
|
||||||
|
func (m *Measurer) ExperimentName() string {
|
||||||
|
return testName
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExperimentVersion implements ExperimentMeasurer.ExperimentVersion.
|
||||||
|
func (m *Measurer) ExperimentVersion() string {
|
||||||
|
return testVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// errInvalidTestHelper indicates that the given test helper is not an URL
|
||||||
|
errInvalidTestHelper = errors.New("testhelper is not an URL")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run implements ExperimentMeasurer.Run.
|
||||||
|
func (m *Measurer) Run(
|
||||||
|
ctx context.Context,
|
||||||
|
sess model.ExperimentSession,
|
||||||
|
measurement *model.Measurement,
|
||||||
|
callbacks model.ExperimentCallbacks,
|
||||||
|
) error {
|
||||||
|
// TODO(DecFox): Replace the localhost deployment with an OONI testhelper
|
||||||
|
// Ensure that we only do this once we have a deployed testhelper
|
||||||
|
testhelper := "http://127.0.0.1"
|
||||||
|
parsed, err := url.Parse(testhelper)
|
||||||
|
if err != nil {
|
||||||
|
return errInvalidTestHelper
|
||||||
|
}
|
||||||
|
tk := new(TestKeys)
|
||||||
|
measurement.TestKeys = tk
|
||||||
|
out := make(chan *model.ArchivalTCPConnectResult)
|
||||||
|
go m.tcpConnectLoop(ctx, measurement.MeasurementStartTimeSaved, sess.Logger(), parsed.Host, out)
|
||||||
|
for len(tk.TCPConnect) < len(Ports) {
|
||||||
|
tk.TCPConnect = append(tk.TCPConnect, <-out)
|
||||||
|
}
|
||||||
|
return nil // return nil so we always submit the measurement
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExperimentMeasurer creates a new ExperimentMeasurer.
|
||||||
|
func NewExperimentMeasurer(config Config) model.ExperimentMeasurer {
|
||||||
|
return &Measurer{config: config}
|
||||||
|
}
|
48
internal/engine/experiment/portfiltering/measurer_test.go
Normal file
48
internal/engine/experiment/portfiltering/measurer_test.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMeasurerExperimentNameVersion(t *testing.T) {
|
||||||
|
measurer := NewExperimentMeasurer(Config{})
|
||||||
|
if measurer.ExperimentName() != "portfiltering" {
|
||||||
|
t.Fatal("unexpected ExperimentName")
|
||||||
|
}
|
||||||
|
if measurer.ExperimentVersion() != "0.1.0" {
|
||||||
|
t.Fatal("unexpected ExperimentVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(DecFox): Skip this test with -short in a future iteration.
|
||||||
|
func TestMeasurer_run(t *testing.T) {
|
||||||
|
m := NewExperimentMeasurer(Config{})
|
||||||
|
meas := &model.Measurement{}
|
||||||
|
sess := &mocks.Session{
|
||||||
|
MockLogger: func() model.Logger {
|
||||||
|
return model.DiscardLogger
|
||||||
|
},
|
||||||
|
}
|
||||||
|
callbacks := model.NewPrinterCallbacks(model.DiscardLogger)
|
||||||
|
ctx := context.Background()
|
||||||
|
err := m.Run(ctx, sess, meas, callbacks)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tk := meas.TestKeys.(*TestKeys)
|
||||||
|
if len(tk.TCPConnect) != len(Ports) {
|
||||||
|
t.Fatal("unexpected number of ports")
|
||||||
|
}
|
||||||
|
ask, err := m.GetSummaryKeys(meas)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("cannot obtain summary")
|
||||||
|
}
|
||||||
|
summary := ask.(SummaryKeys)
|
||||||
|
if summary.IsAnomaly {
|
||||||
|
t.Fatal("expected no anomaly")
|
||||||
|
}
|
||||||
|
}
|
73
internal/engine/experiment/portfiltering/ports.go
Normal file
73
internal/engine/experiment/portfiltering/ports.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
//
|
||||||
|
// List of ports we want to measure
|
||||||
|
//
|
||||||
|
|
||||||
|
// List generated from nmap-services: https://github.com/nmap/nmap/blob/master/nmap-services
|
||||||
|
// Note: Using privileged ports like :80 requires elevated permissions
|
||||||
|
var Ports = []string{
|
||||||
|
"80", // tcp - World Wide Web HTTP
|
||||||
|
"631", // udp - Internet Printing Protocol
|
||||||
|
"161", // udp - Simple Net Mgmt Proto
|
||||||
|
"137", // udp - NETBIOS Name Service
|
||||||
|
"123", // udp - Network Time Protocol
|
||||||
|
"138", // udp - NETBIOS Datagram Service
|
||||||
|
"1434", // udp - Microsoft-SQL-Monitor
|
||||||
|
"135", // udp, tcp - epmap | Microsoft RPC services | DCE endpoint resolution
|
||||||
|
"67", // udp - DHCP/Bootstrap Protocol Server
|
||||||
|
"23", // tcp
|
||||||
|
"53", // udp, tcp - Domain Name Server
|
||||||
|
"443", // tcp - secure http (SSL)
|
||||||
|
"21", // tcp - File Transfer [Control]
|
||||||
|
"22", // tcp - Secure Shell Login
|
||||||
|
"500", // udp
|
||||||
|
"68", // udp - DHCP/Bootstrap Protocol Client
|
||||||
|
"520", // udp - router routed -- RIP
|
||||||
|
"1900", // udp - Universal PnP
|
||||||
|
"25", // tcp - Simple Mail Transfer
|
||||||
|
"4500", // udp - IKE Nat Traversal negotiation (RFC3947)
|
||||||
|
"514", // udp - BSD syslogd(8)
|
||||||
|
"49152", // udp
|
||||||
|
"162", // udp - snmp-trap
|
||||||
|
"69", // udp - Trivial File Transfer
|
||||||
|
"5353", // udp - Mac OS X Bonjour/Zeroconf port
|
||||||
|
"49154", // udp
|
||||||
|
"3389", // tcp - Microsoft Remote Display Protocol (aka ms-term-serv, microsoft-rdp) | MS WBT Server
|
||||||
|
"110", // tcp - PostOffice V.3 | Post Office Protocol - Version 3
|
||||||
|
"1701", // udp
|
||||||
|
"998", // udp
|
||||||
|
"996", // udp
|
||||||
|
"997", // udp
|
||||||
|
"999", // udp - Applix ac
|
||||||
|
"3283", // udp - Apple Remote Desktop Net Assistant reporting feature
|
||||||
|
"49153", // udp
|
||||||
|
"445", // tcp - SMB directly over IP
|
||||||
|
"1812", // udp - RADIUS authentication protocol (RFC 2138)
|
||||||
|
"136", // udp - PROFILE Naming System
|
||||||
|
"139", // tcp, udp - NETBIOS Session Service
|
||||||
|
"143", // tcp - Interim Mail Access Protocol v2 | Internet Message Access Protocol
|
||||||
|
"2222", // udp - Microsoft Office OS X antipiracy network monitor
|
||||||
|
"3306", // tcp
|
||||||
|
"2049", // udp - networked file system
|
||||||
|
"32768", // udp - OpenMosix Autodiscovery Daemon
|
||||||
|
"5060", // udp - Session Initiation Protocol (SIP)
|
||||||
|
"8080", // tcp - http-alt | Common HTTP proxy/second web server port | HTTP Alternate (see port 80)
|
||||||
|
"1433", // udp - Microsoft-SQL-Server
|
||||||
|
"3456", // udp - also VAT default data
|
||||||
|
"1723", // tcp - Point-to-point tunnelling protocol
|
||||||
|
"111", // tcp, udp - sunrpc | portmapper, rpcbind | SUN Remote Procedure Call
|
||||||
|
"995", // tcp - POP3 protocol over TLS/SSL | pop3 protocol over TLS/SSL (was spop3) | POP3 over TLS protocol
|
||||||
|
"993", // tcp - imap4 protocol over TLS/SSL | IMAP over TLS protocol
|
||||||
|
"20031", // udp - BakBone NetVault primary communications port
|
||||||
|
"1026", // udp - Commonly used to send MS Messenger spam
|
||||||
|
"7", // udp
|
||||||
|
"5900", // tcp - rfb | Virtual Network Computer display 0 | Remote Framebuffer
|
||||||
|
"1646", // udp - radius accounting
|
||||||
|
"1645", // udp - radius authentication
|
||||||
|
"593", // udp # HTTP RPC Ep Map
|
||||||
|
"1025", // tcp, udp - blackjack | IIS, NFS, or listener RFS remote_file_sharing | network blackjack
|
||||||
|
"518", // udp - (talkd)
|
||||||
|
"2048", // udp
|
||||||
|
"626", // udp - Mac OS X Server serial number (licensing) daemon
|
||||||
|
}
|
20
internal/engine/experiment/portfiltering/summary.go
Normal file
20
internal/engine/experiment/portfiltering/summary.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
//
|
||||||
|
// Summary
|
||||||
|
//
|
||||||
|
|
||||||
|
import "github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
|
||||||
|
// SummaryKeys contains summary keys for this experiment.
|
||||||
|
//
|
||||||
|
// Note that this structure is part of the ABI contract with ooniprobe
|
||||||
|
// therefore we should be careful when changing it.
|
||||||
|
type SummaryKeys struct {
|
||||||
|
IsAnomaly bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys.
|
||||||
|
func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) {
|
||||||
|
return SummaryKeys{IsAnomaly: false}, nil
|
||||||
|
}
|
48
internal/engine/experiment/portfiltering/tcpconnect.go
Normal file
48
internal/engine/experiment/portfiltering/tcpconnect.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
//
|
||||||
|
// TCPConnect for portfiltering
|
||||||
|
//
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/measurexlite"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tcpPingLoop sends the TCP Connect requests to all ports and emits the results onto the out channel
|
||||||
|
func (m *Measurer) tcpConnectLoop(ctx context.Context, zeroTime time.Time,
|
||||||
|
logger model.Logger, address string, out chan<- *model.ArchivalTCPConnectResult) {
|
||||||
|
ticker := time.NewTicker(m.config.delay())
|
||||||
|
defer ticker.Stop()
|
||||||
|
rand.Shuffle(len(Ports), func(i, j int) {
|
||||||
|
Ports[i], Ports[j] = Ports[j], Ports[i]
|
||||||
|
})
|
||||||
|
for i, port := range Ports {
|
||||||
|
addr := net.JoinHostPort(address, port)
|
||||||
|
go m.tcpConnectAsync(ctx, int64(i), zeroTime, logger, addr, out)
|
||||||
|
<-ticker.C
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpPingAsync performs a TCP Connect and emits the result onto the out channel.
|
||||||
|
func (m *Measurer) tcpConnectAsync(ctx context.Context, index int64,
|
||||||
|
zeroTime time.Time, logger model.Logger, address string, out chan<- *model.ArchivalTCPConnectResult) {
|
||||||
|
out <- m.tcpConnect(ctx, index, zeroTime, logger, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpConnect performs a TCP connect and returns the result to the caller.
|
||||||
|
func (m *Measurer) tcpConnect(ctx context.Context, index int64,
|
||||||
|
zeroTime time.Time, logger model.Logger, address string) *model.ArchivalTCPConnectResult {
|
||||||
|
trace := measurexlite.NewTrace(index, zeroTime)
|
||||||
|
ol := measurexlite.NewOperationLogger(logger, "TCPConnect #%d %s", index, address)
|
||||||
|
dialer := trace.NewDialerWithoutResolver(logger)
|
||||||
|
conn, err := dialer.DialContext(ctx, "tcp", address)
|
||||||
|
ol.Stop(err)
|
||||||
|
measurexlite.MaybeClose(conn)
|
||||||
|
return trace.FirstTCPConnectOrNil()
|
||||||
|
}
|
8
internal/engine/experiment/portfiltering/testkeys.go
Normal file
8
internal/engine/experiment/portfiltering/testkeys.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package portfiltering
|
||||||
|
|
||||||
|
import "github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
|
||||||
|
// TestKeys contains the experiment results.
|
||||||
|
type TestKeys struct {
|
||||||
|
TCPConnect []*model.ArchivalTCPConnectResult `json:"tcp_connect"`
|
||||||
|
}
|
23
internal/registry/portfiltering.go
Normal file
23
internal/registry/portfiltering.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package registry
|
||||||
|
|
||||||
|
//
|
||||||
|
// Registers the 'portfiltering' experiment
|
||||||
|
//
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/engine/experiment/portfiltering"
|
||||||
|
"github.com/ooni/probe-cli/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AllExperiments["portfiltering"] = &Factory{
|
||||||
|
build: func(config any) model.ExperimentMeasurer {
|
||||||
|
return portfiltering.NewExperimentMeasurer(
|
||||||
|
config.(portfiltering.Config),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
config: portfiltering.Config{},
|
||||||
|
interruptible: false,
|
||||||
|
inputPolicy: model.InputNone,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user