From e0bb119bdc1549d731f371202428c0cb667d3388 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 22 Feb 2010 10:28:26 +0100 Subject: Restrict family lookups Adds a new option that tells resolver which address family to prefer or use exclusively. Fixes: #404 --- src/confdb/confdb.h | 1 + src/config/SSSDConfig.py | 1 + src/config/SSSDConfigTest.py | 2 ++ src/config/etc/sssd.api.conf | 1 + src/man/sssd.conf.5.xml | 28 ++++++++++++++++++++++ src/providers/data_provider_fo.c | 52 +++++++++++++++++++++++++++++++++++++--- src/providers/fail_over.c | 25 ++++++++++++------- src/providers/fail_over.h | 22 ++++++++++++++--- src/resolv/async_resolv.c | 36 +++++++++++++++++++--------- src/resolv/async_resolv.h | 10 +++++++- src/tests/fail_over-tests.c | 10 ++++++-- src/tests/resolv-tests.c | 18 ++++++++++---- 12 files changed, 172 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 28098b80..66576c35 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -100,6 +100,7 @@ #define CONFDB_DOMAIN_MPG "magic_private_groups" #define CONFDB_DOMAIN_FQ "use_fully_qualified_names" #define CONFDB_DOMAIN_ENTRY_CACHE_TIMEOUT "entry_cache_timeout" +#define CONFDB_DOMAIN_FAMILY_ORDER "lookup_family_order" /* Local Provider */ #define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py index a004c33b..471ecb6c 100644 --- a/src/config/SSSDConfig.py +++ b/src/config/SSSDConfig.py @@ -79,6 +79,7 @@ option_strings = { 'store_legacy_passwords' : _('Store password hashes'), 'use_fully_qualified_names' : _('Display users/groups in fully-qualified form'), 'entry_cache_timeout' : _('Entry cache timeout length (seconds)'), + 'lookup_family_order' : _('Restrict or prefer a specific address family when performing DNS lookups'), # [provider/ipa] 'ipa_domain' : _('IPA domain'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 153146f8..eed1de31 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -414,6 +414,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'store_legacy_passwords', 'use_fully_qualified_names', 'entry_cache_timeout', + 'lookup_family_order', 'id_provider', 'auth_provider', 'access_provider', @@ -724,6 +725,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'store_legacy_passwords', 'use_fully_qualified_names', 'entry_cache_timeout', + 'lookup_family_order', 'id_provider', 'auth_provider', 'access_provider', diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf index 19053538..35890acc 100644 --- a/src/config/etc/sssd.api.conf +++ b/src/config/etc/sssd.api.conf @@ -54,6 +54,7 @@ cache_credentials = bool, None, true, false store_legacy_passwords = bool, None, false use_fully_qualified_names = bool, None, false entry_cache_timeout = int, None, false +lookup_family_order = str, None, false # Special providers [provider/permit] diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index 7b240c8f..496c1712 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -589,6 +589,34 @@ + + + lookup_family_order (string) + + + Provides the ability to select preferred address family + to use when performing DNS lookups. + + + Supported values: + + + ipv4_first: Try looking up IPv4 address, if that fails, try IPv6 + + + ipv4_only: Only attempt to resolve hostnames to IPv4 addresses. + + + ipv6_first: Try looking up IPv6 address, if that fails, try IPv4 + + + ipv6_only: Only attempt to resolve hostnames to IPv6 addresses. + + + Default: ipv4_first + + + diff --git a/src/providers/data_provider_fo.c b/src/providers/data_provider_fo.c index 7d024048..482f7444 100644 --- a/src/providers/data_provider_fo.c +++ b/src/providers/data_provider_fo.c @@ -53,9 +53,47 @@ struct be_failover_ctx { struct be_svc_data *svcs; }; +static int be_fo_get_options(TALLOC_CTX *mem_ctx, struct be_ctx *ctx, + struct fo_options *opts) +{ + char *str_opt; + int ret; + + /* todo get timeout from configuration */ + opts->retry_timeout = 30; + + ret = confdb_get_string(ctx->cdb, mem_ctx, ctx->conf_path, + CONFDB_DOMAIN_FAMILY_ORDER, + "ipv4_first", &str_opt); + if (ret != EOK) { + return ret; + } + + DEBUG(7, ("Lookup order: %s\n", str_opt)); + + if (strcasecmp(str_opt, "ipv4_first") == 0) { + opts->family_order = IPV4_FIRST; + } else if (strcasecmp(str_opt, "ipv4_only") == 0) { + opts->family_order = IPV4_ONLY; + } else if (strcasecmp(str_opt, "ipv6_first") == 0) { + opts->family_order = IPV6_FIRST; + } else if (strcasecmp(str_opt, "ipv6_only") == 0) { + opts->family_order = IPV6_ONLY; + } else { + DEBUG(1, ("Unknown value for option %s: %s\n", + CONFDB_DOMAIN_FAMILY_ORDER, str_opt)); + talloc_free(str_opt); + return EINVAL; + } + + talloc_free(str_opt); + return EOK; +} + int be_init_failover(struct be_ctx *ctx) { int ret; + struct fo_options fopts; if (ctx->be_fo != NULL) { return EOK; @@ -72,8 +110,13 @@ int be_init_failover(struct be_ctx *ctx) return ret; } - /* todo get timeout from configuration */ - ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, 30); + ret = be_fo_get_options(ctx->be_fo, ctx, &fopts); + if (ret != EOK) { + talloc_zfree(ctx->be_fo); + return ret; + } + + ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, &fopts); if (!ctx->be_fo->fo_ctx) { talloc_zfree(ctx->be_fo); return ENOMEM; @@ -250,7 +293,9 @@ struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx, state->attempts = 0; subreq = fo_resolve_service_send(state, ev, - ctx->be_fo->resolv, svc->fo_service); + ctx->be_fo->resolv, + ctx->be_fo->fo_ctx, + svc->fo_service); if (!subreq) { talloc_zfree(req); return NULL; @@ -305,6 +350,7 @@ static void be_resolve_server_done(struct tevent_req *subreq) DEBUG(6, ("Trying with the next one!\n")); subreq = fo_resolve_service_send(state, state->ev, state->ctx->be_fo->resolv, + state->ctx->be_fo->fo_ctx, state->svc->fo_service); if (!subreq) { tevent_req_error(req, ENOMEM); diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c index 7560b89e..54ad0329 100644 --- a/src/providers/fail_over.c +++ b/src/providers/fail_over.c @@ -45,8 +45,7 @@ struct fo_ctx { struct fo_service *service_list; struct server_common *server_common_list; - /* Settings. */ - time_t retry_timeout; + struct fo_options *opts; }; struct fo_service { @@ -99,7 +98,7 @@ struct status { }; struct fo_ctx * -fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout) +fo_context_init(TALLOC_CTX *mem_ctx, struct fo_options *opts) { struct fo_ctx *ctx; @@ -108,11 +107,17 @@ fo_context_init(TALLOC_CTX *mem_ctx, time_t retry_timeout) DEBUG(1, ("No memory\n")); return NULL; } + ctx->opts = talloc_zero(ctx, struct fo_options); + if (ctx->opts == NULL) { + DEBUG(1, ("No memory\n")); + return NULL; + } - ctx->retry_timeout = retry_timeout; + ctx->opts->retry_timeout = opts->retry_timeout; + ctx->opts->family_order = opts->family_order; DEBUG(3, ("Created new fail over context, retry timeout is %d\n", - retry_timeout)); + ctx->opts->retry_timeout)); return ctx; } @@ -166,7 +171,7 @@ get_server_status(struct fo_server *server) DEBUG(7, ("Status of server '%s' is '%s'\n", SERVER_NAME(server), str_server_status(server->common->server_status))); - timeout = server->service->ctx->retry_timeout; + timeout = server->service->ctx->opts->retry_timeout; if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) { gettimeofday(&tv, NULL); if (STATUS_DIFF(server->common, tv) > timeout) { @@ -193,7 +198,7 @@ get_port_status(struct fo_server *server) DEBUG(7, ("Port status of port %d for server '%s' is '%s'\n", server->port, SERVER_NAME(server), str_port_status(server->port_status))); - timeout = server->service->ctx->retry_timeout; + timeout = server->service->ctx->opts->retry_timeout; if (timeout != 0 && server->port_status == PORT_NOT_WORKING) { gettimeofday(&tv, NULL); if (STATUS_DIFF(server, tv) > timeout) { @@ -467,7 +472,8 @@ static void fo_resolve_service_done(struct tevent_req *subreq); struct tevent_req * fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, - struct resolv_ctx *resolv, struct fo_service *service) + struct resolv_ctx *resolv, struct fo_ctx *ctx, + struct fo_service *service) { int ret; struct fo_server *server; @@ -498,7 +504,8 @@ fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, switch (get_server_status(server)) { case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */ subreq = resolv_gethostbyname_send(server->common, ev, resolv, - server->common->name); + server->common->name, + ctx->opts->family_order); if (subreq == NULL) { ret = ENOMEM; goto done; diff --git a/src/providers/fail_over.h b/src/providers/fail_over.h index f1184832..ffcd0687 100644 --- a/src/providers/fail_over.h +++ b/src/providers/fail_over.h @@ -28,9 +28,10 @@ #include #include +#include "resolv/async_resolv.h" + /* Some forward declarations that don't have to do anything with fail over. */ struct hostent; -struct resolv_ctx; struct tevent_context; struct tevent_req; @@ -53,12 +54,26 @@ struct fo_service; struct fo_server; /* - * Create a new fail over context. The 'retry_timeout' argument specifies the + * Failover settings. + * + * The 'retry_timeout' member specifies the * duration in seconds of how long a server or port will be considered * non-working after being marked as such. + * + * The family_order member specifies the order of address families to + * try when looking up the service. + */ +struct fo_options { + time_t retry_timeout; + enum restrict_family family_order; +}; + +/* + * Create a new fail over context based on options passed in the + * opts parameter */ struct fo_ctx *fo_context_init(TALLOC_CTX *mem_ctx, - time_t retry_timeout); + struct fo_options *opts); /* * Create a new service structure for 'ctx', saving it to the location pointed @@ -94,6 +109,7 @@ int fo_add_server(struct fo_service *service, struct tevent_req *fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct resolv_ctx *resolv, + struct fo_ctx *ctx, struct fo_service *service); int fo_resolve_service_recv(struct tevent_req *req, diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c index 70d8d11e..d0c5b7a3 100644 --- a/src/resolv/async_resolv.c +++ b/src/resolv/async_resolv.c @@ -459,6 +459,8 @@ struct gethostbyname_state { /* Part of the query. */ const char *name; int family; + /* In which order to use IPv4, or v6 */ + enum restrict_family family_order; /* These are returned by ares. The hostent struct will be freed * when the user callback returns. */ struct hostent *hostent; @@ -472,14 +474,13 @@ ares_gethostbyname_wakeup(struct tevent_req *req); struct tevent_req * resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, - struct resolv_ctx *ctx, const char *name) + struct resolv_ctx *ctx, const char *name, + enum restrict_family family_order) { struct tevent_req *req, *subreq; struct gethostbyname_state *state; struct timeval tv = { 0, 0 }; - DEBUG(4, ("Trying to resolve A record of '%s'\n", name)); - if (ctx->channel == NULL) { DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); return NULL; @@ -491,11 +492,16 @@ resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, state->resolv_ctx = ctx; state->name = name; - state->family = AF_INET; state->hostent = NULL; state->status = 0; state->timeouts = 0; state->retrying = 0; + state->family_order = family_order; + state->family = (family_order < IPV6_ONLY) ? AF_INET : AF_INET6; + + DEBUG(4, ("Trying to resolve %s record of '%s'\n", + state->family == AF_INET ? "A" : "AAAA", + state->name)); /* We need to have a wrapper around ares_gethostbyname(), because * ares_gethostbyname() can in some cases call it's callback immediately. @@ -513,7 +519,7 @@ resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, } static void -resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent); +resolv_gethostbyname_next_done(void *arg, int status, int timeouts, struct hostent *hostent); static void resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent) @@ -545,14 +551,22 @@ resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *h if (status != ARES_SUCCESS) { if (status == ARES_ENOTFOUND || status == ARES_ENODATA) { - /* IPv4 failure. Try IPv6 */ - state->family = AF_INET6; + if (state->family_order == IPV4_FIRST) { + state->family = AF_INET6; + } else if (state->family_order == IPV6_FIRST) { + state->family = AF_INET; + } else { + tevent_req_error(req, return_code(status)); + return; + } + state->retrying = 0; state->timeouts = 0; - DEBUG(4, ("Trying to resolve AAAA record of '%s'\n", + DEBUG(4, ("Trying to resolve %s record of '%s'\n", + state->family == AF_INET ? "A" : "AAAA", state->name)); ares_gethostbyname(state->resolv_ctx->channel, state->name, - state->family, resolv_gethostbyname6_done, + state->family, resolv_gethostbyname_next_done, req); return; } @@ -568,7 +582,7 @@ resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *h } static void -resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent) +resolv_gethostbyname_next_done(void *arg, int status, int timeouts, struct hostent *hostent) { struct tevent_req *req = talloc_get_type(arg, struct tevent_req); struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); @@ -576,7 +590,7 @@ resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent * if (state->retrying == 0 && status == ARES_EDESTRUCTION) { state->retrying = 1; ares_gethostbyname(state->resolv_ctx->channel, state->name, - state->family, resolv_gethostbyname6_done, req); + state->family, resolv_gethostbyname_next_done, req); return; } diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h index 2ba6449b..5f87f12e 100644 --- a/src/resolv/async_resolv.h +++ b/src/resolv/async_resolv.h @@ -57,10 +57,18 @@ struct hostent *resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src); /** Get host by name **/ +enum restrict_family { + IPV4_ONLY, + IPV4_FIRST, + IPV6_ONLY, + IPV6_FIRST +}; + struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct resolv_ctx *ctx, - const char *name); + const char *name, + enum restrict_family family_order); int resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, diff --git a/src/tests/fail_over-tests.c b/src/tests/fail_over-tests.c index e794f03b..4e97a1e2 100644 --- a/src/tests/fail_over-tests.c +++ b/src/tests/fail_over-tests.c @@ -60,6 +60,7 @@ static struct test_ctx * setup_test(void) { struct test_ctx *ctx; + struct fo_options fopts; int ret; ctx = talloc_zero(global_talloc_context, struct test_ctx); @@ -77,7 +78,10 @@ setup_test(void) fail("Could not init resolv context"); } - ctx->fo_ctx = fo_context_init(ctx, 5 * 60); + fopts.retry_timeout = 30; + fopts.family_order = IPV4_FIRST; + + ctx->fo_ctx = fo_context_init(ctx, &fopts); if (ctx->fo_ctx == NULL) { talloc_free(ctx); fail("Could not init fail over context"); @@ -198,7 +202,9 @@ _get_request(struct test_ctx *test_ctx, struct fo_service *service, task->location = location; test_ctx->tasks++; - req = fo_resolve_service_send(test_ctx, test_ctx->ev, test_ctx->resolv, service); + req = fo_resolve_service_send(test_ctx, test_ctx->ev, + test_ctx->resolv, + test_ctx->fo_ctx, service); fail_if(req == NULL, "%s: fo_resolve_service_send() failed", location); tevent_req_set_callback(req, test_resolve_service_callback, task); diff --git a/src/tests/resolv-tests.c b/src/tests/resolv-tests.c index 04b9e2e7..e93d0f19 100644 --- a/src/tests/resolv-tests.c +++ b/src/tests/resolv-tests.c @@ -178,7 +178,8 @@ START_TEST(test_resolv_localhost) } check_leaks_push(test_ctx); - req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname); + req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, + test_ctx->resolv, hostname, IPV4_FIRST); DEBUG(7, ("Sent resolv_gethostbyname\n")); if (req == NULL) { ret = ENOMEM; @@ -232,7 +233,8 @@ START_TEST(test_resolv_negative) } check_leaks_push(test_ctx); - req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname); + req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, + test_ctx->resolv, hostname, IPV4_FIRST); DEBUG(7, ("Sent resolv_gethostbyname\n")); if (req == NULL) { ret = ENOMEM; @@ -292,6 +294,9 @@ static void test_internet(struct tevent_req *req) srvptr->host)); } break; + default: + recv_status = EINVAL; + break; } talloc_zfree(req); fail_if(recv_status != EOK, "The recv function failed: %d", recv_status); @@ -322,7 +327,8 @@ START_TEST(test_resolv_internet) test_ctx->tested_function = TESTING_HOSTNAME; check_leaks_push(test_ctx); - req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname); + req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, + test_ctx->resolv, hostname, IPV4_FIRST); DEBUG(7, ("Sent resolv_gethostbyname\n")); if (req == NULL) { ret = ENOMEM; @@ -425,7 +431,8 @@ START_TEST(test_resolv_free_context) return; } - req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname); + req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, + test_ctx->resolv, hostname, IPV4_FIRST); DEBUG(7, ("Sent resolv_gethostbyname\n")); if (req == NULL) { fail("Error calling resolv_gethostbyname_send"); @@ -484,7 +491,8 @@ START_TEST(test_resolv_free_req) } check_leaks_push(test_ctx); - req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, test_ctx->resolv, hostname); + req = resolv_gethostbyname_send(test_ctx, test_ctx->ev, + test_ctx->resolv, hostname, IPV4_FIRST); DEBUG(7, ("Sent resolv_gethostbyname\n")); if (req == NULL) { fail("Error calling resolv_gethostbyname_send"); -- cgit