diff options
author | Francis Dupont <fdupont@isc.org> | 2016-02-23 10:40:10 +0100 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2016-02-23 10:40:10 +0100 |
commit | 785c1a519e88bfebb70bd3384589de36bee02dd2 (patch) | |
tree | 04f794b247068983b2df432fb20f229de1e8bf03 | |
parent | 08ad1e3c9089b51888c73837cf41de49112de29c (diff) | |
download | isc-dhcp-785c1a519e88bfebb70bd3384589de36bee02dd2.tar.gz |
Merged rt35711c (DHCPv4-over-DHCPv6 support)
-rw-r--r-- | RELNOTES | 9 | ||||
-rw-r--r-- | client/clparse.c | 42 | ||||
-rw-r--r-- | client/dhc6.c | 16 | ||||
-rw-r--r-- | client/dhclient.8 | 14 | ||||
-rw-r--r-- | client/dhclient.c | 746 | ||||
-rw-r--r-- | common/Makefile.am | 10 | ||||
-rw-r--r-- | common/Makefile.in | 28 | ||||
-rw-r--r-- | common/discover.c | 49 | ||||
-rw-r--r-- | common/inet.c | 28 | ||||
-rw-r--r-- | common/options.c | 25 | ||||
-rw-r--r-- | common/tables.c | 37 | ||||
-rwxr-xr-x | configure | 34 | ||||
-rw-r--r-- | configure.ac | 27 | ||||
-rw-r--r-- | includes/config.h.in | 3 | ||||
-rw-r--r-- | includes/dhcp6.h | 72 | ||||
-rw-r--r-- | includes/dhcpd.h | 32 | ||||
-rw-r--r-- | includes/site.h | 1 | ||||
-rw-r--r-- | relay/dhcrelay.c | 4 | ||||
-rw-r--r-- | server/bootp.c | 30 | ||||
-rw-r--r-- | server/confpars.c | 11 | ||||
-rw-r--r-- | server/dhcp.c | 374 | ||||
-rw-r--r-- | server/dhcpd.8 | 16 | ||||
-rw-r--r-- | server/dhcpd.c | 60 | ||||
-rw-r--r-- | server/dhcpleasequery.c | 7 | ||||
-rw-r--r-- | server/dhcpv6.c | 889 | ||||
-rw-r--r-- | server/mdb.c | 12 | ||||
-rw-r--r-- | server/stables.c | 2 |
27 files changed, 2494 insertions, 84 deletions
@@ -227,6 +227,15 @@ by Eric Young (eay@cryptsoft.com). - Corrected minor Coverity issues. [ISC-Bugs #35144] +- Add support for RFC 7341 DHCPv4 over DHCPv6 with a new configuration + option "--enable-dhcpv4o6". Note this feature requires DHCPv6 support + and is not compatible with delayed-ack. Both client and server use 2 + processes which communicate over UDP on a pair of sockets. The new + "-4o6 <port>" command line argment enables DHCPv4 over DHCPv6 support + and specifies the consecutive ports to use for inter-process communication. + Please look at doc/DHCPv4-over-DHCPv6 for more details. + [ISC-Bugs #35711] + Changes since 4.3.3b1 - None diff --git a/client/clparse.c b/client/clparse.c index 320c42f5..643b3fae 100644 --- a/client/clparse.c +++ b/client/clparse.c @@ -3,7 +3,7 @@ Parser for dhclient config and lease files... */ /* - * Copyright (c) 2004-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2004-2014,2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1996-2003 by Internet Software Consortium * * Permission to use, copy, modify, and distribute this software for any @@ -32,7 +32,8 @@ struct client_config top_level_config; #define NUM_DEFAULT_REQUESTED_OPTS 9 -struct option *default_requested_options[NUM_DEFAULT_REQUESTED_OPTS + 1]; +/* There can be 2 extra requested options for DHCPv4-over-DHCPv6. */ +struct option *default_requested_options[NUM_DEFAULT_REQUESTED_OPTS + 2 + 1]; static void parse_client_default_duid(struct parse *cfile); static void parse_client6_lease_statement(struct parse *cfile); @@ -120,6 +121,43 @@ isc_result_t read_client_conf () "assembly.", code); } +#ifdef DHCP4o6 + /* DHCPv4-over-DHCPv6 extra requested options in code order */ + if (dhcpv4_over_dhcpv6 == 1) { + /* The DHCP4o6 server option should be requested */ + code = D6O_DHCP4_O_DHCP6_SERVER; + option_code_hash_lookup(&default_requested_options[9], + dhcpv6_universe.code_hash, + &code, 0, MDL); + if (default_requested_options[9] == NULL) { + log_fatal("Unable to find option definition for " + "index %u during default parameter request " + "assembly.", code); + } + } else if (dhcpv4_over_dhcpv6 > 1) { + /* Called from run_stateless so the IRT should + be requested too */ + code = D6O_INFORMATION_REFRESH_TIME; + option_code_hash_lookup(&default_requested_options[9], + dhcpv6_universe.code_hash, + &code, 0, MDL); + if (default_requested_options[9] == NULL) { + log_fatal("Unable to find option definition for " + "index %u during default parameter request " + "assembly.", code); + } + code = D6O_DHCP4_O_DHCP6_SERVER; + option_code_hash_lookup(&default_requested_options[10], + dhcpv6_universe.code_hash, + &code, 0, MDL); + if (default_requested_options[10] == NULL) { + log_fatal("Unable to find option definition for " + "index %u during default parameter request " + "assembly.", code); + } + } +#endif + /* Initialize the top level client configuration. */ memset (&top_level_config, 0, sizeof top_level_config); diff --git a/client/dhc6.c b/client/dhc6.c index aacd4aea..c8d16e8a 100644 --- a/client/dhc6.c +++ b/client/dhc6.c @@ -4835,6 +4835,11 @@ start_bound(struct client_state *client) script_go(client); } +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6) + dhcp4o6_start(); +#endif + go_daemon(); if (client->old_lease != NULL) { @@ -5314,8 +5319,12 @@ dhc6_check_irt(struct client_state *client) } } /* Simply return gives a endless loop waiting for nothing. */ - if (!found) + if (!found) { +#ifdef DHCP4o6 + if (!dhcpv4_over_dhcpv6) +#endif exit(0); + } oc = lookup_option(&dhcpv6_universe, client->active_lease->options, D6O_INFORMATION_REFRESH_TIME); @@ -5368,6 +5377,11 @@ start_informed(struct client_state *client) script_write_requested6(client); script_go(client); +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6) + dhcp4o6_start(); +#endif + go_daemon(); if (client->old_lease != NULL) { diff --git a/client/dhclient.8 b/client/dhclient.8 index 83260e61..1946d9bb 100644 --- a/client/dhclient.8 +++ b/client/dhclient.8 @@ -1,6 +1,6 @@ .\" $Id: dhclient.8,v 1.36 2011/04/15 21:58:12 sar Exp $ .\" -.\" Copyright (c) 2004,2007-2015 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 2004,2007-2016 by Internet Systems Consortium, Inc. ("ISC") .\" Copyright (c) 1996-2003 by Internet Software Consortium .\" .\" Permission to use, copy, modify, and distribute this software for any @@ -64,6 +64,10 @@ dhclient - Dynamic Host Configuration Protocol Client .B -I ] [ +.B -4o6 +.I port +] +[ .B -D .I LL|LLT ] @@ -239,6 +243,14 @@ along with configuration parameters. It cannot be combined with processing. Note: it is not recommended to mix queries of different types together or even to share the lease file between them. .TP +.BI \-4o6 \ port +Participate in the DHCPv4 over DHCPv6 protocol specified by RFC 7341. +This associates a DHCPv4 and a DHCPv6 client to allow the v4 client to +send v4 requests encapuslated in a v6 packet. Communication +between the two clients is done on a pair of UDP sockets bound +to ::1 \fIport\fR and \fIport + 1\fR. Both clients must +be launched using the same \fIport\fR argument. +.TP .BI \-1 Try to get a lease once. On failure exit with code 2. In DHCPv6 this sets the maximum duration of the initial exchange to diff --git a/client/dhclient.c b/client/dhclient.c index df7f8836..d1d83a01 100644 --- a/client/dhclient.c +++ b/client/dhclient.c @@ -80,6 +80,9 @@ static const char url [] = "For info, please visit https://www.isc.org/software/ u_int16_t local_port = 0; u_int16_t remote_port = 0; +#if defined(DHCPv6) && defined(DHCP4o6) +int dhcp4o6_state = -1; /* -1 = stopped, 0 = polling, 1 = started */ +#endif int no_daemon = 0; struct string_list *client_env = NULL; int client_env_count = 0; @@ -97,7 +100,7 @@ char *mockup_relay = NULL; char *progname = NULL; -void run_stateless(int exit_mode); +void run_stateless(int exit_mode, u_int16_t port); static isc_result_t write_duid(struct data_string *duid); static void add_reject(struct packet *packet); @@ -126,6 +129,17 @@ static void dhclient_ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, * \return Nothing */ +#if defined(DHCPv6) && defined(DHCP4o6) +static void dhcp4o6_poll(void *dummy); +static void dhcp4o6_resume(void); +static void recv_dhcpv4_response(struct data_string *raw); +static int send_dhcpv4_query(struct client_state *client, int broadcast); + +static void dhcp4o6_stop(void); +static void forw_dhcpv4_response(struct packet *packet); +static void forw_dhcpv4_query(struct data_string *raw); +#endif + #ifndef UNIT_TEST /* These are only used when we call usage() from the main routine * which isn't compiled when building for unit tests @@ -151,14 +165,19 @@ usage(const char *sfmt, const char *sarg) log_fatal("Usage: %s " #ifdef DHCPv6 +#ifdef DHCP4o6 + "[-4|-6] [-SNTPRI1dvrxi] [-nw] -4o6 <port>]\n" + " [-p <port>] [-D LL|LLT] \n" +#else /* DHCP4o6 */ "[-4|-6] [-SNTPRI1dvrxi] [-nw] [-p <port>] [-D LL|LLT] \n" +#endif #else /* DHCPv6 */ "[-I1dvrxi] [-nw] [-p <port>] [-D LL|LLT] \n" #endif /* DHCPv6 */ " [-s server-addr] [-cf config-file]\n" " [-df duid-file] [-lf lease-file]\n" " [-pf pid-file] [--no-pid] [-e VAR=val]\n" - " [-sf script-file] [interface]", + " [-sf script-file] [interface]*", isc_file_basename(progname)); } @@ -183,6 +202,9 @@ main(int argc, char **argv) { int no_dhclient_script = 0; #ifdef DHCPv6 int local_family_set = 0; +#ifdef DHCP4o6 + u_int16_t dhcp4o6_port = 0; +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ char *s; @@ -252,6 +274,17 @@ main(int argc, char **argv) { "both."); local_family_set = 1; local_family = AF_INET6; +#ifdef DHCP4o6 + } else if (!strcmp(argv[i], "-4o6")) { + if (++i == argc) + usage(use_noarg, argv[i-1]); + dhcp4o6_port = validate_port_pair(argv[i]); + + log_debug("DHCPv4 over DHCPv6 over ::1 port %d and %d", + ntohs(dhcp4o6_port), + ntohs(dhcp4o6_port) + 1); + dhcpv4_over_dhcpv6 = 1; +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ } else if (!strcmp(argv[i], "-x")) { /* eXit, no release */ release_mode = 0; @@ -434,6 +467,17 @@ main(int argc, char **argv) { usage("PD %s only supports one requested interface", "-P"); } +#if defined(DHCPv6) && defined(DHCP4o6) + if ((local_family == AF_INET6) && dhcpv4_over_dhcpv6 && + (exit_mode || release_mode)) + log_error("Can't relay DHCPv4-over-DHCPv6 " + "without a persistent DHCPv6 client"); + if ((local_family == AF_INET) && dhcpv4_over_dhcpv6 && + (interfaces_requested != 1)) + log_fatal("DHCPv4-over-DHCPv6 requires an explicit " + "interface on which to be applied"); +#endif + if (!no_dhclient_conf && (s = getenv("PATH_DHCLIENT_CONF"))) { path_dhclient_conf = s; } @@ -572,7 +616,11 @@ main(int argc, char **argv) { usage("Stateless commnad: %s incompatibile with " "other commands", "-S"); } - run_stateless(exit_mode); +#if defined(DHCPv6) && defined(DHCP4o6) + run_stateless(exit_mode, dhcp4o6_port); +#else + run_stateless(exit_mode, 0); +#endif return 0; } @@ -682,6 +730,11 @@ main(int argc, char **argv) { } } +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && !exit_mode) + dhcp4o6_setup(dhcp4o6_port); +#endif + /* Start a configuration state machine for each interface. */ #ifdef DHCPv6 if (local_family == AF_INET6) { @@ -715,7 +768,7 @@ main(int argc, char **argv) { client = client->next) { if (exit_mode) state_stop(client); - else if (release_mode) + if (release_mode) do_release(client); else { client->state = S_INIT; @@ -752,7 +805,7 @@ main(int argc, char **argv) { #ifndef DHCPv6 return 0; #else - if (local_family == AF_INET6) { + if ((local_family == AF_INET6) || dhcpv4_over_dhcpv6) { if (onetry) return 0; } else @@ -813,13 +866,25 @@ main(int argc, char **argv) { return 0; } -void run_stateless(int exit_mode) +/* + * \brief Run the DHCPv6 stateless client (dhclient -6 -S) + * + * \param exist_mode set to 1 when dhclient was called with -x + * \param port DHCPv4-over-DHCPv6 client inter-process communication + * UDP port pair (port,port+1 with port in network byte order) + */ + +void run_stateless(int exit_mode, u_int16_t port) { #ifdef DHCPv6 struct client_state *client; omapi_object_t *listener; isc_result_t result; +#ifndef DHCP4o6 + IGNORE_UNUSED(port); +#endif + /* Discover the network interface. */ discover_interfaces(DISCOVER_REQUESTED); @@ -827,6 +892,12 @@ void run_stateless(int exit_mode) usage("No interfaces available for stateless command: %s", "-S"); /* Parse the dhclient.conf file. */ +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6) { + /* Mark we want to request IRT too! */ + dhcpv4_over_dhcpv6++; + } +#endif read_client_conf(); /* Parse the lease database. */ @@ -845,6 +916,11 @@ void run_stateless(int exit_mode) form_duid(&default_duid, MDL); } +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6 && !exit_mode) + dhcp4o6_setup(port); +#endif + /* Start a configuration state machine. */ for (client = interfaces->client ; client != NULL ; @@ -967,6 +1043,17 @@ void state_reboot (cpp) { struct client_state *client = cpp; +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (dhcp4o6_state <= 0)) { + if (dhcp4o6_state < 0) + dhcp4o6_poll(NULL); + client->pending = P_REBOOT; + return; + } +#endif + + client->pending= P_NONE; + /* If we don't remember an active lease, go straight to INIT. */ if (!client -> active || client -> active -> is_bootp || @@ -1405,6 +1492,8 @@ void state_stop (cpp) { struct client_state *client = cpp; + client->pending = P_NONE; + /* Cancel all timeouts. */ cancel_timeout(state_selecting, client); cancel_timeout(send_discover, client); @@ -1553,6 +1642,17 @@ dhcpv6(struct packet *packet) { /* Screen out nonsensical messages. */ switch(packet->dhcpv6_msg_type) { +#ifdef DHCP4o6 + case DHCPV6_DHCPV4_RESPONSE: + if (dhcpv4_over_dhcpv6) { + log_info("RCV: %s message on %s from %s.", + dhcpv6_type_names[packet->dhcpv6_msg_type], + packet->interface->name, + piaddr(packet->client_addr)); + forw_dhcpv4_response(packet); + } + return; +#endif case DHCPV6_ADVERTISE: case DHCPV6_RECONFIGURE: if (stateless) @@ -1581,6 +1681,176 @@ dhcpv6(struct packet *packet) { /* XXX: temporary log for debugging */ log_info("Packet received, but nothing done with it."); } + +#ifdef DHCP4o6 +/* + * \brief Forward a DHCPv4-response to the DHCPv4 client. + * (DHCPv6 client function) + * + * The DHCPv6 client receives a DHCPv4-response which is forwarded + * to the DHCPv4 client. + * Format: address:16 + DHCPv4 message content + * (we have no state to keep the address so it is transported in + * DHCPv6 <-> DHCPv6 inter-process messages) + * + * \param packet the DHCPv4-response packet + */ +static void forw_dhcpv4_response(struct packet *packet) +{ + struct option_cache *oc; + struct data_string enc_opt_data; + struct data_string ds; + int cc; + + /* + * Discard if relay is not ready. + */ + if (dhcp4o6_state == -1) { + log_info("forw_dhcpv4_response: not ready."); + return; + } + + if (packet->client_addr.len != 16) { + log_error("forw_dhcpv4_response: bad address"); + return; + } + + /* + * Get our encapsulated DHCPv4 message. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_DHCPV4_MSG); + if (oc == NULL) { + log_info("DHCPv4-response from %s missing " + "DHCPv4 Message option.", + piaddr(packet->client_addr)); + return; + } + + memset(&enc_opt_data, 0, sizeof(enc_opt_data)); + if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, + NULL, NULL, &global_scope, oc, MDL)) { + log_error("forw_dhcpv4_response: error evaluating " + "DHCPv4 message."); + data_string_forget(&enc_opt_data, MDL); + return; + } + + if (enc_opt_data.len < DHCP_FIXED_NON_UDP) { + log_error("forw_dhcpv4_response: " + "no memory for encapsulated packet."); + data_string_forget(&enc_opt_data, MDL); + return; + } + + /* + * Append address. + */ + memset(&ds, 0, sizeof(ds)); + if (!buffer_allocate(&ds.buffer, enc_opt_data.len + 16, MDL)) { + log_error("forw_dhcpv4_response: no memory buffer."); + data_string_forget(&enc_opt_data, MDL); + return; + } + ds.data = ds.buffer->data; + ds.len = enc_opt_data.len + 16; + memcpy(ds.buffer->data, enc_opt_data.data, enc_opt_data.len); + memcpy(ds.buffer->data + enc_opt_data.len, + packet->client_addr.iabuf, 16); + data_string_forget(&enc_opt_data, MDL); + + /* + * Forward them. + */ + cc = send(dhcp4o6_fd, ds.data, ds.len, 0); + if (cc < 0) + log_error("forw_dhcpv4_response: send(): %m"); + + data_string_forget(&ds, MDL); +} + +/* + * \brief Receive a DHCPv4-response from the DHCPv6 client. + * (DHCPv4 client function) + * + * The DHCPv4 client receives a DHCPv4-response forwarded + * by the DHCPv6 client (using \ref forw_dhcpv4_response()) + * + * \param raw the DHCPv4-response raw packet + */ +static void recv_dhcpv4_response(struct data_string *raw) +{ + struct packet *packet; + struct iaddr from; + + if (interfaces == NULL) { + log_error("recv_dhcpv4_response: no interfaces."); + return; + } + + from.len = 16; + memcpy(from.iabuf, raw->data + (raw->len - 16), 16); + + /* + * Build a packet structure. + */ + packet = NULL; + if (!packet_allocate(&packet, MDL)) { + log_error("recv_dhcpv4_response: no memory for packet."); + return; + } + + packet->raw = (struct dhcp_packet *) raw->data; + packet->packet_length = raw->len - 16; + packet->client_port = remote_port; + packet->client_addr = from; + interface_reference(&packet->interface, interfaces, MDL); + + /* Allocate packet->options now so it is non-null for all packets */ + if (!option_state_allocate (&packet->options, MDL)) { + log_error("recv_dhcpv4_response: no memory for options."); + packet_dereference (&packet, MDL); + return; + } + + /* If there's an option buffer, try to parse it. */ + if (packet->packet_length >= DHCP_FIXED_NON_UDP + 4) { + struct option_cache *op; + if (!parse_options(packet)) { + if (packet->options) + option_state_dereference + (&packet->options, MDL); + packet_dereference (&packet, MDL); + return; + } + + if (packet->options_valid && + (op = lookup_option(&dhcp_universe, + packet->options, + DHO_DHCP_MESSAGE_TYPE))) { + struct data_string dp; + memset(&dp, 0, sizeof dp); + evaluate_option_cache(&dp, packet, NULL, NULL, + packet->options, NULL, + NULL, op, MDL); + if (dp.len > 0) + packet->packet_type = dp.data[0]; + else + packet->packet_type = 0; + data_string_forget(&dp, MDL); + } + } + + if (validate_packet(packet) != 0) { + if (packet->packet_type) + dhcp(packet); + else + bootp(packet); + } + + /* If the caller kept the packet, they'll have upped the refcnt. */ + packet_dereference(&packet, MDL); +} +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ void dhcpoffer (packet) @@ -2002,16 +2272,33 @@ void send_discover (cpp) client -> packet.secs = htons (65535); client -> secs = client -> packet.secs; +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_info ("DHCPDISCOVER interval %ld", + (long)(client -> interval)); + } else +#endif log_info ("DHCPDISCOVER on %s to %s port %d interval %ld", client -> name ? client -> name : client -> interface -> name, inet_ntoa (sockaddr_broadcast.sin_addr), ntohs (sockaddr_broadcast.sin_port), (long)(client -> interval)); /* Send out a packet. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + result = send_dhcpv4_query(client, 1); + } else +#endif result = send_packet(client->interface, NULL, &client->packet, client->packet_length, inaddr_any, &sockaddr_broadcast, NULL); if (result < 0) { +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_error("%s:%d: Failed to send %d byte long packet.", + MDL, client->packet_length); + } else +#endif log_error("%s:%d: Failed to send %d byte long packet over %s " "interface.", MDL, client->packet_length, client->interface->name); @@ -2274,11 +2561,28 @@ void send_request (cpp) client -> packet.secs = htons (65535); } +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_info ("DHCPREQUEST"); + } else +#endif log_info ("DHCPREQUEST on %s to %s port %d", client -> name ? client -> name : client -> interface -> name, inet_ntoa (destination.sin_addr), ntohs (destination.sin_port)); +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + int broadcast = 0; + if (destination.sin_addr.s_addr == INADDR_BROADCAST) + broadcast = 1; + result = send_dhcpv4_query(client, broadcast); + if (result < 0) { + log_error("%s:%d: Failed to send %d byte long packet.", + MDL, client->packet_length); + } + } else +#endif if (destination.sin_addr.s_addr != INADDR_BROADCAST && fallback_interface) { result = send_packet(fallback_interface, NULL, &client->packet, @@ -2317,16 +2621,32 @@ void send_decline (cpp) int result; +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_info ("DHCPDECLINE"); + } else +#endif log_info ("DHCPDECLINE on %s to %s port %d", client->name ? client->name : client->interface->name, inet_ntoa(sockaddr_broadcast.sin_addr), ntohs(sockaddr_broadcast.sin_port)); /* Send out a packet. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + result = send_dhcpv4_query(client, 1); + } else +#endif result = send_packet(client->interface, NULL, &client->packet, client->packet_length, inaddr_any, &sockaddr_broadcast, NULL); if (result < 0) { +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_error("%s:%d: Failed to send %d byte long packet.", + MDL, client->packet_length); + } else +#endif log_error("%s:%d: Failed to send %d byte long packet over %s" " interface.", MDL, client->packet_length, client->interface->name); @@ -2363,11 +2683,28 @@ void send_release (cpp) return; } +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + log_info ("DHCPRELEASE"); + } else +#endif log_info ("DHCPRELEASE on %s to %s port %d", client -> name ? client -> name : client -> interface -> name, inet_ntoa (destination.sin_addr), ntohs (destination.sin_port)); +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + int broadcast = 0; + if (destination.sin_addr.s_addr == INADDR_BROADCAST) + broadcast = 1; + result = send_dhcpv4_query(client, broadcast); + if (result < 0) { + log_error("%s:%d: Failed to send %d byte long packet.", + MDL, client->packet_length); + } + } else +#endif if (fallback_interface) { result = send_packet(fallback_interface, NULL, &client->packet, client->packet_length, from, &destination, @@ -2393,6 +2730,151 @@ void send_release (cpp) } } +#if defined(DHCPv6) && defined(DHCP4o6) +/* + * \brief Send a DHCPv4-query to the DHCPv6 client + * (DHCPv4 client function) + * + * The DHCPv4 client sends a DHCPv4-query to the DHCPv6 client over + * the inter-process communication socket. + * + * \param client the DHCPv4 client state + * \param broadcast the broadcast flag + * \return the sent byte count (-1 on error) + */ +static int send_dhcpv4_query(struct client_state *client, int broadcast) { + struct data_string ds; + struct dhcpv4_over_dhcpv6_packet *query; + int ofs, len, cc; + + if (dhcp4o6_state <= 0) { + log_info("send_dhcpv4_query: not ready."); + return -1; + } + + /* + * Compute buffer length and allocate it. + */ + len = ofs = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); + len += dhcpv6_universe.tag_size + dhcpv6_universe.length_size; + len += client->packet_length; + memset(&ds, 0, sizeof(ds)); + if (!buffer_allocate(&ds.buffer, len, MDL)) { + log_error("Unable to allocate memory for DHCPv4-query."); + return -1; + } + ds.data = ds.buffer->data; + ds.len = len; + + /* + * Fill header. + */ + query = (struct dhcpv4_over_dhcpv6_packet *)ds.data; + query->msg_type = DHCPV6_DHCPV4_QUERY; + query->flags[0] = query->flags[1] = query->flags[2] = 0; + if (!broadcast) + query->flags[0] |= DHCP4O6_QUERY_UNICAST; + + /* + * Append DHCPv4 message. + */ + dhcpv6_universe.store_tag(ds.buffer->data + ofs, D6O_DHCPV4_MSG); + ofs += dhcpv6_universe.tag_size; + dhcpv6_universe.store_length(ds.buffer->data + ofs, + client->packet_length); + ofs += dhcpv6_universe.length_size; + memcpy(ds.buffer->data + ofs, &client->packet, client->packet_length); + + /* + * Send DHCPv6 message. + */ + cc = send(dhcp4o6_fd, ds.data, ds.len, 0); + if (cc < 0) + log_error("send_dhcpv4_query: send(): %m"); + + data_string_forget(&ds, MDL); + + return cc; +} + +/* + * \brief Forward a DHCPv4-query to all DHCPv4 over DHCPv6 server addresses. + * (DHCPv6 client function) + * + * \param raw the DHCPv6 DHCPv4-query message raw content + */ +static void forw_dhcpv4_query(struct data_string *raw) { + struct interface_info *ip; + struct client_state *client; + struct dhc6_lease *lease; + struct option_cache *oc; + struct data_string addrs; + struct sockaddr_in6 sin6; + int i, send_ret, attempt, success; + + attempt = success = 0; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = remote_port; +#ifdef HAVE_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif + memset(&addrs, 0, sizeof(addrs)); + for (ip = interfaces; ip != NULL; ip = ip->next) { + for (client = ip->client; client != NULL; + client = client->next) { + if ((client->state != S_BOUND) && + (client->state != S_RENEWING) && + (client->state != S_REBINDING)) + continue; + lease = client->active_lease; + if ((lease == NULL) || lease->released) + continue; + oc = lookup_option(&dhcpv6_universe, + lease->options, + D6O_DHCP4_O_DHCP6_SERVER); + if ((oc == NULL) || + !evaluate_option_cache(&addrs, NULL, NULL, NULL, + lease->options, NULL, + &global_scope, oc, MDL) || + ((addrs.len % sizeof(sin6.sin6_addr)) != 0)) { + data_string_forget(&addrs, MDL); + continue; + } + if (addrs.len == 0) { + /* note there is nothing to forget */ + inet_pton(AF_INET6, + All_DHCP_Relay_Agents_and_Servers, + &sin6.sin6_addr); + attempt++; + send_ret = send_packet6(ip, raw->data, + raw->len, &sin6); + if (send_ret == raw->len) + success++; + continue; + } + for (i = 0; i < addrs.len; + i += sizeof(sin6.sin6_addr)) { + memcpy(&sin6.sin6_addr, addrs.data + i, + sizeof(sin6.sin6_addr)); + attempt++; + send_ret = send_packet6(ip, raw->data, + raw->len, &sin6); + if (send_ret == raw->len) + success++; + } + data_string_forget(&addrs, MDL); + } + } + + log_info("forw_dhcpv4_query: sent(%d): %d/%d", + raw->len, success, attempt); + + if (attempt == 0) + dhcp4o6_stop(); +} +#endif + void make_client_options(struct client_state *client, struct client_lease *lease, u_int8_t *type, struct option_cache *sid, @@ -2579,6 +3061,7 @@ void make_discover (client, lease) client -> packet.op = BOOTREQUEST; client -> packet.htype = client -> interface -> hw_address.hbuf [0]; + /* Assumes hw_address is known, otherwise a random value may result */ client -> packet.hlen = client -> interface -> hw_address.hlen - 1; client -> packet.hops = 0; client -> packet.xid = random (); @@ -2652,6 +3135,7 @@ void make_request (client, lease) client -> packet.op = BOOTREQUEST; client -> packet.htype = client -> interface -> hw_address.hbuf [0]; + /* Assumes hw_address is known, otherwise a random value may result */ client -> packet.hlen = client -> interface -> hw_address.hlen - 1; client -> packet.hops = 0; client -> packet.xid = client -> xid; @@ -2726,6 +3210,7 @@ void make_decline (client, lease) client -> packet.op = BOOTREQUEST; client -> packet.htype = client -> interface -> hw_address.hbuf [0]; + /* Assumes hw_address is known, otherwise a random value may result */ client -> packet.hlen = client -> interface -> hw_address.hlen - 1; client -> packet.hops = 0; client -> packet.xid = client -> xid; @@ -2787,6 +3272,7 @@ void make_release (client, lease) client -> packet.op = BOOTREQUEST; client -> packet.htype = client -> interface -> hw_address.hbuf [0]; + /* Assumes hw_address is known, otherwise a random value may result */ client -> packet.hlen = client -> interface -> hw_address.hlen - 1; client -> packet.hops = 0; client -> packet.xid = random (); @@ -3796,6 +4282,15 @@ void do_release(client) struct data_string ds; struct option_cache *oc; +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (dhcp4o6_state <= 0)) { + if (dhcp4o6_state < 0) + dhcp4o6_poll(NULL); + client->pending = P_RELEASE; + return; + } +#endif + /* Pick a random xid. */ client -> xid = random (); @@ -3852,6 +4347,11 @@ void do_release(client) cancel_timeout (send_request, client); cancel_timeout (state_reboot, client); client -> state = S_STOPPED; + +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) + exit(0); +#endif } int dhclient_interface_shutdown_hook (struct interface_info *interface) @@ -4647,3 +5147,237 @@ dhclient_ddns_cb_free(dhcp_ddns_cb_t *ddns_cb, char* file, int line) { ddns_cb_free(ddns_cb, file, line); } } + +#if defined(DHCPv6) && defined(DHCP4o6) +/* + * \brief Omapi I/O handler + * + * The inter-process communication receive handler. + * + * On the DHCPv6 side, the message is either a POLL (which is answered + * by a START or a STOP) or a DHCPv4-QUERY (which is forwarded to + * DHCPv4 over DHCPv6 servers by forw_dhcpv4_query()). + * + * On the DHCPv4 side, the message is either a START, a STOP + * (both for the DHCP4 over DHCPv6 state machine) or a DHCPv4-RESPONSE + * (which is processed by recv_dhcpv4_response()). + * + * \param h the OMAPI object + * \return a result for I/O success or error (used by the I/O subsystem) + */ +isc_result_t dhcpv4o6_handler(omapi_object_t *h) { + char buf[65536]; + char start_msg[5] = { 'S', 'T', 'A', 'R', 'T' }; + char stop_msg[4] = { 'S', 'T', 'O', 'P' }; + char poll_msg[4] = { 'P', 'O', 'L', 'L' }; + struct data_string raw; + int cc; + + if (h->type != dhcp4o6_type) + return DHCP_R_INVALIDARG; + + cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0); + if (cc <= 0) + return ISC_R_UNEXPECTED; + + if (local_family == AF_INET6) { + if ((cc == 4) && + (memcmp(buf, poll_msg, sizeof(poll_msg)) == 0)) { + log_info("RCV: POLL"); + if (dhcp4o6_state < 0) + cc = send(dhcp4o6_fd, stop_msg, + sizeof(stop_msg), 0); + else + cc = send(dhcp4o6_fd, start_msg, + sizeof(start_msg), 0); + if (cc < 0) { + log_error("dhcpv4o6_handler: send(): %m"); + return ISC_R_IOERROR; + } + } else { + if (cc < DHCP_FIXED_NON_UDP + 8) + return ISC_R_UNEXPECTED; + memset(&raw, 0, sizeof(raw)); + if (!buffer_allocate(&raw.buffer, cc, MDL)) { + log_error("dhcpv4o6_handler: " + "no memory buffer."); + return ISC_R_NOMEMORY; + } + raw.data = raw.buffer->data; + raw.len = cc; + memcpy(raw.buffer->data, buf, cc); + + forw_dhcpv4_query(&raw); + + data_string_forget(&raw, MDL); + } + } else { + if ((cc == 4) && + (memcmp(buf, stop_msg, sizeof(stop_msg)) == 0)) { + log_info("RCV: STOP"); + if (dhcp4o6_state > 0) { + dhcp4o6_state = 0; + dhcp4o6_poll(NULL); + } + } else if ((cc == 5) && + (memcmp(buf, start_msg, sizeof(start_msg)) == 0)) { + log_info("RCV: START"); + if (dhcp4o6_state == 0) + cancel_timeout(dhcp4o6_poll, NULL); + dhcp4o6_state = 1; + dhcp4o6_resume(); + } else { + if (cc < DHCP_FIXED_NON_UDP + 16) + return ISC_R_UNEXPECTED; + memset(&raw, 0, sizeof(raw)); + if (!buffer_allocate(&raw.buffer, cc, MDL)) { + log_error("dhcpv4o6_handler: " + "no memory buffer."); + return ISC_R_NOMEMORY; + } + raw.data = raw.buffer->data; + raw.len = cc; + memcpy(raw.buffer->data, buf, cc); + + recv_dhcpv4_response(&raw); + + data_string_forget(&raw, MDL); + } + } + + return ISC_R_SUCCESS; +} + +/* + * \brief Poll the DHCPv6 client + * (DHCPv4 client function) + * + * A POLL message is sent to the DHCPv6 client periodically to check + * if the DHCPv6 is ready (i.e., has a valid DHCPv4-over-DHCPv6 server + * address option). + */ +static void dhcp4o6_poll(void *dummy) { + char msg[4] = { 'P', 'O', 'L', 'L' }; + struct timeval tv; + int cc; + + IGNORE_UNUSED(dummy); + + if (dhcp4o6_state < 0) + dhcp4o6_state = 0; + + log_info("POLL"); + + cc = send(dhcp4o6_fd, msg, sizeof(msg), 0); + if (cc < 0) + log_error("dhcp4o6_poll: send(): %m"); + + tv.tv_sec = cur_time + 60; + tv.tv_usec = random() % 1000000; + + add_timeout(&tv, dhcp4o6_poll, NULL, 0, 0); +} + +/* + * \brief Resume pending operations + * (DHCPv4 client function) + * + * A START message was received from the DHCPv6 client so pending + * operations (RELEASE or REBOOT) must be resumed. + */ +static void dhcp4o6_resume() { + struct interface_info *ip; + struct client_state *client; + + for (ip = interfaces; ip != NULL; ip = ip->next) { + for (client = ip->client; client != NULL; + client = client->next) { + if (client->pending == P_RELEASE) + do_release(client); + else if (client->pending == P_REBOOT) + state_reboot(client); + } + } +} + +/* + * \brief Send a START to the DHCPv4 client + * (DHCPv6 client function) + * + * First check if there is a valid DHCPv4-over-DHCPv6 server address option, + * and when found go UP and on a transition from another state send + * a START message to the DHCPv4 client. + */ +void dhcp4o6_start() { + struct interface_info *ip; + struct client_state *client; + struct dhc6_lease *lease; + struct option_cache *oc; + struct data_string addrs; + char msg[5] = { 'S', 'T', 'A', 'R', 'T' }; + int cc; + + memset(&addrs, 0, sizeof(addrs)); + for (ip = interfaces; ip != NULL; ip = ip->next) { + for (client = ip->client; client != NULL; + client = client->next) { + if ((client->state != S_BOUND) && + (client->state != S_RENEWING) && + (client->state != S_REBINDING)) + continue; + lease = client->active_lease; + if ((lease == NULL) || lease->released) + continue; + oc = lookup_option(&dhcpv6_universe, + lease->options, + D6O_DHCP4_O_DHCP6_SERVER); + if ((oc == NULL) || + !evaluate_option_cache(&addrs, NULL, NULL, NULL, + lease->options, NULL, + &global_scope, oc, MDL)) + continue; + if ((addrs.len % 16) != 0) { + data_string_forget(&addrs, MDL); + continue; + } + data_string_forget(&addrs, MDL); + goto found; + } + } + log_info("dhcp4o6_start: failed"); + dhcp4o6_stop(); + return; + +found: + if (dhcp4o6_state == 1) + return; + log_info("dhcp4o6_start: go to UP"); + dhcp4o6_state = 1; + + cc = send(dhcp4o6_fd, msg, sizeof(msg), 0); + if (cc < 0) + log_info("dhcp4o6_start: send(): %m"); +} + +/* + * Send a STOP to the DHCPv4 client + * (DHCPv6 client function) + * + * Go DOWN and on a transition from another state send a STOP message + * to the DHCPv4 client. + */ +static void dhcp4o6_stop() { + char msg[4] = { 'S', 'T', 'O', 'P' }; + int cc; + + if (dhcp4o6_state == -1) + return; + + log_info("dhcp4o6_stop: go to DOWN"); + dhcp4o6_state = -1; + + cc = send(dhcp4o6_fd, msg, sizeof(msg), 0); + if (cc < 0) + log_error("dhcp4o6_stop: send(): %m"); +} +#endif /* DHCPv6 && DHCP4o6 */ diff --git a/common/Makefile.am b/common/Makefile.am index c579719a..113aee84 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -2,11 +2,11 @@ AM_CPPFLAGS = -I$(top_srcdir) -DLOCALSTATEDIR='"@localstatedir@"' AM_CFLAGS = $(LDAP_CFLAGS) noinst_LIBRARIES = libdhcp.a -libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \ - dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \ - icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \ - packet.c parse.c print.c raw.c resolv.c socket.c \ - tables.c tr.c tree.c upf.c +libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c dhcp4o6.c \ + discover.c dispatch.c dlpi.c dns.c ethernet.c execute.c \ + fddi.c icmp.c inet.c lpf.c memory.c nit.c ns_name.c \ + options.c packet.c parse.c print.c raw.c resolv.c \ + socket.c tables.c tr.c tree.c upf.c man_MANS = dhcp-eval.5 dhcp-options.5 EXTRA_DIST = $(man_MANS) diff --git a/common/Makefile.in b/common/Makefile.in index a8d3c651..e78e4f7c 100644 --- a/common/Makefile.in +++ b/common/Makefile.in @@ -107,14 +107,15 @@ am__v_AR_1 = libdhcp_a_AR = $(AR) $(ARFLAGS) libdhcp_a_LIBADD = am_libdhcp_a_OBJECTS = alloc.$(OBJEXT) bpf.$(OBJEXT) comapi.$(OBJEXT) \ - conflex.$(OBJEXT) ctrace.$(OBJEXT) discover.$(OBJEXT) \ - dispatch.$(OBJEXT) dlpi.$(OBJEXT) dns.$(OBJEXT) \ - ethernet.$(OBJEXT) execute.$(OBJEXT) fddi.$(OBJEXT) \ - icmp.$(OBJEXT) inet.$(OBJEXT) lpf.$(OBJEXT) memory.$(OBJEXT) \ - nit.$(OBJEXT) ns_name.$(OBJEXT) options.$(OBJEXT) \ - packet.$(OBJEXT) parse.$(OBJEXT) print.$(OBJEXT) raw.$(OBJEXT) \ - resolv.$(OBJEXT) socket.$(OBJEXT) tables.$(OBJEXT) \ - tr.$(OBJEXT) tree.$(OBJEXT) upf.$(OBJEXT) + conflex.$(OBJEXT) ctrace.$(OBJEXT) dhcp4o6.$(OBJEXT) \ + discover.$(OBJEXT) dispatch.$(OBJEXT) dlpi.$(OBJEXT) \ + dns.$(OBJEXT) ethernet.$(OBJEXT) execute.$(OBJEXT) \ + fddi.$(OBJEXT) icmp.$(OBJEXT) inet.$(OBJEXT) lpf.$(OBJEXT) \ + memory.$(OBJEXT) nit.$(OBJEXT) ns_name.$(OBJEXT) \ + options.$(OBJEXT) packet.$(OBJEXT) parse.$(OBJEXT) \ + print.$(OBJEXT) raw.$(OBJEXT) resolv.$(OBJEXT) \ + socket.$(OBJEXT) tables.$(OBJEXT) tr.$(OBJEXT) tree.$(OBJEXT) \ + upf.$(OBJEXT) libdhcp_a_OBJECTS = $(am_libdhcp_a_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) @@ -358,11 +359,11 @@ top_srcdir = @top_srcdir@ AM_CPPFLAGS = -I$(top_srcdir) -DLOCALSTATEDIR='"@localstatedir@"' AM_CFLAGS = $(LDAP_CFLAGS) noinst_LIBRARIES = libdhcp.a -libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c discover.c \ - dispatch.c dlpi.c dns.c ethernet.c execute.c fddi.c \ - icmp.c inet.c lpf.c memory.c nit.c ns_name.c options.c \ - packet.c parse.c print.c raw.c resolv.c socket.c \ - tables.c tr.c tree.c upf.c +libdhcp_a_SOURCES = alloc.c bpf.c comapi.c conflex.c ctrace.c dhcp4o6.c \ + discover.c dispatch.c dlpi.c dns.c ethernet.c execute.c \ + fddi.c icmp.c inet.c lpf.c memory.c nit.c ns_name.c \ + options.c packet.c parse.c print.c raw.c resolv.c \ + socket.c tables.c tr.c tree.c upf.c man_MANS = dhcp-eval.5 dhcp-options.5 EXTRA_DIST = $(man_MANS) @@ -425,6 +426,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/comapi.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conflex.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctrace.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp4o6.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/discover.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dispatch.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dlpi.Po@am__quote@ diff --git a/common/discover.c b/common/discover.c index 3cd64a75..b5f297e7 100644 --- a/common/discover.c +++ b/common/discover.c @@ -3,7 +3,7 @@ Find and identify the network interfaces. */ /* - * Copyright (c) 2013-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2013-2014,2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2004-2009,2011 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * @@ -45,6 +45,7 @@ int interfaces_invalidated; int quiet_interface_discovery; u_int16_t local_port; u_int16_t remote_port; +int dhcpv4_over_dhcpv6 = 0; int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); int (*dhcp_interface_discovery_hook) (struct interface_info *); isc_result_t (*dhcp_interface_startup_hook) (struct interface_info *); @@ -1002,7 +1003,8 @@ discover_interfaces(int state) { /* We don't want the loopback interface. */ if (a->sin_addr.s_addr == htonl(INADDR_LOOPBACK) && ((tmp->flags & INTERFACE_AUTOMATIC) && - state == DISCOVER_SERVER)) + ((state == DISCOVER_SERVER) || + (state == DISCOVER_SERVER46)))) continue; /* If the only address we have is 0.0.0.0, we @@ -1029,7 +1031,8 @@ discover_interfaces(int state) { /* We don't want the loopback interface. */ if (IN6_IS_ADDR_LOOPBACK(&a->sin6_addr) && ((tmp->flags & INTERFACE_AUTOMATIC) && - state == DISCOVER_SERVER)) + ((state == DISCOVER_SERVER) || + (state == DISCOVER_SERVER46)))) continue; /* If the only address we have is 0.0.0.0, we @@ -1226,31 +1229,48 @@ discover_interfaces(int state) { tmp -> index = -1; /* Register the interface... */ - if (local_family == AF_INET) { - if_register_receive(tmp); - if_register_send(tmp); + switch (local_family) { + case AF_INET: + if (!dhcpv4_over_dhcpv6) { + if_register_receive(tmp); + if_register_send(tmp); + } else { + /* get_hw_addr() was called by register. */ + get_hw_addr(tmp->name, &tmp->hw_address); + } + break; #ifdef DHCPv6 - } else { + case AF_INET6: if ((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) { if_register6(tmp, 1); + } else if (state == DISCOVER_SERVER46) { + /* get_hw_addr() was called by if_register*6 + so now we have to call it explicitly + to not leave the hardware address unknown + (some code expects it cannot be. */ + get_hw_addr(tmp->name, &tmp->hw_address); } else { if_register_linklocal6(tmp); } + break; #endif /* DHCPv6 */ } interface_stash (tmp); wifcount++; #if defined (F_SETFD) - if (fcntl (tmp -> rfdesc, F_SETFD, 1) < 0) + /* if_register*() are no longer always called so + descriptors must be checked. */ + if ((tmp -> rfdesc >= 0) && + (fcntl (tmp -> rfdesc, F_SETFD, 1) < 0)) + log_error ("Can't set close-on-exec on %s: %m", + tmp -> name); + if ((tmp -> wfdesc != tmp -> rfdesc) && + (tmp -> wfdesc >= 0) && + (fcntl (tmp -> wfdesc, F_SETFD, 1) < 0)) log_error ("Can't set close-on-exec on %s: %m", tmp -> name); - if (tmp -> rfdesc != tmp -> wfdesc) { - if (fcntl (tmp -> wfdesc, F_SETFD, 1) < 0) - log_error ("Can't set close-on-exec on %s: %m", - tmp -> name); - } #endif next: interface_dereference (&tmp, MDL); @@ -1308,7 +1328,8 @@ discover_interfaces(int state) { log_fatal ("Not configured to listen on any interfaces!"); } - if ((local_family == AF_INET) && !setup_fallback) { + if ((local_family == AF_INET) && + !setup_fallback && !dhcpv4_over_dhcpv6) { setup_fallback = 1; maybe_setup_fallback(); } diff --git a/common/inet.c b/common/inet.c index 0cff19d0..52852d83 100644 --- a/common/inet.c +++ b/common/inet.c @@ -4,7 +4,7 @@ way... */ /* - * Copyright (c) 2011,2013,2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2011,2013,2014,2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2007-2009 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2004,2005 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium @@ -622,3 +622,29 @@ validate_port(char *port) { return htons((u_int16_t)local_port); } + +/* \brief Validate that the string represents a valid port pair (i.e. n,n+1) + * + * \param the string to validate + * \return the first port number in network byte order + */ + +u_int16_t +validate_port_pair(char *port) { + long local_port = 0; + long lower = 1; + long upper = 65534; + char *endptr; + + errno = 0; + local_port = strtol(port, &endptr, 10); + + if ((*endptr != '\0') || (errno == ERANGE) || (errno == EINVAL)) + log_fatal ("Invalid port pair specification: %s", port); + + if (local_port < lower || local_port > upper) + log_fatal("Port pair specified is out of range (%ld-%ld).", + lower, upper); + + return htons((u_int16_t)local_port); +} diff --git a/common/options.c b/common/options.c index 5abccf89..50271f22 100644 --- a/common/options.c +++ b/common/options.c @@ -3955,6 +3955,9 @@ do_packet6(struct interface_info *interface, const char *packet, unsigned char msg_type; const struct dhcpv6_packet *msg; const struct dhcpv6_relay_packet *relay; +#ifdef DHCP4o6 + const struct dhcpv4_over_dhcpv6_packet *msg46; +#endif struct packet *decoded_packet; #if defined (DEBUG_MEMORY_LEAKAGE) unsigned long previous_outstanding = dmalloc_outstanding; @@ -4016,6 +4019,28 @@ do_packet6(struct interface_info *interface, const char *packet, packet_dereference(&decoded_packet, MDL); return; } +#ifdef DHCP4o6 + } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || + (msg_type == DHCPV6_DHCPV4_RESPONSE)) { + int msglen = + (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); + msg46 = (struct dhcpv4_over_dhcpv6_packet *)packet; + decoded_packet->dhcpv6_msg_type = msg46->msg_type; + + /* message-specific data */ + memcpy(decoded_packet->dhcp4o6_flags, + msg46->flags, + sizeof(decoded_packet->dhcp4o6_flags)); + + if (!parse_option_buffer(decoded_packet->options, + msg46->options, len - msglen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + packet_dereference(&decoded_packet, MDL); + return; + } +#endif } else { int msglen = (int)(offsetof(struct dhcpv6_packet, options)); msg = (const struct dhcpv6_packet *)packet; diff --git a/common/tables.c b/common/tables.c index 1c9360cb..7617b7ef 100644 --- a/common/tables.c +++ b/common/tables.c @@ -3,7 +3,7 @@ Tables of information... */ /* - * Copyright (c) 2011-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2011-2014, 2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * @@ -565,13 +565,20 @@ static struct option dhcpv6_options[] = { { "ipv6-address-andsf", "6A", &dhcpv6_universe, 143, 1 }, #endif + /* RFC7341 OPTIONS */ +#if defined(RFC7341_OPTIONS) + { "dhcpv4-msg", "X", &dhcpv6_universe, 87, 1 }, + { "dhcp4-o-dhcp6-server", "6A", &dhcpv6_universe, 88, 1 }, +#endif + { NULL, NULL, NULL, 0, 0 } }; struct enumeration_value dhcpv6_duid_type_values[] = { - { "duid-llt", DUID_LLT }, /* Link-Local Plus Time */ - { "duid-en", DUID_EN }, /* DUID based upon enterprise-ID. */ - { "duid-ll", DUID_LL }, /* DUID from Link Local address only. */ + { "duid-llt", DUID_LLT }, /* Link-Local Plus Time */ + { "duid-en", DUID_EN }, /* DUID based upon enterprise-ID. */ + { "duid-ll", DUID_LL }, /* DUID from Link Local address only. */ + { "duid-uuid", DUID_UUID }, /* DUID based upon UUID */ { NULL, 0 } }; @@ -593,6 +600,7 @@ struct enumeration_value dhcpv6_status_code_values[] = { { "MalformedQuery", 8 }, /* Leasequery not valid. */ { "NotConfigured", 9 }, /* The target address is not in config. */ { "NotAllowed", 10 }, /* Server doesn't allow the leasequery. */ + { "QueryTerminated", 11 }, /* Leasequery terminated. */ { NULL, 0 } }; @@ -605,6 +613,9 @@ struct enumeration dhcpv6_status_codes = { struct enumeration_value lq6_query_type_values[] = { { "query-by-address", 1 }, { "query-by-clientid", 2 }, + { "query-by-relay-id", 3 }, + { "query-by-link-address", 4 }, + { "query-by-remote-id", 5 }, { NULL, 0 } }; @@ -630,6 +641,12 @@ struct enumeration_value dhcpv6_message_values[] = { { "RELAY-REPL", 13 }, { "LEASEQUERY", 14 }, { "LEASEQUERY-REPLY", 15 }, + { "LEASEQUERY-DONE", 16 }, + { "LEASEQUERY-DATA", 17 }, + { "RECONFIGURE-REQUEST", 18 }, + { "RECONFIGURE-REPLY", 19 }, + { "DHCPV4-QUERY", 20 }, + { "DHCPV4-RESPONSE", 21 }, { NULL, 0 } }; @@ -650,7 +667,13 @@ const char *dhcpv6_type_names[] = { "Relay-forward", "Relay-reply", "Leasequery", - "Leasequery-reply" + "Leasequery-reply", + "Leasequery-done", + "Leasequery-data", + "Reconfigure-request", + "Reconfigure-reply", + "Dhcpv4-query", + "Dhcpv4-response" }; const int dhcpv6_type_name_max = (sizeof(dhcpv6_type_names) / sizeof(dhcpv6_type_names[0])); @@ -670,7 +693,9 @@ static struct option vsio_options[] = { struct universe isc6_universe; static struct option isc6_options[] = { { "media", "t", &isc6_universe, 1, 1 }, - { "update-assist", "X", &isc6_universe, 2, 1 }, + { "update-assist", "X", &isc6_universe, 2, 1 }, + { "4o6-interface", "t", &isc6_universe, 60000, 1 }, + { "4o6-source-address", "6", &isc6_universe, 60001, 1 }, { NULL, NULL, NULL, 0, 0 } }; @@ -753,6 +753,7 @@ enable_execute enable_tracing enable_delayed_ack enable_dhcpv6 +enable_dhcpv4o6 enable_paranoia enable_early_chroot enable_ipv4_pktinfo @@ -1423,6 +1424,8 @@ Optional Features: is yes) --enable-delayed-ack queues multiple DHCPACK replies (default is no) --enable-dhcpv6 enable support for DHCPv6 (default is yes) + --enable-dhcpv4o6 enable support for DHCPv4-over-DHCPv6 (default is + no) --enable-paranoia enable support for chroot/setuid (default is no) --enable-early-chroot enable chrooting prior to configuration (default is no) @@ -5430,6 +5433,27 @@ $as_echo "#define DHCPv6 1" >>confdefs.h fi +# DHCPv4o6 optional compile-time feature. +# Check whether --enable-dhcpv4o6 was given. +if test "${enable_dhcpv4o6+set}" = set; then : + enableval=$enable_dhcpv4o6; +fi + +# DHCPv4o6 is off by default, so define if it is explicitly enabled. +if test "$enable_dhcpv4o6" = "yes"; then + # DHCPv4o6 requires DHCPv6 + if test "$enable_dhcpv6" = "no"; then + as_fn_error $? "dhcpv4o6 requires dhcpv6" "$LINENO" 5 + fi + # DHCPv4o6 is not yet compatible with delayed-ack + if test "$enable_delayed_ack" = "yes"; then + as_fn_error $? "dhcpv4o6 is not compatible with delayed-ack" "$LINENO" 5 + fi + +$as_echo "#define DHCP4o6 1" >>confdefs.h + +fi + # PARANOIA is off by default (until we can test it with all features) # Check whether --enable-paranoia was given. if test "${enable_paranoia+set}" = set; then : @@ -8641,6 +8665,14 @@ $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi +if test "$enable_dhcpv4o6" = "yes"; then + DHCP_VERSIONS="DHCPv4, DHCPv6 and DHCPv4-over-DHCPv6" +elif test "$enable_dhcpv6" != "no"; then + DHCP_VERSIONS="DHCPv4 and DHCPv6" +else + DHCP_VERSIONS="DHCPv4" +fi + (cd $srcdir sh util/bindvar.sh if test $? -ne 0; then @@ -8663,6 +8695,8 @@ Flags: DEFS: $DEFS CFLAGS: $CFLAGS +DHCP versions: $DHCP_VERSIONS + Features: debug: $enable_debug failover: $enable_failover diff --git a/configure.ac b/configure.ac index 9351150a..7f05b7d6 100644 --- a/configure.ac +++ b/configure.ac @@ -149,6 +149,23 @@ if test "$enable_dhcpv6" != "no"; then [Define to 1 to include DHCPv6 support.]) fi +# DHCPv4o6 optional compile-time feature. +AC_ARG_ENABLE(dhcpv4o6, + AS_HELP_STRING([--enable-dhcpv4o6],[enable support for DHCPv4-over-DHCPv6 (default is no)])) +# DHCPv4o6 is off by default, so define if it is explicitly enabled. +if test "$enable_dhcpv4o6" = "yes"; then + # DHCPv4o6 requires DHCPv6 + if test "$enable_dhcpv6" = "no"; then + AC_MSG_ERROR([dhcpv4o6 requires dhcpv6]) + fi + # DHCPv4o6 is not yet compatible with delayed-ack + if test "$enable_delayed_ack" = "yes"; then + AC_MSG_ERROR([dhcpv4o6 is not compatible with delayed-ack]) + fi + AC_DEFINE([DHCP4o6], [1], + [Define to 1 to include DHCPv4 over DHCPv6 support.]) +fi + # PARANOIA is off by default (until we can test it with all features) AC_ARG_ENABLE(paranoia, AS_HELP_STRING([--enable-paranoia],[enable support for chroot/setuid (default is no)])) @@ -799,6 +816,14 @@ AC_CONFIG_FILES([ ]) AC_OUTPUT +if test "$enable_dhcpv4o6" = "yes"; then + DHCP_VERSIONS="DHCPv4, DHCPv6 and DHCPv4-over-DHCPv6" +elif test "$enable_dhcpv6" != "no"; then + DHCP_VERSIONS="DHCPv4 and DHCPv6" +else + DHCP_VERSIONS="DHCPv4" +fi + (cd $srcdir sh util/bindvar.sh if test $? -ne 0; then @@ -821,6 +846,8 @@ Flags: DEFS: $DEFS CFLAGS: $CFLAGS +DHCP versions: $DHCP_VERSIONS + Features: debug: $enable_debug failover: $enable_failover diff --git a/includes/config.h.in b/includes/config.h.in index 80f3b083..403a28f1 100644 --- a/includes/config.h.in +++ b/includes/config.h.in @@ -12,6 +12,9 @@ /* Define to queue multiple DHCPACK replies per fsync. */ #undef DELAYED_ACK +/* Define to 1 to include DHCPv4 over DHCPv6 support. */ +#undef DHCP4o6 + /* Define to BIG_ENDIAN for MSB (Motorola or SPARC CPUs) or LITTLE_ENDIAN for LSB (Intel CPUs). */ #undef DHCP_BYTE_ORDER diff --git a/includes/dhcp6.h b/includes/dhcp6.h index 03fedfa8..4d7a9e34 100644 --- a/includes/dhcp6.h +++ b/includes/dhcp6.h @@ -76,10 +76,49 @@ #define D6O_CLT_TIME 46 /* RFC5007 */ #define D6O_LQ_RELAY_DATA 47 /* RFC5007 */ #define D6O_LQ_CLIENT_LINK 48 /* RFC5007 */ +#define D6O_MIP6_HNIDF 49 /* RFC6610 */ +#define D6O_MIP6_VDINF 50 /* RFC6610 */ +#define D6O_V6_LOST 51 /* RFC5223 */ +#define D6O_CAPWAP_AC_V6 52 /* RFC5417 */ +#define D6O_RELAY_ID 53 /* RFC5460 */ +#define D6O_IPV6_ADDRESS_MOS 54 /* RFC5678 */ +#define D6O_IPV6_FQDN_MOS 55 /* RFC5678 */ +#define D6O_NTP_SERVER 56 /* RFC5908 */ +#define D6O_V6_ACCESS_DOMAIN 57 /* RFC5986 */ +#define D6O_SIP_UA_CS_LIST 58 /* RFC6011 */ +#define D6O_BOOTFILE_URL 59 /* RFC5970 */ +#define D6O_BOOTFILE_PARAM 60 /* RFC5970 */ +#define D6O_CLIENT_ARCH_TYPE 61 /* RFC5970 */ +#define D6O_NII 62 /* RFC5970 */ +#define D6O_GEOLOCATION 63 /* RFC6225 */ +#define D6O_AFTR_NAME 64 /* RFC6334 */ +#define D6O_ERP_LOCAL_DOMAIN_NAME 65 /* RFC6440 */ +#define D6O_RSOO 66 /* RFC6422 */ +#define D6O_PD_EXCLUDE 67 /* RFC6603 */ +#define D6O_VSS 68 /* RFC6607 */ +#define D6O_MIP6_IDINF 69 /* RFC6610 */ +#define D6O_MIP6_UDINF 70 /* RFC6610 */ +#define D6O_MIP6_HNP 71 /* RFC6610 */ +#define D6O_MIP6_HAA 72 /* RFC6610 */ +#define D6O_MIP6_HAF 73 /* RFC6610 */ +#define D6O_RDNSS_SELECTION 74 /* RFC6731 */ +#define D6O_KRB_PRINCIPAL_NAME 75 /* RFC6784 */ +#define D6O_KRB_REALM_NAME 76 /* RFC6784 */ +#define D6O_KRB_DEFAULT_REALM_NAME 77 /* RFC6784 */ +#define D6O_KRB_KDC 78 /* RFC6784 */ #define D6O_CLIENT_LINKLAYER_ADDR 79 /* RFC6939 */ +#define D6O_LINK_ADDRESS 80 /* RFC6977 */ +#define D6O_RADIUS 81 /* RFC7037 */ +#define D6O_SOL_MAX_RT 82 /* RFC7083 */ +#define D6O_INF_MAX_RT 83 /* RFC7083 */ +#define D6O_ADDRSEL 84 /* RFC7078 */ +#define D6O_ADDRSEL_TABLE 85 /* RFC7078 */ +#define D6O_V6_PCP_SERVER 86 /* RFC7291 */ +#define D6O_DHCPV4_MSG 87 /* RFC7341 */ +#define D6O_DHCP4_O_DHCP6_SERVER 88 /* RFC7341 */ /* - * Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007. + * Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007, 5460. */ #define STATUS_Success 0 #define STATUS_UnspecFail 1 @@ -92,6 +131,7 @@ #define STATUS_MalformedQuery 8 #define STATUS_NotConfigured 9 #define STATUS_NotAllowed 10 +#define STATUS_QueryTerminated 11 /* * DHCPv6 message types, defined in section 5.3 of RFC 3315 @@ -109,8 +149,14 @@ #define DHCPV6_INFORMATION_REQUEST 11 #define DHCPV6_RELAY_FORW 12 #define DHCPV6_RELAY_REPL 13 -#define DHCPV6_LEASEQUERY 14 -#define DHCPV6_LEASEQUERY_REPLY 15 +#define DHCPV6_LEASEQUERY 14 /* RFC5007 */ +#define DHCPV6_LEASEQUERY_REPLY 15 /* RFC5007 */ +#define DHCPV6_LEASEQUERY_DONE 16 /* RFC5460 */ +#define DHCPV6_LEASEQUERY_DATA 17 /* RFC5460 */ +#define DHCPV6_RECONFIGURE_REQUEST 18 /* RFC6977 */ +#define DHCPV6_RECONFIGURE_REPLY 19 /* RFC6977 */ +#define DHCPV6_DHCPV4_QUERY 20 /* RFC7341 */ +#define DHCPV6_DHCPV4_RESPONSE 21 /* RFC7341 */ extern const char *dhcpv6_type_names[]; extern const int dhcpv6_type_name_max; @@ -120,6 +166,7 @@ extern const int dhcpv6_type_name_max; #define DUID_LLT 1 #define DUID_EN 2 #define DUID_LL 3 +#define DUID_UUID 4 /* RFC6355 */ /* Offsets into IA_*'s where Option spaces commence. */ #define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */ @@ -197,10 +244,27 @@ struct dhcpv6_relay_packet { }; #define MAX_V6RELAY_HOPS 32 -/* Leasequery query-types (RFC 5007) */ +/* + * DHCPv4-over-DHCPv6 packet format, defined in RFC 4731 + */ +struct dhcpv4_over_dhcpv6_packet { + unsigned char msg_type; + unsigned char flags[3]; + unsigned char options[FLEXIBLE_ARRAY_MEMBER]; +}; +#define DHCP4O6_QUERY_UNICAST 128 + +/* DHCPv4-over-DHCPv6 ISC vendor suboptions */ +#define D4O6_INTERFACE 60000 +#define D4O6_SRC_ADDRESS 60001 + +/* Leasequery query-types (RFC 5007, 5460) */ #define LQ6QT_BY_ADDRESS 1 #define LQ6QT_BY_CLIENTID 2 +#define LQ6QT_BY_RELAY_ID 3 +#define LQ6QT_BY_LINK_ADDRESS 4 +#define LQ6QT_BY_REMOTE_ID 5 /* * DUID time starts 2000-01-01. diff --git a/includes/dhcpd.h b/includes/dhcpd.h index bdd00c6d..4b3001a8 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -421,6 +421,12 @@ struct packet { /* DHCPv6 packet containing this one, or NULL if none */ struct packet *dhcpv6_container_packet; + /* DHCPv4-over-DHCPv6 flags */ + unsigned char dhcp4o6_flags[3]; + + /* DHCPv4-over-DHCPv6 response, or NULL */ + struct data_string *dhcp4o6_response; + int options_valid; int client_port; struct iaddr client_addr; @@ -684,7 +690,8 @@ struct lease_state { #define DISCOVER_SERVER 1 #define DISCOVER_UNCONFIGURED 2 #define DISCOVER_RELAY 3 -#define DISCOVER_REQUESTED 4 +#define DISCOVER_SERVER46 4 +#define DISCOVER_REQUESTED 5 /* DDNS_UPDATE_STYLE enumerations. */ #define DDNS_UPDATE_STYLE_NONE 0 @@ -1167,6 +1174,13 @@ enum dhcp_state { S_STOPPED = 8 }; +/* Possible pending client operations. */ +enum dhcp_pending { + P_NONE = 0, + P_REBOOT = 1, + P_RELEASE = 2 +}; + /* Authentication and BOOTP policy possibilities (not all values work for each). */ enum policy { P_IGNORE, P_ACCEPT, P_PREFER, P_REQUIRE, P_DONT }; @@ -1245,6 +1259,7 @@ struct client_state { struct option_state *sent_options; /* Options we sent. */ enum dhcp_state state; /* Current state for this interface. */ TIME last_write; /* Last time this state was written. */ + enum dhcp_pending pending; /* Current pending operation. */ /* DHCPv4 values. */ struct client_lease *active; /* Currently active lease. */ @@ -2029,6 +2044,17 @@ void parse_vendor_option(struct packet *packet, struct option_state *out_options, struct binding_scope **scope); +/* dhcp4o6.c */ +#if defined(DHCP4o6) +extern int dhcp4o6_fd; +extern omapi_object_t *dhcp4o6_object; +extern omapi_object_type_t *dhcp4o6_type; +extern void dhcp4o6_setup(u_int16_t); + +/* dependency */ +extern isc_result_t dhcpv4o6_handler(omapi_object_t *); + +#endif /* dhcpd.c */ extern struct timeval cur_tv; #define cur_time cur_tv.tv_sec @@ -2748,6 +2774,7 @@ extern struct in_addr local_address; extern u_int16_t local_port; extern u_int16_t remote_port; +extern int dhcpv4_over_dhcpv6; extern int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); extern int (*dhcp_interface_discovery_hook) (struct interface_info *); @@ -2858,6 +2885,7 @@ const char *piaddr (struct iaddr); char *piaddrmask(struct iaddr *, struct iaddr *); char *piaddrcidr(const struct iaddr *, unsigned int); u_int16_t validate_port(char *); +u_int16_t validate_port_pair(char *); /* dhclient.c */ extern int nowait; @@ -2951,6 +2979,8 @@ void dhcpv4_client_assignments(void); void dhcpv6_client_assignments(void); void form_duid(struct data_string *duid, const char *file, int line); +void dhcp4o6_start(void); + /* dhc6.c */ void dhc6_lease_destroy(struct dhc6_lease **src, const char *file, int line); void start_init6(struct client_state *client); diff --git a/includes/site.h b/includes/site.h index 0586717e..944dbefc 100644 --- a/includes/site.h +++ b/includes/site.h @@ -337,5 +337,6 @@ #define RFC6939_OPTIONS #define RFC6977_OPTIONS #define RFC7083_OPTIONS +#define RFC7341_OPTIONS #define RFC7618_OPTIONS #define RFC7710_OPTIONS diff --git a/relay/dhcrelay.c b/relay/dhcrelay.c index e06eb4c2..9ec39cd1 100644 --- a/relay/dhcrelay.c +++ b/relay/dhcrelay.c @@ -1498,6 +1498,7 @@ process_up6(struct packet *packet, struct stream_list *dp) { case DHCPV6_INFORMATION_REQUEST: case DHCPV6_RELAY_FORW: case DHCPV6_LEASEQUERY: + case DHCPV6_DHCPV4_QUERY: log_info("Relaying %s from %s port %d going up.", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), @@ -1509,6 +1510,7 @@ process_up6(struct packet *packet, struct stream_list *dp) { case DHCPV6_RECONFIGURE: case DHCPV6_RELAY_REPL: case DHCPV6_LEASEQUERY_REPLY: + case DHCPV6_DHCPV4_RESPONSE: log_info("Discarding %s from %s port %d going up.", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), @@ -1727,6 +1729,7 @@ process_down6(struct packet *packet) { case DHCPV6_RECONFIGURE: case DHCPV6_RELAY_FORW: case DHCPV6_LEASEQUERY_REPLY: + case DHCPV6_DHCPV4_RESPONSE: log_info("Relaying %s to %s port %d down.", dhcpv6_type_names[msg->msg_type], piaddr(peer), @@ -1742,6 +1745,7 @@ process_down6(struct packet *packet) { case DHCPV6_DECLINE: case DHCPV6_INFORMATION_REQUEST: case DHCPV6_LEASEQUERY: + case DHCPV6_DHCPV4_QUERY: log_info("Discarding %s to %s port %d down.", dhcpv6_type_names[msg->msg_type], piaddr(peer), diff --git a/server/bootp.c b/server/bootp.c index ca54be64..2e752c5b 100644 --- a/server/bootp.c +++ b/server/bootp.c @@ -3,7 +3,7 @@ BOOTP Protocol support. */ /* - * Copyright (c) 2009,2012-2014 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 2009,2012-2014,2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 2004,2005,2007 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-2003 by Internet Software Consortium * @@ -350,6 +350,34 @@ void bootp (packet) /* We're done with the option state. */ option_state_dereference (&options, MDL); +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + /* Report what we're doing... */ + log_info("%s", msgbuf); + log_info("DHCP4o6 BOOTREPLY for %s to %s (%s) via %s", + piaddr(lease->ip_addr), + ((hp != NULL) && (hp->name != NULL)) ? + hp -> name : "unknown", + print_hw_addr (packet->raw->htype, + packet->raw->hlen, + packet->raw->chaddr), + piaddr(packet->client_addr)); + + /* fill dhcp4o6_response */ + packet->dhcp4o6_response->len = outgoing.packet_length; + packet->dhcp4o6_response->buffer = NULL; + if (!buffer_allocate(&packet->dhcp4o6_response->buffer, + outgoing.packet_length, MDL)) { + log_fatal("No memory to store DHCP4o6 reply."); + } + packet->dhcp4o6_response->data = + packet->dhcp4o6_response->buffer->data; + memcpy(packet->dhcp4o6_response->buffer->data, + outgoing.raw, outgoing.packet_length); + goto out; + } +#endif + /* Set up the hardware destination address... */ hto.hbuf [0] = packet -> raw -> htype; hto.hlen = packet -> raw -> hlen + 1; diff --git a/server/confpars.c b/server/confpars.c index bc85b3b6..1b37a22e 100644 --- a/server/confpars.c +++ b/server/confpars.c @@ -2786,12 +2786,21 @@ parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) { 0xF0, 0xF8, 0xFC, 0xFE }; struct iaddr iaddr; - if (local_family != AF_INET6) { +#if defined(DHCP4o6) + if ((local_family != AF_INET6) && !dhcpv4_over_dhcpv6) { + parse_warn(cfile, "subnet6 statement is only supported " + "in DHCPv6 and DHCPv4o6 modes."); + skip_to_semi(cfile); + return; + } +#else /* defined(DHCP4o6) */ + if (local_family != AF_INET6) { parse_warn(cfile, "subnet6 statement is only supported " "in DHCPv6 mode."); skip_to_semi(cfile); return; } +#endif /* !defined(DHCP4o6) */ subnet = NULL; status = subnet_allocate(&subnet, MDL); diff --git a/server/dhcp.c b/server/dhcp.c index dcbadc39..a823a4e8 100644 --- a/server/dhcp.c +++ b/server/dhcp.c @@ -36,6 +36,9 @@ static void maybe_return_agent_options(struct packet *packet, static int reuse_lease (struct packet* packet, struct lease* new_lease, struct lease* lease, struct lease_state *state, int offer); +#if defined(DHCPv6) && defined(DHCP4o6) +static int locate_network6(struct packet *packet); +#endif int outstanding_pings; @@ -108,6 +111,20 @@ dhcp (struct packet *packet) { s = typebuf; } +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + log_info("DHCP4o6 %s from %s via %s: %s", s, + (packet->raw->htype + ? print_hw_addr(packet->raw->htype, + packet->raw->hlen, + packet->raw->chaddr) + : "<no identifier>"), + piaddr(packet->client_addr), + errmsg); + goto out; + } +#endif + log_info("%s from %s via %s: %s", s, (packet->raw->htype ? print_hw_addr(packet->raw->htype, @@ -292,6 +309,21 @@ void dhcpdiscover (packet, ms_nulltp) /* %Audit% This is log output. %2004.06.17,Safe% * If we truncate we hope the user can get a hint from the log. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + snprintf (msgbuf, sizeof msgbuf, + "DHCP4o6 DHCPDISCOVER from %s %s%s%svia %s", + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + piaddr(packet->client_addr)); + } else +#endif snprintf (msgbuf, sizeof msgbuf, "DHCPDISCOVER from %s %s%s%svia %s", (packet -> raw -> htype ? print_hw_addr (packet -> raw -> htype, @@ -307,6 +339,12 @@ void dhcpdiscover (packet, ms_nulltp) /* Sourceless packets don't make sense here. */ if (!packet -> shared_network) { +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + log_info ("DHCP4o6 packet from unknown subnet: %s", + piaddr(packet->client_addr)); + } else +#endif log_info ("Packet from unknown subnet: %s", inet_ntoa (packet -> raw -> giaddr)); goto out; @@ -482,6 +520,22 @@ void dhcprequest (packet, ms_nulltp, ip_lease) /* %Audit% This is log output. %2004.06.17,Safe% * If we truncate we hope the user can get a hint from the log. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + snprintf (msgbuf, sizeof msgbuf, + "DHCP4o6 DHCPREQUEST for %s%s from %s %s%s%svia %s", + piaddr (cip), smbuf, + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + piaddr(packet->client_addr)); + } else +#endif snprintf (msgbuf, sizeof msgbuf, "DHCPREQUEST for %s%s from %s %s%s%svia %s", piaddr (cip), smbuf, @@ -801,6 +855,24 @@ void dhcprelease (packet, ms_nulltp) /* %Audit% This is log output. %2004.06.17,Safe% * If we truncate we hope the user can get a hint from the log. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + snprintf (msgbuf, sizeof msgbuf, + "DHCP4o6 DHCPRELEASE of %s from %s %s%s%svia " + "%s (%sfound)", + cstr, + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + piaddr(packet->client_addr), + lease ? "" : "not "); + } else +#endif snprintf (msgbuf, sizeof msgbuf, "DHCPRELEASE of %s from %s %s%s%svia %s (%sfound)", cstr, @@ -892,6 +964,22 @@ void dhcpdecline (packet, ms_nulltp) /* %Audit% This is log output. %2004.06.17,Safe% * If we truncate we hope the user can get a hint from the log. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + snprintf (msgbuf, sizeof msgbuf, + "DHCP4o6 DHCPDECLINE of %s from %s %s%s%svia %s", + piaddr (cip), + (packet -> raw -> htype + ? print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr) + : (lease + ? print_hex_1(lease->uid_len, lease->uid, 60) + : "<no identifier>")), + s ? "(" : "", s ? s : "", s ? ") " : "", + piaddr(packet->client_addr)); + } else +#endif snprintf (msgbuf, sizeof msgbuf, "DHCPDECLINE of %s from %s %s%s%svia %s", piaddr (cip), @@ -1003,9 +1091,17 @@ void dhcpinform (packet, ms_nulltp) source address if they didn't set ciaddr. */ if (!packet->raw->ciaddr.s_addr) { zeroed_ciaddr = ISC_TRUE; - cip.len = 4; - memcpy(cip.iabuf, &packet->client_addr.iabuf, 4); - addr_type = "source"; + /* With DHCPv4-over-DHCPv6 it can be an IPv6 address + so we check its length. */ + if (packet->client_addr.len == 4) { + cip.len = 4; + memcpy(cip.iabuf, &packet->client_addr.iabuf, 4); + addr_type = "source"; + } else { + cip.len = 0; + memset(cip.iabuf, 0, 4); + addr_type = "v4o6"; + } } else { zeroed_ciaddr = ISC_FALSE; cip.len = 4; @@ -1028,6 +1124,14 @@ void dhcpinform (packet, ms_nulltp) /* %Audit% This is log output. %2004.06.17,Safe% * If we truncate we hope the user can get a hint from the log. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + snprintf(msgbuf, sizeof(msgbuf), + "DHCP4o6 DHCPINFORM from %s via %s", + piaddr(cip), + piaddr(packet->client_addr)); + } else +#endif snprintf(msgbuf, sizeof(msgbuf), "DHCPINFORM from %s via %s", piaddr(cip), packet->raw->giaddr.s_addr ? @@ -1511,6 +1615,36 @@ void dhcpinform (packet, ms_nulltp) dump_raw ((unsigned char *)&raw, outgoing.packet_length); #endif +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + /* Report what we're sending. */ + snprintf(msgbuf, sizeof msgbuf, + "DHCP4o6 DHCPACK to %s (%s) via", piaddr(cip), + (packet->raw->htype && packet->raw->hlen) ? + print_hw_addr(packet->raw->htype, packet->raw->hlen, + packet->raw->chaddr) : + "<no client hardware address>"); + log_info("%s %s", msgbuf, piaddr(packet->client_addr)); + + /* fill dhcp4o6_response */ + packet->dhcp4o6_response->len = outgoing.packet_length; + packet->dhcp4o6_response->buffer = NULL; + if (!buffer_allocate(&packet->dhcp4o6_response->buffer, + outgoing.packet_length, MDL)) { + log_fatal("No memory to store DHCP4o6 reply."); + } + packet->dhcp4o6_response->data = + packet->dhcp4o6_response->buffer->data; + memcpy(packet->dhcp4o6_response->buffer->data, + outgoing.raw, outgoing.packet_length); + + /* done */ + if (subnet) + subnet_dereference (&subnet, MDL); + return; + } +#endif + /* Set up the common stuff... */ to.sin_family = AF_INET; #ifdef HAVE_SA_LEN @@ -1711,7 +1845,21 @@ void nak_lease (packet, cip, network_group) raw.hops = packet -> raw -> hops; raw.op = BOOTREPLY; + /* Make sure that the packet is at least as big as a BOOTP packet. */ + if (outgoing.packet_length < BOOTP_MIN_LEN) + outgoing.packet_length = BOOTP_MIN_LEN; + /* Report what we're sending... */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + log_info ("DHCP4o6 DHCPNAK on %s to %s via %s", + piaddr (*cip), + print_hw_addr (packet -> raw -> htype, + packet -> raw -> hlen, + packet -> raw -> chaddr), + piaddr(packet->client_addr)); + } else +#endif log_info ("DHCPNAK on %s to %s via %s", piaddr (*cip), print_hw_addr (packet -> raw -> htype, @@ -1728,6 +1876,23 @@ void nak_lease (packet, cip, network_group) dump_raw ((unsigned char *)&raw, outgoing.packet_length); #endif +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + /* fill dhcp4o6_response */ + packet->dhcp4o6_response->len = outgoing.packet_length; + packet->dhcp4o6_response->buffer = NULL; + if (!buffer_allocate(&packet->dhcp4o6_response->buffer, + outgoing.packet_length, MDL)) { + log_fatal("No memory to store DHCP4o6 reply."); + } + packet->dhcp4o6_response->data = + packet->dhcp4o6_response->buffer->data; + memcpy(packet->dhcp4o6_response->buffer->data, + outgoing.raw, outgoing.packet_length); + return; + } +#endif + /* Set up the common stuff... */ to.sin_family = AF_INET; #ifdef HAVE_SA_LEN @@ -1735,10 +1900,6 @@ void nak_lease (packet, cip, network_group) #endif memset (to.sin_zero, 0, sizeof to.sin_zero); - /* Make sure that the packet is at least as big as a BOOTP packet. */ - if (outgoing.packet_length < BOOTP_MIN_LEN) - outgoing.packet_length = BOOTP_MIN_LEN; - /* If this was gatewayed, send it back to the gateway. Otherwise, broadcast it on the local network. */ if (raw.giaddr.s_addr) { @@ -1964,7 +2125,7 @@ void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) struct in_addr from; TIME remaining_time; struct iaddr cip; -#if defined(DELAYED_ACK) +#if defined(DELAYED_ACK) && !defined(DHCP4o6) /* By default we don't do the enqueue */ isc_boolean_t enqueue = ISC_FALSE; #endif @@ -2965,7 +3126,7 @@ void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) commit = 0; } -#if !defined(DELAYED_ACK) +#if !defined(DELAYED_ACK) || defined(DHCP4o6) /* Install the new information on 'lt' onto the lease at * 'lease'. If this is a DHCPOFFER, it is a 'soft' promise, * if it is a DHCPACK, it is a 'hard' binding, so it needs @@ -2977,7 +3138,7 @@ void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) if ((use_old_lease == 0) && !supersede_lease(lease, lt, commit, offer == DHCPACK, offer == DHCPACK, 0)) { -#else /* defined(DELAYED_ACK) */ +#else /* defined(DELAYED_ACK) && !defined(DHCP4o6) */ /* * If there already isn't a need for a lease commit, and we * can just answer right away, set a flag to indicate this. @@ -3374,7 +3535,7 @@ void ack_lease (packet, lease, offer, when, msg, ms_nulltp, hp) ++outstanding_pings; } else { lease->cltt = cur_time; -#if defined(DELAYED_ACK) +#if defined(DELAYED_ACK) && !defined(DHCP4o6) if (enqueue) delayed_ack_enqueue(lease); else @@ -3650,6 +3811,48 @@ void dhcp_reply (lease) } else s = (char *)0; + /* Make sure outgoing packets are at least as big + as a BOOTP packet. */ + if (packet_length < BOOTP_MIN_LEN) + packet_length = BOOTP_MIN_LEN; + +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (state->packet->dhcp4o6_response != NULL)) { + /* Say what we're doing... */ + log_info ("DHCP4o6 %s on %s to %s %s%s%svia %s", + (state -> offer + ? (state -> offer == DHCPACK + ? "DHCPACK" : "DHCPOFFER") + : "BOOTREPLY"), + piaddr (lease -> ip_addr), + (lease -> hardware_addr.hlen + ? print_hw_addr (lease -> hardware_addr.hbuf [0], + lease -> hardware_addr.hlen - 1, + &lease -> hardware_addr.hbuf [1]) + : print_hex_1(lease->uid_len, lease->uid, 60)), + s ? "(" : "", s ? s : "", s ? ") " : "", + piaddr(state->packet->client_addr)); + + /* fill dhcp4o6_response */ + state->packet->dhcp4o6_response->len = packet_length; + state->packet->dhcp4o6_response->buffer = NULL; + if (!buffer_allocate(&state->packet->dhcp4o6_response->buffer, + packet_length, MDL)) { + log_fatal("No memory to store DHCP4o6 reply."); + } + state->packet->dhcp4o6_response->data = + state->packet->dhcp4o6_response->buffer->data; + memcpy(state->packet->dhcp4o6_response->buffer->data, + &raw, packet_length); + + /* done */ + free_lease_state (state, MDL); + lease -> state = (struct lease_state *)0; + + return; + } +#endif + /* Say what we're doing... */ log_info ("%s on %s to %s %s%s%svia %s", (state -> offer @@ -3666,6 +3869,10 @@ void dhcp_reply (lease) ? inet_ntoa (state -> giaddr) : state -> ip -> name)); +#ifdef DEBUG_PACKET + dump_raw ((unsigned char *)&raw, packet_length); +#endif + /* Set up the hardware address... */ hto.hlen = lease -> hardware_addr.hlen; memcpy (hto.hbuf, lease -> hardware_addr.hbuf, hto.hlen); @@ -3676,15 +3883,6 @@ void dhcp_reply (lease) #endif memset (to.sin_zero, 0, sizeof to.sin_zero); -#ifdef DEBUG_PACKET - dump_raw ((unsigned char *)&raw, packet_length); -#endif - - /* Make sure outgoing packets are at least as big - as a BOOTP packet. */ - if (packet_length < BOOTP_MIN_LEN) - packet_length = BOOTP_MIN_LEN; - /* If this was gatewayed, send it back to the gateway... */ if (raw.giaddr.s_addr) { to.sin_addr = raw.giaddr; @@ -4801,6 +4999,132 @@ int permitted (packet, permit_list) return 0; } +#if defined(DHCPv6) && defined(DHCP4o6) +static int locate_network6 (packet) + struct packet *packet; +{ + const struct packet *chk_packet; + const struct in6_addr *link_addr, *first_link_addr; + struct iaddr ia; + struct data_string data; + struct subnet *subnet = NULL; + struct option_cache *oc; + + /* from locate_network() */ + + /* See if there's a Relay Agent Link Selection Option, or a + * Subnet Selection Option. The Link-Select and Subnet-Select + * are formatted and used precisely the same, but we must prefer + * the link-select over the subnet-select. + * BTW in DHCPv4 over DHCPv6 no cross version relay was specified + * so it is unlikely to see a link-select. + */ + if ((oc = lookup_option(&agent_universe, packet->options, + RAI_LINK_SELECT)) == NULL) + oc = lookup_option(&dhcp_universe, packet->options, + DHO_SUBNET_SELECTION); + + /* If there's an option indicating link connection or subnet + * selection, and it's valid, use it to figure out the subnet. + * If it's not valid, fail. + */ + if (oc) { + memset(&data, 0, sizeof data); + if (!evaluate_option_cache(&data, packet, NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL)) { + return (0); + } + if (data.len == 0) { + return (0); + } + if (data.len != 4) { + data_string_forget(&data, MDL); + return (0); + } + ia.len = 4; + memcpy(ia.iabuf, data.data, 4); + data_string_forget(&data, MDL); + + if (find_subnet(&subnet, ia, MDL)) { + shared_network_reference(&packet->shared_network, + subnet->shared_network, MDL); + subnet_dereference(&subnet, MDL); + return (1); + } + return (0); + } + + /* See if there is a giaddr (still unlikely), if there is one + * use it to figure out the subnet. If it's not valid, fail. + */ + if (packet->raw->giaddr.s_addr) { + ia.len = 4; + memcpy(ia.iabuf, &packet->raw->giaddr, 4); + + if (find_subnet(&subnet, ia, MDL)) { + shared_network_reference(&packet->shared_network, + subnet->shared_network, MDL); + subnet_dereference(&subnet, MDL); + return (1); + } + return (0); + } + + /* from shared_network_from_packet6() */ + + /* 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; + break; + } + 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) { + ia.len = sizeof(*first_link_addr); + memcpy(ia.iabuf, first_link_addr, sizeof(*first_link_addr)); + if (find_subnet (&subnet, ia, MDL)) { + shared_network_reference(&packet->shared_network, + subnet->shared_network, MDL); + subnet_dereference(&subnet, MDL); + return (1); + } + return (0); + } + + /* If there is no link address, we will use the interface + * that this packet came in on to pick the shared_network. + */ + if (packet->interface != NULL) { + if (packet->interface->shared_network == NULL) + return (0); + shared_network_reference(&packet->shared_network, + packet->interface->shared_network, + MDL); + return (1); + } + + /* We shouldn't be able to get here but if there is no link + * address and no interface we don't know where to get the + * shared_network from, log an error and return an error. + */ + log_error("No interface and no link address " + "can't determine DHCP4o6 shared network"); + return (0); +} +#endif + int locate_network (packet) struct packet *packet; { @@ -4809,6 +5133,12 @@ int locate_network (packet) struct subnet *subnet = (struct subnet *)0; struct option_cache *oc; +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6 && (packet->dhcp4o6_response != NULL)) { + return (locate_network6 (packet)); + } +#endif + /* See if there's a Relay Agent Link Selection Option, or a * Subnet Selection Option. The Link-Select and Subnet-Select * are formatted and used precisely the same, but we must prefer @@ -4843,7 +5173,11 @@ int locate_network (packet) &global_scope, oc, MDL)) { return 0; } + if (data.len == 0) { + return 0; + } if (data.len != 4) { + data_string_forget (&data, MDL); return 0; } ia.len = 4; diff --git a/server/dhcpd.8 b/server/dhcpd.8 index bfda6397..55466766 100644 --- a/server/dhcpd.8 +++ b/server/dhcpd.8 @@ -1,6 +1,6 @@ .\" dhcpd.8 .\" -.\" Copyright (c) 2009-2012,2015 by Internet Systems Consortium, Inc. ("ISC") +.\" Copyright (c) 2009-2012,2015-2016 by Internet Systems Consortium, Inc. ("ISC") .\" Copyright (c) 2004-2007 by Internet Systems Consortium, Inc. ("ISC") .\" Copyright (c) 1996-2003 by Internet Software Consortium .\" @@ -59,6 +59,10 @@ dhcpd - Dynamic Host Configuration Protocol Server .B -6 ] [ +.B -4o6 +.I port +] +[ .B -s .I server ] @@ -208,8 +212,16 @@ Run as a DHCP server. This is the default and cannot be combined with .BI \-6 Run as a DHCPv6 server. This cannot be combined with \fB\-4\fR. .TP +.BI \-4o6 \ port +Participate in the DHCPv4 over DHCPv6 protocol specified by RFC 7341. +This associates a DHCPv4 and a DHCPv6 server to allow the v4 server to +receive v4 requests that were encapsualted in a v6 packet. Communication +between the two servers is done on a pair of UDP sockets bound +to ::1 \fIport\fR and \fIport + 1\fR. Both servers must +be launched using the same \fIport\fR argument. +.TP .BI \-p \ port -The udp port number on which +The UDP port number on which .B dhcpd should listen. If unspecified .B dhcpd diff --git a/server/dhcpd.c b/server/dhcpd.c index abc63df9..145561c0 100644 --- a/server/dhcpd.c +++ b/server/dhcpd.c @@ -164,7 +164,12 @@ usage(const char *sfmt, const char *sarg) { log_fatal("Usage: %s [-p <UDP port #>] [-f] [-d] [-q] [-t|-T]\n" #ifdef DHCPv6 +#ifdef DHCP4o6 + " [-4|-6] [-4o6 <port>]\n" + " [-cf config-file] [-lf lease-file]\n" +#else /* DHCP4o6 */ " [-4|-6] [-cf config-file] [-lf lease-file]\n" +#endif /* DHCP4o6 */ #else /* !DHCPv6 */ " [-cf config-file] [-lf lease-file]\n" #endif /* DHCPv6 */ @@ -228,6 +233,9 @@ main(int argc, char **argv) { int no_dhcpd_pid = 0; #ifdef DHCPv6 int local_family_set = 0; +#ifdef DHCP4o6 + u_int16_t dhcp4o6_port = 0; +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ #if defined (TRACING) char *traceinfile = (char *)0; @@ -369,6 +377,17 @@ main(int argc, char **argv) { } local_family = AF_INET6; local_family_set = 1; +#ifdef DHCP4o6 + } else if (!strcmp(argv[i], "-4o6")) { + if (++i == argc) + usage(use_noarg, argv[i-1]); + dhcp4o6_port = validate_port_pair(argv[i]); + + log_debug("DHCPv4 over DHCPv6 over ::1 port %d and %d", + ntohs(dhcp4o6_port), + ntohs(dhcp4o6_port) + 1); + dhcpv4_over_dhcpv6 = 1; +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ } else if (!strcmp (argv [i], "--version")) { const char vstring[] = "isc-dhcpd-"; @@ -415,6 +434,18 @@ main(int argc, char **argv) { } } +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + if (!local_family_set) + log_error("please specify the address family " + "with DHPv4 over DHCPv6 [-4|-6]."); + if ((local_family == AF_INET) && (interfaces != NULL)) + log_fatal("DHCPv4 server in DHPv4 over DHCPv6 " + "mode with command line specified " + "interfaces."); + } +#endif /* DHCPv6 && DHCP4o6 */ + if (!no_dhcpd_conf && (s = getenv ("PATH_DHCPD_CONF"))) { path_dhcpd_conf = s; } @@ -677,6 +708,15 @@ main(int argc, char **argv) { postconf_initialization (quiet); +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + if ((local_family == AF_INET) && (interfaces != NULL)) + log_fatal("DHCPv4 server in DHPv4 over DHCPv6 " + "mode with config file specified " + "interfaces."); + } +#endif /* DHCPv6 && DHCP4o6 */ + #if defined (PARANOIA) && !defined (EARLY_CHROOT) if (set_chroot) setup_chroot (set_chroot); #endif /* PARANOIA && !EARLY_CHROOT */ @@ -727,6 +767,20 @@ main(int argc, char **argv) { exit (0); /* Discover all the network interfaces and initialize them. */ +#if defined(DHCPv6) && defined(DHCP4o6) + if (dhcpv4_over_dhcpv6) { + int real_family = local_family; + local_family = AF_INET6; + /* The DHCPv4 side of DHCPv4-over-DHCPv6 service + uses a specific discovery which doesn't register + DHCPv6 sockets. */ + if (real_family == AF_INET) + discover_interfaces(DISCOVER_SERVER46); + else + discover_interfaces(DISCOVER_SERVER); + local_family = real_family; + } else +#endif /* DHCPv6 && DHCP4o6 */ discover_interfaces(DISCOVER_SERVER); #ifdef DHCPv6 @@ -772,7 +826,7 @@ main(int argc, char **argv) { * server-duid from the lease file * server-duid from the config file (the config file is read first * and the lease file overwrites the config file information) - * genrate a new one + * generate a new one from the interface hardware addresses. * In all cases we write it out to the lease file. * See dhcpv6.c for discussion of setting DUID. */ @@ -782,6 +836,10 @@ main(int argc, char **argv) { log_fatal("Unable to set server identifier."); } write_server_duid(); +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6) + dhcp4o6_setup(dhcp4o6_port); +#endif /* DHCP4o6 */ #endif /* DHCPv6 */ #ifndef DEBUG diff --git a/server/dhcpleasequery.c b/server/dhcpleasequery.c index 0766b849..91ca870c 100644 --- a/server/dhcpleasequery.c +++ b/server/dhcpleasequery.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2011-2013,2016 by Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2006-2007,2009 by Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and distribute this software for any @@ -172,6 +172,11 @@ dhcpleasequery(struct packet *packet, int ms_nulltp) { /* * We can't reply if there is no giaddr field. */ + /* + * Note: this makes DHCPv4-over-DHCPv6 always fail but it should not + * really be a problem because it is not a specified use case + * (or even one that makes sense). + */ if (!packet->raw->giaddr.s_addr) { log_info("%s: missing giaddr, ciaddr is %s, no reply sent", msgbuf, inet_ntoa(packet->raw->ciaddr)); diff --git a/server/dhcpv6.c b/server/dhcpv6.c index d85d0b07..18d8bb88 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -20,6 +20,15 @@ #ifdef DHCPv6 +#ifdef DHCP4o6 +static void forw_dhcpv4_query(struct packet *packet); +static void send_dhcpv4_response(struct data_string *raw); + +static void recv_dhcpv4_query(struct data_string *raw); +static void dhcp4o6_dhcpv4_query(struct data_string *reply_ret, + struct packet *packet); +#endif + /* * We use print_hex_1() to output DUID values. We could actually output * the DUID with more information... MAC address if using type 1 or 3, @@ -167,6 +176,104 @@ static isc_result_t get_first_ia_addr_val (struct packet* packet, int addr_type, static void set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor); +#ifdef DHCP4o6 +/* + * \brief Omapi I/O handler + * + * The inter-process communication receive handler. + * Get the message, put it into the raw data_string + * and call \ref send_dhcpv4_response() (DHCPv6 side) or + * \ref recv_dhcpv4_query() (DHCPv4 side) + * + * \param h the OMAPI object + * \return a result for I/O success or error (used by the I/O subsystem) + */ +isc_result_t dhcpv4o6_handler(omapi_object_t *h) { + char buf[65536]; + struct data_string raw; + int cc; + + if (h->type != dhcp4o6_type) + return DHCP_R_INVALIDARG; + + cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0); + + if (cc < DHCP_FIXED_NON_UDP + 32) + return ISC_R_UNEXPECTED; + memset(&raw, 0, sizeof(raw)); + if (!buffer_allocate(&raw.buffer, cc, MDL)) { + log_error("dhcpv4o6_handler: no memory buffer."); + return ISC_R_NOMEMORY; + } + raw.data = raw.buffer->data; + raw.len = cc; + memcpy(raw.buffer->data, buf, cc); + + if (local_family == AF_INET6) { + send_dhcpv4_response(&raw); + } else { + recv_dhcpv4_query(&raw); + } + + data_string_forget(&raw, MDL); + + return ISC_R_SUCCESS; +} + +/* + * \brief Send the DHCPv4-response back to the DHCPv6 side + * (DHCPv6 server function) + * + * Format: interface:16 + address:16 + DHCPv6 DHCPv4-response message + * + * \param raw the IPC message content + */ +static void send_dhcpv4_response(struct data_string *raw) { + struct interface_info *ip; + char name[16 + 1]; + struct sockaddr_in6 to_addr; + char pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + int send_ret; + + memset(name, 0, sizeof(name)); + memcpy(name, raw->data, 16); + for (ip = interfaces; ip != NULL; ip = ip->next) { + if (!strcmp(name, ip->name)) + break; + } + if (ip == NULL) { + log_error("send_dhcpv4_response: can't find interface %s.", + name); + return; + } + + memset(&to_addr, 0, sizeof(to_addr)); + to_addr.sin6_family = AF_INET6; + memcpy(&to_addr.sin6_addr, raw->data + 16, 16); + if ((raw->data[32] == DHCPV6_RELAY_FORW) || + (raw->data[32] == DHCPV6_RELAY_REPL)) { + to_addr.sin6_port = local_port; + } else { + to_addr.sin6_port = remote_port; + } + + log_info("send_dhcpv4_response(): sending %s on %s to %s port %d", + dhcpv6_type_names[raw->data[32]], + name, + inet_ntop(AF_INET6, raw->data + 16, pbuf, sizeof(pbuf)), + ntohs(to_addr.sin6_port)); + + send_ret = send_packet6(ip, raw->data + 32, raw->len - 32, &to_addr); + if (send_ret < 0) { + log_error("send_dhcpv4_response: send_packet6(): %m"); + } else if (send_ret != raw->len - 32) { + log_error("send_dhcpv4_response: send_packet6() " + "sent %d of %d bytes", + send_ret, raw->len - 32); + } +} +#endif /* DHCP4o6 */ + /* * Schedule lease timeouts for all of the iasubopts in the reply. * This is currently used to schedule timeouts for soft leases. @@ -754,6 +861,12 @@ static const int required_opts_STATUS_CODE[] = { D6O_STATUS_CODE, 0 }; +#ifdef DHCP4o6 +static const int required_opts_4o6[] = { + D6O_DHCPV4_MSG, + 0 +}; +#endif static const int unicast_reject_opts[] = { D6O_CLIENTID, @@ -1379,7 +1492,7 @@ try_client_v6_prefix(struct iasubopt **pref, * hash the address. After a number of failures we * conclude the pool is basically full. */ -static isc_result_t +static isc_result_t pick_v6_prefix(struct reply_state *reply) { struct ipv6_pool *p = NULL; struct ipv6_pond *pond; @@ -6231,6 +6344,7 @@ dhcpv6_information_request(struct data_string *reply, struct packet *packet) { /* XXX: this is very, very similar to do_packet6(), and should probably be combined in a clever way */ +/* DHCPv6 server side */ static void dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { struct option_cache *oc; @@ -6275,12 +6389,14 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, NULL, NULL, &global_scope, oc, MDL)) { + /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: error evaluating " "relayed message."); goto exit; } if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) { + /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: encapsulated packet too short."); goto exit; } @@ -6290,12 +6406,14 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { */ enc_packet = NULL; if (!packet_allocate(&enc_packet, MDL)) { + /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: " "no memory for encapsulated packet."); goto exit; } if (!option_state_allocate(&enc_packet->options, MDL)) { + /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: " "no memory for encapsulated packet's options."); goto exit; @@ -6328,6 +6446,23 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { cases where it fails */ goto exit; } + } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || + (msg_type == DHCPV6_DHCPV4_RESPONSE)) { +#ifdef DHCP4o6 + if (!dhcpv4_over_dhcpv6 || + (msg_type == DHCPV6_DHCPV4_RESPONSE)) { + log_error("dhcpv6_relay_forw: " + "unsupported %s message type.", + dhcpv6_type_names[msg_type]); + goto exit; + } + forw_dhcpv4_query(packet); + goto exit; +#else /* DHCP4o6 */ + log_error("dhcpv6_relay_forw: unsupported %s message type.", + dhcpv6_type_names[msg_type]); + goto exit; +#endif /* DHCP4o6 */ } else { int msglen = (int)(offsetof(struct dhcpv6_packet, options)); msg = (struct dhcpv6_packet *)enc_opt_data.data; @@ -6509,6 +6644,536 @@ exit: } } +#ifdef DHCP4o6 +/* \brief Internal processing of a relayed DHCPv4-query + * (DHCPv4 server side) + * + * Code copied from \ref dhcpv6_relay_forw() which itself is + * from \ref do_packet6(). + * + * \param reply_ret pointer to the response + * \param packet the query + */ +static void +dhcp4o6_relay_forw(struct data_string *reply_ret, struct packet *packet) { + struct option_cache *oc; + struct data_string enc_opt_data; + struct packet *enc_packet; + unsigned char msg_type; + const struct dhcpv6_relay_packet *relay; + const struct dhcpv4_over_dhcpv6_packet *msg; + struct data_string enc_reply; + char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + struct data_string a_opt, packet_ero; + struct option_state *opt_state; + static char reply_data[65536]; + struct dhcpv6_relay_packet *reply; + int reply_ofs; + + /* + * Initialize variables for early exit. + */ + opt_state = NULL; + memset(&a_opt, 0, sizeof(a_opt)); + memset(&packet_ero, 0, sizeof(packet_ero)); + memset(&enc_reply, 0, sizeof(enc_reply)); + memset(&enc_opt_data, 0, sizeof(enc_opt_data)); + enc_packet = NULL; + + /* + * Get our encapsulated relay message. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG); + if (oc == NULL) { + inet_ntop(AF_INET6, &packet->dhcpv6_link_address, + link_addr, sizeof(link_addr)); + inet_ntop(AF_INET6, &packet->dhcpv6_peer_address, + peer_addr, sizeof(peer_addr)); + log_info("Relay-forward from %s with link address=%s and " + "peer address=%s missing Relay Message option.", + piaddr(packet->client_addr), link_addr, peer_addr); + goto exit; + } + + if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, + NULL, NULL, &global_scope, oc, MDL)) { + log_error("dhcp4o6_relay_forw: error evaluating " + "relayed message."); + goto exit; + } + + if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) { + log_error("dhcp4o6_relay_forw: " + "encapsulated packet too short."); + goto exit; + } + + /* + * Build a packet structure from this encapsulated packet. + */ + if (!packet_allocate(&enc_packet, MDL)) { + log_error("dhcp4o6_relay_forw: " + "no memory for encapsulated packet."); + goto exit; + } + + if (!option_state_allocate(&enc_packet->options, MDL)) { + log_error("dhcp4o6_relay_forw: " + "no memory for encapsulated packet's options."); + goto exit; + } + + enc_packet->client_port = packet->client_port; + enc_packet->client_addr = packet->client_addr; + interface_reference(&enc_packet->interface, packet->interface, MDL); + enc_packet->dhcpv6_container_packet = packet; + + msg_type = enc_opt_data.data[0]; + if ((msg_type == DHCPV6_RELAY_FORW) || + (msg_type == DHCPV6_RELAY_REPL)) { + int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); + relay = (struct dhcpv6_relay_packet *)enc_opt_data.data; + enc_packet->dhcpv6_msg_type = relay->msg_type; + + /* relay-specific data */ + enc_packet->dhcpv6_hop_count = relay->hop_count; + memcpy(&enc_packet->dhcpv6_link_address, + relay->link_address, sizeof(relay->link_address)); + memcpy(&enc_packet->dhcpv6_peer_address, + relay->peer_address, sizeof(relay->peer_address)); + + if (!parse_option_buffer(enc_packet->options, + relay->options, + enc_opt_data.len - relaylen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + goto exit; + } + } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || + (msg_type == DHCPV6_DHCPV4_RESPONSE)) { + int msglen = + (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); + msg = (struct dhcpv4_over_dhcpv6_packet *)enc_opt_data.data; + enc_packet->dhcpv6_msg_type = msg->msg_type; + + /* message-specific data */ + memcpy(enc_packet->dhcp4o6_flags, + msg->flags, + sizeof(enc_packet->dhcp4o6_flags)); + + if (!parse_option_buffer(enc_packet->options, + msg->options, + enc_opt_data.len - msglen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + goto exit; + } + } else { + log_error("dhcp4o6_relay_forw: unexpected message of type %d.", + (int)msg_type); + goto exit; + } + + /* + * This is recursive. It is possible to exceed maximum packet size. + * XXX: This will cause the packet send to fail. + */ + build_dhcpv6_reply(&enc_reply, enc_packet); + + /* + * If we got no encapsulated data, then it is discarded, and + * our reply-forw is also discarded. + */ + if (enc_reply.data == NULL) { + goto exit; + } + + /* + * Now we can use the reply_data buffer. + * Packet header stuff all comes from the forward message. + */ + reply = (struct dhcpv6_relay_packet *)reply_data; + reply->msg_type = DHCPV6_RELAY_REPL; + reply->hop_count = packet->dhcpv6_hop_count; + memcpy(reply->link_address, &packet->dhcpv6_link_address, + sizeof(reply->link_address)); + memcpy(reply->peer_address, &packet->dhcpv6_peer_address, + sizeof(reply->peer_address)); + reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options)); + + /* + * Get the reply option state. + */ + if (!option_state_allocate(&opt_state, MDL)) { + log_error("dhcp4o6_relay_forw: no memory for option state."); + goto exit; + } + + /* + * Append the interface-id if present. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, + D6O_INTERFACE_ID); + if (oc != NULL) { + if (!evaluate_option_cache(&a_opt, packet, + NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL)) { + log_error("dhcp4o6_relay_forw: error evaluating " + "Interface ID."); + goto exit; + } + if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, + (unsigned char *)a_opt.data, + a_opt.len, + D6O_INTERFACE_ID, 0)) { + log_error("dhcp4o6_relay_forw: error saving " + "Interface ID."); + goto exit; + } + data_string_forget(&a_opt, MDL); + } + + /* + * Append our encapsulated stuff for caller. + */ + if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, + (unsigned char *)enc_reply.data, + enc_reply.len, + D6O_RELAY_MSG, 0)) { + log_error("dhcp4o6_relay_forw: error saving Relay MSG."); + goto exit; + } + + /* + * Get the ERO if any. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO); + if (oc != NULL) { + unsigned req; + int i; + + if (!evaluate_option_cache(&packet_ero, packet, + NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL) || + (packet_ero.len & 1)) { + log_error("dhcp4o6_relay_forw: error evaluating ERO."); + goto exit; + } + + /* Decode and apply the ERO. */ + for (i = 0; i < packet_ero.len; i += 2) { + req = getUShort(packet_ero.data + i); + /* Already in the reply? */ + oc = lookup_option(&dhcpv6_universe, opt_state, req); + if (oc != NULL) + continue; + /* Get it from the packet if present. */ + oc = lookup_option(&dhcpv6_universe, + packet->options, + req); + if (oc == NULL) + continue; + if (!evaluate_option_cache(&a_opt, packet, + NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL)) { + log_error("dhcp4o6_relay_forw: error " + "evaluating option %u.", req); + goto exit; + } + if (!save_option_buffer(&dhcpv6_universe, + opt_state, + NULL, + (unsigned char *)a_opt.data, + a_opt.len, + req, + 0)) { + log_error("dhcp4o6_relay_forw: error saving " + "option %u.", req); + goto exit; + } + data_string_forget(&a_opt, MDL); + } + } + + reply_ofs += store_options6(reply_data + reply_ofs, + sizeof(reply_data) - reply_ofs, + opt_state, packet, + required_opts_agent, &packet_ero); + + /* + * Return our reply to the caller. + */ + reply_ret->len = reply_ofs; + reply_ret->buffer = NULL; + if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) { + log_fatal("No memory to store reply."); + } + reply_ret->data = reply_ret->buffer->data; + memcpy(reply_ret->buffer->data, reply_data, reply_ofs); + +exit: + if (opt_state != NULL) + option_state_dereference(&opt_state, MDL); + if (a_opt.data != NULL) { + data_string_forget(&a_opt, MDL); + } + if (packet_ero.data != NULL) { + data_string_forget(&packet_ero, MDL); + } + if (enc_reply.data != NULL) { + data_string_forget(&enc_reply, MDL); + } + if (enc_opt_data.data != NULL) { + data_string_forget(&enc_opt_data, MDL); + } + if (enc_packet != NULL) { + packet_dereference(&enc_packet, MDL); + } +} + +/* + * \brief Internal processing of a DHCPv4-query + * (DHCPv4 server function) + * + * Code copied from \ref do_packet(). + * + * \param reply_ret pointer to the response + * \param packet the query + */ +static void +dhcp4o6_dhcpv4_query(struct data_string *reply_ret, struct packet *packet) { + struct option_cache *oc; + struct data_string enc_opt_data; + struct packet *enc_packet; + struct data_string enc_response; + struct option_state *opt_state; + static char response_data[65536]; + struct dhcpv4_over_dhcpv6_packet *response; + int response_ofs; + + /* + * Initialize variables for early exit. + */ + opt_state = NULL; + memset(&enc_response, 0, sizeof(enc_response)); + memset(&enc_opt_data, 0, sizeof(enc_opt_data)); + enc_packet = NULL; + + /* + * Get our encapsulated relay message. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_DHCPV4_MSG); + if (oc == NULL) { + log_info("DHCPv4-query from %s missing DHCPv4 Message option.", + piaddr(packet->client_addr)); + goto exit; + } + + if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, + NULL, NULL, &global_scope, oc, MDL)) { + log_error("dhcp4o6_dhcpv4_query: error evaluating " + "DHCPv4 message."); + goto exit; + } + + if (enc_opt_data.len < DHCP_FIXED_NON_UDP) { + log_error("dhcp4o6_dhcpv4_query: DHCPv4 packet too short."); + goto exit; + } + + /* + * Build a packet structure from this encapsulated packet. + */ + if (!packet_allocate(&enc_packet, MDL)) { + log_error("dhcp4o6_dhcpv4_query: " + "no memory for encapsulated packet."); + goto exit; + } + + enc_packet->raw = (struct dhcp_packet *)enc_opt_data.data; + enc_packet->packet_length = enc_opt_data.len; + enc_packet->dhcp4o6_response = &enc_response; + enc_packet->client_port = packet->client_port; + enc_packet->client_addr = packet->client_addr; + interface_reference(&enc_packet->interface, packet->interface, MDL); + enc_packet->dhcpv6_container_packet = packet; + if (packet->dhcp4o6_flags[0] & DHCP4O6_QUERY_UNICAST) + enc_packet->unicast = 1; + + if (enc_packet->raw->hlen > sizeof(enc_packet->raw->chaddr)) { + log_info("dhcp4o6_dhcpv4_query: " + "discarding packet with bogus hlen."); + goto exit; + } + + /* Allocate packet->options now so it is non-null for all packets */ + if (!option_state_allocate (&enc_packet->options, MDL)) { + log_error("dhcp4o6_dhcpv4_query: no memory for options."); + goto exit; + } + + /* If there's an option buffer, try to parse it. */ + if (enc_packet->packet_length >= DHCP_FIXED_NON_UDP + 4) { + struct option_cache *op; + if (!parse_options(enc_packet)) { + if (enc_packet->options) + option_state_dereference + (&enc_packet->options, MDL); + packet_dereference (&enc_packet, MDL); + goto exit; + } + + if (enc_packet->options_valid && + (op = lookup_option(&dhcp_universe, + enc_packet->options, + DHO_DHCP_MESSAGE_TYPE))) { + struct data_string dp; + memset(&dp, 0, sizeof dp); + evaluate_option_cache(&dp, enc_packet, NULL, NULL, + enc_packet->options, NULL, + NULL, op, MDL); + if (dp.len > 0) + enc_packet->packet_type = dp.data[0]; + else + enc_packet->packet_type = 0; + data_string_forget(&dp, MDL); + } + } + + if (validate_packet(enc_packet) != 0) { + if (enc_packet->packet_type) + dhcp(enc_packet); + else + bootp(enc_packet); + } + + /* If the caller kept the packet, they'll have upped the refcnt. */ + packet_dereference(&enc_packet, MDL); + + /* + * If we got no response data, then it is discarded, and + * our DHCPv4-response is also discarded. + */ + if (enc_response.data == NULL) { + goto exit; + } + + /* + * Now we can use the response_data buffer. + */ + response = (struct dhcpv4_over_dhcpv6_packet *)response_data; + response->msg_type = DHCPV6_DHCPV4_RESPONSE; + response->flags[0] = response->flags[1] = response->flags[2] = 0; + response_ofs = + (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); + + /* + * Get the response option state. + */ + if (!option_state_allocate(&opt_state, MDL)) { + log_error("dhcp4o6_dhcpv4_query: no memory for option state."); + goto exit; + } + + /* + * Append our encapsulated stuff for caller. + */ + if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, + (unsigned char *)enc_response.data, + enc_response.len, + D6O_DHCPV4_MSG, 0)) { + log_error("dhcp4o6_dhcpv4_query: error saving DHCPv4 MSG."); + goto exit; + } + + response_ofs += store_options6(response_data + response_ofs, + sizeof(response_data) - response_ofs, + opt_state, packet, + required_opts_4o6, NULL); + + /* + * Return our response to the caller. + */ + reply_ret->len = response_ofs; + reply_ret->buffer = NULL; + if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) { + log_fatal("dhcp4o6_dhcpv4_query: no memory to store reply."); + } + reply_ret->data = reply_ret->buffer->data; + memcpy(reply_ret->buffer->data, response_data, response_ofs); + +exit: + if (opt_state != NULL) + option_state_dereference(&opt_state, MDL); + if (enc_response.data != NULL) { + data_string_forget(&enc_response, MDL); + } + if (enc_opt_data.data != NULL) { + data_string_forget(&enc_opt_data, MDL); + } + if (enc_packet != NULL) { + packet_dereference(&enc_packet, MDL); + } +} + +/* + * \brief Forward a DHCPv4-query message to the DHCPv4 side + * (DHCPv6 server function) + * + * Format: interface:16 + address:16 + DHCPv6 DHCPv4-query message + * + * \brief packet the DHCPv6 DHCPv4-query message + */ +static void forw_dhcpv4_query(struct packet *packet) { + struct data_string ds; + unsigned len; + int cc; + + /* Get the initial message. */ + while (packet->dhcpv6_container_packet != NULL) + packet = packet->dhcpv6_container_packet; + + /* Check the initial message. */ + if ((packet->raw == NULL) || + (packet->client_addr.len != 16) || + (packet->interface == NULL)) { + log_error("forw_dhcpv4_query: can't find initial message."); + return; + } + + /* Get a buffer. */ + len = packet->packet_length + 32; + memset(&ds, 0, sizeof(ds)); + if (!buffer_allocate(&ds.buffer, len, MDL)) { + log_error("forw_dhcpv4_query: " + "no memory for encapsulating packet."); + return; + } + ds.data = ds.buffer->data; + ds.len = len; + + /* Fill the buffer. */ + strncpy((char *)ds.buffer->data, packet->interface->name, 16); + memcpy(ds.buffer->data + 16, + packet->client_addr.iabuf, 16); + memcpy(ds.buffer->data + 32, + (unsigned char *)packet->raw, + packet->packet_length); + + /* Forward to the DHCPv4 server. */ + cc = send(dhcp4o6_fd, ds.data, ds.len, 0); + if (cc < 0) + log_error("forw_dhcpv4_query: send(): %m"); + data_string_forget(&ds, MDL); +} +#endif + static void dhcpv6_discard(struct packet *packet) { /* INSIST(packet->msg_type > 0); */ @@ -6574,6 +7239,11 @@ build_dhcpv6_reply(struct data_string *reply, struct packet *packet) { dhcpv6_information_request(reply, packet); break; case DHCPV6_RELAY_FORW: +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6 && (local_family == AF_INET)) + dhcp4o6_relay_forw(reply, packet); + else +#endif /* DHCP4o6 */ dhcpv6_relay_forw(reply, packet); break; case DHCPV6_RELAY_REPL: @@ -6586,6 +7256,21 @@ build_dhcpv6_reply(struct data_string *reply, struct packet *packet) { case DHCPV6_LEASEQUERY_REPLY: dhcpv6_discard(packet); break; + case DHCPV6_DHCPV4_QUERY: +#ifdef DHCP4o6 + if (dhcpv4_over_dhcpv6) { + if (local_family == AF_INET6) { + forw_dhcpv4_query(packet); + } else { + dhcp4o6_dhcpv4_query(reply, packet); + } + } else +#endif /* DHCP4o6 */ + dhcpv6_discard(packet); + break; + case DHCPV6_DHCPV4_RESPONSE: + dhcpv6_discard(packet); + break; default: /* XXX: would be nice if we had "notice" level, as syslog, for this */ @@ -6626,7 +7311,8 @@ log_packet_in(const struct packet *packet) { data_string_sprintfa(&s, ", peer address %s", inet_ntop(AF_INET6, addr, tmp_addr, sizeof(tmp_addr))); - } else { + } else if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) && + (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) { tid = 0; memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3); data_string_sprintfa(&s, ", transaction ID 0x%06X", tid); @@ -6712,6 +7398,203 @@ dhcpv6(struct packet *packet) { } } +#ifdef DHCP4o6 +/* + * \brief Receive a DHCPv4-query message from the DHCPv6 side + * (DHCPv4 server function) + * + * Receive a message with a DHCPv4-query inside from the DHCPv6 server. + * (code copied from \ref do_packet6() \ref and dhcpv6()) + * + * Format: interface:16 + address:16 + DHCPv6 DHCPv4-query message + * + * \param raw the DHCPv6 DHCPv4-query message raw content + */ +static void recv_dhcpv4_query(struct data_string *raw) { + struct interface_info *ip; + char name[16 + 1]; + struct iaddr iaddr; + struct packet *packet; + unsigned char msg_type; + const struct dhcpv6_relay_packet *relay; + const struct dhcpv4_over_dhcpv6_packet *msg; + struct data_string reply; + struct data_string ds; + unsigned len; + int cc; + + memset(name, 0, sizeof(name)); + memcpy(name, raw->data, 16); + for (ip = interfaces; ip != NULL; ip = ip->next) { + if (!strcmp(name, ip->name)) + break; + } + if (ip == NULL) { + log_error("recv_dhcpv4_query: can't find interface %s.", + name); + return; + } + + iaddr.len = 16; + memcpy(iaddr.iabuf, raw->data + 16, 16); + + /* + * From do_packet6(). + */ + + if (!packet6_len_okay((char *)raw->data + 32, raw->len - 32)) { + log_error("recv_dhcpv4_query: " + "short packet from %s, len %d, dropped", + piaddr(iaddr), raw->len - 32); + return; + } + + /* + * Build a packet structure. + */ + packet = NULL; + if (!packet_allocate(&packet, MDL)) { + log_error("recv_dhcpv4_query: no memory for packet."); + return; + } + + if (!option_state_allocate(&packet->options, MDL)) { + log_error("recv_dhcpv4_query: no memory for options."); + packet_dereference(&packet, MDL); + return; + } + + packet->raw = (struct dhcp_packet *)(raw->data + 32); + packet->packet_length = raw->len - 32; + packet->client_port = remote_port; + packet->client_addr = iaddr; + interface_reference(&packet->interface, ip, MDL); + + msg_type = raw->data[32]; + if ((msg_type == DHCPV6_RELAY_FORW) || + (msg_type == DHCPV6_RELAY_REPL)) { + int relaylen = + (int)(offsetof(struct dhcpv6_relay_packet, options)); + relay = (const struct dhcpv6_relay_packet *)(raw->data + 32); + packet->dhcpv6_msg_type = relay->msg_type; + + /* relay-specific data */ + packet->dhcpv6_hop_count = relay->hop_count; + memcpy(&packet->dhcpv6_link_address, + relay->link_address, sizeof(relay->link_address)); + memcpy(&packet->dhcpv6_peer_address, + relay->peer_address, sizeof(relay->peer_address)); + + if (!parse_option_buffer(packet->options, + relay->options, + raw->len - 32 - relaylen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + packet_dereference(&packet, MDL); + return; + } + } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || + (msg_type == DHCPV6_DHCPV4_RESPONSE)) { + int msglen = + (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); + msg = (struct dhcpv4_over_dhcpv6_packet *)(raw->data + 32); + packet->dhcpv6_msg_type = msg->msg_type; + + /* message-specific data */ + memcpy(packet->dhcp4o6_flags, msg->flags, + sizeof(packet->dhcp4o6_flags)); + + if (!parse_option_buffer(packet->options, + msg->options, + raw->len - 32 - msglen, + &dhcpv6_universe)) { + /* no logging here, as parse_option_buffer() logs all + cases where it fails */ + packet_dereference(&packet, MDL); + return; + } + } else { + log_error("recv_dhcpv4_query: unexpected message of type %d.", + (int)msg_type); + packet_dereference(&packet, MDL); + return; + } + + /* + * From dhcpv6(). + */ + + /* + * Log a message that we received this packet. + */ + /* log_packet_in(packet); */ + memset(&ds, 0, sizeof(ds)); + if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) { + data_string_sprintfa(&ds, "%s message from %s", + dhcpv6_type_names[packet->dhcpv6_msg_type], + piaddr(packet->client_addr)); + } else { + data_string_sprintfa(&ds, + "Unknown message type %d from %s", + packet->dhcpv6_msg_type, + piaddr(packet->client_addr)); + } + if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || + (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) { + char tmp_addr[INET6_ADDRSTRLEN]; + const void *addr; + + addr = &packet->dhcpv6_link_address; + data_string_sprintfa(&ds, ", link address %s", + inet_ntop(AF_INET6, addr, + tmp_addr, sizeof(tmp_addr))); + addr = &packet->dhcpv6_peer_address; + data_string_sprintfa(&ds, ", peer address %s", + inet_ntop(AF_INET6, addr, + tmp_addr, sizeof(tmp_addr))); + } else if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) && + (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) { + u_int32_t tid = 0; + + memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3); + data_string_sprintfa(&ds, ", transaction ID 0x%06X", tid); + } + log_info("%s", ds.data); + data_string_forget(&ds, MDL); + + /* + * Build our reply packet. + */ + build_dhcpv6_reply(&reply, packet); + + packet_dereference(&packet, MDL); + + if (reply.data == NULL) + return; + + /* + * Forward the response. + */ + len = reply.len + 32; + memset(&ds, 0, sizeof(ds)); + if (!buffer_allocate(&ds.buffer, len, MDL)) { + log_error("recv_dhcpv4_query: no memory."); + return; + } + ds.data = ds.buffer->data; + ds.len = len; + + memcpy(ds.buffer->data, name, 16); + memcpy(ds.buffer->data + 16, iaddr.iabuf, 16); + memcpy(ds.buffer->data + 32, reply.data, reply.len); + cc = send(dhcp4o6_fd, ds.data, ds.len, 0); + if (cc < 0) + log_error("recv_dhcpv4_query: send(): %m"); + data_string_forget(&ds, MDL); +} +#endif /* DHCP4o6 */ + static void seek_shared_host(struct host_decl **hp, struct shared_network *shared) { struct host_decl *nofixed = NULL; @@ -6803,7 +7686,7 @@ unicast_reject(struct data_string *reply_ret, struct reply_state reply; memset(&reply, 0x0, sizeof(struct reply_state)); - /* Locate the client. */ + /* Locate the client. */ if (shared_network_from_packet6(&reply.shared, packet) != ISC_R_SUCCESS) { log_error("unicast_reject: could not locate client."); diff --git a/server/mdb.c b/server/mdb.c index ed89b5b3..148659fb 100644 --- a/server/mdb.c +++ b/server/mdb.c @@ -914,6 +914,10 @@ int find_subnet (struct subnet **sp, struct subnet *rv; for (rv = subnets; rv; rv = rv -> next_subnet) { +#if defined(DHCP4o6) + if (addr.len != rv->netmask.len) + continue; +#endif if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) { if (subnet_reference (sp, rv, file, line) != ISC_R_SUCCESS) @@ -931,6 +935,10 @@ int find_grouped_subnet (struct subnet **sp, struct subnet *rv; for (rv = share -> subnets; rv; rv = rv -> next_sibling) { +#if defined(DHCP4o6) + if (addr.len != rv->netmask.len) + continue; +#endif if (addr_eq (subnet_number (addr, rv -> netmask), rv -> net)) { if (subnet_reference (sp, rv, file, line) != ISC_R_SUCCESS) @@ -946,6 +954,10 @@ int subnet_inner_than(const struct subnet *subnet, const struct subnet *scan, int warnp) { +#if defined(DHCP4o6) + if (subnet->net.len != scan->net.len) + return 0; +#endif if (addr_eq(subnet_number(subnet->net, scan->netmask), scan->net) || addr_eq(subnet_number(scan->net, subnet->netmask), subnet->net)) { char n1buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255")]; diff --git a/server/stables.c b/server/stables.c index d375bad6..f34a6721 100644 --- a/server/stables.c +++ b/server/stables.c @@ -234,7 +234,7 @@ static struct option server_options[] = { { "limit-addrs-per-ia", "L", &server_universe, 56, 1 }, { "limit-prefs-per-ia", "L", &server_universe, 57, 1 }, /* Assert a configuration parsing error if delayed-ack isn't compiled in. */ -#if defined(DELAYED_ACK) +#if defined(DELAYED_ACK) && !defined(DHCP4o6) { "delayed-ack", "S", &server_universe, 58, 1 }, { "max-ack-delay", "L", &server_universe, 59, 1 }, #endif |