]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
smb: client: fix sending the iwrap custom IRD/ORD negotiation messages
authorStefan Metzmacher <metze@samba.org>
Wed, 20 Aug 2025 09:25:06 +0000 (11:25 +0200)
committerSteve French <stfrench@microsoft.com>
Sun, 28 Sep 2025 23:29:48 +0000 (18:29 -0500)
Do a real negotiation and check the servers initiator_depth and
responder_resources.

This should use big endian in order to be useful.
I have captures of windows clients showing this.

The fact that we used little endian up to now
means that we sent very large numbers and the
negotiation with the server truncated them to the
server limits.

Note the reason why this uses u8 for
initiator_depth and responder_resources is
that the rdma layer also uses it.

The inconsitency regarding the initiator_depth
and responder_resources values being reversed
for iwarp devices in RDMA_CM_EVENT_ESTABLISHED
should also be fixed later, but for now we should
fix it.

Cc: Steve French <smfrench@gmail.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Long Li <longli@microsoft.com>
Acked-by: Namjae Jeon <linkinjeon@kernel.org>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: linux-rdma@vger.kernel.org
Fixes: c7398583340a ("CIFS: SMBD: Implement RDMA memory registration")
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/smbdirect.c
fs/smb/client/smbdirect.h

index e0fce5033004c7265663d2f4a53dd0dadac76a40..6480945c24592325988ecf098a36c1b183afccd6 100644 (file)
@@ -179,6 +179,8 @@ static int smbd_conn_upcall(
        struct smbd_connection *info = id->context;
        struct smbdirect_socket *sc = &info->socket;
        const char *event_name = rdma_event_msg(event->event);
+       u8 peer_initiator_depth;
+       u8 peer_responder_resources;
 
        log_rdma_event(INFO, "event=%s status=%d\n",
                event_name, event->status);
@@ -204,6 +206,85 @@ static int smbd_conn_upcall(
 
        case RDMA_CM_EVENT_ESTABLISHED:
                log_rdma_event(INFO, "connected event=%s\n", event_name);
+
+               /*
+                * Here we work around an inconsistency between
+                * iWarp and other devices (at least rxe and irdma using RoCEv2)
+                */
+               if (rdma_protocol_iwarp(id->device, id->port_num)) {
+                       /*
+                        * iWarp devices report the peer's values
+                        * with the perspective of the peer here.
+                        * Tested with siw and irdma (in iwarp mode)
+                        * We need to change to our perspective here,
+                        * so we need to switch the values.
+                        */
+                       peer_initiator_depth = event->param.conn.responder_resources;
+                       peer_responder_resources = event->param.conn.initiator_depth;
+               } else {
+                       /*
+                        * Non iWarp devices report the peer's values
+                        * already changed to our perspective here.
+                        * Tested with rxe and irdma (in roce mode).
+                        */
+                       peer_initiator_depth = event->param.conn.initiator_depth;
+                       peer_responder_resources = event->param.conn.responder_resources;
+               }
+               if (rdma_protocol_iwarp(id->device, id->port_num) &&
+                   event->param.conn.private_data_len == 8) {
+                       /*
+                        * Legacy clients with only iWarp MPA v1 support
+                        * need a private blob in order to negotiate
+                        * the IRD/ORD values.
+                        */
+                       const __be32 *ird_ord_hdr = event->param.conn.private_data;
+                       u32 ird32 = be32_to_cpu(ird_ord_hdr[0]);
+                       u32 ord32 = be32_to_cpu(ird_ord_hdr[1]);
+
+                       /*
+                        * cifs.ko sends the legacy IRD/ORD negotiation
+                        * event if iWarp MPA v2 was used.
+                        *
+                        * Here we check that the values match and only
+                        * mark the client as legacy if they don't match.
+                        */
+                       if ((u32)event->param.conn.initiator_depth != ird32 ||
+                           (u32)event->param.conn.responder_resources != ord32) {
+                               /*
+                                * There are broken clients (old cifs.ko)
+                                * using little endian and also
+                                * struct rdma_conn_param only uses u8
+                                * for initiator_depth and responder_resources,
+                                * so we truncate the value to U8_MAX.
+                                *
+                                * smb_direct_accept_client() will then
+                                * do the real negotiation in order to
+                                * select the minimum between client and
+                                * server.
+                                */
+                               ird32 = min_t(u32, ird32, U8_MAX);
+                               ord32 = min_t(u32, ord32, U8_MAX);
+
+                               info->legacy_iwarp = true;
+                               peer_initiator_depth = (u8)ird32;
+                               peer_responder_resources = (u8)ord32;
+                       }
+               }
+
+               /*
+                * negotiate the value by using the minimum
+                * between client and server if the client provided
+                * non 0 values.
+                */
+               if (peer_initiator_depth != 0)
+                       info->initiator_depth =
+                                       min_t(u8, info->initiator_depth,
+                                             peer_initiator_depth);
+               if (peer_responder_resources != 0)
+                       info->responder_resources =
+                                       min_t(u8, info->responder_resources,
+                                             peer_responder_resources);
+
                sc->status = SMBDIRECT_SOCKET_CONNECTED;
                wake_up_interruptible(&info->status_wait);
                break;
@@ -1551,7 +1632,7 @@ static struct smbd_connection *_smbd_get_connection(
        struct ib_qp_init_attr qp_attr;
        struct sockaddr_in *addr_in = (struct sockaddr_in *) dstaddr;
        struct ib_port_immutable port_immutable;
-       u32 ird_ord_hdr[2];
+       __be32 ird_ord_hdr[2];
 
        info = kzalloc(sizeof(struct smbd_connection), GFP_KERNEL);
        if (!info)
@@ -1559,6 +1640,9 @@ static struct smbd_connection *_smbd_get_connection(
        sc = &info->socket;
        sp = &sc->parameters;
 
+       info->initiator_depth = 1;
+       info->responder_resources = SMBD_CM_RESPONDER_RESOURCES;
+
        sc->status = SMBDIRECT_SOCKET_CONNECTING;
        rc = smbd_ia_open(info, dstaddr, port);
        if (rc) {
@@ -1639,22 +1723,22 @@ static struct smbd_connection *_smbd_get_connection(
        }
        sc->ib.qp = sc->rdma.cm_id->qp;
 
-       memset(&conn_param, 0, sizeof(conn_param));
-       conn_param.initiator_depth = 0;
-
-       conn_param.responder_resources =
-               min(sc->ib.dev->attrs.max_qp_rd_atom,
-                   SMBD_CM_RESPONDER_RESOURCES);
-       info->responder_resources = conn_param.responder_resources;
+       info->responder_resources =
+               min_t(u8, info->responder_resources,
+                     sc->ib.dev->attrs.max_qp_rd_atom);
        log_rdma_mr(INFO, "responder_resources=%d\n",
                info->responder_resources);
 
+       memset(&conn_param, 0, sizeof(conn_param));
+       conn_param.initiator_depth = info->initiator_depth;
+       conn_param.responder_resources = info->responder_resources;
+
        /* Need to send IRD/ORD in private data for iWARP */
        sc->ib.dev->ops.get_port_immutable(
                sc->ib.dev, sc->rdma.cm_id->port_num, &port_immutable);
        if (port_immutable.core_cap_flags & RDMA_CORE_PORT_IWARP) {
-               ird_ord_hdr[0] = info->responder_resources;
-               ird_ord_hdr[1] = 1;
+               ird_ord_hdr[0] = cpu_to_be32(conn_param.responder_resources);
+               ird_ord_hdr[1] = cpu_to_be32(conn_param.initiator_depth);
                conn_param.private_data = ird_ord_hdr;
                conn_param.private_data_len = sizeof(ird_ord_hdr);
        } else {
@@ -2121,6 +2205,12 @@ static int allocate_mr_list(struct smbd_connection *info)
        atomic_set(&info->mr_used_count, 0);
        init_waitqueue_head(&info->wait_for_mr_cleanup);
        INIT_WORK(&info->mr_recovery_work, smbd_mr_recovery_work);
+
+       if (info->responder_resources == 0) {
+               log_rdma_mr(ERR, "responder_resources negotiated as 0\n");
+               return -EINVAL;
+       }
+
        /* Allocate more MRs (2x) than hardware responder_resources */
        for (i = 0; i < info->responder_resources * 2; i++) {
                smbdirect_mr = kzalloc(sizeof(*smbdirect_mr), GFP_KERNEL);
index e45aa9ddd71da5c8e73b4c5649b2d7c8ada1cad3..4ca9b2b2c57f936d72f37001ed2ccd1b3db3e26e 100644 (file)
@@ -67,7 +67,9 @@ struct smbd_connection {
 
        /* Memory registrations */
        /* Maximum number of RDMA read/write outstanding on this connection */
-       int responder_resources;
+       bool legacy_iwarp;
+       u8 initiator_depth;
+       u8 responder_resources;
        /* Maximum number of pages in a single RDMA write/read on this connection */
        int max_frmr_depth;
        /*