Exemplo n.º 1
0
void 
dhcpleasequery(struct packet *packet, int ms_nulltp) {
	char msgbuf[256];
	char dbg_info[128];
	struct iaddr cip;
	struct iaddr gip;
	struct data_string uid;
	struct hardware h;
	struct lease *tmp_lease;
	struct lease *lease;
	int want_associated_ip;
	int assoc_ip_cnt;
	u_int32_t assoc_ips[40];  /* XXXSK: arbitrary maximum number of IPs */
	const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);

	unsigned char dhcpMsgType;
	const char *dhcp_msg_type_name;
	struct subnet *subnet;
	struct group *relay_group;
	struct option_state *options;
	struct option_cache *oc;
	int allow_leasequery;
	int ignorep;
	u_int32_t lease_duration;
	u_int32_t time_renewal;
	u_int32_t time_rebinding;
	u_int32_t time_expiry;
	u_int32_t client_last_transaction_time;
	struct sockaddr_in to;
	struct in_addr siaddr;
	struct data_string prl;
	struct data_string *prl_ptr;

	int i;
	struct interface_info *interface;

	/* INSIST(packet != NULL); */

	/*
	 * Prepare log information.
	 */
	snprintf(msgbuf, sizeof(msgbuf), 
		"DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));

	/* 
	 * We can't reply if there is no giaddr field.
	 */
	if (!packet->raw->giaddr.s_addr) {
		log_info("%s: missing giaddr, ciaddr is %s, no reply sent", 
			 msgbuf, inet_ntoa(packet->raw->ciaddr));
		return;
	}

	/* 
	 * Initially we use the 'giaddr' subnet options scope to determine if
	 * the giaddr-identified relay agent is permitted to perform a
	 * leasequery.  The subnet is not required, and may be omitted, in
	 * which case we are essentially interrogating the root options class
	 * to find a globally permit.
	 */
	gip.len = sizeof(packet->raw->giaddr);
	memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));

	subnet = NULL;
	find_subnet(&subnet, gip, MDL);
	if (subnet != NULL)
		relay_group = subnet->group;
	else
		relay_group = root_group;

	subnet_dereference(&subnet, MDL);

	options = NULL;
	if (!option_state_allocate(&options, MDL)) {
		log_error("No memory for option state.");
		log_info("%s: out of memory, no reply sent", msgbuf);
		return;
	}

	execute_statements_in_scope(NULL, packet, NULL, NULL, packet->options,
				    options, &global_scope, relay_group,
				    NULL, NULL);

	for (i=packet->class_count-1; i>=0; i--) {
		execute_statements_in_scope(NULL, packet, NULL, NULL,
					    packet->options, options,
					    &global_scope,
					    packet->classes[i]->group,
					    relay_group, NULL);
	}

	/* 
	 * Because LEASEQUERY has some privacy concerns, default to deny.
	 */
	allow_leasequery = 0;

	/*
	 * See if we are authorized to do LEASEQUERY.
	 */
	oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
	if (oc != NULL) {
		allow_leasequery = evaluate_boolean_option_cache(&ignorep,
					 packet, NULL, NULL, packet->options,
					 options, &global_scope, oc, MDL);
	}

	if (!allow_leasequery) {
		log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
		option_state_dereference(&options, MDL);
		return;
	}


	/* 
	 * Copy out the client IP address.
	 */
	cip.len = sizeof(packet->raw->ciaddr);
	memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));

	/* 
	 * If the client IP address is valid (not all zero), then we 
	 * are looking for information about that IP address.
	 */
	assoc_ip_cnt = 0;
	lease = tmp_lease = NULL;
	if (memcmp(cip.iabuf, "\0\0\0", 4)) {

		want_associated_ip = 0;

		snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
		find_lease_by_ip_addr(&lease, cip, MDL);


	} else {

		want_associated_ip = 1;

		/*
		 * If the client IP address is all zero, then we will
		 * either look up by the client identifier (if we have
		 * one), or by the MAC address.
		 */

		memset(&uid, 0, sizeof(uid));
		if (get_option(&uid, 
			       &dhcp_universe,
			       packet,
			       NULL,
			       NULL,
			       packet->options,
			       NULL,
			       packet->options, 
			       &global_scope,
			       DHO_DHCP_CLIENT_IDENTIFIER,
			       MDL)) {

			snprintf(dbg_info, 
				 sizeof(dbg_info), 
				 "client-id %s",
				 print_hex_1(uid.len, uid.data, 60));

			find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
			data_string_forget(&uid, MDL);
			get_newest_lease(&lease, tmp_lease, next_uid);
			assoc_ip_cnt = get_associated_ips(tmp_lease,
							  next_uid, 
							  lease,
							  assoc_ips, 
							  nassoc_ips);

		} else {

			if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
				log_info("%s: hardware length too long, "
					 "no reply sent", msgbuf);
				option_state_dereference(&options, MDL);
				return;
			}

			h.hlen = packet->raw->hlen + 1;
			h.hbuf[0] = packet->raw->htype;
			memcpy(&h.hbuf[1], 
			       packet->raw->chaddr, 
			       packet->raw->hlen);

			snprintf(dbg_info, 
				 sizeof(dbg_info), 
				 "MAC address %s",
				 print_hw_addr(h.hbuf[0], 
					       h.hlen - 1, 
					       &h.hbuf[1]));

			find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
			get_newest_lease(&lease, tmp_lease, next_hw);
			assoc_ip_cnt = get_associated_ips(tmp_lease,
							  next_hw, 
							  lease,
							  assoc_ips, 
							  nassoc_ips);

		}

		lease_dereference(&tmp_lease, MDL);

		if (lease != NULL) {
			memcpy(&packet->raw->ciaddr, 
			       lease->ip_addr.iabuf,
			       sizeof(packet->raw->ciaddr));
		}

		/*
		 * Log if we have too many IP addresses associated
		 * with this client.
		 */
		if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
			log_info("%d IP addresses associated with %s, "
				 "only %d sent in reply.",
				 assoc_ip_cnt, dbg_info, nassoc_ips);
		}
	}

	/*
	 * We now know the query target too, so can report this in 
	 * our log message.
	 */
	snprintf(msgbuf, sizeof(msgbuf), 
		"DHCPLEASEQUERY from %s for %s",
		inet_ntoa(packet->raw->giaddr), dbg_info);

	/*
	 * Figure our our return type.
	 */
	if (lease == NULL) {
		dhcpMsgType = DHCPLEASEUNKNOWN;
		dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
	} else {
		if (lease->binding_state == FTS_ACTIVE) {
			dhcpMsgType = DHCPLEASEACTIVE;
			dhcp_msg_type_name = "DHCPLEASEACTIVE";
		} else {
			dhcpMsgType = DHCPLEASEUNASSIGNED;
			dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
		}
	}

	/* 
	 * Set options that only make sense if we have an active lease.
	 */

	if (dhcpMsgType == DHCPLEASEACTIVE)
	{
		/*
		 * RFC 4388 uses the PRL to request options for the agent to
		 * receive that are "about" the client.  It is confusing
		 * because in some cases it wants to know what was sent to
		 * the client (lease times, adjusted), and in others it wants
		 * to know information the client sent.  You're supposed to
		 * know this on a case-by-case basis.
		 *
		 * "Name servers", "domain name", and the like from the relay
		 * agent's scope seems less than useful.  Our options are to
		 * restart the option cache from the lease's best point of view
		 * (execute statements from the lease pool's group), or to
		 * simply restart the option cache from empty.
		 *
		 * I think restarting the option cache from empty best
		 * approaches RFC 4388's intent; specific options are included.
		 */
		option_state_dereference(&options, MDL);

		if (!option_state_allocate(&options, MDL)) {
			log_error("%s: out of memory, no reply sent", msgbuf);
			lease_dereference(&lease, MDL);
			return;
		}

		/* 
		 * Set the hardware address fields.
		 */

		packet->raw->hlen = lease->hardware_addr.hlen - 1;
		packet->raw->htype = lease->hardware_addr.hbuf[0];
		memcpy(packet->raw->chaddr, 
		       &lease->hardware_addr.hbuf[1], 
		       sizeof(packet->raw->chaddr));

		/*
		 * Set client identifier option.
		 */
		if (lease->uid_len > 0) {
			if (!add_option(options,
					DHO_DHCP_CLIENT_IDENTIFIER,
					lease->uid,
					lease->uid_len)) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}


		/*
		 * Calculate T1 and T2, the times when the client
		 * tries to extend its lease on its networking
		 * address.
		 * These seem to be hard-coded in ISC DHCP, to 0.5 and
		 * 0.875 of the lease time.
		 */

		lease_duration = lease->ends - lease->starts;
		time_renewal = lease->starts + 
			(lease_duration / 2);
		time_rebinding = lease->starts + 
			(lease_duration / 2) +
			(lease_duration / 4) +
			(lease_duration / 8);

		if (time_renewal > cur_time) {
			time_renewal = htonl(time_renewal - cur_time);

			if (!add_option(options, 
					DHO_DHCP_RENEWAL_TIME,
					&time_renewal, 
					sizeof(time_renewal))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		if (time_rebinding > cur_time) {
			time_rebinding = htonl(time_rebinding - cur_time);

			if (!add_option(options, 
					DHO_DHCP_REBINDING_TIME,
					&time_rebinding, 
					sizeof(time_rebinding))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		if (lease->ends > cur_time) {
			time_expiry = htonl(lease->ends - cur_time);

			if (!add_option(options, 
					DHO_DHCP_LEASE_TIME,
					&time_expiry, 
					sizeof(time_expiry))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		/* Supply the Vendor-Class-Identifier. */
		if (lease->scope != NULL) {
			struct data_string vendor_class;

			memset(&vendor_class, 0, sizeof(vendor_class));

			if (find_bound_string(&vendor_class, lease->scope,
					      "vendor-class-identifier")) {
				if (!add_option(options,
						DHO_VENDOR_CLASS_IDENTIFIER,
						(void *)vendor_class.data,
						vendor_class.len)) {
					option_state_dereference(&options,
								 MDL);
					lease_dereference(&lease, MDL);
					log_error("%s: error adding vendor "
						  "class identifier, no reply "
						  "sent", msgbuf);
					data_string_forget(&vendor_class, MDL);
					return;
				}
				data_string_forget(&vendor_class, MDL);
			}
		}

		/*
		 * Set the relay agent info.
		 *
		 * Note that because agent info is appended without regard
		 * to the PRL in cons_options(), this will be sent as the
		 * last option in the packet whether it is listed on PRL or
		 * not.
		 */

		if (lease->agent_options != NULL) {
			int idx = agent_universe.index;
			struct option_chain_head **tmp1 = 
				(struct option_chain_head **)
				&(options->universes[idx]);
				struct option_chain_head *tmp2 = 
				(struct option_chain_head *)
				lease->agent_options;

			option_chain_head_reference(tmp1, tmp2, MDL);
		}

		/* 
	 	 * Set the client last transaction time.
		 * We check to make sure we have a timestamp. For
		 * lease files that were saved before running a 
		 * timestamp-aware version of the server, this may
		 * not be set.
	 	 */

		if (lease->cltt != MIN_TIME) {
			if (cur_time > lease->cltt) {
				client_last_transaction_time = 
					htonl(cur_time - lease->cltt);
			} else {
				client_last_transaction_time = htonl(0);
			}
			if (!add_option(options, 
					DHO_CLIENT_LAST_TRANSACTION_TIME,
					&client_last_transaction_time,
		     			sizeof(client_last_transaction_time))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}

		/*
	 	 * Set associated IPs, if requested and there are some.
	 	 */
		if (want_associated_ip && (assoc_ip_cnt > 0)) {
			if (!add_option(options, 
					DHO_ASSOCIATED_IP,
					assoc_ips,
					assoc_ip_cnt * sizeof(assoc_ips[0]))) {
				option_state_dereference(&options, MDL);
				lease_dereference(&lease, MDL);
				log_info("%s: out of memory, no reply sent",
					 msgbuf);
				return;
			}
		}
	}

	/* 
	 * Set the message type.
	 */

	packet->raw->op = BOOTREPLY;

	/*
	 * Set DHCP message type.
	 */
	if (!add_option(options, 
		        DHO_DHCP_MESSAGE_TYPE,
		        &dhcpMsgType, 
			sizeof(dhcpMsgType))) {
		option_state_dereference(&options, MDL);
		lease_dereference(&lease, MDL);
		log_info("%s: error adding option, no reply sent", msgbuf);
		return;
	}

	/*
	 * Log the message we've received.
	 */
	log_info("%s", msgbuf);

	/*
	 * Figure out which address to use to send from.
	 */
	get_server_source_address(&siaddr, options, options, packet);

	/* 
	 * Set up the option buffer.
	 */

	memset(&prl, 0, sizeof(prl));
	oc = lookup_option(&dhcp_universe, options, 
			   DHO_DHCP_PARAMETER_REQUEST_LIST);
	if (oc != NULL) {
		evaluate_option_cache(&prl, 
				      packet, 
				      NULL,
				      NULL,
				      packet->options,
				      options,
				      &global_scope,
				      oc,
				      MDL);
	}
	if (prl.len > 0) {
		prl_ptr = &prl;
	} else {
		prl_ptr = NULL;
	}

	packet->packet_length = cons_options(packet, 
					     packet->raw, 
					     lease,
					     NULL,
					     0,
					     packet->options,
					     options,
					     &global_scope,
					     0,
					     0,
					     0, 
					     prl_ptr,
					     NULL);

	data_string_forget(&prl, MDL);	/* SK: safe, even if empty */
	option_state_dereference(&options, MDL);
	lease_dereference(&lease, MDL);

	to.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
	to.sin_len = sizeof(to);
#endif
	memset(to.sin_zero, 0, sizeof(to.sin_zero));

	/* 
	 * Leasequery packets are be sent to the gateway address.
	 */
	to.sin_addr = packet->raw->giaddr;
	if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
		to.sin_port = local_port;
	} else {
		to.sin_port = remote_port; /* XXXSK: For debugging. */
	}

	/* 
	 * The fallback_interface lets us send with a real IP
	 * address. The packet interface sends from all-zeros.
	 */
	if (fallback_interface != NULL) {
		interface = fallback_interface;
	} else {
		interface = packet->interface;
	}

	/*
	 * Report what we're sending.
	 */
	log_info("%s to %s for %s (%d associated IPs)",
		dhcp_msg_type_name, 
		inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);

	send_packet(interface,
		    NULL,
		    packet->raw, 
		    packet->packet_length,
		    siaddr,
		    &to,
		    NULL);
}
Exemplo n.º 2
0
void
bootp(struct packet *packet)
{
	struct host_decl *hp, *host = NULL;
	struct packet outgoing;
	struct dhcp_packet raw;
	struct sockaddr_in to;
	struct in_addr from;
	struct tree_cache *options[256];
	struct subnet *subnet = NULL;
	struct lease *lease;
	struct iaddr ip_address;
	int i;

	if (packet->raw->op != BOOTREQUEST)
		return;

	note("BOOTREQUEST from %s via %s%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,
	    packet->options_valid ? "" : " (non-rfc1048)");

	if (!locate_network(packet))
		return;

	hp = find_hosts_by_haddr(packet->raw->htype, packet->raw->chaddr,
	    packet->raw->hlen);

	lease = find_lease(packet, packet->shared_network, 0);

	/*
	 * 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);

	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;
				}

		if (host && (!host->group->allow_booting)) {
			note("Ignoring excluded BOOTP client %s", host->name ?
			    host->name : print_hw_addr (packet->raw->htype,
			    packet->raw->hlen, packet->raw->chaddr));
			return;
		}

		if (host && (!host->group->allow_bootp)) {
			note("Ignoring BOOTP request from client %s",
			    host->name ? host->name :
			    print_hw_addr(packet->raw->htype,
			    packet->raw->hlen, packet->raw->chaddr));
			return;
		}

		/*
		 * 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 we've been told not to boot with bootp on this network,
		 * ignore it.
		 */
		if (!host &&
		    !(packet->shared_network->group->allow_bootp)) {
			note("Ignoring BOOTP request from 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 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 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;
			}

			 /*
			  * If dynamic BOOTP is no longer allowed for this
			  * lease, set it free.
			  */
			release_lease(lease);
		}

		/*
		 * 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;
			}
		}
		goto lose;
	}

	/* Make sure we're allowed to boot this client. */
	if (hp && (!hp->group->allow_booting)) {
		note("Ignoring excluded BOOTP client %s", hp->name);
		return;
	}

	/* Make sure we're allowed to boot this client with bootp. */
	if (hp && (!hp->group->allow_bootp)) {
		note("Ignoring BOOTP request from client %s", hp->name);
		return;
	}

	/* Set up the outgoing packet... */
	memset(&outgoing, 0, sizeof outgoing);
	memset(&raw, 0, sizeof raw);
	outgoing.raw = &raw;

	/*
	 * If we didn't get a known vendor magic number on the way in, just
	 * copy the input options to the output.
	 */
	if (!packet->options_valid && !subnet->group->always_reply_rfc1048 &&
	    (!hp || !hp->group->always_reply_rfc1048)) {
		memcpy(outgoing.raw->options, packet->raw->options,
		    DHCP_OPTION_LEN);
		outgoing.packet_length = BOOTP_MIN_LEN;
	} else {
		struct tree_cache netmask_tree;   /*  -- RBF */

		/*
		 * 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));

		for (i = 0; i < 256; i++)
			if (hp->group->options[i])
				options[i] = hp->group->options[i];

		/*
		 * Use the subnet mask from the subnet declaration if no other
		 * mask has been provided.
		 */
		if (!options[DHO_SUBNET_MASK]) {
			options[DHO_SUBNET_MASK] = &netmask_tree;
			netmask_tree.flags = TC_TEMPORARY;
			netmask_tree.value = lease->subnet->netmask.iabuf;
			netmask_tree.len = lease->subnet->netmask.len;
			netmask_tree.buf_size = lease->subnet->netmask.len;
			netmask_tree.timeout = -1;
			netmask_tree.tree = NULL;
		}

		/*
		 * 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,
		    0, options, 0, 0, 1, NULL, 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, sizeof(raw.chaddr));
	raw.hops = packet->raw->hops;
	raw.xid = packet->raw->xid;
	raw.secs = packet->raw->secs;
	raw.flags = packet->raw->flags;
	raw.ciaddr = packet->raw->ciaddr;
	memcpy(&raw.yiaddr, ip_address.iabuf, sizeof(raw.yiaddr));

	/* 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
		raw.siaddr = packet->interface->primary_address;

	raw.giaddr = packet->raw->giaddr;
	if (hp->group->server_name)
		strncpy(raw.sname, hp->group->server_name, sizeof(raw.sname));
	else if (subnet->group->server_name)
		strncpy(raw.sname, subnet->group->server_name,
		    sizeof(raw.sname));

	if (hp->group->filename)
		strncpy(raw.file, hp->group->filename, sizeof(raw.file));
	else if (subnet->group->filename)
		strncpy(raw.file, subnet->group->filename, sizeof(raw.file));
	else
		memcpy(raw.file, packet->raw->file, sizeof(raw.file));

	from = packet->interface->primary_address;

	/* 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);

	/* Set up the parts of the address that are in common. */
	memset(&to, 0, sizeof(to));
	to.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
	to.sin_len = sizeof(to);
#endif

	/* If this was gatewayed, send it back to the gateway... */
	if (raw.giaddr.s_addr) {
		to.sin_addr = raw.giaddr;
		to.sin_port = server_port;

		(void) send_packet(packet->interface, &raw,
		    outgoing.packet_length, from, &to, packet->haddr);
		return;
	}

	/*
	 * 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))) {
		to.sin_addr = raw.yiaddr;
		to.sin_port = client_port;
	} else {
		/* Otherwise, broadcast it on the local network. */
		to.sin_addr.s_addr = INADDR_BROADCAST;
		to.sin_port = client_port; /* XXX */
	}

	errno = 0;
	(void) send_packet(packet->interface, &raw,
	    outgoing.packet_length, from, &to, packet->haddr);
}