SystemTap----通过pid和fd来获取对应的socket实例地址

时间:2020-12-14 22:25:45
  说明: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();
}
  在上面的脚本中, 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  TCP 192. 168. 56. 102 : 80 (LISTEN)
nginx     6771  nobody     6u  IPv4   34649      0t0  TCP 192. 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
  测试机器的IP地址就是192.168.56.102,根据输出结果验证了这个脚本是可以工作的。