146 lines
4.7 KiB
Go
146 lines
4.7 KiB
Go
package webconnectivity
|
|
|
|
//
|
|
// HTTP core analysis
|
|
//
|
|
|
|
import (
|
|
"net/url"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
// analysisHTTPToplevel is the toplevel analysis function for HTTP results.
|
|
//
|
|
// This function's job is to determine whether there were unexpected TLS
|
|
// handshake results (compared to what the TH observed), or unexpected
|
|
// failures during HTTP round trips (using the TH as benchmark), or whether
|
|
// the obtained body differs from the one obtained by the TH.
|
|
//
|
|
// This results in possibly setting these XBlockingFlags:
|
|
//
|
|
// - analysisFlagTLSBlocking
|
|
//
|
|
// - analysisFlagHTTPBlocking
|
|
//
|
|
// - analysisFlagHTTPDiff
|
|
//
|
|
// In websteps fashion, we don't stop at the first failure, rather we
|
|
// process all the available data and evaluate all possible errors.
|
|
func (tk *TestKeys) analysisHTTPToplevel(logger model.Logger) {
|
|
// don't perform any analysis without TH data
|
|
if tk.Control == nil || tk.ControlRequest == nil {
|
|
return
|
|
}
|
|
ctrl := tk.Control.HTTPRequest
|
|
|
|
// don't perform any analysis if the TH's HTTP measurement failed
|
|
if ctrl.Failure != nil {
|
|
return
|
|
}
|
|
|
|
// determine whether the original URL was HTTPS
|
|
origURL, err := url.Parse(tk.ControlRequest.HTTPRequest)
|
|
if err != nil {
|
|
return // this seeems like a bug?
|
|
}
|
|
isHTTPS := origURL.Scheme == "https"
|
|
|
|
// determine whether we had any TLS handshake issue and, in such a case,
|
|
// declare that we had a case of "http-failure" through TLS.
|
|
//
|
|
// Note that this would eventually count as an "http-failure" for .Blocking
|
|
// because Web Connectivity did not have a concept of TLS based blocking.
|
|
if tk.hasWellKnownTLSHandshakeIssues(isHTTPS, logger) {
|
|
tk.BlockingFlags |= analysisFlagTLSBlocking
|
|
// continue processing
|
|
}
|
|
|
|
// determine whether we had well known cleartext HTTP round trip issues
|
|
// and, in such a case, declare we had an "http-failure".
|
|
if tk.hasWellKnownHTTPRoundTripIssues(logger) {
|
|
tk.BlockingFlags |= analysisFlagHTTPBlocking
|
|
// continue processing
|
|
}
|
|
|
|
// if we don't have any request to check, there's not much more we
|
|
// can actually do here, so let's just return.
|
|
if len(tk.Requests) <= 0 {
|
|
return
|
|
}
|
|
|
|
// if the request has failed in any other way, we don't know. By convention, the first
|
|
// entry in the tk.Requests array is the last entry that was measured.
|
|
finalRequest := tk.Requests[0]
|
|
if finalRequest.Failure != nil {
|
|
return
|
|
}
|
|
|
|
// fallback to the HTTP diff algo.
|
|
tk.analysisHTTPDiff(logger, finalRequest, &ctrl)
|
|
}
|
|
|
|
// hasWellKnownTLSHandshakeIssues returns true in case we observed
|
|
// a set of well-known issues during the TLS handshake.
|
|
func (tk *TestKeys) hasWellKnownTLSHandshakeIssues(isHTTPS bool, logger model.Logger) (result bool) {
|
|
// TODO(bassosimone): we should return TLS information in the TH
|
|
// such that we can perform a TCP-like check. For now, instead, we
|
|
// only perform comparison when the initial URL was HTTPS. Given
|
|
// that we unconditionally check for HTTPS even when the URL is HTTP,
|
|
// we cannot blindly treat all TLS errors as blocking. A website
|
|
// may just not have HTTPS. While in the obvious cases we will see
|
|
// certificate errors, in some cases it may actually timeout.
|
|
if isHTTPS {
|
|
for _, thx := range tk.TLSHandshakes {
|
|
fail := thx.Failure
|
|
if fail == nil {
|
|
continue // this handshake succeded, so skip it
|
|
}
|
|
switch *fail {
|
|
case netxlite.FailureConnectionReset,
|
|
netxlite.FailureGenericTimeoutError,
|
|
netxlite.FailureEOFError,
|
|
netxlite.FailureSSLInvalidHostname,
|
|
netxlite.FailureSSLInvalidCertificate,
|
|
netxlite.FailureSSLUnknownAuthority:
|
|
logger.Warnf(
|
|
"TLS: endpoint %s fails with %s (see #%d)",
|
|
thx.Address, *fail, thx.TransactionID,
|
|
)
|
|
result = true // flip the result but continue looping so we print them all
|
|
default:
|
|
// check next handshake
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// hasWellKnownHTTPRoundTripIssues checks whether any HTTP round
|
|
// trip failed in a well-known suspicious way
|
|
func (tk *TestKeys) hasWellKnownHTTPRoundTripIssues(logger model.Logger) (result bool) {
|
|
for _, rtx := range tk.Requests {
|
|
fail := rtx.Failure
|
|
if fail == nil {
|
|
// This one succeded, so skip it. Note that, in principle, we know
|
|
// the fist entry is the last request occurred, but I really do not
|
|
// want to embed this bad assumption in one extra place!
|
|
continue
|
|
}
|
|
switch *fail {
|
|
case netxlite.FailureConnectionReset,
|
|
netxlite.FailureGenericTimeoutError,
|
|
netxlite.FailureEOFError:
|
|
logger.Warnf(
|
|
"TLS: endpoint %s fails with %s (see #%d)",
|
|
"N/A", *fail, rtx.TransactionID, // TODO(bassosimone): implement
|
|
)
|
|
result = true // flip the result but continue looping so we print them all
|
|
default:
|
|
// check next round trip
|
|
}
|
|
}
|
|
return
|
|
}
|