Files
dnstest/backend/internal/checker/checker.go
robertas_stauskas a70f3262e0 Initial commit: DNS Test - DNS health checking tool
Go backend (miekg/dns) + Nuxt 3 frontend (Tailwind CSS v4).

8 check categories, 52 checks total:
- Overview: @ record, WWW, MX with ASN/provider lookup
- Domain Registration: expiry, registrar (RDAP + whois fallback)
- Parent Delegation: NS records, glue, consistency
- Nameservers: 17 checks (reachability, auth, recursion, TCP/UDP, AXFR, etc.)
- SOA: serial consistency, timing values
- Mail (MX): 11 checks (CNAME, PTR, public IPs, consistency)
- Mail Auth: SPF, DKIM, DMARC
- WWW: A record, CNAME

Features:
- SSE streaming (results appear as each category completes)
- SQLite history (modernc.org/sqlite)
- Rate limiting, CORS, request logging
- Dark mode, responsive design
2026-03-20 13:39:57 +02:00

146 lines
2.8 KiB
Go

package checker
import (
"sync"
"time"
"github.com/intodns/backend/internal/resolver"
)
// Checker orchestrates all DNS health checks for a domain.
type Checker struct {
resolver *resolver.Resolver
}
// NewChecker creates a Checker with a default Resolver.
func NewChecker() *Checker {
return &Checker{
resolver: resolver.NewResolver(),
}
}
// StreamEvent is sent for each completed category during streaming.
type StreamEvent struct {
Domain string `json:"domain,omitempty"`
Category Category `json:"category"`
Done bool `json:"done,omitempty"`
}
// Check runs all category checks in parallel and returns a Report.
func (c *Checker) Check(domain string) *Report {
start := time.Now()
type catResult struct {
index int
cat Category
}
var wg sync.WaitGroup
results := make(chan catResult, 8)
categories := c.categoryFuncs()
for _, cf := range categories {
wg.Add(1)
go func(idx int, fn func(string, *resolver.Resolver) Category) {
defer wg.Done()
cat := fn(domain, c.resolver)
results <- catResult{index: idx, cat: cat}
}(cf.index, cf.fn)
}
go func() {
wg.Wait()
close(results)
}()
ordered := make([]Category, len(categories))
for cr := range results {
ordered[cr.index] = cr.cat
}
var summary Summary
for _, cat := range ordered {
for _, check := range cat.Checks {
switch check.Status {
case StatusPass:
summary.Pass++
case StatusWarn:
summary.Warn++
case StatusFail:
summary.Fail++
case StatusInfo:
summary.Info++
}
}
}
return &Report{
Domain: domain,
Timestamp: time.Now().UTC().Format(time.RFC3339),
DurationMs: time.Since(start).Milliseconds(),
Summary: summary,
Categories: ordered,
}
}
// CheckStream runs all categories in parallel and sends each category as it completes.
func (c *Checker) CheckStream(domain string) <-chan StreamEvent {
out := make(chan StreamEvent, 6)
go func() {
defer close(out)
type catResult struct {
index int
cat Category
}
var wg sync.WaitGroup
results := make(chan catResult, 8)
categories := c.categoryFuncs()
for _, cf := range categories {
wg.Add(1)
go func(idx int, fn func(string, *resolver.Resolver) Category) {
defer wg.Done()
cat := fn(domain, c.resolver)
results <- catResult{index: idx, cat: cat}
}(cf.index, cf.fn)
}
go func() {
wg.Wait()
close(results)
}()
for cr := range results {
out <- StreamEvent{
Domain: domain,
Category: cr.cat,
}
}
}()
return out
}
type catFunc struct {
index int
fn func(string, *resolver.Resolver) Category
}
func (c *Checker) categoryFuncs() []catFunc {
return []catFunc{
{0, checkOverview},
{1, checkDomainWhois},
{2, checkParent},
{3, checkNS},
{4, checkSOA},
{5, checkMX},
{6, checkMail},
{7, checkWWW},
}
}