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;
}