You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

309 lines
7.1 KiB

package main
import (
"context"
"fmt"
"math/rand"
"net"
"os"
"runtime"
"strconv"
"testing"
"time"
"github.com/erikh/ldnsd/config"
"github.com/erikh/ldnsd/proto"
"github.com/erikh/ldnsd/service"
"github.com/miekg/dns"
)
func init() {
seed := os.Getenv("TEST_SEED")
s, err := strconv.ParseInt(seed, 10, 64)
if err != nil {
s = time.Now().Unix()
fmt.Println("Seed:", s)
}
rand.Seed(s)
os.Remove("test.db")
}
const (
defaultCAFile = "/etc/ldnsd/rootCA.pem"
defaultCertFile = "/etc/ldnsd/client.pem"
defaultKeyFile = "/etc/ldnsd/client.key"
defaultDNSListen = "127.0.0.1:5300"
)
func msgClient(fqdn string) (*dns.Msg, error) {
m := new(dns.Msg)
m.SetQuestion(fqdn, dns.TypeA)
return dns.Exchange(m, defaultDNSListen)
}
func startService() (*service.Service, error) {
c := config.Empty()
c.DBFile = "test.db"
c.DNSListen = defaultDNSListen
srv, err := service.New("test-ldnsd", c)
if err != nil {
return nil, err
}
go srv.Boot()
time.Sleep(100 * time.Millisecond)
return srv, nil
}
func BenchmarkDNSSingleDomain(b *testing.B) {
srv, err := startService()
if err != nil {
b.Fatal(err)
}
defer os.Remove("test.db")
defer srv.Shutdown()
client, err := proto.NewClient(config.DefaultGRPCListen, defaultCAFile, defaultCertFile, defaultKeyFile)
if err != nil {
b.Fatal(err)
}
if _, err := client.SetA(context.Background(), &proto.Record{Host: "test", Address: "1.2.3.4"}); err != nil {
b.Fatal(err)
}
ip := net.ParseIP("1.2.3.4")
b.Run("test queries", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
m, err := msgClient("test.internal.")
if err != nil {
b.Log(err)
continue
}
aRecord := m.Answer[0].(*dns.A).A
if !aRecord.Equal(ip) {
b.Fatalf("IP %q does not match registered IP %q", aRecord, ip)
}
}
})
})
}
func randString(count, min int) string {
s := []rune{}
for i := 0; i < rand.Intn(count-min)+min; i++ {
s = append(s, rune('a'+rand.Intn(26)))
}
return string(s)
}
func BenchmarkRecordInsert(b *testing.B) {
srv, err := startService()
if err != nil {
b.Fatal(err)
}
defer os.Remove("test.db")
defer srv.Shutdown()
client, err := proto.NewClient(config.DefaultGRPCListen, defaultCAFile, defaultCertFile, defaultKeyFile)
if err != nil {
b.Fatal(err)
}
hostChan := make(chan string, runtime.NumCPU()*2)
hosts := map[string]struct{}{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func(ctx context.Context) {
for {
retry:
select {
case <-ctx.Done():
return
default:
}
host := randString(30, 3)
if _, ok := hosts[host]; ok {
goto retry
}
select {
case <-ctx.Done():
return
case hostChan <- host:
hosts[host] = struct{}{}
}
}
}(ctx)
b.Run("record insert", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if _, err := client.SetA(context.Background(), &proto.Record{Host: <-hostChan, Address: "1.2.3.4"}); err != nil {
b.Fatal(err)
}
}
})
})
}
func BenchmarkRecordInsertThenQuery(b *testing.B) {
srv, err := startService()
if err != nil {
b.Fatal(err)
}
defer os.Remove("test.db")
defer srv.Shutdown()
client, err := proto.NewClient(config.DefaultGRPCListen, defaultCAFile, defaultCertFile, defaultKeyFile)
if err != nil {
b.Fatal(err)
}
ip := net.ParseIP("1.2.3.4")
// no buffer here otherwise we won't be able to resolve the on-buffer items that will get pushed to the map
hostChan := make(chan string)
hosts := map[string]struct{}{}
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
retry:
select {
case <-ctx.Done():
return
default:
}
host := randString(30, 3)
if _, ok := hosts[host]; ok {
goto retry
}
select {
case <-ctx.Done():
return
case hostChan <- host:
hosts[host] = struct{}{}
}
}
}(ctx)
// BUG: not running this in parallel largely because I can't get the selects right above and it causes locking issues.
b.Run("record insert", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if _, err := client.SetA(context.Background(), &proto.Record{Host: <-hostChan, Address: "1.2.3.4"}); err != nil {
b.Fatal(err)
}
}
})
cancel()
hostChan = make(chan string, runtime.NumCPU()*2)
go func() {
for {
for key := range hosts {
hostChan <- key
}
}
}()
b.Run("query", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
m, err := msgClient(fmt.Sprintf("%s.internal.", <-hostChan))
if err != nil {
b.Log(err)
continue
}
aRecord := m.Answer[0].(*dns.A).A
if !aRecord.Equal(ip) {
b.Fatalf("IP %q does not match registered IP %q", aRecord, ip)
}
}
})
})
}
func TestProtoRecordValidation(t *testing.T) {
srv, err := startService()
if err != nil {
t.Fatal(err)
}
defer os.Remove("test.db")
defer srv.Shutdown()
table := map[string]struct {
r *proto.Record
success bool
}{
"basic": {
r: &proto.Record{Host: "test", Address: "127.0.0.1"},
success: true,
},
"empty host": {
r: &proto.Record{Host: "", Address: "127.0.0.1"},
success: false,
},
"empty ip": {
r: &proto.Record{Host: "test", Address: ""},
success: false,
},
"bad ip": {
r: &proto.Record{Host: "test", Address: "abcdefgh"},
success: false,
},
"ipv6 ip": {
r: &proto.Record{Host: "test", Address: "fe80::1"},
success: false,
},
"invalid ipv4 ip": {
r: &proto.Record{Host: "test", Address: "256.1.1.1"},
success: false,
},
"long string is looooooong": {
r: &proto.Record{Host: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Address: "fe80::1"},
success: false,
},
"long string is too looooooong": {
r: &proto.Record{Host: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Address: "fe80::1"},
success: false,
},
"long domain is looooooong": {
r: &proto.Record{Host: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Address: "127.0.0.1"},
success: true,
},
"long domain has a really long part": {
r: &proto.Record{Host: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", Address: "127.0.0.1"},
success: false,
},
}
client, err := proto.NewClient(config.DefaultGRPCListen, defaultCAFile, defaultCertFile, defaultKeyFile)
if err != nil {
t.Fatal(err)
}
for testName, result := range table {
_, resultErr := client.SetA(context.Background(), result.r)
if result.success && resultErr != nil {
t.Fatalf("Result for %q should be success but was %v", testName, resultErr)
}
if !result.success && resultErr == nil {
t.Fatalf("Result for %q should NOT be success but was.", testName)
}
}
}