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 /client | |
parent | 08ad1e3c9089b51888c73837cf41de49112de29c (diff) | |
download | isc-dhcp-785c1a519e88bfebb70bd3384589de36bee02dd2.tar.gz |
Merged rt35711c (DHCPv4-over-DHCPv6 support)
Diffstat (limited to 'client')
-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 |
4 files changed, 808 insertions, 10 deletions
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 */ |