#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/module.h>
#include <freeradius-devel/io/pair.h>
+#include <freeradius-devel/util/udp_queue.h>
#include <freeradius-devel/dhcpv4/dhcpv4.h>
+#include <freeradius-devel/unlang/module.h>
+
#include <ctype.h>
static fr_dict_t const *dict_dhcpv4;
char const *name;
char const *xlat_name;
- int fd; //!< only relay, no proxying for now
-
- fr_ipaddr_t ipaddr; //!< socket IP address
- uint16_t port; //!< socket port
-
- char const *interface; //!< Interface to bind to.
-
- uint32_t recv_buff; //!< How big the kernel's receive buffer should be.
- uint32_t send_buff; //!< How big the kernel's send buffer should be.
+ fr_udp_queue_config_t config; //!< UDP queue config
uint32_t max_packet_size; //!< Maximum packet size.
-
- bool recv_buff_is_set; //!< Whether we were provided with a recv_buf
- bool send_buff_is_set; //!< Whether we were provided with a send_buf
} rlm_dhcpv4_t;
typedef struct {
+ fr_udp_queue_t *uq; //!< udp queue handler
uint8_t *buffer; //!< for encoding packets
uint32_t buffer_size; //!< Maximum packet size.
+ uint32_t xid; //!< XID
} rlm_dhcpv4_thread_t;
static const CONF_PARSER module_config[] = {
- { FR_CONF_OFFSET("ipaddr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, ipaddr), },
- { FR_CONF_OFFSET("ipv4addr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, ipaddr) },
+ { FR_CONF_OFFSET("ipaddr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, config.ipaddr), },
+ { FR_CONF_OFFSET("ipv4addr", FR_TYPE_IPV4_ADDR, rlm_dhcpv4_t, config.ipaddr) },
- { FR_CONF_OFFSET("port", FR_TYPE_UINT16, rlm_dhcpv4_t, port), .dflt = "68" },
+ { FR_CONF_OFFSET("port", FR_TYPE_UINT16, rlm_dhcpv4_t, config.port), .dflt = "68" },
- { FR_CONF_OFFSET("interface", FR_TYPE_STRING, rlm_dhcpv4_t, interface) },
+ { FR_CONF_OFFSET("interface", FR_TYPE_STRING, rlm_dhcpv4_t, config.interface) },
- { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, rlm_dhcpv4_t, recv_buff) },
- { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, rlm_dhcpv4_t, send_buff) },
+ { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, rlm_dhcpv4_t, config.send_buff) },
{ FR_CONF_OFFSET("max_packet_size", FR_TYPE_UINT32, rlm_dhcpv4_t, max_packet_size), .dflt = "576" },
+ { FR_CONF_OFFSET("max_queued_packets", FR_TYPE_UINT32, rlm_dhcpv4_t, config.max_queued_packets), .dflt = "65536" },
+
+ { FR_CONF_OFFSET("timeout", FR_TYPE_TIME_DELTA, rlm_dhcpv4_t, config.max_queued_time), .dflt = "0" },
CONF_PARSER_TERMINATOR
};
/*
* Ensure that we have a destination address.
*/
- if (inst->ipaddr.af == AF_UNSPEC) {
+ if (inst->config.ipaddr.af == AF_UNSPEC) {
cf_log_err(conf, "A value must be given for 'ipaddr'");
return -1;
}
- if (inst->ipaddr.af != AF_INET) {
+ if (inst->config.ipaddr.af != AF_INET) {
cf_log_err(conf, "DHCPv4 can only use IPv4 addresses in 'ipaddr'");
return -1;
}
- if (!inst->port) {
+ if (!inst->config.port) {
cf_log_err(conf, "A value must be given for 'port'");
return -1;
}
/*
- * Clamp max_packet_size first before checking recv_buff and send_buff
+ * Clamp max_packet_size
*/
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, DEFAULT_PACKET_SIZE);
FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, MAX_PACKET_SIZE);
- if (inst->send_buff_is_set) {
- FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, >=, inst->max_packet_size);
- FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, <=, (1 << 30));
- }
-
return 0;
}
-/** Instantiate the module
- *
- * Instantiate I/O and type submodules.
- *
- * @param[in] instance data for this module
- * @param[in] conf our configuration section parsed to give us instance.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-static int mod_instantiate(void *instance, CONF_SECTION *conf)
-{
- rlm_dhcpv4_t *inst = talloc_get_type_abort(instance, rlm_dhcpv4_t);
-
- /*
- * Open the outgoing socket.
- */
- inst->fd = fr_socket_server_udp(&inst->ipaddr, &inst->port, NULL, true);
- if (inst->fd < 0) {
- cf_log_err(conf, "Failed opening socket: %s", fr_strerror());
- return -1;
- }
-
- /*
- * Bind to the interface, if required.
- */
- if (inst->interface) {
- if (fr_socket_bind(inst->fd, &inst->ipaddr, &inst->port, inst->interface) < 0) {
- cf_log_err(conf, "Failed binding to interface %s: %s", inst->interface, fr_strerror());
- return -1;
- }
- }
-
- fr_nonblock(inst->fd);
-
-#ifdef SO_RCVBUF
- if (inst->recv_buff_is_set) {
- int opt;
-
- opt = inst->recv_buff;
- if (setsockopt(inst->fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) {
- cf_log_warn(conf, "Failed setting 'SO_RCVBUF': %s", fr_syserror(errno));
- }
- }
-#endif
+typedef struct {
+ request_t *request;
+ bool sent;
+} rlm_dhcpv4_delay_t;
-#ifdef SO_SNDBUF
- if (inst->send_buff_is_set) {
- int opt;
+static void dhcpv4_queue_resume(bool sent, void *rctx)
+{
+ rlm_dhcpv4_delay_t *d = talloc_get_type_abort(rctx, rlm_dhcpv4_delay_t);
- opt = inst->send_buff;
- if (setsockopt(inst->fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) {
- cf_log_warn(conf, "Failed setting 'SO_SNDBUF', write performance may be sub-optimal: %s",
- fr_syserror(errno));
- }
- }
-#endif
+ d->sent = sent;
- return 0;
+ unlang_interpret_mark_runnable(d->request);
}
/** Instantiate thread data for the submodule.
*
*/
-static int mod_thread_instantiate(CONF_SECTION const *cs, void *instance, UNUSED fr_event_list_t *el, void *thread)
+static int mod_thread_instantiate(CONF_SECTION const *cs, void *instance, fr_event_list_t *el, void *thread)
{
rlm_dhcpv4_t *inst = talloc_get_type_abort(instance, rlm_dhcpv4_t);
rlm_dhcpv4_thread_t *t = talloc_get_type_abort(thread, rlm_dhcpv4_thread_t);
t->buffer_size = inst->max_packet_size;
+ t->uq = fr_udp_queue_alloc(t, &inst->config, el, dhcpv4_queue_resume);
+ if (!t->uq) {
+ cf_log_err(cs, "Failed allocating outbound udp queue - %s", fr_strerror());
+ return -1;
+ }
+
return 0;
}
+static unlang_action_t dhcpv4_resume(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+ UNUSED request_t *request, void *rctx)
+{
+ rlm_dhcpv4_delay_t *d = talloc_get_type_abort(rctx, rlm_dhcpv4_delay_t);
+
+ if (!d->sent) {
+ talloc_free(d);
+ RETURN_MODULE_FAIL;
+ }
+
+ talloc_free(d);
+ RETURN_MODULE_OK;
+}
+
+
/** Send packets outbound.
*
*/
static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
{
- rlm_dhcpv4_t *inst = talloc_get_type_abort(mctx->instance, rlm_dhcpv4_t);
rlm_dhcpv4_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_dhcpv4_thread_t);
ssize_t data_len;
dhcp_packet_t *original = (dhcp_packet_t *) request->packet->data;
dhcp_packet_t *packet;
- struct sockaddr_storage sockaddr;
- socklen_t socklen;
+
uint32_t xid;
fr_pair_t *vp;
- int code, port;
+ int code, port, rcode;
- /*
- * Discard any incoming packets, as we don't care about
- * them.
- *
- * @todo - maybe set up a thread listener to do this, if
- * we care.
- */
- while (read(inst->fd, t->buffer, t->buffer_size) > 0) {
- /* do nothing with the data, we don't care */
- }
+ rlm_dhcpv4_delay_t *d;
/*
* We can only send relayed packets, which have a gateway IP
xid = ntohl(original->xid);
} else {
- xid = fr_rand(); /* shouldn't happen, as we're relaying packets, not creating them (yet) */
+ xid = t->xid++;
}
/*
RETURN_MODULE_NOOP;
}
- fr_ipaddr_to_sockaddr(&sockaddr, &socklen, &vp->vp_ip, port);
-
/*
* Encode the packet using the original information.
*/
FR_PROTO_HEX_DUMP(t->buffer, data_len, "DHCPv4");
- /*
- * Send the packet, via "fire and forget".
- */
- if (sendto(inst->fd, t->buffer, data_len, 0, (struct sockaddr *) &sockaddr, socklen) < 0) {
- REDEBUG("Failed sending packet to %pV: %s", &vp->data, fr_syserror(errno));
+ d = talloc_zero(request, rlm_dhcpv4_delay_t);
+ if (!d) RETURN_MODULE_FAIL;
+
+ *d = (rlm_dhcpv4_delay_t) {
+ .request = request,
+ .sent = false,
+ };
+
+ rcode = fr_udp_queue_write(d, t->uq, t->buffer, data_len, &vp->vp_ip, port, d);
+ if (rcode > 0) {
+ talloc_free(d);
+ RETURN_MODULE_OK;
+ }
+ if (rcode < 0) {
+ talloc_free(d);
RETURN_MODULE_FAIL;
}
- RETURN_MODULE_OK;
+ return unlang_module_yield(request, dhcpv4_resume, NULL, d);
}
extern module_t rlm_dhcpv4;
.name = "dhcpv4",
.inst_size = sizeof(rlm_dhcpv4_t),
.bootstrap = mod_bootstrap,
- .instantiate = mod_instantiate,
.config = module_config,