update.c revision 15a44745412679c30a6d022733925af70a38b715
* This module implements dynamic update as in RFC2136. - document strict minimality /**************************************************************************/ * Convenience macro of common isc_log_write() arguments * to use in reportings server errors. * Convenience macro of common isc_log_write() arguments * to use in tracing dynamic update protocol requests. * Convenience macro of common isc_log_write() arguments * to use in low-level debug tracing. * Check an operation for failure. These macros all assume that * the function using them has a 'result' variable and a 'failure' * Fail unconditionally with result 'code', which must not * be ISC_R_SUCCESS. The reason for failure presumably has * The test against ISC_R_SUCCESS is there to keep the Solaris compiler * from complaining about "end-of-loop code not reached". * Fail unconditionally and log as a client error. * The test against ISC_R_SUCCESS is there to keep the Solaris compiler * from complaining about "end-of-loop code not reached". "dynamic update failed: %s (%s)", \
* Fail unconditionally and log as a server error. * The test against ISC_R_SUCCESS is there to keep the Solaris compiler * from complaining about "end-of-loop code not reached". "dynamic update error: %s: %s", \
/**************************************************************************/ /**************************************************************************/ /**************************************************************************/ * Update a single RR in version 'ver' of 'db' and log the * '*tuple' == NULL. Either the tuple is freed, or its * ownership has been transferred to the diff. * Create a singleton diff. * Apply it to the database. * Merge it into the current pending journal entry. * Do not clear temp_diff. /**************************************************************************/ * Callback-style iteration over rdatasets and rdatas. * foreach_rrset() can be used to iterate over the RRsets * of a name and call a callback function with each * one. Similarly, foreach_rr() can be used to iterate * over the individual RRs at name, optionally restricted * to RRs of a given type. * The callback functions are called "actions" and take * two arguments: a void pointer for passing arbitrary * context information, and a pointer to the current RRset * or RR. By convention, their names end in "_action". * XXXRTH We might want to make this public somewhere in libdns. * Function type for foreach_rrset() iterator actions. * Function type for foreach_rr() iterator actions. * Internal context struct for foreach_node_rr(). * Internal helper function for foreach_node_rr(). * For each rdataset of 'name' in 'ver' of 'db', call 'action' * with the rdataset and 'action_data' as arguments. If the name * does not exist, do nothing. * If 'action' returns an error, abort iteration and return the error. * For each RR of 'name' in 'ver' of 'db', call 'action' * with the RR and 'action_data' as arguments. If the name * does not exist, do nothing. * If 'action' returns an error, abort iteration * For each of the RRs specified by 'db', 'ver', 'name', 'type', * (which can be dns_rdatatype_any to match any type), and 'covers', call * 'action' with the RR and 'action_data' as arguments. If the name * does not exist, or if no RRset of the given type exists at the name, * If 'action' returns an error, abort iteration and return the error. /**************************************************************************/ * Various tests on the database contents (for prerequisites, etc). * Function type for predicate functions that compare a database RR 'db_rr' * against an update RR 'update_rr'. * Helper function for rrset_exists(). * Utility macro for RR existence checking functions. * If the variable 'result' has the value ISC_R_EXISTS or * ISC_R_SUCCESS, set *exists to ISC_TRUE or ISC_FALSE, * respectively, and return success. * If 'result' has any other value, there was a failure. * Return the failure result code and do not set *exists. * This would be more readable as "do { if ... } while(0)", * but that form generates tons of warnings on Solaris 2.6. * Set '*exists' to true iff an rrset of the given type exists, * Helper function for cname_incompatible_rrset_exists. * Check whether there is an rrset incompatible with adding a CNAME RR, * i.e., anything but another CNAME (which can be replaced) or a * DNSSEC RR (which can coexist). * If such an incompatible rrset exists, set '*exists' to ISC_TRUE. * Otherwise, set it to ISC_FALSE. * Helper function for rr_count(). * Count the number of RRs of 'type' belonging to 'name' in 'ver' of 'db'. * Context struct for matching_rr_exists(). * Helper function for matching_rr_exists(). * Compare the 'update_rr' with all RRs in the RRset specified by 'db', * 'ver', 'name', and 'type' using 'predicate'. If the predicate returns * true for at least one of them, set '*exists' to ISC_TRUE. Otherwise, * Context struct and helper function for name_exists(). * Set '*exists' to true iff the given name exists, to false otherwise. * If we're deleting all records, it's ok to delete SIG and NXT even * if we're normally not allowed to. /**************************************************************************/ * Checking of "RRset exists (value dependent)" prerequisites. * In the RFC2136 section 3.2.5, this is the pseudocode involving * a variable called "temp", a mapping of <name, type> tuples to rrsets. * Here, we represent the "temp" data structure as (non-minimial) "dns_diff_t" * where each typle has op==DNS_DIFFOP_EXISTS. * Append a tuple asserting the existence of the RR with * 'name' and 'rdata' to 'diff'. * Compare two rdatasets represented as sorted lists of tuples. * All list elements must have the same owner name and type. * Return ISC_R_SUCCESS if the rdatasets are equal, rcode(dns_rcode_nxrrset) * A comparison function defining the sorting order for the entries * in the "temp" data structure. The major sort key is the owner name, * followed by the type and rdata. * Check the "RRset exists (value dependent)" prerequisite information * in 'temp' against the contents of the database 'db'. * Return ISC_R_SUCCESS if the prerequisites are satisfied, * rcode(dns_rcode_nxrrset) if not. /* Exit early if the list is empty (for efficiency only). */ * Sort the prerequisite records by owner name, * For each name and type in the prerequisites, * construct a sorted rdata list of the corresponding * database contents, and compare the lists. /* A new unique name begins here. */ /* A new unique type begins here. */ * Collect all database RRs for this name and type * onto d_rrs and sort them. * Collect all update RRs for this name and type * onto u_rrs. No need to sort them here - * they are already sorted. /* Compare the two sorted lists. */ * We are done with the tuples, but we can't free * them yet because "name" still points into one * of them. Move them on a temporary list. /**************************************************************************/ * Conditional deletion of RRs. * Context structure for delete_if(). * Predicate functions for delete_if(). * Return true iff 'update_rr' is neither a SOA nor an NS RR. * Return true iff the two RRs have identical rdata. * XXXRTH This is not a problem, but we should consider creating * dns_rdata_equal() (that used dns_name_equal()), since it * would be faster. Not a priority. * Return true iff 'update_rr' should replace 'db_rr' according * to the special RFC2136 rules for CNAME, SOA, and WKS records. * RFC2136 does not mention NXT, but multiple NXTs make little * sense, so we replace those, too. * Compare the address and protocol fields only. These * form the first five bytes of the RR data. Do a * raw binary comparison; unpacking the WKS RRs using * dns_rdata_tostruct() might be cleaner in some ways, * but it would require us to pass around an mctx. * Internal helper function for delete_if(). * Conditionally delete RRs. Apply 'predicate' to the RRs * specified by 'db', 'ver', 'name', and 'type' (which can * be dns_rdatatype_any to match any type). Delete those * RRs for which the predicate returns true, and log the /**************************************************************************/ * Miscellaneous subroutines. * Extract a single update RR from 'section' of dynamic update message * 'msg', with consistency checking. * Stores the owner name, rdata, and TTL of the update RR at 'name', * 'rdata', and 'ttl', respectively. * Increment the SOA serial number of database 'db', version 'ver'. * Replace the SOA record in the database, and log the * XXXRTH Failures in this routine will be worth logging, when * we have a logging system. Failure to find the zonename * or the SOA rdataset warrant at least an UNEXPECTED_ERROR(). * Check that the new SOA record at 'update_rdata' does not * illegally cause the SOA serial number to decrease or stay * unchanged relative to the existing SOA in 'db'. * Sets '*ok' to ISC_TRUE if the update is legal, ISC_FALSE if not. * William King points out that RFC2136 is inconsistent about * the case where the serial number stays unchanged: * section 3.4.2.2 requires a server to ignore a SOA update request * if the serial number on the update SOA is less_than_or_equal to * section 3.6 requires a server to ignore a SOA update request if * the serial is less_than the zone SOA serial. * Paul says 3.4.2.2 is correct. /**************************************************************************/ * Incremental updating of NXTs and SIGs. #
define MAXZONEKEYS 32 /* Maximum number of zone keys supported. */ * We abuse the dns_diff_t type to represent a set of domain names * affected by the update. * Helper function for non_nxt_rrset_exists(). * Check whether there is an rrset other than a NXT or SIG NXT, * i.e., anything that justifies the continued existence of a name * If such an rrset exists, set '*exists' to ISC_TRUE. * Otherwise, set it to ISC_FALSE. * A comparison function for sorting dns_diff_t:s by name. /* XXX should omit non-delegation types from NXT */ * In other words, skip empty database nodes and names that * have had their NXTs removed because they are obscured by * The iterator may hold the tree lock, and * rrset_exists() calls dns_db_findnode() which * may try to reacquire it. To avoid deadlock * we must pause the iterator first. * Add a NXT record for "name", recording the change in "diff". * Find the successor name, aka NXT target. * Create a diff tuple, update the database, and record the change. * Add a placeholder NXT record for "name", recording the change in "diff". unsigned char data[
1] = { 0 };
/* The root domain, no bits. */ * Add SIG records for an RRset, recording the change in "diff". unsigned char data[
1024];
/* XXX */ /* Get the rdataset to sign. */ for (i = 0; i <
nkeys; i++) {
/* Calculate the signature, creating a SIG RDATA. */ /* Update the database and journal with the SIG. */ /* XXX inefficient - will cause dataset merging */ * Update SIG and NXT records affected by an update. The original * update, including the SOA serial update but exluding the SIG & NXT * changes, is in "diff" and has already been applied to "newver" of "db". * The database version prior to the update is "oldver". * The necessary SIG and NXT changes will be applied to "newver" * and added (as a minimal diff) to "diff". * The SIGs generated will be valid for 'sigvalidityinterval' seconds. "could not get zone keys for secure " * Find all RRsets directly affected by the update, and * update their SIGs. Also build a list of names affected * by the update in "diffnames". /* Now "name" is a new, unique name affected by the update. */ * Now "name" and "type" denote a new unique RRset * affected by the update. * Delete all old SIGs covering this type, since they * are all invalid when the signed RRset has changed. * We may not be able to recreate all of them - tough. * If this RRset still exists after the update, * add a new signature for it. /* Skip any other updates to the same RRset. */ /* Remove orphaned NXTs and SIG NXTs. */ * When a name is created or deleted, its predecessor needs to * When names become obscured or unobscured in this update * transaction, we may find the wrong predecessor because * the NXTs have not yet been updated to reflect the delegation * change. This should not matter because in this case, * the correct predecessor is either the delegation node or * a newly unobscured node, and those nodes are on the * "affected" list in any case. /* Find names affected by delegation changes. */ * There was a delegation change. Mark all subdomains * of t->name as potentially needing a NXT update. * NXTs to make it so. We don't know the final NXT targets yet, * so we just create placeholder NXTs with arbitrary contents * to indicate that their respective owner names should be part of * This name is obscured. Delete any * This name is not obscured. It should have a NXT. * Now we know which names are part of the NXT chain. * Make them all point at their correct targets. * There is a NXT, but we don't know if it is correct. * Delete it and create a correct one to be sure. * If the update was unnecessary, the diff minimization * will take care of eliminating it from the journal, * The SIG bit should always be set in the NXTs * we generate, because they will all get SIG NXTs. * (XXX what if the zone keys are missing?). * Because the SIG NXTs have not necessarily been * created yet, the correctness of the bit mask relies * on the assumption that NXTs are only created if * there is other data, and if there is other data, * Minimize the set of NXT updates so that we don't * have to regenerate the SIG NXTs for NXTs that were * replaced with identical ones. /* Record our changes for the journal. */ for (i = 0; i <
nkeys; i++)
/**************************************************************************/ * The actual update code in all its glory. We try to follow * the RFC2136 pseudocode as closely as possible. "could not create update response message: %s",
* Interpret the zone section. "update zone section empty");
* The zone section must contain exactly one "question", and * it must be of type SOA. "update zone section contains non-SOA");
"update zone section contains multiple RRs");
/* The zone section must have exactly one name. */ "update zone section contains multiple RRs");
"not authoritative for update zone");
"update forwarding");
/* XXX implement */ "not authoritative for update zone");
* We failed without having sent an update event to the zone. * We are still in the client task context, so we can * simply give an error response without switching tasks. "prerequisite name is out of zone");
"class ANY prerequisite " "'name in use' prerequisite " /* RRset does not exist. */ "'rrset exists (value independent)' " "prerequisite not satisfied");
"class NONE prerequisite" "'name not in use' prerequisite " "'rrset does not exist' " "prerequisite not satisfied");
/* "temp<rr.name, rr.type> += rr;" */ "temp entry creation failed: %s",
* Perform the final check of the "rrset exists (value dependent)" "prerequisite not satisfied");
* Check Requestor's Permissions. It seems a bit silly to do this * only after prerequisite testing, but that is what RFC2136 says. /* This gets us a free log message. */ * Perform the Update Section Prescan. "update RR is outside zone");
* Check for meta-RRs. The RFC2136 pseudocode says * check for ANY|AXFR|MAILA|MAILB, but the text adds * "or any other QUERY metatype" "update RR has incorrect class %d",
* draft-ietf-dnsind-simple-secure-update-01 says * "Unlike traditional dynamic update, the client * is forbidden from updating NXT records." "explicit NXT updates are not allowed " "explicit SIG updates are currently not " "supported in secure zones");
"rejected by secure update");
"rejected by secure update");
* Process the Update Section. "attempt to add non-CNAME " "alongside CNAME ignored");
* Add an RR. If an identical RR already exists, * do nothing. If a similar but not identical * CNAME, SOA, or WKS exists, remove it first. "attempt to add existing RR " "delete all rrsets from a name");
"attempt to delete all SOA " "or NS records ignored");
* The (name == zonename) condition appears in * RFC2136 3.4.2.4 but is missing from the pseudocode. "attempt to delete last " * If any changes were made, increment the SOA serial number, * update SIGs and NXTs (if zone is secure), and write the update * Increment the SOA serial, but only if it was not * changed as a result of an update operation. * XXXRTH Just a note that this committing code will have to change * to handle databases that need two-phase commit, but this * Notify slaves of the change we just made. * The reason for failure should have been logged at this point.