这篇文章讲讲服务器端RPC报文的处理流程。服务器端RPC报文的处理函数是svc_process,这个函数位于net/sunrpc/svc.c中。这个函数需要一个svc_rqst结构的指针作为参数,svc_rqst是与RPC请求相关的一个数据结构,这里包含了接收到的RPC消息,RPC消息的解析结果也放在这个数据结构中,RPC消息的处理结果也放在这个消息中了。这个数据结构的定义在include/linux/sunrpc/svc.h。由于我主要想讲解NFS,所以很多RPC的知识就略过不讲了。
-
-
-
-
int
-
svc_process(struct svc_rqst *rqstp)
-
{
-
-
-
struct kvec *argv = &rqstp->rq_arg.head[0];
-
-
struct kvec *resv = &rqstp->rq_res.head[0];
-
struct svc_serv *serv = rqstp->rq_server;
-
u32 dir;
-
-
-
-
-
-
rqstp->rq_resused = 1;
-
-
resv->iov_base = page_address(rqstp->rq_respages[0]);
-
resv->iov_len = 0;
-
rqstp->rq_res.pages = rqstp->rq_respages + 1;
-
rqstp->rq_res.len = 0;
-
rqstp->rq_res.page_base = 0;
-
rqstp->rq_res.page_len = 0;
-
rqstp->rq_res.buflen = PAGE_SIZE;
-
rqstp->rq_res.tail[0].iov_base = NULL;
-
rqstp->rq_res.tail[0].iov_len = 0;
-
-
rqstp->rq_xid = svc_getu32(argv);
-
-
dir = svc_getnl(argv);
-
if (dir != 0) {
-
-
svc_printk(rqstp, "bad direction %d, dropping request\n", dir);
-
serv->sv_stats->rpcbadfmt++;
-
svc_drop(rqstp);
-
return 0;
-
}
-
-
-
-
-
-
if (svc_process_common(rqstp, argv, resv))
-
return svc_send(rqstp);
-
else {
-
svc_drop(rqstp);
-
return 0;
-
}
-
}
svc_process_common是主要的处理函数,这个函数的定义如下:
static int svc_process_common(struct svc_rqst *rqstp, struct kvec *argv, struct kvec *resv)
参数rqstp 表示一个RPC请求。
参数argv是一块缓存,这块缓存中保存了接收到的RPC请求报文。
参数resv是一块缓存,这块缓存用来保存组装后的RPC应答报文。
这个函数的处理流程就比较复杂了,基本上包含下面五个处理步骤:
(1)组装RPC报文头基本信息
(2)解析RPC服务信息
(3)对用户身份进行验证
(4)检查服务器端是否可以处理这个RPC请求
(5)处理RPC请求
下面详细讲解各个步骤
(1)组装RPC报文头基本信息
svc_process()中解析了RPC请求报文头的前两个字段(XID和Message Type),这里解析解析请求报文中的第3个字段,组装了应答报文中的前三个字段。处理程序如下:
-
svc_putu32(resv, rqstp->rq_xid);
-
vers = svc_getnl(argv);
-
-
-
svc_putnl(resv, 1);
-
-
-
if (vers != 2)
-
goto err_bad_rpc;
-
-
-
-
-
reply_statp = resv->iov_base + resv->iov_len;
-
svc_putnl(resv, 0);
(2)解析RPC服务信息
-
-
rqstp->rq_prog = prog = svc_getnl(argv);
-
-
rqstp->rq_vers = vers = svc_getnl(argv);
-
-
rqstp->rq_proc = proc = svc_getnl(argv);
也很简单,直接从RPC请求消息中提取数据就可以了。
(3)对用户身份进行验证
-
auth_res = svc_authenticate(rqstp, &auth_stat);
-
-
if (auth_res == SVC_OK && progp) {
-
auth_stat = rpc_autherr_badcred;
-
auth_res = progp->pg_authenticate(rqstp);
-
}
这里包含了两个函数svc_authenticate和progp->pg_authenticate。svc_authenticate的作用是解析RPC请求报文中的认证信息, progp->pg_authenticate的作用是根据解析出的信息对用户身份进行验证。这两个函数都和采用的认证方式有关,我们这里只简单介绍,下一篇文章中将以UNIX认证为例详细讲解解析和认证过程。
解析RPC消息中用户信息的函数是svc_authenticate()。rqstp是输入参数,表示一个RPC请求;auth_stat是输出参数,表示解析结果。这个函数的代码如下:
-
int
-
svc_authenticate(struct svc_rqst *rqstp, __be32 *authp)
-
{
-
rpc_authflavor_t flavor;
-
struct auth_ops *aops;
-
-
*authp = rpc_auth_ok;
-
-
-
-
flavor = svc_getnl(&rqstp->rq_arg.head[0]);
-
-
dprintk("svc: svc_authenticate (%d)\n", flavor);
-
-
spin_lock(&authtab_lock);
-
-
-
if (flavor >= RPC_AUTH_MAXFLAVOR || !(aops = authtab[flavor]) ||
-
!try_module_get(aops->owner)) {
-
spin_unlock(&authtab_lock);
-
*authp = rpc_autherr_badcred;
-
return SVC_DENIED;
-
}
-
spin_unlock(&authtab_lock);
-
-
rqstp->rq_authop = aops;
-
-
-
return aops->accept(rqstp, authp);
-
}
这个函数的主要作用是解析RPC请求消息中的Credential字段和Verifier字段,然后填充RPC应答消息中的Verifier字段。这个函数只解析了Credential中的第一个字段,这个字段表示认证类型,然后就调用相应认证方式中的函数进行处理了。目前Linux中支持下列认证方式
-
enum rpc_auth_flavors {
-
RPC_AUTH_NULL = 0,
-
RPC_AUTH_UNIX = 1,
-
RPC_AUTH_SHORT = 2,
-
RPC_AUTH_DES = 3,
-
RPC_AUTH_KRB = 4,
-
RPC_AUTH_GSS = 6,
-
RPC_AUTH_MAXFLAVOR = 8,
-
-
RPC_AUTH_GSS_KRB5 = 390003,
-
RPC_AUTH_GSS_KRB5I = 390004,
-
RPC_AUTH_GSS_KRB5P = 390005,
-
RPC_AUTH_GSS_LKEY = 390006,
-
RPC_AUTH_GSS_LKEYI = 390007,
-
RPC_AUTH_GSS_LKEYP = 390008,
-
RPC_AUTH_GSS_SPKM = 390009,
-
RPC_AUTH_GSS_SPKMI = 390010,
-
RPC_AUTH_GSS_SPKMP = 390011,
-
};
RPC_AUTH_MAXFLAVOR表示认证方式种类,下面的认证方式全部属于RPC_AUTH_GSS认证的子类。每种认证方式都需要实现下面的函数
-
struct auth_ops {
-
char * name;
-
struct module *owner;
-
int flavour;
-
int (*accept)(struct svc_rqst *rq, __be32 *authp);
-
int (*release)(struct svc_rqst *rq);
-
void (*domain_release)(struct auth_domain *);
-
int (*set_client)(struct svc_rqst *rq);
-
};
下篇文章中我们会详细介绍UNIX认证的操作过程,这里就不深入讲解了。
(4)检查服务器端是否可以处理这个RPC请求
-
progp = serv->sv_program;
-
-
-
-
for (progp = serv->sv_program; progp; progp = progp->pg_next)
-
if (prog == progp->pg_prog)
-
break;
-
-
-
-
if (progp == NULL)
-
goto err_bad_prog;
-
-
-
if (vers >= progp->pg_nvers ||
-
!(versp = progp->pg_vers[vers]))
-
goto err_bad_vers;
-
-
-
-
-
-
procp = versp->vs_proc + proc;
-
if (proc >= versp->vs_nproc || !procp->pc_func)
-
goto err_bad_proc;
-
rqstp->rq_procinfo = procp;
-
-
-
serv->sv_stats->rpccnt++;
-
-
-
statp = resv->iov_base +resv->iov_len;
-
-
-
svc_putnl(resv, RPC_SUCCESS);
该解释的内容都写在注释中了,这里说明一下Linux中保存RPC处理程序的数据结构。首先是RPC例程的数据结构。
-
struct svc_procedure {
-
-
svc_procfunc pc_func;
-
-
-
kxdrproc_t pc_decode;
-
-
-
kxdrproc_t pc_encode;
-
-
kxdrproc_t pc_release;
-
-
unsigned int pc_argsize;
-
-
unsigned int pc_ressize;
-
-
unsigned int pc_count;
-
-
-
unsigned int pc_cachetype;
-
-
unsigned int pc_xdrressize;
-
};
下面是RPC版本的数据结构,一个版本中包含多个例程。
-
struct svc_version {
-
-
u32 vs_vers;
-
-
u32 vs_nproc;
-
-
-
struct svc_procedure * vs_proc;
-
-
u32 vs_xdrsize;
-
-
-
unsigned int vs_hidden : 1;
-
-
-
-
-
-
-
-
-
int (*vs_dispatch)(struct svc_rqst *, __be32 *);
-
};
最后是RPC服务程序的数据结构,每个RPC服务程序包含多个版本。
-
struct svc_program {
-
-
struct svc_program * pg_next;
-
-
u32 pg_prog;
-
-
unsigned int pg_lovers;
-
-
unsigned int pg_hivers;
-
-
unsigned int pg_nvers;
-
-
struct svc_version ** pg_vers;
-
-
char * pg_name;
-
-
char * pg_class;
-
-
struct svc_stat * pg_stats;
-
-
int (*pg_authenticate)(struct svc_rqst *);
-
};
对于NFS服务来说,NFS服务对应的数据结构是svc_program。NFS目前包含3个不同的版本(NFSV2、NFSV3、NFSV4),每个版本对应一个svc_version结构。每个版本中包含多个处理例程,每个处理例程对应一个svc_procedure结构。
还有一点需要注意,svc_program中包含一个函数pg_authenticate,需要注意这个函数和前面提到的认证方式中accept的区别。accept的作用是解析RPC报文中的认证信息,只是解析数据,但是不对用户进行认证。pg_authenticate才是真正的认证函数。
(5)处理RPC请求
-
-
if (!versp->vs_dispatch) {
-
-
xdr = procp->pc_decode;
-
-
if (xdr && !xdr(rqstp, argv->iov_base, rqstp->rq_argp))
-
goto err_garbage;
-
-
-
*statp = procp->pc_func(rqstp, rqstp->rq_argp, rqstp->rq_resp);
-
-
-
if (rqstp->rq_dropme) {
-
if (procp->pc_release)
-
procp->pc_release(rqstp, NULL, rqstp->rq_resp);
-
goto dropit;
-
}
-
-
if (*statp == rpc_success &&
-
(xdr = procp->pc_encode) &&
-
!xdr(rqstp, resv->iov_base+resv->iov_len, rqstp->rq_resp)) {
-
dprintk("svc: failed to encode reply\n");
-
-
*statp = rpc_system_err;
-
}
-
} else {
-
dprintk("svc: calling dispatcher\n");
-
if (!versp->vs_dispatch(rqstp, statp)) {
-
-
if (procp->pc_release)
-
procp->pc_release(rqstp, NULL, rqstp->rq_resp);
-
goto dropit;
-
}
-
}
经过步骤1--步骤4的处理,我们已经解析了RPC请求报头的数据,找到了RPC请求的处理函数,最后一步就是开始处理这个请求了。处理一个RPC请求的函数是svc_version结构中的vs_dispatch函数。如果RPC程序没有定义这个函数,就按照标准的流程进行处理。在标准的流程中,首先调用svc_procedure结构中的pc_decode函数,这个函数的内容是解析RPC报文的净荷,对于NFS服务来说,这个函数的作用就是解析RPC报文中的NFS数据,这些数据就是处理函数的参数。真正的处理函数是svc_procedure结构中的pc_func函数,每个例程都需要定义自己的处理函数。处理完毕后,需要将处理结果封装在RPC应答报文中返回给客户端。比如对于READ操作,我们需要将读取的数据封装在RPC报文中返回,这个封装过程是由svc_procedure结构中的pc_encode函数实现的。
NFS服务定义了自己的vs_dispatch函数,NFSV2、NFSV3、NFSV4使用了同一个vs_dispatch函数,这个函数的定义是nfsd_dispatch,这个函数定义在fs/nfsd/nfssvc.c中,处理流程基本上和上面讲的流程相同,就不讲解了。
Linux内核RPC请求过程,布布扣,bubuko.com
Linux内核RPC请求过程
原文:http://blog.csdn.net/panfengyun12345/article/details/24045305