在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