nvme-tcp: fix request PDU size when header digest is enabled

Since commit 61b0ed3be8 ("nvme-tcp: parallelize I/O queue allocation
and startup") the I/O tag set is allocated before the I/O queues.
nvme_tcp_init_request() sizes the PDU with nvme_tcp_hdgst_len(queue),
but queue->hdr_digest is only set later by nvme_tcp_alloc_queue(), so
every I/O PDU is allocated without room for the header digest.

With hdr_digest enabled, the digest is then written 4 bytes past the
PDU, over the header of the next PDU in the page_frag cache, and the
target drops the connection on the first I/O:

  nvmet_tcp: unexpected pdu type 159
  nvmet: ctrl 1 fatal error occurred!

Queues that a later reconnect adds for the first time have the same
problem.

Set queue->hdr_digest when allocating the controller, together with
pf_cache_lock, so it is valid before any request is initialized.

Fixes: 61b0ed3be8 ("nvme-tcp: parallelize I/O queue allocation and startup")
Signed-off-by: Christian Aurich Zanettini Martins <christian.aurichzm@gmail.com>
Signed-off-by: Keith Busch <kbusch@kernel.org>
This commit is contained in:
Christian Aurich Zanettini Martins 2026-10-27 16:47:10 -03:00 committed by Keith Busch
parent 12c20e2903
commit cf54a0dff3

View File

@ -2079,7 +2079,6 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid,
}
}
queue->hdr_digest = nctrl->opts->hdr_digest;
queue->data_digest = nctrl->opts->data_digest;
rcv_pdu_size = sizeof(struct nvme_tcp_rsp_pdu) +
@ -3229,10 +3228,13 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
/*
* The I/O tag set is allocated before the I/O queues, and initializing
* its requests already allocates the PDUs from the page frag caches of
* the queues, so initialize their locks here.
* the queues, sized by their header digest setting, so set up the
* locks and hdr_digest here.
*/
for (i = 0; i < ctrl->ctrl.queue_count; i++)
for (i = 0; i < ctrl->ctrl.queue_count; i++) {
mutex_init(&ctrl->queues[i].pf_cache_lock);
ctrl->queues[i].hdr_digest = opts->hdr_digest;
}
ctrl->net = get_net(current->nsproxy->net_ns);