Files
Bryan Boreham 34dc4dabce Clean up some lint warnings
I ran golangci-lint and it pointed out a few things to clean up.
2020-07-31 10:28:55 +00:00

176 lines
4.1 KiB
Go

package router
import (
"fmt"
"net"
"sync"
"time"
"github.com/google/gopacket/pcap"
)
// Must be less than 2^31 usec - see https://github.com/google/gopacket/issues/708
const longTimeout = time.Duration(30 * time.Minute)
type Pcap struct {
NonDiscardingFlowOp
iface *net.Interface
bufSz int
// The libpcap handle for writing packets. It's possible that a
// single handle could be used for reading and writing, but
// we'd have to examine the performance implications.
writeHandle *pcap.Handle
// pcap handles are single-threaded, so we need to lock around
// uses of writeHandle.
mutex sync.Mutex
// The libpcap handle for reading packets
readHandle *pcap.Handle
}
func NewPcap(iface *net.Interface, bufSz int) (InjectorConsumer, error) {
wh, err := newPcapHandle(iface.Name, false, 0, 0)
if err != nil {
return nil, err
}
return &Pcap{iface: iface, bufSz: bufSz, writeHandle: wh}, nil
}
func (p *Pcap) StartConsumingPackets(consumer Consumer) error {
rh, err := newPcapHandle(p.iface.Name, true, 65535, p.bufSz)
if err != nil {
return err
}
// Under Linux, libpcap implements the SetDirection filtering
// in userspace. So set a BPF filter to discard outbound
// packets inside the kernel. We do this here rather than in
// newPcapIO because libpcap doesn't like this in combination
// with a 0 snaplen.
err = rh.SetBPFFilter("inbound")
if err != nil {
rh.Close()
return err
}
// readHandle is just for the benefit of Stats.
p.mutex.Lock()
defer p.mutex.Unlock()
if p.readHandle != nil {
panic("already consuming")
}
p.readHandle = rh
go p.sniff(rh, consumer)
return nil
}
func newPcapHandle(ifName string, promisc bool, snaplen int, bufSz int) (handle *pcap.Handle, err error) {
inactive, err := pcap.NewInactiveHandle(ifName)
if err != nil {
return
}
defer inactive.CleanUp()
if err = inactive.SetPromisc(promisc); err != nil {
return
}
if err = inactive.SetSnapLen(snaplen); err != nil {
return
}
if err = inactive.SetTimeout(longTimeout); err != nil {
return
}
if err = inactive.SetImmediateMode(true); err != nil {
// If gopacket is compiled against an older pcap.h that
// doesn't have pcap_set_immediate_mode, it supplies a dummy
// definition that always returns PCAP_ERROR. That becomes
// "Generic error", which is not very helpful. The real
// pcap_set_immediate_mode never returns PCAP_ERROR, so this
// turns it into a more informative message.
if fmt.Sprint(err) == "Generic error" {
err = fmt.Errorf("compiled against an old version of libpcap; please compile against libpcap-1.5.0 or later")
}
return
}
if err = inactive.SetBufferSize(bufSz); err != nil {
return
}
handle, err = inactive.Activate()
if err != nil {
return
}
err = handle.SetDirection(pcap.DirectionIn)
return
}
func (p *Pcap) Interface() *net.Interface {
return p.iface
}
func (p *Pcap) String() string {
return fmt.Sprint(p.iface.Name, " (via pcap)")
}
func (p *Pcap) InjectPacket(PacketKey) FlowOp {
return p
}
func (p *Pcap) Process(frame []byte, dec *EthernetDecoder, broadcast bool) {
p.mutex.Lock()
defer p.mutex.Unlock()
checkWarn(p.writeHandle.WritePacketData(frame))
}
func (p *Pcap) sniff(readHandle *pcap.Handle, consumer Consumer) {
dec := NewEthernetDecoder()
for {
pkt, _, err := readHandle.ZeroCopyReadPacketData()
if err == pcap.NextErrorTimeoutExpired {
continue
}
checkFatal(err)
dec.DecodeLayers(pkt)
if len(dec.decoded) == 0 {
continue
}
if fop := consumer(dec.PacketKey()); !fop.Discards() {
// We are handing over the frame to
// forwarders, so we need to make a copy of it
// in order to prevent the next capture from
// overwriting the data
pktLen := len(pkt)
pktCopy := make([]byte, pktLen)
copy(pktCopy, pkt)
fop.Process(pktCopy, dec, false)
}
}
}
func (p *Pcap) Stats() map[string]int {
p.mutex.Lock()
rh := p.readHandle
p.mutex.Unlock()
if rh != nil {
stats, err := rh.Stats()
if err == nil {
return map[string]int{
"PacketsReceived": stats.PacketsReceived,
"PacketsDropped": stats.PacketsDropped,
"PacketsIfDropped": stats.PacketsIfDropped,
}
}
}
return nil
}