Skip to content

Commit f5c4d69

Browse files
authored
feat(filter): Add support for capturing traffic based on user ID (#233)
1 parent 924c6fa commit f5c4d69

21 files changed

+98
-3
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ Multiple interfaces:
7676
sudo ptcpdump -i eth0 -i lo
7777
```
7878

79-
Filter by process:
79+
Filter by process or user:
8080

8181
```
8282
sudo ptcpdump -i any --pid 1234 --pid 233 -f
8383
sudo ptcpdump -i any --pname curl
84+
sudo ptcpdump -i any --uid 1000
8485
```
8586

8687
Capture by process via run target program:
@@ -146,6 +147,7 @@ With `-v`:
146147
13:44:41.529003 eth0 In IP (tos 0x4, ttl 45, id 45428, offset 0, flags [DF], proto TCP (6), length 52)
147148
139.178.84.217.443 > 172.19.0.2.42606: Flags [.], cksum 0x5284, seq 3173118145, ack 1385712707, win 118, options [nop,nop,TS val 134560683 ecr 1627716996], length 0
148149
Process (pid 553587, cmd /usr/bin/wget, args wget kernel.org)
150+
User (uid 1000)
149151
ParentProc (pid 553296, cmd /bin/sh, args sh)
150152
Container (name test, id d9028334568bf75a5a084963a8f98f78c56bba7f45f823b3780a135b71b91e95, image docker.io/library/alpine:3.18, labels {"io.cri-containerd.kind":"container","io.kubernetes.container.name":"test","io.kubernetes.pod.name":"test","io.kubernetes.pod.namespace":"default","io.kubernetes.pod.uid":"9e4bc54b-de48-4b1c-8b9e-54709f67ed0c"})
151153
Pod (name test, namespace default, UID 9e4bc54b-de48-4b1c-8b9e-54709f67ed0c, labels {"run":"test"}, annotations {"kubernetes.io/config.seen":"2024-07-21T12:41:00.460249620Z","kubernetes.io/config.source":"api"})
@@ -277,7 +279,7 @@ Flags:
277279
--container-id string Filter by container id (only TCP and UDP packets are supported)
278280
--container-name string Filter by container name (only TCP and UDP packets are supported)
279281
--containerd-address string Address of containerd service (default "/run/containerd/containerd.sock")
280-
--context strings Specify which context information to include in the output (default [process,parentproc,container,pod])
282+
--context strings Specify which context information to include in the output (default [process,thread,parentproc,user,container,pod])
281283
--count Print only on stdout the packet count when reading capture file instead of parsing/printing the packets
282284
--cri-runtime-address string Address of CRI container runtime service (default: uses in order the first successful one of [/var/run/dockershim.sock, /var/run/cri-dockerd.sock, /run/crio/crio.sock, /run/containerd/containerd.sock])
283285
--delay-before-handle-packet-events duration Delay some durations before handle packet events
@@ -311,6 +313,7 @@ Flags:
311313
-c, --receive-count uint Exit after receiving count packets
312314
-s, --snapshot-length uint32 Snarf snaplen bytes of data from each packet rather than the default of 262144 bytes (default 262144)
313315
--time-stamp-precision string When capturing, set the time stamp precision for the capture to the format (default "micro")
316+
--uid uints Filter by user IDs (only TCP and UDP packets are supported) (default [])
314317
-v, --verbose count When parsing and printing, produce (slightly more) verbose output
315318
--version Print the ptcpdump and libpcap version strings and exit
316319
-w, --write-file string Write the raw packets to file rather than parsing and printing them out. They can later be printed with the -r option. Standard output is used if file is '-'. e.g. ptcpdump.pcapng
@@ -334,6 +337,7 @@ Flags:
334337
| -r *-* || |
335338
| --pid *process_id* | ||
336339
| --pname *process_name* | ||
340+
| --uid *user_id* | ||
337341
| --container-id *container_id* | ||
338342
| --container-name *container_name* | ||
339343
| --pod-name *pod_name.namespace* | ||

bpf/bpf.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type BPF struct {
4242
type Options struct {
4343
haveFilter uint8
4444
pids []uint32
45+
uids []uint32
4546
comm [16]int8
4647
filterComm uint8
4748
followForks uint8
@@ -517,6 +518,14 @@ func (b *BPF) applyFilters() error {
517518
}
518519
}
519520

521+
log.Infof("start to update FilterUidMap with %+v", opts.pids)
522+
for _, uid := range opts.uids {
523+
uid := uid
524+
if err := b.objs.BpfMaps.FilterUidMap.Update(uid, value, ebpf.UpdateAny); err != nil {
525+
return fmt.Errorf("update FilterUidMap: %w", err)
526+
}
527+
}
528+
520529
log.Infof("start to update FilterPidnsMap with %+v", opts.pidnsIds)
521530
for _, id := range opts.pidnsIds {
522531
id := id
@@ -565,6 +574,16 @@ func (opts *Options) WithPids(pids []uint) *Options {
565574
return opts
566575
}
567576

577+
func (opts *Options) WithUids(uids []uint) *Options {
578+
for _, id := range uids {
579+
opts.uids = append(opts.uids, uint32(id))
580+
}
581+
if len(opts.uids) > 0 {
582+
opts.haveFilter = 1
583+
}
584+
return opts
585+
}
586+
568587
func (opts *Options) WithComm(comm string) *Options {
569588
opts.comm = [16]int8{}
570589
if len(comm) > 0 {

bpf/bpf_arm64_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_arm64_bpfel.o

14 KB
Binary file not shown.

bpf/bpf_legacy_arm64_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_legacy_arm64_bpfel.o

6.66 KB
Binary file not shown.

bpf/bpf_legacy_x86_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_legacy_x86_bpfel.o

6.66 KB
Binary file not shown.

bpf/bpf_no_tracing_arm64_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_no_tracing_arm64_bpfel.o

7.89 KB
Binary file not shown.

bpf/bpf_no_tracing_x86_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_no_tracing_x86_bpfel.o

7.88 KB
Binary file not shown.

bpf/bpf_x86_bpfel.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bpf/bpf_x86_bpfel.o

14 KB
Binary file not shown.

bpf/process.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ struct {
7575
__type(value, u8);
7676
} filter_pid_map SEC(".maps");
7777

78+
struct {
79+
__uint(type, BPF_MAP_TYPE_HASH);
80+
__uint(max_entries, 10);
81+
__type(key, u32);
82+
__type(value, u8);
83+
} filter_uid_map SEC(".maps");
84+
7885
struct {
7986
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
8087
__uint(max_entries, 1);
@@ -111,6 +118,13 @@ static __always_inline int filter_pid(u32 pid) {
111118
return -1;
112119
}
113120

121+
static __always_inline int filter_uid(u32 uid) {
122+
if (bpf_map_lookup_elem(&filter_uid_map, &uid)) {
123+
return 0;
124+
}
125+
return -1;
126+
}
127+
114128
static __always_inline int filter_mntns(u32 ns) {
115129
if (bpf_map_lookup_elem(&filter_mntns_map, &ns)) {
116130
return 0;
@@ -154,7 +168,9 @@ static __always_inline int process_filter(struct task_struct *task) {
154168
u32 mntns_id = BPF_CORE_READ(task, nsproxy, mnt_ns, ns.inum);
155169
u32 netns_id = BPF_CORE_READ(task, nsproxy, net_ns, ns.inum);
156170
u32 pidns_id = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns.inum);
157-
if ((filter_pidns(pidns_id) == 0) || (filter_mntns(mntns_id) == 0) || (filter_netns(netns_id) == 0)) {
171+
u32 uid = BPF_CORE_READ(task, cred, uid.val);
172+
if ((filter_pidns(pidns_id) == 0) || (filter_mntns(mntns_id) == 0) || (filter_netns(netns_id) == 0) ||
173+
(filter_uid(uid) == 0)) {
158174
// debug_log("%u %u %u\n", mntns_id, netns_id, pidns_id);
159175
should_filter = true;
160176
}

cmd/capture.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ func getCurrentConnects(ctx context.Context, pcache *metadata.ProcessCache, opts
175175
ps := pcache.GetPidsByComm(opts.comm)
176176
pids = append(pids, ps...)
177177
}
178+
if len(opts.uids) > 0 {
179+
filterPid = true
180+
for _, uid := range opts.uids {
181+
ps := pcache.GetPidsByUid(int(uid))
182+
pids = append(pids, ps...)
183+
}
184+
}
178185
if len(opts.pidnsIds) > 0 {
179186
filterPid = true
180187
for _, id := range opts.pidnsIds {

cmd/options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type Options struct {
8686
mntnsIds []uint32
8787
netnsIds []uint32
8888
pidnsIds []uint32
89+
uids []uint
8990

9091
stdout io.Writer
9192
enhancedContexts []string
@@ -356,6 +357,7 @@ func (o *Options) ToCapturerOptions() *capturer.Options {
356357
MntnsIds: o.mntnsIds,
357358
NetnsIds: o.netnsIds,
358359
PidnsIds: o.pidnsIds,
360+
Uids: o.uids,
359361
BTFPath: o.btfPath,
360362
AllDev: o.allDev,
361363
AllNetNs: o.allNetNs,

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func init() {
5656
"Interfaces to capture")
5757
rootCmd.Flags().UintSliceVar(&opts.pids, "pid", nil, "Filter by process IDs (only TCP and UDP packets are supported)")
5858
rootCmd.Flags().StringVar(&opts.comm, "pname", "", "Filter by process name (only TCP and UDP packets are supported)")
59+
rootCmd.Flags().UintSliceVar(&opts.uids, "uid", nil, "Filter by user IDs (only TCP and UDP packets are supported)")
5960
rootCmd.Flags().BoolVarP(&opts.followForks, "follow-forks", "f", false,
6061
"Trace child processes as they are created by currently traced processes when filter by process")
6162
rootCmd.Flags().BoolVarP(&opts.listInterfaces, "list-interfaces", "D", false,

internal/capturer/capturer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Capturer struct {
4343

4444
type Options struct {
4545
Pids []uint
46+
Uids []uint
4647
Comm string
4748
FollowForks bool
4849
WriteFilePath string
@@ -156,6 +157,7 @@ func (c *Capturer) Prepare() error {
156157
bpfopts = bpfopts.WithPids(c.opts.Pids).
157158
WithComm(c.opts.Comm).
158159
WithFollowFork(c.opts.FollowForks).
160+
WithUids(c.opts.Uids).
159161
WithPidNsIds(c.opts.PidnsIds).
160162
WithMntNsIds(c.opts.MntnsIds).
161163
WithNetNsIds(c.opts.NetnsIds).
@@ -518,6 +520,7 @@ func updateFlowPidMapValues(bf *bpf.BPF, conns []metadata.Connection) error {
518520
}
519521
v := bpf.BpfProcessMetaT{
520522
Pid: uint32(conn.Pid),
523+
Uid: uint32(conn.Uid),
521524
MntnsId: uint32(conn.MntNs),
522525
NetnsId: uint32(conn.NetNs),
523526
}

internal/metadata/net.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type Connection struct {
1414
LocalIP netip.Addr
1515
LocalPort int
1616
Pid int
17+
Uid int
1718
MntNs int64
1819
NetNs int64
1920
}
@@ -58,6 +59,9 @@ func convertConnectionStat(stat net.ConnectionStat) (Connection, error) {
5859
conn.LocalIP = addr
5960
conn.LocalPort = port
6061
conn.Pid = int(stat.Pid)
62+
if len(stat.Uids) > 0 {
63+
conn.Uid = int(stat.Uids[0])
64+
}
6165
conn.MntNs = utils.GetMountNamespaceFromPid(conn.Pid)
6266
conn.NetNs = utils.GetNetworkNamespaceFromPid(conn.Pid)
6367
return conn, nil

internal/metadata/process.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,27 @@ func (c *ProcessCache) GetPidsByComm(name string) []int {
325325
return pids
326326
}
327327

328+
func (c *ProcessCache) GetPidsByUid(uid int) []int {
329+
var pids []int
330+
331+
c.pids.Range(func(key, value any) bool {
332+
pid, ok := key.(int)
333+
if !ok {
334+
return true
335+
}
336+
info, ok := value.(*types.PacketContext)
337+
if !ok {
338+
return true
339+
}
340+
if info.UserId == uid {
341+
pids = append(pids, pid)
342+
}
343+
return true
344+
})
345+
346+
return pids
347+
}
348+
328349
func (c *ProcessCache) GetPidsByPidNsId(nsid int64) []int {
329350
var pids []int
330351

0 commit comments

Comments
 (0)