From 22017dc2cde453fe0a5f052adaae1f20cf8e7741 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Tue, 29 Oct 2019 00:09:50 +0100 Subject: [PATCH] Returns follow-up experiment types for TLS Work related to ooni/probe-engine#87 --- cmd/httpclient/main.go | 7 +- x/nervoushandler/nervoushandler.go | 152 +++++++++++++++++++++++++++ x/nervousresolver/nervousresolver.go | 2 - 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 x/nervoushandler/nervoushandler.go diff --git a/cmd/httpclient/main.go b/cmd/httpclient/main.go index 6844be6..f5f4913 100644 --- a/cmd/httpclient/main.go +++ b/cmd/httpclient/main.go @@ -40,6 +40,7 @@ import ( "github.com/ooni/netx/httpx" "github.com/ooni/netx/model" "github.com/ooni/netx/x/logger" + "github.com/ooni/netx/x/nervoushandler" "github.com/ooni/netx/x/nervousresolver" ) @@ -110,11 +111,15 @@ func fetch(client *http.Client, url string) (err error) { }() req, err := http.NewRequest("GET", url, nil) rtx.PanicOnError(err, "http.NewRequest failed") + handler := nervoushandler.New(makehandler()) root := &model.MeasurementRoot{ Beginning: time.Now(), - Handler: makehandler(), + Handler: handler, LookupHost: nervousresolver.Default.LookupHost, } + defer func() { + fmt.Printf("%+v\n", handler.MustFollowup()) + }() ctx := model.WithMeasurementRoot(req.Context(), root) req = req.WithContext(ctx) resp, err := client.Do(req) diff --git a/x/nervoushandler/nervoushandler.go b/x/nervoushandler/nervoushandler.go new file mode 100644 index 0000000..b6f8d25 --- /dev/null +++ b/x/nervoushandler/nervoushandler.go @@ -0,0 +1,152 @@ +// Package nervoushandler contains OONI's nervous handler. +// +// This is currently experimental software. +package nervoushandler + +import ( + "errors" + "sync" + + "github.com/m-lab/go/rtx" + "github.com/ooni/netx/model" +) + +// Transaction contains the transaction summary. The purpose of such +// summary is to recap what we know about the domain -> address -> TLS +// handshake chain. This is inspired by a design document that I am +// working on with Vinicius Fortuna (Jigsaw). +type Transaction struct { + ResolveErr error + Domain string + ConnectErr error + Address string + TriedTLS bool + TLSErr error +} + +// Handler is the nervous handler. When Unreliable is nonzero, then +// some of our assumptions about events are broken. +type Handler struct { + All map[int64]*Transaction + Unrealiable int64 + child model.Handler + mu sync.Mutex +} + +// New creates a new handler +func New(handler model.Handler) *Handler { + return &Handler{ + All: make(map[int64]*Transaction), + child: handler, + } +} + +// OnMeasurement dispatches the measurements +func (h *Handler) OnMeasurement(m model.Measurement) { + defer h.child.OnMeasurement(m) + // Implementation note: measurements shall not contain more than + // one event inside each, but one never knows... + if m.ResolveDone != nil { + h.resolveDone(m.ResolveDone) + } + if m.Connect != nil { + h.connectDone(m.Connect) + } + if m.TLSHandshakeDone != nil { + h.tlsDone(m.TLSHandshakeDone) + } +} + +func (h *Handler) resolveDone(ev *model.ResolveDoneEvent) { + txID := ev.TransactionID + h.mu.Lock() + defer h.mu.Unlock() + if _, ok := h.All[txID]; ok { + h.Unrealiable++ // unclear what's happening + return + } + h.All[txID] = &Transaction{ + Domain: ev.Hostname, + ResolveErr: ev.Error, + } +} + +func (h *Handler) connectDone(ev *model.ConnectEvent) { + txID := ev.TransactionID + h.mu.Lock() + defer h.mu.Unlock() + if _, ok := h.All[txID]; !ok { + h.Unrealiable++ // unclear what's happening + return + } + h.All[txID].Address = ev.RemoteAddress + h.All[txID].ConnectErr = ev.Error +} + +func (h *Handler) tlsDone(ev *model.TLSHandshakeDoneEvent) { + txID := ev.TransactionID + h.mu.Lock() + defer h.mu.Unlock() + if _, ok := h.All[txID]; !ok { + h.Unrealiable++ // unclear what's happening + return + } + h.All[txID].TLSErr = ev.Error + h.All[txID].TriedTLS = true +} + +// ExperimentKind is the kind of follow-up experiment +type ExperimentKind string + +const ( + // SNIBlockingExperiment is an experiment where we perform a TLS handshake + // with a test helper, from the probe, using once the Domain that may be + // censored and a second time another domain. It is important that the test + // helper _is not_ using the IPAddress also provided as argument. + SNIBlockingExperiment = ExperimentKind("SNIBlocking") + + // IPValidForDomainExperiment is an experiment where from another vantage + // point we check whether we can establish a TLS connection using the + // specified Domain as SNI to the indicated IPAddress. + IPValidForDomainExperiment = ExperimentKind("IPValidForDomain") +) + +// FollowupInfo describes a follow-up measurement. The semantics of the +// Domain and IPAddress fields depends on the ExperimentKind. +type FollowupInfo struct { + Experiment ExperimentKind + Domain string + IPAddress string +} + +// Followup returns info the required follow-up measurements. +func (h *Handler) Followup() ([]FollowupInfo, error) { + h.mu.Lock() + defer h.mu.Unlock() + if h.Unrealiable != 0 { + return nil, errors.New("unreliable measurements") + } + var result []FollowupInfo + for _, ts := range h.All { + if ts.TriedTLS == false || ts.TLSErr != nil { + result = append(result, FollowupInfo{ + Experiment: SNIBlockingExperiment, + Domain: ts.Domain, + IPAddress: ts.Address, // see above for semantics + }) + result = append(result, FollowupInfo{ + Experiment: IPValidForDomainExperiment, + IPAddress: ts.Address, + Domain: ts.Domain, + }) + } + } + return result, nil +} + +// MustFollowup is like Followup but panics on error. +func (h *Handler) MustFollowup() []FollowupInfo { + result, err := h.Followup() + rtx.PanicOnError(err, "h.Followup failed") + return result +} diff --git a/x/nervousresolver/nervousresolver.go b/x/nervousresolver/nervousresolver.go index c8e0fa5..f7e35de 100644 --- a/x/nervousresolver/nervousresolver.go +++ b/x/nervousresolver/nervousresolver.go @@ -63,8 +63,6 @@ type bogonLookup struct { // design document on which we're working with Vinicius Fortuna, // Jigsaw, aimed at significantly improving OONI measurements quality. // -// See https://docs.google.com/document/d/1jcidvZGxBlucyLivAtvwrCidkIgb3yV6bhQIH_jf21M/edit?ts=5db300e4# -// // TODO(bassosimone): integrate more ideas from the design doc. func (c *Resolver) LookupHost(ctx context.Context, hostname string) ([]string, error) { addrs, err := c.primary.LookupHost(ctx, hostname)