mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
Now you are be able to capture traffic inside k8s like this: ``` gor --input-raw k8s://namespace/deployment/app:80 --output-http http://replay.com ``` Supported format for filtering required pods: ``` k8s://[namespace/]pod/[pod_name] - k8s://default/pod/nginx-7848d4b86f-5nxz8 k8s://[namespace/]deployment/[deployment_name] - k8s://default/deployment/nginx k8s://[namespace/]daemonset/[daemonset_name] - k8s://default/daemonset/nginx k8s://[namespace/]labelSelector/[selector] - k8s://default/labelSelector/app=nginx k8s://[namespace/]fieldSelector/[selector] - k8s://default/fieldSelector/metadata.name=nginx-7848d4b86f-5nxz8 ``` `namespace` is optional, omit to use all namespaces: `k8s://labelSelector/app=replay` GoReplay designed to be running running as a daemonset (e.g. on each physical k8s node). See the full guide in here: https://github.com/buger/goreplay/blob/ca8205a5c5d2a1facb00214c78e4120aae6d772d/k8s/README.md
177 lines
3.9 KiB
Go
177 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/buger/goreplay/capture"
|
|
"github.com/buger/goreplay/proto"
|
|
"github.com/buger/goreplay/tcp"
|
|
)
|
|
|
|
// RAWInputConfig represents configuration that can be applied on raw input
|
|
type RAWInputConfig = capture.PcapOptions
|
|
|
|
// RAWInput used for intercepting traffic for given address
|
|
type RAWInput struct {
|
|
sync.Mutex
|
|
config RAWInputConfig
|
|
messageStats []tcp.Stats
|
|
listener *capture.Listener
|
|
messageParser *tcp.MessageParser
|
|
cancelListener context.CancelFunc
|
|
closed bool
|
|
|
|
quit chan bool // Channel used only to indicate goroutine should shutdown
|
|
host string
|
|
ports []uint16
|
|
}
|
|
|
|
// NewRAWInput constructor for RAWInput. Accepts raw input config as arguments.
|
|
func NewRAWInput(address string, config RAWInputConfig) (i *RAWInput) {
|
|
i = new(RAWInput)
|
|
i.config = config
|
|
i.quit = make(chan bool)
|
|
|
|
host, _ports, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
// If we are reading pcap file, no port needed
|
|
if strings.HasSuffix(address, "pcap") {
|
|
host = address
|
|
_ports = "0"
|
|
err = nil
|
|
} else if strings.HasPrefix(address, "k8s://") {
|
|
portIndex := strings.LastIndex(address, ":")
|
|
host = address[:portIndex]
|
|
_ports = address[portIndex+1:]
|
|
} else {
|
|
log.Fatalf("input-raw: error while parsing address: %s", err)
|
|
}
|
|
}
|
|
|
|
if strings.HasSuffix(host, "pcap") {
|
|
i.config.Engine = capture.EnginePcapFile
|
|
}
|
|
|
|
var ports []uint16
|
|
if _ports != "" {
|
|
portsStr := strings.Split(_ports, ",")
|
|
|
|
for _, portStr := range portsStr {
|
|
port, err := strconv.Atoi(strings.TrimSpace(portStr))
|
|
if err != nil {
|
|
log.Fatalf("parsing port error: %v", err)
|
|
}
|
|
ports = append(ports, uint16(port))
|
|
|
|
}
|
|
}
|
|
|
|
i.host = host
|
|
i.ports = ports
|
|
|
|
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:
|
|
return nil, ErrorStopped
|
|
case msgTCP = <-i.listener.Messages():
|
|
msg.Data = msgTCP.Data()
|
|
}
|
|
|
|
var msgType byte = ResponsePayload
|
|
if msgTCP.Direction == tcp.DirIncoming {
|
|
msgType = RequestPayload
|
|
if i.config.RealIPHeader != "" {
|
|
msg.Data = proto.SetHeader(msg.Data, []byte(i.config.RealIPHeader), []byte(msgTCP.SrcAddr))
|
|
}
|
|
}
|
|
msg.Meta = payloadHeader(msgType, msgTCP.UUID(), msgTCP.Start.UnixNano(), msgTCP.End.UnixNano()-msgTCP.Start.UnixNano())
|
|
|
|
// to be removed....
|
|
if msgTCP.Truncated {
|
|
Debug(2, "[INPUT-RAW] message truncated, increase copy-buffer-size")
|
|
}
|
|
// to be removed...
|
|
if msgTCP.TimedOut {
|
|
Debug(2, "[INPUT-RAW] message timeout reached, increase input-raw-expire")
|
|
}
|
|
if i.config.Stats {
|
|
stat := msgTCP.Stats
|
|
go i.addStats(stat)
|
|
}
|
|
msgTCP = nil
|
|
return &msg, nil
|
|
}
|
|
|
|
func (i *RAWInput) listen(address string) {
|
|
var err error
|
|
i.listener, err = capture.NewListener(i.host, i.ports, i.config)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
err = i.listener.Activate()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var ctx context.Context
|
|
ctx, i.cancelListener = context.WithCancel(context.Background())
|
|
errCh := i.listener.ListenBackground(ctx)
|
|
<-i.listener.Reading
|
|
Debug(1, i)
|
|
go func() {
|
|
<-errCh // the listener closed voluntarily
|
|
i.Close()
|
|
}()
|
|
}
|
|
|
|
func (i *RAWInput) String() string {
|
|
return fmt.Sprintf("Intercepting traffic from: %s:%s", i.host, strings.Join(strings.Fields(fmt.Sprint(i.ports)), ","))
|
|
}
|
|
|
|
// GetStats returns the stats so far and reset the stats
|
|
func (i *RAWInput) GetStats() []tcp.Stats {
|
|
i.Lock()
|
|
defer func() {
|
|
i.messageStats = []tcp.Stats{}
|
|
i.Unlock()
|
|
}()
|
|
return i.messageStats
|
|
}
|
|
|
|
// Close closes the input raw listener
|
|
func (i *RAWInput) Close() error {
|
|
i.Lock()
|
|
defer i.Unlock()
|
|
if i.closed {
|
|
return nil
|
|
}
|
|
i.cancelListener()
|
|
close(i.quit)
|
|
i.closed = true
|
|
return nil
|
|
}
|
|
|
|
func (i *RAWInput) addStats(mStats tcp.Stats) {
|
|
i.Lock()
|
|
if len(i.messageStats) >= 10000 {
|
|
i.messageStats = []tcp.Stats{}
|
|
}
|
|
i.messageStats = append(i.messageStats, mStats)
|
|
i.Unlock()
|
|
}
|