Process

誰有這個 unix socketpair 的另一端?

  • September 12, 2020

我想確定哪個程序擁有 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 開始,可以使用ssorlsof-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選項可以告訴您哪些程序打開了該套接字。它通過做一些readlinks 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基於- 和 -systemtapgdb解決方案,但它們需要/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);

引用自:https://unix.stackexchange.com/questions/16300