diff --git a/CHANGES b/CHANGES index 32b52a8cb4..a8eda336d6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ + 173. [func] Keep a queue of zones waiting for zone transfer + quota so that a new transfer can be dispatched + immediately whenever quota becomes available. + 172. [bug] $TTL directive was sometimes missing from dumped master files because totext_ctx_init() failed to initialize ctx->current_ttl_valid. diff --git a/lib/dns/include/dns/events.h b/lib/dns/include/dns/events.h index f3ec2164a8..f669752638 100644 --- a/lib/dns/include/dns/events.h +++ b/lib/dns/include/dns/events.h @@ -49,6 +49,7 @@ #define DNS_EVENT_VIEWREQSHUTDOWN (ISC_EVENTCLASS_DNS + 22) #define DNS_EVENT_NOTIFYSENDTOADDR (ISC_EVENTCLASS_DNS + 23) #define DNS_EVENT_ZONE (ISC_EVENTCLASS_DNS + 24) +#define DNS_EVENT_ZONESTARTXFRIN (ISC_EVENTCLASS_DNS + 25) #define DNS_EVENT_FIRSTEVENT (ISC_EVENTCLASS_DNS + 0) #define DNS_EVENT_LASTEVENT (ISC_EVENTCLASS_DNS + 65535) diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 07b3358107..132785a8c5 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -96,8 +96,8 @@ typedef isc_uint32_t dns_ttl_t; typedef struct dns_validator dns_validator_t; typedef struct dns_view dns_view_t; typedef ISC_LIST(dns_view_t) dns_viewlist_t; -typedef struct dns_xfrinlist dns_xfrinlist_t; typedef struct dns_zone dns_zone_t; +typedef ISC_LIST(dns_zone_t) dns_zonelist_t; typedef struct dns_zonemgr dns_zonemgr_t; typedef struct dns_zt dns_zt_t; diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h index a2b4e1fb1e..7b4b852fb1 100644 --- a/lib/dns/include/dns/xfrin.h +++ b/lib/dns/include/dns/xfrin.h @@ -42,12 +42,6 @@ /* A transfer in progress. This is an opaque type. */ typedef struct dns_xfrin_ctx dns_xfrin_ctx_t; -/* A list of transfers in progress. */ -struct dns_xfrinlist { - isc_mutex_t lock; - ISC_LIST(dns_xfrin_ctx_t) transfers; -}; - /*** *** Functions ***/ @@ -55,7 +49,8 @@ struct dns_xfrinlist { ISC_LANG_BEGINDECLS isc_result_t -dns_xfrin_create(dns_zone_t *zone, isc_sockaddr_t *masteraddr, +dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, + isc_sockaddr_t *masteraddr, dns_tsigkey_t *tsigkey, isc_mem_t *mctx, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, isc_task_t *task, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp); @@ -84,10 +79,6 @@ void dns_xfrin_detach(dns_xfrin_ctx_t **xfrp); * only be one reference). */ -isc_result_t dns_xfrinlist_init(dns_xfrinlist_t *list); - -void dns_xfrinlist_destroy(dns_xfrinlist_t *list); - ISC_LANG_ENDDECLS #endif /* DNS_XFRIN_H */ diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 22580d298e..71a6752758 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -880,9 +880,6 @@ dns_zonemgr_settransfersperns(dns_zonemgr_t *zmgr, int value); int dns_zonemgr_getttransfersperns(dns_zonemgr_t *zmgr); -dns_xfrinlist_t * -dns_zonemgr_gettransferlist(dns_zonemgr_t *zmgr); - ISC_LANG_ENDDECLS #endif /* DNS_ZONE_H */ diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index a0d22ad49e..cdeba73c44 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -15,7 +15,7 @@ * SOFTWARE. */ -/* $Id: xfrin.c,v 1.68 2000/05/10 03:33:54 tale Exp $ */ +/* $Id: xfrin.c,v 1.69 2000/05/14 20:01:27 gson Exp $ */ #include @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -155,9 +154,6 @@ struct dns_xfrin_ctx { dns_journal_t *journal; } ixfr; - - ISC_LINK(dns_xfrin_ctx_t) link; - dns_xfrinlist_t *transferlist; }; #define XFRIN_MAGIC 0x58667269U /* XfrI. */ @@ -503,132 +499,21 @@ xfr_rr(dns_xfrin_ctx_t *xfr, } isc_result_t -dns_xfrin_create(dns_zone_t *zone, isc_sockaddr_t *masteraddr, +dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, + isc_sockaddr_t *masteraddr, dns_tsigkey_t *tsigkey, isc_mem_t *mctx, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, isc_task_t *task, dns_xfrindone_t done, dns_xfrin_ctx_t **xfrp) { - dns_name_t *zonename; - dns_xfrin_ctx_t *xfr, *x; + dns_name_t *zonename = dns_zone_getorigin(zone); + dns_xfrin_ctx_t *xfr; isc_result_t result; dns_db_t *db = NULL; - dns_rdatatype_t xfrtype; - dns_tsigkey_t *key = NULL; - dns_name_t *keyname = NULL; - isc_netaddr_t masterip; - dns_peer_t *peer = NULL; - int maxtransfersin, maxtransfersperns; - int nxfrsin, nxfrsperns; - dns_xfrinlist_t *transferlist; - + REQUIRE(xfrp != NULL && *xfrp == NULL); - zonename = dns_zone_getorigin(zone); - - xfrin_log1(ISC_LOG_INFO, zonename, masteraddr, "starting"); - - /* - * Find any configured information about the server we are about - * to transfer from. - */ - isc_netaddr_fromsockaddr(&masterip, masteraddr); - (void) dns_peerlist_peerbyaddr(dns_zone_getview(zone)->peers, - &masterip, &peer); - - result = dns_zone_getdb(zone, &db); - if (result == DNS_R_NOTLOADED) - INSIST(db == NULL); - else - CHECK(result); - - /* - * Decide whether we should request IXFR or AXFR. - */ - if (db == NULL) { - xfrin_log1(ISC_LOG_DEBUG(3), zonename, masteraddr, - "no database exists yet, " - "requesting AXFR of initial version"); - xfrtype = dns_rdatatype_axfr; - } else { - isc_boolean_t use_ixfr = ISC_TRUE; - if (peer != NULL && - dns_peer_getrequestixfr(peer, &use_ixfr) == - ISC_R_SUCCESS) { - ; /* Using peer setting */ - } else { - use_ixfr = dns_zone_getview(zone)->requestixfr; - } - if (use_ixfr == ISC_FALSE) { - xfrin_log1(ISC_LOG_DEBUG(3), zonename, masteraddr, - "IXFR disabled, requesting AXFR"); - xfrtype = dns_rdatatype_axfr; - } else { - xfrin_log1(ISC_LOG_DEBUG(3), zonename, masteraddr, - "requesting IXFR"); - xfrtype = dns_rdatatype_ixfr; - } - } - - /* - * Determine the maximum number of simultaneous transfers - * allowed for this server, then count the number of - * transfers already in progress and fail if the quota - * is already full. - * - * Count the number of transfers that are in progress from - * this master. We linearly scan a list of all transfers; - * if this turns out to be too slow, we could hash on the - * master address. - * - * Note that we must keep the transfer list locked for an - * awkwardly long time because the scanning of the list - * and the creation of a new entry must be done atomically, - * and we don't want to create the transfer object until we - * know there is quota available. - */ - maxtransfersin = - dns_zonemgr_getttransfersin(dns_zone_getmgr(zone)); - maxtransfersperns = - dns_zonemgr_getttransfersperns(dns_zone_getmgr(zone)); - if (peer != NULL) { - (void) dns_peer_gettransfers(peer, &maxtransfersperns); - } - - /* - * Determine if we should attempt to sign the request with TSIG. - */ - if (peer != NULL && dns_peer_getkey(peer, &keyname) == ISC_R_SUCCESS) { - dns_view_t *view = dns_zone_getview(zone); - result = dns_tsigkey_find(&key, keyname, NULL, - view->statickeys); - if (result == ISC_R_NOTFOUND) - result = dns_tsigkey_find(&key, keyname, NULL, - view->dynamickeys); - if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) - goto failure; - } + (void) dns_zone_getdb(zone, &db); - transferlist = dns_zonemgr_gettransferlist(dns_zone_getmgr(zone)); - LOCK(&transferlist->lock); - nxfrsin = nxfrsperns = 0; - for (x = ISC_LIST_HEAD(transferlist->transfers); - x != NULL; - x = ISC_LIST_NEXT(x, link)) - { - isc_netaddr_t xip; - isc_netaddr_fromsockaddr(&xip, &x->masteraddr); - nxfrsin++; - if (isc_netaddr_equal(&xip, &masterip)) - nxfrsperns++; - } - - if (nxfrsin >= maxtransfersin || nxfrsperns >= maxtransfersperns) { - result = ISC_R_QUOTA; - xfrin_log1(ISC_LOG_INFO, zonename, masteraddr, - "deferred: %s", isc_result_totext(result)); - goto unlock; - } - result = xfrin_create(mctx, zone, db, @@ -637,15 +522,11 @@ dns_xfrin_create(dns_zone_t *zone, isc_sockaddr_t *masteraddr, socketmgr, zonename, dns_zone_getclass(zone), xfrtype, - masteraddr, key, &xfr); + masteraddr, tsigkey, &xfr); if (result != ISC_R_SUCCESS) goto unlock; - xfr->transferlist = transferlist; - ISC_LIST_APPEND(transferlist->transfers, xfr, link); - unlock: - UNLOCK(&transferlist->lock); CHECK(result); CHECK(xfrin_start(xfr)); @@ -773,9 +654,6 @@ xfrin_create(isc_mem_t *mctx, xfr->axfr.add_func = NULL; xfr->axfr.add_private = NULL; - ISC_LINK_INIT(xfr, link); - xfr->transferlist = NULL; - CHECK(dns_name_dup(zonename, mctx, &xfr->name)); isc_interval_set(&maxinterval, dns_zone_getmaxxfrin(xfr->zone), 0); @@ -1267,13 +1145,6 @@ maybe_free(dns_xfrin_ctx_t *xfr) { xfrin_log(xfr, ISC_LOG_INFO, "end of transfer"); - if (xfr->transferlist != NULL) { - LOCK(&xfr->transferlist->lock); - ISC_LIST_UNLINK(xfr->transferlist->transfers, xfr, link); - UNLOCK(&xfr->transferlist->lock); - xfr->transferlist = NULL; - } - if (xfr->socket != NULL) isc_socket_detach(&xfr->socket); @@ -1369,14 +1240,3 @@ xfrin_log(dns_xfrin_ctx_t *xfr, unsigned int level, const char *fmt, ...) xfrin_logv(level, &xfr->name, &xfr->masteraddr, fmt, ap); va_end(ap); } - -isc_result_t -dns_xfrinlist_init(dns_xfrinlist_t *list) { - ISC_LIST_INIT(list->transfers); - return (isc_mutex_init(&list->lock)); -} - -void -dns_xfrinlist_destroy(dns_xfrinlist_t *list) { - isc_mutex_destroy(&list->lock); -} diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 5dd9c21b26..38122ba3b6 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -15,7 +15,7 @@ * SOFTWARE. */ -/* $Id: zone.c,v 1.112 2000/05/12 10:21:06 marka Exp $ */ +/* $Id: zone.c,v 1.113 2000/05/14 20:01:25 gson Exp $ */ #include @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -139,6 +141,16 @@ struct dns_zone { isc_event_t ctlevent; dns_ssutable_t *ssutable; dns_view_t *view; + /* + * Zones in certain states such as "waiting for zone transfer" + * or "zone transfer in progress" are kept on per-state linked lists + * in the zone manager using the 'statelink' field. The 'statelist' + * field points at the list the zone is currently on. It the zone + * is not on any such list, statelist is NULL. + */ + ISC_LINK(dns_zone_t) statelink; + dns_zonelist_t *statelist; + }; #define DNS_ZONE_FLAG(z,f) (((z)->flags & (f)) != 0) @@ -175,12 +187,13 @@ struct dns_zonemgr { isc_rwlock_t rwlock; isc_rwlock_t conflock; /* Locked by rwlock. */ - ISC_LIST(dns_zone_t) zones; + dns_zonelist_t zones; + dns_zonelist_t waiting_for_xfrin; + dns_zonelist_t xfrin_in_progress; + /* Locked by conflock. */ int transfersin; int transfersperns; - /* Contains its own lock. */ - dns_xfrinlist_t transferlist; }; /* @@ -202,7 +215,7 @@ static void cancel_refresh(dns_zone_t *); static void zone_log(dns_zone_t *zone, const char *, int, const char *msg, ...); -static void dns_zone_transfer_in(dns_zone_t *zone); +static void queue_xfrin(dns_zone_t *zone); static isc_result_t dns_zone_tostr(dns_zone_t *zone, isc_mem_t *mctx, char **s); static void zone_unload(dns_zone_t *zone); @@ -231,7 +244,10 @@ static isc_result_t notify_createmessage(dns_zone_t *zone, static void notify_done(isc_task_t *task, isc_event_t *event); static void notify_send_toaddr(isc_task_t *task, isc_event_t *event); static isc_result_t zone_dump(dns_zone_t *); - +static void got_transfer_quota(isc_task_t *task, isc_event_t *event); +static isc_result_t zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone); +static void zmgr_resume_xfrs(dns_zonemgr_t *zmgr); + #define PRINT_ZONE_REF(zone) \ do { \ char *s = NULL; \ @@ -331,6 +347,9 @@ dns_zone_create(dns_zone_t **zonep, isc_mem_t *mctx) { zone->diff_on_reload = ISC_FALSE; zone->ssutable = NULL; zone->view = NULL; + ISC_LINK_INIT(zone, statelink); + zone->statelist = NULL; + zone->magic = ZONE_MAGIC; ISC_EVENT_INIT(&zone->ctlevent, sizeof(zone->ctlevent), 0, NULL, DNS_EVENT_ZONECONTROL, zone_shutdown, zone, zone, @@ -349,7 +368,6 @@ zone_free(dns_zone_t *zone) { zone->flags |= DNS_ZONE_F_EXITING; UNLOCK(&zone->lock); - /* * Managed objects. Order is important. */ @@ -365,8 +383,10 @@ zone_free(dns_zone_t *zone) { isc_task_detach(&zone->task); if (zone->zmgr) dns_zonemgr_releasezone(zone->zmgr, zone); + + INSIST(zone->statelist == NULL); - /* unmanaged objects */ + /* Unmanaged objects */ if (zone->dbname != NULL) isc_mem_free(zone->mctx, zone->dbname); zone->dbname = NULL; @@ -1635,9 +1655,9 @@ notify_send_queue(notify_t *notify) { isc_result_t result; e = isc_event_allocate(notify->mctx, NULL, - DNS_EVENT_NOTIFYSENDTOADDR, - notify_send_toaddr, - notify, sizeof(isc_event_t)); + DNS_EVENT_NOTIFYSENDTOADDR, + notify_send_toaddr, + notify, sizeof(isc_event_t)); if (e == NULL) return (ISC_R_NOMEMORY); e->ev_arg = notify; @@ -1885,7 +1905,7 @@ dns_zone_notify(dns_zone_t *zone) { static void refresh_callback(isc_task_t *task, isc_event_t *event) { - char me[] = "refresh_callback"; + const char me[] = "refresh_callback"; dns_requestevent_t *revent = (dns_requestevent_t *)event; dns_zone_t *zone; dns_message_t *msg = NULL; @@ -2045,7 +2065,7 @@ refresh_callback(isc_task_t *task, isc_event_t *event) { tcp_transfer: isc_event_free(&event); dns_request_destroy(&zone->request); - dns_zone_transfer_in(zone); + queue_xfrin(zone); } else if (isc_serial_eq(soa.serial, zone->serial)) { dns_zone_uptodate(zone); goto next_master; @@ -2212,6 +2232,19 @@ zone_shutdown(isc_task_t *task, isc_event_t *event) { LOCK(&zone->lock); zone->flags |= DNS_ZONE_F_EXITING; UNLOCK(&zone->lock); + + /* + * If we were waiting for xfrin quota, step out of + * the queue. + */ + if (zone->statelist == &zone->zmgr->waiting_for_xfrin) { + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + ISC_LIST_UNLINK(zone->zmgr->waiting_for_xfrin, zone, + statelink); + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + zone->statelist = NULL; + } + if (zone->xfr != NULL) dns_xfrin_shutdown(zone->xfr); @@ -2720,8 +2753,7 @@ dns_zone_getjournalsize(dns_zone_t *zone) { } static void -zone_log(dns_zone_t *zone, const char *me, int level, - const char *fmt, ...) { +zone_log(dns_zone_t *zone, const char *me, int level, const char *fmt, ...) { va_list ap; char message[4096]; char namebuf[1024+32]; @@ -2881,7 +2913,7 @@ record_serial() { static void notify_done(isc_task_t *task, isc_event_t *event) { - char me[] = "notify_done"; + const char me[] = "notify_done"; notify_t *notify; dns_zone_t *zone = NULL; @@ -3112,6 +3144,16 @@ xfrdone(dns_zone_t *zone, isc_result_t result) { if (zone->xfr != NULL) dns_xfrin_detach(&zone->xfr); + /* + * This transfer finishing freed up a transfer quota slot. + * Let any zones waiting for quota have it. + */ + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + ISC_LIST_UNLINK(zone->zmgr->xfrin_in_progress, zone, statelink); + zone->statelist = NULL; + zmgr_resume_xfrs(zone->zmgr); + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_write); + /* * Retry with a different server if necessary. */ @@ -3134,28 +3176,152 @@ dns_zone_setssutable(dns_zone_t *zone, dns_ssutable_t *table) { zone->ssutable = table; } -/*** - *** Zone manager. - ***/ - static void -dns_zone_transfer_in(dns_zone_t *zone) { - const char me[] = "dns_zone_transfer_in"; +queue_xfrin(dns_zone_t *zone) { + const char me[] = "queue_xfrin"; isc_result_t result; + dns_zonemgr_t *zmgr = zone->zmgr; DNS_ENTER; - if (zone->masterscnt < 1) - return; + INSIST(zone->statelist == NULL); - result = dns_xfrin_create(zone, &zone->masteraddr, zone->mctx, + RWLOCK(&zmgr->rwlock, isc_rwlocktype_write); + ISC_LIST_APPEND(zmgr->waiting_for_xfrin, zone, statelink); + zone->statelist = &zmgr->waiting_for_xfrin; + result = zmgr_start_xfrin_ifquota(zmgr, zone); + RWUNLOCK(&zmgr->rwlock, isc_rwlocktype_write); + + if (result == ISC_R_QUOTA) { + zone_log(zone, me, ISC_LOG_DEBUG(1), + "zone transfer deferred due to quota"); + } else if (result != ISC_R_SUCCESS) { + zone_log(zone, me, ISC_LOG_ERROR, + "starting zone transfer: %s", + isc_result_totext(result)); + } +} + +/* + * Format a human-readable representation of the socket address '*sa' + * into the character array 'array', which is of size 'size'. + * The resulting string is guaranteed to be null-terminated. + */ +static void +sockaddr_format(isc_sockaddr_t *sa, char *array, unsigned int size) +{ + isc_result_t result; + isc_buffer_t buf; + + isc_buffer_init(&buf, array, size); + result = isc_sockaddr_totext(sa, &buf); + if (result != ISC_R_SUCCESS) { + snprintf(array, size, + "", + sa->type.sa.sa_family); + array[size - 1] = '\0'; + } +} + +/* + * This event callback is called when a zone has received + * any necessary zone transfer quota. This is the time + * to go ahead and start the transfer. + */ +static void +got_transfer_quota(isc_task_t *task, isc_event_t *event) { + const char me[] = "got_transfer_quota"; + isc_result_t result; + dns_peer_t *peer = NULL; + dns_tsigkey_t *tsigkey = NULL; + dns_name_t *keyname = NULL; + char mastertext[256]; + dns_rdatatype_t xfrtype; + dns_zone_t *zone = event->ev_arg; + isc_netaddr_t masterip; + + UNUSED(task); + + INSIST(task == zone->task); + + if (DNS_ZONE_FLAG(zone, DNS_ZONE_F_EXITING)) { + result = ISC_R_CANCELED; + goto cleanup; + } + + sockaddr_format(&zone->masteraddr, mastertext, sizeof(mastertext)); + + isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr); + (void)dns_peerlist_peerbyaddr(zone->view->peers, + &masterip, &peer); + + /* + * Decide whether we should request IXFR or AXFR. + */ + if (zone->db == NULL) { + zone_log(zone, me, ISC_LOG_DEBUG(3), + "no database exists yet, requesting AXFR of " + "initial version from %s", mastertext); + xfrtype = dns_rdatatype_axfr; + } else { + isc_boolean_t use_ixfr = ISC_TRUE; + if (peer != NULL && + dns_peer_getrequestixfr(peer, &use_ixfr) == + ISC_R_SUCCESS) { + ; /* Using peer setting */ + } else { + use_ixfr = zone->view->requestixfr; + } + if (use_ixfr == ISC_FALSE) { + zone_log(zone, me, ISC_LOG_DEBUG(3), + "IXFR disabled, requesting AXFR from %s", + mastertext); + xfrtype = dns_rdatatype_axfr; + } else { + zone_log(zone, me, ISC_LOG_DEBUG(3), + "requesting IXFR form %s", + mastertext); + xfrtype = dns_rdatatype_ixfr; + } + } + + /* + * Determine if we should attempt to sign the request with TSIG. + */ + if (peer != NULL && dns_peer_getkey(peer, &keyname) == ISC_R_SUCCESS) { + dns_view_t *view = dns_zone_getview(zone); + result = dns_tsigkey_find(&tsigkey, keyname, NULL, + view->statickeys); + if (result == ISC_R_NOTFOUND) + result = dns_tsigkey_find(&tsigkey, keyname, NULL, + view->dynamickeys); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + zone_log(zone, me, ISC_LOG_ERROR, + "error getting tsig keys for zone transfer: %s", + isc_result_totext(result)); + goto cleanup; + } + } + + result = dns_xfrin_create(zone, xfrtype, &zone->masteraddr, + tsigkey, zone->mctx, zone->zmgr->timermgr, zone->zmgr->socketmgr, - zone->task, - xfrdone, &zone->xfr); + zone->task, xfrdone, &zone->xfr); if (result != ISC_R_SUCCESS) xfrdone(zone, result); + + cleanup: + isc_event_free(&event); + + dns_zone_detach(&zone); + return; + } +/*** + *** Zone manager. + ***/ + isc_result_t dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, isc_socketmgr_t *socketmgr, @@ -3177,6 +3343,8 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, zmgr->task = NULL; zmgr->rl = NULL; ISC_LIST_INIT(zmgr->zones); + ISC_LIST_INIT(zmgr->waiting_for_xfrin); + ISC_LIST_INIT(zmgr->xfrin_in_progress); result = isc_rwlock_init(&zmgr->rwlock, 0, 0); if (result != ISC_R_SUCCESS) { UNEXPECTED_ERROR(__FILE__, __LINE__, @@ -3197,20 +3365,11 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, zmgr->transfersin = 10; zmgr->transfersperns = 2; - result = dns_xfrinlist_init(&zmgr->transferlist); - if (result != ISC_R_SUCCESS) { - UNEXPECTED_ERROR(__FILE__, __LINE__, - "dns_transferlist_init() failed: %s", - isc_result_totext(result)); - result = ISC_R_UNEXPECTED; - goto free_conflock; - } - /* Create the zone task pool. */ result = isc_taskpool_create(taskmgr, mctx, 8 /* XXX */, 0, &zmgr->zonetasks); if (result != ISC_R_SUCCESS) - goto free_transferlist; + goto free_conflock; /* Create a single task for queueing of SOA queries. */ result = isc_task_create(taskmgr, 1, &zmgr->task); @@ -3232,8 +3391,6 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr, free_task: isc_task_detach(&zmgr->task); - free_transferlist: - dns_xfrinlist_destroy(&zmgr->transferlist); free_taskpool: isc_taskpool_destroy(&zmgr->zonetasks); free_conflock: @@ -3386,13 +3543,143 @@ dns_zonemgr_getttransfersperns(dns_zonemgr_t *zmgr) { return (zmgr->transfersperns); } -dns_xfrinlist_t * -dns_zonemgr_gettransferlist(dns_zonemgr_t *zmgr) { - return (&zmgr->transferlist); +/* + * Try to start a new incoming zone transfer to fill a quota + * slot that was just vacated. + * + * Requires: + * The zone manager is locked by the caller. + */ +static void +zmgr_resume_xfrs(dns_zonemgr_t *zmgr) { + static char me[] = "zmgr_resume_xfrs"; + dns_zone_t *zone; + + for (zone = ISC_LIST_HEAD(zmgr->waiting_for_xfrin); + zone != NULL; + zone = ISC_LIST_NEXT(zone, statelink)) + { + isc_result_t result; + result = zmgr_start_xfrin_ifquota(zmgr, zone); + if (result == ISC_R_SUCCESS) { + /* + * We successfully filled the slot. We're done. + */ + break; + } else if (result == ISC_R_QUOTA) { + /* + * Not enough quota. This is probably the per-server + * quota, because we only get called when a unit of + * global quota has just been freed. Try the next + * zone, it may succeed if it uses another master. + */ + continue; + } else { + zone_log(zone, me, ISC_LOG_DEBUG(3), + "starting zone transfer: %s", + isc_result_totext(result)); + break; + } + } +} + +/* + * Try to start an incoming zone transfer for 'zone', quota permitting. + * + * Requires: + * The zone manager is locked by the caller. + * + * Returns: + * ISC_R_SUCCESS There was enough quota and we attempted to + * start a transfer. xfrdone() has been or will + * be called. + * ISC_R_QUOTA Not enough quota. + * Others Failure. + */ +static isc_result_t +zmgr_start_xfrin_ifquota(dns_zonemgr_t *zmgr, dns_zone_t *zone) { + dns_peer_t *peer = NULL; + isc_netaddr_t masterip; + int nxfrsin, nxfrsperns; + dns_zone_t *x; + int maxtransfersin, maxtransfersperns; + isc_event_t *e; + + /* + * Find any configured information about the server we'd + * like to transfer this zone from. + */ + isc_netaddr_fromsockaddr(&masterip, &zone->masteraddr); + (void)dns_peerlist_peerbyaddr(zone->view->peers, + &masterip, &peer); + + /* + * Determine the total maximum number of simultaneous + * transfers allowed, and the maximum for this specific + * master. + */ + maxtransfersin = zmgr->transfersin; + maxtransfersperns = zmgr->transfersperns; + if (peer != NULL) + (void)dns_peer_gettransfers(peer, &maxtransfersperns); + + /* + * Count the total number of transfers that are in progress, + * and the number of transfers in progress from this master. + * We linearly scan a list of all transfers; if this turns + * out to be too slow, we could hash on the master address. + */ + nxfrsin = nxfrsperns = 0; + for (x = ISC_LIST_HEAD(zmgr->xfrin_in_progress); + x != NULL; + x = ISC_LIST_NEXT(x, statelink)) + { + isc_netaddr_t xip; + isc_netaddr_fromsockaddr(&xip, &x->masteraddr); + nxfrsin++; + if (isc_netaddr_equal(&xip, &masterip)) + nxfrsperns++; + } + + /* Enforce quota. */ + if (nxfrsin >= maxtransfersin) + return (ISC_R_QUOTA); + + if (nxfrsperns >= maxtransfersperns) + return (ISC_R_QUOTA); + + /* + * We have sufficient quota. Move the zone to the "xfrin_in_progress" + * list and send it an event to let it start the actual transfer in the + * context of its own task. + */ + e = isc_event_allocate(zmgr->mctx, zmgr, + DNS_EVENT_ZONESTARTXFRIN, + got_transfer_quota, zone, + sizeof(isc_event_t)); + if (e == NULL) + return (ISC_R_NOMEMORY); + + LOCK(&zone->lock); + INSIST(zone->statelist == &zmgr->waiting_for_xfrin); + ISC_LIST_UNLINK(zmgr->waiting_for_xfrin, zone, statelink); + ISC_LIST_APPEND(zmgr->xfrin_in_progress, zone, statelink); + zone->statelist = &zmgr->xfrin_in_progress; + isc_task_send(zone->task, &e); + /* + * Make sure the zone does not go away before it has processed + * the event; in effect, the event is attached to the zone. + * We must use erefs rather than irefs because accesses + * to irefs are not locked. + */ + zone->erefs++; + UNLOCK(&zone->lock); + + return (ISC_R_SUCCESS); } #if 0 -/* hook for ondestroy notifcation from a database. */ +/* Hook for ondestroy notifcation from a database. */ static void dns_zonemgr_dbdestroyed(isc_task_t *task, isc_event_t *event) {