175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"flag"
|
||
|
|
"fmt"
|
||
|
|
"log"
|
||
|
|
"net"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/shlex"
|
||
|
|
|
||
|
|
"github.com/google/gopacket"
|
||
|
|
"github.com/google/gopacket/pcap"
|
||
|
|
"github.com/google/gopacket/pcapgo"
|
||
|
|
)
|
||
|
|
|
||
|
|
type PcapClient struct {
|
||
|
|
writer *pcapgo.Writer
|
||
|
|
totalPackets uint64
|
||
|
|
totalBytes uint64
|
||
|
|
}
|
||
|
|
|
||
|
|
func lookupHostnameWithTimeout(addr net.Addr, timeout time.Duration) (string, string, error) {
|
||
|
|
// Extract the IP address and port from the Addr object
|
||
|
|
tcpAddr, ok := addr.(*net.TCPAddr)
|
||
|
|
if !ok {
|
||
|
|
return "", "", fmt.Errorf("unsupported address type: %T", addr)
|
||
|
|
}
|
||
|
|
ip := tcpAddr.IP.String()
|
||
|
|
port := fmt.Sprintf("%d", tcpAddr.Port)
|
||
|
|
|
||
|
|
// Create a new context with the given timeout
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
// Create a new Resolver and perform the IP lookup with the given context
|
||
|
|
resolver := net.Resolver{}
|
||
|
|
names, err := resolver.LookupAddr(ctx, ip)
|
||
|
|
if err != nil {
|
||
|
|
return "", "", err
|
||
|
|
}
|
||
|
|
if len(names) == 0 {
|
||
|
|
return "", "", fmt.Errorf("no hostnames found for %s", ip)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return the first IP address found and the original port
|
||
|
|
return names[0], port, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
|
||
|
|
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")
|
||
|
|
flag.Parse()
|
||
|
|
|
||
|
|
if *pcapCommand == "" {
|
||
|
|
*pcapCommand = os.Getenv("PCAP_COMMAND")
|
||
|
|
if *pcapCommand == "" {
|
||
|
|
log.Fatalf("Error: PCAP_COMMAND or -cmd not set, see --help for usage")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if *listenAddress == "" {
|
||
|
|
*listenAddress = os.Getenv("LISTEN_ADDRESS")
|
||
|
|
if *listenAddress == "" {
|
||
|
|
*listenAddress = "localhost:4242"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("config PCAP_COMMAND = %q", *pcapCommand)
|
||
|
|
log.Printf("config LISTEN_ADDRESS = %q", *listenAddress)
|
||
|
|
|
||
|
|
// Create connections to PcapClient map
|
||
|
|
var connMap = map[net.Conn]PcapClient{}
|
||
|
|
|
||
|
|
// Create a pipe for the command to write to, will be read by pcap.OpenOfflineFile
|
||
|
|
rStdout, wStdout, err := os.Pipe()
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Important or these will eventually be garbage collected and the pipe will close
|
||
|
|
defer rStdout.Close()
|
||
|
|
defer wStdout.Close()
|
||
|
|
|
||
|
|
// Acquire pcap data
|
||
|
|
args, err := shlex.Split(*pcapCommand)
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
cmd := exec.Command(args[0], args[1:]...)
|
||
|
|
log.Printf("cmd = %v", cmd.Args)
|
||
|
|
cmd.Stdout = wStdout
|
||
|
|
cmd.Stderr = os.Stderr
|
||
|
|
|
||
|
|
err = cmd.Start()
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
log.Printf("PID %v", cmd.Process.Pid)
|
||
|
|
go func() {
|
||
|
|
err := cmd.Wait()
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal("Process exited with error: ", err)
|
||
|
|
}
|
||
|
|
log.Printf("process exited")
|
||
|
|
os.Exit(0)
|
||
|
|
}()
|
||
|
|
|
||
|
|
// Read from process stdout pipe
|
||
|
|
handle, err := pcap.OpenOfflineFile(rStdout)
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
||
|
|
packetSource.Lazy = true
|
||
|
|
packetSource.NoCopy = true
|
||
|
|
go processPackets(packetSource, connMap)
|
||
|
|
|
||
|
|
log.Printf("PCAP-over-IP server listening on %v", *listenAddress)
|
||
|
|
l, err := net.Listen("tcp", *listenAddress)
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
for {
|
||
|
|
conn, err := l.Accept()
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if *noReverseLookup {
|
||
|
|
log.Printf("PCAP-over-IP connection from %v", conn.RemoteAddr())
|
||
|
|
} else {
|
||
|
|
ip, port, err := lookupHostnameWithTimeout(conn.RemoteAddr(), 100*time.Millisecond)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("PCAP-over-IP connection from %v", conn.RemoteAddr())
|
||
|
|
} else {
|
||
|
|
log.Printf("PCAP-over-IP connection from %s:%s", ip, port)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
writer := pcapgo.NewWriter(conn)
|
||
|
|
|
||
|
|
// Write pcap header
|
||
|
|
writer.WriteFileHeader(65535, handle.LinkType())
|
||
|
|
|
||
|
|
// add connection to map
|
||
|
|
connMap[conn] = PcapClient{writer: writer}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func processPackets(packetSource *gopacket.PacketSource, connMap map[net.Conn]PcapClient) {
|
||
|
|
for packet := range packetSource.Packets() {
|
||
|
|
for conn, stats := range connMap {
|
||
|
|
ci := packet.Metadata().CaptureInfo
|
||
|
|
err := stats.writer.WritePacket(ci, packet.Data())
|
||
|
|
if err != nil {
|
||
|
|
log.Println(err)
|
||
|
|
delete(connMap, conn)
|
||
|
|
conn.Close()
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
stats.totalPackets += 1
|
||
|
|
stats.totalBytes += uint64(ci.CaptureLength)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|