forked from controlplaneio/netassertv2-packet-sniffer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
153 lines (122 loc) · 4.34 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// main package is the entrypoint of this program
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/ardanlabs/conf/v3"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"go.uber.org/zap"
"github.com/controlplaneio/netassertv2-packet-sniffer/pkg/logger"
"github.com/controlplaneio/netassertv2-packet-sniffer/pkg/packets"
)
var (
version = "development" // version that will be overridden by LD flag(s)
service = "packet-capture" // service/application name to be overridden by LD flag
)
// config - holds the configuration for this program
type config struct {
NetworkInterface string `conf:"default:eth0,flag:interface,env:IFACE"`
SnapLen int32 `conf:"default:1024,flag:snaplen,env:SNAPLEN"`
Promisc bool `conf:"default:false,flag:promisc,env:PROMISC"`
SearchString string `conf:"default:control-plane.io,flag:search-string,env:SEARCH_STRING"`
Protocol string `conf:"default:tcp,flag:protocol,env:PROTOCOL"`
// environment can be production or development
Environment string `conf:"default:production,flag:environment,env:ENV"`
NumberOfMatches int `conf:"default:3,flag:matches,env:MATCHES"`
TimeoutSeconds int `conf:"default:60,flag:timeout-seconds,env:TIMEOUT_SECONDS"`
}
// processTimeout - time to process each packet
const processTimeout = 500 * time.Millisecond
// initConfigAndLogger - initialises the configuration and logger
func initConfigAndLogger() (*config, *zap.SugaredLogger, error) {
var cfg config
// Parse the config struct to get the configuration
help, err := conf.Parse("", &cfg)
if err != nil {
if errors.Is(err, conf.ErrHelpWanted) {
fmt.Println(help)
return nil, nil, conf.ErrHelpWanted
}
return nil, nil, fmt.Errorf("parsing config failed: %v", err)
}
lg, err := logger.New(version, service, cfg.Environment)
if err != nil {
return nil, nil, fmt.Errorf("failed to initialise the logger: %w", err)
}
return &cfg, lg, nil
}
// setupPacketCapture - sets up the packet capture
func setupPacketCapture(cfg *config) (*pcap.Handle, error) {
// Set up packet capture
handle, err := pcap.OpenLive(
cfg.NetworkInterface, // name of the interface to capture
cfg.SnapLen, // snap length
cfg.Promisc, // set the interface in promiscuous mode
processTimeout, // ticker
)
if err != nil {
return nil, fmt.Errorf("unable to capture packet on the %q interface - %w",
cfg.NetworkInterface, err)
}
if err := handle.SetBPFFilter(strings.ToLower(cfg.Protocol)); err != nil {
return nil, fmt.Errorf("unable to set BPF filter to %q: %v", cfg.Protocol, err)
}
return handle, nil
}
// entrypoint of this program
func main() {
if err := run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func run() error {
cfg, lg, err := initConfigAndLogger()
if err != nil {
return fmt.Errorf("failed to initialise the config and logger: %w", err)
}
defer func() {
_ = lg.Sync()
}()
lg.Infof("Working with following configuration:\n%+v\n", cfg)
// setup up the packet capture
handle, err := setupPacketCapture(cfg)
if err != nil {
return fmt.Errorf("unable to capture packet on the %s interface - %v", cfg.NetworkInterface, err)
}
//nolint:staticcheck // Close function does not return anything
defer handle.Close()
lg.Infof("capturing %q traffic on %q interface", cfg.Protocol, cfg.NetworkInterface)
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
lg.Info("starting to process packets")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
signalChan := make(chan os.Signal, 1)
defer close(signalChan)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGQUIT)
result := make(chan error)
defer close(result)
// create a new instance of the StringSearchService
svc := packets.NewStringSearchService(lg)
go func() { // start a goroutine to process the packets
result <- processPackets(ctx, packetSource, lg, cfg, svc)
}()
select { // wait for the result or signal
case err := <-result:
if err != nil {
return fmt.Errorf("unable to find string %s in %s packets: %w", cfg.SearchString, cfg.Protocol, err)
}
return err
case sig := <-signalChan:
lg.Infof("received signal %v from OS, quitting", sig)
cancel()
return fmt.Errorf("context cancelled while searching for string in packets: %w", ctx.Err())
}
}