rtnetlink的portid选择

在iproute2-5.9.0的文件lib/libnetlink.c中,创建netlink套接口时,地址结构sockaddr_nl,即rth->local没有对其成员nl_pid赋值,仅赋值了nl_family和nl_groups,由内核完成nl_pid的选取。

int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
              int protocol)
{
    memset(rth, 0, sizeof(*rth));

    rth->proto = protocol;
    rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
    if (rth->fd < 0) {
        perror("Cannot open netlink socket");
        return -1;
    }

    memset(&rth->local, 0, sizeof(rth->local));
    rth->local.nl_family = AF_NETLINK;
    rth->local.nl_groups = subscriptions;

    if (bind(rth->fd, (struct sockaddr *)&rth->local,
         sizeof(rth->local)) < 0) {
        perror("Cannot bind netlink socket");
        return -1;
    }

套接口绑定

如果套接口已经bind过(bound为真),检查要绑定地址中的nl_pid是否与套接口中保存的portid相同,不相同返回错误。

static int netlink_bind(struct socket *sock, struct sockaddr *addr,
            int addr_len)
{
    struct netlink_sock *nlk = nlk_sk(sk);
    struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;

    bound = nlk->bound;
    if (bound) {
        /* Ensure nlk->portid is up-to-date. */
        smp_rmb();

        if (nladdr->nl_pid != nlk->portid)
            return -EINVAL;
    }

对于NETLINK_ROUTE,在netlink_create函数中为netlink_bind指针赋值为rtnetlink_bind,之后将看到此函数,其仅是检查group为多播组时的权限问题。

    netlink_lock_table();
    if (nlk->netlink_bind && groups) {
        int group;

        for (group = 0; group < nlk->ngroups; group++) {
            if (!test_bit(group, &groups))
                continue;
            err = nlk->netlink_bind(net, group + 1);
            if (!err)
                continue;
            netlink_undo_bind(group, groups, sk);
            goto unlock;
        }
    }

如果应用层在地址结构(sockaddr_nl)中指定了nl_pid,使用其值。否则,由内核函数netlink_autobind进行自动分配。

    /* No need for barriers here as we return to user-space without
     * using any of the bound attributes.
     */
    if (!bound) {
        err = nladdr->nl_pid ?
            netlink_insert(sk, nladdr->nl_pid) :
            netlink_autobind(sock);
        if (err) {
            netlink_undo_bind(nlk->ngroups, groups, sk);
            goto unlock;
        }
    }

现在看一下函数rtnetlink_bind,其检查group为RTNLGRP_IPV4_MROUTE_R或者RTNLGRP_IPV6_MROUTE_R时,当前用户是否具有ROOT权限。

static int rtnetlink_bind(struct net *net, int group)
{
    switch (group) {
    case RTNLGRP_IPV4_MROUTE_R:
    case RTNLGRP_IPV6_MROUTE_R:
        if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
            return -EPERM;
        break;
    }
    return 0;
}

netlink套接口自动绑定

首先由函数task_tgid_vnr根据当前进程结构current生成一个有符号的32位portid值; 之后由函数__netlink_lookup检查portid是否被其它套接口使用,如果有冲突,进行如下的处理:

1) 如果rover等于-4096(初始值),那么将rover调整为[S32_MIN, -4097]之间的一个随机值。
函数prandom_u32_max返回一个位于[0, -4096 - S32_MIN)之间的无符号的32位随机值。

2) 如果rover大于等于-4096,将rover设置为-4097;

之后将rover的值赋给portid,随后将rover自减一,重新执行__netlink_lookup检查新的portid是否可用。随着rover的自减操作,其值变得越来越小,达到最小值时变为正数,由于所有正数都符合以上的条件2),rover被赋值为-4097。

由以上可见,最终portid的值的范围为: [S32_MIN, -4097]。

static int netlink_autobind(struct socket *sock)
{
    struct sock *sk = sock->sk;
    struct net *net = sock_net(sk);
    struct netlink_table *table = &nl_table[sk->sk_protocol];
    s32 portid = task_tgid_vnr(current);
    s32 rover = -4096;

retry:
    cond_resched();
    rcu_read_lock();
    ok = !__netlink_lookup(table, portid, net);
    rcu_read_unlock();
    if (!ok) {
        /* Bind collision, search negative portid values. */
        if (rover == -4096)
            /* rover will be in range [S32_MIN, -4097] */
            rover = S32_MIN + prandom_u32_max(-4096 - S32_MIN);
        else if (rover >= -4096)
            rover = -4097;
        portid = rover--;
        goto retry;
    }

以下将选择的portid赋值给sk表示的netlink套接口,并且其插入全局链表nl_table。

    err = netlink_insert(sk, portid);
    if (err == -EADDRINUSE)
        goto retry;

    /* If 2 threads race to autobind, that is fine.  */
    if (err == -EBUSY)
        err = 0;

内核版本 5.0

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页