in tcpdumpw/main.go [607:808]
func main() {
flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
defer func() {
if r := recover(); r != nil {
jlog(FATAL, &emptyTcpdumpJob, stringFormatter.Format("panic: {0}", r))
fmt.Fprintln(os.Stderr, string(debug.Stack()))
}
}()
jid.Store(uuid.Nil)
xid.Store(uuid.Nil)
if *compat || strings.EqualFold(*filter, "DISABLED") {
*filter = ""
} else {
*filter = strings.TrimSpace(*filter)
}
compatFilters := pcap.NewPcapFilters()
filters := []pcap.PcapFilterProvider{}
if *compat || *filter == "" {
// if complex filter is empty, build it using 'Simple PCAP filters'
filters = appendFilter(ctx, filters, compatFilters, l3_protos, pcapFilter.NewL3ProtoFilterProvider)
filters = appendFilter(ctx, filters, compatFilters, l4_protos, pcapFilter.NewL4ProtoFilterProvider)
filters = appendFilter(ctx, filters, compatFilters, ports, pcapFilter.NewPortsFilterProvider)
filters = appendFilter(ctx, filters, compatFilters, tcp_flags, pcapFilter.NewTCPFlagsFilterProvider)
ipFilterProvider := pcapFilter.NewIPFilterProvider(ipv4, ipv6, hosts, compatFilters)
if _, ok := ipFilterProvider.Get(ctx); ok {
jlog(INFO, &emptyTcpdumpJob, stringFormatter.Format("using filter: {0}", ipFilterProvider.String()))
filters = append(filters, ipFilterProvider)
}
if len(filters) == 0 && !*compat {
// if no simple filters are available:
// - use a default 'catch-all' filter
// - but only if compat mode is disabled
*filter = string(pcap.PcapDefaultFilter)
}
}
noProcsInterval := *no_procs_interval
if noProcsInterval > maxNoProcsInterval {
noProcsInterval = maxNoProcsInterval
}
processFilter := pcapFilter.NewProcessFilterProvider(supervisor, no_procs, uint8(noProcsInterval), *no_procs_debug, compatFilters)
// initialize `ProcessFilterProvider` for its side effects
filters = append(filters, processFilter.Initialize(ctx))
ephemeralPortRange := parseEphemeralPorts(ephemerals)
pcapVerbosity := parsePcapVerbosity(pcap_verbosity)
tasks := createTasks(ctx, pcap_iface, timezone, directory, extension,
filter, filters, compatFilters, snaplen, interval, compat, pcap_debug, tcp_dump,
json_dump, json_log, ordered, conntrack, gcp_gae, ephemeralPortRange, pcapVerbosity)
if len(tasks) == 0 {
jlog(FATAL, &emptyTcpdumpJob, "no PCAP tasks available")
os.Exit(1)
}
pcapMutex := flock.New(pcapLockFile)
if locked, lockErr := pcapMutex.TryLock(); !locked || lockErr != nil {
jlog(FATAL, &emptyTcpdumpJob, fmt.Sprintf("failed to acquire PCAP lock | locked: %t | %v", locked, lockErr))
os.Exit(2)
}
jobs = haxmap.New[string, *tcpdumpJob]()
timeout := time.Duration(*duration) * time.Second
jlog(INFO, &emptyTcpdumpJob, fmt.Sprintf("parsed timeout: %v", timeout))
// the file to be created when `tcpdumpw` exists
exitSignal := fmt.Sprintf("%s/TCPDUMPW_EXITED", *directory)
// receives status of TCP listener termination: `true` means successful
tcpStopChannel := make(chan bool, 1)
// create empty job: used if CRON is not enabled
job := &tcpdumpJob{
Jid: uuid.Nil.String(),
tasks: tasks,
debug: *pcap_debug,
}
jlog(INFO, job, fmt.Sprintf("acquired PCAP lock: %s", pcapLockFile))
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
go func() {
signal := <-signals
jlog(INFO, job, fmt.Sprintf("signaled: %v", signal))
cancel()
// unblock TCP listener; next iteration will find `ctx` done
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", *hc_port))
if err == nil {
conn.Close()
}
}()
// Skip scheduling, execute `tcpdump` immediately
if !*use_cron {
id := uuid.New().String()
ctx = context.WithValue(ctx, pcap.PcapContextID, id)
logName := fmt.Sprintf("projects/%s/pcaps/%s", os.Getenv("PROJECT_ID"), id)
ctx = context.WithValue(ctx, pcap.PcapContextLogName, logName)
ctx = context.WithValue(ctx, pcap.PcapContextDebug, *pcap_debug)
ctx = context.WithValue(ctx, pcap.PcapContextVerbosity, pcapVerbosity)
// start the TCP listener for health checks
go startTCPListener(ctx, hc_port, job, tcpStopChannel)
start(ctx, &timeout, job)
waitDone(job, pcapMutex, &exitSignal)
<-tcpStopChannel
close(tcpStopChannel)
return
}
// The `timezone` to be used when scheduling `tcpdump` cron jobs
location, err := time.LoadLocation(*timezone)
if err != nil {
*timezone = "UTC"
location = time.UTC
jlog(ERROR, &emptyTcpdumpJob, fmt.Sprintf("could not load timezone '%s': %v", *timezone, err))
}
jlog(INFO, &emptyTcpdumpJob, fmt.Sprintf("parsed timezone: %v", location))
// Create a scheduler using the requested timezone.
// no more than 1 packet capturing job (all its tasks) should ever be executed.
s, err := gocron.NewScheduler(
gocron.WithLimitConcurrentJobs(1, gocron.LimitModeReschedule),
gocron.WithLocation(location),
gocron.WithGlobalJobOptions(
gocron.WithTags(
os.Getenv("PROJECT_ID"),
os.Getenv("APP_SERVICE"),
os.Getenv("GCP_REGION"),
os.Getenv("APP_REVISION"),
os.Getenv("INSTANCE_ID"),
),
),
)
if err != nil {
jlog(ERROR, &emptyTcpdumpJob, fmt.Sprintf("failed to create scheduler: %v", err))
os.Exit(3)
}
// Use the provided `cron` expression ro schedule the packet capturing job
j, err := s.NewJob(
gocron.CronJob(fmt.Sprintf("TZ=%s %s", *timezone, *cron_exp), true),
gocron.NewTask(tcpdump, timeout, *pcap_debug, pcapVerbosity),
gocron.WithName("tcpdump"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
gocron.WithEventListeners(
gocron.AfterJobRuns(afterTcpdump),
gocron.BeforeJobRuns(beforeTcpdump),
),
)
if err != nil {
jlog(ERROR, &emptyTcpdumpJob, fmt.Sprintf("failed to create scheduled job: %v", err))
s.Shutdown()
os.Exit(4)
}
jid.Store(j.ID())
// redefine default `job` with the scheduled one
job = &tcpdumpJob{
ctx: ctx,
tasks: tasks,
Jid: j.ID().String(),
Name: j.Name(),
Tags: j.Tags(),
j: &j,
}
jobs.Set(job.Jid, job)
jlog(INFO, job, "scheduled job")
// Start the packet capturing scheduler
s.Start()
nextRun, _ := j.NextRun()
jlog(INFO, job, fmt.Sprintf("next execution: %v", nextRun))
// start the TCP listener for health checks
go startTCPListener(ctx, hc_port, job, tcpStopChannel)
// Block main goroutine until a signal is received
<-ctx.Done()
s.StopJobs()
s.RemoveJob(j.ID())
s.Shutdown()
jlog(INFO, job, "scheduler terminated")
waitDone(job, pcapMutex, &exitSignal)
<-tcpStopChannel
close(tcpStopChannel)
}