mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
This is to make socket implementations independent of the host OS. this is done by creating an interface that define all required behaviors of the socket.
501 lines
12 KiB
Go
501 lines
12 KiB
Go
package capture
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/buger/goreplay/size"
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/google/gopacket/pcap"
|
|
)
|
|
|
|
// Handler is a function that is used to handle packets
|
|
type Handler func(gopacket.Packet)
|
|
|
|
// PcapOptions options that can be set on a pcap capture handle,
|
|
// these options take effect on inactive pcap handles
|
|
type PcapOptions struct {
|
|
BufferTimeout time.Duration `json:"input-raw-buffer-timeout"`
|
|
TimestampType string `json:"input-raw-timestamp-type"`
|
|
BPFFilter string `json:"input-raw-bpf-filter"`
|
|
BufferSize size.Size `json:"input-raw-buffer-size"`
|
|
Promiscuous bool `json:"input-raw-promisc"`
|
|
Monitor bool `json:"input-raw-monitor"`
|
|
Snaplen bool `json:"input-raw-override-snaplen"`
|
|
}
|
|
|
|
// NetInterface represents network interface
|
|
type NetInterface struct {
|
|
net.Interface
|
|
IPs []string
|
|
}
|
|
|
|
// Listener handle traffic capture, this is its representation.
|
|
type Listener struct {
|
|
sync.Mutex
|
|
Transport string // transport layer default to tcp
|
|
Activate func() error // function is used to activate the engine. it must be called before reading packets
|
|
Handles map[string]gopacket.PacketDataSource
|
|
Interfaces []NetInterface
|
|
loopIndex int
|
|
Reading chan bool // this channel is closed when the listener has started reading packets
|
|
PcapOptions
|
|
Engine EngineType
|
|
port uint16 // src or/and dst port
|
|
trackResponse bool
|
|
|
|
host string // pcap file name or interface (name, hardware addr, index or ip address)
|
|
|
|
quit chan bool
|
|
packets chan gopacket.Packet
|
|
}
|
|
|
|
// EngineType ...
|
|
type EngineType uint8
|
|
|
|
// Available engines for intercepting traffic
|
|
const (
|
|
EnginePcap EngineType = 1 << iota
|
|
EnginePcapFile
|
|
EngineRawSocket
|
|
)
|
|
|
|
// Set is here so that EngineType can implement flag.Var
|
|
func (eng *EngineType) Set(v string) error {
|
|
switch v {
|
|
case "", "libpcap":
|
|
*eng = EnginePcap
|
|
case "pcap_file":
|
|
*eng = EnginePcapFile
|
|
case "raw_socket", "af_packet":
|
|
*eng = EngineRawSocket
|
|
default:
|
|
return fmt.Errorf("invalid engine %s", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (eng *EngineType) String() (e string) {
|
|
switch *eng {
|
|
case EnginePcapFile:
|
|
e = "pcap_file"
|
|
case EnginePcap:
|
|
e = "libpcap"
|
|
case EngineRawSocket:
|
|
e = "raw_socket"
|
|
default:
|
|
e = ""
|
|
}
|
|
return e
|
|
}
|
|
|
|
// NewListener creates and initialize a new Listener. if transport or/and engine are invalid/unsupported
|
|
// is "tcp" and "pcap", are assumed. l.Engine and l.Transport can help to get the values used.
|
|
// if there is an error it will be associated with getting network interfaces
|
|
func NewListener(host string, port uint16, transport string, engine EngineType, trackResponse bool) (l *Listener, err error) {
|
|
l = &Listener{}
|
|
|
|
l.host = host
|
|
l.port = port
|
|
l.Transport = "tcp"
|
|
if transport != "" {
|
|
l.Transport = transport
|
|
}
|
|
l.Handles = make(map[string]gopacket.PacketDataSource)
|
|
l.trackResponse = trackResponse
|
|
l.packets = make(chan gopacket.Packet, 1000)
|
|
l.quit = make(chan bool, 1)
|
|
l.Reading = make(chan bool, 1)
|
|
switch engine {
|
|
default:
|
|
l.Engine = EnginePcap
|
|
l.Activate = l.activatePcap
|
|
case EngineRawSocket:
|
|
l.Engine = EngineRawSocket
|
|
l.Activate = l.activateRawSocket
|
|
case EnginePcapFile:
|
|
l.Engine = EnginePcapFile
|
|
l.Activate = l.activatePcapFile
|
|
return
|
|
}
|
|
err = l.setInterfaces()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetPcapOptions set pcap options for all yet to be actived pcap handles
|
|
// setting this on already activated handles will not have any effect
|
|
func (l *Listener) SetPcapOptions(opts PcapOptions) {
|
|
l.PcapOptions = opts
|
|
}
|
|
|
|
// Listen listens for packets from the handles, and call handler on every packet received
|
|
// until the context done signal is sent or EOF on handles.
|
|
// this function should be called after activating pcap handles
|
|
func (l *Listener) Listen(ctx context.Context, handler Handler) (err error) {
|
|
l.read()
|
|
done := ctx.Done()
|
|
var p gopacket.Packet
|
|
var ok bool
|
|
for {
|
|
select {
|
|
case <-done:
|
|
l.quit <- true
|
|
close(l.quit)
|
|
err = ctx.Err()
|
|
done = nil
|
|
case p, ok = <-l.packets:
|
|
if !ok {
|
|
return
|
|
}
|
|
if p == nil {
|
|
continue
|
|
}
|
|
handler(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ListenBackground is like listen but can run concurrently and signal error through channel
|
|
func (l *Listener) ListenBackground(ctx context.Context, handler Handler) chan error {
|
|
err := make(chan error, 1)
|
|
go func() {
|
|
defer close(err)
|
|
if e := l.Listen(ctx, handler); err != nil {
|
|
err <- e
|
|
}
|
|
}()
|
|
return err
|
|
}
|
|
|
|
// Filter returns automatic filter applied by goreplay
|
|
// to a pcap handle of a specific interface
|
|
func (l *Listener) Filter(ifi NetInterface) (filter string) {
|
|
// https://www.tcpdump.org/manpages/pcap-filter.7.html
|
|
|
|
port := fmt.Sprintf("portrange 0-%d", 1<<16-1)
|
|
if l.port != 0 {
|
|
port = fmt.Sprintf("port %d", l.port)
|
|
}
|
|
dir := " dst " // direction
|
|
if l.trackResponse {
|
|
dir = " "
|
|
}
|
|
filter = fmt.Sprintf("(%s%s%s)", l.Transport, dir, port)
|
|
if listenAll(l.host) || isDevice(l.host, ifi) {
|
|
return
|
|
}
|
|
filter = fmt.Sprintf("(%s%s%s and host %s)", l.Transport, dir, port, l.host)
|
|
return
|
|
}
|
|
|
|
// PcapDumpHandler returns a handler to write packet data in PCAP
|
|
// format, See http://wiki.wireshark.org/Development/LibpcapFileFormathandler.
|
|
// if link layer is invalid Ethernet is assumed
|
|
func PcapDumpHandler(file *os.File, link layers.LinkType, debugger func(int, ...interface{})) (handler func(packet gopacket.Packet), err error) {
|
|
if link.String() == "" {
|
|
link = layers.LinkTypeEthernet
|
|
}
|
|
w := NewWriterNanos(file)
|
|
err = w.WriteFileHeader(64<<10, link)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return func(packet gopacket.Packet) {
|
|
err = w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
|
|
if err != nil && debugger != nil {
|
|
go debugger(3, err)
|
|
}
|
|
}, nil
|
|
}
|
|
|
|
// PcapHandle returns new pcap Handle from dev on success.
|
|
// this function should be called after setting all necessary options for this listener
|
|
func (l *Listener) PcapHandle(ifi NetInterface) (handle *pcap.Handle, err error) {
|
|
var inactive *pcap.InactiveHandle
|
|
inactive, err = pcap.NewInactiveHandle(ifi.Name)
|
|
if inactive != nil && err != nil {
|
|
defer inactive.CleanUp()
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inactive handle error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
if l.TimestampType != "" {
|
|
var ts pcap.TimestampSource
|
|
ts, err = pcap.TimestampSourceFromString(l.TimestampType)
|
|
err = inactive.SetTimestampSource(ts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%q: supported timestamps: %q, interface: %q", err, inactive.SupportedTimestamps(), ifi.Name)
|
|
}
|
|
}
|
|
if l.Promiscuous {
|
|
if err = inactive.SetPromisc(l.Promiscuous); err != nil {
|
|
return nil, fmt.Errorf("promiscuous mode error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
}
|
|
if l.Monitor {
|
|
if err = inactive.SetRFMon(l.Monitor); err != nil && !errors.Is(err, pcap.CannotSetRFMon) {
|
|
return nil, fmt.Errorf("monitor mode error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
}
|
|
var snap int
|
|
if l.Snaplen {
|
|
snap = 64<<10 + 200
|
|
} else if ifi.MTU > 0 {
|
|
snap = ifi.MTU + 200
|
|
}
|
|
err = inactive.SetSnapLen(snap)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot length error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
if l.BufferSize > 0 {
|
|
err = inactive.SetBufferSize(int(l.BufferSize))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("handle buffer size error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
}
|
|
if l.BufferTimeout == 0 {
|
|
l.BufferTimeout = pcap.BlockForever
|
|
}
|
|
err = inactive.SetTimeout(l.BufferTimeout)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("handle buffer timeout error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
handle, err = inactive.Activate()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("PCAP Activate device error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
if l.BPFFilter != "" {
|
|
if l.BPFFilter[0] != '(' || l.BPFFilter[len(l.BPFFilter)-1] != ')' {
|
|
l.BPFFilter = "(" + l.BPFFilter + ")"
|
|
}
|
|
} else {
|
|
l.BPFFilter = l.Filter(ifi)
|
|
}
|
|
err = handle.SetBPFFilter(l.BPFFilter)
|
|
if err != nil {
|
|
handle.Close()
|
|
return nil, fmt.Errorf("BPF filter error: %q%s, interface: %q", err, l.BPFFilter, ifi.Name)
|
|
}
|
|
return
|
|
}
|
|
|
|
// SocketHandle returns new unix ethernet handle associated with this listener settings
|
|
func (l *Listener) SocketHandle(ifi NetInterface) (handle Socket, err error) {
|
|
handle, err = NewSocket(ifi.Interface)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sock raw error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
if err = handle.SetPromiscuous(l.Promiscuous || l.Monitor); err != nil {
|
|
return nil, fmt.Errorf("promiscuous mode error: %q, interface: %q", err, ifi.Name)
|
|
}
|
|
if l.BPFFilter != "" {
|
|
if l.BPFFilter[0] != '(' || l.BPFFilter[len(l.BPFFilter)-1] != ')' {
|
|
l.BPFFilter = "(" + l.BPFFilter + ")"
|
|
}
|
|
} else {
|
|
l.BPFFilter = l.Filter(ifi)
|
|
}
|
|
if err = handle.SetBPFFilter(l.BPFFilter); err != nil {
|
|
handle.Close()
|
|
return nil, fmt.Errorf("BPF filter error: %q%s, interface: %q", err, l.BPFFilter, ifi.Name)
|
|
}
|
|
handle.SetLoopbackIndex(int32(l.loopIndex))
|
|
return
|
|
}
|
|
|
|
func (l *Listener) read() {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
for key, handle := range l.Handles {
|
|
var source *gopacket.PacketSource
|
|
linkType := layers.LinkTypeEthernet
|
|
if _, ok := handle.(*pcap.Handle); ok {
|
|
linkType = handle.(*pcap.Handle).LinkType()
|
|
}
|
|
source = gopacket.NewPacketSource(handle, linkType)
|
|
source.Lazy = true
|
|
source.NoCopy = true
|
|
ch := source.Packets()
|
|
go func(key string) {
|
|
defer l.closeHandles(key)
|
|
for {
|
|
select {
|
|
case <-l.quit:
|
|
return
|
|
case p, ok := <-ch:
|
|
if !ok {
|
|
return
|
|
}
|
|
l.packets <- p
|
|
}
|
|
}
|
|
}(key)
|
|
}
|
|
l.Reading <- true
|
|
close(l.Reading)
|
|
}
|
|
|
|
func (l *Listener) closeHandles(key string) {
|
|
l.Lock()
|
|
defer l.Unlock()
|
|
if handle, ok := l.Handles[key]; ok {
|
|
if _, ok = handle.(Socket); ok {
|
|
handle.(Socket).Close()
|
|
} else {
|
|
handle.(*pcap.Handle).Close()
|
|
}
|
|
delete(l.Handles, key)
|
|
if len(l.Handles) == 0 {
|
|
close(l.packets)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *Listener) activatePcap() error {
|
|
var e error
|
|
var msg string
|
|
for _, ifi := range l.Interfaces {
|
|
var handle *pcap.Handle
|
|
handle, e = l.PcapHandle(ifi)
|
|
if e != nil {
|
|
msg += ("\n" + e.Error())
|
|
continue
|
|
}
|
|
l.Handles[ifi.Name] = handle
|
|
}
|
|
if len(l.Handles) == 0 {
|
|
return fmt.Errorf("pcap handles error:%s", msg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Listener) activateRawSocket() error {
|
|
if runtime.GOOS != "linux" {
|
|
return fmt.Errorf("sock_raw is not stabilized on OS other than linux")
|
|
}
|
|
var msg string
|
|
var e error
|
|
for _, ifi := range l.Interfaces {
|
|
var handle Socket
|
|
handle, e = l.SocketHandle(ifi)
|
|
if e != nil {
|
|
msg += ("\n" + e.Error())
|
|
continue
|
|
}
|
|
l.Handles[ifi.Name] = handle
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (l *Listener) activatePcapFile() (err error) {
|
|
var handle *pcap.Handle
|
|
var e error
|
|
if handle, e = pcap.OpenOffline(l.host); e != nil {
|
|
return fmt.Errorf("open pcap file error: %q", e)
|
|
}
|
|
if l.BPFFilter != "" {
|
|
if l.BPFFilter[0] != '(' || l.BPFFilter[len(l.BPFFilter)-1] != ')' {
|
|
l.BPFFilter = "(" + l.BPFFilter + ")"
|
|
}
|
|
} else {
|
|
addr := l.host
|
|
l.host = ""
|
|
l.BPFFilter = l.Filter(NetInterface{})
|
|
l.host = addr
|
|
}
|
|
if e = handle.SetBPFFilter(l.BPFFilter); e != nil {
|
|
handle.Close()
|
|
return fmt.Errorf("BPF filter error: %q, filter: %s", e, l.BPFFilter)
|
|
}
|
|
l.Handles["pcap_file"] = handle
|
|
return
|
|
}
|
|
|
|
func (l *Listener) setInterfaces() (err error) {
|
|
var Ifis []NetInterface
|
|
var ifis []net.Interface
|
|
ifis, err = net.Interfaces()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < len(ifis); i++ {
|
|
if ifis[i].Flags&net.FlagLoopback != 0 {
|
|
l.loopIndex = ifis[i].Index
|
|
}
|
|
if ifis[i].Flags&net.FlagUp == 0 {
|
|
continue
|
|
}
|
|
var addrs []net.Addr
|
|
addrs, err = ifis[i].Addrs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(addrs) == 0 {
|
|
continue
|
|
}
|
|
ifi := NetInterface{}
|
|
ifi.Interface = ifis[i]
|
|
ifi.IPs = make([]string, len(addrs))
|
|
for j, addr := range addrs {
|
|
ifi.IPs[j] = cutMask(addr)
|
|
}
|
|
Ifis = append(Ifis, ifi)
|
|
}
|
|
|
|
if listenAll(l.host) {
|
|
l.Interfaces = Ifis
|
|
return
|
|
}
|
|
found := false
|
|
for _, ifi := range Ifis {
|
|
if isDevice(l.host, ifi) {
|
|
found = true
|
|
}
|
|
for _, ip := range ifi.IPs {
|
|
if ip == l.host {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
l.Interfaces = []NetInterface{ifi}
|
|
return
|
|
}
|
|
}
|
|
err = fmt.Errorf("can not find interface with addr, name or index %s", l.host)
|
|
return err
|
|
}
|
|
|
|
func cutMask(addr net.Addr) string {
|
|
mask := addr.String()
|
|
for i, v := range mask {
|
|
if v == '/' {
|
|
return mask[:i]
|
|
}
|
|
}
|
|
return mask
|
|
}
|
|
|
|
func isDevice(addr string, ifi NetInterface) bool {
|
|
return addr == ifi.Name || addr == fmt.Sprintf("%d", ifi.Index) || addr == ifi.HardwareAddr.String()
|
|
}
|
|
|
|
func listenAll(addr string) bool {
|
|
switch addr {
|
|
case "", "0.0.0.0", "[::]", "::":
|
|
return true
|
|
}
|
|
return false
|
|
}
|