RTNETLINK内核与用户空间网络子系统交互机制

主要涉及net/netlink/af_netlink.c与net/core/rtnetlink.c两个主文件。内核的网络子系统定义了rtnetlink,用做和用户空间的交互,rtnetlink为AF_NETLINK协议的一个类别NETLINK_ROUTE,其它类别包括NETLINK_XFRM、NETLINK_GENERIC等。renetlink主要注册了LINK、ROUTE、ADDRESS、NEIGHOUR等相关的操作。

本文以PF_UNSPEC协议族的RTM_GETLINK为例进行介绍,首先注册rtnetlink内核套接口,主要是注册一个接收函数挂载到AF_NETLINK的处理流程中:

  1.  
    static int __net_init rtnetlink_net_init(struct net *net)
  2.  
    {
  3.  
    struct netlink_kernel_cfg cfg = {
  4.  
    .input = rtnetlink_rcv,
  5.  
    };
  6.  
    sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
  7.  
    }

其次,注册三个回调函数在全局的数组rtnl_msg_handlers上,其结构如下:

  1.  
    struct rtnl_link {
  2.  
    rtnl_doit_func doit;
  3.  
    rtnl_dumpit_func dumpit;
  4.  
    rtnl_calcit_func calcit;
  5.  
    } *rtnl_msg_handlers[RTNL_FAMILY_MAX + 1];

PF_UNSPEC协议组的RTM_GETLINK类型注册了以下三个操作:

  1.  
    void rtnl_register(int protocol, int msgtype,
  2.  
               rtnl_doit_func doit, rtnl_dumpit_func dumpit, rtnl_calcit_func calcit)
  3.  
      
  4.  
    rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, rtnl_dump_ifinfo, rtnl_calcit);

其中,doit函数rtnl_getlink主要获取指定设备的接口信息,dumpit函数rtnl_dump_ifinfo获取全部接口的信息,calcit函数rtnl_calcit配合dumpit函数使用,计算出一个设备的接口信息所占用的空间(if_nlmsg_size()),用来控制分配返回数据的存储空间最小值。

用户空间应用获取全部接口信息时,需要在下发的nlmsghdr结构体成员nlmsg_flags变量中增加NLM_F_DUMP标志:

  1.  
    struct nlmsghdr nlh;
  2.  
     
  3.  
    nlh.nlmsg_type = RTM_GETLINK;
  4.  
    nlh.nlmsg_flags = NLM_F_DUMP|NLM_F_REQUEST;

用户空间应用调用sendmsg发送rtnetlink消息,相应的内核处理函数为netlink_sendmsg,如下所示其最终调用的函数为rtnetlink初始化时注册的rtnetlink_rcv,准备返回用户的数据:

rtnetlink_rcv主要功能在函数rtnetlink_rcv_msg中实现,判断NLM_F_DUMP标志,选择执行doit或者dumpit函数:

  1.  
    static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
  2.  
    {
  3.  
    if (kind == 2 && nlh->nlmsg_flags&NLM_F_DUMP) {
  4.  
    {
  5.  
    struct netlink_dump_control c = {
  6.  
    .dump = dumpit,
  7.  
    .min_dump_alloc = min_dump_alloc,
  8.  
    };
  9.  
    err = netlink_dump_start(rtnl, skb, nlh, &c);
  10.  
    }
  11.  
    rtnl_lock();
  12.  
    return err;
  13.  
    }
  14.  
     
  15.  
    doit = rtnl_get_doit(family, type);
  16.  
    if (doit == NULL)
  17.  
    return -EOPNOTSUPP;
  18.  
     
  19.  
    return doit(skb, nlh);
  20.  
    }

doit函数即rtnl_getlink(),获取指定设备的链路信息,存储于sk_buff中,调用netlink_sendskb(),挂载到相应sock的sk->sk_receive_queue,等待用户空间应用调用recvmsg()返回。

dumpit函数即rtnl_dump_ifinfo(),获取指定命名空间的全部接口信息,数据量存在过大情况,分为多次传输完成。在netlink_dump_start函数中初始化传输参数,netlink_sock结构体成员cb_running标示传输开始,cb->args用作记录当前传输的位置,初始化时清零。netlink_dump获取第一次传输数据,挂载到sk_receive_queue,并且更新当前传输位置cb->args:

  1.  
    int __netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
  2.  
    const struct nlmsghdr *nlh,
  3.  
    struct netlink_dump_control *control)
  4.  
    {
  5.  
    struct netlink_callback *cb;
  6.  
    struct netlink_sock *nlk;
  7.  
     
  8.  
    cb = &nlk->cb;
  9.  
    memset(cb, 0, sizeof(*cb));
  10.  
     
  11.  
    nlk->cb_running = true;
  12.  
        ret = netlink_dump(sk);
  13.  
    }

用户空间应用调用recvmsg获取数据,相应的内核处理函数为netlink_recvmsg,读取sk_receive_queue上之前准备好的数据返回。除此之外,对于NL_F_DUMP调用,调用netlink_dump从之前记录的位置开始(保存在cb->args),继续准备要返回的数据。如果判断数据已经读取完成,在netlink的头机构的nlmsg_flags成员中设置NLMSG_DONE通知应用程序,清除cb_running标志,结束dump传输。

原文地址:https://www.cnblogs.com/liuhongru/p/11413263.html