netlink 应用层如何创建socket

应用层通过socket()系统调用创建Netlink套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK(在Linux系统中它俩实际为同一种宏),第二个参数可以是SOCK_RAW或SOCK_DGRAM(原始套接字或无连接的数据报套接字),

最后一个参为netlink.h中定义的协议类型,用户可以按需求自行创建上述不同种类的套接字。

例如调用 socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 即创建了一个NETLINK_ROUTE类型的Netlink套接字,其系统调用为

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
int retval;
struct socket *sock;
int flags; /* Check the SOCK_* constants for consistency. */
BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out; retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
/*
socket了一些参数检查之后就调用sock_create()函数创建套接字,在创建完成后向内核申请描述符并返回该描述符。进入sock_create()函数内部,它是__sock_create()的一层封装,这里要注意的是调用时又多了两个个参数,
一是当前进程绑定的网络命名空间,而是最后一个kern参数,这里传入0表明是从应用层创建的套接字。
在启用内核模块的情况下,这里会用到内核net_families数组中查找该family(AF_NETLINK)是否已经注册,如果没有注册就会尝试加载网络子系统模块。
其实在内核的netlink初始化函数中已经调用sock_register()完成注册了。接下来从net_families数组中获取已经注册的struct net_proto_family结构实例,也就是netlink_family_ops实例了
最后就会调用调用netlink协议的creat()也就是--->netlink_create函数
*/
/*
用层通过socket()系统调用创建Netlink套接字,socket系统调用的第一个参数可以是AF_NETLINK或PF_NETLINK
(在Linux系统中它俩实际为同一种宏),第二个参数可以是SOCK_RAW或SOCK_DGRAM(原始套接字或无连接的数据报套接字),
最后一个参为netlink.h中定义的协议类型,用户可以按需求自行创建上述不同种类的套接字。 例如调用 socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 即创建了一个NETLINK_ROUTE类型的Netlink套接字。
下面跟进这个系统调用,查看内核是如何为用户层创建这个套接字然后又做了哪些初始化动作:
*/
static int netlink_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
struct module *module = NULL;
struct mutex *cb_mutex;
struct netlink_sock *nlk;
int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group);
int err = 0; sock->state = SS_UNCONNECTED; if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
return -ESOCKTNOSUPPORT; if (protocol < 0 || protocol >= MAX_LINKS)
return -EPROTONOSUPPORT; netlink_lock_table();
#ifdef CONFIG_MODULES
if (!nl_table[protocol].registered) {
netlink_unlock_table();
request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
netlink_lock_table();
}
#endif
if (nl_table[protocol].registered &&
try_module_get(nl_table[protocol].module))
module = nl_table[protocol].module;
else
err = -EPROTONOSUPPORT;
cb_mutex = nl_table[protocol].cb_mutex;
bind = nl_table[protocol].bind;
unbind = nl_table[protocol].unbind;
netlink_unlock_table(); if (err < 0)
goto out; err = __netlink_create(net, sock, cb_mutex, protocol, kern);
if (err < 0)
goto out_module; local_bh_disable();
sock_prot_inuse_add(net, &netlink_proto, 1);
local_bh_enable(); nlk = nlk_sk(sock->sk);
nlk->module = module;
nlk->netlink_bind = bind;
nlk->netlink_unbind = unbind;
out:
return err;
/*
cb_mutex = nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex;
module = nl_table[NETLINK_ROUTE].module = THIS_MODULE;
bind = nl_table[NETLINK_ROUTE].bind = NULL;
unbind = nl_table[NETLINK_ROUTE].unbind = NULL; 接下来将调用__netlink_create()完成核心的创建初始化,这个函数在前面已经分析过了,就不进入继续分析了。
再往下调用sock_prot_inuse_add添加协议的引用计数,最后完成赋值: nlk->module = module = THIS_MODULE ;
nlk->netlink_bind = bind = NULL;
nlk->netlink_unbind = unbind = NULL;
offset_sock(nlk->sk--..-->socket)->ops = &netlink_ops;
至此用户态NETLINK_ROUTE类型的套接字就创建完成了。
*/
out_module:
module_put(module);
goto out;
}

到此用户态NETLINK_ROUTE类型的套接字就创建完成

netlink bind socket

socket将该套接字绑定到一个特定的地址或者加入一个多播组中,以后内核或其他应用层套接字向该地址单播或向该多播组发送组播消息时即可通过recv()函数接收消息了。

绑定地址时需要使用到sockaddr_nl地址结构,如果使用使用单播则需要将地址本地地址信息填入nl_pid变量并设置nl_groups为0,

如果使用多播则将nl_pid设置为0并填充nl_groups为多播地址,

bind函数 主要实现的有:根据用户传入的fd文件描述符向内核查找对应的socket结构,然后将用户空间传入的地址struct sockaddr拷贝到内核中(会使用到copy_from_user()),

接下来继续跳过安全检查函数security_socket_bind(),剩下的主要工作就交给了sock->ops->bind()注册函数了。

在创建套接字时调用的__netlink_create()函数中已经将sock->ops赋值为netlink_ops了

static const struct proto_ops netlink_ops = {
.family = PF_NETLINK,
.owner = THIS_MODULE,
.release = netlink_release,
.bind = netlink_bind,
.connect = netlink_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = netlink_getname,
.poll = datagram_poll,
.ioctl = netlink_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = netlink_setsockopt,
.getsockopt = netlink_getsockopt,
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
结构中的各个函数指针都会由系统调用根据套接字的协议类型 调用对应的回调函数,此时就会调用到这里的netlink_bind()函数
/*
首先根据用户传入的fd文件描述符向内核查找对应的socket结构,
然后将用户空间传入的地址struct sockaddr拷贝到内核中(会使用到copy_from_user()),
接下来继续跳过安全检查函数security_socket_bind(),剩下的主要工作就交给了sock->ops->bind()注册函数了。
在创建套接字时调用的__netlink_create()函数中已经将sock->ops赋值为netlink_ops了,来看一下这个结构实例: 接着如果用户设定了需要绑定的多播地址,这里会去检擦nl_table中注册的套接字是否已经设置了NL_CFG_F_NONROOT_RECV标识,
如果没有设置将拒绝用户绑定到组播组,显然在前文中已经看到了NETLINK_ROUTE类型的套接字是设置了这个标识的,
所以这里会调用netlink_realloc_groups分配组播空间,进入看一下: */
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);
//传入的地址类型强制转换成了sockaddr_nl类型的地址结构
struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
int err;
long unsigned int groups = nladdr->nl_groups;
bool bound;
//参数的检查
if (addr_len < sizeof(struct sockaddr_nl))
return -EINVAL; if (nladdr->nl_family != AF_NETLINK)
return -EINVAL; /* Only superuser is allowed to listen multicasts */
if (groups) {//用户设定了需要绑定的多播地址,检擦nl_table中注册的套接字是否已经设置了NL_CFG_F_NONROOT_RECV标识
if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV))
return -EPERM;
//有设置将拒绝用户绑定到组播组,NETLINK_ROUTE类型的套接字是设置了这个标识的,所以这里会调用netlink_realloc_groups分配组播空间
err = netlink_realloc_groups(sk);
if (err)
return err;
} bound = nlk->bound;
if (bound) {//接下来如果已经绑定过了,会检查新需要绑定的id号是否等于已经绑定的id号,若不相等则返回失败
/* Ensure nlk->portid is up-to-date. */
smp_rmb();
//如果已经绑定过了,会检查新需要绑定的id号是否等于已经绑定的id号
if (nladdr->nl_pid != nlk->portid)
return -EINVAL;
}
/*
接着如果netlink套接字子协议存在特有的bind函数且用户指定了需要绑定的组播地址,则调用之为其绑定到特定的组播组中去。
现由于NETLINK_ROUTE套接字并不存在nlk->netlink_bind()函数实现,所以这里并不会调用。
*/
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);
return err;
}
} /* No need for barriers here as we return to user-space without
* using any of the bound attributes.
*/
/*
如果本套接字并没有被绑定过(目前就是这种情况),这里会根据用户是否指定了单播的绑定地址来调用不同的函数。
首先假定用户空间指定了单播的绑定地址,这里会调用netlink_insert()函数将这个套接字插入到nl_table[NETLINK_ROUTE]数组项的哈希表中去,
同时设置nlk_sk(sk)->bound = nlk_sk(sk)->portid = nladdr->nl_pid。
*/
if (!bound) {
err = nladdr->nl_pid ?
netlink_insert(sk, nladdr->nl_pid) :
netlink_autobind(sock);//我们再假定用户空间没有设置单播的绑定地址,这里会调用netlink_autobind()动态的绑定一个地址,进入该函数简单的看一下:
if (err) {
netlink_undo_bind(nlk->ngroups, groups, sk);
return err;
}
} if (!groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
return 0; netlink_table_grab();
// 程序运行到这里意味着用户态指定了需要绑定的组播地址
// 将指定套接字加入所属netlink协议类型的多播hash链表
//设置 list add socket 到组播 mc_list--->sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list)
netlink_update_subscriptions(sk, nlk->subscriptions +
hweight32(groups) - hweight32(nlk->groups[0]));//函数的目的是求32位二进制数中的'1'的个数
nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | groups;// // 保存组播地址mask值到开头申请的组播空间
netlink_update_listeners(sk);//??
netlink_table_ungrab(); return 0;
}

Netlink 内核实现分析 2的更多相关文章

  1. Netlink 内核实现分析(二):通信

    在前一篇博文<Netlink 内核实现分析(一):创建>中已经较为具体的分析了Linux内核netlink子系统的初始化流程.内核netlink套接字的创建.应用层netlink套接字的创 ...

  2. Netlink 内核实现分析 3

    Netlink IPC 数据结构 #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unuse ...

  3. Netlink 内核实现分析 1

    Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,在一般情况下,用户态和内核态通信会使用传统的Ioctl.sysfs属性文件 ...

  4. Netlink 内核实现分析 4

    netlink 库函数: http://www.infradead.org/~tgr/libnl/doc/core.html#core_netlink_fundamentals #define NET ...

  5. MINIX3 内核时钟分析

    MINIX3 内核时钟分析  4.1 内核时钟概要  先想想为什么 OS 需要时钟?时钟是异步的一个非常重要的标志,设想一下,如 果我们的应用程序需要在多少秒后将触发某个程序或者进程,我们该怎么做到? ...

  6. mkimage工具 加载地址和入口地址 内核启动分析

    第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...

  7. 第3阶段——内核启动分析之start_kernel初始化函数(5)

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...

  8. 几个常用内核函数(《Windows内核情景分析》)

    参考:<Windows内核情景分析> 0x01  ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...

  9. [1]windows 内核情景分析---说明

    本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...

随机推荐

  1. linux处理l2tp协议的示意图

  2. 多测师讲解pthon_002字符,列表,元组,字段等

    # # # 索引:# # # 正向索引:  0  1  2  3  4  5  6# # #      l=   a  b  c  d  e  f  g# # # 反向索引: -7 -6 -5 -4 ...

  3. JS中实现Trim(),TrimStart(),TrimEnd() 的方法

    //去除字符串头尾空格或指定字符 String.prototype.Trim = function (c) { if (c == null || c == "") { var st ...

  4. Django基础之Ajax

    知识预览 Ajax前戏:json Ajax简介 Jquery实现的ajax JS实现的ajax Ajax前戏:json 什么是json? 定义: JSON(JavaScript Object Nota ...

  5. mysql间隙锁 转

    前面一文 mysql锁 介绍了mysql innodb存储引擎的各种锁,本文介绍一下innodb存储引擎的间隙锁,就以下问题展开讨论 1.什么是间隙锁?间隙锁是怎样产生的? 2.间隙锁有什么作用? 3 ...

  6. es7.8启动报错 说是主节点没找到

    p.p1 { margin: 0; font: 11px Menlo; color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1 ...

  7. Anderson《空气动力学基础》5th读书笔记 第0记——白金汉PI定理

    目录 量纲分析:白金汉PI定理 相似参数 量纲分析:白金汉PI定理 在空气动力学中,飞机的空气动力主要由自由来流的密度ρ∞,自由来流数V∞,翼弦长度c,自由来流的粘性系数μ∞以及音速a∞,所以假设我们 ...

  8. Nacos注册中心使用

    创建两个工程,一个是nacos-provider, 另一个是naocos-consumer: 添加nacos-provider的依赖 <parent> <groupId>org ...

  9. D. Concatenated Multiples 解析(思維)

    Codeforce 1029 D. Concatenated Multiples 解析(思維) 今天我們來看看CF1029D 題目連結 題目 給你一個序列\(a\)和一個數字\(k\),求有幾種ind ...

  10. 显示器类型对美乐威NDI IP转换器延时影响测试

    背景 用户在选择用网络传输视频时,传输延迟通常是他们非常关心的数据.集成商在探究如何降低视频传输延时,往往专注于网络本身和视频编码的优化,容易忽略视频解码和播出也是整个视频传输过程中非常重要的环节.本 ...