pcap-cli/cmd/pcap.go (143 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
"regexp"
"sync"
"syscall"
"time"
"github.com/GoogleCloudPlatform/pcap-sidecar/pcap-cli/pkg/pcap"
"github.com/google/uuid"
)
var (
engine = flag.String("eng", "google", "Engine to use for capturing packets: tcpdump or google")
iface = flag.String("i", "any", "Interface to read packets from")
snaplen = flag.Int("s", 0, "Snap length (number of bytes max to read per packet")
writeTo = flag.String("w", "stdout", "Where to write packet capture to: stdout or a file path")
tsType = flag.String("ts_type", "", "Type of timestamps to use")
promisc = flag.Bool("promisc", true, "Set promiscuous mode")
format = flag.String("fmt", "default", "Set the output format: default, text or json")
filter = flag.String("filter", "", "Set BPF filter to be used")
timeout = flag.Int("timeout", 0, "Set packet capturing total duration in seconds")
interval = flag.Int("interval", 0, "Set packet capture file rotation interval in seconds")
extension = flag.String("ext", "", "Set pcap files extension: pcap, json, txt")
stdout = flag.Bool("stdout", false, "Log translation to standard output; only if 'w' is not 'stdout'")
ordered = flag.Bool("ordered", false, "write translation in the order in which packets were captured")
conntrack = flag.Bool("conntrack", false, "enable connection tracking (includes 'ordered')")
timezone = flag.String("tz", "UTC", "timezone to be used by PCAP files template")
)
var logger = log.New(os.Stderr, "[pcap] - ", log.LstdFlags)
func handleError(prefix *string, err error) {
if errors.Is(err, context.Canceled) {
logger.Printf("%s cancelled\n", *prefix)
os.Exit(1)
}
if errors.Is(err, context.DeadlineExceeded) {
logger.Printf("%s complete\n", *prefix)
}
}
func newPcapEngine(engine *string, config *pcap.PcapConfig) (pcap.PcapEngine, error) {
pcapEngine := *engine
switch pcapEngine {
case "google":
return pcap.NewPcap(config)
case "tcpdump":
return pcap.NewTcpdump(config)
default:
/* no-go */
}
return nil, fmt.Errorf("unavailable: %s", pcapEngine)
}
func main() {
flag.Parse()
config := &pcap.PcapConfig{
Promisc: *promisc,
Snaplen: *snaplen,
TsType: *tsType,
Format: *format,
Filter: *filter,
Output: *writeTo,
Interval: *interval,
Extension: *extension,
Ordered: *ordered,
ConnTrack: *conntrack,
}
exp, _ := regexp.Compile(fmt.Sprintf("^(?:ipvlan-)?%s.*", *iface))
devs, _ := pcap.FindDevicesByRegex(exp)
ctx := context.Background()
var cancel context.CancelFunc
id := fmt.Sprintf("cli/%s", uuid.New())
ctx = context.WithValue(ctx, pcap.PcapContextID, id)
ctx = context.WithValue(ctx, pcap.PcapContextLogName, `log/`+id)
if *timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, time.Duration(*timeout)*time.Second)
} else {
ctx, cancel = context.WithCancel(ctx)
}
var wg sync.WaitGroup
stopDeadlineChan := make(chan *time.Duration, 1)
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-signals
cancel()
deadline := 3 * time.Second
stopDeadlineChan <- &deadline
}()
for _, dev := range devs {
wg.Add(1)
go startPCAP(ctx, &id, dev, config, &wg, stopDeadlineChan)
}
wg.Wait()
}
func startPCAP(
ctx context.Context,
id *string,
dev *pcap.PcapDevice,
config *pcap.PcapConfig,
wg *sync.WaitGroup,
stopDeadlineChan chan *time.Duration,
) {
iface := dev.NetInterface.Name
logger.Printf("device: %+v\n", iface)
ifaceNameAndIndex := fmt.Sprintf("%d/%s", dev.NetInterface.Index, dev.Name)
config.Iface = iface
if *engine == "tcpdump" && *stdout {
*writeTo = "stdout"
}
var err error
var pcapEngine pcap.PcapEngine
pcapEngine, err = newPcapEngine(engine, config)
if err != nil {
log.Fatalf("%s", err)
return
}
if *writeTo == "stdout" {
*stdout = true
}
pcapWriters := []pcap.PcapWriter{}
var pcapWriter pcap.PcapWriter
if *engine == "google" && *stdout {
pcapWriter, err = pcap.NewStdoutPcapWriter(ctx, &ifaceNameAndIndex)
if err == nil {
pcapWriters = append(pcapWriters, pcapWriter)
}
}
if *engine == "google" && *writeTo != "stdout" {
pcapWriter, err = pcap.NewPcapWriter(ctx, &ifaceNameAndIndex, writeTo, extension, timezone, *interval)
if err == nil {
pcapWriters = append(pcapWriters, pcapWriter)
}
}
prefix := fmt.Sprintf("[iface:%s] execution '%s'", iface, *id)
logger.Printf("%s started", prefix)
// this is a blocking call
err = pcapEngine.Start(ctx, pcapWriters, stopDeadlineChan)
if err != nil {
handleError(&prefix, err)
}
wg.Done()
}