Netlink 内核实现分析 2
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的更多相关文章
- Netlink 内核实现分析(二):通信
在前一篇博文<Netlink 内核实现分析(一):创建>中已经较为具体的分析了Linux内核netlink子系统的初始化流程.内核netlink套接字的创建.应用层netlink套接字的创 ...
- Netlink 内核实现分析 3
Netlink IPC 数据结构 #define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unuse ...
- Netlink 内核实现分析 1
Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,在一般情况下,用户态和内核态通信会使用传统的Ioctl.sysfs属性文件 ...
- Netlink 内核实现分析 4
netlink 库函数: http://www.infradead.org/~tgr/libnl/doc/core.html#core_netlink_fundamentals #define NET ...
- MINIX3 内核时钟分析
MINIX3 内核时钟分析 4.1 内核时钟概要 先想想为什么 OS 需要时钟?时钟是异步的一个非常重要的标志,设想一下,如 果我们的应用程序需要在多少秒后将触发某个程序或者进程,我们该怎么做到? ...
- mkimage工具 加载地址和入口地址 内核启动分析
第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...
- 第3阶段——内核启动分析之start_kernel初始化函数(5)
内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...
- 几个常用内核函数(《Windows内核情景分析》)
参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...
- [1]windows 内核情景分析---说明
本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...
随机推荐
- linux的pci驱动模型
做个笔记 linux通过pcibios_scan_root函数以深度优先的算法搜索整个pci架构,建立一个树形的链表,如下: 之后再调用pci_bus_add_devices函数把所有搜索到的pci_ ...
- python 读取文件时报错UnicodeDecodeError
python 读取文件时报错UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 205: illegal multib ...
- centos8平台上php7.4的生产环境配置
一,php错误与日志的配置 1,创建php的日志保存目录: [root@yjweb ~]# mkdir -p /data/logs/phplogs [root@yjweb ~]# chmod 777 ...
- flink 处理实时数据的三重保障
flink 处理实时数据的三重保障 window+watermark 来处理乱序数据对于 TumblingEventTimeWindows window 的元数据startTime,endTime 和 ...
- Activity去掉标题不成功的解决方法
在设置Activity去掉标题的时候遇到的问题,记录一下. 一般会有以下两种方式: 1.Activity中设置 this.requestWindowFeature(Window.FEATURE_NO_ ...
- ssh执行命令
执行一条命令 ssh user@ip command 执行多条命令 ssh user@ip "command1;command2" #分号隔开 关于转义 ssh user@ip ' ...
- ThreadLocal使用说明
让变量只能在这个线程内被读写,在其他线程内无法被访问.以键值对存放变量,并继承弱应用,内存随时会被回收,用完要remove不然会内存泄漏,使用的时候直接设置值就可以了,键就是ThreadLocal本身 ...
- poj 2229 一道动态规划思维题
http://poj.org/problem?id=2229 先把题目连接发上.题目的意思就是: 把n拆分为2的幂相加的形式,问有多少种拆分方法. 看了大佬的完全背包代码很久都没懂,就照着网上的写了动 ...
- springboot配置定时任务并发执行
@Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void c ...
- Spring Cloud 学习笔记 (一)-- Eureka 服务器
开局一张图,截取了本人学习资料中的一张图,很好地展示了Eureka的架构. Eureka服务器 管理服务的作用.细分为服务注册,服务发现. 所有的客户端在Eureka服务器上注册服务,再从Eureka ...