是時候講講NFS客戶端和服務器之間反向通道的建立過程了,因爲馬上就會講到delegation了。一般情況下,NFS客戶端向服務器發送RPC請求,服務器進行處理,這個通道稱爲正向通道。而delegation需要使用反向通道,當服務器檢測到文件衝突後,後向客戶端發送RPC請求。客戶端接收到請求後釋放delegation。與反向通道相關的數據結構是struct nfs4_cb_conn,這個數據結構保存了反向通道中RPC客戶端地址、RPC服務器地址等信息,這個數據結構的定義如下:
struct nfs4_cb_conn {
// 這是反向通道中RPC服務器地址,也就是NFS客戶端地址
struct sockaddr_storage cb_addr;
// 這是反向通道中RPC客戶端地址,也就是NFS服務器地址
struct sockaddr_storage cb_saddr;
// 這是反向通道中RPC服務器地址的長度
size_t cb_addrlen;
// 這是CALLBACK服務的編號 0x40000000
u32 cb_prog;
// NFS客戶端中的一個編號,每執行一個SETCLIENTID請求,這個編號加1
// 這是NFS客戶端中NFS文件系統的編號,因爲NFS客戶端可能掛載了多個文件系統
u32 cb_ident; /* minorversion 0 only */
// 這是NFSv4.1中區分不同文件系統的方法
struct svc_xprt *cb_xprt; /* minorversion 1 only */
};
nfs4_cb_conn中cb_ident和cb_xprt的作用相同,都是爲了區分NFS客戶端掛載的文件系統,只不過cb_ident是NFSv4.0中的方法,cb_xprt是NFSv4.1中的方法。
NFS服務器通過反向通道向NFS客戶端發送RPC請求,稱爲CALLBACK請求,每個CALLBACK請求用數據結構nfs4_callback表示,這個數據結構的定義如下:
struct nfsd4_callback {
// 這是CALLBACK請求使用的一個私有字段,
// 不同的CALLBACK請求中這個字段的值不一樣
void *cb_op;
// 向哪個NFS客戶端發送CALLBACK請求
struct nfs4_client *cb_clp;
// 每個客戶端上可以鏈接多個CALLBACK請求,這些CALLBACK請求構成了一個鏈表
struct list_head cb_per_client;
// 這是NFSv4次版本號
u32 cb_minorversion;
// 這裏包含了CALLBACK請求的信息,如CALLBACK例程編號,參數,返回值。
struct rpc_message cb_msg;
// 這是CALLBACK請求執行前後調用的函數
const struct rpc_call_ops *cb_ops;
// 這是工作隊列中工作的數據結構,實現CALLBACK請求的延後執行,處理函數是nfsd4_do_callback_rpc()
struct work_struct cb_work;
// CALLBACK請求執行結束了.
bool cb_done;
};
nfs4_client結構中與反向通道相關的字段如下:
struct nfs4_client {
// 這個字段中保存了反向通道的信息
struct nfs4_cb_conn cl_cb_conn;
// 這是反向通道中的RPC客戶端結構,NFS服務器通過這個客戶端發送CALLBACK請求
struct rpc_clnt *cl_cb_client;
// 這是客戶端NFS文件系統的編號
u32 cl_cb_ident;
// 這是反向通道的狀態
int cl_cb_state; // CALLBACK狀態
// 這是NFSPROC4_CLNT_CB_NULL請求中的數據結構
struct nfsd4_callback cl_cb_null;
// 這是一個鏈表,鏈表中的數據結構是nfsd4_callback,表示這個NFS客戶端上需要執行的CALLBACK請求
struct list_head cl_callbacks;
}
反向通道是在SETCIENTID請求的處理過程中創建的。SETCLIENTID請求報文中包含了創建反向通道的信息。當服務器接收到SETCLIENTID請求後,會創建一個新的nfs4_client結構表示NFS客戶端,然後調用gen_callback()創建NFS客戶端和服務器之間的反向通道,這個函數的代碼如下:
參數clp:這是新建立的NFS客戶端結構,代表一個客戶端
參數se:這裏包含了從SETCLIENTID請求報文中解析出的數據
參數rqstp:表示一個RPC請求
static void
gen_callback(struct nfs4_client *clp, struct nfsd4_setclientid *se, struct svc_rqst *rqstp)
{
// 這是NFS客戶端中保存反向通道信息的字段.
struct nfs4_cb_conn *conn = &clp->cl_cb_conn;
// 取出NFS客戶端的地址,也就是反向通道中RPC服務器的地址
struct sockaddr *sa = svc_addr(rqstp);
u32 scopeid = rpc_get_scope_id(sa);
unsigned short expected_family;
/* Currently, we only support tcp and tcp6 for the callback channel */
// 目前只支持TCP和TCP6兩種傳輸層協議,檢查使用的網絡協議
if (se->se_callback_netid_len == 3 && // CALLBACK中使用的傳輸層協議的長度
!memcmp(se->se_callback_netid_val, "tcp", 3)) // TCP協議
expected_family = AF_INET;
else if (se->se_callback_netid_len == 4 &&
!memcmp(se->se_callback_netid_val, "tcp6", 4)) // TCP6協議
expected_family = AF_INET6;
else
goto out_err;
// 設置反向通道中RPC服務器的地址
conn->cb_addrlen = rpc_uaddr2sockaddr(&init_net, se->se_callback_addr_val,
se->se_callback_addr_len,
(struct sockaddr *)&conn->cb_addr,
sizeof(conn->cb_addr));
if (!conn->cb_addrlen || conn->cb_addr.ss_family != expected_family)
goto out_err;
if (conn->cb_addr.ss_family == AF_INET6)
((struct sockaddr_in6 *)&conn->cb_addr)->sin6_scope_id = scopeid;
conn->cb_prog = se->se_callback_prog; // 設置CALLBACK程序的編號
conn->cb_ident = se->se_callback_ident; // 設置客戶端中NFS文件系統的編號
// 設置反向通道中RPC客戶端地址
memcpy(&conn->cb_saddr, &rqstp->rq_daddr, rqstp->rq_daddrlen);
return;
out_err:
conn->cb_addr.ss_family = AF_UNSPEC;
conn->cb_addrlen = 0;
dprintk(KERN_INFO "NFSD: this client (clientid %08x/%08x) "
"will not receive delegations\n",
clp->cl_clientid.cl_boot, clp->cl_clientid.cl_id);
return;
}
這個函數很簡單,代碼中進行了註釋。看到代碼後我們會發現gen_callback()根本沒有建立反向通道,只是創建了一個新的nfs4_cb_conn結構,然後用SETCLIENTID請求報文中的數據填充了這個結構。沒錯,就是這樣的。那麼反向通道什麼時候建立的呢?其實是在SETCLIENTID_CONFIRM請求中創建的。SETCLIENTID_CONFIRM對SETCLIENTID請求中創建的客戶端進行了確認,然後調用nfsd4_probe_callback()創建了CALLBACK請求使用的RPC客戶端,並且向NFS客戶端發送NULL請求,測試反向通道是否可用,這個函數的代碼如下:
void nfsd4_probe_callback(struct nfs4_client *clp)
{
// 設置標誌位,現在還不確定反向通道是否可用。
clp->cl_cb_state = NFSD4_CB_UNKNOWN;
// NFSD4_CLIENT_CB_UPDATE表示反向通道中的信息(如客戶端地址、服務器端地址等)是最新的,
// 可以發起CALLBACK請求了.
set_bit(NFSD4_CLIENT_CB_UPDATE, &clp->cl_flags);
// 創建反向通道使用的RPC客戶端,發起NULL調用,測試反向通道使用可用.
do_probe_callback(clp);
}
do_probe_callback()的代碼如下:
static void do_probe_callback(struct nfs4_client *clp)
{
struct nfsd4_callback *cb = &clp->cl_cb_null;
cb->cb_op = NULL;
cb->cb_clp = clp;
// 這是NULL請求的編號,參數和返回值。按照RPC的慣例,NULL請求的主要目的是測試
// RPC客戶端和服務器是否連通,沒有參數和返回值。
cb->cb_msg.rpc_proc = &nfs4_cb_procedures[NFSPROC4_CLNT_CB_NULL];
cb->cb_msg.rpc_argp = NULL;
cb->cb_msg.rpc_resp = NULL;
// 設置RPC請求中使用的認證方式,這裏設置爲UNIX認證.
cb->cb_msg.rpc_cred = callback_cred;
cb->cb_ops = &nfsd4_cb_probe_ops; // 這是RPC請求執行過程中的輔助函數
// 發起RPC請求,處理函數是nfsd4_do_callback_rpc()
run_nfsd4_cb(cb);
}
NULL是RPC中的一個通用例程,這個例程的主要作用是測試RPC客戶端和服務器是否連通,這個例程沒有參數,沒有返回值,採用NULL認證。但是這裏的NULL歷程採用了UNIX認證,callback_cred是NFS服務器端設置的一個UNIX認證信息。估計這裏沒有采用NULL認證的原因是因爲這裏不僅僅檢測RPC通道是否連通,還在測試反向通道是否連通,也就是說反向通道上的其他請求是否能正常執行。do_probe_callback()設置好了NULL請求中的參數信息,然後就調用了run_nfsd4_cb(),這個函數將這個請求掛載到了工作隊列callback_wq中,這個工作的處理函數是nfsd4_do_callback_rpc(),代碼如下:
void nfsd4_do_callback_rpc(struct work_struct *w)
{
// 取出nfsd4_callback結構
struct nfsd4_callback *cb = container_of(w, struct nfsd4_callback, cb_work);
struct nfs4_client *clp = cb->cb_clp; // 這是NFS客戶端的結構
struct rpc_clnt *clnt;
if (clp->cl_flags & NFSD4_CLIENT_CB_FLAG_MASK)
nfsd4_process_cb_update(cb); // 更新RPC客戶端的信息.
clnt = clp->cl_cb_client; // 取出RPC客戶端結構
if (!clnt) {
/* Callback channel broken, or client killed; give up: */
nfsd4_release_cb(cb); // 反向通道不可用了,不能執行RPC請求了
return;
}
// 發起一個異步RPC調用
rpc_call_async(clnt, &cb->cb_msg, RPC_TASK_SOFT | RPC_TASK_SOFTCONN,
cb->cb_ops, cb);
}
NFSD4_CLIENT_CB_FLAG_MASK的定義如下:
#define NFSD4_CLIENT_CB_FLAG_MASK (1 << NFSD4_CLIENT_CB_UPDATE | \
1 << NFSD4_CLIENT_CB_KILL)
NFSD4_CLIENT_CB_UPDATE表示反向通道的信息更新了,需要更新反向通道使用的RPC客戶端。NFSD4_CLIENT_CB_KILL表示用戶請求關閉反向通道。當檢測到這兩個標誌位時會調用nfsd4_process_cb_update()更新RPC客戶端。nfsd4_probe_callback()中設置了標誌位NFSD4_CLIENT_CB_UPDATE,因此nfsd4_process_cb_update()會爲反向通道創建一個新的RPC客戶端,NULL請求就在這個客戶端上執行。如果NULL請求正常結束,就會設置標誌位NFSD4_CB_UP,表示反向通道工作正常。反向通道終於建立起來了。