Quem tem a outra extremidade deste par de socket unix?

51

Eu quero determinar qual processo tem a outra extremidade de um soquete UNIX.

Especificamente, estou perguntando sobre um que foi criado com socketpair() , embora o problema seja o mesmo para qualquer soquete UNIX.

Eu tenho um programa parent que cria um socketpair(AF_UNIX, SOCK_STREAM, 0, fds) e fork() s. O processo pai fecha fds[1] e mantém fds[0] para se comunicar. A criança faz o oposto, close(fds[0]); s=fds[1] . Então a criança exec() s outro programa, child1 . Os dois podem se comunicar através deste par de soquetes.

Agora, digamos que eu saiba quem é parent , mas quero descobrir quem é child1 . Como eu faço isso?

Existem várias ferramentas à minha disposição, mas nenhuma pode me dizer qual processo está na outra extremidade do soquete. Eu tentei:

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

Basicamente, eu posso ver os dois soquetes e tudo sobre eles, mas não posso dizer que eles estão conectados. Estou tentando determinar qual FD no pai está se comunicando com qual processo filho.

    
por Jonathon Reinhart 09.07.2011 / 00:16

6 respostas

26

Desde o kernel 3.3, é possível usar ss ou lsof-4.89 ou acima - veja a resposta de Stéphane Chazelas .

Em versões mais antigas, de acordo com o autor de lsof , era impossível descobrir isso: o kernel do Linux não expõe essa informação. Fonte: thread de 2003 no comp.unix.admin .

O número mostrado em /proc/$pid/fd/$fd é o número do inode do soquete no sistema de arquivos do soquete virtual. Quando você cria um par de tubos ou sockets, cada extremidade recebe sucessivamente um número de inode. Os números são atribuídos sequencialmente, portanto, há uma alta probabilidade de os números diferirem em 1, mas isso não é garantido (seja porque o primeiro soquete era N e N +1 já estava em uso devido ao empacotamento, ou porque algum outro thread foi agendado entre as duas alocações de inode e esse encadeamento também criou alguns inodes).

Eu verifiquei a definição de socketpair no kernel 2.6.39 , e as duas extremidades do socket não são correlacionadas, exceto pelo tipo específico método socketpair . Para soquetes unix, isso é unix_socketpair in net/unix/af_unix.c .

    
por 09.07.2011 / 16:22
34

Note: I now maintain a lsof wrapper that combines both approaches described here and also adds information for peers of loopback TCP connections at https://github.com/stephane-chazelas/misc-scripts/blob/master/lsofc

Linux-3.3 e superior.

No Linux, desde a versão 3.3 do kernel (e desde que o recurso UNIX_DIAG seja construído no kernel), o par de um determinado socket de domínio unix (inclui socketpairs) pode ser obtido usando uma nova API baseada em netlink .

lsof desde a versão 4.89 pode fazer uso dessa API:

lsof +E -aUc Xorg

Listará todos os sockets do domínio Unix que possuem um processo cujo nome começa com Xorg em cada extremidade em um formato semelhante a:

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

Se a sua versão de lsof for muito antiga, existem mais algumas opções.

O utilitário ss (de iproute2 ) faz uso dessa mesma API para recuperar e exibir informações na lista de soquetes de domínio Unix no sistema, incluindo informações de mesmo nível.

Os soquetes são identificados pelo seu número de inode . Note que não está relacionado ao inode do sistema de arquivos do arquivo socket.

Por exemplo, em:

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

ele diz que o soquete 3435997 (que estava ligado ao soquete ABSTRACT /tmp/.X11-unix/X0 ) está conectado com o soquete 3435996. A opção -p pode informar a você qual processo (es) tem esse soquete aberto. Ele faz isso fazendo readlink s em /proc/$pid/fd/* , então só pode fazer isso nos processos que você possui (a menos que você seja root ). Por exemplo, aqui:

$ 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]

Para descobrir quais processos tem 3435996, você pode procurar sua própria entrada na saída de ss -xp :

$ ss -xp | awk '$6 == 3435996'
u_str  ESTAB  0  0  * 3435996  * 3435997 users:(("xterm",pid=29215,fd=3))

Você também pode usar esse script como um wrapper em torno de lsof para mostrar facilmente as informações relevantes:

#! /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);

Por exemplo:

$ sudo that-lsof-wrapper -ad3 -p 29215
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF    NODE NAME
xterm   29215 stephane    3u  unix 0xffff8800a07da4c0      0t0 3435996 type=STREAM <-> 3435997[Xorg,3080,@/tmp/.X11-unix/X0]

Antes do linux-3.3

A API do Linux antiga para recuperar as informações do soquete unix é através do arquivo de texto /proc/net/unix . Ele lista todos os sockets de domínio Unix (incluindo socketpairs). O primeiro campo lá (se não oculto para os não-superusuários com o parâmetro kernel.kptr_restrict sysctl) como já explicado pelo @Totor contém o endereço do kernel de uma estrutura unix_sock que contém um campo peer apontando para o correspondente peer unix_sock . É também o que lsof produz para a coluna DEVICE em um soquete Unix.

Agora, obter o valor desse campo peer significa poder ler a memória do kernel e saber o deslocamento desse campo peer em relação ao endereço unix_sock .

Várias gdb -based e systemtap -based já foram fornecidas soluções, mas elas exigem gdb / systemtap e símbolos de depuração do kernel Linux para o kernel em execução que está sendo instalado, o que geralmente não é o caso em sistemas de produção. / p>

Hardcoding o deslocamento não é realmente uma opção, pois varia com a versão do kernel.

Agora podemos usar uma abordagem heurística para determinar o deslocamento: faça com que nossa ferramenta crie um dummy socketpair (então sabemos o endereço de ambos os pares) e procure o endereço do peer em torno da memória na outra extremidade para determinar o deslocamento.

Aqui está um script de prova de conceito que faz exatamente isso usando perl (testado com sucesso com o kernel 2.4.27 e 2.6.32 no i386 e 3.13 e 3.16 no amd64). Como acima, funciona como um wrapper em torno de lsof :

Por exemplo:

$ that-lsof-wrapper -aUc nm-applet
COMMAND    PID     USER   FD   TYPE             DEVICE SIZE/OFF  NODE NAME
nm-applet 4183 stephane    4u  unix 0xffff8800a055eb40      0t0 36888 type=STREAM -> 0xffff8800a055e7c0[dbus-daemon,4190,@/tmp/dbus-AiBCXOnuP6]
nm-applet 4183 stephane    7u  unix 0xffff8800a055e440      0t0 36890 type=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 4183 stephane   11u  unix 0xffff8800a055d080      0t0 36219 type=STREAM -> 0xffff8800a055d400[dbus-daemon,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]

Aqui está o script:

#! /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);
    
por 17.03.2015 / 01:18
9

Erkki Seppala na verdade tem uma ferramenta que recupera essas informações do kernel do Linux com gdb .. Está disponível aqui .

    
por 27.07.2011 / 16:11
8

Desde o kernel 3.3

Você pode agora obtenha esta informação com < href="http://man7.org/linux/man-pages/man8/ss.8.html"> ss :

# ss -xp

Agora você pode ver na coluna Peer uma ID (inode number) que corresponde a outra ID na coluna Local . IDs correspondentes são as duas extremidades de um soquete.

Nota: A opção UNIX_DIAG deve estar ativada em seu kernel.

Antes do kernel 3.3

O Linux não expôs esta informação ao userland.

No entanto, ao observar a memória do kernel , podemos acessar essas informações.

Observação: essa resposta é feita usando gdb , no entanto, consulte @ StéphaneChazelas 'resposta , que é mais elaborada a este respeito.

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

Existem 2 tomadas diferentes, 1 escuta e 1 estabelecida. O número hexa é o endereço para o kernel correspondente unix_sock structure , tendo um atributo peer sendo o endereço de a outra extremidade do socket (também uma instância unix_sock structure).

Agora podemos usar gdb para encontrar o peer na memória do kernel:

# 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

Aqui você vai, a outra extremidade do socket é mantida por mysql , PID 14815.

Seu kernel deve ser compilado com KCORE_ELF para usar /proc/kcore . Além disso, você precisa de uma versão da sua imagem do kernel com símbolos de depuração. No Debian 7, apt-get install linux-image-3.2.0-4-amd64-dbg fornecerá este arquivo.

Não há necessidade da imagem do kernel depurável ...

Se você não tem (ou não deseja manter) a imagem do kernel de depuração no sistema, você pode dar a gdb o deslocamento da memória para acessar "manualmente" o valor peer . Esse valor de deslocamento geralmente difere com a versão ou arquitetura do kernel.

No meu kernel, eu sei que o deslocamento é de 680 bytes, ou seja, 85 vezes 64 bits. Então eu posso fazer:

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

Voilà, mesmo resultado acima.

Se você tiver o mesmo kernel rodando em várias máquinas, é mais fácil usar essa variante porque você não precisa da imagem de depuração, apenas o valor de deslocamento.

Para (facilmente) descobrir esse valor de deslocamento em primeiro lugar, você precisa da imagem de depuração:

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

Aqui você vai, 680 bytes, isto é 85 x 64 bits, ou 170 x 32 bits.

A maior parte do crédito desta resposta vai para MvG .

    
por 12.03.2015 / 12:30
5

This solution, though working, is of limited interest since if you have a recent-enough systemtap, chances are you'll have a recent-enough kernel where you can use ss based approaches, and if you're on an older kernel, that other solution, though more hacky is more likely to work and doesn't require addition software.

Still useful as a demonstration of how to use systemtap for this kind of task.

Se em um sistema Linux recente com um systemtap de trabalho (1.8 ou mais recente), você poderia usar o script abaixo para pós-processar a saída de lsof :

Por exemplo:

$ lsof -aUc nm-applet | sudo that-script
COMMAND    PID     USER   FD   TYPE             DEVICE SIZE/OFF  NODE NAME
nm-applet 4183 stephane    4u  unix 0xffff8800a055eb40      0t0 36888 type=STREAM -> 0xffff8800a055e7c0[dbus-daemon,4190,@/tmp/dbus-AiBCXOnuP6]
nm-applet 4183 stephane    7u  unix 0xffff8800a055e440      0t0 36890 type=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 4183 stephane   11u  unix 0xffff8800a055d080      0t0 36219 type=STREAM -> 0xffff8800a055d400[dbus-daemon,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]

(se você ver 0x0000000000000000 acima em vez de 0xffff ..., é porque o parâmetro kernel.kptr_restrict sysctl está definido em seu sistema, o que faz com que os ponteiros do kernel fiquem ocultos de processos não privilegiados, caso em que você precisará execute lsof como root para obter um resultado significativo.

Este script não faz nenhuma tentativa de lidar com nomes de arquivo de soquete com caracteres de nova linha, mas também lsof (nem lsof cope com espaços em branco ou vírgula).

systemtap aqui é usado para despejar o endereço e o endereço dos pares de todas as estruturas unix_sock no hash unix_socket_table no kernel.

Apenas testado no Linux 3.16 amd64 com systemtap 2.6 e 3.13 com 2.3.

#! /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";
}
    
por 13.03.2015 / 13:10
2

4.89 of lsof suporta a exibição de opções de terminal.

Citado em lsof.8:

+|-E +E specifies that process intercommunication channels should be
     displayed with endpoint information and the channels
     of the endpoints should also be displayed.  Currently
     only pipe on Linux is implemented.

     Endpoint information is displayed in the NAME column
     in the form "PID,cmd,FDmode".  PID is the endpoint
     process ID; cmd is the endpoint process command; FD is
     the endpoint file's descriptor; and mode is the
     endpoint file's access mode.  Multiple occurrences of
     this information can appear in a file's NAME column.

     -E specfies that Linux pipe files should only be
     displayed with endpoint information.

Exemplo de saída:

mozStorag 21535 22254  yamato    6u     unix 0xf...       0t0     348924 type=STREAM pino=351122 4249,dbus-daem,55u
mozStorag 21535 22254  yamato   10u     unix 0xf...       0t0     356193 type=STREAM pino=356194 21535,gdbus,11u
mozStorag 21535 22254  yamato   11u     unix 0xf...       0t0     356194 type=STREAM pino=356193 21535,gdbus,10u
mozStorag 21535 22254  yamato   21u     unix 0xf...       0t0     355141 type=STREAM pino=357544 4249,dbus-daem,60u
mozStorag 21535 22254  yamato   26u     unix 0xf...       0t0     351134 type=STREAM pino=355142 5015,gdbus,17u
mozStorag 21535 22254  yamato   69u     unix 0xf...       0t0     469354 type=STREAM pino=468160 4545,alsa-sink,21u
mozStorag 21535 22254  yamato   82u     unix 0xf...       0t0     449383 type=STREAM pino=449384 12257,Chrome_Ch,3u
mozStorag 21535 22254  yamato   86u     unix 0xf...       0t0     355174 type=SEQPACKET pino=355175 21535,gdbus,95u
mozStorag 21535 22254  yamato   95u     unix 0xf...       0t0     355175 type=SEQPACKET pino=355174 21535,gdbus,86u 12257,Chrome_Ch,4u
mozStorag 21535 22254  yamato  100u     unix 0xf...       0t0     449389 type=STREAM pino=456453 3614,Xorg,38u
mozStorag 21535 22254  yamato  105u     unix 0xf...       0t0     582613 type=STREAM pino=586261
obexd     22163        yamato    1u     unix 0xf...       0t0     361859 type=STREAM pino=365931
obexd     22163        yamato    2u     unix 0xf...       0t0     361860 type=STREAM pino=365934
obexd     22163        yamato    3u     unix 0xf...       0t0     361241 type=DGRAM pino=10028
obexd     22163        yamato    6u     unix 0xf...       0t0     361242 type=STREAM pino=361864 4249,dbus-daem,70u
    
por 05.08.2015 / 12:22