Commit 905c58b9 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[master] Adds DDNS Dual Stack Mixed Mode

    Merges in rt42620.
parent 1aca5897
......@@ -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.
......
This diff is collapsed.
......@@ -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.
*/
......
......@@ -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);
......
......@@ -105,7 +105,6 @@
/* Define this if you want to see the requests and replies between the
DHCP code and the DNS library code. */
/* #define DEBUG_DNS_UPDATES */
/* Define this if you want to debug the host part of the inform processing */
......
This diff is collapsed.
......@@ -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.
......
......@@ -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
......
......@@ -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 },