diff --git a/pcapgo/capture.go b/pcapgo/capture.go index a68e5416f..15559cdbc 100644 --- a/pcapgo/capture.go +++ b/pcapgo/capture.go @@ -10,7 +10,7 @@ package pcapgo import ( "fmt" "net" - "runtime" + "os" "sync" "syscall" "time" @@ -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{} @@ -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) @@ -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 @@ -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 @@ -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, @@ -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. @@ -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) } @@ -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 }