sniffer-agent/vendor/github.com/google/gopacket/pcapgo/capture.go

287 lines
8.7 KiB
Go

// Copyright 2012 Google, Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.
// +build linux,go1.7
package pcapgo
import (
"fmt"
"net"
"runtime"
"sync"
"syscall"
"time"
"unsafe"
"golang.org/x/net/bpf"
"golang.org/x/sys/unix"
"github.com/google/gopacket"
)
var hdrLen = unix.CmsgSpace(0)
var auxLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.TpacketAuxdata{})))
var timensLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.Timespec{})))
var timeLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.Timeval{})))
func htons(data uint16) uint16 { return data<<8 | data>>8 }
// EthernetHandle holds shared buffers and file descriptor of af_packet socket
type EthernetHandle struct {
fd int
buffer []byte
oob []byte
ancil []interface{}
mu sync.Mutex
intf int
addr net.HardwareAddr
}
// readOne reads a packet from the handle and returns a capture info + vlan info
func (h *EthernetHandle) readOne() (ci gopacket.CaptureInfo, vlan int, haveVlan bool, err error) {
// we could use unix.Recvmsg, but that does a memory allocation (for the returned sockaddr) :(
var msg unix.Msghdr
var sa unix.RawSockaddrLinklayer
msg.Name = (*byte)(unsafe.Pointer(&sa))
msg.Namelen = uint32(unsafe.Sizeof(sa))
var iov unix.Iovec
if len(h.buffer) > 0 {
iov.Base = &h.buffer[0]
iov.SetLen(len(h.buffer))
}
msg.Iov = &iov
msg.Iovlen = 1
if len(h.oob) > 0 {
msg.Control = &h.oob[0]
msg.SetControllen(len(h.oob))
}
// use msg_trunc so we know packet size without auxdata, which might be missing
n, _, e := syscall.Syscall(unix.SYS_RECVMSG, uintptr(h.fd), uintptr(unsafe.Pointer(&msg)), uintptr(unix.MSG_TRUNC))
if e != 0 {
return gopacket.CaptureInfo{}, 0, false, fmt.Errorf("couldn't read packet: %s", e)
}
if sa.Family == unix.AF_PACKET {
ci.InterfaceIndex = int(sa.Ifindex)
} else {
ci.InterfaceIndex = h.intf
}
// custom aux parsing so we don't allocate stuff (unix.ParseSocketControlMessage allocates a slice)
// we're getting at most 2 cmsgs anyway and know which ones they are (auxdata + timestamp(ns))
oob := h.oob[:msg.Controllen]
gotAux := false
for len(oob) > hdrLen { // > hdrLen, because we also need something after the cmsg header
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&oob[0]))
switch {
case hdr.Level == unix.SOL_PACKET && hdr.Type == unix.PACKET_AUXDATA && len(oob) >= auxLen:
aux := (*unix.TpacketAuxdata)(unsafe.Pointer(&oob[hdrLen]))
ci.CaptureLength = int(n)
ci.Length = int(aux.Len)
vlan = int(aux.Vlan_tci)
haveVlan = (aux.Status & unix.TP_STATUS_VLAN_VALID) != 0
gotAux = true
case hdr.Level == unix.SOL_SOCKET && hdr.Type == unix.SO_TIMESTAMPNS && len(oob) >= timensLen:
tstamp := (*unix.Timespec)(unsafe.Pointer(&oob[hdrLen]))
ci.Timestamp = time.Unix(int64(tstamp.Sec), int64(tstamp.Nsec))
case hdr.Level == unix.SOL_SOCKET && hdr.Type == unix.SO_TIMESTAMP && len(oob) >= timeLen:
tstamp := (*unix.Timeval)(unsafe.Pointer(&oob[hdrLen]))
ci.Timestamp = time.Unix(int64(tstamp.Sec), int64(tstamp.Usec)*1000)
}
oob = oob[unix.CmsgSpace(int(hdr.Len))-hdrLen:]
}
if !gotAux {
// fallback for no aux cmsg
ci.CaptureLength = int(n)
ci.Length = int(n)
haveVlan = false
}
// fix up capture length if we needed to truncate
if ci.CaptureLength > len(h.buffer) {
ci.CaptureLength = len(h.buffer)
}
if ci.Timestamp.IsZero() {
// we got no timestamp info -> emulate it
ci.Timestamp = time.Now()
}
return ci, vlan, haveVlan, nil
}
// ReadPacketData implements gopacket.PacketDataSource. If this was captured on a vlan, the vlan id will be in the AncillaryData[0]
func (h *EthernetHandle) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
h.mu.Lock()
ci, vlan, haveVlan, err := h.readOne()
if err != nil {
h.mu.Unlock()
return nil, gopacket.CaptureInfo{}, fmt.Errorf("couldn't read packet data: %s", err)
}
b := make([]byte, ci.CaptureLength)
copy(b, h.buffer)
h.mu.Unlock()
if haveVlan {
ci.AncillaryData = []interface{}{vlan}
}
return b, ci, nil
}
// ZeroCopyReadPacketData implements gopacket.ZeroCopyPacketDataSource. If this was captured on a vlan, the vlan id will be in the AncillaryData[0].
// This function does not allocate memory. Beware that the next call to ZeroCopyReadPacketData will overwrite existing slices (returned data AND AncillaryData)!
// Due to shared buffers this must not be called concurrently
func (h *EthernetHandle) ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
ci, vlan, haveVlan, err := h.readOne()
if err != nil {
return nil, gopacket.CaptureInfo{}, fmt.Errorf("couldn't read packet data: %s", err)
}
if haveVlan {
h.ancil[0] = vlan
ci.AncillaryData = h.ancil
}
return h.buffer[:ci.CaptureLength], ci, nil
}
// Close closes the underlying socket
func (h *EthernetHandle) Close() {
if h.fd != -1 {
unix.Close(h.fd)
h.fd = -1
runtime.SetFinalizer(h, nil)
}
}
// SetCaptureLength sets the maximum capture length to the given value
func (h *EthernetHandle) SetCaptureLength(len int) error {
if len < 0 {
return fmt.Errorf("illegal capture length %d. Must be at least 0", len)
}
h.buffer = make([]byte, len)
return nil
}
// GetCaptureLength returns the maximum capture length
func (h *EthernetHandle) GetCaptureLength() int {
return len(h.buffer)
}
// SetBPF attaches the given BPF filter to the socket. After this, only the packets for which the filter returns a value greater than zero are received.
// If a filter was already attached, it will be overwritten. To remove the filter, provide an empty slice.
func (h *EthernetHandle) SetBPF(filter []bpf.RawInstruction) error {
if len(filter) == 0 {
return unix.SetsockoptInt(h.fd, unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
}
f := make([]unix.SockFilter, len(filter))
for i := range filter {
f[i].Code = filter[i].Op
f[i].Jf = filter[i].Jf
f[i].Jt = filter[i].Jt
f[i].K = filter[i].K
}
fprog := &unix.SockFprog{
Len: uint16(len(filter)),
Filter: &f[0],
}
return unix.SetsockoptSockFprog(h.fd, unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
}
// LocalAddr returns the local network address
func (h *EthernetHandle) LocalAddr() net.HardwareAddr {
// Hardware Address might have changed. Fetch new one and fall back to the stored one if fetching interface fails
intf, err := net.InterfaceByIndex(h.intf)
if err == nil {
h.addr = intf.HardwareAddr
}
return h.addr
}
// SetPromiscuous sets promiscous mode to the required value. If it is enabled, traffic not destined for the interface will also be captured.
func (h *EthernetHandle) SetPromiscuous(b bool) error {
mreq := unix.PacketMreq{
Ifindex: int32(h.intf),
Type: unix.PACKET_MR_PROMISC,
}
opt := unix.PACKET_ADD_MEMBERSHIP
if !b {
opt = unix.PACKET_DROP_MEMBERSHIP
}
return unix.SetsockoptPacketMreq(h.fd, unix.SOL_PACKET, opt, &mreq)
}
// Stats returns number of packets and dropped packets. This will be the number of packets/dropped packets since the last call to stats (not the cummulative sum!).
func (h *EthernetHandle) Stats() (*unix.TpacketStats, error) {
return unix.GetsockoptTpacketStats(h.fd, unix.SOL_PACKET, unix.PACKET_STATISTICS)
}
// NewEthernetHandle implements pcap.OpenLive for network devices.
// If you want better performance have a look at github.com/google/gopacket/afpacket.
// SetCaptureLength can be used to limit the maximum capture length.
func NewEthernetHandle(ifname string) (*EthernetHandle, error) {
intf, err := net.InterfaceByName(ifname)
if err != nil {
return nil, fmt.Errorf("couldn't query interface %s: %s", ifname, err)
}
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
if err != nil {
return nil, fmt.Errorf("couldn't open packet socket: %s", err)
}
addr := unix.SockaddrLinklayer{
Protocol: htons(unix.ETH_P_ALL),
Ifindex: intf.Index,
}
if err := unix.Bind(fd, &addr); err != nil {
return nil, fmt.Errorf("couldn't bind to interface %s: %s", ifname, err)
}
ooblen := 0
if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_AUXDATA, 1); err != nil {
// we can't get auxdata -> no vlan info
} else {
ooblen += auxLen
}
if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMPNS, 1); err != nil {
// no nanosecond resolution :( -> try ms
if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMP, 1); err != nil {
// if this doesn't work we well use time.Now() -> ignore errors here
} else {
ooblen += timeLen
}
} else {
ooblen += timensLen
}
handle := &EthernetHandle{
fd: fd,
buffer: make([]byte, intf.MTU),
oob: make([]byte, ooblen),
ancil: make([]interface{}, 1),
intf: intf.Index,
addr: intf.HardwareAddr,
}
runtime.SetFinalizer(handle, (*EthernetHandle).Close)
return handle, nil
}