天天看點

linux socket pairAF_UNIXserver.plclient.pl通過lsof netstat 查找用戶端連接配接的socket檔案通過ss指令或列印peer值

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檔案以及服務端程序, 友善定位問題。

繼續閱讀