diff options
-rw-r--r-- | client/clparse.c | 394 | ||||
-rw-r--r-- | client/dhc6.c | 447 | ||||
-rw-r--r-- | client/dhclient.c | 40 | ||||
-rw-r--r-- | common/conflex.c | 6 | ||||
-rw-r--r-- | common/parse.c | 41 | ||||
-rw-r--r-- | includes/dhcpd.h | 119 | ||||
-rw-r--r-- | includes/dhctoken.h | 29 | ||||
-rw-r--r-- | server/confpars.c | 620 | ||||
-rw-r--r-- | server/db.c | 127 | ||||
-rw-r--r-- | server/dhcpd.c | 12 | ||||
-rw-r--r-- | server/dhcpv6.c | 126 | ||||
-rw-r--r-- | server/mdb6.c | 1414 |
12 files changed, 3151 insertions, 224 deletions
diff --git a/client/clparse.c b/client/clparse.c index a4921f57..6adad35f 100644 --- a/client/clparse.c +++ b/client/clparse.c @@ -43,8 +43,11 @@ struct option *default_requested_options[NUM_DEFAULT_REQUESTED_OPTS + 1]; static void parse_client_default_duid(struct parse *cfile); static void parse_client6_lease_statement(struct parse *cfile); #ifdef DHCPv6 -static struct dhc6_ia *parse_client6_ia_statement(struct parse *cfile); +static struct dhc6_ia *parse_client6_ia_na_statement(struct parse *cfile); +static struct dhc6_ia *parse_client6_ia_ta_statement(struct parse *cfile); +static struct dhc6_ia *parse_client6_ia_pd_statement(struct parse *cfile); static struct dhc6_addr *parse_client6_iaaddr_statement(struct parse *cfile); +static struct dhc6_addr *parse_client6_iaprefix_statement(struct parse *cfile); #endif /* DHCPv6 */ /* client-conf-file :== client-declarations END_OF_FILE @@ -1292,7 +1295,29 @@ parse_client6_lease_statement(struct parse *cfile) switch(token) { case IA_NA: - *ia = parse_client6_ia_statement(cfile); + *ia = parse_client6_ia_na_statement(cfile); + if (*ia != NULL) { + ia = &(*ia)->next; + has_ia = 1; + } + + no_semi = 1; + + break; + + case IA_TA: + *ia = parse_client6_ia_ta_statement(cfile); + if (*ia != NULL) { + ia = &(*ia)->next; + has_ia = 1; + } + + no_semi = 1; + + break; + + case IA_PD: + *ia = parse_client6_ia_pd_statement(cfile); if (*ia != NULL) { ia = &(*ia)->next; has_ia = 1; @@ -1461,7 +1486,7 @@ parse_client6_lease_statement(struct parse *cfile) */ #ifdef DHCPv6 static struct dhc6_ia * -parse_client6_ia_statement(struct parse *cfile) +parse_client6_ia_na_statement(struct parse *cfile) { struct data_string id; struct option_cache *oc = NULL; @@ -1476,6 +1501,7 @@ parse_client6_ia_statement(struct parse *cfile) skip_to_semi(cfile); return NULL; } + ia->ia_type = D6O_IA_NA; /* Get IAID. */ memset(&id, 0, sizeof(id)); @@ -1593,6 +1619,260 @@ parse_client6_ia_statement(struct parse *cfile) } #endif /* DHCPv6 */ +/* Parse an ia_ta object from the client lease. + */ +#ifdef DHCPv6 +static struct dhc6_ia * +parse_client6_ia_ta_statement(struct parse *cfile) +{ + struct data_string id; + struct option_cache *oc = NULL; + struct dhc6_ia *ia; + struct dhc6_addr **addr; + const char *val; + int token, no_semi; + + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + parse_warn(cfile, "Out of memory allocating IA_TA state."); + skip_to_semi(cfile); + return NULL; + } + ia->ia_type = D6O_IA_TA; + + /* Get IAID. */ + memset(&id, 0, sizeof(id)); + if (parse_cshl(&id, cfile)) { + if (id.len == 4) + memcpy(ia->iaid, id.data, 4); + else { + parse_warn(cfile, "Expecting IAID of length 4, got %d.", + id.len); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + data_string_forget(&id, MDL); + } else { + parse_warn(cfile, "Expecting IAID."); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + + token = next_token(NULL, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "Expecting open curly brace."); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + + option_state_allocate(&ia->options, MDL); + if (ia->options == NULL) { + parse_warn(cfile, "Unable to allocate option state."); + skip_to_rbrace(cfile, 1); + dfree(ia, MDL); + return NULL; + } + + addr = &ia->addrs; + token = next_token(&val, NULL, cfile); + while (token != RBRACE) { + no_semi = 0; + + switch (token) { + case STARTS: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + ia->starts = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + /* No RENEW or REBIND */ + + case IAADDR: + *addr = parse_client6_iaaddr_statement(cfile); + + if (*addr != NULL) + addr = &(*addr)->next; + + no_semi = 1; + + break; + + case OPTION: + if (parse_option_decl(&oc, cfile)) { + save_option(oc->option->universe, + ia->options, oc); + option_cache_dereference(&oc, MDL); + } + no_semi = 1; + break; + + default: + parse_warn(cfile, "Unexpected token."); + no_semi = 1; + skip_to_semi(cfile); + break; + } + + if (!no_semi) + parse_semi(cfile); + + token = next_token(&val, NULL, cfile); + + if (token == END_OF_FILE) { + parse_warn(cfile, "Unexpected end of file."); + break; + } + } + + return ia; +} +#endif /* DHCPv6 */ + +/* Parse an ia_pd object from the client lease. + */ +#ifdef DHCPv6 +static struct dhc6_ia * +parse_client6_ia_pd_statement(struct parse *cfile) +{ + struct data_string id; + struct option_cache *oc = NULL; + struct dhc6_ia *ia; + struct dhc6_addr **pref; + const char *val; + int token, no_semi; + + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + parse_warn(cfile, "Out of memory allocating IA_PD state."); + skip_to_semi(cfile); + return NULL; + } + ia->ia_type = D6O_IA_PD; + + /* Get IAID. */ + memset(&id, 0, sizeof(id)); + if (parse_cshl(&id, cfile)) { + if (id.len == 4) + memcpy(ia->iaid, id.data, 4); + else { + parse_warn(cfile, "Expecting IAID of length 4, got %d.", + id.len); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + data_string_forget(&id, MDL); + } else { + parse_warn(cfile, "Expecting IAID."); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + + token = next_token(NULL, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "Expecting open curly brace."); + skip_to_semi(cfile); + dfree(ia, MDL); + return NULL; + } + + option_state_allocate(&ia->options, MDL); + if (ia->options == NULL) { + parse_warn(cfile, "Unable to allocate option state."); + skip_to_rbrace(cfile, 1); + dfree(ia, MDL); + return NULL; + } + + pref = &ia->addrs; + token = next_token(&val, NULL, cfile); + while (token != RBRACE) { + no_semi = 0; + + switch (token) { + case STARTS: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + ia->starts = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case RENEW: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + ia->renew = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case REBIND: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + ia->rebind = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case IAPREFIX: + *pref = parse_client6_iaprefix_statement(cfile); + + if (*pref != NULL) + pref = &(*pref)->next; + + no_semi = 1; + + break; + + case OPTION: + if (parse_option_decl(&oc, cfile)) { + save_option(oc->option->universe, + ia->options, oc); + option_cache_dereference(&oc, MDL); + } + no_semi = 1; + break; + + default: + parse_warn(cfile, "Unexpected token."); + no_semi = 1; + skip_to_semi(cfile); + break; + } + + if (!no_semi) + parse_semi(cfile); + + token = next_token(&val, NULL, cfile); + + if (token == END_OF_FILE) { + parse_warn(cfile, "Unexpected end of file."); + break; + } + } + + return ia; +} +#endif /* DHCPv6 */ + /* Parse an iaaddr {} structure. */ #ifdef DHCPv6 static struct dhc6_addr * @@ -1701,6 +1981,114 @@ parse_client6_iaaddr_statement(struct parse *cfile) } #endif /* DHCPv6 */ +/* Parse an iaprefix {} structure. */ +#ifdef DHCPv6 +static struct dhc6_addr * +parse_client6_iaprefix_statement(struct parse *cfile) +{ + struct option_cache *oc = NULL; + struct dhc6_addr *pref; + const char *val; + int token, no_semi; + + pref = dmalloc(sizeof(*pref), MDL); + if (pref == NULL) { + parse_warn(cfile, "Unable to allocate IAPREFIX state."); + skip_to_semi(cfile); + return NULL; + } + + /* Get IP prefix. */ + if (!parse_ip6_prefix(cfile, &pref->address, &pref->plen)) { + skip_to_semi(cfile); + dfree(pref, MDL); + return NULL; + } + + token = next_token(NULL, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "Expecting open curly bracket."); + skip_to_semi(cfile); + dfree(pref, MDL); + return NULL; + } + + option_state_allocate(&pref->options, MDL); + if (pref->options == NULL) { + parse_warn(cfile, "Unable to allocate option state."); + skip_to_semi(cfile); + dfree(pref, MDL); + return NULL; + } + + token = next_token(&val, NULL, cfile); + while (token != RBRACE) { + no_semi = 0; + + switch (token) { + case STARTS: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + pref->starts = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case PREFERRED_LIFE: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + pref->preferred_life = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case MAX_LIFE: + token = next_token(&val, NULL, cfile); + if (token == NUMBER) { + pref->max_life = atoi(val); + } else { + parse_warn(cfile, "Expecting a number."); + skip_to_semi(cfile); + no_semi = 1; + } + break; + + case OPTION: + if (parse_option_decl(&oc, cfile)) { + save_option(oc->option->universe, + pref->options, oc); + option_cache_dereference(&oc, MDL); + } + no_semi = 1; + break; + + default: + parse_warn(cfile, "Unexpected token."); + skip_to_rbrace(cfile, 1); + no_semi = 1; + break; + } + + if (!no_semi) + parse_semi(cfile); + + token = next_token(&val, NULL, cfile); + if (token == END_OF_FILE) { + parse_warn(cfile, "Unexpected end of file."); + break; + } + } + + return pref; +} +#endif /* DHCPv6 */ + void parse_string_list (cfile, lp, multiple) struct parse *cfile; struct string_list **lp; diff --git a/client/dhc6.c b/client/dhc6.c index 8bf89b09..c84a8a53 100644 --- a/client/dhc6.c +++ b/client/dhc6.c @@ -34,7 +34,10 @@ struct sockaddr_in6 DHCPv6DestAddr; struct option *clientid_option = NULL; struct option *elapsed_option = NULL; struct option *ia_na_option = NULL; +struct option *ia_ta_option = NULL; +struct option *ia_pd_option = NULL; struct option *iaaddr_option = NULL; +struct option *iaprefix_option = NULL; struct option *oro_option = NULL; static struct dhc6_lease *dhc6_dup_lease(struct dhc6_lease *lease, @@ -47,20 +50,29 @@ static void dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line); static isc_result_t dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet, struct option_state *options); +static isc_result_t dhc6_parse_ia_ta(struct dhc6_ia **pia, + struct packet *packet, + struct option_state *options); +static isc_result_t dhc6_parse_ia_pd(struct dhc6_ia **pia, + struct packet *packet, + struct option_state *options); static isc_result_t dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet, struct option_state *options); -static struct dhc6_ia *find_ia(struct dhc6_ia *head, const char *id); +static isc_result_t dhc6_parse_prefs(struct dhc6_addr **ppref, + struct packet *packet, + struct option_state *options); +static struct dhc6_ia *find_ia_na(struct dhc6_ia *head, const char *id); static struct dhc6_addr *find_addr(struct dhc6_addr *head, struct iaddr *address); void init_handler(struct packet *packet, struct client_state *client); void do_init6(void *input); void do_confirm6(void *input); void reply_handler(struct packet *packet, struct client_state *client); -static isc_result_t dhc6_add_ia(struct client_state *client, - struct data_string *packet, - struct dhc6_lease *lease, - u_int8_t message); +static isc_result_t dhc6_add_ia_na(struct client_state *client, + struct data_string *packet, + struct dhc6_lease *lease, + u_int8_t message); static void dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst); void do_select6(void *input); void do_refresh6(void *input); @@ -177,11 +189,27 @@ dhcpv6_client_assignments(void) &code, 0, MDL)) log_fatal("Unable to find the IA_NA option definition."); + code = D6O_IA_TA; + if (!option_code_hash_lookup(&ia_ta_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IA_TA option definition."); + + code = D6O_IA_PD; + if (!option_code_hash_lookup(&ia_pd_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IA_PD option definition."); + code = D6O_IAADDR; if (!option_code_hash_lookup(&iaaddr_option, dhcpv6_universe.code_hash, &code, 0, MDL)) log_fatal("Unable to find the IAADDR option definition."); + code = D6O_IAPREFIX; + if (!option_code_hash_lookup(&iaprefix_option, + dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IAPREFIX option definition."); + code = D6O_ORO; if (!option_code_hash_lookup(&oro_option, dhcpv6_universe.code_hash, &code, 0, MDL)) @@ -409,6 +437,7 @@ dhc6_dup_ia(struct dhc6_ia *ia, const char *file, int line) memcpy(copy->iaid, ia->iaid, sizeof(copy->iaid)); + copy->ia_type = ia->ia_type; copy->starts = ia->starts; copy->renew = ia->renew; copy->rebind = ia->rebind; @@ -432,7 +461,7 @@ dhc6_dup_ia(struct dhc6_ia *ia, const char *file, int line) return copy; } -/* Duplicate an IAADDR structure. +/* Duplicate an IAADDR or IAPREFIX structure. */ static struct dhc6_addr * dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line) @@ -446,6 +475,7 @@ dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line) memcpy(©->address, &addr->address, sizeof(copy->address)); + copy->plen = addr->plen; copy->flags = addr->flags; copy->starts = addr->starts; copy->preferred_life = addr->preferred_life; @@ -459,7 +489,7 @@ dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line) } /* Form a DHCPv6 lease structure based upon packet contents. Creates and - * populates IA's and any IAADDR's they contain. + * populates IA's and any IAADDR/IAPREFIX's they contain. */ static struct dhc6_lease * dhc6_leaseify(struct packet *packet) @@ -508,6 +538,24 @@ dhc6_leaseify(struct packet *packet) dhc6_lease_destroy(&lease, MDL); return NULL; } + /* Dig into recursive DHCPv6 pockets for IA_TA and contained IAADDR + * options. + */ + if (dhc6_parse_ia_ta(&lease->bindings, packet, + lease->options) != ISC_R_SUCCESS) { + /* Error conditions are logged by the caller. */ + dhc6_lease_destroy(&lease, MDL); + return NULL; + } + /* Dig into recursive DHCPv6 pockets for IA_PD and contained IAPREFIX + * options. + */ + if (dhc6_parse_ia_pd(&lease->bindings, packet, + lease->options) != ISC_R_SUCCESS) { + /* Error conditions are logged by the caller. */ + dhc6_lease_destroy(&lease, MDL); + return NULL; + } /* This is last because in the future we may want to make a different * key based upon additional information from the packet (we may need @@ -555,6 +603,7 @@ dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet, &global_scope, oc, MDL) && ds.len >= 12) { memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_NA; ia->starts = cur_time; ia->renew = getULong(ds.data + 4); ia->rebind = getULong(ds.data + 8); @@ -636,6 +685,188 @@ dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet, return ISC_R_SUCCESS; } +static isc_result_t +dhc6_parse_ia_ta(struct dhc6_ia **pia, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct dhc6_ia *ia; + struct option_cache *oc; + isc_result_t result; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IA_TA); + for ( ; oc != NULL ; oc = oc->next) { + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + log_error("Out of memory allocating IA_TA structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, + &global_scope, oc, MDL) && + ds.len >= 4) { + memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_TA; + ia->starts = cur_time; + + log_debug("RCV: X-- IA_TA %s", + print_hex_1(4, ia->iaid, 59)); + /* XXX: This should be the printed time I think. */ + log_debug("RCV: | X-- starts %u", + (unsigned)ia->starts); + + if (ds.len > 4) { + log_debug("RCV: | X-- [Options]"); + + if (!option_state_allocate(&ia->options, + MDL)) { + log_error("Out of memory allocating " + "IA option state."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(ia->options, + ds.data + 4, + ds.len - 4, + &dhcpv6_universe)) { + log_error("Corrupt IA_TA options."); + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_BADPARSE; + } + } + data_string_forget(&ds, MDL); + + if (ia->options != NULL) { + result = dhc6_parse_addrs(&ia->addrs, packet, + ia->options); + if (result != ISC_R_SUCCESS) { + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + return result; + } + } + + *pia = ia; + pia = &ia->next; + } else { + log_error("Invalid IA_TA option cache."); + dfree(ia, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + + return ISC_R_SUCCESS; +} + +static isc_result_t +dhc6_parse_ia_pd(struct dhc6_ia **pia, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct dhc6_ia *ia; + struct option_cache *oc; + isc_result_t result; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IA_PD); + for ( ; oc != NULL ; oc = oc->next) { + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + log_error("Out of memory allocating IA_PD structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, + &global_scope, oc, MDL) && + ds.len >= 12) { + memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_PD; + ia->starts = cur_time; + ia->renew = getULong(ds.data + 4); + ia->rebind = getULong(ds.data + 8); + + log_debug("RCV: X-- IA_PD %s", + print_hex_1(4, ia->iaid, 59)); + /* XXX: This should be the printed time I think. */ + log_debug("RCV: | X-- starts %u", + (unsigned)ia->starts); + log_debug("RCV: | X-- t1 - renew +%u", ia->renew); + log_debug("RCV: | X-- t2 - rebind +%u", ia->rebind); + + /* RFC3315 section 22.4, discard IA_PD's that + * have t1 greater than t2, and both not zero. + * Since RFC3315 defines this behaviour, it is not + * an error - just normal operation. + */ + if ((ia->renew > 0) && (ia->rebind > 0) && + (ia->renew > ia->rebind)) { + log_debug("RCV: | !-- INVALID renew/rebind " + "times, IA_PD discarded."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + continue; + } + + if (ds.len > 12) { + log_debug("RCV: | X-- [Options]"); + + if (!option_state_allocate(&ia->options, + MDL)) { + log_error("Out of memory allocating " + "IA option state."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(ia->options, + ds.data + 12, + ds.len - 12, + &dhcpv6_universe)) { + log_error("Corrupt IA_PD options."); + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_BADPARSE; + } + } + data_string_forget(&ds, MDL); + + if (ia->options != NULL) { + result = dhc6_parse_prefs(&ia->addrs, packet, + ia->options); + if (result != ISC_R_SUCCESS) { + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + return result; + } + } + + *pia = ia; + pia = &ia->next; + } else { + log_error("Invalid IA_PD option cache."); + dfree(ia, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + + return ISC_R_SUCCESS; +} + static isc_result_t dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet, @@ -730,6 +961,109 @@ dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet, return ISC_R_SUCCESS; } +static isc_result_t +dhc6_parse_prefs(struct dhc6_addr **ppref, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct option_cache *oc; + struct dhc6_addr *pref; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IAPREFIX); + for ( ; oc != NULL ; oc = oc->next) { + pref = dmalloc(sizeof(*pref), MDL); + if (pref == NULL) { + log_error("Out of memory allocating " + "prefix structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, &global_scope, + oc, MDL) && + (ds.len >= 25)) { + + pref->plen = getUChar(ds.data); + pref->address.len = 16; + memcpy(pref->address.iabuf, ds.data + 1, 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); + log_debug("RCV: | | | X-- Preferred lifetime %u.", + pref->preferred_life); + log_debug("RCV: | | | X-- Max lifetime %u.", + pref->max_life); + + /* Sanity check over the prefix length */ + if ((pref->plen < 4) || (pref->plen > 128)) { + log_debug("RCV: | | | !-- INVALID prefix " + "length, IAPREFIX discarded. " + "Check your server configuration."); + dfree(pref, MDL); + data_string_forget(&ds, MDL); + continue; + } + /* RFC 3315 section 22.6 says we must discard + * prefixes whose pref is later than valid. + */ + if ((pref->preferred_life > pref->max_life)) { + log_debug("RCV: | | | !-- INVALID lifetimes, " + "IAPREFIX discarded. Check your " + "server configuration."); + dfree(pref, MDL); + data_string_forget(&ds, MDL); + continue; + } + + /* Fortunately this is the last recursion in the + * protocol. + */ + if (ds.len > 25) { + if (!option_state_allocate(&pref->options, + MDL)) { + log_error("Out of memory allocating " + "IAPREFIX option state."); + dfree(pref, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(pref->options, + ds.data + 25, + ds.len - 25, + &dhcpv6_universe)) { + log_error("Corrupt IAPREFIX options."); + option_state_dereference(&pref->options, + MDL); + dfree(pref, MDL); + data_string_forget(&ds, MDL); + return ISC_R_BADPARSE; + } + } + + if (pref->options != NULL) + log_debug("RCV: | | | X-- " + "[Options]"); + + data_string_forget(&ds, MDL); + + *ppref = pref; + ppref = &pref->next; + } else { + log_error("Invalid IAPREFIX option cache."); + dfree(pref, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + + return ISC_R_SUCCESS; +} + /* Clean up a lease object, deallocate all its parts, and set it to NULL. */ void dhc6_lease_destroy(struct dhc6_lease **src, const char *file, int line) @@ -1032,8 +1366,8 @@ do_init6(void *input) log_debug("XMT: | X-- Request rebind in +%u", (unsigned)t2); if ((client->active_lease != NULL) && - ((old_ia = find_ia(client->active_lease->bindings, - (char *)ia.buffer->data)) != NULL)) { + ((old_ia = find_ia_na(client->active_lease->bindings, + (char *)ia.buffer->data)) != NULL)) { /* For each address in the old IA, request a binding. */ memset(&addr, 0, sizeof(addr)); for (old_addr = old_ia->addrs ; old_addr != NULL ; @@ -1174,8 +1508,8 @@ do_confirm6(void *input) &dhcpv6_universe); /* Append IA's. */ - if (dhc6_add_ia(client, &ds, client->active_lease, - DHCPV6_CONFIRM) != ISC_R_SUCCESS) { + if (dhc6_add_ia_na(client, &ds, client->active_lease, + DHCPV6_CONFIRM) != ISC_R_SUCCESS) { data_string_forget(&ds, MDL); return; } @@ -1295,8 +1629,8 @@ do_release6(void *input) &dhcpv6_universe); /* Append IA's. */ - if (dhc6_add_ia(client, &ds, client->active_lease, - DHCPV6_RELEASE) != ISC_R_SUCCESS) { + if (dhc6_add_ia_na(client, &ds, client->active_lease, + DHCPV6_RELEASE) != ISC_R_SUCCESS) { data_string_forget(&ds, MDL); return; } @@ -1453,15 +1787,32 @@ dhc6_check_advertise(struct dhc6_lease *lease) isc_result_t rval = ISC_R_SUCCESS; int have_addrs = ISC_FALSE; unsigned code; + const char *scope; rval = dhc6_check_status(rval, lease->options, "message", &code); for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { - rval = dhc6_check_status(rval, ia->options, "IA_NA", &code); + switch (ia->ia_type) { + case D6O_IA_NA: + default: + scope = "IA_NA"; + break; + case D6O_IA_TA: + scope = "IA_TA"; + break; + case D6O_IA_PD: + scope = "IA_PD"; + break; + } + rval = dhc6_check_status(rval, ia->options, scope, &code); for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (ia->ia_type != D6O_IA_PD) + scope = "IAADDR"; + else + scope = "IAPREFIX"; rval = dhc6_check_status(rval, addr->options, - "IAADDR", &code); + scope, &code); have_addrs = ISC_TRUE; } } @@ -1695,6 +2046,7 @@ dhc6_check_reply(struct client_state *client, struct dhc6_lease *new) struct dhc6_addr *addr; isc_result_t rval = ISC_R_SUCCESS; unsigned code; + const char *scope; int nscore, sscore; if ((client == NULL) || (new == NULL)) @@ -1725,15 +2077,31 @@ dhc6_check_reply(struct client_state *client, struct dhc6_lease *new) return ISC_R_CANCELED; for (ia = new->bindings ; ia != NULL ; ia = ia->next) { - rval = dhc6_check_status(rval, ia->options, "IA_NA", - &code); + switch (ia->ia_type) { + case D6O_IA_NA: + default: + scope = "IA_NA"; + break; + case D6O_IA_TA: + scope = "IA_TA"; + break; + case D6O_IA_PD: + scope = "IA_PD"; + break; + } + rval = dhc6_check_status(rval, ia->options, + scope, &code); if (action(client, rval, code)) return ISC_R_CANCELED; for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (ia->ia_type != D6O_IA_PD) + scope = "IAADDR"; + else + scope = "IAPREFIX"; rval = dhc6_check_status(rval, addr->options, - "IAADDR", &code); + scope, &code); if (action(client, rval, code)) return ISC_R_CANCELED; } @@ -2098,8 +2466,9 @@ do_select6(void *input) NULL, client->sent_options, &global_scope, &dhcpv6_universe); - /* Now append any IA_NA's, and within them any IAADDRs. */ - if (dhc6_add_ia(client, &ds, lease, DHCPV6_REQUEST) != ISC_R_SUCCESS) { + /* Now append any IA's, and within them any IAADDR/IAPREFIXs. */ + if (dhc6_add_ia_na(client, &ds, lease, + DHCPV6_REQUEST) != ISC_R_SUCCESS) { data_string_forget(&ds, MDL); return; } @@ -2122,12 +2491,12 @@ do_select6(void *input) dhc6_retrans_advance(client); } -/* For each IA in the lease, for each address in the IA, append that - * information onto the packet-so-far. +/* For each IA_NA in the lease, for each address in the IA_NA, + * append that information onto the packet-so-far. */ static isc_result_t -dhc6_add_ia(struct client_state *client, struct data_string *packet, - struct dhc6_lease *lease, u_int8_t message) +dhc6_add_ia_na(struct client_state *client, struct data_string *packet, + struct dhc6_lease *lease, u_int8_t message) { struct data_string iads; struct data_string addrds; @@ -2141,6 +2510,9 @@ dhc6_add_ia(struct client_state *client, struct data_string *packet, for (ia = lease->bindings; ia != NULL && rval == ISC_R_SUCCESS; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; + if (!buffer_allocate(&iads.buffer, 12, MDL)) { log_error("Unable to allocate memory for IA_NA."); rval = ISC_R_NOMEMORY; @@ -2469,6 +2841,9 @@ dhc6_check_times(struct client_state *client) for(ia = lease->bindings ; ia != NULL ; ia = ia->next) { TIME this_ia_lo_expire, this_ia_hi_expire, use_expire; + if (ia->ia_type != D6O_IA_NA) + continue; + this_ia_lo_expire = MAX_TIME; this_ia_hi_expire = 0; @@ -2632,11 +3007,13 @@ dhc6_check_times(struct client_state *client) /* In a given IA chain, find the IA with the same 'iaid'. */ static struct dhc6_ia * -find_ia(struct dhc6_ia *head, const char *id) +find_ia_na(struct dhc6_ia *head, const char *id) { struct dhc6_ia *ia; for (ia = head ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; if (memcmp(ia->iaid, id, 4) == 0) return ia; } @@ -2676,7 +3053,9 @@ dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst) return; for (sia = src->bindings ; sia != NULL ; sia = sia->next) { - dia = find_ia(dst->bindings, (char *)sia->iaid); + if (sia->ia_type != D6O_IA_NA) + continue; + dia = find_ia_na(dst->bindings, (char *)sia->iaid); if (dia == NULL) { tia = dhc6_dup_ia(sia, MDL); @@ -2774,8 +3153,11 @@ start_bound(struct client_state *client) oldia = NULL; for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; + if (old != NULL) - oldia = find_ia(old->bindings, (char *)ia->iaid); + oldia = find_ia_na(old->bindings, (char *)ia->iaid); else oldia = NULL; @@ -2983,8 +3365,8 @@ do_refresh6(void *input) &dhcpv6_universe); /* Append IA's */ - if (dhc6_add_ia(client, &ds, lease, - client->refresh_type) != ISC_R_SUCCESS) { + if (dhc6_add_ia_na(client, &ds, lease, + client->refresh_type) != ISC_R_SUCCESS) { data_string_forget(&ds, MDL); return; } @@ -3062,6 +3444,8 @@ do_depref(void *input) return; for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { if (addr->flags & DHC6_ADDR_DEPREFFED) continue; @@ -3110,6 +3494,8 @@ do_expire(void *input) return; for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { if (addr->flags & DHC6_ADDR_EXPIRED) continue; @@ -3174,6 +3560,9 @@ unconfigure6(struct client_state *client, const char *reason) return; for (ia = client->active_lease->bindings ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { script_init(client, reason, NULL); dhc6_marshall_values("old_", client, diff --git a/client/dhclient.c b/client/dhclient.c index 6b0bd0a4..c2276875 100644 --- a/client/dhclient.c +++ b/client/dhclient.c @@ -2487,6 +2487,7 @@ write_client6_lease(struct client_state *client, struct dhc6_lease *lease, struct dhc6_ia *ia; struct dhc6_addr *addr; int stat; + const char *ianame; /* This should include the current lease. */ if (!rewrite && (leases_written++ > 20)) { @@ -2516,21 +2517,44 @@ write_client6_lease(struct client_state *client, struct dhc6_lease *lease, return ISC_R_IOERROR; for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { - stat = fprintf(leaseFile, " ia-na %s {\n", - print_hex_1(4, ia->iaid, 12)); + switch (ia->ia_type) { + case D6O_IA_NA: + default: + ianame = "ia-na"; + break; + case D6O_IA_TA: + ianame = "ia-ta"; + break; + case D6O_IA_PD: + ianame = "ia-pd"; + break; + } + stat = fprintf(leaseFile, " %s %s {\n", + ianame, print_hex_1(4, ia->iaid, 12)); if (stat <= 0) return ISC_R_IOERROR; - stat = fprintf(leaseFile, " starts %d;\n" - " renew %u;\n" - " rebind %u;\n", - (int)ia->starts, ia->renew, ia->rebind); + if (ia->ia_type != D6O_IA_TA) + stat = fprintf(leaseFile, " starts %d;\n" + " renew %u;\n" + " rebind %u;\n", + (int)ia->starts, ia->renew, ia->rebind); + else + stat = fprintf(leaseFile, " starts %d;\n", + (int)ia->starts); if (stat <= 0) return ISC_R_IOERROR; for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { - stat = fprintf(leaseFile, " iaaddr %s {\n", - piaddr(addr->address)); + if (ia->ia_type != D6O_IA_PD) + stat = fprintf(leaseFile, + " iaaddr %s {\n", + piaddr(addr->address)); + else + stat = fprintf(leaseFile, + " iaprefix %s/%d {\n", + piaddr(addr->address), + (int)addr->plen); if (stat <= 0) return ISC_R_IOERROR; diff --git a/common/conflex.c b/common/conflex.c index 25767bf8..2c3621fe 100644 --- a/common/conflex.c +++ b/common/conflex.c @@ -951,8 +951,14 @@ intern(char *atom, enum dhcp_token dfv) { case 'i': if (!strcasecmp(atom+1, "a-na")) return IA_NA; + if (!strcasecmp(atom+1, "a-ta")) + return IA_TA; + if (!strcasecmp(atom+1, "a-pd")) + return IA_PD; if (!strcasecmp(atom+1, "aaddr")) return IAADDR; + if (!strcasecmp(atom+1, "aprefix")) + return IAPREFIX; if (!strcasecmp (atom + 1, "nclude")) return INCLUDE; if (!strcasecmp (atom + 1, "nteger")) diff --git a/common/parse.c b/common/parse.c index ba53584f..c1c66425 100644 --- a/common/parse.c +++ b/common/parse.c @@ -435,6 +435,47 @@ parse_ip6_addr_expr(struct expression **expr, } /* + * ip6-prefix :== ip6-address "/" NUMBER + */ +int +parse_ip6_prefix(struct parse *cfile, struct iaddr *addr, u_int8_t *plen) { + enum dhcp_token token; + const char *val; + int n; + + if (!parse_ip6_addr(cfile, addr)) { + return 0; + } + token = next_token(&val, NULL, cfile); + if (token != SLASH) { + parse_warn(cfile, "Slash expected."); + if (token != SEMI) + skip_to_semi(cfile); + return 0; + } + token = next_token(&val, NULL, cfile); + if (token != NUMBER) { + parse_warn(cfile, "Number expected."); + if (token != SEMI) + skip_to_semi(cfile); + return 0; + } + n = atoi(val); + if ((n < 0) || (n > 128)) { + parse_warn(cfile, "Invalid IPv6 prefix length."); + skip_to_semi(cfile); + return 0; + } + if (!is_cidr_mask_valid(addr, n)) { + parse_warn(cfile, "network mask too short."); + skip_to_semi(cfile); + return 0; + } + *plen = n; + return 1; +} + +/* * ip-address-with-subnet :== ip-address | * ip-address "/" NUMBER */ diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 1d82eb33..243c7857 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -889,6 +889,7 @@ struct client_lease { struct dhc6_addr { struct dhc6_addr *next; struct iaddr address; + u_int8_t plen; /* Address state flags. */ #define DHC6_ADDR_DEPREFFED 0x01 @@ -905,6 +906,7 @@ struct dhc6_addr { struct dhc6_ia { struct dhc6_ia *next; unsigned char iaid[4]; + u_int16_t ia_type; TIME starts; u_int32_t renew; @@ -1308,6 +1310,7 @@ typedef struct hash_table iaaddr_hash_t; struct iaaddr { int refcnt; /* reference count */ struct in6_addr addr; /* IPv6 address */ + u_int8_t plen; /* unused/placeholder */ binding_state_t state; /* state */ struct binding_scope *scope; /* "set var = value;" */ time_t valid_lifetime_end_time; /* time address expires */ @@ -1326,12 +1329,14 @@ struct iaaddr { struct ia_na { int refcnt; /* reference count */ struct data_string iaid_duid; /* from the client */ + u_int16_t ia_type; /* IA_NA or IA_TA */ int num_iaaddr; /* number of IAADDR for this IA_NA */ int max_iaaddr; /* space available for IAADDR */ struct iaaddr **iaaddr; /* pointers to the various IAADDRs */ }; -extern ia_na_hash_t *ia_active; +extern ia_na_hash_t *ia_na_active; +extern ia_na_hash_t *ia_ta_active; struct ipv6_pool { int refcnt; /* reference count */ @@ -1350,6 +1355,51 @@ struct ipv6_pool { extern struct ipv6_pool **pools; extern int num_pools; +/* Sames thing for IA_PDs */ + +typedef struct hash_table ia_pd_hash_t; +typedef struct hash_table iaprefix_hash_t; + +struct iaprefix { + /* Must keep the same layout than iaaddr */ + int refcnt; /* reference count */ + struct in6_addr pref; /* IPv6 prefix */ + u_int8_t plen; /* prefix length */ + binding_state_t state; /* state */ + struct binding_scope *scope; /* "set var = value;" */ + time_t valid_lifetime_end_time; /* time prefix expires */ + struct ia_pd *ia_pd; /* IA for this prefix */ + struct ipv6_ppool *ipv6_ppool; /* pool for this prefix */ + int heap_index; /* index into heap, or -1 + (internal use only) */ +}; + +struct ia_pd { + int refcnt; /* reference count */ + struct data_string iaid_duid; /* from the client */ + int num_iaprefix; /* number of IAPREFIX for this IA_PD */ + int max_iaprefix; /* space available for IAPREFIX */ + struct iaprefix **iaprefix; /* pointers to the various IAPREFIXs */ +}; + +extern ia_pd_hash_t *ia_pd_active; + +struct ipv6_ppool { + int refcnt; /* reference count */ + struct in6_addr start_pref; /* first IPv6 prefix */ + u_int8_t pool_plen; /* pool prefix length */ + u_int8_t alloc_plen; /* allocation prefix length */ + iaprefix_hash_t *prefs; /* non-free IAPREFIX */ + int num_active; /* count of active IAPREFIX */ + isc_heap_t *active_timeouts; /* timeouts for active leases */ + int num_inactive; /* count of inactive IAADDR */ + isc_heap_t *inactive_timeouts; /* timeouts for expired or + released leases */ +}; + +extern struct ipv6_ppool **ppools; +extern int num_ppools; + /* External definitions... */ HASH_FUNCTIONS_DECL (group, const char *, struct group_object, group_hash_t) @@ -1665,10 +1715,13 @@ int parse_fixed_addr_param PROTO ((struct option_cache **, int parse_lease_declaration PROTO ((struct lease **, struct parse *)); int parse_ip6_addr(struct parse *, struct iaddr *); int parse_ip6_addr_expr(struct expression **, struct parse *); +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_ia_na_declaration(struct parse *); +void parse_ia_ta_declaration(struct parse *); +void parse_ia_pd_declaration(struct parse *); void parse_server_duid(struct parse *cfile); void parse_server_duid_conf(struct parse *cfile); @@ -2487,7 +2540,8 @@ int commit_leases PROTO ((void)); void db_startup PROTO ((int)); int new_lease_file PROTO ((void)); int group_writer (struct group_object *); -int write_ia_na(const struct ia_na *); +int write_ia(const struct ia_na *); +int write_ia_pd(const struct ia_pd *); /* packet.c */ u_int32_t checksum PROTO ((unsigned char *, unsigned, u_int32_t)); @@ -3161,7 +3215,10 @@ const char *binding_state_print (enum failover_state); /* mdb6.c */ HASH_FUNCTIONS_DECL(ia_na, unsigned char *, struct ia_na, ia_na_hash_t); +HASH_FUNCTIONS_DECL(ia_pd, unsigned char *, struct ia_pd, ia_pd_hash_t); HASH_FUNCTIONS_DECL(iaaddr, struct in6_addr *, struct iaaddr, iaaddr_hash_t); +HASH_FUNCTIONS_DECL(iaprefix, struct in6_addr *, + struct iaprefix, iaaddr_hash_t); isc_result_t iaaddr_allocate(struct iaaddr **iaaddr, const char *file, int line); @@ -3170,9 +3227,17 @@ isc_result_t iaaddr_reference(struct iaaddr **iaaddr, struct iaaddr *src, isc_result_t iaaddr_dereference(struct iaaddr **iaaddr, const char *file, int line); -isc_result_t ia_na_make_key(struct data_string *key, u_int32_t iaid, - const char *duid, unsigned int duid_len, - const char *file, int line); +isc_result_t iaprefix_allocate(struct iaprefix **iaprefix, + const char *file, int line); +isc_result_t iaprefix_reference(struct iaprefix **iaprefix, + struct iaprefix *src, + const char *file, int line); +isc_result_t iaprefix_dereference(struct iaprefix **iaprefix, + const char *file, int line); + +isc_result_t ia_make_key(struct data_string *key, u_int32_t iaid, + const char *duid, unsigned int duid_len, + const char *file, int line); isc_result_t ia_na_allocate(struct ia_na **ia_na, u_int32_t iaid, const char *duid, unsigned int duid_len, const char *file, int line); @@ -3186,6 +3251,18 @@ void ia_na_remove_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, const char *file, int line); isc_boolean_t ia_na_equal(const struct ia_na *a, const struct ia_na *b); +isc_result_t ia_pd_allocate(struct ia_pd **ia_pd, u_int32_t iaid, + const char *duid, unsigned int duid_len, + const char *file, int line); +isc_result_t ia_pd_reference(struct ia_pd **ia_pd, struct ia_pd *src, + const char *file, int line); +isc_result_t ia_pd_dereference(struct ia_pd **ia_pd, + const char *file, int line); +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_result_t ipv6_pool_allocate(struct ipv6_pool **pool, const struct in6_addr *start_addr, int bits, const char *file, int line); @@ -3194,6 +3271,15 @@ isc_result_t ipv6_pool_reference(struct ipv6_pool **pool, const char *file, int line); isc_result_t ipv6_pool_dereference(struct ipv6_pool **pool, const char *file, int line); +isc_result_t ipv6_ppool_allocate(struct ipv6_ppool **ppool, + const struct in6_addr *start_pref, + u_int8_t pool_plen, u_int8_t alloc_plen, + const char *file, int line); +isc_result_t ipv6_ppool_reference(struct ipv6_ppool **ppool, + struct ipv6_ppool *src, + const char *file, int line); +isc_result_t ipv6_ppool_dereference(struct ipv6_ppool **ppool, + const char *file, int line); isc_result_t activate_lease6(struct ipv6_pool *pool, struct iaaddr **addr, unsigned int *attempts, @@ -3212,11 +3298,29 @@ isc_boolean_t lease6_exists(const struct ipv6_pool *pool, isc_result_t mark_address_unavailble(struct ipv6_pool *pool, const struct in6_addr *addr); +isc_result_t activate_prefix6(struct ipv6_ppool *ppool, + struct iaprefix **pref, + unsigned int *attempts, + const struct data_string *uid, + time_t valid_lifetime_end_time); +isc_result_t add_prefix6(struct ipv6_ppool *ppool, + struct iaprefix *pref, + time_t valid_lifetime_end_time); +isc_result_t renew_prefix6(struct ipv6_ppool *ppool, struct iaprefix *pref); +isc_result_t expire_prefix6(struct iaprefix **pref, + struct ipv6_ppool *ppool, time_t now); +isc_result_t release_prefix6(struct ipv6_ppool *ppool, struct iaprefix *pref); +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, 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_result_t renew_leases(struct ia_na *ia_na); isc_result_t release_leases(struct ia_na *ia_na); @@ -3224,6 +3328,11 @@ isc_result_t decline_leases(struct ia_na *ia_na); void schedule_lease_timeout(struct ipv6_pool *pool); void schedule_all_ipv6_lease_timeouts(); +isc_result_t renew_prefixes(struct ia_pd *ia_pd); +isc_result_t release_prefixes(struct ia_pd *ia_pd); +void schedule_prefix_timeout(struct ipv6_ppool *ppool); +void schedule_all_ipv6_prefix_timeouts(); + void mark_hosts_unavailable(void); void mark_interfaces_unavailable(void); diff --git a/includes/dhctoken.h b/includes/dhctoken.h index 80ad0b3d..dd39a3e6 100644 --- a/includes/dhctoken.h +++ b/includes/dhctoken.h @@ -333,19 +333,22 @@ enum dhcp_token { SUBNET6 = 636, HOST_IDENTIFIER = 637, IA_NA = 638, - IAADDR = 639, - LEASE6 = 640, - PREFERRED_LIFE = 641, - MAX_LIFE = 642, - DEFAULT_DUID = 643, - SERVER_DUID = 644, - LLT = 645, - EN = 646, - LL = 647, - RANGE6 = 648, - WHITESPACE = 649, - TOKEN_ALSO = 650, - AFTER = 651 + IA_TA = 639, + IA_PD = 640, + IAADDR = 641, + IAPREFIX = 642, + LEASE6 = 643, + PREFERRED_LIFE = 644, + MAX_LIFE = 645, + DEFAULT_DUID = 646, + SERVER_DUID = 647, + LLT = 648, + EN = 649, + LL = 650, + RANGE6 = 651, + WHITESPACE = 652, + TOKEN_ALSO = 653, + AFTER = 654 }; #define is_identifier(x) ((x) >= FIRST_TOKEN && \ diff --git a/server/confpars.c b/server/confpars.c index 715e6542..7a0a3d21 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -266,6 +266,10 @@ isc_result_t lease_file_subparse (struct parse *cfile) "possibly corrupt lease file"); } else if (token == IA_NA) { parse_ia_na_declaration(cfile); + } else if (token == IA_TA) { + parse_ia_ta_declaration(cfile); + } else if (token == IA_PD) { + parse_ia_pd_declaration(cfile); } else if (token == CLASS) { parse_class_declaration(0, cfile, root_group, CLASS_TYPE_CLASS); @@ -3801,9 +3805,9 @@ parse_ia_na_declaration(struct parse *cfile) { skip_to_semi(cfile); #else /* defined(DHCPv6) */ enum dhcp_token token; - struct ia_na *ia_na; + struct ia_na *ia; const char *val; - struct ia_na *old_ia_na; + struct ia_na *old_ia; unsigned int len; u_int32_t iaid; struct iaddr iaddr; @@ -3832,10 +3836,11 @@ parse_ia_na_declaration(struct parse *cfile) { } memcpy(&iaid, val, 4); - ia_na = NULL; - if (ia_na_allocate(&ia_na, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) { + ia = NULL; + if (ia_na_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) { log_fatal("Out of memory."); } + ia->ia_type = D6O_IA_NA; token = next_token(&val, NULL, cfile); if (token != LBRACE) { @@ -4041,8 +4046,8 @@ parse_ia_na_declaration(struct parse *cfile) { } /* add to our various structures */ - ia_na_add_iaaddr(ia_na, iaaddr, MDL); - ia_na_reference(&iaaddr->ia_na, ia_na, MDL); + 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) { inet_ntop(AF_INET6, &iaaddr->addr, @@ -4059,25 +4064,602 @@ parse_ia_na_declaration(struct parse *cfile) { /* * If we have an existing record for this IA_NA, remove it. */ - old_ia_na = NULL; - if (ia_na_hash_lookup(&old_ia_na, ia_active, - (unsigned char *)ia_na->iaid_duid.data, - ia_na->iaid_duid.len, MDL)) { - ia_na_hash_delete(ia_active, - (unsigned char *)ia_na->iaid_duid.data, - ia_na->iaid_duid.len, MDL); - ia_na_dereference(&old_ia_na, MDL); + old_ia = NULL; + if (ia_na_hash_lookup(&old_ia, ia_na_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, MDL)) { + ia_na_hash_delete(ia_na_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, MDL); + ia_na_dereference(&old_ia, MDL); } /* * If we have addresses, add this, otherwise don't bother. */ - if (ia_na->num_iaaddr > 0) { - ia_na_hash_add(ia_active, - (unsigned char *)ia_na->iaid_duid.data, - ia_na->iaid_duid.len, ia_na, MDL); + if (ia->num_iaaddr > 0) { + ia_na_hash_add(ia_na_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, ia, MDL); } - ia_na_dereference(&ia_na, MDL); + ia_na_dereference(&ia, MDL); +#endif /* defined(DHCPv6) */ +} + +void +parse_ia_ta_declaration(struct parse *cfile) { +#if !defined(DHCPv6) + parse_warn(cfile, "No DHCPv6 support."); + skip_to_semi(cfile); +#else /* defined(DHCPv6) */ + enum dhcp_token token; + struct ia_na *ia; + const char *val; + struct ia_na *old_ia; + unsigned int len; + u_int32_t iaid; + struct iaddr iaddr; + binding_state_t state; + TIME end_time; + struct iaaddr *iaaddr; + struct ipv6_pool *pool; + char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + isc_boolean_t newbinding; + struct binding_scope *scope=NULL; + struct binding *bnd; + struct binding_value *nv=NULL; + + token = next_token(&val, &len, cfile); + if (token != STRING) { + parse_warn(cfile, "corrupt lease file; " + "expecting an iaid+ia_ta string"); + skip_to_semi(cfile); + return; + } + if (len < 5) { + parse_warn(cfile, "corrupt lease file; " + "iaid+ia_ta string too short"); + skip_to_semi(cfile); + return; + } + + memcpy(&iaid, val, 4); + ia = NULL; + if (ia_na_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) { + log_fatal("Out of memory."); + } + ia->ia_type = D6O_IA_TA; + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "corrupt lease file; expecting left brace"); + skip_to_semi(cfile); + return; + } + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token == RBRACE) break; + + if (token != IAADDR) { + parse_warn(cfile, "corrupt lease file; " + "expecting IAADDR or right brace"); + skip_to_semi(cfile); + return; + } + + if (!parse_ip6_addr(cfile, &iaddr)) { + parse_warn(cfile, "corrupt lease file; " + "expecting IPv6 address"); + skip_to_semi(cfile); + return; + } + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "corrupt lease file; " + "expecting left brace"); + skip_to_semi(cfile); + return; + } + + state = FTS_LAST+1; + end_time = -1; + for (;;) { + token = next_token(&val, NULL, cfile); + if (token == RBRACE) break; + + switch(token) { + /* Lease binding state. */ + case BINDING: + token = next_token(&val, NULL, cfile); + if (token != STATE) { + parse_warn(cfile, "corrupt lease file; " + "expecting state"); + skip_to_semi(cfile); + return; + } + token = next_token(&val, NULL, cfile); + switch (token) { + case TOKEN_ABANDONED: + state = FTS_ABANDONED; + break; + case TOKEN_FREE: + state = FTS_FREE; + break; + case TOKEN_ACTIVE: + state = FTS_ACTIVE; + break; + case TOKEN_EXPIRED: + state = FTS_EXPIRED; + break; + case TOKEN_RELEASED: + state = FTS_RELEASED; + break; + default: + parse_warn(cfile, + "corrupt lease " + "file; " + "expecting a " + "binding state."); + skip_to_semi(cfile); + return; + } + + token = next_token(&val, NULL, cfile); + if (token != SEMI) { + parse_warn(cfile, "corrupt lease file; " + "expecting " + "semicolon."); + } + break; + + /* Lease expiration time. */ + case ENDS: + end_time = parse_date(cfile); + break; + + /* Lease binding scopes. */ + case TOKEN_SET: + token = next_token(&val, NULL, cfile); + if ((token != NAME) && + (token != NUMBER_OR_NAME)) { + parse_warn(cfile, "%s is not a valid " + "variable name", + val); + skip_to_semi(cfile); + continue; + } + + if (scope != NULL) + bnd = find_binding(scope, val); + else { + if (!binding_scope_allocate(&scope, + MDL)) { + log_fatal("Out of memory for " + "lease binding " + "scope."); + } + + bnd = NULL; + } + + if (bnd == NULL) { + bnd = dmalloc(sizeof(*bnd), + MDL); + if (bnd == NULL) { + log_fatal("No memory for " + "lease binding."); + } + + bnd->name = dmalloc(strlen(val) + 1, + MDL); + if (bnd->name == NULL) { + log_fatal("No memory for " + "binding name."); + } + strcpy(bnd->name, val); + + newbinding = ISC_TRUE; + } else { + newbinding = ISC_FALSE; + } + + if (!binding_value_allocate(&nv, MDL)) { + log_fatal("no memory for binding " + "value."); + } + + token = next_token(NULL, NULL, cfile); + if (token != EQUAL) { + parse_warn(cfile, "expecting '=' in " + "set statement."); + goto binding_err; + } + + if (!parse_binding_value(cfile, nv)) { + binding_err: + binding_value_dereference(&nv, MDL); + binding_scope_dereference(&scope, MDL); + return; + } + + if (newbinding) { + binding_value_reference(&bnd->value, + nv, MDL); + bnd->next = scope->bindings; + scope->bindings = bnd; + } else { + binding_value_dereference(&bnd->value, + MDL); + binding_value_reference(&bnd->value, + nv, MDL); + } + + binding_value_dereference(&nv, MDL); + parse_semi(cfile); + break; + + default: + parse_warn(cfile, "corrupt lease file; " + "expecting ia_ta contents, " + "got '%s'", val); + skip_to_semi(cfile); + continue; + } + } + + if (state == FTS_LAST+1) { + parse_warn(cfile, "corrupt lease file; " + "missing state in iaaddr"); + return; + } + if (end_time == -1) { + parse_warn(cfile, "corrupt lease file; " + "missing end time in iaaddr"); + return; + } + + iaaddr = NULL; + if (iaaddr_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) { + log_fatal("Out of memory."); + } + memcpy(&iaaddr->addr, iaddr.iabuf, sizeof(iaaddr->addr)); + iaaddr->state = state; + iaaddr->valid_lifetime_end_time = end_time; + + if (scope != NULL) { + binding_scope_reference(&iaaddr->scope, scope, MDL); + binding_scope_dereference(&scope, MDL); + } + + /* add to our various structures */ + 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) { + inet_ntop(AF_INET6, &iaaddr->addr, + addr_buf, sizeof(addr_buf)); + parse_warn(cfile, "no pool found for address %s", + addr_buf); + return; + } + add_lease6(pool, iaaddr, end_time); + ipv6_pool_dereference(&pool, MDL); + iaaddr_dereference(&iaaddr, MDL); + } + + /* + * If we have an existing record for this IA_TA, remove it. + */ + old_ia = NULL; + if (ia_na_hash_lookup(&old_ia, ia_ta_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, MDL)) { + ia_na_hash_delete(ia_ta_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, MDL); + ia_na_dereference(&old_ia, MDL); + } + + /* + * If we have addresses, add this, otherwise don't bother. + */ + if (ia->num_iaaddr > 0) { + ia_na_hash_add(ia_ta_active, + (unsigned char *)ia->iaid_duid.data, + ia->iaid_duid.len, ia, MDL); + } + ia_na_dereference(&ia, MDL); +#endif /* defined(DHCPv6) */ +} + +void +parse_ia_pd_declaration(struct parse *cfile) { +#if !defined(DHCPv6) + parse_warn(cfile, "No DHCPv6 support."); + skip_to_semi(cfile); +#else /* defined(DHCPv6) */ + enum dhcp_token token; + struct ia_pd *ia_pd; + const char *val; + struct ia_pd *old_ia_pd; + unsigned int len; + u_int32_t iaid; + struct iaddr iaddr; + u_int8_t plen; + binding_state_t state; + TIME end_time; + struct iaprefix *iapref; + struct ipv6_ppool *ppool; + char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + isc_boolean_t newbinding; + struct binding_scope *scope=NULL; + struct binding *bnd; + struct binding_value *nv=NULL; + + token = next_token(&val, &len, cfile); + if (token != STRING) { + parse_warn(cfile, "corrupt lease file; " + "expecting an iaid+ia_pd string"); + skip_to_semi(cfile); + return; + } + if (len < 5) { + parse_warn(cfile, "corrupt lease file; " + "iaid+ia_pd string too short"); + skip_to_semi(cfile); + return; + } + + memcpy(&iaid, val, 4); + ia_pd = NULL; + if (ia_pd_allocate(&ia_pd, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) { + log_fatal("Out of memory."); + } + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "corrupt lease file; expecting left brace"); + skip_to_semi(cfile); + return; + } + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token == RBRACE) break; + + if (token != IAPREFIX) { + parse_warn(cfile, "corrupt lease file; expecting " + "IAPREFIX or right brace"); + skip_to_semi(cfile); + return; + } + + if (!parse_ip6_prefix(cfile, &iaddr, &plen)) { + parse_warn(cfile, "corrupt lease file; " + "expecting IPv6 prefix"); + skip_to_semi(cfile); + return; + } + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) { + parse_warn(cfile, "corrupt lease file; " + "expecting left brace"); + skip_to_semi(cfile); + return; + } + + state = FTS_LAST+1; + end_time = -1; + for (;;) { + token = next_token(&val, NULL, cfile); + if (token == RBRACE) break; + + switch(token) { + /* Prefix binding state. */ + case BINDING: + token = next_token(&val, NULL, cfile); + if (token != STATE) { + parse_warn(cfile, "corrupt lease file; " + "expecting state"); + skip_to_semi(cfile); + return; + } + token = next_token(&val, NULL, cfile); + switch (token) { + case TOKEN_ABANDONED: + state = FTS_ABANDONED; + break; + case TOKEN_FREE: + state = FTS_FREE; + break; + case TOKEN_ACTIVE: + state = FTS_ACTIVE; + break; + case TOKEN_EXPIRED: + state = FTS_EXPIRED; + break; + case TOKEN_RELEASED: + state = FTS_RELEASED; + break; + default: + parse_warn(cfile, + "corrupt lease " + "file; " + "expecting a " + "binding state."); + skip_to_semi(cfile); + return; + } + + token = next_token(&val, NULL, cfile); + if (token != SEMI) { + parse_warn(cfile, "corrupt lease file; " + "expecting " + "semicolon."); + } + break; + + /* Prefix expiration time. */ + case ENDS: + end_time = parse_date(cfile); + break; + + /* Prefix binding scopes. */ + case TOKEN_SET: + token = next_token(&val, NULL, cfile); + if ((token != NAME) && + (token != NUMBER_OR_NAME)) { + parse_warn(cfile, "%s is not a valid " + "variable name", + val); + skip_to_semi(cfile); + continue; + } + + if (scope != NULL) + bnd = find_binding(scope, val); + else { + if (!binding_scope_allocate(&scope, + MDL)) { + log_fatal("Out of memory for " + "lease binding " + "scope."); + } + + bnd = NULL; + } + + if (bnd == NULL) { + bnd = dmalloc(sizeof(*bnd), + MDL); + if (bnd == NULL) { + log_fatal("No memory for " + "prefix binding."); + } + + bnd->name = dmalloc(strlen(val) + 1, + MDL); + if (bnd->name == NULL) { + log_fatal("No memory for " + "binding name."); + } + strcpy(bnd->name, val); + + newbinding = ISC_TRUE; + } else { + newbinding = ISC_FALSE; + } + + if (!binding_value_allocate(&nv, MDL)) { + log_fatal("no memory for binding " + "value."); + } + + token = next_token(NULL, NULL, cfile); + if (token != EQUAL) { + parse_warn(cfile, "expecting '=' in " + "set statement."); + goto binding_err; + } + + if (!parse_binding_value(cfile, nv)) { + binding_err: + binding_value_dereference(&nv, MDL); + binding_scope_dereference(&scope, MDL); + return; + } + + if (newbinding) { + binding_value_reference(&bnd->value, + nv, MDL); + bnd->next = scope->bindings; + scope->bindings = bnd; + } else { + binding_value_dereference(&bnd->value, + MDL); + binding_value_reference(&bnd->value, + nv, MDL); + } + + binding_value_dereference(&nv, MDL); + parse_semi(cfile); + break; + + default: + parse_warn(cfile, "corrupt lease file; " + "expecting ia_pd contents, " + "got '%s'", val); + skip_to_semi(cfile); + continue; + } + } + + if (state == FTS_LAST+1) { + parse_warn(cfile, "corrupt lease file; " + "missing state in iaprefix"); + return; + } + if (end_time == -1) { + parse_warn(cfile, "corrupt lease file; " + "missing end time in iaprefix"); + return; + } + + iapref = NULL; + if (iaprefix_allocate(&iapref, MDL) != ISC_R_SUCCESS) { + log_fatal("Out of memory."); + } + memcpy(&iapref->pref, iaddr.iabuf, sizeof(iapref->pref)); + iapref->plen = plen; + iapref->state = state; + iapref->valid_lifetime_end_time = end_time; + + if (scope != NULL) { + binding_scope_reference(&iapref->scope, scope, MDL); + binding_scope_dereference(&scope, MDL); + } + + /* add to our various structures */ + ia_pd_add_iaprefix(ia_pd, iapref, MDL); + ia_pd_reference(&iapref->ia_pd, ia_pd, MDL); + ppool = NULL; + if (find_ipv6_ppool(&ppool, &iapref->pref) != ISC_R_SUCCESS) { + inet_ntop(AF_INET6, &iapref->pref, + addr_buf, sizeof(addr_buf)); + parse_warn(cfile, "no ppool found for address %s", + addr_buf); + return; + } + add_prefix6(ppool, iapref, end_time); + ipv6_ppool_dereference(&ppool, MDL); + iaprefix_dereference(&iapref, MDL); + } + + /* + * If we have an existing record for this IA_PD, remove it. + */ + old_ia_pd = NULL; + if (ia_pd_hash_lookup(&old_ia_pd, ia_pd_active, + (unsigned char *)ia_pd->iaid_duid.data, + ia_pd->iaid_duid.len, MDL)) { + ia_pd_hash_delete(ia_pd_active, + (unsigned char *)ia_pd->iaid_duid.data, + ia_pd->iaid_duid.len, MDL); + ia_pd_dereference(&old_ia_pd, MDL); + } + + /* + * If we have prefixes, add this, otherwise don't bother. + */ + if (ia_pd->num_iaprefix > 0) { + ia_pd_hash_add(ia_pd_active, + (unsigned char *)ia_pd->iaid_duid.data, + ia_pd->iaid_duid.len, ia_pd, MDL); + } + ia_pd_dereference(&ia_pd, MDL); #endif /* defined(DHCPv6) */ } diff --git a/server/db.c b/server/db.c index 3c92c5fa..424a3abf 100644 --- a/server/db.c +++ b/server/db.c @@ -497,10 +497,10 @@ int write_group (group) } /* - * Write an IA_NA and the options it has. + * Write an IA and the options it has. */ int -write_ia_na(const struct ia_na *ia_na) { +write_ia(const struct ia_na *ia) { struct iaaddr *iaaddr; struct binding *bnd; int i; @@ -525,24 +525,32 @@ write_ia_na(const struct ia_na *ia_na) { } - s = quotify_buf(ia_na->iaid_duid.data, ia_na->iaid_duid.len, MDL); + s = quotify_buf(ia->iaid_duid.data, ia->iaid_duid.len, MDL); if (s == NULL) { goto error_exit; } - fprintf_ret = fprintf(db_file, "ia-na \"%s\" {\n", s); + if (ia->ia_type == D6O_IA_NA) { + fprintf_ret = fprintf(db_file, "ia-na \"%s\" {\n", s); + } else if (ia->ia_type == D6O_IA_TA) { + fprintf_ret = fprintf(db_file, "ia-ta \"%s\" {\n", s); + } else { + log_error("Unknown ia type %u at %s:%d", + (unsigned)ia->ia_type, MDL); + goto error_exit; + } dfree(s, MDL); if (fprintf_ret < 0) { goto error_exit; } - for (i=0; i<ia_na->num_iaaddr; i++) { - iaaddr = ia_na->iaaddr[i]; + for (i=0; i<ia->num_iaaddr; i++) { + iaaddr = ia->iaaddr[i]; inet_ntop(AF_INET6, &iaaddr->addr, addr_buf, sizeof(addr_buf)); if (fprintf(db_file, " iaaddr %s {\n", addr_buf) < 0) { goto error_exit; } if ((iaaddr->state <= 0) || (iaaddr->state > FTS_LAST)) { - log_fatal("Unknown ia_na state %d at %s:%d", + log_fatal("Unknown iaaddr state %d at %s:%d", iaaddr->state, MDL); } binding_state = binding_state_names[iaaddr->state-1]; @@ -593,7 +601,110 @@ write_ia_na(const struct ia_na *ia_na) { return 1; error_exit: - log_info("write_ia_na: unable to write ia-na"); + log_info("write_ia: unable to write ia-na"); + lease_file_is_corrupt = 1; + return 0; +} + +/* + * Write an IA_PD and the options it has. + */ +int +write_ia_pd(const struct ia_pd *ia_pd) { + struct iaprefix *iapref; + struct binding *bnd; + int i; + char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff.255.255.255.255")]; + const char *binding_state; + const char *tval; + char *s; + int fprintf_ret; + + /* + * If the lease file is corrupt, don't try to write any more + * leases until we've written a good lease file. + */ + if (lease_file_is_corrupt) { + if (!new_lease_file()) { + return 0; + } + } + + if (counting) { + ++count; + } + + + s = quotify_buf(ia_pd->iaid_duid.data, ia_pd->iaid_duid.len, MDL); + if (s == NULL) { + goto error_exit; + } + fprintf_ret = fprintf(db_file, "ia-pd \"%s\" {\n", s); + dfree(s, MDL); + if (fprintf_ret < 0) { + goto error_exit; + } + for (i=0; i<ia_pd->num_iaprefix; i++) { + iapref = ia_pd->iaprefix[i]; + + inet_ntop(AF_INET6, &iapref->pref, addr_buf, sizeof(addr_buf)); + if (fprintf(db_file, " iaprefix %s/%d {\n", + addr_buf, (int)iapref->plen) < 0) { + goto error_exit; + } + if ((iapref->state <= 0) || (iapref->state > FTS_LAST)) { + log_fatal("Unknown iaprefix state %d at %s:%d", + iapref->state, MDL); + } + binding_state = binding_state_names[iapref->state-1]; + if (fprintf(db_file, " binding state %s;\n", + binding_state) < 0) { + goto error_exit; + } + + /* Note that from here on out, the \n is prepended to the + * next write, rather than appended to the current write. + */ + tval = print_time(iapref->valid_lifetime_end_time); + if (tval == NULL) { + goto error_exit; + } + if (fprintf(db_file, " ends %s", tval) < 0) { + goto error_exit; + } + + /* Write out any binding scopes: note that 'ends' above does + * not have \n on the end! We want that. + */ + if (iapref->scope != NULL) + bnd = iapref->scope->bindings; + else + bnd = NULL; + + for (; bnd != NULL ; bnd = bnd->next) { + if (bnd->value == NULL) + continue; + + /* We don't do a regular error_exit because the + * lease db is not corrupt in this case. + */ + if (write_binding_scope(db_file, bnd, + "\n ") != ISC_R_SUCCESS) + goto error_exit; + + } + + if (fprintf(db_file, "\n }\n") < 0) + goto error_exit; + } + if (fprintf(db_file, "}\n\n") < 0) + goto error_exit; + + fflush(db_file); + return 1; + +error_exit: + log_info("write_ia_pd: unable to write ia-pd"); lease_file_is_corrupt = 1; return 0; } diff --git a/server/dhcpd.c b/server/dhcpd.c index 904d50d6..41e3813a 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -560,9 +560,15 @@ main(int argc, char **argv) { #endif #ifdef DHCPv6 - /* set up DHCPv6 hash */ - if (!ia_na_new_hash(&ia_active, DEFAULT_HASH_SIZE, MDL)) { - log_fatal("Out of memory creating hash for active IA."); + /* set up DHCPv6 hashes */ + if (!ia_na_new_hash(&ia_na_active, DEFAULT_HASH_SIZE, MDL)) { + log_fatal("Out of memory creating hash for active IA_NA."); + } + if (!ia_na_new_hash(&ia_ta_active, DEFAULT_HASH_SIZE, MDL)) { + log_fatal("Out of memory creating hash for active IA_TA."); + } + if (!ia_pd_new_hash(&ia_pd_active, DEFAULT_HASH_SIZE, MDL)) { + log_fatal("Out of memory creating hash for active IA_PD."); } #endif /* DHCPv6 */ diff --git a/server/dhcpv6.c b/server/dhcpv6.c index 6a34a965..94dbe411 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -49,11 +49,11 @@ struct reply_state { /* IA level persistent state */ unsigned ia_count; - unsigned client_addresses; + unsigned client_resources; isc_boolean_t ia_addrs_included; isc_boolean_t static_lease; struct ia_na *ia_na; - struct ia_na *old_ia; + struct ia_na *old_ia_na; struct option_state *reply_ia; struct data_string fixed; @@ -96,8 +96,8 @@ 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(struct reply_state *reply, - struct option_cache *ia); +static isc_result_t reply_process_ia_na(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, @@ -592,6 +592,14 @@ valid_client_info_req(struct packet *packet, struct data_string *server_id) { piaddr(packet->client_addr), client_id_str); goto exit; } + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); + if (oc != NULL) { + log_debug("Discarding %s from %s; " + "IA_PD option present%s", + dhcpv6_type_names[packet->dhcpv6_msg_type], + piaddr(packet->client_addr), client_id_str); + goto exit; + } oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID); if (oc != NULL) { @@ -649,6 +657,7 @@ static const int required_opts_solicit[] = { D6O_SERVERID, D6O_IA_NA, D6O_IA_TA, + D6O_IA_PD, D6O_RAPID_COMMIT, D6O_STATUS_CODE, D6O_VENDOR_OPTS, @@ -662,6 +671,20 @@ static const int required_opts_IA_NA[] = { D6O_VENDOR_OPTS, 0 }; +/* +static const int required_opts_IA_TA[] = { + D6O_IAADDR, + D6O_STATUS_CODE, + D6O_VENDOR_OPTS, + 0 +}; +static const int required_opts_IA_PD[] = { + D6O_IAPREFIX, + D6O_STATUS_CODE, + D6O_VENDOR_OPTS, + 0 +}; +*/ static const int required_opts_STATUS_CODE[] = { D6O_STATUS_CODE, 0 @@ -1090,11 +1113,11 @@ lease_to_client(struct data_string *reply_ret, for (; oc != NULL ; oc = oc->next) { isc_result_t status; - /* Start counting addresses offered. */ - reply.client_addresses = 0; + /* Start counting resources (addresses) offered. */ + reply.client_resources = 0; reply.ia_addrs_included = ISC_FALSE; - status = reply_process_ia(&reply, oc); + status = reply_process_ia_na(&reply, oc); /* * We continue to try other IA's whether we can address @@ -1108,10 +1131,12 @@ lease_to_client(struct data_string *reply_ret, * If any address can be given to any IA, then do not set the * NoAddrsAvail status code. */ - if (reply.client_addresses == 0) + if (reply.client_resources == 0) no_addrs_avail = ISC_TRUE; } + /* Do IA_TA and IA_PD */ + /* * Make no reply if we gave no resources and is not * for Information-Request. @@ -1150,7 +1175,7 @@ lease_to_client(struct data_string *reply_ret, * INTERPRETATION; * * Solicit and Request are fairly explicit; we send NoAddrsAvail. - * We handle SOLICIT here and REQUEST in the reply_process_ia() + * We handle SOLICIT here and REQUEST in the reply_process_ia_na() * function (because SOLICIT only counts if we never get around to * it). * @@ -1237,7 +1262,7 @@ lease_to_client(struct data_string *reply_ret, * the reply packet being built in the reply_state structure. */ static isc_result_t -reply_process_ia(struct reply_state *reply, struct option_cache *ia) { +reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { isc_result_t status = ISC_R_SUCCESS; u_int32_t iaid; unsigned ia_cursor; @@ -1257,7 +1282,7 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { /* Make sure there is at least room for the header. */ if ((reply->cursor + IA_NA_OFFSET + 4) > sizeof(reply->buf)) { - log_error("reply_process_ia: Reply too long for IA."); + log_error("reply_process_ia_na: Reply too long for IA."); return ISC_R_NOSPACE; } @@ -1265,7 +1290,7 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { /* Fetch the IA_NA contents. */ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet, ia, IA_NA_OFFSET)) { - log_error("reply_process_ia: error evaluating ia_na"); + log_error("reply_process_ia_na: error evaluating ia_na"); status = ISC_R_FAILURE; goto cleanup; } @@ -1282,9 +1307,10 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { status = ISC_R_NOMEMORY; goto cleanup; } + reply->ia_na->ia_type = D6O_IA_NA; /* Cache pre-existing IA, if any. */ - ia_na_hash_lookup(&reply->old_ia, ia_active, + ia_na_hash_lookup(&reply->old_ia_na, ia_na_active, (unsigned char *)reply->ia_na->iaid_duid.data, reply->ia_na->iaid_duid.len, MDL); @@ -1302,14 +1328,14 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL, NULL, NULL, &global_scope, reply->host->fixed_addr, MDL)) { - log_error("reply_process_ia: unable to evaluate " + log_error("reply_process_ia_na: unable to evaluate " "fixed address."); status = ISC_R_FAILURE; goto cleanup; } if (reply->fixed.len < 16) { - log_error("reply_process_ia: invalid fixed address."); + log_error("reply_process_ia_na: invalid fixed address."); status = ISC_R_INVALIDARG; goto cleanup; } @@ -1375,7 +1401,7 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { * If we fell through the above and never gave the client * an address, give it one now. */ - if ((status != ISC_R_CANCELED) && (reply->client_addresses == 0)) { + if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) { status = find_client_address(reply); if (status == ISC_R_NORESOURCES) { @@ -1401,7 +1427,7 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { if (!option_state_allocate(&reply->reply_ia, MDL)) { - log_error("reply_process_ia: No " + log_error("reply_process_ia_na: No " "memory for option state " "wipe."); status = ISC_R_NOMEMORY; @@ -1412,7 +1438,7 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { "No addresses available " "for this interface.", reply->reply_ia)) { - log_error("reply_process_ia: Unable " + log_error("reply_process_ia_na: Unable " "to set NoAddrsAvail status " "code."); status = ISC_R_FAILURE; @@ -1551,20 +1577,20 @@ reply_process_ia(struct reply_state *reply, struct option_cache *ia) { } /* Remove any old ia_na from the hash. */ - if (reply->old_ia != NULL) { - ia_id = &reply->old_ia->iaid_duid; - ia_na_hash_delete(ia_active, + if (reply->old_ia_na != NULL) { + ia_id = &reply->old_ia_na->iaid_duid; + ia_na_hash_delete(ia_na_active, (unsigned char *)ia_id->data, ia_id->len, MDL); - ia_na_dereference(&reply->old_ia, MDL); + ia_na_dereference(&reply->old_ia_na, MDL); } /* Put new ia_na into the hash. */ ia_id = &reply->ia_na->iaid_duid; - ia_na_hash_add(ia_active, (unsigned char *)ia_id->data, + ia_na_hash_add(ia_na_active, (unsigned char *)ia_id->data, ia_id->len, reply->ia_na, MDL); - write_ia_na(reply->ia_na); + write_ia(reply->ia_na); /* * Note that we wrote the lease into the database, @@ -1578,8 +1604,8 @@ reply_process_ia(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 != NULL)) { - if (ia_na_equal(reply->old_ia, reply->ia_na)) { + (reply->old_ia_na != NULL)) { + if (ia_na_equal(reply->old_ia_na, reply->ia_na)) { lease_in_database = ISC_TRUE; } } @@ -1595,8 +1621,8 @@ reply_process_ia(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 != NULL) - ia_na_dereference(&reply->old_ia, MDL); + if (reply->old_ia_na != NULL) + ia_na_dereference(&reply->old_ia_na, MDL); if (reply->lease != NULL) { if (!lease_in_database) { release_lease6(reply->lease->ipv6_pool, reply->lease); @@ -1837,12 +1863,12 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) { } /* - * If client_addresses is nonzero, then the reply_process_is_addressed + * If client_resources is nonzero, then the reply_process_is_addressed * 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 addresses, and similar. */ - if (reply->client_addresses != 0) { + if (reply->client_resources != 0) { unsigned limit = 1; /* @@ -1859,7 +1885,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) { reply->opt_state, scope, oc, MDL) || (data.len != 4)) { - log_error("reply_process_ia: unable to " + log_error("reply_process_addr: unable to " "evaluate addrs-per-ia value."); status = ISC_R_FAILURE; goto cleanup; @@ -1873,7 +1899,7 @@ reply_process_addr(struct reply_state *reply, struct option_cache *addr) { * If we wish to limit the client to a certain number of * addresses, then omit the address from the reply. */ - if (reply->client_addresses >= limit) + if (reply->client_resources >= limit) goto cleanup; } @@ -1918,13 +1944,13 @@ address_is_owned(struct reply_state *reply, struct iaddr *addr) { return ISC_FALSE; } - if ((reply->old_ia == NULL) || (reply->old_ia->num_iaaddr == 0)) + if ((reply->old_ia_na == NULL) || (reply->old_ia_na->num_iaaddr == 0)) return ISC_FALSE; - for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) { + for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) { struct iaaddr *tmp; - tmp = reply->old_ia->iaaddr[i]; + tmp = reply->old_ia_na->iaaddr[i]; if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) { iaaddr_reference(&reply->lease, tmp, MDL); @@ -1998,9 +2024,9 @@ find_client_address(struct reply_state *reply) { goto send_addr; } - if (reply->old_ia != NULL) { - for (i = 0 ; i < reply->old_ia->num_iaaddr ; i++) { - lease = reply->old_ia->iaaddr[i]; + if (reply->old_ia_na != NULL) { + for (i = 0 ; i < reply->old_ia_na->num_iaaddr ; i++) { + lease = reply->old_ia_na->iaaddr[i]; best_lease = lease_compare(lease, best_lease); } @@ -2082,7 +2108,7 @@ reply_process_is_addressed(struct reply_state *reply, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { - log_error("reply_process_ia: unable to " + log_error("reply_process_is_addressed: unable to " "evaluate default lease time"); status = ISC_R_FAILURE; goto cleanup; @@ -2109,7 +2135,7 @@ reply_process_is_addressed(struct reply_state *reply, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { - log_error("reply_process_ia: unable to " + log_error("reply_process_is_addressed: unable to " "evaluate preferred lease time"); status = ISC_R_FAILURE; goto cleanup; @@ -2173,7 +2199,7 @@ reply_process_is_addressed(struct reply_state *reply, data_string_forget(&data, MDL); if (status == ISC_R_SUCCESS) - reply->client_addresses++; + reply->client_resources++; return status; } @@ -2189,7 +2215,7 @@ 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_ia: out of memory allocating " + log_error("reply_process_send_addr: out of memory allocating " "new IAADDR buffer."); status = ISC_R_NOMEMORY; goto cleanup; @@ -2203,7 +2229,7 @@ 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_ia: unable to save IAADDR " + log_error("reply_process_send_addr: unable to save IAADDR " "option"); status = ISC_R_FAILURE; goto cleanup; @@ -2695,7 +2721,7 @@ ia_na_match_decline(const struct data_string *client_id, tmp_addr, sizeof(tmp_addr))); if (lease != NULL) { decline_lease6(lease->ipv6_pool, lease); - write_ia_na(lease->ia_na); + write_ia(lease->ia_na); } } @@ -2971,16 +2997,16 @@ iterate_over_ia_na(struct data_string *reply_ret, /* * Find existing IA_NA. */ - if (ia_na_make_key(&key, iaid, - (char *)client_id->data, - client_id->len, - MDL) != ISC_R_SUCCESS) { + if (ia_make_key(&key, iaid, + (char *)client_id->data, + client_id->len, + MDL) != ISC_R_SUCCESS) { log_fatal("iterate_over_ia_na: no memory for " "key."); } existing_ia_na = NULL; - if (ia_na_hash_lookup(&existing_ia_na, ia_active, + if (ia_na_hash_lookup(&existing_ia_na, ia_na_active, (unsigned char *)key.data, key.len, MDL)) { /* @@ -3103,7 +3129,7 @@ ia_na_match_release(const struct data_string *client_id, inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr))); if (lease != NULL) { release_lease6(lease->ipv6_pool, lease); - write_ia_na(lease->ia_na); + write_ia(lease->ia_na); } } diff --git a/server/mdb6.c b/server/mdb6.c index 70b6c7f2..0c08ea93 100644 --- a/server/mdb6.c +++ b/server/mdb6.c @@ -32,14 +32,26 @@ HASH_FUNCTIONS(ia_na, unsigned char *, struct ia_na, ia_na_hash_t, ia_na_reference, ia_na_dereference, do_string_hash); -ia_na_hash_t *ia_active; +ia_na_hash_t *ia_na_active; +ia_na_hash_t *ia_ta_active; + +HASH_FUNCTIONS(ia_pd, unsigned char *, struct ia_pd, ia_pd_hash_t, + ia_pd_reference, ia_pd_dereference, do_string_hash); + +ia_pd_hash_t *ia_pd_active; HASH_FUNCTIONS(iaaddr, struct in6_addr *, struct iaaddr, iaaddr_hash_t, iaaddr_reference, iaaddr_dereference, do_string_hash); +HASH_FUNCTIONS(iaprefix, struct in6_addr *, struct iaprefix, iaprefix_hash_t, + iaprefix_reference, iaprefix_dereference, do_string_hash); + struct ipv6_pool **pools; int num_pools; +struct ipv6_ppool **ppools; +int num_ppools; + /* * Create a new IAADDR structure. * @@ -138,13 +150,111 @@ iaaddr_dereference(struct iaaddr **iaaddr, const char *file, int line) { return ISC_R_SUCCESS; } +/* + * Create a new IAPREFIX structure. + * + * - iapref must be a pointer to a (struct iaprefix *) pointer previously + * initialized to NULL + */ +isc_result_t +iaprefix_allocate(struct iaprefix **iapref, const char *file, int line) { + struct iaprefix *tmp; + + if (iapref == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*iapref != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = dmalloc(sizeof(*tmp), file, line); + if (tmp == NULL) { + return ISC_R_NOMEMORY; + } + + tmp->refcnt = 1; + tmp->state = FTS_FREE; + tmp->heap_index = -1; + + *iapref = tmp; + return ISC_R_SUCCESS; +} + +/* + * Reference an IAPREFIX structure. + * + * - iapref must be a pointer to a (struct iaprefix *) pointer previously + * initialized to NULL + */ +isc_result_t +iaprefix_reference(struct iaprefix **iapref, struct iaprefix *src, + const char *file, int line) { + if (iapref == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*iapref != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + if (src == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + *iapref = src; + src->refcnt++; + return ISC_R_SUCCESS; +} + + +/* + * Dereference an IAPREFIX structure. + * + * If it is the last reference, then the memory for the + * structure is freed. + */ +isc_result_t +iaprefix_dereference(struct iaprefix **iapref, const char *file, int line) { + struct iaprefix *tmp; + + if ((iapref == NULL) || (*iapref == NULL)) { + log_error("%s(%d): NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = *iapref; + *iapref = NULL; + + tmp->refcnt--; + if (tmp->refcnt < 0) { + log_error("%s(%d): negative refcnt", file, line); + tmp->refcnt = 0; + } + if (tmp->refcnt == 0) { + if (tmp->ia_pd != NULL) { + ia_pd_dereference(&(tmp->ia_pd), file, line); + } + if (tmp->ipv6_ppool != NULL) { + ipv6_ppool_dereference(&(tmp->ipv6_ppool), file, line); + } + if (tmp->scope != NULL) { + binding_scope_dereference(&tmp->scope, file, line); + } + dfree(tmp, file, line); + } + + return ISC_R_SUCCESS; +} + /* - * Make the key that we use for IA_NA. + * Make the key that we use for IA. */ isc_result_t -ia_na_make_key(struct data_string *key, u_int32_t iaid, - const char *duid, unsigned int duid_len, - const char *file, int line) { +ia_make_key(struct data_string *key, u_int32_t iaid, + const char *duid, unsigned int duid_len, + const char *file, int line) { memset(key, 0, sizeof(*key)); key->len = duid_len + sizeof(iaid); @@ -159,9 +269,9 @@ ia_na_make_key(struct data_string *key, u_int32_t iaid, } /* - * Create a new IA_NA structure. + * Create a new IA structure. * - * - ia_na must be a pointer to a (struct ia_na *) pointer previously + * - ia must be a pointer to a (struct ia_na *) pointer previously * initialized to NULL * - iaid and duid are values from the client * @@ -170,16 +280,16 @@ ia_na_make_key(struct data_string *key, u_int32_t iaid, * between machines of different byte order */ isc_result_t -ia_na_allocate(struct ia_na **ia_na, u_int32_t iaid, +ia_na_allocate(struct ia_na **ia, u_int32_t iaid, const char *duid, unsigned int duid_len, const char *file, int line) { struct ia_na *tmp; - if (ia_na == NULL) { + if (ia == NULL) { log_error("%s(%d): NULL pointer reference", file, line); return ISC_R_INVALIDARG; } - if (*ia_na != NULL) { + if (*ia != NULL) { log_error("%s(%d): non-NULL pointer", file, line); return ISC_R_INVALIDARG; } @@ -189,32 +299,32 @@ ia_na_allocate(struct ia_na **ia_na, u_int32_t iaid, return ISC_R_NOMEMORY; } - if (ia_na_make_key(&tmp->iaid_duid, iaid, - duid, duid_len, file, line) != ISC_R_SUCCESS) { + if (ia_make_key(&tmp->iaid_duid, iaid, + duid, duid_len, file, line) != ISC_R_SUCCESS) { dfree(tmp, file, line); return ISC_R_NOMEMORY; } tmp->refcnt = 1; - *ia_na = tmp; + *ia = tmp; return ISC_R_SUCCESS; } /* - * Reference an IA_NA structure. + * Reference an IA structure. * - * - ia_na must be a pointer to a (struct ia_na *) pointer previously + * - ia must be a pointer to a (struct ia_na *) pointer previously * initialized to NULL */ isc_result_t -ia_na_reference(struct ia_na **ia_na, struct ia_na *src, +ia_na_reference(struct ia_na **ia, struct ia_na *src, const char *file, int line) { - if (ia_na == NULL) { + if (ia == NULL) { log_error("%s(%d): NULL pointer reference", file, line); return ISC_R_INVALIDARG; } - if (*ia_na != NULL) { + if (*ia != NULL) { log_error("%s(%d): non-NULL pointer", file, line); return ISC_R_INVALIDARG; } @@ -222,29 +332,29 @@ ia_na_reference(struct ia_na **ia_na, struct ia_na *src, log_error("%s(%d): NULL pointer reference", file, line); return ISC_R_INVALIDARG; } - *ia_na = src; + *ia = src; src->refcnt++; return ISC_R_SUCCESS; } /* - * Dereference an IA_NA structure. + * Dereference an IA structure. * * If it is the last reference, then the memory for the * structure is freed. */ isc_result_t -ia_na_dereference(struct ia_na **ia_na, const char *file, int line) { +ia_na_dereference(struct ia_na **ia, const char *file, int line) { struct ia_na *tmp; int i; - if ((ia_na == NULL) || (*ia_na == NULL)) { + if ((ia == NULL) || (*ia == NULL)) { log_error("%s(%d): NULL pointer", file, line); return ISC_R_INVALIDARG; } - tmp = *ia_na; - *ia_na = NULL; + tmp = *ia; + *ia = NULL; tmp->refcnt--; if (tmp->refcnt < 0) { @@ -267,10 +377,10 @@ ia_na_dereference(struct ia_na **ia_na, const char *file, int line) { /* - * Add an IAADDR entry to an IA_NA structure. + * Add an IAADDR entry to an IA structure. */ isc_result_t -ia_na_add_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, +ia_na_add_iaaddr(struct ia_na *ia, struct iaaddr *iaaddr, const char *file, int line) { int max; struct iaaddr **new; @@ -282,69 +392,69 @@ ia_na_add_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, * guess as to how many addresses we might expect on an * interface. */ - if (ia_na->max_iaaddr <= ia_na->num_iaaddr) { - max = ia_na->max_iaaddr + 4; + if (ia->max_iaaddr <= ia->num_iaaddr) { + max = ia->max_iaaddr + 4; new = dmalloc(max * sizeof(struct iaaddr *), file, line); if (new == NULL) { return ISC_R_NOMEMORY; } - memcpy(new, ia_na->iaaddr, - ia_na->num_iaaddr * sizeof(struct iaaddr *)); - ia_na->iaaddr = new; - ia_na->max_iaaddr = max; + memcpy(new, ia->iaaddr, + ia->num_iaaddr * sizeof(struct iaaddr *)); + ia->iaaddr = new; + ia->max_iaaddr = max; } - iaaddr_reference(&(ia_na->iaaddr[ia_na->num_iaaddr]), iaaddr, + iaaddr_reference(&(ia->iaaddr[ia->num_iaaddr]), iaaddr, file, line); - ia_na->num_iaaddr++; + ia->num_iaaddr++; return ISC_R_SUCCESS; } /* - * Remove an IAADDR entry to an IA_NA structure. + * Remove an IAADDR entry to an IA structure. * * Note: if an IAADDR appears more than once, then only ONE will be removed. */ void -ia_na_remove_iaaddr(struct ia_na *ia_na, struct iaaddr *iaaddr, +ia_na_remove_iaaddr(struct ia_na *ia, struct iaaddr *iaaddr, const char *file, int line) { int i, j; - for (i=0; i<ia_na->num_iaaddr; i++) { - if (ia_na->iaaddr[i] == iaaddr) { + for (i=0; i<ia->num_iaaddr; i++) { + if (ia->iaaddr[i] == iaaddr) { /* remove this IAADDR */ - iaaddr_dereference(&(ia_na->iaaddr[i]), file, line); + iaaddr_dereference(&(ia->iaaddr[i]), file, line); /* move remaining IAADDR pointers down one */ - for (j=i+1; j < ia_na->num_iaaddr; j++) { - ia_na->iaaddr[j-1] = ia_na->iaaddr[j]; + for (j=i+1; j < ia->num_iaaddr; j++) { + ia->iaaddr[j-1] = ia->iaaddr[j]; } /* decrease our total count */ /* remove the back-reference in the IAADDR itself */ ia_na_dereference(&iaaddr->ia_na, file, line); - ia_na->num_iaaddr--; + ia->num_iaaddr--; return; } } - log_error("%s(%d): IAADDR not in IA_NA", file, line); + log_error("%s(%d): IAADDR not in IA", file, line); } /* - * Remove all addresses from an IA_NA. + * Remove all addresses from an IA. */ void -ia_na_remove_all_iaaddr(struct ia_na *ia_na, const char *file, int line) { +ia_na_remove_all_iaaddr(struct ia_na *ia, const char *file, int line) { int i; - for (i=0; i<ia_na->num_iaaddr; i++) { - ia_na_dereference(&(ia_na->iaaddr[i]->ia_na), file, line); - iaaddr_dereference(&(ia_na->iaaddr[i]), file, line); + for (i=0; i<ia->num_iaaddr; i++) { + ia_na_dereference(&(ia->iaaddr[i]->ia_na), file, line); + iaaddr_dereference(&(ia->iaaddr[i]), file, line); } - ia_na->num_iaaddr = 0; + ia->num_iaaddr = 0; } /* - * Compare two IA_NA. + * Compare two IA. */ isc_boolean_t ia_na_equal(const struct ia_na *a, const struct ia_na *b) @@ -364,6 +474,13 @@ ia_na_equal(const struct ia_na *a, const struct ia_na *b) } /* + * Check the type is the same. + */ + if (a->ia_type != b->ia_type) { + return ISC_FALSE; + } + + /* * Check the DUID is the same. */ if (a->iaid_duid.len != b->iaid_duid.len) { @@ -389,7 +506,257 @@ ia_na_equal(const struct ia_na *a, const struct ia_na *b) for (j=0; j<a->num_iaaddr; j++) { if (memcmp(&(a->iaaddr[i]->addr), &(b->iaaddr[j]->addr), - sizeof(struct in6_addr) == 0)) { + sizeof(struct in6_addr)) == 0) { + found = ISC_TRUE; + break; + } + } + if (!found) { + return ISC_FALSE; + } + } + + /* + * These are the same in every way we care about. + */ + return ISC_TRUE; +} + +/* + * Create a new IA_PD structure. + * + * - ia_pd must be a pointer to a (struct ia_pd *) pointer previously + * initialized to NULL + * - iaid and duid are values from the client + * + * XXXsk: we don't concern ourself with the byte order of the IAID, + * which might be a problem if we transfer this structure + * between machines of different byte order + */ +isc_result_t +ia_pd_allocate(struct ia_pd **ia_pd, u_int32_t iaid, + const char *duid, unsigned int duid_len, + const char *file, int line) { + struct ia_pd *tmp; + + if (ia_pd == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*ia_pd != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = dmalloc(sizeof(*tmp), file, line); + if (tmp == NULL) { + return ISC_R_NOMEMORY; + } + + if (ia_make_key(&tmp->iaid_duid, iaid, + duid, duid_len, file, line) != ISC_R_SUCCESS) { + dfree(tmp, file, line); + return ISC_R_NOMEMORY; + } + + tmp->refcnt = 1; + + *ia_pd = tmp; + return ISC_R_SUCCESS; +} + +/* + * Reference an IA_PD structure. + * + * - ia_pd must be a pointer to a (struct ia_pd *) pointer previously + * initialized to NULL + */ +isc_result_t +ia_pd_reference(struct ia_pd **ia_pd, struct ia_pd *src, + const char *file, int line) { + if (ia_pd == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*ia_pd != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + if (src == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + *ia_pd = src; + src->refcnt++; + return ISC_R_SUCCESS; +} + +/* + * Dereference an IA_PD structure. + * + * If it is the last reference, then the memory for the + * structure is freed. + */ +isc_result_t +ia_pd_dereference(struct ia_pd **ia_pd, const char *file, int line) { + struct ia_pd *tmp; + int i; + + if ((ia_pd == NULL) || (*ia_pd == NULL)) { + log_error("%s(%d): NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = *ia_pd; + *ia_pd = NULL; + + tmp->refcnt--; + if (tmp->refcnt < 0) { + log_error("%s(%d): negative refcnt", file, line); + tmp->refcnt = 0; + } + if (tmp->refcnt == 0) { + if (tmp->iaprefix != NULL) { + for (i=0; i<tmp->num_iaprefix; i++) { + iaprefix_dereference(&(tmp->iaprefix[i]), + file, line); + } + dfree(tmp->iaprefix, file, line); + } + data_string_forget(&(tmp->iaid_duid), file, line); + dfree(tmp, file, line); + } + return ISC_R_SUCCESS; +} + + +/* + * Add an IAPREFIX entry to an IA_PD structure. + */ +isc_result_t +ia_pd_add_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iapref, + const char *file, int line) { + int max; + struct iaprefix **new; + + /* + * Grow our array if we need to. + * + * Note: we pick 4 as the increment, as that seems a reasonable + * guess as to how many prefixes we might expect on an + * interface. + */ + if (ia_pd->max_iaprefix <= ia_pd->num_iaprefix) { + max = ia_pd->max_iaprefix + 4; + new = dmalloc(max * sizeof(struct iaprefix *), file, line); + if (new == NULL) { + return ISC_R_NOMEMORY; + } + memcpy(new, ia_pd->iaprefix, + ia_pd->num_iaprefix * sizeof(struct iaprefix *)); + ia_pd->iaprefix = new; + ia_pd->max_iaprefix = max; + } + + iaprefix_reference(&(ia_pd->iaprefix[ia_pd->num_iaprefix]), iapref, + file, line); + ia_pd->num_iaprefix++; + + return ISC_R_SUCCESS; +} + +/* + * Remove an IAPREFIX entry to an IA_PD structure. + * + * Note: if an IAPREFIX appears more than once, then only ONE will be removed. + */ +void +ia_pd_remove_iaprefix(struct ia_pd *ia_pd, struct iaprefix *iapref, + const char *file, int line) { + int i, j; + + for (i=0; i<ia_pd->num_iaprefix; i++) { + if (ia_pd->iaprefix[i] == iapref) { + /* remove this IAPREFIX */ + iaprefix_dereference(&(ia_pd->iaprefix[i]), + file, line); + /* move remaining IAPREFIX pointers down one */ + for (j=i+1; j < ia_pd->num_iaprefix; j++) { + ia_pd->iaprefix[j-1] = ia_pd->iaprefix[j]; + } + /* decrease our total count */ + /* remove the back-reference in the IAPREFIX itself */ + ia_pd_dereference(&iapref->ia_pd, file, line); + ia_pd->num_iaprefix--; + return; + } + } + log_error("%s(%d): IAPREFIX not in IA_PD", file, line); +} + +/* + * Remove all prefixes from an IA_PD. + */ +void +ia_pd_remove_all_iaprefix(struct ia_pd *ia_pd, const char *file, int line) { + int i; + + for (i=0; i<ia_pd->num_iaprefix; i++) { + ia_pd_dereference(&(ia_pd->iaprefix[i]->ia_pd), file, line); + iaprefix_dereference(&(ia_pd->iaprefix[i]), file, line); + } + ia_pd->num_iaprefix = 0; +} + +/* + * Compare two IA_PD. + */ +isc_boolean_t +ia_pd_equal(const struct ia_pd *a, const struct ia_pd *b) +{ + isc_boolean_t found; + int i, j; + + /* + * Handle cases where one or both of the inputs is NULL. + */ + if (a == NULL) { + if (b == NULL) { + return ISC_TRUE; + } else { + return ISC_FALSE; + } + } + + /* + * Check the DUID is the same. + */ + if (a->iaid_duid.len != b->iaid_duid.len) { + return ISC_FALSE; + } + if (memcmp(a->iaid_duid.data, + b->iaid_duid.data, a->iaid_duid.len) != 0) { + return ISC_FALSE; + } + + /* + * Make sure we have the same number of prefixes in each. + */ + if (a->num_iaprefix != b->num_iaprefix) { + return ISC_FALSE; + } + + /* + * Check that each prefix is present in both. + */ + for (i=0; i<a->num_iaprefix; i++) { + found = ISC_FALSE; + for (j=0; j<a->num_iaprefix; j++) { + if (a->iaprefix[i]->plen != b->iaprefix[i]->plen) + continue; + if (memcmp(&(a->iaprefix[i]->pref), + &(b->iaprefix[j]->pref), + sizeof(struct in6_addr)) == 0) { found = ISC_TRUE; break; } @@ -408,6 +775,7 @@ ia_na_equal(const struct ia_na *a, const struct ia_na *b) /* * Helper function for lease heaps. * Makes the top of the heap the oldest lease. + * Note: this relies on the unique layout for leases! */ static isc_boolean_t lease_older(void *a, void *b) { @@ -419,7 +787,7 @@ lease_older(void *a, void *b) { } /* - * Helper function for lease heaps. + * Helper function for lease address heaps. * Callback when an address's position in the heap changes. */ static void @@ -427,9 +795,18 @@ lease_address_index_changed(void *iaaddr, unsigned int new_heap_index) { ((struct iaaddr *)iaaddr)-> heap_index = new_heap_index; } +/* + * Helper function for lease prefix heaps. + * Callback when a prefix's position in the heap changes. + */ +static void +lease_prefix_index_changed(void *iapref, unsigned int new_heap_index) { + ((struct iaprefix *)iapref)-> heap_index = new_heap_index; +} + /* - * Create a new IPv6 lease pool structure. + * Create a new IPv6 lease (address) pool structure. * * - pool must be a pointer to a (struct ipv6_pool *) pointer previously * initialized to NULL @@ -576,6 +953,156 @@ ipv6_pool_dereference(struct ipv6_pool **pool, const char *file, int line) { return ISC_R_SUCCESS; } +/* + * Create a new IPv6 lease (prefix) pool structure. + * + * - ppool must be a pointer to a (struct ipv6_ppool *) pointer previously + * initialized to NULL + */ +isc_result_t +ipv6_ppool_allocate(struct ipv6_ppool **ppool, + const struct in6_addr *start_pref, + u_int8_t pool_plen, u_int8_t alloc_plen, + const char *file, int line) { + struct ipv6_ppool *tmp; + + if (ppool == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*ppool != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = dmalloc(sizeof(*tmp), file, line); + if (tmp == NULL) { + return ISC_R_NOMEMORY; + } + + tmp->refcnt = 1; + tmp->start_pref = *start_pref; + tmp->pool_plen = pool_plen; + tmp->alloc_plen = alloc_plen; + if (!iaprefix_new_hash(&tmp->prefs, DEFAULT_HASH_SIZE, file, line)) { + dfree(tmp, file, line); + return ISC_R_NOMEMORY; + } + if (isc_heap_create(lease_older, lease_prefix_index_changed, + 0, &(tmp->active_timeouts)) != ISC_R_SUCCESS) { + iaprefix_free_hash_table(&(tmp->prefs), file, line); + dfree(tmp, file, line); + return ISC_R_NOMEMORY; + } + if (isc_heap_create(lease_older, lease_prefix_index_changed, + 0, &(tmp->inactive_timeouts)) != ISC_R_SUCCESS) { + isc_heap_destroy(&(tmp->active_timeouts)); + iaprefix_free_hash_table(&(tmp->prefs), file, line); + dfree(tmp, file, line); + return ISC_R_NOMEMORY; + } + + *ppool = tmp; + return ISC_R_SUCCESS; +} + +/* + * Reference an IPv6 prefix pool structure. + * + * - ppool must be a pointer to a (struct ppool *) pointer previously + * initialized to NULL + */ +isc_result_t +ipv6_ppool_reference(struct ipv6_ppool **ppool, struct ipv6_ppool *src, + const char *file, int line) { + if (ppool == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + if (*ppool != NULL) { + log_error("%s(%d): non-NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + if (src == NULL) { + log_error("%s(%d): NULL pointer reference", file, line); + return ISC_R_INVALIDARG; + } + *ppool = src; + src->refcnt++; + return ISC_R_SUCCESS; +} + +/* + * Note: Each IAPREFIX in a pool is referenced by the pool. This is needed + * to prevent the IAPREFIX from being garbage collected out from under the + * pool. + * + * The references are made from the hash and from the heap. The following + * helper functions dereference these when a pool is destroyed. + */ + +/* + * Helper function for prefix pool cleanup. + * Dereference each of the hash entries in a pool. + */ +static isc_result_t +dereference_phash_entry(const void *name, unsigned len, void *value) { + struct iaprefix *iapref = (struct iaprefix *)value; + + iaprefix_dereference(&iapref, MDL); + return ISC_R_SUCCESS; +} + +/* + * Helper function for prefix pool cleanup. + * Dereference each of the heap entries in a pool. + */ +static void +dereference_pheap_entry(void *value, void *dummy) { + struct iaprefix *iapref = (struct iaprefix *)value; + + iaprefix_dereference(&iapref, MDL); +} + + +/* + * Dereference an IPv6 prefix pool structure. + * + * If it is the last reference, then the memory for the + * structure is freed. + */ +isc_result_t +ipv6_ppool_dereference(struct ipv6_ppool **ppool, const char *file, int line) { + struct ipv6_ppool *tmp; + + if ((ppool == NULL) || (*ppool == NULL)) { + log_error("%s(%d): NULL pointer", file, line); + return ISC_R_INVALIDARG; + } + + tmp = *ppool; + *ppool = NULL; + + tmp->refcnt--; + if (tmp->refcnt < 0) { + log_error("%s(%d): negative refcnt", file, line); + tmp->refcnt = 0; + } + if (tmp->refcnt == 0) { + iaprefix_hash_foreach(tmp->prefs, dereference_phash_entry); + iaprefix_free_hash_table(&(tmp->prefs), file, line); + isc_heap_foreach(tmp->active_timeouts, + dereference_pheap_entry, NULL); + isc_heap_destroy(&(tmp->active_timeouts)); + isc_heap_foreach(tmp->inactive_timeouts, + dereference_pheap_entry, NULL); + isc_heap_destroy(&(tmp->inactive_timeouts)); + dfree(tmp, file, line); + } + + return ISC_R_SUCCESS; +} + /* * Create an address by hashing the input, and using that for * the non-network part. @@ -1018,6 +1545,417 @@ release_lease6(struct ipv6_pool *pool, struct iaaddr *addr) { } } +/* + * Create a prefix by hashing the input, and using that for + * the part subject to allocation. + */ +static void +create_prefix(struct in6_addr *pref, + const struct in6_addr *net_start_pref, + int pool_bits, int pref_bits, + const struct data_string *input) { + MD5_CTX ctx; + int net_bytes; + int i; + char *str; + const char *net_str; + + /* + * Use MD5 to get a nice 128 bit hash of the input. + * Yes, we know MD5 isn't cryptographically sound. + * No, we don't care. + */ + MD5_Init(&ctx); + MD5_Update(&ctx, input->data, input->len); + MD5_Final((unsigned char *)pref, &ctx); + + /* + * Copy the network bits over. + */ + str = (char *)pref; + net_str = (const char *)net_start_pref; + net_bytes = pool_bits / 8; + for (i=0; i<net_bytes; i++) { + str[i] = net_str[i]; + } + switch (pool_bits % 8) { + case 1: str[i] = (str[i] & 0x7F) | (net_str[i] & 0x80); break; + case 2: str[i] = (str[i] & 0x3F) | (net_str[i] & 0xC0); break; + case 3: str[i] = (str[i] & 0x1F) | (net_str[i] & 0xE0); break; + case 4: str[i] = (str[i] & 0x0F) | (net_str[i] & 0xF0); break; + case 5: str[i] = (str[i] & 0x07) | (net_str[i] & 0xF8); break; + case 6: str[i] = (str[i] & 0x03) | (net_str[i] & 0xFC); break; + case 7: str[i] = (str[i] & 0x01) | (net_str[i] & 0xFE); break; + } + /* + * Zero the remaining bits. + */ + net_bytes = pref_bits / 8; + for (i=net_bytes+1; i<16; i++) { + str[i] = 0; + } + switch (pref_bits % 8) { + case 0: str[i] &= 0; + case 1: str[i] &= 0x80; + case 2: str[i] &= 0xC0; + case 3: str[i] &= 0xE0; + case 4: str[i] &= 0xF0; + case 5: str[i] &= 0xF8; + case 6: str[i] &= 0xFC; + case 7: str[i] &= 0xFE; + } +} + +/* + * Create a lease for the given prefix and client duid. + * + * - ppool must be a pointer to a (struct ppool *) pointer previously + * initialized to NULL + * + * Right now we simply hash the DUID, and if we get a collision, we hash + * again until we find a free prefix. We try this a fixed number of times, + * to avoid getting stuck in a loop (this is important on small pools + * where we can run out of space). + * + * We return the number of attempts that it took to find an available + * prefix. This tells callers when a pool is are filling up, as + * well as an indication of how full the pool is; statistically the + * more full a pool is the more attempts must be made before finding + * a free prefix. Realistically this will only happen in very full + * pools. + * + * We probably want different algorithms depending on the network size, in + * the long term. + */ +isc_result_t +activate_prefix(struct ipv6_ppool *ppool, struct iaprefix **pref, + unsigned int *attempts, + const struct data_string *uid, + time_t valid_lifetime_end_time) { + struct data_string ds; + struct in6_addr tmp; + struct iaprefix *test_iapref; + struct data_string new_ds; + struct iaprefix *iapref; + isc_result_t result; + + /* + * Use the UID as our initial seed for the hash + */ + memset(&ds, 0, sizeof(ds)); + data_string_copy(&ds, (struct data_string *)uid, MDL); + + *attempts = 0; + for (;;) { + /* + * Give up at some point. + */ + if (++(*attempts) > 10) { + data_string_forget(&ds, MDL); + return ISC_R_NORESOURCES; + } + + /* + * Create a prefix + */ + create_prefix(&tmp, &ppool->start_pref, + (int)ppool->pool_plen, (int)ppool->alloc_plen, + &ds); + + /* + * If this prefix is not in use, we're happy with it + */ + test_iapref = NULL; + if (iaprefix_hash_lookup(&test_iapref, ppool->prefs, + &tmp, sizeof(tmp), MDL) == 0) { + break; + } + iaprefix_dereference(&test_iapref, MDL); + + /* + * Otherwise, we create a new input, adding the prefix + */ + memset(&new_ds, 0, sizeof(new_ds)); + new_ds.len = ds.len + sizeof(tmp); + if (!buffer_allocate(&new_ds.buffer, new_ds.len, MDL)) { + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + new_ds.data = new_ds.buffer->data; + memcpy(new_ds.buffer->data, ds.data, ds.len); + memcpy(new_ds.buffer->data + ds.len, &tmp, sizeof(tmp)); + data_string_forget(&ds, MDL); + data_string_copy(&ds, &new_ds, MDL); + data_string_forget(&new_ds, MDL); + } + + data_string_forget(&ds, MDL); + + /* + * We're happy with the prefix, create an IAPREFIX + * to hold it. + */ + iapref = NULL; + result = iaprefix_allocate(&iapref, MDL); + if (result != ISC_R_SUCCESS) { + return result; + } + memcpy(&iapref->pref, &tmp, sizeof(iapref->pref)); + + /* + * Add the prefix to the pool. + */ + result = add_prefix6(ppool, iapref, valid_lifetime_end_time); + if (result == ISC_R_SUCCESS) { + iaprefix_reference(pref, iapref, MDL); + } + iaprefix_dereference(&iapref, MDL); + return result; +} + +/* + * Put a prefix in the pool directly. This is intended to be used when + * loading leases from the file. + */ +isc_result_t +add_prefix6(struct ipv6_ppool *ppool, struct iaprefix *iapref, + time_t valid_lifetime_end_time) { + isc_result_t insert_result; + struct iaprefix *test_iapref; + struct iaprefix *tmp_iapref; + + /* If a state was not assigned by the caller, assume active. */ + if (iapref->state == 0) + iapref->state = FTS_ACTIVE; + + iapref->valid_lifetime_end_time = valid_lifetime_end_time; + ipv6_ppool_reference(&iapref->ipv6_ppool, ppool, MDL); + + /* + * If this IAPREFIX is already in our structures, remove the + * old one. + */ + test_iapref = NULL; + if (iaprefix_hash_lookup(&test_iapref, ppool->prefs, + &iapref->pref, sizeof(iapref->pref), MDL)) { + /* XXX: we should probably ask the iaprefix what heap it is on + * (as a consistency check). + * XXX: we should probably have one function to "put this + * prefix on its heap" rather than doing these if's + * everywhere. If you add more states to this list, don't. + */ + if ((test_iapref->state == FTS_ACTIVE) || + (test_iapref->state == FTS_ABANDONED)) { + isc_heap_delete(ppool->active_timeouts, + test_iapref->heap_index); + ppool->num_active--; + } else { + isc_heap_delete(ppool->inactive_timeouts, + test_iapref->heap_index); + ppool->num_inactive--; + } + + iaprefix_hash_delete(ppool->prefs, &test_iapref->pref, + sizeof(test_iapref->pref), MDL); + + /* + * We're going to do a bit of evil trickery here. + * + * We need to dereference the entry once to remove our + * current reference (in test_iapref), and then one + * more time to remove the reference left when the + * prefix was added to the pool before. + */ + tmp_iapref = test_iapref; + iaprefix_dereference(&test_iapref, MDL); + iaprefix_dereference(&tmp_iapref, MDL); + } + + /* + * Add IAPREFIX to our structures. + */ + tmp_iapref = NULL; + iaprefix_reference(&tmp_iapref, iapref, MDL); + if ((tmp_iapref->state == FTS_ACTIVE) || + (tmp_iapref->state == FTS_ABANDONED)) { + iaprefix_hash_add(ppool->prefs, &tmp_iapref->pref, + sizeof(tmp_iapref->pref), iapref, MDL); + insert_result = isc_heap_insert(ppool->active_timeouts, + tmp_iapref); + if (insert_result == ISC_R_SUCCESS) + ppool->num_active++; + } else { + insert_result = isc_heap_insert(ppool->inactive_timeouts, + tmp_iapref); + if (insert_result == ISC_R_SUCCESS) + ppool->num_inactive++; + } + if (insert_result != ISC_R_SUCCESS) { + iaprefix_hash_delete(ppool->prefs, &iapref->pref, + sizeof(iapref->pref), MDL); + iaprefix_dereference(&tmp_iapref, MDL); + return insert_result; + } + + /* + * Note: we intentionally leave tmp_iapref referenced; there + * is a reference in the heap/hash, after all. + */ + + return ISC_R_SUCCESS; +} + +/* + * Determine if a prefix is present in a pool or not. + */ +isc_boolean_t +prefix6_exists(const struct ipv6_ppool *ppool, + const struct in6_addr *pref, u_int8_t plen) { + struct iaprefix *test_iapref; + + if (plen != ppool->alloc_plen) + return ISC_FALSE; + + test_iapref = NULL; + if (iaprefix_hash_lookup(&test_iapref, ppool->prefs, + (void *)pref, sizeof(*pref), MDL)) { + iaprefix_dereference(&test_iapref, MDL); + return ISC_TRUE; + } else { + return ISC_FALSE; + } +} + +/* + * Put the prefix on our active pool. + */ +static isc_result_t +move_prefix_to_active(struct ipv6_ppool *ppool, struct iaprefix *pref) { + isc_result_t insert_result; + int old_heap_index; + + old_heap_index = pref->heap_index; + insert_result = isc_heap_insert(ppool->active_timeouts, pref); + if (insert_result == ISC_R_SUCCESS) { + iaprefix_hash_add(ppool->prefs, &pref->pref, + sizeof(pref->pref), pref, MDL); + isc_heap_delete(ppool->inactive_timeouts, old_heap_index); + ppool->num_active++; + ppool->num_inactive--; + pref->state = FTS_ACTIVE; + } + return insert_result; +} + +/* + * Renew a prefix in the pool. + * + * To do this, first set the new valid_lifetime_end_time for the prefix, + * and then invoke renew_prefix() on the prefix. + * + * WARNING: lease times must only be extended, never reduced!!! + */ +isc_result_t +renew_prefix6(struct ipv6_ppool *ppool, struct iaprefix *pref) { + /* + * If we're already active, then we can just move our expiration + * time down the heap. + * + * Otherwise, we have to move from the inactive heap to the + * active heap. + */ + if (pref->state == FTS_ACTIVE) { + isc_heap_decreased(ppool->active_timeouts, pref->heap_index); + return ISC_R_SUCCESS; + } else { + return move_prefix_to_active(ppool, pref); + } +} + +/* + * Put the prefix on our inactive pool, with the specified state. + */ +static isc_result_t +move_prefix_to_inactive(struct ipv6_ppool *ppool, struct iaprefix *pref, + binding_state_t state) { + isc_result_t insert_result; + int old_heap_index; + + old_heap_index = pref->heap_index; + insert_result = isc_heap_insert(ppool->inactive_timeouts, pref); + if (insert_result == ISC_R_SUCCESS) { + /* Process events upon expiration. */ + /* No DDNS for prefixes. */ + + /* Binding scopes are no longer valid after expiry or + * release. + */ + if (pref->scope != NULL) { + binding_scope_dereference(&pref->scope, MDL); + } + + iaprefix_hash_delete(ppool->prefs, + &pref->pref, sizeof(pref->pref), MDL); + isc_heap_delete(ppool->active_timeouts, old_heap_index); + pref->state = state; + ppool->num_active--; + ppool->num_inactive++; + } + return insert_result; +} + +/* + * Expire the oldest prefix if it's lifetime_end_time is + * older than the given time. + * + * - iapref must be a pointer to a (struct iaprefix *) pointer previously + * initialized to NULL + * + * On return iapref has a reference to the removed entry. It is left + * pointing to NULL if the oldest prefix has not expired. + */ +isc_result_t +expire_prefix6(struct iaprefix **pref, struct ipv6_ppool *ppool, time_t now) { + struct iaprefix *tmp; + isc_result_t result; + + if (pref == NULL) { + log_error("%s(%d): NULL pointer reference", MDL); + return ISC_R_INVALIDARG; + } + if (*pref != NULL) { + log_error("%s(%d): non-NULL pointer", MDL); + return ISC_R_INVALIDARG; + } + + if (ppool->num_active > 0) { + tmp = (struct iaprefix *) + isc_heap_element(ppool->active_timeouts, 1); + if (now > tmp->valid_lifetime_end_time) { + result = move_prefix_to_inactive(ppool, tmp, + FTS_EXPIRED); + if (result == ISC_R_SUCCESS) { + iaprefix_reference(pref, tmp, MDL); + } + return result; + } + } + return ISC_R_SUCCESS; +} + + +/* + * Put the returned prefix on our inactive pool. + */ +isc_result_t +release_prefix6(struct ipv6_ppool *ppool, struct iaprefix *pref) { + if (pref->state == FTS_ACTIVE) { + return move_prefix_to_inactive(ppool, pref, FTS_RELEASED); + } else { + return ISC_R_SUCCESS; + } +} + /* * Mark an IPv6 address as unavailable from a pool. * @@ -1063,12 +2001,38 @@ add_ipv6_pool(struct ipv6_pool *pool) { return ISC_R_SUCCESS; } +/* + * Add a prefix pool. + */ +isc_result_t +add_ipv6_ppool(struct ipv6_ppool *ppool) { + struct ipv6_ppool **new_ppools; + + new_ppools = dmalloc(sizeof(struct ipv6_ppool *) * (num_ppools + 1), + MDL); + if (new_ppools == NULL) { + return ISC_R_NOMEMORY; + } + + if (num_ppools > 0) { + memcpy(new_ppools, ppools, + sizeof(struct ipv6_ppool *) * num_ppools); + dfree(ppools, MDL); + } + ppools = new_ppools; + + ppools[num_ppools] = NULL; + ipv6_ppool_reference(&ppools[num_ppools], ppool, MDL); + num_ppools++; + return ISC_R_SUCCESS; +} + static void cleanup_old_expired(struct ipv6_pool *pool) { struct iaaddr *tmp; - struct ia_na *ia_na; - struct ia_na *ia_na_active; + struct ia_na *ia; + struct ia_na *ia_active; unsigned char *tmpd; while (pool->num_inactive > 0) { @@ -1084,24 +2048,34 @@ cleanup_old_expired(struct ipv6_pool *pool) { if (tmp->ia_na != NULL) { /* - * Check to see if this IA_NA is in the active list, + * Check to see if this IA is in the active list, * but has no remaining addresses. If so, remove it * from the active list. */ - ia_na = NULL; - ia_na_reference(&ia_na, tmp->ia_na, MDL); - ia_na_remove_iaaddr(ia_na, tmp, MDL); - ia_na_active = NULL; - tmpd = (unsigned char *)ia_na->iaid_duid.data; - if ((ia_na->num_iaaddr <= 0) && - (ia_na_hash_lookup(&ia_na_active, ia_active, tmpd, - ia_na->iaid_duid.len, + ia = NULL; + ia_na_reference(&ia, tmp->ia_na, MDL); + ia_na_remove_iaaddr(ia, tmp, MDL); + ia_active = NULL; + tmpd = (unsigned char *)ia->iaid_duid.data; + if ((ia->ia_type == D6O_IA_NA) && + (ia->num_iaaddr <= 0) && + (ia_na_hash_lookup(&ia_active, ia_na_active, tmpd, + ia->iaid_duid.len, + MDL) == 0) && + (ia_active == ia)) { + ia_na_hash_delete(ia_na_active, tmpd, + ia->iaid_duid.len, MDL); + } + if ((ia->ia_type == D6O_IA_TA) && + (ia->num_iaaddr <= 0) && + (ia_na_hash_lookup(&ia_active, ia_ta_active, tmpd, + ia->iaid_duid.len, MDL) == 0) && - (ia_na_active == ia_na)) { - ia_na_hash_delete(ia_active, tmpd, - ia_na->iaid_duid.len, MDL); + (ia_active == ia)) { + ia_na_hash_delete(ia_ta_active, tmpd, + ia->iaid_duid.len, MDL); } - ia_na_dereference(&ia_na, MDL); + ia_na_dereference(&ia, MDL); } iaaddr_dereference(&tmp, MDL); } @@ -1137,7 +2111,7 @@ lease_timeout_support(void *vpool) { */ ddns_removals(NULL, addr); - write_ia_na(addr->ia_na); + write_ia(addr->ia_na); iaaddr_dereference(&addr, MDL); } @@ -1202,6 +2176,138 @@ schedule_all_ipv6_lease_timeouts(void) { } } +static void +cleanup_old_pexpired(struct ipv6_ppool *ppool) { + struct iaprefix *tmp; + struct ia_pd *ia_pd; + struct ia_pd *ia_active; + unsigned char *tmpd; + + while (ppool->num_inactive > 0) { + tmp = (struct iaprefix *) + isc_heap_element(ppool->inactive_timeouts, 1); + if (cur_time < + tmp->valid_lifetime_end_time + EXPIRED_IPV6_CLEANUP_TIME) { + break; + } + + isc_heap_delete(ppool->inactive_timeouts, tmp->heap_index); + ppool->num_inactive--; + + if (tmp->ia_pd != NULL) { + /* + * Check to see if this IA_PD is in the active list, + * but has no remaining prefixes. If so, remove it + * from the active list. + */ + ia_pd = NULL; + ia_pd_reference(&ia_pd, tmp->ia_pd, MDL); + ia_pd_remove_iaprefix(ia_pd, tmp, MDL); + ia_active = NULL; + tmpd = (unsigned char *)ia_pd->iaid_duid.data; + if ((ia_pd->num_iaprefix <= 0) && + (ia_pd_hash_lookup(&ia_active, ia_pd_active, + tmpd, ia_pd->iaid_duid.len, + MDL) == 0) && + (ia_active == ia_pd)) { + ia_pd_hash_delete(ia_pd_active, tmpd, + ia_pd->iaid_duid.len, MDL); + } + ia_pd_dereference(&ia_pd, MDL); + } + iaprefix_dereference(&tmp, MDL); + } +} + +static void +prefix_timeout_support(void *vppool) { + struct ipv6_ppool *ppool; + struct iaprefix *pref; + + ppool = (struct ipv6_ppool *)vppool; + for (;;) { + /* + * Get the next prefix scheduled to expire. + * + * Note that if there are no prefixes in the pool, + * expire_prefix6() will return ISC_R_SUCCESS with + * a NULL prefix. + */ + pref = NULL; + if (expire_prefix6(&pref, ppool, cur_time) != ISC_R_SUCCESS) { + break; + } + if (pref == NULL) { + break; + } + + /* No DDNS for prefixes. */ + + write_ia_pd(pref->ia_pd); + + iaprefix_dereference(&pref, MDL); + } + + /* + * Do some cleanup of our expired prefixes. + */ + cleanup_old_pexpired(ppool); + + /* + * Schedule next round of expirations. + */ + schedule_prefix_timeout(ppool); +} + +/* + * For a given prefix pool, add a timer that will remove the next + * prefix to expire. + */ +void +schedule_prefix_timeout(struct ipv6_ppool *ppool) { + struct iaprefix *tmp; + time_t timeout; + time_t next_timeout; + + next_timeout = MAX_TIME; + + if (ppool->num_active > 0) { + tmp = (struct iaprefix *) + isc_heap_element(ppool->active_timeouts, 1); + if (tmp->valid_lifetime_end_time < next_timeout) { + next_timeout = tmp->valid_lifetime_end_time + 1; + } + } + + if (ppool->num_inactive > 0) { + tmp = (struct iaprefix *) + isc_heap_element(ppool->inactive_timeouts, 1); + timeout = tmp->valid_lifetime_end_time + + EXPIRED_IPV6_CLEANUP_TIME; + if (timeout < next_timeout) { + next_timeout = timeout; + } + } + + if (next_timeout < MAX_TIME) { + add_timeout(next_timeout, prefix_timeout_support, ppool, + (tvref_t)ipv6_ppool_reference, + (tvunref_t)ipv6_ppool_dereference); + } +} + +/* + * Schedule timeouts across all pools. + */ +void +schedule_all_ipv6_prefix_timeouts(void) { + int i; + + for (i=0; i<num_ppools; i++) { + schedule_prefix_timeout(ppools[i]); + } +} + /* * Given an address and the length of the network mask, return * only the network portion. @@ -1303,7 +2409,7 @@ find_ipv6_pool(struct ipv6_pool **pool, const struct in6_addr *addr) { * pools. */ static isc_result_t -change_leases(struct ia_na *ia_na, +change_leases(struct ia_na *ia, isc_result_t (*change_func)(struct ipv6_pool *, struct iaaddr*)) { isc_result_t retval; isc_result_t renew_retval; @@ -1312,11 +2418,11 @@ change_leases(struct ia_na *ia_na, int i; retval = ISC_R_SUCCESS; - for (i=0; i<ia_na->num_iaaddr; i++) { + for (i=0; i<ia->num_iaaddr; i++) { pool = NULL; - addr = &ia_na->iaaddr[i]->addr; + addr = &ia->iaaddr[i]->addr; if (find_ipv6_pool(&pool, addr) == ISC_R_SUCCESS) { - renew_retval = change_func(pool, ia_na->iaaddr[i]); + renew_retval = change_func(pool, ia->iaaddr[i]); if (renew_retval != ISC_R_SUCCESS) { retval = renew_retval; } @@ -1327,31 +2433,124 @@ change_leases(struct ia_na *ia_na, } /* - * Renew all leases in an IA_NA from all pools. + * Renew all leases in an IA from all pools. * * The new valid_lifetime_end_time should be updated for the addresses. * * WARNING: lease times must only be extended, never reduced!!! */ isc_result_t -renew_leases(struct ia_na *ia_na) { - return change_leases(ia_na, renew_lease6); +renew_leases(struct ia_na *ia) { + return change_leases(ia, renew_lease6); } /* - * Release all leases in an IA_NA from all pools. + * Release all leases in an IA from all pools. */ isc_result_t -release_leases(struct ia_na *ia_na) { - return change_leases(ia_na, release_lease6); +release_leases(struct ia_na *ia) { + return change_leases(ia, release_lease6); } /* - * Decline all leases in an IA_NA from all pools. + * Decline all leases in an IA from all pools. */ isc_result_t -decline_leases(struct ia_na *ia_na) { - return change_leases(ia_na, decline_lease6); +decline_leases(struct ia_na *ia) { + return change_leases(ia, decline_lease6); +} + +/* + * Determine if the given prefix is in the pool. + */ +isc_boolean_t +ipv6_prefix_in_ppool(const struct in6_addr *pref, + const struct ipv6_ppool *ppool) { + struct in6_addr tmp; + + ipv6_network_portion(&tmp, pref, (int)ppool->pool_plen); + if (memcmp(&tmp, &ppool->start_pref, sizeof(tmp)) == 0) { + return ISC_TRUE; + } else { + return ISC_FALSE; + } +} + +/* + * Find the pool that contains the given prefix. + * + * - pool must be a pointer to a (struct ipv6_ppool *) pointer previously + * initialized to NULL + */ +isc_result_t +find_ipv6_ppool(struct ipv6_ppool **ppool, const struct in6_addr *pref) { + int i; + + if (ppool == NULL) { + log_error("%s(%d): NULL pointer reference", MDL); + return ISC_R_INVALIDARG; + } + if (*ppool != NULL) { + log_error("%s(%d): non-NULL pointer", MDL); + return ISC_R_INVALIDARG; + } + + for (i=0; i<num_ppools; i++) { + if (ipv6_prefix_in_ppool(pref, ppools[i])) { + ipv6_ppool_reference(ppool, ppools[i], MDL); + return ISC_R_SUCCESS; + } + } + return ISC_R_NOTFOUND; +} + +/* + * Helper function for the various functions that act across all + * prefix pools. + */ +static isc_result_t +change_prefixes(struct ia_pd *ia_pd, + isc_result_t (*change_func)(struct ipv6_ppool *, + struct iaprefix*)) { + isc_result_t retval; + isc_result_t renew_retval; + struct ipv6_ppool *ppool; + struct in6_addr *pref; + int i; + + retval = ISC_R_SUCCESS; + for (i=0; i<ia_pd->num_iaprefix; i++) { + ppool = NULL; + pref = &ia_pd->iaprefix[i]->pref; + if (find_ipv6_ppool(&ppool, pref) == ISC_R_SUCCESS) { + renew_retval = change_func(ppool, ia_pd->iaprefix[i]); + if (renew_retval != ISC_R_SUCCESS) { + retval = renew_retval; + } + } + /* XXXsk: should we warn if we don't find a pool? */ + } + return retval; +} + +/* + * Renew all prefixes in an IA_PD from all pools. + * + * The new valid_lifetime_end_time should be updated for the addresses. + * + * WARNING: lease times must only be extended, never reduced!!! + */ +isc_result_t +renew_prefixes(struct ia_pd *ia_pd) { + return change_prefixes(ia_pd, renew_prefix6); +} + +/* + * Release all prefixes in an IA_PD from all pools. + */ +isc_result_t +release_prefixes(struct ia_pd *ia_pd) { + return change_prefixes(ia_pd, release_prefix6); } #ifdef DHCPv6 @@ -1361,11 +2560,26 @@ decline_leases(struct ia_na *ia_na) { static int write_error; static isc_result_t -write_ia_na_leases(const void *name, unsigned len, void *value) { - struct ia_na *ia_na = (struct ia_na *)value; +write_ia_leases(const void *name, unsigned len, void *value) { + struct ia_na *ia = (struct ia_na *)value; + + if (!write_error) { + if (!write_ia(ia)) { + write_error = 1; + } + } + return ISC_R_SUCCESS; +} + +/* + * Helper function to output prefixes. + */ +static isc_result_t +write_ia_pd_prefixes(const void *name, unsigned len, void *value) { + struct ia_pd *ia_pd = (struct ia_pd *)value; if (!write_error) { - if (!write_ia_na(ia_na)) { + if (!write_ia_pd(ia_pd)) { write_error = 1; } } @@ -1379,7 +2593,15 @@ int write_leases6(void) { write_error = 0; write_server_duid(); - ia_na_hash_foreach(ia_active, write_ia_na_leases); + ia_na_hash_foreach(ia_na_active, write_ia_leases); + if (write_error) { + return 0; + } + ia_na_hash_foreach(ia_ta_active, write_ia_leases); + if (write_error) { + return 0; + } + ia_pd_hash_foreach(ia_pd_active, write_ia_pd_prefixes); if (write_error) { return 0; } @@ -2070,6 +3292,26 @@ main(int argc, char *argv[]) { return 1; }*/ + { + struct in6_addr r; + struct data_string ds; + char buf[64]; + int i, j; + + ds.len = 16; + ds.data = &addr; + + inet_pton(AF_INET6, "3ffe:501:ffff:100::", &addr); + for (i = 32; i < 42; i++) + for (j = i + 1; j < 49; j++) { + memset(&r, 0, sizeof(r)); + memset(buf, 0, 64); + create_prefix(&r, &addr, i, j, &ds); + inet_ntop(AF_INET6, &r, buf, 64); + printf("%d,%d-> %s/%d\n", i, j, buf, j); + } + } + printf("SUCCESS: all tests passed (ignore any warning messages)\n"); return 0; } |