]> git.ipfire.org Git - thirdparty/dhcp.git/blobdiff - server/bootp.c
Update RELNOTES
[thirdparty/dhcp.git] / server / bootp.c
index 2703266d61a2d7a622a7c6b48e41f7d056db6ca7..d81f4f3680d07a3887684ccacfdd129bf1837eb3 100644 (file)
    BOOTP Protocol support. */
 
 /*
- * Copyright (c) 1995, 1996 The Internet Software Consortium.
- * All rights reserved.
+ * Copyright (c) 2004-2017 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of The Internet Software Consortium nor the names
- *    of its contributors may be used to endorse or promote products derived
- *    from this software without specific prior written permission.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  *
- * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
- * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
+ *   Internet Systems Consortium, Inc.
+ *   PO Box 360
+ *   Newmarket, NH 03857 USA
+ *   <info@isc.org>
+ *   https://www.isc.org/
  *
- * This software has been written for the Internet Software Consortium
- * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
- * Enterprises.  To learn more about the Internet Software Consortium,
- * see ``http://www.vix.com/isc''.  To learn more about Vixie
- * Enterprises, see ``http://www.vix.com''.
  */
 
-#ifndef lint
-static char copyright[] =
-"$Id: bootp.c,v 1.23 1997/02/19 10:50:40 mellon Exp $ Copyright (c) 1995, 1996 The Internet Software Consortium.  All rights reserved.\n";
-#endif /* not lint */
-
 #include "dhcpd.h"
+#include <errno.h>
+
+#if defined (TRACING)
+# define send_packet trace_packet_send
+#endif
 
 void bootp (packet)
        struct packet *packet;
 {
        int result;
-       struct host_decl *hp;
+       struct host_decl *hp = (struct host_decl *)0;
        struct host_decl *host = (struct host_decl *)0;
        struct packet outgoing;
        struct dhcp_packet raw;
        struct sockaddr_in to;
        struct in_addr from;
        struct hardware hto;
-       struct tree_cache *options [256];
-       struct subnet *subnet;
-       struct lease *lease;
-       struct iaddr ip_address;
-       int i;
+       struct option_state *options = (struct option_state *)0;
+       struct lease *lease = (struct lease *)0;
+       unsigned i;
+       struct data_string d1;
+       struct option_cache *oc;
+       char msgbuf [1024];
+       int ignorep;
+       int peer_has_leases = 0;
+
+       if (packet -> raw -> op != BOOTREQUEST)
+               return;
 
-       note ("BOOTREQUEST from %s via %s",
-             print_hw_addr (packet -> raw -> htype,
-                            packet -> raw -> hlen,
-                            packet -> raw -> chaddr),
-             packet -> raw -> giaddr.s_addr
-             ? inet_ntoa (packet -> raw -> giaddr)
-             : packet -> interface -> name);
+       /* %Audit% This is log output. %2004.06.17,Safe%
+        * If we truncate we hope the user can get a hint from the log.
+        */
+       snprintf (msgbuf, sizeof msgbuf, "BOOTREQUEST from %s via %s",
+                print_hw_addr (packet -> raw -> htype,
+                               packet -> raw -> hlen,
+                               packet -> raw -> chaddr),
+                packet -> raw -> giaddr.s_addr
+                ? inet_ntoa (packet -> raw -> giaddr)
+                : packet -> interface -> name);
+
+       if (!locate_network (packet)) {
+               log_info ("%s: network unknown", msgbuf);
+               return;
+       }
 
+       find_lease (&lease, packet, packet -> shared_network,
+                   0, 0, (struct lease *)0, MDL);
 
+       if (lease && lease->host)
+               host_reference(&hp, lease->host, MDL);
 
-       if (!locate_network (packet))
-               return;
+       if (!lease || ((lease->flags & STATIC_LEASE) == 0)) {
+               struct host_decl *h;
 
-       hp = find_hosts_by_haddr (packet -> raw -> htype,
-                                 packet -> raw -> chaddr,
-                                 packet -> raw -> hlen);
+               /* We didn't find an applicable fixed-address host
+                  declaration.  Just in case we may be able to dynamically
+                  assign an address, see if there's a host declaration
+                  that doesn't have an ip address associated with it. */
 
-       lease = find_lease (packet, packet -> shared_network);
+               if (!hp)
+                       find_hosts_by_haddr(&hp, packet->raw->htype,
+                                           packet->raw->chaddr,
+                                           packet->raw->hlen, MDL);
 
-       /* Find an IP address in the host_decl that matches the
-          specified network. */
-       if (hp)
-               subnet = find_host_for_network (&hp, &ip_address,
-                                               packet -> shared_network);
-       else
-               subnet = (struct subnet *)0;
-
-       if (!subnet) {
-               /* We didn't find an applicable host declaration.
-                  Just in case we may be able to dynamically assign
-                  an address, see if there's a host declaration
-                  that doesn't have an ip address associated with it. */
-               if (hp) {
-                       for (; hp; hp = hp -> n_ipaddr) {
-                               if (!hp -> fixed_addr) {
-                                       host = hp;
-                                       break;
-                               }
+               for (h = hp; h; h = h -> n_ipaddr) {
+                       if (!h -> fixed_addr) {
+                               host_reference(&host, h, MDL);
+                               break;
                        }
                }
 
-               /* If we've been told not to boot unknown clients,
-                  and we didn't find any host record for this client,
-                  ignore it. */
-               if (!host && !(packet -> shared_network ->
-                              group -> boot_unknown_clients)) {
-                       note ("Ignoring unknown BOOTP client %s via %s",
-                             print_hw_addr (packet -> raw -> htype,
-                                            packet -> raw -> hlen,
-                                            packet -> raw -> chaddr),
-                             packet -> raw -> giaddr.s_addr
-                             ? inet_ntoa (packet -> raw -> giaddr)
-                             : packet -> interface -> name);
-                       return;
-               }
+               if (hp)
+                       host_dereference(&hp, MDL);
 
-               /* If the packet is from a host we don't know and there
-                  are no dynamic bootp addresses on the network it came
-                  in on, drop it on the floor. */
-               if (!(packet -> shared_network -> group -> dynamic_bootp)) {
-                     lose:
-                       note ("No applicable record for BOOTP host %s via %s",
-                             print_hw_addr (packet -> raw -> htype,
-                                            packet -> raw -> hlen,
-                                            packet -> raw -> chaddr),
-                             packet -> raw -> giaddr.s_addr
-                             ? inet_ntoa (packet -> raw -> giaddr)
-                             : packet -> interface -> name);
-                       return;
+               if (host) {
+                       host_reference(&hp, host, MDL);
+                       host_dereference(&host, MDL);
                }
 
-               /* If a lease has already been assigned to this client
-                  and it's still okay to use dynamic bootp on
-                  that lease, reassign it. */
-               if (lease) {
-                       /* If this lease can be used for dynamic bootp,
-                          do so. */
-                       if ((lease -> flags & DYNAMIC_BOOTP_OK)) {
-
-                               /* If it's not a DYNAMIC_BOOTP lease,
-                                  release it before reassigning it
-                                  so that we don't get a lease
-                                  conflict. */
-                               if (!(lease -> flags & BOOTP_LEASE))
-                                       release_lease (lease);
-
-                               lease -> host = host;
-                               ack_lease (packet, lease, 0, 0);
-                               return;
-                       }
+               /* Allocate a lease if we have not yet found one. */
+               if (!lease)
+                       allocate_lease (&lease, packet,
+                                       packet -> shared_network -> pools,
+                                       &peer_has_leases);
 
-                        /* If dynamic BOOTP is no longer allowed for
-                          this lease, set it free. */
-                       release_lease (lease);
+               if (lease == NULL) {
+                       log_info("%s: BOOTP from dynamic client and no "
+                                "dynamic leases", msgbuf);
+                       goto out;
                }
 
-               /* If there are dynamic bootp addresses that might be
-                  available, try to snag one. */
-               for (lease = packet -> shared_network -> last_lease;
-                    lease && lease -> ends <= cur_time;
-                    lease = lease -> prev) {
-                       if ((lease -> flags & DYNAMIC_BOOTP_OK)) {
-                               lease -> host = host;
-                               ack_lease (packet, lease, 0, 0);
-                               return;
+#if defined(FAILOVER_PROTOCOL)
+               if ((lease->pool != NULL) &&
+                   (lease->pool->failover_peer != NULL)) {
+                       dhcp_failover_state_t *peer;
+
+                       peer = lease->pool->failover_peer;
+
+                       /* If we are in a failover state that bars us from
+                        * answering, do not do so.
+                        * If we are in a cooperative state, load balance
+                        * (all) responses.
+                        */
+                       if ((peer->service_state == not_responding) ||
+                           (peer->service_state == service_startup)) {
+                               log_info("%s: not responding%s",
+                                        msgbuf, peer->nrr);
+                               goto out;
+                       } else if((peer->service_state == cooperating) &&
+                                 !load_balance_mine(packet, peer)) {
+                               log_info("%s: load balance to peer %s",
+                                        msgbuf, peer->name);
+                               goto out;
                        }
                }
-               goto lose;
+#endif
+
+               ack_lease (packet, lease, 0, 0, msgbuf, 0, hp);
+               goto out;
+       }
+
+       /* Run the executable statements to compute the client and server
+          options. */
+       option_state_allocate (&options, MDL);
+
+       /* Execute the subnet statements. */
+       execute_statements_in_scope (NULL, packet, lease, NULL,
+                                    packet->options, options,
+                                    &lease->scope, lease->subnet->group,
+                                    NULL, NULL);
+
+       /* Execute statements from class scopes. */
+       for (i = packet -> class_count; i > 0; i--) {
+               execute_statements_in_scope(NULL, packet, lease, NULL,
+                                           packet->options, options,
+                                           &lease->scope,
+                                           packet->classes[i - 1]->group,
+                                           lease->subnet->group, NULL);
+       }
+
+       /* Execute the host statements. */
+       if (hp != NULL) {
+               execute_statements_in_scope(NULL, packet, lease, NULL,
+                                           packet->options, options,
+                                           &lease->scope, hp->group,
+                                           lease->subnet->group, NULL);
+       }
+
+       /* Drop the request if it's not allowed for this client. */
+       if ((oc = lookup_option (&server_universe, options, SV_ALLOW_BOOTP)) &&
+           !evaluate_boolean_option_cache(&ignorep, packet, lease,
+                                          NULL,
+                                          packet->options, options,
+                                          &lease->scope, oc, MDL)) {
+               if (!ignorep)
+                       log_info ("%s: bootp disallowed", msgbuf);
+               goto out;
+       }
+
+       if ((oc = lookup_option(&server_universe,
+                                options, SV_ALLOW_BOOTING)) &&
+           !evaluate_boolean_option_cache(&ignorep, packet, lease,
+                                          NULL,
+                                          packet->options, options,
+                                          &lease->scope, oc, MDL)) {
+               if (!ignorep)
+                       log_info ("%s: booting disallowed", msgbuf);
+               goto out;
        }
 
        /* Set up the outgoing packet... */
@@ -179,80 +202,206 @@ void bootp (packet)
        memset (&raw, 0, sizeof raw);
        outgoing.raw = &raw;
 
-       /* Come up with a list of options that we want to send to this
-          client.   Start with the per-subnet options, and then override
-          those with client-specific options. */
-
-       memcpy (options, subnet -> group -> options, sizeof options);
+       /* If we didn't get a known vendor magic number on the way in,
+          just copy the input options to the output. */
+       i = SV_ALWAYS_REPLY_RFC1048;
+       if (!packet->options_valid &&
+           !(evaluate_boolean_option_cache(&ignorep, packet, lease, NULL,
+                                           packet->options, options,
+                                           &lease->scope,
+                                           lookup_option (&server_universe,
+                                                          options, i), MDL))) {
+               if (packet->packet_length > DHCP_FIXED_NON_UDP) {
+                       memcpy(outgoing.raw->options, packet->raw->options,
+                       packet->packet_length - DHCP_FIXED_NON_UDP);
+               }
 
-       for (i = 0; i < 256; i++) {
-               if (hp -> group -> options [i])
-                       options [i] = hp -> group -> options [i];
-       }
+               outgoing.packet_length =
+                       (packet->packet_length < BOOTP_MIN_LEN)
+                                              ? BOOTP_MIN_LEN
+                                              : packet->packet_length;
+       } else {
 
-       /* Pack the options into the buffer.   Unlike DHCP, we can't
-          pack options into the filename and server name buffers. */
+               /* Use the subnet mask from the subnet declaration if no other
+                  mask has been provided. */
+               oc = (struct option_cache *)0;
+               i = DHO_SUBNET_MASK;
+               if (!lookup_option (&dhcp_universe, options, i)) {
+                       if (option_cache_allocate (&oc, MDL)) {
+                               if (make_const_data
+                                   (&oc -> expression,
+                                    lease -> subnet -> netmask.iabuf,
+                                    lease -> subnet -> netmask.len,
+                                    0, 0, MDL)) {
+                                       option_code_hash_lookup(&oc->option,
+                                                       dhcp_universe.code_hash,
+                                                               &i, 0, MDL);
+                                       save_option (&dhcp_universe,
+                                                    options, oc);
+                               }
+                               option_cache_dereference (&oc, MDL);
+                       }
+               }
 
-       outgoing.packet_length =
-               cons_options (packet, outgoing.raw, options, 0, 0);
-       if (outgoing.packet_length < BOOTP_MIN_LEN)
-               outgoing.packet_length = BOOTP_MIN_LEN;
+               /* If use-host-decl-names is enabled and there is a hostname
+                * defined in the host delcartion, send it back in hostname
+                * option */
+               use_host_decl_name(packet, lease, options);
+
+               /* Pack the options into the buffer.  Unlike DHCP, we
+                  can't pack options into the filename and server
+                  name buffers. */
+
+               outgoing.packet_length =
+                       cons_options (packet, outgoing.raw, lease,
+                                     (struct client_state *)0, 0,
+                                     packet -> options, options,
+                                     &lease -> scope,
+                                     0, 0, 1, (struct data_string *)0,
+                                     (const char *)0);
+               if (outgoing.packet_length < BOOTP_MIN_LEN)
+                       outgoing.packet_length = BOOTP_MIN_LEN;
+       }
 
        /* Take the fields that we care about... */
        raw.op = BOOTREPLY;
        raw.htype = packet -> raw -> htype;
        raw.hlen = packet -> raw -> hlen;
-       memcpy (raw.chaddr, packet -> raw -> chaddr, raw.hlen);
-       memset (&raw.chaddr [raw.hlen], 0,
-               (sizeof raw.chaddr) - raw.hlen);
+       memcpy (raw.chaddr, packet -> raw -> chaddr, sizeof raw.chaddr);
        raw.hops = packet -> raw -> hops;
        raw.xid = packet -> raw -> xid;
        raw.secs = packet -> raw -> secs;
-       raw.flags = 0;
+       raw.flags = packet -> raw -> flags;
        raw.ciaddr = packet -> raw -> ciaddr;
-       memcpy (&raw.yiaddr, ip_address.iabuf, sizeof raw.yiaddr);
+
+       /* yiaddr is an ipv4 address, it must be 4 octets. */
+       memcpy (&raw.yiaddr, lease->ip_addr.iabuf, 4);
+
+       /* If we're always supposed to broadcast to this client, set
+          the broadcast bit in the bootp flags field. */
+       if ((oc = lookup_option (&server_universe,
+                               options, SV_ALWAYS_BROADCAST)) &&
+           evaluate_boolean_option_cache (&ignorep, packet, lease,
+                                          (struct client_state *)0,
+                                          packet -> options, options,
+                                          &lease -> scope, oc, MDL))
+               raw.flags |= htons (BOOTP_BROADCAST);
 
        /* Figure out the address of the next server. */
-       if (hp  && hp -> group -> next_server.len)
-               memcpy (&raw.siaddr, hp -> group -> next_server.iabuf, 4);
-       else if (subnet -> group -> next_server.len)
-               memcpy (&raw.siaddr, subnet -> group -> next_server.iabuf, 4);
-       else if (subnet -> interface_address.len)
-               memcpy (&raw.siaddr, subnet -> interface_address.iabuf, 4);
-       else
-               memcpy (&raw.siaddr, server_identifier.iabuf, 4);
+       memset (&d1, 0, sizeof d1);
+       oc = lookup_option (&server_universe, options, SV_NEXT_SERVER);
+       if (oc &&
+           evaluate_option_cache (&d1, packet, lease,
+                                  (struct client_state *)0,
+                                  packet -> options, options,
+                                  &lease -> scope, oc, MDL)) {
+               /* If there was more than one answer, take the first. */
+               if (d1.len >= 4 && d1.data)
+                       memcpy (&raw.siaddr, d1.data, 4);
+               data_string_forget (&d1, MDL);
+       } else {
+               if ((lease->subnet->shared_network->interface != NULL) &&
+                   lease->subnet->shared_network->interface->address_count)
+                   raw.siaddr =
+                       lease->subnet->shared_network->interface->addresses[0];
+               else if (packet->interface->address_count)
+                       raw.siaddr = packet->interface->addresses[0];
+       }
 
        raw.giaddr = packet -> raw -> giaddr;
-       if (hp -> group -> server_name) {
-               strncpy (raw.sname, hp -> group -> server_name,
-                        (sizeof raw.sname) - 1);
-               raw.sname [(sizeof raw.sname) - 1] = 0;
+
+       /* Figure out the filename. */
+       oc = lookup_option (&server_universe, options, SV_FILENAME);
+       if (oc &&
+           evaluate_option_cache (&d1, packet, lease,
+                                  (struct client_state *)0,
+                                  packet -> options, options,
+                                  &lease -> scope, oc, MDL)) {
+               memcpy (raw.file, d1.data,
+                       d1.len > sizeof raw.file ? sizeof raw.file : d1.len);
+               if (sizeof raw.file > d1.len)
+                       memset (&raw.file [d1.len],
+                               0, (sizeof raw.file) - d1.len);
+               data_string_forget (&d1, MDL);
+       } else
+               memcpy (raw.file, packet -> raw -> file, sizeof raw.file);
+
+       /* Choose a server name as above. */
+       oc = lookup_option (&server_universe, options, SV_SERVER_NAME);
+       if (oc &&
+           evaluate_option_cache (&d1, packet, lease,
+                                  (struct client_state *)0,
+                                  packet -> options, options,
+                                  &lease -> scope, oc, MDL)) {
+               memcpy (raw.sname, d1.data,
+                       d1.len > sizeof raw.sname ? sizeof raw.sname : d1.len);
+               if (sizeof raw.sname > d1.len)
+                       memset (&raw.sname [d1.len],
+                               0, (sizeof raw.sname) - d1.len);
+               data_string_forget (&d1, MDL);
        }
-       if (hp -> group -> filename) {
-               strncpy (raw.file, hp -> group -> filename,
-                        (sizeof raw.file) - 1);
-               raw.file [(sizeof raw.file) - 1] = 0;
+
+       /* Execute the commit statements, if there are any. */
+       execute_statements (NULL, packet, lease, NULL, packet->options,
+                           options, &lease->scope, lease->on_star.on_commit,
+                           NULL);
+
+       /* 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.htype = packet -> raw -> htype;
-       hto.hlen = packet -> raw -> hlen;
-       memcpy (hto.haddr, packet -> raw -> chaddr, hto.hlen);
+       hto.hbuf [0] = packet -> raw -> htype;
+       hto.hlen = packet -> raw -> hlen + 1;
+       memcpy (&hto.hbuf [1], packet -> raw -> chaddr, packet -> raw -> hlen);
 
-       if (server_identifier.len)
-               memcpy (&from, server_identifier.iabuf, 4);
-       else
-               memset (&from, 0, 4);
+       if (packet->interface->address_count) {
+               from = packet->interface->addresses[0];
+       } else {
+               log_error("%s: Interface %s appears to have no IPv4 "
+                         "addresses, and so dhcpd cannot select a source "
+                         "address.", msgbuf, packet->interface->name);
+               goto out;
+       }
 
        /* Report what we're doing... */
-       note ("BOOTREPLY for %s to %s (%s) via %s",
-             piaddr (ip_address), hp -> name,
-             print_hw_addr (packet -> raw -> htype,
-                            packet -> raw -> hlen,
-                            packet -> raw -> chaddr),
-             packet -> raw -> giaddr.s_addr
-             ? inet_ntoa (packet -> raw -> giaddr)
-             : packet -> interface -> name);
+       log_info("%s", msgbuf);
+       log_info("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),
+                packet->raw->giaddr.s_addr
+                ? inet_ntoa (packet->raw->giaddr)
+                : packet->interface->name);
 
        /* Set up the parts of the address that are in common. */
        to.sin_family = AF_INET;
@@ -266,25 +415,52 @@ void bootp (packet)
                to.sin_addr = raw.giaddr;
                to.sin_port = local_port;
 
-#ifdef USE_FALLBACK
-               result = send_fallback (&fallback_interface,
-                                       (struct packet *)0,
-                                       &raw, outgoing.packet_length,
-                                       from, &to, &hto);
-               if (result < 0)
-                       warn ("send_fallback: %m");
-               return;
-#endif
+               if (fallback_interface) {
+                       result = send_packet (fallback_interface, NULL, &raw,
+                                             outgoing.packet_length, from,
+                                             &to, &hto);
+                       if (result < 0) {
+                               log_error ("%s:%d: Failed to send %d byte long "
+                                          "packet over %s interface.", MDL,
+                                          outgoing.packet_length,
+                                          fallback_interface->name);
+                       }
+
+                       goto out;
+               }
+
+       /* If it comes from a client that already knows its address
+          and is not requesting a broadcast response, and we can
+          unicast to a client without using the ARP protocol, sent it
+          directly to that client. */
+       } else if (!(raw.flags & htons (BOOTP_BROADCAST)) &&
+                  can_unicast_without_arp (packet -> interface)) {
+               to.sin_addr = raw.yiaddr;
+               to.sin_port = remote_port;
+
        /* Otherwise, broadcast it on the local network. */
        } else {
-               to.sin_addr.s_addr = INADDR_BROADCAST;
+               to.sin_addr = limited_broadcast;
                to.sin_port = remote_port; /* XXX */
        }
 
        errno = 0;
-       result = send_packet (packet -> interface,
-                             packet, &raw, outgoing.packet_length,
-                             from, &to, &hto);
-       if (result < 0)
-               warn ("send_packet: %m");
+       result = send_packet(packet->interface, packet, &raw,
+                            outgoing.packet_length, from, &to, &hto);
+       if (result < 0) {
+               log_error ("%s:%d: Failed to send %d byte long packet over %s"
+                          " interface.", MDL, outgoing.packet_length,
+                          packet->interface->name);
+       }
+
+      out:
+
+       if (options)
+               option_state_dereference (&options, MDL);
+       if (lease)
+               lease_dereference (&lease, MDL);
+       if (hp)
+               host_dereference (&hp, MDL);
+       if (host)
+               host_dereference (&host, MDL);
 }