diff options
author | Thomas Markwalder <tmark@isc.org> | 2017-12-08 11:56:10 -0500 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2017-12-08 11:56:10 -0500 |
commit | 905c58b9cac7b734c0518786476bd7c2e31b8977 (patch) | |
tree | 2710dcf3cd0c36bc686ce38e4dc8c7827cfbd8b7 | |
parent | 1aca5897f269bdfa922a000ec3f3e4b588a7b966 (diff) | |
download | isc-dhcp-905c58b9cac7b734c0518786476bd7c2e31b8977.tar.gz |
[master] Adds DDNS Dual Stack Mixed Mode
Merges in rt42620.
-rw-r--r-- | RELNOTES | 11 | ||||
-rw-r--r-- | common/dns.c | 884 | ||||
-rw-r--r-- | common/print.c | 185 | ||||
-rw-r--r-- | includes/dhcpd.h | 65 | ||||
-rw-r--r-- | includes/site.h | 1 | ||||
-rw-r--r-- | server/ddns.c | 537 | ||||
-rw-r--r-- | server/dhcpd.c | 43 | ||||
-rw-r--r-- | server/dhcpd.conf.5 | 115 | ||||
-rw-r--r-- | server/stables.c | 3 |
9 files changed, 1373 insertions, 471 deletions
@@ -278,6 +278,17 @@ dhcp-users@lists.isc.org. pool types: NA, TA, and PD. [ISC-Bugs #45292] +- Added three new server configuration parameters which influence DDNS: + 1. ddns-dual-stack-mixed-mode - alters DNS conflict resolution behavior + to mitigate issues with non-compliant clients in dual stack environments. + + 2. ddns-guard-id-must-match - relaxes the DHCID RR client id matching + requirement of DNS conflict resolution. + + 3. ddns-other-guard is-dynamic - alters dual-stack-mixed-mode behavior to + allow unguarded DNS entries to be overwritten in certain cases + [ISC-Bugs #42620] + Changes since 4.3.0 (bug fixes) - Tidy up several small tickets. diff --git a/common/dns.c b/common/dns.c index 63bbc860..86a1ecd1 100644 --- a/common/dns.c +++ b/common/dns.c @@ -64,7 +64,7 @@ * * You can also include IPv6 addresses via the primary6 and secondary6 * options. The search order for the addresses is primary, primary6, - * secondary and lastly secondary6, with a limit on the number of + * secondary and lastly secondary6, with a limit on the number of * addresses used. Currently this limit is 3. * * The DHCP server tries to find an existing zone for any given name by @@ -89,8 +89,8 @@ * this while hunting up a matching zone for a name, it looks up the SOA, * fills in the IP addresses, and uses that record for the update. * If the SOA lookup returns NXRRSET, a warning is printed and the zone is - * discarded, TSIG key and all. The search for the zone then continues - * as if the zone record hadn't been found. Zones without IP addresses + * discarded, TSIG key and all. The search for the zone then continues + * as if the zone record hadn't been found. Zones without IP addresses * don't match when initially hunting for a zone to update. * * When an update is attempted and no predefined zone is found @@ -152,6 +152,11 @@ typedef struct dhcp_ddns_rdata { dns_rdataset_t rdataset; } dhcp_ddns_data_t; +/* Function pointer type for functions which build DDNS update contents */ +typedef isc_result_t (*builder_func_t)(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, dns_name_t *uname); + #if defined (NSUPDATE) #if defined (DNS_ZONE_LOOKUP) @@ -168,7 +173,7 @@ typedef struct dhcp_ddns_rdata { */ typedef struct dhcp_ddns_ns { - struct dhcp_ddns_ns *next; + struct dhcp_ddns_ns *next; struct data_string oname; /* the original name for DDNS */ char *zname; /* a pointer into the original name for the zone we are checking */ @@ -176,7 +181,7 @@ typedef struct dhcp_ddns_ns { namelist, we can't free the eventp until we free the namelist */ dns_name_t *ns_name; /* current name server we are examining */ - dns_rdataset_t *rdataset; + dns_rdataset_t *rdataset; dns_rdatatype_t rdtype; /* type of address we want */ struct in_addr addrs[DHCP_MAXNS]; /* space for v4 addresses */ @@ -303,8 +308,8 @@ void ddns_interlude(isc_task_t *, isc_event_t *); /* * Structure used to map old pointers to new pointers. - * Old pointers are 8 bytes long as we don't know if the trace was - * done on a 64 bit or 32 bit machine. + * Old pointers are 8 bytes long as we don't know if the trace was + * done on a 64 bit or 32 bit machine. */ #define TRACE_PTR_LEN 8 @@ -332,7 +337,7 @@ trace_ddns_input_write(dhcp_ddns_cb_t *ddns_cb, isc_result_t result) trace_iov_t iov[2]; u_int32_t old_result; char old_pointer[TRACE_PTR_LEN]; - + old_result = htonl((u_int32_t)result); memset(old_pointer, 0, TRACE_PTR_LEN); memcpy(old_pointer, &ddns_cb, sizeof(ddns_cb)); @@ -384,7 +389,7 @@ trace_ddns_input_read(trace_type_t *ttype, unsigned length, if (ddns_map_ptr == NULL) { log_error("trace_dns_input_read: unable to map cb pointer"); return; - } + } eventp = (dns_clientupdateevent_t *) isc_event_allocate(dhcp_gbl_ctx.mctx, @@ -418,7 +423,7 @@ trace_ddns_input_stop(trace_type_t *ttype) * If we are doing playback we read the next packet from the file * and compare the type. If it matches we extract the results and pointer * from the trace file. The results are returned to the caller as if - * they had called the dns routine. The pointer is used to construct a + * they had called the dns routine. The pointer is used to construct a * map for when the "reply" is processed. * * The data written to trace file is: @@ -439,7 +444,7 @@ trace_ddns_output_write(dns_client_t *client, dns_rdataclass_t rdclass, u_int32_t old_result; char old_pointer[TRACE_PTR_LEN]; dhcp_ddns_map_t *ddns_map_ptr; - + if (trace_playback() != 0) { /* We are doing playback, extract the entry from the file */ unsigned buflen = 0; @@ -477,7 +482,7 @@ trace_ddns_output_write(dns_client_t *client, dns_rdataclass_t rdclass, if (ddns_map_ptr == NULL) { ddns_map_ptr = dmalloc(sizeof(*ddns_map_ptr), MDL); if (ddns_map_ptr == NULL) { - log_error("trace_ddns_output_write: " + log_error("trace_ddns_output_write: " "unable to allocate map entry"); return(ISC_R_FAILURE); } @@ -572,7 +577,7 @@ ddns_cb_alloc(const char *file, int line) return(ddns_cb); } - + void ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) { @@ -583,7 +588,7 @@ ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) data_string_forget(&ddns_cb->fwd_name, file, line); data_string_forget(&ddns_cb->rev_name, file, line); data_string_forget(&ddns_cb->dhcid, file, line); - + if (ddns_cb->zone != NULL) { forget_zone((struct dns_zone **)&ddns_cb->zone); } @@ -675,7 +680,7 @@ isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name) dns_zone_hash_delete(dns_zone_hash, (*zone)->name, 0, MDL); dns_zone_dereference(zone, MDL); status = ISC_R_NOTFOUND; - } else + } else status = ISC_R_SUCCESS; if (tname) @@ -737,7 +742,7 @@ int dns_zone_dereference (ptr, file, line) #if defined (NSUPDATE) #if defined (DNS_ZONE_LOOKUP) -/* Helper function to copy the address from an rdataset to +/* Helper function to copy the address from an rdataset to * the nameserver control block. Mostly to avoid really long * lines in the nested for loops */ @@ -748,7 +753,7 @@ zone_addr_to_ns(dhcp_ddns_ns_t *ns_cb, dns_rdata_t rdata; dns_rdata_in_a_t a; dns_rdata_in_aaaa_t aaaa; - + dns_rdata_init(&rdata); dns_rdataset_current(rdataset, &rdata); switch (rdataset->type) { @@ -815,7 +820,7 @@ find_zone_addrs(isc_task_t *taskp, dns_name_t *name; dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_ns_t ns; - + /* the transaction is done, get rid of the tag */ dns_client_destroyrestrans(&ns_cb->transaction); @@ -871,7 +876,7 @@ find_zone_addrs(isc_task_t *taskp, for (; rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { - + if (rdataset->type != dns_rdatatype_ns) continue; dns_rdata_init(&rdata); @@ -888,10 +893,10 @@ find_zone_addrs(isc_task_t *taskp, } } else { if ((!dns_rdataset_isassociated(rdataset)) || - (dns_rdataset_first(rdataset) != + (dns_rdataset_first(rdataset) != ISC_R_SUCCESS)) continue; - } + } dns_rdataset_current(rdataset, &rdata); if (dns_rdata_tostruct(&rdata, &ns, NULL) != @@ -962,7 +967,7 @@ find_zone_addrs(isc_task_t *taskp, isc_event_free(&eventp); return; - + } /* @@ -970,7 +975,7 @@ find_zone_addrs(isc_task_t *taskp, * This is routine is called when we are still trying to get a list * of nameservers to process. */ - + void find_zone_ns(isc_task_t *taskp, isc_event_t *eventp) @@ -1019,7 +1024,7 @@ find_zone_ns(isc_task_t *taskp, isc_result_totext(result)); goto cleanup; } - + /* we have successfully started the next iteration * of this step, clean up from the call and continue */ dns_client_freeresanswer(dhcp_gbl_ctx.dnsclient, @@ -1044,7 +1049,7 @@ find_zone_ns(isc_task_t *taskp, continue; if ((!dns_rdataset_isassociated(rdataset)) || - (dns_rdataset_first(rdataset) != + (dns_rdataset_first(rdataset) != ISC_R_SUCCESS)) continue; @@ -1104,7 +1109,7 @@ find_zone_ns(isc_task_t *taskp, data_string_forget(&ns_cb->oname, MDL); dfree(ns_cb, MDL); return; - + } /* @@ -1116,7 +1121,7 @@ find_zone_ns(isc_task_t *taskp, * the control block will be filled in as we continue processing. */ isc_result_t -find_zone_start(dhcp_ddns_cb_t *ddns_cb, int direction) +find_zone_start(dhcp_ddns_cb_t *ddns_cb, int direction) { isc_result_t status = ISC_R_NOTFOUND; dhcp_ddns_ns_t *ns_cb; @@ -1186,7 +1191,7 @@ find_zone_start(dhcp_ddns_cb_t *ddns_cb, int direction) #endif isc_result_t -find_cached_zone(dhcp_ddns_cb_t *ddns_cb, int direction) +find_cached_zone(dhcp_ddns_cb_t *ddns_cb, int direction) { isc_result_t status = ISC_R_NOTFOUND; const char *np; @@ -1408,7 +1413,7 @@ void cache_found_zone(dhcp_ddns_ns_t *ns_cb) goto cleanup; } memcpy(zone->primary->data.buffer->data, ns_cb->addrs, len); - zone->primary->data.data = + zone->primary->data.data = &zone->primary->data.buffer->data[0]; zone->primary->data.len = len; } @@ -1422,7 +1427,7 @@ void cache_found_zone(dhcp_ddns_ns_t *ns_cb) goto cleanup; } memcpy(zone->primary6->data.buffer->data, ns_cb->addrs6, len); - zone->primary6->data.data = + zone->primary6->data.data = &zone->primary6->data.buffer->data[0]; zone->primary6->data.len = len; } @@ -1515,7 +1520,7 @@ int get_std_dhcid(dhcp_ddns_cb_t *ddns_cb, * This version of the function is for the interim style. It is retained * to allow users to continue using the interim style but they should * switch to the standard style (which uses get_std_dhcid) for better - * interoperability. + * interoperability. * * This function takes information from the type and data fields and * mangles it into a dhcid string which it places in ddns_cb. It also @@ -1552,7 +1557,7 @@ int get_int_dhcid (dhcp_ddns_cb_t *ddns_cb, id->data = id->buffer->data; /* - * We put the length into the first byte to turn + * We put the length into the first byte to turn * this into a dns text string. This avoid needing to * copy the string to add the byte later. */ @@ -1565,7 +1570,7 @@ int get_int_dhcid (dhcp_ddns_cb_t *ddns_cb, * to avoid disturbing customer's lease files */ id->buffer->data[2] = "0123456789abcdef"[type % 15]; - + /* Mash together an MD5 hash of the identifier. */ isc_md5_init(&md5); isc_md5_update(&md5, data, len); @@ -1593,7 +1598,7 @@ int get_dhcid(dhcp_ddns_cb_t *ddns_cb, { if (ddns_cb->dhcid_class == dns_rdatatype_dhcid) return get_std_dhcid(ddns_cb, type, identifier, id_len); - else + else return get_int_dhcid(ddns_cb, type, identifier, id_len); } @@ -1639,7 +1644,7 @@ dhcid_fromlease(struct data_string *dhcid, return(ISC_R_SUCCESS); } -/* +/* * Construct the dataset for this item. * This is a fairly simple arrangement as the operations we do are simple. * If there is data we simply have the rdata point to it - the formatting @@ -1703,6 +1708,27 @@ make_dns_dataset(dns_rdataclass_t dataclass, return(ISC_R_SUCCESS); } +#if defined (DEBUG_DNS_UPDATES) +static void log_call(char *text, dns_name_t* pname, dns_name_t* uname) { + char buf1[512]; + char buf2[512]; + if (pname) { + dns_name_format(pname, buf1, 512); + } else { + *buf1=0; + } + + if (uname) { + dns_name_format(uname, buf2, 512); + } else { + *buf2=0; + } + + log_info ("DDNS: %s: pname:[%s] uname:[%s]", text, buf1, buf2); +} +#endif + + /* * When a DHCP client or server intends to update an A RR, it first * prepares a DNS UPDATE query which includes as a prerequisite the @@ -1725,13 +1751,17 @@ make_dns_dataset(dns_rdataclass_t dataclass, */ static isc_result_t -ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb, +build_fwd_add1(dhcp_ddns_cb_t *ddns_cb, dhcp_ddns_data_t *dataspace, dns_name_t *pname, dns_name_t *uname) { isc_result_t result; +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_add1", pname, uname); +#endif + /* Construct the prerequisite list */ if ((ddns_cb->flags & DDNS_INCLUDE_RRSET) != 0) { /* The A RR shouldn't exist */ @@ -1764,7 +1794,7 @@ ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb, /* Add the DHCID RR */ result = make_dns_dataset(dns_rdataclass_in, ddns_cb->dhcid_class, - dataspace, + dataspace, (unsigned char *)ddns_cb->dhcid.data, ddns_cb->dhcid.len, ddns_cb->ttl); if (result != ISC_R_SUCCESS) { @@ -1788,8 +1818,18 @@ ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb, * -- "Interaction between DHCP and DNS" * * The message for the second step depends on if we are doing conflict - * resolution. If we are we include a prerequisite. If not we delete - * the DHCID in addition to all A rrsets. + * resolution. If we are we include the prerequisite. The prerequiste + * will either: + * A. require the data value of the DHCID RR to match that of the client + * or + * B. required only that the DHCID RR of the configured class (DHCID or + * TXT) exist + * + * based on whether DDNS_GUARD_ID_MUST_MATCH is on (default) or off. + * + * The prerequisite is omitted if conflict detection is off. + * + * If not we delete the DHCID in addition to all A rrsets. * * Conflict resolution: * DHCID RR exists, and matches client identity. @@ -1804,24 +1844,42 @@ ddns_modify_fwd_add1(dhcp_ddns_cb_t *ddns_cb, */ static isc_result_t -ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, +build_fwd_add2(dhcp_ddns_cb_t *ddns_cb, dhcp_ddns_data_t *dataspace, dns_name_t *pname, dns_name_t *uname) { isc_result_t result = ISC_R_SUCCESS; +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_add2", pname, uname); +#endif + /* - * If we are doing conflict resolution (unset) we use a prereq list. + * If we are doing conflict detection we use a prereq list. * If not we delete the DHCID in addition to all A rrsets. */ - if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) { + if (ddns_cb->flags & DDNS_CONFLICT_DETECTION) { /* Construct the prereq list */ - /* The DHCID RR exists and matches the client identity */ - result = make_dns_dataset(dns_rdataclass_in, ddns_cb->dhcid_class, - dataspace, - (unsigned char *)ddns_cb->dhcid.data, - ddns_cb->dhcid.len, 0); + /* The DHCID RR exists and optionally matches the client's + * identity. If matching is turned off, we use the presence + * of a DHCID RR to signal that this is a dynamic entry and + * thus eligible for us to overwrite. If matching is on + * then we can only replace the entries if they belong to + * this client. */ + unsigned char *match_id = NULL; + int match_id_len = 0; + int match_class = dns_rdataclass_any; + if (ddns_cb->flags & DDNS_GUARD_ID_MUST_MATCH) { + match_id = (unsigned char*)(ddns_cb->dhcid.data); + match_id_len = ddns_cb->dhcid.len; + match_class = dns_rdataclass_in; + } + + result = make_dns_dataset(match_class, + ddns_cb->dhcid_class, + dataspace, + match_id, match_id_len, 0); if (result != ISC_R_SUCCESS) { return(result); } @@ -1839,9 +1897,10 @@ ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); dataspace++; - /* Add current DHCID RR */ - result = make_dns_dataset(dns_rdataclass_in, ddns_cb->dhcid_class, - dataspace, + /* Add current DHCID RR, always include client id */ + result = make_dns_dataset(dns_rdataclass_in, + ddns_cb->dhcid_class, + dataspace, (unsigned char *)ddns_cb->dhcid.data, ddns_cb->dhcid.len, ddns_cb->ttl); if (result != ISC_R_SUCCESS) { @@ -1852,7 +1911,7 @@ ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, } /* Start or continue constructing the update list */ - /* Delete the A RRset */ + /* Delete the address RRset */ result = make_dns_dataset(dns_rdataclass_any, ddns_cb->address_type, dataspace, NULL, 0, 0); if (result != ISC_R_SUCCESS) { @@ -1861,9 +1920,9 @@ ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); dataspace++; - /* Add the A RR */ + /* Add the address RR */ result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type, - dataspace, + dataspace, (unsigned char *)ddns_cb->address.iabuf, ddns_cb->address.len, ddns_cb->ttl); if (result != ISC_R_SUCCESS) { @@ -1875,47 +1934,241 @@ ddns_modify_fwd_add2(dhcp_ddns_cb_t *ddns_cb, } /* - * The entity chosen to handle the A record for this client (either the - * client or the server) SHOULD delete the A record that was added when - * the lease was made to the client. - * - * In order to perform this delete, the updater prepares an UPDATE - * query which contains two prerequisites. The first prerequisite - * asserts that the DHCID RR exists whose data is the client identity - * described in Section 4.3. The second prerequisite asserts that the - * data in the A RR contains the IP address of the lease that has - * expired or been released. - * -- "Interaction between DHCP and DNS" + * Creates the DNS foward update add used for DSMM add attempt #3 and + * ddns-other-guard-is-dynamic is off * - * RFC 4703 has relaxed the prereqisites to only checking the DHCID RR - * and we have adopted that to minizmie problems due to interruptions - * when doing a deletion. * - * First try has: - * DHCID RR exists, and matches client identity. - * Delete appropriate A RR. + * If the second update failed with NXRRSET, this indicates that: + * + * 1. our FQDN is in use + * 2 no guard record (DHCID RR) for that FQDN, of our class (and optionally + * client id) exists + * + * In Dual Stack Mixed Mode, we need to attempt a third add, to distinguish + * between static entries that we cannot modify and dynamic entries belonging + * to the "other" side of dual stack. The prerequisites for this add are: + * + * 1. No address record of my type exists + * 2. No guard record of my type exists + * 3. A guard record of the other type exists + * + * and updates which will add the new address and guard record: + * + * prereq nxrrset <name> <addr_t> # no address record of my type + * prereq nxrrset <name> <guard_t> # no guard record of my type + * prereq yxrrset <name> <other_guard_t> # other guard type does exist + * update add <name> <addr_t> <address> # add the new address record + * update add <name> <guard_t> <client-id> # add the new address record */ - static isc_result_t -ddns_modify_fwd_rem1(dhcp_ddns_cb_t *ddns_cb, +build_dsmm_fwd_add3(dhcp_ddns_cb_t *ddns_cb, dhcp_ddns_data_t *dataspace, dns_name_t *pname, dns_name_t *uname) { isc_result_t result = ISC_R_SUCCESS; - /* Consruct the prereq list */ - /* The DHCID RR exists and matches the client identity */ +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_add3", pname, uname); +#endif + /* Construct the prereq list */ + /* No address record of my type exists */ + result = make_dns_dataset(dns_rdataclass_none, + ddns_cb->address_type, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* No guard record of my type exists */ + result = make_dns_dataset(dns_rdataclass_none, + ddns_cb->dhcid_class, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Guard record of the other type DOES exist */ + result = make_dns_dataset(dns_rdataclass_any, + ddns_cb->other_dhcid_class, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Start constructing the update list. */ + /* Add the address RR */ + result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type, + dataspace, + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add current DHCID RR */ result = make_dns_dataset(dns_rdataclass_in, ddns_cb->dhcid_class, - dataspace, + dataspace, (unsigned char *)ddns_cb->dhcid.data, - ddns_cb->dhcid.len, 0); + ddns_cb->dhcid.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * Creates the DNS foward update add used for DSMM add attempt #3 and + * ddns-other-guard-is-dynamic is ON + * + * If the second update failed with NXRRSET, this indicates that: + * + * 1. our FQDN is in use + * 2 no guard record (DHCID RR) for that FQDN, of our class (and optionally + * client id) exists + * + * When we're In Dual Stack Mixed Mode and ddns-other-guard-is-dynamic is ON + * we need only determine if a guard record of the other type exists, to know + * if we can add/replace and address record of our type. In other words, + * the presence of a dynamic entry made belonging to the "other" stack means + * all entries for this name should be dynamic and we overwrite an unguarded + * address record of our type. + * + * The udpate will contain a single prequisite for a guard record of the + * other type, an update to delete any address records of our type, and + * updates to add the address and guard records: + * + * prereq yxrrset <name> <other_guard_t> # other guard type exists + * update delete <name> <addr_t> # delete existing address record + * # (if one) + * update add <name> <addr_t> <address> # add new address record + * update add <name> <guard_t> <client-id> # add new guard record + */ +static isc_result_t +build_dsmm_fwd_add3_other(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result = ISC_R_SUCCESS; + +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_add3_other", pname, uname); +#endif + /* Construct the prereq list */ + /* A guard record of the other type exists */ + result = make_dns_dataset(dns_rdataclass_any, + ddns_cb->other_dhcid_class, + dataspace, NULL, 0, 0); if (result != ISC_R_SUCCESS) { return(result); } ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); dataspace++; + /* Start constructing the update list. */ + /* Delete the existing address record of my type (if one) */ + result = make_dns_dataset(dns_rdataclass_any, + ddns_cb->address_type, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add the address RR */ + result = make_dns_dataset(dns_rdataclass_in, ddns_cb->address_type, + dataspace, + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + dataspace++; + + /* Add current DHCID RR */ + result = make_dns_dataset(dns_rdataclass_in, ddns_cb->dhcid_class, + dataspace, + (unsigned char *)ddns_cb->dhcid.data, + ddns_cb->dhcid.len, ddns_cb->ttl); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * The entity chosen to handle the A record for this client (either the + * client or the server) SHOULD delete the A (or AAAA) record that was + * added when the lease was made to the client. + * + * If we are doing conflict resolution, the udpate will contain a prequisite + * that will either: + * A. require that a guard record of the configure class (DHCID or TXT) with + * a data value matching that the client exist (per RFC 4703) + * or + * B. require only that the guard record of the configured class exist + * + * based on whether DDNS_GUARD_ID_MUST_MATCH is on (default) or off. + * + * The prerequisite is omitted if conflict detection is off. + * + */ +static isc_result_t +build_fwd_rem1(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result = ISC_R_SUCCESS; + +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_rem1", pname, uname); +#endif + + /* If we're doing conflict detection, add the guard record pre-req */ + if (ddns_cb->flags & DDNS_CONFLICT_DETECTION) { + /* Construct the prereq list */ + /* The guard record exists and optionally matches the client's + * identity. If matching is turned off, we use the presence + * of a DHCID RR to signal that this is a dynamic entry and + * thus eligible for us to overwrite. If matching is on + * then we can only delete the entries if they belong to + * this client. */ + unsigned char *match_id = NULL; + int match_id_len = 0; + int match_class = dns_rdataclass_any; + if (ddns_cb->flags & DDNS_GUARD_ID_MUST_MATCH) { + match_id = (unsigned char*)(ddns_cb->dhcid.data); + match_id_len = ddns_cb->dhcid.len; + match_class = dns_rdataclass_in; + } + + result = make_dns_dataset(match_class, + ddns_cb->dhcid_class, + dataspace, + match_id, match_id_len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + } + /* Construct the update list */ /* Delete A RRset */ result = make_dns_dataset(dns_rdataclass_none, ddns_cb->address_type, @@ -1942,14 +2195,20 @@ ddns_modify_fwd_rem1(dhcp_ddns_cb_t *ddns_cb, * AAAA RR does not exist. * Delete appropriate DHCID RR. */ - static isc_result_t -ddns_modify_fwd_rem2(dhcp_ddns_cb_t *ddns_cb, +build_fwd_rem2(dhcp_ddns_cb_t *ddns_cb, dhcp_ddns_data_t *dataspace, dns_name_t *pname, dns_name_t *uname) { isc_result_t result; + unsigned char *match_id = NULL; + int match_id_len = 0; + int match_class = dns_rdataclass_any; + +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_rem2", pname, uname); +#endif /* Construct the prereq list */ /* The A RR does not exist */ @@ -1972,10 +2231,143 @@ ddns_modify_fwd_rem2(dhcp_ddns_cb_t *ddns_cb, /* Construct the update list */ /* Delete DHCID RR */ + + /* We'll specify the client id in the guard record delete if + * matching is enabled, otherwise we leave it off. */ + if (ddns_cb->flags & DDNS_GUARD_ID_MUST_MATCH) { + match_id = (unsigned char*)(ddns_cb->dhcid.data); + match_id_len = ddns_cb->dhcid.len; + match_class = dns_rdataclass_none; + } + + result = make_dns_dataset(match_class, ddns_cb->dhcid_class, + dataspace, + match_id, match_id_len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * Constructs the second stage forward remove, when the first stage + * succeeds and DSMM is enabled, and ddns-other-guard-is-dynamic is OFF + * + * Normal conflict detection requires that the guard record of the + * configured type only be deleted if there are no address records of + * any type. In Dual Stack Mixed Mode, we are only concerned with whether + * there any records or our configured address type remaining. + * + * This update consists of a single prequisite that there be no address + * records of our type followed by a delete of the guard record of our type + * and optionally matching client-id. + * + * prereq nxrrset name <addr_t> # no records of this address type exist + * update delete name <guard_t> <client_id> # delete the existing guard record + */ +static isc_result_t +build_fwd_rem2_dsmm (dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + unsigned char *match_id = NULL; + int match_id_len = 0; + int match_class = dns_rdataclass_any; + +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_rem2_dsmm", pname, uname); +#endif + + /* Construct the prereq list */ + /* The address RR does not exist */ + result = make_dns_dataset(dns_rdataclass_none, + ddns_cb->address_type, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Construct the update list */ + /* Delete DHCID RR */ + + /* We'll specify the client id in the guard record delete if + * matching is enabled, otherwise we leave it off. */ + if (ddns_cb->flags & DDNS_GUARD_ID_MUST_MATCH) { + match_id = (unsigned char*)(ddns_cb->dhcid.data); + match_id_len = ddns_cb->dhcid.len; + match_class = dns_rdataclass_none; + } + + result = make_dns_dataset(match_class, ddns_cb->dhcid_class, + dataspace, + match_id, match_id_len, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(uname->list, &dataspace->rdataset, link); + + return(ISC_R_SUCCESS); +} + +/* + * Constructs the second stage forward remove, when the first stage + * succeeds and DSMM is enabled and ddns-other-guard-is-dynamic is ON + * + * This update addresses the case when an address record of our type exists + * without a guard record of our type, yet a dynamic entry of the other type + * exists. The presence of a guard of the other type indicates that all + * entries for this name should be treated as dynamic, thus permitting us to + * remove the address record of our type. + * + * prereq nxrrset <name> <guard_t> # my guard type does not exist + * prereq yxrrset <name> <other_guard_t> # other guard type does exist + * update delete <name> <addr_t> address # delete the existing address record + * + */ +static isc_result_t +build_fwd_rem2_dsmm_other(dhcp_ddns_cb_t *ddns_cb, + dhcp_ddns_data_t *dataspace, + dns_name_t *pname, + dns_name_t *uname) +{ + isc_result_t result; + +#if defined (DEBUG_DNS_UPDATES) + log_call("build_fwd_rem2_dsmm_other", pname, uname); +#endif + + /* Construct the prereq list */ + /* No guard record of my type exists */ result = make_dns_dataset(dns_rdataclass_none, ddns_cb->dhcid_class, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Guard record of the OTHER type DOES exist */ + result = make_dns_dataset(dns_rdataclass_any, + ddns_cb->other_dhcid_class, + dataspace, NULL, 0, 0); + if (result != ISC_R_SUCCESS) { + return(result); + } + ISC_LIST_APPEND(pname->list, &dataspace->rdataset, link); + dataspace++; + + /* Construct the update list */ + /* Delete the address RRset */ + result = make_dns_dataset(dns_rdataclass_none, ddns_cb->address_type, dataspace, - (unsigned char *)ddns_cb->dhcid.data, - ddns_cb->dhcid.len, 0); + (unsigned char *)ddns_cb->address.iabuf, + ddns_cb->address.len, 0); if (result != ISC_R_SUCCESS) { return(result); } @@ -2026,11 +2418,11 @@ void ddns_interlude(isc_task_t *taskp, if ((ddns_cb->flags & DDNS_ABORT) == 0) { log_info("DDNS: cleaning up lease pointer for a cancel " "cb=%p", ddns_cb); - /* + /* * We shouldn't actually be able to get here but * we are. This means we haven't cleaned up * the lease pointer so we need to do that before - * freeing the cb. + * freeing the cb. */ ddns_cb->cur_func(ddns_cb, eresult); return; @@ -2077,7 +2469,7 @@ void ddns_interlude(isc_task_t *taskp, /* pass it along to be processed */ ddns_cb->cur_func(ddns_cb, eresult); } - + return; } @@ -2093,6 +2485,10 @@ ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) isc_result_t result; dns_tsec_t *tsec_key = NULL; +#if defined (DEBUG_DNS_UPDATES) + log_info("DDNS: ddns_modify_fwd"); +#endif + unsigned char *clientname; dhcp_ddns_data_t *dataspace = NULL; dns_namelist_t prereqlist, updatelist; @@ -2155,7 +2551,7 @@ ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) /* * If we have a zone try to get any information we need - * from it - name, addresses and the key. The address + * from it - name, addresses and the key. The address * and key may be empty the name can't be. */ if (ddns_cb->zone) { @@ -2173,7 +2569,7 @@ ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) /* If we have any addresses get them */ zlist = &ddns_cb->zone_server_list; } - + if (ddns_cb->zone->key != NULL) { /* @@ -2204,7 +2600,7 @@ ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 4); if (dataspace == NULL) { log_error("Unable to allocate memory for fwd update"); - result = ISC_R_NOMEMORY; + result = ISC_R_NOMEMORY; goto cleanup; } @@ -2213,48 +2609,82 @@ ddns_modify_fwd(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) switch(ddns_cb->state) { case DDNS_STATE_ADD_FW_NXDOMAIN: - result = ddns_modify_fwd_add1(ddns_cb, dataspace, - pname, uname); + result = build_fwd_add1(ddns_cb, dataspace, pname, uname); if (result != ISC_R_SUCCESS) { goto cleanup; } ISC_LIST_APPEND(prereqlist, pname, link); break; + case DDNS_STATE_ADD_FW_YXDHCID: - result = ddns_modify_fwd_add2(ddns_cb, dataspace, - pname, uname); + result = build_fwd_add2(ddns_cb, dataspace, pname, uname); if (result != ISC_R_SUCCESS) { goto cleanup; } - /* If we aren't doing conflict override we have entries + /* If we are doing conflict detection we have entries * in the pname list and we need to attach it to the * prereqlist */ - if ((ddns_cb->flags & DDNS_CONFLICT_OVERRIDE) == 0) { + if (ddns_cb->flags & DDNS_CONFLICT_DETECTION) { ISC_LIST_APPEND(prereqlist, pname, link); } break; - case DDNS_STATE_REM_FW_YXDHCID: - result = ddns_modify_fwd_rem1(ddns_cb, dataspace, - pname, uname); + + case DDNS_STATE_DSMM_FW_ADD3: { + /* We should only be here if we're doing DSMM */ + builder_func_t builder; + if (ddns_cb->flags & DDNS_OTHER_GUARD_IS_DYNAMIC) { + builder = build_dsmm_fwd_add3_other; + } else { + builder = build_dsmm_fwd_add3; + } + + result = (*builder)(ddns_cb, dataspace, pname, uname); if (result != ISC_R_SUCCESS) { goto cleanup; } + ISC_LIST_APPEND(prereqlist, pname, link); break; - case DDNS_STATE_REM_FW_NXRR: - result = ddns_modify_fwd_rem2(ddns_cb, dataspace, - pname, uname); + } + + case DDNS_STATE_REM_FW_YXDHCID: + result = build_fwd_rem1(ddns_cb, dataspace, pname, uname); if (result != ISC_R_SUCCESS) { goto cleanup; } ISC_LIST_APPEND(prereqlist, pname, link); break; + case DDNS_STATE_REM_FW_NXRR: { + builder_func_t builder; + + if (ddns_cb->flags & DDNS_DUAL_STACK_MIXED_MODE) { + builder = build_fwd_rem2_dsmm; + } else { + builder = build_fwd_rem2; + } + + result = (*builder)(ddns_cb, dataspace, pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; } + ISC_LIST_APPEND(prereqlist, pname, link); + break; + } + + case DDNS_STATE_REM_FW_DSMM_OTHER: { + result = build_fwd_rem2_dsmm_other(ddns_cb, dataspace, + pname, uname); + if (result != ISC_R_SUCCESS) { + goto cleanup; } + ISC_LIST_APPEND(prereqlist, pname, link); + break; + } + default: - log_error("Invalid operation in ddns code."); + log_error("ddns_modify_fwd: Invalid state: %d", ddns_cb->state); result = DHCP_R_INVALIDARG; goto cleanup; break; @@ -2315,6 +2745,10 @@ ddns_modify_ptr(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) unsigned char buf[256]; int buflen; +#if defined (DEBUG_DNS_UPDATES) + log_info("DDNS: ddns_modify_ptr"); +#endif + /* Creates client context if we need to */ result = dns_client_init(); if (result != ISC_R_SUCCESS) { @@ -2403,7 +2837,7 @@ ddns_modify_ptr(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) dataspace = isc_mem_get(dhcp_gbl_ctx.mctx, sizeof(*dataspace) * 2); if (dataspace == NULL) { log_error("Unable to allocate memory for fwd update"); - result = ISC_R_NOMEMORY; + result = ISC_R_NOMEMORY; goto cleanup; } @@ -2422,46 +2856,10 @@ ddns_modify_ptr(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) ISC_LIST_APPEND(uname->list, &dataspace[0].rdataset, link); /* - * If we are updating the pointer we then add the new one + * If we are updating the pointer we then add the new one * Add PTR RR. */ if (ddns_cb->state == DDNS_STATE_ADD_PTR) { -#if 0 - /* - * I've left this dead code in the file for now in case - * we decide to try and get rid of the ns_name functions. - * sar - */ - - /* - * Need to convert pointer into on the wire representation - * We replace the '.' characters with the lengths of the - * next name and add a length to the beginning for the first - * name. - */ - if (ddns_cb->fwd_name.len == 1) { - /* the root */ - buf[0] = 0; - buflen = 1; - } else { - unsigned char *cp; - buf[0] = '.'; - memcpy(&buf[1], ddns_cb->fwd_name.data, - ddns_cb->fwd_name.len); - for(cp = buf + ddns_cb->fwd_name.len, buflen = 0; - cp != buf; - cp--) { - if (*cp == '.') { - *cp = buflen; - buflen = 0; - } else { - buflen++; - } - } - *cp = buflen; - buflen = ddns_cb->fwd_name.len + 1; - } -#endif /* * Need to convert pointer into on the wire representation */ @@ -2545,3 +2943,215 @@ ddns_cancel(dhcp_ddns_cb_t *ddns_cb, const char *file, int line) { HASH_FUNCTIONS (dns_zone, const char *, struct dns_zone, dns_zone_hash_t, dns_zone_reference, dns_zone_dereference, do_case_hash) + +#if defined (NSUPDATE) +#if defined (DEBUG_DNS_UPDATES) +/* Defines a type for creating list of labeled integers */ +typedef struct { + int val; + char *name; +} LabeledInt; + +char* +ddns_state_name(int state) { + static LabeledInt ints[] = { + { DDNS_STATE_CLEANUP, "DDNS_STATE_CLEANUP" }, + { DDNS_STATE_ADD_FW_NXDOMAIN, "DDNS_STATE_ADD_FW_NXDOMAIN" }, + { DDNS_STATE_ADD_FW_YXDHCID, "DDNS_STATE_ADD_FW_YXDHCID" }, + { DDNS_STATE_ADD_PTR, "DDNS_STATE_ADD_PTR" }, + { DDNS_STATE_DSMM_FW_ADD3, "DDNS_STATE_DSMM_FW_ADD3" }, + { DDNS_STATE_REM_FW_YXDHCID, "DDNS_STATE_REM_FW_YXDHCID" }, + { DDNS_STATE_REM_FW_NXRR, "DDNS_STATE_FW_NXRR" }, + { DDNS_STATE_REM_PTR, "DDNS_STATE_REM_PTR" }, + { -1, "unknown" }, + }; + + LabeledInt* li = ints; + while (li->val != -1 && li->val != state) { + ++li; + } + + return (li->name); +} + +int +add_nstring(char **orig, char *max, char *add, int add_len) { + if (*orig && (*orig + add_len < max)) { + strncpy(*orig, add, add_len); + *orig += add_len; + **orig = 0; + return (0); + } + + return (-1); +} + +int +add_string(char **orig, char *max, char *add) { + return (add_nstring(orig, max, add, strlen(add))); +} + +/* + * direction outbound (messages to the dns server) + * inbound (messages from the dns server) + * ddns_cb is the control block associated with the message + * result is the result from the dns code. For outbound calls + * it is from the call to pass the message to the dns library. + * For inbound calls it is from the event returned by the library. + * + * For outbound messages we print whatever we think is interesting + * from the control block. + * For inbound messages we only print the transaction id pointer + * and the result and expect that the user will match them up as + * necessary. Note well: the transaction information is opaque to + * us so we simply print the pointer to it. This should be sufficient + * to match requests and replys in a short sequence but is awkward + * when trying to use it for longer sequences. + */ +void +print_dns_status (int direction, + struct dhcp_ddns_cb *ddns_cb, + isc_result_t result) +{ + char obuf[1024]; + char *s = obuf, *end = &obuf[sizeof(obuf)-2]; + char *en; + const char *result_str; + char ddns_address[ + sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + + if (direction == DDNS_PRINT_INBOUND) { + log_info("DDNS reply: id ptr %p, result: %s", + ddns_cb->transaction, isc_result_totext(result)); + return; + } + + /* + * To avoid having to figure out if any of the strings + * aren't NULL terminated, just 0 the whole string + */ + memset(obuf, 0, 1024); + + en = "DDNS request: id ptr "; + if (s + strlen(en) + 16 < end) { + sprintf(s, "%s%p", en, ddns_cb->transaction); + s += strlen(s); + } else { + goto bailout; + } + + en = ddns_state_name(ddns_cb->state); + + switch (ddns_cb->state) { + case DDNS_STATE_ADD_FW_NXDOMAIN: + case DDNS_STATE_ADD_FW_YXDHCID: + case DDNS_STATE_REM_FW_YXDHCID: + case DDNS_STATE_REM_FW_NXRR: + case DDNS_STATE_DSMM_FW_ADD3: + strcpy(ddns_address, piaddr(ddns_cb->address)); + if (s + strlen(en) + strlen(ddns_address) + + ddns_cb->fwd_name.len + 7 < end) { + sprintf(s, " %s %s for %.*s", en, ddns_address, + ddns_cb->fwd_name.len, + ddns_cb->fwd_name.data); + s += strlen(s); + } else { + goto bailout; + } + break; + + case DDNS_STATE_ADD_PTR: + case DDNS_STATE_REM_PTR: + if (s + strlen(en) + ddns_cb->fwd_name.len + + ddns_cb->rev_name.len + 7 < end) { + sprintf(s, " %s %.*s for %.*s", en, + ddns_cb->fwd_name.len, + ddns_cb->fwd_name.data, + ddns_cb->rev_name.len, + ddns_cb->rev_name.data); + s += strlen(s); + } else { + goto bailout; + } + break; + + case DDNS_STATE_CLEANUP: + default: + if (s + strlen(en) < end) { + sprintf(s, "%s", en); + s += strlen(s); + } else { + goto bailout; + } + break; + } + + en = " zone: "; + if (s + strlen(en) + strlen((char *)ddns_cb->zone_name) < end) { + sprintf(s, "%s%s", en, ddns_cb->zone_name); + s += strlen(s); + } else { + goto bailout; + } + + /* @todo replace with format that matches bind9 zone file */ + if (ddns_cb->dhcid_class == dns_rdatatype_dhcid) { + char *idbuf = NULL; + if (add_string(&s, end, "dhcid: [")) { + goto bailout; + } + + idbuf = buf_to_hex(ddns_cb->dhcid.data, + ddns_cb->dhcid.len, MDL); + if (idbuf) { + int ret = add_string(&s, end, idbuf); + dfree(idbuf, MDL); + if (!ret) { + goto bailout; + } + } + + if (add_string(&s, end, "]")) { + goto bailout; + } + } else { + /* 1st byte of a txt dhcid is length, so we skip printing it + * In the event it's empty, we end up not adding anything */ + int skip_length_byte = (ddns_cb->dhcid.len > 0 ? 1 : 0); + if (add_string (&s, end, "txt: [") || + add_nstring (&s, end, + (char *)ddns_cb->dhcid.data + skip_length_byte, + ddns_cb->dhcid.len - skip_length_byte) || + add_string (&s, end, "]")) { + goto bailout; + } + } + + en = " ttl: "; + if (s + strlen(en) + 10 < end) { + sprintf(s, "%s%ld", en, ddns_cb->ttl); + s += strlen(s); + } else { + goto bailout; + } + + en = " result: "; + result_str = isc_result_totext(result); + if (s + strlen(en) + strlen(result_str) < end) { + sprintf(s, "%s%s", en, result_str); + s += strlen(s); + } else { + goto bailout; + } + + bailout: + /* + * We either finished building the string or ran out + * of space, print whatever we have in case it is useful + */ + log_info("%s", obuf); + + return; +} +#endif /* DEBUG_DNS_UPDATES */ +#endif /* NSUPDATE */ diff --git a/common/print.c b/common/print.c index 2926e6cb..5993c0e2 100644 --- a/common/print.c +++ b/common/print.c @@ -1305,191 +1305,6 @@ void indent_spaces (FILE *file, int indent) fputc (' ', file); } -#if defined (NSUPDATE) -#if defined (DEBUG_DNS_UPDATES) -/* - * direction outbound (messages to the dns server) - * inbound (messages from the dns server) - * ddns_cb is the control block associated with the message - * result is the result from the dns code. For outbound calls - * it is from the call to pass the message to the dns library. - * For inbound calls it is from the event returned by the library. - * - * For outbound messages we print whatever we think is interesting - * from the control block. - * For inbound messages we only print the transaction id pointer - * and the result and expect that the user will match them up as - * necessary. Note well: the transaction information is opaque to - * us so we simply print the pointer to it. This should be sufficient - * to match requests and replys in a short sequence but is awkward - * when trying to use it for longer sequences. - */ -void -print_dns_status (int direction, - struct dhcp_ddns_cb *ddns_cb, - isc_result_t result) -{ - char obuf[1024]; - char *s = obuf, *end = &obuf[sizeof(obuf)-2]; - char *en; - const char *result_str; - char ddns_address[ - sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; - - if (direction == DDNS_PRINT_INBOUND) { - log_info("DDNS reply: id ptr %p, result: %s", - ddns_cb->transaction, isc_result_totext(result)); - return; - } - - /* - * To avoid having to figure out if any of the strings - * aren't NULL terminated, just 0 the whole string - */ - memset(obuf, 0, 1024); - - en = "DDNS request: id ptr "; - if (s + strlen(en) + 16 < end) { - sprintf(s, "%s%p", en, ddns_cb->transaction); - s += strlen(s); - } else { - goto bailout; - } - - switch (ddns_cb->state) { - case DDNS_STATE_ADD_FW_NXDOMAIN: - en = " add forward "; - break; - case DDNS_STATE_ADD_FW_YXDHCID: - en = " modify forward "; - break; - - case DDNS_STATE_ADD_PTR: - en = " add reverse "; - break; - - case DDNS_STATE_REM_FW_YXDHCID: - en = " remove forward "; - break; - - case DDNS_STATE_REM_FW_NXRR: - en = " remove rrset "; - break; - - case DDNS_STATE_REM_PTR: - en = " remove reverse "; - break; - - case DDNS_STATE_CLEANUP: - en = " cleanup "; - break; - - default: - en = " unknown state "; - break; - } - - switch (ddns_cb->state) { - case DDNS_STATE_ADD_FW_NXDOMAIN: - case DDNS_STATE_ADD_FW_YXDHCID: - case DDNS_STATE_REM_FW_YXDHCID: - case DDNS_STATE_REM_FW_NXRR: - strcpy(ddns_address, piaddr(ddns_cb->address)); - if (s + strlen(en) + strlen(ddns_address) + - ddns_cb->fwd_name.len + 5 < end) { - sprintf(s, "%s%s for %.*s", en, ddns_address, - ddns_cb->fwd_name.len, - ddns_cb->fwd_name.data); - s += strlen(s); - } else { - goto bailout; - } - break; - - case DDNS_STATE_ADD_PTR: - case DDNS_STATE_REM_PTR: - if (s + strlen(en) + ddns_cb->fwd_name.len + - ddns_cb->rev_name.len + 5 < end) { - sprintf(s, "%s%.*s for %.*s", en, - ddns_cb->fwd_name.len, - ddns_cb->fwd_name.data, - ddns_cb->rev_name.len, - ddns_cb->rev_name.data); - s += strlen(s); - } else { - goto bailout; - } - break; - - case DDNS_STATE_CLEANUP: - default: - if (s + strlen(en) < end) { - sprintf(s, "%s", en); - s += strlen(s); - } else { - goto bailout; - } - break; - } - - en = " zone: "; - if (s + strlen(en) + strlen((char *)ddns_cb->zone_name) < end) { - sprintf(s, "%s%s", en, ddns_cb->zone_name); - s += strlen(s); - } else { - goto bailout; - } - - en = " dhcid: "; - if (ddns_cb->dhcid.len > 0) { - if (s + strlen(en) + ddns_cb->dhcid.len-1 < end) { - strcpy(s, en); - s += strlen(s); - strncpy(s, (char *)ddns_cb->dhcid.data+1, - ddns_cb->dhcid.len-1); - s += strlen(s); - } else { - goto bailout; - } - } else { - en = " dhcid: <empty>"; - if (s + strlen(en) < end) { - strcpy(s, en); - s += strlen(s); - } else { - goto bailout; - } - } - - en = " ttl: "; - if (s + strlen(en) + 10 < end) { - sprintf(s, "%s%ld", en, ddns_cb->ttl); - s += strlen(s); - } else { - goto bailout; - } - - en = " result: "; - result_str = isc_result_totext(result); - if (s + strlen(en) + strlen(result_str) < end) { - sprintf(s, "%s%s", en, result_str); - s += strlen(s); - } else { - goto bailout; - } - - bailout: - /* - * We either finished building the string or ran out - * of space, print whatever we have in case it is useful - */ - log_info("%s", obuf); - - return; -} -#endif -#endif /* NSUPDATE */ - /* Format the given time as "A; # B", where A is the format * used by the parser, and B is the local time, for humans. */ diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 1a6e8ef3..44d543d1 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -805,6 +805,9 @@ struct lease_state { #if defined (FAILOVER_PROTOCOL) #define SV_CHECK_SECS_BYTE_ORDER 91 #endif +#define SV_DDNS_DUAL_STACK_MIXED_MODE 92 +#define SV_DDNS_GUARD_ID_MUST_MATCH 93 +#define SV_DDNS_OTHER_GUARD_IS_DYNAMIC 94 #if !defined (DEFAULT_PING_TIMEOUT) # define DEFAULT_PING_TIMEOUT 1 @@ -1738,29 +1741,37 @@ struct ipv6_pond { */ #define POND_TRACK_MAX ISC_UINT64_MAX -/* Flags and state for dhcp_ddns_cb_t */ -#define DDNS_UPDATE_ADDR 0x01 -#define DDNS_UPDATE_PTR 0x02 -#define DDNS_INCLUDE_RRSET 0x04 -#define DDNS_CONFLICT_OVERRIDE 0x08 -#define DDNS_CLIENT_DID_UPDATE 0x10 -#define DDNS_EXECUTE_NEXT 0x20 -#define DDNS_ABORT 0x40 -#define DDNS_STATIC_LEASE 0x80 -#define DDNS_ACTIVE_LEASE 0x100 -/* - * The following two groups are separate and we could reuse - * values but not reusing them may be useful in the future. - */ -#define DDNS_STATE_CLEANUP 0 // The previous step failed, cleanup - -#define DDNS_STATE_ADD_FW_NXDOMAIN 1 -#define DDNS_STATE_ADD_FW_YXDHCID 2 -#define DDNS_STATE_ADD_PTR 3 - -#define DDNS_STATE_REM_FW_YXDHCID 17 -#define DDNS_STATE_REM_FW_NXRR 18 -#define DDNS_STATE_REM_PTR 19 +/* Flags for dhcp_ddns_cb_t */ +#define DDNS_UPDATE_ADDR 0x0001 +#define DDNS_UPDATE_PTR 0x0002 +#define DDNS_INCLUDE_RRSET 0x0004 +#define DDNS_CONFLICT_DETECTION 0x0008 +#define DDNS_CLIENT_DID_UPDATE 0x0010 +#define DDNS_EXECUTE_NEXT 0x0020 +#define DDNS_ABORT 0x0040 +#define DDNS_STATIC_LEASE 0x0080 +#define DDNS_ACTIVE_LEASE 0x0100 +#define DDNS_DUAL_STACK_MIXED_MODE 0x0200 +#define DDNS_GUARD_ID_MUST_MATCH 0x0400 +#define DDNS_OTHER_GUARD_IS_DYNAMIC 0x0800 + +#define CONFLICT_BITS (DDNS_CONFLICT_DETECTION|\ + DDNS_DUAL_STACK_MIXED_MODE|\ + DDNS_GUARD_ID_MUST_MATCH|\ + DDNS_OTHER_GUARD_IS_DYNAMIC) + +/* States for dhcp_ddns_cb_t */ +#define DDNS_STATE_CLEANUP 0 /* startup or the previous step failed, cleanup */ + +#define DDNS_STATE_ADD_FW_NXDOMAIN 1 +#define DDNS_STATE_ADD_FW_YXDHCID 2 +#define DDNS_STATE_ADD_PTR 3 +#define DDNS_STATE_DSMM_FW_ADD3 4 + +#define DDNS_STATE_REM_FW_YXDHCID 17 +#define DDNS_STATE_REM_FW_NXRR 18 +#define DDNS_STATE_REM_PTR 19 +#define DDNS_STATE_REM_FW_DSMM_OTHER 20 /* * Flags for the dns print function @@ -1803,11 +1814,12 @@ typedef struct dhcp_ddns_cb { void *dataspace; dns_rdataclass_t dhcid_class; + dns_rdataclass_t other_dhcid_class; char *lease_tag; } dhcp_ddns_cb_t; extern struct ipv6_pool **pools; -extern int num_pools; + /* External definitions... */ @@ -2081,6 +2093,9 @@ extern struct timeval cur_tv; #define cur_time cur_tv.tv_sec extern int ddns_update_style; +#if defined (NSUPDATE) +extern u_int16_t ddns_conflict_mask; +#endif extern int dont_use_fsync; extern int server_id_check; @@ -2193,6 +2208,7 @@ int ddns_updates(struct packet *, struct lease *, struct lease *, struct iasubopt *, struct iasubopt *, struct option_state *); isc_result_t ddns_removals(struct lease *, struct iasubopt *, struct dhcp_ddns_cb *, isc_boolean_t); +u_int16_t get_conflict_mask(struct option_state *input_options); #if defined (TRACING) void trace_ddns_init(void); #endif @@ -3156,6 +3172,7 @@ isc_result_t ddns_update_fwd(struct data_string *, struct iaddr, unsigned); isc_result_t ddns_remove_fwd(struct data_string *, struct iaddr, struct data_string *); +char *ddns_state_name(int state); #endif /* NSUPDATE */ dhcp_ddns_cb_t *ddns_cb_alloc(const char *file, int line); diff --git a/includes/site.h b/includes/site.h index 220d31a5..461eaccf 100644 --- a/includes/site.h +++ b/includes/site.h @@ -105,7 +105,6 @@ /* Define this if you want to see the requests and replies between the DHCP code and the DNS library code. */ - /* #define DEBUG_DNS_UPDATES */ /* Define this if you want to debug the host part of the inform processing */ diff --git a/server/ddns.c b/server/ddns.c index d370bbed..396eda5d 100644 --- a/server/ddns.c +++ b/server/ddns.c @@ -3,7 +3,7 @@ Dynamic DNS updates. */ /* - * + * * Copyright (c) 2004-2017 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2000-2003 by Internet Software Consortium * @@ -40,12 +40,23 @@ char *ddns_interim_tag = "ddns-txt"; #ifdef NSUPDATE +#if defined (DEBUG_DNS_UPDATES) +static char* dump_ddns_cb_func(void *func); +static char* dump_ddns_cb (dhcp_ddns_cb_t *ddns_cb); + +extern struct enumeration_value ddns_styles_values[]; +#endif + static void ddns_fwd_srv_connector(struct lease *lease, struct iasubopt *lease6, struct binding_scope **inscope, dhcp_ddns_cb_t *ddns_cb, isc_result_t eresult); +static void copy_conflict_flags(u_int16_t *target, u_int16_t source); + +static void ddns_fwd_srv_add3(dhcp_ddns_cb_t *ddns_cb, isc_result_t eresult); + /* DN: No way of checking that there is enough space in a data_string's buffer. Be certain to allocate enough! TL: This is why the expression evaluation code allocates a *new* @@ -123,7 +134,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, } /* * Assume that we shall update both the A and ptr records and, - * as this is an update, set the active flag + * as this is an update, set the active flag */ ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR | DDNS_ACTIVE_LEASE; @@ -293,7 +304,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, goto in; } #endif - + /* See if the administrator wants to do updates even in cases where the update already appears to have been done. */ @@ -325,7 +336,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, } } in: - + /* If we don't have a name that the client has been assigned, we can just skip all this. */ @@ -351,7 +362,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, * The new behavior continues to allow the customer to set * up an option but the defaults are a little different. * We now use 1/2 of the (preferred) lease time for both - * v4 and v6 and cap them at a maximum value. + * v4 and v6 and cap them at a maximum value. * If the customer chooses to use an experession that references * part of the lease the v6 value will be the default as there * isn't a lease available for v6. @@ -373,7 +384,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, if (ddns_ttl > MAX_DEFAULT_DDNS_TTL) { ddns_ttl = MAX_DEFAULT_DDNS_TTL; } -#endif +#endif if ((oc = lookup_option(&server_universe, options, SV_DDNS_TTL))) { if (evaluate_option_cache(&d1, packet, lease, NULL, @@ -398,8 +409,8 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, else s1 = 0; - /* - * Figure out the length of the part of the name that depends + /* + * Figure out the length of the part of the name that depends * on the address. */ if (ddns_cb->address.len == 4) { @@ -422,8 +433,8 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, } } } else if (ddns_cb->address.len == 16) { - /* - * IPv6 reverse names are always the same length, with + /* + * IPv6 reverse names are always the same length, with * 32 hex characters separated by dots. */ rev_name_len = sizeof("0.1.2.3.4.5.6.7." @@ -458,7 +469,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, if (ddns_cb->address.len == 4) { rname->len = sprintf((char *)rname->buffer->data, - "%u.%u.%u.%u.", + "%u.%u.%u.%u.", ddns_cb->address.iabuf[3] & 0xff, ddns_cb->address.iabuf[2] & 0xff, ddns_cb->address.iabuf[1] & 0xff, @@ -474,7 +485,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, char *p = (char *)&rname->buffer->data; unsigned char *a = ddns_cb->address.iabuf + 15; for (i=0; i<16; i++) { - sprintf(p, "%x.%x.", + sprintf(p, "%x.%x.", (*a & 0xF), ((*a >> 4) & 0xF)); p += 4; a -= 1; @@ -512,12 +523,14 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, /* The standard style */ ddns_cb->lease_tag = ddns_standard_tag; ddns_cb->dhcid_class = dns_rdatatype_dhcid; + ddns_cb->other_dhcid_class = dns_rdatatype_txt; ddns_type = 1; ddns_len = 4; } else { /* The older interim style */ ddns_cb->lease_tag = ddns_interim_tag; ddns_cb->dhcid_class = dns_rdatatype_txt; + ddns_cb->other_dhcid_class = dns_rdatatype_dhcid; /* for backwards compatibility */ ddns_type = DHO_DHCP_CLIENT_IDENTIFIER; /* IAID incorrectly included */ @@ -565,14 +578,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, */ if (ddns_cb->flags & DDNS_UPDATE_ADDR) { - oc = lookup_option(&server_universe, options, - SV_DDNS_CONFLICT_DETECT); - if (oc && - !evaluate_boolean_option_cache(&ignorep, packet, lease, - NULL, packet->options, - options, scope, oc, MDL)) - ddns_cb->flags |= DDNS_CONFLICT_OVERRIDE; - + copy_conflict_flags(&ddns_cb->flags, ddns_conflict_mask); } /* @@ -772,7 +778,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old, * Lastly, if we needed to find the scope we write it out, if we used a * scope that was passed as an argument we don't write it, assuming that * our caller (or his ...) will do the write. - * + * *\li ddns_cb - the control block for the DDNS request * *\li inscope - a pointer to the scope to update. This may be NULL @@ -797,7 +803,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, struct ipv6_pool *pool = NULL; struct in6_addr addr; struct data_string lease_dhcid; - + /* * If the lease was static (for a fixed address) * we don't need to do any work. @@ -805,7 +811,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, if (ddns_cb->flags & DDNS_STATIC_LEASE) return (ISC_R_SUCCESS); - /* + /* * If we are processing an expired or released v6 lease * or some types of v4 leases we don't actually have a * scope to update @@ -823,7 +829,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, memcpy(&addr, &ddns_cb->address.iabuf, 16); if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) == ISC_R_SUCCESS) || - (find_ipv6_pool(&pool, D6O_IA_NA, &addr) == + (find_ipv6_pool(&pool, D6O_IA_NA, &addr) == ISC_R_SUCCESS)) { if (iasubopt_hash_lookup(&lease6, pool->leases, &addr, 16, MDL)) { @@ -867,10 +873,12 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, case DDNS_STATE_ADD_FW_YXDHCID: case DDNS_STATE_ADD_FW_NXDOMAIN: + case DDNS_STATE_DSMM_FW_ADD3: bind_ds_value(scope, "ddns-fwd-name", &ddns_cb->fwd_name); if (ddns_cb->lease_tag == ddns_standard_tag) { - bind_ds_value(scope, ddns_standard_tag, &ddns_cb->dhcid); + bind_ds_value(scope, ddns_standard_tag, + &ddns_cb->dhcid); } else { /* convert from dns version to lease version of dhcid */ memset(&lease_dhcid, 0, sizeof(lease_dhcid)); @@ -882,11 +890,12 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, case DDNS_STATE_REM_FW_NXRR: case DDNS_STATE_REM_FW_YXDHCID: + case DDNS_STATE_REM_FW_DSMM_OTHER: unset(*scope, "ddns-fwd-name"); unset(*scope, ddns_cb->lease_tag); break; } - + /* If necessary write it out and get rid of the lease */ if (lease) { write_lease(lease); @@ -906,7 +915,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb, * cases (all are configuration mistakes): * a) IPv4: user have duplicate fixed-address entries (the same * address is defined twice). We may have found wrong lease. - * b) IPv6: user have overlapping pools (we tried to find + * b) IPv6: user have overlapping pools (we tried to find * a lease in a wrong pool) * c) IPv6: user have duplicate fixed-address6 entires (the same * address is defined twice). We may have found wrong lease. @@ -930,29 +939,29 @@ update_lease_failed(struct lease *lease, sprintf(lease_address, "unknown"); /* - * let's pretend that everything is ok, so we can continue for + * let's pretend that everything is ok, so we can continue for * information gathering purposes */ - + if (ddns_cb != NULL) { - strncpy(lease_address, piaddr(ddns_cb->address), + strncpy(lease_address, piaddr(ddns_cb->address), MAX_ADDRESS_STRING_LEN); - + if (ddns_cb->address.len == 4) { sprintf(reason, "duplicate IPv4 fixed-address entry"); } else if (ddns_cb->address.len == 16) { sprintf(reason, "duplicate IPv6 fixed-address6 entry " "or overlapping pools"); } else { - /* - * Should not happen. We have non-IPv4, non-IPv6 + /* + * Should not happen. We have non-IPv4, non-IPv6 * address. Something is very wrong here. */ sprintf(reason, "corrupted ddns_cb structure (address " "length is %d)", ddns_cb->address.len); } } - + log_error("Failed to properly update internal lease structure with " "DDNS"); log_error("control block structures. Tried to update lease for" @@ -989,7 +998,7 @@ safe_lease_update(struct lease *lease, { if (lease == NULL) { /* should never get here */ - log_fatal("Impossible condition at %s:%d (called from %s:%d).", + log_fatal("Impossible condition at %s:%d (called from %s:%d).", MDL, file, line); } @@ -999,7 +1008,7 @@ safe_lease_update(struct lease *lease, * are most likely trying to update wrong lease here. */ - /* + /* * Previously this error message popped out during * DNS update for fixed leases. As we no longer * try to update the lease for a fixed (static) lease @@ -1013,18 +1022,18 @@ safe_lease_update(struct lease *lease, #if defined (DNS_UPDATES_MEMORY_CHECKS) update_lease_failed(lease, NULL, oldcb, newcb, file, line); #endif - /* - * May not reach this: update_lease_failed calls + /* + * May not reach this: update_lease_failed calls * log_fatal. - */ + */ return; } if ( (lease->ddns_cb != NULL) && (lease->ddns_cb != oldcb) ) { - /* + /* * There is existing cb structure, but it differs from - * what we expected to see there. Most likely we are - * trying to update wrong lease. + * what we expected to see there. Most likely we are + * trying to update wrong lease. */ log_error("%s(%d): Failed to update internal lease " "structure with DDNS control block. Existing" @@ -1036,11 +1045,11 @@ safe_lease_update(struct lease *lease, #if defined (DNS_UPDATES_MEMORY_CHECKS) update_lease_failed(lease, NULL, oldcb, newcb, file, line); #endif - /* - * May not reach this: update_lease_failed calls + /* + * May not reach this: update_lease_failed calls * log_fatal. - */ - return; + */ + return; } /* additional IPv4 specific checks may be added here */ @@ -1059,12 +1068,12 @@ safe_lease6_update(struct iasubopt *lease6, if (lease6 == NULL) { /* should never get here */ - log_fatal("Impossible condition at %s:%d (called from %s:%d).", + log_fatal("Impossible condition at %s:%d (called from %s:%d).", MDL, file, line); } if ( (lease6->ddns_cb == NULL) && (newcb == NULL) ) { - inet_ntop(AF_INET6, &lease6->addr, addrbuf, + inet_ntop(AF_INET6, &lease6->addr, addrbuf, MAX_ADDRESS_STRING_LEN); /* * Trying to clean up pointer that is already null. We @@ -1079,20 +1088,20 @@ safe_lease6_update(struct iasubopt *lease6, update_lease_failed(NULL, lease6, oldcb, newcb, file, line); #endif - /* - * May not reach this: update_lease_failed calls + /* + * May not reach this: update_lease_failed calls * log_fatal. - */ - return; + */ + return; } if ( (lease6->ddns_cb != NULL) && (lease6->ddns_cb != oldcb) ) { - /* + /* * there is existing cb structure, but it differs from - * what we expected to see there. Most likely we are - * trying to update wrong lease. + * what we expected to see there. Most likely we are + * trying to update wrong lease. */ - inet_ntop(AF_INET6, &lease6->addr, addrbuf, + inet_ntop(AF_INET6, &lease6->addr, addrbuf, MAX_ADDRESS_STRING_LEN); log_error("%s(%d): Failed to update internal lease " @@ -1105,14 +1114,14 @@ safe_lease6_update(struct iasubopt *lease6, #if defined (DNS_UPDATES_MEMORY_CHECKS) update_lease_failed(NULL, lease6, oldcb, newcb, file, line); #endif - /* - * May not reach this: update_lease_failed calls + /* + * May not reach this: update_lease_failed calls * log_fatal. - */ + */ return; } /* additional IPv6 specific checks may be added here */ - + /* update the lease */ lease6->ddns_cb = newcb; } @@ -1132,7 +1141,7 @@ safe_lease6_update(struct iasubopt *lease6, * using the same value twice allows me more flexibility. */ -isc_result_t +isc_result_t ddns_update_lease_ptr(struct lease *lease, struct iasubopt *lease6, dhcp_ddns_cb_t *ddns_cb, @@ -1165,7 +1174,7 @@ ddns_update_lease_ptr(struct lease *lease, return (ISC_R_SUCCESS); } - /* + /* * If we are processing an expired or released v6 lease * we don't actually have a lease to update */ @@ -1175,10 +1184,10 @@ ddns_update_lease_ptr(struct lease *lease, } if (lease != NULL) { - safe_lease_update(lease, ddns_cb, ddns_cb_set, + safe_lease_update(lease, ddns_cb, ddns_cb_set, file, line); } else if (lease6 != NULL) { - safe_lease6_update(lease6, ddns_cb, ddns_cb_set, + safe_lease6_update(lease6, ddns_cb, ddns_cb_set, file, line); } else if (ddns_cb->address.len == 4) { struct lease *find_lease = NULL; @@ -1186,10 +1195,10 @@ ddns_update_lease_ptr(struct lease *lease, ddns_cb->address, MDL) != 0) { #if defined (DEBUG_DNS_UPDATES) log_info("%s(%d): find_lease_by_ip_addr(%s) successful:" - "lease=%p", file, line, ddns_address, + "lease=%p", file, line, ddns_address, find_lease); #endif - + safe_lease_update(find_lease, ddns_cb, ddns_cb_set, file, line); lease_dereference(&find_lease, MDL); @@ -1204,11 +1213,11 @@ ddns_update_lease_ptr(struct lease *lease, file, line); #endif /* - * may not reach this. update_lease_failed - * calls log_fatal. + * may not reach this. update_lease_failed + * calls log_fatal. */ - return(ISC_R_FAILURE); - + return(ISC_R_FAILURE); + } } else if (ddns_cb->address.len == 16) { struct iasubopt *find_lease6 = NULL; @@ -1217,9 +1226,9 @@ ddns_update_lease_ptr(struct lease *lease, char addrbuf[MAX_ADDRESS_STRING_LEN]; memcpy(&addr, &ddns_cb->address.iabuf, 16); - if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) != + if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) != ISC_R_SUCCESS) && - (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != + (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS)) { inet_ntop(AF_INET6, &addr, addrbuf, MAX_ADDRESS_STRING_LEN); @@ -1230,8 +1239,8 @@ ddns_update_lease_ptr(struct lease *lease, file, line); #endif /* - * never reached. update_lease_failed - * calls log_fatal. + * never reached. update_lease_failed + * calls log_fatal. */ return(ISC_R_FAILURE); } @@ -1250,20 +1259,20 @@ ddns_update_lease_ptr(struct lease *lease, file, line); #endif /* - * never reached. update_lease_failed - * calls log_fatal. + * never reached. update_lease_failed + * calls log_fatal. */ return(ISC_R_FAILURE); } ipv6_pool_dereference(&pool, MDL); } else { /* shouldn't get here */ - log_fatal("Impossible condition at %s:%d, called from %s:%d.", + log_fatal("Impossible condition at %s:%d, called from %s:%d.", MDL, file, line); } return(ISC_R_SUCCESS); -} +} void ddns_ptr_add(dhcp_ddns_cb_t *ddns_cb, @@ -1373,8 +1382,12 @@ ddns_ptr_remove(dhcp_ddns_cb_t *ddns_cb, * -- "Interaction between DHCP and DNS" * * If the second query fails with NXRRSET, the updater must conclude - * that the client's desired name is in use by another host. At this - * juncture, the updater can decide (based on some administrative + * that the client's desired name is in use by another host. If + * Dual Stack Mixed Mode (DSMM) is enabled and we proceed to a + * third stage forward update attempt specific to DSMM rules. If not, + * then the existing entries are left intact: + * + * At this juncture, the updater can decide (based on some administrative * configuration outside of the scope of this document) whether to let * the existing owner of the name keep that name, and to (possibly) * perform some name disambiguation operation on behalf of the current @@ -1394,6 +1407,11 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb, const char *logstr = NULL; char ddns_address[MAX_ADDRESS_STRING_LEN]; +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS:ddns_fwd_srv_add2: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif + /* Construct a printable form of the address for logging */ strcpy(ddns_address, piaddr(ddns_cb->address)); @@ -1414,7 +1432,7 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb, ddns_cb->state = DDNS_STATE_ADD_PTR; ddns_cb->cur_func = ddns_ptr_add; - + result = ddns_modify_ptr(ddns_cb, MDL); if (result == ISC_R_SUCCESS) { return; @@ -1427,8 +1445,21 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb, logstr = "DHCID mismatch, belongs to another client."; break; - case DNS_R_NXRRSET: case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + /* If DSMM is on we need to try forward add3 */ + if (ddns_cb->flags & DDNS_DUAL_STACK_MIXED_MODE) { + ddns_cb->state = DDNS_STATE_DSMM_FW_ADD3; + ddns_cb->cur_func = ddns_fwd_srv_add3; + + result = ddns_modify_fwd(ddns_cb, MDL); + if (result == ISC_R_SUCCESS) { + return; + } + + break; + } + logstr = "Has an address record but no DHCID, not mine."; break; @@ -1467,6 +1498,11 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb, isc_result_t result; char ddns_address[MAX_ADDRESS_STRING_LEN]; +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_fwd_srv_add1: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif + /* Construct a printable form of the address for logging */ strcpy(ddns_address, piaddr(ddns_cb->address)); @@ -1487,7 +1523,7 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb, ddns_cb->state = DDNS_STATE_ADD_PTR; ddns_cb->cur_func = ddns_ptr_add; - + result = ddns_modify_ptr(ddns_cb, MDL); if (result == ISC_R_SUCCESS) { return; @@ -1499,13 +1535,12 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb, /* we can reuse the zone information */ ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID; ddns_cb->cur_func = ddns_fwd_srv_add2; - + result = ddns_modify_fwd(ddns_cb, MDL); if (result == ISC_R_SUCCESS) { return; } break; - default: log_error ("Unable to add forward map from %.*s to %s: %s", (int)ddns_cb->fwd_name.len, @@ -1531,6 +1566,89 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb, return; } +/* + * This action routine is invoked after the DSMM third add stage is + * attempted. If we succeeded we attempt to update the reverse DNS, + * if not we cleanup and leave. + */ +void +ddns_fwd_srv_add3(dhcp_ddns_cb_t *ddns_cb, + isc_result_t eresult) +{ + isc_result_t result; + const char *logstr = NULL; + char ddns_address[MAX_ADDRESS_STRING_LEN]; + +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_fwd_srv_add3: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif + + /* Construct a printable form of the address for logging */ + strcpy(ddns_address, piaddr(ddns_cb->address)); + + switch(eresult) { + case ISC_R_SUCCESS: + log_info("Added new forward map from %.*s to %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address); + + ddns_update_lease_text(ddns_cb, NULL); + + if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) { + /* if we have zone information get rid of it */ + if (ddns_cb->zone != NULL) { + ddns_cb_forget_zone(ddns_cb); + } + + ddns_cb->state = DDNS_STATE_ADD_PTR; + ddns_cb->cur_func = ddns_ptr_add; + + result = ddns_modify_ptr(ddns_cb, MDL); + if (result == ISC_R_SUCCESS) { + return; + } + } + break; + + case DNS_R_YXRRSET: + logstr = "an entry that is either static or " + "owned by another client exists."; + break; + + case DNS_R_NXRRSET: + logstr = "static entry of the other protocol type exists."; + break; + + default: + logstr = isc_result_totext(eresult); + break; + } + + if (logstr != NULL) { + log_error("Forward map from %.*s to %s FAILED: %s", + (int)ddns_cb->fwd_name.len, + (const char *)ddns_cb->fwd_name.data, + ddns_address, logstr); + } + + ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL); + ddns_cb_free(ddns_cb, MDL); + /* + * A single DDNS operation may require several calls depending on + * the current state as the prerequisites for the first message + * may not succeed requiring a second operation and potentially + * a ptr operation after that. The commit_leases operation is + * invoked at the end of this set of operations in order to require + * a single write for all of the changes. We call commit_leases + * here rather than immediately after the call to update the lease + * text in order to save any previously written data. + */ + commit_leases(); + return; +} + static void ddns_fwd_srv_connector(struct lease *lease, struct iasubopt *lease6, @@ -1540,6 +1658,11 @@ ddns_fwd_srv_connector(struct lease *lease, { isc_result_t result = ISC_R_FAILURE; +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_fwd_srv_connector: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif + if (ddns_cb == NULL) { /* nothing to do */ return; @@ -1565,6 +1688,7 @@ ddns_fwd_srv_connector(struct lease *lease, } } + if (result == ISC_R_SUCCESS) { ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb, MDL); } else { @@ -1591,6 +1715,10 @@ void ddns_fwd_srv_rem2(dhcp_ddns_cb_t *ddns_cb, isc_result_t eresult) { +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_fwd_srv_rem2: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif /* * To get here we have already managed to remove the A/AAAA @@ -1651,12 +1779,17 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb, isc_result_t result = eresult; char ddns_address[MAX_ADDRESS_STRING_LEN]; +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_fwd_srv_rem1: %s eresult: %d", + dump_ddns_cb(ddns_cb), eresult); +#endif + switch(eresult) { case ISC_R_SUCCESS: /* Construct a printable form of the address for logging */ strcpy(ddns_address, piaddr(ddns_cb->address)); log_info("Removed forward map from %.*s to %s", - (int)ddns_cb->fwd_name.len, + (int)ddns_cb->fwd_name.len, (const char*)ddns_cb->fwd_name.data, ddns_address); @@ -1671,12 +1804,31 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb, case DNS_R_NXRRSET: case DNS_R_NXDOMAIN: + /* A result of not found means rem1 did not find a guard of + * our * type. From this we assume either there are no address + * record(s) of our type to delete or they are unguarded and + * therefore presumed to be static. Either way, we're done + * unless we're in DSMM and ddns-other-guard-is-dynamic is on. + * In which case we need to see if a guard of the other type + * exists, which permits us to delete any address records of + * our type. */ + #define DSMM_OGD (DDNS_DUAL_STACK_MIXED_MODE | \ + DDNS_OTHER_GUARD_IS_DYNAMIC) + if ((ddns_cb->flags & DSMM_OGD) == DSMM_OGD) { + ddns_cb->state = DDNS_STATE_REM_FW_DSMM_OTHER; + ddns_cb->cur_func = ddns_fwd_srv_rem2; + result = ddns_modify_fwd(ddns_cb, MDL); + if (result == ISC_R_SUCCESS) { + return; + } + break; + } + ddns_update_lease_text(ddns_cb, NULL); #if defined (DEBUG_DNS_UPDATES) log_info("DDNS: no forward map to remove. %p", ddns_cb); #endif - /* Trigger the add operation */ eresult = ISC_R_SUCCESS; @@ -1719,19 +1871,19 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb, * Remove relevant entries from DNS. * * \li lease - lease to start with if this is for v4 - * + * * \li lease6 - lease to start with if this is for v6 - * + * * \li add_ddns_cb - control block for additional DDNS work. This * is used when the code is going to add a DDNS entry after removing * the current entry. - * + * * \li active - indication about the status of the lease. It is * ISC_TRUE if the lease is still active, and FALSE if the lease * is inactive. This is used to indicate if the lease is inactive or going * to inactive so we can avoid trying to update the lease with cb pointers * and text information if it isn't useful. - * + * * Returns * \li #ISC_R_FAILURE - badness occurred and we weren't able to do what was wanted * \li #ISC_R_SUCCESS - we were able to do stuff but it's in progress @@ -1751,15 +1903,20 @@ ddns_removals(struct lease *lease, dhcp_ddns_cb_t *ddns_cb = NULL; struct data_string leaseid; +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS: ddns_removals: %s", + dump_ddns_cb(add_ddns_cb)); +#endif + /* * See if we need to cancel an outstanding request. Mostly this is * used to handle the case where this routine is called twice for * the same release or abandon event. - * + * * When called from the dns code as part of an update request * (add_ddns_cb != NULL) any outstanding requests will have already * been cancelled. - * + * * If the new request is just a removal and we have an outstanding * request we have several options: * @@ -1773,13 +1930,13 @@ ddns_removals(struct lease *lease, * done after the removal we need to kill the update part of the * request. */ - + if (add_ddns_cb == NULL) { if ((lease != NULL) && (lease->ddns_cb != NULL)) { ddns_cb = lease->ddns_cb; /* - * Is the old request an update or did the + * Is the old request an update or did the * the active flag change? */ if (((ddns_cb->state == DDNS_STATE_ADD_PTR) || @@ -1807,7 +1964,7 @@ ddns_removals(struct lease *lease, ddns_cb = lease6->ddns_cb; /* - * Is the old request an update or did the + * Is the old request an update or did the * the active flag change? */ if (((ddns_cb->state == DDNS_STATE_ADD_PTR) || @@ -1841,6 +1998,9 @@ ddns_removals(struct lease *lease, goto cleanup; } + /* Set the conflict detection flags based on global configuration */ + copy_conflict_flags(&ddns_cb->flags, ddns_conflict_mask); + /* * For v4 we flag static leases so we don't try * and manipulate the lease later. For v6 we don't @@ -1906,23 +2066,25 @@ ddns_removals(struct lease *lease, } /* - * Find the txt or dhcid tag and copy it to the control block. If we don't - * have one this isn't an interim or standard record so we can't delete - * the A record using this mechanism but we can delete the ptr record. - * In this case we will attempt to do any requested next step. + * Find the txt or dhcid tag and copy it to the control block. If we + * don't have one this isn't an interim or standard record so we can't + * delete the A record using this mechanism but we can delete the ptr + * record. In this case we will attempt to do any requested next step. */ memset(&leaseid, 0, sizeof(leaseid)); if (find_bound_string (&leaseid, *scope, ddns_standard_tag)) { /* We have a standard tag */ ddns_cb->lease_tag = ddns_standard_tag; ddns_cb->dhcid_class = dns_rdatatype_dhcid; + ddns_cb->other_dhcid_class = dns_rdatatype_txt; data_string_copy(&ddns_cb->dhcid, &leaseid, MDL); data_string_forget(&leaseid, MDL); } else if (find_bound_string (&leaseid, *scope, ddns_interim_tag)) { /* we have an interim tag */ ddns_cb->lease_tag = ddns_interim_tag; ddns_cb->dhcid_class = dns_rdatatype_txt; - if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) != + ddns_cb->other_dhcid_class = dns_rdatatype_dhcid; + if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) != ISC_R_SUCCESS) { /* We couldn't convert the dhcid from the lease * version to the dns version. We can't delete @@ -1933,7 +2095,7 @@ ddns_removals(struct lease *lease, data_string_forget(&leaseid, MDL); } else { ddns_cb->flags &= ~DDNS_UPDATE_ADDR; - } + } /* * Find the rev name and copy it to the control block. If we don't @@ -1943,7 +2105,8 @@ ddns_removals(struct lease *lease, if (!find_bound_string(&ddns_cb->rev_name, *scope, "ddns-rev-name")) { ddns_cb->flags &= ~DDNS_UPDATE_PTR; } - + + /* * If we have a second control block for doing an add * after the remove finished attach it to our control block. @@ -1996,7 +2159,7 @@ ddns_removals(struct lease *lease, */ if (execute_add != ISC_R_SUCCESS) { ddns_cb->next_op = NULL; - ddns_fwd_srv_connector(lease, lease6, scope, + ddns_fwd_srv_connector(lease, lease6, scope, add_ddns_cb, execute_add); add_ddns_cb = NULL; } @@ -2025,10 +2188,174 @@ ddns_removals(struct lease *lease, * we allocated here. */ ddns_fwd_srv_connector(lease, lease6, scope, add_ddns_cb, execute_add); - if (ddns_cb != NULL) + if (ddns_cb != NULL) ddns_cb_free(ddns_cb, MDL); return (result); } +/* Convenience function for setting flag bits in a mask */ +void set_flag (u_int16_t *flags, + u_int16_t flag, + u_int16_t value) { + if (flags) { + if (value) { + *flags |= flag; + } else { + *flags &= ~flag; + } + } +} + +/* + * Convenience function which replicates the conflict flags set in one + * mask to another, while preserving all other flags. + */ +void copy_conflict_flags(u_int16_t *target, + u_int16_t source) { + if (target) { + /* Preserve non conflict flags */ + *target &= ~CONFLICT_BITS; + + /* Enable conflict flags per source */ + *target |= source & CONFLICT_BITS; + } +} + +/* + * Given an option_state, create a mask of conflict detection flags based + * on the appropriate configuration parameters within the option state. + */ +u_int16_t +get_conflict_mask(struct option_state *options) { + + int ddns_update_conflict_detection = 1; /* default on */ + int ddns_dual_stack_mixed_mode = 0; /* default off */ + int ddns_guard_id_must_match = 1; /* default on */ + int ddns_other_guard_is_dynamic = 0; /* default off */ + struct option_cache *oc = NULL; + + u_int16_t mask = 0; + oc = lookup_option(&server_universe, options, SV_DDNS_CONFLICT_DETECT); + if (oc) { + ddns_update_conflict_detection = + evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options, + NULL, &global_scope, oc, MDL); + } + + set_flag(&mask, DDNS_CONFLICT_DETECTION, + ddns_update_conflict_detection); + + if (!ddns_update_conflict_detection) { +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS conflict detection: off"); +#endif + /* Turn the rest of the conflict related flags off */ + set_flag(&mask, DDNS_DUAL_STACK_MIXED_MODE, 0); + set_flag(&mask, DDNS_GUARD_ID_MUST_MATCH, 0); + set_flag(&mask, DDNS_OTHER_GUARD_IS_DYNAMIC, 0); + return (mask); + } + + // Get the values + oc = lookup_option(&server_universe, options, + SV_DDNS_DUAL_STACK_MIXED_MODE); + if (oc) { + ddns_dual_stack_mixed_mode = + evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options, + NULL, &global_scope, oc, MDL); + } + + oc = lookup_option(&server_universe, options, + SV_DDNS_GUARD_ID_MUST_MATCH); + if (oc) { + ddns_guard_id_must_match = + evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options, + NULL, &global_scope, oc, MDL); + } + + oc = lookup_option(&server_universe, options, + SV_DDNS_OTHER_GUARD_IS_DYNAMIC); + if (oc) { + ddns_other_guard_is_dynamic = + evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options, + NULL, &global_scope, oc, MDL); + } + + // Set the flags + set_flag(&mask, DDNS_DUAL_STACK_MIXED_MODE, + ddns_dual_stack_mixed_mode); + + set_flag(&mask, DDNS_GUARD_ID_MUST_MATCH, + ddns_guard_id_must_match); + + set_flag(&mask, DDNS_OTHER_GUARD_IS_DYNAMIC, + ddns_other_guard_is_dynamic); + +#if defined (DEBUG_DNS_UPDATES) + log_info ("DDNS conflict behavior:\n" + "\tddns-update-style: %s\n" + "\tupdate-conflict-detection: %d\n" + "\tddns-dual-stack-mixed-mode: %d\n" + "\tddns-guard-id-must-match %d\n" + "\tddns-other-guard-is-dynamic: %d\n", + ddns_styles_values[ddns_update_style].name, + ddns_update_conflict_detection, + ddns_dual_stack_mixed_mode, + ddns_guard_id_must_match, + ddns_other_guard_is_dynamic); +#endif + return (mask); +} + +#if defined (DEBUG_DNS_UPDATES) +/* Type used for creating lists of function pointers and their names */ +typedef struct { + void *ptr; + char *name; +} LabeledPtr; + +/* Returns the name of the function referred to by the given address */ +char* +dump_ddns_cb_func(void *func) { + static LabeledPtr funcs[] = { + { ddns_ptr_add, "ddns_ptr_add" }, + { ddns_fwd_srv_add2, "ddns_fwd_srv_add2" }, + { ddns_fwd_srv_add1, "ddns_fwd_srv_add1" }, + { ddns_ptr_remove, "ddns_ptr_remove" }, + { ddns_fwd_srv_rem2, "ddns_fwd_srv_rem2" }, + { ddns_fwd_srv_rem1, "ddns_fwd_srv_rem1" }, + { ddns_fwd_srv_add3, "ddns_fwd_srv_adde" }, + { NULL, "unknown" } + }; + + LabeledPtr* lp = funcs; + if (!func) { + return ("<null>"); + } + + while ((lp->ptr) && (lp->ptr != func)) { + ++lp; + } + + return (lp->name); +} + +/* Dumps basic control block info to the log */ +char* +dump_ddns_cb (dhcp_ddns_cb_t *ddns_cb) { + static char output_buf[4096]; + if (!ddns_cb) { + return ("<ddns_cb is null>"); + } + + sprintf (output_buf, "ddns_cb: %p flags: %x state: %s cur_func: %s", + ddns_cb, ddns_cb->flags, + ddns_state_name(ddns_cb->state), + dump_ddns_cb_func(ddns_cb->cur_func)); + + return(output_buf); +} +#endif /* DEBUG_DNS_UPDATES */ + #endif /* NSUPDATE */ diff --git a/server/dhcpd.c b/server/dhcpd.c index fbbb37c0..bf0036ca 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -48,7 +48,7 @@ static const char url [] = # include <unistd.h> # include <pwd.h> /* get around the ISC declaration of group */ -# define group real_group +# define group real_group # include <grp.h> # undef group @@ -73,7 +73,10 @@ option server.ddns-hostname = \n\ option server.ddns-domainname = config-option domain-name; \n\ option server.ddns-rev-domainname = \"in-addr.arpa.\";"; +/* Stores configured DDNS conflict detection flags */ +u_int16_t ddns_conflict_mask; #endif /* NSUPDATE */ + int ddns_update_style; int dont_use_fsync = 0; /* 0 = default, use fsync, 1 = don't use fsync */ int server_id_check = 0; /* 0 = default, don't check server id, 1 = do check */ @@ -237,7 +240,7 @@ static void setup_chroot (char *chroot_dir) { } #endif /* PARANOIA */ -int +int main(int argc, char **argv) { int fd; int i, status; @@ -598,7 +601,7 @@ main(int argc, char **argv) { const char *path = path_dhcpd_db; path_dhcpd_db = realpath(path_dhcpd_db, NULL); if (path_dhcpd_db == NULL) - log_fatal("Failed to get realpath for %s: %s", path, + log_fatal("Failed to get realpath for %s: %s", path, strerror(errno)); } @@ -696,7 +699,7 @@ main(int argc, char **argv) { #endif } } - + if (local_family == AF_INET) { remote_port = htons(ntohs(local_port) + 1); } else { @@ -789,13 +792,13 @@ main(int argc, char **argv) { log_error ("** You must specify a lease file with -lf."); log_error (" Dhcpd will not overwrite your default"); log_fatal (" lease file when playing back a trace. **"); - } + } trace_file_replay (traceinfile); #if defined (DEBUG_MEMORY_LEAKAGE) && \ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) free_everything (); - omapi_print_dmalloc_usage_by_caller (); + omapi_print_dmalloc_usage_by_caller (); #endif exit (0); @@ -844,7 +847,7 @@ main(int argc, char **argv) { #endif /* test option should cause an early exit */ - if (cftest && !lftest) + if (cftest && !lftest) exit(0); /* @@ -905,8 +908,8 @@ main(int argc, char **argv) { * Remove addresses from our pools that we should not issue * to clients. * - * We currently have no support for this in IPv4. It is not - * as important in IPv4, as making pools with ranges that + * We currently have no support for this in IPv4. It is not + * as important in IPv4, as making pools with ranges that * leave out interfaces and hosts is fairly straightforward * using range notation, but not so handy with CIDR notation. */ @@ -985,7 +988,7 @@ main(int argc, char **argv) { log_fatal ("setgroups: %m"); if (setgid (set_gid)) log_fatal ("setgid(%d): %m", (int) set_gid); - } + } if (set_uid) { if (setuid (set_uid)) @@ -1268,6 +1271,12 @@ void postconf_initialization (int quiet) log_fatal("Unable to complete ddns initialization"); } } + + /* Set the conflict detection flag mask based on globally + * defined DDNS configuration params. This mask should be + * to init ddns_cb::flags before for every DDNS transaction. */ + ddns_conflict_mask = get_conflict_mask(options); + #else /* If we don't have support for updates compiled in tell the user */ if (ddns_update_style != DDNS_UPDATE_STYLE_NONE) { @@ -1352,7 +1361,7 @@ void postconf_initialization (int quiet) } oc = lookup_option(&server_universe, options, SV_PREFIX_LEN_MODE); - if ((oc != NULL) && + if ((oc != NULL) && evaluate_option_cache(&db, NULL, NULL, NULL, options, NULL, &global_scope, oc, MDL)) { if (db.len == 1) { @@ -1518,7 +1527,7 @@ int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia) interface_reference (&subnet -> interface, ip, MDL); subnet -> interface_address = *ia; } else if (subnet -> interface != ip) { - log_error ("Multiple interfaces match the %s: %s %s", + log_error ("Multiple interfaces match the %s: %s %s", "same subnet", subnet -> interface -> name, ip -> name); } @@ -1532,11 +1541,11 @@ int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia) shared_network_reference (&ip -> shared_network, share, MDL); } - + if (!share -> interface) { interface_reference (&share -> interface, ip, MDL); } else if (share -> interface != ip) { - log_error ("Multiple interfaces match the %s: %s %s", + log_error ("Multiple interfaces match the %s: %s %s", "same shared network", share -> interface -> name, ip -> name); } @@ -1657,13 +1666,13 @@ static isc_result_t dhcp_io_shutdown_countdown (void *vlp) if (no_pid_file == ISC_FALSE) (void) unlink(path_dhcpd_pid); exit (0); - } + } #else if (shutdown_state == shutdown_done) { #if defined (DEBUG_MEMORY_LEAKAGE) && \ defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) free_everything (); - omapi_print_dmalloc_usage_by_caller (); + omapi_print_dmalloc_usage_by_caller (); #endif if (no_pid_file == ISC_FALSE) (void) unlink(path_dhcpd_pid); @@ -1707,7 +1716,7 @@ isc_result_t dhcp_set_control_state (control_object_state_t oldstate, /* Called on signal. */ log_info("Received signal %d, initiating shutdown.", shutdown_signal); shutdown_signal = SIGUSR1; - + /* * Prompt the shutdown event onto the timer queue * and return to the dispatch loop. diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5 index 076a35fb..2c064fa6 100644 --- a/server/dhcpd.conf.5 +++ b/server/dhcpd.conf.5 @@ -1227,6 +1227,74 @@ on the PTR record, but the \fIinterim\fR update method does not do this. In the final RFC this requirement was relaxed such that a server may add a DHCID RR to the PTR record. .PP +.SH DDNS IN DUAL STACK ENVIRONMENTS +As described in RFC 4703, section 5.2, in order to perform DDNS in dual +stack environments, both IPv4 and IPv6 servers would need to be configured +to use the standard update style and participating IPv4 clients MUST +convey DUIDs as described in RFC 4361, section 6.1., in their +dhcp-client-identifiers. +.PP +In a nutshell, this mechanism is intended to use globally unique DUIDs +to idenfity both IPv4 and IPv6 clients, and where a device has both +IPv4 and IPv6 leases it is identified by the same DUID. This allows +a dual stack client to use the same FQDN for both mappings, while +being protected from updates for other clients by the rules of conflict +detection. +.PP +However, not all IPv4 clients implement this behavior which makes +supporting them dual stack environments problematic. In order to +address this issue ISC DHCP (as of 4.4.0) supports a new mode of +DDNS conflict resolution referred to as Dual Stack Mixed Mode (DSMM). +.PP +The concept behind DSMM is relatively simple. All dhcp servers of one +protocol (IPv4 or v6) use one ddns-update-style (interim or standard) +while all servers of the "other" protocol will use the "other" +ddns-udpate-style. In this way, all servers of a given protocol are +using the same record type (TXT or DHCID) for their DHCID RR entries. +This allows conflict detection to be enforced within each protocol +without interferring with the other's entries. +.PP +DSMM modifications now ensure that IPv4 DSMM servers only ever modify +A records, their associated PTR records and DHCID records, while DSMM +IPv6 severs only modify AAAA records, their associated PTR records, +and DHCID records. +.PP +Note that DSMM is not a perfect solution, it is a compromise that can +work well provided all participating DNS updaters play by DSMM rules. +As with anything else in life, it only works as well as those who +particpate behave. +.PP +While conflict detection is enabled by default, DSMM is not. To enable +DSMM, both update-conflict-detection and ddns-dual-stack-mixed-mode must +be true. +.PP +.SH PROTECTING DNS ENTRIES FOR STATIC CLIENTS +Built into conflict resolution is the protection of manually made entries +for static clients. Per the rules of conflict resolution, a DNS updater +may not alter forward DNS entries unless there is a DHCID RR which matches +for whom the update is being made. Therefore, any forward DNS entries +without a corresponding DHCID RR cannot be altered by such an updater. +.PP +In some environments, it may be desirable to use only this aspect of conflict +resolution and allow DNS updaters to overwrite entries for dynamic clients +regardless of what client owns them. In other words, the presence or lack +of a DHCID RR is used to determine whether entries may or may not be +overwritten. Whether or not the client matches the data value of the DHCID +RR is irrelevant. This behavior, off by default, can be configured through +the parameter, ddns-guard-id-must-match. As with DSMM, this behavior is +can only be enabled if conflict resolution is enabled. This behavior should +be considered carefully before electing to use it. +.PP +There is an additional parameter that can be used with DSMM +ddns-other-guard-is-dynamic. When enabled along with DSMM, a server will +regard the presence of a DHCID RR of the other style type as indicating that +the forward DNS entries for that FQDN should be dynamic and may be overwritten. +For example, such a server using interim style could overwrite the DNS entries +for an FQDN if there is only a DHDID type DHDID RR for the FQDN. Essentially, +if there are dynamic entries for one protocol, that is enough to overcome the +static protection of entries for the other protocol. This behavior warrants +careful consideration before electing to use it. +.PP .SH DYNAMIC DNS UPDATE SECURITY .PP When you set your DNS server up to allow updates from the DHCP server, @@ -2070,6 +2138,34 @@ appended to the client's hostname to form a fully-qualified domain-name (FQDN). .RE .PP +The \fIddns-dual-stack-mixed-mode\fR statement +.RS 0.25i +.PP +.B ddns-dual-stack-mixed-mode \fIflag\fB;\fR +.PP +The \fIddns-dual-stack-mixed-mode\fR parameter controls whether or not the +server applies Dual Stack Mixed Mode rules during DDNS conflict resolution. +This parameter is off by default, has no effect unless +update-conflict-detection is enabled, and may only be specified at the +global scope. +.RE +.PP +The \fIddns-guard-id-must-match\fR statement +.RS 0.25i +.PP +.B ddns-guard-id-must-match \fIflag\fB;\fR +.PP +The \fIddns-guard-id-must-match\fR parameter controls whether or not a +the client id within a DHCID RR must match that of the DNS update's client +to permit DNS entries associated with that DHCID RR to be ovewritten. +Proper conflict resolution requires ID matching and should only be disabled +after careful consideration. When disabled, it is allows any DNS updater to +replace DNS entries that have an associated DHCID RR, regardless of client +identity. This parameter is on by default, has no effect unless +update-conflict-detection is enabled, and may only be specified at the global +scope. +.RE +.PP The \fddns-local-address4\fR and \fddns-local-address6\fR statements .RS 0.25i .PP @@ -2082,6 +2178,20 @@ the server should use as the from address when sending DDNS update requests. .RE .PP +The \fIddns-other-guard-is-dynamic\fR statement +.RS 0.25i +.PP +.B ddns-other-guard-is-dynamic \fIflag\fB;\fR +.PP +The \fIddns-other-guard-is-dynamic\fR parameter controls whether or not a +a server running DSMM will consider the presence of the other update style +DHCID RR as an indcation that a DNS entries may be overwritten. It should +only be enabled after careful study as it allows DNS entries that would +otherwise be protected as static, to be overwritten in certain cases. This +paramater is off by default, has no effect unless ddns-dual-stack-mixed-mode +is enabled, and may only be specified at the global scope. +.RE +.PP The \fIddns-rev-domainname\fR statement .RS 0.25i .PP @@ -3125,7 +3235,7 @@ server will use dhcp-renewal-time and dhcp-rebinding-time, respectively. A value of zero tells the client it may choose its own value. When those options are not defined then values will be set to zero unless the -global \fIdhcpv6-set-tee-times\R is enabled. When this option is enabled the +global \fIdhcpv6-set-tee-times\fR is enabled. When this option is enabled the times are calculated as recommended by RFC 3315, Section 22.4: T1 will be set to 0.5 times the shortest preferred lifetime @@ -3199,7 +3309,8 @@ If the \fIupdate-conflict-detection\fR parameter is true, the server will perform standard DHCID multiple-client, one-name conflict detection. If the parameter has been set false, the server will skip this check and instead simply tear down any previous bindings to install the new -binding without question. The default is true. +binding without question. The default is true and this parameter may only +be specified at the global scope. .RE .PP The diff --git a/server/stables.c b/server/stables.c index 59df5e83..107728d2 100644 --- a/server/stables.c +++ b/server/stables.c @@ -283,6 +283,9 @@ static struct option server_options[] = { #if defined (FAILOVER_PROTOCOL) { "check-secs-byte-order", "f", &server_universe, SV_CHECK_SECS_BYTE_ORDER, 1 }, #endif + { "ddns-dual-stack-mixed-mode", "f", &server_universe, SV_DDNS_DUAL_STACK_MIXED_MODE, 1 }, + { "ddns-guard-id-must-match", "f", &server_universe, SV_DDNS_GUARD_ID_MUST_MATCH, 1 }, + { "ddns-other-guard-is-dynamic", "f", &server_universe, SV_DDNS_OTHER_GUARD_IS_DYNAMIC, 1 }, { NULL, NULL, NULL, 0, 0 } }; |