]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- DHCPv6 server Confirm message processing has been enhanced - it no
authorDavid Hankins <dhankins@isc.org>
Fri, 5 Oct 2007 15:57:48 +0000 (15:57 +0000)
committerDavid Hankins <dhankins@isc.org>
Fri, 5 Oct 2007 15:57:48 +0000 (15:57 +0000)
  longer replies only to clients with host {} records, it now replies
  as directed in RFC3315 section 18.2.2 - that is, to all clients
  regardless of the existence of bindings. [ISC-Bugs #17183]

RELNOTES
includes/dhcp6.h
server/dhcpv6.c

index 9417be7bc238f7305b6d62509eb4ac6c4ee5249c..706f00b220ec5a350a7c6ca486e7378f62e5d916 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -77,6 +77,11 @@ suggested fixes to <dhcp-users@isc.org>.
 - Error in decoding IA_NA option if multiple interfaces are present 
   fixed by Marcus Goller.
 
+- DHCPv6 server Confirm message processing has been enhanced - it no
+  longer replies only to clients with host {} records, it now replies
+  as directed in RFC3315 section 18.2.2 - that is, to all clients
+  regardless of the existence of bindings.
+
                        Changes since 4.0.0a2
 
 - Fix for startup where there are no IPv4 addresses on an interface.
index 146302a34f45767e6bf0b9072d9523c6ac0eae13..3de15ce3a60041656931b2757cc64a2d1b3a6427 100644 (file)
@@ -103,6 +103,10 @@ extern const int dhcpv6_type_name_max;
 #define DUID_EN                2
 #define DUID_LL                3
 
+/* Offsets into IA_*'s where Option spaces commence.  */
+#define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
+#define IA_TA_OFFSET  4 /* IAID only, 4 octets */
+
 /* 
  * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
  */
index 3272664ad9da080b78cd46169a06707cdcefa42a..13ac3e4e3f422127ca3f709e58f16fea07f4a668 100644 (file)
 /* 
  * Prototypes local to this file.
  */
+static int get_encapsulated_IA_state(struct option_state **enc_opt_state,
+                                    struct data_string *enc_opt_data,
+                                    struct packet *packet,
+                                    struct option_cache *oc,
+                                    int offset);
 static void build_dhcpv6_reply(struct data_string *, struct packet *);
+static isc_result_t shared_network_from_packet6(struct shared_network **shared,
+                                               struct packet *packet);
 
 /*
  * DUID time starts 2000-01-01.
@@ -593,16 +600,18 @@ static const int required_opts_STATUS_CODE[] = {
 };
 
 /*
- * Creates an option state and data string, based on the packet contents,
- * and the specific option defined in the option cache.
+ * Extracts from packet contents an IA_* option, storing the IA structure
+ * in its entirety in enc_opt_data, and storing any decoded DHCPv6 options
+ * in enc_opt_state for later lookup and evaluation.  The 'offset' indicates
+ * where in the IA_* the DHCPv6 options commence.
  */
 static int
 get_encapsulated_IA_state(struct option_state **enc_opt_state, 
                          struct data_string *enc_opt_data,
                          struct packet *packet,
-                         struct option_cache *oc)
+                         struct option_cache *oc,
+                         int offset)
 {
-
        /* 
         * Get the raw data for the encapsulated options.
         */
@@ -614,7 +623,7 @@ get_encapsulated_IA_state(struct option_state **enc_opt_state,
                          "error evaluating raw option.");
                return 0;
        }
-       if (enc_opt_data->len < 12) {
+       if (enc_opt_data->len < offset) {
                log_error("get_encapsulated_IA_state: raw option too small.");
                data_string_forget(enc_opt_data, MDL);
                return 0;
@@ -631,8 +640,8 @@ get_encapsulated_IA_state(struct option_state **enc_opt_state,
                return 0;
        }
        if (!parse_option_buffer(*enc_opt_state, 
-                                enc_opt_data->data+12
-                                enc_opt_data->len-12,
+                                enc_opt_data->data + offset
+                                enc_opt_data->len - offset,
                                 &dhcpv6_universe)) {
                log_error("get_encapsulated_IA_state: error parsing options.");
                option_state_dereference(enc_opt_state, MDL);
@@ -846,65 +855,23 @@ pick_v6_address(struct iaaddr **addr,
                const struct data_string *requested_iaaddr,
                const struct data_string *client_id)
 {
-       const struct packet *chk_packet;
-       const struct in6_addr *link_addr;
-       const struct in6_addr *first_link_addr;
-       struct iaddr tmp_addr;
-       struct subnet *subnet;
        struct shared_network *shared_network;
        struct ipv6_pool *p;
+       isc_result_t status;
        int i;
        int start_pool;
        unsigned int attempts;
        char tmp_buf[INET6_ADDRSTRLEN];
 
-       /*
-        * First, find the link address where the packet from the client
-        * first appeared.
+       /* First, find the shared network the client inhabits, so that an
+        * appropriate address can be selected.
         */
-       first_link_addr = NULL;
-       chk_packet = packet->dhcpv6_container_packet;
-       while (chk_packet != NULL) {
-               link_addr = &chk_packet->dhcpv6_link_address;
-               if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
-                   !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
-                       first_link_addr = link_addr;
-               }
-               chk_packet = chk_packet->dhcpv6_container_packet;
-       }
-
-       /*
-        * If there is a link address, find the subnet associated 
-        * with that, and use that to get the appropriate 
-        * shared_network.
-        */
-       if (first_link_addr != NULL) {
-               tmp_addr.len = sizeof(*first_link_addr);
-               memcpy(tmp_addr.iabuf, 
-                      first_link_addr, sizeof(*first_link_addr));
-               subnet = NULL;
-               if (!find_subnet(&subnet, tmp_addr, MDL)) {
-                       log_debug("Unable to pick client address: "
-                                 "no subnet found for link-address %s.",
-                                 piaddr(tmp_addr));
-                       return ISC_R_NOTFOUND;
-               }
-               shared_network = NULL;
-               shared_network_reference(&shared_network, 
-                                        subnet->shared_network, MDL);
-               subnet_dereference(&subnet, MDL);
-       }
-
-       /* 
-        * If there is no link address, we will use the interface
-        * that this packet came in on to pick the shared_network.
-        */
-       else {
-               shared_network = NULL;
-               shared_network_reference(&shared_network, 
-                                        packet->interface->shared_network,
-                                        MDL);
-       }
+       shared_network = NULL;
+       status = shared_network_from_packet6(&shared_network, packet);
+       if (status != ISC_R_SUCCESS)
+               return status;
+       else if (shared_network == NULL)
+               return ISC_R_NOTFOUND; /* invarg?  invalid condition... */
 
        /*
         * No pools, we're done.
@@ -1154,7 +1121,7 @@ lease_to_client(struct data_string *reply_ret,
 
                if (!get_encapsulated_IA_state(&cli_enc_opt_state,
                                               &cli_enc_opt_data,
-                                              packet, ia)) {
+                                              packet, ia, IA_NA_OFFSET)) {
                        goto exit;
                }
 
@@ -1899,142 +1866,233 @@ dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
        data_string_forget(&server_id, MDL);
 }
 
+/* Find a DHCPv6 packet's shared network from hints in the packet.
+ */
+static isc_result_t
+shared_network_from_packet6(struct shared_network **shared,
+                           struct packet *packet)
+{
+       const struct packet *chk_packet;
+       const struct in6_addr *link_addr, *first_link_addr;
+       struct iaddr tmp_addr;
+       struct subnet *subnet;
+       isc_result_t status;
+
+       if ((shared == NULL) || (*shared != NULL) || (packet == NULL))
+               return ISC_R_INVALIDARG;
+
+       /*
+        * First, find the link address where the packet from the client
+        * first appeared (if this packet was relayed).
+        */
+       first_link_addr = NULL;
+       chk_packet = packet->dhcpv6_container_packet;
+       while (chk_packet != NULL) {
+               link_addr = &chk_packet->dhcpv6_link_address;
+               if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
+                   !IN6_IS_ADDR_LINKLOCAL(link_addr)) {
+                       first_link_addr = link_addr;
+               }
+               chk_packet = chk_packet->dhcpv6_container_packet;
+       }
+
+       /*
+        * If there is a relayed link address, find the subnet associated
+        * with that, and use that to get the appropriate
+        * shared_network.
+        */
+       if (first_link_addr != NULL) {
+               tmp_addr.len = sizeof(*first_link_addr);
+               memcpy(tmp_addr.iabuf,
+                      first_link_addr, sizeof(*first_link_addr));
+               subnet = NULL;
+               if (!find_subnet(&subnet, tmp_addr, MDL)) {
+                       log_debug("No subnet found for link-address %s.",
+                                 piaddr(tmp_addr));
+                       return ISC_R_NOTFOUND;
+               }
+               status = shared_network_reference(shared,
+                                                 subnet->shared_network, MDL);
+               subnet_dereference(&subnet, MDL);
+
+       /*
+        * If there is no link address, we will use the interface
+        * that this packet came in on to pick the shared_network.
+        */
+       } else {
+               status = shared_network_reference(shared,
+                                        packet->interface->shared_network,
+                                        MDL);
+       }
+
+       return status;
+}
+
 /*
  * When a client thinks it might be on a new link, it sends a 
  * Confirm message.
+ *
+ * From RFC3315 section 18.2.2:
+ *
+ *   When the server receives a Confirm message, the server determines
+ *   whether the addresses in the Confirm message are appropriate for the
+ *   link to which the client is attached.  If all of the addresses in the
+ *   Confirm message pass this test, the server returns a status of
+ *   Success.  If any of the addresses do not pass this test, the server
+ *   returns a status of NotOnLink.  If the server is unable to perform
+ *   this test (for example, the server does not have information about
+ *   prefixes on the link to which the client is connected), or there were
+ *   no addresses in any of the IAs sent by the client, the server MUST
+ *   NOT send a reply to the client.
  */
 
 /* TODO: discard unicast messages, unless we set unicast option */
 static void
 dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
-       struct data_string client_id;
-       struct option_state *opt_state;
+       struct shared_network *shared;
+       struct subnet *subnet;
+       struct option_cache *ia, *ta, *oc;
+       struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro;
+       struct option_state *cli_enc_opt_state, *opt_state;
+       struct iaddr cli_addr;
+       int pass;
+       isc_boolean_t inappropriate, has_addrs;
        char reply_data[65536];
        struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
        int reply_ofs = (int)((char *)reply->options - (char *)reply);
-       struct option_cache *ia;
-       struct option_cache *oc;
-       /* cli_enc_... variables come from the IA_NA/IA_TA options */
-       struct data_string cli_enc_opt_data;
-       struct option_state *cli_enc_opt_state;
-       struct data_string iaaddr;
-       struct host_decl *host;
-       int num_ia;
-       int num_addr;
-       struct data_string fixed_addr;
-       struct data_string packet_oro;
 
        /* 
-        * Validate the message.
+        * Basic client message validation.
         */
+       memset(&client_id, 0, sizeof(client_id));
        if (!valid_client_msg(packet, &client_id)) {
                return;
        }
 
+       /* Do not process Confirms that do not have IA's we do not recognize.
+        */
+       ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
+       ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
+       if ((ia == NULL) && (ta == NULL))
+               return;
+
        /* 
         * Bit of variable initialization.
         */
+       opt_state = cli_enc_opt_state = NULL;
+       memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
        memset(&iaaddr, 0, sizeof(iaaddr));
-       num_ia = 0;
-       num_addr = 0;
-       memset(&fixed_addr, 0, sizeof(fixed_addr));
        memset(&packet_oro, 0, sizeof(packet_oro));
 
-       /* 
-        * Set up reply.
+       /* Determine what shared network the client is connected to.  We
+        * must not respond if we don't have any information about the
+        * network the client is on.
         */
-       if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
+       shared = NULL;
+       if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) ||
+           (shared == NULL))
                goto exit;
-       }
 
-       /* 
-        * Search each IA to make sure we know about it.
+       /* If there are no recorded subnets, then we have no
+        * information about this subnet - ignore Confirms.
         */
-       ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
-       for (; ia != NULL; ia = ia->next) {
-               num_ia++;
+       subnet = shared->subnets;
+       if (subnet == NULL)
+               goto exit;
 
-               if (!get_encapsulated_IA_state(&cli_enc_opt_state,
-                                              &cli_enc_opt_data,
-                                              packet, ia)) {
-                       goto exit;
+       /* Are the addresses in all the IA's appropriate for that link? */
+       has_addrs = inappropriate = ISC_FALSE;
+       pass = D6O_IA_NA;
+       while(!inappropriate) {
+               /* If we've reached the end of the IA_NA pass, move to the
+                * IA_TA pass.
+                */
+               if ((pass == D6O_IA_NA) && (ia == NULL)) {
+                       pass = D6O_IA_TA;
+                       ia = ta;
                }
 
-               memset(&iaaddr, 0, sizeof(iaaddr));
-               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, 
-                                  D6O_IAADDR);
-               if (oc == NULL) {
-                       /* 
-                        * No address with this IA, check next.
-                        */
-                       continue;
-               }
+               /* If we've reached the end of all passes, we're done. */
+               if (ia == NULL)
+                       break;
 
-               if (!evaluate_option_cache(&iaaddr, packet, 
-                                          NULL, NULL, 
-                                          packet->options, 
-                                          NULL, &global_scope,
-                                          oc, MDL)) {
-                       log_error("dhcpv6_confirm: "
-                                 "error evaluating IAADDR.");
+               if (((pass == D6O_IA_NA) &&
+                    !get_encapsulated_IA_state(&cli_enc_opt_state,
+                                               &cli_enc_opt_data,
+                                               packet, ia, IA_NA_OFFSET)) ||
+                   ((pass == D6O_IA_TA) &&
+                    !get_encapsulated_IA_state(&cli_enc_opt_state,
+                                               &cli_enc_opt_data,
+                                               packet, ia, IA_TA_OFFSET))) {
                        goto exit;
                }
 
-               host = NULL;
-               if (!find_hosts_by_uid(&host, 
-                                      client_id.data, client_id.len, MDL)) {
-                       /*
-                        * RFC 3315, section 18.2.2 implies that when the
-                        * server doesn't know about the client it should
-                        * not send a reply.
-                        */
-                       goto exit;
-               }
+               oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
+                                  D6O_IAADDR);
 
-               /* 
-                * Find the matching host entry, if any.
-                */
-               for (; host != NULL; host = host->n_ipaddr) {
-                       if (host->fixed_addr == NULL) {
-                               continue;
-                       } 
-                       if (!evaluate_option_cache(&fixed_addr, NULL, 
-                                                  NULL, NULL, NULL, 
-                                                  NULL, &global_scope,
-                                                  host->fixed_addr, 
-                                                  MDL)) {
-                               log_error("dhcpv6_confirm: error "
-                                         "evaluating host address.");
+               for ( ; oc != NULL ; oc = oc->next) {
+                       if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
+                                                  packet->options, NULL,
+                                                  &global_scope, oc, MDL) ||
+                           (iaaddr.len < 24)) {
+                               log_error("dhcpv6_confirm: "
+                                         "error evaluating IAADDR.");
                                goto exit;
                        }
-                       if ((iaaddr.len >= 16) &&
-                           !memcmp(fixed_addr.data, iaaddr.data, 16)) {
-                               data_string_forget(&fixed_addr, MDL);
+
+                       /* Copy out the IPv6 address for processing. */
+                       cli_addr.len = 16;
+                       memcpy(cli_addr.iabuf, iaaddr.data, 16);
+
+                       /* Record that we've processed at least one address. */
+                       has_addrs = ISC_TRUE;
+
+                       /* Find out if any subnets cover this address. */
+                       for (subnet = shared->subnets ; subnet != NULL ;
+                            subnet = subnet->next_sibling) {
+                               if (addr_eq(subnet_number(cli_addr,
+                                                         subnet->netmask),
+                                           subnet->net))
+                                       break;
+                       }
+
+                       data_string_forget(&iaaddr, MDL);
+
+                       /* If we reach the end of the subnet list, and no
+                        * subnet matches the client address, then it must
+                        * be inappropriate to the link (so far as our
+                        * configuration says).  Once we've found one
+                        * inappropriate address, there is no reason to
+                        * continue searching.
+                        */
+                       if (subnet == NULL) {
+                               inappropriate = ISC_TRUE;
                                break;
                        }
-                       data_string_forget(&fixed_addr, MDL);
-               }
+                }
 
-               /* 
-                * No matching entry found, so this is a bad address.
-                */
-               if (host != NULL) {
-                       num_addr++;
-               }
+               option_state_dereference(&cli_enc_opt_state, MDL);
+               data_string_forget(&cli_enc_opt_data, MDL);
 
+               /* Advance to the next IA_*. */
+               ia = ia->next;
        }
 
+       /* If the client supplied no addresses, do not reply. */
+       if (!has_addrs)
+               goto exit;
+
        /* 
-        * If we have no addresses from the client, we don't send a reply.
+        * Set up reply.
         */
-       if (num_addr == 0) {
+       if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
                goto exit;
        }
 
        /* 
         * Set our status.
         */
-       if (num_addr < num_ia) {
+       if (inappropriate) {
                if (!set_status_code(STATUS_NotOnLink, 
                                     "Some of the addresses are not on link.",
                                     opt_state)) {
@@ -2068,13 +2126,21 @@ dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
        memcpy(reply_ret->buffer->data, reply, reply_ofs);
 
 exit:
-       if (iaaddr.buffer != NULL) {
+       /* Cleanup any stale data strings. */
+       if (cli_enc_opt_data.buffer != NULL)
+               data_string_forget(&cli_enc_opt_data, MDL);
+       if (iaaddr.buffer != NULL)
                data_string_forget(&iaaddr, MDL);
-       }
-       if (fixed_addr.buffer != NULL) {
-               data_string_forget(&fixed_addr, MDL);
-       }
-       data_string_forget(&client_id, MDL);
+       if (client_id.buffer != NULL)
+               data_string_forget(&client_id, MDL);
+       if (packet_oro.buffer != NULL)
+               data_string_forget(&packet_oro, MDL);
+
+       /* Release any stale option states. */
+       if (cli_enc_opt_state != NULL)
+               option_state_dereference(&cli_enc_opt_state, MDL);
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
 }
 
 /*
@@ -2348,7 +2414,7 @@ iterate_over_ia_na(struct data_string *reply_ret,
 
                if (!get_encapsulated_IA_state(&cli_enc_opt_state,
                                               &cli_enc_opt_data,
-                                              packet, ia)) {
+                                              packet, ia, IA_NA_OFFSET)) {
                        goto exit;
                }