mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
### performance - handling of the very big packet(any size that can be buffered) - speeding up TCP sessions by using message hints: Added **proto.HasFullPayload** that helps to validate the entire HTTP request, it supports `Chunked` encoding too! Added **proto.HasRequestTitle** and **proto.HasResponseTitle** for validating the beginning of HTTP request. Those methods are used `input_raw.go` with `TCP`. - supports Keep-Alive: the above functions helps to support keep-alive ### Packaging - **capture:** engines(capture/doc.go) - **tcp:** tcp message parser (tcp/doc.go) ### benchmarking - **capture.BenchmarkPcapDump:** the benchmarks regarding dumping packets in a pcap file - **capture.BenchmarkPcapFile:** the benchmarks of reading packets from a pcap file - **capture.BenchmarkPcap:** the benchmarks of parsing packets from the loopback interface with pcap handles - **proto.BenchmarkHasFullPayload:**: benchmarking this function which validates the HTTP payload - **tcp.BenchmarkPacketParseAndSort:** benchmarks of parsing and sorting packets - **tcp.BenchmarkMessageParserWithoutHint:** benchmarks of message reasembling by using `SYN` and `FIN` flag - **tcp.BenchmarkMessageParserWithHint:** benchmarks of message reasembling by using `proto.HasRequestTitle` and `proto.HasFullPayload` flag ### issues see linked issues ### tests - fixed input raw and engine tests **Most of the changed of the files, was about using functionalities of** `tcp` **and** `capture` **in existing functionalities**
236 lines
4.8 KiB
Go
236 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"hash/fnv"
|
|
"io"
|
|
"log"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type emitter struct {
|
|
sync.Mutex
|
|
sync.WaitGroup
|
|
quit chan int
|
|
plugins *InOutPlugins
|
|
}
|
|
|
|
// NewEmitter creates and initializes new `emitter` object.
|
|
func NewEmitter(quit chan int) *emitter {
|
|
return &emitter{
|
|
quit: quit,
|
|
}
|
|
}
|
|
|
|
// Start initialize loop for sending data from inputs to outputs
|
|
func (e *emitter) Start(plugins *InOutPlugins, middlewareCmd string) {
|
|
defer e.Wait()
|
|
if Settings.CopyBufferSize < 1 {
|
|
Settings.CopyBufferSize = 5 << 20
|
|
}
|
|
e.plugins = plugins
|
|
|
|
if middlewareCmd != "" {
|
|
middleware := NewMiddleware(middlewareCmd)
|
|
|
|
for _, in := range plugins.Inputs {
|
|
middleware.ReadFrom(in)
|
|
}
|
|
|
|
// We are going only to read responses, so using same ReadFrom method
|
|
for _, out := range plugins.Outputs {
|
|
if r, ok := out.(io.Reader); ok {
|
|
middleware.ReadFrom(r)
|
|
}
|
|
}
|
|
e.Add(1)
|
|
go func() {
|
|
defer e.Done()
|
|
if err := CopyMulty(e.quit, middleware, plugins.Outputs...); err != nil {
|
|
Debug(2, "Error during copy: ", err)
|
|
e.Close()
|
|
}
|
|
}()
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-e.quit:
|
|
middleware.Close()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
} else {
|
|
for _, in := range plugins.Inputs {
|
|
e.Add(1)
|
|
go func(in io.Reader) {
|
|
defer e.Done()
|
|
if err := CopyMulty(e.quit, in, plugins.Outputs...); err != nil {
|
|
Debug(2, "Error during copy: ", err)
|
|
e.Close()
|
|
}
|
|
}(in)
|
|
}
|
|
|
|
for _, out := range plugins.Outputs {
|
|
if r, ok := out.(io.Reader); ok {
|
|
e.Add(1)
|
|
go func(r io.Reader) {
|
|
defer e.Done()
|
|
if err := CopyMulty(e.quit, r, plugins.Outputs...); err != nil {
|
|
Debug(2, "Error during copy: ", err)
|
|
e.Close()
|
|
}
|
|
}(r)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *emitter) close() {
|
|
select {
|
|
case <-e.quit:
|
|
default:
|
|
close(e.quit)
|
|
}
|
|
}
|
|
|
|
// Close closes all the goroutine and waits for it to finish.
|
|
func (e *emitter) Close() {
|
|
e.close()
|
|
for _, p := range e.plugins.Inputs {
|
|
if cp, ok := p.(io.Closer); ok {
|
|
cp.Close()
|
|
}
|
|
}
|
|
for _, p := range e.plugins.Outputs {
|
|
if cp, ok := p.(io.Closer); ok {
|
|
cp.Close()
|
|
}
|
|
}
|
|
e.plugins = nil // avoid further accidental usage
|
|
}
|
|
|
|
// CopyMulty copies from 1 reader to multiple writers
|
|
func CopyMulty(stop chan int, src io.Reader, writers ...io.Writer) error {
|
|
buf := make([]byte, Settings.CopyBufferSize)
|
|
wIndex := 0
|
|
modifier := NewHTTPModifier(&Settings.ModifierConfig)
|
|
filteredRequests := make(map[string]time.Time)
|
|
filteredRequestsLastCleanTime := time.Now()
|
|
|
|
i := 0
|
|
for {
|
|
var nr int
|
|
nr, err := src.Read(buf)
|
|
|
|
select {
|
|
case <-stop:
|
|
return nil
|
|
default:
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_maxN := nr
|
|
if nr > 500 {
|
|
_maxN = 500
|
|
}
|
|
if nr > 0 {
|
|
payload := buf[:nr]
|
|
meta := payloadMeta(payload)
|
|
if len(meta) < 3 {
|
|
Debug(2, "[EMITTER] Found malformed record", string(payload[0:_maxN]), nr, "from:", src)
|
|
continue
|
|
}
|
|
requestID := string(meta[1])
|
|
|
|
Debug(3, "[EMITTER] input:", string(payload[0:_maxN]), nr, "from:", src)
|
|
|
|
if modifier != nil {
|
|
if isRequestPayload(payload) {
|
|
headSize := bytes.IndexByte(payload, '\n') + 1
|
|
body := payload[headSize:]
|
|
originalBodyLen := len(body)
|
|
body = modifier.Rewrite(body)
|
|
|
|
// If modifier tells to skip request
|
|
if len(body) == 0 {
|
|
filteredRequests[requestID] = time.Now()
|
|
continue
|
|
}
|
|
|
|
if originalBodyLen != len(body) {
|
|
payload = append(payload[:headSize], body...)
|
|
}
|
|
|
|
Debug(3, "[EMITTER] Rewritten input:", len(payload), "First %d bytes:", _maxN, string(payload[0:_maxN]))
|
|
|
|
} else {
|
|
if _, ok := filteredRequests[requestID]; ok {
|
|
delete(filteredRequests, requestID)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
if Settings.PrettifyHTTP {
|
|
payload = prettifyHTTP(payload)
|
|
if len(payload) == 0 {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if Settings.SplitOutput {
|
|
if Settings.RecognizeTCPSessions {
|
|
if !PRO {
|
|
log.Fatal("Detailed TCP sessions work only with PRO license")
|
|
}
|
|
hasher := fnv.New32a()
|
|
// First 20 bytes contain tcp session
|
|
id := payloadID(payload)
|
|
hasher.Write(id)
|
|
|
|
wIndex = int(hasher.Sum32()) % len(writers)
|
|
writers[wIndex].Write(payload)
|
|
} else {
|
|
// Simple round robin
|
|
if _, err := writers[wIndex].Write(payload); err != nil {
|
|
return err
|
|
}
|
|
|
|
wIndex++
|
|
|
|
if wIndex >= len(writers) {
|
|
wIndex = 0
|
|
}
|
|
}
|
|
} else {
|
|
for _, dst := range writers {
|
|
if _, err := dst.Write(payload); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run GC on each 1000 request
|
|
if i%1000 == 0 {
|
|
// Clean up filtered requests for which we didn't get a response to filter
|
|
now := time.Now()
|
|
if now.Sub(filteredRequestsLastCleanTime) > 60*time.Second {
|
|
for k, v := range filteredRequests {
|
|
if now.Sub(v) > 60*time.Second {
|
|
delete(filteredRequests, k)
|
|
}
|
|
}
|
|
filteredRequestsLastCleanTime = time.Now()
|
|
}
|
|
}
|
|
|
|
i++
|
|
}
|
|
}
|