首页 > 系统相关 >Linux内核bind系统调用源码分析

Linux内核bind系统调用源码分析

时间:2024-01-01 11:11:07浏览次数:42  
标签:addr bind sock sk 源码 Linux inet struct

一、环境说明

内核版本:Linux 3.10

内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)

二、应用层-bind()函数

将socket 套接字绑定指定的地址:

/* 
 * sockfd:由socket函数返回的套接口描述符
 * sockaddr:一个指向特定于协议的地址结构的指针
 * socklen_t:该地址结构的长度
 * return:若成功则为0,若出错则为-1
 */
#include <sys/socket.h>
struct sockaddr_in sock_addr;
memset(&sock_addr,0,sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sock_addr.sin_port = htons(SERVER_PORT);
// int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
err = bind(sockfd,(struct sockaddr*)(&sock_addr),sizeof(sock_addr));

三、BSD Socket层-sys_socketcall()函数

网络栈专用操作函数集的总入口函数,主要是将请求分配,调用具体的底层函数进行处理:

// file: net/socket.c
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
{
    ......
    switch (call) {
    case SYS_SOCKET:
        err = sys_socket(a0, a1, a[2]);
        break;
    case SYS_BIND:
        err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
        break;
    case SYS_CONNECT:
        err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
        break;
    case SYS_LISTEN:
        err = sys_listen(a0, a1);
        break;
    case SYS_ACCEPT:
        err = sys_accept4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0);
        break;
    ......

    }
    return err;
}

 四、sys_bind()函数

// file: net/socket.c
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
    struct socket *sock;
    struct sockaddr_storage address;
    int err, fput_needed;

    sock = sockfd_lookup_light(fd, &err, &fput_needed); //获取fd对应的socket结构
    if (sock) {
        err = move_addr_to_kernel(umyaddr, addrlen, &address); //将地址从用户缓冲区复制到内核缓冲区,umyaddr->address
        if (err >= 0) {
            err = security_socket_bind(sock,(struct sockaddr *)&address, addrlen); //SElinux相关,跳过
            if (!err)
                err = sock->ops->bind(sock,(struct sockaddr *) &address, addrlen); //调用bind函数
        }
        fput_light(sock->file, fput_needed);
    }
    return err;
}

在上一篇文章中(socket系统调用分析),我们分析了sock->ops = answer->ops,而answer对应的结构:

// file: net/ipv4/af_inet.c
static struct inet_protosw inetsw_array[] =
{
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .no_check =   0,
        .flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
    },
    ......
}
// file: net/ipv4/af_inet.c
const struct proto_ops inet_stream_ops = {
    .family           = PF_INET,
    .owner           = THIS_MODULE,
    .release       = inet_release,
    .bind           = inet_bind,
    .connect       = inet_stream_connect,
    .socketpair       = sock_no_socketpair,
    .accept           = inet_accept,
    .getname       = inet_getname,
    .poll           = tcp_poll,
    .ioctl           = inet_ioctl,
    .listen           = inet_listen,
    ......
};

因此,sock->ops->bind最终调用的是inet_bind函数。

五、inet_bind()函数

inet_bind()函数的主要工作:
调用具体协议的bind函数;
校验端口是否冲突,是否可绑定
把传入的struct sockaddr赋值给struct inet_sock(四元组确定了源IP、源端口);

// file: net/ipv4/af_inet.c
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
    struct sock *sk = sock->sk;
    struct inet_sock *inet = inet_sk(sk);
    struct net *net = sock_net(sk);
    unsigned short snum;
    int chk_addr_ret;
    int err;

    /* If the socket has its own bind function then use it. (RAW) */
    if (sk->sk_prot->bind) { 
        err = sk->sk_prot->bind(sk, uaddr, addr_len);
        goto out;
    }
    err = -EINVAL;
    if (addr_len < sizeof(struct sockaddr_in))
        goto out;

    if (addr->sin_family != AF_INET) {
        /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
         * only if s_addr is INADDR_ANY.
         */
        err = -EAFNOSUPPORT;
        if (addr->sin_family != AF_UNSPEC ||
            addr->sin_addr.s_addr != htonl(INADDR_ANY))
            goto out;
    }

    chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);

    /* Not specified by any standard per-se, however it breaks too
     * many applications when removed.  It is unfortunate since
     * allowing applications to make a non-local bind solves
     * several problems with systems using dynamic addressing.
     * (ie. your servers still start up even if your ISDN link
     *  is temporarily down)
     */
    err = -EADDRNOTAVAIL;
    if (!sysctl_ip_nonlocal_bind &&
        !(inet->freebind || inet->transparent) &&
        addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
        chk_addr_ret != RTN_LOCAL &&
        chk_addr_ret != RTN_MULTICAST &&
        chk_addr_ret != RTN_BROADCAST)
        goto out;

    snum = ntohs(addr->sin_port);
    err = -EACCES;
    if (snum && snum < PROT_SOCK &&
        !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
        goto out;

    /*      We keep a pair of addresses. rcv_saddr is the one
     *      used by hash lookups, and saddr is used for transmit.
     *
     *      In the BSD API these are the same except where it
     *      would be illegal to use them (multicast/broadcast) in
     *      which case the sending device address is used.
     */
    lock_sock(sk);

    /* Check these errors (active socket, double bind). */
    err = -EINVAL;
    if (sk->sk_state != TCP_CLOSE || inet->inet_num) //bind时,state为TCP_CLOSE
        goto out_release_sock;

    inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; //地址绑定
    if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
        inet->inet_saddr = 0;  /* Use device */

    /* Make sure we are allowed to bind here. */
    if (sk->sk_prot->get_port(sk, snum)) { //校验端口是否冲突,是否可绑定
        inet->inet_saddr = inet->inet_rcv_saddr = 0;
        err = -EADDRINUSE;
        goto out_release_sock;
    }

    if (inet->inet_rcv_saddr)
        sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
    if (snum)
        sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
    inet->inet_sport = htons(inet->inet_num); //端口绑定
    inet->inet_daddr = 0; //目标地址置为0
    inet->inet_dport = 0; //目标ip地址置为0
    sk_dst_reset(sk);
    err = 0;
out_release_sock:
    release_sock(sk);
out:
    return err;
}

在上一篇文章中(socket系统调用分析),我们分析了sk->sk_prot = answer->prot,而answer对应的结构:

// file: net/ipv4/af_inet.c
static struct inet_protosw inetsw_array[] =
{
    {
        .type =       SOCK_STREAM,
        .protocol =   IPPROTO_TCP,
        .prot =       &tcp_prot,
        .ops =        &inet_stream_ops,
        .no_check =   0,
        .flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
    },
    ......
}
// file: net/ipv4/tcp_ipv4.c
struct proto tcp_prot = {
    .name            = "TCP",
    .owner            = THIS_MODULE,
    .close            = tcp_close,
    .connect        = tcp_v4_connect,
    .disconnect        = tcp_disconnect,
    .accept            = inet_csk_accept,
    .ioctl            = tcp_ioctl,
    .init            = tcp_v4_init_sock,
    .get_port        = inet_csk_get_port,
    ......
}

针对,tcp_prot未设置bind函数,所以sk->sk_prot->bind为false,跳过执行下面的代码。

sk->sk_prot->get_port对应的则是inet_csk_get_port()函数,inet_csk_get_port()主要是校验端口是否冲突,是否可绑定。它里面的inet_get_local_port_range()函数,会获取内核设置的端口号范围(对应内核参数/proc/sys/net/ipv4/ip_local_port_range)

六、bind代码流程图

 

标签:addr,bind,sock,sk,源码,Linux,inet,struct
From: https://www.cnblogs.com/573583868wuy/p/17938512

相关文章

  • AlmaLinux 8.8 发布 - RHEL 下游免费发行版(CentOS 稳定版的替代品)
    AlmaLinux8.8发布-RHEL下游免费发行版(CentOS稳定版的替代品)AlmaLinuxOS是一个开源、社区驱动的项目,旨在提供CentOS稳定版的替代品。作者主页:sysin.org2023.05.18,AlmaLinux8.8StableNowAvailable!关于新增功能可以参考:RedHatEnterpriseLinux(RHEL)8.8(x86_64,a......
  • Cisco AnyConnect Secure Mobility Client 4.10.07061 (macOS, Linux, Windows)
    CiscoAnyConnectSecureMobilityClient4.10.07061(macOS,Linux,Windows)CiscoSecureClient(包括AnyConnect)作者主页:sysin.org新版已发布:CiscoSecureClient5.0.02075(macOS,Linux,Windows&iOS,Andrord)CiscoSecureClient(includingAnyConnect)思科安全客户端(......
  • Rocky Linux 9 x86_64 OVF (sysin)
    RockyLinux9x86_64OVF(sysin)以社区方式驱动的企业Linux作者主页:sysin.orgRockyLinux9.0(5.14.0-70.13.1.el9_0.x86_64)以社区方式驱动的企业LinuxRockyLinux是一个开源的企业级操作系统,旨在与RedHatEnterpriseLinux®100%1:1兼容。它正处于社区的密集开发中。......
  • Red Hat Enterprise Linux (RHEL) 9.2 (x86_64, aarch64) - 红帽企业 Linux 9.2 发布
    RedHatEnterpriseLinux(RHEL)9.2(x86_64,aarch64)红帽企业Linux9.2作者主页:sysin.org红帽企业Linux9红帽企业Linux9.2来了红帽2023-05-1112:10发表于北京红帽企业Linux9.2和8.8推动混合云中更大规模的Linux自动化,有助于弥合IT技能差距,确保运营一致性,扩......
  • Linux 挂载 Windows 共享文件夹 cifs
    Linux挂载Windows共享文件夹cifs一台Windows系统(192.168.1.10),创建一个共享文件夹share。Windows客户端访问路径就是:\\192.168.1.10\shareLinux下挂载方法如下(CentOS7):1).需要安装cifs-utils软件包yuminstall-ycifs-utils2).不管是开机自动挂载还是手动挂载,我们都首先在/mn......
  • 【B/S架构】医院不良事件报告系统源码
    医院不良事件报告系统为医院内质量控制、患者安全关注、医疗安全不良事件方面的精细化的管理提供了平台,是等级医院评审的必备内容,评审要求医院直报系统要与卫生部“医疗安全(不良)事件报告系统”建立网络对接。不良事件报告系统源码包括护理相关事件、医疗相关事件、药件相关事件......
  • SaaS版Java基层健康卫生云HIS信息管理平台源码
    云his系统源码,系统采用主流成熟技术开发,B/S架构,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问,前后端分离,多服务协同,服务可拆分,功能易扩展。多集团统一登录患者主索引建立、主数据管理,统一对外接口管理。1.系统管理及基础数据:科室、病区、人员及其岗位、权限管理,各种基础资料......
  • 安卓期末大作业Android studio-记单词app(资源链接在文末,含注册登录,含设计报告,含导出ap
    安卓期末大作业Androidstudio-记单词app(注册登录、增删改查)1、注册登录界面2.用户点击加号按钮输入单词,键盘会自动弹出并焦点在输入框,用户输入英文和中文释义,单词本上将在最上面显示这个单词,并会有下拉动画。用户可以通过上方放回箭头取消输入。2.用户可以通过单词盘边的按钮来关......
  • 安卓期末大作业-调色App(源码+导出apk+运行截图)
    安卓期末大作业-调色App(源码+导出apk+运行截图)开发软件:AndroidStudio开发语言:Java2023年上半年移动开发期末大作业,比较简单的一个安卓项目,导入即可使用,适合初学者学习使用app进入界面:调色卡app主界面:可以复制颜色代码App主要代码展示:publicclassColorDetailextendsAppCompatAc......
  • 将linux的manpages换成中文
    一种解决方法:https://blog.csdn.net/qq_23274715/article/details/104710448具体方法ubuntu:1、安装软件包sudoaptupdatesudoaptinstallmanpages-zh.使用此命令安装中文manpages库。2、查看中文包的安装路径dpkg-Lmanpages-zh使用此命令查看manpages-zh库......