package model // // Network extensions // import ( "context" "crypto/tls" "net" "net/http" "syscall" "time" "github.com/lucas-clemente/quic-go" ) // DNSResponse is a parsed DNS response ready for further processing. 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) } // The DNSDecoder decodes DNS responses. 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) } // DNSQuery is an encoded DNS query ready to be sent using a DNSTransport. 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 } // The DNSEncoder encodes DNS queries to bytes 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 } // DNSTransportWrapper is a type that takes in input a DNSTransport // and returns in output a wrapped DNSTransport. type DNSTransportWrapper interface { WrapDNSTransport(txp DNSTransport) DNSTransport } // DNSTransport represents an abstract DNS transport. 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() } // DialerWrapper is a type that takes in input a Dialer // and returns in output a wrapped Dialer. type DialerWrapper interface { WrapDialer(d Dialer) Dialer } // SimpleDialer establishes network connections. type SimpleDialer interface { // DialContext behaves like net.Dialer.DialContext. DialContext(ctx context.Context, network, address string) (net.Conn, error) } // Dialer is a SimpleDialer with the possibility of closing open connections. type Dialer interface { // A Dialer is also a SimpleDialer. SimpleDialer // CloseIdleConnections closes idle connections, if any. CloseIdleConnections() } // HTTPClient is an http.Client-like interface. type HTTPClient interface { Do(req *http.Request) (*http.Response, error) CloseIdleConnections() } // HTTPTransport is an http.Transport-like structure. 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() } // HTTPSSvc is the reply to an HTTPS DNS query. 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 } // QUICListener listens for QUIC connections. type QUICListener interface { // Listen creates a new listening UDPLikeConn. Listen(addr *net.UDPAddr) (UDPLikeConn, error) } // QUICDialerWrapper is a type that takes in input a QUICDialer // and returns in output a wrapped QUICDialer. type QUICDialerWrapper interface { WrapQUICDialer(qd QUICDialer) QUICDialer } // QUICDialer dials QUIC sessions. 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() } // Resolver performs domain name resolutions. 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) } // TLSDialer is a Dialer dialing TLS connections. 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) } // TLSHandshaker is the generic TLS handshaker. 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) } // 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 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) } // 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. 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) } // UnderlyingNetwork implements the underlying network APIs on // top of which we implement network extensions. type UnderlyingNetwork interface { // DialContext is equivalent to net.Dialer.DialContext except that // there is also an explicit timeout for dialing. DialContext(ctx context.Context, timeout time.Duration, network, address string) (net.Conn, error) // ListenUDP is equivalent to net.ListenUDP. ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error) // GetaddrinfoLookupANY is like net.Resolver.LookupHost except that it // also returns to the caller the CNAME when it is available. GetaddrinfoLookupANY(ctx context.Context, domain string) ([]string, string, error) // GetaddrinfoResolverNetwork returns the resolver network. GetaddrinfoResolverNetwork() string }