143 lines
2.9 KiB
Go
143 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gizak/termui/v3"
|
|
)
|
|
|
|
func exit(s string) {
|
|
fmt.Println("Start sniffer failed:", s)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Options is the options set for the sniffer instance.
|
|
type Options struct {
|
|
// BPFFilter is the string pcap filter with the BPF syntax
|
|
// eg. "tcp and port 80"
|
|
BPFFilter string
|
|
|
|
// Interval is the interval for refresh rate in seconds
|
|
Interval int
|
|
|
|
// ViewMode represents the sniffer view mode, optional: bytes, packets, processes
|
|
ViewMode ViewMode
|
|
|
|
// DevicesPrefix represents prefixed devices to monitor
|
|
DevicesPrefix []string
|
|
|
|
// Pids to watch in processes mode
|
|
Pids []int32
|
|
|
|
// Unit of stats in processes mode, optional: B, Kb, KB, Mb, MB, Gb, GB
|
|
Unit Unit
|
|
|
|
// DisableDNSResolve decides whether if disable the DNS resolution
|
|
DisableDNSResolve bool
|
|
}
|
|
|
|
func (o Options) Validate() error {
|
|
if err := o.ViewMode.Validate(); err != nil {
|
|
return err
|
|
}
|
|
if err := o.Unit.Validate(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DefaultOptions() Options {
|
|
return Options{
|
|
BPFFilter: "tcp or udp",
|
|
Interval: 1,
|
|
ViewMode: ModeTableBytes,
|
|
Unit: UnitKB,
|
|
DevicesPrefix: []string{"en", "lo", "eth", "em", "bond"},
|
|
DisableDNSResolve: false,
|
|
}
|
|
}
|
|
|
|
type Sniffer struct {
|
|
opts Options
|
|
dnsResolver *DNSResolver
|
|
pcapClient *PcapClient
|
|
statsManager *StatsManager
|
|
ui *UIComponent
|
|
socketFetcher SocketFetcher
|
|
}
|
|
|
|
func NewSniffer(opts Options) (*Sniffer, error) {
|
|
dnsResolver := NewDnsResolver()
|
|
pcapClient, err := NewPcapClient(dnsResolver.Lookup, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Sniffer{
|
|
opts: opts,
|
|
dnsResolver: dnsResolver,
|
|
pcapClient: pcapClient,
|
|
statsManager: NewStatsManager(opts),
|
|
ui: NewUIComponent(opts),
|
|
socketFetcher: GetSocketFetcher(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Sniffer) SwitchViewMode() {
|
|
s.opts.ViewMode = (s.opts.ViewMode + 1) % 3
|
|
s.statsManager = NewStatsManager(s.opts)
|
|
|
|
s.ui.Close()
|
|
s.ui = NewUIComponent(s.opts)
|
|
}
|
|
|
|
func (s *Sniffer) Start() {
|
|
events := termui.PollEvents()
|
|
s.Refresh()
|
|
var paused bool
|
|
|
|
ticker := time.Tick(time.Duration(s.opts.Interval) * time.Second)
|
|
for {
|
|
select {
|
|
case e := <-events:
|
|
switch e.ID {
|
|
case "<Tab>":
|
|
s.ui.viewer.Shift()
|
|
case "<Space>":
|
|
paused = !paused
|
|
case "<Resize>":
|
|
payload := e.Payload.(termui.Resize)
|
|
s.ui.viewer.Resize(payload.Width, payload.Height)
|
|
case "s", "S":
|
|
s.SwitchViewMode()
|
|
case "q", "Q", "<C-c>":
|
|
return
|
|
}
|
|
|
|
case <-ticker:
|
|
if !paused {
|
|
s.Refresh()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Sniffer) Close() {
|
|
s.ui.Close()
|
|
s.pcapClient.Close()
|
|
s.dnsResolver.Close()
|
|
}
|
|
|
|
func (s *Sniffer) Refresh() {
|
|
utilization := s.pcapClient.GetUtilization()
|
|
openSockets, err := s.socketFetcher.GetOpenSockets(s.opts.Pids...)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
s.statsManager.Put(Stat{OpenSockets: openSockets, Utilization: utilization})
|
|
s.ui.viewer.Render(s.statsManager.GetStats())
|
|
}
|