AF_UNIX
AF_UNIX 用于本地,通過socket檔案通信 , 不用經過cpu對包解析,放到網卡, 核心直接放到對應的socket緩沖檔案。如果用戶端與服務端通過socket檔案通信,那通過netstat指令, 能找到用戶端與服務端的連接配接關系嗎?
請看測試執行個體:
server.pl
#! /usr/bin/perl -w
use strict;
use IO::Socket::UNIX qw( SOCK_STREAM SOMAXCONN );
my $SOCK_PATH = '/tmp/test.sock';
unlink($SOCK_PATH) if -e $SOCK_PATH;
my $server = IO::Socket::UNIX->new(
Type => SOCK_STREAM(),
Local => $SOCK_PATH,
Listen => SOMAXCONN,
)or die("Can't create server socket: $!\n");
while (1) {
my $connection = $server->accept;
if (fork() == 0) {
print "** New connection received **\n";
$connection->autoflush(1);
my $count = 1;
while (my $line = <$connection>) {
if ($line){
chomp($line);
$connection->print($count . ' -> ' . $line . "\n");
print "Received and replied to $count '$line'\n";
$count++;
}
}
close $connection;
exit;
}
}
client.pl
#!/usr/bin/perl -w
use strict;
use IO::Socket::UNIX qw( SOCK_STREAM );
my $SOCK_PATH = '/tmp/test.sock';
my $client = IO::Socket::UNIX->new(
Type => SOCK_STREAM(),
Peer => $SOCK_PATH
)or die("Can't connect to server: $!\n");
$client->autoflush(1);
## Listen for replies
if (fork() == 0) {
while (my $line = <$client>) {
if ($line){
chomp($line);
print("Recv: '" . $line . "'\n");
}
}
}
## Send something
while(1){
for my $itm ('Alpha','Beta','Gamma','Delta'){
print("Send: " . $itm . "\n");
print($client $itm . "\n") or warn("Can't send: $!\n"); # send to server, \n terminates
}
sleep 5
}
print "** Client Finished **\n";
通過lsof netstat 查找用戶端連接配接的socket檔案
檢視程序id
[[email protected] ~]# ps auxf | grep server.pl
root 19083 0.0 0.1 135592 4324 pts/1 S+ 19:18 0:00 | \_ perl server.pl
root 19092 0.0 0.1 135592 2700 pts/1 S+ 19:18 0:00 | \_ perl server.pl
root 19157 0.0 0.0 112708 984 pts/3 S+ 19:18 0:00 \_ grep --color=auto server.pl
[[email protected] ~]# ps auxf | grep client.pl
root 19090 0.0 0.1 135592 4308 pts/2 S+ 19:18 0:00 | \_ perl client.pl
root 19091 0.0 0.0 135592 2344 pts/2 S+ 19:18 0:00 | \_ perl client.pl
root 19211 0.0 0.0 112708 984 pts/3 S+ 19:19 0:00 | \_ grep --color=auto client.pl
檢視服務端檔案占用
[[email protected] ~]# lsof -p 19092 | grep sock
perl 19092 root 3u unix 0xffff93dbd27ba400 0t0 70144 /tmp/test.sock
perl 19092 root 4u unix 0xffff93dbd1ee5800 0t0 70145 /tmp/test.sock
[[email protected] ~]# netstat -anlp | grep 19092
unix 3 [ ] STREAM CONNECTED 70145 19092/perl /tmp/test.sock
檢視用戶端檔案占用
[[email protected] ~]# lsof -p 19090 |grep sock
perl 19090 root 3u unix 0xffff93dbd1ee3c00 0t0 70162 socket
[[email protected] ~]# lsof -p 19091 |grep sock
perl 19091 root 3u unix 0xffff93dbd1ee3c00 0t0 70162 socket
[[email protected] ~]# netstat -anlp | grep 19090
unix 3 [ ] STREAM CONNECTED 70162 19090/perl
[[email protected] ~]# netstat -anlp | grep 19091
[[email protected] ~]#
[[email protected] ~]# ll /proc/19090/fd/
total 0
lrwx------. 1 root root 64 Apr 27 19:21 0 -> /dev/pts/2
lrwx------. 1 root root 64 Apr 27 19:21 1 -> /dev/pts/2
lrwx------. 1 root root 64 Apr 27 19:18 2 -> /dev/pts/2
lrwx------. 1 root root 64 Apr 27 19:21 3 -> socket:[70162]
通過以上幾種方法檢視用戶端的情況, 我們隻知道它有建立一個socket連接配接,但是具體連接配接到那個socket檔案,并不知道。那根據lsof 輸出的 device=0xffff93dbd1ee3c00, 以及node=70162, 能找到對應的socket檔案嗎?
[[email protected] ~]# stat /tmp/test.sock
File: ‘/tmp/test.sock’
Size: 0 Blocks: 0 IO Block: 4096 socket
Device: fd00h/64768d Inode: 67785956 Links: 1
Access: (0755/srwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2020-04-27 19:18:33.922022962 +0800
Modify: 2020-04-27 19:18:28.245022727 +0800
Change: 2020-04-27 19:18:28.245022727 +0800
Birth: -
[[email protected] ~]#
發現實際 /tmp/test.sock 的inode是67785956 顯然這倆不是一個概念。
繼續看,0xffff93dbd1ee3c00應該是一個記憶體位址,那我們在proc下找找。
[[email protected] ~]# grep -Irn "ffff93dbd1ee3c0" /proc/net
/proc/net/unix:80:ffff93dbd1ee3c00: 00000003 00000000 00000000 0001 03 70162
也看不出具體是那個檔案(但對于如tcp連接配接,其實在/proc/net/tcp中是可以找到連接配接的 ip:port 資訊的)
那要怎麼才能找對對應連接配接的socket檔案呢,
最後還是在外文上找到了資料,https://unix.stackexchange.com/questions/16300/whos-got-the-other-end-of-this-unix-socketpair/190606#190606
通過ss指令或列印peer值
ss指令
對于核心大于3.3, 可以使用ss指令
[[email protected] ~]# ss | grep 70162
u_str ESTAB 0 0 * 70162 * 70145
u_str ESTAB 0 0 /tmp/test.sock 70145 * 70162
[[email protected] ~]#
70162在ss指令種,表示Port, 在netstat種又表示 I-Node, 有什麼不一樣嗎?
列印peer記憶體位址
對于沒有ss指令的環境,則可以從上述的記憶體位址找到答案。即 0xffff93dbd1ee3c00
利用之前一篇部落格搭建的核心調試環境,參見: https://www.jianshu.com/p/caff00d28b5e
這次我們使用kcore來檢視核心的目前運作情況
gdb /usr/lib/debug/lib/modules/3.10.0-957.el7.x86_64/vmlinux /proc/kcore
Core was generated by `BOOT_IMAGE=/vmlinuz-3.10.0-957.el7.x86_64 root=/dev/mapper/centos-root ro crashk'.
#0 0x0000000000000000 in irq_stack_union ()
(gdb) print ((struct unix_sock*) 0xffff93dbd1ee3c00)->peer
$1 = (struct sock *) 0xffff93dbd1ee5800
(gdb) quit
[[email protected] ~]# lsof | grep 0xffff93dbd1ee5800
perl 19092 root 4u unix 0xffff93dbd1ee5800 0t0 70145 /tmp/test.sock
[[email protected] ~]#
Linux核心關于unix_sock的定義
/* The AF_UNIX socket */
struct unix_sock {
/* WARNING: sk has to be the first member */
struct sock sk;
struct unix_address *addr;
struct path path;
struct mutex readlock;
struct sock *peer;
struct list_head link;
atomic_long_t inflight;
spinlock_t lock;
unsigned char recursion_level;
unsigned long gc_flags;
#define UNIX_GC_CANDIDATE 0
#define UNIX_GC_MAYBE_CYCLE 1
struct socket_wq peer_wq;
wait_queue_t peer_wake;
};
與我們的用戶端代碼是一緻的
my $client = IO::Socket::UNIX->new(
Type => SOCK_STREAM(),
Peer => $SOCK_PATH
)or die("Can't connect to server: $!\n");
該方法即将peer的記憶體位址列印出來,對應的就是path資訊了。 有了這些排查方法,我們就能找到client連接配接的是哪個socket檔案以及服務端程序, 友善定位問題。