mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
218 lines
5.3 KiB
Go
218 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/buger/goreplay/capture"
|
|
"github.com/buger/goreplay/proto"
|
|
"github.com/buger/goreplay/size"
|
|
"github.com/buger/goreplay/stat"
|
|
"github.com/buger/goreplay/tcp"
|
|
)
|
|
|
|
// TCPProtocol is a number to indicate type of protocol
|
|
type TCPProtocol uint8
|
|
|
|
const (
|
|
// ProtocolHTTP ...
|
|
ProtocolHTTP TCPProtocol = iota
|
|
// ProtocolBinary ...
|
|
ProtocolBinary
|
|
)
|
|
|
|
// Set is here so that TCPProtocol can implement flag.Var
|
|
func (protocol *TCPProtocol) Set(v string) error {
|
|
switch v {
|
|
case "", "http":
|
|
*protocol = ProtocolHTTP
|
|
case "binary":
|
|
*protocol = ProtocolBinary
|
|
default:
|
|
return fmt.Errorf("unsupported protocol %s", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (protocol *TCPProtocol) String() string {
|
|
switch *protocol {
|
|
case ProtocolBinary:
|
|
return "binary"
|
|
case ProtocolHTTP:
|
|
return "http"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// RAWInputConfig represents configuration that can be applied on raw input
|
|
type RAWInputConfig struct {
|
|
capture.PcapOptions
|
|
Expire time.Duration `json:"input-raw-expire"`
|
|
CopyBufferSize size.Size `json:"copy-buffer-size"`
|
|
Engine capture.EngineType `json:"input-raw-engine"`
|
|
TrackResponse bool `json:"input-raw-track-response"`
|
|
Protocol TCPProtocol `json:"input-raw-protocol"`
|
|
RealIPHeader string `json:"input-raw-realip-header"`
|
|
Stats bool `json:"input-raw-stats"`
|
|
quit chan bool // Channel used only to indicate goroutine should shutdown
|
|
host string
|
|
port uint16
|
|
}
|
|
|
|
// RAWInput used for intercepting traffic for given address
|
|
type RAWInput struct {
|
|
sync.Mutex
|
|
RAWInputConfig
|
|
st *stat.Writer
|
|
listener *capture.Listener
|
|
message chan *tcp.Message
|
|
cancelListener context.CancelFunc
|
|
}
|
|
|
|
// NewRAWInput constructor for RAWInput. Accepts raw input config as arguments.
|
|
func NewRAWInput(address string, config RAWInputConfig) (i *RAWInput) {
|
|
i = new(RAWInput)
|
|
i.RAWInputConfig = config
|
|
i.message = make(chan *tcp.Message, 1000)
|
|
i.quit = make(chan bool)
|
|
var host, _port string
|
|
var err error
|
|
var port int
|
|
host, _port, err = net.SplitHostPort(address)
|
|
if err != nil {
|
|
log.Fatalf("input-raw: error while parsing address: %s", err)
|
|
}
|
|
if _port != "" {
|
|
port, err = strconv.Atoi(_port)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Fatalf("parsing port error: %v", err)
|
|
}
|
|
i.host = host
|
|
i.port = uint16(port)
|
|
if i.Stats {
|
|
i.st = initializeStatWriter("input_raw_message.csv", "type: GOR_STAT\ntitle: raw messages info")
|
|
statsWriteRecord(i.st, []string{
|
|
"length", "lostdata", "source", "destination", "ipversion",
|
|
"timedout", "truncated", "id",
|
|
}, "[INPUT-RAW]")
|
|
}
|
|
|
|
i.listen(address)
|
|
|
|
return
|
|
}
|
|
|
|
// PluginRead reads meassage from this plugin
|
|
func (i *RAWInput) PluginRead() (*Message, error) {
|
|
var msgTCP *tcp.Message
|
|
var msg Message
|
|
select {
|
|
case <-i.quit:
|
|
if i.st != nil {
|
|
i.st.Close()
|
|
i.st = nil
|
|
}
|
|
return nil, ErrorStopped
|
|
case msgTCP = <-i.message:
|
|
msg.Data = msgTCP.Data()
|
|
}
|
|
var msgType byte = ResponsePayload
|
|
timestamp := msgTCP.Start.UnixNano()
|
|
latency := msgTCP.End.UnixNano() - timestamp
|
|
if msgTCP.IsIncoming {
|
|
msgType = RequestPayload
|
|
if i.RealIPHeader != "" {
|
|
msg.Data = proto.SetHeader(msg.Data, []byte(i.RealIPHeader), []byte(msgTCP.SrcAddr))
|
|
}
|
|
}
|
|
id := msgTCP.UUID()
|
|
msg.Meta = payloadHeader(msgType, id, timestamp, latency)
|
|
|
|
if i.Stats && i.st != nil {
|
|
statsWriteRecord(i.st, getStats(msgTCP.Stats, id), "[INPUT-RAW]")
|
|
}
|
|
msgTCP = nil
|
|
return &msg, nil
|
|
}
|
|
|
|
func (i *RAWInput) listen(address string) {
|
|
var err error
|
|
i.listener, err = capture.NewListener(i.host, i.port, "", i.Engine, i.TrackResponse)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
i.listener.SetPcapOptions(i.PcapOptions)
|
|
err = i.listener.Activate()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
pool := tcp.NewMessagePool(i.CopyBufferSize, i.Expire, Debug, i.handler)
|
|
pool.MatchUUID(i.TrackResponse)
|
|
if i.Protocol == ProtocolHTTP {
|
|
pool.Start = http1StartHint
|
|
}
|
|
var ctx context.Context
|
|
ctx, i.cancelListener = context.WithCancel(context.Background())
|
|
errCh := i.listener.ListenBackground(ctx, pool.Handler)
|
|
select {
|
|
case err := <-errCh:
|
|
log.Fatal(err)
|
|
case <-i.listener.Reading:
|
|
Debug(1, i)
|
|
}
|
|
}
|
|
|
|
func (i *RAWInput) handler(m *tcp.Message) {
|
|
i.message <- m
|
|
}
|
|
|
|
func (i *RAWInput) String() string {
|
|
return fmt.Sprintf("Intercepting traffic from: %s:%d", i.host, i.port)
|
|
}
|
|
|
|
// GetStats returns the stats so far and reset the stats
|
|
func getStats(stat tcp.Stats, id []byte) []string {
|
|
var s [8]string
|
|
|
|
s[0] = strconv.Itoa(stat.Length) // length
|
|
s[1] = strconv.Itoa(stat.LostData) // lostdata
|
|
s[2] = stat.SrcAddr // source
|
|
s[3] = stat.DstAddr // destination
|
|
s[4] = string(stat.IPversion + '0') // ipversion
|
|
if stat.TimedOut { // timedout
|
|
s[5] = "true"
|
|
} else {
|
|
s[5] = "false"
|
|
}
|
|
if stat.Truncated { // truncated
|
|
s[6] = "true"
|
|
} else {
|
|
s[6] = "false"
|
|
}
|
|
s[7] = string(id)
|
|
return s[:]
|
|
}
|
|
|
|
// Close closes the input raw listener
|
|
func (i *RAWInput) Close() error {
|
|
i.cancelListener()
|
|
close(i.quit)
|
|
return nil
|
|
}
|
|
|
|
func http1StartHint(pckt *tcp.Packet) (isIncoming, isOutgoing bool) {
|
|
isIncoming = proto.HasRequestTitle(pckt.Payload)
|
|
if isIncoming {
|
|
return
|
|
}
|
|
return false, proto.HasResponseTitle(pckt.Payload)
|
|
}
|