说明:1.这个脚本可能会导致内核panic,所以一定不要在生产环境中使用,切记!
2.这个脚本不具备通用性,如果你使用了lxc或者cgroup,获得的结果可能有误或者找不到
3.这个脚本只是提供了找到socket实例的方法,如果你想获取这个套接字的其他信息需要自己编写脚本,通过获得的socket实例来获取。插入的位置在脚本中已标注,可以直接插入代码或函数。如果你对SystemTap比较熟悉的话可以自己灵活地修改。
思路很简单,根据pid查找对应的task_struct结构实例,然后找到打开文件描述符表,查找fd对应的file结构实例。如果fd是套接字,socket结构实例保存在file结构的private_data成员中。在从private_data成员中获取数据前,会检查文件的操作是否指向socket_file_ops,但是该成员没有导出,所以脚本中没有这个校验,由使用者自己来保证指定的fd是套接字。
脚本如下所示:
%{
#include <linux/fdtable.h>
#include <linux/file.h>
#include <net/inet_connection_sock.h>
%}
%{
int err = 0, fput_needed = 0;
%}
function stap_fget_light(fd_param:long, pid_param:long)%{
int fd = STAP_ARG_fd_param;
struct file *file;
struct files_struct *files;
struct task_struct *p;
p = pid_task(find_pid_ns(STAP_ARG_pid_param,
current->nsproxy->pid_ns), PIDTYPE_PID);
if (!p) {
_stp_printf("Find task %d failed\n", STAP_ARG_pid_param);
STAP_RETVALUE = (long)NULL;
return;
}
files = p->files;
fput_needed = 0;
if (likely((atomic_read(&files->count)== 1))) {
file = fcheck_files(files, fd);
} else {
rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (atomic_long_inc_not_zero(&file->f_count))
fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
if (!file) {
_stp_printf("Task %d did not have fd %d\n" ,STAP_ARG_pid_param, STAP_ARG_fd_param);
}
STAP_RETVALUE = (long)file;
return;
%}
function stap_sockfd_lookup_light(file_param:long)%{
struct file *file = (typeof(file))STAP_ARG_file_param;
STAP_RETVALUE = (long)file->private_data;
return;
%}
global file_ptr
function get_socket_by_pid_and_fd()
{
sock_ptr = 0;
file_ptr = stap_fget_light($1, $2);
if (file_ptr) {
sock_ptr = stap_sockfd_lookup_light(file_ptr);
}
return sock_ptr;
}
function stap_fput_light(file_param:long)%{
struct file *file = (typeof(file))STAP_ARG_file_param;
if (file) {
fput_light(file, fput_needed);
}
%}
function verify_socket(socket_param:long)%{
struct socket *sock = (typeof(sock))STAP_ARG_socket_param;
struct sock *sk = sock->sk;
struct inet_sock *isk = inet_sk(sk);
_stp_printf("\n\nverify_socket: local IP: " NIPQUAD_FMT", port: %u\n", NIPQUAD(isk->saddr), isk->num);
%}
probe begin
{
printf("Start to get socket. fd: %d, process: %d\n", $1, $2);
sock_address = get_socket_by_pid_and_fd();
if (sock_address) {
/* You can remove printf and verify_socket, just for test */
printf("sock_address = %p\n", sock_address);
verify_socket(sock_address);
/* ******** Insert your code or function here ******** */
stap_fput_light(file_ptr);
}
exit();
}
#include <linux/fdtable.h>
#include <linux/file.h>
#include <net/inet_connection_sock.h>
%}
%{
int err = 0, fput_needed = 0;
%}
function stap_fget_light(fd_param:long, pid_param:long)%{
int fd = STAP_ARG_fd_param;
struct file *file;
struct files_struct *files;
struct task_struct *p;
p = pid_task(find_pid_ns(STAP_ARG_pid_param,
current->nsproxy->pid_ns), PIDTYPE_PID);
if (!p) {
_stp_printf("Find task %d failed\n", STAP_ARG_pid_param);
STAP_RETVALUE = (long)NULL;
return;
}
files = p->files;
fput_needed = 0;
if (likely((atomic_read(&files->count)== 1))) {
file = fcheck_files(files, fd);
} else {
rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (atomic_long_inc_not_zero(&file->f_count))
fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
if (!file) {
_stp_printf("Task %d did not have fd %d\n" ,STAP_ARG_pid_param, STAP_ARG_fd_param);
}
STAP_RETVALUE = (long)file;
return;
%}
function stap_sockfd_lookup_light(file_param:long)%{
struct file *file = (typeof(file))STAP_ARG_file_param;
STAP_RETVALUE = (long)file->private_data;
return;
%}
global file_ptr
function get_socket_by_pid_and_fd()
{
sock_ptr = 0;
file_ptr = stap_fget_light($1, $2);
if (file_ptr) {
sock_ptr = stap_sockfd_lookup_light(file_ptr);
}
return sock_ptr;
}
function stap_fput_light(file_param:long)%{
struct file *file = (typeof(file))STAP_ARG_file_param;
if (file) {
fput_light(file, fput_needed);
}
%}
function verify_socket(socket_param:long)%{
struct socket *sock = (typeof(sock))STAP_ARG_socket_param;
struct sock *sk = sock->sk;
struct inet_sock *isk = inet_sk(sk);
_stp_printf("\n\nverify_socket: local IP: " NIPQUAD_FMT", port: %u\n", NIPQUAD(isk->saddr), isk->num);
%}
probe begin
{
printf("Start to get socket. fd: %d, process: %d\n", $1, $2);
sock_address = get_socket_by_pid_and_fd();
if (sock_address) {
/* You can remove printf and verify_socket, just for test */
printf("sock_address = %p\n", sock_address);
verify_socket(sock_address);
/* ******** Insert your code or function here ******** */
stap_fput_light(file_ptr);
}
exit();
}
在上面的脚本中,verify_socket()函数是为了验证获得的结果,实际使用时可以去掉。将上面的脚本保存为get_socket_by_pid_and_fd.stp文件,使用方法如下所示(fd是文件描述符,pid是fd所属的进程的PID):
stap -g get_socket_by_pid_and_fd.stp fd pid
下面我们通过一个简单的实例,来了解一下具体的使用。这里以nginx为例,查找其监听套接字的地址,监听端口是80。首先获取nginx的pid和监听套接字的fd,过程如下所示:
[root@CentOS_102 ~]# lsof -iTCP -sTCP:LISTEN -P | grep ":80"
nginx 6770 root 6u IPv4 34649 0t0 TCP192.168.56.102:80 (LISTEN)
nginx 6771 nobody 6u IPv4 34649 0t0 TCP192.168.56.102:80 (LISTEN)
[root@CentOS_102 ~]# ps aux | grep -v grep | grep nginx
root 6770 0.0 0.2 23932 616 ? Ss 13:15 0:00 nginx: master process/usr/local/nginx-1.5.8/sbin/nginx
nobody 6771 0.0 0.3 24336 1180? S 13:15 0:00 nginx: worker process
[root@CentOS_102 ~]#
nginx 6770 root 6u IPv4 34649 0t0 TCP192.168.56.102:80 (LISTEN)
nginx 6771 nobody 6u IPv4 34649 0t0 TCP192.168.56.102:80 (LISTEN)
[root@CentOS_102 ~]# ps aux | grep -v grep | grep nginx
root 6770 0.0 0.2 23932 616 ? Ss 13:15 0:00 nginx: master process/usr/local/nginx-1.5.8/sbin/nginx
nobody 6771 0.0 0.3 24336 1180? S 13:15 0:00 nginx: worker process
[root@CentOS_102 ~]#
我们这里使用nginx的master进程中的fd,通过上面的两个命令,我们可以看到fd为6,pid是6770。下面执行我们的脚本,输出结果如下所示:
[root@CentOS_102 ~]# stap -g get_socket_by_pid_and_fd.stp 6 6770
Start to get socket. fd: 6, process: 6770
sock_address = 0xffff88001783fa00
verify_socket: local IP:192.168.56.102, port:80
Start to get socket. fd: 6, process: 6770
sock_address = 0xffff88001783fa00
verify_socket: local IP:192.168.56.102, port:80
测试机器的IP地址就是192.168.56.102,根据输出结果验证了这个脚本是可以工作的。