287 lines
8.7 KiB
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
|
|
}
|