Documentation ¶
Overview ¶
Package model contains the shared interfaces and data structures.
Criteria for adding a type to this package ¶
This package should contain two types:
1. important interfaces that are shared by several packages within the codebase, with the objective of separating unrelated pieces of code and making unit testing easier;
2. important pieces of data that are shared across different packages (e.g., the representation of a Measurement).
In general, this package should not contain logic, unless this logic is strictly related to data structures and we cannot implement this logic elsewhere.
Content of this package ¶
The following list (which may not always be up-to-date) summarizes the categories of types that currently belong here and names the files in which they are implemented:
- experiment.go: generic definition of a network experiment and all the required support types;
- keyvaluestore.go: generic definition of a key-value store, used in several places across the codebase;
- logger.go: generic definition of an apex/log compatible logger, used in several places across the codebase;
- measurement.go: data type representing the result of a network measurement, used in many many places;
- netx.go: network extension interfaces and data used everywhere we need to perform network operations;
- ooapi.go: types to communicate with the OONI API.
Index ¶
- Constants
- Variables
- func ErrorToStringOrOK(err error) string
- func ScrubMeasurement(m *Measurement, currentIP string) error
- type ArchivalDNSAnswer
- type ArchivalDNSLookupResult
- type ArchivalExtSpec
- type ArchivalHTTPBody
- type ArchivalHTTPHeader
- type ArchivalHTTPRequest
- type ArchivalHTTPRequestResult
- type ArchivalHTTPResponse
- type ArchivalHTTPTor
- type ArchivalMaybeBinaryData
- type ArchivalNetworkEvent
- type ArchivalTCPConnectResult
- type ArchivalTCPConnectStatus
- type ArchivalTLSOrQUICHandshakeResult
- type DNSDecoder
- type DNSEncoder
- type DNSQuery
- type DNSResponse
- type DNSTransport
- type DNSTransportWrapper
- type DebugLogger
- type Dialer
- type DialerWrapper
- type Experiment
- type ExperimentAsyncTestKeys
- type ExperimentBuilder
- type ExperimentCallbacks
- type ExperimentInputLoader
- type ExperimentInputProcessor
- type ExperimentMeasurer
- type ExperimentMeasurerAsync
- type ExperimentOptionInfo
- type ExperimentSession
- type HTTPClient
- type HTTPSSvc
- type HTTPTransport
- type InfoLogger
- type InputPolicy
- type KeyValueStore
- type Logger
- type Measurement
- type MeasurementTarget
- type OOAPICheckInConfig
- type OOAPICheckInConfigWebConnectivity
- type OOAPICheckInInfo
- type OOAPICheckInInfoWebConnectivity
- type OOAPIService
- type OOAPITorTarget
- type OOAPIURLInfo
- type OOAPIURLListConfig
- type PrinterCallbacks
- type QUICDialer
- type QUICDialerWrapper
- type QUICListener
- type Resolver
- type RunType
- type Saver
- type SimpleDialer
- type Submitter
- type THDNSResult
- type THHTTPRequestResult
- type THIPInfo
- type THRequest
- type THResponse
- type THTCPConnectResult
- type THTLSHandshakeResult
- type TLSDialer
- type TLSHandshaker
- type Trace
- type UDPLikeConn
Constants ¶
const ( // InputOrQueryBackend indicates that the experiment requires // external input to run and that this kind of input is URLs // from the citizenlab/test-lists repository. If this input // not provided to the experiment, then the code that runs the // experiment is supposed to fetch from URLs from OONI's backends. InputOrQueryBackend = InputPolicy("or_query_backend") // InputStrictlyRequired indicates that the experiment // requires input and we currently don't have an API for // fetching such input. Therefore, either the user specifies // input or the experiment will fail for the lack of input. InputStrictlyRequired = InputPolicy("strictly_required") // InputOptional indicates that the experiment handles input, // if any; otherwise it fetchs input/uses a default. InputOptional = InputPolicy("optional") // InputNone indicates that the experiment does not want any // input and ignores the input if provided with it. InputNone = InputPolicy("none") // We gather input from StaticInput and SourceFiles. If there is // input, we return it. Otherwise, we return an internal static // list of inputs to be used with this experiment. InputOrStaticDefault = InputPolicy("or_static_default") )
const ( // HTTPHeaderAccept is the Accept header used for measuring. HTTPHeaderAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" // HTTPHeaderAcceptLanguage is the Accept-Language header used for measuring. HTTPHeaderAcceptLanguage = "en-US,en;q=0.9" // HTTPHeaderUserAgent is the User-Agent header used for measuring. The current header // is 19.3% of the browser population as of Sep 04, 2022 according to the // https://techblog.willshouse.com/2012/01/03/most-common-user-agents/ webpage. HTTPHeaderUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" )
Headers we use for measuring.
const ( // DefaultProbeASN is the default probe ASN as a number. DefaultProbeASN uint = 0 // DefaultProbeCC is the default probe CC. DefaultProbeCC = "ZZ" // DefaultProbeIP is the default probe IP. DefaultProbeIP = "127.0.0.1" // DefaultProbeNetworkName is the default probe network name. DefaultProbeNetworkName = "" // DefaultResolverASN is the default resolver ASN. DefaultResolverASN uint = 0 // DefaultResolverIP is the default resolver IP. DefaultResolverIP = "127.0.0.2" // DefaultResolverNetworkName is the default resolver network name. DefaultResolverNetworkName = "" )
const ( // RunTypeManual indicates that the user manually run `ooniprobe run`. Command // line tools such as miniooni should always use this run type. RunTypeManual = RunType("manual") // RunTypeTimed indicates that the user run `ooniprobe run unattended`, which // is the correct way to run ooniprobe from scripts and cronjobs. RunTypeTimed = RunType("timed") )
const ( // THIPInfoFlagResolvedByProbe indicates that the probe has // resolved this IP address. THIPInfoFlagResolvedByProbe = 1 << iota // THIPInfoFlagResolvedByTH indicates that the test helper // has resolved this IP address. THIPInfoFlagResolvedByTH // THIPInfoFlagIsBogon indicates that the address is a bogon THIPInfoFlagIsBogon // THIPInfoFlagValidForDomain indicates that an IP address // is valid for the domain because it works with TLS THIPInfoFlagValidForDomain )
const Scrubbed = `[scrubbed]`
Scrubbed is the string that replaces IP addresses.
const THDNSNameError = "dns_name_error"
THDNSNameError is the error returned by the control on NXDOMAIN
Variables ¶
var ( // ArchivalExtDNS is the version of df-002-dnst.md ArchivalExtDNS = ArchivalExtSpec{Name: "dnst", V: 0} // ArchivalExtNetevents is the version of df-008-netevents.md ArchivalExtNetevents = ArchivalExtSpec{Name: "netevents", V: 0} // ArchivalExtHTTP is the version of df-001-httpt.md ArchivalExtHTTP = ArchivalExtSpec{Name: "httpt", V: 0} // ArchivalExtTCPConnect is the version of df-005-tcpconnect.md ArchivalExtTCPConnect = ArchivalExtSpec{Name: "tcpconnect", V: 0} // ArchivalExtTLSHandshake is the version of df-006-tlshandshake.md ArchivalExtTLSHandshake = ArchivalExtSpec{Name: "tlshandshake", V: 0} // ArchivalExtTunnel is the version of df-009-tunnel.md ArchivalExtTunnel = ArchivalExtSpec{Name: "tunnel", V: 0} )
var ( // HTTPUnexpectedStatusCode indicates that we re not getting // the expected (range of) HTTP status code(s). HTTPUnexpectedStatusCode = "http_unexpected_status_code" // HTTPUnexpectedRedirectURL indicates that the redirect URL // returned by the server is not the expected one. HTTPUnexpectedRedirectURL = "http_unexpected_redirect_url" )
Additional strings used to report HTTP errors. They're currently only used by experiment/whatsapp but may be used by more experiments in the future. They must be addressable (i.e., var and not const) because experiments typically want to take their addresses to fill fields with `string|null` type.
var ( // DefaultProbeASNString is the default probe ASN as a string. DefaultProbeASNString = fmt.Sprintf("AS%d", DefaultProbeASN) // DefaultResolverASNString is the default resolver ASN as a string. DefaultResolverASNString = fmt.Sprintf("AS%d", DefaultResolverASN) )
var ErrInvalidProbeIP = errors.New("model: invalid probe IP")
ErrInvalidProbeIP indicates that we're dealing with a string that is not the valid serialization of an IP address.
Functions ¶
func ErrorToStringOrOK ¶ added in v3.15.0
ErrorToStringOrOK emits "ok" on "<nil>"" values for success.
func ScrubMeasurement ¶ added in v3.16.2
func ScrubMeasurement(m *Measurement, currentIP string) error
ScrubMeasurement removes [currentIP] from [m] by rewriting it in place while preserving the underlying types
Types ¶
type ArchivalDNSAnswer ¶
type ArchivalDNSAnswer struct { ASN int64 `json:"asn,omitempty"` ASOrgName string `json:"as_org_name,omitempty"` AnswerType string `json:"answer_type"` Hostname string `json:"hostname,omitempty"` IPv4 string `json:"ipv4,omitempty"` IPv6 string `json:"ipv6,omitempty"` TTL *uint32 `json:"ttl"` }
ArchivalDNSAnswer is a DNS answer.
type ArchivalDNSLookupResult ¶
type ArchivalDNSLookupResult struct { Answers []ArchivalDNSAnswer `json:"answers"` Engine string `json:"engine"` Failure *string `json:"failure"` GetaddrinfoError int64 `json:"getaddrinfo_error,omitempty"` Hostname string `json:"hostname"` QueryType string `json:"query_type"` RawResponse []byte `json:"raw_response,omitempty"` Rcode int64 `json:"rcode,omitempty"` ResolverHostname *string `json:"resolver_hostname"` ResolverPort *string `json:"resolver_port"` ResolverAddress string `json:"resolver_address"` T0 float64 `json:"t0,omitempty"` T float64 `json:"t"` TransactionID int64 `json:"transaction_id,omitempty"` }
ArchivalDNSLookupResult is the result of a DNS lookup.
See https://github.com/ooni/spec/blob/master/data-formats/df-002-dnst.md.
type ArchivalExtSpec ¶
ArchivalExtSpec describes a data format extension
func (ArchivalExtSpec) AddTo ¶
func (spec ArchivalExtSpec) AddTo(m *Measurement)
AddTo adds the current ExtSpec to the specified measurement
type ArchivalHTTPBody ¶
type ArchivalHTTPBody = ArchivalMaybeBinaryData
ArchivalHTTPBody is an HTTP body. As an implementation note, this type must be an alias for the MaybeBinaryValue type, otherwise the specific serialisation mechanism implemented by MaybeBinaryValue is not working.
type ArchivalHTTPHeader ¶
type ArchivalHTTPHeader struct { Key string Value ArchivalMaybeBinaryData }
ArchivalHTTPHeader is a single HTTP header.
func (ArchivalHTTPHeader) MarshalJSON ¶
func (hh ArchivalHTTPHeader) MarshalJSON() ([]byte, error)
MarshalJSON marshals a single HTTP header to a tuple where the first element is a string and the second element is maybe-binary data.
func (*ArchivalHTTPHeader) UnmarshalJSON ¶
func (hh *ArchivalHTTPHeader) UnmarshalJSON(d []byte) error
UnmarshalJSON is the opposite of MarshalJSON.
type ArchivalHTTPRequest ¶
type ArchivalHTTPRequest struct { Body ArchivalHTTPBody `json:"body"` BodyIsTruncated bool `json:"body_is_truncated"` HeadersList []ArchivalHTTPHeader `json:"headers_list"` Headers map[string]ArchivalMaybeBinaryData `json:"headers"` Method string `json:"method"` Tor ArchivalHTTPTor `json:"tor"` Transport string `json:"x_transport"` URL string `json:"url"` }
ArchivalHTTPRequest contains an HTTP request.
Headers are a map in Web Connectivity data format but we have added support for a list since January 2020.
type ArchivalHTTPRequestResult ¶
type ArchivalHTTPRequestResult struct { Network string `json:"network,omitempty"` Address string `json:"address,omitempty"` ALPN string `json:"alpn,omitempty"` Failure *string `json:"failure"` Request ArchivalHTTPRequest `json:"request"` Response ArchivalHTTPResponse `json:"response"` T0 float64 `json:"t0,omitempty"` T float64 `json:"t"` TransactionID int64 `json:"transaction_id,omitempty"` }
ArchivalHTTPRequestResult is the result of sending an HTTP request.
See https://github.com/ooni/spec/blob/master/data-formats/df-001-httpt.md.
type ArchivalHTTPResponse ¶
type ArchivalHTTPResponse struct { Body ArchivalHTTPBody `json:"body"` BodyIsTruncated bool `json:"body_is_truncated"` Code int64 `json:"code"` HeadersList []ArchivalHTTPHeader `json:"headers_list"` Headers map[string]ArchivalMaybeBinaryData `json:"headers"` // The following fields are not serialised but are useful to simplify // analysing the measurements in telegram, whatsapp, etc. Locations []string `json:"-"` }
ArchivalHTTPResponse contains an HTTP response.
Headers are a map in Web Connectivity data format but we have added support for a list since January 2020.
type ArchivalHTTPTor ¶
type ArchivalHTTPTor struct { ExitIP *string `json:"exit_ip"` ExitName *string `json:"exit_name"` IsTor bool `json:"is_tor"` }
ArchivalHTTPTor contains Tor information.
type ArchivalMaybeBinaryData ¶
type ArchivalMaybeBinaryData struct {
Value string
}
ArchivalMaybeBinaryData is a possibly binary string. We use this helper class to define a custom JSON encoder that allows us to choose the proper representation depending on whether the Value field is valid UTF-8 or not.
See https://github.com/ooni/spec/blob/master/data-formats/df-001-httpt.md#maybebinarydata
func (ArchivalMaybeBinaryData) MarshalJSON ¶
func (hb ArchivalMaybeBinaryData) MarshalJSON() ([]byte, error)
MarshalJSON marshals a string-like to JSON following the OONI spec that says that UTF-8 content is represented as string and non-UTF-8 content is instead represented using `{"format":"base64","data":"..."}`.
func (*ArchivalMaybeBinaryData) UnmarshalJSON ¶
func (hb *ArchivalMaybeBinaryData) UnmarshalJSON(d []byte) error
UnmarshalJSON is the opposite of MarshalJSON.
type ArchivalNetworkEvent ¶
type ArchivalNetworkEvent struct { Address string `json:"address,omitempty"` Failure *string `json:"failure"` NumBytes int64 `json:"num_bytes,omitempty"` Operation string `json:"operation"` Proto string `json:"proto,omitempty"` T0 float64 `json:"t0,omitempty"` T float64 `json:"t"` TransactionID int64 `json:"transaction_id,omitempty"` Tags []string `json:"tags,omitempty"` }
ArchivalNetworkEvent is a network event. It contains all the possible fields and most fields are optional. They are only added when it makes sense for them to be there _and_ we have data to show.
See https://github.com/ooni/spec/blob/master/data-formats/df-008-netevents.md.
type ArchivalTCPConnectResult ¶
type ArchivalTCPConnectResult struct { IP string `json:"ip"` Port int `json:"port"` Status ArchivalTCPConnectStatus `json:"status"` T0 float64 `json:"t0,omitempty"` T float64 `json:"t"` TransactionID int64 `json:"transaction_id,omitempty"` }
ArchivalTCPConnectResult contains the result of a TCP connect.
See https://github.com/ooni/spec/blob/master/data-formats/df-005-tcpconnect.md.
type ArchivalTCPConnectStatus ¶
type ArchivalTCPConnectStatus struct { Blocked *bool `json:"blocked,omitempty"` Failure *string `json:"failure"` Success bool `json:"success"` }
ArchivalTCPConnectStatus is the status of ArchivalTCPConnectResult.
type ArchivalTLSOrQUICHandshakeResult ¶
type ArchivalTLSOrQUICHandshakeResult struct { Network string `json:"network"` Address string `json:"address"` CipherSuite string `json:"cipher_suite"` Failure *string `json:"failure"` NegotiatedProtocol string `json:"negotiated_protocol"` NoTLSVerify bool `json:"no_tls_verify"` PeerCertificates []ArchivalMaybeBinaryData `json:"peer_certificates"` ServerName string `json:"server_name"` T0 float64 `json:"t0,omitempty"` T float64 `json:"t"` Tags []string `json:"tags"` TLSVersion string `json:"tls_version"` TransactionID int64 `json:"transaction_id,omitempty"` }
ArchivalTLSOrQUICHandshakeResult is the result of a TLS or QUIC handshake.
See https://github.com/ooni/spec/blob/master/data-formats/df-006-tlshandshake.md
type DNSDecoder ¶
type DNSDecoder interface { // DecodeResponse decodes a DNS response message. // // Arguments: // // - data is the raw reply // // This function fails if we cannot parse data as a DNS // message or the message is not a response. // // Regarding the returned response, remember that the Rcode // MAY still be nonzero (this method does not treat a nonzero // Rcode as an error when parsing the response). DecodeResponse(data []byte, query DNSQuery) (DNSResponse, error) }
The DNSDecoder decodes DNS responses.
type DNSEncoder ¶
type DNSEncoder interface { // Encode transforms its arguments into a serialized DNS query. // // Every time you call Encode, you get a new DNSQuery value // using a query ID selected at random. // // Serialization to bytes is lazy to acommodate DNS transports that // do not need to serialize and send bytes, e.g., getaddrinfo. // // You serialize to bytes using DNSQuery.Bytes. This operation MAY fail // if the domain name cannot be packed into a DNS message (e.g., it is // too long to fit into the message). // // Arguments: // // - domain is the domain for the query (e.g., x.org); // // - qtype is the query type (e.g., dns.TypeA); // // - padding is whether to add padding to the query. // // This function will transform the domain into an FQDN is it's not // already expressed in the FQDN format. Encode(domain string, qtype uint16, padding bool) DNSQuery }
The DNSEncoder encodes DNS queries to bytes
type DNSQuery ¶ added in v3.16.0
type DNSQuery interface { // Domain is the domain we're querying for. Domain() string // Type is the query type. Type() uint16 // Bytes serializes the query to bytes. This function may fail if we're not // able to correctly encode the domain into a query message. // // The value returned by this function WILL be memoized after the first call, // so you SHOULD create a new DNSQuery if you need to retry a query. Bytes() ([]byte, error) // ID returns the query ID. ID() uint16 }
DNSQuery is an encoded DNS query ready to be sent using a DNSTransport.
type DNSResponse ¶ added in v3.16.0
type DNSResponse interface { // Query is the query associated with this response. Query() DNSQuery // Bytes returns the bytes from which we parsed the query. Bytes() []byte // Rcode returns the response's Rcode. Rcode() int // DecodeHTTPS returns information gathered from all the HTTPS // records found inside of this response. DecodeHTTPS() (*HTTPSSvc, error) // DecodeLookupHost returns the addresses in the response matching // the original query type (one of A and AAAA). DecodeLookupHost() ([]string, error) // DecodeNS returns all the NS entries in this response. DecodeNS() ([]*net.NS, error) // DecodeCNAME returns the first CNAME entry in this response. DecodeCNAME() (string, error) }
DNSResponse is a parsed DNS response ready for further processing.
type DNSTransport ¶
type DNSTransport interface { // RoundTrip sends a DNS query and receives the reply. RoundTrip(ctx context.Context, query DNSQuery) (DNSResponse, error) // RequiresPadding returns whether this transport needs padding. RequiresPadding() bool // Network is the network of the round tripper (e.g. "dot"). Network() string // Address is the address of the round tripper (e.g. "1.1.1.1:853"). Address() string // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() }
DNSTransport represents an abstract DNS transport.
type DNSTransportWrapper ¶ added in v3.16.0
type DNSTransportWrapper interface {
WrapDNSTransport(txp DNSTransport) DNSTransport
}
DNSTransportWrapper is a type that takes in input a DNSTransport and returns in output a wrapped DNSTransport.
type DebugLogger ¶
type DebugLogger interface { // Debug emits a debug message. Debug(msg string) // Debugf formats and emits a debug message. Debugf(format string, v ...interface{}) }
DebugLogger is a logger emitting only debug messages.
type Dialer ¶
type Dialer interface { // A Dialer is also a SimpleDialer. SimpleDialer // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() }
Dialer is a SimpleDialer with the possibility of closing open connections.
type DialerWrapper ¶ added in v3.16.0
DialerWrapper is a type that takes in input a Dialer and returns in output a wrapped Dialer.
type Experiment ¶ added in v3.16.0
type Experiment interface { // KibiBytesReceived accounts for the KibiBytes received by the experiment. KibiBytesReceived() float64 // KibiBytesSent is like KibiBytesReceived but for the bytes sent. KibiBytesSent() float64 // Name returns the experiment name. Name() string // GetSummaryKeys returns a data structure containing a // summary of the test keys for ooniprobe. GetSummaryKeys(m *Measurement) (any, error) // ReportID returns the open report's ID, if we have opened a report // successfully before, or an empty string, otherwise. // // Deprecated: new code should use a Submitter. ReportID() string // MeasureAsync runs an async measurement. This operation could post // one or more measurements onto the returned channel. We'll close the // channel when we've emitted all the measurements. // // Arguments: // // - ctx is the context for deadline/cancellation/timeout; // // - input is the input (typically a URL but it could also be // just an endpoint or an empty string for input-less experiments // such as, e.g., ndt7 and dash). // // Return value: // // - on success, channel where to post measurements (the channel // will be closed when done) and nil error; // // - on failure, nil channel and non-nil error. MeasureAsync(ctx context.Context, input string) (<-chan *Measurement, error) // MeasureWithContext performs a synchronous measurement. // // Return value: strictly either a non-nil measurement and // a nil error or a nil measurement and a non-nil error. // // CAVEAT: while this API is perfectly fine for experiments that // return a single measurement, it will only return the first measurement // when used with an asynchronous experiment. MeasureWithContext(ctx context.Context, input string) (measurement *Measurement, err error) // SaveMeasurement saves a measurement on the specified file path. // // Deprecated: new code should use a Saver. SaveMeasurement(measurement *Measurement, filePath string) error // SubmitAndUpdateMeasurementContext submits a measurement and updates the // fields whose value has changed as part of the submission. // // Deprecated: new code should use a Submitter. SubmitAndUpdateMeasurementContext( ctx context.Context, measurement *Measurement) error // OpenReportContext will open a report using the given context // to possibly limit the lifetime of this operation. // // Deprecated: new code should use a Submitter. OpenReportContext(ctx context.Context) error }
Experiment is an experiment instance.
type ExperimentAsyncTestKeys ¶
type ExperimentAsyncTestKeys struct { // Extensions contains the extensions used by this experiment. Extensions map[string]int64 // Input is the input this measurement refers to. Input MeasurementTarget // MeasurementRuntime is the total measurement runtime. MeasurementRuntime float64 // TestHelpers contains the test helpers used in the experiment TestHelpers map[string]interface{} // TestKeys contains the actual test keys. TestKeys interface{} }
ExperimentAsyncTestKeys is the type of test keys returned by an experiment when running in async fashion rather than in sync fashion.
type ExperimentBuilder ¶ added in v3.16.0
type ExperimentBuilder interface { // Interruptible tells you whether this is an interruptible experiment. This kind // of experiments (e.g. ndt7) may be interrupted mid way. Interruptible() bool // InputPolicy returns the experiment input policy. InputPolicy() InputPolicy // Options returns information about the experiment's options. Options() (map[string]ExperimentOptionInfo, error) // SetOptionAny sets an option whose value is an any value. We will use reasonable // heuristics to convert the any value to the proper type of the field whose name is // contained by the key variable. If we cannot convert the provided any value to // the proper type, then this function returns an error. SetOptionAny(key string, value any) error // SetOptionsAny sets options from a map[string]any. See the documentation of // the SetOptionAny method for more information. SetOptionsAny(options map[string]any) error // SetCallbacks sets the experiment's interactive callbacks. SetCallbacks(callbacks ExperimentCallbacks) // NewExperiment creates the experiment instance. NewExperiment() Experiment }
ExperimentBuilder builds an experiment.
type ExperimentCallbacks ¶
type ExperimentCallbacks interface { // OnProgress provides information about an experiment progress. OnProgress(percentage float64, message string) }
ExperimentCallbacks contains experiment event-handling callbacks
type ExperimentInputLoader ¶ added in v3.16.0
type ExperimentInputLoader interface {
Load(ctx context.Context) ([]OOAPIURLInfo, error)
}
ExperimentInputLoader loads inputs from local or remote sources.
type ExperimentInputProcessor ¶ added in v3.16.0
ExperimentInputProcessor processes inputs for an experiment.
type ExperimentMeasurer ¶
type ExperimentMeasurer interface { // ExperimentName returns the experiment name. ExperimentName() string // ExperimentVersion returns the experiment version. ExperimentVersion() string // Run runs the experiment with the specified context, session, // measurement, and experiment calbacks. This method should only // return an error in case the experiment could not run (e.g., // a required input is missing). Otherwise, the code should just // set the relevant OONI error inside of the measurement and // return nil. This is important because the caller WILL NOT submit // the measurement if this method returns an error. Run( ctx context.Context, sess ExperimentSession, measurement *Measurement, callbacks ExperimentCallbacks, ) error // GetSummaryKeys returns summary keys expected by ooni/probe-cli. GetSummaryKeys(*Measurement) (interface{}, error) }
ExperimentMeasurer is the interface that allows to run a measurement for a specific experiment.
type ExperimentMeasurerAsync ¶
type ExperimentMeasurerAsync interface { // RunAsync runs the experiment in async fashion. // // Arguments: // // - ctx is the context for deadline/timeout/cancellation // // - sess is the measurement session // // - input is the input URL to measure // // - callbacks contains the experiment callbacks // // Returns either a channel where TestKeys are posted or an error. // // An error indicates that specific preconditions for running the experiment // are not met (e.g., the input URL is invalid). // // On success, the experiment will post on the channel each new // measurement until it is done and closes the channel. RunAsync(ctx context.Context, sess ExperimentSession, input string, callbacks ExperimentCallbacks) (<-chan *ExperimentAsyncTestKeys, error) }
ExperimentMeasurerAsync is a measurer that can run in async fashion.
Currently this functionality is optional, but we will likely migrate all experiments to use this functionality in 2022.
type ExperimentOptionInfo ¶ added in v3.16.0
type ExperimentOptionInfo struct { // Doc contains the documentation. Doc string // Type contains the type. Type string }
ExperimentOptionInfo contains info about an experiment option.
type ExperimentSession ¶
type ExperimentSession interface { // GetTestHelpersByName returns a list of test helpers with the given name. GetTestHelpersByName(name string) ([]OOAPIService, bool) // DefaultHTTPClient returns the default HTTPClient used by the session. DefaultHTTPClient() HTTPClient // FetchPsiphonConfig returns psiphon's config as a serialized JSON or an error. FetchPsiphonConfig(ctx context.Context) ([]byte, error) // FetchTorTargets returns the targets for the Tor experiment or an error. FetchTorTargets(ctx context.Context, cc string) (map[string]OOAPITorTarget, error) // Logger returns the logger used by the session. Logger() Logger // ProbeCC returns the country code. ProbeCC() string // ResolverIP returns the resolver's IP. ResolverIP() string // TempDir returns the session's temporary directory. TempDir() string // TorArgs returns the arguments we should pass to tor when executing it. TorArgs() []string // TorBinary returns the path of the tor binary. TorBinary() string // TunnelDir is the directory where to store tunnel information. TunnelDir() string // UserAgent returns the user agent we should be using when we're fine // with identifying ourselves as ooniprobe. UserAgent() string }
ExperimentSession is the experiment's view of a session.
type HTTPClient ¶
HTTPClient is an http.Client-like interface.
type HTTPSSvc ¶
type HTTPSSvc struct { // ALPN contains the ALPNs inside the HTTPS reply. ALPN []string // IPv4 contains the IPv4 hints (which may be empty). IPv4 []string // IPv6 contains the IPv6 hints (which may be empty). IPv6 []string }
HTTPSSvc is the reply to an HTTPS DNS query.
type HTTPTransport ¶
type HTTPTransport interface { // Network returns the network used by the transport, which // should be one of "tcp" and "udp". Network() string // RoundTrip performs the HTTP round trip. RoundTrip(req *http.Request) (*http.Response, error) // CloseIdleConnections closes idle connections. CloseIdleConnections() }
HTTPTransport is an http.Transport-like structure.
type InfoLogger ¶
type InfoLogger interface { // An InfoLogger is also a DebugLogger. DebugLogger // Info emits an informational message. Info(msg string) // Infof formats and emits an informational message. Infof(format string, v ...interface{}) }
InfoLogger is a logger emitting debug and infor messages.
type InputPolicy ¶ added in v3.16.0
type InputPolicy string
InputPolicy describes the experiment policy with respect to input. That is whether it requires input, optionally accepts input, does not want input.
type KeyValueStore ¶
type KeyValueStore interface { // Get gets the value of the given key or returns an // error if there is no such key or we cannot read // from the key-value store. Get(key string) (value []byte, err error) // Set sets the value of the given key and returns // whether the operation was successful or not. Set(key string, value []byte) (err error) }
KeyValueStore is a generic key-value store.
type Logger ¶
type Logger interface { // A Logger is also an InfoLogger. InfoLogger // Warn emits a warning message. Warn(msg string) // Warnf formats and emits a warning message. Warnf(format string, v ...interface{}) }
Logger defines the common interface that a logger should have. It is out of the box compatible with `log.Log` in `apex/log`.
var DiscardLogger Logger = logDiscarder{}
DiscardLogger is the default logger that discards its input
func ValidLoggerOrDefault ¶ added in v3.16.0
ValidLoggerOrDefault is a factory that either returns the logger provided as argument, if not nil, or DiscardLogger.
type Measurement ¶
type Measurement struct { // Annotations contains results annotations Annotations map[string]string `json:"annotations,omitempty"` // DataFormatVersion is the version of the data format DataFormatVersion string `json:"data_format_version"` // Extensions contains information about the extensions included // into the test_keys of this measurement. Extensions map[string]int64 `json:"extensions,omitempty"` // ID is the locally generated measurement ID ID string `json:"id,omitempty"` // Input is the measurement input Input MeasurementTarget `json:"input"` // InputHashes contains input hashes InputHashes []string `json:"input_hashes,omitempty"` // MeasurementStartTime is the time when the measurement started MeasurementStartTime string `json:"measurement_start_time"` // MeasurementStartTimeSaved is the moment in time when we // started the measurement. This is not included into the JSON // and is only used within the ./internal pkg as a "zero" time. MeasurementStartTimeSaved time.Time `json:"-"` // Options contains command line options Options []string `json:"options,omitempty"` // ProbeASN contains the probe autonomous system number ProbeASN string `json:"probe_asn"` // ProbeCC contains the probe country code ProbeCC string `json:"probe_cc"` // ProbeCity contains the probe city ProbeCity string `json:"probe_city,omitempty"` // ProbeIP contains the probe IP ProbeIP string `json:"probe_ip,omitempty"` // ProbeNetworkName contains the probe network name ProbeNetworkName string `json:"probe_network_name"` // ReportID contains the report ID ReportID string `json:"report_id"` // ResolverASN is the ASN of the resolver ResolverASN string `json:"resolver_asn"` // ResolverIP is the resolver IP ResolverIP string `json:"resolver_ip"` // ResolverNetworkName is the network name of the resolver. ResolverNetworkName string `json:"resolver_network_name"` // SoftwareName contains the software name SoftwareName string `json:"software_name"` // SoftwareVersion contains the software version SoftwareVersion string `json:"software_version"` // TestHelpers contains the test helpers. It seems this structure is more // complex than we would like. In particular, using a map from string to // string does not fit into the web_connectivity use case. Hence, for now // we're going to represent this using interface{}. In going forward we // may probably want to have more uniform test helpers. TestHelpers map[string]interface{} `json:"test_helpers,omitempty"` // TestKeys contains the real test result. This field is opaque because // each experiment will insert here a different structure. TestKeys interface{} `json:"test_keys"` // TestName contains the test name TestName string `json:"test_name"` // MeasurementRuntime contains the measurement runtime. The JSON name // is test_runtime because this is the name expected by the OONI backend // even though that name is clearly a misleading one. MeasurementRuntime float64 `json:"test_runtime"` // TestStartTime contains the test start time TestStartTime string `json:"test_start_time"` // TestVersion contains the test version TestVersion string `json:"test_version"` }
Measurement is a OONI measurement.
This structure is compatible with the definition of the base data format in https://github.com/ooni/spec/blob/master/data-formats/df-000-base.md.
func (*Measurement) AddAnnotation ¶
func (m *Measurement) AddAnnotation(key, value string)
AddAnnotation adds a single annotations to m.Annotations.
func (*Measurement) AddAnnotations ¶
func (m *Measurement) AddAnnotations(input map[string]string)
AddAnnotations adds the annotations from input to m.Annotations.
type MeasurementTarget ¶
type MeasurementTarget string
MeasurementTarget is the target of a OONI measurement.
func (MeasurementTarget) MarshalJSON ¶
func (t MeasurementTarget) MarshalJSON() ([]byte, error)
MarshalJSON serializes the MeasurementTarget.
type OOAPICheckInConfig ¶
type OOAPICheckInConfig struct { Charging bool `json:"charging"` // Charging indicate if the phone is actually charging OnWiFi bool `json:"on_wifi"` // OnWiFi indicate if the phone is actually connected to a WiFi network Platform string `json:"platform"` // Platform of the probe ProbeASN string `json:"probe_asn"` // ProbeASN is the probe country code ProbeCC string `json:"probe_cc"` // ProbeCC is the probe country code RunType RunType `json:"run_type"` // RunType SoftwareName string `json:"software_name"` // SoftwareName of the probe SoftwareVersion string `json:"software_version"` // SoftwareVersion of the probe WebConnectivity OOAPICheckInConfigWebConnectivity `json:"web_connectivity"` // WebConnectivity class contain an array of categories }
OOAPICheckInConfig contains configuration for calling the checkin API.
type OOAPICheckInConfigWebConnectivity ¶
type OOAPICheckInConfigWebConnectivity struct {
CategoryCodes []string `json:"category_codes"` // CategoryCodes is an array of category codes
}
OOAPICheckInConfigWebConnectivity is the configuration for the WebConnectivity test
type OOAPICheckInInfo ¶
type OOAPICheckInInfo struct {
WebConnectivity *OOAPICheckInInfoWebConnectivity `json:"web_connectivity"`
}
OOAPICheckInInfo contains the return test objects from the checkin API
type OOAPICheckInInfoWebConnectivity ¶
type OOAPICheckInInfoWebConnectivity struct { ReportID string `json:"report_id"` URLs []OOAPIURLInfo `json:"urls"` }
OOAPICheckInInfoWebConnectivity contains the array of URLs returned by the checkin API
type OOAPIService ¶
type OOAPIService struct { // Address is the address of the server. Address string `json:"address"` // Type is the type of the service. Type string `json:"type"` // Front is the front to use with "cloudfront" type entries. Front string `json:"front,omitempty"` }
OOAPIService describes a backend service.
The fields of this struct have the meaning described in v2.0.0 of the OONI bouncer specification defined by https://github.com/ooni/spec/blob/master/backends/bk-004-bouncer.md.
type OOAPITorTarget ¶
type OOAPITorTarget struct { // Address is the address of the target. Address string `json:"address"` // Name is the name of the target. Name string `json:"name"` // Params contains optional params for, e.g., pluggable transports. Params map[string][]string `json:"params"` // Protocol is the protocol to use with the target. Protocol string `json:"protocol"` // Source is the source from which we fetched this specific // target. Whenever the source is non-empty, we will treat // this specific target as a private target. Source string `json:"source"` }
OOAPITorTarget is a target for the tor experiment.
type OOAPIURLInfo ¶
type OOAPIURLInfo struct { CategoryCode string `json:"category_code"` CountryCode string `json:"country_code"` URL string `json:"url"` }
OOAPIURLInfo contains info on a test lists URL
type OOAPIURLListConfig ¶
type OOAPIURLListConfig struct { Categories []string // Categories to query for (empty means all) CountryCode string // CountryCode is the optional country code Limit int64 // Max number of URLs (<= 0 means no limit) }
OOAPIURLListConfig contains configuration for fetching the URL list.
type PrinterCallbacks ¶
type PrinterCallbacks struct {
Logger
}
PrinterCallbacks is the default event handler
func NewPrinterCallbacks ¶
func NewPrinterCallbacks(logger Logger) PrinterCallbacks
NewPrinterCallbacks returns a new default callback handler
func (PrinterCallbacks) OnProgress ¶
func (d PrinterCallbacks) OnProgress(percentage float64, message string)
OnProgress provides information about an experiment progress.
type QUICDialer ¶
type QUICDialer interface { // DialContext establishes a new QUIC session using the given // network and address. The tlsConfig and the quicConfig arguments // MUST NOT be nil. Returns either the session or an error. // // Recommended tlsConfig setup: // // - set ServerName to be the SNI; // // - set RootCAs to NewDefaultCertPool(); // // - set NextProtos to []string{"h3"}. // // Typically, you want to pass `&quic.Config{}` as quicConfig. DialContext(ctx context.Context, address string, tlsConfig *tls.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() }
QUICDialer dials QUIC sessions.
type QUICDialerWrapper ¶ added in v3.16.0
type QUICDialerWrapper interface {
WrapQUICDialer(qd QUICDialer) QUICDialer
}
QUICDialerWrapper is a type that takes in input a QUICDialer and returns in output a wrapped QUICDialer.
type QUICListener ¶
type QUICListener interface { // Listen creates a new listening UDPLikeConn. Listen(addr *net.UDPAddr) (UDPLikeConn, error) }
QUICListener listens for QUIC connections.
type Resolver ¶
type Resolver interface { // LookupHost behaves like net.Resolver.LookupHost. LookupHost(ctx context.Context, hostname string) (addrs []string, err error) // Network returns the resolver type. It should be one of: // // - go: means we're using whatever resolver the Go stdlib uses // depending on the current build configuration; // // - system: means we've been compiled with `CGO_ENABLED=1` // so we can bypass the go resolver and call getaddrinfo directly; // // - udp: is a custom DNS-over-UDP resolver; // // - tcp: is a custom DNS-over-TCP resolver; // // - dot: is a custom DNS-over-TLS resolver; // // - doh: is a custom DNS-over-HTTPS resolver; // // - doh3: is a custom DNS-over-HTTP3 resolver. // // See https://github.com/ooni/probe/issues/2029#issuecomment-1140805266 // for an explanation of why it would not be proper to call "netgo" the // resolver we get by default from the standard library. Network() string // Address returns the resolver address (e.g., 8.8.8.8:53). Address() string // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() // LookupHTTPS issues an HTTPS query for a domain. LookupHTTPS( ctx context.Context, domain string) (*HTTPSSvc, error) // LookupNS issues a NS query for a domain. LookupNS(ctx context.Context, domain string) ([]*net.NS, error) }
Resolver performs domain name resolutions.
type Saver ¶ added in v3.16.0
type Saver interface {
SaveMeasurement(m *Measurement) error
}
Saver saves a measurement on some persistent storage.
type SimpleDialer ¶
type SimpleDialer interface { // DialContext behaves like net.Dialer.DialContext. DialContext(ctx context.Context, network, address string) (net.Conn, error) }
SimpleDialer establishes network connections.
type Submitter ¶ added in v3.16.0
type Submitter interface { // Submit submits the measurement and updates its // report ID field in case of success. Submit(ctx context.Context, m *Measurement) error }
Submitter submits a measurement to the OONI collector.
type THDNSResult ¶ added in v3.16.0
type THDNSResult struct { Failure *string `json:"failure"` Addrs []string `json:"addrs"` ASNs []int64 `json:"-"` // not visible from the JSON }
THDNSResult is the result of the DNS lookup performed by the control vantage point.
type THHTTPRequestResult ¶ added in v3.16.0
type THHTTPRequestResult struct { BodyLength int64 `json:"body_length"` Failure *string `json:"failure"` Title string `json:"title"` Headers map[string]string `json:"headers"` StatusCode int64 `json:"status_code"` }
THHTTPRequestResult is the result of the HTTP request performed by the control vantage point.
type THIPInfo ¶ added in v3.16.0
type THIPInfo struct { // ASN contains the address' AS number. ASN int64 `json:"asn"` // Flags contains flags describing this address. Flags int64 `json:"flags"` }
THIPInfo contains information about IP addresses resolved either by the probe or by the TH and processed by the TH.
type THRequest ¶ added in v3.16.0
type THRequest struct { HTTPRequest string `json:"http_request"` HTTPRequestHeaders map[string][]string `json:"http_request_headers"` TCPConnect []string `json:"tcp_connect"` }
THRequest is the request that we send to the control
type THResponse ¶ added in v3.16.0
type THResponse struct { TCPConnect map[string]THTCPConnectResult `json:"tcp_connect"` TLSHandshake map[string]THTLSHandshakeResult `json:"tls_handshake,omitempty"` HTTPRequest THHTTPRequestResult `json:"http_request"` DNS THDNSResult `json:"dns"` IPInfo map[string]*THIPInfo `json:"ip_info,omitempty"` }
THResponse is the response from the control service.
type THTCPConnectResult ¶ added in v3.16.0
THTCPConnectResult is the result of the TCP connect attempt performed by the control vantage point.
type THTLSHandshakeResult ¶ added in v3.16.0
type THTLSHandshakeResult struct { ServerName string `json:"server_name"` Status bool `json:"status"` Failure *string `json:"failure"` }
THTLSHandshakeResult is the result of the TLS handshake attempt performed by the control vantage point.
type TLSDialer ¶
type TLSDialer interface { // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() // DialTLSContext dials a TLS connection. This method will always return // to you a oohttp.TLSConn, so you can always safely cast to it. DialTLSContext(ctx context.Context, network, address string) (net.Conn, error) }
TLSDialer is a Dialer dialing TLS connections.
type TLSHandshaker ¶
type TLSHandshaker interface { // Handshake creates a new TLS connection from the given connection and // the given config. This function DOES NOT take ownership of the connection // and it's your responsibility to close it on failure. // // Recommended tlsConfig setup: // // - set ServerName to be the SNI; // // - set RootCAs to NewDefaultCertPool(); // // - set NextProtos to []string{"h2", "http/1.1"} for HTTPS // and []string{"dot"} for DNS-over-TLS. // // QUIRK: The returned connection will always implement the TLSConn interface // exposed by ooni/oohttp. A future version of this interface may instead // return directly a TLSConn to avoid unconditional castings. Handshake(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) ( net.Conn, tls.ConnectionState, error) }
TLSHandshaker is the generic TLS handshaker.
type Trace ¶ added in v3.16.0
type Trace interface { // TimeNow returns the current time. Normally, this should be the same // value returned by time.Now but you may want to manipulate the time // returned when testing to have deterministic tests. To this end, you // can use functionality exported by the ./internal/testingx pkg. TimeNow() time.Time // MaybeWrapNetConn possibly wraps a net.Conn with the caller trace. If there's no // desire to wrap the net.Conn, this function just returns the original net.Conn. // // Arguments: // // - conn is the non-nil underlying net.Conn to be wrapped MaybeWrapNetConn(conn net.Conn) net.Conn // MaybeWrapUDPLikeConn is like MaybeWrapNetConn but for UDPLikeConn. // // Arguments: // // - conn is the non-nil underlying UDPLikeConn to be wrapped MaybeWrapUDPLikeConn(conn UDPLikeConn) UDPLikeConn // OnDNSRoundTripForLookupHost is used with a DNSTransport and called // when the RoundTrip terminates. // // Arguments: // // - started is when we called transport.RoundTrip // // - reso is the parent resolver for the trace; // // - query is the non-nil DNS query we use for the RoundTrip // // - response is a valid DNS response, obtained after the RoundTrip; // // - addrs is the list of addresses obtained after the RoundTrip, which // is empty if the RoundTrip failed // // - err is the result of DNSLookup; either an error or nil // // - finished is the time right after the RoundTrip OnDNSRoundTripForLookupHost(started time.Time, reso Resolver, query DNSQuery, response DNSResponse, addrs []string, err error, finished time.Time) // OnDelayedDNSResponse is used with a DNSOverUDPTransport and called // when we get delayed, unexpected DNS responses. // // Arguments: // // - started is when we started reading the delayed response; // // - txp is the DNS transport used with the resolver; // // - query is the non-nil DNS query we use for the RoundTrip; // // - response is the non-nil valid DNS response, obtained after some delay; // // - addrs is the list of addresses obtained after decoding the delayed response, // which is empty if the response did not contain any addresses, which we // extract by calling the DecodeLookupHost method. // // - err is the result of DecodeLookupHost: either an error or nil; // // - finished is when we have read the delayed response. OnDelayedDNSResponse(started time.Time, txp DNSTransport, query DNSQuery, resp DNSResponse, addrs []string, err error, finsihed time.Time) error // OnConnectDone is called when connect terminates. // // Arguments: // // - started is when we called connect; // // - network is the network we're using (one of "tcp" and "udp"); // // - domain is the domain for which we're calling connect. If the user called // connect for an IP address and a port, then domain will be an IP address; // // - remoteAddr is the TCP endpoint with which we are connecting: it will // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - err is the result of connect: either an error or nil; // // - finished is when connect returned. // // The error passed to this function will always be wrapped such that the // string returned by Error is an OONI error. OnConnectDone( started time.Time, network, domain, remoteAddr string, err error, finished time.Time) // OnTLSHandshakeStart is called when the TLS handshake starts. // // Arguments: // // - now is the moment before we start the handshake; // // - remoteAddr is the TCP endpoint with which we are connecting: it will // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - config is the non-nil TLS config we're using. OnTLSHandshakeStart(now time.Time, remoteAddr string, config *tls.Config) // OnTLSHandshakeDone is called when the TLS handshake terminates. // // Arguments: // // - started is when we started the handshake; // // - remoteAddr is the TCP endpoint with which we are connecting: it will // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - config is the non-nil TLS config we're using; // // - state is the state of the TLS connection after the handshake, where all // fields are zero-initialized if the handshake failed; // // - err is the result of the handshake: either an error or nil; // // - finished is right after the handshake. // // The error passed to this function will always be wrapped such that the // string returned by Error is an OONI error. OnTLSHandshakeDone(started time.Time, remoteAddr string, config *tls.Config, state tls.ConnectionState, err error, finished time.Time) // OnQUICHandshakeStart is called before the QUIC handshake. // // Arguments: // // - now is the moment before we start the handshake; // // - remoteAddr is the QUIC endpoint with which we are connecting: it will // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - config is the possibly-nil QUIC config we're using. OnQUICHandshakeStart(now time.Time, remoteAddr string, quicConfig *quic.Config) // OnQUICHandshakeDone is called after the QUIC handshake. // // Arguments: // // - started is when we started the handshake; // // - remoteAddr is the QUIC endpoint with which we are connecting: it will // consist of an IP address and a port (e.g., 8.8.8.8:443, [::1]:5421); // // - qconn is the QUIC connection we receive after the handshake: either // a valid quic.EarlyConnection or nil; // // - config is the non-nil TLS config we are using; // // - err is the result of the handshake: either an error or nil; // // - finished is right after the handshake. // // The error passed to this function will always be wrapped such that the // string returned by Error is an OONI error. OnQUICHandshakeDone(started time.Time, remoteAddr string, qconn quic.EarlyConnection, config *tls.Config, err error, finished time.Time) }
Trace allows to collect measurement traces. A trace is injected into netx operations using context.WithValue. Netx code retrieves the trace using context.Value. See docs/design/dd-003-step-by-step.md for the design document explaining why we implemented context-based tracing.
type UDPLikeConn ¶
type UDPLikeConn interface { // An UDPLikeConn is a net.PacketConn conn. net.PacketConn // SetReadBuffer allows setting the read buffer. SetReadBuffer(bytes int) error // SyscallConn returns a conn suitable for calling syscalls, // which is also instrumental to setting the read buffer. SyscallConn() (syscall.RawConn, error) }
UDPLikeConn is a net.PacketConn with some extra functions required to convince the QUIC library (lucas-clemente/quic-go) to inflate the receive buffer of the connection.
The QUIC library will treat this connection as a "dumb" net.PacketConn, calling its ReadFrom and WriteTo methods as opposed to more efficient methods that are available under Linux and (maybe?) FreeBSD.
It seems fine to avoid performance optimizations, because they would complicate the implementation on our side and our use cases (blocking and heavy throttling) do not seem to require such optimizations.
See https://github.com/ooni/probe/issues/1754 for a more comprehensive discussion of UDPLikeConn.