Lưu ý : Bây giờ tôi duy trì lsof
trình bao bọc kết hợp cả hai cách tiếp cận được mô tả ở đây và cũng thêm thông tin cho các đồng nghiệp của kết nối TCP loopback tại https://github.com/stephane-chazelas/misc-scripts/blob/master/lsofc
Linux-3.3 trở lên.
Trên Linux, vì phiên bản kernel 3.3 (và được cung cấp UNIX_DIAG
tính năng này được xây dựng trong kernel), có thể lấy được đồng đẳng của một ổ cắm miền unix nhất định (bao gồm cả socket) bằng cách sử dụng API dựa trên netlink mới .
lsof
kể từ phiên bản 4.89 có thể sử dụng API đó:
lsof +E -aUc Xorg
Sẽ liệt kê tất cả các socket miền Unix có một quá trình có tên bắt đầu bằng Xorg
một trong hai định dạng tương tự như:
Xorg 2777 root 56u unix 0xffff8802419a7c00 0t0 34036 @/tmp/.X11-unix/X0 type=STREAM ->INO=33273 4120,xterm,3u
Nếu phiên bản của bạn lsof
quá cũ, có một vài lựa chọn khác.
Các ss
tiện ích (từ iproute2
) tận dụng cùng API để lấy và hiển thị thông tin về danh sách các ổ cắm miền unix trên hệ thống bao gồm thông tin ngang hàng.
Các ổ cắm được xác định bởi số inode của chúng . Lưu ý rằng nó không liên quan đến inode hệ thống tập tin của tệp socket.
Ví dụ:
$ ss -x
[...]
u_str ESTAB 0 0 @/tmp/.X11-unix/X0 3435997 * 3435996
nó nói rằng ổ cắm 3435997 (được gắn với ổ cắm ABSTRACT /tmp/.X11-unix/X0
) được kết nối với ổ cắm 3435996. -p
Tùy chọn này có thể cho bạn biết quá trình nào mở ổ cắm đó. Nó thực hiện điều đó bằng cách thực hiện một số readlink
s trên /proc/$pid/fd/*
, vì vậy nó chỉ có thể làm điều đó vào quá trình bạn sở hữu (trừ khi bạn root
). Ví dụ ở đây:
$ 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]
Để tìm hiểu quá trình nào có 3435996, bạn có thể tra cứu mục nhập riêng của nó trong đầu ra của ss -xp
:
$ ss -xp | awk '$6 == 3435996'
u_str ESTAB 0 0 * 3435996 * 3435997 users:(("xterm",pid=29215,fd=3))
Bạn cũng có thể sử dụng tập lệnh này như một trình bao bọc xung quanh lsof
để dễ dàng hiển thị thông tin liên quan ở đó:
#! /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);
Ví dụ:
$ sudo that-lsof-Wrapper -ad3 -p 29215
NGƯỜI DÙNG ĐIỀU KHIỂN NGƯỜI DÙNG FD LOẠI THIẾT BỊ THIẾT BỊ / TẮT NODE
xterm 29215 stephane 3u unix 0xffff8800a07da4c0 0t0 3435996 loại = STREAM <-> 3435997 [Xorg, 3080, @ / tmp / .X11-unix / X0]
Trước linux-3.3
API Linux cũ để truy xuất thông tin ổ cắm unix là thông qua /proc/net/unix
tệp văn bản. Nó liệt kê tất cả các socket miền Unix (bao gồm cả socketpairs). Trường đầu tiên trong đó (nếu không bị ẩn đối với những người không phải là siêu nhân với kernel.kptr_restrict
tham số sysctl) như đã giải thích bởi @Totor chứa địa chỉ kernel của unix_sock
cấu trúc có chứa một peer
trường trỏ đến ngang hàng tương ứng unix_sock
. Đó cũng là những gì lsof
đầu ra cho DEVICE
cột trên ổ cắm Unix.
Bây giờ nhận được giá trị của peer
trường đó có nghĩa là có thể đọc bộ nhớ kernel và biết phần bù của peer
trường đó liên quan đến unix_sock
địa chỉ.
Một số gdb
dựa trên và systemtap
dựa trên các giải pháp đã được đưa ra nhưng họ yêu cầu gdb
/ systemtap
và biểu tượng Linux kernel debug cho kernel chạy được cài đặt mà nói chung là không phải như vậy trên các hệ thống sản xuất.
Mã hóa phần bù không thực sự là một tùy chọn vì nó thay đổi theo phiên bản kernel.
Bây giờ chúng ta có thể sử dụng cách tiếp cận heuristic để xác định phần bù: yêu cầu công cụ của chúng ta tạo một hình nộm socketpair
(sau đó chúng ta biết địa chỉ của cả hai đồng nghiệp) và tìm kiếm địa chỉ của đồng đẳng xung quanh bộ nhớ ở đầu kia để xác định phần bù.
Dưới đây là một kịch bản bằng chứng khái niệm chỉ sử dụng perl
(đã thử nghiệm thành công với kernel 2.4.27 và 2.6.32 trên i386 và 3.13 và 3.16 trên amd64). Giống như ở trên, nó hoạt động như một trình bao bọc xung quanh lsof
:
Ví dụ:
$ that-lsof-Wrapper -aUc nm-applet
NGƯỜI DÙNG ĐIỀU KHIỂN NGƯỜI DÙNG FD LOẠI THIẾT BỊ THIẾT BỊ / TẮT NODE
nm-applet 4183 stephane 4u unix 0xffff8800a055eb40 0t0 36.888 type = STREAM -> 0xffff8800a055e7c0 [dbus-daemon, 4190, @ / tmp / dbus-AiBCXOnuP6]
nm-applet 4183 stephane 7U unix 0xffff8800a055e440 0t0 36.890 type = STREAM -> 0xffff8800a055e0c0 [Xorg, 3080, @ / tmp / .X11-unix / X0]
nm-applet 4183 stephane 8u unix 0xffff8800a05c1040 0t0 36.201 type = STREAM -> 0xffff8800a05c13c0 [dbus-daemon, 4118, @ / tmp / dbus-yxxNr1NkYC]
nm-applet 4183 11u stephane unix 0xffff8800a055d080 0t0 36.219 type = STREAM -> 0xffff8800a055d400 [dbus-daemon, 4118, @ / tmp / dbus-yxxNr1NkYC]
nm-applet 4183 stephane 12u unix 0xffff88022e0dfb80 0t0 36.221 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]
Đây là kịch bản:
#! /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);