Initial commit: IT Nexus Web-App
This commit is contained in:
94
nexus-scanner/internal/modules/dnsreverse/dnsreverse.go
Normal file
94
nexus-scanner/internal/modules/dnsreverse/dnsreverse.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Package dnsreverse implements the dns_reverse module.
|
||||
// It enriches already-known hosts by performing reverse-DNS lookups
|
||||
// for every host that has no hostname recorded yet.
|
||||
package dnsreverse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cereda-systems/nexus-scanner/internal/db"
|
||||
)
|
||||
|
||||
// Module enriches hosts in the database with reverse-DNS hostnames.
|
||||
type Module struct {
|
||||
site string
|
||||
store *db.Store
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// New returns a new dns_reverse module.
|
||||
func New(site string, store *db.Store) *Module {
|
||||
return &Module{
|
||||
site: site,
|
||||
store: store,
|
||||
log: slog.With("module", "dns_reverse"),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Name() string { return "dns_reverse" }
|
||||
func (m *Module) Interval() time.Duration { return 10 * time.Minute }
|
||||
|
||||
// Run looks up hostnames for all hosts that have none recorded yet.
|
||||
func (m *Module) Run(ctx context.Context) error {
|
||||
scanID, err := m.store.BeginScan(m.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin scan: %w", err)
|
||||
}
|
||||
|
||||
scanErr := m.scan(ctx)
|
||||
if endErr := m.store.EndScan(scanID, scanErr); endErr != nil {
|
||||
m.log.Error("record scan end", "err", endErr)
|
||||
}
|
||||
return scanErr
|
||||
}
|
||||
|
||||
func (m *Module) scan(ctx context.Context) error {
|
||||
hosts, err := m.store.ListHosts("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("list hosts: %w", err)
|
||||
}
|
||||
|
||||
resolved := 0
|
||||
for _, h := range hosts {
|
||||
// Skip hosts that already have a hostname.
|
||||
if h.Hostname != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
names, err := net.DefaultResolver.LookupAddr(ctx, h.IP)
|
||||
if err != nil {
|
||||
// A lookup failure is not fatal — continue with the next host.
|
||||
m.log.Debug("reverse lookup failed", "ip", h.IP, "err", err)
|
||||
continue
|
||||
}
|
||||
if len(names) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// LookupAddr returns FQDNs with a trailing dot — strip it.
|
||||
hostname := strings.TrimSuffix(names[0], ".")
|
||||
|
||||
h.Hostname = hostname
|
||||
if err := m.store.UpsertHost(h); err != nil {
|
||||
m.log.Error("upsert host", "ip", h.IP, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
m.log.Info("hostname resolved", "ip", h.IP, "hostname", hostname)
|
||||
resolved++
|
||||
}
|
||||
|
||||
m.log.Info("scan complete", "checked", len(hosts), "resolved", resolved)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user