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内核情景分析>(毛德操著).< ...
随机推荐
- Oracle函数总结
<Trunc()> 描 述(实际应用):截取小数或者日期整数 简 介:https://baike.baidu.com/item/trunc/9657216?fr=al ...
- 【Luogu】P3005 [USACO10DEC]槽的游戏The Trough Game
一.题目 题目描述 农夫约翰和贝西又在玩游戏.这个游戏需要很多个槽. 农夫约翰在谷仓里藏起来了N(1<=N<=20)个槽,并且他已经把其中的一些装上了食物.贝西以"在这个表里(表 ...
- spring boot:actuator的安全配置:使用spring security做ip地址限制(spring boot 2.3.2)
一,actuator有哪些环节要做安全配置? actuator是应用广泛的监控工具, 但在生产环境中使用时,需要做严格的安全保障, 避免造成信息泄露等严重的安全问题 actuator可以采取的安全措施 ...
- go panic
panic 抛出异常 通过recover捕获 类似 php python等语言的try catch package mainimport ( "fmt" "errors& ...
- [转]CSS学习笔记
原文:http://www.fx114.net/qa-266-93710.aspx 01.什么是CSS. CSS指层叠样式表(Cascading Style Sheets). ·样式定义如 ...
- Windows快捷键与Dos命令学习
部分Windows常用快捷键 复制:Ctrl + C 粘贴:Ctrl + V 全选:Ctrl + A 剪切:Ctrl + X 撤销:Ctrl + Z 保存:Ctrl + S 关闭窗口:Alt + F4 ...
- python中实现格式化输入(史上最简单、最高效的实现方法,不借助任何模块)
今天我在写python作业时突然想到格式化输入一段文字,譬如只需读取输入的前几个字符就行,而不幸的是,python中的输入并没有c中的read().getchar()函数,于是我网上搜了一下,网上的解 ...
- *新阿里镜像maven配置流程
解决idea中maven仓库下载jar包慢的问题 配置阿里镜像 设置idea 配置阿里镜像参考链接:https://blog.csdn.net/redhat0921/article/details/1 ...
- pyqt5按钮退出程序
import sys from PyQt5.QtWidgets import QHBoxLayout,QMainWindow,QApplication,QPushButton,QWidget clas ...
- ATOM基础教程一使用前端插件emmet(16)
emmet简介 http://blog.csdn.net/zsl10/article/details/51956791 emmet的前身是Zen coding,从事Web前端开发的工程师对该插件并不陌 ...