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. MeteoInfoLab脚本示例:读取远程文件

    利用Unidata netCDF Java库对远程文件的读取能力(OpenDAP, ADDE, THREDDS等),可以读取远程文件并绘图.脚本程序: fn = 'http://monsoondata ...

  2. 【C语言编程入门笔记】C语言果然博大精深!函数还分内部和外部?

    ۞ 外部函数与内部函数 前面我们讲解了关于函数的调用都是针对同一个源文件中其他函数进行调用的,而在有些情况下,函数也可以对另外一个源文件中的函数进行调用.当一个程序由多个源文件组成时,根据函数是否能被 ...

  3. WebFlux快速上手

    一.新建项目 示例使用IDEA快速创建基于SpringBoot的工程. springboot 2.3.1 java 8 WebFlux 必须选用Reactive的库 POM 依赖 <depend ...

  4. git merge 与 git rebase的区别?

    一,git merge 与 git rebase的区别 1,git merge 例如: master分支合并dev分支,git将两个分支dev和master上的所有commit , 按照提交时间的先后 ...

  5. Apollo基于K8S的部署以及接入

    Apollo镜像服务 基于开源Apollo服务进行相关服务镜像打包,实际将分发apollo-adminservice.apollo-configservice和apollo-portal 这三个镜像安 ...

  6. 边界层吞吸技术(BLI)

    气流在机体表面前进时,由于受到摩擦,其速度会不断降低,从而会产生湍流甚至气流分离,而流动分离又会造成大量紊流.涡,使升力大量损失,同时也会造成阻力急剧增加.边界层吞吸技术就是一种对附面层气流" ...

  7. 连肝三个通宵,JVM77道高频面试题详细分析,就这?

    为方便大家记忆,记得收藏加关注哦 ,需要下载PDF版本请在公众号[程序员空间]回复"资料"即可获取下载方式,你也可以 点在文末微信扫描二维码关注! 1.java 中会存在内存泄漏吗 ...

  8. [开源] .Net ORM FreeSql 1.10.0 稳步向行

    写在开头 FreeSql 是 .NET 开源生态下的 ORM 轮子,转眼快两年了,说真的开源不容易(只有经历过才明白).今天带点干货和湿货给大家,先说下湿货. 认识我的人,知道 CSRedisCore ...

  9. MySQL 日志之 binlog 格式 → 关于 MySQL 默认隔离级别的探讨

    开心一刻 产品还没测试直接投入生产时,这尼玛... 背景问题 在讲 binlog 之前,我们先来回顾下主流关系型数据库的默认隔离级别,是默认隔离级别,不是事务有哪几种隔离级别,别会错题意了 1.Ora ...

  10. ServletResponse使用介绍

    ServletResponse为将响应发送到客户端的对象:Servlet 容器创建 ServletResponse 对象,并将它作为参数传递给Servlet的service 方法,如下图 : Http ...