RPC中的UNIX认证
来源:互联网 发布:如何在C语言中用开根 编辑:程序博客网 时间:2024/06/10 09:18
上一篇文章中我们讲解了一个RPC请求的处理流程,其中涉及到了RPC请求报文中的认证信息。Linux支持多种认证方式,不同的认证方式中认证信息的内容是不同的,需要按照认证方式进行处理。这篇文件中我们以UNIX认证为例,详细讲解UNIX认证方式中认证信息的处理过程。
Linux中,每种认证方式用数据结构auth_ops表示,每种认证方式都需要实现这个数据结构中的函数。
struct auth_ops { char * name; // 这是认证方式的名称 struct module *owner; int flavour; // 这是认证方式的编号 // 这是认证信息处理函数,负责解析RPC请求报文中的Credential字段和Verifier字段 // 然后填充RPC应答报文中的Verifier字段 int (*accept)(struct svc_rqst *rq, __be32 *authp); // 这个函数负责一些清理工作,当RPC请求处理结束后会调用这个函数 int (*release)(struct svc_rqst *rq); // 这是释放认证域的函数,auth_domain是一个与认证信息相关的数据结构, // 存放了认证相关的数据 void (*domain_release)(struct auth_domain *); // 这个函数负责对用户进行认证,负责填充auth_domain结构, // 这个函数被svc_program结构中的pg_authenticate函数调用 int (*set_client)(struct svc_rqst *rq);};
UNIX认证中,这个数据结构的定义如下
struct auth_ops svcauth_unix = { .name = "unix", .owner = THIS_MODULE, .flavour = RPC_AUTH_UNIX, .accept = svcauth_unix_accept, .release = svcauth_unix_release, .domain_release = svcauth_unix_domain_release, .set_client = svcauth_unix_set_client,};
1.svcauth_unix_accept
这个函数负责解析RPC请求报文中的认证信息,并组装RPC应答报文中的认证信息。这个函数只解析数据,不进行用户身份验证。这个函数的完整流程如下:
static intsvcauth_unix_accept(struct svc_rqst *rqstp, __be32 *authp){ // 调用这个函数前已经解析出了Credential中的Flavor字段,确定了RPC请求报文中包含的是UNIX认证数据, // 接着解析报文中下一个字段Length,这个字段表示认证信息的长度 struct kvec *argv = &rqstp->rq_arg.head[0]; // resv是RPC应答消息的缓存,调用这个函数前已经封装了XID、Message Type、Reply State三个字段, // 这个函数中需要继续封装Verifier字段. struct kvec *resv = &rqstp->rq_res.head[0]; // 这是保存认证信息的数据结构,从RPC请求报文中解析出的UID、GID将保存在这个数据结构中. struct svc_cred *cred = &rqstp->rq_cred; u32 slen, i; // 由于argv指向的缓存中保存的是RPC请求报文中的数据,因此argv->iov_len是RPC报文剩余数据的长度. int len = argv->iov_len; // 这是缓冲区长度 96 剩余的字节数 // 先将用户信息设置为空. cred->cr_group_info = NULL; cred->cr_principal = NULL; rqstp->rq_client = NULL; // length stamp machine name length // 这里在检查RPC报文中是否有足够的数据,RPC报文中的数据经过了XDR编码,以4字节为一个单位. // 通过对比RPC报文,3*4表示LENGTH、STAMP、machine name length三个字段的长度. // 如果缓存中的数据小于这个长度,就说明RPC请求报文中的认证信息有问题,直接返回. if ((len -= 3*4) < 0) return SVC_GARBAGE; // 取出RPC请求报文中标明的认证信息的长度 svc_getu32(argv); /* length */ // 这是认证信息中封装的时间戳 svc_getu32(argv); /* time stamp */ // 解析认证信息中machine name的长度 slen = XDR_QUADLEN(svc_getnl(argv)); /* machname length */ // 这又是一个检查函数,(slen + 3)*4表示machine name、UID、GID三个字段的长度 if (slen > 64 || (len -= (slen + 3)*4) < 0) goto badcred; // 这里直接跳过了主机名,没有使用主机名的信息. argv->iov_base = (void*)((__be32*)argv->iov_base + slen); /* skip machname */ argv->iov_len -= slen*4; // 因此,现在argv指向了UID字段感觉这个函数中虽然在一些地方检查了RPC请求报文中剩余信息长度,但是好像不严谨。
2.svcauth_unix_set_client
这是UNIX认证方式中的用户身份认证函数,这个函数有点复杂,因为这个函数涉及到了RPC中的缓存机制,我们将在下篇文章中介绍这种机制。这里先以NFS服务为例简单讲讲这种缓存是干什么用的。我们在前面的文章中讲过,当服务器端开启NFS服务以后,会将允许挂载的客户端范围和文件系统的信息解析出来,保存在内存中。但是需要注意,这保存在了线程rpc.mountd的内存地址中。当内存接收到NFS请求后内核需要查询这些信息,确认客户端是否有访问权限,内核会将查询的结果作为缓存保存在内核空间中,这就是RPC缓存。当下个请求到来时,如果缓存中的数据仍然有效,就不需要向rpc.mountd查询了。在实际操作过程中,内核和rpc.mountd线程是通过proc文件系统进行通信的,当rpc.mountd解析完/var/lib/nfs/etab中的数据后,会通过select()函数监听proc文件系统中的一个文件。内核需要向rpc.mountd查询数据时就将相应的信息写入这个文件中,rpc.mountd读取这个文件中的数据,进行查询,将查询结果写回这个文件中,内核就得到结果了。先说这么多,下篇文章中我们进一步解释。
下面开始讲解svcauth_unix_set_client函数流程。
(1)跳过NULL例程
rqstp->rq_client = NULL;if (rqstp->rq_proc == 0) // RPC例程编号0,不需要认证 return SVC_OK;RPC中,编号为0的例程是一个标准例程,这个例程的作用是测试客户端和服务器端的连接是否正常,这个例程没有处理函数,也不需要认证。因此,如果例程编号是0,则svcauth_unix_set_client直接退出。
这里还需要说说rqstp->rq_client,这个字段的类型是struct auth_domain,这个数据结构的定义如下:
struct auth_domain { struct kref ref; // 这个结构的引用计数 struct hlist_node hash; // 有的auth_domain结构保存在一个全局hash表中 char *name; // 名称,如 192.168.6.0/24 struct auth_ops *flavour; // 认证方式};
这是一个通用的数据结构,被各种认证方式使用。UNIX认证方式中对应的数据结构是
struct unix_domain { struct auth_domain h; /* other stuff later */};auth_domain和unix_domain之间的关系类似于文件系统中索引节点inode和ext2_inode_info的关系。auth_domain中包含了各种认证方式中都需要使用的信息,unix_domain中保存了UNIX认证方式中特定的信息,只不过UNIX认证方式中不需要其他信息,因此unix_domain中只有一个字段h。函数svcauth_unix_set_client对用户身份进行认证,认证后的信息就保存在数据结构unix_domain中,也就是保存在rqstp->rq_client中。
(2)用户身份验证
switch (rqstp->rq_addr.ss_family) { // 这是RPC客户端的地址类型case AF_INET: // IPv4网络 sin = svc_addr_in(rqstp); sin6 = &sin6_storage; ipv6_addr_set_v4mapped(sin->sin_addr.s_addr, &sin6->sin6_addr); break; case AF_INET6: sin6 = svc_addr_in6(rqstp); break; default: BUG(); }ipm = __ip_map_lookup(sn->ip_map_cache, rqstp->rq_server->sv_program->pg_class, &sin6->sin6_addr);// cache_check()就是RPC缓存了,如果缓存中的数据无效,就向用户态进程请求数据. switch (cache_check(sn->ip_map_cache, &ipm->h, &rqstp->rq_chandle)) { default: BUG(); case -ETIMEDOUT: return SVC_CLOSE; case -EAGAIN: return SVC_DROP; case -ENOENT: return SVC_DENIED; // 如果不允许这个客户端访问,这里就拒绝了. case 0: // 这里设置了RPC请求的认证域 cache_check中设置了ipm->m_client rqstp->rq_client = &ipm->m_client->h; // 这是一个auth_domain结构,就采用这个认证域了 kref_get(&rqstp->rq_client->ref); ip_map_cached_put(xprt, ipm); break; }
这是UNIX认证方式中用户身份认证的主要代码段,rqstp->rq_addr中保存的是客户端的地址,包括IP地址以及地址类型,这是从RPC请求报文的IP头中解析出来的,客户端无法伪造这个地址。__ip_map_lookup()是和RPC缓存相关的一个函数,这个函数主要在操作一个数据结构ip_map
struct ip_map { struct cache_head h; // 这是RPC缓存需要使用的数据结构 char m_class[8]; /* e.g. "nfsd" */ // 这是一类RPC服务 struct in6_addr m_addr; // 这是客户端的地址 struct unix_domain *m_client; // 认证域};struct ip_map结构的定义如上所示,__ip_map_lookup()在系统中查找是否存在符合条件的ip_map结构,如果不存在就创建一个新的ip_map结构。查找条件包含两个:(1)m_class 这是一个字符串,相当于一个类型,比如nfsd,通过__ip_map_lookup()第二个参数设置;(2)m_addr 这是客户端的地址,通过__ip_map_lookup()第三个参数设置。
cache_check()也是和RPC缓存相关的一个函数,这个函数的作用是检查ip_map中的数据是否有效,如果无效就向rpc.mountd进程发送请求更新这个数据结构中的数据。更新结束后,m_client就包含有效的数据了。这个更新过程是通过文件/proc/net/rpc/auth.unix.ip/channel实现的。内核将m_class和m_addr写入channel中,rpc.mountd中的处理函数是auth_unix_ip(),这个函数从channel文件中读取客户端的IP地址,查找这个客户端所属于的认证区域,将结果写回channel文件中,内核解析channel文件中的数据。auth_unix_ip()的代码可以在nfs-utils中找到。这里以一个例子说说处理后的结果。假设服务器端导出了下列两个文件系统
/tmp/nfs/root1 192.168.6.0/24(rw,sync)
/tmp/nfs/root2 192.168.0.0/16(rw,sync)
如果客户端192.168.6.14向服务器发起了RPC请求,请求的是文件系统/tmp/nfs/root1。通过上面文件系统的配置信息我们可以看到,192.168.6.14属于两个认证域192.168.6.0/24和192.168.0.0/16,因此cache_check()执行结束后,m_client中各个字段的信息如下
struct auth_domain {
struct kref ref;
struct hlist_node hash;
char *name; // 名称,这是客户端所属于的所有的认证域,这里的值是 192.168.6.0/24,192.168.0.0/16
struct auth_ops *flavour; // 认证方式,这里就是UNIX认证方式操作函数集合
};
因此,这里只是一个基本的认证过程,只要客户端对服务器有访问权限,认证就会通过。假设客户端的地址是192.168.5.10,请求的是/tmp/nfs/root1中的数据,虽然192.168.5.10对root1没有访问权限,但是对root2有访问权限,认证也会通过。NFS在处理具体请求中会再次检查192.168.5.10是否有root1的访问权限。
(3)设置用户身份
gi = unix_gid_find(cred->cr_uid, rqstp); switch (PTR_ERR(gi)) {case -EAGAIN: return SVC_DROP;case -ESHUTDOWN: return SVC_CLOSE;case -ENOENT: break; // 服务器端没有查找数据,使用RPC请求报文中的GIDSdefault: put_group_info(cred->cr_group_info); cred->cr_group_info = gi; // 使用服务器端的GIDS}Linux系统中,每个用户都有自己的UID,同时用户会属于一个或多个用户组,每个用户组用一个GID表示。RPC请求报文中包含了用户的UID和GIDs,但是这是客户端中的信息,不是服务器端的信息。比如用户在客户端的UID是500,而服务器端可能不存在UID是500的用户,或者虽然存在UID是500的用户,但是这个用户的GIDs和客户端的GIDs不同,因此UNIX认证需要对这种情况进行处理,这个处理过程是通过函数unix_gid_find()完成的。这个处理过程也使用了RPC缓存,内核将从RPC请求报文中解析出的UID写到文件/proc/net/rpc/auth.unix.gid/channel中,rpc.mountd读取channel文件中的数据,查找这个用户所属于的用户组,将查找结果写回channel文件中,内核再解析channel文件中的数据。
3.svcauth_unix_domain_release
释放一个auth_domain结构
static void svcauth_unix_domain_release(struct auth_domain *dom){ // 找到unix_domain结构 struct unix_domain *ud = container_of(dom, struct unix_domain, h); // 释放内存 kfree(dom->name); kfree(ud);}这个函数很简单,直接释放内存就可以了。
4.svcauth_unix_release
这个函数也很简单
static intsvcauth_unix_release(struct svc_rqst *rqstp){ /* Verifier (such as it is) is already in place. */ if (rqstp->rq_client) // rqstp->rq_client是一个认证域结构(auth_domain) auth_domain_put(rqstp->rq_client); // 释放这个认证域 rqstp->rq_client = NULL; // 撤销关联的认证域 if (rqstp->rq_cred.cr_group_info) // 释放group info结构 put_group_info(rqstp->rq_cred.cr_group_info); rqstp->rq_cred.cr_group_info = NULL; return 0;}当RPC请求处理完毕后需要执行这个函数,这个函数的主要任务也是释放内存。auth_domain_put()减少auth_domain的引用计数,当计数减到0时执行svcauth_unix_domain_release()。
- RPC中的UNIX认证
- Hadoop中的RPC实现
- Hadoop中的RPC
- Android中的RPC
- openstack中的rpc通信
- Python中的 XML-RPC
- Nova中的RPC
- 微软认证考试,还有各种unix认证考试
- UNIX SSH证书认证来取代普通的密码认证
- Unix下使用Apache实现用户认证
- Hadoop中的RPC实现(概述)
- Hadoop中的RPC实现(概述)
- Hadoop中的RPC调用原理
- Hadoop中的RPC机制分析
- RPC在neutron中的应用
- openstack中的RPC请求分析
- 使用Hadoop中的RPC框架
- golang中的rpc包用法
- 一生俯首拜阳明
- GPS编码格式及C语言解码
- 裘宗燕-C/C++ 语言中的表达式求值
- 获取Windows Cluster IP的VBS脚本和命令(无powershell)
- 最新51单片机GPS解码程序
- RPC中的UNIX认证
- 云服务器(云主机)与VPS的区别
- Visual Studio通过Property Manager建立的*.vsprops文件定义的宏,优先于环境变量
- [4_1_fence6] Finding Shortest Cycle
- Linux常用命令之tar打包器
- 回溯算法
- crontab坑
- parser解析中文问题,xml
- 收藏的资源