From 80c9fdb0e739898bb28cae633e3aa6f6cd0ecd4d Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Wed, 20 Feb 2008 12:45:53 +0000 Subject: [PATCH] Add IA_TA and IA_PD support in server --- client/dhc6.c | 8 +- common/conflex.c | 6 + includes/dhcpd.h | 13 +- includes/dhctoken.h | 5 +- server/confpars.c | 237 ++++- server/dhcpd.c | 1 + server/dhcpd.conf.5 | 27 +- server/dhcpv6.c | 2113 +++++++++++++++++++++++++++++++++++++++++-- server/mdb6.c | 231 ++++- server/stables.c | 3 +- 10 files changed, 2520 insertions(+), 124 deletions(-) diff --git a/client/dhc6.c b/client/dhc6.c index 37cdab61..90826223 100644 --- a/client/dhc6.c +++ b/client/dhc6.c @@ -1012,12 +1012,12 @@ dhc6_parse_prefs(struct dhc6_addr **ppref, struct packet *packet, oc, MDL) && (ds.len >= 25)) { - pref->plen = getUChar(ds.data); + pref->preferred_life = getULong(ds.data); + pref->max_life = getULong(ds.data + 4); + pref->plen = getUChar(ds.data + 8); pref->address.len = 16; - memcpy(pref->address.iabuf, ds.data + 1, 16); + memcpy(pref->address.iabuf, ds.data + 9, 16); pref->starts = cur_time; - pref->preferred_life = getULong(ds.data + 17); - pref->max_life = getULong(ds.data + 21); log_debug("RCV: | | X-- IAPREFIX %s/%d", piaddr(pref->address), (int)pref->plen); diff --git a/common/conflex.c b/common/conflex.c index 3cdc9146..d676c643 100644 --- a/common/conflex.c +++ b/common/conflex.c @@ -918,6 +918,8 @@ intern(char *atom, enum dhcp_token dfv) { return FIXED_ADDR; if (!strcasecmp (atom + 1, "ixed-address6")) return FIXED_ADDR6; + if (!strcasecmp (atom + 1, "ixed-prefix6")) + return FIXED_PREFIX6; if (!strcasecmp (atom + 1, "ddi")) return TOKEN_FDDI; if (!strcasecmp (atom + 1, "ormerr")) @@ -1145,6 +1147,8 @@ intern(char *atom, enum dhcp_token dfv) { return PACKET; if (!strcasecmp (atom + 1, "ool")) return POOL; + if (!strcasecmp (atom + 1, "refix6")) + return PREFIX6; if (!strcasecmp (atom + 1, "seudo")) return PSEUDO; if (!strcasecmp (atom + 1, "eer")) @@ -1365,6 +1369,8 @@ intern(char *atom, enum dhcp_token dfv) { return TSFP; if (!strcasecmp (atom + 1, "ransmission")) return TRANSMISSION; + if (!strcasecmp(atom + 1, "emporary")) + return TEMPORARY; break; case 'u': if (!strcasecmp (atom + 1, "case")) diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 240bcf5a..7f04e0cb 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -622,7 +622,8 @@ struct lease_state { #define SV_DHCPV6_LEASE_FILE_NAME 54 #define SV_DHCPV6_PID_FILE_NAME 55 #define SV_LIMIT_ADDRS_PER_IA 56 -#define SV_DELAYED_ACK 57 +#define SV_LIMIT_PREFS_PER_IA 57 +#define SV_DELAYED_ACK 58 #if !defined (DEFAULT_PING_TIMEOUT) # define DEFAULT_PING_TIMEOUT 1 @@ -741,6 +742,7 @@ struct host_decl { not an option_cache, but it's referenced in a lot of places, so we'll leave it for now. */ struct option_cache *fixed_addr; + struct iaddrcidrnetlist *fixed_prefix; struct group *group; struct group_object *named_group; struct data_string auth_key_id; @@ -1358,6 +1360,7 @@ extern ia_na_hash_t *ia_ta_active; struct ipv6_pool { int refcnt; /* reference count */ struct in6_addr start_addr; /* first IPv6 address */ +#define POOL_IS_FOR_TEMP 0x8000 int bits; /* number of bits, CIDR style */ iaaddr_hash_t *addrs; /* non-free IAADDR */ int num_active; /* count of active IAADDR */ @@ -1738,6 +1741,8 @@ int parse_ip6_prefix(struct parse *, struct iaddr *, u_int8_t *); void parse_address_range PROTO ((struct parse *, struct group *, int, struct pool *, struct lease **)); void parse_address_range6(struct parse *cfile, struct group *group); +void parse_prefix6(struct parse *cfile, struct group *group); +void parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl); void parse_ia_na_declaration(struct parse *); void parse_ia_ta_declaration(struct parse *); void parse_ia_pd_declaration(struct parse *); @@ -3290,6 +3295,7 @@ isc_result_t ia_pd_add_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iaprefix, const char *file, int line); void ia_pd_remove_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iaprefix, const char *file, int line); +isc_boolean_t ia_pd_equal(const struct ia_pd *a, const struct ia_pd *b); isc_result_t ipv6_pool_allocate(struct ipv6_pool **pool, const struct in6_addr *start_addr, int bits, @@ -3342,13 +3348,15 @@ isc_boolean_t prefix6_exists(const struct ipv6_ppool *ppool, const struct in6_addr *pref, u_int8_t plen); isc_result_t add_ipv6_pool(struct ipv6_pool *pool); -isc_result_t find_ipv6_pool(struct ipv6_pool **pool, +isc_result_t find_ipv6_pool(struct ipv6_pool **pool, int temp, const struct in6_addr *addr); isc_boolean_t ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool); isc_result_t add_ipv6_ppool(struct ipv6_ppool *ppool); isc_result_t find_ipv6_ppool(struct ipv6_ppool **pool, const struct in6_addr *pref); +isc_boolean_t ipv6_prefix_in_ppool(const struct in6_addr *pref, + const struct ipv6_ppool *ppool); isc_result_t renew_leases(struct ia_na *ia_na); isc_result_t release_leases(struct ia_na *ia_na); @@ -3362,5 +3370,6 @@ void schedule_prefix_timeout(struct ipv6_ppool *ppool); void schedule_all_ipv6_prefix_timeouts(); void mark_hosts_unavailable(void); +void mark_phosts_unavailable(void); void mark_interfaces_unavailable(void); diff --git a/includes/dhctoken.h b/includes/dhctoken.h index a23567c1..0f013885 100644 --- a/includes/dhctoken.h +++ b/includes/dhctoken.h @@ -349,7 +349,10 @@ enum dhcp_token { WHITESPACE = 652, TOKEN_ALSO = 653, AFTER = 654, - ZEROLEN = 655 + ZEROLEN = 655, + TEMPORARY = 656, + PREFIX6 = 657, + FIXED_PREFIX6 = 658 }; #define is_identifier(x) ((x) >= FIRST_TOKEN && \ diff --git a/server/confpars.c b/server/confpars.c index eca7b80b..a367d61c 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -617,6 +617,30 @@ int parse_statement (cfile, group, type, host_decl, declaration) } parse_address_range6(cfile, group); return declaration; + + case PREFIX6: + next_token(NULL, NULL, cfile); + if (type != ROOT_GROUP) { + parse_warn (cfile, + "prefix6 definitions may not be scoped."); + skip_to_semi(cfile); + return declaration; + } + parse_prefix6(cfile, group); + return declaration; + + case FIXED_PREFIX6: + next_token(&val, NULL, cfile); + if (!host_decl) { + parse_warn (cfile, + "fixed-prefix6 declaration not " + "allowed here."); + skip_to_semi(cfile); + break; + } + parse_fixed_prefix6(cfile, host_decl); + break; + #endif /* DHCPv6 */ case TOKEN_NOT: @@ -3672,7 +3696,8 @@ add_ipv6_pool_to_shared_network(struct shared_network *share, } /* address-range6-declaration :== ip-address6 ip-address6 SEMI - | ip-address6 SLASH number SEMI */ + | ip-address6 SLASH number SEMI + | ip-address6 TEMPORARY SEMI */ void parse_address_range6(struct parse *cfile, struct group *group) { @@ -3707,7 +3732,7 @@ parse_address_range6(struct parse *cfile, struct group *group) { } /* - * See if we we're using range or CIDR notation. + * See if we we're using range or CIDR notation or TEMPORARY */ token = peek_token(&val, NULL, cfile); if (token == SLASH) { @@ -3735,6 +3760,20 @@ parse_address_range6(struct parse *cfile, struct group *group) { add_ipv6_pool_to_shared_network(share, &lo, bits); + } else if (token == TEMPORARY) { + /* + * temporary (RFC 4941) + */ + next_token(NULL, NULL, cfile); + bits = 64; + if (!is_cidr_mask_valid(&lo, bits)) { + parse_warn(cfile, "network mask too short"); + skip_to_semi(cfile); + return; + } + bits |= POOL_IS_FOR_TEMP; + + add_ipv6_pool_to_shared_network(share, &lo, bits); } else { /* * No '/', so we are looking for the end address of @@ -3769,6 +3808,196 @@ parse_address_range6(struct parse *cfile, struct group *group) { return; } } + +static void +add_ipv6_ppool_to_global(struct iaddr *start_addr, + int pool_bits, + int alloc_bits) { + struct ipv6_ppool *ppool; + struct in6_addr tmp_in6_addr; + + /* + * Create our prefix pool. + */ + if (start_addr->len != sizeof(tmp_in6_addr)) { + log_fatal("Internal error: Attempt to add non-IPv6 prefix."); + } + memcpy(&tmp_in6_addr, start_addr->iabuf, sizeof(tmp_in6_addr)); + ppool = NULL; + if (ipv6_ppool_allocate(&ppool, &tmp_in6_addr, + (u_int8_t) pool_bits, (u_int8_t) alloc_bits, + MDL) != ISC_R_SUCCESS) { + log_fatal("Out of memory"); + } + + /* + * Add to our IPv6 prefix pool set. + */ + if (add_ipv6_ppool(ppool) != ISC_R_SUCCESS) { + log_fatal ("Out of memory"); + } +} + +/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */ + +void +parse_prefix6(struct parse *cfile, struct group *group) { + struct iaddr lo, hi; + int bits; + enum dhcp_token token; + const char *val; + struct iaddrcidrnetlist *nets; + struct iaddrcidrnetlist *p; + + /* + * Read starting and ending address. + */ + if (!parse_ip6_addr(cfile, &lo)) { + return; + } + if (!parse_ip6_addr(cfile, &hi)) { + return; + } + + /* + * Next is '/' number ';'. + */ + token = next_token(NULL, NULL, cfile); + if (token != SLASH) { + parse_warn(cfile, "expecting '/'"); + if (token != SEMI) + skip_to_semi(cfile); + return; + } + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "expecting number"); + if (token != SEMI) + skip_to_semi(cfile); + return; + } + bits = atoi(val); + if ((bits <= 0) || (bits >= 128)) { + parse_warn(cfile, "networks have 0 to 128 bits (exclusive)"); + return; + } + if (!is_cidr_mask_valid(&lo, bits) || + !is_cidr_mask_valid(&hi, bits)) { + parse_warn(cfile, "network mask too short"); + return; + } + token = next_token(NULL, NULL, cfile); + if (token != SEMI) { + parse_warn(cfile, "semicolon expected."); + skip_to_semi(cfile); + return; + } + + + /* + * Convert our range to a set of CIDR networks. + */ + nets = NULL; + if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) { + log_fatal("Error converting prefix to CIDR"); + } + + for (p = nets; p != NULL; p = p->next) { + /* Normalize and check. */ + if (p->cidrnet.bits == 128) { + p->cidrnet.bits = bits; + } + if (p->cidrnet.bits > bits) { + parse_warn(cfile, "impossible mask length"); + continue; + } + add_ipv6_ppool_to_global(&p->cidrnet.lo_addr, + p->cidrnet.bits, bits); + } + + free_iaddrcidrnetlist(&nets); +} + +/* fixed-prefix6 :== ip6-address SLASH number SEMI */ + +void +parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl) { + struct iaddrcidrnetlist *ia, **h; + enum dhcp_token token; + const char *val; + + /* + * Get the head of the fixed-prefix list. + */ + h = &host_decl->fixed_prefix; + + /* + * Walk to the end. + */ + while (*h != NULL) { + h = &((*h)->next); + } + + /* + * Allocate a new iaddrcidrnetlist structure. + */ + ia = dmalloc(sizeof(*ia), MDL); + if (!ia) { + log_fatal("Out of memory"); + } + + /* + * Parse it. + */ + if (!parse_ip6_addr(cfile, &ia->cidrnet.lo_addr)) { + dfree(ia, MDL); + return; + } + token = next_token(NULL, NULL, cfile); + if (token != SLASH) { + dfree(ia, MDL); + parse_warn(cfile, "expecting '/'"); + if (token != SEMI) + skip_to_semi(cfile); + return; + } + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + dfree(ia, MDL); + parse_warn(cfile, "expecting number"); + if (token != SEMI) + skip_to_semi(cfile); + return; + } + token = next_token(NULL, NULL, cfile); + if (token != SEMI) { + dfree(ia, MDL); + parse_warn(cfile, "semicolon expected."); + skip_to_semi(cfile); + return; + } + + /* + * Fill it. + */ + ia->cidrnet.bits = atoi(val); + if ((ia->cidrnet.bits < 0) || (ia->cidrnet.bits > 128)) { + dfree(ia, MDL); + parse_warn(cfile, "networks have 0 to 128 bits"); + return; + } + if (!is_cidr_mask_valid(&ia->cidrnet.lo_addr, ia->cidrnet.bits)) { + dfree(ia, MDL); + parse_warn(cfile, "network mask too short"); + return; + } + + /* + * Store it. + */ + *h = ia; + return; +} #endif /* DHCPv6 */ /* allow-deny-keyword :== BOOTP @@ -4099,7 +4328,7 @@ parse_ia_na_declaration(struct parse *cfile) { ia_na_add_iaaddr(ia, iaaddr, MDL); ia_na_reference(&iaaddr->ia_na, ia, MDL); pool = NULL; - if (find_ipv6_pool(&pool, &iaaddr->addr) != ISC_R_SUCCESS) { + if (find_ipv6_pool(&pool, 0, &iaaddr->addr) != ISC_R_SUCCESS) { inet_ntop(AF_INET6, &iaaddr->addr, addr_buf, sizeof(addr_buf)); parse_warn(cfile, "no pool found for address %s", @@ -4393,7 +4622,7 @@ parse_ia_ta_declaration(struct parse *cfile) { ia_na_add_iaaddr(ia, iaaddr, MDL); ia_na_reference(&iaaddr->ia_na, ia, MDL); pool = NULL; - if (find_ipv6_pool(&pool, &iaaddr->addr) != ISC_R_SUCCESS) { + if (find_ipv6_pool(&pool, 1, &iaaddr->addr) != ISC_R_SUCCESS) { inet_ntop(AF_INET6, &iaaddr->addr, addr_buf, sizeof(addr_buf)); parse_warn(cfile, "no pool found for address %s", diff --git a/server/dhcpd.c b/server/dhcpd.c index 2ac796e6..0b488d38 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -611,6 +611,7 @@ main(int argc, char **argv) { */ if (local_family == AF_INET6) { mark_hosts_unavailable(); + mark_phosts_unavailable(); mark_interfaces_unavailable(); } #endif /* DHCPv6 */ diff --git a/server/dhcpd.conf.5 b/server/dhcpd.conf.5 index 6fbcf40d..221ce295 100644 --- a/server/dhcpd.conf.5 +++ b/server/dhcpd.conf.5 @@ -28,7 +28,7 @@ .\" see ``http://www.vix.com''. To learn more about Nominum, Inc., see .\" ``http://www.nominum.com''. .\" -.\" $Id: dhcpd.conf.5,v 1.93 2008/01/24 02:43:05 each Exp $ +.\" $Id: dhcpd.conf.5,v 1.94 2008/02/20 12:45:53 fdupont Exp $ .\" .TH dhcpd.conf 5 .SH NAME @@ -1515,6 +1515,7 @@ single address, \fIhigh-address\fR can be omitted. .nf .B range6\fR \fIlow-address\fR \fIhigh-address\fR\fB;\fR .B range6\fR \fIsubnet6-number\fR\fB;\fR +.B range6\fR \fIaddress\fR \fBtemporary\fR\fB;\fR .fi .PP For any IPv6 subnet6 on which addresses will be assigned dynamically, there @@ -1524,9 +1525,33 @@ use CIDR notation, specified as ip6-address/bits. All IP addresses in the \fIrange6\fR should be in the subnet6 in which the \fIrange6\fR statement is declared. .PP +The \fItemporay\fR variant makes the 64 bit prefix \fIaddress\fR available +for temporary (RFC 4941) addresses. A new address per prefix in the shared +network is computed at each request with an IA_TA option. Release and Confirm +ignores temporary addresses, Renew and Rebind are not defined and raise an +error. +.PP Any IPv6 addresses given to hosts with \fIfixed-address6\fR are excluded from the \fIrange6\fR, as are IPv6 addresses on the server itself. .PP +.PP +.B The +.I prefix6 +.B statement +.PP +.nf +.B prefix6\fR \fIlow-address\fR \fIhigh-address\fR \fB/\fR \fIbits\fR\fB;\fR +.fi +.PP +The \fIprefix6\fR is the \fIrange6\fR equivalent for Prefix Delegation +(RFC 3633). Prefixes of \fIbits\fR length are assigned between +\fIlow-address\fR and \fIhigh-address\fR. +.PP +Any IPv6 prefixes given to static entries (hosts) with \fIfixed-prefix6\fR +are excluded from the \fIprefix6\fR. +.PP +This statement is currently global but it should have a shared-network scope. +.PP .B The .I host .B statement diff --git a/server/dhcpv6.c b/server/dhcpv6.c index 4480e6cf..6965d8d9 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -49,16 +49,26 @@ struct reply_state { /* IA level persistent state */ unsigned ia_count; + unsigned ia_pd_count; unsigned client_resources; - isc_boolean_t ia_addrs_included; + isc_boolean_t ia_resources_included; isc_boolean_t static_lease; + unsigned static_prefixes; struct ia_na *ia_na; - struct ia_na *old_ia_na; + struct ia_na *ia_ta; + union { + struct ia_na *old__ia; + struct ia_pd *old__pd; + } old; +#define old_ia old.old__ia +#define old_ia_pd old.old__pd + struct ia_pd *ia_pd; struct option_state *reply_ia; struct data_string fixed; - /* IAADDR level persistent state */ + /* IAADDR/IAPREFIX level persistent state */ struct iaaddr *lease; + struct iaprefix *prefix; /* * "t1", "t2", preferred, and valid lifetimes records for calculating @@ -72,6 +82,9 @@ struct reply_state { /* Chosen values to transmit for valid and preferred lifetimes. */ u_int32_t send_valid, send_prefer; + /* Preferred prefix length (-1 is any). */ + int preflen; + /* Index into the data field that has been consumed. */ unsigned cursor; @@ -90,27 +103,49 @@ static int get_encapsulated_IA_state(struct option_state **enc_opt_state, struct option_cache *oc, int offset); static void build_dhcpv6_reply(struct data_string *, struct packet *); -static isc_result_t shared_network_from_packet6(struct shared_network **shared, - struct packet *packet); +static void shared_network_from_packet6(struct shared_network **shared, + struct packet *packet); static void seek_shared_host(struct host_decl **hp, struct shared_network *shared); static isc_boolean_t fixed_matches_shared(struct host_decl *host, struct shared_network *shared); static isc_result_t reply_process_ia_na(struct reply_state *reply, struct option_cache *ia); +static isc_result_t reply_process_ia_ta(struct reply_state *reply, + struct option_cache *ia); static isc_result_t reply_process_addr(struct reply_state *reply, struct option_cache *addr); static isc_boolean_t address_is_owned(struct reply_state *reply, struct iaddr *addr); +static isc_result_t find_client_temporaries(struct reply_state *reply); static isc_result_t reply_process_try_addr(struct reply_state *reply, struct iaddr *addr); static isc_result_t find_client_address(struct reply_state *reply); static isc_result_t reply_process_is_addressed(struct reply_state *reply, + struct ia_na *ia, struct binding_scope **scope, struct group *group); static isc_result_t reply_process_send_addr(struct reply_state *reply, struct iaddr *addr); static struct iaaddr *lease_compare(struct iaaddr *alpha, struct iaaddr *beta); +static isc_result_t reply_process_ia_pd(struct reply_state *reply, + struct option_cache *ia_pd); +static isc_result_t reply_process_prefix(struct reply_state *reply, + struct option_cache *pref); +static isc_boolean_t prefix_is_owned(struct reply_state *reply, + struct iaddrcidrnet *pref); +static isc_result_t find_client_prefix(struct reply_state *reply); +static isc_result_t reply_process_try_prefix(struct reply_state *reply, + struct iaddrcidrnet *pref); +static isc_result_t reply_process_is_prefixed(struct reply_state *reply, + struct ia_pd *ia_pd, + struct binding_scope **scope, + struct group *group); +static isc_result_t reply_process_send_prefix(struct reply_state *reply, + struct iaddrcidrnet *pref); +static struct iaprefix *prefix_compare(struct reply_state *reply, + struct iaprefix *alpha, + struct iaprefix *beta); /* * This function returns the time since DUID time start for the @@ -664,14 +699,7 @@ static const int required_opts_solicit[] = { D6O_PREFERENCE, 0 }; -static const int required_opts_IA_NA[] = { - D6O_IAADDR, - D6O_STATUS_CODE, - D6O_VENDOR_OPTS, - 0 -}; -/* -static const int required_opts_IA_TA[] = { +static const int required_opts_IA[] = { D6O_IAADDR, D6O_STATUS_CODE, D6O_VENDOR_OPTS, @@ -683,7 +711,6 @@ static const int required_opts_IA_PD[] = { D6O_VENDOR_OPTS, 0 }; -*/ static const int required_opts_STATUS_CODE[] = { D6O_STATUS_CODE, 0 @@ -973,13 +1000,25 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network, char tmp_buf[INET6_ADDRSTRLEN]; /* - * No pools, we're done. + * No pools or all temporary, we're done. */ if (shared_network->ipv6_pools == NULL) { log_debug("Unable to pick client address: " "no IPv6 pools on this shared network"); return ISC_R_NORESOURCES; } + for (i = 0;; i++) { + p = shared_network->ipv6_pools[i]; + if (p == NULL) { + log_debug("Unable to pick client address: " + "only temporary IPv6 pools " + "on this shared network"); + return ISC_R_NORESOURCES; + } + if ((p->bits & POOL_IS_FOR_TEMP) == 0) { + break; + } + } /* * Otherwise try to get a lease from the first subnet possible. @@ -993,8 +1032,9 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network, do { p = shared_network->ipv6_pools[i]; - if (activate_lease6(p, addr, &attempts, - client_id, 0) == ISC_R_SUCCESS) { + if (((p->bits & POOL_IS_FOR_TEMP) == 0) && + (activate_lease6(p, addr, &attempts, + client_id, 0) == ISC_R_SUCCESS)) { /* * Record the pool used (or next one if there * was a collision). @@ -1027,6 +1067,125 @@ pick_v6_address(struct iaaddr **addr, struct shared_network *shared_network, return ISC_R_NORESOURCES; } +/* + * Try to get the IPv6 prefix the client asked for from the + * prefix pool. + * + * pref is the result (should be a pointer to NULL on entry) + * ppool is the prefix pool to search in + * requested_pref is the address the client wants + */ +static isc_result_t +try_client_v6_prefix(struct iaprefix **pref, + struct ipv6_ppool *ppool, + const struct data_string *requested_pref) +{ + u_int8_t tmp_plen; + struct in6_addr tmp_pref; + struct iaddr ia; + isc_result_t result; + + if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) { + return ISC_R_INVALIDARG; + } + tmp_plen = (int) requested_pref->data[0]; + if ((tmp_plen < 3) || (tmp_plen > 128)) { + return ISC_R_FAILURE; + } + memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref)); + if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) { + return ISC_R_FAILURE; + } + ia.len = 16; + memcpy(&ia.iabuf, &tmp_pref, 16); + if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) { + return ISC_R_FAILURE; + } + + if ((tmp_plen != ppool->alloc_plen) || + !ipv6_prefix_in_ppool(&tmp_pref, ppool)) { + return ISC_R_FAILURE; + } + + if (prefix6_exists(ppool, &tmp_pref, tmp_plen)) { + return ISC_R_ADDRINUSE; + } + + result = iaprefix_allocate(pref, MDL); + if (result != ISC_R_SUCCESS) { + return result; + } + (*pref)->pref = tmp_pref; + (*pref)->plen = tmp_plen; + + result = add_prefix6(ppool, *pref, 0); + if (result != ISC_R_SUCCESS) { + iaprefix_dereference(pref, MDL); + } + return result; +} + +/* + * Get an IPv6 prefix for the client. + * + * pref is the result (should be a pointer to NULL on entry) + * packet is the information about the packet from the client + * requested_iaprefix is a hint from the client + * plen is -1 or the requested prefix length + * client_id is the DUID for the client + */ +static isc_result_t +pick_v6_prefix(struct iaprefix **pref, int plen, + const struct data_string *client_id) +{ + struct ipv6_ppool *p; + int i; + unsigned int attempts; + char tmp_buf[INET6_ADDRSTRLEN]; + + /* + * No prefix pools, we're done. + */ + if (!num_ppools) { + log_debug("Unable to pick client prefix: " + "no IPv6 prefix pools"); + return ISC_R_NORESOURCES; + } + + /* + * Otherwise try to get a prefix. + */ + for (i = 0; i < num_ppools; i++) { + p = ppools[i]; + if (p == NULL) { + continue; + } + + /* + * Try only pools with the requested prefix length if any. + */ + if ((plen >= 0) && ((int) p->alloc_plen != plen)) { + continue; + } + + if (activate_prefix6(p, pref, &attempts, + client_id, 0) == ISC_R_SUCCESS) { + log_debug("Picking pool prefix %s/%u", + inet_ntop(AF_INET6, &((*pref)->pref), + tmp_buf, sizeof(tmp_buf)), + (unsigned) (*pref)->plen); + return ISC_R_SUCCESS; + } + } + + /* + * If we failed to pick an IPv6 prefix + * Presumably that means we have no prefixes for the client. + */ + log_debug("Unable to pick client prefix: no prefixes available"); + return ISC_R_NORESOURCES; +} + /* * lease_to_client() is called from several messages to construct a * reply that contains all that we know about the client's correct lease @@ -1060,12 +1219,10 @@ lease_to_client(struct data_string *reply_ret, static struct reply_state reply; struct option_cache *oc; struct data_string packet_oro; - isc_boolean_t no_addrs_avail; + isc_boolean_t no_resources_avail; /* Locate the client. */ - if (shared_network_from_packet6(&reply.shared, - packet) != ISC_R_SUCCESS) - goto exit; + shared_network_from_packet6(&reply.shared, packet); /* * Initialize the reply. @@ -1100,25 +1257,33 @@ lease_to_client(struct data_string *reply_ret, * valid for the shared network the client is on. */ if (find_hosts_by_option(&reply.host, packet, packet->options, MDL)) { - seek_shared_host(&reply.host, reply.shared); + if (reply.shared != NULL) { + seek_shared_host(&reply.host, reply.shared); + } } if ((reply.host == NULL) && find_hosts_by_uid(&reply.host, client_id->data, client_id->len, MDL)) { - seek_shared_host(&reply.host, reply.shared); + if (reply.shared != NULL) { + seek_shared_host(&reply.host, reply.shared); + } } - /* Process the client supplied IA_NA's onto the reply buffer. */ + /* Process the client supplied IA's onto the reply buffer. */ reply.ia_count = 0; oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA); - no_addrs_avail = ISC_FALSE; + no_resources_avail = ISC_FALSE; for (; oc != NULL ; oc = oc->next) { isc_result_t status; + /* A shared network is required. */ + if (reply.shared == NULL) + goto exit; + /* Start counting resources (addresses) offered. */ reply.client_resources = 0; - reply.ia_addrs_included = ISC_FALSE; + reply.ia_resources_included = ISC_FALSE; status = reply_process_ia_na(&reply, oc); @@ -1131,21 +1296,76 @@ lease_to_client(struct data_string *reply_ret, goto exit; /* - * If any address can be given to any IA, then do not set the + * If any address cannot be given to any IA, then set the * NoAddrsAvail status code. */ if (reply.client_resources == 0) - no_addrs_avail = ISC_TRUE; + no_resources_avail = ISC_TRUE; + } + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA); + for (; oc != NULL ; oc = oc->next) { + isc_result_t status; + + /* A shared network is required. */ + if (reply.shared == NULL) + goto exit; + + /* Start counting resources (addresses) offered. */ + reply.client_resources = 0; + reply.ia_resources_included = ISC_FALSE; + + status = reply_process_ia_ta(&reply, oc); + + /* + * We continue to try other IA's whether we can address + * this one or not. Any other result is an immediate fail. + */ + if ((status != ISC_R_SUCCESS) && + (status != ISC_R_NORESOURCES)) + goto exit; + + /* + * If any address cannot be given to any IA, then set the + * NoAddrsAvail status code. + */ + if (reply.client_resources == 0) + no_resources_avail = ISC_TRUE; } - /* Do IA_TA and IA_PD */ + /* Same for IA_PD's. */ + reply.ia_pd_count = 0; + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); + for (; oc != NULL ; oc = oc->next) { + isc_result_t status; + + /* Start counting resources (prefixes) offered. */ + reply.client_resources = 0; + reply.ia_resources_included = ISC_FALSE; + + status = reply_process_ia_pd(&reply, oc); + + /* + * We continue to try other IA_PD's whether we can address + * this one or not. Any other result is an immediate fail. + */ + if ((status != ISC_R_SUCCESS) && + (status != ISC_R_NORESOURCES)) + goto exit; + + /* + * If any prefix cannot be given to any IA_PD, then + * set the NoPrefixAvail status code. + */ + if (reply.client_resources == 0) + no_resources_avail = ISC_TRUE; + } /* * Make no reply if we gave no resources and is not * for Information-Request. */ - if ((reply.ia_count == 0) && - (packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST)) + if ((reply.ia_count == 0) && (reply.ia_pd_count == 0) && + (reply.packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST)) goto exit; /* @@ -1172,7 +1392,7 @@ lease_to_client(struct data_string *reply_ret, * the server. * Sends a Renew/Rebind if the IA is not in the Reply message. */ - if (no_addrs_avail && + if (no_resources_avail && (reply.ia_count != 0) && (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT)) { /* Set the NoAddrsAvail status code. */ @@ -1187,6 +1407,36 @@ lease_to_client(struct data_string *reply_ret, /* Rewind the cursor to the start. */ reply.cursor = REPLY_OPTIONS_INDEX; + /* + * Produce an advertise that includes only: + * + * Status code. + * Server DUID. + * Client DUID. + */ + reply.buf.reply.msg_type = DHCPV6_ADVERTISE; + reply.cursor += store_options6((char *)reply.buf.data + + reply.cursor, + sizeof(reply.buf) - + reply.cursor, + reply.opt_state, reply.packet, + required_opts_NAA, + NULL); + } else if (no_resources_avail && (reply.ia_count == 0) && + (reply.packet->dhcpv6_msg_type == DHCPV6_SOLICIT)) + { + /* Set the NoPrefixAvail status code. */ + if (!set_status_code(STATUS_NoPrefixAvail, + "No prefixes available for this " + "interface.", reply.opt_state)) { + log_error("lease_to_client: Unable to set " + "NoPrefixAvail status code."); + goto exit; + } + + /* Rewind the cursor to the start. */ + reply.cursor = REPLY_OPTIONS_INDEX; + /* * Produce an advertise that includes only: * @@ -1204,7 +1454,7 @@ lease_to_client(struct data_string *reply_ret, NULL); } else { /* - * Having stored the client's IA_NA's, store any options that + * Having stored the client's IA's, store any options that * will fit in the remaining space. */ reply.cursor += store_options6((char *)reply.buf.data + @@ -1293,7 +1543,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { reply->ia_na->ia_type = D6O_IA_NA; /* Cache pre-existing IA, if any. */ - ia_na_hash_lookup(&reply->old_ia_na, ia_na_active, + ia_na_hash_lookup(&reply->old_ia, ia_na_active, (unsigned char *)reply->ia_na->iaid_duid.data, reply->ia_na->iaid_duid.len, MDL); @@ -1396,9 +1646,10 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { switch (reply->packet->dhcpv6_msg_type) { case DHCPV6_SOLICIT: /* - * No address for all the IA's is handled + * No address for any IA is handled * by the caller. */ + /* FALL THROUGH */ case DHCPV6_REQUEST: /* Section 18.2.1 (Request): @@ -1452,7 +1703,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { * provide zero addresses including zeroed * lifetimes. */ - if (reply->ia_addrs_included) + if (reply->ia_resources_included) status = ISC_R_SUCCESS; else goto cleanup; @@ -1467,7 +1718,7 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { reply->cursor += store_options6((char *)reply->buf.data + reply->cursor, sizeof(reply->buf) - reply->cursor, reply->reply_ia, reply->packet, - required_opts_IA_NA, NULL); + required_opts_IA, NULL); /* Reset the length of this IA to match what was just written. */ putUShort(reply->buf.data + ia_cursor + 2, @@ -1561,12 +1812,12 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { } /* Remove any old ia_na from the hash. */ - if (reply->old_ia_na != NULL) { - ia_id = &reply->old_ia_na->iaid_duid; + if (reply->old_ia != NULL) { + ia_id = &reply->old_ia->iaid_duid; ia_na_hash_delete(ia_na_active, (unsigned char *)ia_id->data, ia_id->len, MDL); - ia_na_dereference(&reply->old_ia_na, MDL); + ia_na_dereference(&reply->old_ia, MDL); } /* Put new ia_na into the hash. */ @@ -1588,8 +1839,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { * suggesting the existing database entry to the client. */ } else if ((status != ISC_R_CANCELED) && !reply->static_lease && - (reply->old_ia_na != NULL)) { - if (ia_na_equal(reply->old_ia_na, reply->ia_na)) { + (reply->old_ia != NULL)) { + if (ia_na_equal(reply->old_ia, reply->ia_na)) { lease_in_database = ISC_TRUE; } } @@ -1605,8 +1856,8 @@ reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { data_string_forget(&data, MDL); if (reply->ia_na != NULL) ia_na_dereference(&reply->ia_na, MDL); - if (reply->old_ia_na != NULL) - ia_na_dereference(&reply->old_ia_na, MDL); + if (reply->old_ia != NULL) + ia_na_dereference(&reply->old_ia, MDL); if (reply->lease != NULL) { if (!lease_in_database) { release_lease6(reply->lease->ipv6_pool, reply->lease); @@ -1890,7 +2141,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) { goto cleanup; } - status = reply_process_is_addressed(reply, scope, group); + status = reply_process_is_addressed(reply, reply->ia_na, scope, group); if (status != ISC_R_SUCCESS) goto cleanup; @@ -1931,13 +2182,13 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) { return ISC_FALSE; } - if ((reply->old_ia_na == NULL) || (reply->old_ia_na->num_iaaddr == 0)) + if ((reply->old_ia == NULL) || (reply->old_ia->num_iaaddr == 0)) return ISC_FALSE; - for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) { + for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) { struct iaaddr *tmp; - tmp = reply->old_ia_na->iaaddr[i]; + tmp = reply->old_ia->iaaddr[i]; if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) { iaaddr_reference(&reply->lease, tmp, MDL); @@ -1948,6 +2199,350 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) { return ISC_FALSE; } +/* Process a client-supplied IA_TA. This may append options to the tail of + * the reply packet being built in the reply_state structure. + */ +static isc_result_t +reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) { + isc_result_t status = ISC_R_SUCCESS; + u_int32_t iaid; + unsigned ia_cursor; + struct option_state *packet_ia; + struct option_cache *oc; + struct data_string ia_data, data; + struct data_string iaaddr; + u_int32_t pref_life, valid_life; + + /* Initialize values that will get cleaned up on return. */ + packet_ia = NULL; + memset(&ia_data, 0, sizeof(ia_data)); + memset(&data, 0, sizeof(data)); + memset(&iaaddr, 0, sizeof(iaaddr)); + + /* Make sure there is at least room for the header. */ + if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) { + log_error("reply_process_ia_ta: Reply too long for IA."); + return ISC_R_NOSPACE; + } + + + /* Fetch the IA_TA contents. */ + if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet, + ia, IA_TA_OFFSET)) { + log_error("reply_process_ia_ta: error evaluating ia_ta"); + status = ISC_R_FAILURE; + goto cleanup; + } + + /* Extract IA_TA header contents. */ + iaid = getULong(ia_data.data); + + /* Create an IA_TA structure. */ + if (ia_na_allocate(&reply->ia_ta, iaid, (char *)reply->client_id.data, + reply->client_id.len, MDL) != ISC_R_SUCCESS) { + log_error("lease_to_client: no memory for ia_ta."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + reply->ia_ta->ia_type = D6O_IA_TA; + + /* Cache pre-existing IA, if any. */ + ia_na_hash_lookup(&reply->old_ia, ia_ta_active, + (unsigned char *)reply->ia_ta->iaid_duid.data, + reply->ia_ta->iaid_duid.len, MDL); + + /* + * Create an option cache to carry the IA_TA option contents, and + * execute any user-supplied values into it. + */ + if (!option_state_allocate(&reply->reply_ia, MDL)) { + status = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * Temporary leases are dynamic by definition. + */ + reply->static_lease = ISC_FALSE; + + /* + * Save the cursor position at the start of the IA, so we can + * set length later. We write a temporary + * header out now just in case we decide to adjust the packet + * within sub-process functions. + */ + ia_cursor = reply->cursor; + + /* Initialize the IA_TA header. First the code. */ + putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA); + reply->cursor += 2; + + /* Then option length. */ + putUShort(reply->buf.data + reply->cursor, 0x04u); + reply->cursor += 2; + + /* Then IA_TA header contents; IAID. */ + putULong(reply->buf.data + reply->cursor, iaid); + reply->cursor += 4; + + /* + * Deal with an IAADDR for lifetimes. + */ + reply->valid = reply->prefer = 0xffffffff; + reply->client_valid = reply->client_prefer = 0; + oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR); + if (oc != NULL) { + if (!evaluate_option_cache(&iaaddr, reply->packet, + NULL, NULL, + reply->packet->options, NULL, + &global_scope, oc, MDL) || + (iaaddr.len < IAADDR_OFFSET)) { + log_error("reply_process_ia_ta: error " + "evaluating IAADDR."); + status = ISC_R_FAILURE; + goto cleanup; + } + /* The first 16 bytes are the IPv6 address. */ + pref_life = getULong(iaaddr.data + 16); + valid_life = getULong(iaaddr.data + 20); + + if ((reply->client_valid == 0) || + (reply->client_valid > valid_life)) + reply->client_valid = valid_life; + + if ((reply->client_prefer == 0) || + (reply->client_prefer > pref_life)) + reply->client_prefer = pref_life; + } + reply->ia_count++; + + /* + * Cancel if not Solicit or Request. + */ + if ((reply->packet->dhcpv6_msg_type != DHCPV6_SOLICIT) && + (reply->packet->dhcpv6_msg_type != DHCPV6_REQUEST)) { + if (!set_status_code(STATUS_UnspecFail, + "Unsupported message for temporary.", + reply->reply_ia)) { + log_error("reply_process_ia_ta: Unable to set " + "UnspecFail status code."); + status = ISC_R_FAILURE; + goto cleanup; + } + status = ISC_R_CANCELED; + goto store; + } + + /* + * Give the client temporary addresses. + */ + status = find_client_temporaries(reply); + if (status == ISC_R_NORESOURCES) { + switch (reply->packet->dhcpv6_msg_type) { + case DHCPV6_SOLICIT: + /* + * No address for any IA is handled + * by the caller. + */ + /* FALL THROUGH */ + + case DHCPV6_REQUEST: + /* Section 18.2.1 (Request): + * + * If the server cannot assign any addresses to + * an IA in the message from the client, the + * server MUST include the IA in the Reply + * message with no addresses in the IA and a + * Status Code option in the IA containing + * status code NoAddrsAvail. + */ + option_state_dereference(&reply->reply_ia, MDL); + if (!option_state_allocate(&reply->reply_ia, MDL)) { + log_error("reply_process_ia_ta: No " + "memory for option state wipe."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + + if (!set_status_code(STATUS_NoAddrsAvail, + "No addresses available " + "for this interface.", + reply->reply_ia)) { + log_error("reply_process_ia_ta: Unable " + "to set NoAddrsAvail status code."); + status = ISC_R_FAILURE; + goto cleanup; + } + + status = ISC_R_SUCCESS; + break; + + default: + /* Should not happen! */ + goto cleanup; + } + } else if (status != ISC_R_SUCCESS) + goto cleanup; + + store: + reply->cursor += store_options6((char *)reply->buf.data + reply->cursor, + sizeof(reply->buf) - reply->cursor, + reply->reply_ia, reply->packet, + required_opts_IA, NULL); + + /* Reset the length of this IA to match what was just written. */ + putUShort(reply->buf.data + ia_cursor + 2, + reply->cursor - (ia_cursor + 4)); + + /* + * Consume the new changes into the database (if any have been + * attached to the ia_ta). + * + * Loop through the assigned dynamic addresses, referencing the + * leases onto this IA_TA rather than any old ones, and updating + * pool timers for each (if any). + */ + if ((status != ISC_R_CANCELED) && + (reply->buf.reply.msg_type == DHCPV6_REPLY) && + (reply->ia_ta->num_iaaddr != 0)) { + struct iaaddr *tmp; + struct data_string *ia_id; + int i; + + for (i = 0 ; i < reply->ia_ta->num_iaaddr ; i++) { + tmp = reply->ia_ta->iaaddr[i]; + + if (tmp->ia_na != NULL) + ia_na_dereference(&tmp->ia_na, MDL); + ia_na_reference(&tmp->ia_na, reply->ia_ta, MDL); + + schedule_lease_timeout(tmp->ipv6_pool); + + /* + * Perform ddns updates. + */ + oc = lookup_option(&server_universe, reply->opt_state, + SV_DDNS_UPDATES); + if ((oc == NULL) || + evaluate_boolean_option_cache(NULL, reply->packet, + NULL, NULL, + reply->packet->options, + reply->opt_state, + &tmp->scope, + oc, MDL)) { + ddns_updates(reply->packet, NULL, NULL, + tmp, NULL, reply->opt_state); + } + } + + /* Remove any old ia_ta from the hash. */ + if (reply->old_ia != NULL) { + ia_id = &reply->old_ia->iaid_duid; + ia_na_hash_delete(ia_ta_active, + (unsigned char *)ia_id->data, + ia_id->len, MDL); + ia_na_dereference(&reply->old_ia, MDL); + } + + /* Put new ia_ta into the hash. */ + ia_id = &reply->ia_ta->iaid_duid; + ia_na_hash_add(ia_ta_active, (unsigned char *)ia_id->data, + ia_id->len, reply->ia_ta, MDL); + + write_ia(reply->ia_ta); + } + + cleanup: + if (packet_ia != NULL) + option_state_dereference(&packet_ia, MDL); + if (iaaddr.data != NULL) + data_string_forget(&iaaddr, MDL); + if (reply->reply_ia != NULL) + option_state_dereference(&reply->reply_ia, MDL); + if (ia_data.data != NULL) + data_string_forget(&ia_data, MDL); + if (data.data != NULL) + data_string_forget(&data, MDL); + if (reply->ia_ta != NULL) + ia_na_dereference(&reply->ia_ta, MDL); + if (reply->old_ia != NULL) + ia_na_dereference(&reply->old_ia, MDL); + + /* + * ISC_R_CANCELED is a status code used by the addr processing to + * indicate we're replying with a status code. This is still a + * success at higher layers. + */ + return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status); +} + +/* + * Get a temporary address per prefix. + */ +static isc_result_t +find_client_temporaries(struct reply_state *reply) { + struct shared_network *shared; + int i; + struct ipv6_pool *p; + isc_result_t status; + unsigned int attempts; + struct iaddr send_addr; + + /* + * No pools, we're done. + */ + shared = reply->shared; + if (shared->ipv6_pools == NULL) { + log_debug("Unable to get client addresses: " + "no IPv6 pools on this shared network"); + return ISC_R_NORESOURCES; + } + + status = ISC_R_NORESOURCES; + for (i = 0;; i++) { + p = shared->ipv6_pools[i]; + if (p == NULL) { + break; + } + if ((p->bits & POOL_IS_FOR_TEMP) == 0) { + continue; + } + + /* + * Get an address in this temporary pool. + */ + status = activate_lease6(p, &reply->lease, &attempts, + &reply->client_id, 0); + if (status != ISC_R_SUCCESS) { + log_debug("Unable to get a temporary address."); + goto cleanup; + } + + status = reply_process_is_addressed(reply, + reply->ia_ta, + &reply->lease->scope, + reply->shared->group); + if (status != ISC_R_SUCCESS) { + goto cleanup; + } + send_addr.len = 16; + memcpy(send_addr.iabuf, &reply->lease->addr, 16); + status = reply_process_send_addr(reply, &send_addr); + if (status != ISC_R_SUCCESS) { + goto cleanup; + } + if (reply->lease != NULL) { + iaaddr_dereference(&reply->lease, MDL); + } + } + + cleanup: + if (reply->lease != NULL) { + iaaddr_dereference(&reply->lease, MDL); + } + return status; +} + /* * This function only returns failure on 'hard' failures. If it succeeds, * it will leave a lease structure behind. @@ -2011,9 +2606,9 @@ find_client_address(struct reply_state *reply) { goto send_addr; } - if (reply->old_ia_na != NULL) { - for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) { - lease = reply->old_ia_na->iaaddr[i]; + if (reply->old_ia != NULL) { + for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) { + lease = reply->old_ia->iaaddr[i]; best_lease = lease_compare(lease, best_lease); } @@ -2053,7 +2648,7 @@ find_client_address(struct reply_state *reply) { memcpy(send_addr.iabuf, &reply->lease->addr, 16); send_addr: - status = reply_process_is_addressed(reply, scope, group); + status = reply_process_is_addressed(reply, reply->ia_na, scope, group); if (status != ISC_R_SUCCESS) return status; @@ -2066,7 +2661,7 @@ find_client_address(struct reply_state *reply) { * into the option state. */ static isc_result_t -reply_process_is_addressed(struct reply_state *reply, +reply_process_is_addressed(struct reply_state *reply, struct ia_na *ia, struct binding_scope **scope, struct group *group) { isc_result_t status = ISC_R_SUCCESS; @@ -2161,18 +2756,18 @@ reply_process_is_addressed(struct reply_state *reply, reply->send_valid; renew_lease6(reply->lease->ipv6_pool, reply->lease); - status = ia_na_add_iaaddr(reply->ia_na, reply->lease, MDL); + status = ia_na_add_iaaddr(ia, reply->lease, MDL); if (status != ISC_R_SUCCESS) { - log_fatal("reply_process_addr: Unable to attach lease " - "to new IA: %s", isc_result_totext(status)); + log_fatal("reply_process_is_addressed: Unable to " + "attach lease to new IA: %s", + isc_result_totext(status)); } /* * If this is a new lease, make sure it is attached somewhere. */ if (reply->lease->ia_na == NULL) { - ia_na_reference(&reply->lease->ia_na, reply->ia_na, - MDL); + ia_na_reference(&reply->lease->ia_na, ia, MDL); } } @@ -2191,7 +2786,7 @@ reply_process_is_addressed(struct reply_state *reply, return status; } -/* Simply send an IAADDR within the IA_NA scope as described. */ +/* Simply send an IAADDR within the IA scope as described. */ static isc_result_t reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) { isc_result_t status = ISC_R_SUCCESS; @@ -2202,8 +2797,8 @@ reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) { /* Now append the lease. */ data.len = IAADDR_OFFSET; if (!buffer_allocate(&data.buffer, data.len, MDL)) { - log_error("reply_process_send_addr: out of memory allocating " - "new IAADDR buffer."); + log_error("reply_process_send_addr: out of memory" + "allocating new IAADDR buffer."); status = ISC_R_NOMEMORY; goto cleanup; } @@ -2216,13 +2811,13 @@ reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) { if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia, data.buffer, data.buffer->data, data.len, D6O_IAADDR, 0)) { - log_error("reply_process_send_addr: unable to save IAADDR " - "option"); + log_error("reply_process_send_addr: unable " + "to save IAADDR option"); status = ISC_R_FAILURE; goto cleanup; } - reply->ia_addrs_included = ISC_TRUE; + reply->ia_resources_included = ISC_TRUE; cleanup: if (data.data != NULL) @@ -2307,6 +2902,1000 @@ lease_compare(struct iaaddr *alpha, struct iaaddr *beta) { return NULL; } +/* Process a client-supplied IA_PD. This may append options to the tail of + * the reply packet being built in the reply_state structure. + */ +static isc_result_t +reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia_pd) { + isc_result_t status = ISC_R_SUCCESS; + u_int32_t iaid; + unsigned ia_cursor; + struct option_state *packet_ia; + struct option_cache *oc; + struct data_string ia_pd_data, data; + isc_boolean_t prefix_in_database; + + /* Initialize values that will get cleaned up on return. */ + packet_ia = NULL; + memset(&ia_pd_data, 0, sizeof(ia_pd_data)); + memset(&data, 0, sizeof(data)); + prefix_in_database = ISC_FALSE; + /* + * Note that find_client_prefix() may set reply->prefix. + */ + + /* Make sure there is at least room for the header. */ + if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) { + log_error("reply_process_ia_pd: Reply too long for IA."); + return ISC_R_NOSPACE; + } + + + /* Fetch the IA_PD contents. */ + if (!get_encapsulated_IA_state(&packet_ia, &ia_pd_data, reply->packet, + ia_pd, IA_PD_OFFSET)) { + log_error("reply_process_ia_pd: error evaluating ia_pd"); + status = ISC_R_FAILURE; + goto cleanup; + } + + /* Extract IA_PD header contents. */ + iaid = getULong(ia_pd_data.data); + reply->renew = getULong(ia_pd_data.data + 4); + reply->rebind = getULong(ia_pd_data.data + 8); + + /* Create an IA_PD structure. */ + if (ia_pd_allocate(&reply->ia_pd, iaid, (char *)reply->client_id.data, + reply->client_id.len, MDL) != ISC_R_SUCCESS) { + log_error("reply_process_ia_pd: no memory for ia_pd."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + + /* Cache pre-existing IA_PD, if any. */ + ia_pd_hash_lookup(&reply->old_ia_pd, ia_pd_active, + (unsigned char *)reply->ia_pd->iaid_duid.data, + reply->ia_pd->iaid_duid.len, MDL); + + /* + * Create an option cache to carry the IA_PD option contents, and + * execute any user-supplied values into it. + */ + if (!option_state_allocate(&reply->reply_ia, MDL)) { + status = ISC_R_NOMEMORY; + goto cleanup; + } + + /* Check & count the fixed prefix host records. */ + reply->static_prefixes = 0; + if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) { + struct iaddrcidrnetlist *fp; + + for (fp = reply->host->fixed_prefix; fp != NULL; + fp = fp->next) { + reply->static_prefixes += 1; + } + } + + /* + * Save the cursor position at the start of the IA_PD, so we can + * set length and adjust t1/t2 values later. We write a temporary + * header out now just in case we decide to adjust the packet + * within sub-process functions. + */ + ia_cursor = reply->cursor; + + /* Initialize the IA_PD header. First the code. */ + putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD); + reply->cursor += 2; + + /* Then option length. */ + putUShort(reply->buf.data + reply->cursor, 0x0Cu); + reply->cursor += 2; + + /* Then IA_PD header contents; IAID. */ + putULong(reply->buf.data + reply->cursor, iaid); + reply->cursor += 4; + + /* We store the client's t1 for now, and may over-ride it later. */ + putULong(reply->buf.data + reply->cursor, reply->renew); + reply->cursor += 4; + + /* We store the client's t2 for now, and may over-ride it later. */ + putULong(reply->buf.data + reply->cursor, reply->rebind); + reply->cursor += 4; + + /* + * For each prefix in this IA_PD, decide what to do about it. + */ + oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX); + reply->valid = reply->prefer = 0xffffffff; + reply->client_valid = reply->client_prefer = 0; + reply->preflen = -1; + for (; oc != NULL ; oc = oc->next) { + status = reply_process_prefix(reply, oc); + + /* + * Canceled means we did not allocate prefixes to the + * client, but we're "done" with this IA - we set a status + * code. So transmit this reply, e.g., move on to the next + * IA. + */ + if (status == ISC_R_CANCELED) + break; + + if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE)) + goto cleanup; + } + + reply->ia_pd_count++; + + /* + * If we fell through the above and never gave the client + * a prefix, give it one now. + */ + if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) { + status = find_client_prefix(reply); + + if (status == ISC_R_NORESOURCES) { + switch (reply->packet->dhcpv6_msg_type) { + case DHCPV6_SOLICIT: + /* + * No prefix for any IA is handled + * by the caller. + */ + /* FALL THROUGH */ + + case DHCPV6_REQUEST: + /* Same than for addresses. */ + option_state_dereference(&reply->reply_ia, MDL); + if (!option_state_allocate(&reply->reply_ia, + MDL)) + { + log_error("reply_process_ia_pd: No " + "memory for option state " + "wipe."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + + if (!set_status_code(STATUS_NoPrefixAvail, + "No prefixes available " + "for this interface.", + reply->reply_ia)) { + log_error("reply_process_ia_pd: " + "Unable to set " + "NoPrefixAvail status " + "code."); + status = ISC_R_FAILURE; + goto cleanup; + } + + status = ISC_R_SUCCESS; + break; + + default: + if (reply->ia_resources_included) + status = ISC_R_SUCCESS; + else + goto cleanup; + break; + } + } + + if (status != ISC_R_SUCCESS) + goto cleanup; + } + + reply->cursor += store_options6((char *)reply->buf.data + reply->cursor, + sizeof(reply->buf) - reply->cursor, + reply->reply_ia, reply->packet, + required_opts_IA_PD, NULL); + + /* Reset the length of this IA_PD to match what was just written. */ + putUShort(reply->buf.data + ia_cursor + 2, + reply->cursor - (ia_cursor + 4)); + + /* + * T1/T2 time selection is kind of weird. We actually use DHCP + * (v4) scoped options as handy existing places where these might + * be configured by an administrator. A value of zero tells the + * client it may choose its own renewal time. + */ + reply->renew = 0; + oc = lookup_option(&dhcp_universe, reply->opt_state, + DHO_DHCP_RENEWAL_TIME); + if (oc != NULL) { + if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, + reply->packet->options, + reply->opt_state, &global_scope, + oc, MDL) || + (data.len != 4)) { + log_error("Invalid renewal time."); + } else { + reply->renew = getULong(data.data); + } + + if (data.data != NULL) + data_string_forget(&data, MDL); + } + putULong(reply->buf.data + ia_cursor + 8, reply->renew); + + /* Now T2. */ + reply->rebind = 0; + oc = lookup_option(&dhcp_universe, reply->opt_state, + DHO_DHCP_REBINDING_TIME); + if (oc != NULL) { + if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, + reply->packet->options, + reply->opt_state, &global_scope, + oc, MDL) || + (data.len != 4)) { + log_error("Invalid rebinding time."); + } else { + reply->rebind = getULong(data.data); + } + + if (data.data != NULL) + data_string_forget(&data, MDL); + } + putULong(reply->buf.data + ia_cursor + 12, reply->rebind); + + /* + * If this is not a 'soft' binding, consume the new changes into + * the database (if any have been attached to the ia_pd). + * + * Loop through the assigned dynamic prefixes, referencing the + * prefixes onto this IA_PD rather than any old ones, and updating + * prefix pool timers for each (if any). + */ + if ((status != ISC_R_CANCELED) && (reply->static_prefixes == 0) && + (reply->buf.reply.msg_type == DHCPV6_REPLY) && + (reply->ia_pd->num_iaprefix != 0)) { + struct iaprefix *tmp; + struct data_string *ia_id; + int i; + + for (i = 0 ; i < reply->ia_pd->num_iaprefix ; i++) { + tmp = reply->ia_pd->iaprefix[i]; + + if (tmp->ia_pd != NULL) + ia_pd_dereference(&tmp->ia_pd, MDL); + ia_pd_reference(&tmp->ia_pd, reply->ia_pd, MDL); + + schedule_prefix_timeout(tmp->ipv6_ppool); + } + + /* Remove any old ia_pd from the hash. */ + if (reply->old_ia_pd != NULL) { + ia_id = &reply->old_ia_pd->iaid_duid; + ia_pd_hash_delete(ia_pd_active, + (unsigned char *)ia_id->data, + ia_id->len, MDL); + ia_pd_dereference(&reply->old_ia_pd, MDL); + } + + /* Put new ia_pd into the hash. */ + ia_id = &reply->ia_pd->iaid_duid; + ia_pd_hash_add(ia_pd_active, (unsigned char *)ia_id->data, + ia_id->len, reply->ia_pd, MDL); + + write_ia_pd(reply->ia_pd); + + /* + * Note that we wrote the prefix into the database, + * so that we know not to release it when we're done + * with this function. + */ + prefix_in_database = ISC_TRUE; + + /* + * If this is a soft binding, we will check to see if we are + * suggesting the existing database entry to the client. + */ + } else if ((status != ISC_R_CANCELED) && + (reply->static_prefixes == 0) && + (reply->old_ia_pd != NULL)) { + if (ia_pd_equal(reply->old_ia_pd, reply->ia_pd)) { + prefix_in_database = ISC_TRUE; + } + } + + cleanup: + if (packet_ia != NULL) + option_state_dereference(&packet_ia, MDL); + if (reply->reply_ia != NULL) + option_state_dereference(&reply->reply_ia, MDL); + if (ia_pd_data.data != NULL) + data_string_forget(&ia_pd_data, MDL); + if (data.data != NULL) + data_string_forget(&data, MDL); + if (reply->old_ia_pd != NULL) + ia_pd_dereference(&reply->old_ia_pd, MDL); + if (reply->prefix != NULL) + iaprefix_dereference(&reply->prefix, MDL); + if (!prefix_in_database) { + /* + * Cleanup soft bindings, assume: + * reply->static_prefixes == 0 + * reply->ia_pd != NULL + * reply->ia_pd->num_iaprefix != 0 + */ + struct iaprefix *tmp; + int i; + + for (i = 0 ; i < reply->ia_pd->num_iaprefix ; i++) { + tmp = reply->ia_pd->iaprefix[i]; + release_prefix6(tmp->ipv6_ppool, tmp); + } + } + if (reply->ia_pd != NULL) + ia_pd_dereference(&reply->ia_pd, MDL); + + /* + * ISC_R_CANCELED is a status code used by the prefix processing to + * indicate we're replying with a status code. This is still a + * success at higher layers. + */ + return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status); +} + +/* + * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply + * contents into the reply's current ia_pd-scoped option cache. Returns + * ISC_R_CANCELED in the event we are replying with a status code and do + * not wish to process more IAPREFIXes within this IA_PD. + */ +static isc_result_t +reply_process_prefix(struct reply_state *reply, struct option_cache *pref) { + u_int32_t pref_life, valid_life; + struct binding_scope **scope; + struct group *group; + struct iaddrcidrnet tmp_pref; + struct option_cache *oc; + struct data_string iapref, data; + isc_boolean_t held_prefix; + isc_result_t status = ISC_R_SUCCESS; + + /* Initializes values that will be cleaned up. */ + held_prefix = ISC_FALSE; + memset(&iapref, 0, sizeof(iapref)); + memset(&data, 0, sizeof(data)); + /* Note that reply->prefix may be set by prefix_is_owned() */ + + /* + * There is no point trying to process an incoming prefix if there + * is no room for an outgoing prefix. + */ + if ((reply->cursor + 29) > sizeof(reply->buf)) { + log_error("reply_process_prefix: Out of room for prefix."); + return ISC_R_NOSPACE; + } + + /* Extract this IAPREFIX option. */ + if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL, + reply->packet->options, NULL, &global_scope, + pref, MDL) || + (iapref.len < IAPREFIX_OFFSET)) { + log_error("reply_process_prefix: error evaluating IAPREFIX."); + status = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Layout: preferred and valid lifetimes followed by the prefix + * length and the IPv6 address. + */ + pref_life = getULong(iapref.data); + valid_life = getULong(iapref.data + 4); + + if ((reply->client_valid == 0) || + (reply->client_valid > valid_life)) + reply->client_valid = valid_life; + + if ((reply->client_prefer == 0) || + (reply->client_prefer > pref_life)) + reply->client_prefer = pref_life; + + /* + * Clients may choose to send ::/0 as a prefix, with the idea to give + * hints about preferred-lifetime or valid-lifetime. + */ + tmp_pref.lo_addr.len = 16; + memset(tmp_pref.lo_addr.iabuf, 0, 16); + if ((iapref.data[8] == 0) && + (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) { + /* Status remains success; we just ignore this one. */ + goto cleanup; + } + + /* + * Clients may choose to send ::/X as a prefix to specify a + * preferred/requested prefix length. Note X is never zero here. + */ + tmp_pref.bits = (int) iapref.data[8]; + if (reply->preflen < 0) { + /* Cache the first preferred prefix length. */ + reply->preflen = tmp_pref.bits; + } + if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) { + goto cleanup; + } + + memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16); + + /* Verify the prefix belongs to the client. */ + if (!prefix_is_owned(reply, &tmp_pref)) { + /* Same than for addresses. */ + if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) || + (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) || + (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) { + status = reply_process_try_prefix(reply, &tmp_pref); + + /* Either error out or skip this prefix. */ + if ((status != ISC_R_SUCCESS) && + (status != ISC_R_ADDRINUSE)) + goto cleanup; + + if (reply->prefix == NULL) { + if (reply->packet->dhcpv6_msg_type == + DHCPV6_REBIND) { + reply->send_prefer = 0; + reply->send_valid = 0; + goto send_pref; + } + + /* status remains success - ignore */ + goto cleanup; + } + + held_prefix = ISC_TRUE; + + /* + * RFC3633 section 18.2.3: + * + * If the delegating router cannot find a binding + * for the requesting router's IA_PD the delegating + * router returns the IA_PD containing no prefixes + * with a Status Code option set to NoBinding in the + * Reply message. + * + * On mismatch we (ab)use this pretending we have not the IA + * as soon as we have not a prefix. + */ + } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) { + /* Rewind the IA_PD to empty. */ + option_state_dereference(&reply->reply_ia, MDL); + if (!option_state_allocate(&reply->reply_ia, MDL)) { + log_error("reply_process_prefix: No memory " + "for option state wipe."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + + /* Append a NoBinding status code. */ + if (!set_status_code(STATUS_NoBinding, + "Prefix not bound to this " + "interface.", reply->reply_ia)) { + log_error("reply_process_prefix: Unable to " + "attach status code."); + status = ISC_R_FAILURE; + goto cleanup; + } + + /* Fin (no more IAPREFIXes). */ + status = ISC_R_CANCELED; + goto cleanup; + } else { + log_error("It is impossible to lease a client that is " + "not sending a solicit, request, renew, or " + "rebind message."); + status = ISC_R_FAILURE; + goto cleanup; + } + } + + if (reply->static_prefixes > 0) { + if (reply->host == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + scope = &global_scope; + group = reply->host->group; + } else { + if (reply->prefix == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + scope = &reply->prefix->scope; + group = root_group; + } + + /* + * If client_resources is nonzero, then the reply_process_is_prefixed + * function has executed configuration state into the reply option + * cache. We will use that valid cache to derive configuration for + * whether or not to engage in additional prefixes, and similar. + */ + if (reply->client_resources != 0) { + unsigned limit = 1; + + /* + * Does this client have "enough" prefixes already? Default + * to one. Everybody gets one, and one should be enough for + * anybody. + */ + oc = lookup_option(&server_universe, reply->opt_state, + SV_LIMIT_PREFS_PER_IA); + if (oc != NULL) { + if (!evaluate_option_cache(&data, reply->packet, + NULL, NULL, + reply->packet->options, + reply->opt_state, + scope, oc, MDL) || + (data.len != 4)) { + log_error("reply_process_prefix: unable to " + "evaluate prefs-per-ia value."); + status = ISC_R_FAILURE; + goto cleanup; + } + + limit = getULong(data.data); + data_string_forget(&data, MDL); + } + + /* + * If we wish to limit the client to a certain number of + * prefixes, then omit the prefix from the reply. + */ + if (reply->client_resources >= limit) + goto cleanup; + } + + status = reply_process_is_prefixed(reply, reply->ia_pd, scope, group); + if (status != ISC_R_SUCCESS) + goto cleanup; + + send_pref: + status = reply_process_send_prefix(reply, &tmp_pref); + + cleanup: + if (iapref.data != NULL) + data_string_forget(&iapref, MDL); + if (data.data != NULL) + data_string_forget(&data, MDL); + if (held_prefix && (status != ISC_R_SUCCESS)) + release_prefix6(reply->prefix->ipv6_ppool, reply->prefix); + if (reply->prefix != NULL) + iaprefix_dereference(&reply->prefix, MDL); + + return status; +} + +/* + * Verify the prefix belongs to the client. If we've got a host + * record with fixed prefixes, it has to be an assigned prefix + * (fault out all else). Otherwise it's a dynamic prefix, so lookup + * that prefix and make sure it belongs to this DUID:IAID pair. + */ +static isc_boolean_t +prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) { + struct iaddrcidrnetlist *l; + int i; + + /* + * This faults out prefixes that don't match fixed prefixes. + */ + if (reply->static_prefixes > 0) { + for (l = reply->host->fixed_prefix; l != NULL; l = l->next) { + if ((pref->bits == l->cidrnet.bits) && + (memcmp(pref->lo_addr.iabuf, + l->cidrnet.lo_addr.iabuf, 16) == 0)) + return ISC_TRUE; + } + return ISC_FALSE; + } + + if ((reply->old_ia_pd == NULL) || + (reply->old_ia_pd->num_iaprefix == 0)) + return ISC_FALSE; + + for (i = 0 ; i < reply->old_ia_pd->num_iaprefix ; i++) { + struct iaprefix *tmp; + + tmp = reply->old_ia_pd->iaprefix[i]; + + if ((pref->bits == (int) tmp->plen) && + memcmp(pref->lo_addr.iabuf, &tmp->pref, 16) == 0) { + iaprefix_reference(&reply->prefix, tmp, MDL); + return ISC_TRUE; + } + } + + return ISC_FALSE; +} + +/* + * This function only returns failure on 'hard' failures. If it succeeds, + * it will leave a prefix structure behind. + */ +static isc_result_t +reply_process_try_prefix(struct reply_state *reply, + struct iaddrcidrnet *pref) { + isc_result_t status = ISC_R_FAILURE; + struct ipv6_ppool *ppool; + int i; + struct data_string data_pref; + + if ((reply == NULL) || (pref == NULL) || (reply->prefix != NULL)) + return ISC_R_INVALIDARG; + + memset(&data_pref, 0, sizeof(data_pref)); + data_pref.len = 17; + if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) { + log_error("reply_process_try_prefix: out of memory."); + return ISC_R_NOMEMORY; + } + data_pref.data = data_pref.buffer->data; + data_pref.buffer->data[0] = (u_int8_t) pref->bits; + memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16); + + for (i = 0 ; i < num_ppools ; i++) { + ppool = ppools[i]; + if (ppool == NULL) + continue; + status = try_client_v6_prefix(&reply->prefix, ppool, + &data_pref); + if (status == ISC_R_SUCCESS) + break; + } + + data_string_forget(&data_pref, MDL); + /* Return just the most recent status... */ + return status; +} + +/* Look around for a prefix to give the client. First, look through the old + * IA_PD for prefixes we can extend. Second, try to allocate a new prefix. + * Finally, actually add that prefix into the current reply IA_PD. + */ +static isc_result_t +find_client_prefix(struct reply_state *reply) { + struct iaddrcidrnet send_pref; + isc_result_t status = ISC_R_NORESOURCES; + struct iaprefix *prefix, *best_prefix = NULL; + struct binding_scope **scope; + struct group *group; + int i; + + if (reply->host != NULL) + group = reply->host->group; + else if (reply->shared != NULL) + group = reply->shared->group; + else + group = root_group; + + if (reply->static_prefixes > 0) { + struct iaddrcidrnetlist *l; + + if (reply->host == NULL) + return ISC_R_INVALIDARG; + + for (l = reply->host->fixed_prefix; l != NULL; l = l->next) { + if (l->cidrnet.bits == reply->preflen) + break; + } + if (l == NULL) { + /* + * If no fixed prefix has the preferred length, + * get the first one. + */ + l = reply->host->fixed_prefix; + } + memcpy(&send_pref, &l->cidrnet, sizeof(send_pref)); + + status = ISC_R_SUCCESS; + scope = &global_scope; + goto send_pref; + } + + if (reply->old_ia_pd != NULL) { + for (i = 0 ; i < reply->old_ia_pd->num_iaprefix ; i++) { + prefix = reply->old_ia_pd->iaprefix[i]; + + best_prefix = prefix_compare(reply, prefix, + best_prefix); + } + } + + /* Try to pick a new prefix if we didn't find one, or if we found an + * abandoned prefix. + */ + if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) { + status = pick_v6_prefix(&reply->prefix, reply->preflen, + &reply->client_id); + } else if (best_prefix != NULL) { + iaprefix_reference(&reply->prefix, best_prefix, MDL); + status = ISC_R_SUCCESS; + } + + /* Pick the abandoned prefix as a last resort. */ + if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) { + /* I don't see how this is supposed to be done right now. */ + log_error("Reclaiming abandoned prefixes is not yet " + "supported. Treating this as an out of space " + "condition."); + /* prefix_reference(&reply->prefix, best_prefix, MDL); */ + } + + /* Give up now if we didn't find a prefix. */ + if (status != ISC_R_SUCCESS) + return status; + + if (reply->prefix == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + scope = &reply->prefix->scope; + if (reply->shared != NULL) { + group = reply->shared->group; + } else { + group = root_group; + } + + send_pref.lo_addr.len = 16; + memcpy(send_pref.lo_addr.iabuf, &reply->prefix->pref, 16); + send_pref.bits = (int) reply->prefix->plen; + + send_pref: + status = reply_process_is_prefixed(reply, reply->ia_pd, scope, group); + if (status != ISC_R_SUCCESS) + return status; + + status = reply_process_send_prefix(reply, &send_pref); + return status; +} + +/* Once a prefix is found for a client, perform several common functions; + * Calculate and store valid and preferred prefix times, draw client options + * into the option state. + */ +static isc_result_t +reply_process_is_prefixed(struct reply_state *reply, struct ia_pd *ia_pd, + struct binding_scope **scope, struct group *group) +{ + isc_result_t status = ISC_R_SUCCESS; + struct data_string data; + struct option_cache *oc; + + /* Initialize values we will cleanup. */ + memset(&data, 0, sizeof(data)); + + /* Execute relevant options into root scope. */ + execute_statements_in_scope(NULL, reply->packet, NULL, NULL, + reply->packet->options, reply->opt_state, + scope, group, root_group); + + /* Determine valid lifetime. */ + if (reply->client_valid == 0) + reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME; + else + reply->send_valid = reply->client_valid; + + oc = lookup_option(&server_universe, reply->opt_state, + SV_DEFAULT_LEASE_TIME); + if (oc != NULL) { + if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, + reply->packet->options, + reply->opt_state, + scope, oc, MDL) || + (data.len != 4)) { + log_error("reply_process_is_prefixed: unable to " + "evaluate default prefix time"); + status = ISC_R_FAILURE; + goto cleanup; + } + + reply->send_valid = getULong(data.data); + data_string_forget(&data, MDL); + } + + if (reply->client_prefer == 0) + reply->send_prefer = reply->send_valid; + else + reply->send_prefer = reply->client_prefer; + + if (reply->send_prefer >= reply->send_valid) + reply->send_prefer = (reply->send_valid / 2) + + (reply->send_valid / 8); + + oc = lookup_option(&server_universe, reply->opt_state, + SV_PREFER_LIFETIME); + if (oc != NULL) { + if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, + reply->packet->options, + reply->opt_state, + scope, oc, MDL) || + (data.len != 4)) { + log_error("reply_process_is_prefixed: unable to " + "evaluate preferred prefix time"); + status = ISC_R_FAILURE; + goto cleanup; + } + + reply->send_prefer = getULong(data.data); + data_string_forget(&data, MDL); + } + + /* Note lowest values for later calculation of renew/rebind times. */ + if (reply->prefer > reply->send_prefer) + reply->prefer = reply->send_prefer; + + if (reply->valid > reply->send_valid) + reply->valid = reply->send_valid; + + /* Perform dynamic prefix related update work. */ + if (reply->prefix != NULL) { + /* Advance (or rewind) the valid lifetime. */ + reply->prefix->valid_lifetime_end_time = cur_time + + reply->send_valid; + renew_prefix6(reply->prefix->ipv6_ppool, reply->prefix); + + status = ia_pd_add_iaprefix(ia_pd, reply->prefix, MDL); + if (status != ISC_R_SUCCESS) { + log_fatal("reply_process_is_prefixed: Unable to " + "attach prefix to new IA_PD: %s", + isc_result_totext(status)); + } + + /* + * If this is a new prefix, make sure it is attached somewhere. + */ + if (reply->prefix->ia_pd == NULL) { + ia_pd_reference(&reply->prefix->ia_pd, ia_pd, MDL); + } + } + + /* Bring a copy of the relevant options into the IA_PD scope. */ + execute_statements_in_scope(NULL, reply->packet, NULL, NULL, + reply->packet->options, reply->reply_ia, + scope, group, root_group); + + cleanup: + if (data.data != NULL) + data_string_forget(&data, MDL); + + if (status == ISC_R_SUCCESS) + reply->client_resources++; + + return status; +} + +/* Simply send an IAPREFIX within the IA_PD scope as described. */ +static isc_result_t +reply_process_send_prefix(struct reply_state *reply, + struct iaddrcidrnet *pref) { + isc_result_t status = ISC_R_SUCCESS; + struct data_string data; + + memset(&data, 0, sizeof(data)); + + /* Now append the prefix. */ + data.len = IAPREFIX_OFFSET; + if (!buffer_allocate(&data.buffer, data.len, MDL)) { + log_error("reply_process_send_prefix: out of memory" + "allocating new IAPREFIX buffer."); + status = ISC_R_NOMEMORY; + goto cleanup; + } + data.data = data.buffer->data; + + putULong(data.buffer->data, reply->send_prefer); + putULong(data.buffer->data + 4, reply->send_valid); + data.buffer->data[8] = pref->bits; + memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16); + + if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia, + data.buffer, data.buffer->data, + data.len, D6O_IAPREFIX, 0)) { + log_error("reply_process_send_prefix: unable " + "to save IAPREFIX option"); + status = ISC_R_FAILURE; + goto cleanup; + } + + reply->ia_resources_included = ISC_TRUE; + + cleanup: + if (data.data != NULL) + data_string_forget(&data, MDL); + + return status; +} + +/* Choose the better of two prefixes. */ +static struct iaprefix * +prefix_compare(struct reply_state *reply, + struct iaprefix *alpha, struct iaprefix *beta) { + if (alpha == NULL) + return beta; + if (beta == NULL) + return alpha; + + if (reply->preflen >= 0) { + if ((alpha->plen == reply->preflen) && + (beta->plen != reply->preflen)) + return alpha; + if ((beta->plen == reply->preflen) && + (alpha->plen != reply->preflen)) + return beta; + } + + switch(alpha->state) { + case FTS_ACTIVE: + switch(beta->state) { + case FTS_ACTIVE: + /* Choose the prefix with the longest lifetime (most + * likely the most recently allocated). + */ + if (alpha->valid_lifetime_end_time < + beta->valid_lifetime_end_time) + return beta; + else + return alpha; + + case FTS_EXPIRED: + case FTS_ABANDONED: + return alpha; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + break; + + case FTS_EXPIRED: + switch (beta->state) { + case FTS_ACTIVE: + return beta; + + case FTS_EXPIRED: + /* Choose the most recently expired prefix. */ + if (alpha->valid_lifetime_end_time < + beta->valid_lifetime_end_time) + return beta; + else + return alpha; + + case FTS_ABANDONED: + return alpha; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + break; + + case FTS_ABANDONED: + switch (beta->state) { + case FTS_ACTIVE: + case FTS_EXPIRED: + return alpha; + + case FTS_ABANDONED: + /* Choose the prefix that was abandoned longest ago. */ + if (alpha->valid_lifetime_end_time < + beta->valid_lifetime_end_time) + return alpha; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + + log_fatal("Triple impossible condition at %s:%d.", MDL); + return NULL; +} + /* * Solicit is how a client starts requesting addresses. * @@ -2368,7 +3957,7 @@ dhcpv6_request(struct data_string *reply_ret, struct packet *packet) { /* Find a DHCPv6 packet's shared network from hints in the packet. */ -static isc_result_t +static void shared_network_from_packet6(struct shared_network **shared, struct packet *packet) { @@ -2376,10 +3965,11 @@ shared_network_from_packet6(struct shared_network **shared, const struct in6_addr *link_addr, *first_link_addr; struct iaddr tmp_addr; struct subnet *subnet; - isc_result_t status; - if ((shared == NULL) || (*shared != NULL) || (packet == NULL)) - return ISC_R_INVALIDARG; + if ((shared == NULL) || (*shared != NULL) || (packet == NULL)) { + log_error("shared_network_from_packet6: invalid arg."); + return; + } /* * First, find the link address where the packet from the client @@ -2409,10 +3999,9 @@ shared_network_from_packet6(struct shared_network **shared, if (!find_subnet(&subnet, tmp_addr, MDL)) { log_debug("No subnet found for link-address %s.", piaddr(tmp_addr)); - return ISC_R_NOTFOUND; + return; } - status = shared_network_reference(shared, - subnet->shared_network, MDL); + shared_network_reference(shared, subnet->shared_network, MDL); subnet_dereference(&subnet, MDL); /* @@ -2420,12 +4009,10 @@ shared_network_from_packet6(struct shared_network **shared, * that this packet came in on to pick the shared_network. */ } else { - status = shared_network_reference(shared, + shared_network_reference(shared, packet->interface->shared_network, MDL); } - - return status; } /* @@ -2468,13 +4055,19 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) { return; } - /* Do not process Confirms that do not have IA's we do not recognize. + /* + * Do not process Confirms that do not have IA's we do not recognize. */ ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA); ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA); if ((ia == NULL) && (ta == NULL)) return; + /* + * IA_PD's are simply ignored. + */ + delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD); + /* * Bit of variable initialization. */ @@ -2488,8 +4081,8 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) { * network the client is on. */ shared = NULL; - if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) || - (shared == NULL)) + shared_network_from_packet6(&shared, packet); + if (shared == NULL) goto exit; /* If there are no recorded subnets, then we have no @@ -2643,10 +4236,10 @@ exit: } /* - * Renew is when a client wants to extend its lease, at time T1. + * Renew is when a client wants to extend its lease/prefix, at time T1. * - * We handle this the same as if the client wants a new lease, except - * for the error code of when addresses don't match. + * We handle this the same as if the client wants a new lease/prefix, + * except for the error code of when addresses don't match. */ /* TODO: reject unicast messages, unless we set unicast option */ @@ -3097,11 +4690,19 @@ dhcpv6_decline(struct data_string *reply, struct packet *packet) { return; } + /* + * Undefined for IA_PD. + */ + delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD); + /* * And operate on each IA_NA in this packet. */ iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline", ia_na_match_decline, ia_na_nomatch_decline); + + data_string_forget(&server_id, MDL); + data_string_forget(&client_id, MDL); } static void @@ -3194,6 +4795,348 @@ exit: option_state_dereference(&host_opt_state, MDL); } +static void +ia_pd_match_release(const struct data_string *client_id, + const struct data_string *iapref, + struct iaprefix *prefix) +{ + char tmp_addr[INET6_ADDRSTRLEN]; + + log_info("Client %s releases prefix %s/%u", + print_hex_1(client_id->len, client_id->data, 60), + inet_ntop(AF_INET6, iapref->data + 9, + tmp_addr, sizeof(tmp_addr)), + (unsigned) getUChar(iapref->data + 8)); + if (prefix != NULL) { + release_prefix6(prefix->ipv6_ppool, prefix); + write_ia_pd(prefix->ia_pd); + } +} + +static void +ia_pd_nomatch_release(const struct data_string *client_id, + const struct data_string *iapref, + u_int32_t *ia_pd_id, + struct packet *packet, + char *reply_data, + int *reply_ofs, + int reply_len) +{ + char tmp_addr[INET6_ADDRSTRLEN]; + struct option_state *host_opt_state; + int len; + + log_info("Client %s releases prefix %s/%u, which is not leased to it.", + print_hex_1(client_id->len, client_id->data, 60), + inet_ntop(AF_INET6, iapref->data + 9, + tmp_addr, sizeof(tmp_addr)), + (unsigned) getUChar(iapref->data + 8)); + + /* + * Create state for this IA_PD. + */ + host_opt_state = NULL; + if (!option_state_allocate(&host_opt_state, MDL)) { + log_error("ia_pd_nomatch_release: out of memory " + "allocating option_state."); + goto exit; + } + + if (!set_status_code(STATUS_NoBinding, + "Release for non-leased prefix.", + host_opt_state)) { + goto exit; + } + + /* + * Insure we have enough space + */ + if (reply_len < (*reply_ofs + 16)) { + log_error("ia_pd_nomatch_release: " + "out of space for reply packet."); + goto exit; + } + + /* + * Put our status code into the reply packet. + */ + len = store_options6(reply_data+(*reply_ofs)+16, + reply_len-(*reply_ofs)-16, + host_opt_state, packet, + required_opts_STATUS_CODE, NULL); + + /* + * Store the non-encapsulated option data for this + * IA_PD into our reply packet. Defined in RFC 3315, + * section 22.4. + */ + /* option number */ + putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD); + /* option length */ + putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12); + /* IA_PD, copied from the client */ + memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4); + /* t1 and t2, odd that we need them, but here it is */ + putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0); + putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0); + + /* + * Get ready for next IA_PD. + */ + *reply_ofs += (len + 16); + +exit: + option_state_dereference(&host_opt_state, MDL); +} + +static void +iterate_over_ia_pd(struct data_string *reply_ret, + struct packet *packet, + const struct data_string *client_id, + const struct data_string *server_id, + const char *packet_type, + void (*ia_pd_match)(), + void (*ia_pd_nomatch)()) +{ + struct data_string reply_new; + int reply_len; + struct option_state *opt_state; + struct host_decl *packet_host; + struct option_cache *ia; + struct option_cache *oc; + /* cli_enc_... variables come from the IA_PD options */ + struct data_string cli_enc_opt_data; + struct option_state *cli_enc_opt_state; + struct host_decl *host; + struct option_state *host_opt_state; + struct data_string iaprefix; + int iaprefix_is_found; + char reply_data[65536]; + int reply_ofs; + struct iaprefix *prefix; + struct ia_pd *existing_ia_pd; + int i; + struct data_string key; + u_int32_t iaid; + + /* + * Initialize to empty values, in case we have to exit early. + */ + memset(&reply_new, 0, sizeof(reply_new)); + opt_state = NULL; + memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data)); + cli_enc_opt_state = NULL; + memset(&iaprefix, 0, sizeof(iaprefix)); + host_opt_state = NULL; + prefix = NULL; + + /* + * Compute the available length for the reply. + */ + reply_len = sizeof(reply_data) - reply_ret->len; + reply_ofs = 0; + + /* + * Find the host record that matches from the packet, if any. + */ + packet_host = NULL; + if (!find_hosts_by_uid(&packet_host, + client_id->data, client_id->len, MDL)) { + packet_host = NULL; + /* + * Note: In general, we don't expect a client to provide + * enough information to match by option for these + * types of messages, but if we don't have a UID + * match we can check anyway. + */ + if (!find_hosts_by_option(&packet_host, + packet, packet->options, MDL)) { + packet_host = NULL; + } + } + + /* + * Build our option state for reply. + */ + opt_state = NULL; + if (!option_state_allocate(&opt_state, MDL)) { + log_error("iterate_over_ia_pd: no memory for option_state."); + goto exit; + } + execute_statements_in_scope(NULL, packet, NULL, NULL, + packet->options, opt_state, + &global_scope, root_group, NULL); + + /* + * Loop through the IA_PD reported by the client, and deal with + * prefixes reported as already in use. + */ + for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); + ia != NULL; ia = ia->next) { + iaprefix_is_found = 0; + + if (!get_encapsulated_IA_state(&cli_enc_opt_state, + &cli_enc_opt_data, + packet, ia, IA_PD_OFFSET)) { + goto exit; + } + + iaid = getULong(cli_enc_opt_data.data); + + oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, + D6O_IAPREFIX); + if (oc == NULL) { + /* no prefix given for this IA_PD, ignore */ + option_state_dereference(&cli_enc_opt_state, MDL); + data_string_forget(&cli_enc_opt_data, MDL); + continue; + } + + for (; oc != NULL; oc = oc->next) { + memset(&iaprefix, 0, sizeof(iaprefix)); + if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL)) { + log_error("iterate_over_ia_pd: " + "error evaluating IAPREFIX."); + goto exit; + } + + /* + * Now we need to figure out which host record matches + * this IA_PD and IAPREFIX. + * + * XXX: We don't currently track IA_PD separately, but + * we will need to do this! + */ + host = NULL; + if (!find_hosts_by_option(&host, packet, + cli_enc_opt_state, MDL)) { + if (packet_host != NULL) { + host = packet_host; + } else { + host = NULL; + } + } + while (host != NULL) { + if (host->fixed_prefix != NULL) { + struct iaddrcidrnetlist *l; + int plen = (int) getUChar(iaprefix.data + 8); + + for (l = host->fixed_prefix; l != NULL; + l = l->next) { + if (plen != l->cidrnet.bits) + continue; + if (memcmp(iaprefix.data + 9, + l->cidrnet.lo_addr.iabuf, + 16) == 0) + break; + } + if ((l != NULL) && (iaprefix.len >= 17)) + break; + } + host = host->n_ipaddr; + } + + if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) { + /* + * Find existing IA_PD. + */ + if (ia_make_key(&key, iaid, + (char *)client_id->data, + client_id->len, + MDL) != ISC_R_SUCCESS) { + log_fatal("iterate_over_ia_pd: no memory for " + "key."); + } + + existing_ia_pd = NULL; + if (ia_pd_hash_lookup(&existing_ia_pd, ia_pd_active, + (unsigned char *)key.data, + key.len, MDL)) { + /* + * Make sure this prefix is in the IA_PD. + */ + for (i = 0; + i < existing_ia_pd->num_iaprefix; + i++) { + struct iaprefix *tmp; + u_int8_t plen; + + plen = getUChar(iaprefix.data + 8); + tmp = existing_ia_pd->iaprefix[i]; + if ((tmp->plen == plen) && + (memcmp(&tmp->pref, + iaprefix.data + 9, + 16) == 0)) { + iaprefix_reference(&prefix, + tmp, MDL); + break; + } + } + } + + data_string_forget(&key, MDL); + } + + if ((host != NULL) || (prefix != NULL)) { + ia_pd_match(client_id, &iaprefix, prefix); + } else { + ia_pd_nomatch(client_id, &iaprefix, + (u_int32_t *)cli_enc_opt_data.data, + packet, reply_data, &reply_ofs, + reply_len - reply_ofs); + } + + if (prefix != NULL) { + iaprefix_dereference(&prefix, MDL); + } + + data_string_forget(&iaprefix, MDL); + } + + option_state_dereference(&cli_enc_opt_state, MDL); + data_string_forget(&cli_enc_opt_data, MDL); + } + + /* + * Return our reply to the caller. + * The IA_NA routine has already filled at least the header. + */ + reply_new.len = reply_ret->len + reply_ofs; + if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) { + log_fatal("No memory to store reply."); + } + reply_new.data = reply_new.buffer->data; + memcpy(reply_new.buffer->data, + reply_ret->buffer->data, reply_ret->len); + memcpy(reply_new.buffer->data + reply_ret->len, + reply_data, reply_ofs); + data_string_forget(reply_ret, MDL); + data_string_copy(reply_ret, &reply_new, MDL); + data_string_forget(&reply_new, MDL); + +exit: + if (prefix != NULL) { + iaprefix_dereference(&prefix, MDL); + } + if (host_opt_state != NULL) { + option_state_dereference(&host_opt_state, MDL); + } + if (iaprefix.buffer != NULL) { + data_string_forget(&iaprefix, MDL); + } + if (cli_enc_opt_state != NULL) { + option_state_dereference(&cli_enc_opt_state, MDL); + } + if (cli_enc_opt_data.buffer != NULL) { + data_string_forget(&cli_enc_opt_data, MDL); + } + if (opt_state != NULL) { + option_state_dereference(&opt_state, MDL); + } +} + /* * Release means a client is done with the addresses. */ @@ -3217,6 +5160,12 @@ dhcpv6_release(struct data_string *reply, struct packet *packet) { iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release", ia_na_match_release, ia_na_nomatch_release); + /* + * And operate on each IA_PD in this packet. + */ + iterate_over_ia_pd(reply, packet, &client_id, &server_id, "Release", + ia_pd_match_release, ia_pd_nomatch_release); + data_string_forget(&server_id, MDL); data_string_forget(&client_id, MDL); } @@ -3249,8 +5198,8 @@ dhcpv6_information_request(struct data_string *reply, struct packet *packet) { /* * Use the lease_to_client() function. This will work fine, * because the valid_client_info_req() insures that we - * don't have any IA_NA or IA_TA that would cause us to - * allocate addresses to the client. + * don't have any IA that would cause us to allocate + * resources to the client. */ lease_to_client(reply, packet, &client_id, server_id.data != NULL ? &server_id : NULL); diff --git a/server/mdb6.c b/server/mdb6.c index 383facef..3cd3b2b7 100644 --- a/server/mdb6.c +++ b/server/mdb6.c @@ -1149,6 +1149,50 @@ create_address(struct in6_addr *addr, str[8] &= ~0x02; } +/* + * Create a temporary address by a variant of RFC 4941 algo. + */ +static void +create_temporary(struct in6_addr *addr, + const struct in6_addr *net_start_addr, + const struct data_string *input) { + static u_int8_t history[8]; + static u_int32_t counter = 0; + MD5_CTX ctx; + unsigned char md[16]; + extern int dst_s_random(u_int8_t *, unsigned); + + /* + * First time/time to reseed. + * Please use a good pseudo-random generator here! + */ + if (counter == 0) { + if (dst_s_random(history, 8) != 8) + log_fatal("Random failed."); + } + + /* + * Use MD5 as recommended by RFC 4941. + */ + MD5_Init(&ctx); + MD5_Update(&ctx, history, 8UL); + MD5_Update(&ctx, input->data, input->len); + MD5_Final(md, &ctx); + + /* + * Build the address. + */ + memcpy(&addr->s6_addr[0], &net_start_addr->s6_addr[0], 8); + memcpy(&addr->s6_addr[8], md, 8); + addr->s6_addr[8] &= ~0x02; + + /* + * Save history for the next call. + */ + memcpy(history, md + 8, 8); + counter++; +} + /* Reserved Subnet Router Anycast ::0:0:0:0. */ static struct in6_addr rtany; /* Reserved Subnet Anycasts ::fdff:ffff:ffff:ff80-::fdff:ffff:ffff:ffff. */ @@ -1216,9 +1260,14 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, } /* - * Create an address + * Create an address or a temporary address. */ - create_address(&tmp, &pool->start_addr, pool->bits, &ds); + if ((pool->bits & POOL_IS_FOR_TEMP) == 0) { + create_address(&tmp, &pool->start_addr, + pool->bits, &ds); + } else { + create_temporary(&tmp, &pool->start_addr, &ds); + } /* * Avoid reserved interface IDs. @@ -1277,7 +1326,7 @@ activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, memcpy(&iaaddr->addr, &tmp, sizeof(iaaddr->addr)); /* - * Add the lease to the pool. + * Add the lease to the pool (note state is free, not active?!). */ result = add_lease6(pool, iaaddr, valid_lifetime_end_time); if (result == ISC_R_SUCCESS) { @@ -1578,6 +1627,7 @@ create_prefix(struct in6_addr *pref, for (i=0; iplen = ppool->alloc_plen; memcpy(&iapref->pref, &tmp, sizeof(iapref->pref)); /* - * Add the prefix to the pool. + * Add the prefix to the pool (note state is free, not active?!). */ result = add_prefix6(ppool, iapref, valid_lifetime_end_time); if (result == ISC_R_SUCCESS) { @@ -1976,6 +2028,27 @@ mark_address_unavailable(struct ipv6_pool *pool, const struct in6_addr *addr) { return result; } +/* + * Mark an IPv6 prefix as unavailable from a prefix pool. + * + * This is used for host entries. + */ +isc_result_t +mark_prefix_unavailable(struct ipv6_ppool *ppool, + const struct in6_addr *pref) { + struct iaprefix *dummy_iapref; + isc_result_t result; + + dummy_iapref = NULL; + result = iaprefix_allocate(&dummy_iapref, MDL); + if (result == ISC_R_SUCCESS) { + dummy_iapref->pref = *pref; + iaprefix_hash_add(ppool->prefs, &dummy_iapref->pref, + sizeof(*pref), dummy_iapref, MDL); + } + return result; +} + /* * Add a pool. */ @@ -2374,7 +2447,7 @@ isc_boolean_t ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool) { struct in6_addr tmp; - ipv6_network_portion(&tmp, addr, pool->bits); + ipv6_network_portion(&tmp, addr, pool->bits & ~POOL_IS_FOR_TEMP); if (memcmp(&tmp, &pool->start_addr, sizeof(tmp)) == 0) { return ISC_TRUE; } else { @@ -2389,7 +2462,8 @@ ipv6_addr_in_pool(const struct in6_addr *addr, const struct ipv6_pool *pool) { * initialized to NULL */ isc_result_t -find_ipv6_pool(struct ipv6_pool **pool, const struct in6_addr *addr) { +find_ipv6_pool(struct ipv6_pool **pool, int temp, + const struct in6_addr *addr) { int i; if (pool == NULL) { @@ -2402,6 +2476,12 @@ find_ipv6_pool(struct ipv6_pool **pool, const struct in6_addr *addr) { } for (i=0; ibits & POOL_IS_FOR_TEMP) == 0)) { + continue; + } + if (!temp && ((pools[i]->bits & POOL_IS_FOR_TEMP) != 0)) { + continue; + } if (ipv6_addr_in_pool(addr, pools[i])) { ipv6_pool_reference(pool, pools[i], MDL); return ISC_R_SUCCESS; @@ -2421,13 +2501,21 @@ change_leases(struct ia_na *ia, isc_result_t renew_retval; struct ipv6_pool *pool; struct in6_addr *addr; - int i; + int temp, i; retval = ISC_R_SUCCESS; + if (ia->ia_type == D6O_IA_NA) { + temp = 0; + } else if (ia->ia_type == D6O_IA_TA) { + temp = 1; + } else { + log_error("IA without type."); + return ISC_R_INVALIDARG; + } for (i=0; inum_iaaddr; i++) { pool = NULL; addr = &ia->iaaddr[i]->addr; - if (find_ipv6_pool(&pool, addr) == ISC_R_SUCCESS) { + if (find_ipv6_pool(&pool, temp, addr) == ISC_R_SUCCESS) { renew_retval = change_func(pool, ia->iaaddr[i]); if (renew_retval != ISC_R_SUCCESS) { retval = renew_retval; @@ -2655,7 +2743,11 @@ mark_hosts_unavailable_support(const void *name, unsigned len, void *value) { * sit in any pool.) */ p = NULL; - if (find_ipv6_pool(&p, &addr) == ISC_R_SUCCESS) { + if (find_ipv6_pool(&p, 0, &addr) == ISC_R_SUCCESS) { + mark_address_unavailable(p, &addr); + ipv6_pool_dereference(&p, MDL); + } + if (find_ipv6_pool(&p, 1, &addr) == ISC_R_SUCCESS) { mark_address_unavailable(p, &addr); ipv6_pool_dereference(&p, MDL); } @@ -2668,6 +2760,56 @@ mark_hosts_unavailable(void) { hash_foreach(host_name_hash, mark_hosts_unavailable_support); } +static isc_result_t +mark_phosts_unavailable_support(const void *name, unsigned len, void *value) { + struct host_decl *h; + struct iaddrcidrnetlist *l; + struct in6_addr pref; + struct ipv6_ppool *p; + + h = (struct host_decl *)value; + + /* + * If the host has no prefix, we don't need to mark anything. + */ + if (h->fixed_prefix == NULL) { + return ISC_R_SUCCESS; + } + + /* + * Get the fixed prefixes. + */ + for (l = h->fixed_prefix; l != NULL; l = l->next) { + if (l->cidrnet.lo_addr.len != 16) { + continue; + } + memcpy(&pref, l->cidrnet.lo_addr.iabuf, 16); + + /* + * Find the pool holding this host, and mark the prefix. + * (I suppose it is arguably valid to have a host that does not + * sit in any pool.) + */ + p = NULL; + if (find_ipv6_ppool(&p, &pref) != ISC_R_SUCCESS) { + continue; + } + if (l->cidrnet.bits != (int) p->alloc_plen) { + ipv6_ppool_dereference(&p, MDL); + continue; + } + mark_prefix_unavailable(p, &pref); + ipv6_ppool_dereference(&p, MDL); + } + + return ISC_R_SUCCESS; +} + +void +mark_phosts_unavailable(void) { + hash_foreach(host_name_hash, mark_phosts_unavailable_support); +} + void mark_interfaces_unavailable(void) { struct interface_info *ip; @@ -2678,7 +2820,13 @@ mark_interfaces_unavailable(void) { while (ip != NULL) { for (i=0; iv6address_count; i++) { p = NULL; - if (find_ipv6_pool(&p, &ip->v6addresses[i]) + if (find_ipv6_pool(&p, 0, &ip->v6addresses[i]) + == ISC_R_SUCCESS) { + mark_address_unavailable(p, + &ip->v6addresses[i]); + ipv6_pool_dereference(&p, MDL); + } + if (find_ipv6_pool(&p, 1, &ip->v6addresses[i]) == ISC_R_SUCCESS) { mark_address_unavailable(p, &ip->v6addresses[i]); @@ -3006,8 +3154,8 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } - if (pool->num_active != 1) { - printf("ERROR: bad num_active %s:%d\n", MDL); + if (pool->num_inactive != 1) { + printf("ERROR: bad num_inactive %s:%d\n", MDL); return 1; } if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { @@ -3058,6 +3206,10 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } + if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { + printf("ERROR: renew_lease6() %s:%d\n", MDL); + return 1; + } if (pool->num_active != 1) { printf("ERROR: bad num_active %s:%d\n", MDL); return 1; @@ -3079,6 +3231,10 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } + if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { + printf("ERROR: renew_lease6() %s:%d\n", MDL); + return 1; + } if (pool->num_active != 1) { printf("ERROR: bad num_active %s:%d\n", MDL); return 1; @@ -3155,6 +3311,10 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } + if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { + printf("ERROR: renew_lease6() %s:%d\n", MDL); + return 1; + } if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) { printf("ERROR: iaaddr_dereference() %s:%d\n", MDL); return 1; @@ -3211,6 +3371,7 @@ main(int argc, char *argv[]) { * Test 8: small pool */ pool = NULL; + addr.s6_addr[14] = 0x81; if (ipv6_pool_allocate(&pool, &addr, 127, MDL) != ISC_R_SUCCESS) { printf("ERROR: ipv6_pool_allocate() %s:%d\n", MDL); return 1; @@ -3220,6 +3381,10 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } + if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { + printf("ERROR: renew_lease6() %s:%d\n", MDL); + return 1; + } if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) { printf("ERROR: iaaddr_dereference() %s:%d\n", MDL); return 1; @@ -3229,6 +3394,10 @@ main(int argc, char *argv[]) { printf("ERROR: activate_lease6() %s:%d\n", MDL); return 1; } + if (renew_lease6(pool, iaaddr) != ISC_R_SUCCESS) { + printf("ERROR: renew_lease6() %s:%d\n", MDL); + return 1; + } if (iaaddr_dereference(&iaaddr, MDL) != ISC_R_SUCCESS) { printf("ERROR: iaaddr_dereference() %s:%d\n", MDL); return 1; @@ -3242,6 +3411,7 @@ main(int argc, char *argv[]) { printf("ERROR: ipv6_pool_dereference() %s:%d\n", MDL); return 1; } + addr.s6_addr[14] = 0; /* * Test 9: functions across all pools @@ -3260,7 +3430,7 @@ main(int argc, char *argv[]) { return 1; } pool = NULL; - if (find_ipv6_pool(&pool, &addr) != ISC_R_SUCCESS) { + if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_SUCCESS) { printf("ERROR: find_ipv6_pool() %s:%d\n", MDL); return 1; } @@ -3270,7 +3440,7 @@ main(int argc, char *argv[]) { } inet_pton(AF_INET6, "1:2:3:4:ffff:ffff:ffff:ffff", &addr); pool = NULL; - if (find_ipv6_pool(&pool, &addr) != ISC_R_SUCCESS) { + if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_SUCCESS) { printf("ERROR: find_ipv6_pool() %s:%d\n", MDL); return 1; } @@ -3280,13 +3450,13 @@ main(int argc, char *argv[]) { } inet_pton(AF_INET6, "1:2:3:5::", &addr); pool = NULL; - if (find_ipv6_pool(&pool, &addr) != ISC_R_NOTFOUND) { + if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_NOTFOUND) { printf("ERROR: find_ipv6_pool() %s:%d\n", MDL); return 1; } inet_pton(AF_INET6, "1:2:3:3:ffff:ffff:ffff:ffff", &addr); pool = NULL; - if (find_ipv6_pool(&pool, &addr) != ISC_R_NOTFOUND) { + if (find_ipv6_pool(&pool, 0, &addr) != ISC_R_NOTFOUND) { printf("ERROR: find_ipv6_pool() %s:%d\n", MDL); return 1; } @@ -3301,11 +3471,14 @@ main(int argc, char *argv[]) { { struct in6_addr r; struct data_string ds; + u_char data[16]; char buf[64]; int i, j; + memset(&ds, 0, sizeof(ds)); + memset(data, 0xaa, sizeof(data)); ds.len = 16; - ds.data = &addr; + ds.data = data; inet_pton(AF_INET6, "3ffe:501:ffff:100::", &addr); for (i = 32; i < 42; i++) diff --git a/server/stables.c b/server/stables.c index eaa338ae..abb96479 100644 --- a/server/stables.c +++ b/server/stables.c @@ -238,7 +238,8 @@ static struct option server_options[] = { { "dhcpv6-lease-file-name", "t", &server_universe, 54, 1 }, { "dhcpv6-pid-file-name", "t", &server_universe, 55, 1 }, { "limit-addrs-per-ia", "L", &server_universe, 56, 1 }, - { "delayed-ack", "S", &server_universe, 57, 1 }, + { "limit-prefs-per-ia", "L", &server_universe, 57, 1 }, + { "delayed-ack", "S", &server_universe, 58, 1 }, { NULL, NULL, NULL, 0, 0 } };