mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
Issues is that Go built-in net.Interfaces function in newer Windows versions return wrong interface names, which libpcap can't consume. Now we use pcap.FindDevices instead of net.Interfaces. See this Article for deep understanding of the issue https://haydz.github.io/2020/07/06/Go-Windows-NIC.html Additionally, found a bug causing big memory allocations, for large requests, when we perform check if messages finished or not. Because of this bug chunked body encoding check was not working properly. Was not caught in tests, because test was working on packet array level, and this issue happens when dealing with TCP message object. Additionally, added a small fix for windows Makefile task, it now generates proper file name.
277 lines
7.1 KiB
Go
277 lines
7.1 KiB
Go
package capture
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/google/gopacket/pcap"
|
|
)
|
|
|
|
const (
|
|
// ETHALL htons(ETH_P_ALL)
|
|
ETHALL uint16 = unix.ETH_P_ALL<<8 | unix.ETH_P_ALL>>8
|
|
// BLOCKSIZE ring buffer block_size
|
|
BLOCKSIZE = 64 << 10
|
|
// BLOCKNR ring buffer block_nr
|
|
BLOCKNR = (2 << 20) / BLOCKSIZE // 2mb / 64kb
|
|
// FRAMESIZE ring buffer frame_size
|
|
FRAMESIZE = BLOCKSIZE
|
|
// FRAMENR ring buffer frame_nr
|
|
FRAMENR = BLOCKNR * BLOCKSIZE / FRAMESIZE
|
|
// MAPHUGE2MB 2mb huge map
|
|
MAPHUGE2MB = 21 << unix.MAP_HUGE_SHIFT
|
|
)
|
|
|
|
var tpacket2hdrlen = tpAlign(int(unsafe.Sizeof(unix.Tpacket2Hdr{})))
|
|
|
|
// SockRaw is a linux M'maped af_packet socket
|
|
type SockRaw struct {
|
|
mu sync.Mutex
|
|
fd int
|
|
ifindex int
|
|
snaplen int
|
|
pollTimeout uintptr
|
|
frame uint32 // current frame
|
|
buf []byte // points to the memory space of the ring buffer shared with the kernel.
|
|
loopIndex int32 // this field must filled to avoid reading packet twice on a loopback device
|
|
}
|
|
|
|
// NewSocket returns new M'maped sock_raw on packet version 2.
|
|
func NewSocket(pifi pcap.Interface) (*SockRaw, error) {
|
|
var ifi net.Interface
|
|
|
|
infs, _ := net.Interfaces()
|
|
found := false
|
|
for _, i := range infs {
|
|
if i.Name == pifi.Name {
|
|
ifi = i
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return nil, fmt.Errorf("Can't find matching interface")
|
|
}
|
|
|
|
// sock create
|
|
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(ETHALL))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sock := &SockRaw{
|
|
fd: fd,
|
|
ifindex: ifi.Index,
|
|
snaplen: FRAMESIZE,
|
|
pollTimeout: ^uintptr(0),
|
|
}
|
|
|
|
// set packet version
|
|
err = unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_VERSION, unix.TPACKET_V2)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("setsockopt packet_version: %v", err)
|
|
}
|
|
|
|
// bind to interface
|
|
addr := unix.RawSockaddrLinklayer{
|
|
Family: unix.AF_PACKET,
|
|
Protocol: ETHALL,
|
|
Ifindex: int32(ifi.Index),
|
|
}
|
|
_, _, e := unix.Syscall(
|
|
unix.SYS_BIND,
|
|
uintptr(fd),
|
|
uintptr(unsafe.Pointer(&addr)),
|
|
uintptr(unix.SizeofSockaddrLinklayer),
|
|
)
|
|
if e != 0 {
|
|
unix.Close(fd)
|
|
return nil, e
|
|
}
|
|
|
|
// create shared-memory ring buffer
|
|
tp := &unix.TpacketReq{
|
|
Block_size: BLOCKSIZE,
|
|
Block_nr: BLOCKNR,
|
|
Frame_size: FRAMESIZE,
|
|
Frame_nr: FRAMENR,
|
|
}
|
|
err = unix.SetsockoptTpacketReq(sock.fd, unix.SOL_PACKET, unix.PACKET_RX_RING, tp)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("setsockopt packet_rx_ring: %v", err)
|
|
}
|
|
sock.buf, err = unix.Mmap(
|
|
sock.fd,
|
|
0,
|
|
BLOCKSIZE*BLOCKNR,
|
|
unix.PROT_READ|unix.PROT_WRITE,
|
|
unix.MAP_SHARED|MAPHUGE2MB,
|
|
)
|
|
if err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("socket mmap error: %v", err)
|
|
}
|
|
return sock, nil
|
|
}
|
|
|
|
// ReadPacketData implements gopacket.PacketDataSource.
|
|
func (sock *SockRaw) ZeroCopyReadPacketData() (buf []byte, ci gopacket.CaptureInfo, err error) {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
var tpHdr *unix.Tpacket2Hdr
|
|
poll := &unix.PollFd{
|
|
Fd: int32(sock.fd),
|
|
Events: unix.POLLIN,
|
|
}
|
|
var i int
|
|
read:
|
|
i = int(sock.frame * FRAMESIZE)
|
|
tpHdr = (*unix.Tpacket2Hdr)(unsafe.Pointer(&sock.buf[i]))
|
|
sock.frame = (sock.frame + 1) % FRAMENR
|
|
|
|
if tpHdr.Status&unix.TP_STATUS_USER == 0 {
|
|
_, _, e := unix.Syscall(unix.SYS_POLL, uintptr(unsafe.Pointer(poll)), 1, sock.pollTimeout)
|
|
if e != 0 && e != unix.EINTR {
|
|
return buf, ci, e
|
|
}
|
|
// it might be some other frame with data!
|
|
if tpHdr.Status&unix.TP_STATUS_USER == 0 {
|
|
goto read
|
|
}
|
|
}
|
|
tpHdr.Status = unix.TP_STATUS_KERNEL
|
|
sockAddr := (*unix.RawSockaddrLinklayer)(unsafe.Pointer(&sock.buf[i+tpacket2hdrlen]))
|
|
|
|
// parse out repeating packets on loopback
|
|
if sockAddr.Ifindex == sock.loopIndex && sock.frame%2 != 0 {
|
|
goto read
|
|
}
|
|
|
|
ci.Length = int(tpHdr.Len)
|
|
ci.Timestamp = time.Unix(int64(tpHdr.Sec), int64(tpHdr.Nsec))
|
|
ci.InterfaceIndex = int(sockAddr.Ifindex)
|
|
buf = make([]byte, tpHdr.Snaplen)
|
|
ci.CaptureLength = copy(buf, sock.buf[i+int(tpHdr.Mac):])
|
|
|
|
return
|
|
}
|
|
|
|
// Close closes the underlying socket
|
|
func (sock *SockRaw) Close() (err error) {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
if sock.fd != -1 {
|
|
unix.Munmap(sock.buf)
|
|
sock.buf = nil
|
|
err = unix.Close(sock.fd)
|
|
sock.fd = -1
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetSnapLen sets the maximum capture length to the given value.
|
|
// for this to take effects on the kernel level SetBPFilter should be called too.
|
|
func (sock *SockRaw) SetSnapLen(snap int) error {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
if snap < 0 {
|
|
return fmt.Errorf("expected %d snap length to be at least 0", snap)
|
|
}
|
|
if snap > FRAMESIZE {
|
|
snap = FRAMESIZE
|
|
}
|
|
sock.snaplen = snap
|
|
return nil
|
|
}
|
|
|
|
// SetTimeout sets poll wait timeout for the socket.
|
|
// negative value will block forever
|
|
func (sock *SockRaw) SetTimeout(t time.Duration) error {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
sock.pollTimeout = uintptr(t)
|
|
return nil
|
|
}
|
|
|
|
// GetSnapLen returns the maximum capture length
|
|
func (sock *SockRaw) GetSnapLen() int {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
return sock.snaplen
|
|
}
|
|
|
|
// SetBPFFilter compiles and sets a BPF filter for the socket handle.
|
|
func (sock *SockRaw) SetBPFFilter(expr string) error {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
if expr == "" {
|
|
return unix.SetsockoptInt(sock.fd, unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
|
|
}
|
|
filter, err := pcap.CompileBPFFilter(layers.LinkTypeEthernet, sock.snaplen, expr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(filter) > int(^uint16(0)) {
|
|
return fmt.Errorf("filters out of range 0-%d", ^uint16(0))
|
|
}
|
|
if len(filter) == 0 {
|
|
return unix.SetsockoptInt(sock.fd, unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
|
|
}
|
|
fprog := &unix.SockFprog{
|
|
Len: uint16(len(filter)),
|
|
Filter: &(*(*[]unix.SockFilter)(unsafe.Pointer(&filter)))[0],
|
|
}
|
|
return unix.SetsockoptSockFprog(sock.fd, unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
|
|
}
|
|
|
|
// SetPromiscuous sets promiscuous mode to the required value. for better result capture on all interfaces instead.
|
|
// If it is enabled, traffic not destined for the interface will also be captured.
|
|
func (sock *SockRaw) SetPromiscuous(b bool) error {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
mreq := unix.PacketMreq{
|
|
Ifindex: int32(sock.ifindex),
|
|
Type: unix.PACKET_MR_PROMISC,
|
|
}
|
|
|
|
opt := unix.PACKET_ADD_MEMBERSHIP
|
|
if !b {
|
|
opt = unix.PACKET_DROP_MEMBERSHIP
|
|
}
|
|
|
|
return unix.SetsockoptPacketMreq(sock.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 (sock *SockRaw) Stats() (*unix.TpacketStats, error) {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
return unix.GetsockoptTpacketStats(sock.fd, unix.SOL_PACKET, unix.PACKET_STATISTICS)
|
|
}
|
|
|
|
// SetLoopbackIndex necessary to avoid reading packet twice on a loopback device
|
|
func (sock *SockRaw) SetLoopbackIndex(i int32) {
|
|
sock.mu.Lock()
|
|
defer sock.mu.Unlock()
|
|
sock.loopIndex = i
|
|
}
|
|
|
|
// WritePacketData transmits a raw packet.
|
|
func (sock *SockRaw) WritePacketData(pkt []byte) error {
|
|
_, err := unix.Write(sock.fd, pkt)
|
|
return err
|
|
}
|
|
|
|
func tpAlign(x int) int {
|
|
return int((uint(x) + unix.TPACKET_ALIGNMENT - 1) &^ (unix.TPACKET_ALIGNMENT - 1))
|
|
}
|