2018-02-13 10:48:46 +01:00
|
|
|
package nettests
|
|
|
|
|
|
|
|
import (
|
2018-03-19 16:23:30 +01:00
|
|
|
"encoding/json"
|
2018-03-22 15:22:29 +01:00
|
|
|
"fmt"
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-02-13 10:48:46 +01:00
|
|
|
"github.com/apex/log"
|
|
|
|
"github.com/measurement-kit/go-measurement-kit"
|
|
|
|
ooni "github.com/openobservatory/gooni"
|
2018-02-13 16:16:23 +01:00
|
|
|
"github.com/openobservatory/gooni/internal/cli/version"
|
2018-03-20 12:38:33 +01:00
|
|
|
"github.com/openobservatory/gooni/internal/colors"
|
2018-02-21 16:06:30 +01:00
|
|
|
"github.com/openobservatory/gooni/internal/database"
|
2018-03-22 15:22:29 +01:00
|
|
|
"github.com/openobservatory/gooni/internal/output"
|
2018-02-13 10:48:46 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Nettest interface. Every Nettest should implement this.
|
|
|
|
type Nettest interface {
|
|
|
|
Run(*Controller) error
|
2018-02-13 16:16:23 +01:00
|
|
|
Summary(map[string]interface{}) interface{}
|
2018-02-13 10:48:46 +01:00
|
|
|
LogSummary(string) error
|
|
|
|
}
|
|
|
|
|
2018-02-13 16:16:23 +01:00
|
|
|
// NewController creates a nettest controller
|
2018-03-19 16:23:30 +01:00
|
|
|
func NewController(nt Nettest, ctx *ooni.Context, res *database.Result, msmtPath string) *Controller {
|
2018-02-13 10:48:46 +01:00
|
|
|
return &Controller{
|
2018-03-19 16:23:30 +01:00
|
|
|
Ctx: ctx,
|
|
|
|
nt: nt,
|
|
|
|
res: res,
|
|
|
|
msmtPath: msmtPath,
|
2018-02-13 10:48:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-13 16:16:23 +01:00
|
|
|
// Controller is passed to the run method of every Nettest
|
2018-03-19 13:20:42 +01:00
|
|
|
// each nettest instance has one controller
|
2018-02-13 16:16:23 +01:00
|
|
|
type Controller struct {
|
2018-03-19 13:20:42 +01:00
|
|
|
Ctx *ooni.Context
|
|
|
|
res *database.Result
|
2018-03-19 16:23:30 +01:00
|
|
|
nt Nettest
|
|
|
|
msmts map[int64]*database.Measurement
|
2018-03-20 12:38:33 +01:00
|
|
|
msmtPath string // XXX maybe we can drop this and just use a temporary file
|
2018-02-13 16:16:23 +01:00
|
|
|
}
|
|
|
|
|
2018-02-13 10:48:46 +01:00
|
|
|
// Init should be called once to initialise the nettest
|
2018-03-19 13:20:42 +01:00
|
|
|
func (c *Controller) Init(nt *mk.Nettest) error {
|
2018-03-08 11:53:04 +01:00
|
|
|
log.Debugf("Init: %v", nt)
|
2018-03-23 12:41:06 +01:00
|
|
|
c.Ctx.LocationLookup()
|
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
c.msmts = make(map[int64]*database.Measurement)
|
2018-03-19 13:20:42 +01:00
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
msmtTemplate := database.Measurement{
|
|
|
|
ASN: "",
|
|
|
|
IP: "",
|
|
|
|
CountryCode: "",
|
|
|
|
ReportID: "",
|
|
|
|
Name: nt.Name,
|
|
|
|
ResultID: c.res.ID,
|
|
|
|
ReportFilePath: c.msmtPath,
|
|
|
|
}
|
|
|
|
|
2018-03-19 13:20:42 +01:00
|
|
|
log.Debugf("OutputPath: %s", c.msmtPath)
|
2018-02-13 16:16:23 +01:00
|
|
|
nt.Options = mk.NettestOptions{
|
2018-03-23 12:41:06 +01:00
|
|
|
IncludeIP: c.Ctx.Config.Sharing.IncludeIP,
|
|
|
|
IncludeASN: c.Ctx.Config.Sharing.IncludeASN,
|
|
|
|
IncludeCountry: c.Ctx.Config.Advanced.IncludeCountry,
|
|
|
|
|
|
|
|
ProbeCC: c.Ctx.Location.CountryCode,
|
|
|
|
ProbeASN: fmt.Sprintf("AS%d", c.Ctx.Location.ASN),
|
|
|
|
ProbeIP: c.Ctx.Location.IP,
|
|
|
|
|
2018-02-13 16:16:23 +01:00
|
|
|
DisableCollector: false,
|
|
|
|
SoftwareName: "ooniprobe",
|
|
|
|
SoftwareVersion: version.Version,
|
|
|
|
|
|
|
|
// XXX
|
|
|
|
GeoIPCountryPath: "",
|
2018-03-08 13:46:21 +01:00
|
|
|
GeoIPASNPath: "",
|
2018-03-19 13:20:42 +01:00
|
|
|
OutputPath: c.msmtPath,
|
2018-03-08 13:46:21 +01:00
|
|
|
CaBundlePath: "/etc/ssl/cert.pem",
|
2018-02-13 16:16:23 +01:00
|
|
|
}
|
2018-03-19 13:20:42 +01:00
|
|
|
|
2018-03-08 13:46:21 +01:00
|
|
|
nt.On("log", func(e mk.Event) {
|
2018-03-19 19:28:32 +01:00
|
|
|
level := e.Value.LogLevel
|
|
|
|
msg := e.Value.Message
|
2018-03-08 13:46:21 +01:00
|
|
|
|
|
|
|
switch level {
|
|
|
|
case "ERROR":
|
|
|
|
log.Error(msg)
|
|
|
|
case "INFO":
|
|
|
|
log.Info(msg)
|
|
|
|
default:
|
|
|
|
log.Debug(msg)
|
2018-02-21 16:06:30 +01:00
|
|
|
}
|
2018-03-08 13:46:21 +01:00
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.queued", func(e mk.Event) {
|
|
|
|
log.Debugf("%s", e.Key)
|
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.started", func(e mk.Event) {
|
|
|
|
log.Debugf("%s", e.Key)
|
2018-02-13 16:16:23 +01:00
|
|
|
})
|
2018-03-08 13:46:21 +01:00
|
|
|
|
|
|
|
nt.On("status.report_created", func(e mk.Event) {
|
|
|
|
log.Debugf("%s", e.Key)
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
msmtTemplate.ReportID = e.Value.ReportID
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.geoip_lookup", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
msmtTemplate.ASN = e.Value.ProbeASN
|
|
|
|
msmtTemplate.IP = e.Value.ProbeIP
|
|
|
|
msmtTemplate.CountryCode = e.Value.ProbeCC
|
2018-03-19 16:23:30 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.measurement_started", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
idx := e.Value.Idx
|
|
|
|
msmt, err := database.CreateMeasurement(c.Ctx.DB, msmtTemplate, e.Value.Input)
|
2018-03-19 16:23:30 +01:00
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Failed to create measurement")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.msmts[idx] = msmt
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.progress", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 19:28:32 +01:00
|
|
|
c.OnProgress(e.Value.Percentage, e.Value.Message)
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.update.*", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("failure.measurement", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
c.msmts[e.Value.Idx].Failed(c.Ctx.DB, e.Value.Failure)
|
2018-03-19 16:23:30 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("failure.measurement_submission", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-19 19:28:32 +01:00
|
|
|
failure := e.Value.Failure
|
|
|
|
c.msmts[e.Value.Idx].UploadFailed(c.Ctx.DB, failure)
|
2018-03-19 16:23:30 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("status.measurement_uploaded", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-20 12:38:33 +01:00
|
|
|
if err := c.msmts[e.Value.Idx].UploadSucceeded(c.Ctx.DB); err != nil {
|
|
|
|
log.WithError(err).Error("failed to mark msmt as uploaded")
|
|
|
|
}
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
nt.On("status.measurement_done", func(e mk.Event) {
|
2018-03-20 12:38:33 +01:00
|
|
|
log.Debugf(colors.Red(e.Key))
|
2018-03-19 16:23:30 +01:00
|
|
|
|
2018-03-20 12:38:33 +01:00
|
|
|
if err := c.msmts[e.Value.Idx].Done(c.Ctx.DB); err != nil {
|
|
|
|
log.WithError(err).Error("failed to mark msmt as done")
|
|
|
|
}
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
nt.On("measurement", func(e mk.Event) {
|
2018-03-19 19:28:32 +01:00
|
|
|
c.OnEntry(e.Value.Idx, e.Value.JSONStr)
|
2018-03-08 13:46:21 +01:00
|
|
|
})
|
|
|
|
|
2018-03-20 12:38:33 +01:00
|
|
|
nt.On("status.end", func(e mk.Event) {
|
|
|
|
log.Debugf("status.end")
|
|
|
|
for idx, msmt := range c.msmts {
|
|
|
|
log.Debugf("adding msmt#%d to result", idx)
|
|
|
|
if err := msmt.AddToResult(c.Ctx.DB, c.res); err != nil {
|
|
|
|
log.WithError(err).Error("failed to add to result")
|
|
|
|
}
|
|
|
|
}
|
2018-03-19 13:20:42 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
2018-02-13 10:48:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnProgress should be called when a new progress event is available.
|
2018-03-08 13:46:21 +01:00
|
|
|
func (c *Controller) OnProgress(perc float64, msg string) {
|
2018-02-13 10:48:46 +01:00
|
|
|
log.Debugf("OnProgress: %f - %s", perc, msg)
|
2018-03-22 15:22:29 +01:00
|
|
|
|
|
|
|
key := fmt.Sprintf("%T", c.nt)
|
|
|
|
output.Progress(key, perc, msg)
|
2018-02-13 10:48:46 +01:00
|
|
|
}
|
|
|
|
|
2018-03-19 16:23:30 +01:00
|
|
|
// Entry is an opaque measurement entry
|
|
|
|
type Entry struct {
|
|
|
|
TestKeys map[string]interface{} `json:"test_keys"`
|
|
|
|
}
|
|
|
|
|
2018-02-13 10:48:46 +01:00
|
|
|
// OnEntry should be called every time there is a new entry
|
2018-03-19 16:23:30 +01:00
|
|
|
func (c *Controller) OnEntry(idx int64, jsonStr string) {
|
2018-03-19 19:28:32 +01:00
|
|
|
log.Debugf("OnEntry")
|
2018-03-19 16:23:30 +01:00
|
|
|
|
|
|
|
var entry Entry
|
|
|
|
json.Unmarshal([]byte(jsonStr), &entry)
|
|
|
|
summary := c.nt.Summary(entry.TestKeys)
|
|
|
|
summaryBytes, err := json.Marshal(summary)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("failed to serialize summary")
|
|
|
|
}
|
2018-03-19 19:28:32 +01:00
|
|
|
log.Debugf("Fetching: %s %v", idx, c.msmts[idx])
|
2018-03-19 16:23:30 +01:00
|
|
|
c.msmts[idx].WriteSummary(c.Ctx.DB, string(summaryBytes))
|
2018-02-13 10:48:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// MKStart is the interface for the mk.Nettest Start() function
|
|
|
|
type MKStart func(name string) (chan bool, error)
|
|
|
|
|
2018-02-21 16:06:30 +01:00
|
|
|
// Start should be called to start the test
|
2018-02-13 10:48:46 +01:00
|
|
|
func (c *Controller) Start(f MKStart) {
|
|
|
|
log.Debugf("MKStart: %s", f)
|
|
|
|
}
|