커널 3.3부터 시작

커널 3.3부터 시작

UNIX 소켓의 다른 쪽 끝을 소유한 프로세스를 확인하고 싶습니다.

socketpair()특히, 질문은 모든 UNIX 소켓에 대해 동일하지만 생성된 소켓을 사용하는 것에 대해 묻고 있습니다 .

a 와 s를 parent생성하는 프로그램이 있습니다 . 상위 프로세스가 종료 되고 통신이 유지됩니다. 아이들은 그 반대다 . 그런 다음 어린이를 위한 또 다른 프로그램인 . 두 소켓은 이 소켓 쌍을 통해 앞뒤로 통신할 수 있습니다.socketpair(AF_UNIX, SOCK_STREAM, 0, fds)fork()fds[1]fds[0]close(fds[0]); s=fds[1]exec()child1

이제, 나는 그것이 누구인지 알고 있지만, 그것이 누구인지 parent알아내고 싶다고 가정해 보겠습니다 . child1어떻게 해야 하나요?

사용할 수 있는 도구는 여러 가지가 있지만 그 중 어떤 프로세스가 소켓의 반대편에 있는지 알려주는 도구는 없습니다. 나는 시도했다:

  • lsof -c progname
  • lsof -c parent -c child1
  • ls -l /proc/$(pidof server)/fd
  • cat /proc/net/unix

기본적으로 소켓과 그 내용을 모두 볼 수 있지만 연결되어 있는지는 알 수 없습니다. 부모 프로세스의 어떤 FD가 어떤 자식 프로세스와 통신하는지 확인하려고 합니다.

답변1

노트: 이제 lsof여기에 설명된 두 가지 접근 방식을 결합하고 루프백 TCP 연결 피어에 대한 정보를 다음 위치에 추가하는 래퍼를 유지 관리합니다.https://github.com/stephane-chazelas/misc-scripts/blob/master/lsofc

Linux-3.3 이상.

Linux에서는 커널 버전 3.3부터 시작합니다(및UNIX_DIAG기능이 커널에 내장되어 있음), 특정 UNIX 도메인 소켓(소켓 쌍 포함)의 피어는 새로운인터넷 연결API를 기반으로 합니다.

lsof이 API는 버전 4.89부터 사용할 수 있습니다.

lsof +E -aUc Xorg

Xorg이름이 양쪽 끝으로 시작하는 프로세스가 있는 모든 Unix 도메인 소켓을 다음과 유사한 형식으로 나열합니다 .

Xorg       2777       root   56u  unix 0xffff8802419a7c00      0t0   34036 @/tmp/.X11-unix/X0 type=STREAM ->INO=33273 4120,xterm,3u

버전이 lsof너무 오래된 경우 더 많은 옵션이 있습니다.

ssResources의 이 유틸리티 iproute2는 동일한 API를 사용하여 피어 정보를 포함하여 시스템의 UNIX 도메인 소켓 목록에 대한 정보를 검색하고 표시합니다.

소켓은 다음으로 식별됩니다.아이노드 번호. 소켓 파일의 파일 시스템 inode와는 아무런 관련이 없습니다.

예를 들어:

$ ss -x
[...]
u_str  ESTAB    0    0   @/tmp/.X11-unix/X0 3435997     * 3435996

이는 소켓 3435997(ABSTRACT 소켓에 바인딩됨 /tmp/.X11-unix/X0)이 소켓 3435996에 연결되어 있음을 나타냅니다. 이 -p옵션은 소켓이 열려 있는 프로세스를 알려줍니다. readlink에 대해 일부 작업을 수행하여 이를 수행하므로 귀하 /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 스티븐 3u 유닉스 0xffff8800a07da4c0 0t0 3435996 유형=스트림<-> 3435997[Xorg,3080,@/tmp/.X11-unix/X0]

Linux-3.3 이전

이전 Linux API는 텍스트 파일에서 Unix 소켓 정보를 검색했습니다 /proc/net/unix. 여기에는 모든 Unix 도메인 소켓(소켓 쌍 포함)이 나열됩니다. 첫 번째 필드(sysctl 매개변수가 사용되지 않으면 수퍼유저가 아닌 사용자에게는 숨겨짐 kernel.kptr_restrict)는 다음과 같습니다.@Totor는 이미 설명했습니다.해당 항목을 가리키는 필드를 unix_sock포함하는 구조체의 커널 주소를 포함합니다.peer동료 unix_sock. 이는 lsofUnix 소켓에 있는 열의 출력 이기도 합니다 .DEVICE

이제 이 필드의 값을 얻는다는 것은 peer커널 메모리를 읽고 주소 peer에 상대적인 이 필드 unix_sock의 오프셋을 알 수 있다는 것을 의미합니다.

일부gdb-기반그리고systemtap-기반솔루션이 제공되었지만 실행 중인 커널을 설치하려면 gdb/ systemtap및 Linux 커널 디버그 기호가 필요합니다. 이는 일반적으로 프로덕션 시스템에는 해당되지 않습니다.

오프셋을 하드코딩하는 것은 커널 버전에서 커널 버전으로 변경되므로 실제로 옵션이 아닙니다.

이제 경험적 방법을 사용하여 오프셋을 결정할 수 있습니다. 도구에서 더미 객체를 생성하고 socketpair(그러면 두 피어의 주소를 알 수 있음) 검색할 수 있습니다.동료반대쪽 끝에 있는 메모리 주위의 오프셋을 결정합니다.

이것은 바로 이를 수행하는 개념 증명 스크립트입니다 perl(커널 2.4.27 및 2.6.32를 사용하는 i386과 3.13 및 3.16을 사용하는 amd64에서 성공적으로 테스트됨). 위와 마찬가지로 다음을 감싸는 역할을 합니다 lsof.

예를 들어:

$ that-lsof-wrapper -aUc nm-applet
명령 PID 사용자 FD 유형 장치 크기/종료 노드 이름
nm-applet 4183 Stephane 4u unix 0xffff8800a055eb40 0t0 36888 유형=STREAM-> 0xffff8800a055e7c0[dbus-daemon,4190,@/tmp/dbus-AiBCXOnuP6]
nm-applet 4183 Stephane 7u unix 0xffff8800a055e440 0t0 36890 유형=STREAM-> 0xffff8800a055e0c0[Xorg,3080,@/tmp/.X11-unix/X0]
nm-applet 4183 스테판 8u unix 0xffff8800a05c1040 0t0 36201 유형=STREAM-> 0xffff8800a05c13c0[dbus-daemon,4118,@/tmp/dbus-yxxNr1NkYC]
nm-applet 4183 스테판 11u unix 0xffff8800a055d080 0t0 36219 유형=STREAM-> 0xffff8800a055d400[dbus-daemon,4118,@/tmp/dbus-yxxNr1NkYC]
nm-applet 4183 스테판 12u unix 0xffff88022e0dfb80 0t0 36221 유형=스트림-> 0xffff88022e0df800[dbus-daemon, 2268, /var/run/dbus/system_bus_socket]
nm-applet 4183 스테판 13u unix 0xffff88022e0f80c0 0t0 37025 유형=STREAM-> 0xffff88022e29ec00 [dbus-데몬, 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);

답변2

커널 3.3부터 다음을 사용할 수 있습니다.ss또는 lsof-4.89그 이상 — 참조Stefan Chazeras의 답변.

저자에 따르면 이전 버전에서는 lsof이를 발견하는 것이 불가능했습니다. Linux 커널은 이 정보를 노출하지 않았습니다. 원천:comp.unix.admin의 2003 스레드.

에 표시된 숫자는/proc/$pid/fd/$fd 가상 소켓 파일 시스템에 있는 소켓의 inode 번호입니다. 파이프 또는 소켓 쌍을 생성할 때 각 끝은 연속적으로 inode 번호를 받습니다. 번호는 순차적으로 할당되므로 1씩 차이가 날 확률이 높지만, 이는 보장되지 않습니다(첫 번째 소켓은질소그리고질소+1은 패키징으로 인해 이미 사용 중이거나 두 inode 할당 사이에 다른 스레드가 예약되어 있고 해당 스레드도 일부 inode를 생성하기 때문에 사용 중입니다.

나는 확인했다socketpair커널 2.6.39의 정의, 소켓의 두 끝은 특정 유형을 제외하고는 관련이 없습니다.socketpair방법. 유닉스 소켓의 경우unix_socketpair존재하다net/unix/af_unix.c.

답변3

커널 3.3부터 시작

할 수 있는 지금이 정보를 얻으세요ss:

# ss -xp

이제 열의 다른 ID에 해당하는 Peer열의 ID(인덱스 노드 번호)를 볼 수 있습니다. Local일치하는 ID는 소켓의 두 끝입니다.

참고: UNIX_DIAG이 옵션은 커널에서 활성화되어야 합니다.

커널 3.3 이전

Linux는 이 정보를 사용자 공간에 노출하지 않습니다.

그러나,커널 메모리 보기, 우리는 이 정보에 접근할 수 있습니다.

gdb참고 : 이 답변은 다음을 사용하여 구현되었습니다.@StéphaneChazelas의 답변이 부분은 좀 더 자세히 설명되어 있습니다.

# lsof | grep whatever
mysqld 14450 (...) unix 0xffff8801011e8280 (...) /var/run/mysqld/mysqld.sock
mysqld 14450 (...) unix 0xffff8801011e9600 (...) /var/run/mysqld/mysqld.sock

2개의 서로 다른 소켓이 있으며, 1개는 수신 대기 중이고 1개는 설정됩니다. 16진수는 해당 코어의 주소입니다.unix_sock구조, 하나의 peer속성은 주소입니다소켓의 다른 쪽 끝(또한 unix_sock구조체 인스턴스).

이제 다음을 사용하여 커널 메모리를 gdb찾을 수 있습니다.peer

# gdb /usr/lib/debug/boot/vmlinux-3.2.0-4-amd64 /proc/kcore
(gdb) print ((struct unix_sock*)0xffff8801011e9600)->peer
$1 = (struct sock *) 0xffff880171f078c0

# lsof | grep 0xffff880171f078c0
mysql 14815 (...) unix 0xffff880171f078c0 (...) socket

여기서 소켓의 다른 쪽 끝은 PID 14815에 의해 유지됩니다 mysql.

커널을 KCORE_ELF사용하려면 컴파일해야 합니다 /proc/kcore. 또한 디버그 기호가 있는 커널 이미지 버전이 필요합니다. Debian 7에서는 apt-get install linux-image-3.2.0-4-amd64-dbg이 파일을 사용할 수 있습니다.

디버깅 가능한 커널 이미지가 필요하지 않습니다...

시스템에 디버그 커널 이미지가 없거나 유지하고 싶지 않은 경우 "수동으로" 값에 gdb액세스하기 위한 메모리 오프셋을 제공할 수 있습니다. peer이 오프셋은 일반적으로 커널 버전이나 아키텍처에 따라 다릅니다.

내 커널에서는 오프셋이 680바이트, 즉 64비트의 85배라는 것을 알고 있습니다. 그래서 나는 이것을 할 수 있습니다 :

# gdb /boot/vmlinux-3.2.0-4-amd64 /proc/kcore
(gdb) print ((void**)0xffff8801011e9600)[85]
$1 = (void *) 0xffff880171f078c0

짜잔, 결과는 위와 같습니다.

여러 시스템에서 동일한 커널을 실행하는 경우 디버그 이미지가 필요 없고 오프셋 값만 필요하므로 이 변형을 사용하는 것이 더 쉽습니다.

처음에 이 오프셋 값을 (쉽게) 찾으려면 실제로 이미지를 디버그해야 합니다.

$ pahole -C unix_sock /usr/lib/debug/boot/vmlinux-3.2.0-4-amd64
struct unix_sock {
  (...)
  struct sock *              peer;                 /*   680     8 */
  (...)
}

680바이트는 85 x 64비트, 즉 170 x 32비트입니다.

이 답변에 대한 대부분의 크레딧은 다음과 같습니다.평균 전압.

답변4

이 솔루션은 유효하지만 관심이 제한적입니다. 충분한 새 시스템 탭이 있으면 다음에서 사용할 수 있는 충분히 새로운 커널을 갖게 될 가능성이 높기 때문입니다.ss기반으로 방법, 이전 커널을 사용하는 경우기타 솔루션, 비록 더 많은마구 자르기작동 가능성이 높으며 추가 소프트웨어가 필요하지 않습니다.

systemtap이러한 유형의 작업을 사용하는 방법을 보여주는 데 여전히 유용합니다.

systemtap을 사용할 수 있는 최신 Linux 시스템(1.8 이상)을 사용하는 경우 다음 스크립트를 사용하여 다음 출력을 사후 처리할 수 있습니다 lsof.

예를 들어:

$ lsof -aUc nm 애플릿 sudo 스크립트 |
명령 PID 사용자 FD 유형 장치 크기/종료 노드 이름
nm-applet 4183 Stephane 4u unix 0xffff8800a055eb40 0t0 36888 유형=STREAM-> 0xffff8800a055e7c0[dbus-daemon,4190,@/tmp/dbus-AiBCXOnuP6]
nm-applet 4183 Stephane 7u unix 0xffff8800a055e440 0t0 36890 유형=STREAM-> 0xffff8800a055e0c0[Xorg,3080,@/tmp/.X11-unix/X0]
nm-applet 4183 스테판 8u unix 0xffff8800a05c1040 0t0 36201 유형=STREAM-> 0xffff8800a05c13c0[dbus-daemon,4118,@/tmp/dbus-yxxNr1NkYC]
nm-applet 4183 스테판 11u unix 0xffff8800a055d080 0t0 36219 유형=STREAM-> 0xffff8800a055d400[dbus-daemon,4118,@/tmp/dbus-yxxNr1NkYC]
nm-applet 4183 스테판 12u unix 0xffff88022e0dfb80 0t0 36221 유형=스트림-> 0xffff88022e0df800[dbus-daemon, 2268, /var/run/dbus/system_bus_socket]
nm-applet 4183 스테판 13u unix 0xffff88022e0f80c0 0t0 37025 유형=STREAM-> 0xffff88022e29ec00 [dbus-데몬, 2268, /var/run/dbus/system_bus_socket]

(위에 0xffff... 대신 0x0000000000000000이 표시되는 경우 이는 sysctl 매개변수가 시스템에 설정되어 커널 포인터가 권한이 없는 프로세스에서 숨겨지기 때문입니다 . 이 경우 의미 있는 결과를 얻으려면 루트로 실행 kernel.kptr_restrict해야 합니다 ) lsof.

스크립트는 개행 문자가 포함된 소켓 파일 이름을 처리하려고 시도하지 않지만 그렇지 않습니다 lsof( lsof공백이나 콜론도 처리하지 않음).

systemtapunix_sock이는 커널의 해시에 있는 모든 구조의 주소와 피어 주소를 덤프하는 데 사용됩니다 .unix_socket_table

systemtap 2.6이 포함된 Linux 3.16 amd64 및 2.3이 포함된 3.13에서만 테스트되었습니다.

#! /usr/bin/perl
# meant to process lsof output to try and find the peer of a given
# unix domain socket. Needs a working systemtap, lsof, and superuser
# privileges. Copyright Stephane Chazelas 2015, public domain.
# Example: lsof -aUc X | sudo this-script
open STAP, '-|', 'stap', '-e', q{
  probe begin {
    offset = &@cast(0, "struct sock")->__sk_common->skc_node;
    for (i = 0; i < 512; i++) 
      for (p = @var("unix_socket_table@net/unix/af_unix.c")[i]->first;
           p;
           p=@cast(p, "struct hlist_node")->next
          ) {
        sock = p - offset;
        printf("%p %p\n", sock, @cast(sock, "struct unix_sock")->peer);
    }
    exit()
  }
};  
my %peer;
while (<STAP>) {
  chomp;
  my ($a, $b) = split;
  $peer{$a} = $b;
}
close STAP;

my %f, %addr;
open LSOF, '-|', 'lsof', '-nPUFpcfdn';
while (<LSOF>) {
  if (/(.)(.*)/) {
    $f{$1} = $2;
    if ($1 eq 'n') {
      $addr{$f{d}}->{"$f{c},$f{p}" . ($f{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = "";
    }
  }
}
close LSOF;

while (<>) {
  chomp;
  for my $addr (/0x[0-9a-f]+/g) {
    my $peer = $peer{$addr};
    if (defined($peer)) {
      $_ .= $peer eq '0x0' ?
            "[LISTENING]" :
            " -> $peer\[" . join("|", keys%{$addr{$peer}}) . "]";
      last;
    }
  }
  print "$_\n";
}

관련 정보