• Socket层实现系列 — getsockname()和getpeername()的实现


    本文主要介绍了getsockname()和getpeername()的内核实现。

    内核版本:3.6

    Author:zhangskd @ csdn blog

    应用层

    int getsockname(int s, struct sockaddr *name, socklen_t *namelen);

    Get the current name for the specified socket.

    获取本地套接口的名字,包括它的IP和端口。

    int getpeername(int s, struct sockaddr *name, socklen_t *namelen);

    Get the name of connected peer socket.

    获取远程套接口的名字,包括它的IP和端口。

    getsockname()在指定的套接口绑定地址和端口后才能调用,即服务器在bind()后可调用,

    客户端在bind()或connect()之后可调用。getpeername()在连接建立之后才可调用。

    系统调用

    getsockname()和getpeername()是由glibc提供的,声明位于include/sys/socket.h中,实现位于

    sysdeps/mach/hurd/getsockname.c和sysdeps/mach/hurd/getpeername.c中。它们主要是用于从用户空间

    进入名为sys_socketcall的系统调用,并传递参数。sys_socketcall()实际上是所有socket函数进入内核空间

    的共同入口。

    在sys_socketcall()中会调用sys_getsockname()和sys_getpeername()。

    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
    {
        ...
        switch(call) {
            case SYS_GETSOCKNAME:
                err = sys_getsockname(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
                break;
    
            case SYS_GETPEERNAME:
                err = sys_getpeername(a0, (struct sockaddr __user *)a1, (int __user *)a[2]);
                break;
            ...
        }
        return err;
    }
    

    经过了Socket层的总入口sys_socketcall(),现在进入sys_getsockname()。

    /* Get the local address ('name') of a socket object.
     * Move the obtained name to user space.
     */
    SYSCALL_DEFINE3(getsockname, int, fd, struct sockaddr __user *, usockaddr, int __user *, usockaddr_len)
    {
        struct socket *sock;
        struct sockaddr_storage address;
        int len, err, fput_needed;
    
        /* 通过文件描述符fd,找到对应的socket。
         * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
         * 然后从file实例的private_data成员中获取socket实例。
         */
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
        if (! sock)
            goto out;
    
        err = security_socket_getsockname(sock); /* SELinux相关 */
        if (err)
            goto out_put;
    
        /* SOCKET层的操作函数,如果是SOCK_STREAM,proto_ops为inet_stream_ops,
         * 接下来调用inet_getname()。
         */
        err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 0);
        if (err)
            goto out_put;
    
        /* 把内核空间的socket地址复制到用户空间 */
        err = move_addr_to_user(&address, len, usockaddr, usockaddr_len);
    
    out_put:
        fput_light(sock->file, fput_needed);
    out:
        return err;
    }
    static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen, void __user *uaddr, int __user *ulen)
    {
        int err;
        int len;
    
        /* 把用户空间的地址长度保存到len */
        err = get_user(len, ulen);
        if (err)
            return err;
    
        if (len > klen)
            len = klen;
    
        if (len < 0 || len > sizeof(struct sockaddr_storage))
            return -EINVAL;
    
        if (len) {
            if (audit_sockaddr(klen, kaddr))
                return -ENOMEM;
    
            if (copy_to_user(uaddr, kaddr, len)) /* 拷贝到用户空间 */
                return -EFAULT;
        }
    
        return __put_user(klen, ulen); /* 保存socket地址长度到用户空间 */
    }
    

    sys_getpeername()和sys_getsockname()差不多。

    /* Get the remote address ('name') of a socket object.
     * Move the obtained name to user space.
     */
    SYSCALL_DEFINE3(getpeername, int, fd, struct sockaddr __user *, usockaddr, int __user *, usockaddr_len)
    {
        struct socket *sock;
        struct sockaddr_storage address;
        int len, err, fput_needed;
    
        /* 通过文件描述符fd,找到对应的socket。
         * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
         * 然后从file实例的private_data成员中获取socket实例。
         */
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
    
        if (sock != NULL) {
            err = security_socket_getpeername(sock);
            if (err) {
                fput_light(sock->file, fput_needed);
                return err;
            }
    
            /* SOCKET层的操作函数,如果是SOCK_STREAM,proto_ops为inet_stream_ops,
             * 接下来调用inet_getname()。
             */
            err = sock->ops->getname(sock, (struct sockaddr *)&address, &len, 1);
    
            if (! err)    /* 把内核空间的socket地址复制到用户空间 */
                err = move_addr_to_user(&address, len, usockaddr, usockaddr_len);
    
            fput_light(sock->file, fput_needed);
        }
        return err;
    }
    

    Socket层

    SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops。

    getsockname()和getpeername()的socket层操作函数为inet_getname()。

    const struct proto_ops inet_stream_ops = {
        .family = PF_INET,
        .owner = THIS_MODULE,
        ...
        .getname = inet_getname,
        ...
    };

    如果是getsockname(),则peer为0。是getpeername(),则peer为1。

    /* This does both peername and sockname. */
    int inet_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer)
    {
        struct sock *sk = sock->sk;
        struct inet_sock *inet = inet_sk(sk);
        DECLARE_SOCKADDR(struct sockaddr_in *, sin, uaddr);
    
        sin->sin_family = AF_INET;
        if (peer) { /* 如果是getpeername,要求连接已经建立 */
            if (! inet->inet_dport || (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_SYN_SENT)) &&
                peer == 1))
                return -ENOTCONN; /* Transport endpoint is not connected */
    
            sin->sin_port = inet->inet_dport; /* 获取对端端口 */
            sin->sin_addr.s_addr = inet->inet_daddr; /* 获取对端IP */
    
        } else {
            __be32 addr = inet->inet_rcv_saddr;
            if (! addr)
                addr = inet->inet_saddr;
    
            sin->sin_port = inet->inet_sport; /* 获取本端端口 */
            sin->sin_addr.s_addr = addr; /* 获取本端IP */
        }
    
        memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
        *uaddr_len = sizeof(*sin);
        return 0;
    }
    
    #define DECLARE_SOCKADDR(type, dst, src) 
        type dst = ({ __sockaddr_check_size(sizeof(*dst)); (type) src; })
    
    #define __sockaddr_check_size(size) 
        BUILD_BUG_ON(((size) > sizeof(struct __kernel_sockaddr_storage)))
    
  • 相关阅读:
    centos安装配置jdk
    java封装数据类型——Byte
    centos7安装mysql8
    centos安装redis
    centos源码安装nginx
    Linux查看系统及版本信息
    sqlyog无操作一段时间后重新操作会卡死问题
    mysql8中查询语句表别名不能使用 “of”
    一次腾讯云centos服务器被入侵的处理
    java封装数据类型——Long
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333320.html
Copyright © 2020-2023  润新知