創建反向通道

    是時候講講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,表示反向通道工作正常。反向通道終於建立起來了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章