]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
Basic and partial DHCPv6 leasequery support
authorFrancis Dupont <fdupont@isc.org>
Tue, 26 Feb 2008 23:45:05 +0000 (23:45 +0000)
committerFrancis Dupont <fdupont@isc.org>
Tue, 26 Feb 2008 23:45:05 +0000 (23:45 +0000)
RELNOTES
includes/dhcp6.h
includes/dhcpd.h
server/dhcpleasequery.c
server/dhcpv6.c

index 8dfa5d9e31a8b08028a1b36d850deb4b37ceb36e..31e0050783c6ac369cffe9af0ea8be685478eeda 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -17,6 +17,8 @@ in DHCP 4.0.x.  These include:
 
 - A basic DHCPv6 relay agent, dhcrelay6
 
+- Basic and partial DHCPv6 leasequery support
+
 There are a number of DHCPv6 limitations and features missing in this
 release, which will be addressed in the future:
 
@@ -89,7 +91,9 @@ work on other platforms. Please report any problems and suggested fixes to
   The default delayed ack limit is 28.  Thanks entirely to a patch from
   Christof Chen.
 
- - ERO (RFC 4994) server support.
+- ERO (RFC 4994) server support.
+
+- Basic and partial DHCPv6 leasequery support.
 
                        Changes since 4.0.0 (bug fixes)
 
index c5ed02d910fc1291e4b2dae2d0a1c53ed1252ff9..4b526b05073d8baedb18812319efb7c3da6bab4f 100644 (file)
@@ -140,7 +140,7 @@ extern const int dhcpv6_type_name_max;
 #define All_DHCP_Servers "FF05::1:3"
 
 /*
- * DHCPv6 Retransmission Constants (RFC3315 section 5.5)
+ * DHCPv6 Retransmission Constants (RFC3315 section 5.5, RFC 5007)
  */
 
 #define SOL_MAX_DELAY     1
@@ -167,6 +167,9 @@ extern const int dhcpv6_type_name_max;
 #define REC_TIMEOUT       2
 #define REC_MAX_RC        8
 #define HOP_COUNT_LIMIT  32
+#define LQ6_TIMEOUT       1
+#define LQ6_MAX_RT       10
+#define LQ6_MAX_RC        5
 
 /* 
  * Normal packet format, defined in section 6 of RFC 3315 
index d11ea8acd0bda9ea50074d756db84c67c3bdb993..489075c64faa6dfc899957269884d3e1e6798be4 100644 (file)
@@ -1946,7 +1946,6 @@ void dhcprequest PROTO ((struct packet *, int, struct lease *));
 void dhcprelease PROTO ((struct packet *, int));
 void dhcpdecline PROTO ((struct packet *, int));
 void dhcpinform PROTO ((struct packet *, int));
-void dhcpleasequery PROTO ((struct packet *, int));
 void nak_lease PROTO ((struct packet *, struct iaddr *cip));
 void ack_lease PROTO ((struct packet *, struct lease *,
                       unsigned int, TIME, char *, int, struct host_decl *));
@@ -1974,6 +1973,10 @@ void get_server_source_address(struct in_addr *from,
                               struct option_state *options,
                               struct packet *packet);
 
+/* dhcpleasequery.c */
+void dhcpleasequery PROTO ((struct packet *, int));
+void dhcpv6_leasequery PROTO ((struct data_string *, struct packet *));
+
 /* dhcpv6.c */
 isc_boolean_t server_duid_isset(void);
 void copy_server_duid(struct data_string *ds, const char *file, int line);
@@ -1981,6 +1984,7 @@ void set_server_duid(struct data_string *new_duid);
 isc_result_t set_server_duid_from_option(void);
 void set_server_duid_type(int type);
 isc_result_t generate_new_server_duid(void);
+isc_result_t get_client_id(struct packet *, struct data_string *);
 void dhcpv6(struct packet *);
 
 /* bootp.c */
index c46e4acb1169f33d0799a339daf0251ca2329b59..dad45182d2f62fd1ef21cc23bc6f7f5dfbbd4476 100644 (file)
@@ -645,3 +645,565 @@ dhcpleasequery(struct packet *packet, int ms_nulltp) {
                    NULL);
 }
 
+#ifdef DHCPv6
+
+/*
+ * TODO: RFC5007 query-by-clientid.
+ *
+ * TODO: RFC5007 look at the pools according to the link-address.
+ *
+ * TODO: get fixed leases too.
+ *
+ * TODO: RFC5007 ORO in query-options.
+ *
+ * TODO: RFC5007 not default preferred and valid time.
+ *
+ * TODO: RFC5007 not zero Client Last Transaction Time (clt-time).
+ *
+ * TODO: RFC5007 lq-relay-data.
+ *
+ * TODO: RFC5007 lq-client-link.
+ *
+ * Note: the code is still nearly compliant and usable for the target
+ * case with these missing features!
+ */
+
+/*
+ * The structure to handle a leasequery.
+ */
+struct lq6_state {
+       struct packet *packet;
+       struct data_string client_id;
+       struct data_string server_id;
+       struct data_string lq_query;
+       uint8_t query_type;
+       struct in6_addr link_addr;
+       struct option_state *query_opts;
+
+       struct option_state *reply_opts;
+       unsigned cursor;
+       union reply_buffer {
+               unsigned char data[65536];
+               struct dhcpv6_packet reply;
+       } buf;
+};
+
+/*
+ * Options that we want to send.
+ */
+static const int required_opts_lq[] = {
+       D6O_CLIENTID,
+       D6O_SERVERID,
+       D6O_STATUS_CODE,
+       D6O_CLIENT_DATA,
+       D6O_LQ_RELAY_DATA,
+       D6O_LQ_CLIENT_LINK,
+       0
+};
+static const int required_opt_CLIENT_DATA[] = {
+       D6O_CLIENTID,
+       D6O_IAADDR,
+       D6O_IAPREFIX,
+       D6O_CLT_TIME,
+       0
+};
+
+/*
+ * Get the lq-query option from the packet.
+ */
+static isc_result_t
+get_lq_query(struct lq6_state *lq)
+{
+       struct data_string *lq_query = &lq->lq_query;
+       struct packet *packet = lq->packet;
+       struct option_cache *oc;
+
+       /*
+        * Verify our lq_query structure is empty.
+        */
+       if ((lq_query->data != NULL) || (lq_query->len != 0)) {
+               return ISC_R_INVALIDARG;
+       }
+
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_LQ_QUERY);
+       if (oc == NULL) {
+               return ISC_R_NOTFOUND;
+       }
+
+       if (!evaluate_option_cache(lq_query, packet, NULL, NULL,
+                                  packet->options, NULL,
+                                  &global_scope, oc, MDL)) {
+               return ISC_R_FAILURE;
+       }
+
+       return ISC_R_SUCCESS;
+}
+
+/*
+ * Message validation, RFC 5007 section 4.2.1:
+ *  dhcpv6.c:valid_client_msg() - unicast + lq-query option.
+ */
+static int
+valid_query_msg(struct lq6_state *lq) {
+       struct packet *packet = lq->packet;
+       int ret_val = 0;
+       struct option_cache *oc;
+
+       /* INSIST((lq != NULL) || (packet != NULL)); */
+
+       switch (get_client_id(packet, &lq->client_id)) {
+               case ISC_R_SUCCESS:
+                       break;
+               case ISC_R_NOTFOUND:
+                       log_debug("Discarding %s from %s; "
+                                 "client identifier missing", 
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 piaddr(packet->client_addr));
+                       goto exit;
+               default:
+                       log_error("Error processing %s from %s; "
+                                 "unable to evaluate Client Identifier",
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 piaddr(packet->client_addr));
+                       goto exit;
+       }
+
+       oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
+       if (oc != NULL) {
+               if (evaluate_option_cache(&lq->server_id, packet, NULL, NULL,
+                                         packet->options, NULL, 
+                                         &global_scope, oc, MDL)) {
+                       log_debug("Discarding %s from %s; " 
+                                 "server identifier found "
+                                 "(CLIENTID %s, SERVERID %s)", 
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 piaddr(packet->client_addr),
+                                 print_hex_1(lq->client_id.len, 
+                                             lq->client_id.data, 60),
+                                 print_hex_2(lq->server_id.len,
+                                             lq->server_id.data, 60));
+               } else {
+                       log_debug("Discarding %s from %s; " 
+                                 "server identifier found "
+                                 "(CLIENTID %s)", 
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 print_hex_1(lq->client_id.len, 
+                                             lq->client_id.data, 60),
+                                 piaddr(packet->client_addr));
+               }
+               goto exit;
+       }
+
+       switch (get_lq_query(lq)) {
+               case ISC_R_SUCCESS:
+                       break;
+               case ISC_R_NOTFOUND:
+                       log_debug("Discarding %s from %s; lq-query missing",
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 piaddr(packet->client_addr));
+                       goto exit;
+               default:
+                       log_error("Error processing %s from %s; "
+                                 "unable to evaluate LQ-Query",
+                                 dhcpv6_type_names[packet->dhcpv6_msg_type],
+                                 piaddr(packet->client_addr));
+                       goto exit;
+       }
+
+       /* looks good */
+       ret_val = 1;
+
+exit:
+       if (!ret_val) {
+               if (lq->client_id.len > 0) {
+                       data_string_forget(&lq->client_id, MDL);
+               }
+               if (lq->server_id.len > 0) {
+                       data_string_forget(&lq->server_id, MDL);
+               }
+               if (lq->lq_query.len > 0) {
+                       data_string_forget(&lq->lq_query, MDL);
+               }
+       }
+       return ret_val;
+}
+
+/*
+ * Set an error in a status-code option (from set_status_code).
+ */
+static int
+set_error(struct lq6_state *lq, u_int16_t code, const char *message) {
+       struct data_string d;
+       int ret_val;
+
+       memset(&d, 0, sizeof(d));
+       d.len = sizeof(code) + strlen(message);
+       if (!buffer_allocate(&d.buffer, d.len, MDL)) {
+               log_fatal("set_error: no memory for status code.");
+       }
+       d.data = d.buffer->data;
+       putUShort(d.buffer->data, code);
+       memcpy(d.buffer->data + sizeof(code), message, d.len - sizeof(code));
+       if (!save_option_buffer(&dhcpv6_universe, lq->reply_opts,
+                               d.buffer, (unsigned char *)d.data, d.len, 
+                               D6O_STATUS_CODE, 0)) {
+               log_error("set_error: error saving status code.");
+               ret_val = 0;
+       } else {
+               ret_val = 1;
+       }
+       data_string_forget(&d, MDL);
+       return ret_val;
+}
+
+/*
+ * Process a by-address lease query.
+ */
+static int
+process_lq_by_address(struct lq6_state *lq) {
+       struct packet *packet = lq->packet;
+       struct option_cache *oc;
+       struct ipv6_pool *pool = NULL;
+       struct data_string data;
+       struct in6_addr addr;
+       struct iaaddr *iaaddr = NULL;
+       struct option_state *opt_state = NULL;
+       u_int32_t lifetime;
+       unsigned opt_cursor;
+       int ret_val = 0;
+
+       /*
+        * Get the IAADDR.
+        */
+       oc = lookup_option(&dhcpv6_universe, lq->query_opts, D6O_IAADDR);
+       if (oc == NULL) {
+               if (!set_error(lq, STATUS_MalformedQuery,
+                              "No OPTION_IAADDR.")) {
+                       log_error("process_lq_by_address: unable "
+                                 "to set MalformedQuery status code.");
+                       return 0;
+               }
+               return 1;
+       }
+       memset(&data, 0, sizeof(data));
+       if (!evaluate_option_cache(&data, packet,
+                                  NULL, NULL,
+                                  lq->query_opts, NULL,
+                                  &global_scope, oc, MDL) ||
+           (data.len < IAADDR_OFFSET)) {
+               log_error("process_lq_by_address: error evaluating IAADDR.");
+               goto exit;
+       }
+       memcpy(&addr, data.data, sizeof(addr));
+       data_string_forget(&data, MDL);
+
+       /*
+        * Find the lease.
+        * Note the RFC 5007 says to use the link-address to find the link
+        * or the ia-aadr when it is :: but in any case the ia-addr has
+        * to be on the link, so we ignore the link-address here.
+        */
+       if (find_ipv6_pool(&pool, &addr) != ISC_R_SUCCESS) {
+               if (!set_error(lq, STATUS_NotConfigured,
+                              "Address not in a pool.")) {
+                       log_error("process_lq_by_address: unable "
+                                 "to set NotConfigured status code.");
+                       goto exit;
+               }
+               ret_val = 1;
+               goto exit;
+       }
+       if (iaaddr_hash_lookup(&iaaddr, pool->addrs, &addr,
+                              sizeof(addr), MDL) == 0) {
+               ret_val = 1;
+               goto exit;
+       }
+       if ((iaaddr == NULL) || (iaaddr->state != FTS_ACTIVE) ||
+           (iaaddr->ia_na == NULL) ||
+           (iaaddr->ia_na->iaid_duid.len <= 4)) {
+               ret_val = 1;
+               goto exit;
+       }
+
+       /*
+        * Build the client-data option (with client-id, ia-addr and clt-time).
+        */
+       if (!option_state_allocate(&opt_state, MDL)) {
+               log_error("process_lq_by_address: "
+                         "no memory for option state.");
+               goto exit;
+       }
+
+       data_string_copy(&data, &iaaddr->ia_na->iaid_duid, MDL);
+       data.data += 4;
+       data.len -= 4;
+       if (!save_option_buffer(&dhcpv6_universe, opt_state,
+                               NULL, (unsigned char *)data.data, data.len,
+                               D6O_CLIENTID, 0)) {
+               log_error("process_lq_by_address: error saving client ID.");
+               goto exit;
+       }
+       data_string_forget(&data, MDL);
+
+       data.len = IAADDR_OFFSET;
+       if (!buffer_allocate(&data.buffer, data.len, MDL)) {
+               log_error("process_lq_by_address: no memory for ia-addr.");
+               goto exit;
+       }
+       data.data = data.buffer->data;
+       memcpy(data.buffer->data, &iaaddr->addr, 16);
+       lifetime = DEFAULT_DEFAULT_LEASE_TIME;
+       lifetime = (lifetime / 2) + (lifetime / 8);
+       putULong(data.buffer->data + 16, lifetime);
+       lifetime = DEFAULT_DEFAULT_LEASE_TIME;
+       putULong(data.buffer->data + 20, lifetime);
+       if (!save_option_buffer(&dhcpv6_universe, opt_state,
+                               NULL, (unsigned char *)data.data, data.len,
+                               D6O_IAADDR, 0)) {
+               log_error("process_lq_by_address: error saving ia-addr.");
+               goto exit;
+       }
+       data_string_forget(&data, MDL);
+
+       lifetime = 0;
+       if (!save_option_buffer(&dhcpv6_universe, opt_state,
+                               NULL, (unsigned char *)&lifetime, 4,
+                               D6O_CLT_TIME, 0)) {
+               log_error("process_lq_by_address: error saving clt time.");
+               goto exit;
+       }
+
+       /*
+        * Store the client-data option.
+        */
+       opt_cursor = lq->cursor;
+       putUShort(lq->buf.data + lq->cursor, (unsigned)D6O_CLIENT_DATA);
+       lq->cursor += 2;
+       /* Skip option length. */
+       lq->cursor += 2;
+
+       lq->cursor += store_options6((char *)lq->buf.data + lq->cursor,
+                                    sizeof(lq->buf) - lq->cursor,
+                                    opt_state, lq->packet,
+                                    required_opt_CLIENT_DATA, NULL);
+       /* Reset the length. */
+       putUShort(lq->buf.data + opt_cursor + 2,
+                 lq->cursor - (opt_cursor + 4));
+
+       /* Done. */
+       ret_val = 1;
+
+     exit:
+       if (data.data != NULL)
+               data_string_forget(&data, MDL);
+       if (pool != NULL)
+               ipv6_pool_dereference(&pool, MDL);
+       if (iaaddr != NULL)
+               iaaddr_dereference(&iaaddr, MDL);
+       if (opt_state != NULL)
+               option_state_dereference(&opt_state, MDL);
+       return ret_val;
+}
+
+
+/*
+ * Process a lease query.
+ */
+void
+dhcpv6_leasequery(struct data_string *reply_ret, struct packet *packet) {
+       static struct lq6_state lq;
+       struct data_string server_duid;
+       struct option_cache *oc;
+       int allow_lq;
+
+       /*
+        * Initialize the lease query state.
+        */
+       lq.packet = NULL;
+       memset(&lq.client_id, 0, sizeof(lq.client_id));
+       memset(&lq.server_id, 0, sizeof(lq.server_id));
+       memset(&lq.lq_query, 0, sizeof(lq.lq_query));
+       lq.query_opts = NULL;
+       lq.reply_opts = NULL;
+       packet_reference(&lq.packet, packet, MDL);
+
+       /*
+        * Validate our input.
+        */
+       if (!valid_query_msg(&lq)) {
+               goto exit;
+       }
+
+       /*
+        * Prepare our reply.
+        */
+       if (!option_state_allocate(&lq.reply_opts, MDL)) {
+               log_error("dhcpv6_leasequery: no memory for option state.");
+               goto exit;
+       }
+       execute_statements_in_scope(NULL, lq.packet, NULL, NULL,
+                                   lq.packet->options, lq.reply_opts,
+                                   &global_scope, root_group, NULL);
+
+       lq.buf.reply.msg_type = DHCPV6_LEASEQUERY_REPLY;
+
+       memcpy(lq.buf.reply.transaction_id,
+              lq.packet->dhcpv6_transaction_id,
+              sizeof(lq.buf.reply.transaction_id));
+
+       /* 
+        * Because LEASEQUERY has some privacy concerns, default to deny.
+        */
+       allow_lq = 0;
+
+       /*
+        * See if we are authorized to do LEASEQUERY.
+        */
+       oc = lookup_option(&server_universe, lq.reply_opts, SV_LEASEQUERY);
+       if (oc != NULL) {
+               allow_lq = evaluate_boolean_option_cache(NULL,
+                                                        lq.packet,
+                                                        NULL, NULL,
+                                                        lq.packet->options,
+                                                        lq.reply_opts,
+                                                        &global_scope,
+                                                        oc, MDL);
+       }
+
+       if (!allow_lq) {
+               log_info("dhcpv6_leasequery: not allowed, query ignored.");
+               goto exit;
+       }
+           
+       /*
+        * Same than transmission of REPLY message in RFC 3315:
+        *  server-id
+        *  client-id
+        */
+
+       oc = lookup_option(&dhcpv6_universe, lq.reply_opts, D6O_SERVERID);
+       if (oc == NULL) {
+               /* If not already in options, get from query then global. */
+               if (lq.server_id.data == NULL)
+                       copy_server_duid(&lq.server_id, MDL);
+               if (!save_option_buffer(&dhcpv6_universe,
+                                       lq.reply_opts,
+                                       NULL,
+                                       (unsigned char *)lq.server_id.data,
+                                       lq.server_id.len, 
+                                       D6O_SERVERID,
+                                       0)) {
+                       log_error("dhcpv6_leasequery: "
+                                 "error saving server identifier.");
+                       goto exit;
+               }
+       }
+
+       if (!save_option_buffer(&dhcpv6_universe,
+                               lq.reply_opts,
+                               lq.client_id.buffer,
+                               (unsigned char *)lq.client_id.data,
+                               lq.client_id.len,
+                               D6O_CLIENTID,
+                               0)) {
+               log_error("dhcpv6_leasequery: "
+                         "error saving client identifier.");
+               goto exit;
+       }
+
+       lq.cursor = 4;
+
+       /*
+        * Decode the lq-query option.
+        */
+
+       if (lq.lq_query.len <= LQ_QUERY_OFFSET) {
+               if (!set_error(&lq, STATUS_MalformedQuery,
+                              "OPTION_LQ_QUERY too short.")) {
+                       log_error("dhcpv6_leasequery: unable "
+                                 "to set MalformedQuery status code.");
+                       goto exit;
+               }
+               goto done;
+       }
+
+       lq.query_type = lq.lq_query.data [0];
+       memcpy(&lq.link_addr, lq.lq_query.data + 1, sizeof(lq.link_addr));
+       switch (lq.query_type) {
+               case LQ6QT_BY_ADDRESS:
+                       break;
+               case LQ6QT_BY_CLIENTID:
+                       if (!set_error(&lq, STATUS_UnknownQueryType,
+                                      "QUERY_BY_CLIENTID not supported.")) {
+                               log_error("dhcpv6_leasequery: unable to "
+                                         "set UnknownQueryType status code.");
+                               goto exit;
+                       }
+                       goto done;
+               default:
+                       if (!set_error(&lq, STATUS_UnknownQueryType,
+                                      "Unknown query-type.")) {
+                               log_error("dhcpv6_leasequery: unable to "
+                                         "set UnknownQueryType status code.");
+                               goto exit;
+                       }
+                       goto done;
+       }
+
+       if (!option_state_allocate(&lq.query_opts, MDL)) {
+               log_error("dhcpv6_leasequery: no memory for option state.");
+               goto exit;
+       }
+       if (!parse_option_buffer(lq.query_opts,
+                                lq.lq_query.data + LQ_QUERY_OFFSET,
+                                lq.lq_query.len - LQ_QUERY_OFFSET,
+                                &dhcpv6_universe)) {
+               log_error("dhcpv6_leasequery: error parsing query-options.");
+               if (!set_error(&lq, STATUS_MalformedQuery,
+                              "Bad query-options.")) {
+                       log_error("dhcpv6_leasequery: unable "
+                                 "to set MalformedQuery status code.");
+                       goto exit;
+               }
+               goto done;
+       }
+
+       /* Do it. */
+       if (!process_lq_by_address(&lq))
+               goto exit;
+
+      done:
+       /* Store the options. */
+       lq.cursor += store_options6((char *)lq.buf.data + lq.cursor,
+                                   sizeof(lq.buf) - lq.cursor,
+                                   lq.reply_opts,
+                                   lq.packet,
+                                   required_opts_lq,
+                                   NULL);
+
+       /* Return our reply to the caller. */
+       reply_ret->len = lq.cursor;
+       reply_ret->buffer = NULL;
+       if (!buffer_allocate(&reply_ret->buffer, lq.cursor, MDL)) {
+               log_fatal("dhcpv6_leasequery: no memory to store Reply.");
+       }
+       memcpy(reply_ret->buffer->data, lq.buf.data, lq.cursor);
+       reply_ret->data = reply_ret->buffer->data;
+
+      exit:
+       /* Cleanup. */
+       if (lq.packet != NULL)
+               packet_dereference(&lq.packet, MDL);
+       if (lq.client_id.data != NULL)
+               data_string_forget(&lq.client_id, MDL);
+       if (lq.server_id.data != NULL)
+               data_string_forget(&lq.server_id, MDL);
+       if (lq.lq_query.data != NULL)
+               data_string_forget(&lq.lq_query, MDL);
+       if (lq.query_opts != NULL)
+               option_state_dereference(&lq.query_opts, MDL);
+       if (lq.reply_opts != NULL)
+               option_state_dereference(&lq.reply_opts, MDL);
+}
+
+#endif /* DHCPv6 */
index b4f1adbedd0aba04a80bf1fee301dd343e259148..61c0df2baa60255725f3948126ae59613276507a 100644 (file)
@@ -5521,6 +5521,12 @@ build_dhcpv6_reply(struct data_string *reply, struct packet *packet) {
                case DHCPV6_RELAY_REPL:
                        dhcpv6_discard(packet);
                        break;
+               case DHCPV6_LEASEQUERY:
+                       dhcpv6_leasequery(reply, packet);
+                       break;
+               case DHCPV6_LEASEQUERY_REPLY:
+                       dhcpv6_discard(packet);
+                       break;
                default:
                        /* XXX: would be nice if we had "notice" level, 
                                as syslog, for this */