struct nfsd4_compound_state *cstate,
                      struct nfsd4_destroy_session *sessionid)
 {
-       return -1;      /* stub */
+       struct nfsd4_session *ses;
+       u32 status = nfserr_badsession;
+
+       /* Notes:
+        * - The confirmed nfs4_client->cl_sessionid holds destroyed sessinid
+        * - Should we return nfserr_back_chan_busy if waiting for
+        *   callbacks on to-be-destroyed session?
+        * - Do we need to clear any callback info from previous session?
+        */
+
+       dump_sessionid(__func__, &sessionid->sessionid);
+       spin_lock(&sessionid_lock);
+       ses = find_in_sessionid_hashtbl(&sessionid->sessionid);
+       if (!ses) {
+               spin_unlock(&sessionid_lock);
+               goto out;
+       }
+
+       unhash_session(ses);
+       spin_unlock(&sessionid_lock);
+
+       /* wait for callbacks */
+       shutdown_callback_client(ses->se_client);
+       nfsd4_put_session(ses);
+       status = nfs_ok;
+out:
+       dprintk("%s returns %d\n", __func__, ntohl(status));
+       return status;
 }
 
 __be32
 
 nfsd4_decode_destroy_session(struct nfsd4_compoundargs *argp,
                             struct nfsd4_destroy_session *destroy_session)
 {
-       return nfserr_opnotsupp;        /* stub */
+       DECODE_HEAD;
+       READ_BUF(NFS4_MAX_SESSIONID_LEN);
+       COPYMEM(destroy_session->sessionid.data, NFS4_MAX_SESSIONID_LEN);
+
+       DECODE_TAIL;
 }
 
 static __be32
 nfsd4_encode_destroy_session(struct nfsd4_compoundres *resp, int nfserr,
                             struct nfsd4_destroy_session *destroy_session)
 {
-       /* stub */
        return nfserr;
 }