Skip to content

Commit bf942de

Browse files
committed
[net][linux]: prevent incorrect deduplication of unnamed UNIX sockets
The existing dedup logic used only (sockType, src, dst, status) as the key, which caused all unnamed UNIX sockets (empty address) to collapse into a single entry when querying system-wide connections. Include pid and fd in the dedup key for AF_UNIX sockets so that each socket is correctly preserved. Inet socket dedup is unchanged. Extract connectionDedupKey() and add unit tests. Fixes #2030
1 parent d7abb9a commit bf942de

2 files changed

Lines changed: 125 additions & 5 deletions

File tree

net/net_linux.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ type connTmp struct {
348348
pid int32
349349
boundPid int32
350350
path string
351+
inode string
351352
}
352353

353354
func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) {
@@ -405,14 +406,26 @@ func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, p
405406
return statsFromInodesWithContext(ctx, root, pid, tmap, inodes, skipUids)
406407
}
407408

409+
// connectionDedupKey builds a key to deduplicate connections.
410+
// For inet sockets, the tuple (type, src, dst, status) is sufficient.
411+
// For unix sockets, unnamed sockets share the same empty address,
412+
// so pid, fd, and inode must be included to avoid incorrect deduplication.
413+
// The inode is especially important when pid/fd are unavailable (e.g.,
414+
// unprivileged queries where inode-to-pid mapping fails).
415+
func connectionDedupKey(family uint32, c connTmp) string {
416+
if family == syscall.AF_UNIX {
417+
return fmt.Sprintf("%d-%d-%s-%d-%s:%d-%s:%d-%s", c.pid, c.fd, c.inode, c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status)
418+
}
419+
return fmt.Sprintf("%d-%s:%d-%s:%d-%s", c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status)
420+
}
421+
408422
func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) {
409423
dupCheckMap := make(map[string]struct{})
410424
var ret []ConnectionStat
411425

412426
var err error
413427
for _, t := range tmap {
414428
var path string
415-
var connKey string
416429
var ls []connTmp
417430
if pid == 0 {
418431
path = fmt.Sprintf("%s/net/%s", root, t.filename)
@@ -429,10 +442,7 @@ func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tma
429442
return nil, err
430443
}
431444
for _, c := range ls {
432-
// Build TCP key to id the connection uniquely
433-
// socket type, src ip, src port, dst ip, dst port and state should be enough
434-
// to prevent duplications.
435-
connKey = fmt.Sprintf("%d-%s:%d-%s:%d-%s", c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status)
445+
connKey := connectionDedupKey(t.family, c)
436446
if _, ok := dupCheckMap[connKey]; ok {
437447
continue
438448
}
@@ -728,6 +738,7 @@ func processInet(file string, kind netConnectionKindType, inodes map[string][]in
728738
raddr: ra,
729739
status: status,
730740
pid: pid,
741+
inode: inode,
731742
})
732743
}
733744

@@ -785,6 +796,7 @@ func processUnix(file string, kind netConnectionKindType, inodes map[string][]in
785796
pid: pair.pid,
786797
status: "NONE",
787798
path: path,
799+
inode: inode,
788800
})
789801
}
790802
}

net/net_linux_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,114 @@ func TestDecodeAddress(t *testing.T) {
189189
}
190190
}
191191

192+
func TestConnectionDedupKey(t *testing.T) {
193+
// Two unnamed UNIX sockets with different pid/fd should have different keys
194+
c1 := connTmp{
195+
fd: 3,
196+
family: syscall.AF_UNIX,
197+
sockType: syscall.SOCK_STREAM,
198+
laddr: Addr{},
199+
raddr: Addr{},
200+
status: "NONE",
201+
pid: 100,
202+
}
203+
c2 := connTmp{
204+
fd: 4,
205+
family: syscall.AF_UNIX,
206+
sockType: syscall.SOCK_STREAM,
207+
laddr: Addr{},
208+
raddr: Addr{},
209+
status: "NONE",
210+
pid: 100,
211+
}
212+
c3 := connTmp{
213+
fd: 3,
214+
family: syscall.AF_UNIX,
215+
sockType: syscall.SOCK_STREAM,
216+
laddr: Addr{},
217+
raddr: Addr{},
218+
status: "NONE",
219+
pid: 200,
220+
}
221+
222+
k1 := connectionDedupKey(syscall.AF_UNIX, c1)
223+
k2 := connectionDedupKey(syscall.AF_UNIX, c2)
224+
k3 := connectionDedupKey(syscall.AF_UNIX, c3)
225+
226+
assert.NotEqual(t, k1, k2, "different fd same pid should produce different keys")
227+
assert.NotEqual(t, k1, k3, "same fd different pid should produce different keys")
228+
229+
// Same unnamed UNIX socket should produce the same key
230+
assert.Equal(t, k1, connectionDedupKey(syscall.AF_UNIX, c1))
231+
232+
// Named UNIX sockets with different paths should have different keys
233+
c4 := connTmp{
234+
fd: 3,
235+
family: syscall.AF_UNIX,
236+
sockType: syscall.SOCK_STREAM,
237+
laddr: Addr{IP: "/var/run/a.sock"},
238+
raddr: Addr{},
239+
status: "NONE",
240+
pid: 100,
241+
}
242+
c5 := connTmp{
243+
fd: 3,
244+
family: syscall.AF_UNIX,
245+
sockType: syscall.SOCK_STREAM,
246+
laddr: Addr{IP: "/var/run/b.sock"},
247+
raddr: Addr{},
248+
status: "NONE",
249+
pid: 100,
250+
}
251+
assert.NotEqual(t, connectionDedupKey(syscall.AF_UNIX, c4), connectionDedupKey(syscall.AF_UNIX, c5))
252+
253+
// Inet sockets: same tuple should produce the same key regardless of pid/fd
254+
c6 := connTmp{
255+
fd: 3,
256+
family: syscall.AF_INET,
257+
sockType: syscall.SOCK_STREAM,
258+
laddr: Addr{IP: "127.0.0.1", Port: 8080},
259+
raddr: Addr{IP: "10.0.0.1", Port: 443},
260+
status: "ESTABLISHED",
261+
pid: 100,
262+
}
263+
c7 := connTmp{
264+
fd: 5,
265+
family: syscall.AF_INET,
266+
sockType: syscall.SOCK_STREAM,
267+
laddr: Addr{IP: "127.0.0.1", Port: 8080},
268+
raddr: Addr{IP: "10.0.0.1", Port: 443},
269+
status: "ESTABLISHED",
270+
pid: 200,
271+
}
272+
assert.Equal(t, connectionDedupKey(syscall.AF_INET, c6), connectionDedupKey(syscall.AF_INET, c7),
273+
"inet sockets with same tuple should dedup regardless of pid/fd")
274+
275+
// When pid/fd are unknown (0/0), inode should distinguish sockets
276+
c8 := connTmp{
277+
fd: 0,
278+
family: syscall.AF_UNIX,
279+
sockType: syscall.SOCK_STREAM,
280+
laddr: Addr{},
281+
raddr: Addr{},
282+
status: "NONE",
283+
pid: 0,
284+
inode: "111",
285+
}
286+
c9 := connTmp{
287+
fd: 0,
288+
family: syscall.AF_UNIX,
289+
sockType: syscall.SOCK_STREAM,
290+
laddr: Addr{},
291+
raddr: Addr{},
292+
status: "NONE",
293+
pid: 0,
294+
inode: "222",
295+
}
296+
assert.NotEqual(t, connectionDedupKey(syscall.AF_UNIX, c8), connectionDedupKey(syscall.AF_UNIX, c9),
297+
"unnamed unix sockets with unknown pid/fd should use inode in dedup key")
298+
}
299+
192300
func TestReverse(t *testing.T) {
193301
src := []byte{0x01, 0x02, 0x03}
194302
assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src))

0 commit comments

Comments
 (0)