@@ -15,6 +15,7 @@ import (
1515 "net"
1616 "os"
1717 "path"
18+ "regexp"
1819 "strconv"
1920 "strings"
2021 "sync"
@@ -56,6 +57,12 @@ type Input struct {
5657 ignoreComms map [string ]struct {} // exact matches (fast path)
5758 ignoreCommGlobs []string // glob patterns (path.Match syntax)
5859
60+ ignoreCmdlines []string // plain substring matches
61+ ignoreCmdlineGlobs []* regexp.Regexp // compiled glob-to-regex patterns
62+
63+ ignoreParents map [string ]struct {} // exact parent name matches
64+ ignoreParentGlobs []string // glob patterns for parent name
65+
5966 objs bpfObjects
6067 links []link.Link
6168
@@ -86,13 +93,19 @@ func (e *Input) Description() string {
8693 - "ebpf:ignore-comm:<name>": drop events from this process name
8794 (matched against the resolved /proc name; may be specified multiple
8895 times; supports glob wildcards * ? [...] e.g. "chrome*", "*-worker")
96+ - "ebpf:ignore-cmdline:<pattern>": drop events whose full command line
97+ matches this pattern (substring match by default; supports glob
98+ wildcards * ? [...] where * crosses any character including /)
99+ - "ebpf:ignore-parent:<name>": drop events whose parent process name
100+ matches (same syntax as ignore-comm: exact or glob)
89101
90102 Example:
91103 sudo egress-auditor -i ebpf -o logfmt \
92104 -I ebpf:ignore-cidr:10.0.0.0/8 \
93105 -I ebpf:ignore-cidr:192.168.0.0/16 \
94106 -I ebpf:ignore-port:53 \
95- -I ebpf:ignore-comm:chronyd
107+ -I ebpf:ignore-comm:chronyd \
108+ -I ebpf:ignore-cmdline:'/usr/sbin/unbound*'
96109 `
97110}
98111
@@ -142,12 +155,69 @@ func (e *Input) SetOption(k, v string) error {
142155 }
143156 e .ignoreComms [v ] = struct {}{}
144157 }
158+ case "ignore-cmdline" :
159+ if v == "" {
160+ return fmt .Errorf ("ignore-cmdline requires a non-empty value" )
161+ }
162+ if strings .ContainsAny (v , "*?[" ) {
163+ re , err := regexp .Compile ("^" + globToRegex (v ) + "$" )
164+ if err != nil {
165+ return fmt .Errorf ("invalid ignore-cmdline pattern %q: %w" , v , err )
166+ }
167+ e .ignoreCmdlineGlobs = append (e .ignoreCmdlineGlobs , re )
168+ } else {
169+ e .ignoreCmdlines = append (e .ignoreCmdlines , v )
170+ }
171+ case "ignore-parent" :
172+ if v == "" {
173+ return fmt .Errorf ("ignore-parent requires a non-empty value" )
174+ }
175+ if strings .ContainsAny (v , "*?[" ) {
176+ if _ , err := path .Match (v , "" ); err != nil {
177+ return fmt .Errorf ("invalid ignore-parent pattern %q: %w" , v , err )
178+ }
179+ e .ignoreParentGlobs = append (e .ignoreParentGlobs , v )
180+ } else {
181+ if e .ignoreParents == nil {
182+ e .ignoreParents = make (map [string ]struct {})
183+ }
184+ e .ignoreParents [v ] = struct {}{}
185+ }
145186 default :
146187 return fmt .Errorf ("option %q unknown for ebpf input" , k )
147188 }
148189 return nil
149190}
150191
192+ // globToRegex converts a shell-style glob pattern to a regex string.
193+ // Unlike path.Match, * and ? cross any character (including /).
194+ func globToRegex (pattern string ) string {
195+ var b strings.Builder
196+ inBracket := false
197+ for i := 0 ; i < len (pattern ); i ++ {
198+ c := pattern [i ]
199+ switch {
200+ case c == '*' && ! inBracket :
201+ b .WriteString (".*" )
202+ case c == '?' && ! inBracket :
203+ b .WriteByte ('.' )
204+ case c == '[' && ! inBracket :
205+ inBracket = true
206+ b .WriteByte ('[' )
207+ case c == ']' && inBracket :
208+ inBracket = false
209+ b .WriteByte (']' )
210+ default :
211+ // Escape regex metacharacters outside brackets.
212+ if ! inBracket && strings .ContainsRune (`\.+^${}()|` , rune (c )) {
213+ b .WriteByte ('\\' )
214+ }
215+ b .WriteByte (c )
216+ }
217+ }
218+ return b .String ()
219+ }
220+
151221// isNetFiltered returns true if the destination IP/port should be dropped.
152222// This is checked early, before any process resolution.
153223func (e * Input ) isNetFiltered (destIP net.IP , dport uint16 ) bool {
@@ -162,12 +232,11 @@ func (e *Input) isNetFiltered(destIP net.IP, dport uint16) bool {
162232 return false
163233}
164234
165- // isProcFiltered returns true if the resolved process name matches an
166- // ignore-comm rule. We match against proc.Name (from /proc) rather than the
167- // eBPF-captured thread comm, because multi-threaded daemons set per-thread
168- // names via prctl(PR_SET_NAME) — the kernel comm at hook time can differ
169- // from the user-visible process name (e.g. unbound worker threads).
170- func (e * Input ) isProcFiltered (procName string ) bool {
235+ // isProcFiltered returns true if the resolved process name or command line
236+ // matches an ignore-comm or ignore-cmdline rule. We match against proc.Name
237+ // and proc.CmdLine (from /proc) rather than the eBPF-captured thread comm,
238+ // because multi-threaded daemons set per-thread names via prctl(PR_SET_NAME).
239+ func (e * Input ) isProcFiltered (procName , cmdLine , parentName string ) bool {
171240 if _ , skip := e .ignoreComms [procName ]; skip {
172241 return true
173242 }
@@ -176,6 +245,24 @@ func (e *Input) isProcFiltered(procName string) bool {
176245 return true
177246 }
178247 }
248+ for _ , sub := range e .ignoreCmdlines {
249+ if strings .Contains (cmdLine , sub ) {
250+ return true
251+ }
252+ }
253+ for _ , re := range e .ignoreCmdlineGlobs {
254+ if re .MatchString (cmdLine ) {
255+ return true
256+ }
257+ }
258+ if _ , skip := e .ignoreParents [parentName ]; skip {
259+ return true
260+ }
261+ for _ , pat := range e .ignoreParentGlobs {
262+ if ok , _ := path .Match (pat , parentName ); ok {
263+ return true
264+ }
265+ }
179266 return false
180267}
181268
@@ -284,7 +371,11 @@ func (e *Input) Process(ctx context.Context, c chan<- entry.Connection) {
284371 proc = fallbackProc (int32 (evt .Pid ), evt .Comm [:])
285372 }
286373
287- if e .isProcFiltered (proc .Name ) {
374+ parentName := ""
375+ if proc .Parent != nil {
376+ parentName = proc .Parent .Name
377+ }
378+ if e .isProcFiltered (proc .Name , proc .CmdLine , parentName ) {
288379 continue
289380 }
290381
0 commit comments