Skip to content
Open
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
111 changes: 81 additions & 30 deletions pcapgo/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ package pcapgo
import (
"fmt"
"net"
"runtime"
"os"
"sync"
"syscall"
"time"
Expand All @@ -31,7 +31,7 @@ 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
file *os.File
buffer []byte
oob []byte
ancil []interface{}
Expand Down Expand Up @@ -62,8 +62,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, uintptr(h.fd), 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
}

if e != 0 {
return gopacket.CaptureInfo{}, 0, false, fmt.Errorf("couldn't read packet: %s", e)
Expand Down Expand Up @@ -160,11 +183,7 @@ func (h *EthernetHandle) ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo,

// Close closes the underlying socket
func (h *EthernetHandle) Close() {
if h.fd != -1 {
unix.Close(h.fd)
h.fd = -1
runtime.SetFinalizer(h, nil)
}
h.file.Close()
}

// SetCaptureLength sets the maximum capture length to the given value
Expand All @@ -183,22 +202,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(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(h.fd, unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
return
}

// LocalAddr returns the local network address
Expand All @@ -212,7 +243,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 @@ -223,12 +259,28 @@ func (h *EthernetHandle) SetPromiscuous(b bool) error {
opt = unix.PACKET_DROP_MEMBERSHIP
}

return unix.SetsockoptPacketMreq(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(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 @@ -240,7 +292,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(unix.ETH_P_ALL)))
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW|unix.SOCK_CLOEXEC|unix.SOCK_NONBLOCK, int(htons(unix.ETH_P_ALL)))
if err != nil {
return nil, fmt.Errorf("couldn't open packet socket: %s", err)
}
Expand Down Expand Up @@ -274,13 +326,12 @@ func NewEthernetHandle(ifname string) (*EthernetHandle, error) {
}

handle := &EthernetHandle{
fd: 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
}