Skip to content
This repository has been archived by the owner on Mar 6, 2020. It is now read-only.

Commit

Permalink
Returns follow-up experiment types for TLS
Browse files Browse the repository at this point in the history
Work related to ooni/probe-engine#87
  • Loading branch information
bassosimone committed Oct 28, 2019
1 parent cb398ac commit 991be9c
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 1 deletion.
7 changes: 6 additions & 1 deletion cmd/httpclient/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand Down
154 changes: 154 additions & 0 deletions x/nervoushandler/nervoushandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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 Fortuna Vinicius (Jigsaw). See:
//
// https://docs.google.com/document/d/1jcidvZGxBlucyLivAtvwrCidkIgb3yV6bhQIH_jf21M/edit?ts=5db300e4#
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
}

0 comments on commit 991be9c

Please sign in to comment.