123 lines
2.6 KiB
Go
123 lines
2.6 KiB
Go
package capture
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/pcap"
|
|
)
|
|
|
|
// Mode определяет режим захвата трафика
|
|
type Mode string
|
|
|
|
const (
|
|
ModeLive Mode = "live"
|
|
ModePCAP Mode = "pcap"
|
|
)
|
|
|
|
// Config параметры захвата
|
|
type Config struct {
|
|
Interface string
|
|
PCAPFile string
|
|
Mode Mode
|
|
Snaplen int32
|
|
Promiscuous bool
|
|
}
|
|
|
|
// Capturer захватывает пакеты и отправляет их в канал
|
|
type Capturer struct {
|
|
cfg Config
|
|
handle *pcap.Handle
|
|
}
|
|
|
|
// New создаёт новый захватчик трафика
|
|
func New(cfg Config) (*Capturer, error) {
|
|
return &Capturer{cfg: cfg}, nil
|
|
}
|
|
|
|
// Start открывает источник пакетов и запускает захват.
|
|
// Пакеты отправляются в возвращаемый канал до отмены ctx.
|
|
func (c *Capturer) Start(ctx context.Context) (<-chan gopacket.Packet, error) {
|
|
var handle *pcap.Handle
|
|
var err error
|
|
|
|
switch c.cfg.Mode {
|
|
case ModeLive:
|
|
handle, err = pcap.OpenLive(
|
|
c.cfg.Interface,
|
|
c.cfg.Snaplen,
|
|
c.cfg.Promiscuous,
|
|
pcap.BlockForever,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open live capture on %s: %w", c.cfg.Interface, err)
|
|
}
|
|
case ModePCAP:
|
|
handle, err = pcap.OpenOffline(c.cfg.PCAPFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open pcap file %s: %w", c.cfg.PCAPFile, err)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unknown capture mode: %s", c.cfg.Mode)
|
|
}
|
|
|
|
c.handle = handle
|
|
|
|
pkts := make(chan gopacket.Packet, 1024)
|
|
go func() {
|
|
defer close(pkts)
|
|
defer handle.Close()
|
|
|
|
src := gopacket.NewPacketSource(handle, handle.LinkType())
|
|
src.NoCopy = true
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
|
|
pkt, err := src.NextPacket()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return
|
|
}
|
|
// pcap timeout или другая recoverable ошибка
|
|
log.Printf("[capture] packet read error: %v", err)
|
|
continue
|
|
}
|
|
select {
|
|
case pkts <- pkt:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return pkts, nil
|
|
}
|
|
|
|
// Stop принудительно закрывает handle (используется при остановке из API)
|
|
func (c *Capturer) Stop() {
|
|
if c.handle != nil {
|
|
c.handle.Close()
|
|
}
|
|
}
|
|
|
|
// ListInterfaces возвращает доступные сетевые интерфейсы
|
|
func ListInterfaces() ([]string, error) {
|
|
devs, err := pcap.FindAllDevs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, 0, len(devs))
|
|
for _, d := range devs {
|
|
names = append(names, d.Name)
|
|
}
|
|
return names, nil
|
|
}
|