Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 82 additions & 48 deletions pcapgo/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ package pcapgo

import (
"fmt"
"io"
"net"
"runtime"
"os"
"sync"
"sync/atomic"
"syscall"
"time"
"unsafe"
Expand All @@ -32,7 +30,7 @@ var timeLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.Timeval{})))

// EthernetHandle holds shared buffers and file descriptor of af_packet socket
type EthernetHandle struct {
fd uintptr
file *os.File
buffer []byte
oob []byte
ancil []interface{}
Expand All @@ -46,16 +44,6 @@ func (h *EthernetHandle) readOne() (ci gopacket.CaptureInfo, vlan int, haveVlan
// we could use unix.Recvmsg, but that does a memory allocation (for the returned sockaddr) :(
var msg unix.Msghdr
var sa unix.RawSockaddrLinklayer
var handle = atomic.LoadUintptr(&h.fd)

/*
* check if the handle got closed already
* if so return EOF to also stop waiting for packets
*/
if int(handle) < 0 {
err = io.EOF
return
}

msg.Name = (*byte)(unsafe.Pointer(&sa))
msg.Namelen = uint32(unsafe.Sizeof(sa))
Expand All @@ -73,10 +61,31 @@ func (h *EthernetHandle) readOne() (ci gopacket.CaptureInfo, vlan int, haveVlan
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, handle, uintptr(unsafe.Pointer(&msg)), uintptr(unix.MSG_TRUNC))
rawConn, err := h.file.SyscallConn()
if err != nil {
return
}

var (
e syscall.Errno
n uintptr
)
err = rawConn.Read(func(fd uintptr) bool {
// use msg_trunc so we know packet size without auxdata, which might be missing
n, _, e = syscall.Syscall(unix.SYS_RECVMSG, fd, uintptr(unsafe.Pointer(&msg)), uintptr(unix.MSG_TRUNC))
switch e {
case 0:
return true
case syscall.EAGAIN:
return false
default:
// some other error
return true
}
})
if err != nil {
return
}

switch {
case e.Temporary() || e.Timeout():
Expand Down Expand Up @@ -175,15 +184,8 @@ func (h *EthernetHandle) ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo,
}

// Close closes the underlying socket
func (h *EthernetHandle) Close() (err error) {
if handle := atomic.LoadUintptr(&h.fd); handle != 0 {
_ = unix.Shutdown(int(handle), unix.SHUT_RDWR)
// close no matter if shutdown returned an error or not to make sure the socket is closed
err = unix.Close(int(handle))
atomic.SwapUintptr(&h.fd, 0)
runtime.SetFinalizer(h, nil)
}
return err
func (h *EthernetHandle) Close() error {
return h.file.Close()
}

// SetCaptureLength sets the maximum capture length to the given value
Expand All @@ -202,22 +204,34 @@ func (h *EthernetHandle) GetCaptureLength() int {

// 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(int(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
func (h *EthernetHandle) SetBPF(filter []bpf.RawInstruction) (err error) {
rawConn, err := h.file.SyscallConn()
if err != nil {
return
}
fprog := &unix.SockFprog{
Len: uint16(len(filter)),
Filter: &f[0],

err2 := rawConn.Control(func(fd uintptr) {
if len(filter) == 0 {
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
return
}
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],
}
err = unix.SetsockoptSockFprog(int(fd), unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
})
if err2 != nil {
return err2
}
return unix.SetsockoptSockFprog(int(h.fd), unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
return
}

// LocalAddr returns the local network address
Expand All @@ -231,7 +245,12 @@ func (h *EthernetHandle) LocalAddr() net.HardwareAddr {
}

// 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 {
func (h *EthernetHandle) SetPromiscuous(b bool) (err error) {
rawConn, err := h.file.SyscallConn()
if err != nil {
return
}

mreq := unix.PacketMreq{
Ifindex: int32(h.intf),
Type: unix.PACKET_MR_PROMISC,
Expand All @@ -242,12 +261,28 @@ func (h *EthernetHandle) SetPromiscuous(b bool) error {
opt = unix.PACKET_DROP_MEMBERSHIP
}

return unix.SetsockoptPacketMreq(int(h.fd), unix.SOL_PACKET, opt, &mreq)
err2 := rawConn.Control(func(fd uintptr) {
err = unix.SetsockoptPacketMreq(int(fd), unix.SOL_PACKET, opt, &mreq)
})
if err2 != nil {
return err2
}
return
}

// 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(int(h.fd), unix.SOL_PACKET, unix.PACKET_STATISTICS)
func (h *EthernetHandle) Stats() (res *unix.TpacketStats, err error) {
rawConn, err := h.file.SyscallConn()
if err != nil {
return
}
err2 := rawConn.Control(func(fd uintptr) {
res, err = unix.GetsockoptTpacketStats(int(fd), unix.SOL_PACKET, unix.PACKET_STATISTICS)
})
if err2 != nil {
return nil, err2
}
return
}

// NewEthernetHandle implements pcap.OpenLive for network devices.
Expand All @@ -259,7 +294,7 @@ func NewEthernetHandle(ifname string) (*EthernetHandle, error) {
return nil, fmt.Errorf("couldn't query interface %s: %s", ifname, err)
}

fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons.Htons(unix.ETH_P_ALL)))
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW|unix.SOCK_CLOEXEC|unix.SOCK_NONBLOCK, int(htons.Htons(unix.ETH_P_ALL)))
if err != nil {
return nil, fmt.Errorf("couldn't open packet socket: %s", err)
}
Expand Down Expand Up @@ -293,13 +328,12 @@ func NewEthernetHandle(ifname string) (*EthernetHandle, error) {
}

handle := &EthernetHandle{
fd: uintptr(fd),
file: os.NewFile(uintptr(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
}