github.com/gopacket/gopacket@v1.1.0/pcapgo/capture.go (about)

     1  // Copyright 2012 Google, Inc. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style license
     4  // that can be found in the LICENSE file in the root of the source
     5  // tree.
     6  //go:build linux
     7  
     8  package pcapgo
     9  
    10  import (
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"runtime"
    15  	"sync"
    16  	"sync/atomic"
    17  	"syscall"
    18  	"time"
    19  	"unsafe"
    20  
    21  	"golang.org/x/net/bpf"
    22  	"golang.org/x/sys/unix"
    23  
    24  	"github.com/gopacket/gopacket"
    25  )
    26  
    27  var hdrLen = unix.CmsgSpace(0)
    28  var auxLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.TpacketAuxdata{})))
    29  var timensLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.Timespec{})))
    30  var timeLen = unix.CmsgSpace(int(unsafe.Sizeof(unix.Timeval{})))
    31  
    32  func htons(data uint16) uint16 { return data<<8 | data>>8 }
    33  
    34  // EthernetHandle holds shared buffers and file descriptor of af_packet socket
    35  type EthernetHandle struct {
    36  	fd     uintptr
    37  	buffer []byte
    38  	oob    []byte
    39  	ancil  []interface{}
    40  	mu     sync.Mutex
    41  	intf   int
    42  	addr   net.HardwareAddr
    43  }
    44  
    45  // readOne reads a packet from the handle and returns a capture info + vlan info
    46  func (h *EthernetHandle) readOne() (ci gopacket.CaptureInfo, vlan int, haveVlan bool, err error) {
    47  	// we could use unix.Recvmsg, but that does a memory allocation (for the returned sockaddr) :(
    48  	var msg unix.Msghdr
    49  	var sa unix.RawSockaddrLinklayer
    50  	var handle = atomic.LoadUintptr(&h.fd)
    51  
    52  	/*
    53  	 * check if the handle got closed already
    54  	 * if so return EOF to also stop waiting for packets
    55  	 */
    56  	if int(handle) < 0 {
    57  		err = io.EOF
    58  		return
    59  	}
    60  
    61  	msg.Name = (*byte)(unsafe.Pointer(&sa))
    62  	msg.Namelen = uint32(unsafe.Sizeof(sa))
    63  
    64  	var iov unix.Iovec
    65  	if len(h.buffer) > 0 {
    66  		iov.Base = &h.buffer[0]
    67  		iov.SetLen(len(h.buffer))
    68  	}
    69  	msg.Iov = &iov
    70  	msg.Iovlen = 1
    71  
    72  	if len(h.oob) > 0 {
    73  		msg.Control = &h.oob[0]
    74  		msg.SetControllen(len(h.oob))
    75  	}
    76  
    77  	/*
    78  	 * use msg_trunc, so we know packet size without auxdata, which might be missing
    79  	 */
    80  	n, _, e := syscall.Syscall(unix.SYS_RECVMSG, handle, uintptr(unsafe.Pointer(&msg)), uintptr(unix.MSG_TRUNC))
    81  
    82  	switch {
    83  	case e.Temporary() || e.Timeout():
    84  		return ci, 0, false, e
    85  	case e != 0:
    86  		return ci, 0, false, fmt.Errorf("couldn't read packet: %w", e)
    87  	}
    88  
    89  	if sa.Family == unix.AF_PACKET {
    90  		ci.InterfaceIndex = int(sa.Ifindex)
    91  	} else {
    92  		ci.InterfaceIndex = h.intf
    93  	}
    94  
    95  	// custom aux parsing so we don't allocate stuff (unix.ParseSocketControlMessage allocates a slice)
    96  	// we're getting at most 2 cmsgs anyway and know which ones they are (auxdata + timestamp(ns))
    97  	oob := h.oob[:msg.Controllen]
    98  	gotAux := false
    99  
   100  	for len(oob) > hdrLen { // > hdrLen, because we also need something after the cmsg header
   101  		hdr := (*unix.Cmsghdr)(unsafe.Pointer(&oob[0]))
   102  		switch {
   103  		case hdr.Level == unix.SOL_PACKET && hdr.Type == unix.PACKET_AUXDATA && len(oob) >= auxLen:
   104  			aux := (*unix.TpacketAuxdata)(unsafe.Pointer(&oob[hdrLen]))
   105  			ci.CaptureLength = int(n)
   106  			ci.Length = int(aux.Len)
   107  			vlan = int(aux.Vlan_tci)
   108  			haveVlan = (aux.Status & unix.TP_STATUS_VLAN_VALID) != 0
   109  			gotAux = true
   110  		case hdr.Level == unix.SOL_SOCKET && hdr.Type == unix.SO_TIMESTAMPNS && len(oob) >= timensLen:
   111  			tstamp := (*unix.Timespec)(unsafe.Pointer(&oob[hdrLen]))
   112  			ci.Timestamp = time.Unix(tstamp.Sec, tstamp.Nsec)
   113  		case hdr.Level == unix.SOL_SOCKET && hdr.Type == unix.SO_TIMESTAMP && len(oob) >= timeLen:
   114  			tstamp := (*unix.Timeval)(unsafe.Pointer(&oob[hdrLen]))
   115  			ci.Timestamp = time.Unix(tstamp.Sec, int64(tstamp.Usec)*1000)
   116  		}
   117  		oob = oob[unix.CmsgSpace(int(hdr.Len))-hdrLen:]
   118  	}
   119  
   120  	if !gotAux {
   121  		// fallback for no aux cmsg
   122  		ci.CaptureLength = int(n)
   123  		ci.Length = int(n)
   124  		haveVlan = false
   125  	}
   126  
   127  	// fix up capture length if we needed to truncate
   128  	if ci.CaptureLength > len(h.buffer) {
   129  		ci.CaptureLength = len(h.buffer)
   130  	}
   131  
   132  	if ci.Timestamp.IsZero() {
   133  		// we got no timestamp info -> emulate it
   134  		ci.Timestamp = time.Now()
   135  	}
   136  
   137  	return ci, vlan, haveVlan, nil
   138  }
   139  
   140  // ReadPacketData implements gopacket.PacketDataSource. If this was captured on a vlan, the vlan id will be in the AncillaryData[0]
   141  func (h *EthernetHandle) ReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
   142  	h.mu.Lock()
   143  	ci, vlan, haveVlan, err := h.readOne()
   144  	if err != nil {
   145  		h.mu.Unlock()
   146  		return nil, gopacket.CaptureInfo{}, fmt.Errorf("couldn't read packet data: %s", err)
   147  	}
   148  
   149  	b := make([]byte, ci.CaptureLength)
   150  	copy(b, h.buffer)
   151  	h.mu.Unlock()
   152  
   153  	if haveVlan {
   154  		ci.AncillaryData = []interface{}{vlan}
   155  
   156  	}
   157  
   158  	return b, ci, nil
   159  }
   160  
   161  // ZeroCopyReadPacketData implements gopacket.ZeroCopyPacketDataSource. If this was captured on a vlan, the vlan id will be in the AncillaryData[0].
   162  // This function does not allocate memory. Beware that the next call to ZeroCopyReadPacketData will overwrite existing slices (returned data AND AncillaryData)!
   163  // Due to shared buffers this must not be called concurrently
   164  func (h *EthernetHandle) ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo, error) {
   165  	ci, vlan, haveVlan, err := h.readOne()
   166  	if err != nil {
   167  		return nil, gopacket.CaptureInfo{}, fmt.Errorf("couldn't read packet data: %s", err)
   168  	}
   169  
   170  	if haveVlan {
   171  		h.ancil[0] = vlan
   172  		ci.AncillaryData = h.ancil
   173  	}
   174  
   175  	return h.buffer[:ci.CaptureLength], ci, nil
   176  }
   177  
   178  // Close closes the underlying socket
   179  func (h *EthernetHandle) Close() (err error) {
   180  	if handle := atomic.LoadUintptr(&h.fd); handle != 0 {
   181  		_ = unix.Shutdown(int(handle), unix.SHUT_RDWR)
   182  		// close no matter if shutdown returned an error or not to make sure the socket is closed
   183  		err = unix.Close(int(handle))
   184  		atomic.SwapUintptr(&h.fd, 0)
   185  		runtime.SetFinalizer(h, nil)
   186  	}
   187  	return err
   188  }
   189  
   190  // SetCaptureLength sets the maximum capture length to the given value
   191  func (h *EthernetHandle) SetCaptureLength(len int) error {
   192  	if len < 0 {
   193  		return fmt.Errorf("illegal capture length %d. Must be at least 0", len)
   194  	}
   195  	h.buffer = make([]byte, len)
   196  	return nil
   197  }
   198  
   199  // GetCaptureLength returns the maximum capture length
   200  func (h *EthernetHandle) GetCaptureLength() int {
   201  	return len(h.buffer)
   202  }
   203  
   204  // 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.
   205  // If a filter was already attached, it will be overwritten. To remove the filter, provide an empty slice.
   206  func (h *EthernetHandle) SetBPF(filter []bpf.RawInstruction) error {
   207  	if len(filter) == 0 {
   208  		return unix.SetsockoptInt(int(h.fd), unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
   209  	}
   210  	f := make([]unix.SockFilter, len(filter))
   211  	for i := range filter {
   212  		f[i].Code = filter[i].Op
   213  		f[i].Jf = filter[i].Jf
   214  		f[i].Jt = filter[i].Jt
   215  		f[i].K = filter[i].K
   216  	}
   217  	fprog := &unix.SockFprog{
   218  		Len:    uint16(len(filter)),
   219  		Filter: &f[0],
   220  	}
   221  	return unix.SetsockoptSockFprog(int(h.fd), unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, fprog)
   222  }
   223  
   224  // LocalAddr returns the local network address
   225  func (h *EthernetHandle) LocalAddr() net.HardwareAddr {
   226  	// Hardware Address might have changed. Fetch new one and fall back to the stored one if fetching interface fails
   227  	intf, err := net.InterfaceByIndex(h.intf)
   228  	if err == nil {
   229  		h.addr = intf.HardwareAddr
   230  	}
   231  	return h.addr
   232  }
   233  
   234  // SetPromiscuous sets promiscous mode to the required value. If it is enabled, traffic not destined for the interface will also be captured.
   235  func (h *EthernetHandle) SetPromiscuous(b bool) error {
   236  	mreq := unix.PacketMreq{
   237  		Ifindex: int32(h.intf),
   238  		Type:    unix.PACKET_MR_PROMISC,
   239  	}
   240  
   241  	opt := unix.PACKET_ADD_MEMBERSHIP
   242  	if !b {
   243  		opt = unix.PACKET_DROP_MEMBERSHIP
   244  	}
   245  
   246  	return unix.SetsockoptPacketMreq(int(h.fd), unix.SOL_PACKET, opt, &mreq)
   247  }
   248  
   249  // 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!).
   250  func (h *EthernetHandle) Stats() (*unix.TpacketStats, error) {
   251  	return unix.GetsockoptTpacketStats(int(h.fd), unix.SOL_PACKET, unix.PACKET_STATISTICS)
   252  }
   253  
   254  // NewEthernetHandle implements pcap.OpenLive for network devices.
   255  // If you want better performance have a look at github.com/gopacket/gopacket/afpacket.
   256  // SetCaptureLength can be used to limit the maximum capture length.
   257  func NewEthernetHandle(ifname string) (*EthernetHandle, error) {
   258  	intf, err := net.InterfaceByName(ifname)
   259  	if err != nil {
   260  		return nil, fmt.Errorf("couldn't query interface %s: %s", ifname, err)
   261  	}
   262  
   263  	fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, int(htons(unix.ETH_P_ALL)))
   264  	if err != nil {
   265  		return nil, fmt.Errorf("couldn't open packet socket: %s", err)
   266  	}
   267  
   268  	addr := unix.SockaddrLinklayer{
   269  		Protocol: htons(unix.ETH_P_ALL),
   270  		Ifindex:  intf.Index,
   271  	}
   272  
   273  	if err := unix.Bind(fd, &addr); err != nil {
   274  		return nil, fmt.Errorf("couldn't bind to interface %s: %s", ifname, err)
   275  	}
   276  
   277  	ooblen := 0
   278  
   279  	if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_AUXDATA, 1); err != nil {
   280  		// we can't get auxdata -> no vlan info
   281  	} else {
   282  		ooblen += auxLen
   283  	}
   284  
   285  	if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMPNS, 1); err != nil {
   286  		// no nanosecond resolution :( -> try ms
   287  		if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMP, 1); err != nil {
   288  			// if this doesn't work we well use time.Now() -> ignore errors here
   289  		} else {
   290  			ooblen += timeLen
   291  		}
   292  	} else {
   293  		ooblen += timensLen
   294  	}
   295  
   296  	handle := &EthernetHandle{
   297  		fd:     uintptr(fd),
   298  		buffer: make([]byte, intf.MTU),
   299  		oob:    make([]byte, ooblen),
   300  		ancil:  make([]interface{}, 1),
   301  		intf:   intf.Index,
   302  		addr:   intf.HardwareAddr,
   303  	}
   304  	runtime.SetFinalizer(handle, (*EthernetHandle).Close)
   305  	return handle, nil
   306  }