ldap_server: Use an array of struct iovec to avoid data_blob_append()
authorAndrew Bartlett <abartlet@samba.org>
Thu, 4 Apr 2019 04:25:30 +0000 (17:25 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Mon, 20 May 2019 04:01:11 +0000 (04:01 +0000)
This avoids a the implicit 256MB limit on LDAP replies (allowing this
to be increased in the future) and means we copy less memory around.

However because we can only have 1024 entries in a struct iovec (on Linux)
we will need to call tstream_writev_queue_send() multiple times.

Calling it in chunks of 1024 seems a reasonable compromise, the
gensec layer will chunk it out smaller if required.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
source4/ldap_server/ldap_server.c
source4/ldap_server/ldap_server.h

index d0be35972b060191b16b6d7f7046e4acb5c8e979..53e9af94888fa92ee423506a58f6a0225051c3d7 100644 (file)
@@ -672,30 +672,31 @@ static void ldapsrv_call_wait_done(struct tevent_req *subreq)
 static void ldapsrv_call_writev_start(struct ldapsrv_call *call)
 {
        struct ldapsrv_connection *conn = call->conn;
-       DATA_BLOB blob = data_blob_null;
+       struct ldapsrv_reply *reply = NULL;
        struct tevent_req *subreq = NULL;
+       size_t length = 0;
+       size_t i;
 
-       /* build all the replies into a single blob */
-       while (call->replies) {
-               bool ret;
+       call->iov_count = 0;
 
-               ret = data_blob_append(call,
-                                      &blob,
-                                      call->replies->blob.data,
-                                      call->replies->blob.length);
-               data_blob_free(&call->replies->blob);
+       /* build all the replies into an IOV (no copy) */
+       for (reply = call->replies;
+            reply != NULL;
+            reply = reply->next) {
+               /*
+                * Overflow is harmless here, just used below to
+                * decide if to read or write
+                */
+               length += reply->blob.length;
 
-               if (!ret) {
-                       ldapsrv_terminate_connection(conn, "data_blob_append failed");
-                       return;
-               }
-
-               talloc_set_name_const(blob.data, "Outgoing, encoded LDAP packet");
-
-               DLIST_REMOVE(call->replies, call->replies);
+               /*
+                * At worst an overflow would mean we send less
+                * replies
+                */
+               call->iov_count++;
        }
 
-       if (blob.length == 0) {
+       if (length == 0) {
                if (!call->notification.busy) {
                        TALLOC_FREE(call);
                }
@@ -704,14 +705,48 @@ static void ldapsrv_call_writev_start(struct ldapsrv_call *call)
                return;
        }
 
-       call->out_iov.iov_base = blob.data;
-       call->out_iov.iov_len = blob.length;
+       /* Cap call->iov_count at IOV_MAX */
+       call->iov_count = MIN(call->iov_count, IOV_MAX);
+
+       call->out_iov = talloc_array(call,
+                                    struct iovec,
+                                    call->iov_count);
+       if (!call->out_iov) {
+               /* This is not ideal */
+               ldapsrv_terminate_connection(conn,
+                                            "failed to allocate "
+                                            "iovec array");
+               return;
+       }
+
+       /* We may have had to cap the number of replies at IOV_MAX */
+       for (i = 0;
+            i < call->iov_count && call->replies != NULL;
+            i++) {
+               reply = call->replies;
+               call->out_iov[i].iov_base = reply->blob.data;
+               call->out_iov[i].iov_len = reply->blob.length;
+
+               /* Keep only the ASN.1 encoded data */
+               talloc_steal(call->out_iov, reply->blob.data);
+
+               DLIST_REMOVE(call->replies, reply);
+               TALLOC_FREE(reply);
+       }
+
+       if (i > call->iov_count) {
+               /* This is not ideal, but also (essentially) impossible */
+               ldapsrv_terminate_connection(conn,
+                                            "call list ended"
+                                            "before iov_count");
+               return;
+       }
 
        subreq = tstream_writev_queue_send(call,
                                           conn->connection->event.ctx,
                                           conn->sockets.active,
                                           conn->sockets.send_queue,
-                                          &call->out_iov, 1);
+                                          call->out_iov, call->iov_count);
        if (subreq == NULL) {
                ldapsrv_terminate_connection(conn, "stream_writev_queue_send failed");
                return;
@@ -732,6 +767,9 @@ static void ldapsrv_call_writev_done(struct tevent_req *subreq)
 
        rc = tstream_writev_queue_recv(subreq, &sys_errno);
        TALLOC_FREE(subreq);
+
+       /* This releases the ASN.1 encoded packets from memory */
+       TALLOC_FREE(call->out_iov);
        if (rc == -1) {
                const char *reason;
 
@@ -762,6 +800,12 @@ static void ldapsrv_call_writev_done(struct tevent_req *subreq)
                return;
        }
 
+       /* Perhaps still some more to send */
+       if (call->replies != NULL) {
+               ldapsrv_call_writev_start(call);
+               return;
+       }
+
        if (!call->notification.busy) {
                TALLOC_FREE(call);
        }
index 9f21da512f9aa5c1765aaaf0f0dd51eb0ebf709b..75c8adcb5efd056c0eb92a9cb318897e911b0383 100644 (file)
@@ -72,7 +72,8 @@ struct ldapsrv_call {
                struct ldap_message *msg;
                DATA_BLOB blob;
        } *replies;
-       struct iovec out_iov;
+       struct iovec *out_iov;
+       size_t iov_count;
 
        struct tevent_req *(*wait_send)(TALLOC_CTX *mem_ctx,
                                        struct tevent_context *ev,