summaryrefslogtreecommitdiffstats
path: root/src/modules/proto_dhcp/dhcpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/proto_dhcp/dhcpd.c')
-rw-r--r--src/modules/proto_dhcp/dhcpd.c866
1 files changed, 866 insertions, 0 deletions
diff --git a/src/modules/proto_dhcp/dhcpd.c b/src/modules/proto_dhcp/dhcpd.c
new file mode 100644
index 0000000..bc51c36
--- /dev/null
+++ b/src/modules/proto_dhcp/dhcpd.c
@@ -0,0 +1,866 @@
+/*
+ * dhcp.c DHCP processing.
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2008 The FreeRADIUS server project
+ * Copyright 2008,2011 Alan DeKok <aland@deployingradius.com>
+ */
+
+/*
+ * Standard sequence:
+ * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 DISCOVER
+ * CLIENT_IP : 68 <- DHCP_SERVER_IP : 67 OFFER
+ * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 REQUEST
+ * CLIENT_IP : 68 <- DHCP_SERVER_IP : 67 ACK
+ *
+ * Relay sequence:
+ * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 DISCOVER
+ * RELAY_IP : 67 -> NEXT_SERVER_IP : 67 DISCOVER
+ * (NEXT_SERVER_IP can be a relay itself)
+ * FIRST_RELAY_IP : 67 <- DHCP_SERVER_IP : 67 OFFER
+ * CLIENT_IP : 68 <- FIRST_RELAY_IP : 67 OFFER
+ * INADDR_ANY : 68 -> INADDR_BROADCAST : 67 REQUEST
+ * RELAY_IP : 67 -> NEXT_SERVER_IP : 67 REQUEST
+ * (NEXT_SERVER_IP can be a relay itself)
+ * FIRST_RELAY_IP : 67 <- DHCP_SERVER_IP : 67 ACK
+ * CLIENT_IP : 68 <- FIRST_RELAY_IP : 67 ACK
+ *
+ * Note: NACK are broadcasted, rest is unicast, unless client asked
+ * for a broadcast
+ */
+
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/protocol.h>
+#include <freeradius-devel/process.h>
+#include <freeradius-devel/dhcp.h>
+#include <freeradius-devel/rad_assert.h>
+
+#ifndef __MINGW32__
+#include <sys/ioctl.h>
+#endif
+
+/*
+ * Same contents as listen_socket_t.
+ */
+typedef struct dhcp_socket_t {
+ listen_socket_t lsock;
+
+ /*
+ * DHCP-specific additions.
+ */
+ bool suppress_responses;
+ RADCLIENT dhcp_client;
+ char const *src_interface;
+ fr_ipaddr_t src_ipaddr;
+} dhcp_socket_t;
+
+#ifdef WITH_UDPFROMTO
+static int dhcprelay_process_client_request(REQUEST *request)
+{
+ int rcode;
+ uint8_t maxhops = 16;
+ VALUE_PAIR *vp, *giaddr;
+ dhcp_socket_t *sock;
+ RADIUS_PACKET *packet;
+
+ rad_assert(request->packet->data[0] == 1);
+
+ /*
+ * Do the forward by ourselves, do not rely on dhcp_socket_send()
+ */
+ request->reply->code = 0;
+
+ /*
+ * It's invalid to have giaddr=0 AND a relay option
+ */
+ giaddr = fr_pair_find_by_num(request->packet->vps, 266, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Gateway-IP-Address */
+ if (giaddr && (giaddr->vp_ipaddr == htonl(INADDR_ANY)) &&
+ fr_pair_find_by_num(request->packet->vps, 82, DHCP_MAGIC_VENDOR, TAG_ANY)) { /* DHCP-Relay-Agent-Information */
+ DEBUG("DHCP: Received packet with giaddr = 0 and containing relay option: Discarding packet\n");
+ return 1;
+ }
+
+ /*
+ * RFC 1542 (BOOTP), page 15
+ *
+ * Drop requests if hop-count > 16 or admin specified another value
+ */
+ if ((vp = fr_pair_find_by_num(request->config, 271, DHCP_MAGIC_VENDOR, TAG_ANY))) { /* DHCP-Relay-Max-Hop-Count */
+ maxhops = vp->vp_integer;
+ }
+ vp = fr_pair_find_by_num(request->packet->vps, 259, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Hop-Count */
+ rad_assert(vp != NULL);
+ if (vp->vp_byte > maxhops) {
+ DEBUG("DHCP: Number of hops is greater than %d: not relaying\n", maxhops);
+ return 1;
+ } else {
+ /* Increment hop count */
+ vp->vp_byte++;
+ }
+
+ sock = request->listener->data;
+
+ /*
+ * Don't muck with the original request packet. That's
+ * bad form. Plus, dhcp_encode() does nothing if
+ * packet->data is already set.
+ */
+ packet = rad_alloc(request, false);
+
+ /*
+ * Forward the request to the next server using the
+ * incoming request as a template.
+ */
+ packet->code = request->packet->code;
+ packet->sockfd = request->packet->sockfd;
+
+ /*
+ * Forward the request to the next server using the
+ * incoming request as a template.
+ */
+ /* set SRC ipaddr/port to the listener ipaddr/port */
+ packet->src_ipaddr.af = AF_INET;
+ packet->src_ipaddr.ipaddr.ip4addr.s_addr = sock->lsock.my_ipaddr.ipaddr.ip4addr.s_addr;
+ packet->src_port = sock->lsock.my_port;
+
+ vp = fr_pair_find_by_num(request->config, 270, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Relay-To-IP-Address */
+ rad_assert(vp != NULL);
+
+ /* set DEST ipaddr/port to the next server ipaddr/port */
+ packet->dst_ipaddr.af = AF_INET;
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ packet->dst_port = sock->lsock.my_port;
+
+ packet->vps = request->packet->vps; /* hackity hack */
+
+ if (fr_dhcp_encode(packet) < 0) {
+ packet->vps = NULL;
+ talloc_free(packet);
+ DEBUG("dhcprelay_process_client_request: ERROR in fr_dhcp_encode\n");
+ return -1;
+ }
+
+ rcode = fr_dhcp_send(packet);
+ packet->vps = NULL;
+ talloc_free(packet);
+
+ return rcode;
+}
+
+
+/*
+ * We've seen a reply from a server.
+ * i.e. we're a relay.
+ */
+static int dhcprelay_process_server_reply(REQUEST *request)
+{
+ int rcode;
+ VALUE_PAIR *vp, *giaddr;
+ dhcp_socket_t *sock;
+ RADIUS_PACKET *packet;
+
+ rad_assert(request->packet->data[0] == 2);
+
+ /*
+ * Do the forward by ourselves, do not rely on dhcp_socket_send()
+ */
+ request->reply->code = 0;
+
+ sock = request->listener->data;
+
+ /*
+ * Check that packet is for us.
+ */
+ giaddr = fr_pair_find_by_num(request->packet->vps, 266, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Gateway-IP-Address */
+
+ /* --with-udpfromto is needed just for the following test */
+ if (!giaddr || giaddr->vp_ipaddr != request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr) {
+ DEBUG("DHCP: Packet received from server was not for us (was for 0x%x). Discarding packet",
+ ntohl(request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr));
+ return 1;
+ }
+
+ /*
+ * Don't muck with the original request packet. That's
+ * bad form. Plus, dhcp_encode() does nothing if
+ * packet->data is already set.
+ */
+ packet = rad_alloc(request, false);
+ rcode = -1;
+
+ /*
+ * Forward the request to the next server using the
+ * incoming request as a template.
+ */
+ packet->code = request->packet->code;
+ packet->sockfd = request->packet->sockfd;
+
+ /* set SRC ipaddr/port to the listener ipaddr/port */
+ packet->src_ipaddr.af = AF_INET;
+ packet->src_port = sock->lsock.my_port;
+
+ /* set DEST ipaddr/port to clientip/68 or broadcast in specific cases */
+ packet->dst_ipaddr.af = AF_INET;
+
+ /*
+ * We're a relay, figure out where to send the packet.
+ */
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+ packet->dst_port = request->packet->dst_port; /* server port */
+
+ /*
+ * Unicast the response to another relay if requested.
+ */
+ vp = fr_pair_find_by_num(request->config, 270, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Relay-To-IP-Address */
+ if (vp) {
+ RDEBUG("DHCP: response will be relayed to previous gateway");
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ giaddr->vp_ipaddr = vp->vp_ipaddr;
+
+ } else if ((packet->code == PW_DHCP_NAK) ||
+ !sock->src_interface ||
+ ((vp = fr_pair_find_by_num(request->packet->vps, 262, DHCP_MAGIC_VENDOR, TAG_ANY)) /* DHCP-Flags */ &&
+ (vp->vp_integer & 0x8000) &&
+ ((vp = fr_pair_find_by_num(request->packet->vps, 263, DHCP_MAGIC_VENDOR, TAG_ANY)) /* DHCP-Client-IP-Address */ &&
+ (vp->vp_ipaddr == htonl(INADDR_ANY))))) {
+ /*
+ * RFC 2131, page 23
+ *
+ * Broadcast on
+ * - DHCPNAK
+ * or
+ * - Broadcast flag is set up and ciaddr == NULL
+ */
+ RDEBUG("DHCP: response will be broadcast");
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+
+ } else if ((vp = fr_pair_find_by_num(request->packet->vps, 263, DHCP_MAGIC_VENDOR, TAG_ANY)) /* DHCP-Client-IP-Address */ &&
+ (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+ /*
+ * RFC 2131, page 23
+ *
+ * Unicast to
+ * - ciaddr if present
+ * otherwise to yiaddr
+ */
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ } else {
+ vp = fr_pair_find_by_num(request->packet->vps, 264, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Your-IP-Address */
+ if (!vp) {
+ DEBUG("DHCP: Failed to find IP Address for request");
+ goto error;
+ }
+
+ RDEBUG("DHCP: response will be unicast to your-ip-address");
+ packet->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+
+ /*
+ * When sending a DHCP_OFFER, make sure our ARP table
+ * contains an entry for the client IP address, or else
+ * packet may not be forwarded if it was the first time
+ * the client was requesting an IP address.
+ */
+ if (packet->code == PW_DHCP_OFFER) {
+ VALUE_PAIR *hwvp = fr_pair_find_by_num(request->packet->vps, 267, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Client-Hardware-Address */
+ if (hwvp == NULL) {
+ DEBUG("DHCP: DHCP_OFFER packet received with "
+ "no Client Hardware Address. Discarding packet");
+ goto error;
+ }
+ if (fr_dhcp_add_arp_entry(packet->sockfd, sock->src_interface, hwvp, vp) < 0) {
+ DEBUG("Failed adding ARP entry: %s", fr_strerror());
+ goto error;
+ }
+ }
+ }
+
+ packet->vps = request->packet->vps; /* hackity hack */
+
+ if (fr_dhcp_encode(packet) < 0) {
+ DEBUG("dhcprelay_process_server_reply: ERROR in fr_dhcp_encode\n");
+ goto error;
+ }
+
+ rcode = fr_dhcp_send(packet);
+
+error:
+ packet->vps = NULL;
+ talloc_free(packet);
+ return rcode;
+}
+#else /* WITH_UDPFROMTO */
+static int dhcprelay_process_server_reply(UNUSED REQUEST *request)
+{
+ WARN("DHCP Relaying requires the server to be configured with UDPFROMTO");
+ return -1;
+}
+
+static int dhcprelay_process_client_request(UNUSED REQUEST *request)
+{
+ WARN("DHCP Relaying requires the server to be configured with UDPFROMTO");
+ return -1;
+}
+
+#endif /* WITH_UDPFROMTO */
+
+static const uint32_t attrnums[] = {
+ 57, /* DHCP-DHCP-Maximum-Msg-Size */
+ 256, /* DHCP-Opcode */
+ 257, /* DHCP-Hardware-Type */
+ 258, /* DHCP-Hardware-Address-Length */
+ 259, /* DHCP-Hop-Count */
+ 260, /* DHCP-Transaction-Id */
+ 262, /* DHCP-Flags */
+ 263, /* DHCP-Client-IP-Address */
+ 266, /* DHCP-Gateway-IP-Address */
+ 267 /* DHCP-Client-Hardware-Address */
+};
+
+static int dhcp_process(REQUEST *request)
+{
+ int rcode;
+ unsigned int i;
+ VALUE_PAIR *vp;
+ dhcp_socket_t *sock;
+
+ /*
+ * If there's a giaddr, save it as the Relay-IP-Address
+ * in the response. That way the later code knows where
+ * to send the reply.
+ */
+ vp = fr_pair_find_by_num(request->packet->vps, 266, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Gateway-IP-Address */
+ if (vp && (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+ VALUE_PAIR *relay;
+
+ /* DHCP-Relay-IP-Address */
+ relay = radius_pair_create(request->reply, &request->reply->vps,
+ 272, DHCP_MAGIC_VENDOR);
+ if (relay) relay->vp_ipaddr = vp->vp_ipaddr;
+ }
+
+ /*
+ * RFC 6842: If there's a DHCP-Client-Identifier ("uid") in the
+ * request then echo this in the reply.
+ */
+ vp = fr_pair_find_by_num(request->packet->vps, 61, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Client-Identifier */
+ if (vp && !fr_pair_find_by_num(request->reply->vps, 61, DHCP_MAGIC_VENDOR, TAG_ANY)) {
+ fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
+ }
+
+ vp = fr_pair_find_by_num(request->packet->vps, 53, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Message-Type */
+ if (vp) {
+ DICT_VALUE *dv = dict_valbyattr(53, DHCP_MAGIC_VENDOR, vp->vp_byte);
+ DEBUG("Trying sub-section dhcp %s {...}",
+ dv ? dv->name : "<unknown>");
+ rcode = process_post_auth(vp->vp_byte, request);
+ } else {
+ DEBUG("DHCP: Failed to find DHCP-Message-Type in packet!");
+ rcode = RLM_MODULE_FAIL;
+ }
+
+ vp = fr_pair_find_by_num(request->reply->vps, 53, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Message-Type */
+ if (vp) {
+ request->reply->code = vp->vp_byte;
+ if ((request->reply->code != 0) &&
+ (request->reply->code < PW_DHCP_OFFSET)) {
+ request->reply->code += PW_DHCP_OFFSET;
+ }
+ }
+ else switch (rcode) {
+ case RLM_MODULE_OK:
+ case RLM_MODULE_UPDATED:
+ if (request->packet->code == PW_DHCP_DISCOVER) {
+ request->reply->code = PW_DHCP_OFFER;
+ break;
+
+ } else if (request->packet->code == PW_DHCP_REQUEST) {
+ request->reply->code = PW_DHCP_ACK;
+ break;
+ }
+ request->reply->code = PW_DHCP_NAK;
+ break;
+
+ default:
+ case RLM_MODULE_REJECT:
+ case RLM_MODULE_FAIL:
+ case RLM_MODULE_INVALID:
+ case RLM_MODULE_NOOP:
+ case RLM_MODULE_NOTFOUND:
+ if (request->packet->code == PW_DHCP_DISCOVER) {
+ request->reply->code = 0; /* ignore the packet */
+ } else {
+ request->reply->code = PW_DHCP_NAK;
+ }
+ break;
+
+ case RLM_MODULE_HANDLED:
+ request->reply->code = 0; /* ignore the packet */
+ break;
+ }
+
+ /*
+ * TODO: Handle 'output' of RLM_MODULE when acting as a
+ * DHCP relay We may want to not forward packets in
+ * certain circumstances.
+ */
+
+ /*
+ * Handle requests when acting as a DHCP relay
+ */
+ vp = fr_pair_find_by_num(request->packet->vps, 256, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Opcode */
+ if (!vp) {
+ RDEBUG("FAILURE: Someone deleted the DHCP-Opcode!");
+ return 1;
+ }
+
+ /* BOOTREPLY received on port 67 (i.e. from a server) */
+ if (vp->vp_byte == 2) {
+ if (request->reply->code == 0) {
+ return 1;
+ }
+ return dhcprelay_process_server_reply(request);
+ }
+
+ /*
+ * If it's not BOOTREQUEST, we ignore it.
+ */
+ if (vp->vp_byte != 1) {
+ REDEBUG("Ignoring invalid packet code %u", vp->vp_byte);
+ return 1;
+ }
+
+ /* Packet from client, and we have DHCP-Relay-To-IP-Address */
+ if (fr_pair_find_by_num(request->config, 270, DHCP_MAGIC_VENDOR, TAG_ANY)) {
+ return dhcprelay_process_client_request(request);
+ }
+
+ sock = request->listener->data;
+
+ /*
+ * Handle requests when acting as a DHCP server
+ */
+
+ /*
+ * Releases don't get replies.
+ */
+ if (request->packet->code == PW_DHCP_RELEASE) {
+ request->reply->code = 0;
+ }
+
+ if (request->reply->code == 0) {
+ return 1;
+ }
+
+ request->reply->sockfd = request->packet->sockfd;
+
+ /*
+ * Copy specific fields from packet to reply, if they
+ * don't already exist
+ */
+ for (i = 0; i < sizeof(attrnums) / sizeof(attrnums[0]); i++) {
+ uint32_t attr = attrnums[i];
+
+ if (fr_pair_find_by_num(request->reply->vps, attr, DHCP_MAGIC_VENDOR, TAG_ANY)) continue;
+
+ vp = fr_pair_find_by_num(request->packet->vps, attr, DHCP_MAGIC_VENDOR, TAG_ANY);
+ if (vp) {
+ fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp));
+ }
+ }
+
+ vp = fr_pair_find_by_num(request->reply->vps, 256, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Opcode */
+ rad_assert(vp != NULL);
+ vp->vp_byte = 2; /* BOOTREPLY */
+
+ /*
+ * Allow NAKs to be delayed for a short period of time.
+ */
+ if (request->reply->code == PW_DHCP_NAK) {
+ vp = fr_pair_find_by_num(request->reply->vps, PW_FREERADIUS_RESPONSE_DELAY, 0, TAG_ANY);
+ if (vp) {
+ if (vp->vp_integer <= 10) {
+ request->response_delay.tv_sec = vp->vp_integer;
+ request->response_delay.tv_usec = 0;
+ } else {
+ request->response_delay.tv_sec = 10;
+ request->response_delay.tv_usec = 0;
+ }
+ } else {
+#define USEC 1000000
+ vp = fr_pair_find_by_num(request->reply->vps, PW_FREERADIUS_RESPONSE_DELAY_USEC, 0, TAG_ANY);
+ if (vp) {
+ if (vp->vp_integer <= 10 * USEC) {
+ request->response_delay.tv_sec = vp->vp_integer / USEC;
+ request->response_delay.tv_usec = vp->vp_integer % USEC;
+ } else {
+ request->response_delay.tv_sec = 10;
+ request->response_delay.tv_usec = 0;
+ }
+ }
+ }
+ }
+
+ /*
+ * Prepare the reply packet for sending through dhcp_socket_send()
+ */
+ request->reply->dst_ipaddr.af = AF_INET;
+ request->reply->src_ipaddr.af = AF_INET;
+ request->reply->src_ipaddr.prefix = 32;
+
+ /*
+ * Packet-Src-IP-Address has highest precedence
+ */
+ vp = fr_pair_find_by_num(request->reply->vps, PW_PACKET_SRC_IP_ADDRESS, 0, TAG_ANY);
+ if (vp) {
+ request->reply->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ /*
+ * The request was unicast (via a relay)
+ */
+ } else if (request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_BROADCAST) &&
+ request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_ANY)) {
+ request->reply->src_ipaddr.ipaddr.ip4addr.s_addr = request->packet->dst_ipaddr.ipaddr.ip4addr.s_addr;
+ /*
+ * The listener was bound to an IP address, or we determined
+ * the address automatically, as it was the only address bound
+ * to the interface, and we bound to the interface.
+ */
+ } else if (sock->src_ipaddr.ipaddr.ip4addr.s_addr != htonl(INADDR_ANY)) {
+ request->reply->src_ipaddr.ipaddr.ip4addr.s_addr = sock->src_ipaddr.ipaddr.ip4addr.s_addr;
+ /*
+ * There's a Server-Identification attribute
+ */
+ } else if ((vp = fr_pair_find_by_num(request->reply->vps, 54, DHCP_MAGIC_VENDOR, TAG_ANY))) {
+ request->reply->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ } else {
+ REDEBUG("Unable to determine correct src_ipaddr for response");
+ return -1;
+ }
+ request->reply->dst_port = request->packet->src_port;
+ request->reply->src_port = request->packet->dst_port;
+
+ /*
+ * Answer to client's nearest DHCP relay.
+ *
+ * Which may be different than the giaddr given in the
+ * packet to the client. i.e. the relay may have a
+ * public IP, but the gateway a private one.
+ */
+ vp = fr_pair_find_by_num(request->reply->vps, 272, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Relay-IP-Address */
+ if (vp && (vp->vp_ipaddr != ntohl(INADDR_ANY))) {
+ RDEBUG("DHCP: Reply will be unicast to giaddr from original packet");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ request->reply->dst_port = request->packet->dst_port;
+
+ vp = fr_pair_find_by_num(request->reply->vps, PW_PACKET_DST_PORT, 0, TAG_ANY);
+ if (vp) request->reply->dst_port = vp->vp_integer;
+
+ return 1;
+ }
+
+ /*
+ * Answer to client's nearest DHCP gateway. In this
+ * case, the client can reach the gateway, as can the
+ * server.
+ *
+ * We also use *our* source port as the destination port.
+ * Gateways are servers, and listen on the server port,
+ * not the client port.
+ */
+ vp = fr_pair_find_by_num(request->reply->vps, 266, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Gateway-IP-Address */
+ if (vp && (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+ RDEBUG("DHCP: Reply will be unicast to giaddr");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ request->reply->dst_port = request->packet->dst_port;
+ return 1;
+ }
+
+ /*
+ * If it's a NAK, or the broadcast flag was set, ond
+ * there's no client-ip-address, send a broadcast.
+ */
+ if ((request->reply->code == PW_DHCP_NAK) ||
+ ((vp = fr_pair_find_by_num(request->reply->vps, 262, DHCP_MAGIC_VENDOR, TAG_ANY)) && /* DHCP-Flags */
+ (vp->vp_integer & 0x8000) &&
+ ((vp = fr_pair_find_by_num(request->reply->vps, 263, DHCP_MAGIC_VENDOR, TAG_ANY)) && /* DHCP-Client-IP-Address */
+ (vp->vp_ipaddr == htonl(INADDR_ANY))))) {
+ /*
+ * RFC 2131, page 23
+ *
+ * Broadcast on
+ * - DHCPNAK
+ * or
+ * - Broadcast flag is set up and ciaddr == NULL
+ */
+ RDEBUG("DHCP: Reply will be broadcast");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+ return 1;
+ }
+
+ /*
+ * RFC 2131, page 23
+ *
+ * Unicast to ciaddr if present, otherwise to yiaddr.
+ */
+ if ((vp = fr_pair_find_by_num(request->reply->vps, 263, DHCP_MAGIC_VENDOR, TAG_ANY)) && /* DHCP-Client-IP-Address */
+ (vp->vp_ipaddr != htonl(INADDR_ANY))) {
+ RDEBUG("DHCP: Reply will be sent unicast to client-ip-address");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+ return 1;
+ }
+
+ vp = fr_pair_find_by_num(request->reply->vps, 264, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Your-IP-Address */
+ if (!vp) {
+ RDEBUG("DHCP: Failed to find DHCP-Client-IP-Address or DHCP-Your-IP-Address for request; "
+ "not responding");
+ /*
+ * There is nowhere to send the response to, so don't bother.
+ */
+ request->reply->code = 0;
+ return -1;
+ }
+
+#ifdef SIOCSARP
+ /*
+ * The system is configured to listen for broadcast
+ * packets, which means we'll need to send unicast
+ * replies, to IPs which haven't yet been assigned.
+ * Therefore, we need to update the ARP table.
+ *
+ * However, they haven't specified a interface. So we
+ * can't update the ARP table. And we must send a
+ * broadcast response.
+ */
+ if (sock->lsock.broadcast && !sock->src_interface) {
+ WARN("You MUST set \"interface\" if you have \"broadcast = yes\"");
+ RDEBUG("DHCP: Reply will be broadcast as no interface was defined");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+ return 1;
+ }
+
+ RDEBUG("DHCP: Reply will be unicast to your-ip-address");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
+
+ /*
+ * When sending a DHCP_OFFER, make sure our ARP table
+ * contains an entry for the client IP address.
+ * Otherwise the packet may not be sent to the client, as
+ * the OS has no ARP entry for it.
+ *
+ * This is a cute hack to avoid us having to create a raw
+ * socket to send DHCP packets.
+ */
+ if (request->reply->code == PW_DHCP_OFFER) {
+ VALUE_PAIR *hwvp = fr_pair_find_by_num(request->reply->vps, 267, DHCP_MAGIC_VENDOR, TAG_ANY); /* DHCP-Client-Hardware-Address */
+
+ if (!hwvp) return -1;
+
+ if (fr_dhcp_add_arp_entry(request->reply->sockfd, sock->src_interface, hwvp, vp) < 0) {
+ RDEBUG("Failed adding ARP entry: %s", fr_strerror());
+ return -1;
+ }
+ }
+#else
+ if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr != ntohl(INADDR_NONE)) {
+ RDEBUG("DHCP: Request will be unicast to the unicast source IP address");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr;
+ } else {
+ RDEBUG("DHCP: Reply will be broadcast as this system does not support ARP updates");
+ request->reply->dst_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_BROADCAST);
+ }
+#endif
+
+ return 1;
+}
+
+static int dhcp_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+ int rcode, broadcast = 1;
+ int on = 1;
+ dhcp_socket_t *sock;
+ RADCLIENT *client;
+ CONF_PAIR *cp;
+
+ /*
+ * Set if before parsing, so the user can forcibly turn
+ * it off later.
+ */
+ this->nodup = true;
+
+ rcode = common_socket_parse(cs, this);
+ if (rcode != 0) return rcode;
+
+ if (check_config) return 0;
+
+ sock = this->data;
+
+ if (!sock->lsock.interface) {
+ WARN("No \"interface\" setting is defined. Only unicast DHCP will work");
+ }
+
+ /*
+ * See whether or not we enable broadcast packets.
+ */
+ cp = cf_pair_find(cs, "broadcast");
+ if (cp) {
+ char const *value = cf_pair_value(cp);
+ if (value && (strcmp(value, "no") == 0)) {
+ broadcast = 0;
+ }
+ }
+
+ if (broadcast) {
+ if (setsockopt(this->fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
+ ERROR("Can't set broadcast option: %s\n",
+ fr_syserror(errno));
+ return -1;
+ }
+ }
+
+ if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+ ERROR("Can't set re-use address option: %s\n",
+ fr_syserror(errno));
+ return -1;
+ }
+
+ /*
+ * Undocumented extension for testing without
+ * destroying your network!
+ */
+ sock->suppress_responses = false;
+ cp = cf_pair_find(cs, "suppress_responses");
+ if (cp) {
+ rcode = cf_item_parse(cs, "suppress_responses", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &sock->suppress_responses), NULL);
+ if (rcode < 0) return -1;
+ }
+
+ cp = cf_pair_find(cs, "src_interface");
+ if (cp) {
+ rcode = cf_item_parse(cs, "src_interface", FR_ITEM_POINTER(PW_TYPE_STRING, &sock->src_interface), NULL);
+ if (rcode < 0) return -1;
+ } else {
+ sock->src_interface = sock->lsock.interface;
+ }
+
+ if (!sock->src_interface && sock->lsock.interface) {
+ sock->src_interface = talloc_typed_strdup(sock, sock->lsock.interface);
+ }
+
+ cp = cf_pair_find(cs, "src_ipaddr");
+ if (cp) {
+ memset(&sock->src_ipaddr, 0, sizeof(sock->src_ipaddr));
+ sock->src_ipaddr.ipaddr.ip4addr.s_addr = htonl(INADDR_NONE);
+ rcode = cf_item_parse(cs, "src_ipaddr", FR_ITEM_POINTER(PW_TYPE_IPV4_ADDR, &sock->src_ipaddr), NULL);
+ if (rcode < 0) return -1;
+
+ sock->src_ipaddr.af = AF_INET;
+ } else {
+ memcpy(&sock->src_ipaddr, &sock->lsock.my_ipaddr, sizeof(sock->src_ipaddr));
+ }
+
+ /*
+ * Initialize the fake client.
+ */
+ client = &sock->dhcp_client;
+ memset(client, 0, sizeof(*client));
+ client->ipaddr.af = AF_INET;
+ client->ipaddr.ipaddr.ip4addr.s_addr = ntohl(INADDR_NONE);
+ client->ipaddr.prefix = 0;
+ client->longname = client->shortname = "dhcp";
+ client->secret = client->shortname;
+ client->nas_type = talloc_typed_strdup(sock, "none");
+
+ return 0;
+}
+
+
+/*
+ * Check if an incoming request is "ok"
+ *
+ * It takes packets, not requests. It sees if the packet looks
+ * OK. If so, it does a number of sanity checks on it.
+ */
+static int dhcp_socket_recv(rad_listen_t *listener)
+{
+ RADIUS_PACKET *packet;
+ dhcp_socket_t *sock;
+
+ packet = fr_dhcp_recv(listener->fd);
+ if (!packet) {
+ ERROR("%s", fr_strerror());
+ return 0;
+ }
+
+ sock = listener->data;
+ if (!request_receive(NULL, listener, packet, &sock->dhcp_client, dhcp_process)) {
+ rad_free(&packet);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/*
+ * Send an authentication response packet
+ */
+static int dhcp_socket_send(rad_listen_t *listener, REQUEST *request)
+{
+ dhcp_socket_t *sock;
+
+ rad_assert(request->listener == listener);
+ rad_assert(listener->send == dhcp_socket_send);
+
+ if (request->reply->code == 0) return 0; /* don't reply */
+
+ if (fr_dhcp_encode(request->reply) < 0) {
+ DEBUG("dhcp_socket_send: ERROR\n");
+ return -1;
+ }
+
+ sock = listener->data;
+ if (sock->suppress_responses) return 0;
+
+ return fr_dhcp_send(request->reply);
+}
+
+
+static int dhcp_socket_encode(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
+{
+ return 0;
+}
+
+
+static int dhcp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request)
+{
+ return fr_dhcp_decode(request->packet);
+}
+
+extern fr_protocol_t proto_dhcp;
+fr_protocol_t proto_dhcp = {
+ .magic = RLM_MODULE_INIT,
+ .name = "dhcp",
+ .inst_size = sizeof(dhcp_socket_t),
+ .parse = dhcp_socket_parse,
+ .recv = dhcp_socket_recv,
+ .send = dhcp_socket_send,
+ .print = common_socket_print,
+ .encode = dhcp_socket_encode,
+ .decode = dhcp_socket_decode
+};