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

125 lines
3.2 KiB
Go

package analyzer
import (
"context"
"log"
"sync/atomic"
"time"
"dip-ids/internal/config"
"dip-ids/internal/storage"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// Engine — основной движок анализа трафика.
// Получает пакеты из канала, передаёт каждый всем детекторам,
// собирает алерты и передаёт их в хранилище.
type Engine struct {
db *storage.DB
detectors []Detector
totalPkts atomic.Int64
totalBytes atomic.Int64
}
// New создаёт движок с детекторами, сконфигурированными из cfg
func New(db *storage.DB, cfg config.DetectorsConfig) *Engine {
return &Engine{
db: db,
detectors: []Detector{
NewPortScanDetector(cfg.PortScan),
NewBruteForceDetector(cfg.BruteForce),
NewDoSDetector(cfg.DoS),
NewARPSpoofDetector(cfg.ARPSpoof),
NewDNSAnomalyDetector(cfg.DNSAnomaly),
},
}
}
// Run запускает обработку пакетов из канала pkts до отмены ctx.
func (e *Engine) Run(ctx context.Context, pkts <-chan gopacket.Packet) {
for {
select {
case <-ctx.Done():
return
case pkt, ok := <-pkts:
if !ok {
return
}
e.process(pkt)
}
}
}
func (e *Engine) process(pkt gopacket.Packet) {
e.totalPkts.Add(1)
if meta := pkt.Metadata(); meta != nil {
e.totalBytes.Add(int64(meta.CaptureLength))
}
// Сохраняем статистику пакета
e.recordStat(pkt)
for _, det := range e.detectors {
alerts := det.Analyze(pkt)
for i := range alerts {
if err := e.db.CreateAlert(&alerts[i]); err != nil {
log.Printf("[engine] save alert (%s): %v", det.Name(), err)
}
}
}
}
func (e *Engine) recordStat(pkt gopacket.Packet) {
stat := &storage.PacketStat{
CreatedAt: time.Now(),
}
if netLayer := pkt.NetworkLayer(); netLayer != nil {
stat.SrcIP = netLayer.NetworkFlow().Src().String()
stat.DstIP = netLayer.NetworkFlow().Dst().String()
}
// Определяем протокол
switch {
case pkt.Layer(layers.LayerTypeTCP) != nil:
stat.Protocol = "TCP"
case pkt.Layer(layers.LayerTypeUDP) != nil:
stat.Protocol = "UDP"
case pkt.Layer(layers.LayerTypeICMPv4) != nil:
stat.Protocol = "ICMP"
case pkt.Layer(layers.LayerTypeARP) != nil:
stat.Protocol = "ARP"
case pkt.Layer(layers.LayerTypeDNS) != nil:
stat.Protocol = "DNS"
default:
stat.Protocol = "OTHER"
}
if meta := pkt.Metadata(); meta != nil {
stat.Bytes = int64(meta.CaptureLength)
}
// Сохраняем не каждый пакет (каждый 10-й), чтобы не раздувать БД
if e.totalPkts.Load()%10 == 0 {
if err := e.db.CreatePacketStat(stat); err != nil {
log.Printf("[engine] save stat: %v", err)
}
}
}
// Stats возвращает счётчики текущей сессии
func (e *Engine) Stats() (pkts, bytes int64) {
return e.totalPkts.Load(), e.totalBytes.Load()
}
// Reset сбрасывает состояние всех детекторов и счётчики
func (e *Engine) Reset() {
for _, det := range e.detectors {
det.Reset()
}
e.totalPkts.Store(0)
e.totalBytes.Store(0)
}