--- /dev/null
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for SMB-Direct RDMA as transport
+ Copyright (C) Stefan Metzmacher 2012,2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/network.h"
+#include <tevent.h>
+#include "../util/tevent_ntstatus.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/util_net.h"
+#include "libcli/smb/smb_direct.h"
+#include "lib/util/dlinklist.h"
+
+#ifdef SMB_TRANSPORT_ENABLE_RDMA
+#include <rdma/rdma_cma_abi.h>
+#include <rdma/rdma_cma.h>
+#include <infiniband/verbs.h>
+
+struct smb_direct_io;
+
+struct smb_direct_connection {
+ struct {
+ uint32_t max_send_size;
+ uint32_t max_receive_size;
+ uint32_t max_fragmented_size;
+ uint32_t max_read_write_size;
+ uint16_t send_credit_target;
+ uint16_t send_credits;
+ uint16_t receive_credit_max;
+ uint16_t receive_credit_target;
+ uint16_t receive_credits;
+ uint32_t keep_alive_internal;
+ } state;
+ struct {
+ int tmp_fd; /* given to the caller end */
+ int fd;
+ struct tevent_fd *fde;
+ } sock;
+ struct {
+ struct rdma_cm_id *cm_id;
+ struct rdma_event_channel *cm_channel;
+ struct tevent_fd *fde_channel;
+ enum rdma_cm_event_type expected_event;
+ struct rdma_cm_event *cm_event;
+ } rdma;
+ struct {
+ struct ibv_pd *pd;
+ struct ibv_comp_channel *comp_channel;
+ struct tevent_fd *fde_channel;
+ struct ibv_cq *send_cq;
+ struct ibv_cq *recv_cq;
+ struct ibv_qp *qp;
+ struct ibv_qp_init_attr init_attr;
+ } ibv;
+
+ TALLOC_CTX *io_mem_ctx;
+ struct {
+ struct smb_direct_io *idle;
+ struct smb_direct_io *pending;
+ struct smb_direct_io *ready;
+ } in;
+ struct {
+ struct smb_direct_io *idle;
+ struct smb_direct_io *pending;
+ struct smb_direct_io *ready;
+ } out;
+};
+
+#define SMB_DIRECT_IO_MAX_DATA 8192
+
+struct smb_direct_io {
+ struct smb_direct_io *prev, *next;
+
+ struct ibv_mr *hdr_mr;
+ struct ibv_mr *data_mr;
+ struct ibv_sge sge[2];
+
+ struct ibv_recv_wr rwr;
+ struct ibv_send_wr swr;
+
+ uint8_t nbt_hdr[0x04];
+ uint8_t smbd_hdr[0x18];
+ uint8_t data[SMB_DIRECT_IO_MAX_DATA];
+};
+
+static int smb_direct_io_destructor(struct smb_direct_io *io);
+
+static struct smb_direct_io *smb_direct_io_create(struct smb_direct_connection *c)
+{
+ struct smb_direct_io *io;
+
+ if (c->io_mem_ctx == NULL) {
+ return NULL;
+ }
+
+ io = talloc_zero(c->io_mem_ctx, struct smb_direct_io);
+ if (io == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(io, smb_direct_io_destructor);
+
+ io->hdr_mr = ibv_reg_mr(c->ibv.pd,
+ io->smbd_hdr,
+ sizeof(io->smbd_hdr),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (io->hdr_mr == NULL) {
+ TALLOC_FREE(io);
+ return NULL;
+ }
+
+ io->data_mr = ibv_reg_mr(c->ibv.pd,
+ io->data,
+ sizeof(io->data),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (io->data_mr == NULL) {
+ TALLOC_FREE(io);
+ return NULL;
+ }
+
+ return io;
+}
+
+static int smb_direct_io_destructor(struct smb_direct_io *io)
+{
+ if (io->hdr_mr != NULL) {
+ ibv_dereg_mr(io->hdr_mr);
+ io->hdr_mr = NULL;
+ }
+
+ if (io->data_mr != NULL) {
+ ibv_dereg_mr(io->data_mr);
+ io->data_mr = NULL;
+ }
+
+ return 0;
+}
+
+static int smb_direct_connection_destructor(struct smb_direct_connection *c);
+
+struct smb_direct_connection *smb_direct_connection_create(TALLOC_CTX *mem_ctx)
+{
+ struct smb_direct_connection *c;
+ int sfd[2];
+ int ret;
+ uint16_t i;
+
+ c = talloc_zero(mem_ctx, struct smb_direct_connection);
+ if (c == NULL) {
+ return NULL;
+ }
+ c->sock.fd = -1;
+ c->sock.tmp_fd = -1;
+
+ talloc_set_destructor(c, smb_direct_connection_destructor);
+
+ c->state.max_send_size = 1364;
+ c->state.max_receive_size = SMB_DIRECT_IO_MAX_DATA;
+ c->state.max_fragmented_size = 1048576;
+ c->state.max_read_write_size = 0;
+ c->state.receive_credit_max = 16;
+ c->state.send_credit_target = 255;
+ c->state.keep_alive_internal = 5;
+
+ ret = socketpair(AF_UNIX, 0, SOCK_STREAM, sfd);
+ if (ret == -1) {
+ int saved_errno = errno;
+ TALLOC_FREE(c);
+ errno = saved_errno;
+ return NULL;
+ }
+ c->sock.tmp_fd = sfd[0];
+ c->sock.fd = sfd[1];
+
+ smb_set_close_on_exec(c->sock.tmp_fd);
+ smb_set_close_on_exec(c->sock.fd);
+ set_blocking(c->sock.fd, false);
+
+ c->rdma.cm_channel = rdma_create_event_channel();
+ if (c->rdma.cm_channel == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+ smb_set_close_on_exec(c->rdma.cm_channel->fd);
+ set_blocking(c->rdma.cm_channel->fd, false);
+
+#if RDMA_USER_CM_MAX_ABI_VERSION >= 2
+ ret = rdma_create_id(c->rdma.cm_channel,
+ &c->rdma.cm_id,
+ c, RDMA_PS_TCP);
+#else
+#error
+ ret = rdma_create_id(c->rdma.cm_channel,
+ &c->rdma.cm_id,
+ c);
+#endif
+ if (ret != 0) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+
+ c->ibv.pd = ibv_alloc_pd(c->rdma.cm_id->verbs);
+ if (c->ibv.pd == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+
+ c->ibv.comp_channel = ibv_create_comp_channel(c->rdma.cm_id->verbs);
+ if (c->ibv.comp_channel == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+ smb_set_close_on_exec(c->ibv.comp_channel->fd);
+ set_blocking(c->ibv.comp_channel->fd, false);
+
+ c->ibv.init_attr.cap.max_send_wr = 2;
+ c->ibv.init_attr.cap.max_recv_wr = 2;
+ c->ibv.init_attr.cap.max_recv_sge = 2;
+ c->ibv.init_attr.cap.max_send_sge = 2;
+ c->ibv.init_attr.qp_type = IBV_QPT_RC;
+ c->ibv.init_attr.sq_sig_all = 1;
+
+ c->ibv.send_cq = ibv_create_cq(c->rdma.cm_id->verbs,
+ c->ibv.init_attr.cap.max_send_wr,
+ c, c->ibv.comp_channel, 0);
+ if (c->ibv.send_cq == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+ c->ibv.init_attr.send_cq = c->ibv.send_cq;
+
+ c->ibv.recv_cq = ibv_create_cq(c->rdma.cm_id->verbs,
+ c->ibv.init_attr.cap.max_recv_wr,
+ c, c->ibv.comp_channel, 0);
+ if (c->ibv.recv_cq == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+ c->ibv.init_attr.recv_cq = c->ibv.recv_cq;
+
+ ret = ibv_req_notify_cq(c->ibv.send_cq, 0);
+ if (ret != 0) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+
+ ret = ibv_req_notify_cq(c->ibv.recv_cq, 0);
+ if (ret != 0) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+
+ ret = rdma_create_qp(c->rdma.cm_id, c->ibv.pd, &c->ibv.init_attr);
+ if (ret != 0) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+ c->ibv.qp = c->rdma.cm_id->qp;
+
+ c->io_mem_ctx = talloc_named_const(c, 0, "io_mem_ctx");
+ if (c->io_mem_ctx == NULL) {
+ TALLOC_FREE(c);
+ return NULL;
+ }
+
+ for (i = 0; i < c->state.receive_credit_max; i++) {
+ struct smb_direct_io *io;
+
+ io = smb_direct_io_create(c);
+ DLIST_ADD_END(c->in.idle, io);
+ }
+
+ for (i = 0; i < c->state.send_credit_target; i++) {
+ struct smb_direct_io *io;
+
+ io = smb_direct_io_create(c);
+ DLIST_ADD_END(c->out.idle, io);
+ }
+
+ return c;
+}
+
+static int smb_direct_connection_destructor(struct smb_direct_connection *c)
+{
+ TALLOC_FREE(c->sock.fde);
+
+ if (c->sock.fd != -1) {
+ close(c->sock.fd);
+ c->sock.fd = -1;
+ }
+
+ if (c->sock.tmp_fd != -1) {
+ close(c->sock.tmp_fd);
+ c->sock.tmp_fd = -1;
+ }
+
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+
+ TALLOC_FREE(c->io_mem_ctx);
+ ZERO_STRUCT(c->in);
+ ZERO_STRUCT(c->out);
+
+ if (c->rdma.cm_event != NULL) {
+ rdma_ack_cm_event(c->rdma.cm_event);
+ c->rdma.cm_event = NULL;
+ }
+
+ if (c->ibv.qp != NULL) {
+ ibv_destroy_qp(c->ibv.qp);
+ c->ibv.qp = NULL;
+ }
+
+ if (c->ibv.send_cq != NULL) {
+ ibv_destroy_cq(c->ibv.send_cq);
+ c->ibv.send_cq = NULL;
+ }
+
+ if (c->ibv.recv_cq != NULL) {
+ ibv_destroy_cq(c->ibv.recv_cq);
+ c->ibv.recv_cq = NULL;
+ }
+
+ if (c->ibv.comp_channel != NULL) {
+ ibv_destroy_comp_channel(c->ibv.comp_channel);
+ c->ibv.comp_channel = NULL;
+ }
+
+ if (c->ibv.pd != NULL) {
+ ibv_dealloc_pd(c->ibv.pd);
+ c->ibv.pd = NULL;
+ }
+
+ if (c->rdma.cm_id != NULL) {
+ rdma_destroy_id(c->rdma.cm_id);
+ c->rdma.cm_id = NULL;
+ }
+
+ if (c->rdma.cm_channel != NULL) {
+ rdma_destroy_event_channel(c->rdma.cm_channel);
+ c->rdma.cm_channel = NULL;
+ }
+
+ return 0;
+}
+
+struct smb_direct_connection_rdma_connect_state {
+ struct smb_direct_connection *c;
+};
+
+static void smb_direct_connection_rdma_connect_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+static struct tevent_req *smb_direct_connection_rdma_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_direct_connection *c,
+ const struct sockaddr_storage *src,
+ const struct sockaddr_storage *dst,
+ struct tsocket_address *local_addr,
+ struct tsocket_address *remote_addr)
+{
+ struct tevent_req *req;
+ struct smb_direct_connection_rdma_connect_state *state;
+ int ret;
+ //struct sockaddr *src_addr = (const struct sockaddr *)src;
+ struct sockaddr *src_addr = NULL;
+ struct sockaddr_storage _dst_addr = *dst;
+ struct sockaddr *dst_addr = (struct sockaddr *)&_dst_addr;
+
+ set_sockaddr_port(dst_addr, 5445);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_direct_connection_rdma_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->c = c;
+
+ state->c->rdma.fde_channel = tevent_add_fd(ev, state->c,
+ state->c->rdma.cm_channel->fd,
+ TEVENT_FD_READ,
+ smb_direct_connection_rdma_connect_handler,
+ req);
+ if (tevent_req_nomem(state->c->rdma.fde_channel, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ errno = 0;
+ ret = rdma_resolve_addr(state->c->rdma.cm_id,
+ src_addr, dst_addr,
+ 5000);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ state->c->rdma.expected_event = RDMA_CM_EVENT_ADDR_RESOLVED;
+
+ return req;
+}
+
+static void smb_direct_connection_rdma_connect_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(private_data,
+ struct tevent_req);
+ struct smb_direct_connection_rdma_connect_state *state =
+ tevent_req_data(req,
+ struct smb_direct_connection_rdma_connect_state);
+ struct rdma_conn_param conn_param;
+ uint8_t ird_ord_hdr[8];
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ int ret;
+
+ errno = 0;
+
+ ret = rdma_get_cm_event(state->c->rdma.cm_channel,
+ &state->c->rdma.cm_event);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ errno = 0;
+ if (state->c->rdma.cm_event->status != 0) {
+ errno = state->c->rdma.cm_event->status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (state->c->rdma.cm_event->event != state->c->rdma.expected_event) {
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+
+ }
+
+ switch (state->c->rdma.cm_event->event) {
+ case RDMA_CM_EVENT_ADDR_RESOLVED:
+ errno = 0;
+ ret = rdma_resolve_route(state->c->rdma.cm_id, 5000);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ state->c->rdma.expected_event = RDMA_CM_EVENT_ROUTE_RESOLVED;
+ break;
+ case RDMA_CM_EVENT_ROUTE_RESOLVED:
+ errno = 0;
+ ret = 0;
+#if 0
+ state->c->ibv.pd = ibv_alloc_pd(state->c->rdma.cm_id->verbs);
+ if (state->c->ibv.pd == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->c->ibv.comp_channel = ibv_create_comp_channel(state->c->rdma.cm_id->verbs);
+ if (state->c->ibv.comp_channel == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ set_blocking(state->c->ibv.comp_channel->fd, false);
+ smb_set_close_on_exec(state->c->ibv.comp_channel->fd);
+
+ ZERO_STRUCT(init_attr);
+ init_attr.cap.max_send_wr = 2;
+ init_attr.cap.max_recv_wr = 2;
+ init_attr.cap.max_recv_sge = 2;
+ init_attr.cap.max_send_sge = 2;
+ init_attr.qp_type = IBV_QPT_RC;
+ init_attr.sq_sig_all = 1;
+
+ state->c->ibv.send_cq = ibv_create_cq(state->c->rdma.cm_id->verbs,
+ init_attr.cap.max_send_wr,
+ state->c,
+ state->c->ibv.comp_channel,
+ 0);
+ if (state->c->ibv.send_cq == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ init_attr.send_cq = state->c->ibv.send_cq;
+ state->c->ibv.recv_cq = ibv_create_cq(state->c->rdma.cm_id->verbs,
+ init_attr.cap.max_recv_wr,
+ state->c,
+ state->c->ibv.comp_channel,
+ 0);
+ if (state->c->ibv.recv_cq == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ init_attr.recv_cq = state->c->ibv.recv_cq;
+
+ errno = 0;
+ ret = ibv_req_notify_cq(state->c->ibv.send_cq, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ errno = 0;
+ ret = ibv_req_notify_cq(state->c->ibv.recv_cq, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ errno = 0;
+ ret = rdma_create_qp(state->c->rdma.cm_id, state->c->ibv.pd,
+ &init_attr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ state->c->ibv.qp = state->c->rdma.cm_id->qp;
+#endif
+ RSIVAL(ird_ord_hdr, 0, 16);
+ RSIVAL(ird_ord_hdr, 4, 0);
+
+ ZERO_STRUCT(conn_param);
+ conn_param.private_data = ird_ord_hdr;
+ conn_param.private_data_len = sizeof(ird_ord_hdr);
+ conn_param.responder_resources = 1;
+ conn_param.initiator_depth = 1;
+ conn_param.retry_count = 10;
+
+ errno = 0;
+ ret = rdma_connect(state->c->rdma.cm_id, &conn_param);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ state->c->rdma.expected_event = RDMA_CM_EVENT_ESTABLISHED;
+ break;
+
+ case RDMA_CM_EVENT_ESTABLISHED:
+ errno = 0;
+ ret = 0;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+
+ state->c->rdma.expected_event = RDMA_CM_EVENT_DISCONNECTED;
+ TALLOC_FREE(state->c->rdma.fde_channel);
+ rdma_ack_cm_event(state->c->rdma.cm_event);
+ state->c->rdma.cm_event = NULL;
+ tevent_req_done(req);
+ return;
+
+ case RDMA_CM_EVENT_ADDR_ERROR:
+ case RDMA_CM_EVENT_ROUTE_ERROR:
+ case RDMA_CM_EVENT_CONNECT_REQUEST:
+ case RDMA_CM_EVENT_CONNECT_RESPONSE:
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ case RDMA_CM_EVENT_UNREACHABLE:
+ case RDMA_CM_EVENT_REJECTED:
+ case RDMA_CM_EVENT_DISCONNECTED:
+ case RDMA_CM_EVENT_DEVICE_REMOVAL:
+ case RDMA_CM_EVENT_MULTICAST_JOIN:
+ case RDMA_CM_EVENT_MULTICAST_ERROR:
+ case RDMA_CM_EVENT_ADDR_CHANGE:
+ case RDMA_CM_EVENT_TIMEWAIT_EXIT:
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: event[%d] ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__,
+ state->c->rdma.cm_event->event, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ rdma_ack_cm_event(state->c->rdma.cm_event);
+ state->c->rdma.cm_event = NULL;
+}
+
+static NTSTATUS smb_direct_connection_rdma_connect_recv(struct tevent_req *req)
+{
+ struct smb_direct_connection_rdma_connect_state *state =
+ tevent_req_data(req,
+ struct smb_direct_connection_rdma_connect_state);
+ NTSTATUS status;
+
+ // TODO: _cleanup_fn
+ TALLOC_FREE(state->c->rdma.fde_channel);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct smb_direct_connection_negotiate_connect_state {
+ struct smb_direct_connection *c;
+ struct {
+ struct ibv_sge sge[1];
+ struct ibv_send_wr wr;
+ } rdma_read;
+ struct {
+ uint8_t buffer[0x14];
+ struct ibv_mr *mr;
+ struct ibv_sge sge[1];
+ struct ibv_send_wr wr;
+ } req;
+ struct {
+ uint8_t buffer[512];//0x20];
+ struct ibv_mr *mr;
+ struct ibv_sge sge[1];
+ struct ibv_recv_wr wr;
+ } rep;
+};
+
+static int smb_direct_connection_negotiate_connect_destructor(
+ struct smb_direct_connection_negotiate_connect_state *state)
+{
+ TALLOC_FREE(state->c->ibv.fde_channel);
+ TALLOC_FREE(state->c->rdma.fde_channel);
+
+ if (state->req.mr != NULL) {
+ ibv_dereg_mr(state->req.mr);
+ state->req.mr = NULL;
+ }
+
+ return 0;
+}
+
+static void smb_direct_connection_negotiate_connect_rdma_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+static void smb_direct_connection_negotiate_connect_ibv_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+static struct tevent_req *smb_direct_connection_negotiate_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_direct_connection *c)
+{
+ struct tevent_req *req;
+ struct smb_direct_connection_negotiate_connect_state *state;
+ struct ibv_recv_wr *bad_recv_wr = NULL;
+ struct ibv_send_wr *bad_send_wr = NULL;
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_direct_connection_negotiate_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->c = c;
+
+ talloc_set_destructor(state, smb_direct_connection_negotiate_connect_destructor);
+
+ state->c->ibv.fde_channel = tevent_add_fd(ev, state->c,
+ state->c->ibv.comp_channel->fd,
+ TEVENT_FD_READ,
+ smb_direct_connection_negotiate_connect_ibv_handler,
+ req);
+ if (tevent_req_nomem(state->c->ibv.fde_channel, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->c->rdma.fde_channel = tevent_add_fd(ev, state->c,
+ state->c->rdma.cm_channel->fd,
+ TEVENT_FD_READ,
+ smb_direct_connection_negotiate_connect_rdma_handler,
+ req);
+ if (tevent_req_nomem(state->c->rdma.fde_channel, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->rdma_read.sge[0].addr = 1;
+ state->rdma_read.sge[0].length = 0;
+ state->rdma_read.sge[0].lkey = 1;
+ state->rdma_read.wr.opcode = IBV_WR_RDMA_READ;
+ state->rdma_read.wr.send_flags = IBV_SEND_SIGNALED;
+ state->rdma_read.wr.sg_list = state->rdma_read.sge;
+ state->rdma_read.wr.num_sge = ARRAY_SIZE(state->rdma_read.sge);
+ state->rdma_read.wr.wr.rdma.rkey = 1;
+ state->rdma_read.wr.wr.rdma.remote_addr = 1;
+
+ SSVAL(state->req.buffer, 0x00, 0x0100);
+ SSVAL(state->req.buffer, 0x02, 0x0100);
+ SSVAL(state->req.buffer, 0x04, 0x0000);
+ SSVAL(state->req.buffer, 0x06, state->c->state.send_credit_target);
+ SIVAL(state->req.buffer, 0x08, state->c->state.max_send_size);
+ SIVAL(state->req.buffer, 0x0C, state->c->state.max_receive_size);
+ SIVAL(state->req.buffer, 0x10, state->c->state.max_fragmented_size);
+
+ state->req.mr = ibv_reg_mr(state->c->ibv.pd,
+ state->req.buffer,
+ 0x14,//sizeof(state->req.buffer),
+ 0);
+ if (tevent_req_nomem(state->req.mr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->req.sge[0].addr = (uint64_t) (uintptr_t) state->req.buffer;
+ state->req.sge[0].length = 0x14;//sizeof(state->req.buffer);
+ state->req.sge[0].lkey = state->req.mr->lkey;
+ state->req.wr.opcode = IBV_WR_SEND;
+ state->req.wr.send_flags = IBV_SEND_SIGNALED;
+ state->req.wr.sg_list = state->req.sge;
+ state->req.wr.num_sge = ARRAY_SIZE(state->req.sge);
+
+ state->rep.mr = ibv_reg_mr(state->c->ibv.pd,
+ state->rep.buffer,
+ sizeof(state->rep.buffer),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (tevent_req_nomem(state->rep.mr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ memset(state->rep.buffer, 0x1F, sizeof(state->rep.buffer));
+ state->rep.sge[0].addr = (uint64_t) (uintptr_t) state->rep.buffer;
+ state->rep.sge[0].length = sizeof(state->rep.buffer);
+ state->rep.sge[0].lkey = state->rep.mr->lkey;
+ state->rep.wr.sg_list = state->rep.sge;
+ state->rep.wr.num_sge = ARRAY_SIZE(state->rep.sge);
+
+ errno = 0;
+ ret = ibv_post_recv(state->c->ibv.qp, &state->rep.wr, &bad_recv_wr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->rdma_read.wr.next = &state->req.wr;
+ errno = 0;
+ ret = ibv_post_send(state->c->ibv.qp, &state->rdma_read.wr, &bad_send_wr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ return req;
+}
+
+static void smb_direct_connection_negotiate_connect_rdma_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(private_data,
+ struct tevent_req);
+ struct smb_direct_connection_negotiate_connect_state *state =
+ tevent_req_data(req,
+ struct smb_direct_connection_negotiate_connect_state);
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ int ret;
+
+ errno = 0;
+
+ ret = rdma_get_cm_event(state->c->rdma.cm_channel,
+ &state->c->rdma.cm_event);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (state->c->rdma.cm_event->status != 0) {
+ errno = state->c->rdma.cm_event->status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ switch (state->c->rdma.cm_event->event) {
+ case RDMA_CM_EVENT_DISCONNECTED:
+ status = NT_STATUS_CONNECTION_DISCONNECTED;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ case RDMA_CM_EVENT_ADDR_RESOLVED:
+ case RDMA_CM_EVENT_ADDR_ERROR:
+ case RDMA_CM_EVENT_ROUTE_RESOLVED:
+ case RDMA_CM_EVENT_ESTABLISHED:
+ case RDMA_CM_EVENT_ROUTE_ERROR:
+ case RDMA_CM_EVENT_CONNECT_REQUEST:
+ case RDMA_CM_EVENT_CONNECT_RESPONSE:
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ case RDMA_CM_EVENT_UNREACHABLE:
+ case RDMA_CM_EVENT_REJECTED:
+ case RDMA_CM_EVENT_DEVICE_REMOVAL:
+ case RDMA_CM_EVENT_MULTICAST_JOIN:
+ case RDMA_CM_EVENT_MULTICAST_ERROR:
+ case RDMA_CM_EVENT_ADDR_CHANGE:
+ case RDMA_CM_EVENT_TIMEWAIT_EXIT:
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+}
+
+static void smb_direct_connection_negotiate_connect_ibv_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(private_data,
+ struct tevent_req);
+ struct smb_direct_connection_negotiate_connect_state *state =
+ tevent_req_data(req,
+ struct smb_direct_connection_negotiate_connect_state);
+ struct ibv_cq *cq = NULL;
+ void *cq_context = NULL;
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ struct ibv_wc wc;
+ int ret;
+ uint16_t credits_requested;
+ uint16_t credits_granted;
+ uint32_t max_read_write_size;
+ uint32_t preferred_send_size;
+ uint32_t max_receive_size;
+ uint32_t max_fragmented_size;
+ uint32_t tmp;
+ uint8_t *ptr;
+ struct ibv_recv_wr *bad_recv_wr = NULL;
+
+ errno = 0;
+ ret = ibv_get_cq_event(state->c->ibv.comp_channel,
+ &cq, &cq_context);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ ibv_ack_cq_events(cq, 1);
+
+ if (cq_context != state->c) {
+ status = NT_STATUS_INTERNAL_ERROR;;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ errno = 0;
+ ret = ibv_req_notify_cq(cq, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ errno = 0;
+ ZERO_STRUCT(wc);
+ ret = ibv_poll_cq(cq, 1, &wc);
+ if (ret != 1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ ret = 0;
+
+ if (wc.status == IBV_WC_WR_FLUSH_ERR) {
+ //errno = wc.status;
+ status = map_nt_error_from_unix_common(wc.status);//errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ TALLOC_FREE(state->c->ibv.fde_channel);
+ TALLOC_FREE(state->c->rdma.fde_channel);
+ smb_direct_connection_negotiate_connect_rdma_handler(ev, fde, flags, private_data);
+ return;
+ }
+ if (wc.status != IBV_WC_SUCCESS) {
+ errno = wc.status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s] ibv[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status),
+ ibv_wc_status_str(wc.status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ switch (wc.opcode) {
+ case IBV_WC_SEND:
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ break;
+ case IBV_WC_RDMA_READ:
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ break;
+ case IBV_WC_RECV:
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ dump_data(0, state->rep.buffer, wc.byte_len);
+ if (wc.byte_len < 0x20) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ if (SVAL(state->rep.buffer, 0x00) != 0x0100) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ if (SVAL(state->rep.buffer, 0x02) != 0x0100) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ if (SVAL(state->rep.buffer, 0x04) != 0x0100) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ credits_requested = SVAL(state->rep.buffer, 0x08);
+ if (credits_requested == 0) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ credits_granted = SVAL(state->rep.buffer, 0x0A);
+ if (credits_granted == 0) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ status = NT_STATUS(IVAL(state->rep.buffer, 0x0C));
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ max_read_write_size = IVAL(state->rep.buffer, 0x10);
+ preferred_send_size = IVAL(state->rep.buffer, 0x14);
+ if (preferred_send_size > state->c->state.max_receive_size) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ max_receive_size = IVAL(state->rep.buffer, 0x18);
+ if (max_receive_size < 0x80) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ max_fragmented_size = IVAL(state->rep.buffer, 0x1C);
+ if (max_fragmented_size < 0x20000) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->c->state.receive_credit_target = credits_requested;
+
+ tmp = state->c->state.max_receive_size;
+ tmp = MIN(tmp, preferred_send_size);
+ tmp = MAX(tmp, 128);
+ state->c->state.max_receive_size = tmp;
+
+ tmp = state->c->state.max_send_size;
+ tmp = MIN(tmp, max_receive_size);
+ state->c->state.max_send_size = tmp;
+
+ tmp = MIN(1048576, max_read_write_size);
+ state->c->state.max_read_write_size = tmp;
+
+ tmp = state->c->state.max_fragmented_size;
+ tmp = MIN(tmp, max_fragmented_size);
+ state->c->state.max_fragmented_size = tmp;
+
+ state->c->state.send_credits = credits_granted;
+
+ TALLOC_FREE(state->c->ibv.fde_channel);
+ TALLOC_FREE(state->c->rdma.fde_channel);
+
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+
+ state->c->inbuf.hdr_mr = ibv_reg_mr(state->c->ibv.pd,
+ state->c->inbuf.hdr,
+ sizeof(state->c->inbuf.hdr),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (tevent_req_nomem(state->c->inbuf.hdr_mr, req)) {
+ return;
+ }
+
+ ptr = talloc_array(state->c, uint8_t,
+ state->c->state.max_receive_size - 0x14);
+ if (tevent_req_nomem(ptr, req)) {
+ return;
+ }
+ state->c->inbuf.reassembly_buffer = ptr;
+
+ state->c->inbuf.cur_mr = ibv_reg_mr(state->c->ibv.pd,
+ ptr,
+ talloc_get_size(ptr),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (tevent_req_nomem(state->c->inbuf.cur_mr, req)) {
+ return;
+ }
+
+ state->c->inbuf.sge[0].addr = (uint64_t) (uintptr_t) state->c->inbuf.hdr;
+ state->c->inbuf.sge[0].length = sizeof(state->c->inbuf.hdr);
+ state->c->inbuf.sge[0].lkey = state->c->inbuf.hdr_mr->lkey;
+ state->c->inbuf.sge[1].addr = (uint64_t) (uintptr_t) (ptr + 4);
+ state->c->inbuf.sge[1].length = talloc_get_size(ptr) - 4;
+ state->c->inbuf.sge[1].lkey = state->c->inbuf.cur_mr->lkey;
+ state->c->inbuf.wr.sg_list = state->c->inbuf.sge;
+ state->c->inbuf.wr.num_sge = ARRAY_SIZE(state->c->inbuf.sge);
+
+ errno = 0;
+ ret = ibv_post_recv(state->c->ibv.qp, &state->c->inbuf.wr, &bad_recv_wr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+
+ case IBV_WC_RDMA_WRITE:
+ default:
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+}
+
+static NTSTATUS smb_direct_connection_negotiate_connect_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static void smb_direct_connection_disconnect(struct smb_direct_connection *c,
+ NTSTATUS status)
+{
+ if (NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
+ }
+
+ if (c->reqs.read_pdu_req != NULL) {
+ tevent_req_defer_callback(c->reqs.read_pdu_req,
+ c->reqs.last_ev);
+ tevent_req_nterror(c->reqs.read_pdu_req, status);
+ c->reqs.read_pdu_req = NULL;
+ }
+
+ if (c->reqs.write_pdu_req != NULL) {
+ tevent_req_defer_callback(c->reqs.write_pdu_req,
+ c->reqs.last_ev);
+ tevent_req_nterror(c->reqs.write_pdu_req, status);
+ c->reqs.write_pdu_req = NULL;
+ }
+
+ smb_direct_connection_destructor(c);
+}
+
+static void smb_direct_connection_rdma_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smb_direct_connection *c =
+ talloc_get_type_abort(private_data,
+ struct smb_direct_connection);
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ int ret;
+
+ errno = 0;
+
+ ret = rdma_get_cm_event(c->rdma.cm_channel,
+ &c->rdma.cm_event);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ if (c->rdma.cm_event->status != 0) {
+ errno = c->rdma.cm_event->status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ switch (c->rdma.cm_event->event) {
+ case RDMA_CM_EVENT_DISCONNECTED:
+ status = NT_STATUS_CONNECTION_DISCONNECTED;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ case RDMA_CM_EVENT_ADDR_RESOLVED:
+ case RDMA_CM_EVENT_ADDR_ERROR:
+ case RDMA_CM_EVENT_ROUTE_RESOLVED:
+ case RDMA_CM_EVENT_ESTABLISHED:
+ case RDMA_CM_EVENT_ROUTE_ERROR:
+ case RDMA_CM_EVENT_CONNECT_REQUEST:
+ case RDMA_CM_EVENT_CONNECT_RESPONSE:
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ case RDMA_CM_EVENT_UNREACHABLE:
+ case RDMA_CM_EVENT_REJECTED:
+ case RDMA_CM_EVENT_DEVICE_REMOVAL:
+ case RDMA_CM_EVENT_MULTICAST_JOIN:
+ case RDMA_CM_EVENT_MULTICAST_ERROR:
+ case RDMA_CM_EVENT_ADDR_CHANGE:
+ case RDMA_CM_EVENT_TIMEWAIT_EXIT:
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+}
+
+static void smb_direct_connection_ibv_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t fde_flags,
+ void *private_data)
+{
+ struct smb_direct_connection *c =
+ talloc_get_type_abort(private_data,
+ struct smb_direct_connection);
+ struct ibv_cq *cq = NULL;
+ void *cq_context = NULL;
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ struct ibv_wc wc;
+ int ret;
+ uint8_t *ptr;
+ uint16_t credits_requested;
+ uint16_t credits_granted;
+ uint16_t flags;
+ uint32_t remaining_length;
+ uint32_t data_offset;
+ uint32_t data_length;
+ struct ibv_recv_wr *bad_recv_wr = NULL;
+
+ errno = 0;
+ ret = ibv_get_cq_event(c->ibv.comp_channel,
+ &cq, &cq_context);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ ibv_ack_cq_events(cq, 1);
+
+ if (cq_context != c) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ errno = 0;
+ ret = ibv_req_notify_cq(cq, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ errno = 0;
+ ZERO_STRUCT(wc);
+ ret = ibv_poll_cq(cq, 1, &wc);
+ if (ret != 1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ ret = 0;
+
+ if (wc.status == IBV_WC_WR_FLUSH_ERR) {
+ errno = wc.status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s] ibv[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status),
+ ibv_wc_status_str(wc.status)));
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+ smb_direct_connection_rdma_handler(ev, fde, 0 /* flags */, private_data);
+ return;
+ }
+ if (wc.status != IBV_WC_SUCCESS) {
+ errno = wc.status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s] ibv[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status),
+ ibv_wc_status_str(wc.status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ switch (wc.opcode) {
+ case IBV_WC_SEND:
+ DEBUG(0,("%s:%s: ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ if (c->reqs.write_pdu_req == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ tevent_req_defer_callback(c->reqs.write_pdu_req,
+ c->reqs.last_ev);
+ tevent_req_done(c->reqs.write_pdu_req);
+ return;
+
+ case IBV_WC_RECV:
+ DEBUG(0,("%s:%s: GOT WC_RECV ret[%d] errno[%d]\n",
+ __location__, __FUNCTION__, ret, errno));
+ if (wc.byte_len < 0x14) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ dump_data(0, c->inbuf.hdr, MIN(wc.byte_len, sizeof(c->inbuf.hdr)));
+ credits_requested = SVAL(c->inbuf.hdr, 0x00);
+ if (credits_requested == 0) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ credits_granted = SVAL(c->inbuf.hdr, 0x02);
+ if (credits_granted == 0) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ flags = SVAL(c->inbuf.hdr, 0x04);
+ remaining_length = IVAL(c->inbuf.hdr, 0x08);
+ data_offset = IVAL(c->inbuf.hdr, 0x0C);
+ data_length = IVAL(c->inbuf.hdr, 0x10);
+
+ c->state.receive_credits -= 1;
+ c->state.receive_credit_target = credits_requested;
+ c->state.send_credits += credits_granted;
+
+ if (data_offset == 0) {
+ if (wc.byte_len != 0x14) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ goto repost_receive;
+ } else if (data_offset == 0x18) {
+ if (data_length >= (c->state.max_receive_size - data_offset)) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ } else {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ if (remaining_length > 0) {
+ // TODO: ...
+ }
+
+ if (flags) {
+ // TODO: ...
+ }
+
+ if (c->reqs.read_pdu_req == NULL) {
+ // TODO: is this correct???
+ goto repost_receive;
+ }
+
+ if (c->reqs.read_pdu_req != NULL) {
+ TALLOC_FREE(c->inbuf.full_buffer);
+ if (c->inbuf.cur_mr != NULL) {
+ ibv_dereg_mr(c->inbuf.cur_mr);
+ c->inbuf.cur_mr = NULL;
+ }
+ ptr = c->inbuf.reassembly_buffer;
+ ptr = talloc_realloc(c, ptr, uint8_t, 4 + data_length);
+ if (ptr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ c->inbuf.full_buffer = ptr;
+ c->inbuf.reassembly_buffer= NULL;
+ tevent_req_defer_callback(c->reqs.read_pdu_req,
+ c->reqs.last_ev);
+ tevent_req_done(c->reqs.read_pdu_req);
+ /* no return here */
+ }
+
+ ptr = talloc_array(c, uint8_t,
+ c->state.max_receive_size - 0x14);
+ if (ptr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+ c->inbuf.reassembly_buffer = ptr;
+
+ c->inbuf.cur_mr = ibv_reg_mr(c->ibv.pd,
+ ptr,
+ talloc_get_size(ptr),
+ IBV_ACCESS_LOCAL_WRITE);
+ if (c->inbuf.cur_mr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ c->inbuf.sge[1].addr = (uint64_t) (uintptr_t) (ptr + 4);
+ c->inbuf.sge[1].length = talloc_get_size(ptr) - 4;
+ c->inbuf.sge[1].lkey = c->inbuf.cur_mr->lkey;
+
+repost_receive:
+ errno = 0;
+ DEBUG(0,("%s:%s: REPOST_RECV...\n",
+ __location__, __FUNCTION__));
+ ret = ibv_post_recv(c->ibv.qp, &c->inbuf.wr, &bad_recv_wr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+
+ return;
+
+ case IBV_WC_RDMA_READ:
+ case IBV_WC_RDMA_WRITE:
+ default:
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(c, status);
+ return;
+ }
+}
+
+static NTSTATUS smb_direct_connection_setup_events(struct smb_direct_connection *c,
+ struct tevent_context *ev)
+{
+ if (tevent_fd_get_flags(c->ibv.fde_channel) == 0) {
+ c->reqs.last_ev = NULL;
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+ }
+
+ if (tevent_fd_get_flags(c->rdma.fde_channel) == 0) {
+ c->reqs.last_ev = NULL;
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+ }
+
+ if (c->reqs.read_pdu_req == NULL && c->reqs.write_pdu_req == NULL) {
+ c->reqs.last_ev = NULL;
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+ }
+
+ if (ev == NULL) {
+ c->reqs.last_ev = NULL;
+ TALLOC_FREE(c->ibv.fde_channel);
+ TALLOC_FREE(c->rdma.fde_channel);
+ } else if (ev == c->reqs.last_ev) {
+ return NT_STATUS_OK;
+ } else if (c->reqs.last_ev == NULL) {
+ /* fallthrough */
+ } else {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ c->ibv.fde_channel = tevent_add_fd(ev, c,
+ c->ibv.comp_channel->fd,
+ TEVENT_FD_READ,
+ smb_direct_connection_ibv_handler,
+ c);
+ if (c->ibv.fde_channel == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->rdma.fde_channel = tevent_add_fd(ev, c,
+ c->rdma.cm_channel->fd,
+ TEVENT_FD_READ,
+ smb_direct_connection_rdma_handler,
+ c);
+ if (c->rdma.fde_channel == NULL) {
+ TALLOC_FREE(c->ibv.fde_channel);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ c->reqs.last_ev = ev;
+ return NT_STATUS_OK;
+}
+
+struct smb_direct_write_pdu_state {
+ struct smb_direct_connection *c;
+ uint8_t hdr[0x18];
+ uint8_t *buffer;
+ struct ibv_mr *hdr_mr;
+ struct ibv_mr *buffer_mr;
+ struct ibv_sge sge[2];
+ struct ibv_send_wr wr;
+};
+
+static int smb_direct_write_pdu_destructor(
+ struct smb_direct_write_pdu_state *state)
+{
+ if (state->c) {
+ state->c->reqs.write_pdu_req = NULL;
+ }
+
+ if (state->hdr_mr != NULL) {
+ ibv_dereg_mr(state->hdr_mr);
+ state->hdr_mr = NULL;
+ }
+ if (state->buffer_mr != NULL) {
+ ibv_dereg_mr(state->buffer_mr);
+ state->buffer_mr = NULL;
+ }
+
+ return 0;
+}
+
+static struct tevent_req *smb_direct_write_pdu_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_transport *transport,
+ const struct iovec *vector,
+ size_t count)
+{
+ struct tevent_req *req;
+ struct smb_direct_write_pdu_state *state;
+ struct smb_direct_connection *c =
+ smb_transport_data(transport,
+ struct smb_direct_connection);
+ size_t to_write = 0;
+ size_t current_len = 0;
+ size_t remaining_len = 0;
+ uint8_t *ptr = NULL;
+ size_t buf_len = 0;
+ size_t i;
+ NTSTATUS status;
+ int ret;
+ struct ibv_send_wr *bad_send_wr = NULL;
+ uint16_t granted = 0;
+ uint16_t flags = 0;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_direct_write_pdu_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->c = c;
+ talloc_set_destructor(state, smb_direct_write_pdu_destructor);
+
+ /* first check if the input is ok */
+#ifdef IOV_MAX
+ if (count > IOV_MAX) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+#endif
+
+ for (i=0; i < count; i++) {
+ size_t tmp = to_write;
+ tmp += vector[i].iov_len;
+
+ if (tmp < to_write) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ to_write = tmp;
+ }
+
+ if (to_write == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ if (to_write > state->c->state.max_fragmented_size) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->c->state.send_credits == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ current_len = MIN(state->c->state.max_send_size - 0x18, to_write);
+ remaining_len = to_write - current_len;
+
+ buf_len = current_len;
+ state->buffer = talloc_zero_array(state, uint8_t, buf_len);
+ if (tevent_req_nomem(state->buffer, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ granted = state->c->state.receive_credit_max;
+ granted -= state->c->state.receive_credits;
+ granted = MIN(granted, state->c->state.receive_credit_target);
+ state->c->state.receive_credits += granted;
+ state->c->state.send_credits -= 1;
+
+ if (state->c->state.send_credits == 0) {
+ flags |= 0x0001;
+ }
+
+ SSVAL(state->hdr, 0x00, state->c->state.send_credit_target);
+ SSVAL(state->hdr, 0x02, granted);
+ SSVAL(state->hdr, 0x04, flags);
+ SSVAL(state->hdr, 0x06, 0x0000);
+ SIVAL(state->hdr, 0x08, remaining_len);
+ SIVAL(state->hdr, 0x0C, 0x00000018);
+ SIVAL(state->hdr, 0x10, current_len);
+ SIVAL(state->hdr, 0x14, 0x00000000);
+
+ ptr = state->buffer;
+ for (i=0; i < count; i++) {
+ const uint8_t *this_buf = (const uint8_t *)vector[i].iov_base;
+ size_t this_len = MIN(current_len, vector[i].iov_len);
+
+ memcpy(ptr, this_buf, this_len);
+ ptr += this_len;
+ current_len -= this_len;
+ if (current_len == 0) {
+ break;
+ }
+ }
+
+ status = smb_direct_connection_setup_events(state->c, ev);
+ if (!NT_STATUS_IS_OK(status)) {
+ smb_direct_connection_disconnect(state->c, status);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ state->c->reqs.write_pdu_req = req;
+
+ state->hdr_mr = ibv_reg_mr(state->c->ibv.pd,
+ state->hdr,
+ sizeof(state->hdr),
+ 0);
+ if (state->hdr_mr == NULL) {
+ smb_direct_connection_disconnect(state->c, NT_STATUS_NO_MEMORY);
+ return req;
+ }
+
+ state->buffer_mr = ibv_reg_mr(state->c->ibv.pd,
+ state->buffer,
+ state->c->state.max_send_size,
+ 0);
+ if (state->buffer_mr == NULL) {
+ smb_direct_connection_disconnect(state->c, NT_STATUS_NO_MEMORY);
+ return req;
+ }
+
+ state->sge[0].addr = (uint64_t) (uintptr_t) state->hdr;
+ state->sge[0].length = sizeof(state->hdr);;
+ state->sge[0].lkey = state->hdr_mr->lkey;
+ state->sge[1].addr = (uint64_t) (uintptr_t) state->buffer;
+ state->sge[1].length = buf_len;
+ state->sge[1].lkey = state->buffer_mr->lkey;
+ state->wr.opcode = IBV_WR_SEND;
+ state->wr.send_flags = IBV_SEND_SIGNALED;
+ state->wr.sg_list = state->sge;
+ state->wr.num_sge = ARRAY_SIZE(state->sge);
+
+ errno = 0;
+ ret = ibv_post_send(state->c->ibv.qp, &state->wr, &bad_send_wr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("%s:%s: ret[%d] errno[%d] status[%s]\n",
+ __location__, __FUNCTION__, ret, errno, nt_errstr(status)));
+ smb_direct_connection_disconnect(state->c, status);
+ return req;
+ }
+
+ return req;
+}
+
+static NTSTATUS smb_direct_write_pdu_recv(struct tevent_req *req)
+{
+ struct smb_direct_write_pdu_state *state =
+ tevent_req_data(req,
+ struct smb_direct_write_pdu_state);
+ NTSTATUS status;
+
+ state->c->reqs.write_pdu_req = NULL;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct smb_direct_read_pdu_state {
+ struct smb_direct_connection *c;
+ uint8_t *inbuf;
+};
+
+static int smb_direct_read_pdu_destructor(
+ struct smb_direct_read_pdu_state *state)
+{
+ if (state->c != NULL) {
+ state->c->reqs.read_pdu_req = NULL;
+ }
+
+ return 0;
+}
+
+static struct tevent_req *smb_direct_read_pdu_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_transport *transport)
+{
+ struct tevent_req *req;
+ struct smb_direct_read_pdu_state *state;
+ struct smb_direct_connection *c =
+ smb_transport_data(transport,
+ struct smb_direct_connection);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_direct_read_pdu_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->c = c;
+ talloc_set_destructor(state, smb_direct_read_pdu_destructor);
+
+ state->c->reqs.read_pdu_req = req;
+
+ return req;
+}
+
+static NTSTATUS smb_direct_read_pdu_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct iovec *vector)
+{
+ struct smb_direct_read_pdu_state *state =
+ tevent_req_data(req,
+ struct smb_direct_read_pdu_state);
+ NTSTATUS status;
+
+ state->c->reqs.read_pdu_req = NULL;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ vector->iov_len = talloc_get_size(state->c->inbuf.full_buffer);
+ _smb_setlen_tcp(state->c->inbuf.full_buffer, vector->iov_len - 4);
+ vector->iov_base = talloc_move(mem_ctx, &state->c->inbuf.full_buffer);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static const struct smb_transport_ops smb_direct_ops = {
+ .name = "smbdirect",
+
+ .read_pdu_send = smb_direct_read_pdu_send,
+ .read_pdu_recv = smb_direct_read_pdu_recv,
+
+ .write_pdu_send = smb_direct_write_pdu_send,
+ .write_pdu_recv = smb_direct_write_pdu_recv,
+};
+
+#endif /* SMB_TRANSPORT_ENABLE_RDMA */