1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

Add tracing annotations

This patch changes several internal packages to receive and pass tracing
annotations, making use of the new tracing library, so we can have
better debugging information.
This commit is contained in:
Alberto Bertogli
2022-03-07 01:43:58 +00:00
parent 9c6661eca2
commit 4a00a83c23
15 changed files with 151 additions and 80 deletions

View File

@@ -20,6 +20,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/config" "blitiri.com.ar/go/chasquid/internal/config"
"blitiri.com.ar/go/chasquid/internal/envelope" "blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/normalize" "blitiri.com.ar/go/chasquid/internal/normalize"
"blitiri.com.ar/go/chasquid/internal/trace"
"blitiri.com.ar/go/chasquid/internal/userdb" "blitiri.com.ar/go/chasquid/internal/userdb"
"golang.org/x/term" "golang.org/x/term"
"google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/encoding/prototext"
@@ -250,7 +251,10 @@ func aliasesResolve() {
} }
} }
rcpts, err := r.Resolve(args["$2"]) tr := trace.New("chasquid-util", "aliasesResolve")
defer tr.Finish()
rcpts, err := r.Resolve(tr, args["$2"])
if err != nil { if err != nil {
Fatalf("Error resolving: %v", err) Fatalf("Error resolving: %v", err)
} }
@@ -289,7 +293,9 @@ func domaininfoRemove() {
} }
} }
func allUsersExist(user, domain string) (bool, error) { return true, nil } func allUsersExist(tr *trace.Trace, user, domain string) (bool, error) {
return true, nil
}
// chasquid-util aliases-add <source> <target> // chasquid-util aliases-add <source> <target>
func aliasesAdd() { func aliasesAdd() {
@@ -327,8 +333,11 @@ func aliasesAdd() {
Fatalf("%s: error loading %q: %v", domain, aliasesFilePath, err) Fatalf("%s: error loading %q: %v", domain, aliasesFilePath, err)
} }
tr := trace.New("chasquid-util", "aliasesAdd")
defer tr.Finish()
// Check for existing entry. // Check for existing entry.
if _, ok := r.Exists(source); ok { if _, ok := r.Exists(tr, source); ok {
Fatalf("There's already an entry for %v", source) Fatalf("There's already an entry for %v", source)
} }

View File

@@ -12,8 +12,8 @@ and debugging tools should appear.
These include: These include:
- Command-line flags. - Command-line flags.
- [Traces](https://godoc.org/golang.org/x/net/trace) of both short and long - [Traces](https://pkg.go.dev/blitiri.com.ar/go/chasquid/internal/trace) of
lived requests (sampled). both short and long lived requests.
- State of the queue. - State of the queue.
- State of goroutines. - State of goroutines.
- [Exported variables](#variables) for whitebox monitoring. - [Exported variables](#variables) for whitebox monitoring.

View File

@@ -104,7 +104,7 @@ var (
) )
// Type of the "does this user exist" function", for convenience. // Type of the "does this user exist" function", for convenience.
type existsFn func(user, domain string) (bool, error) type existsFn func(tr *trace.Trace, user, domain string) (bool, error)
// Resolver represents the aliases resolver. // Resolver represents the aliases resolver.
type Resolver struct { type Resolver struct {
@@ -145,8 +145,8 @@ func NewResolver(userExists existsFn) *Resolver {
// Resolve the given address, returning the list of corresponding recipients // Resolve the given address, returning the list of corresponding recipients
// (if any). // (if any).
func (v *Resolver) Resolve(addr string) ([]Recipient, error) { func (v *Resolver) Resolve(tr *trace.Trace, addr string) ([]Recipient, error) {
tr := trace.New("Alias.Resolve", addr) tr = tr.NewChild("Alias.Resolve", addr)
defer tr.Finish() defer tr.Finish()
return v.resolve(0, addr, tr) return v.resolve(0, addr, tr)
} }
@@ -155,8 +155,8 @@ func (v *Resolver) Resolve(addr string) ([]Recipient, error) {
// It returns the cleaned address, and a boolean indicating the result. // It returns the cleaned address, and a boolean indicating the result.
// The clean address can be used to look it up in other databases, even if it // The clean address can be used to look it up in other databases, even if it
// doesn't exist. It must only be called for local addresses. // doesn't exist. It must only be called for local addresses.
func (v *Resolver) Exists(addr string) (string, bool) { func (v *Resolver) Exists(tr *trace.Trace, addr string) (string, bool) {
tr := trace.New("Alias.Exists", addr) tr = tr.NewChild("Alias.Exists", addr)
defer tr.Finish() defer tr.Finish()
addr = v.cleanIfLocal(addr) addr = v.cleanIfLocal(addr)
@@ -181,7 +181,7 @@ func (v *Resolver) lookup(addr string, tr *trace.Trace) ([]Recipient, error) {
v.mu.Unlock() v.mu.Unlock()
// Augment with the hook results. // Augment with the hook results.
hr, err := v.runResolveHook(addr) hr, err := v.runResolveHook(tr, addr)
if err != nil { if err != nil {
tr.Debugf("lookup(%q) hook error: %v", addr, err) tr.Debugf("lookup(%q) hook error: %v", addr, err)
return nil, err return nil, err
@@ -222,7 +222,7 @@ func (v *Resolver) resolve(rcount int, addr string, tr *trace.Trace) ([]Recipien
if len(rcpts) == 0 { if len(rcpts) == 0 {
tr.Debugf("%d| no alias found", rcount) tr.Debugf("%d| no alias found", rcount)
// If the user exists, then use it as-is, no need to recurse further. // If the user exists, then use it as-is, no need to recurse further.
ok, err := v.userExistsInDB(user, domain) ok, err := v.userExistsInDB(tr, user, domain)
if err != nil { if err != nil {
tr.Debugf("%d| error checking if user exists: %v", rcount, err) tr.Debugf("%d| error checking if user exists: %v", rcount, err)
return nil, err return nil, err
@@ -474,7 +474,7 @@ func removeChars(s, chars string) string {
return s return s
} }
func (v *Resolver) runResolveHook(addr string) ([]Recipient, error) { func (v *Resolver) runResolveHook(tr *trace.Trace, addr string) ([]Recipient, error) {
if v.ResolveHook == "" { if v.ResolveHook == "" {
hookResults.Add("resolve:notset", 1) hookResults.Add("resolve:notset", 1)
return nil, nil return nil, nil
@@ -486,7 +486,7 @@ func (v *Resolver) runResolveHook(addr string) ([]Recipient, error) {
} }
// TODO: this should be done via a context propagated all the way through. // TODO: this should be done via a context propagated all the way through.
tr := trace.New("Hook.Alias-Resolve", addr) tr = tr.NewChild("Hook.Alias-Resolve", addr)
defer tr.Finish() defer tr.Finish()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

View File

@@ -7,6 +7,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"blitiri.com.ar/go/chasquid/internal/trace"
) )
type Cases []struct { type Cases []struct {
@@ -17,8 +19,11 @@ type Cases []struct {
func (cases Cases) check(t *testing.T, r *Resolver) { func (cases Cases) check(t *testing.T, r *Resolver) {
t.Helper() t.Helper()
tr := trace.New("test", "check")
defer tr.Finish()
for _, c := range cases { for _, c := range cases {
got, err := r.Resolve(c.addr) got, err := r.Resolve(tr, c.addr)
if err != c.err { if err != c.err {
t.Errorf("case %q: expected error %v, got %v", t.Errorf("case %q: expected error %v, got %v",
c.addr, c.err, err) c.addr, c.err, err)
@@ -32,8 +37,10 @@ func (cases Cases) check(t *testing.T, r *Resolver) {
func mustExist(t *testing.T, r *Resolver, addrs ...string) { func mustExist(t *testing.T, r *Resolver, addrs ...string) {
t.Helper() t.Helper()
tr := trace.New("test", "mustExist")
defer tr.Finish()
for _, addr := range addrs { for _, addr := range addrs {
if _, ok := r.Exists(addr); !ok { if _, ok := r.Exists(tr, addr); !ok {
t.Errorf("address %q does not exist, it should", addr) t.Errorf("address %q does not exist, it should", addr)
} }
} }
@@ -41,18 +48,20 @@ func mustExist(t *testing.T, r *Resolver, addrs ...string) {
func mustNotExist(t *testing.T, r *Resolver, addrs ...string) { func mustNotExist(t *testing.T, r *Resolver, addrs ...string) {
t.Helper() t.Helper()
tr := trace.New("test", "mustNotExist")
defer tr.Finish()
for _, addr := range addrs { for _, addr := range addrs {
if _, ok := r.Exists(addr); ok { if _, ok := r.Exists(tr, addr); ok {
t.Errorf("address %q exists, it should not", addr) t.Errorf("address %q exists, it should not", addr)
} }
} }
} }
func allUsersExist(user, domain string) (bool, error) { func allUsersExist(tr *trace.Trace, user, domain string) (bool, error) {
return true, nil return true, nil
} }
func usersWithXDontExist(user, domain string) (bool, error) { func usersWithXDontExist(tr *trace.Trace, user, domain string) (bool, error) {
if strings.HasPrefix(user, "x") { if strings.HasPrefix(user, "x") {
return false, nil return false, nil
} }
@@ -61,7 +70,7 @@ func usersWithXDontExist(user, domain string) (bool, error) {
var errUserLookup = errors.New("test error errUserLookup") var errUserLookup = errors.New("test error errUserLookup")
func usersWithXErrorYDontExist(user, domain string) (bool, error) { func usersWithXErrorYDontExist(tr *trace.Trace, user, domain string) (bool, error) {
if strings.HasPrefix(user, "x") { if strings.HasPrefix(user, "x") {
return false, errUserLookup return false, errUserLookup
} }
@@ -200,6 +209,9 @@ func TestExistsRewrite(t *testing.T) {
mustExist(t, resolver, "abc@def", "a.bc+blah@def", "ño.ño@def") mustExist(t, resolver, "abc@def", "a.bc+blah@def", "ño.ño@def")
mustNotExist(t, resolver, "abc@d.ef", "nothere@def") mustNotExist(t, resolver, "abc@d.ef", "nothere@def")
tr := trace.New("test", "TestExistsRewrite")
defer tr.Finish()
cases := []struct { cases := []struct {
addr string addr string
expectAddr string expectAddr string
@@ -215,7 +227,7 @@ func TestExistsRewrite(t *testing.T) {
{"x.yz@d.ef", "x.yz@d.ef", false}, {"x.yz@d.ef", "x.yz@d.ef", false},
} }
for _, c := range cases { for _, c := range cases {
addr, exists := resolver.Exists(c.addr) addr, exists := resolver.Exists(tr, c.addr)
if addr != c.expectAddr { if addr != c.expectAddr {
t.Errorf("%q: expected addr %q, got %q", t.Errorf("%q: expected addr %q, got %q",
c.addr, c.expectAddr, addr) c.addr, c.expectAddr, addr)
@@ -236,7 +248,10 @@ func TestTooMuchRecursion(t *testing.T) {
"c@d": {{"a@b", EMAIL}}, "c@d": {{"a@b", EMAIL}},
} }
rs, err := resolver.Resolve("a@b") tr := trace.New("test", "TestTooMuchRecursion")
defer tr.Finish()
rs, err := resolver.Resolve(tr, "a@b")
if err != ErrRecursionLimitExceeded { if err != ErrRecursionLimitExceeded {
t.Errorf("expected ErrRecursionLimitExceeded, got %v", err) t.Errorf("expected ErrRecursionLimitExceeded, got %v", err)
} }
@@ -264,7 +279,10 @@ func TestTooMuchRecursionOnCatchAll(t *testing.T) {
cases.check(t, resolver) cases.check(t, resolver)
for _, addr := range []string{"a@dom", "x@dom", "xx@dom"} { for _, addr := range []string{"a@dom", "x@dom", "xx@dom"} {
rs, err := resolver.Resolve(addr) tr := trace.New("TestTooMuchRecursionOnCatchAll", addr)
defer tr.Finish()
rs, err := resolver.Resolve(tr, addr)
if err != ErrRecursionLimitExceeded { if err != ErrRecursionLimitExceeded {
t.Errorf("%s: expected ErrRecursionLimitExceeded, got %v", addr, err) t.Errorf("%s: expected ErrRecursionLimitExceeded, got %v", addr, err)
} }
@@ -319,6 +337,9 @@ func TestAddFile(t *testing.T) {
{"a:| \n", []Recipient{{"a@dom", EMAIL}}}, {"a:| \n", []Recipient{{"a@dom", EMAIL}}},
} }
tr := trace.New("test", "TestAddFile")
defer tr.Finish()
for _, c := range cases { for _, c := range cases {
fname := mustWriteFile(t, c.contents) fname := mustWriteFile(t, c.contents)
defer os.Remove(fname) defer os.Remove(fname)
@@ -329,7 +350,7 @@ func TestAddFile(t *testing.T) {
t.Fatalf("error adding file: %v", err) t.Fatalf("error adding file: %v", err)
} }
got, err := resolver.Resolve("a@dom") got, err := resolver.Resolve(tr, "a@dom")
if err != nil { if err != nil {
t.Errorf("case %q, got error: %v", c.contents, err) t.Errorf("case %q, got error: %v", c.contents, err)
continue continue
@@ -432,6 +453,9 @@ func TestManyFiles(t *testing.T) {
} }
func TestHookError(t *testing.T) { func TestHookError(t *testing.T) {
tr := trace.New("TestHookError", "test")
defer tr.Finish()
resolver := NewResolver(allUsersExist) resolver := NewResolver(allUsersExist)
resolver.AddDomain("localA") resolver.AddDomain("localA")
resolver.aliases = map[string][]Recipient{ resolver.aliases = map[string][]Recipient{
@@ -449,7 +473,7 @@ func TestHookError(t *testing.T) {
// Check that the hook is run and the error is propagated. // Check that the hook is run and the error is propagated.
mustNotExist(t, resolver, "a@localA") mustNotExist(t, resolver, "a@localA")
rcpts, err := resolver.Resolve("a@localA") rcpts, err := resolver.Resolve(tr, "a@localA")
if len(rcpts) != 0 { if len(rcpts) != 0 {
t.Errorf("expected no recipients, got %v", rcpts) t.Errorf("expected no recipients, got %v", rcpts)
} }

View File

@@ -65,8 +65,8 @@ func (a *Authenticator) Register(domain string, be Backend) {
} }
// Authenticate the user@domain with the given password. // Authenticate the user@domain with the given password.
func (a *Authenticator) Authenticate(user, domain, password string) (bool, error) { func (a *Authenticator) Authenticate(tr *trace.Trace, user, domain, password string) (bool, error) {
tr := trace.New("Auth.Authenticate", user+"@"+domain) tr = tr.NewChild("Auth.Authenticate", user+"@"+domain)
defer tr.Finish() defer tr.Finish()
// Make sure the call takes a.AuthDuration + 0-20% regardless of the // Make sure the call takes a.AuthDuration + 0-20% regardless of the
@@ -104,8 +104,8 @@ func (a *Authenticator) Authenticate(user, domain, password string) (bool, error
} }
// Exists checks that user@domain exists. // Exists checks that user@domain exists.
func (a *Authenticator) Exists(user, domain string) (bool, error) { func (a *Authenticator) Exists(tr *trace.Trace, user, domain string) (bool, error) {
tr := trace.New("Auth.Exists", user+"@"+domain) tr = tr.NewChild("Auth.Exists", user+"@"+domain)
defer tr.Finish() defer tr.Finish()
if be, ok := a.backends[domain]; ok { if be, ok := a.backends[domain]; ok {

View File

@@ -7,6 +7,7 @@ import (
"time" "time"
"blitiri.com.ar/go/chasquid/internal/dovecot" "blitiri.com.ar/go/chasquid/internal/dovecot"
"blitiri.com.ar/go/chasquid/internal/trace"
"blitiri.com.ar/go/chasquid/internal/userdb" "blitiri.com.ar/go/chasquid/internal/userdb"
) )
@@ -60,6 +61,8 @@ func TestDecodeResponse(t *testing.T) {
func TestAuthenticate(t *testing.T) { func TestAuthenticate(t *testing.T) {
db := userdb.New("/dev/null") db := userdb.New("/dev/null")
db.AddUser("user", "password") db.AddUser("user", "password")
tr := trace.New("test", "TestAuthenticate")
defer tr.Finish()
a := NewAuthenticator() a := NewAuthenticator()
a.Register("domain", WrapNoErrorBackend(db)) a.Register("domain", WrapNoErrorBackend(db))
@@ -73,7 +76,7 @@ func TestAuthenticate(t *testing.T) {
// Wrong password, but valid user@domain. // Wrong password, but valid user@domain.
ts := time.Now() ts := time.Now()
if ok, _ := a.Authenticate("user", "domain", "invalid"); ok { if ok, _ := a.Authenticate(tr, "user", "domain", "invalid"); ok {
t.Errorf("invalid password, but authentication succeeded") t.Errorf("invalid password, but authentication succeeded")
} }
if time.Since(ts) < a.AuthDuration { if time.Since(ts) < a.AuthDuration {
@@ -98,8 +101,10 @@ func TestAuthenticate(t *testing.T) {
func check(t *testing.T, a *Authenticator, user, domain, passwd string, expect bool) { func check(t *testing.T, a *Authenticator, user, domain, passwd string, expect bool) {
c := fmt.Sprintf("{%s@%s %s}", user, domain, passwd) c := fmt.Sprintf("{%s@%s %s}", user, domain, passwd)
ts := time.Now() ts := time.Now()
tr := trace.New("test", "check")
defer tr.Finish()
ok, err := a.Authenticate(user, domain, passwd) ok, err := a.Authenticate(tr, user, domain, passwd)
if time.Since(ts) < a.AuthDuration { if time.Since(ts) < a.AuthDuration {
t.Errorf("auth on %v was too fast", c) t.Errorf("auth on %v was too fast", c)
} }
@@ -110,7 +115,7 @@ func check(t *testing.T, a *Authenticator, user, domain, passwd string, expect b
t.Errorf("auth on %v: got error %v", c, err) t.Errorf("auth on %v: got error %v", c, err)
} }
ok, err = a.Exists(user, domain) ok, err = a.Exists(tr, user, domain)
if ok != expect { if ok != expect {
t.Errorf("exists on %v: got %v, expected %v", c, ok, expect) t.Errorf("exists on %v: got %v, expected %v", c, ok, expect)
} }
@@ -217,7 +222,10 @@ func TestErrors(t *testing.T) {
a.Register("domain", be) a.Register("domain", be)
a.AuthDuration = 0 a.AuthDuration = 0
ok, err := a.Authenticate("user", "domain", "passwd") tr := trace.New("test", "TestErrors")
defer tr.Finish()
ok, err := a.Authenticate(tr, "user", "domain", "passwd")
if err != nil || !ok { if err != nil || !ok {
t.Fatalf("failed auth") t.Fatalf("failed auth")
} }
@@ -225,7 +233,7 @@ func TestErrors(t *testing.T) {
expectedErr := fmt.Errorf("test error") expectedErr := fmt.Errorf("test error")
be.nextError = expectedErr be.nextError = expectedErr
ok, err = a.Authenticate("user", "domain", "passwd") ok, err = a.Authenticate(tr, "user", "domain", "passwd")
if ok { if ok {
t.Errorf("authentication succeeded, expected error") t.Errorf("authentication succeeded, expected error")
} }
@@ -233,7 +241,7 @@ func TestErrors(t *testing.T) {
t.Errorf("expected error, got %v", err) t.Errorf("expected error, got %v", err)
} }
ok, err = a.Exists("user", "domain") ok, err = a.Exists(tr, "user", "domain")
if ok { if ok {
t.Errorf("exists succeeded, expected error") t.Errorf("exists succeeded, expected error")
} }

View File

@@ -163,7 +163,7 @@ func (a *attempt) deliver(mx string) (error, bool) {
a.tr.Debugf("Insecure - NOT using TLS") a.tr.Debugf("Insecure - NOT using TLS")
} }
if !a.courier.Dinfo.OutgoingSecLevel(a.toDomain, secLevel) { if !a.courier.Dinfo.OutgoingSecLevel(a.tr, a.toDomain, secLevel) {
// We consider the failure transient, so transient misconfigurations // We consider the failure transient, so transient misconfigurations
// do not affect deliveries. // do not affect deliveries.
slcResults.Add("fail", 1) slcResults.Add("fail", 1)

View File

@@ -75,8 +75,8 @@ func (db *DB) Reload() error {
return nil return nil
} }
func (db *DB) write(d *Domain) { func (db *DB) write(tr *trace.Trace, d *Domain) {
tr := trace.New("DomainInfo.write", d.Name) tr = tr.NewChild("DomainInfo.write", d.Name)
defer tr.Finish() defer tr.Finish()
err := db.store.Put(d.Name, d) err := db.store.Put(d.Name, d)
@@ -89,8 +89,8 @@ func (db *DB) write(d *Domain) {
// IncomingSecLevel checks an incoming security level for the domain. // IncomingSecLevel checks an incoming security level for the domain.
// Returns true if allowed, false otherwise. // Returns true if allowed, false otherwise.
func (db *DB) IncomingSecLevel(domain string, level SecLevel) bool { func (db *DB) IncomingSecLevel(tr *trace.Trace, domain string, level SecLevel) bool {
tr := trace.New("DomainInfo.Incoming", domain) tr = tr.NewChild("DomainInfo.Incoming", domain)
defer tr.Finish() defer tr.Finish()
tr.Debugf("incoming at level %s", level) tr.Debugf("incoming at level %s", level)
@@ -101,7 +101,7 @@ func (db *DB) IncomingSecLevel(domain string, level SecLevel) bool {
if !exists { if !exists {
d = &Domain{Name: domain} d = &Domain{Name: domain}
db.info[domain] = d db.info[domain] = d
defer db.write(d) defer db.write(tr, d)
} }
if level < d.IncomingSecLevel { if level < d.IncomingSecLevel {
@@ -117,7 +117,7 @@ func (db *DB) IncomingSecLevel(domain string, level SecLevel) bool {
d.Name, level, d.IncomingSecLevel) d.Name, level, d.IncomingSecLevel)
d.IncomingSecLevel = level d.IncomingSecLevel = level
if exists { if exists {
defer db.write(d) defer db.write(tr, d)
} }
return true return true
} }
@@ -125,8 +125,8 @@ func (db *DB) IncomingSecLevel(domain string, level SecLevel) bool {
// OutgoingSecLevel checks an incoming security level for the domain. // OutgoingSecLevel checks an incoming security level for the domain.
// Returns true if allowed, false otherwise. // Returns true if allowed, false otherwise.
func (db *DB) OutgoingSecLevel(domain string, level SecLevel) bool { func (db *DB) OutgoingSecLevel(tr *trace.Trace, domain string, level SecLevel) bool {
tr := trace.New("DomainInfo.Outgoing", domain) tr = tr.NewChild("DomainInfo.Outgoing", domain)
defer tr.Finish() defer tr.Finish()
tr.Debugf("outgoing at level %s", level) tr.Debugf("outgoing at level %s", level)
@@ -137,7 +137,7 @@ func (db *DB) OutgoingSecLevel(domain string, level SecLevel) bool {
if !exists { if !exists {
d = &Domain{Name: domain} d = &Domain{Name: domain}
db.info[domain] = d db.info[domain] = d
defer db.write(d) defer db.write(tr, d)
} }
if level < d.OutgoingSecLevel { if level < d.OutgoingSecLevel {
@@ -153,7 +153,7 @@ func (db *DB) OutgoingSecLevel(domain string, level SecLevel) bool {
d.Name, level, d.OutgoingSecLevel) d.Name, level, d.OutgoingSecLevel)
d.OutgoingSecLevel = level d.OutgoingSecLevel = level
if exists { if exists {
defer db.write(d) defer db.write(tr, d)
} }
return true return true
} }

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"blitiri.com.ar/go/chasquid/internal/testlib" "blitiri.com.ar/go/chasquid/internal/testlib"
"blitiri.com.ar/go/chasquid/internal/trace"
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
@@ -13,14 +14,16 @@ func TestBasic(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tr := trace.New("test", "basic")
defer tr.Finish()
if !db.IncomingSecLevel("d1", SecLevel_PLAIN) { if !db.IncomingSecLevel(tr, "d1", SecLevel_PLAIN) {
t.Errorf("new domain as plain not allowed") t.Errorf("new domain as plain not allowed")
} }
if !db.IncomingSecLevel("d1", SecLevel_TLS_SECURE) { if !db.IncomingSecLevel(tr, "d1", SecLevel_TLS_SECURE) {
t.Errorf("increment to tls-secure not allowed") t.Errorf("increment to tls-secure not allowed")
} }
if db.IncomingSecLevel("d1", SecLevel_TLS_INSECURE) { if db.IncomingSecLevel(tr, "d1", SecLevel_TLS_INSECURE) {
t.Errorf("decrement to tls-insecure was allowed") t.Errorf("decrement to tls-insecure was allowed")
} }
@@ -29,7 +32,7 @@ func TestBasic(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if db2.IncomingSecLevel("d1", SecLevel_TLS_INSECURE) { if db2.IncomingSecLevel(tr, "d1", SecLevel_TLS_INSECURE) {
t.Errorf("decrement to tls-insecure was allowed in new DB") t.Errorf("decrement to tls-insecure was allowed in new DB")
} }
} }
@@ -41,6 +44,8 @@ func TestNewDomain(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tr := trace.New("test", "basic")
defer tr.Finish()
cases := []struct { cases := []struct {
domain string domain string
@@ -51,10 +56,10 @@ func TestNewDomain(t *testing.T) {
{"secure", SecLevel_TLS_SECURE}, {"secure", SecLevel_TLS_SECURE},
} }
for _, c := range cases { for _, c := range cases {
if !db.IncomingSecLevel(c.domain, c.level) { if !db.IncomingSecLevel(tr, c.domain, c.level) {
t.Errorf("domain %q not allowed (in) at %s", c.domain, c.level) t.Errorf("domain %q not allowed (in) at %s", c.domain, c.level)
} }
if !db.OutgoingSecLevel(c.domain, c.level) { if !db.OutgoingSecLevel(tr, c.domain, c.level) {
t.Errorf("domain %q not allowed (out) at %s", c.domain, c.level) t.Errorf("domain %q not allowed (out) at %s", c.domain, c.level)
} }
} }
@@ -67,6 +72,8 @@ func TestProgressions(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tr := trace.New("test", "basic")
defer tr.Finish()
cases := []struct { cases := []struct {
domain string domain string
@@ -85,11 +92,11 @@ func TestProgressions(t *testing.T) {
{"ssip", SecLevel_PLAIN, false}, {"ssip", SecLevel_PLAIN, false},
} }
for i, c := range cases { for i, c := range cases {
if ok := db.IncomingSecLevel(c.domain, c.lvl); ok != c.ok { if ok := db.IncomingSecLevel(tr, c.domain, c.lvl); ok != c.ok {
t.Errorf("%2d %q in attempt for %s failed: got %v, expected %v", t.Errorf("%2d %q in attempt for %s failed: got %v, expected %v",
i, c.domain, c.lvl, ok, c.ok) i, c.domain, c.lvl, ok, c.ok)
} }
if ok := db.OutgoingSecLevel(c.domain, c.lvl); ok != c.ok { if ok := db.OutgoingSecLevel(tr, c.domain, c.lvl); ok != c.ok {
t.Errorf("%2d %q out attempt for %s failed: got %v, expected %v", t.Errorf("%2d %q out attempt for %s failed: got %v, expected %v",
i, c.domain, c.lvl, ok, c.ok) i, c.domain, c.lvl, ok, c.ok)
} }
@@ -111,7 +118,10 @@ func TestErrors(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !db.IncomingSecLevel("d1", SecLevel_TLS_SECURE) { tr := trace.New("test", "basic")
defer tr.Finish()
if !db.IncomingSecLevel(tr, "d1", SecLevel_TLS_SECURE) {
t.Errorf("increment to tls-secure not allowed") t.Errorf("increment to tls-secure not allowed")
} }

View File

@@ -156,8 +156,12 @@ func (q *Queue) Len() int {
} }
// Put an envelope in the queue. // Put an envelope in the queue.
func (q *Queue) Put(from string, to []string, data []byte) (string, error) { func (q *Queue) Put(tr *trace.Trace, from string, to []string, data []byte) (string, error) {
tr = tr.NewChild("Queue.Put", from)
defer tr.Finish()
if q.Len() >= maxQueueSize { if q.Len() >= maxQueueSize {
tr.Errorf("queue full")
return "", errQueueFull return "", errQueueFull
} }
putCount.Add(1) putCount.Add(1)
@@ -174,7 +178,7 @@ func (q *Queue) Put(from string, to []string, data []byte) (string, error) {
for _, t := range to { for _, t := range to {
item.To = append(item.To, t) item.To = append(item.To, t)
rcpts, err := q.aliases.Resolve(t) rcpts, err := q.aliases.Resolve(tr, t)
if err != nil { if err != nil {
return "", fmt.Errorf("error resolving aliases for %q: %v", t, err) return "", fmt.Errorf("error resolving aliases for %q: %v", t, err)
} }
@@ -195,15 +199,16 @@ func (q *Queue) Put(from string, to []string, data []byte) (string, error) {
default: default:
log.Errorf("unknown alias type %v when resolving %q", log.Errorf("unknown alias type %v when resolving %q",
aliasRcpt.Type, t) aliasRcpt.Type, t)
return "", fmt.Errorf("internal error - unknown alias type") return "", tr.Errorf("internal error - unknown alias type")
} }
item.Rcpt = append(item.Rcpt, r) item.Rcpt = append(item.Rcpt, r)
tr.Debugf("recipient: %v", r.Address)
} }
} }
err := item.WriteTo(q.path) err := item.WriteTo(q.path)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to write item: %v", err) return "", tr.Errorf("failed to write item: %v", err)
} }
q.mu.Lock() q.mu.Lock()
@@ -213,6 +218,7 @@ func (q *Queue) Put(from string, to []string, data []byte) (string, error) {
// Begin to send it right away. // Begin to send it right away.
go item.SendLoop(q) go item.SendLoop(q)
tr.Debugf("queued")
return item.ID, nil return item.ID, nil
} }
@@ -450,7 +456,7 @@ func sendDSN(tr *trace.Trace, q *Queue, item *Item) {
return return
} }
id, err := q.Put("<>", []string{item.From}, msg) id, err := q.Put(tr, "<>", []string{item.From}, msg)
if err != nil { if err != nil {
tr.Errorf("failed to queue DSN: %v", err) tr.Errorf("failed to queue DSN: %v", err)
return return

View File

@@ -10,9 +10,12 @@ import (
"blitiri.com.ar/go/chasquid/internal/aliases" "blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/set" "blitiri.com.ar/go/chasquid/internal/set"
"blitiri.com.ar/go/chasquid/internal/testlib" "blitiri.com.ar/go/chasquid/internal/testlib"
"blitiri.com.ar/go/chasquid/internal/trace"
) )
func allUsersExist(user, domain string) (bool, error) { return true, nil } func allUsersExist(tr *trace.Trace, user, domain string) (bool, error) {
return true, nil
}
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
dir := testlib.MustTempDir(t) dir := testlib.MustTempDir(t)
@@ -22,10 +25,12 @@ func TestBasic(t *testing.T) {
q, _ := New(dir, set.NewString("loco"), q, _ := New(dir, set.NewString("loco"),
aliases.NewResolver(allUsersExist), aliases.NewResolver(allUsersExist),
localC, remoteC) localC, remoteC)
tr := trace.New("test", "TestBasic")
defer tr.Finish()
localC.Expect(2) localC.Expect(2)
remoteC.Expect(1) remoteC.Expect(1)
id, err := q.Put("from", []string{"am@loco", "x@remote", "nodomain"}, []byte("data")) id, err := q.Put(tr, "from", []string{"am@loco", "x@remote", "nodomain"}, []byte("data"))
if err != nil { if err != nil {
t.Fatalf("Put: %v", err) t.Fatalf("Put: %v", err)
} }
@@ -118,6 +123,8 @@ func TestAliases(t *testing.T) {
q, _ := New(dir, set.NewString("loco"), q, _ := New(dir, set.NewString("loco"),
aliases.NewResolver(allUsersExist), aliases.NewResolver(allUsersExist),
localC, remoteC) localC, remoteC)
tr := trace.New("test", "TestAliases")
defer tr.Finish()
q.aliases.AddDomain("loco") q.aliases.AddDomain("loco")
q.aliases.AddAliasForTesting("ab@loco", "pq@loco", aliases.EMAIL) q.aliases.AddAliasForTesting("ab@loco", "pq@loco", aliases.EMAIL)
@@ -128,7 +135,7 @@ func TestAliases(t *testing.T) {
localC.Expect(2) localC.Expect(2)
remoteC.Expect(1) remoteC.Expect(1)
_, err := q.Put("from", []string{"ab@loco", "cd@loco"}, []byte("data")) _, err := q.Put(tr, "from", []string{"ab@loco", "cd@loco"}, []byte("data"))
if err != nil { if err != nil {
t.Fatalf("Put: %v", err) t.Fatalf("Put: %v", err)
} }
@@ -163,6 +170,8 @@ func TestFullQueue(t *testing.T) {
q, _ := New(dir, set.NewString(), q, _ := New(dir, set.NewString(),
aliases.NewResolver(allUsersExist), aliases.NewResolver(allUsersExist),
testlib.DumbCourier, testlib.DumbCourier) testlib.DumbCourier, testlib.DumbCourier)
tr := trace.New("test", "TestFullQueue")
defer tr.Finish()
// Force-insert maxQueueSize items in the queue. // Force-insert maxQueueSize items in the queue.
oneID := "" oneID := ""
@@ -182,7 +191,7 @@ func TestFullQueue(t *testing.T) {
} }
// This one should fail due to the queue being too big. // This one should fail due to the queue being too big.
id, err := q.Put("from", []string{"to"}, []byte("data-qf")) id, err := q.Put(tr, "from", []string{"to"}, []byte("data-qf"))
if err != errQueueFull { if err != errQueueFull {
t.Errorf("Not failed as expected: %v - %v", id, err) t.Errorf("Not failed as expected: %v - %v", id, err)
} }
@@ -193,7 +202,7 @@ func TestFullQueue(t *testing.T) {
q.q[oneID].WriteTo(q.path) q.q[oneID].WriteTo(q.path)
q.Remove(oneID) q.Remove(oneID)
id, err = q.Put("from", []string{"to"}, []byte("data")) id, err = q.Put(tr, "from", []string{"to"}, []byte("data"))
if err != nil { if err != nil {
t.Errorf("Put: %v", err) t.Errorf("Put: %v", err)
} }
@@ -206,6 +215,7 @@ func TestPipes(t *testing.T) {
q, _ := New(dir, set.NewString("loco"), q, _ := New(dir, set.NewString("loco"),
aliases.NewResolver(allUsersExist), aliases.NewResolver(allUsersExist),
testlib.DumbCourier, testlib.DumbCourier) testlib.DumbCourier, testlib.DumbCourier)
item := &Item{ item := &Item{
Message: Message{ Message: Message{
ID: <-newID, ID: <-newID,

View File

@@ -488,8 +488,13 @@ func (c *Conn) checkSPF(addr string) (spf.Result, error) {
} }
if tcp, ok := c.remoteAddr.(*net.TCPAddr); ok { if tcp, ok := c.remoteAddr.(*net.TCPAddr); ok {
spfTr := c.tr.NewChild("SPF", tcp.IP.String())
defer spfTr.Finish()
res, err := spf.CheckHostWithSender( res, err := spf.CheckHostWithSender(
tcp.IP, envelope.DomainOf(addr), addr) tcp.IP, envelope.DomainOf(addr), addr,
spf.WithTraceFunc(func(f string, a ...interface{}) {
spfTr.Debugf(f, a...)
}))
c.tr.Debugf("SPF %v (%v)", res, err) c.tr.Debugf("SPF %v (%v)", res, err)
spfResultCount.Add(string(res), 1) spfResultCount.Add(string(res), 1)
@@ -519,7 +524,7 @@ func (c *Conn) secLevelCheck(addr string) bool {
level = domaininfo.SecLevel_TLS_CLIENT level = domaininfo.SecLevel_TLS_CLIENT
} }
ok := c.dinfo.IncomingSecLevel(domain, level) ok := c.dinfo.IncomingSecLevel(c.tr, domain, level)
if ok { if ok {
slcResults.Add("pass", 1) slcResults.Add("pass", 1)
c.tr.Debugf("security level check for %s passed (%s)", domain, level) c.tr.Debugf("security level check for %s passed (%s)", domain, level)
@@ -671,7 +676,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
// There are no partial failures here: we put it in the queue, and then if // There are no partial failures here: we put it in the queue, and then if
// individual deliveries fail, we report via email. // individual deliveries fail, we report via email.
// If we fail to queue, return a transient error. // If we fail to queue, return a transient error.
msgID, err := c.queue.Put(c.mailFrom, c.rcptTo, c.data) msgID, err := c.queue.Put(c.tr, c.mailFrom, c.rcptTo, c.data)
if err != nil { if err != nil {
return 451, fmt.Sprintf("4.3.0 Failed to queue message: %v", err) return 451, fmt.Sprintf("4.3.0 Failed to queue message: %v", err)
} }
@@ -1065,7 +1070,7 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
} }
// https://tools.ietf.org/html/rfc4954#section-6 // https://tools.ietf.org/html/rfc4954#section-6
authOk, err := c.authr.Authenticate(user, domain, passwd) authOk, err := c.authr.Authenticate(c.tr, user, domain, passwd)
if err != nil { if err != nil {
c.tr.Errorf("error authenticating %q@%q: %v", user, domain, err) c.tr.Errorf("error authenticating %q@%q: %v", user, domain, err)
maillog.Auth(c.remoteAddr, user+"@"+domain, false) maillog.Auth(c.remoteAddr, user+"@"+domain, false)
@@ -1093,7 +1098,7 @@ func (c *Conn) resetEnvelope() {
func (c *Conn) userExists(addr string) (bool, error) { func (c *Conn) userExists(addr string) (bool, error) {
var ok bool var ok bool
addr, ok = c.aliasesR.Exists(addr) addr, ok = c.aliasesR.Exists(c.tr, addr)
if ok { if ok {
return true, nil return true, nil
} }
@@ -1103,7 +1108,7 @@ func (c *Conn) userExists(addr string) (bool, error) {
// look up "user" in our databases if the domain is local, which is what // look up "user" in our databases if the domain is local, which is what
// we want. // we want.
user, domain := envelope.Split(addr) user, domain := envelope.Split(addr)
return c.authr.Exists(user, domain) return c.authr.Exists(c.tr, user, domain)
} }
func (c *Conn) readCommand() (cmd, params string, err error) { func (c *Conn) readCommand() (cmd, params string, err error) {

View File

@@ -12,6 +12,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/config" "blitiri.com.ar/go/chasquid/internal/config"
"blitiri.com.ar/go/chasquid/internal/expvarom" "blitiri.com.ar/go/chasquid/internal/expvarom"
"blitiri.com.ar/go/chasquid/internal/nettrace"
"blitiri.com.ar/go/log" "blitiri.com.ar/go/log"
"google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/encoding/prototext"
@@ -56,6 +57,7 @@ func launchMonitoringServer(conf *config.Config) {
http.HandleFunc("/metrics", expvarom.MetricsHandler) http.HandleFunc("/metrics", expvarom.MetricsHandler)
http.HandleFunc("/debug/flags", debugFlagsHandler) http.HandleFunc("/debug/flags", debugFlagsHandler)
http.HandleFunc("/debug/config", debugConfigHandler(conf)) http.HandleFunc("/debug/config", debugConfigHandler(conf))
http.HandleFunc("/debug/traces", nettrace.RenderTraces)
if err := srv.ListenAndServe(); err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Monitoring server failed: %v", err) log.Fatalf("Monitoring server failed: %v", err)
@@ -111,8 +113,7 @@ os hostname <i>{{.Hostname}}</i><br>
<li><a href="/debug/queue">queue</a> <li><a href="/debug/queue">queue</a>
<li>monitoring <li>monitoring
<ul> <ul>
<li><a href="/debug/requests?exp=1">requests (short-lived)</a> <li><a href="/debug/traces">traces</a>
<li><a href="/debug/events?exp=1">events (long-lived)</a>
<li><a href="https://blitiri.com.ar/p/chasquid/monitoring/#variables"> <li><a href="https://blitiri.com.ar/p/chasquid/monitoring/#variables">
exported variables</a>: exported variables</a>:
<a href="/debug/vars">expvar</a> <a href="/debug/vars">expvar</a>

View File

@@ -11,10 +11,9 @@ import (
"net/smtp" "net/smtp"
"time" "time"
"golang.org/x/net/trace"
_ "net/http/pprof" _ "net/http/pprof"
"blitiri.com.ar/go/chasquid/internal/nettrace"
"blitiri.com.ar/go/log" "blitiri.com.ar/go/log"
) )
@@ -80,13 +79,13 @@ func main() {
// C represents a single connection. // C represents a single connection.
type C struct { type C struct {
tr trace.Trace tr nettrace.Trace
n net.Conn n net.Conn
s *smtp.Client s *smtp.Client
} }
func newC() (*C, error) { func newC() (*C, error) {
tr := trace.New("conn", *addr) tr := nettrace.New("conn", *addr)
conn, err := net.Dial("tcp", *addr) conn, err := net.Dial("tcp", *addr)
if err != nil { if err != nil {

View File

@@ -15,8 +15,7 @@ import (
_ "net/http/pprof" _ "net/http/pprof"
"golang.org/x/net/trace" "blitiri.com.ar/go/chasquid/internal/nettrace"
"blitiri.com.ar/go/chasquid/internal/smtp" "blitiri.com.ar/go/chasquid/internal/smtp"
"blitiri.com.ar/go/log" "blitiri.com.ar/go/log"
) )
@@ -148,7 +147,7 @@ func serial(id int) {
} }
func one() error { func one() error {
tr := trace.New("one", *addr) tr := nettrace.New("one", *addr)
defer tr.Finish() defer tr.Finish()
conn, err := net.Dial("tcp", *addr) conn, err := net.Dial("tcp", *addr)