Files
dip-ids/internal/analyzer/dns.go
T
2026-04-12 22:26:26 +04:00

148 lines
3.7 KiB
Go

package analyzer
import (
"fmt"
"strings"
"sync"
"time"
"dip-ids/internal/config"
"dip-ids/internal/storage"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// dnsAnomalyDetector обнаруживает аномалии DNS:
// 1. Слишком длинные метки (признак DNS-туннелирования)
// 2. Аномально высокая частота запросов от одного источника
type dnsAnomalyDetector struct {
mu sync.Mutex
cfg config.DNSAnomalyConfig
tracker map[string][]time.Time // srcIP -> query timestamps
alerted map[string]time.Time
}
func NewDNSAnomalyDetector(cfg config.DNSAnomalyConfig) Detector {
return &dnsAnomalyDetector{
cfg: cfg,
tracker: make(map[string][]time.Time),
alerted: make(map[string]time.Time),
}
}
func (d *dnsAnomalyDetector) Name() string { return "dns_anomaly" }
func (d *dnsAnomalyDetector) Analyze(pkt gopacket.Packet) []storage.Alert {
if !d.cfg.Enabled {
return nil
}
netLayer := pkt.NetworkLayer()
dnsLayer := pkt.Layer(layers.LayerTypeDNS)
if netLayer == nil || dnsLayer == nil {
return nil
}
dns := dnsLayer.(*layers.DNS)
if !dns.QR { // QR=0 — это запрос, QR=1 — ответ; анализируем запросы
return nil
}
// Анализируем только запросы (QR=false в gopacket означает запрос)
if len(dns.Questions) == 0 {
return nil
}
srcIP := netLayer.NetworkFlow().Src().String()
now := pkt.Metadata().Timestamp
if now.IsZero() {
now = time.Now()
}
var alerts []storage.Alert
for _, q := range dns.Questions {
name := string(q.Name)
// Проверка 1: DNS-туннелирование по длине метки
if alert := d.checkTunneling(name, srcIP, now); alert != nil {
alerts = append(alerts, *alert)
}
}
// Проверка 2: высокая частота запросов
if alert := d.checkFrequency(srcIP, now); alert != nil {
alerts = append(alerts, *alert)
}
return alerts
}
func (d *dnsAnomalyDetector) checkTunneling(name, srcIP string, now time.Time) *storage.Alert {
labels := strings.Split(name, ".")
for _, label := range labels {
if len(label) > d.cfg.MaxLabelLength {
key := "tunnel:" + srcIP
d.mu.Lock()
last, exists := d.alerted[key]
if exists && now.Sub(last) < 60*time.Second {
d.mu.Unlock()
return nil
}
d.alerted[key] = now
d.mu.Unlock()
return &storage.Alert{
CreatedAt: now,
Type: "dns_anomaly",
Severity: d.cfg.Severity,
SrcIP: srcIP,
Protocol: "DNS",
Description: fmt.Sprintf("DNS tunneling suspected: label length %d in query '%s'", len(label), name),
Count: 1,
}
}
}
return nil
}
func (d *dnsAnomalyDetector) checkFrequency(srcIP string, now time.Time) *storage.Alert {
d.mu.Lock()
defer d.mu.Unlock()
window := time.Duration(d.cfg.WindowSeconds) * time.Second
cutoff := now.Add(-window)
d.tracker[srcIP] = append(d.tracker[srcIP], now)
d.tracker[srcIP] = filterTimes(d.tracker[srcIP], cutoff)
count := len(d.tracker[srcIP])
if count < d.cfg.QueryThreshold {
return nil
}
key := "freq:" + srcIP
if last, ok := d.alerted[key]; ok && now.Sub(last) < 30*time.Second {
return nil
}
d.alerted[key] = now
return &storage.Alert{
CreatedAt: now,
Type: "dns_anomaly",
Severity: d.cfg.Severity,
SrcIP: srcIP,
Protocol: "DNS",
Description: fmt.Sprintf("High DNS query rate from %s: %d queries in %ds", srcIP, count, d.cfg.WindowSeconds),
Count: count,
}
}
func (d *dnsAnomalyDetector) Reset() {
d.mu.Lock()
defer d.mu.Unlock()
d.tracker = make(map[string][]time.Time)
d.alerted = make(map[string]time.Time)
}