Re: [RFC PATCH 16/36] cifs: Rewrite base TCP transmission
Stefan Metzmacher <[email protected]> Tue, 19 May 2026 12:36:21 +0200
| Newsgroups | dev.linux.lists.netfs,org.kernel.vger.linux-cifs,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Am 19.05.26 um 12:21 schrieb David Howells: > Rewrite the base TCP transmission in cifs to copy all the content in a > (compound) message into a buffer of pages in a bvecq chain. In future, the > pages in this bvecq chain will be allocated from the netmem allocator so > that the advance DMA/IOMMU handling will be done. > > This list of pages can then be attached to an ITER_BVEC_QUEUE-type iov_iter > and passed in a single call to sendmsg() with MSG_SPLICE_PAGES, thereby > avoiding the need to copy the data in the TCP stack. If smbd_send() would just call smbdirect_connection_send_iter() the iov_iter_advance(iter, 4); is no longer needed. It also allows me to make these static: smbdirect_connection_send_batch_flush smbdirect_init_send_batch_storage smbdirect_connection_send_single_iter smbdirect_connection_send_wait_zero_pending And it will help with the transition to use sendmsg() with MSG_SPLICE_PAGES for smbdirect too. Below are some inline comments basically saying the same. Thanks! metze > The encryption code can also be simplified as it only needs to encrypt the > data stored in those pages, with the bonus that the content in the pages is > correctly aligned for the encryption in place such that it does not need to > copy the data whilst encrypting it. > > It could be arranged for the data to be held in contiguous pages if > possible and if necessary such that crypto can be offloaded to devices that > only permit a single contiguous buffer. > > Note that with this patch, "mount -o compress,seal" will now compress > writes and then encrypt them. > > Signed-off-by: David Howells <[email protected]> > cc: Steve French <[email protected]> > cc: Paulo Alcantara <[email protected]> > cc: Shyam Prasad N <[email protected]> > cc: Tom Talpey <[email protected]> > cc: [email protected] > cc: [email protected] > cc: [email protected] > --- > fs/smb/client/cifsglob.h | 6 +- > fs/smb/client/cifsproto.h | 3 +- > fs/smb/client/compress.c | 146 ++++++++----- > fs/smb/client/compress.h | 10 +- > fs/smb/client/smb1ops.c | 5 +- > fs/smb/client/smb2ops.c | 273 +----------------------- > fs/smb/client/smb2proto.h | 4 + > fs/smb/client/smb2transport.c | 113 ++++++++++ > fs/smb/client/smbdirect.c | 105 ++------- > fs/smb/client/smbdirect.h | 5 +- > fs/smb/client/transport.c | 387 +++++++++++++++++++++++----------- > 11 files changed, 517 insertions(+), 540 deletions(-) > > diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h > index 2cb858495bc1..f7c12d24e2ea 100644 > --- a/fs/smb/client/cifsglob.h > +++ b/fs/smb/client/cifsglob.h > @@ -571,8 +571,10 @@ struct smb_version_operations { > long (*fallocate)(struct file *, struct cifs_tcon *, int, loff_t, > loff_t); > /* init transform (compress/encrypt) request */ > - int (*init_transform_rq)(struct TCP_Server_Info *, int num_rqst, > - struct smb_rqst *, struct smb_rqst *); > + int (*init_transform_rq)(struct TCP_Server_Info *server, > + int num_rqst, const struct smb_rqst *rqst, > + struct smb2_transform_hdr *tr_hdr, > + struct iov_iter *iter); > enum securityEnum (*select_sectype)(struct TCP_Server_Info *, > enum securityEnum); > int (*next_header)(struct TCP_Server_Info *server, char *buf, > diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h > index 8dedfd677f8d..1c9ac67b7294 100644 > --- a/fs/smb/client/cifsproto.h > +++ b/fs/smb/client/cifsproto.h > @@ -106,8 +106,7 @@ int compound_send_recv(const unsigned int xid, struct cifs_ses *ses, > int *resp_buf_type, struct kvec *resp_iov); > int cifs_sync_mid_result(struct smb_message *mid, > struct TCP_Server_Info *server); > -int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > - struct smb_rqst *rqst); > +int __smb_send_rqst(struct TCP_Server_Info *server, struct iov_iter *iter); > int wait_for_free_request(struct TCP_Server_Info *server, const int flags, > unsigned int *instance); > int cifs_wait_mtu_credits(struct TCP_Server_Info *server, size_t size, > diff --git a/fs/smb/client/compress.c b/fs/smb/client/compress.c > index be9023f841e6..a9496983ac56 100644 > --- a/fs/smb/client/compress.c > +++ b/fs/smb/client/compress.c > @@ -16,6 +16,7 @@ > #include <linux/kernel.h> > #include <linux/uio.h> > #include <linux/sort.h> > +#include <linux/vmalloc.h> > > #include "cifsglob.h" > #include "../common/smb2pdu.h" > @@ -191,7 +192,7 @@ static int collect_sample(const struct iov_iter *source, ssize_t max, u8 *sample > * Tests shows that this function is quite reliable in predicting data compressibility, > * matching close to 1:1 with the behaviour of LZ77 compression success and failures. > */ > -static bool is_compressible(const struct iov_iter *data) > +bool is_compressible(const struct iov_iter *data) > { > const size_t read_size = SZ_2K, bkt_size = 256, max = SZ_4M; > struct bucket *bkt = NULL; > @@ -295,74 +296,123 @@ bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq) > if (le32_to_cpu(wreq->Length) < SMB_COMPRESS_MIN_LEN) > return false; > > - return is_compressible(&rq->rq_iter); > + return true; > } > > return (shdr->Command == SMB2_READ); > } > > -int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, compress_send_fn send_fn) > +/* > + * vmap the pages from a BVECQ-type iterator. > + */ > +static void *vmap_bvecq(struct iov_iter *iter, pgprot_t prot) > { > - struct iov_iter iter; > - u32 slen, dlen; > - void *src, *dst = NULL; > - int ret; > + struct page **pages = NULL; > + ssize_t size, offset; > + void *map; > > - if (!server || !rq || !rq->rq_iov || !rq->rq_iov->iov_base) > - return -EINVAL; > + if (WARN_ON(!iov_iter_is_bvecq(iter))) > + return ERR_PTR(-EIO); > > - if (rq->rq_iov->iov_len != sizeof(struct smb2_write_req)) > - return -EINVAL; > + size = iov_iter_extract_pages(iter, &pages, INT_MAX, INT_MAX, 0, &offset); > + if (size < 0) > + return ERR_PTR(size); > > - slen = iov_iter_count(&rq->rq_iter); > - src = kvzalloc(slen, GFP_KERNEL); > - if (!src) { > - ret = -ENOMEM; > - goto err_free; > + if (size == 0 || offset > 0) { > + kvfree(pages); > + return ERR_PTR(-EIO); > } > > - /* Keep the original iter intact. */ > - iter = rq->rq_iter; > + map = vmap(pages, DIV_ROUND_UP(size, PAGE_SIZE), 0, prot); > + kvfree(pages); > + if (!map) > + return ERR_PTR(-ENOMEM); > + return map; > +} > + > +int smb_compress(struct TCP_Server_Info *server, struct iov_iter *iter, > + struct bvecq **bq) > +{ > + struct smb2_compression_hdr *z_hdr; > + struct smb2_write_req *w_hdr; > + struct smb2_hdr *shdr; > + struct iov_iter tmp; > + struct bvecq *sbq = *bq, *dbq = NULL; > + void *src, *dst = NULL; > + u32 slen, dlen, hlen, datalen; > + int ret; > > - if (!copy_from_iter_full(src, slen, &iter)) { > - ret = smb_EIO(smb_eio_trace_compress_copy); > + /* We need contiguous buffers for the compression algorithm. */ > + slen = iov_iter_count(iter); > + src = vmap_bvecq(iter, PAGE_KERNEL_RO); > + if (IS_ERR(src)) { > + ret = PTR_ERR(src); > + src = NULL; > goto err_free; > } > > + shdr = src; > + hlen = le16_to_cpu(shdr->StructureSize); > + w_hdr = src; > + hlen = le16_to_cpu(w_hdr->DataOffset); > + datalen = le32_to_cpu(w_hdr->Length); > + if (datalen != slen - hlen) { > + pr_warn("datalen %x != slen-hlen %x\n", datalen, slen - hlen); > + return -EMSGSIZE; > + } > + > + /* > + * This is just overprovisioning, as the algorithm will error out if @dst reaches 7/8 > + * of @slen. > + */ > dlen = lz77_compressed_alloc_size(slen); > - dst = kvzalloc(dlen, GFP_KERNEL); > - if (!dst) { > + dlen = sizeof(*z_hdr) + slen; > + dbq = bvecq_alloc_buffer(dlen, GFP_NOFS); > + if (!dbq) { > ret = -ENOMEM; > goto err_free; > } > > - ret = lz77_compress(src, slen, dst, &dlen); > - if (!ret) { > - struct smb2_compression_hdr hdr = { 0 }; > - struct smb_rqst comp_rq = { .rq_nvec = 3, }; > - struct kvec iov[3]; > - > - hdr.ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID; > - hdr.OriginalCompressedSegmentSize = cpu_to_le32(slen); > - hdr.CompressionAlgorithm = SMB3_COMPRESS_LZ77; > - hdr.Flags = SMB2_COMPRESSION_FLAG_NONE; > - hdr.Offset = cpu_to_le32(rq->rq_iov[0].iov_len); > - > - iov[0].iov_base = &hdr; > - iov[0].iov_len = sizeof(hdr); > - iov[1] = rq->rq_iov[0]; > - iov[2].iov_base = dst; > - iov[2].iov_len = dlen; > - > - comp_rq.rq_iov = iov; > - > - ret = send_fn(server, 1, &comp_rq); > - } else if (ret == -EMSGSIZE || dlen >= slen) { > - ret = send_fn(server, 1, rq); > + iov_iter_bvec_queue(&tmp, ITER_DEST, dbq, 1, 0, dlen); > + dst = vmap_bvecq(&tmp, PAGE_KERNEL); > + if (IS_ERR(dst)) { > + ret = PTR_ERR(dst); > + dst = NULL; > + goto err_free; > } > -err_free: > - kvfree(dst); > - kvfree(src); > + z_hdr = dst; > + dst += sizeof(*z_hdr) + hlen; > + dlen -= sizeof(*z_hdr) + hlen; > + > + ret = lz77_compress(src + hlen, slen - hlen, dst, &dlen); > + if (ret) > + goto err_free; > + > + dlen += sizeof(*z_hdr) + hlen; > + dst -= hlen; > + memcpy(dst, src, hlen); > > + z_hdr->ProtocolId = SMB2_COMPRESSION_TRANSFORM_ID; > + z_hdr->OriginalCompressedSegmentSize = cpu_to_le32(datalen); > + z_hdr->CompressionAlgorithm = SMB3_COMPRESS_LZ77; > + z_hdr->Flags = SMB2_COMPRESSION_FLAG_NONE; > + z_hdr->Offset = cpu_to_le32(hlen); > + > + vunmap(z_hdr); > + vunmap(src); > + > + dbq->bv[0] = sbq->bv[0]; > + memset(&sbq->bv[0], 0, sizeof(sbq->bv[0])); > + bvecq_shorten_buffer(dbq, 1, dlen); > + bvecq_dump(dbq); > + > + bvecq_put(sbq); > + *bq = dbq; > + return dlen; > + > +err_free: > + vunmap(z_hdr); > + vunmap(src); > + bvecq_put(dbq); > return ret; > } > diff --git a/fs/smb/client/compress.h b/fs/smb/client/compress.h > index 2679baca129b..48812b0e7476 100644 > --- a/fs/smb/client/compress.h > +++ b/fs/smb/client/compress.h > @@ -30,9 +30,10 @@ > typedef int (*compress_send_fn)(struct TCP_Server_Info *, int, struct smb_rqst *); > > > -int smb_compress(struct TCP_Server_Info *server, struct smb_rqst *rq, > - compress_send_fn send_fn); > +int smb_compress(struct TCP_Server_Info *server, struct iov_iter *iter, > + struct bvecq **bq); > bool should_compress(const struct cifs_tcon *tcon, const struct smb_rqst *rq); > +bool is_compressible(const struct iov_iter *data); > > /* > * smb_compress_alg_valid() - Validate a compression algorithm. > @@ -58,11 +59,14 @@ static __always_inline int smb_compress_alg_valid(__le16 alg, bool valid_none) > return false; > } > #else /* !CONFIG_CIFS_COMPRESSION */ > -static inline int smb_compress(void *unused1, void *unused2, void *unused3) > +static inline > +int smb_compress(struct TCP_Server_Info *server, struct iov_iter *iter, > + struct bvecq **bq) > { > return -EOPNOTSUPP; > } > > +static inline bool is_compressible(const struct iov_iter *data) { return false; } > static inline bool should_compress(void *unused1, void *unused2) > { > return false; > diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c > index 042d44788321..8477440bbcc2 100644 > --- a/fs/smb/client/smb1ops.c > +++ b/fs/smb/client/smb1ops.c > @@ -142,6 +142,7 @@ send_nt_cancel(struct cifs_ses *ses, struct TCP_Server_Info *server, > struct smb_rqst *rqst, struct smb_message *smb, > unsigned int xid) > { > + struct iov_iter iter; > struct smb_hdr *in_buf = (struct smb_hdr *)rqst->rq_iov[0].iov_base; > struct kvec iov[1]; > struct smb_rqst crqst = { .rq_iov = iov, .rq_nvec = 1 }; > @@ -162,13 +163,15 @@ send_nt_cancel(struct cifs_ses *ses, struct TCP_Server_Info *server, > return rc; > } > > + iov_iter_kvec(&iter, ITER_SOURCE, iov, 1, iov[0].iov_len); > + > /* > * The response to this call was already factored into the sequence > * number when the call went out, so we must adjust it back downward > * after signing here. > */ > --server->sequence_number; > - rc = __smb_send_rqst(server, 1, &crqst); > + rc = __smb_send_rqst(server, &iter); > if (rc < 0) > server->sequence_number--; > > diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c > index 3c30417a3ea6..6125184d9826 100644 > --- a/fs/smb/client/smb2ops.c > +++ b/fs/smb/client/smb2ops.c > @@ -2746,9 +2746,7 @@ void > smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst) > { > struct smb2_hdr *shdr; > - struct cifs_ses *ses = tcon->ses; > - struct TCP_Server_Info *server = ses->server; > - unsigned long len = smb_rqst_len(server, rqst); > + size_t len = 0; > int num_padding; > > shdr = (struct smb2_hdr *)(rqst->rq_iov[0].iov_base); > @@ -2757,6 +2755,10 @@ smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst) > return; > } > > + for (int i = 0; i < rqst->rq_nvec; i++) > + len += rqst->rq_iov[i].iov_len; > + len += iov_iter_count(&rqst->rq_iter); > + > /* SMB headers in a compound are 8 byte aligned. */ > if (IS_ALIGNED(len, 8)) > goto out; > @@ -4326,105 +4328,6 @@ smb2_dir_needs_close(struct cifsFileInfo *cfile) > return !cfile->invalidHandle; > } > > -static void > -fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len, > - struct smb_rqst *old_rq, __le16 cipher_type) > -{ > - struct smb2_hdr *shdr = > - (struct smb2_hdr *)old_rq->rq_iov[0].iov_base; > - > - memset(tr_hdr, 0, sizeof(struct smb2_transform_hdr)); > - tr_hdr->ProtocolId = SMB2_TRANSFORM_PROTO_NUM; > - tr_hdr->OriginalMessageSize = cpu_to_le32(orig_len); > - tr_hdr->Flags = cpu_to_le16(0x01); > - if ((cipher_type == SMB2_ENCRYPTION_AES128_GCM) || > - (cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > - get_random_bytes(&tr_hdr->Nonce, SMB3_AES_GCM_NONCE); > - else > - get_random_bytes(&tr_hdr->Nonce, SMB3_AES_CCM_NONCE); > - memcpy(&tr_hdr->SessionId, &shdr->SessionId, 8); > -} > - > -static void *smb2_aead_req_alloc(struct crypto_aead *tfm, const struct smb_rqst *rqst, > - int num_rqst, const u8 *sig, u8 **iv, > - struct aead_request **req, struct sg_table *sgt, > - unsigned int *num_sgs) > -{ > - unsigned int req_size = sizeof(**req) + crypto_aead_reqsize(tfm); > - unsigned int iv_size = crypto_aead_ivsize(tfm); > - unsigned int len; > - u8 *p; > - > - *num_sgs = cifs_get_num_sgs(rqst, num_rqst, sig); > - if (IS_ERR_VALUE((long)(int)*num_sgs)) > - return ERR_PTR(*num_sgs); > - > - len = iv_size; > - len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1); > - len = ALIGN(len, crypto_tfm_ctx_alignment()); > - len += req_size; > - len = ALIGN(len, __alignof__(struct scatterlist)); > - len += array_size(*num_sgs, sizeof(struct scatterlist)); > - > - p = kzalloc(len, GFP_NOFS); > - if (!p) > - return ERR_PTR(-ENOMEM); > - > - *iv = (u8 *)PTR_ALIGN(p, crypto_aead_alignmask(tfm) + 1); > - *req = (struct aead_request *)PTR_ALIGN(*iv + iv_size, > - crypto_tfm_ctx_alignment()); > - sgt->sgl = (struct scatterlist *)PTR_ALIGN((u8 *)*req + req_size, > - __alignof__(struct scatterlist)); > - return p; > -} > - > -static void *smb2_get_aead_req(struct crypto_aead *tfm, struct smb_rqst *rqst, > - int num_rqst, const u8 *sig, u8 **iv, > - struct aead_request **req, struct scatterlist **sgl) > -{ > - struct sg_table sgtable = {}; > - unsigned int skip, num_sgs, i, j; > - ssize_t rc; > - void *p; > - > - p = smb2_aead_req_alloc(tfm, rqst, num_rqst, sig, iv, req, &sgtable, &num_sgs); > - if (IS_ERR(p)) > - return ERR_CAST(p); > - > - sg_init_marker(sgtable.sgl, num_sgs); > - > - /* > - * The first rqst has a transform header where the > - * first 20 bytes are not part of the encrypted blob. > - */ > - skip = 20; > - > - for (i = 0; i < num_rqst; i++) { > - struct iov_iter *iter = &rqst[i].rq_iter; > - size_t count = iov_iter_count(iter); > - > - for (j = 0; j < rqst[i].rq_nvec; j++) { > - cifs_sg_set_buf(&sgtable, > - rqst[i].rq_iov[j].iov_base + skip, > - rqst[i].rq_iov[j].iov_len - skip); > - > - /* See the above comment on the 'skip' assignment */ > - skip = 0; > - } > - sgtable.orig_nents = sgtable.nents; > - > - rc = extract_iter_to_sg(iter, count, &sgtable, > - num_sgs - sgtable.nents, 0); > - iov_iter_revert(iter, rc); > - sgtable.orig_nents = sgtable.nents; > - } > - > - cifs_sg_set_buf(&sgtable, sig, SMB2_SIGNATURE_SIZE); > - sg_mark_end(&sgtable.sgl[sgtable.nents - 1]); > - *sgl = sgtable.sgl; > - return p; > -} > - > int > smb2_get_enc_key(struct TCP_Server_Info *server, __u64 ses_id, int enc, u8 *key) > { > @@ -4454,172 +4357,6 @@ smb2_get_enc_key(struct TCP_Server_Info *server, __u64 ses_id, int enc, u8 *key) > return -EAGAIN; > } > > -/* > - * Encrypt @rqst message. @rqst[0] has the following format: > - * iov[0] - transform header (associate data), > - * iov[1-N] - SMB2 header and pages - data to encrypt. > - * On success return encrypted data in iov[1-N] and pages, leave iov[0] > - * untouched. > - */ > -static int > -encrypt_message(struct TCP_Server_Info *server, int num_rqst, > - struct smb_rqst *rqst, struct crypto_aead *tfm) > -{ > - struct smb2_transform_hdr *tr_hdr = > - (struct smb2_transform_hdr *)rqst[0].rq_iov[0].iov_base; > - unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20; > - int rc = 0; > - struct scatterlist *sg; > - u8 sign[SMB2_SIGNATURE_SIZE] = {}; > - u8 key[SMB3_ENC_DEC_KEY_SIZE]; > - struct aead_request *req; > - u8 *iv; > - DECLARE_CRYPTO_WAIT(wait); > - unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize); > - void *creq; > - > - rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), 1, key); > - if (rc) { > - cifs_server_dbg(FYI, "%s: Could not get encryption key. sid: 0x%llx\n", > - __func__, le64_to_cpu(tr_hdr->SessionId)); > - return rc; > - } > - > - if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) || > - (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > - rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE); > - else > - rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE); > - > - if (rc) { > - cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc); > - return rc; > - } > - > - rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE); > - if (rc) { > - cifs_server_dbg(VFS, "%s: Failed to set authsize %d\n", __func__, rc); > - return rc; > - } > - > - creq = smb2_get_aead_req(tfm, rqst, num_rqst, sign, &iv, &req, &sg); > - if (IS_ERR(creq)) > - return PTR_ERR(creq); > - > - if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) || > - (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > - memcpy(iv, (char *)tr_hdr->Nonce, SMB3_AES_GCM_NONCE); > - else { > - iv[0] = 3; > - memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES_CCM_NONCE); > - } > - > - aead_request_set_tfm(req, tfm); > - aead_request_set_crypt(req, sg, sg, crypt_len, iv); > - aead_request_set_ad(req, assoc_data_len); > - > - aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, > - crypto_req_done, &wait); > - > - rc = crypto_wait_req(crypto_aead_encrypt(req), &wait); > - > - if (!rc) > - memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE); > - > - kfree_sensitive(creq); > - return rc; > -} > - > -/* > - * Copy data from an iterator to the pages in a bvec queue buffer. > - */ > -static bool cifs_copy_iter_to_bvecq(struct iov_iter *iter, size_t size, > - struct bvecq *buffer) > -{ > - for (; buffer; buffer = buffer->next) { > - for (int s = 0; s < buffer->nr_slots; s++) { > - struct bio_vec *bv = &buffer->bv[s]; > - size_t part = umin(bv->bv_len, size); > - > - if (copy_page_from_iter(bv->bv_page, bv->bv_offset, > - part, iter) != part) > - return false; > - size -= part; > - } > - } > - return true; > -} > - > -void > -smb3_free_compound_rqst(int num_rqst, struct smb_rqst *rqst) > -{ > - for (int i = 0; i < num_rqst; i++) > - bvecq_put(rqst[i].rq_buffer); > -} > - > -/* > - * This function will initialize new_rq and encrypt the content. > - * The first entry, new_rq[0], only contains a single iov which contains > - * a smb2_transform_hdr and is pre-allocated by the caller. > - * This function then populates new_rq[1+] with the content from olq_rq[0+]. > - * > - * The end result is an array of smb_rqst structures where the first structure > - * only contains a single iov for the transform header which we then can pass > - * to crypt_message(). > - * > - * new_rq[0].rq_iov[0] : smb2_transform_hdr pre-allocated by the caller > - * new_rq[1+].rq_iov[*] == old_rq[0+].rq_iov[*] : SMB2/3 requests > - */ > -static int > -smb3_init_transform_rq(struct TCP_Server_Info *server, int num_rqst, > - struct smb_rqst *new_rq, struct smb_rqst *old_rq) > -{ > - struct smb2_transform_hdr *tr_hdr = new_rq[0].rq_iov[0].iov_base; > - unsigned int orig_len = 0; > - int rc = -ENOMEM; > - > - for (int i = 1; i < num_rqst; i++) { > - struct smb_rqst *old = &old_rq[i - 1]; > - struct smb_rqst *new = &new_rq[i]; > - struct bvecq *buffer = NULL; > - size_t size = iov_iter_count(&old->rq_iter); > - > - orig_len += smb_rqst_len(server, old); > - new->rq_iov = old->rq_iov; > - new->rq_nvec = old->rq_nvec; > - > - if (size > 0) { > - rc = -ENOMEM; > - buffer = bvecq_alloc_buffer(size, GFP_NOFS); > - if (!buffer) > - goto err_free; > - > - new->rq_buffer = buffer; > - iov_iter_bvec_queue(&new->rq_iter, ITER_SOURCE, > - buffer, 0, 0, size); > - > - if (!cifs_copy_iter_to_bvecq(&old->rq_iter, size, buffer)) { > - rc = smb_EIO1(smb_eio_trace_tx_copy_iter_to_buf, size); > - goto err_free; > - } > - } > - } > - > - /* fill the 1st iov with a transform header */ > - fill_transform_hdr(tr_hdr, orig_len, old_rq, server->cipher_type); > - > - rc = encrypt_message(server, num_rqst, new_rq, server->secmech.enc); > - cifs_dbg(FYI, "Encrypt message returned %d\n", rc); > - if (rc) > - goto err_free; > - > - return rc; > - > -err_free: > - smb3_free_compound_rqst(num_rqst - 1, &new_rq[1]); > - return rc; > -} > - > int __cifs_sfu_make_node(unsigned int xid, struct inode *inode, > struct dentry *dentry, struct cifs_tcon *tcon, > const char *full_path, umode_t mode, dev_t dev, > diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h > index 0b3df644222b..3b4bc946d1bb 100644 > --- a/fs/smb/client/smb2proto.h > +++ b/fs/smb/client/smb2proto.h > @@ -115,6 +115,10 @@ int smb2_unlock_range(struct cifsFileInfo *cfile, struct file_lock *flock, > int smb2_push_mandatory_locks(struct cifsFileInfo *cfile); > void smb2_reconnect_server(struct work_struct *work); > int smb3_crypto_aead_allocate(struct TCP_Server_Info *server); > +int smb3_init_transform_rq(struct TCP_Server_Info *server, > + int num_rqst, const struct smb_rqst *rqst, > + struct smb2_transform_hdr *tr_hdr, > + struct iov_iter *iter); > unsigned long smb_rqst_len(struct TCP_Server_Info *server, > struct smb_rqst *rqst); > void smb2_set_next_command(struct cifs_tcon *tcon, struct smb_rqst *rqst); > diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c > index 1086877e9d84..874956515322 100644 > --- a/fs/smb/client/smb2transport.c > +++ b/fs/smb/client/smb2transport.c > @@ -886,6 +886,119 @@ static void *smb2_get_aead_req_new(struct crypto_aead *tfm, const struct iov_ite > return p; > } > > +/* > + * Encrypt the message in the buffer described by the iterator. > + * On success return encrypted data in iov[1-N] and pages, leave iov[0] > + * untouched. > + */ > +static int > +encrypt_message(struct TCP_Server_Info *server, > + struct smb2_transform_hdr *tr_hdr, > + struct iov_iter *iter, struct crypto_aead *tfm) > +{ > + unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20; > + int rc = 0; > + struct scatterlist *sg; > + u8 key[SMB3_ENC_DEC_KEY_SIZE]; > + struct aead_request *req; > + u8 *iv; > + DECLARE_CRYPTO_WAIT(wait); > + unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize); > + void *creq; > + size_t sensitive_size; > + > + rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), 1, key); > + if (rc) { > + cifs_server_dbg(FYI, "%s: Could not get encryption key. sid: 0x%llx\n", > + __func__, le64_to_cpu(tr_hdr->SessionId)); > + return rc; > + } > + > + if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) || > + (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > + rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE); > + else > + rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE); > + > + if (rc) { > + cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc); > + return rc; > + } > + > + rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE); > + if (rc) { > + cifs_server_dbg(VFS, "%s: Failed to set authsize %d\n", __func__, rc); > + return rc; > + } > + > + creq = smb2_get_aead_req_new(tfm, iter, tr_hdr->Signature, &iv, &req, &sg, > + &sensitive_size); > + if (IS_ERR(creq)) > + return PTR_ERR(creq); > + > + if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) || > + (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > + memcpy(iv, (char *)tr_hdr->Nonce, SMB3_AES_GCM_NONCE); > + else { > + iv[0] = 3; > + memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES_CCM_NONCE); > + } > + > + aead_request_set_tfm(req, tfm); > + aead_request_set_crypt(req, sg, sg, crypt_len, iv); > + aead_request_set_ad(req, assoc_data_len); > + > + aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, > + crypto_req_done, &wait); > + > + rc = crypto_wait_req(crypto_aead_encrypt(req), &wait); > + > + kvfree_sensitive(creq, sensitive_size); > + return rc; > +} > + > +static void > +fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, unsigned int orig_len, > + const struct smb_rqst *old_rq, __le16 cipher_type) > +{ > + struct smb2_hdr *shdr = (struct smb2_hdr *)old_rq->rq_iov[0].iov_base; > + > + *tr_hdr = (struct smb2_transform_hdr){ > + .ProtocolId = SMB2_TRANSFORM_PROTO_NUM, > + .OriginalMessageSize = cpu_to_le32(orig_len), > + .Flags = cpu_to_le16(0x01), > + .SessionId = shdr->SessionId, > + }; > + if ((cipher_type == SMB2_ENCRYPTION_AES128_GCM) || > + (cipher_type == SMB2_ENCRYPTION_AES256_GCM)) > + get_random_bytes(&tr_hdr->Nonce, SMB3_AES_GCM_NONCE); > + else > + get_random_bytes(&tr_hdr->Nonce, SMB3_AES_CCM_NONCE); > +} > + > +/* > + * This function encrypts the content in the buffer described by the iterator > + * and fills in the transform header. The source request buffers are provided > + * for reference. > + */ > +int > +smb3_init_transform_rq(struct TCP_Server_Info *server, > + int num_rqst, const struct smb_rqst *rqst, > + struct smb2_transform_hdr *tr_hdr, > + struct iov_iter *iter) > +{ > + size_t orig_len = iov_iter_count(iter) - sizeof(*tr_hdr); > + int rc; > + > + fill_transform_hdr(tr_hdr, orig_len, rqst, server->cipher_type); > + > + iov_iter_advance(iter, offsetof(struct smb2_transform_hdr, Nonce)); > + > + rc = encrypt_message(server, tr_hdr, iter, server->secmech.enc); > + cifs_dbg(FYI, "Encrypt message returned %d\n", rc); > + return rc; > +} > + > /* > * Decrypt the PDU in the iterator. The PDU begins with the transform header. > */ > diff --git a/fs/smb/client/smbdirect.c b/fs/smb/client/smbdirect.c > index 563ef488a225..e20fbc10c82e 100644 > --- a/fs/smb/client/smbdirect.c > +++ b/fs/smb/client/smbdirect.c > @@ -164,36 +164,6 @@ do { \ > #define log_rdma_mr(level, fmt, args...) \ > log_rdma(level, LOG_RDMA_MR, fmt, ##args) > > -static int smbd_post_send_full_iter(struct smbdirect_socket *sc, > - struct smbdirect_send_batch *batch, > - struct iov_iter *iter, > - u32 remaining_data_length) > -{ > - int bytes = 0; > - > - /* > - * smbdirect_connection_send_single_iter() respects the > - * negotiated max_send_size, so we need to > - * loop until the full iter is posted > - */ > - > - while (iov_iter_count(iter) > 0) { > - int rc; > - > - rc = smbdirect_connection_send_single_iter(sc, > - batch, > - iter, > - 0, /* flags */ > - remaining_data_length); > - if (rc < 0) > - return rc; > - remaining_data_length -= rc; > - bytes += rc; > - } > - > - return bytes; > -} > - > /* > * Destroy the transport and related RDMA and memory resources > * Need to go through all the pending counters and make sure on one is using > @@ -405,85 +375,46 @@ int smbd_recv(struct smbd_connection *info, struct msghdr *msg) > > /* > * Send data to transport > - * Each rqst is transported as a SMBDirect payload > - * rqst: the data to write > * return value: 0 if successfully write, otherwise error code > */ > -int smbd_send(struct TCP_Server_Info *server, > - int num_rqst, struct smb_rqst *rqst_array) > +int smbd_send(struct TCP_Server_Info *server, struct iov_iter *iter) > { I think this can become a tiny wrapper just calling smbdirect_connection_send_iter() > struct smbd_connection *info = server->smbd_conn; > struct smbdirect_socket *sc = info->socket; > const struct smbdirect_socket_parameters *sp = smbd_get_parameters(info); > - struct smb_rqst *rqst; > - struct iov_iter iter; > struct smbdirect_send_batch_storage bstorage; > struct smbdirect_send_batch *batch; > - unsigned int remaining_data_length, klen; > - int rc, i, rqst_idx; > - int error = 0; > + size_t size = iov_iter_count(iter); > + int rc, error = 0; > > if (!smbdirect_connection_is_connected(sc)) > return -EAGAIN; > > - /* > - * Add in the page array if there is one. The caller needs to set > - * rq_tailsz to PAGE_SIZE when the buffer has multiple pages and > - * ends at page boundary > - */ > - remaining_data_length = 0; > - for (i = 0; i < num_rqst; i++) > - remaining_data_length += smb_rqst_len(server, &rqst_array[i]); > - > - if (unlikely(remaining_data_length > sp->max_fragmented_send_size)) { > + if (unlikely(size > sp->max_fragmented_send_size)) { > /* assertion: payload never exceeds negotiated maximum */ > - log_write(ERR, "payload size %d > max size %d\n", > - remaining_data_length, sp->max_fragmented_send_size); > + log_write(ERR, "payload size %zu > max size %d\n", > + size, sp->max_fragmented_send_size); > return -EINVAL; > } > > - log_write(INFO, "num_rqst=%d total length=%u\n", > - num_rqst, remaining_data_length); > + log_write(INFO, "size=%zu\n", size); > > - rqst_idx = 0; > + /* > + * smbdirect_connection_send_single_iter() respects the negotiated > + * max_send_size, so we need to loop until the full iter is posted > + */ > batch = smbdirect_init_send_batch_storage(&bstorage, false, 0); > - do { > - rqst = &rqst_array[rqst_idx]; > - > - cifs_dbg(FYI, "Sending smb (RDMA): idx=%d smb_len=%lu\n", > - rqst_idx, smb_rqst_len(server, rqst)); > - for (i = 0; i < rqst->rq_nvec; i++) > - dump_smb(rqst->rq_iov[i].iov_base, rqst->rq_iov[i].iov_len); > - > - log_write(INFO, "RDMA-WR[%u] nvec=%d len=%u iter=%zu rqlen=%lu\n", > - rqst_idx, rqst->rq_nvec, remaining_data_length, > - iov_iter_count(&rqst->rq_iter), smb_rqst_len(server, rqst)); > - > - /* Send the metadata pages. */ > - klen = 0; > - for (i = 0; i < rqst->rq_nvec; i++) > - klen += rqst->rq_iov[i].iov_len; > - iov_iter_kvec(&iter, ITER_SOURCE, rqst->rq_iov, rqst->rq_nvec, klen); > - > - rc = smbd_post_send_full_iter(sc, batch, &iter, remaining_data_length); > + while (iov_iter_count(iter) > 0) { > + rc = smbdirect_connection_send_single_iter(sc, > + batch, > + iter, > + 0, /* flags */ > + iov_iter_count(iter)); > if (rc < 0) { > error = rc; > break; > } > - remaining_data_length -= rc; > - > - if (iov_iter_count(&rqst->rq_iter) > 0) { > - /* And then the data pages if there are any */ > - rc = smbd_post_send_full_iter(sc, batch, &rqst->rq_iter, > - remaining_data_length); > - if (rc < 0) { > - error = rc; > - break; > - } > - remaining_data_length -= rc; > - } > - > - } while (++rqst_idx < num_rqst); > + } > > rc = smbdirect_connection_send_batch_flush(sc, batch, true); > if (unlikely(!rc && error)) > diff --git a/fs/smb/client/smbdirect.h b/fs/smb/client/smbdirect.h > index be205ec02077..a5c17e796ff9 100644 > --- a/fs/smb/client/smbdirect.h > +++ b/fs/smb/client/smbdirect.h > @@ -40,8 +40,7 @@ void smbd_destroy(struct TCP_Server_Info *server); > > /* Interface for carrying upper layer I/O through send/recv */ > int smbd_recv(struct smbd_connection *info, struct msghdr *msg); > -int smbd_send(struct TCP_Server_Info *server, > - int num_rqst, struct smb_rqst *rqst); > +int smbd_send(struct TCP_Server_Info *server, struct iov_iter *iter); > > /* Interfaces to register and deregister MR for RDMA read/write */ > struct smbdirect_mr_io *smbd_register_mr( > @@ -61,7 +60,7 @@ static inline void *smbd_get_connection( > static inline int smbd_reconnect(struct TCP_Server_Info *server) {return -1; } > static inline void smbd_destroy(struct TCP_Server_Info *server) {} > static inline int smbd_recv(struct smbd_connection *info, struct msghdr *msg) {return -1; } > -static inline int smbd_send(struct TCP_Server_Info *server, int num_rqst, struct smb_rqst *rqst) {return -1; } > +static inline int smbd_send(struct TCP_Server_Info *server, struct iov_iter *iter) {return -1; } > #endif > > #endif > diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c > index 3f4d1a52b45c..3ead26f76112 100644 > --- a/fs/smb/client/transport.c > +++ b/fs/smb/client/transport.c > @@ -23,6 +23,9 @@ > #include <linux/sched/signal.h> > #include <linux/task_io_accounting_ops.h> > #include <linux/task_work.h> > +#include <linux/iov_iter.h> > +#include "rfc1002pdu.h" > +#include "cifspdu.h" > #include "cifsglob.h" > #include "cifsproto.h" > #include "cifs_debug.h" > @@ -252,51 +255,100 @@ smb_send_kvec(struct TCP_Server_Info *server, struct msghdr *smb_msg, > return 0; > } > > -unsigned long > -smb_rqst_len(struct TCP_Server_Info *server, struct smb_rqst *rqst) > +/* > + * smb_sendmsg - send a buffer to the socket > + * @server: Server to send the data to > + * @iter: The data to send (not advanced) > + * @sent: The amount of data sent > + * > + * Our basic "send data to server" function. Should be called with srv_mutex > + * held. The caller is responsible for handling the results. > + */ > +static int smb_sendmsg(struct TCP_Server_Info *server, const struct iov_iter *iter, > + size_t *sent) > { > - unsigned int i; > - struct kvec *iov; > - int nvec; > - unsigned long buflen = 0; > - > - if (!is_smb1(server) && rqst->rq_nvec >= 2 && > - rqst->rq_iov[0].iov_len == 4) { > - iov = &rqst->rq_iov[1]; > - nvec = rqst->rq_nvec - 1; > - } else { > - iov = rqst->rq_iov; > - nvec = rqst->rq_nvec; > - } > + struct socket *ssocket = server->ssocket; > + struct msghdr msg = { > + /* > + * MSG_SPLICE_PAGES causes tcp_sendmsg() to splice in the pages > + * in the iterator rather than copying from them. > + */ > + .msg_flags = MSG_NOSIGNAL | MSG_SPLICE_PAGES, > + .msg_iter = *iter, > + }; > + int retries = 0; > + int rc = 0; > + > + *sent = 0; > + if (server->noblocksnd) > + msg.msg_flags = MSG_DONTWAIT; > + > + while (msg_data_left(&msg)) { > + /* > + * If blocking send, we try 3 times, since each can block for 5 > + * seconds. For nonblocking we have to try more but wait > + * increasing amounts of time allowing time for socket to > + * clear. The overall time we wait in either case to send on > + * the socket is about 15 seconds. Similarly we wait for 15 > + * seconds for a response from the server in SendReceive[2] for > + * the server to send a response back for most types of > + * requests (except SMB Write past end of file which can be > + * slow, and blocking lock operations). NFS waits slightly > + * longer than CIFS, but this can make it take longer for > + * nonresponsive servers to be detected and 15 seconds is more > + * than enough time for modern networks to send a packet. In > + * most cases if we fail to send after the retries we will kill > + * the socket and reconnect which may clear the network > + * problem. > + */ > + rc = sock_sendmsg(ssocket, &msg); > + if (rc == -EAGAIN) { > + retries++; > + if (retries >= 14 || > + (!server->noblocksnd && (retries > 2))) { > + cifs_server_dbg(VFS, "sends on sock %p stuck for 15 seconds\n", > + ssocket); > + return -EAGAIN; > + } > + msleep(1 << retries); > + continue; > + } > + > + if (rc < 0) > + return rc; > + > + if (rc == 0) { > + /* should never happen, letting socket clear before > + retrying is our only obvious option here */ > + cifs_server_dbg(VFS, "tcp sent no data\n"); > + msleep(500); > + continue; > + } > > - /* total up iov array first */ > - for (i = 0; i < nvec; i++) > - buflen += iov[i].iov_len; > + *sent += rc; > > - buflen += iov_iter_count(&rqst->rq_iter); > - return buflen; > + /* send was at least partially successful */ > + retries = 0; /* in case we get ENOSPC on the next send */ > + } > + return 0; > } > > -int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > - struct smb_rqst *rqst) > +int > +__smb_send_rqst(struct TCP_Server_Info *server, struct iov_iter *iter) > { > - int rc; > - struct kvec *iov; > - int n_vec; > - unsigned int send_length = 0; > - unsigned int i, j; > - sigset_t mask, oldmask; > - size_t total_len = 0, sent, size; > struct socket *ssocket = server->ssocket; > - struct msghdr smb_msg = {}; > - __be32 rfc1002_marker; > + sigset_t mask, oldmask; > + size_t total_to_send = iov_iter_count(iter), sent = 0; > + int rc; > > cifs_in_send_inc(server); > if (cifs_rdma_enabled(server)) { > /* return -EAGAIN when connecting or reconnecting */ > rc = -EAGAIN; > - if (server->smbd_conn) > - rc = smbd_send(server, num_rqst, rqst); > + if (server->smbd_conn) { > + iov_iter_advance(iter, 4); This is not needed if smbd_send() just calls smbdirect_connection_send_iter() > + rc = smbd_send(server, iter); > + } > goto smbd_done; > } > > @@ -314,10 +366,6 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > /* cork the socket */ > tcp_sock_set_cork(ssocket->sk, true); > > - for (j = 0; j < num_rqst; j++) > - send_length += smb_rqst_len(server, &rqst[j]); > - rfc1002_marker = cpu_to_be32(send_length); > - > /* > * We should not allow signals to interrupt the network send because > * any partial send will cause session reconnects thus increasing > @@ -328,83 +376,44 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > sigfillset(&mask); > sigprocmask(SIG_BLOCK, &mask, &oldmask); > > - /* Generate a rfc1002 marker */ > - { > - struct kvec hiov = { > - .iov_base = &rfc1002_marker, > - .iov_len = 4 > - }; > - iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, &hiov, 1, 4); > - rc = smb_send_kvec(server, &smb_msg, &sent); > - if (rc < 0) > - goto unmask; > - > - total_len += sent; > - send_length += 4; > - } > - > - cifs_dbg(FYI, "Sending smb: smb_len=%u\n", send_length); > - > - for (j = 0; j < num_rqst; j++) { > - iov = rqst[j].rq_iov; > - n_vec = rqst[j].rq_nvec; > - > - size = 0; > - for (i = 0; i < n_vec; i++) { > - dump_smb(iov[i].iov_base, iov[i].iov_len); > - size += iov[i].iov_len; > - } > - > - iov_iter_kvec(&smb_msg.msg_iter, ITER_SOURCE, iov, n_vec, size); > + cifs_dbg(FYI, "Sending smb: smb_len=%zu\n", iov_iter_count(iter)); > + rc = smb_sendmsg(server, iter, &sent); > > - rc = smb_send_kvec(server, &smb_msg, &sent); > - if (rc < 0) > - goto unmask; > - > - total_len += sent; > - > - if (iov_iter_count(&rqst[j].rq_iter) > 0) { > - smb_msg.msg_iter = rqst[j].rq_iter; > - rc = smb_send_kvec(server, &smb_msg, &sent); > - if (rc < 0) > - break; > - total_len += sent; > - } > - } > - > -unmask: > sigprocmask(SIG_SETMASK, &oldmask, NULL); > > - /* > - * If signal is pending but we have already sent the whole packet to > - * the server we need to return success status to allow a corresponding > - * mid entry to be kept in the pending requests queue thus allowing > - * to handle responses from the server by the client. > - * > - * If only part of the packet has been sent there is no need to hide > - * interrupt because the session will be reconnected anyway, so there > - * won't be any response from the server to handle. > - */ > - > - if (signal_pending(current) && (total_len != send_length)) { > - cifs_dbg(FYI, "signal is pending after attempt to send\n"); > - rc = -ERESTARTSYS; > - } > - > /* uncork it */ > tcp_sock_set_cork(ssocket->sk, false); > > - if ((total_len > 0) && (total_len != send_length)) { > - cifs_dbg(FYI, "partial send (wanted=%u sent=%zu): terminating session\n", > - send_length, total_len); > + if (sent > 0) { > + /* > + * If signal is pending but we have already sent the whole > + * packet to the server we need to return success status to > + * allow a corresponding mid entry to be kept in the pending > + * requests queue thus allowing to handle responses from the > + * server by the client. > + * > + * If only part of the packet has been sent there is no need to > + * hide interrupt because the session will be reconnected > + * anyway, so there won't be any response from the server to > + * handle. > + */ > + if (signal_pending(current)) { > + cifs_dbg(FYI, "signal is pending after attempt to send\n"); > + rc = -ERESTARTSYS; > + } > + > /* > * If we have only sent part of an SMB then the next SMB could > * be taken as the remainder of this one. We need to kill the > * socket so the server throws away the partial SMB > */ > - cifs_signal_cifsd_for_reconnect(server, false); > - trace_smb3_partial_send_reconnect(server->current_mid, > - server->conn_id, server->hostname); > + if (sent != total_to_send) { > + cifs_dbg(FYI, "partial send (wanted=%zu sent=%zu): terminating session\n", > + total_to_send, sent); > + cifs_signal_cifsd_for_reconnect(server, false); > + trace_smb3_partial_send_reconnect(server->current_mid, > + server->conn_id, server->hostname); > + } > } > smbd_done: > /* > @@ -424,41 +433,167 @@ int __smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > return rc; > } > > +static size_t smb3_copy_data_iter(void *iter_from, size_t progress, size_t len, > + void *priv, void *priv2) > +{ > + struct iov_iter *iter = priv; > + return copy_to_iter(iter_from, len, iter) == len ? 0 : len; > +} > + > +/* > + * Copy the data into a buffer that we can use for encryption in place and also > + * pass to sendmsg() with MSG_SPLICE_PAGES. This avoids a lot of copies in TCP > + * at the expense of doing it upfront here. A spare slot is left in the bvec > + * queue at the front for the header(s). > + * > + * TODO: In future, the buffers should be allocated by the marshalling code. > + */ > +static int smb_copy_data_into_buffer(struct TCP_Server_Info *server, > + int num_rqst, struct smb_rqst *rqst, > + struct iov_iter *iter, struct bvecq **_bq) > +{ > + struct bvecq *bq; > + size_t total_len = 0, offset = 0; > + > + for (int i = 0; i < num_rqst; i++) { > + struct smb_rqst *req = &rqst[i]; > + size_t size = iov_iter_count(&req->rq_iter); > + > + for (int j = 0; j < req->rq_nvec; j++) > + size += req->rq_iov[j].iov_len; > + total_len = ALIGN8(total_len); > + total_len += size; > + } > + > + bq = bvecq_alloc_buffer(total_len, GFP_NOFS); > + if (!bq) > + return -ENOMEM; > + > + iov_iter_bvec_queue(iter, ITER_DEST, bq, 1, 0, total_len); > + > + for (int i = 0; i < num_rqst; i++) { > + struct smb_rqst *req = &rqst[i]; > + size_t size = iov_iter_count(&req->rq_iter); > + > + if (offset & 7) { > + unsigned int tmp = offset; > + offset = ALIGN8(offset); > + iov_iter_zero(offset - tmp, iter); > + } > + > + for (int j = 0; j < req->rq_nvec; j++) { > + size_t len = req->rq_iov[j].iov_len; > + if (copy_to_iter(req->rq_iov[j].iov_base, len, iter) != len) > + goto error; > + offset += len; > + } > + > + if (iterate_and_advance_kernel(&req->rq_iter, > + size, iter, NULL, > + smb3_copy_data_iter) != size) > + goto error; > + > + offset += size; > + } > + > + if (WARN_ONCE(offset != total_len, > + "offset=%zx total_len=%zx\n", offset, total_len)) { > + goto error; > + } > + iov_iter_bvec_queue(iter, ITER_DEST, bq, 1, 0, total_len); > + *_bq = bq; > + return 0; > +error: > + bvecq_put(bq); > + *_bq = NULL; > + return -EIO; > +} > + > static int > smb_send_rqst(struct TCP_Server_Info *server, int num_rqst, > struct smb_rqst *rqst, int flags) > { > - struct smb2_transform_hdr tr_hdr; > - struct smb_rqst new_rqst[MAX_COMPOUND] = {}; > - struct kvec iov = { > - .iov_base = &tr_hdr, > - .iov_len = sizeof(tr_hdr), > - }; > + struct iov_iter iter; > + struct bvecq *bq; > + u32 content_len; > int rc; > > - if (flags & CIFS_COMPRESS_REQ) > - return smb_compress(server, &rqst[0], __smb_send_rqst); > - > - if (!(flags & CIFS_TRANSFORM_REQ)) > - return __smb_send_rqst(server, num_rqst, rqst); > - > - if (WARN_ON_ONCE(num_rqst > MAX_COMPOUND - 1)) > - return smb_EIO1(smb_eio_trace_tx_max_compound, num_rqst); > - > - if (!server->ops->init_transform_rq) { > + if ((flags & CIFS_TRANSFORM_REQ) && > + !server->ops->init_transform_rq) { > cifs_server_dbg(VFS, "Encryption requested but transform callback is missing\n"); > return smb_EIO(smb_eio_trace_tx_need_transform); > } > > - new_rqst[0].rq_iov = &iov; > - new_rqst[0].rq_nvec = 1; > + rc = smb_copy_data_into_buffer(server, num_rqst, rqst, &iter, &bq); > + if (rc) > + return rc; > + content_len = iov_iter_count(&iter); > + > + { > + struct smb2_transform_hdr *tr_hdr; > + unsigned int hdr_len = 4, troff = 0; > + __le32 *rfc1002; > + void *hdr_blob; > + > + if (flags & CIFS_TRANSFORM_REQ) { > + troff = hdr_len; > + hdr_len += sizeof(*tr_hdr); > + } > + > + /* TODO: Allocate netmem here */ > + rc = -ENOMEM; > + hdr_blob = (void *)__get_free_page(GFP_NOFS); > + if (!hdr_blob) > + goto error; > + bvec_set_virt(&bq->bv[0], hdr_blob, hdr_len); > + > + if (flags & CIFS_COMPRESS_REQ) { > + struct smb2_write_req *whdr = bvec_virt(&bq->bv[1]); > + size_t doff = le16_to_cpu(whdr->DataOffset); > + > + iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 1, doff, > + content_len - doff); > + > + if (is_compressible(&iter)) { > + iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 1, 0, > + content_len); > + > + rc = smb_compress(server, &iter, &bq); > + if (rc > 0) { > + content_len = rc; > + } else if (rc == -EMSGSIZE) { > + /* Fall back to uncompressed. */ > + } else { > + if (rc == 0) > + rc = -EIO; > + goto error; > + } > + } > + } > + > + if (flags & CIFS_TRANSFORM_REQ) { > + iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, > + hdr_len + content_len); > + iov_iter_advance(&iter, troff); > + tr_hdr = hdr_blob + troff; > + > + rc = server->ops->init_transform_rq(server, num_rqst, rqst, tr_hdr, &iter); > + if (rc) > + goto error; > + content_len += sizeof(*tr_hdr); > + } else { > + bvec_set_virt(&bq->bv[0], hdr_blob, hdr_len); > + } > + > + /* Set the RFC1002 header at the front. */ > + rfc1002 = hdr_blob; > + *rfc1002 = cpu_to_be32(RFC1002_SESSION_MESSAGE << 24 | content_len); > > - rc = server->ops->init_transform_rq(server, num_rqst + 1, > - new_rqst, rqst); > - if (!rc) { > - rc = __smb_send_rqst(server, num_rqst + 1, new_rqst); > - smb3_free_compound_rqst(num_rqst, &new_rqst[1]); > + iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, 4 + content_len); > } > + rc = __smb_send_rqst(server, &iter); > +error: > + bvecq_put(bq); > return rc; > } > >