Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/clouddns/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/poyrazK/cloudDNS/internal/infrastructure/metrics"
)

// main is the entry point for the cloudDNS daemon.
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
Expand All @@ -36,6 +37,7 @@ func main() {
}
}

// run is the main entry point for the cloudDNS daemon, initializing all components.
func run(ctx context.Context) error {
runCtx, cancel := context.WithCancel(ctx)
defer cancel()
Expand Down Expand Up @@ -352,6 +354,7 @@ func run(ctx context.Context) error {
return nil
}

// getEnvUint32 returns an environment variable as uint32, or a default if unset/invalid.
func getEnvUint32(key string, def uint32) uint32 {
val := os.Getenv(key)
if val == "" {
Expand Down
2 changes: 2 additions & 0 deletions internal/adapters/routing/system_vip.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type commandExecutor interface {

type realExecutor struct{}

// Run executes a system command and returns its combined stdout/stderr output.
func (e *realExecutor) Run(ctx context.Context, name string, arg ...string) ([]byte, error) {
return exec.CommandContext(ctx, name, arg...).CombinedOutput()
}
Expand Down Expand Up @@ -114,6 +115,7 @@ func (a *SystemVIPAdapter) Unbind(ctx context.Context, vip, iface string) error
return nil
}

// handleUnsupportedOS returns an error indicating the current OS is not supported for VIP management.
func (a *SystemVIPAdapter) handleUnsupportedOS() error {
return fmt.Errorf("unsupported OS for VIP management: %s", a.os)
}
Expand Down
10 changes: 10 additions & 0 deletions internal/core/services/dns_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func NewDNSService(repo ports.DNSRepository, cache ports.CacheInvalidator) ports
}
}

// CreateZone creates a new DNS zone with default SOA and NS records.
func (s *dnsService) CreateZone(ctx context.Context, zone *domain.Zone) error {
zone.ID = uuid.New().String()
zone.CreatedAt = time.Now()
Expand Down Expand Up @@ -77,6 +78,7 @@ func (s *dnsService) CreateZone(ctx context.Context, zone *domain.Zone) error {
return nil
}

// CreateRecord creates a new DNS record and invalidates the cache.
func (s *dnsService) CreateRecord(ctx context.Context, record *domain.Record) error {
record.ID = uuid.New().String()
record.CreatedAt = time.Now()
Expand All @@ -101,6 +103,7 @@ func (s *dnsService) CreateRecord(ctx context.Context, record *domain.Record) er
return nil
}

// audit logs an action to the audit trail via the repository.
func (s *dnsService) audit(ctx context.Context, tenantID, action, resType, resID, details string) {
logEntry := &domain.AuditLog{
ID: uuid.New().String(),
Expand All @@ -116,6 +119,7 @@ func (s *dnsService) audit(ctx context.Context, tenantID, action, resType, resID
}
}

// Resolve looks up DNS records for a name and type, checking direct and wildcard matches.
func (s *dnsService) Resolve(ctx context.Context, name string, qType domain.RecordType, clientIP string) ([]domain.Record, error) {
// 1. Direct Match
records, err := s.repo.GetRecords(ctx, name, qType, clientIP)
Expand Down Expand Up @@ -150,6 +154,7 @@ func (s *dnsService) Resolve(ctx context.Context, name string, qType domain.Reco
return nil, nil
}

// filterHealthy returns only records that are not marked unhealthy, or all records if all are unhealthy.
func (s *dnsService) filterHealthy(records []domain.Record) []domain.Record {
var healthy []domain.Record
for _, rec := range records {
Expand All @@ -165,14 +170,17 @@ func (s *dnsService) filterHealthy(records []domain.Record) []domain.Record {
return healthy
}

// ListZones returns all zones belonging to a tenant.
func (s *dnsService) ListZones(ctx context.Context, tenantID string) ([]domain.Zone, error) {
return s.repo.ListZones(ctx, tenantID)
}

// ListRecordsForZone returns all records in a zone for a given tenant.
func (s *dnsService) ListRecordsForZone(ctx context.Context, zoneID string, tenantID string) ([]domain.Record, error) {
return s.repo.ListRecordsForZone(ctx, zoneID, tenantID)
}

// DeleteZone deletes a zone and all its records for a tenant.
func (s *dnsService) DeleteZone(ctx context.Context, zoneID string, tenantID string) error {
if err := s.repo.DeleteZone(ctx, zoneID, tenantID); err != nil {
return err
Expand All @@ -181,6 +189,7 @@ func (s *dnsService) DeleteZone(ctx context.Context, zoneID string, tenantID str
return nil
}

// DeleteRecord deletes a DNS record and invalidates the cache.
func (s *dnsService) DeleteRecord(ctx context.Context, recordID string, zoneID string, tenantID string) error {
// Fetch record details to invalidate the cache
record, err := s.repo.GetRecord(ctx, recordID, zoneID, tenantID)
Expand All @@ -206,6 +215,7 @@ func (s *dnsService) DeleteRecord(ctx context.Context, recordID string, zoneID s
return nil
}

// ImportZone parses a zone file and imports it for a tenant.
func (s *dnsService) ImportZone(ctx context.Context, tenantID string, r io.Reader) (*domain.Zone, error) {
parser := master.New()
data, err := parser.Parse(r)
Expand Down
4 changes: 4 additions & 0 deletions internal/core/services/health_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (m *HealthMonitor) Start(ctx context.Context, interval time.Duration) {
}
}

// runChecks fetches records in batches and probes their health status concurrently.
func (m *HealthMonitor) runChecks(ctx context.Context) {
const batchSize = 100

Expand Down Expand Up @@ -121,6 +122,7 @@ func (m *HealthMonitor) runChecks(ctx context.Context) {
}
}

// probeRecord checks a record's health and updates its status in the repository.
func (m *HealthMonitor) probeRecord(ctx context.Context, rec domain.Record) {
var status domain.HealthStatus
var errMsg string
Expand All @@ -139,6 +141,7 @@ func (m *HealthMonitor) probeRecord(ctx context.Context, rec domain.Record) {
}
}

// probeHTTP performs an HTTP health check and returns status and error message.
func (m *HealthMonitor) probeHTTP(ctx context.Context, target string) (domain.HealthStatus, string) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil)
if err != nil {
Expand All @@ -157,6 +160,7 @@ func (m *HealthMonitor) probeHTTP(ctx context.Context, target string) (domain.He
return domain.HealthStatusUnhealthy, fmt.Sprintf("HTTP status: %d", resp.StatusCode)
}

// probeTCP attempts a TCP connection and returns healthy if the connection succeeds.
func (m *HealthMonitor) probeTCP(ctx context.Context, target string) (domain.HealthStatus, string) {
dialer := &net.Dialer{Timeout: 3 * time.Second}
conn, err := dialer.DialContext(ctx, "tcp", target)
Expand Down
1 change: 1 addition & 0 deletions internal/dns/packet/dnssec.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func SignRRSet(records []DNSRecord, privKey any, algorithm uint8, signerName str
return sig, nil
}

// countLabels returns the number of DNS name labels (e.g., "www.example.com." has 3).
func countLabels(name string) int {
name = strings.TrimSuffix(name, ".")
if name == "" {
Expand Down
3 changes: 3 additions & 0 deletions internal/dns/server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/poyrazK/cloudDNS/internal/dns/packet"
)

// refreshZone initiates a zone refresh for a slave zone by querying the master for SOA.
func (s *Server) refreshZone(ctx context.Context, zone *domain.Zone) {
if zone.MasterServer == "" {
s.Logger.Warn("slave zone has no master server configured", "zone", zone.Name)
Expand Down Expand Up @@ -80,6 +81,7 @@ func (s *Server) refreshZone(ctx context.Context, zone *domain.Zone) {
}
}

// performIXFR performs an incremental zone transfer from the master server.
func (s *Server) performIXFR(ctx context.Context, zone *domain.Zone, masterAddr string, localSerial uint32) error {
conn, err := net.DialTimeout("tcp", masterAddr, 10*time.Second)
if err != nil {
Expand Down Expand Up @@ -257,6 +259,7 @@ func (s *Server) performIXFR(ctx context.Context, zone *domain.Zone, masterAddr
return nil
}

// performAXFR performs a full zone transfer from the master server.
func (s *Server) performAXFR(ctx context.Context, zone *domain.Zone, masterAddr string) error {
s.Logger.Info("starting AXFR", "zone", zone.Name, "master", masterAddr)

Expand Down
2 changes: 2 additions & 0 deletions internal/dns/server/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func (h *bucketIdleHeap) Pop() any {
return item
}

// newRateLimiter creates a new rate limiter with the given token rate, burst, and max bucket count.
func newRateLimiter(rate float64, burst int, maxBuckets int) *rateLimiter {
h := bucketIdleHeap{}
heap.Init(&h)
Expand All @@ -59,6 +60,7 @@ func newRateLimiter(rate float64, burst int, maxBuckets int) *rateLimiter {
}
}

// Allow checks if a request from the given IP is allowed under the token bucket limits.
func (rl *rateLimiter) Allow(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
Expand Down
8 changes: 8 additions & 0 deletions internal/dns/server/recursive.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type recursiveResolver struct {
// Defaults to 30s but can be overridden in tests.
var recursiveTimeout = 30 * time.Second

// newRecursiveResolver creates a recursive resolver with IANA root server hints and fallback resolvers.
func newRecursiveResolver() *recursiveResolver {
return &recursiveResolver{
rootHints: []string{
Expand All @@ -46,6 +47,7 @@ func newRecursiveResolver() *recursiveResolver {
}
}

// getShuffledRoots returns root hints rotated by a random offset for unpredictable ordering.
func (r *recursiveResolver) getShuffledRoots() []string {
shuffled := make([]string, len(r.rootHints))
copy(shuffled, r.rootHints)
Expand All @@ -59,6 +61,7 @@ func (r *recursiveResolver) getShuffledRoots() []string {
return result
}

// resolveRecursive performs iterative DNS resolution starting from root servers.
func (s *Server) resolveRecursive(name string, qType packet.QueryType) (*packet.DNSPacket, error) {
// Total timeout to prevent indefinite blocking on failing root servers
const errRecursiveTimeout = "recursive resolution timeout"
Expand Down Expand Up @@ -192,16 +195,19 @@ func (s *Server) resolveRecursive(name string, qType packet.QueryType) (*packet.
return nil, fmt.Errorf("recursion failed after trying roots and fallbacks: %w", lastErr)
}

// generateTransactionID returns a cryptographically random 16-bit DNS transaction ID.
func generateTransactionID() uint16 {
var id uint16
_ = binary.Read(rand.Reader, binary.BigEndian, &id)
return id
}

// sendQuery sends a UDP DNS query to the specified server without recursion desired.
func (s *Server) sendQuery(server string, name string, qType packet.QueryType) (*packet.DNSPacket, error) {
return s.sendQueryInternal(server, name, qType, false)
}

// sendQueryInternal sends a DNS query and validates the transaction ID in the response.
func (s *Server) sendQueryInternal(server string, name string, qType packet.QueryType, recursive bool) (*packet.DNSPacket, error) {
conn, err := net.DialTimeout("udp", server, 5*time.Second)
if err != nil {
Expand Down Expand Up @@ -250,6 +256,7 @@ func (s *Server) sendQueryInternal(server string, name string, qType packet.Quer
return resp, nil
}

// sendTCPQuery sends a TCP DNS query with a 2-byte length prefix.
func (s *Server) sendTCPQuery(server string, name string, qType packet.QueryType) (*packet.DNSPacket, error) {
conn, err := net.DialTimeout("tcp", server, 5*time.Second)
if err != nil {
Expand Down Expand Up @@ -297,6 +304,7 @@ func (s *Server) sendTCPQuery(server string, name string, qType packet.QueryType
return resp, nil
}

// findNextNS extracts the next nameserver address from a DNS response's authority and additional sections.
func (s *Server) findNextNS(resp *packet.DNSPacket) (string, bool) {
// 1. Look for NS records in the Authority section and their corresponding glue in Additional
for _, auth := range resp.Authorities {
Expand Down
1 change: 1 addition & 0 deletions internal/dns/server/reuseport_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package server

// setReusePort is a no-op on Windows (SO_REUSEPORT is not supported).
func setReusePort(fd uintptr) error {
return nil
}
Loading
Loading