文章目录
数据结构
下面仅罗列了和组播机制相关的数据结构及其字段。
协议对象: netlink_table
struct netlink_table {
...
struct hlist_head mc_list; // 组织所有监听该协议的多播数据的传输控制块对象
unsigned long *listeners; // 标记该协议哪些多播组被监听,被监听多播组对应bit为1
unsigned int groups; // 协议支持的多播组个数,最小32个组
...
};
传输控制块: netlink_sock
struct netlink_sock {
...
u32 pid; // 该套接字的标识,相当于端口号,见netlink_bind()
u32 dst_pid; // connect()调用可以设置套接字的目的PID和目的组
u32 dst_group;
u32 subscriptions;
u32 ngroups;
unsigned long *groups;
...
};
- subscriptions
记录该套接字接收多少个多播组的数据,见下方的绑定过程。
- ngroups/groups
当套接字需要接收某些多播组的数据时,在绑定时,会设置ngroups为netlink_table.groups,并且保证groups数组的大小足够表示这些多播组(按位使用,类似netlink_table.listeners的使用)。
组播发送控制信息: netlink_broadcast_data
struct netlink_broadcast_data {
// 发送时排除哪个套接字,通常设置为发送方自己,从而避免发送方收到自己发送的组播数据
struct sock *exclude_sk;
struct net *net;
u32 pid; // 组播目的地址
u32 group;
int failure; // 标记组播发送过程中是否有错误
int congested; // 标记组播发送过程中是否有拥塞
int delivered;
gfp_t allocation;
// skb是要发送的数据,发送过程如果需要拷贝skb,则拷贝后的数据保存在skb2中
struct sk_buff *skb, *skb2;
};
协议注册时
在具体协议向Netlink框架注册自己时,会初始化对应的协议对象netlink_table,其中和组播相关的字段设置如下。
@unit: 协议类型,对应socket(2)中的protocol参数
@groups: 协议支持的多播组个数
struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups,
void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module)
{
struct socket *sock;
struct sock *sk;
struct netlink_sock *nlk;
unsigned long *listeners = NULL;
...
// 根据多播组个数分配listeners数组
if (groups < 32) // 最少32个组播
groups = 32;
listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
if (!listeners)
goto out_sock_release;
...
// 设置协议对象中的相关字段
netlink_table_grab();
if (!nl_table[unit].registered) { // 首次调用就是注册
nl_table[unit].groups = groups;
nl_table[unit].listeners = listeners;
nl_table[unit].cb_mutex = cb_mutex;
nl_table[unit].module = module;
nl_table[unit].registered = 1; // 标记协议已注册
} else { // 重复注册
kfree(listeners);
nl_table[unit].registered++;
}
netlink_table_ungrab();
return sk;
...
}
listeners数组是按照bit使用的,每个多播组对应一个位,索引就是组号。宏NLGRPSZ()可以保证分配足够多的bit。
// sizeof(unsigned long) * 8表示一个unsigned long类型的整数的bit个数,参数x为多播组个数,
// 向上对齐后除以8,得到的结果是按比特表示x个多播组需要多少个字节
#define NLGRPSZ(x) (ALIGN(x, sizeof(unsigned long) * 8) / 8)
套接字绑定时
static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
{
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
...
/* Only superuser is allowed to listen multicasts */
if (nladdr->nl_groups) { // 需要绑定到多播组上
// 调用进程有组播接收权限
if (!netlink_capable(sock, NL_NONROOT_RECV))
return -EPERM;
// 尝试重新分配传输控制块上的groups空间
err = netlink_realloc_groups(sk);
if (err)
return err;
}
...
// 没有绑定组播地址,并且groups信息也为空,那么不需要更新它们,结束绑定过程
if (!nladdr->nl_groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
return 0;
// 更新组播监听者
netlink_table_grab();
// hweight32()计算的是参数指定的整数的二进制位中1的个数,这里为何第二个参数不能直接
// 写成hweight32(nladdr->nl_groups)
netlink_update_subscriptions(sk, nlk->subscriptions +
hweight32(nladdr->nl_groups) - hweight32(nlk->groups[0]));
// 为何不直接设置为nladdr->nl_groups
nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | nladdr->nl_groups;
netlink_update_listeners(sk);
netlink_table_ungrab();
return 0;
}
netlink_realloc_groups()
static int netlink_realloc_groups(struct sock *sk)
{
struct netlink_sock *nlk = nlk_sk(sk);
unsigned int groups;
unsigned long *new_groups;
int err = 0;
netlink_table_grab();
// 拿到该协议支持的多播组个数
groups = nl_table[sk->sk_protocol].groups;
if (!nl_table[sk->sk_protocol].registered) {
err = -ENOENT;
goto out_unlock;
}
// 如果该套接字的groups内存已经足够,则不用重新分配
if (nlk->ngroups >= groups)
goto out_unlock;
// 重新分配一块更大的空间,可见groups的大小和协议对象中listener的大小是一致的
new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC);
if (new_groups == NULL) {
err = -ENOMEM;
goto out_unlock;
}
// 将新分配的部分清零
memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0,
NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups));
// 记录新的groups信息
nlk->groups = new_groups;
nlk->ngroups = groups;
out_unlock:
netlink_table_ungrab();
return err;
}
更新签约信息: netlink_update_subscriptions()
static void
netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions)
{
struct netlink_sock *nlk = nlk_sk(sk);
if (nlk->subscriptions && !subscriptions)
// 新的传输控制块已经不需要接收任何多播组数据了,将其从协议对象的mc_list链表中删除
__sk_del_bind_node(sk);
else if (!nlk->subscriptions && subscriptions)
// 相反,将传输控制块对象加入mc_list中
sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list);
// 记录新的监听多播组个数
nlk->subscriptions = subscriptions;
}
更新监听信息: netlink_update_listeners()
// 得到的结果是按bit表示x个多播组需要的unsigned long整数的个数
#define NLGRPLONGS(x) (NLGRPSZ(x)/sizeof(unsigned long))
static void
netlink_update_listeners(struct sock *sk)
{
struct netlink_table *tbl = &nl_table[sk->sk_protocol]; // 找到协议对象
struct hlist_node *node;
unsigned long mask;
unsigned int i;
// 遍历整个mc_list链表,重新标记listeners,只要对应bit的多播组有套接字监听,
// listeners的对应bit就标记为1
for (i = 0; i < NLGRPLONGS(tbl->groups); i++) {
mask = 0;
sk_for_each_bound(sk, node, &tbl->mc_list) {
if (i < NLGRPLONGS(nlk_sk(sk)->ngroups))
mask |= nlk_sk(sk)->groups[i];
}
tbl->listeners[i] = mask;
}
/* this function is only called with the netlink table "grabbed", which
* makes sure updates are visible before bind or setsockopt return. */
}
套接字连接时
static int netlink_connect(struct socket *sock, struct sockaddr *addr,
int alen, int flags)
{
int err = 0;
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
...
/* Only superuser is allowed to send multicasts */
// 连接到某个多播组,检查调用者是否有组播发送权限
if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
...
// 保存目的地址信息
if (err == 0) {
sk->sk_state = NETLINK_CONNECTED;
nlk->dst_pid = nladdr->nl_pid;
nlk->dst_group = ffs(nladdr->nl_groups);
}
return err;
}
ffs(x)是个体系结构实现相关的宏,它是Find Fist Set的缩写,它按照从低位到高位的顺序查找x中第一个为1的比特的位置,最低位为1,依次类推,所以上述nlk->dst_group最终保存的是多播组的组号(组号是从1开始的)。
套接字发送时
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len)
{
u32 dst_pid;
u32 dst_group;
...
if (msg->msg_namelen) { // 指定了目的地,对组播发送进行权限检查
dst_pid = addr->nl_pid;
// 如果是组播消息,那么校验其发送权限
dst_group = ffs(addr->nl_groups);
if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND))
return -EPERM;
} else {
// 本次发送没有指定目的地址,使用传输控制块中的目的地址,只有连接过的socket这两个字段才有值,
// 见连接调用实现。特别的,如果不指定也未连接过,那么这两个默认都为0,此时会发送消息给内核
dst_pid = nlk->dst_pid;
dst_group = nlk->dst_group;
}
...
// 先进行组播发送,然后进行单播发送
if (dst_group) {
atomic_inc(&skb->users);
netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);
}
err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
return err;
}
@pid/group: 代表了组播目的地址
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,
u32 group, gfp_t allocation)
{
struct net *net = sock_net(ssk);
struct netlink_broadcast_data info;
struct hlist_node *node;
struct sock *sk;
skb = netlink_trim(skb, allocation);
// 填充组播发送控制信息
info.exclude_sk = ssk; // 避免发送者收到自己发送的数据
info.net = net;
info.pid = pid; // 组播数据的目的地址
info.group = group;
info.failure = 0;
info.congested = 0;
info.delivered = 0;
info.allocation = allocation;
info.skb = skb;
info.skb2 = NULL;
/* While we sleep in clone, do not allow to change socket list */
netlink_lock_table();
// 遍历该协议上每个监听组播的传输控制块,调用do_one_broadcast()尝试将skb发送给它
sk_for_each_bound(sk, node, &nl_table[ssk->sk_protocol].mc_list)
do_one_broadcast(sk, &info);
kfree_skb(skb);
netlink_unlock_table();
// 发送过程中可能需要拷贝skb,拷贝后的skb放在了skb2中,发送完毕后将其释放
if (info.skb2)
kfree_skb(info.skb2);
// 处理可能的拥塞和发送失败
if (info.delivered) {
if (info.congested && (allocation & __GFP_WAIT))
yield();
return 0;
}
if (info.failure)
return -ENOBUFS;
return -ESRCH;
}
do_one_broadcast()
如上,该函数尝试将skb发送给参数sk代表的传输控制块。
static inline int do_one_broadcast(struct sock *sk, struct netlink_broadcast_data *p)
{
struct netlink_sock *nlk = nlk_sk(sk);
int val;
if (p->exclude_sk == sk) // 跳过要排除的socket
goto out;
// 目的地址匹配,因为组号是从1开始的,所以这里要减1处理
if (nlk->pid == p->pid || p->group - 1 >= nlk->ngroups ||
!test_bit(p->group - 1, nlk->groups))
goto out;
if (!net_eq(sock_net(sk), p->net))
goto out;
if (p->failure) { // 遍历过程中发生失败,后续socket需要处理这种失败
netlink_overrun(sk);
goto out;
}
sock_hold(sk);
// 将skb拷贝一份
if (p->skb2 == NULL) {
if (skb_shared(p->skb)) {
p->skb2 = skb_clone(p->skb, p->allocation);
} else {
p->skb2 = skb_get(p->skb);
/*
* skb ownership may have been set when
* delivered to a previous socket.
*/
skb_orphan(p->skb2);
}
}
if (p->skb2 == NULL) { // skb拷贝失败
netlink_overrun(sk);
/* Clone failed. Notify ALL listeners. */
p->failure = 1;
} else if (sk_filter(sk, p->skb2)) { // 通过目标socket上挂的过滤器
kfree_skb(p->skb2);
p->skb2 = NULL;
} else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) { // 将skb发送给目标socket
netlink_overrun(sk);
} else {
p->congested |= val; // 发送成功
p->delivered = 1;
p->skb2 = NULL;
}
sock_put(sk);
out:
return 0;
}
static inline int netlink_broadcast_deliver(struct sock *sk,
struct sk_buff *skb)
{
struct netlink_sock *nlk = nlk_sk(sk);
// 接收内存可用的情况下,将skb放入其接收队列,然后唤醒可能的读进程,
// 之后的接收流程,组播数据和普通单播数据并无不同
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
!test_bit(0, &nlk->state)) {
skb_set_owner_r(skb, sk);
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_data_ready(sk, skb->len);
return atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf;
}
// 返回-1表示发生了拥塞
return -1;
}
特别注意:上述组播发送过程中,在目的地址匹配时,要注意只要目的pid和目的组二者有一个匹配即可。
小结
以上就是Netlink组播机制的核心内容。Netlink组播常用于内核态向用户态发送某种通知,可实际上也是支持用户态到用户态的Netlink组播(用户态到内核态组播没有意义,单播就可以搞定),只要发送方和接收方约定好使用哪个协议的哪个多播组即可。