]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
[master] Adds DDNS Dual Stack Mixed Mode
authorThomas Markwalder <tmark@isc.org>
Fri, 8 Dec 2017 16:56:10 +0000 (11:56 -0500)
committerThomas Markwalder <tmark@isc.org>
Fri, 8 Dec 2017 16:56:10 +0000 (11:56 -0500)
    Merges in rt42620.

RELNOTES
common/dns.c
common/print.c
includes/dhcpd.h
includes/site.h
server/ddns.c
server/dhcpd.c
server/dhcpd.conf.5
server/stables.c

index 3928d522a601a77d0d03d52d19b3d78c8f319aa8..b3f08f1d2eef54b88d07e69401d7dd454e7715ab 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -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.
index 63bbc8606652805b6c9cb855428aed7af19536ac..86a1ecd1d4049927b9642168699a3cbbfe73de23 100644 (file)
@@ -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 */
index 2926e6cb602e069a906dc9ceb0b1f3a6c1f2897a..5993c0e29d4937b6b334e5dee7555e69726f2fcb 100644 (file)
@@ -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.
  */
index 1a6e8ef38cfcd040c9962e14bc8897481723441d..44d543d1fabeef251564c1bb5431e07cbced3349 100644 (file)
@@ -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);
index 220d31a5d366c65103eec91199350218f612cf74..461eaccf16aca05678d45a4fc732ec08995cea2b 100644 (file)
 
 /* 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 */
index d370bbed69b295fb2133d87dc27e7cea554add34..396eda5d3dce07b7036d19acb5508b918daa0b81 100644 (file)
@@ -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 */
index fbbb37c0cc44743d48a354cf58f067b62d81d466..bf0036cabf6275c5e49595376e2a4131367f31bb 100644 (file)
@@ -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.
index 076a35fb29cf11d532fecf5d32dfe5924dd011fa..2c064fa69fb61d2ab2fea63c0a7ea70b06887ca2 100644 (file)
@@ -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
index 59df5e83d5b03b5a340c0ea493abd1fe6caf19d2..107728d253f82dea0e7b5767c02b23d55ca86e14 100644 (file)
@@ -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 }
 };