}
}
+ #
+ # When directories provide cookies to track progress through
+ # the list of changes, these can be provided on every update,
+ # which can be an excessive rate.
+ #
+ # FreeRADIUS keeps track of pending change and will only call
+ # store Cookie once the preceding changes have been processed.
+ #
+ # These options rate limit how often cookies will be stored.
+ # Provided all preceding changes have been processed, cookie Store
+ # will be called on a timed interval or after a number of changes
+ # have been completed, whichever occurs first.
+ #
+ # How often to store cookies.
+ #
+ cookie_interval = 10
+
+ #
+ # Number of completed changes which will prompt the storing
+ # of a cookie
+ #
+ cookie_changes = 100
+
#
# Persistent searches.
#
* Neither of these controls take values.
*
* @param[in] conn Connection to issue the search request on.
- * @param[in] config containing callbacks and search parameters.
+ * @param[in] sync_no number of the sync in the array of configs.
+ * @param[in] inst instance of ldap_sync this query relates to.
* @param[in] cookie unused for Active Directory
* @return
* - 0 on success
* - -1 on failure
*/
-int active_directory_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config,
+int active_directory_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst,
UNUSED uint8_t const *cookie)
{
static char const *notify_oid = LDAP_SERVER_NOTIFICATION_OID;
sync_state_t *sync;
fr_rb_tree_t *tree;
char const *filter = NULL;
+ sync_config_t const *config = inst->sync_config[sync_no];
fr_assert(conn);
fr_assert(config);
tree = talloc_get_type_abort(conn->uctx, fr_rb_tree_t);
}
- sync = sync_state_alloc(tree, conn, sync_no, config);
+ sync = sync_state_alloc(tree, conn, inst, sync_no, config);
/*
* Notification control - marks this as a persistent search.
#include <freeradius-devel/ldap/base.h>
#include "proto_ldap_sync_ldap.h"
-int active_directory_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config,
- UNUSED uint8_t const *cookie);
+int active_directory_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no,
+ proto_ldap_sync_t const *inst, UNUSED uint8_t const *cookie);
int active_directory_sync_search_entry(sync_state_t *sync, LDAPMessage *msg, UNUSED LDAPControl **ctrls);
* an ldap_abandon message to the server to tell it to cancel the search.
*
* @param[in] conn Connection to issue the search request on.
- * @param[in] config containing callbacks and search parameters.
+ * @param[in] sync_no number of the sync in the array of configs.
+ * @param[in] inst instance of ldap_sync this query relates to.
* @param[in] cookie not applicable to persistent search LDAP servers.
*/
-int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config, UNUSED uint8_t const *cookie)
+int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst, UNUSED uint8_t const *cookie)
{
static char const *notify_oid = LDAP_CONTROL_PERSIST_REQUEST;
LDAPControl ctrl = {0}, *ctrls[2] = { &ctrl, NULL };
int ret;
sync_state_t *sync;
fr_rb_tree_t *tree;
+ sync_config_t *config = inst->sync_config[sync_no];
fr_assert(conn);
fr_assert(config);
return -1;
}
- sync = sync_state_alloc(tree, conn, sync_no, config);
+ sync = sync_state_alloc(tree, conn, inst, sync_no, config);
memcpy(&ctrl.ldctl_oid, ¬ify_oid, sizeof(ctrl.ldctl_oid));
DEBUG3("Sync created with msgid %i", sync->msgid);
+ /*
+ * Register event to store cookies at a regular interval
+ * Whilst persistent search LDAP servers don't provide cookies as such
+ * we treat change numbers, if provided, as cookies.
+ */
+ fr_event_timer_in(sync, conn->conn->el, &sync->cookie_ev, inst->cookie_interval, ldap_sync_cookie_event, sync);
+
return 0;
}
*/
if (sync->cookie) talloc_free(sync->cookie);
sync->cookie = (uint8_t *)talloc_asprintf(sync, "%d", change_no);
- if (ldap_sync_cookie_store(sync, sync->cookie, false) < 0) goto error;
+ if (ldap_sync_cookie_store(sync, false) < 0) goto error;
}
if (ber_scanf(ber, "}") == LBER_ERROR) {
#include <freeradius-devel/ldap/base.h>
#include "proto_ldap_sync_ldap.h"
-int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config,
+int persistent_sync_state_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst,
UNUSED uint8_t const *cookie);
int persistent_sync_search_entry(sync_state_t *sync, LDAPMessage *msg, LDAPControl **ctrls);
{ FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, proto_ldap_sync_t, max_packet_size) },
{ FR_CONF_OFFSET("num_messages", FR_TYPE_UINT32, proto_ldap_sync_t, num_messages) },
+ { FR_CONF_OFFSET("cookie_interval", FR_TYPE_TIME_DELTA, proto_ldap_sync_t, cookie_interval), .dflt = "10" },
+ { FR_CONF_OFFSET("cookie_changes", FR_TYPE_UINT32, proto_ldap_sync_t, cookie_changes), .dflt = "100" },
/*
* Areas of the DIT to listen on
uint32_t num_messages; //!< for message ring buffer
uint32_t priority; //!< for packet processing.
+ fr_time_delta_t cookie_interval; //!< Interval between storing cookies.
+ uint32_t cookie_changes; //!< Number of LDAP changes to process between
+ //!< each cookie store operation.
+
fr_schedule_t *sc;
fr_listen_t *listen; //!< The listener structure which describes
* controls for type of directory in use.
*
* @param[in] conn to initialise the sync on
- * @param[in] config for the sync
+ * @param[in] sync_no number of the sync in the array of configs.
+ * @param[in] inst instance of ldap_sync this query relates to
* @param[in] cookie to send with the query (RFC 4533 only)
* @return
* - 0 on success.
* - -1 on error.
*/
- typedef int (*sync_init_t)(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config,
+ typedef int (*sync_init_t)(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst,
uint8_t const *cookie);
/** Received an LDAP message related to a sync
* @param[in] config for the sync.
* @return new sync state.
*/
-sync_state_t *sync_state_alloc(TALLOC_CTX *ctx, fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config)
+sync_state_t *sync_state_alloc(TALLOC_CTX *ctx, fr_ldap_connection_t *conn, proto_ldap_sync_t const *inst,
+ size_t sync_no, sync_config_t const *config)
{
sync_state_t *sync;
MEM(sync = talloc_zero(ctx, sync_state_t));
sync->conn = conn;
+ sync->inst = inst;
sync->config = config;
sync->sync_no = sync_no;
sync->phase = SYNC_PHASE_INIT;
+ fr_dlist_talloc_init(&sync->pending, sync_packet_ctx_t, entry);
+
/*
* If the connection is freed, all the sync state is also freed
*/
return sync;
}
-/** Enque a new cookie store packet
+/** Add a new cookie packet ctx to the pending list
*
- * Create a new internal packet containing the cookie we received from the LDAP server.
- * This allows the administrator to store the cookie and provide it on a future call to
- * load Cookie.
+ * Does not actually send the packet.
*
* @param[in] sync the cookie was received for.
- * @param[in] cookie received from the LDAP server. Can be NULL to indicate the stored cookie should be cleared.
* @param[in] refresh the sync after storing this cookie.
* @return
* - 0 on success.
* - -1 on failure
*/
-int ldap_sync_cookie_store(sync_state_t *sync, uint8_t const *cookie, bool refresh)
+int ldap_sync_cookie_store(sync_state_t *sync, bool refresh)
+{
+ sync_packet_ctx_t *sync_packet_ctx = NULL;
+ uint8_t *cookie = sync->cookie;
+
+ MEM(sync_packet_ctx = talloc_zero(sync, sync_packet_ctx_t));
+ sync_packet_ctx->sync = sync;
+
+ sync_packet_ctx->type = SYNC_PACKET_TYPE_COOKIE;
+ if (cookie) sync_packet_ctx->cookie = talloc_memdup(sync_packet_ctx, cookie, talloc_array_length(cookie));
+ sync_packet_ctx->refresh = refresh;
+
+ if (fr_dlist_insert_tail(&sync->pending, sync_packet_ctx) < 0) {
+ talloc_free(sync_packet_ctx);
+ return -1;
+ }
+ sync->pending_cookies++;
+
+ return 0;
+}
+
+/** Event to handle storing of cookies on a timed basis
+ *
+ * Looks at the head of the list of pending sync packets for a cookie.
+ * A cookie at the head says that all the previous changes have been
+ * completed, so the cookie can be sent.
+ */
+void ldap_sync_cookie_event(fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
+{
+ sync_state_t *sync = talloc_get_type_abort(uctx, sync_state_t);
+ sync_packet_ctx_t *sync_packet_ctx;
+
+ if (sync->pending_cookies == 0) goto finish;
+
+ /*
+ * Check the head entry in the list - is it a pending cookie
+ */
+ sync_packet_ctx = fr_dlist_head(&sync->pending);
+ if ((sync_packet_ctx->type != SYNC_PACKET_TYPE_COOKIE) ||
+ (sync_packet_ctx->status != SYNC_PACKET_PENDING)) goto finish;
+
+ ldap_sync_cookie_send(sync_packet_ctx);
+
+finish:
+ fr_event_timer_in(sync, el, &sync->cookie_ev, sync->inst->cookie_interval, ldap_sync_cookie_event, sync);
+}
+
+/** Enque a new cookie store packet
+ *
+ * Create a new internal packet containing the cookie we received from the LDAP server.
+ * This allows the administrator to store the cookie and provide it on a future call to
+ * load Cookie.
+ *
+ * @param[in] sync_packet_ctx packet context containing the cookie to store.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+*/
+int ldap_sync_cookie_send(sync_packet_ctx_t *sync_packet_ctx)
{
+ sync_state_t *sync = sync_packet_ctx->sync;
proto_ldap_sync_ldap_thread_t *thread = talloc_get_type_abort(sync->config->user_ctx, proto_ldap_sync_ldap_thread_t);
fr_dbuff_t *dbuff;
- sync_refresh_packet_t *refresh_packet = NULL;
fr_pair_list_t pairs;
fr_pair_t *vp;
TALLOC_CTX *local = NULL;
+ uint8_t *cookie = sync->cookie;
+
+ if (sync_packet_ctx->status != SYNC_PACKET_PENDING) return 0;
+ sync_packet_ctx->status = SYNC_PACKET_PREPARING;
FR_DBUFF_TALLOC_THREAD_LOCAL(&dbuff, 1024, 4096);
if (!vp) goto error;
}
- /*
- * The LDAP server has indicated that the sync needs a refresh.
- * Create a tracking structure to trigger the refresh once the cookie is stored.
- */
- if (refresh) {
- MEM(refresh_packet = talloc_zero(thread, sync_refresh_packet_t));
- if (cookie) {
- refresh_packet->refresh_cookie = talloc_memdup(refresh_packet, cookie, talloc_array_length(cookie));
- }
- refresh_packet->sync = sync;
- }
-
if (fr_internal_encode_list(dbuff, &pairs, NULL) < 0) goto error;
talloc_free(local);
fr_network_listen_send_packet(thread->nr, thread->li, thread->li, fr_dbuff_buff(dbuff),
- fr_dbuff_used(dbuff), fr_time(), refresh_packet);
+ fr_dbuff_used(dbuff), fr_time(), sync_packet_ctx);
+ sync_packet_ctx->status = SYNC_PACKET_PROCESSING;
return 0;
}
fr_pair_list_t pairs;
fr_pair_t *vp;
TALLOC_CTX *local = NULL;
+ sync_packet_ctx_t *sync_packet_ctx = NULL;
FR_DBUFF_TALLOC_THREAD_LOCAL(&dbuff, 1024, 4096);
error:
if (msg) ldap_msgfree(msg);
talloc_free(local);
+ if (sync_packet_ctx) talloc_free(sync_packet_ctx);
return -1;
};
if (fr_internal_encode_list(dbuff, &pairs, NULL) < 0) goto error;
+ MEM(sync_packet_ctx = talloc_zero(sync, sync_packet_ctx_t));
+ sync_packet_ctx->sync = sync;
+
+ if (fr_dlist_insert_tail(&sync->pending, sync_packet_ctx) < 0) goto error;
+
fr_network_listen_send_packet(thread->nr, thread->li, thread->li,
- fr_dbuff_buff(dbuff), fr_dbuff_used(dbuff), fr_time(), NULL);
+ fr_dbuff_buff(dbuff), fr_dbuff_used(dbuff), fr_time(), sync_packet_ctx);
+ sync_packet_ctx->status = SYNC_PACKET_PROCESSING;
talloc_free(local);
fr_pair_t *vp = NULL;
ssize_t ret;
TALLOC_CTX *local;
+ sync_packet_ctx_t *sync_packet_ctx = NULL;
local = talloc_new(NULL);
fr_dbuff_init(&dbuff, buffer, buffer_len);
+ if (packet_ctx) sync_packet_ctx = talloc_get_type_abort(packet_ctx, sync_packet_ctx_t);
+
/*
* Extract returned attributes into a temporary list
*/
vp = fr_pair_find_by_da_nested(&tmp, NULL, attr_ldap_sync_cookie);
if (vp) cookie = talloc_memdup(inst, vp->vp_octets, vp->vp_length);
- inst->parent->sync_config[packet_id]->init(thread->conn->h, packet_id, inst->parent->sync_config[packet_id], cookie);
+ inst->parent->sync_config[packet_id]->init(thread->conn->h, packet_id, inst->parent, cookie);
}
break;
case FR_LDAP_SYNC_CODE_COOKIE_STORE_RESPONSE:
{
- sync_refresh_packet_t *refresh_packet;
sync_config_t const *sync_config;
- if (!packet_ctx) break;
-
- /*
- * If there is a packet_ctx, it will be the tracking structure
- * indicating that we need to refresh the sync.
- */
- refresh_packet = talloc_get_type_abort(packet_ctx, sync_refresh_packet_t);
+ if (!sync_packet_ctx || !sync_packet_ctx->refresh) break;
/*
* Abandon the old sync and start a new one with the relevant cookie.
*/
- sync_config = refresh_packet->sync->config;
+ sync_config = sync_packet_ctx->sync->config;
DEBUG3("Restarting sync with base %s", sync_config->base_dn);
- talloc_free(refresh_packet->sync);
- inst->parent->sync_config[packet_id]->init(thread->conn->h, packet_id, sync_config, refresh_packet->refresh_cookie);
-
- talloc_free(refresh_packet);
+ talloc_free(sync_packet_ctx->sync);
+ inst->parent->sync_config[packet_id]->init(thread->conn->h, packet_id, inst->parent, sync_packet_ctx->cookie);
}
break;
break;
}
+ if (sync_packet_ctx) {
+ sync_state_t *sync = sync_packet_ctx->sync;
+ sync_packet_ctx_t *pc;
+ proto_ldap_sync_t *ldap_sync = inst->parent;
+
+ sync_packet_ctx->status = SYNC_PACKET_COMPLETE;
+
+ /*
+ * A cookie has been stored, reset the counter of changes
+ */
+ if (sync_packet_ctx->type == SYNC_PACKET_TYPE_COOKIE) sync->changes_since_cookie = 0;
+
+ /*
+ * Pop any processed updates from the head of the list
+ */
+ while ((pc = fr_dlist_head(&sync->pending))) {
+ /*
+ * If the head entry in the list is a pending cookie but we have
+ * not processed enough entries and there are more pending
+ * cookies, mark this one as processed.
+ */
+ if ((pc->type == SYNC_PACKET_TYPE_COOKIE) && (pc->status == SYNC_PACKET_PENDING) &&
+ (sync->changes_since_cookie < ldap_sync->cookie_changes) &&
+ (sync->pending_cookies > 1)) pc->status = SYNC_PACKET_COMPLETE;
+
+ if (pc->status != SYNC_PACKET_COMPLETE) break;
+
+ /*
+ * Update counters depending on entry type
+ */
+ if (pc->type == SYNC_PACKET_TYPE_COOKIE) {
+ sync->pending_cookies--;
+ } else {
+ sync->changes_since_cookie++;
+ }
+ pc = fr_dlist_pop_head(&sync->pending);
+ talloc_free(pc);
+ }
+
+ /*
+ * If the head of the list is a cookie which has not yet
+ * been processed and sufficient changes have been recorded
+ * send the cookie.
+ */
+ if (pc && (pc->type == SYNC_PACKET_TYPE_COOKIE) && (pc->status == SYNC_PACKET_PENDING) &&
+ (sync->changes_since_cookie >= ldap_sync->cookie_changes)) ldap_sync_cookie_send(pc);
+ }
+
fr_pair_list_free(&tmp);
talloc_free(local);
//!< before passing packets to the worker.
//!< Predominantly to overcome Active Directory's lack
//!< of filtering in persistent searches.
+
+ proto_ldap_sync_t const *inst; //!< Module instance for this sync.
+
+ fr_dlist_head_t pending; //!< List of pending changes in progress.
+
+ uint32_t pending_cookies; //!< How many cookies are in the pending heap
+ uint32_t changes_since_cookie; //!< How many changes have been added since
+ //!< the last cookie was stored.
+
+ fr_event_timer_t const *cookie_ev; //!< Timer event for sending cookies.
};
typedef struct sync_state_s sync_state_t;
fr_connection_t *conn; //!< Our connection to the LDAP directory.
} proto_ldap_sync_ldap_thread_t;
-/** Tracking structure for connections requiring refresh
+typedef enum {
+ SYNC_PACKET_PENDING = 0, //!< Packet not yet sent.
+ SYNC_PACKET_PREPARING, //!< Packet being prepared.
+ SYNC_PACKET_PROCESSING, //!< Packet sent to worker.
+ SYNC_PACKET_COMPLETE, //!< Packet response received from worker.
+} sync_packet_status_t;
+
+typedef enum {
+ SYNC_PACKET_TYPE_CHANGE = 0, //!< Packet is an entry change.
+ SYNC_PACKET_TYPE_COOKIE
+} sync_packet_type_t;
+
+/** Tracking structure for ldap sync packets
*/
-struct sync_refresh_packet_s {
- sync_state_t *sync; //!< Sync requiring refresh
+struct sync_packet_ctx_s {
+ sync_packet_type_t type; //!< Type of packet.
+ sync_packet_status_t status; //!< Status of this packet.
+ sync_state_t *sync; //!< Sync packet relates to.
+
+ uint8_t *cookie; //!< Cookie to store - can be NULL.
+ bool refresh; //!< Does the sync require a refresh.
- uint8_t *refresh_cookie; //!< Cookie provided by the server for the refresh.
+ fr_dlist_t entry; //!< Entry in list of pending packets.
};
-typedef struct sync_refresh_packet_s sync_refresh_packet_t;
+typedef struct sync_packet_ctx_s sync_packet_ctx_t;
extern fr_table_num_sorted_t const sync_op_table[];
extern size_t sync_op_table_len;
int8_t sync_state_cmp(void const *one, void const *two);
-sync_state_t *sync_state_alloc(TALLOC_CTX *ctx, fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config);
+sync_state_t *sync_state_alloc(TALLOC_CTX *ctx, fr_ldap_connection_t *conn, proto_ldap_sync_t const *inst,
+ size_t sync_no, sync_config_t const *config);
+
+int ldap_sync_cookie_store(sync_state_t *sync, bool refresh);
+
+void ldap_sync_cookie_event(fr_event_list_t *el, fr_time_t now, void *uctx);
-int ldap_sync_cookie_store(sync_state_t *sync, uint8_t const *cookie, bool refresh);
+int ldap_sync_cookie_send(sync_packet_ctx_t *sync_packet_ctx);
int ldap_sync_entry_send(sync_state_t *sync, uint8_t const uuid[SYNC_UUID_LENGTH], struct berval *orig_dn,
LDAPMessage *msg, sync_op_t op);
*
* The Sync Request Control is only applicable to the SearchRequest Message.
*/
-int rfc4533_sync_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config, uint8_t const *cookie)
+int rfc4533_sync_init(fr_ldap_connection_t *conn, size_t sync_no, proto_ldap_sync_t const *inst, uint8_t const *cookie)
{
LDAPControl ctrl = {0}, *ctrls[2] = { &ctrl, NULL };
BerElement *ber = NULL;
int ret;
fr_rb_tree_t *tree;
sync_state_t *sync;
+ sync_config_t const *config = inst->sync_config[sync_no];
fr_assert(conn);
fr_assert(config);
return -1;
}
- sync = sync_state_alloc(tree, conn, sync_no, config);
+ sync = sync_state_alloc(tree, conn, inst, sync_no, config);
/*
* Might not necessarily have a cookie
DEBUG3("Sync created with msgid %i", sync->msgid);
+ /*
+ * Register event to store cookies at a regular interval
+ */
+ fr_event_timer_in(sync, conn->conn->el, &sync->cookie_ev, inst->cookie_interval, ldap_sync_cookie_event, sync);
+
return 0;
}
* We have a new cookie - store it
*/
if ((ret == 0) && new_cookie) {
- ret = ldap_sync_cookie_store(sync, sync->cookie, false);
+ ret = ldap_sync_cookie_store(sync, false);
}
ber_free(ber, 1);
}
if (new_cookie) {
- ret = ldap_sync_cookie_store(sync, sync->cookie, false);
+ ret = ldap_sync_cookie_store(sync, false);
}
if (ber) ber_free(ber, 1);
new_cookie = true;
}
- return ldap_sync_cookie_store(sync, sync->cookie, true);
+ return ldap_sync_cookie_store(sync, true);
}
#include <freeradius-devel/ldap/base.h>
#include "proto_ldap_sync_ldap.h"
-int rfc4533_sync_init(fr_ldap_connection_t *conn, size_t sync_no, sync_config_t const *config, uint8_t const *cookie);
+int rfc4533_sync_init(fr_ldap_connection_t *conn, size_t sync_no,
+ proto_ldap_sync_t const *inst, uint8_t const *cookie);
int rfc4533_sync_search_entry(sync_state_t *sync, LDAPMessage *msg, LDAPControl **ctrls);