Compare commits

...

3 Commits

View File

@ -89,13 +89,22 @@ func config(input model.MeasurementTarget) (*Config, error) {
// TestKeys contains the experiment results
type TestKeys struct {
Queries []*model.ArchivalDNSLookupResult `json:"queries"`
TCPConnect []*model.ArchivalTCPConnectResult `json:"tcp_connect"`
TLSHandshakes []*model.ArchivalTLSOrQUICHandshakeResult `json:"tls_handshakes"`
SMTPErrors []*string `json:"smtp"`
NoOpCounter uint8 `json:"successful_noops"`
Queries []*model.ArchivalDNSLookupResult `json:"queries"`
Runs map[string]IndividualTestKeys `json:"runs"`
// Used for global failure (DNS resolution)
Failure string `json:"failure"`
// Used to indicate global failure state
Failed bool `json:"failed"`
}
// IndividualTestKeys contains results for TCP/IP level stuff for each address found
// in the DNS lookup
type IndividualTestKeys struct {
NoOpCounter uint8
TCPConnect []*model.ArchivalTCPConnectResult `json:"tcp_connect"`
TLSHandshakes []*model.ArchivalTLSOrQUICHandshakeResult `json:"tls_handshakes"`
// Individual failure aborting the test run for this address/port combo
Failure *string `json:"failure"`
}
type Measurer struct {
@ -117,21 +126,45 @@ func (m Measurer) ExperimentVersion() string {
return testVersion
}
// Manages sequential SMTP sessions to the same hostname (over different IPs)
// don't use in parallel because addr changed dynamically
type SMTPRunner struct {
trace *measurexlite.Trace
logger model.Logger
ctx context.Context
tk *TestKeys
tlsconfig *tls.Config
host string
port string
// addr is changed everytime SMTPRunner.conn(addr) is called
addr string
}
func (r SMTPRunner) resolve(host string) ([]string, bool) {
func (r *SMTPRunner) run_key() string {
return net.JoinHostPort(r.addr, r.port)
}
func (r *SMTPRunner) run_error(err error) {
r.tk.Failed = true
key := r.run_key()
// Key is initialized in conn() no need to check here
entry, _ := r.tk.Runs[key]
entry.Failure = tracex.NewFailure(err)
r.tk.Runs[key] = entry
}
func (r *SMTPRunner) global_error(err error) {
r.tk.Failed = true
r.tk.Failure = *tracex.NewFailure(err)
}
func (r *SMTPRunner) resolve(host string) ([]string, bool) {
r.logger.Infof("Resolving DNS for %s", host)
resolver := r.trace.NewStdlibResolver(r.logger)
addrs, err := resolver.LookupHost(r.ctx, host)
r.tk.Queries = append(r.tk.Queries, r.trace.DNSLookupsFromRoundTrip()...)
if err != nil {
r.tk.Failure = *tracex.NewFailure(err)
r.global_error(err)
return []string{}, false
}
r.logger.Infof("Finished DNS for %s: %v", host, addrs)
@ -139,66 +172,99 @@ func (r SMTPRunner) resolve(host string) ([]string, bool) {
return addrs, true
}
func (r SMTPRunner) conn(addr string, port string) (net.Conn, bool) {
func (r *SMTPRunner) get_run() IndividualTestKeys {
if r.tk.Runs == nil {
r.tk.Runs = make(map[string]IndividualTestKeys)
}
key := r.run_key()
val, exists := r.tk.Runs[key]
if exists {
return val
} else {
return IndividualTestKeys{}
}
}
func (r *SMTPRunner) save_run(itk IndividualTestKeys) {
key := r.run_key()
r.tk.Runs[key] = itk
}
func (r *SMTPRunner) conn(addr string) (net.Conn, bool) {
// Initialize addr field and corresponding errors in TestKeys
r.logger.Infof("Establishing TCP to %s", addr)
r.addr = addr
run := r.get_run()
dialer := r.trace.NewDialerWithoutResolver(r.logger)
conn, err := dialer.DialContext(r.ctx, "tcp", net.JoinHostPort(addr, port))
r.tk.TCPConnect = append(r.tk.TCPConnect, r.trace.TCPConnects()...)
conn, err := dialer.DialContext(r.ctx, "tcp", net.JoinHostPort(addr, r.port))
run.TCPConnect = append(run.TCPConnect, r.trace.TCPConnects()...)
if err != nil {
r.run_error(err)
return nil, false
}
r.save_run(run)
return conn, true
}
func (r SMTPRunner) handshake(conn net.Conn, host string, port string) (net.Conn, bool) {
r.logger.Infof("Starting TLS handshake with %s:%s", host, port)
func (r *SMTPRunner) handshake(conn net.Conn) (net.Conn, bool) {
r.logger.Infof("Starting TLS handshake with %s:%s (%s)", r.host, r.port, r.addr)
run := r.get_run()
thx := r.trace.NewTLSHandshakerStdlib(r.logger)
tconn, _, err := thx.Handshake(r.ctx, conn, r.tlsconfig)
r.tk.TLSHandshakes = append(r.tk.TLSHandshakes, r.trace.FirstTLSHandshakeOrNil())
run.TLSHandshakes = append(run.TLSHandshakes, r.trace.FirstTLSHandshakeOrNil())
if err != nil {
r.run_error(err)
return nil, false
}
r.save_run(run)
r.logger.Infof("Handshake succeeded")
return tconn, true
}
func (r SMTPRunner) starttls(conn net.Conn, host string, port string, message string) (net.Conn, bool) {
r.logger.Infof("Asking for StartTLS upgrade")
conn.Write([]byte(message))
tconn, success := r.handshake(conn, host, port)
func (r *SMTPRunner) starttls(conn net.Conn, message string) (net.Conn, bool) {
if message != "" {
r.logger.Infof("Asking for StartTLS upgrade")
conn.Write([]byte(message))
}
tconn, success := r.handshake(conn)
return tconn, success
}
func (r SMTPRunner) smtp(conn net.Conn, ehlo string, noop uint8) bool {
func (r *SMTPRunner) smtp(conn net.Conn, ehlo string, noop uint8) bool {
client, err := smtp.NewClient(conn, ehlo)
if err != nil {
r.tk.SMTPErrors = append(r.tk.SMTPErrors, []*string{tracex.NewFailure(err)}...)
r.run_error(err)
return false
}
err = client.Hello(ehlo)
if err != nil {
r.tk.SMTPErrors = append(r.tk.SMTPErrors, []*string{tracex.NewFailure(err)}...)
r.run_error(err)
return false
}
if noop > 0 {
run := r.get_run()
r.logger.Infof("Trying to generate more no-op traffic")
// TODO: noop counter per IP address
r.tk.NoOpCounter = 0
for r.tk.NoOpCounter < noop {
r.tk.NoOpCounter += 1
r.logger.Infof("NoOp Iteration %d", r.tk.NoOpCounter)
run.NoOpCounter = 0
for run.NoOpCounter < noop {
run.NoOpCounter += 1
r.logger.Infof("NoOp Iteration %d", run.NoOpCounter)
err = client.Noop()
if err != nil {
r.tk.SMTPErrors = append(r.tk.SMTPErrors, []*string{tracex.NewFailure(err)}...)
r.run_error(err)
break
}
}
if r.tk.NoOpCounter == noop {
r.save_run(run)
if run.NoOpCounter == noop {
r.logger.Infof("Successfully generated no-op traffic")
return true
} else {
r.logger.Infof("Failed no-op traffic at iteration %d", r.tk.NoOpCounter)
r.logger.Infof("Failed no-op traffic at iteration %d", run.NoOpCounter)
return false
}
}
@ -232,12 +298,15 @@ func (m Measurer) Run(
ServerName: config.host,
}
runner := SMTPRunner{
runner := &SMTPRunner{
trace: trace,
logger: log,
ctx: ctx,
tk: tk,
tlsconfig: &tlsconfig,
host: config.host,
port: config.port,
addr: "",
}
// First resolve DNS
@ -247,7 +316,7 @@ func (m Measurer) Run(
}
for _, addr := range addrs {
conn, success := runner.conn(addr, config.port)
conn, success := runner.conn(addr)
if !success {
return nil
}
@ -255,7 +324,7 @@ func (m Measurer) Run(
if config.forced_tls {
// Direct TLS connection
tconn, success := runner.handshake(conn, config.host, config.port)
tconn, success := runner.handshake(conn)
if !success {
continue
}
@ -272,7 +341,7 @@ func (m Measurer) Run(
}
// Upgrade via StartTLS and try EHLO + NoOps
tconn, success := runner.starttls(conn, config.host, config.port, "STARTTLS\n")
tconn, success := runner.starttls(conn, "STARTTLS\n")
if !success {
continue
}