誰有這個 unix socketpair 的另一端?
我想確定哪個程序擁有 UNIX 套接字的另一端。
具體來說,我問的是一個用 . 創建的
socketpair()
,儘管任何 UNIX 套接字的問題都是一樣的。我有一個程序
parent
可以創建 asocketpair(AF_UNIX, SOCK_STREAM, 0, fds)
和fork()
s。父程序關閉fds[1]
並保持fds[0]
通信。孩子反其道而行之close(fds[0]); s=fds[1]
。然後孩子exec()
的另一個程序,child1
。兩者可以通過這個socketpair來回通信。現在,假設我知道誰
parent
是誰,但我想弄清楚誰child1
是誰。我該怎麼做呢?我可以使用多種工具,但沒有人可以告訴我套接字的另一端是哪個程序。我努力了:
lsof -c progname
lsof -c parent -c child1
ls -l /proc/$(pidof server)/fd
cat /proc/net/unix
基本上,我可以看到兩個套接字,以及關於它們的所有內容,但無法判斷它們是否已連接。我正在嘗試確定父程序中的哪個 FD 正在與哪個子程序通信。
從核心 3.3 開始,可以使用
ss
orlsof-4.89
或更高版本 - 請參閱Stéphane Chazelas 的回答。在舊版本中,根據作者的說法
lsof
,這是不可能的:Linux 核心不會公開這些資訊。來源:comp.unix.admin 上的 2003 執行緒。中顯示
/proc/$pid/fd/$fd
的數字是虛擬套接字文件系統中套接字的 inode 號。當您創建管道或套接字對時,每一端都會連續接收一個 inode 編號。這些數字是按順序分配的,因此數字相差 1 的可能性很高,但這不能保證(因為第一個套接字是N並且N +1 由於包裝已經在使用中,或者因為其他一些執行緒是在兩個 inode 分配之間進行調度,並且該執行緒也創建了一些 inode)。我檢查了核心2.6.39中的定義,
socketpair
socket的兩端除了通過type-specificsocketpair
方法之外沒有關聯。對於 unix 套接字,它unix_socketpair
位於net/unix/af_unix.c
.
注意:我現在維護一個
lsof
包裝器,它結合了此處描述的兩種方法,並在https://github.com/stephane-chazelas/misc-scripts/blob/master/lsofc添加了環回 TCP 連接對等方的資訊Linux-3.3 及以上。
在 Linux 上,從核心版本 3.3 開始(並且提供了
UNIX_DIAG
核心中內置的功能),可以使用新的基於netlink的 API 獲得給定 unix 域套接字(包括套接字對)的對等點。
lsof
由於 4.89 版可以使用該 API:lsof +E -aUc Xorg
將列出所有 Unix 域套接字,其程序的名稱以
Xorg
任一端開頭,格式類似於:Xorg 2777 root 56u unix 0xffff8802419a7c00 0t0 34036 @/tmp/.X11-unix/X0 type=STREAM ->INO=33273 4120,xterm,3u
如果您的版本
lsof
太舊,還有更多選擇。該
ss
實用程序(來自iproute2
)使用相同的 API 來檢索和顯示系統上的 unix 域套接字列表上的資訊,包括對等資訊。套接字由它們的inode 編號標識。請注意,它與套接字文件的文件系統 inode 無關。
例如在:
$ ss -x [...] u_str ESTAB 0 0 @/tmp/.X11-unix/X0 3435997 * 3435996
它說套接字 3435997(綁定到 ABSTRACT 套接字
/tmp/.X11-unix/X0
)與套接字 3435996 連接。該-p
選項可以告訴您哪些程序打開了該套接字。它通過做一些readlink
s on/proc/$pid/fd/*
來做到這一點,所以它只能在你擁有的程序上做到這一點(除非你是root
)。例如這裡:$ sudo ss -xp [...] u_str ESTAB 0 0 @/tmp/.X11-unix/X0 3435997 * 3435996 users:(("Xorg",pid=3080,fd=83)) [...] $ sudo ls -l /proc/3080/fd/23 lrwx------ 1 root root 64 Mar 12 16:34 /proc/3080/fd/83 -> socket:[3435997]
要找出哪個程序有 3435996,您可以在以下輸出中查找它自己的條目
ss -xp
:$ ss -xp | awk '$6 == 3435996' u_str ESTAB 0 0 * 3435996 * 3435997 users:(("xterm",pid=29215,fd=3))
您還可以將此腳本用作包裝器,
lsof
以便在此處輕鬆顯示相關資訊:#! /usr/bin/perl # lsof wrapper to add peer information for unix domain socket. # Needs Linux 3.3 or above and CONFIG_UNIX_DIAG enabled. # retrieve peer and direction information from ss my (%peer, %dir); open SS, '-|', 'ss', '-nexa'; while (<SS>) { if (/\s(\d+)\s+\*\s+(\d+) ([<-]-[->])$/) { $peer{$1} = $2; $dir{$1} = $3; } } close SS; # Now get info about processes tied to sockets using lsof my (%fields, %proc); open LSOF, '-|', 'lsof', '-nPUFpcfin'; while (<LSOF>) { if (/(.)(.*)/) { $fields{$1} = $2; if ($1 eq 'n') { $proc{$fields{i}}->{"$fields{c},$fields{p}" . ($fields{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = ""; } } } close LSOF; # and finally process the lsof output open LSOF, '-|', 'lsof', @ARGV; while (<LSOF>) { chomp; if (/\sunix\s+\S+\s+\S+\s+(\d+)\s/) { my $peer = $peer{$1}; if (defined($peer)) { $_ .= $peer ? " ${dir{$1}} $peer\[" . (join("|", keys%{$proc{$peer}})||"?") . "]" : "[LISTENING]"; } } print "$_\n"; } close LSOF or exit(1);
例如:
$ sudo that-lsof-wrapper -ad3 -p 29215 命令 PID 使用者 FD 類型 設備尺寸/關閉節點名稱 xterm 29215 stephane 3u unix 0xffff8800a07da4c0 0t0 3435996 type=STREAM **<-> 3435997[Xorg,3080,@/tmp/.X11-unix/X0]**
linux-3.3之前
用於檢索 unix 套接字資訊的舊 Linux API 是通過
/proc/net/unix
文本文件。它列出了所有 Unix 域套接字(包括套接字對)。正如@Totor 已經解釋的那樣,kernel.kptr_restrict
其中的第一個欄位(如果沒有使用sysctl 參數對非超級使用者隱藏)包含一個結構的核心地址,該結構包含一個指向相應peer的欄位。它也是Unix 套接字上列的輸出。unix_sock``peer
unix_sock``lsof``DEVICE
現在獲取該
peer
欄位的值意味著能夠讀取核心記憶體並知道該peer
欄位相對於unix_sock
地址的偏移量。已經給出了幾個
gdb
基於- 和 -systemtap
的gdb
解決方案,但它們需要/systemtap
和正在安裝的正在執行的核心的 Linux 核心調試符號,這在生產系統上通常不是這種情況。硬編碼偏移量並不是一個真正的選擇,因為它因核心版本而異。
現在我們可以使用啟發式方法來確定偏移量:讓我們的工具創建一個虛擬對象(然後我們知道兩個對等點的地址),然後在另一端的記憶體周圍
socketpair
搜尋對等點的地址以確定偏移量。這是一個概念驗證腳本,它使用
perl
(在 i386 上使用核心 2.4.27 和 2.6.32 以及在 amd64 上使用 3.13 和 3.16 成功測試)。像上面一樣,它作為一個包裝器工作lsof
:例如:
$ that-lsof-wrapper -aUc nm-applet 命令 PID 使用者 FD 類型 設備尺寸/關閉節點名稱 nm-applet 4183 stephane 4u unix 0xffff8800a055eb40 0t0 36888類型= stream **- > 0xffff8800a055e7c0 [dbus-dafff8800a055e7c0 [dbus-dafff8800a055e7c0 [dbus-daemon,4190,@ / tmp / dbus-aibcxon up6]** nm-applet 4183 stephane 7u unix 0xffff8800a055e440 0t0 36890類型= stream **- > 0xffff8800a055e0c0 [xorg, 3080,@/tmp/.X11-unix/X0]** nm-applet 4183 stephane 8u unix 0xffff8800a05c1040 0t0 36201 type=STREAM **-> 0xffff8800a05c13c0[dbus-daemon,4118,@/tmp/dbus-yxxNr1NkYC]** nm-applet 41311 UNIX 0xFFFF8800A055D080 0T0 36219類型=流**- > 0xFFFF8800A055D400 [DBUS-守護程序,4118,@ / TMP / DBUS-YXXNR1NKYC] NM** -Applet 4183 Stephane 12U UNIX 0xFFFF88022E0DFB80 0T0 36221 Type = Stream **- > 0xFFFF88022E0DF800 [DBUS-Daemon,2268,/ var /run/dbus/system_bus_socket]** nm-applet 4183 stephane 13u unix 0xffff88022e0f80c0 0t0 37025 type=STREAM **-> 0xffff88022e29ec00[dbus-daemon,2268,/var/run/dbus/system_bus_socket]**
這是腳本:
#! /usr/bin/perl # wrapper around lsof to add peer information for Unix # domain sockets. needs lsof, and superuser privileges. # Copyright Stephane Chazelas 2015, public domain. # example: sudo this-lsof-wrapper -aUc Xorg use Socket; open K, "<", "/proc/kcore" or die "open kcore: $!"; read K, $h, 8192 # should be more than enough or die "read kcore: $!"; # parse ELF header my ($t,$o,$n) = unpack("x4Cx[C19L!]L!x[L!C8]S", $h); $t = $t == 1 ? "L3x4Lx12" : "Lx4QQx8Qx16"; # program header ELF32 or ELF64 my @headers = unpack("x$o($t)$n",$h); # read data from kcore at given address (obtaining file offset from ELF # @headers) sub readaddr { my @h = @headers; my ($addr, $length) = @_; my $offset; while (my ($t, $o, $v, $s) = splice @h, 0, 4) { if ($addr >= $v && $addr < $v + $s) { $offset = $o + $addr - $v; if ($addr + $length - $v > $s) { $length = $s - ($addr - $v); } last; } } return undef unless defined($offset); seek K, $offset, 0 or die "seek kcore: $!"; my $ret; read K, $ret, $length or die "read($length) kcore \@$offset: $!"; return $ret; } # create a dummy socketpair to try find the offset in the # kernel structure socketpair(Rdr, Wtr, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!"; $r = readlink("/proc/self/fd/" . fileno(Rdr)) or die "readlink Rdr: $!"; $r =~ /\[(\d+)/; $r = $1; $w = readlink("/proc/self/fd/" . fileno(Wtr)) or die "readlink Wtr: $!"; $w =~ /\[(\d+)/; $w = $1; # now $r and $w contain the socket inodes of both ends of the socketpair die "Can't determine peer offset" unless $r && $w; # get the inode->address mapping open U, "<", "/proc/net/unix" or die "open unix: $!"; while (<U>) { if (/^([0-9a-f]+):(?:\s+\S+){5}\s+(\d+)/) { $addr{$2} = hex $1; } } close U; die "Can't determine peer offset" unless $addr{$r} && $addr{$w}; # read 2048 bytes starting at the address of Rdr and hope to find # the address of Wtr referenced somewhere in there. $around = readaddr $addr{$r}, 2048; my $offset = 0; my $ptr_size = length(pack("L!",0)); my $found; for (unpack("L!*", $around)) { if ($_ == $addr{$w}) { $found = 1; last; } $offset += $ptr_size; } die "Can't determine peer offset" unless $found; my %peer; # now retrieve peer for each socket for my $inode (keys %addr) { $peer{$addr{$inode}} = unpack("L!", readaddr($addr{$inode}+$offset,$ptr_size)); } close K; # Now get info about processes tied to sockets using lsof my (%fields, %proc); open LSOF, '-|', 'lsof', '-nPUFpcfdn'; while (<LSOF>) { if (/(.)(.*)/) { $fields{$1} = $2; if ($1 eq 'n') { $proc{hex($fields{d})}->{"$fields{c},$fields{p}" . ($fields{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = ""; } } } close LSOF; # and finally process the lsof output open LSOF, '-|', 'lsof', @ARGV; while (<LSOF>) { chomp; for my $addr (/0x[0-9a-f]+/g) { $addr = hex $addr; my $peer = $peer{$addr}; if (defined($peer)) { $_ .= $peer ? sprintf(" -> 0x%x[", $peer) . join("|", keys%{$proc{$peer}}) . "]" : "[LISTENING]"; last; } } print "$_\n"; } close LSOF or exit(1);