From c379d81508fbfa00ecb5da727ff7b097ebb29a3d Mon Sep 17 00:00:00 2001 From: Petr Spacek Date: Jul 13 2012 15:31:25 +0000 Subject: Add support for replicated environments to SOA serial autoincrement feature. 389 DS sends entry change notification even if modifyTimestamp was modified because of replication from another DS. This code computes digest from resource records in zone object and compares these digests for each received entry change notification. False entry change notifications are revealed this way and no incrementation is done in that case. http://fedorahosted.org/bind-dyndb-ldap/ticket/67 Signed-off-by: Petr Spacek --- diff --git a/src/ldap_driver.c b/src/ldap_driver.c index a87db18..cae45d4 100644 --- a/src/ldap_driver.c +++ b/src/ldap_driver.c @@ -110,7 +110,6 @@ static void *ldapdb_version = &dummy; static void free_ldapdb(ldapdb_t *ldapdb); static void detachnode(dns_db_t *db, dns_dbnode_t **targetp); -static unsigned int rdatalist_length(const dns_rdatalist_t *rdlist); static isc_result_t clone_rdatalist_to_rdataset(isc_mem_t *mctx, dns_rdatalist_t *rdlist, dns_rdataset_t *rdataset); @@ -545,6 +544,7 @@ found: goto skipfind; result = ldapdb_rdatalist_findrdatatype(&rdatalist, type, &rdlist); + if (result != ISC_R_SUCCESS) { /* No exact rdtype match. Check CNAME */ @@ -1006,20 +1006,6 @@ cleanup: return result; } -static unsigned int -rdatalist_length(const dns_rdatalist_t *rdlist) -{ - dns_rdata_t *ptr = HEAD(rdlist->rdata); - unsigned int length = 0; - - while (ptr != NULL) { - length++; - ptr = NEXT(ptr, link); - } - - return length; -} - static isc_result_t subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, dns_rdataset_t *rdataset, unsigned int options, diff --git a/src/ldap_helper.c b/src/ldap_helper.c index a50674b..0b1ed73 100644 --- a/src/ldap_helper.c +++ b/src/ldap_helper.c @@ -71,6 +71,7 @@ #include "ldap_entry.h" #include "ldap_helper.h" #include "log.h" +#include "rdlist.h" #include "semaphore.h" #include "settings.h" #include "str.h" @@ -1002,9 +1003,16 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst) isc_boolean_t publish = ISC_FALSE; isc_task_t *task = inst->task; isc_uint32_t ldap_serial; - isc_uint32_t zr_serial; + isc_uint32_t zr_serial; /* SOA serial value from in-memory zone register */ + unsigned char ldap_digest[RDLIST_DIGESTLENGTH]; + unsigned char *zr_digest = NULL; + ldapdb_rdatalist_t rdatalist; + + REQUIRE(entry != NULL); + REQUIRE(inst != NULL); dns_name_init(&name, NULL); + INIT_LIST(rdatalist); /* Derive the dns name of the zone from the DN. */ dn = entry->dn; @@ -1098,21 +1106,39 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst) * for a new zone (typically after BIND start) * - the zone was possibly changed in meanwhile */ if (publish) { + memset(ldap_digest, 0, RDLIST_DIGESTLENGTH); CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial)); - CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial)); + CHECK(zr_set_zone_serial_digest(inst->zone_register, &name, ldap_serial, + ldap_digest)); } /* SOA serial autoincrement feature for SOA record: - * 1) remember old SOA serial from zone register - * 2) compare old and new SOA serial from LDAP DB - * 3) if (old == new) then do autoincrement, remember new serial otherwise */ + * 1) Remember old (already processed) SOA serial and digest computed from + * zone root records in zone register. + * 2) After each change notification compare the old and new SOA serials + * and recomputed digests. If: + * 3a) Nothing was changed (false change notification received) - do nothing + * 3b) Serial was changed - remember the new serial and recompute digest, + * do not autoincrement (respect external change). + * 3c) The old and new serials are same: autoincrement only if something + * else was changed. + */ if (inst->serial_autoincrement) { CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial)); - CHECK(zr_get_zone_serial(inst->zone_register, &name, &zr_serial)); - if (ldap_serial == zr_serial) - CHECK(soa_serial_increment(inst->mctx, inst, &name)); - else - CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial)); + CHECK(ldapdb_rdatalist_get(inst->mctx, inst, &name, + &name, &rdatalist)); + CHECK(rdatalist_digest(inst->mctx, &rdatalist, ldap_digest)); + + CHECK(zr_get_zone_serial_digest(inst->zone_register, &name, &zr_serial, + &zr_digest)); + if (ldap_serial == zr_serial) { + /* serials are same - increment only if something was changed */ + if (memcmp(zr_digest, ldap_digest, RDLIST_DIGESTLENGTH) != 0) + CHECK(soa_serial_increment(inst->mctx, inst, &name)); + } else { /* serial in LDAP was changed - update zone register */ + CHECK(zr_set_zone_serial_digest(inst->zone_register, &name, + ldap_serial, ldap_digest)); + } } cleanup: @@ -1122,6 +1148,7 @@ cleanup: dns_name_free(&name, inst->mctx); if (zone != NULL) dns_zone_detach(&zone); + ldapdb_rdatalist_destroy(inst->mctx, &rdatalist); return result; } diff --git a/src/rdlist.c b/src/rdlist.c index 903c948..a16de45 100644 --- a/src/rdlist.c +++ b/src/rdlist.c @@ -2,7 +2,7 @@ * Authors: Adam Tkac * Martin Nagy * - * Copyright (C) 2009 Red Hat + * Copyright (C) 2009-2012 Red Hat * see file 'COPYING' for use and warranty information * * This program is free software; you can redistribute it and/or @@ -22,17 +22,27 @@ #include #include #include +#include +#include #include #include #include +#include #include "ldap_helper.h" /* TODO: Move things from ldap_helper here? */ #include "rdlist.h" #include "util.h" +/* useful only for RR sorting purposes */ +typedef struct rr_sort rr_sort_t; +struct rr_sort { + dns_rdatalist_t *rdatalist; /* contains RR class, type, TTL */ + isc_region_t rdatareg; /* handle to binary area with RR data */ +}; + static isc_result_t rdata_clone(isc_mem_t *mctx, dns_rdata_t *source, dns_rdata_t **targetp) { @@ -106,6 +116,121 @@ cleanup: return result; } +unsigned int +rdatalist_length(const dns_rdatalist_t *rdlist) +{ + dns_rdata_t *ptr = HEAD(rdlist->rdata); + unsigned int length = 0; + + while (ptr != NULL) { + length++; + ptr = NEXT(ptr, link); + } + + return length; +} + +static int +rr_sort_compare(const void *rdl1, const void *rdl2) { + const rr_sort_t *r1 = rdl1; + const rr_sort_t *r2 = rdl2; + int res; + + res = r1->rdatalist->rdclass - r2->rdatalist->rdclass; + if (res != 0) + return res; + + res = r1->rdatalist->type - r2->rdatalist->type; + if (res != 0) + return res; + + res = r1->rdatalist->ttl - r2->rdatalist->ttl; + if (res != 0) + return res; + + res = isc_region_compare((isc_region_t *)&r1->rdatareg, + (isc_region_t *)&r2->rdatareg); + + return res; +} + +/** + * Compute MD5 digest from all resource records in input rrdatalist. + * All RRs are sorted by class, type, ttl and data respectively. For this reason + * digest should be unambigous. + * + * @param rdlist[in] List of RRsets. Each RRset contains a list of individual RR + * @param digest[out] Pointer to unsigned char[RDLIST_DIGESTLENGTH] array + * @return ISC_R_SUCCESS and MD5 digest in unsigned char array "digest" + * In case of any error the array will stay untouched. + */ +isc_result_t +rdatalist_digest(isc_mem_t *mctx, ldapdb_rdatalist_t *rdlist, + unsigned char *digest) { + isc_result_t result; + isc_buffer_t *rrs = NULL; /* array of all resource records from input rdlist */ + unsigned int rrs_len = 0; + isc_md5_t md5ctx; + + REQUIRE(rdlist != NULL); + REQUIRE(digest != NULL); + + /* Compute count of RRs to avoid dynamic reallocations. + * The count is expected to be small number (< 20). */ + for (dns_rdatalist_t *rrset = HEAD(*rdlist); + rrset != NULL; + rrset = NEXT(rrset, link)) { + + rrs_len += rdatalist_length(rrset); + } + CHECK(isc_buffer_allocate(mctx, &rrs, rrs_len*sizeof(rr_sort_t))); + + /* Fill each rr_sort structure in array rrs with pointer to RRset + * and coresponding data region from each RR. rrs array will be sorted. */ + for (dns_rdatalist_t *rrset = HEAD(*rdlist); + rrset != NULL; + rrset = NEXT(rrset, link)) { + + for (dns_rdata_t *rr = HEAD(rrset->rdata); + rr != NULL; + rr = NEXT(rr, link)) { + + rr_sort_t rr_sort_rec; + rr_sort_rec.rdatalist = rrset; + dns_rdata_toregion(rr, &rr_sort_rec.rdatareg); + + isc_buffer_putmem(rrs, (const unsigned char *)(&rr_sort_rec), + sizeof(rr_sort_t)); + } + } + qsort(isc_buffer_base(rrs), rrs_len, sizeof(rr_sort_t), rr_sort_compare); + + isc_md5_init(&md5ctx); + for (unsigned int i = 0; i < rrs_len; i++ ) { + rr_sort_t *rr_rec = (rr_sort_t *)isc_buffer_base(rrs) + i; + isc_md5_update(&md5ctx, + (const unsigned char *)&rr_rec->rdatalist->rdclass, + sizeof(rr_rec->rdatalist->rdclass)); + isc_md5_update(&md5ctx, + (const unsigned char *)&rr_rec->rdatalist->type, + sizeof(rr_rec->rdatalist->type)); + isc_md5_update(&md5ctx, + (const unsigned char *)&rr_rec->rdatalist->ttl, + sizeof(rr_rec->rdatalist->ttl)); + isc_md5_update(&md5ctx, + (const unsigned char *)(rr_rec->rdatareg.base), + rr_rec->rdatareg.length); + } + isc_md5_final(&md5ctx, digest); + isc_md5_invalidate(&md5ctx); + +cleanup: + if (rrs != NULL) + isc_buffer_free(&rrs); + + return result; +} + isc_result_t ldap_rdatalist_copy(isc_mem_t *mctx, ldapdb_rdatalist_t source, ldapdb_rdatalist_t *target) diff --git a/src/rdlist.h b/src/rdlist.h index 04b9915..465197a 100644 --- a/src/rdlist.h +++ b/src/rdlist.h @@ -22,6 +22,12 @@ #ifndef _LD_RDLIST_H_ #define _LD_RDLIST_H_ +#include + +#include "types.h" + +#define RDLIST_DIGESTLENGTH ISC_MD5_DIGESTLENGTH + isc_result_t rdatalist_clone(isc_mem_t *mctx, dns_rdatalist_t *source, dns_rdatalist_t **targetp); @@ -30,4 +36,11 @@ isc_result_t ldap_rdatalist_copy(isc_mem_t *mctx, ldapdb_rdatalist_t source, ldapdb_rdatalist_t *target); +unsigned int +rdatalist_length(const dns_rdatalist_t *rdlist); + +isc_result_t +rdatalist_digest(isc_mem_t *mctx, ldapdb_rdatalist_t *rdlist, + unsigned char *digest); + #endif /* !_LD_RDLIST_H_ */ diff --git a/src/zone_register.c b/src/zone_register.c index 1de6bf1..b2b938f 100644 --- a/src/zone_register.c +++ b/src/zone_register.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ #include "log.h" #include "util.h" #include "zone_register.h" +#include "rdlist.h" /* * The zone register is a red-black tree that maps a dns name of a zone to the @@ -51,6 +53,7 @@ typedef struct { dns_zone_t *zone; char *dn; isc_uint32_t serial; /* last value processed by plugin (!= value in DB) */ + unsigned char digest[RDLIST_DIGESTLENGTH]; /* MD5 digest from all RRs in zone record */ } zone_info_t; /* Callback for dns_rbt_create(). */ @@ -316,17 +319,19 @@ zr_get_zone_ptr(zone_register_t *zr, dns_name_t *name, dns_zone_t **zonep) } /** - * Return last SOA serial value processed by autoincrement feature. + * Return last values processed by autoincrement feature. */ isc_result_t -zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp) +zr_get_zone_serial_digest(zone_register_t *zr, dns_name_t *name, + isc_uint32_t *serialp, unsigned char ** digestp) { isc_result_t result; - void *zinfo = NULL; + zone_info_t *zinfo = NULL; REQUIRE(zr != NULL); REQUIRE(name != NULL); REQUIRE(serialp != NULL); + REQUIRE(digestp != NULL && *digestp == NULL); if (!dns_name_isabsolute(name)) { log_bug("trying to find zone with a relative name"); @@ -335,9 +340,11 @@ zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp) RWLOCK(&zr->rwlock, isc_rwlocktype_read); - result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo); - if (result == ISC_R_SUCCESS) - *serialp = ((zone_info_t *)zinfo)->serial; + result = dns_rbt_findname(zr->rbt, name, 0, NULL, (void *)&zinfo); + if (result == ISC_R_SUCCESS) { + *serialp = zinfo->serial; + *digestp = zinfo->digest; + } RWUNLOCK(&zr->rwlock, isc_rwlocktype_read); @@ -345,16 +352,18 @@ zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp) } /** - * Set last SOA serial value processed by autoincrement feature. + * Set last SOA serial and digest from RRs processed by autoincrement feature. */ isc_result_t -zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial) +zr_set_zone_serial_digest(zone_register_t *zr, dns_name_t *name, + isc_uint32_t serial, unsigned char *digest) { isc_result_t result; - void *zinfo = NULL; + zone_info_t *zinfo = NULL; REQUIRE(zr != NULL); REQUIRE(name != NULL); + REQUIRE(digest != NULL); if (!dns_name_isabsolute(name)) { log_bug("trying to find zone with a relative name"); @@ -363,9 +372,11 @@ zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial) RWLOCK(&zr->rwlock, isc_rwlocktype_read); - result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo); - if (result == ISC_R_SUCCESS) - ((zone_info_t *)zinfo)->serial = serial; + result = dns_rbt_findname(zr->rbt, name, 0, NULL, (void *)&zinfo); + if (result == ISC_R_SUCCESS) { + zinfo->serial = serial; + memcpy(zinfo->digest, digest, RDLIST_DIGESTLENGTH); + } RWUNLOCK(&zr->rwlock, isc_rwlocktype_read); diff --git a/src/zone_register.h b/src/zone_register.h index 6ac3a92..dea2c9d 100644 --- a/src/zone_register.h +++ b/src/zone_register.h @@ -49,9 +49,11 @@ isc_mem_t * zr_get_mctx(zone_register_t *zr); isc_result_t -zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial); +zr_set_zone_serial_digest(zone_register_t *zr, dns_name_t *name, + isc_uint32_t serial, unsigned char *digest); isc_result_t -zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp); +zr_get_zone_serial_digest(zone_register_t *zr, dns_name_t *name, + isc_uint32_t *serialp, unsigned char ** digestp); #endif /* !_LD_ZONE_REGISTER_H_ */