>From b0569ebf8dec51464ab8f68f1b92b8e80cda288e Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 9 Apr 2013 17:40:40 +0200 Subject: [PATCH 1/5] dyndns: new option dyndns_refresh_interval This new options adds the possibility of updating the DNS entries periodically regardless if they have changed or not. This feature will be useful mainly in AD environments where the Windows clients periodically update their DNS records. --- Makefile.am | 3 + src/config/SSSDConfig/__init__.py.in | 1 + src/config/SSSDConfigTest.py | 2 + src/config/etc/sssd.api.conf | 1 + src/man/sssd-ipa.5.xml | 16 ++++++ src/providers/dp_dyndns.c | 53 ++++++++++++++++- src/providers/dp_dyndns.h | 17 +++++- src/providers/ipa/ipa_common.c | 4 +- src/providers/ipa/ipa_dyndns.c | 107 +++++++++++++++++++++++++++++++++++ src/providers/ipa/ipa_dyndns.h | 3 + src/providers/ipa/ipa_opts.h | 1 + src/tests/cmocka/test_dyndns.c | 85 +++++++++++++++++++++++++++- 12 files changed, 286 insertions(+), 7 deletions(-) diff --git a/Makefile.am b/Makefile.am index 252f0dad0cdb387ecfe5ab9508632c33070b052d..c2428f800c54c8a06c74a69d91ce580d70af75c8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1260,10 +1260,13 @@ test_io_CFLAGS = \ test_io_LDADD = \ $(CMOCKA_LIBS) +dyndns_tests_DEPENDENCIES = \ + $(ldblib_LTLIBRARIES) dyndns_tests_SOURCES = \ $(TEST_MOCK_OBJ) \ $(SSSD_RESOLV_OBJ) \ src/tests/common_tev.c \ + src/tests/common_dom.c \ src/tests/cmocka/test_dyndns.c \ src/providers/data_provider_opts.c dyndns_tests_CFLAGS = \ diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in index 9f11d31c2dab4583cf6ebb956940fd17d890c985..003153d0141ef88ab2c17cd4165bf3280144cf13 100644 --- a/src/config/SSSDConfig/__init__.py.in +++ b/src/config/SSSDConfig/__init__.py.in @@ -128,6 +128,7 @@ option_strings = { 'dyndns_update' : _("Whether to automatically update the client's DNS entry"), 'dyndns_ttl' : _("The TTL to apply to the client's DNS entry after updating it"), 'dyndns_iface' : _("The interface whose IP should be used for dynamic DNS updates"), + 'dyndns_refresh_interval' : _("How often to periodically update the client's DNS entry"), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index c0d4a1476a0e08f04e7aa24ecc5293992cced3ca..d3bd9f35575aa154490292833002d31eb2477517 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -510,6 +510,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'dyndns_update', 'dyndns_ttl', 'dyndns_iface', + 'dyndns_refresh_interval', 'override_gid', 'case_sensitive', 'override_homedir', @@ -856,6 +857,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'dyndns_update', 'dyndns_ttl', 'dyndns_iface', + 'dyndns_refresh_interval', 'override_gid', 'case_sensitive', 'override_homedir', diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 02e2a0a6625445f1361dceea866a32b916bea01a..b09cbd18768c1ba9bbc2aa1d632df8de400f6f77 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -125,6 +125,7 @@ entry_cache_sudo_timeout = int, None, false dyndns_update = bool, None, false dyndns_ttl = int, None, false dyndns_iface = str, None, false +dyndns_refresh_interval = int, None, false # Special providers [provider/permit] diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml index bedb1107234bf69e60cc174b1086a181fa80f806..426e9a5bbee76c7da62c8100fec5834e89befd0c 100644 --- a/src/man/sssd-ipa.5.xml +++ b/src/man/sssd-ipa.5.xml @@ -204,6 +204,22 @@ + dyndns_refresh_interval (integer) + + + How often should the back end perform periodic DNS update in + addition to the automatic update performed when the back end + goes online. + This option is optional and applicable only when dyndns_update + is true. + + + Default: 0 (disabled) + + + + + ipa_hbac_search_base (string) diff --git a/src/providers/dp_dyndns.c b/src/providers/dp_dyndns.c index 81228d5cfb59e472167b50dda819ec4ae49a928c..337817fc73bed655b4ffac41a8bf88cadc0b2e50 100644 --- a/src/providers/dp_dyndns.c +++ b/src/providers/dp_dyndns.c @@ -879,6 +879,45 @@ be_nsupdate_recv(struct tevent_req *req, int *child_status) return EOK; } +static void be_nsupdate_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct be_nsupdate_ctx *ctx = talloc_get_type(pvt, struct be_nsupdate_ctx); + + talloc_zfree(ctx->refresh_timer); + ctx->timer_callback(ctx->timer_pvt); + + /* timer_callback is responsible for calling be_nsupdate_timer_schedule + * again */ +} + +void be_nsupdate_timer_schedule(struct tevent_context *ev, + struct be_nsupdate_ctx *ctx) +{ + int refresh; + struct timeval tv; + + if (ctx->refresh_timer) { + DEBUG(SSSDBG_FUNC_DATA, ("Timer already scheduled\n")); + return; + } + + refresh = dp_opt_get_int(ctx->opts, DP_OPT_DYNDNS_REFRESH_INTERVAL); + if (refresh == 0) return; + DEBUG(SSSDBG_FUNC_DATA, ("Scheduling timer in %d seconds\n", refresh)); + + tv = tevent_timeval_current_ofs(refresh, 0); + ctx->refresh_timer = tevent_add_timer(ev, ctx, tv, + be_nsupdate_timer, ctx); + + if (!ctx->refresh_timer) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Failed to add dyndns refresh timer event\n")); + } +} + errno_t be_nsupdate_check(void) { @@ -906,6 +945,7 @@ be_nsupdate_check(void) static struct dp_option default_dyndns_opts[] = { { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, @@ -914,13 +954,16 @@ static struct dp_option default_dyndns_opts[] = { errno_t be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, - struct dp_option *defopts, struct be_nsupdate_ctx **_ctx) + struct dp_option *defopts, + nsupdate_timer_fn_t timer_callback, + void *timer_pvt, + struct be_nsupdate_ctx **_ctx) { errno_t ret; struct dp_option *src_opts; struct be_nsupdate_ctx *ctx; - ctx = talloc(mem_ctx, struct be_nsupdate_ctx); + ctx = talloc_zero(mem_ctx, struct be_nsupdate_ctx); if (ctx == NULL) return ENOMEM; src_opts = defopts ? defopts : default_dyndns_opts; @@ -932,6 +975,10 @@ be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, return ret; } + ctx->timer_callback = timer_callback; + ctx->timer_pvt = timer_pvt; + be_nsupdate_timer_schedule(be_ctx->ev, ctx); + *_ctx = ctx; - return EOK; + return ERR_OK; } diff --git a/src/providers/dp_dyndns.h b/src/providers/dp_dyndns.h index 95403a3ae73fc47c6a9b5240c640325da1045e39..e49ab8f0309bda5da6e6b00f1cb53016b88ae22c 100644 --- a/src/providers/dp_dyndns.h +++ b/src/providers/dp_dyndns.h @@ -29,12 +29,21 @@ /* dynamic dns helpers */ struct sss_iface_addr; +typedef void (*nsupdate_timer_fn_t)(void *pvt); + struct be_nsupdate_ctx { struct dp_option *opts; + + time_t last_refresh; + bool timer_in_progress; + struct tevent_timer *refresh_timer; + nsupdate_timer_fn_t timer_callback; + void *timer_pvt; }; enum dp_dyndns_opts { DP_OPT_DYNDNS_UPDATE, + DP_OPT_DYNDNS_REFRESH_INTERVAL, DP_OPT_DYNDNS_IFACE, DP_OPT_DYNDNS_TTL, @@ -48,7 +57,13 @@ errno_t be_nsupdate_check(void); errno_t be_nsupdate_init(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx, - struct dp_option *defopts, struct be_nsupdate_ctx **ctx); + struct dp_option *defopts, + nsupdate_timer_fn_t timer_callback, + void *timer_pvt, + struct be_nsupdate_ctx **_ctx); + +void be_nsupdate_timer_schedule(struct tevent_context *ev, + struct be_nsupdate_ctx *ctx); errno_t sss_iface_addr_list_get(TALLOC_CTX *mem_ctx, const char *ifname, diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c index 51750b2af3e679dafee8cb88081839c11a261757..baece274ed48b9446806bf1103789b2ace9c0d95 100644 --- a/src/providers/ipa/ipa_common.c +++ b/src/providers/ipa/ipa_common.c @@ -28,6 +28,7 @@ #include "db/sysdb_selinux.h" #include "providers/ipa/ipa_common.h" +#include "providers/ipa/ipa_dyndns.h" #include "providers/ldap/sdap_async_private.h" #include "providers/dp_dyndns.h" #include "util/sss_krb5.h" @@ -1018,7 +1019,8 @@ errno_t ipa_get_dyndns_options(struct be_ctx *be_ctx, bool update; int ttl; - ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, &ctx->dyndns_ctx); + ret = be_nsupdate_init(ctx, be_ctx, ipa_dyndns_opts, ipa_dyndns_timer, + ctx, &ctx->dyndns_ctx); if (ret != EOK) { DEBUG(SSSDBG_OP_FAILURE, ("Cannot initialize IPA dyndns opts [%d]: %s\n", diff --git a/src/providers/ipa/ipa_dyndns.c b/src/providers/ipa/ipa_dyndns.c index 8023b5336b0800b6bb019afe5a6337f43a3f2a82..52b8051b0ab1b2b91b3972ab8673517b01cfff0d 100644 --- a/src/providers/ipa/ipa_dyndns.c +++ b/src/providers/ipa/ipa_dyndns.c @@ -55,6 +55,98 @@ errno_t ipa_dyndns_init(struct be_ctx *be_ctx, return EOK; } +struct ipa_dyndns_timer_ctx { + struct sdap_id_op *sdap_op; + struct tevent_context *ev; + + struct ipa_options *ctx; +}; + +static void ipa_dyndns_timer_connected(struct tevent_req *req); + +void ipa_dyndns_timer(void *pvt) +{ + struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options); + struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx; + struct tevent_req *req; + struct ipa_dyndns_timer_ctx *timer_ctx; + errno_t ret; + + timer_ctx = talloc_zero(ctx, struct ipa_dyndns_timer_ctx); + if (timer_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Out of memory\n")); + /* Not much we can do */ + return; + } + timer_ctx->ev = sdap_ctx->be->ev; + timer_ctx->ctx = ctx; + + /* In order to prevent the connection triggering an + * online callback which would in turn trigger a concurrent DNS + * update + */ + ctx->dyndns_ctx->timer_in_progress = true; + + /* Make sure to have a valid LDAP connection */ + timer_ctx->sdap_op = sdap_id_op_create(timer_ctx, sdap_ctx->conn_cache); + if (timer_ctx->sdap_op == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_create failed\n")); + goto fail; + } + + req = sdap_id_op_connect_send(timer_ctx->sdap_op, timer_ctx, &ret); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("sdap_id_op_connect_send failed: [%d](%s)\n", + ret, sss_strerror(ret))); + goto fail; + } + tevent_req_set_callback(req, ipa_dyndns_timer_connected, timer_ctx); + return; + +fail: + ctx->dyndns_ctx->timer_in_progress = false; + be_nsupdate_timer_schedule(timer_ctx->ev, ctx->dyndns_ctx); + talloc_free(timer_ctx); +} + +static void ipa_dyndns_timer_connected(struct tevent_req *req) +{ + errno_t ret; + int dp_error; + struct ipa_dyndns_timer_ctx *timer_ctx = tevent_req_callback_data(req, + struct ipa_dyndns_timer_ctx); + struct tevent_context *ev; + struct ipa_options *ctx; + + ctx = timer_ctx->ctx; + ev = timer_ctx->ev; + ctx->dyndns_ctx->timer_in_progress = false; + + ret = sdap_id_op_connect_recv(req, &dp_error); + talloc_zfree(req); + talloc_free(timer_ctx); + if (ret != EOK) { + if (dp_error == DP_ERR_OFFLINE) { + DEBUG(SSSDBG_MINOR_FAILURE, ("No IPA server is available, " + "dynamic DNS update is skipped in offline mode.\n")); + /* Another timer will be scheduled when provider goes online + * and ipa_dyndns_update() is called */ + } else { + DEBUG(SSSDBG_OP_FAILURE, + ("Failed to connect to LDAP server: [%d](%s)\n", + ret, sss_strerror(ret))); + + /* Just schedule another dyndns retry */ + be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx); + } + return; + } + + /* all OK just call ipa_dyndns_update and schedule another refresh */ + be_nsupdate_timer_schedule(ev, ctx->dyndns_ctx); + return ipa_dyndns_update(ctx); +} + static struct tevent_req *ipa_dyndns_update_send(struct ipa_options *ctx); static errno_t ipa_dyndns_update_recv(struct tevent_req *req); @@ -63,6 +155,11 @@ static void ipa_dyndns_nsupdate_done(struct tevent_req *subreq); void ipa_dyndns_update(void *pvt) { struct ipa_options *ctx = talloc_get_type(pvt, struct ipa_options); + struct sdap_id_ctx *sdap_ctx = ctx->id_ctx->sdap_id_ctx; + + /* Schedule timer after provider went offline */ + be_nsupdate_timer_schedule(sdap_ctx->be->ev, ctx->dyndns_ctx); + struct tevent_req *req = ipa_dyndns_update_send(ctx); if (req == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("Could not update DNS\n")); @@ -109,6 +206,16 @@ ipa_dyndns_update_send(struct ipa_options *ctx) } state->ipa_ctx = ctx; + if (ctx->dyndns_ctx->last_refresh + 60 > time(NULL) || + ctx->dyndns_ctx->timer_in_progress) { + DEBUG(SSSDBG_FUNC_DATA, ("Last periodic update ran recently or timer" + "in progress, not scheduling another update\n")); + tevent_req_done(req); + tevent_req_post(req, sdap_ctx->be->ev); + return req; + } + state->ipa_ctx->dyndns_ctx->last_refresh = time(NULL); + dns_zone = dp_opt_get_string(ctx->basic, IPA_DOMAIN); if (!dns_zone) { ret = EIO; diff --git a/src/providers/ipa/ipa_dyndns.h b/src/providers/ipa/ipa_dyndns.h index d86c6634b9b5eb016cfc3b363b432f3641d2387f..ced3f0977a1dc1f07476f71e340276060d48d531 100644 --- a/src/providers/ipa/ipa_dyndns.h +++ b/src/providers/ipa/ipa_dyndns.h @@ -25,6 +25,9 @@ #ifndef IPA_DYNDNS_H_ #define IPA_DYNDNS_H_ +void ipa_dyndns_update(void *pvt); +void ipa_dyndns_timer(void *pvt); + errno_t ipa_dyndns_init(struct be_ctx *be_ctx, struct ipa_options *ctx); diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h index 392fcd86fbe57c7b699390ed68918208886ffaab..57911082ad6a89f09becdee30f99a89a74e7d12e 100644 --- a/src/providers/ipa/ipa_opts.h +++ b/src/providers/ipa/ipa_opts.h @@ -53,6 +53,7 @@ struct dp_option ipa_basic_opts[] = { struct dp_option ipa_dyndns_opts[] = { { "dyndns_update", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, + { "dyndns_refresh_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, { "dyndns_iface", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "dyndns_ttl", DP_OPT_NUMBER, { .number = 1200 }, NULL_NUMBER }, DP_OPTION_TERMINATOR diff --git a/src/tests/cmocka/test_dyndns.c b/src/tests/cmocka/test_dyndns.c index fd92404388e52bfb9b7be544e9a3ffae923ebeb7..98daebf37ae308d3578296bc0d2e9298fa69854b 100644 --- a/src/tests/cmocka/test_dyndns.c +++ b/src/tests/cmocka/test_dyndns.c @@ -35,6 +35,12 @@ #include "tests/cmocka/common_mock.h" #include "src/providers/dp_dyndns.h" +#define TESTS_PATH "tests_dyndns" +#define TEST_CONF_DB "test_dyndns_conf.ldb" +#define TEST_SYSDB_FILE "cache_dyndns_test.ldb" +#define TEST_DOM_NAME "dyndns_test" +#define TEST_ID_PROVIDER "ldap" + enum mock_nsupdate_states { MOCK_NSUPDATE_OK, MOCK_NSUPDATE_ERR, @@ -44,6 +50,9 @@ enum mock_nsupdate_states { struct dyndns_test_ctx { struct sss_test_ctx *tctx; + struct be_ctx *be_ctx; + struct be_nsupdate_ctx *update_ctx; + enum mock_nsupdate_states state; int child_status; int child_retval; @@ -275,15 +284,73 @@ void dyndns_test_timeout(void **state) talloc_free(tmp_ctx); } +void dyndns_test_timer(void *pvt) +{ + struct dyndns_test_ctx *ctx = talloc_get_type(pvt, struct dyndns_test_ctx); + static int ncalls = 0; + + ncalls++; + if (ncalls == 1) { + be_nsupdate_timer_schedule(ctx->tctx->ev, ctx->update_ctx); + } else if (ncalls == 2) { + ctx->tctx->done = true; + } + ctx->tctx->error = ERR_OK; +} + +void dyndns_test_interval(void **state) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(global_talloc_context); + assert_non_null(tmp_ctx); + check_leaks_push(tmp_ctx); + + ret = be_nsupdate_init(tmp_ctx, dyndns_test_ctx->be_ctx, NULL, + dyndns_test_timer, dyndns_test_ctx, + &dyndns_test_ctx->update_ctx); + assert_int_equal(ret, EOK); + + /* Wait until the timer hits */ + ret = test_ev_loop(dyndns_test_ctx->tctx); + DEBUG(SSSDBG_TRACE_LIBS, + ("Child request returned [%d]: %s\n", ret, strerror(ret))); + assert_int_equal(ret, ERR_OK); + + talloc_free(dyndns_test_ctx->update_ctx); + assert_true(check_leaks_pop(tmp_ctx) == true); + talloc_free(tmp_ctx); +} + /* Testsuite setup and teardown */ void dyndns_test_setup(void **state) { + struct sss_test_conf_param params[] = { + { "dyndns_update", "true" }, + { "dyndns_refresh_interval", "2" }, + { NULL, NULL }, /* Sentinel */ + }; + assert_true(leak_check_setup()); dyndns_test_ctx = talloc_zero(global_talloc_context, struct dyndns_test_ctx); assert_non_null(dyndns_test_ctx); - dyndns_test_ctx->tctx = create_ev_test_ctx(dyndns_test_ctx); + dyndns_test_ctx->tctx = create_dom_test_ctx(dyndns_test_ctx, TESTS_PATH, + TEST_CONF_DB, TEST_SYSDB_FILE, + TEST_DOM_NAME, TEST_ID_PROVIDER, + params); assert_non_null(dyndns_test_ctx->tctx); + + dyndns_test_ctx->be_ctx = talloc_zero(dyndns_test_ctx, struct be_ctx); + assert_non_null(dyndns_test_ctx->be_ctx); + + dyndns_test_ctx->be_ctx->cdb = dyndns_test_ctx->tctx->confdb; + dyndns_test_ctx->be_ctx->ev = dyndns_test_ctx->tctx->ev; + dyndns_test_ctx->be_ctx->conf_path = talloc_asprintf(dyndns_test_ctx, + CONFDB_DOMAIN_PATH_TMPL, + TEST_DOM_NAME); + assert_non_null(dyndns_test_ctx->be_ctx->conf_path); } void dyndns_test_teardown(void **state) @@ -295,11 +362,14 @@ void dyndns_test_teardown(void **state) int main(int argc, const char *argv[]) { int rv; + int no_cleanup = 0; poptContext pc; int opt; struct poptOption long_options[] = { POPT_AUTOHELP SSSD_DEBUG_OPTS + {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0, + _("Do not delete the test database after a test run"), NULL }, POPT_TABLEEND }; @@ -314,6 +384,8 @@ int main(int argc, const char *argv[]) dyndns_test_setup, dyndns_test_teardown), unit_test_setup_teardown(dyndns_test_timeout, dyndns_test_setup, dyndns_test_teardown), + unit_test_setup_teardown(dyndns_test_interval, + dyndns_test_setup, dyndns_test_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ @@ -332,8 +404,17 @@ int main(int argc, const char *argv[]) poptFreeContext(pc); DEBUG_INIT(debug_level); + + /* Even though normally the tests should clean up after themselves + * they might not after a failed run. Remove the old db to be sure */ + tests_set_cwd(); + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + test_dom_suite_setup(TESTS_PATH); + tests_set_cwd(); rv = run_tests(tests); - + if (rv == 0 && !no_cleanup) { + test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_SYSDB_FILE); + } return rv; } -- 1.8.2.1