youtubebeat/vendor/github.com/elastic/beats/packetbeat/sniffer/sniffer.go

330 lines
7.6 KiB
Go
Raw Normal View History

2018-11-18 11:08:38 +01:00
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package sniffer
import (
"fmt"
"io"
"os"
"runtime"
"syscall"
"time"
"github.com/tsg/gopacket"
"github.com/tsg/gopacket/layers"
"github.com/tsg/gopacket/pcap"
"github.com/elastic/beats/libbeat/common/atomic"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/packetbeat/config"
)
// Sniffer provides packet sniffing capabilities, forwarding packets read
// to a Worker.
type Sniffer struct {
config config.InterfacesConfig
dumper *pcap.Dumper
state atomic.Int32 // store snifferState
// bpf filter
filter string
factory WorkerFactory
}
// WorkerFactory constructs a new worker instance for use with a Sniffer.
type WorkerFactory func(layers.LinkType) (Worker, error)
// Worker defines the callback interfaces a Sniffer instance will use
// to forward packets.
type Worker interface {
OnPacket(data []byte, ci *gopacket.CaptureInfo)
}
type snifferHandle interface {
gopacket.PacketDataSource
LinkType() layers.LinkType
Close()
}
// sniffer state values
const (
snifferInactive = 0
snifferClosing = 1
snifferActive = 2
)
// New create a new Sniffer instance. Settings are validated in a best effort
// only, but no device is opened yet. Accessing and configuring the actual device
// is done by the Run method.
func New(
testMode bool,
filter string,
factory WorkerFactory,
interfaces config.InterfacesConfig,
) (*Sniffer, error) {
s := &Sniffer{
filter: filter,
config: interfaces,
factory: factory,
state: atomic.MakeInt32(snifferInactive),
}
logp.Debug("sniffer", "BPF filter: '%s'", filter)
// pre-check and normalize configuration:
// - resolve potential device name
// - check for file output
// - set some defaults
if s.config.File != "" {
logp.Debug("sniffer", "Reading from file: %s", s.config.File)
if s.config.BpfFilter != "" {
logp.Warn("Packet filters are not applied to pcap files.")
}
// we read file with the pcap provider
s.config.Type = "pcap"
s.config.Device = ""
} else {
// try to resolve device name (ignore error if testMode is enabled)
if name, err := resolveDeviceName(s.config.Device); err != nil {
if !testMode {
return nil, err
}
} else {
s.config.Device = name
if name == "any" && !deviceAnySupported {
return nil, fmt.Errorf("any interface is not supported on %s", runtime.GOOS)
}
if s.config.Snaplen == 0 {
s.config.Snaplen = 65535
}
if s.config.BufferSizeMb <= 0 {
s.config.BufferSizeMb = 24
}
if t := s.config.Type; t == "autodetect" || t == "" {
s.config.Type = "pcap"
}
logp.Debug("sniffer", "Sniffer type: %s device: %s", s.config.Type, s.config.Device)
}
}
err := validateConfig(filter, &s.config)
if err != nil {
return nil, err
}
return s, nil
}
// Run opens the sniffing device and processes packets being read from that device.
// Worker instances are instantiated as needed.
func (s *Sniffer) Run() error {
var (
counter = 0
dumper *pcap.Dumper
)
handle, err := s.open()
if err != nil {
return fmt.Errorf("Error starting sniffer: %s", err)
}
defer handle.Close()
if s.config.Dumpfile != "" {
dumper, err = openDumper(s.config.Dumpfile, handle.LinkType())
if err != nil {
return err
}
defer dumper.Close()
}
worker, err := s.factory(handle.LinkType())
if err != nil {
return err
}
// Mark inactive sniffer as active. In case of the sniffer/packetbeat closing
// before/while Run is executed, the state will be snifferClosing.
// => return if state is already snifferClosing.
if !s.state.CAS(snifferInactive, snifferActive) {
return nil
}
defer s.state.Store(snifferInactive)
for s.state.Load() == snifferActive {
if s.config.OneAtATime {
fmt.Println("Press enter to read packet")
fmt.Scanln()
}
data, ci, err := handle.ReadPacketData()
if err == pcap.NextErrorTimeoutExpired || err == syscall.EINTR {
logp.Debug("sniffer", "Interrupted")
continue
}
if err != nil {
// ignore EOF, if sniffer was driven from file
if err == io.EOF && s.config.File != "" {
return nil
}
s.state.Store(snifferInactive)
return fmt.Errorf("Sniffing error: %s", err)
}
if len(data) == 0 {
// Empty packet, probably timeout from afpacket
continue
}
if dumper != nil {
dumper.WritePacketData(data, ci)
}
counter++
logp.Debug("sniffer", "Packet number: %d", counter)
worker.OnPacket(data, &ci)
}
return nil
}
func (s *Sniffer) open() (snifferHandle, error) {
if s.config.File != "" {
return newFileHandler(s.config.File, s.config.TopSpeed, s.config.Loop)
}
switch s.config.Type {
case "pcap":
return openPcap(s.filter, &s.config)
case "af_packet":
return openAFPacket(s.filter, &s.config)
default:
return nil, fmt.Errorf("Unknown sniffer type: %s", s.config.Type)
}
}
// Stop marks a sniffer as stopped. The Run method will return once the stop
// signal has been given.
func (s *Sniffer) Stop() error {
s.state.Store(snifferClosing)
return nil
}
func validateConfig(filter string, cfg *config.InterfacesConfig) error {
if cfg.File == "" {
if err := validatePcapFilter(filter); err != nil {
return err
}
}
switch cfg.Type {
case "pcap":
return validatePcapConfig(cfg)
case "af_packet":
return validateAfPacketConfig(cfg)
default:
return fmt.Errorf("Unknown sniffer type: %s", cfg.Type)
}
}
func validatePcapConfig(cfg *config.InterfacesConfig) error {
return nil
}
func validateAfPacketConfig(cfg *config.InterfacesConfig) error {
_, _, _, err := afpacketComputeSize(cfg.BufferSizeMb, cfg.Snaplen, os.Getpagesize())
return err
}
func validatePcapFilter(expr string) error {
if expr == "" {
return nil
}
// Open a dummy pcap handle to compile the filter
p, err := pcap.OpenDead(layers.LinkTypeEthernet, 65535)
if err != nil {
return fmt.Errorf("OpenDead: %s", err)
}
defer p.Close()
_, err = p.NewBPF(expr)
if err != nil {
return fmt.Errorf("invalid filter '%s': %v", expr, err)
}
return nil
}
func openPcap(filter string, cfg *config.InterfacesConfig) (snifferHandle, error) {
snaplen := int32(cfg.Snaplen)
timeout := 500 * time.Millisecond
h, err := pcap.OpenLive(cfg.Device, snaplen, true, timeout)
if err != nil {
return nil, err
}
err = h.SetBPFFilter(filter)
if err != nil {
h.Close()
return nil, err
}
return h, nil
}
func openAFPacket(filter string, cfg *config.InterfacesConfig) (snifferHandle, error) {
szFrame, szBlock, numBlocks, err := afpacketComputeSize(cfg.BufferSizeMb, cfg.Snaplen, os.Getpagesize())
if err != nil {
return nil, err
}
timeout := 500 * time.Millisecond
h, err := newAfpacketHandle(cfg.Device, szFrame, szBlock, numBlocks, timeout)
if err != nil {
return nil, err
}
err = h.SetBPFFilter(filter)
if err != nil {
h.Close()
return nil, err
}
return h, nil
}
func openDumper(file string, linkType layers.LinkType) (*pcap.Dumper, error) {
p, err := pcap.OpenDead(linkType, 65535)
if err != nil {
return nil, err
}
return p.NewDumper(file)
}