summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2017-12-08 11:56:10 -0500
committerThomas Markwalder <tmark@isc.org>2017-12-08 11:56:10 -0500
commit905c58b9cac7b734c0518786476bd7c2e31b8977 (patch)
tree2710dcf3cd0c36bc686ce38e4dc8c7827cfbd8b7 /server
parent1aca5897f269bdfa922a000ec3f3e4b588a7b966 (diff)
downloadisc-dhcp-905c58b9cac7b734c0518786476bd7c2e31b8977.tar.gz
[master] Adds DDNS Dual Stack Mixed Mode
Merges in rt42620.
Diffstat (limited to 'server')
-rw-r--r--server/ddns.c537
-rw-r--r--server/dhcpd.c43
-rw-r--r--server/dhcpd.conf.5115
-rw-r--r--server/stables.c3
4 files changed, 574 insertions, 124 deletions
diff --git a/server/ddns.c b/server/ddns.c
index d370bbed..396eda5d 100644
--- a/server/ddns.c
+++ b/server/ddns.c
@@ -3,7 +3,7 @@
Dynamic DNS updates. */
/*
- *
+ *
* Copyright (c) 2004-2017 by Internet Systems Consortium, Inc. ("ISC")
* Copyright (c) 2000-2003 by Internet Software Consortium
*
@@ -40,12 +40,23 @@ char *ddns_interim_tag = "ddns-txt";
#ifdef NSUPDATE
+#if defined (DEBUG_DNS_UPDATES)
+static char* dump_ddns_cb_func(void *func);
+static char* dump_ddns_cb (dhcp_ddns_cb_t *ddns_cb);
+
+extern struct enumeration_value ddns_styles_values[];
+#endif
+
static void ddns_fwd_srv_connector(struct lease *lease,
struct iasubopt *lease6,
struct binding_scope **inscope,
dhcp_ddns_cb_t *ddns_cb,
isc_result_t eresult);
+static void copy_conflict_flags(u_int16_t *target, u_int16_t source);
+
+static void ddns_fwd_srv_add3(dhcp_ddns_cb_t *ddns_cb, isc_result_t eresult);
+
/* DN: No way of checking that there is enough space in a data_string's
buffer. Be certain to allocate enough!
TL: This is why the expression evaluation code allocates a *new*
@@ -123,7 +134,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
}
/*
* Assume that we shall update both the A and ptr records and,
- * as this is an update, set the active flag
+ * as this is an update, set the active flag
*/
ddns_cb->flags = DDNS_UPDATE_ADDR | DDNS_UPDATE_PTR |
DDNS_ACTIVE_LEASE;
@@ -293,7 +304,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
goto in;
}
#endif
-
+
/* See if the administrator wants to do updates even
in cases where the update already appears to have been
done. */
@@ -325,7 +336,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
}
}
in:
-
+
/* If we don't have a name that the client has been assigned, we
can just skip all this. */
@@ -351,7 +362,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
* The new behavior continues to allow the customer to set
* up an option but the defaults are a little different.
* We now use 1/2 of the (preferred) lease time for both
- * v4 and v6 and cap them at a maximum value.
+ * v4 and v6 and cap them at a maximum value.
* If the customer chooses to use an experession that references
* part of the lease the v6 value will be the default as there
* isn't a lease available for v6.
@@ -373,7 +384,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
if (ddns_ttl > MAX_DEFAULT_DDNS_TTL) {
ddns_ttl = MAX_DEFAULT_DDNS_TTL;
}
-#endif
+#endif
if ((oc = lookup_option(&server_universe, options, SV_DDNS_TTL))) {
if (evaluate_option_cache(&d1, packet, lease, NULL,
@@ -398,8 +409,8 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
else
s1 = 0;
- /*
- * Figure out the length of the part of the name that depends
+ /*
+ * Figure out the length of the part of the name that depends
* on the address.
*/
if (ddns_cb->address.len == 4) {
@@ -422,8 +433,8 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
}
}
} else if (ddns_cb->address.len == 16) {
- /*
- * IPv6 reverse names are always the same length, with
+ /*
+ * IPv6 reverse names are always the same length, with
* 32 hex characters separated by dots.
*/
rev_name_len = sizeof("0.1.2.3.4.5.6.7."
@@ -458,7 +469,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
if (ddns_cb->address.len == 4) {
rname->len =
sprintf((char *)rname->buffer->data,
- "%u.%u.%u.%u.",
+ "%u.%u.%u.%u.",
ddns_cb->address.iabuf[3] & 0xff,
ddns_cb->address.iabuf[2] & 0xff,
ddns_cb->address.iabuf[1] & 0xff,
@@ -474,7 +485,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
char *p = (char *)&rname->buffer->data;
unsigned char *a = ddns_cb->address.iabuf + 15;
for (i=0; i<16; i++) {
- sprintf(p, "%x.%x.",
+ sprintf(p, "%x.%x.",
(*a & 0xF), ((*a >> 4) & 0xF));
p += 4;
a -= 1;
@@ -512,12 +523,14 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
/* The standard style */
ddns_cb->lease_tag = ddns_standard_tag;
ddns_cb->dhcid_class = dns_rdatatype_dhcid;
+ ddns_cb->other_dhcid_class = dns_rdatatype_txt;
ddns_type = 1;
ddns_len = 4;
} else {
/* The older interim style */
ddns_cb->lease_tag = ddns_interim_tag;
ddns_cb->dhcid_class = dns_rdatatype_txt;
+ ddns_cb->other_dhcid_class = dns_rdatatype_dhcid;
/* for backwards compatibility */
ddns_type = DHO_DHCP_CLIENT_IDENTIFIER;
/* IAID incorrectly included */
@@ -565,14 +578,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
*/
if (ddns_cb->flags & DDNS_UPDATE_ADDR) {
- oc = lookup_option(&server_universe, options,
- SV_DDNS_CONFLICT_DETECT);
- if (oc &&
- !evaluate_boolean_option_cache(&ignorep, packet, lease,
- NULL, packet->options,
- options, scope, oc, MDL))
- ddns_cb->flags |= DDNS_CONFLICT_OVERRIDE;
-
+ copy_conflict_flags(&ddns_cb->flags, ddns_conflict_mask);
}
/*
@@ -772,7 +778,7 @@ ddns_updates(struct packet *packet, struct lease *lease, struct lease *old,
* Lastly, if we needed to find the scope we write it out, if we used a
* scope that was passed as an argument we don't write it, assuming that
* our caller (or his ...) will do the write.
- *
+ *
*\li ddns_cb - the control block for the DDNS request
*
*\li inscope - a pointer to the scope to update. This may be NULL
@@ -797,7 +803,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
struct ipv6_pool *pool = NULL;
struct in6_addr addr;
struct data_string lease_dhcid;
-
+
/*
* If the lease was static (for a fixed address)
* we don't need to do any work.
@@ -805,7 +811,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
if (ddns_cb->flags & DDNS_STATIC_LEASE)
return (ISC_R_SUCCESS);
- /*
+ /*
* If we are processing an expired or released v6 lease
* or some types of v4 leases we don't actually have a
* scope to update
@@ -823,7 +829,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
memcpy(&addr, &ddns_cb->address.iabuf, 16);
if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) ==
ISC_R_SUCCESS) ||
- (find_ipv6_pool(&pool, D6O_IA_NA, &addr) ==
+ (find_ipv6_pool(&pool, D6O_IA_NA, &addr) ==
ISC_R_SUCCESS)) {
if (iasubopt_hash_lookup(&lease6, pool->leases,
&addr, 16, MDL)) {
@@ -867,10 +873,12 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
case DDNS_STATE_ADD_FW_YXDHCID:
case DDNS_STATE_ADD_FW_NXDOMAIN:
+ case DDNS_STATE_DSMM_FW_ADD3:
bind_ds_value(scope, "ddns-fwd-name", &ddns_cb->fwd_name);
if (ddns_cb->lease_tag == ddns_standard_tag) {
- bind_ds_value(scope, ddns_standard_tag, &ddns_cb->dhcid);
+ bind_ds_value(scope, ddns_standard_tag,
+ &ddns_cb->dhcid);
} else {
/* convert from dns version to lease version of dhcid */
memset(&lease_dhcid, 0, sizeof(lease_dhcid));
@@ -882,11 +890,12 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
case DDNS_STATE_REM_FW_NXRR:
case DDNS_STATE_REM_FW_YXDHCID:
+ case DDNS_STATE_REM_FW_DSMM_OTHER:
unset(*scope, "ddns-fwd-name");
unset(*scope, ddns_cb->lease_tag);
break;
}
-
+
/* If necessary write it out and get rid of the lease */
if (lease) {
write_lease(lease);
@@ -906,7 +915,7 @@ ddns_update_lease_text(dhcp_ddns_cb_t *ddns_cb,
* cases (all are configuration mistakes):
* a) IPv4: user have duplicate fixed-address entries (the same
* address is defined twice). We may have found wrong lease.
- * b) IPv6: user have overlapping pools (we tried to find
+ * b) IPv6: user have overlapping pools (we tried to find
* a lease in a wrong pool)
* c) IPv6: user have duplicate fixed-address6 entires (the same
* address is defined twice). We may have found wrong lease.
@@ -930,29 +939,29 @@ update_lease_failed(struct lease *lease,
sprintf(lease_address, "unknown");
/*
- * let's pretend that everything is ok, so we can continue for
+ * let's pretend that everything is ok, so we can continue for
* information gathering purposes
*/
-
+
if (ddns_cb != NULL) {
- strncpy(lease_address, piaddr(ddns_cb->address),
+ strncpy(lease_address, piaddr(ddns_cb->address),
MAX_ADDRESS_STRING_LEN);
-
+
if (ddns_cb->address.len == 4) {
sprintf(reason, "duplicate IPv4 fixed-address entry");
} else if (ddns_cb->address.len == 16) {
sprintf(reason, "duplicate IPv6 fixed-address6 entry "
"or overlapping pools");
} else {
- /*
- * Should not happen. We have non-IPv4, non-IPv6
+ /*
+ * Should not happen. We have non-IPv4, non-IPv6
* address. Something is very wrong here.
*/
sprintf(reason, "corrupted ddns_cb structure (address "
"length is %d)", ddns_cb->address.len);
}
}
-
+
log_error("Failed to properly update internal lease structure with "
"DDNS");
log_error("control block structures. Tried to update lease for"
@@ -989,7 +998,7 @@ safe_lease_update(struct lease *lease,
{
if (lease == NULL) {
/* should never get here */
- log_fatal("Impossible condition at %s:%d (called from %s:%d).",
+ log_fatal("Impossible condition at %s:%d (called from %s:%d).",
MDL, file, line);
}
@@ -999,7 +1008,7 @@ safe_lease_update(struct lease *lease,
* are most likely trying to update wrong lease here.
*/
- /*
+ /*
* Previously this error message popped out during
* DNS update for fixed leases. As we no longer
* try to update the lease for a fixed (static) lease
@@ -1013,18 +1022,18 @@ safe_lease_update(struct lease *lease,
#if defined (DNS_UPDATES_MEMORY_CHECKS)
update_lease_failed(lease, NULL, oldcb, newcb, file, line);
#endif
- /*
- * May not reach this: update_lease_failed calls
+ /*
+ * May not reach this: update_lease_failed calls
* log_fatal.
- */
+ */
return;
}
if ( (lease->ddns_cb != NULL) && (lease->ddns_cb != oldcb) ) {
- /*
+ /*
* There is existing cb structure, but it differs from
- * what we expected to see there. Most likely we are
- * trying to update wrong lease.
+ * what we expected to see there. Most likely we are
+ * trying to update wrong lease.
*/
log_error("%s(%d): Failed to update internal lease "
"structure with DDNS control block. Existing"
@@ -1036,11 +1045,11 @@ safe_lease_update(struct lease *lease,
#if defined (DNS_UPDATES_MEMORY_CHECKS)
update_lease_failed(lease, NULL, oldcb, newcb, file, line);
#endif
- /*
- * May not reach this: update_lease_failed calls
+ /*
+ * May not reach this: update_lease_failed calls
* log_fatal.
- */
- return;
+ */
+ return;
}
/* additional IPv4 specific checks may be added here */
@@ -1059,12 +1068,12 @@ safe_lease6_update(struct iasubopt *lease6,
if (lease6 == NULL) {
/* should never get here */
- log_fatal("Impossible condition at %s:%d (called from %s:%d).",
+ log_fatal("Impossible condition at %s:%d (called from %s:%d).",
MDL, file, line);
}
if ( (lease6->ddns_cb == NULL) && (newcb == NULL) ) {
- inet_ntop(AF_INET6, &lease6->addr, addrbuf,
+ inet_ntop(AF_INET6, &lease6->addr, addrbuf,
MAX_ADDRESS_STRING_LEN);
/*
* Trying to clean up pointer that is already null. We
@@ -1079,20 +1088,20 @@ safe_lease6_update(struct iasubopt *lease6,
update_lease_failed(NULL, lease6, oldcb, newcb, file, line);
#endif
- /*
- * May not reach this: update_lease_failed calls
+ /*
+ * May not reach this: update_lease_failed calls
* log_fatal.
- */
- return;
+ */
+ return;
}
if ( (lease6->ddns_cb != NULL) && (lease6->ddns_cb != oldcb) ) {
- /*
+ /*
* there is existing cb structure, but it differs from
- * what we expected to see there. Most likely we are
- * trying to update wrong lease.
+ * what we expected to see there. Most likely we are
+ * trying to update wrong lease.
*/
- inet_ntop(AF_INET6, &lease6->addr, addrbuf,
+ inet_ntop(AF_INET6, &lease6->addr, addrbuf,
MAX_ADDRESS_STRING_LEN);
log_error("%s(%d): Failed to update internal lease "
@@ -1105,14 +1114,14 @@ safe_lease6_update(struct iasubopt *lease6,
#if defined (DNS_UPDATES_MEMORY_CHECKS)
update_lease_failed(NULL, lease6, oldcb, newcb, file, line);
#endif
- /*
- * May not reach this: update_lease_failed calls
+ /*
+ * May not reach this: update_lease_failed calls
* log_fatal.
- */
+ */
return;
}
/* additional IPv6 specific checks may be added here */
-
+
/* update the lease */
lease6->ddns_cb = newcb;
}
@@ -1132,7 +1141,7 @@ safe_lease6_update(struct iasubopt *lease6,
* using the same value twice allows me more flexibility.
*/
-isc_result_t
+isc_result_t
ddns_update_lease_ptr(struct lease *lease,
struct iasubopt *lease6,
dhcp_ddns_cb_t *ddns_cb,
@@ -1165,7 +1174,7 @@ ddns_update_lease_ptr(struct lease *lease,
return (ISC_R_SUCCESS);
}
- /*
+ /*
* If we are processing an expired or released v6 lease
* we don't actually have a lease to update
*/
@@ -1175,10 +1184,10 @@ ddns_update_lease_ptr(struct lease *lease,
}
if (lease != NULL) {
- safe_lease_update(lease, ddns_cb, ddns_cb_set,
+ safe_lease_update(lease, ddns_cb, ddns_cb_set,
file, line);
} else if (lease6 != NULL) {
- safe_lease6_update(lease6, ddns_cb, ddns_cb_set,
+ safe_lease6_update(lease6, ddns_cb, ddns_cb_set,
file, line);
} else if (ddns_cb->address.len == 4) {
struct lease *find_lease = NULL;
@@ -1186,10 +1195,10 @@ ddns_update_lease_ptr(struct lease *lease,
ddns_cb->address, MDL) != 0) {
#if defined (DEBUG_DNS_UPDATES)
log_info("%s(%d): find_lease_by_ip_addr(%s) successful:"
- "lease=%p", file, line, ddns_address,
+ "lease=%p", file, line, ddns_address,
find_lease);
#endif
-
+
safe_lease_update(find_lease, ddns_cb,
ddns_cb_set, file, line);
lease_dereference(&find_lease, MDL);
@@ -1204,11 +1213,11 @@ ddns_update_lease_ptr(struct lease *lease,
file, line);
#endif
/*
- * may not reach this. update_lease_failed
- * calls log_fatal.
+ * may not reach this. update_lease_failed
+ * calls log_fatal.
*/
- return(ISC_R_FAILURE);
-
+ return(ISC_R_FAILURE);
+
}
} else if (ddns_cb->address.len == 16) {
struct iasubopt *find_lease6 = NULL;
@@ -1217,9 +1226,9 @@ ddns_update_lease_ptr(struct lease *lease,
char addrbuf[MAX_ADDRESS_STRING_LEN];
memcpy(&addr, &ddns_cb->address.iabuf, 16);
- if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) !=
+ if ((find_ipv6_pool(&pool, D6O_IA_TA, &addr) !=
ISC_R_SUCCESS) &&
- (find_ipv6_pool(&pool, D6O_IA_NA, &addr) !=
+ (find_ipv6_pool(&pool, D6O_IA_NA, &addr) !=
ISC_R_SUCCESS)) {
inet_ntop(AF_INET6, &addr, addrbuf,
MAX_ADDRESS_STRING_LEN);
@@ -1230,8 +1239,8 @@ ddns_update_lease_ptr(struct lease *lease,
file, line);
#endif
/*
- * never reached. update_lease_failed
- * calls log_fatal.
+ * never reached. update_lease_failed
+ * calls log_fatal.
*/
return(ISC_R_FAILURE);
}
@@ -1250,20 +1259,20 @@ ddns_update_lease_ptr(struct lease *lease,
file, line);
#endif
/*
- * never reached. update_lease_failed
- * calls log_fatal.
+ * never reached. update_lease_failed
+ * calls log_fatal.
*/
return(ISC_R_FAILURE);
}
ipv6_pool_dereference(&pool, MDL);
} else {
/* shouldn't get here */
- log_fatal("Impossible condition at %s:%d, called from %s:%d.",
+ log_fatal("Impossible condition at %s:%d, called from %s:%d.",
MDL, file, line);
}
return(ISC_R_SUCCESS);
-}
+}
void
ddns_ptr_add(dhcp_ddns_cb_t *ddns_cb,
@@ -1373,8 +1382,12 @@ ddns_ptr_remove(dhcp_ddns_cb_t *ddns_cb,
* -- "Interaction between DHCP and DNS"
*
* If the second query fails with NXRRSET, the updater must conclude
- * that the client's desired name is in use by another host. At this
- * juncture, the updater can decide (based on some administrative
+ * that the client's desired name is in use by another host. If
+ * Dual Stack Mixed Mode (DSMM) is enabled and we proceed to a
+ * third stage forward update attempt specific to DSMM rules. If not,
+ * then the existing entries are left intact:
+ *
+ * At this juncture, the updater can decide (based on some administrative
* configuration outside of the scope of this document) whether to let
* the existing owner of the name keep that name, and to (possibly)
* perform some name disambiguation operation on behalf of the current
@@ -1394,6 +1407,11 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb,
const char *logstr = NULL;
char ddns_address[MAX_ADDRESS_STRING_LEN];
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS:ddns_fwd_srv_add2: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
+
/* Construct a printable form of the address for logging */
strcpy(ddns_address, piaddr(ddns_cb->address));
@@ -1414,7 +1432,7 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb,
ddns_cb->state = DDNS_STATE_ADD_PTR;
ddns_cb->cur_func = ddns_ptr_add;
-
+
result = ddns_modify_ptr(ddns_cb, MDL);
if (result == ISC_R_SUCCESS) {
return;
@@ -1427,8 +1445,21 @@ ddns_fwd_srv_add2(dhcp_ddns_cb_t *ddns_cb,
logstr = "DHCID mismatch, belongs to another client.";
break;
- case DNS_R_NXRRSET:
case DNS_R_NXDOMAIN:
+ case DNS_R_NXRRSET:
+ /* If DSMM is on we need to try forward add3 */
+ if (ddns_cb->flags & DDNS_DUAL_STACK_MIXED_MODE) {
+ ddns_cb->state = DDNS_STATE_DSMM_FW_ADD3;
+ ddns_cb->cur_func = ddns_fwd_srv_add3;
+
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+
+ break;
+ }
+
logstr = "Has an address record but no DHCID, not mine.";
break;
@@ -1467,6 +1498,11 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb,
isc_result_t result;
char ddns_address[MAX_ADDRESS_STRING_LEN];
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_fwd_srv_add1: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
+
/* Construct a printable form of the address for logging */
strcpy(ddns_address, piaddr(ddns_cb->address));
@@ -1487,7 +1523,7 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb,
ddns_cb->state = DDNS_STATE_ADD_PTR;
ddns_cb->cur_func = ddns_ptr_add;
-
+
result = ddns_modify_ptr(ddns_cb, MDL);
if (result == ISC_R_SUCCESS) {
return;
@@ -1499,13 +1535,12 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb,
/* we can reuse the zone information */
ddns_cb->state = DDNS_STATE_ADD_FW_YXDHCID;
ddns_cb->cur_func = ddns_fwd_srv_add2;
-
+
result = ddns_modify_fwd(ddns_cb, MDL);
if (result == ISC_R_SUCCESS) {
return;
}
break;
-
default:
log_error ("Unable to add forward map from %.*s to %s: %s",
(int)ddns_cb->fwd_name.len,
@@ -1531,6 +1566,89 @@ ddns_fwd_srv_add1(dhcp_ddns_cb_t *ddns_cb,
return;
}
+/*
+ * This action routine is invoked after the DSMM third add stage is
+ * attempted. If we succeeded we attempt to update the reverse DNS,
+ * if not we cleanup and leave.
+ */
+void
+ddns_fwd_srv_add3(dhcp_ddns_cb_t *ddns_cb,
+ isc_result_t eresult)
+{
+ isc_result_t result;
+ const char *logstr = NULL;
+ char ddns_address[MAX_ADDRESS_STRING_LEN];
+
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_fwd_srv_add3: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
+
+ /* Construct a printable form of the address for logging */
+ strcpy(ddns_address, piaddr(ddns_cb->address));
+
+ switch(eresult) {
+ case ISC_R_SUCCESS:
+ log_info("Added new forward map from %.*s to %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address);
+
+ ddns_update_lease_text(ddns_cb, NULL);
+
+ if ((ddns_cb->flags & DDNS_UPDATE_PTR) != 0) {
+ /* if we have zone information get rid of it */
+ if (ddns_cb->zone != NULL) {
+ ddns_cb_forget_zone(ddns_cb);
+ }
+
+ ddns_cb->state = DDNS_STATE_ADD_PTR;
+ ddns_cb->cur_func = ddns_ptr_add;
+
+ result = ddns_modify_ptr(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+ break;
+
+ case DNS_R_YXRRSET:
+ logstr = "an entry that is either static or "
+ "owned by another client exists.";
+ break;
+
+ case DNS_R_NXRRSET:
+ logstr = "static entry of the other protocol type exists.";
+ break;
+
+ default:
+ logstr = isc_result_totext(eresult);
+ break;
+ }
+
+ if (logstr != NULL) {
+ log_error("Forward map from %.*s to %s FAILED: %s",
+ (int)ddns_cb->fwd_name.len,
+ (const char *)ddns_cb->fwd_name.data,
+ ddns_address, logstr);
+ }
+
+ ddns_update_lease_ptr(NULL, NULL, ddns_cb, NULL, MDL);
+ ddns_cb_free(ddns_cb, MDL);
+ /*
+ * A single DDNS operation may require several calls depending on
+ * the current state as the prerequisites for the first message
+ * may not succeed requiring a second operation and potentially
+ * a ptr operation after that. The commit_leases operation is
+ * invoked at the end of this set of operations in order to require
+ * a single write for all of the changes. We call commit_leases
+ * here rather than immediately after the call to update the lease
+ * text in order to save any previously written data.
+ */
+ commit_leases();
+ return;
+}
+
static void
ddns_fwd_srv_connector(struct lease *lease,
struct iasubopt *lease6,
@@ -1540,6 +1658,11 @@ ddns_fwd_srv_connector(struct lease *lease,
{
isc_result_t result = ISC_R_FAILURE;
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_fwd_srv_connector: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
+
if (ddns_cb == NULL) {
/* nothing to do */
return;
@@ -1565,6 +1688,7 @@ ddns_fwd_srv_connector(struct lease *lease,
}
}
+
if (result == ISC_R_SUCCESS) {
ddns_update_lease_ptr(lease, lease6, ddns_cb, ddns_cb, MDL);
} else {
@@ -1591,6 +1715,10 @@ void
ddns_fwd_srv_rem2(dhcp_ddns_cb_t *ddns_cb,
isc_result_t eresult)
{
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_fwd_srv_rem2: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
/*
* To get here we have already managed to remove the A/AAAA
@@ -1651,12 +1779,17 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb,
isc_result_t result = eresult;
char ddns_address[MAX_ADDRESS_STRING_LEN];
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_fwd_srv_rem1: %s eresult: %d",
+ dump_ddns_cb(ddns_cb), eresult);
+#endif
+
switch(eresult) {
case ISC_R_SUCCESS:
/* Construct a printable form of the address for logging */
strcpy(ddns_address, piaddr(ddns_cb->address));
log_info("Removed forward map from %.*s to %s",
- (int)ddns_cb->fwd_name.len,
+ (int)ddns_cb->fwd_name.len,
(const char*)ddns_cb->fwd_name.data,
ddns_address);
@@ -1671,12 +1804,31 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb,
case DNS_R_NXRRSET:
case DNS_R_NXDOMAIN:
+ /* A result of not found means rem1 did not find a guard of
+ * our * type. From this we assume either there are no address
+ * record(s) of our type to delete or they are unguarded and
+ * therefore presumed to be static. Either way, we're done
+ * unless we're in DSMM and ddns-other-guard-is-dynamic is on.
+ * In which case we need to see if a guard of the other type
+ * exists, which permits us to delete any address records of
+ * our type. */
+ #define DSMM_OGD (DDNS_DUAL_STACK_MIXED_MODE | \
+ DDNS_OTHER_GUARD_IS_DYNAMIC)
+ if ((ddns_cb->flags & DSMM_OGD) == DSMM_OGD) {
+ ddns_cb->state = DDNS_STATE_REM_FW_DSMM_OTHER;
+ ddns_cb->cur_func = ddns_fwd_srv_rem2;
+ result = ddns_modify_fwd(ddns_cb, MDL);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ break;
+ }
+
ddns_update_lease_text(ddns_cb, NULL);
#if defined (DEBUG_DNS_UPDATES)
log_info("DDNS: no forward map to remove. %p", ddns_cb);
#endif
-
/* Trigger the add operation */
eresult = ISC_R_SUCCESS;
@@ -1719,19 +1871,19 @@ ddns_fwd_srv_rem1(dhcp_ddns_cb_t *ddns_cb,
* Remove relevant entries from DNS.
*
* \li lease - lease to start with if this is for v4
- *
+ *
* \li lease6 - lease to start with if this is for v6
- *
+ *
* \li add_ddns_cb - control block for additional DDNS work. This
* is used when the code is going to add a DDNS entry after removing
* the current entry.
- *
+ *
* \li active - indication about the status of the lease. It is
* ISC_TRUE if the lease is still active, and FALSE if the lease
* is inactive. This is used to indicate if the lease is inactive or going
* to inactive so we can avoid trying to update the lease with cb pointers
* and text information if it isn't useful.
- *
+ *
* Returns
* \li #ISC_R_FAILURE - badness occurred and we weren't able to do what was wanted
* \li #ISC_R_SUCCESS - we were able to do stuff but it's in progress
@@ -1751,15 +1903,20 @@ ddns_removals(struct lease *lease,
dhcp_ddns_cb_t *ddns_cb = NULL;
struct data_string leaseid;
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS: ddns_removals: %s",
+ dump_ddns_cb(add_ddns_cb));
+#endif
+
/*
* See if we need to cancel an outstanding request. Mostly this is
* used to handle the case where this routine is called twice for
* the same release or abandon event.
- *
+ *
* When called from the dns code as part of an update request
* (add_ddns_cb != NULL) any outstanding requests will have already
* been cancelled.
- *
+ *
* If the new request is just a removal and we have an outstanding
* request we have several options:
*
@@ -1773,13 +1930,13 @@ ddns_removals(struct lease *lease,
* done after the removal we need to kill the update part of the
* request.
*/
-
+
if (add_ddns_cb == NULL) {
if ((lease != NULL) && (lease->ddns_cb != NULL)) {
ddns_cb = lease->ddns_cb;
/*
- * Is the old request an update or did the
+ * Is the old request an update or did the
* the active flag change?
*/
if (((ddns_cb->state == DDNS_STATE_ADD_PTR) ||
@@ -1807,7 +1964,7 @@ ddns_removals(struct lease *lease,
ddns_cb = lease6->ddns_cb;
/*
- * Is the old request an update or did the
+ * Is the old request an update or did the
* the active flag change?
*/
if (((ddns_cb->state == DDNS_STATE_ADD_PTR) ||
@@ -1841,6 +1998,9 @@ ddns_removals(struct lease *lease,
goto cleanup;
}
+ /* Set the conflict detection flags based on global configuration */
+ copy_conflict_flags(&ddns_cb->flags, ddns_conflict_mask);
+
/*
* For v4 we flag static leases so we don't try
* and manipulate the lease later. For v6 we don't
@@ -1906,23 +2066,25 @@ ddns_removals(struct lease *lease,
}
/*
- * Find the txt or dhcid tag and copy it to the control block. If we don't
- * have one this isn't an interim or standard record so we can't delete
- * the A record using this mechanism but we can delete the ptr record.
- * In this case we will attempt to do any requested next step.
+ * Find the txt or dhcid tag and copy it to the control block. If we
+ * don't have one this isn't an interim or standard record so we can't
+ * delete the A record using this mechanism but we can delete the ptr
+ * record. In this case we will attempt to do any requested next step.
*/
memset(&leaseid, 0, sizeof(leaseid));
if (find_bound_string (&leaseid, *scope, ddns_standard_tag)) {
/* We have a standard tag */
ddns_cb->lease_tag = ddns_standard_tag;
ddns_cb->dhcid_class = dns_rdatatype_dhcid;
+ ddns_cb->other_dhcid_class = dns_rdatatype_txt;
data_string_copy(&ddns_cb->dhcid, &leaseid, MDL);
data_string_forget(&leaseid, MDL);
} else if (find_bound_string (&leaseid, *scope, ddns_interim_tag)) {
/* we have an interim tag */
ddns_cb->lease_tag = ddns_interim_tag;
ddns_cb->dhcid_class = dns_rdatatype_txt;
- if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) !=
+ ddns_cb->other_dhcid_class = dns_rdatatype_dhcid;
+ if (dhcid_fromlease(&ddns_cb->dhcid, &leaseid) !=
ISC_R_SUCCESS) {
/* We couldn't convert the dhcid from the lease
* version to the dns version. We can't delete
@@ -1933,7 +2095,7 @@ ddns_removals(struct lease *lease,
data_string_forget(&leaseid, MDL);
} else {
ddns_cb->flags &= ~DDNS_UPDATE_ADDR;
- }
+ }
/*
* Find the rev name and copy it to the control block. If we don't
@@ -1943,7 +2105,8 @@ ddns_removals(struct lease *lease,
if (!find_bound_string(&ddns_cb->rev_name, *scope, "ddns-rev-name")) {
ddns_cb->flags &= ~DDNS_UPDATE_PTR;
}
-
+
+
/*
* If we have a second control block for doing an add
* after the remove finished attach it to our control block.
@@ -1996,7 +2159,7 @@ ddns_removals(struct lease *lease,
*/
if (execute_add != ISC_R_SUCCESS) {
ddns_cb->next_op = NULL;
- ddns_fwd_srv_connector(lease, lease6, scope,
+ ddns_fwd_srv_connector(lease, lease6, scope,
add_ddns_cb, execute_add);
add_ddns_cb = NULL;
}
@@ -2025,10 +2188,174 @@ ddns_removals(struct lease *lease,
* we allocated here.
*/
ddns_fwd_srv_connector(lease, lease6, scope, add_ddns_cb, execute_add);
- if (ddns_cb != NULL)
+ if (ddns_cb != NULL)
ddns_cb_free(ddns_cb, MDL);
return (result);
}
+/* Convenience function for setting flag bits in a mask */
+void set_flag (u_int16_t *flags,
+ u_int16_t flag,
+ u_int16_t value) {
+ if (flags) {
+ if (value) {
+ *flags |= flag;
+ } else {
+ *flags &= ~flag;
+ }
+ }
+}
+
+/*
+ * Convenience function which replicates the conflict flags set in one
+ * mask to another, while preserving all other flags.
+ */
+void copy_conflict_flags(u_int16_t *target,
+ u_int16_t source) {
+ if (target) {
+ /* Preserve non conflict flags */
+ *target &= ~CONFLICT_BITS;
+
+ /* Enable conflict flags per source */
+ *target |= source & CONFLICT_BITS;
+ }
+}
+
+/*
+ * Given an option_state, create a mask of conflict detection flags based
+ * on the appropriate configuration parameters within the option state.
+ */
+u_int16_t
+get_conflict_mask(struct option_state *options) {
+
+ int ddns_update_conflict_detection = 1; /* default on */
+ int ddns_dual_stack_mixed_mode = 0; /* default off */
+ int ddns_guard_id_must_match = 1; /* default on */
+ int ddns_other_guard_is_dynamic = 0; /* default off */
+ struct option_cache *oc = NULL;
+
+ u_int16_t mask = 0;
+ oc = lookup_option(&server_universe, options, SV_DDNS_CONFLICT_DETECT);
+ if (oc) {
+ ddns_update_conflict_detection =
+ evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options,
+ NULL, &global_scope, oc, MDL);
+ }
+
+ set_flag(&mask, DDNS_CONFLICT_DETECTION,
+ ddns_update_conflict_detection);
+
+ if (!ddns_update_conflict_detection) {
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS conflict detection: off");
+#endif
+ /* Turn the rest of the conflict related flags off */
+ set_flag(&mask, DDNS_DUAL_STACK_MIXED_MODE, 0);
+ set_flag(&mask, DDNS_GUARD_ID_MUST_MATCH, 0);
+ set_flag(&mask, DDNS_OTHER_GUARD_IS_DYNAMIC, 0);
+ return (mask);
+ }
+
+ // Get the values
+ oc = lookup_option(&server_universe, options,
+ SV_DDNS_DUAL_STACK_MIXED_MODE);
+ if (oc) {
+ ddns_dual_stack_mixed_mode =
+ evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options,
+ NULL, &global_scope, oc, MDL);
+ }
+
+ oc = lookup_option(&server_universe, options,
+ SV_DDNS_GUARD_ID_MUST_MATCH);
+ if (oc) {
+ ddns_guard_id_must_match =
+ evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options,
+ NULL, &global_scope, oc, MDL);
+ }
+
+ oc = lookup_option(&server_universe, options,
+ SV_DDNS_OTHER_GUARD_IS_DYNAMIC);
+ if (oc) {
+ ddns_other_guard_is_dynamic =
+ evaluate_boolean_option_cache(NULL, NULL, NULL, NULL, options,
+ NULL, &global_scope, oc, MDL);
+ }
+
+ // Set the flags
+ set_flag(&mask, DDNS_DUAL_STACK_MIXED_MODE,
+ ddns_dual_stack_mixed_mode);
+
+ set_flag(&mask, DDNS_GUARD_ID_MUST_MATCH,
+ ddns_guard_id_must_match);
+
+ set_flag(&mask, DDNS_OTHER_GUARD_IS_DYNAMIC,
+ ddns_other_guard_is_dynamic);
+
+#if defined (DEBUG_DNS_UPDATES)
+ log_info ("DDNS conflict behavior:\n"
+ "\tddns-update-style: %s\n"
+ "\tupdate-conflict-detection: %d\n"
+ "\tddns-dual-stack-mixed-mode: %d\n"
+ "\tddns-guard-id-must-match %d\n"
+ "\tddns-other-guard-is-dynamic: %d\n",
+ ddns_styles_values[ddns_update_style].name,
+ ddns_update_conflict_detection,
+ ddns_dual_stack_mixed_mode,
+ ddns_guard_id_must_match,
+ ddns_other_guard_is_dynamic);
+#endif
+ return (mask);
+}
+
+#if defined (DEBUG_DNS_UPDATES)
+/* Type used for creating lists of function pointers and their names */
+typedef struct {
+ void *ptr;
+ char *name;
+} LabeledPtr;
+
+/* Returns the name of the function referred to by the given address */
+char*
+dump_ddns_cb_func(void *func) {
+ static LabeledPtr funcs[] = {
+ { ddns_ptr_add, "ddns_ptr_add" },
+ { ddns_fwd_srv_add2, "ddns_fwd_srv_add2" },
+ { ddns_fwd_srv_add1, "ddns_fwd_srv_add1" },
+ { ddns_ptr_remove, "ddns_ptr_remove" },
+ { ddns_fwd_srv_rem2, "ddns_fwd_srv_rem2" },
+ { ddns_fwd_srv_rem1, "ddns_fwd_srv_rem1" },
+ { ddns_fwd_srv_add3, "ddns_fwd_srv_adde" },
+ { NULL, "unknown" }
+ };
+
+ LabeledPtr* lp = funcs;
+ if (!func) {
+ return ("<null>");
+ }
+
+ while ((lp->ptr) && (lp->ptr != func)) {
+ ++lp;
+ }
+
+ return (lp->name);
+}
+
+/* Dumps basic control block info to the log */
+char*
+dump_ddns_cb (dhcp_ddns_cb_t *ddns_cb) {
+ static char output_buf[4096];
+ if (!ddns_cb) {
+ return ("<ddns_cb is null>");
+ }
+
+ sprintf (output_buf, "ddns_cb: %p flags: %x state: %s cur_func: %s",
+ ddns_cb, ddns_cb->flags,
+ ddns_state_name(ddns_cb->state),
+ dump_ddns_cb_func(ddns_cb->cur_func));
+
+ return(output_buf);
+}
+#endif /* DEBUG_DNS_UPDATES */
+
#endif /* NSUPDATE */
diff --git a/server/dhcpd.c b/server/dhcpd.c
index fbbb37c0..bf0036ca 100644
--- a/server/dhcpd.c
+++ b/server/dhcpd.c
@@ -48,7 +48,7 @@ static const char url [] =
# include <unistd.h>
# include <pwd.h>
/* get around the ISC declaration of group */
-# define group real_group
+# define group real_group
# include <grp.h>
# undef group
@@ -73,7 +73,10 @@ option server.ddns-hostname = \n\
option server.ddns-domainname = config-option domain-name; \n\
option server.ddns-rev-domainname = \"in-addr.arpa.\";";
+/* Stores configured DDNS conflict detection flags */
+u_int16_t ddns_conflict_mask;
#endif /* NSUPDATE */
+
int ddns_update_style;
int dont_use_fsync = 0; /* 0 = default, use fsync, 1 = don't use fsync */
int server_id_check = 0; /* 0 = default, don't check server id, 1 = do check */
@@ -237,7 +240,7 @@ static void setup_chroot (char *chroot_dir) {
}
#endif /* PARANOIA */
-int
+int
main(int argc, char **argv) {
int fd;
int i, status;
@@ -598,7 +601,7 @@ main(int argc, char **argv) {
const char *path = path_dhcpd_db;
path_dhcpd_db = realpath(path_dhcpd_db, NULL);
if (path_dhcpd_db == NULL)
- log_fatal("Failed to get realpath for %s: %s", path,
+ log_fatal("Failed to get realpath for %s: %s", path,
strerror(errno));
}
@@ -696,7 +699,7 @@ main(int argc, char **argv) {
#endif
}
}
-
+
if (local_family == AF_INET) {
remote_port = htons(ntohs(local_port) + 1);
} else {
@@ -789,13 +792,13 @@ main(int argc, char **argv) {
log_error ("** You must specify a lease file with -lf.");
log_error (" Dhcpd will not overwrite your default");
log_fatal (" lease file when playing back a trace. **");
- }
+ }
trace_file_replay (traceinfile);
#if defined (DEBUG_MEMORY_LEAKAGE) && \
defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
free_everything ();
- omapi_print_dmalloc_usage_by_caller ();
+ omapi_print_dmalloc_usage_by_caller ();
#endif
exit (0);
@@ -844,7 +847,7 @@ main(int argc, char **argv) {
#endif
/* test option should cause an early exit */
- if (cftest && !lftest)
+ if (cftest && !lftest)
exit(0);
/*
@@ -905,8 +908,8 @@ main(int argc, char **argv) {
* Remove addresses from our pools that we should not issue
* to clients.
*
- * We currently have no support for this in IPv4. It is not
- * as important in IPv4, as making pools with ranges that
+ * We currently have no support for this in IPv4. It is not
+ * as important in IPv4, as making pools with ranges that
* leave out interfaces and hosts is fairly straightforward
* using range notation, but not so handy with CIDR notation.
*/
@@ -985,7 +988,7 @@ main(int argc, char **argv) {
log_fatal ("setgroups: %m");
if (setgid (set_gid))
log_fatal ("setgid(%d): %m", (int) set_gid);
- }
+ }
if (set_uid) {
if (setuid (set_uid))
@@ -1268,6 +1271,12 @@ void postconf_initialization (int quiet)
log_fatal("Unable to complete ddns initialization");
}
}
+
+ /* Set the conflict detection flag mask based on globally
+ * defined DDNS configuration params. This mask should be
+ * to init ddns_cb::flags before for every DDNS transaction. */
+ ddns_conflict_mask = get_conflict_mask(options);
+
#else
/* If we don't have support for updates compiled in tell the user */
if (ddns_update_style != DDNS_UPDATE_STYLE_NONE) {
@@ -1352,7 +1361,7 @@ void postconf_initialization (int quiet)
}
oc = lookup_option(&server_universe, options, SV_PREFIX_LEN_MODE);
- if ((oc != NULL) &&
+ if ((oc != NULL) &&
evaluate_option_cache(&db, NULL, NULL, NULL, options, NULL,
&global_scope, oc, MDL)) {
if (db.len == 1) {
@@ -1518,7 +1527,7 @@ int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia)
interface_reference (&subnet -> interface, ip, MDL);
subnet -> interface_address = *ia;
} else if (subnet -> interface != ip) {
- log_error ("Multiple interfaces match the %s: %s %s",
+ log_error ("Multiple interfaces match the %s: %s %s",
"same subnet",
subnet -> interface -> name, ip -> name);
}
@@ -1532,11 +1541,11 @@ int dhcpd_interface_setup_hook (struct interface_info *ip, struct iaddr *ia)
shared_network_reference
(&ip -> shared_network, share, MDL);
}
-
+
if (!share -> interface) {
interface_reference (&share -> interface, ip, MDL);
} else if (share -> interface != ip) {
- log_error ("Multiple interfaces match the %s: %s %s",
+ log_error ("Multiple interfaces match the %s: %s %s",
"same shared network",
share -> interface -> name, ip -> name);
}
@@ -1657,13 +1666,13 @@ static isc_result_t dhcp_io_shutdown_countdown (void *vlp)
if (no_pid_file == ISC_FALSE)
(void) unlink(path_dhcpd_pid);
exit (0);
- }
+ }
#else
if (shutdown_state == shutdown_done) {
#if defined (DEBUG_MEMORY_LEAKAGE) && \
defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
free_everything ();
- omapi_print_dmalloc_usage_by_caller ();
+ omapi_print_dmalloc_usage_by_caller ();
#endif
if (no_pid_file == ISC_FALSE)
(void) unlink(path_dhcpd_pid);
@@ -1707,7 +1716,7 @@ isc_result_t dhcp_set_control_state (control_object_state_t oldstate,
/* Called on signal. */
log_info("Received signal %d, initiating shutdown.", shutdown_signal);
shutdown_signal = SIGUSR1;
-
+
/*
* Prompt the shutdown event onto the timer queue
* and return to the dispatch loop.
diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5
index 076a35fb..2c064fa6 100644
--- a/server/dhcpd.conf.5
+++ b/server/dhcpd.conf.5
@@ -1227,6 +1227,74 @@ on the PTR record, but the \fIinterim\fR update method does not do this.
In the final RFC this requirement was relaxed such that a server may
add a DHCID RR to the PTR record.
.PP
+.SH DDNS IN DUAL STACK ENVIRONMENTS
+As described in RFC 4703, section 5.2, in order to perform DDNS in dual
+stack environments, both IPv4 and IPv6 servers would need to be configured
+to use the standard update style and participating IPv4 clients MUST
+convey DUIDs as described in RFC 4361, section 6.1., in their
+dhcp-client-identifiers.
+.PP
+In a nutshell, this mechanism is intended to use globally unique DUIDs
+to idenfity both IPv4 and IPv6 clients, and where a device has both
+IPv4 and IPv6 leases it is identified by the same DUID. This allows
+a dual stack client to use the same FQDN for both mappings, while
+being protected from updates for other clients by the rules of conflict
+detection.
+.PP
+However, not all IPv4 clients implement this behavior which makes
+supporting them dual stack environments problematic. In order to
+address this issue ISC DHCP (as of 4.4.0) supports a new mode of
+DDNS conflict resolution referred to as Dual Stack Mixed Mode (DSMM).
+.PP
+The concept behind DSMM is relatively simple. All dhcp servers of one
+protocol (IPv4 or v6) use one ddns-update-style (interim or standard)
+while all servers of the "other" protocol will use the "other"
+ddns-udpate-style. In this way, all servers of a given protocol are
+using the same record type (TXT or DHCID) for their DHCID RR entries.
+This allows conflict detection to be enforced within each protocol
+without interferring with the other's entries.
+.PP
+DSMM modifications now ensure that IPv4 DSMM servers only ever modify
+A records, their associated PTR records and DHCID records, while DSMM
+IPv6 severs only modify AAAA records, their associated PTR records,
+and DHCID records.
+.PP
+Note that DSMM is not a perfect solution, it is a compromise that can
+work well provided all participating DNS updaters play by DSMM rules.
+As with anything else in life, it only works as well as those who
+particpate behave.
+.PP
+While conflict detection is enabled by default, DSMM is not. To enable
+DSMM, both update-conflict-detection and ddns-dual-stack-mixed-mode must
+be true.
+.PP
+.SH PROTECTING DNS ENTRIES FOR STATIC CLIENTS
+Built into conflict resolution is the protection of manually made entries
+for static clients. Per the rules of conflict resolution, a DNS updater
+may not alter forward DNS entries unless there is a DHCID RR which matches
+for whom the update is being made. Therefore, any forward DNS entries
+without a corresponding DHCID RR cannot be altered by such an updater.
+.PP
+In some environments, it may be desirable to use only this aspect of conflict
+resolution and allow DNS updaters to overwrite entries for dynamic clients
+regardless of what client owns them. In other words, the presence or lack
+of a DHCID RR is used to determine whether entries may or may not be
+overwritten. Whether or not the client matches the data value of the DHCID
+RR is irrelevant. This behavior, off by default, can be configured through
+the parameter, ddns-guard-id-must-match. As with DSMM, this behavior is
+can only be enabled if conflict resolution is enabled. This behavior should
+be considered carefully before electing to use it.
+.PP
+There is an additional parameter that can be used with DSMM
+ddns-other-guard-is-dynamic. When enabled along with DSMM, a server will
+regard the presence of a DHCID RR of the other style type as indicating that
+the forward DNS entries for that FQDN should be dynamic and may be overwritten.
+For example, such a server using interim style could overwrite the DNS entries
+for an FQDN if there is only a DHDID type DHDID RR for the FQDN. Essentially,
+if there are dynamic entries for one protocol, that is enough to overcome the
+static protection of entries for the other protocol. This behavior warrants
+careful consideration before electing to use it.
+.PP
.SH DYNAMIC DNS UPDATE SECURITY
.PP
When you set your DNS server up to allow updates from the DHCP server,
@@ -2070,6 +2138,34 @@ appended to the client's hostname to form a fully-qualified
domain-name (FQDN).
.RE
.PP
+The \fIddns-dual-stack-mixed-mode\fR statement
+.RS 0.25i
+.PP
+.B ddns-dual-stack-mixed-mode \fIflag\fB;\fR
+.PP
+The \fIddns-dual-stack-mixed-mode\fR parameter controls whether or not the
+server applies Dual Stack Mixed Mode rules during DDNS conflict resolution.
+This parameter is off by default, has no effect unless
+update-conflict-detection is enabled, and may only be specified at the
+global scope.
+.RE
+.PP
+The \fIddns-guard-id-must-match\fR statement
+.RS 0.25i
+.PP
+.B ddns-guard-id-must-match \fIflag\fB;\fR
+.PP
+The \fIddns-guard-id-must-match\fR parameter controls whether or not a
+the client id within a DHCID RR must match that of the DNS update's client
+to permit DNS entries associated with that DHCID RR to be ovewritten.
+Proper conflict resolution requires ID matching and should only be disabled
+after careful consideration. When disabled, it is allows any DNS updater to
+replace DNS entries that have an associated DHCID RR, regardless of client
+identity. This parameter is on by default, has no effect unless
+update-conflict-detection is enabled, and may only be specified at the global
+scope.
+.RE
+.PP
The \fddns-local-address4\fR and \fddns-local-address6\fR statements
.RS 0.25i
.PP
@@ -2082,6 +2178,20 @@ the server should use as the from address when sending DDNS update
requests.
.RE
.PP
+The \fIddns-other-guard-is-dynamic\fR statement
+.RS 0.25i
+.PP
+.B ddns-other-guard-is-dynamic \fIflag\fB;\fR
+.PP
+The \fIddns-other-guard-is-dynamic\fR parameter controls whether or not a
+a server running DSMM will consider the presence of the other update style
+DHCID RR as an indcation that a DNS entries may be overwritten. It should
+only be enabled after careful study as it allows DNS entries that would
+otherwise be protected as static, to be overwritten in certain cases. This
+paramater is off by default, has no effect unless ddns-dual-stack-mixed-mode
+is enabled, and may only be specified at the global scope.
+.RE
+.PP
The \fIddns-rev-domainname\fR statement
.RS 0.25i
.PP
@@ -3125,7 +3235,7 @@ server will use dhcp-renewal-time and dhcp-rebinding-time, respectively.
A value of zero tells the client it may choose its own value.
When those options are not defined then values will be set to zero unless the
-global \fIdhcpv6-set-tee-times\R is enabled. When this option is enabled the
+global \fIdhcpv6-set-tee-times\fR is enabled. When this option is enabled the
times are calculated as recommended by RFC 3315, Section 22.4:
T1 will be set to 0.5 times the shortest preferred lifetime
@@ -3199,7 +3309,8 @@ If the \fIupdate-conflict-detection\fR parameter is true, the server will
perform standard DHCID multiple-client, one-name conflict detection. If
the parameter has been set false, the server will skip this check and
instead simply tear down any previous bindings to install the new
-binding without question. The default is true.
+binding without question. The default is true and this parameter may only
+be specified at the global scope.
.RE
.PP
The
diff --git a/server/stables.c b/server/stables.c
index 59df5e83..107728d2 100644
--- a/server/stables.c
+++ b/server/stables.c
@@ -283,6 +283,9 @@ static struct option server_options[] = {
#if defined (FAILOVER_PROTOCOL)
{ "check-secs-byte-order", "f", &server_universe, SV_CHECK_SECS_BYTE_ORDER, 1 },
#endif
+ { "ddns-dual-stack-mixed-mode", "f", &server_universe, SV_DDNS_DUAL_STACK_MIXED_MODE, 1 },
+ { "ddns-guard-id-must-match", "f", &server_universe, SV_DDNS_GUARD_ID_MUST_MATCH, 1 },
+ { "ddns-other-guard-is-dynamic", "f", &server_universe, SV_DDNS_OTHER_GUARD_IS_DYNAMIC, 1 },
{ NULL, NULL, NULL, 0, 0 }
};