2024-10-04 17:26:35 +02:00
|
|
|
package pcap_broker
|
2023-07-11 16:54:09 +02:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2023-12-18 10:51:54 +01:00
|
|
|
"errors"
|
2023-07-11 16:54:09 +02:00
|
|
|
"flag"
|
|
|
|
|
"net"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
2023-12-18 10:51:54 +01:00
|
|
|
"os/signal"
|
2023-07-11 16:54:09 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/google/shlex"
|
|
|
|
|
|
|
|
|
|
"github.com/google/gopacket"
|
|
|
|
|
"github.com/google/gopacket/pcap"
|
|
|
|
|
"github.com/google/gopacket/pcapgo"
|
2023-12-18 10:51:54 +01:00
|
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
"github.com/rs/zerolog/log"
|
2023-07-11 16:54:09 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type PcapClient struct {
|
|
|
|
|
writer *pcapgo.Writer
|
|
|
|
|
totalPackets uint64
|
|
|
|
|
totalBytes uint64
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
var (
|
|
|
|
|
pcapCommand = flag.String("cmd", "", "command to execute for pcap data (eg: tcpdump -i eth0 -n --immediate-mode -s 65535 -U -w -)")
|
|
|
|
|
listenAddress = flag.String("listen", "", "listen address for pcap-over-ip (eg: localhost:4242)")
|
|
|
|
|
noReverseLookup = flag.Bool("n", false, "disable reverse lookup of connecting PCAP-over-IP client IP address")
|
|
|
|
|
debug = flag.Bool("debug", false, "enable debug logging")
|
|
|
|
|
json = flag.Bool("json", false, "enable json logging")
|
|
|
|
|
)
|
2023-07-11 16:54:09 +02:00
|
|
|
|
2024-10-04 17:26:35 +02:00
|
|
|
func Main() {
|
2023-07-11 16:54:09 +02:00
|
|
|
flag.Parse()
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
if !*json {
|
|
|
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{
|
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
TimeFormat: time.RFC3339,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *debug {
|
|
|
|
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
|
|
|
} else {
|
|
|
|
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 16:54:09 +02:00
|
|
|
if *pcapCommand == "" {
|
|
|
|
|
*pcapCommand = os.Getenv("PCAP_COMMAND")
|
|
|
|
|
if *pcapCommand == "" {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Msg("PCAP_COMMAND or -cmd not set, see --help for usage")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *listenAddress == "" {
|
|
|
|
|
*listenAddress = os.Getenv("LISTEN_ADDRESS")
|
|
|
|
|
if *listenAddress == "" {
|
|
|
|
|
*listenAddress = "localhost:4242"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Debug().Str("pcapCommand", *pcapCommand).Send()
|
|
|
|
|
log.Debug().Str("listenAddress", *listenAddress).Send()
|
|
|
|
|
|
|
|
|
|
ctx, cancelFunc := signal.NotifyContext(context.Background(), os.Interrupt)
|
2023-07-11 16:54:09 +02:00
|
|
|
|
|
|
|
|
// Create connections to PcapClient map
|
2023-12-18 10:51:54 +01:00
|
|
|
connMap := map[net.Conn]PcapClient{}
|
2023-07-11 16:54:09 +02:00
|
|
|
|
|
|
|
|
// Create a pipe for the command to write to, will be read by pcap.OpenOfflineFile
|
|
|
|
|
rStdout, wStdout, err := os.Pipe()
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("failed to create pipe")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Acquire pcap data
|
|
|
|
|
args, err := shlex.Split(*pcapCommand)
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("failed to parse PCAP_COMMAND")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
2023-12-18 10:51:54 +01:00
|
|
|
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
|
|
|
|
|
log.Debug().Strs("args", args).Send()
|
|
|
|
|
|
2023-07-11 16:54:09 +02:00
|
|
|
cmd.Stdout = wStdout
|
2023-12-24 21:20:24 +01:00
|
|
|
cmd.Stderr = log.Logger.Hook(zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, msg string) {
|
|
|
|
|
e.Str(zerolog.LevelFieldName, zerolog.LevelTraceValue)
|
|
|
|
|
}))
|
2023-07-11 16:54:09 +02:00
|
|
|
|
|
|
|
|
err = cmd.Start()
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("failed to start command")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Debug().Int("pid", cmd.Process.Pid).Msg("started process")
|
|
|
|
|
|
|
|
|
|
// close context on process exit
|
2023-07-11 16:54:09 +02:00
|
|
|
go func() {
|
|
|
|
|
err := cmd.Wait()
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("command exited with error")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
2023-12-18 10:51:54 +01:00
|
|
|
cancelFunc()
|
2023-07-11 16:54:09 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// Read from process stdout pipe
|
|
|
|
|
handle, err := pcap.OpenOfflineFile(rStdout)
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("failed to open pcap file")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
|
|
|
|
packetSource.Lazy = true
|
|
|
|
|
packetSource.NoCopy = true
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
go processPackets(ctx, packetSource, connMap)
|
|
|
|
|
|
|
|
|
|
log.Info().Msgf("PCAP-over-IP server listening on %v. press CTRL-C to exit", *listenAddress)
|
|
|
|
|
|
|
|
|
|
config := net.ListenConfig{}
|
|
|
|
|
l, err := config.Listen(ctx, "tcp", *listenAddress)
|
2023-07-11 16:54:09 +02:00
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Fatal().Err(err).Msg("failed to listen")
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
// close listener on context cancel
|
|
|
|
|
go func() {
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
cancelFunc()
|
|
|
|
|
err := l.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to close listener")
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2023-07-11 16:54:09 +02:00
|
|
|
for {
|
|
|
|
|
conn, err := l.Accept()
|
2023-12-18 10:51:54 +01:00
|
|
|
if err != nil && ctx.Err() == nil {
|
|
|
|
|
log.Fatal().Err(err).Msg("failed to accept connection")
|
|
|
|
|
} else if errors.Is(ctx.Err(), context.Canceled) {
|
|
|
|
|
break
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *noReverseLookup {
|
2024-10-03 09:33:53 +00:00
|
|
|
log.Info().Msgf("PCAP-over-IP connection from %v", conn.RemoteAddr())
|
2023-07-11 16:54:09 +02:00
|
|
|
} else {
|
2024-10-03 09:33:53 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
|
|
|
defer cancel()
|
|
|
|
|
ipAddr := conn.RemoteAddr().(*net.TCPAddr).IP.String()
|
|
|
|
|
names, _ := net.DefaultResolver.LookupAddr(ctx, ipAddr)
|
|
|
|
|
if len(names) == 0 {
|
|
|
|
|
log.Info().Msgf("PCAP-over-IP connection from %v", conn.RemoteAddr())
|
2023-07-11 16:54:09 +02:00
|
|
|
} else {
|
2024-10-03 09:33:53 +00:00
|
|
|
log.Info().Msgf("PCAP-over-IP connection from %v (%v)", conn.RemoteAddr(), names[0])
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writer := pcapgo.NewWriter(conn)
|
|
|
|
|
|
|
|
|
|
// Write pcap header
|
2023-12-18 10:51:54 +01:00
|
|
|
err = writer.WriteFileHeader(65535, handle.LinkType())
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to write pcap header")
|
|
|
|
|
err := conn.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to close connection")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-07-11 16:54:09 +02:00
|
|
|
|
|
|
|
|
// add connection to map
|
|
|
|
|
connMap[conn] = PcapClient{writer: writer}
|
|
|
|
|
}
|
2023-12-18 10:51:54 +01:00
|
|
|
|
|
|
|
|
log.Info().Msg("PCAP-over-IP server exiting")
|
|
|
|
|
|
|
|
|
|
err = rStdout.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to close read pipe")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = wStdout.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to close write pipe")
|
|
|
|
|
}
|
2023-07-11 16:54:09 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 10:51:54 +01:00
|
|
|
func processPackets(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
packetSource *gopacket.PacketSource,
|
|
|
|
|
connMap map[net.Conn]PcapClient,
|
|
|
|
|
) {
|
2023-07-11 16:54:09 +02:00
|
|
|
for packet := range packetSource.Packets() {
|
2023-12-18 10:51:54 +01:00
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 16:54:09 +02:00
|
|
|
for conn, stats := range connMap {
|
|
|
|
|
ci := packet.Metadata().CaptureInfo
|
|
|
|
|
err := stats.writer.WritePacket(ci, packet.Data())
|
|
|
|
|
if err != nil {
|
2023-12-18 10:51:54 +01:00
|
|
|
log.Err(err).Msg("failed to write packet to connection")
|
2023-07-11 16:54:09 +02:00
|
|
|
delete(connMap, conn)
|
2023-12-18 10:51:54 +01:00
|
|
|
err := conn.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Err(err).Msg("failed to close connection")
|
|
|
|
|
}
|
2023-07-11 16:54:09 +02:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
stats.totalPackets += 1
|
|
|
|
|
stats.totalBytes += uint64(ci.CaptureLength)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|