diff --git a/CHANGES b/CHANGES index ad52987e2f..105907004c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +6255. [func] Expose data about incoming zone transfers in progress + using statistics channel. [GL #3883] + 6254. [cleanup] Add semantic patch to do an explicit cast from char to unsigned char in ctype.h class of functions. [GL #4327] diff --git a/bin/named/bind9.xsl b/bin/named/bind9.xsl index 91f59440d2..badb3a1e9a 100644 --- a/bin/named/bind9.xsl +++ b/bin/named/bind9.xsl @@ -303,6 +303,7 @@ Status, Server, Zones, + Incoming Zone Transfers, Network, Memory and Traffic Size

@@ -907,6 +908,59 @@ + + +

Incoming Zone Transfers for View

+ + + + + + + + + + + + + + + + + + + + + + + + + even + odd + + + + + + + + + + + + + + + + + + + + + +
Zone NameZone TypeLocal SerialRemote SerialIXFRStateAdditional Refresh QueuedLocal AddressRemote AddressTransportTSIG Key NameDuration (s)Messages ReceivedRecords ReceivedBytes Received
+
+

Memory Usage Summary

diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index 7edb92e2e6..191006ede0 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -33,7 +33,9 @@ #include #include #include +#include #include +#include #include #include @@ -1264,6 +1266,7 @@ cleanup: #define STATS_XML_STATUS 0x00 /* display only common statistics */ #define STATS_XML_SERVER 0x01 #define STATS_XML_ZONES 0x02 +#define STATS_XML_XFRINS 0x04 #define STATS_XML_NET 0x08 #define STATS_XML_MEM 0x10 #define STATS_XML_TRAFFIC 0x20 @@ -1449,6 +1452,221 @@ cleanup: return (ISC_R_FAILURE); } +static isc_result_t +xfrin_xmlrender(dns_zone_t *zone, void *arg) { + isc_result_t result; + char buf[1024 + 32]; /* sufficiently large for zone name and class */ + dns_rdataclass_t rdclass; + const char *ztype; + uint32_t serial; + const isc_sockaddr_t *addrp = NULL; + char addr_buf[ISC_SOCKADDR_FORMATSIZE]; + const dns_transport_t *transport = NULL; + xmlTextWriterPtr writer = arg; + dns_zonestat_level_t statlevel; + int xmlrc; + dns_xfrin_t *xfr = NULL; + bool is_running, is_deferred, is_pending; + bool needs_refresh; + bool is_first_data_received, is_ixfr; + unsigned int nmsg = 0; + unsigned int nrecs = 0; + uint64_t nbytes = 0; + + statlevel = dns_zone_getstatlevel(zone); + if (statlevel == dns_zonestat_none) { + return (ISC_R_SUCCESS); + } + + result = dns_zone_getxfr(zone, &xfr, &is_running, &is_deferred, + &is_pending, &needs_refresh); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + if (!is_running && !is_deferred && !is_pending && !needs_refresh) { + /* No ongoing/queued transfer. */ + goto cleanup; + } + + if (is_running && xfr == NULL) { + /* The transfer is finished, and it's shutting down. */ + goto cleanup; + } + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "xfrin")); + + dns_zone_nameonly(zone, buf, sizeof(buf)); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name", + ISC_XMLCHAR buf)); + + rdclass = dns_zone_getclass(zone); + dns_rdataclass_format(rdclass, buf, sizeof(buf)); + TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "class", + ISC_XMLCHAR buf)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "type")); + ztype = user_zonetype(zone); + if (ztype != NULL) { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR ztype)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "serial")); + if (dns_zone_getserial(zone, &serial) == ISC_R_SUCCESS) { + TRY0(xmlTextWriterWriteFormatString(writer, "%u", serial)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "remoteserial")); + if (is_running) { + serial = dns_xfrin_getendserial(xfr); + if (serial != 0) { + TRY0(xmlTextWriterWriteFormatString(writer, "%u", + serial)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "state")); + if (is_running) { + const char *xfr_state = NULL; + + dns_xfrin_getstate(xfr, &xfr_state, &is_first_data_received, + &is_ixfr); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR xfr_state)); + } else if (is_deferred) { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "Deferred")); + } else if (is_pending) { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "Pending")); + } else if (needs_refresh) { + TRY0(xmlTextWriterWriteString(writer, + ISC_XMLCHAR "Needs Refresh")); + } else { + UNREACHABLE(); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "refreshqueued")); + TRY0(xmlTextWriterWriteString( + writer, ISC_XMLCHAR(needs_refresh ? "Yes" : "No"))); + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "localaddr")); + if (is_running) { + addrp = dns_xfrin_getsourceaddr(xfr); + isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf)); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "remoteaddr")); + if (is_running) { + addrp = dns_xfrin_getprimaryaddr(xfr); + isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf)); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "transport")); + if (is_running) { + transport = dns_xfrin_gettransport(xfr); + if (transport == NULL || + dns_transport_get_type(transport) == DNS_TRANSPORT_TCP) + { + TRY0(xmlTextWriterWriteString(writer, + ISC_XMLCHAR "TCP")); + } else if (dns_transport_get_type(transport) == + DNS_TRANSPORT_TLS) + { + TRY0(xmlTextWriterWriteString(writer, + ISC_XMLCHAR "TLS")); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "-")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "tsigkeyname")); + if (is_running) { + const dns_name_t *tsigkeyname = dns_xfrin_gettsigkeyname(xfr); + char tsigkeyname_buf[DNS_NAME_FORMATSIZE]; + + if (tsigkeyname != NULL) { + dns_name_format(tsigkeyname, tsigkeyname_buf, + sizeof(tsigkeyname_buf)); + TRY0(xmlTextWriterWriteString( + writer, ISC_XMLCHAR tsigkeyname_buf)); + } + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "duration")); + if (is_running) { + isc_time_t start = dns_xfrin_getstarttime(xfr); + isc_time_t now = isc_time_now(); + isc_time_t diff; + uint32_t sec; + + isc_time_subtract(&now, &start, &diff); + sec = isc_time_seconds(&diff); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu32, sec)); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "0")); + } + TRY0(xmlTextWriterEndElement(writer)); + + if (is_running) { + dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes); + } + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "nmsg")); + TRY0(xmlTextWriterWriteFormatString(writer, "%u", nmsg)); + TRY0(xmlTextWriterEndElement(writer)); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "nrecs")); + TRY0(xmlTextWriterWriteFormatString(writer, "%u", nrecs)); + TRY0(xmlTextWriterEndElement(writer)); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "nbytes")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64, nbytes)); + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ixfr")); + if (is_running && is_first_data_received) { + TRY0(xmlTextWriterWriteString( + writer, ISC_XMLCHAR(is_ixfr ? "Yes" : "No"))); + } else { + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "")); + } + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterEndElement(writer)); /* xfrin */ + +cleanup: + if (xfr != NULL) { + dns_xfrin_detach(&xfr); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, + "Failed at xfrin_xmlrender()"); + } + return (result); +} + static isc_result_t generatexml(named_server_t *server, uint32_t flags, int *buflen, xmlChar **buf) { @@ -1727,8 +1945,8 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, */ view = ISC_LIST_HEAD(server->viewlist); TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "views")); - while (view != NULL && - ((flags & (STATS_XML_SERVER | STATS_XML_ZONES)) != 0)) + while (view != NULL && ((flags & (STATS_XML_SERVER | STATS_XML_ZONES | + STATS_XML_XFRINS)) != 0)) { isc_stats_t *istats = NULL; dns_stats_t *dstats = NULL; @@ -1746,6 +1964,14 @@ generatexml(named_server_t *server, uint32_t flags, int *buflen, TRY0(xmlTextWriterEndElement(writer)); /* /zones */ } + if ((flags & STATS_XML_XFRINS) != 0) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "xfrins")); + CHECK(dns_zt_apply(view->zonetable, true, NULL, + xfrin_xmlrender, writer)); + TRY0(xmlTextWriterEndElement(writer)); /* /xfrins */ + } + if ((flags & STATS_XML_SERVER) == 0) { TRY0(xmlTextWriterEndElement(writer)); /* /view */ view = ISC_LIST_NEXT(view, link); @@ -1934,6 +2160,17 @@ render_xml_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, freecb, freecb_args)); } +static isc_result_t +render_xml_xfrins(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_xml(STATS_XML_XFRINS, arg, retcode, retmsg, mimetype, b, + freecb, freecb_args)); +} + static isc_result_t render_xml_net(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg, unsigned int *retcode, const char **retmsg, @@ -1976,6 +2213,7 @@ render_xml_traffic(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, #define STATS_JSON_STATUS 0x00 /* display only common statistics */ #define STATS_JSON_SERVER 0x01 #define STATS_JSON_ZONES 0x02 +#define STATS_JSON_XFRINS 0x04 #define STATS_JSON_NET 0x08 #define STATS_JSON_MEM 0x10 #define STATS_JSON_TRAFFIC 0x20 @@ -2226,6 +2464,211 @@ cleanup: return (result); } +static isc_result_t +xfrin_jsonrender(dns_zone_t *zone, void *arg) { + isc_result_t result; + char buf[1024 + 32]; /* sufficiently large for zone name and class */ + char classbuf[64]; /* sufficiently large for class */ + char *zone_name_only = NULL; + char *class_only = NULL; + dns_rdataclass_t rdclass; + uint32_t serial; + json_object *xfrinarray = (json_object *)arg; + json_object *xfrinobj = NULL; + const isc_sockaddr_t *addrp = NULL; + char addr_buf[ISC_SOCKADDR_FORMATSIZE]; + const dns_transport_t *transport = NULL; + dns_zonestat_level_t statlevel; + dns_xfrin_t *xfr = NULL; + bool is_running, is_deferred, is_pending; + bool needs_refresh; + bool is_first_data_received, is_ixfr; + unsigned int nmsg = 0; + unsigned int nrecs = 0; + uint64_t nbytes = 0; + + statlevel = dns_zone_getstatlevel(zone); + if (statlevel == dns_zonestat_none) { + return (ISC_R_SUCCESS); + } + + dns_zone_nameonly(zone, buf, sizeof(buf)); + zone_name_only = buf; + + rdclass = dns_zone_getclass(zone); + dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf)); + class_only = classbuf; + + if (dns_zone_getserial(zone, &serial) != ISC_R_SUCCESS) { + xfrinobj = addzone(zone_name_only, class_only, + user_zonetype(zone), 0, false); + } else { + xfrinobj = addzone(zone_name_only, class_only, + user_zonetype(zone), serial, true); + } + + if (xfrinobj == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + result = dns_zone_getxfr(zone, &xfr, &is_running, &is_deferred, + &is_pending, &needs_refresh); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + goto cleanup; + } + + if (!is_running && !is_deferred && !is_pending && !needs_refresh) { + /* No ongoing/queued transfer. */ + goto cleanup; + } + + if (is_running && xfr == NULL) { + /* The transfer is finished, and it's shutting down. */ + goto cleanup; + } + + if (is_running) { + serial = dns_xfrin_getendserial(xfr); + if (serial != 0) { + json_object_object_add(xfrinobj, "remoteserial", + json_object_new_int64(serial)); + } + } + + if (is_running) { + const char *xfr_state = NULL; + + dns_xfrin_getstate(xfr, &xfr_state, &is_first_data_received, + &is_ixfr); + json_object_object_add(xfrinobj, "state", + json_object_new_string(xfr_state)); + } else if (is_deferred) { + json_object_object_add(xfrinobj, "state", + json_object_new_string("Deferred")); + } else if (is_pending) { + json_object_object_add(xfrinobj, "state", + json_object_new_string("Pending")); + } else if (needs_refresh) { + json_object_object_add(xfrinobj, "state", + json_object_new_string("Needs Refresh")); + } else { + UNREACHABLE(); + } + + json_object_object_add( + xfrinobj, "refreshqueued", + json_object_new_string(needs_refresh ? "Yes" : "No")); + + if (is_running) { + addrp = dns_xfrin_getsourceaddr(xfr); + isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf)); + json_object_object_add(xfrinobj, "localaddr", + json_object_new_string(addr_buf)); + } else { + json_object_object_add(xfrinobj, "localaddr", + json_object_new_string("-")); + } + + if (is_running) { + addrp = dns_xfrin_getprimaryaddr(xfr); + isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf)); + json_object_object_add(xfrinobj, "remoteaddr", + json_object_new_string(addr_buf)); + } else { + json_object_object_add(xfrinobj, "remoteaddr", + json_object_new_string("-")); + } + + if (is_running) { + transport = dns_xfrin_gettransport(xfr); + if (transport == NULL || + dns_transport_get_type(transport) == DNS_TRANSPORT_TCP) + { + json_object_object_add(xfrinobj, "transport", + json_object_new_string("TCP")); + } else if (dns_transport_get_type(transport) == + DNS_TRANSPORT_TLS) + { + json_object_object_add(xfrinobj, "transport", + json_object_new_string("TLS")); + } else { + json_object_object_add(xfrinobj, "transport", + json_object_new_string("-")); + } + } else { + json_object_object_add(xfrinobj, "transport", + json_object_new_string("-")); + } + + if (is_running) { + const dns_name_t *tsigkeyname = dns_xfrin_gettsigkeyname(xfr); + char tsigkeyname_buf[DNS_NAME_FORMATSIZE]; + + if (tsigkeyname != NULL) { + dns_name_format(tsigkeyname, tsigkeyname_buf, + sizeof(tsigkeyname_buf)); + json_object_object_add( + xfrinobj, "tsigkeyname", + json_object_new_string(tsigkeyname_buf)); + } else { + json_object_object_add(xfrinobj, "tsigkeyname", NULL); + } + } else { + json_object_object_add(xfrinobj, "tsigkeyname", NULL); + } + + if (is_running) { + isc_time_t start = dns_xfrin_getstarttime(xfr); + isc_time_t now = isc_time_now(); + isc_time_t diff; + uint32_t sec; + + isc_time_subtract(&now, &start, &diff); + sec = isc_time_seconds(&diff); + json_object_object_add(xfrinobj, "duration", + json_object_new_int64((int64_t)sec)); + } else { + json_object_object_add(xfrinobj, "duration", + json_object_new_int64(0)); + } + + if (is_running) { + dns_xfrin_getstats(xfr, &nmsg, &nrecs, &nbytes); + } + json_object_object_add(xfrinobj, "nmsg", + json_object_new_int64((int64_t)nmsg)); + json_object_object_add(xfrinobj, "nrecs", + json_object_new_int64((int64_t)nrecs)); + json_object_object_add( + xfrinobj, "nbytes", + json_object_new_int64(nbytes > INT64_MAX ? INT64_MAX + : (int64_t)nbytes)); + + if (is_running && is_first_data_received) { + json_object_object_add( + xfrinobj, "ixfr", + json_object_new_string(is_ixfr ? "Yes" : "No")); + } else { + json_object_object_add(xfrinobj, "ixfr", + json_object_new_string("")); + } + + json_object_array_add(xfrinarray, xfrinobj); + xfrinobj = NULL; + result = ISC_R_SUCCESS; + +cleanup: + if (xfr != NULL) { + dns_xfrin_detach(&xfr); + } + if (xfrinobj != NULL) { + json_object_put(xfrinobj); + } + return (result); +} + static isc_result_t generatejson(named_server_t *server, size_t *msglen, const char **msg, json_object **rootp, uint32_t flags) { @@ -2444,7 +2887,9 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, #endif /* ifdef HAVE_DNSTAP */ } - if ((flags & (STATS_JSON_ZONES | STATS_JSON_SERVER)) != 0) { + if ((flags & + (STATS_JSON_SERVER | STATS_JSON_ZONES | STATS_JSON_XFRINS)) != 0) + { viewlist = json_object_new_object(); CHECKMEM(viewlist); @@ -2452,7 +2897,7 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, view = ISC_LIST_HEAD(server->viewlist); while (view != NULL) { - json_object *za, *v = json_object_new_object(); + json_object *za, *xa, *v = json_object_new_object(); dns_adb_t *adb = NULL; CHECKMEM(v); @@ -2472,6 +2917,20 @@ generatejson(named_server_t *server, size_t *msglen, const char **msg, json_object_put(za); } + xa = json_object_new_array(); + CHECKMEM(xa); + + if ((flags & STATS_JSON_XFRINS) != 0) { + CHECK(dns_zt_apply(view->zonetable, true, NULL, + xfrin_jsonrender, xa)); + } + + if (json_object_array_length(xa) != 0) { + json_object_object_add(v, "xfrins", xa); + } else { + json_object_put(xa); + } + if ((flags & STATS_JSON_SERVER) != 0) { json_object *res = NULL; dns_stats_t *dstats = NULL; @@ -2855,6 +3314,17 @@ render_json_zones(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, freecb, freecb_args)); } +static isc_result_t +render_json_xfrins(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, + void *arg, unsigned int *retcode, const char **retmsg, + const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + UNUSED(httpd); + UNUSED(urlinfo); + return (render_json(STATS_JSON_XFRINS, arg, retcode, retmsg, mimetype, + b, freecb, freecb_args)); +} + static isc_result_t render_json_mem(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg, unsigned int *retcode, const char **retmsg, @@ -3060,6 +3530,9 @@ add_listener(named_server_t *server, named_statschannel_t **listenerp, isc_httpdmgr_addurl(listener->httpdmgr, "/xml/v" STATS_XML_VERSION_MAJOR "/zones", false, render_xml_zones, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/xml/v" STATS_XML_VERSION_MAJOR "/xfrins", false, + render_xml_xfrins, server); isc_httpdmgr_addurl(listener->httpdmgr, "/xml/v" STATS_XML_VERSION_MAJOR "/net", false, render_xml_net, server); @@ -3085,6 +3558,9 @@ add_listener(named_server_t *server, named_statschannel_t **listenerp, isc_httpdmgr_addurl(listener->httpdmgr, "/json/v" STATS_JSON_VERSION_MAJOR "/zones", false, render_json_zones, server); + isc_httpdmgr_addurl(listener->httpdmgr, + "/json/v" STATS_JSON_VERSION_MAJOR "/xfrins", false, + render_json_xfrins, server); isc_httpdmgr_addurl(listener->httpdmgr, "/json/v" STATS_JSON_VERSION_MAJOR "/net", false, render_json_net, server); diff --git a/bin/tests/system/statschannel/clean.sh b/bin/tests/system/statschannel/clean.sh index b0ab571b24..d1c381e629 100644 --- a/bin/tests/system/statschannel/clean.sh +++ b/bin/tests/system/statschannel/clean.sh @@ -32,4 +32,5 @@ rm -f traffic traffic.out.* traffic.json.* traffic.xml.* rm -f xml.*mem json.*mem rm -f xml.*stats json.*stats rm -f zones zones.out.* zones.json.* zones.xml.* zones.expect.* +rm -f xfrins xfrins.json.* xfrins.xml.* rm -rf ./__pycache__ diff --git a/bin/tests/system/statschannel/ns1/named.conf.in b/bin/tests/system/statschannel/ns1/named.conf.in index 52fd9fd855..52e8a7339f 100644 --- a/bin/tests/system/statschannel/ns1/named.conf.in +++ b/bin/tests/system/statschannel/ns1/named.conf.in @@ -37,6 +37,10 @@ controls { inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +server 10.53.0.3 { + transfer-format one-answer; +}; + zone "example" { type primary; file "example.db"; diff --git a/bin/tests/system/statschannel/tests.sh b/bin/tests/system/statschannel/tests.sh index db8bcc263d..6772ac8d1a 100644 --- a/bin/tests/system/statschannel/tests.sh +++ b/bin/tests/system/statschannel/tests.sh @@ -18,6 +18,7 @@ set -e DIGCMD="$DIG @10.53.0.2 -p ${PORT}" RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s" +NS_PARAMS="-X named.lock -m record -c named.conf -d 99 -g -U 4 -T maxcachesize=2097152" if ! $FEATURETEST --have-json-c then @@ -48,6 +49,32 @@ if [ ! "$PERL_JSON" ] && [ ! "$PERL_XML" ]; then exit 0 fi +retry_quiet_fast() { + __retries="${1}" + shift + + while :; do + if "$@"; then + return 0 + fi + __retries=$((__retries-1)) + if [ "${__retries}" -gt 0 ]; then + # sleep for 0.1 seconds + perl -e 'select(undef, undef, undef, .1)' + else + return 1 + fi + done +} + +wait_for_log_fast() ( + timeout="$1" + msg="$2" + file="$3" + retry_quiet_fast "$timeout" _search_log "$msg" "$file" && return 0 + echo_i "exceeded time limit waiting for literal '$msg' in $file" + return 1 +) getzones() { sleep 1 @@ -63,6 +90,19 @@ getzones() { return $result } +getxfrins() { + echo_i "... using $1" + case $1 in + xml) path='xml/v3/xfrins' ;; + json) path='json/v1/xfrins' ;; + *) return 1 ;; + esac + file=`$PERL fetch.pl -s 10.53.0.3 -p ${EXTRAPORT1} $path` + cp $file $file.$1.$3 + result=$? + return $result +} + # TODO: Move loadkeys_on to conf.sh.common loadkeys_on() { nsidx=$1 @@ -655,5 +695,38 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) +echo_i "Retransfering 'example' from ns1 to ns3 in slow mode ($n)" +ret=0 +i=0 +# Restart ns1 with '-T transferslowly' to see the xfrins information in ns3's statschannel while it's ongoing +stop_server ns1 +start_server --noclean --restart --port ${PORT} ns1 -- "-D statschannel-ns1 $NS_PARAMS -T transferslowly" +# Request a retransfer of the "example" zone +nextpart ns3/named.run > /dev/null +$RNDCCMD 10.53.0.3 retransfer example | sed "s/^/ns3 /" | cat_i +wait_for_log_fast 200 "zone example/IN: Transfer started" ns3/named.run || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +n=$((n + 1)) + +# We have now less than one second to catch the zone transfer in process +echo_i "Checking zone transfer information in the statistics channel ($n)" +ret=0 +i=0 +getxfrins xml example x$n || ret=1 +getxfrins json example j$n || ret=1 +grep -F 'Initial SOA' xfrins.xml.x$n >/dev/null || ret=1 +grep -F '"state":"Initial SOA"' xfrins.json.j$n >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +n=$((n + 1)) + +echo_i "Wait for slow zone transfer to complete ($n)" +ret=0 +wait_for_log 20 "zone example/IN: zone transfer finished: success" ns3/named.run || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) +n=$((n + 1)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 3ec2fffb91..332c189bb6 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -1932,7 +1932,8 @@ default is used. operations and the number of authoritative answers per query type. The default is ``terse``, providing minimal statistics on zones (including name and current serial number, but not query type - counters). + counters), and also information about the currently ongoing incoming zone + transfers. These statistics may be accessed via the ``statistics-channel`` or using :option:`rndc stats`, which dumps them to the file listed in the @@ -5655,6 +5656,7 @@ Broken-out subsets of the statistics can be viewed at http://127.0.0.1:8888/xml/v3/status (server uptime and last reconfiguration time), http://127.0.0.1:8888/xml/v3/server (server and resolver statistics), http://127.0.0.1:8888/xml/v3/zones (zone +statistics), http://127.0.0.1:8888/xml/v3/xfrins (incoming zone transfer statistics), http://127.0.0.1:8888/xml/v3/net (network status and socket statistics), http://127.0.0.1:8888/xml/v3/mem (memory manager statistics), and http://127.0.0.1:8888/xml/v3/traffic (traffic sizes). @@ -5664,6 +5666,7 @@ http://127.0.0.1:8888/json, with the broken-out subsets at http://127.0.0.1:8888/json/v1/status (server uptime and last reconfiguration time), http://127.0.0.1:8888/json/v1/server (server and resolver statistics), http://127.0.0.1:8888/json/v1/zones (zone +statistics), http://127.0.0.1:8888/json/v1/xfrins (incoming zone transfer statistics), http://127.0.0.1:8888/json/v1/net (network status and socket statistics), http://127.0.0.1:8888/json/v1/mem (memory manager statistics), and http://127.0.0.1:8888/json/v1/traffic (traffic sizes). @@ -7535,6 +7538,146 @@ Outgoing Queries The number of outgoing queries for each RR type sent from the internal resolver, maintained per view. +Incoming Zone Transfers + Information about in-progress incoming zone transfers. + + This section describes the information, which can be seen in the + HTML table about in-progress incoming zone transfers. It lists + the meaning, units and possible range of values of each column, + and the key/attribute/element name (in parentheses) for the JSON + and XML output formats. + + ``Zone Name`` (``name``) + Text string. This is the name of the zone being transferred, + as specified in the :any:`zone` declaration on this server. + + ``Zone Type`` (``type``) + Text string. This is the type of zone being transferred, as + specified in the ``zone`` declaration on this server. Possible + values are: ``secondary``, ``stub``, ``redirect``, ``mirror``. + + ``Local Serial`` (``serial``) + 32 bit unsigned Integer. This is the current (old) serial + number of the zone being transferred. It comes from the SOA + record held on the current server. + + ``Remote Serial`` (``remoteserial``) + 32 bit unsigned Integer. This is the new serial number of the + zone being transferred. It comes from the SOA record held on + the primary server from which the zone is being transferred. + + ``IXFR`` (``ixfr``) + Boolean. This says whether the transfer is incremental (using + IXFR) or full (using AXFR). Possible values are: ``Yes``, + ``No``. + + ``State`` (``state``) + Text string. This is the current state of the transfer for + this zone. Possible values and their meanings are: + + ``Needs Refresh`` + The zone is flagged for a refresh, but the process + hasn't started yet. + + ``Pending`` + The zone is flagged for a refresh, but the process is + in waiting state because of rate-limiting, see + :any:`serial-query-rate`. + + ``Deferred`` + The zone is going to be refreshed, but the process was + deferred due to quota, see :any:`transfers-in` and + :any:`transfers-per-ns`. + + ``SOA Query`` + Sending SOA query to get the zone serial number, then + follow with a zone transfer, if necessary. + + ``Got SOA`` + An answer for the SOA query from the previous step is + received, initiating a transfer. + + ``Initial SOA`` + Waiting for the transfer to start, which is expected + to begin with an initial SOA record. + + ``First Data`` + Waiting for the first data record of the transfer. + + ``Receiving IXFR Data`` + Receiving data for an IXFR type incremental zone + transfer. + + ``Finalizing IXFR`` + Finalizing an IXFR type incremental zone transfer. + + ``Receiving AXFR Data`` + Receiving data for an AXFR type zone transfer. + + ``Finalizing AXFR`` + Finalizing an AXFR type zone transfer. + + .. note:: + State names can change between BIND versions. + + ``Additional Refresh Queued`` (``refreshqueued``) + Boolean. This shows that the zone is flagged for a refresh. + This can be set to ``Yes`` either when the zone transfer is + still in one of the pending states (see the description of + the ``State`` column), or when the transfer is in a running + state, but the zone was marked for another refresh again (e.g. + because of "notify" request from a primary server). Possible + values are: ``Yes``, ``No``. + + ``Local Address`` (``localaddr``) + IP address - IPv4 or IPv6, as appropriate, and port number. + This shows the source address used to establish the connection + for the transfer. + + ``Remote Address`` (``remoteaddr``) + IP address - IPv4 or IPv6, as appropriate, and port number. + This shows the destination address used to establish the + connection for the transfer. + + ``Transport`` (``transport``) + Text string. This is the transport protocol in use for the + transfer. Possible values are: ``TCP``, ``TLS``. + + ``TSIG Key Name`` (``tsigkeyname``) + Text string. This is the name of the TSIG key specified for + use with this zone in the :any:`zone` declaration (if any). + + ``Duration (s)`` (``duration``) + 64 bit unsigned Integer. This is the time, in seconds, that + the transfer has been running so far. The clock starts after + the zone transfer process is initialized, and restarts shortly + after, when transport connection has been established and the + XFR request has been sent. + + ``Messages Received`` (``nmsg``) + 64 bit unsigned Integer. This is the number of DNS messages + received. It does not include transport overheads, such as + TCP ACK. + + ``Records Received`` (``nrecs``) + 64 bit unsigned Integer. This is the number of individual RRs + received so far. If an address record has, for example, five + addresses associated with the same name, it counts as five + RRs. + + ``Bytes Received`` (``nbytes``) + 64 bit unsigned Integer. This is the number of usable bytes + of DNS data. It does not include transport overhead. + + .. note:: + Depending on the current state of the transfer, some of the + values may be empty or set to ``-`` (meaning "not available"). + Also, in the case of the JSON output format, the corresponding + keys can be missing or values can be set to ``NULL``. For + example, it isn't known whether a transfer is using AXFR or + IXFR until the first data is received (see the description + of the ``State`` column). + Name Server Statistics Statistics counters for incoming request processing. diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 0cfdcb2f3f..28c11682c6 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -20,7 +20,8 @@ Security Fixes New Features ~~~~~~~~~~~~ -- None. +- The statstics channel now includes information about incoming zone transfers + currently in progress. :gl:`#3883` - The new :any:`resolver-use-dns64` option enables ``named`` to apply :any:`dns64` rules to IPv4 server addresses when sending recursive diff --git a/lib/dns/include/dns/transport.h b/lib/dns/include/dns/transport.h index e6499a97e7..705b1f4f76 100644 --- a/lib/dns/include/dns/transport.h +++ b/lib/dns/include/dns/transport.h @@ -40,23 +40,23 @@ dns_transport_new(const dns_name_t *name, dns_transport_type_t type, */ dns_transport_type_t -dns_transport_get_type(dns_transport_t *transport); +dns_transport_get_type(const dns_transport_t *transport); char * -dns_transport_get_certfile(dns_transport_t *transport); +dns_transport_get_certfile(const dns_transport_t *transport); char * -dns_transport_get_keyfile(dns_transport_t *transport); +dns_transport_get_keyfile(const dns_transport_t *transport); char * -dns_transport_get_cafile(dns_transport_t *transport); +dns_transport_get_cafile(const dns_transport_t *transport); char * -dns_transport_get_remote_hostname(dns_transport_t *transport); +dns_transport_get_remote_hostname(const dns_transport_t *transport); char * -dns_transport_get_endpoint(dns_transport_t *transport); +dns_transport_get_endpoint(const dns_transport_t *transport); dns_http_mode_t -dns_transport_get_mode(dns_transport_t *transport); +dns_transport_get_mode(const dns_transport_t *transport); char * -dns_transport_get_ciphers(dns_transport_t *transport); +dns_transport_get_ciphers(const dns_transport_t *transport); char * -dns_transport_get_tlsname(dns_transport_t *transport); +dns_transport_get_tlsname(const dns_transport_t *transport); uint32_t dns_transport_get_tls_versions(const dns_transport_t *transport); bool diff --git a/lib/dns/include/dns/xfrin.h b/lib/dns/include/dns/xfrin.h index 2d956a38b2..3d10bb42fe 100644 --- a/lib/dns/include/dns/xfrin.h +++ b/lib/dns/include/dns/xfrin.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -82,6 +83,109 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype, * the zone has a database. */ +isc_time_t +dns_xfrin_getstarttime(const dns_xfrin_t *xfr); +/*%< + * Get the start time of the xfrin object. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li Transfer start time + * + */ + +void +dns_xfrin_getstate(const dns_xfrin_t *xfr, const char **statestr, + bool *is_first_data_received, bool *is_ixfr); +/*%< + * Get the current state of the xfrin object as a character string, and whether + * it's currently known to be an IXFR transfer as a boolean value. + * + * Notes: + *\li The 'is_ixfr' value is valid only if 'is_first_data_received' is true. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + */ + +uint32_t +dns_xfrin_getendserial(const dns_xfrin_t *xfr); +/*%< + * Get the 'end_serial' of the xfrin object. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li Serial number of the new version zone (if it's already known), or 0. + * + */ + +void +dns_xfrin_getstats(dns_xfrin_t *xfr, unsigned int *nmsgp, unsigned int *nrecsp, + uint64_t *nbytesp); +/*%< + * Get various statistics values of the xfrin object: number of the received + * messages, number of the received records, number of the received bytes. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + */ + +const isc_sockaddr_t * +dns_xfrin_getsourceaddr(const dns_xfrin_t *xfr); +/*%< + * Get the source socket address of the xfrin object. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li const pointer to the zone transfer's source socket address + */ + +const isc_sockaddr_t * +dns_xfrin_getprimaryaddr(const dns_xfrin_t *xfr); +/*%< + * Get the socket address of the primary server of the xfrin object. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li const pointer to the zone transfer's primary server's socket address + */ + +const dns_transport_t * +dns_xfrin_gettransport(const dns_xfrin_t *xfr); +/*%< + * Get the trnasport of the xfrin object. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li const pointer to the zone transfer's transport + * + */ + +const dns_name_t * +dns_xfrin_gettsigkeyname(const dns_xfrin_t *xfr); +/*%< + * Get the name of the xfrin object's TSIG key. + * + * Requires: + *\li 'xfr' is a valid dns_xfrin_t. + * + * Returns: + *\li const pointer to the zone transfer's TSIG key's name or NULL + * + */ + void dns_xfrin_shutdown(dns_xfrin_t *xfr); /*%< diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index b7084676db..233793cc24 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -35,6 +35,7 @@ #include #include #include +#include #include /* Define to 1 for detailed reference tracing */ @@ -1789,6 +1790,28 @@ dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state); *\li 'state' to be a valid DNS_ZONESTATE_ constant. */ +isc_result_t +dns_zone_getxfr(dns_zone_t *zone, dns_xfrin_t **xfrp, bool *is_running, + bool *is_deferred, bool *is_pending, bool *needs_refresh); +/*%< + * Returns the xfrin associated with the zone (if any) with the current + * transfer states (as booleans). When no longer needed, the returned xfrin + * must be detached. + * + * Requires: + *\li 'zone' to be a valid zone. + *\li 'xfrp' to be non NULL and '*xfrp' to be NULL. + *\li 'is_running' to be non NULL. + *\li 'is_deferred' to be non NULL. + *\li 'is_pending' to be non NULL. + *\li 'needs_refresh' to be non NULL. + * + * Returns: + * ISC_R_SUCCESS xfrin exists and the information was returned. + * ISC_R_NOTFOUND no xfrin was found for the zone. + * ISC_R_FAILED error while trying to get the xfrin information + */ + void dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, isc_sockaddr_t *local, isc_time_t *now); diff --git a/lib/dns/transport.c b/lib/dns/transport.c index 010bff7de0..c7e01c0af4 100644 --- a/lib/dns/transport.c +++ b/lib/dns/transport.c @@ -94,49 +94,49 @@ list_add(dns_transport_list_t *list, const dns_name_t *name, } dns_transport_type_t -dns_transport_get_type(dns_transport_t *transport) { +dns_transport_get_type(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->type); } char * -dns_transport_get_certfile(dns_transport_t *transport) { +dns_transport_get_certfile(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.certfile); } char * -dns_transport_get_keyfile(dns_transport_t *transport) { +dns_transport_get_keyfile(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.keyfile); } char * -dns_transport_get_cafile(dns_transport_t *transport) { +dns_transport_get_cafile(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.cafile); } char * -dns_transport_get_remote_hostname(dns_transport_t *transport) { +dns_transport_get_remote_hostname(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.remote_hostname); } char * -dns_transport_get_endpoint(dns_transport_t *transport) { +dns_transport_get_endpoint(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->doh.endpoint); } dns_http_mode_t -dns_transport_get_mode(dns_transport_t *transport) { +dns_transport_get_mode(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->doh.mode); @@ -294,14 +294,14 @@ dns_transport_set_tlsname(dns_transport_t *transport, const char *tlsname) { } char * -dns_transport_get_ciphers(dns_transport_t *transport) { +dns_transport_get_ciphers(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.ciphers); } char * -dns_transport_get_tlsname(dns_transport_t *transport) { +dns_transport_get_tlsname(const dns_transport_t *transport) { REQUIRE(VALID_TRANSPORT(transport)); return (transport->tls.tlsname); diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 0ee5d3ac57..c830533a27 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -140,11 +141,14 @@ struct dns_xfrin { dns_diff_t diff; /*%< Pending database changes */ int difflen; /*%< Number of pending tuples */ - xfrin_state_t state; + _Atomic xfrin_state_t state; uint32_t end_serial; uint32_t expireopt; - bool edns, is_ixfr, expireoptset; + bool edns, expireoptset; + atomic_bool is_ixfr; + isc_mutex_t statslock; + /* Locked by statslock. */ unsigned int nmsg; /*%< Number of messages recvd */ unsigned int nrecs; /*%< Number of records recvd */ uint64_t nbytes; /*%< Number of bytes received */ @@ -269,7 +273,7 @@ static isc_result_t axfr_init(dns_xfrin_t *xfr) { isc_result_t result; - xfr->is_ixfr = false; + atomic_store(&xfr->is_ixfr, false); if (xfr->db != NULL) { dns_db_detach(&xfr->db); @@ -385,7 +389,7 @@ ixfr_init(dns_xfrin_t *xfr) { return (DNS_R_FORMERR); } - xfr->is_ixfr = true; + atomic_store(&xfr->is_ixfr, true); INSIST(xfr->db != NULL); xfr->difflen = 0; @@ -491,7 +495,9 @@ static isc_result_t xfr_rr(dns_xfrin_t *xfr, dns_name_t *name, uint32_t ttl, dns_rdata_t *rdata) { isc_result_t result; + LOCK(&xfr->statslock); xfr->nrecs++; + UNLOCK(&xfr->statslock); if (rdata->type == dns_rdatatype_none || dns_rdatatype_ismeta(rdata->type)) @@ -519,7 +525,7 @@ xfr_rr(dns_xfrin_t *xfr, dns_name_t *name, uint32_t ttl, dns_rdata_t *rdata) { } redo: - switch (xfr->state) { + switch (atomic_load(&xfr->state)) { case XFRST_SOAQUERY: if (rdata->type != dns_rdatatype_soa) { xfrin_log(xfr, ISC_LOG_NOTICE, @@ -536,7 +542,7 @@ redo: xfr->ixfr.request_serial, xfr->end_serial); FAIL(DNS_R_UPTODATE); } - xfr->state = XFRST_GOTSOA; + atomic_store(&xfr->state, XFRST_GOTSOA); break; case XFRST_GOTSOA: @@ -578,7 +584,7 @@ redo: xfr->firstsoa_data = isc_mem_allocate(xfr->mctx, rdata->length); memcpy(xfr->firstsoa_data, rdata->data, rdata->length); xfr->firstsoa.data = xfr->firstsoa_data; - xfr->state = XFRST_FIRSTDATA; + atomic_store(&xfr->state, XFRST_FIRSTDATA); break; case XFRST_FIRSTDATA: @@ -593,25 +599,25 @@ redo: xfrin_log(xfr, ISC_LOG_DEBUG(3), "got incremental response"); CHECK(ixfr_init(xfr)); - xfr->state = XFRST_IXFR_DELSOA; + atomic_store(&xfr->state, XFRST_IXFR_DELSOA); } else { xfrin_log(xfr, ISC_LOG_DEBUG(3), "got nonincremental response"); CHECK(axfr_init(xfr)); - xfr->state = XFRST_AXFR; + atomic_store(&xfr->state, XFRST_AXFR); } goto redo; case XFRST_IXFR_DELSOA: INSIST(rdata->type == dns_rdatatype_soa); CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata)); - xfr->state = XFRST_IXFR_DEL; + atomic_store(&xfr->state, XFRST_IXFR_DEL); break; case XFRST_IXFR_DEL: if (rdata->type == dns_rdatatype_soa) { uint32_t soa_serial = dns_soa_getserial(rdata); - xfr->state = XFRST_IXFR_ADDSOA; + atomic_store(&xfr->state, XFRST_IXFR_ADDSOA); xfr->ixfr.current_serial = soa_serial; goto redo; } @@ -621,7 +627,7 @@ redo: case XFRST_IXFR_ADDSOA: INSIST(rdata->type == dns_rdatatype_soa); CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata)); - xfr->state = XFRST_IXFR_ADD; + atomic_store(&xfr->state, XFRST_IXFR_ADD); break; case XFRST_IXFR_ADD: @@ -629,7 +635,7 @@ redo: uint32_t soa_serial = dns_soa_getserial(rdata); if (soa_serial == xfr->end_serial) { CHECK(ixfr_commit(xfr)); - xfr->state = XFRST_IXFR_END; + atomic_store(&xfr->state, XFRST_IXFR_END); break; } else if (soa_serial != xfr->ixfr.current_serial) { xfrin_log(xfr, ISC_LOG_NOTICE, @@ -639,7 +645,7 @@ redo: FAIL(DNS_R_FORMERR); } else { CHECK(ixfr_commit(xfr)); - xfr->state = XFRST_IXFR_DELSOA; + atomic_store(&xfr->state, XFRST_IXFR_DELSOA); goto redo; } } @@ -674,7 +680,7 @@ redo: FAIL(DNS_R_FORMERR); } CHECK(axfr_commit(xfr)); - xfr->state = XFRST_AXFR_END; + atomic_store(&xfr->state, XFRST_AXFR_END); break; } break; @@ -762,6 +768,110 @@ xfrin_idledout(void *xfr) { xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded"); } +isc_time_t +dns_xfrin_getstarttime(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + return (xfr->start); +} + +void +dns_xfrin_getstate(const dns_xfrin_t *xfr, const char **statestr, + bool *is_first_data_received, bool *is_ixfr) { + xfrin_state_t state; + + REQUIRE(VALID_XFRIN(xfr)); + REQUIRE(statestr != NULL && *statestr == NULL); + REQUIRE(is_ixfr != NULL); + + state = atomic_load(&xfr->state); + *statestr = ""; + *is_first_data_received = (state > XFRST_FIRSTDATA); + *is_ixfr = atomic_load(&xfr->is_ixfr); + + switch (state) { + case XFRST_SOAQUERY: + *statestr = "SOA Query"; + break; + case XFRST_GOTSOA: + *statestr = "Got SOA"; + break; + case XFRST_INITIALSOA: + *statestr = "Initial SOA"; + break; + case XFRST_FIRSTDATA: + *statestr = "First Data"; + break; + case XFRST_IXFR_DELSOA: + case XFRST_IXFR_DEL: + case XFRST_IXFR_ADDSOA: + case XFRST_IXFR_ADD: + *statestr = "Receiving IXFR Data"; + break; + case XFRST_IXFR_END: + *statestr = "Finalizing IXFR"; + break; + case XFRST_AXFR: + *statestr = "Receiving AXFR Data"; + break; + case XFRST_AXFR_END: + *statestr = "Finalizing AXFR"; + break; + } +} + +uint32_t +dns_xfrin_getendserial(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + return (xfr->end_serial); +} + +void +dns_xfrin_getstats(dns_xfrin_t *xfr, unsigned int *nmsgp, unsigned int *nrecsp, + uint64_t *nbytesp) { + REQUIRE(VALID_XFRIN(xfr)); + REQUIRE(nmsgp != NULL && nrecsp != NULL && nbytesp != NULL); + + LOCK(&xfr->statslock); + *nmsgp = xfr->nmsg; + *nrecsp = xfr->nrecs; + *nbytesp = xfr->nbytes; + UNLOCK(&xfr->statslock); +} + +const isc_sockaddr_t * +dns_xfrin_getsourceaddr(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + return (&xfr->sourceaddr); +} + +const isc_sockaddr_t * +dns_xfrin_getprimaryaddr(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + return (&xfr->primaryaddr); +} + +const dns_transport_t * +dns_xfrin_gettransport(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + return (xfr->transport); +} + +const dns_name_t * +dns_xfrin_gettsigkeyname(const dns_xfrin_t *xfr) { + REQUIRE(VALID_XFRIN(xfr)); + + if (xfr->tsigkey == NULL || xfr->tsigkey->key == NULL) { + return (NULL); + } + + return (dst_key_name(xfr->tsigkey->key)); +} + void dns_xfrin_shutdown(dns_xfrin_t *xfr) { REQUIRE(VALID_XFRIN(xfr)); @@ -823,7 +933,7 @@ xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg) { { xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg, isc_result_totext(result)); - if (xfr->is_ixfr) { + if (atomic_load(&xfr->is_ixfr)) { /* * Pass special result code to force AXFR retry */ @@ -877,7 +987,10 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, dns_view_weakattach(dns_zone_getview(zone), &xfr->view); dns_name_init(&xfr->name, NULL); + isc_mutex_init(&xfr->statslock); + atomic_init(&xfr->shuttingdown, false); + atomic_init(&xfr->is_ixfr, false); if (db != NULL) { dns_db_attach(db, &xfr->db); @@ -886,9 +999,9 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, dns_diff_init(xfr->mctx, &xfr->diff); if (reqtype == dns_rdatatype_soa) { - xfr->state = XFRST_SOAQUERY; + atomic_init(&xfr->state, XFRST_SOAQUERY); } else { - xfr->state = XFRST_INITIALSOA; + atomic_init(&xfr->state, XFRST_INITIALSOA); } xfr->start = isc_time_now(); @@ -1261,9 +1374,11 @@ xfrin_send_request(dns_xfrin_t *xfr) { CHECK(add_opt(msg, udpsize, reqnsid, reqexpire)); } + LOCK(&xfr->statslock); xfr->nmsg = 0; xfr->nrecs = 0; xfr->nbytes = 0; + UNLOCK(&xfr->statslock); xfr->start = isc_time_now(); msg->id = xfr->id; if (xfr->tsigctx != NULL) { @@ -1395,9 +1510,11 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { dns_message_setclass(msg, xfr->rdclass); + LOCK(&xfr->statslock); if (xfr->nmsg > 0) { msg->tcp_continuation = 1; } + UNLOCK(&xfr->statslock); isc_buffer_init(&buffer, region->base, region->length); isc_buffer_add(&buffer, region->length); @@ -1421,8 +1538,8 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { { if (result == ISC_R_SUCCESS && msg->rcode == dns_rcode_formerr && xfr->edns && - (xfr->state == XFRST_SOAQUERY || - xfr->state == XFRST_INITIALSOA)) + (atomic_load(&xfr->state) == XFRST_SOAQUERY || + atomic_load(&xfr->state) == XFRST_INITIALSOA)) { xfr->edns = false; dns_message_detach(&msg); @@ -1457,7 +1574,7 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { dns_message_detach(&msg); xfrin_reset(xfr); xfr->reqtype = dns_rdatatype_soa; - xfr->state = XFRST_SOAQUERY; + atomic_store(&xfr->state, XFRST_SOAQUERY); try_again: result = xfrin_start(xfr); if (result != ISC_R_SUCCESS) { @@ -1481,7 +1598,8 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { goto failure; } - if ((xfr->state == XFRST_SOAQUERY || xfr->state == XFRST_INITIALSOA) && + if ((atomic_load(&xfr->state) == XFRST_SOAQUERY || + atomic_load(&xfr->state) == XFRST_INITIALSOA) && msg->counts[DNS_SECTION_QUESTION] != 1) { xfrin_log(xfr, ISC_LOG_NOTICE, "missing question section"); @@ -1531,7 +1649,7 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { * if the first RR in the answer section is not a SOA record. */ if (xfr->reqtype == dns_rdatatype_ixfr && - xfr->state == XFRST_INITIALSOA && + atomic_load(&xfr->state) == XFRST_INITIALSOA && msg->counts[DNS_SECTION_ANSWER] == 0) { xfrin_log(xfr, ISC_LOG_DEBUG(3), @@ -1598,24 +1716,29 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig)); } else if (dns_message_gettsigkey(msg) != NULL) { xfr->sincetsig++; + LOCK(&xfr->statslock); if (xfr->sincetsig > 100 || xfr->nmsg == 0 || - xfr->state == XFRST_AXFR_END || - xfr->state == XFRST_IXFR_END) + atomic_load(&xfr->state) == XFRST_AXFR_END || + atomic_load(&xfr->state) == XFRST_IXFR_END) { + UNLOCK(&xfr->statslock); result = DNS_R_EXPECTEDTSIG; goto failure; } + UNLOCK(&xfr->statslock); } /* * Update the number of messages received. */ + LOCK(&xfr->statslock); xfr->nmsg++; /* * Update the number of bytes received. */ xfr->nbytes += buffer.used; + UNLOCK(&xfr->statslock); /* * Take the context back. @@ -1628,10 +1751,10 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) { get_edns_expire(xfr, msg); } - switch (xfr->state) { + switch (atomic_load(&xfr->state)) { case XFRST_GOTSOA: xfr->reqtype = dns_rdatatype_axfr; - xfr->state = XFRST_INITIALSOA; + atomic_store(&xfr->state, XFRST_INITIALSOA); CHECK(xfrin_send_request(xfr)); break; case XFRST_AXFR_END: @@ -1724,6 +1847,7 @@ xfrin_destroy(dns_xfrin_t *xfr) { if (msecs == 0) { msecs = 1; } + LOCK(&xfr->statslock); persec = (xfr->nbytes * 1000) / msecs; xfrin_log(xfr, ISC_LOG_INFO, "Transfer completed: %d messages, %d records, " @@ -1732,6 +1856,8 @@ xfrin_destroy(dns_xfrin_t *xfr) { xfr->nmsg, xfr->nrecs, xfr->nbytes, (unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000), (unsigned int)persec, xfr->end_serial); + UNLOCK(&xfr->statslock); + isc_mutex_destroy(&xfr->statslock); if (xfr->dispentry != NULL) { dns_dispatch_done(&xfr->dispentry); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 9a87540586..8e9c4ca5d6 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -19164,6 +19164,45 @@ dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state) { return (count); } +isc_result_t +dns_zone_getxfr(dns_zone_t *zone, dns_xfrin_t **xfrp, bool *is_running, + bool *is_deferred, bool *is_pending, bool *needs_refresh) { + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(xfrp != NULL && *xfrp == NULL); + + if (zone->zmgr == NULL) { + return (ISC_R_NOTFOUND); + } + + RWLOCK(&zone->zmgr->rwlock, isc_rwlocktype_read); + LOCK_ZONE(zone); + if (zone->xfr != NULL) { + dns_xfrin_attach(zone->xfr, xfrp); + } + if (zone->statelist == &zone->zmgr->xfrin_in_progress) { + *is_running = true; + *is_deferred = false; + *is_pending = false; + } else if (zone->statelist == &zone->zmgr->waiting_for_xfrin) { + *is_running = false; + *is_deferred = true; + *is_pending = false; + } else if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_REFRESH)) { + *is_running = false; + *is_deferred = false; + *is_pending = true; + } else { + *is_running = false; + *is_deferred = false; + *is_pending = false; + } + *needs_refresh = DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NEEDREFRESH); + UNLOCK_ZONE(zone); + RWUNLOCK(&zone->zmgr->rwlock, isc_rwlocktype_read); + + return (ISC_R_SUCCESS); +} + void dns_zone_lock_keyfiles(dns_zone_t *zone) { REQUIRE(DNS_ZONE_VALID(zone));