]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
remove last bits of rlm_radius_udp
authorAlan T. DeKok <aland@freeradius.org>
Tue, 24 Dec 2024 13:27:11 +0000 (08:27 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 24 Dec 2024 13:27:11 +0000 (08:27 -0500)
src/modules/rlm_radius/bio.c
src/modules/rlm_radius/rlm_radius_udp.c [deleted file]
src/modules/rlm_radius/rlm_radius_udp.mk [deleted file]

index 89b4aa7b8d38ea9cdd7af85eb5de829070f6e0a4..a2b59d896239cd2051b25b724b40286ff751db31 100644 (file)
@@ -16,8 +16,8 @@
 
 /**
  * $Id$
- * @file rlm_radius_udp.c
- * @brief RADIUS UDP transport
+ * @file src/modules/rlm_radius/bio.c
+ * @brief RADIUS BIO transport
  *
  * @copyright 2017 Network RADIUS SAS
  * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
@@ -846,7 +846,7 @@ static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uc
                fr_assert_fail("%u tracking entries still allocated at conn close", h->tt->num_requests);
        }
 
-       DEBUG4("Freeing rlm_radius_udp handle %p", handle);
+       DEBUG4("Freeing handle %p", handle);
 
        talloc_free(h);
 }
diff --git a/src/modules/rlm_radius/rlm_radius_udp.c b/src/modules/rlm_radius/rlm_radius_udp.c
deleted file mode 100644 (file)
index 5b442d5..0000000
+++ /dev/null
@@ -1,2556 +0,0 @@
-/*
- *   This program is 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
- */
-
-/**
- * $Id$
- * @file rlm_radius_udp.c
- * @brief RADIUS UDP transport
- *
- * @copyright 2017 Network RADIUS SAS
- * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- */
-RCSID("$Id$")
-
-#include <freeradius-devel/io/application.h>
-#include <freeradius-devel/io/listen.h>
-#include <freeradius-devel/io/pair.h>
-#include <freeradius-devel/missing.h>
-#include <freeradius-devel/server/connection.h>
-#include <freeradius-devel/util/debug.h>
-#include <freeradius-devel/util/heap.h>
-#include <freeradius-devel/util/udp.h>
-
-#include <freeradius-devel/unlang/module.h>
-
-#include <sys/socket.h>
-
-#include "rlm_radius.h"
-#include "track.h"
-
-/*
- * Macro to simplify checking packets before calling decode(), so that
- * it gets a known valid length and no longer calls fr_radius_ok() itself.
- */
-#define check(_handle, _len_p) fr_radius_ok((_handle)->buffer, (size_t *)(_len_p), \
-                                           (_handle)->thread->inst->parent->max_attributes, false, NULL)
-
-/** Static configuration for the module.
- *
- */
-typedef struct {
-       rlm_radius_t            *parent;                //!< rlm_radius instance.
-       CONF_SECTION            *config;
-
-       fr_ipaddr_t             dst_ipaddr;             //!< IP of the home server.
-       fr_ipaddr_t             src_ipaddr;             //!< IP we open our socket on.
-       uint16_t                dst_port;               //!< Port of the home server.
-       char const              *secret;                //!< Shared secret.
-
-       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.
-
-       uint32_t                max_packet_size;        //!< Maximum packet size.
-       uint16_t                max_send_coalesce;      //!< Maximum number of packets to coalesce into one mmsg call.
-
-       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
-       bool                    replicate;              //!< Copied from parent->replicate
-
-       fr_radius_ctx_t         common_ctx;
-
-       trunk_conf_t            trunk_conf;             //!< trunk configuration
-
-       fr_retry_config_t       retry_config;           //!< for originating packets
-} rlm_radius_udp_t;
-
-typedef struct {
-       fr_event_list_t         *el;                    //!< Event list.
-
-       rlm_radius_udp_t const  *inst;                  //!< our instance
-
-       trunk_t                 *trunk;                 //!< trunk handler
-} udp_thread_t;
-
-typedef struct {
-       trunk_request_t         *treq;
-       rlm_rcode_t             rcode;                  //!< from the transport
-       bool                    is_retry;
-} udp_result_t;
-
-typedef struct udp_request_s udp_request_t;
-
-typedef struct {
-       struct iovec            out;                    //!< Describes buffer to send.
-       trunk_request_t *treq;                          //!< Used for signalling.
-} udp_coalesced_t;
-
-/** Track the handle, which is tightly correlated with the FD
- *
- */
-typedef struct {
-       char const              *name;                  //!< From IP PORT to IP PORT.
-       char const              *module_name;           //!< the module that opened the connection
-
-       int                     fd;                     //!< File descriptor.
-
-       struct mmsghdr          *mmsgvec;               //!< Vector of inbound/outbound packets.
-       udp_coalesced_t         *coalesced;             //!< Outbound coalesced requests.
-
-       size_t                  send_buff_actual;       //!< What we believe the maximum SO_SNDBUF size to be.
-                                                       ///< We don't try and encode more packet data than this
-                                                       ///< in one go.
-
-       rlm_radius_udp_t const  *inst;                  //!< Our module instance.
-       udp_thread_t            *thread;
-
-       uint8_t                 last_id;                //!< Used when replicating to ensure IDs are distributed
-                                                       ///< evenly.
-
-       uint32_t                max_packet_size;        //!< Our max packet size. may be different from the parent.
-
-       fr_ipaddr_t             src_ipaddr;             //!< Source IP address.  May be altered on bind
-                                                       //!< to be the actual IP address packets will be
-                                                       //!< sent on.  This is why we can't use the inst
-                                                       //!< src_ipaddr field.
-       uint16_t                src_port;               //!< Source port specific to this connection.
-
-       uint8_t                 *buffer;                //!< Receive buffer.
-       size_t                  buflen;                 //!< Receive buffer length.
-
-       radius_track_t          *tt;                    //!< RADIUS ID tracking structure.
-
-       fr_time_t               mrs_time;               //!< Most recent sent time which had a reply.
-       fr_time_t               last_reply;             //!< When we last received a reply.
-       fr_time_t               first_sent;             //!< first time we sent a packet since going idle
-       fr_time_t               last_sent;              //!< last time we sent a packet.
-       fr_time_t               last_idle;              //!< last time we had nothing to do
-
-       fr_event_timer_t const  *zombie_ev;             //!< Zombie timeout.
-
-       bool                    status_checking;        //!< whether we're doing status checks
-       udp_request_t           *status_u;              //!< for sending status check packets
-       udp_result_t            *status_r;              //!< for faking out status checks as real packets
-       request_t               *status_request;
-} udp_handle_t;
-
-
-/** Connect request_t to local tracking structure
- *
- */
-struct udp_request_s {
-       uint32_t                priority;               //!< copied from request->async->priority
-       fr_time_t               recv_time;              //!< copied from request->async->recv_time
-
-       uint32_t                num_replies;            //!< number of reply packets, sent is in retry.count
-
-       bool                    synchronous;            //!< cached from inst->parent->synchronous
-       bool                    require_message_authenticator;          //!< saved from the original packet.
-       bool                    status_check;           //!< is this packet a status check?
-
-       fr_pair_list_t          extra;                  //!< VPs for debugging, like Proxy-State.
-
-       uint8_t                 code;                   //!< Packet code.
-       uint8_t                 id;                     //!< Last ID assigned to this packet.
-       uint8_t                 *packet;                //!< Packet we write to the network.
-       size_t                  packet_len;             //!< Length of the packet.
-
-       radius_track_entry_t    *rr;                    //!< ID tracking, resend count, etc.
-       fr_event_timer_t const  *ev;                    //!< timer for retransmissions
-       fr_retry_t              retry;                  //!< retransmission timers
-};
-
-static const conf_parser_t module_config[] = {
-       { FR_CONF_OFFSET_TYPE_FLAGS("ipaddr", FR_TYPE_COMBO_IP_ADDR, 0, rlm_radius_udp_t, dst_ipaddr), },
-       { FR_CONF_OFFSET_TYPE_FLAGS("ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_radius_udp_t, dst_ipaddr) },
-       { FR_CONF_OFFSET_TYPE_FLAGS("ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_radius_udp_t, dst_ipaddr) },
-
-       { FR_CONF_OFFSET("port", rlm_radius_udp_t, dst_port) },
-
-       { FR_CONF_OFFSET_FLAGS("secret", CONF_FLAG_REQUIRED, rlm_radius_udp_t, secret) },
-
-       { FR_CONF_OFFSET("interface", rlm_radius_udp_t, interface) },
-
-       { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, 0, rlm_radius_udp_t, recv_buff) },
-       { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, 0, rlm_radius_udp_t, send_buff) },
-
-       { FR_CONF_OFFSET("max_packet_size", rlm_radius_udp_t, max_packet_size), .dflt = "4096" },
-       { FR_CONF_OFFSET("max_send_coalesce", rlm_radius_udp_t, max_send_coalesce), .dflt = "1024" },
-
-       { FR_CONF_OFFSET_TYPE_FLAGS("src_ipaddr", FR_TYPE_COMBO_IP_ADDR, 0, rlm_radius_udp_t, src_ipaddr) },
-       { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_radius_udp_t, src_ipaddr) },
-       { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_radius_udp_t, src_ipaddr) },
-
-       CONF_PARSER_TERMINATOR
-};
-
-static fr_dict_t const *dict_radius;
-
-extern fr_dict_autoload_t rlm_radius_udp_dict[];
-fr_dict_autoload_t rlm_radius_udp_dict[] = {
-       { .out = &dict_radius, .proto = "radius" },
-       { NULL }
-};
-
-static fr_dict_attr_t const *attr_error_cause;
-static fr_dict_attr_t const *attr_event_timestamp;
-static fr_dict_attr_t const *attr_extended_attribute_1;
-static fr_dict_attr_t const *attr_message_authenticator;
-static fr_dict_attr_t const *attr_eap_message;
-static fr_dict_attr_t const *attr_nas_identifier;
-static fr_dict_attr_t const *attr_original_packet_code;
-static fr_dict_attr_t const *attr_proxy_state;
-static fr_dict_attr_t const *attr_response_length;
-static fr_dict_attr_t const *attr_user_password;
-static fr_dict_attr_t const *attr_packet_type;
-
-extern fr_dict_attr_autoload_t rlm_radius_udp_dict_attr[];
-fr_dict_attr_autoload_t rlm_radius_udp_dict_attr[] = {
-       { .out = &attr_error_cause, .name = "Error-Cause", .type = FR_TYPE_UINT32, .dict = &dict_radius },
-       { .out = &attr_event_timestamp, .name = "Event-Timestamp", .type = FR_TYPE_DATE, .dict = &dict_radius},
-       { .out = &attr_extended_attribute_1, .name = "Extended-Attribute-1", .type = FR_TYPE_TLV, .dict = &dict_radius},
-       { .out = &attr_message_authenticator, .name = "Message-Authenticator", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
-       { .out = &attr_eap_message, .name = "EAP-Message", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
-       { .out = &attr_nas_identifier, .name = "NAS-Identifier", .type = FR_TYPE_STRING, .dict = &dict_radius},
-       { .out = &attr_original_packet_code, .name = "Extended-Attribute-1.Original-Packet-Code", .type = FR_TYPE_UINT32, .dict = &dict_radius},
-       { .out = &attr_proxy_state, .name = "Proxy-State", .type = FR_TYPE_OCTETS, .dict = &dict_radius},
-       { .out = &attr_response_length, .name = "Extended-Attribute-1.Response-Length", .type = FR_TYPE_UINT32, .dict = &dict_radius },
-       { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius},
-       { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
-       { NULL }
-};
-
-/** Turn a reply code into a module rcode;
- *
- */
-static rlm_rcode_t radius_code_to_rcode[FR_RADIUS_CODE_MAX] = {
-       [FR_RADIUS_CODE_ACCESS_ACCEPT]          = RLM_MODULE_OK,
-       [FR_RADIUS_CODE_ACCESS_CHALLENGE]       = RLM_MODULE_UPDATED,
-       [FR_RADIUS_CODE_ACCESS_REJECT]          = RLM_MODULE_REJECT,
-
-       [FR_RADIUS_CODE_ACCOUNTING_RESPONSE]    = RLM_MODULE_OK,
-
-       [FR_RADIUS_CODE_COA_ACK]                = RLM_MODULE_OK,
-       [FR_RADIUS_CODE_COA_NAK]                = RLM_MODULE_REJECT,
-
-       [FR_RADIUS_CODE_DISCONNECT_ACK] = RLM_MODULE_OK,
-       [FR_RADIUS_CODE_DISCONNECT_NAK] = RLM_MODULE_REJECT,
-
-       [FR_RADIUS_CODE_PROTOCOL_ERROR] = RLM_MODULE_HANDLED,
-};
-
-static void            conn_writable_status_check(UNUSED fr_event_list_t *el, UNUSED int fd,
-                                                  UNUSED int flags, void *uctx);
-
-static int             encode(rlm_radius_udp_t const *inst, request_t *request, udp_request_t *u, uint8_t id);
-
-static fr_radius_decode_fail_t decode(TALLOC_CTX *ctx, fr_pair_list_t *reply, uint8_t *response_code,
-                              udp_handle_t *h, request_t *request, udp_request_t *u,
-                              uint8_t const request_authenticator[static RADIUS_AUTH_VECTOR_LENGTH],
-                              uint8_t *data, size_t data_len);
-
-static void            protocol_error_reply(udp_request_t *u, udp_result_t *r, udp_handle_t *h);
-
-static unlang_action_t mod_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request);
-static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action);
-
-
-#ifndef NDEBUG
-/** Log additional information about a tracking entry
- *
- * @param[in] te       Tracking entry we're logging information for.
- * @param[in] log      destination.
- * @param[in] log_type Type of log message.
- * @param[in] file     the logging request was made in.
- * @param[in] line     logging request was made on.
- */
-static void udp_tracking_entry_log(fr_log_t const *log, fr_log_type_t log_type, char const *file, int line,
-                                  radius_track_entry_t *te)
-{
-       request_t                       *request;
-
-       if (!te->request) return;       /* Free entry */
-
-       request = talloc_get_type_abort(te->request, request_t);
-
-       fr_log(log, log_type, file, line, "request %s, allocated %s:%u", request->name,
-              request->alloc_file, request->alloc_line);
-
-       trunk_request_state_log(log, log_type, file, line, talloc_get_type_abort(te->uctx, trunk_request_t));
-}
-#endif
-
-/** Clear out any connection specific resources from a udp request
- *
- */
-static void udp_request_reset(udp_request_t *u)
-{
-       TALLOC_FREE(u->packet);
-       fr_pair_list_init(&u->extra);   /* Freed with packet */
-
-       /*
-        *      Can have packet put no u->rr
-        *      if this is part of a pre-trunk status check.
-        */
-       if (u->rr) radius_track_entry_release(&u->rr);
-}
-
-/** Reset a status_check packet, ready to reuse
- *
- */
-static void status_check_reset(udp_handle_t *h, udp_request_t *u)
-{
-       fr_assert(u->status_check == true);
-
-       h->status_checking = false;
-       u->num_replies = 0;     /* Reset */
-       u->retry.start = fr_time_wrap(0);
-
-       if (u->ev) (void) fr_event_timer_delete(&u->ev);
-
-       udp_request_reset(u);
-}
-
-/*
- *     Status-Server checks.  Manually build the packet, and
- *     all of its associated glue.
- */
-static void CC_HINT(nonnull) status_check_alloc(udp_handle_t *h)
-{
-       udp_request_t           *u;
-       request_t               *request;
-       rlm_radius_udp_t const  *inst = h->inst;
-       map_t                   *map = NULL;
-
-       fr_assert(!h->status_u && !h->status_r && !h->status_request);
-
-       u = talloc_zero(h, udp_request_t);
-       fr_pair_list_init(&u->extra);
-
-       /*
-        *      Status checks are prioritized over any other packet
-        */
-       u->priority = ~(uint32_t) 0;
-       u->status_check = true;
-
-       /*
-        *      Allocate outside of the free list.
-        *      There appears to be an issue where
-        *      the thread destructor runs too
-        *      early, and frees the freelist's
-        *      head before the module destructor
-        *      runs.
-        */
-       request = request_local_alloc_external(u, NULL);
-       request->async = talloc_zero(request, fr_async_t);
-       talloc_const_free(request->name);
-       request->name = talloc_strdup(request, h->module_name);
-
-       request->packet = fr_packet_alloc(request, false);
-       request->reply = fr_packet_alloc(request, false);
-
-       /*
-        *      Create the VPs, and ignore any errors
-        *      creating them.
-        */
-       while ((map = map_list_next(&inst->parent->status_check_map, map))) {
-               /*
-                *      Skip things which aren't attributes.
-                */
-               if (!tmpl_is_attr(map->lhs)) continue;
-
-               /*
-                *      Ignore internal attributes.
-                */
-               if (tmpl_attr_tail_da(map->lhs)->flags.internal) continue;
-
-               /*
-                *      Ignore signalling attributes.  They shouldn't exist.
-                */
-               if ((tmpl_attr_tail_da(map->lhs) == attr_proxy_state) ||
-                   (tmpl_attr_tail_da(map->lhs) == attr_message_authenticator)) continue;
-
-               /*
-                *      Allow passwords only in Access-Request packets.
-                */
-               if ((inst->parent->status_check != FR_RADIUS_CODE_ACCESS_REQUEST) &&
-                   (tmpl_attr_tail_da(map->lhs) == attr_user_password)) continue;
-
-               (void) map_to_request(request, map, map_to_vp, NULL);
-       }
-
-       /*
-        *      Ensure that there's a NAS-Identifier, if one wasn't
-        *      already added.
-        */
-       if (!fr_pair_find_by_da(&request->request_pairs, NULL, attr_nas_identifier)) {
-               fr_pair_t *vp;
-
-               MEM(pair_append_request(&vp, attr_nas_identifier) >= 0);
-               fr_pair_value_strdup(vp, "status check - are you alive?", false);
-       }
-
-       /*
-        *      Always add an Event-Timestamp, which will be the time
-        *      at which the first packet is sent.  Or for
-        *      Status-Server, the time of the current packet.
-        */
-       if (!fr_pair_find_by_da(&request->request_pairs, NULL, attr_event_timestamp)) {
-               MEM(pair_append_request(NULL, attr_event_timestamp) >= 0);
-       }
-
-       /*
-        *      Initialize the request IO ctx.  Note that we don't set
-        *      destructors.
-        */
-       u->code = inst->parent->status_check;
-       request->packet->code = u->code;
-
-       DEBUG3("%s - Status check packet type will be %s", h->module_name, fr_radius_packet_name[u->code]);
-       log_request_pair_list(L_DBG_LVL_3, request, NULL, &request->request_pairs, NULL);
-
-       MEM(h->status_r = talloc_zero(request, udp_result_t));
-       h->status_u = u;
-       h->status_request = request;
-}
-
-/** Connection errored
- *
- * We were signalled by the event loop that a fatal error occurred on this connection.
- *
- * @param[in] el       The event list signalling.
- * @param[in] fd       that errored.
- * @param[in] flags    El flags.
- * @param[in] fd_errno The nature of the error.
- * @param[in] uctx     The trunk connection handle (tconn).
- */
-static void conn_error_status_check(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
-{
-       connection_t            *conn = talloc_get_type_abort(uctx, connection_t);
-       udp_handle_t            *h;
-
-       /*
-        *      Connection must be in the connecting state when this fires
-        */
-       fr_assert(conn->state == CONNECTION_STATE_CONNECTING);
-
-       h = talloc_get_type_abort(conn->h, udp_handle_t);
-
-       ERROR("%s - Connection %s failed: %s", h->module_name, h->name, fr_syserror(fd_errno));
-
-       connection_signal_reconnect(conn, CONNECTION_FAILED);
-}
-
-/** Status check timer when opening the connection for the first time.
- *
- * Setup retries, or fail the connection.
- */
-static void conn_status_check_timeout(fr_event_list_t *el, fr_time_t now, void *uctx)
-{
-       connection_t            *conn = talloc_get_type_abort(uctx, connection_t);
-       udp_handle_t            *h;
-       udp_request_t           *u;
-
-       /*
-        *      Connection must be in the connecting state when this fires
-        */
-       fr_assert(conn->state == CONNECTION_STATE_CONNECTING);
-
-       h = talloc_get_type_abort(conn->h, udp_handle_t);
-       u = h->status_u;
-
-       /*
-        *      We're only interested in contiguous, good, replies.
-        */
-       u->num_replies = 0;
-
-       switch (fr_retry_next(&u->retry, now)) {
-       case FR_RETRY_MRD:
-               DEBUG("%s - Reached maximum_retransmit_duration (%pVs > %pVs), failing status checks",
-                     h->module_name, fr_box_time_delta(fr_time_sub(now, u->retry.start)),
-                     fr_box_time_delta(u->retry.config->mrd));
-               goto fail;
-
-       case FR_RETRY_MRC:
-               DEBUG("%s - Reached maximum_retransmit_count (%u > %u), failing status checks",
-                     h->module_name, u->retry.count, u->retry.config->mrc);
-       fail:
-               connection_signal_reconnect(conn, CONNECTION_FAILED);
-               return;
-
-       case FR_RETRY_CONTINUE:
-               if (fr_event_fd_insert(h, NULL, el, h->fd, conn_writable_status_check, NULL,
-                                      conn_error_status_check, conn) < 0) {
-                       PERROR("%s - Failed inserting FD event", h->module_name);
-                       connection_signal_reconnect(conn, CONNECTION_FAILED);
-               }
-               return;
-       }
-
-       fr_assert(0);
-}
-
-/** Send the next status check packet
- *
- */
-static void conn_status_check_again(fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
-{
-       connection_t            *conn = talloc_get_type_abort(uctx, connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-
-       if (fr_event_fd_insert(h, NULL, el, h->fd, conn_writable_status_check, NULL, conn_error_status_check, conn) < 0) {
-               PERROR("%s - Failed inserting FD event", h->module_name);
-               connection_signal_reconnect(conn, CONNECTION_FAILED);
-       }
-}
-
-/** Read the incoming status-check response.  If it's correct mark the connection as connected
- *
- */
-static void conn_readable_status_check(fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
-{
-       connection_t            *conn = talloc_get_type_abort(uctx, connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-       trunk_t                 *trunk = h->thread->trunk;
-       rlm_radius_t const      *inst = h->inst->parent;
-       udp_request_t           *u = h->status_u;
-       ssize_t                 slen;
-       fr_pair_list_t          reply;
-       uint8_t                 code = 0;
-
-       fr_pair_list_init(&reply);
-       slen = read(h->fd, h->buffer, h->buflen);
-       if (slen == 0) return;
-
-       if (slen < 0) {
-               switch (errno) {
-#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
-               case EWOULDBLOCK:
-#endif
-               case EAGAIN:
-               case EINTR:
-                       return;         /* Wait to be signalled again */
-
-               case ECONNREFUSED:
-                       ERROR("%s - Failed reading response from socket: there is no server listening on %pV port %u",
-                             h->module_name, fr_box_ipaddr(h->inst->dst_ipaddr), h->inst->dst_port);
-                       break;
-
-               default:
-                       ERROR("%s - Failed reading response from socket: %s",
-                             h->module_name, fr_syserror(errno));
-                       break;
-               }
-
-               connection_signal_reconnect(conn, CONNECTION_FAILED);
-               return;
-       }
-
-       /*
-        *      Where we just return in this function, we're letting
-        *      the response timer take care of progressing the
-        *      connection attempt.
-        */
-       if (slen < RADIUS_HEADER_LENGTH) {
-               ERROR("%s - Packet too short, expected at least %zu bytes got %zd bytes",
-                     h->module_name, (size_t)RADIUS_HEADER_LENGTH, slen);
-               return;
-       }
-
-       if (u->id != h->buffer[1]) {
-               ERROR("%s - Received response with incorrect or expired ID.  Expected %u, got %u",
-                     h->module_name, u->id, h->buffer[1]);
-               return;
-       }
-
-       if (!check(h, &slen)) return;
-
-       if (decode(h, &reply, &code,
-                  h, h->status_request, h->status_u, u->packet + RADIUS_AUTH_VECTOR_OFFSET,
-                  h->buffer, slen) != DECODE_FAIL_NONE) return;
-
-       fr_pair_list_free(&reply);      /* FIXME - Do something with these... */
-
-       /*
-        *      Process the error, and count this as a success.
-        *      This is usually used for dynamic configuration
-        *      on startup.
-        */
-       if (code == FR_RADIUS_CODE_PROTOCOL_ERROR) protocol_error_reply(u, NULL, h);
-
-       /*
-        *      Last trunk event was a failure, be more careful about
-        *      bringing up the connection (require multiple responses).
-        */
-       if ((fr_time_gt(trunk->last_failed, fr_time_wrap(0)) && (fr_time_gt(trunk->last_failed, trunk->last_connected))) &&
-           (u->num_replies < inst->num_answers_to_alive)) {
-               /*
-                *      Leave the timer in place.  This timer is BOTH when we
-                *      give up on the current status check, AND when we send
-                *      the next status check.
-                */
-               DEBUG("%s - Received %u / %u replies for status check, on connection - %s",
-                     h->module_name, u->num_replies, inst->num_answers_to_alive, h->name);
-               DEBUG("%s - Next status check packet will be in %pVs",
-                     h->module_name, fr_box_time_delta(fr_time_sub(u->retry.next, fr_time())));
-
-               /*
-                *      Set the timer for the next retransmit.
-                */
-               if (fr_event_timer_at(h, el, &u->ev, u->retry.next, conn_status_check_again, conn) < 0) {
-                       connection_signal_reconnect(conn, CONNECTION_FAILED);
-               }
-               return;
-       }
-
-       /*
-        *      It's alive!
-        */
-       status_check_reset(h, u);
-
-       DEBUG("%s - Connection open - %s", h->module_name, h->name);
-
-       connection_signal_connected(conn);
-}
-
-/** Send our status-check packet as soon as the connection becomes writable
- *
- */
-static void conn_writable_status_check(fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
-{
-       connection_t            *conn = talloc_get_type_abort(uctx, connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-       udp_request_t           *u = h->status_u;
-       ssize_t                 slen;
-
-       if (fr_time_eq(u->retry.start, fr_time_wrap(0))) {
-               u->id = fr_rand() & 0xff;       /* We don't care what the value is here */
-               h->status_checking = true;      /* Ensure this is valid */
-               fr_retry_init(&u->retry, fr_time(), &h->inst->parent->retry[u->code]);
-
-       /*
-        *      Status checks can never be retransmitted
-        *      So increment the ID here.
-        */
-       } else {
-               udp_request_reset(u);
-               u->id++;
-       }
-
-       DEBUG("%s - Sending %s ID %d over connection %s",
-             h->module_name, fr_radius_packet_name[u->code], u->id, h->name);
-
-       if (encode(h->inst, h->status_request, u, u->id) < 0) {
-       fail:
-               connection_signal_reconnect(conn, CONNECTION_FAILED);
-               return;
-       }
-       DEBUG3("Encoded packet");
-       HEXDUMP3(u->packet, u->packet_len, NULL);
-
-       slen = write(h->fd, u->packet, u->packet_len);
-       if (slen < 0) {
-               ERROR("%s - Failed sending %s ID %d length %ld over connection %s: %s",
-                     h->module_name, fr_radius_packet_name[u->code], u->id, u->packet_len, h->name, fr_syserror(errno));
-               goto fail;
-       }
-       fr_assert((size_t)slen == u->packet_len);
-
-       /*
-        *      Switch to waiting on read and insert the event
-        *      for the response timeout.
-        */
-       if (fr_event_fd_insert(h, NULL, conn->el, h->fd, conn_readable_status_check, NULL, conn_error_status_check, conn) < 0) {
-               PERROR("%s - Failed inserting FD event", h->module_name);
-               goto fail;
-       }
-
-       DEBUG("%s - %s request.  Expecting response within %pVs",
-             h->module_name, (u->retry.count == 1) ? "Originated" : "Retransmitted",
-             fr_box_time_delta(u->retry.rt));
-
-       if (fr_event_timer_at(u, el, &u->ev, u->retry.next, conn_status_check_timeout, conn) < 0) {
-               PERROR("%s - Failed inserting timer event", h->module_name);
-               goto fail;
-       }
-}
-
-/** Free a connection handle, closing associated resources
- *
- */
-static int _udp_handle_free(udp_handle_t *h)
-{
-       fr_assert(h->fd >= 0);
-
-       if (h->status_u) fr_event_timer_delete(&h->status_u->ev);
-
-       fr_event_fd_delete(h->thread->el, h->fd, FR_EVENT_FILTER_IO);
-
-       if (shutdown(h->fd, SHUT_RDWR) < 0) {
-               DEBUG3("%s - Failed shutting down connection %s: %s",
-                      h->module_name, h->name, fr_syserror(errno));
-       }
-
-       if (close(h->fd) < 0) {
-               DEBUG3("%s - Failed closing connection %s: %s",
-                      h->module_name, h->name, fr_syserror(errno));
-       }
-
-       h->fd = -1;
-
-       DEBUG("%s - Connection closed - %s", h->module_name, h->name);
-
-       return 0;
-}
-
-/** Initialise a new outbound connection
- *
- * @param[out] h_out   Where to write the new file descriptor.
- * @param[in] conn     to initialise.
- * @param[in] uctx     A #udp_thread_t
- */
-CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
-static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx)
-{
-       int                     fd;
-       udp_handle_t            *h;
-       udp_thread_t            *thread = talloc_get_type_abort(uctx, udp_thread_t);
-       uint16_t                i;
-
-       MEM(h = talloc_zero(conn, udp_handle_t));
-       h->thread = thread;
-       h->inst = thread->inst;
-       h->module_name = h->inst->parent->name;
-       h->src_ipaddr = h->inst->src_ipaddr;
-       h->src_port = 0;
-       h->max_packet_size = h->inst->max_packet_size;
-       h->last_idle = fr_time();
-
-       /*
-        *      mmsgvec is pre-populated with pointers
-        *      to the iovec structs in coalesced, so we
-        *      just need to setup the iovec, and pass how
-        *      many messages we want to send to sendmmsg.
-        */
-       h->mmsgvec = talloc_zero_array(h, struct mmsghdr, h->inst->max_send_coalesce);
-       h->coalesced = talloc_zero_array(h, udp_coalesced_t, h->inst->max_send_coalesce);
-       for (i = 0; i < h->inst->max_send_coalesce; i++) {
-               h->mmsgvec[i].msg_hdr.msg_iov = &h->coalesced[i].out;
-               h->mmsgvec[i].msg_hdr.msg_iovlen = 1;
-       }
-
-       MEM(h->buffer = talloc_array(h, uint8_t, h->max_packet_size));
-       h->buflen = h->max_packet_size;
-
-       MEM(h->tt = radius_track_alloc(h));
-
-       /*
-        *      Open the outgoing socket.
-        */
-       fd = fr_socket_client_udp(h->inst->interface, &h->src_ipaddr, &h->src_port,
-                                 &h->inst->dst_ipaddr, h->inst->dst_port, true);
-       if (fd < 0) {
-               PERROR("%s - Failed opening socket", h->module_name);
-       fail:
-               talloc_free(h);
-               return CONNECTION_STATE_FAILED;
-       }
-
-       /*
-        *      Set the connection name.
-        */
-       h->name = fr_asprintf(h, "proto udp local %pV port %u remote %pV port %u",
-                             fr_box_ipaddr(h->src_ipaddr), h->src_port,
-                             fr_box_ipaddr(h->inst->dst_ipaddr), h->inst->dst_port);
-
-       talloc_set_destructor(h, _udp_handle_free);
-
-#ifdef SO_RCVBUF
-       if (h->inst->recv_buff_is_set) {
-               int opt;
-
-               opt = h->inst->recv_buff;
-               if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) {
-                       WARN("%s - Failed setting 'SO_RCVBUF': %s", h->module_name, fr_syserror(errno));
-               }
-       }
-#endif
-
-#ifdef SO_SNDBUF
-       {
-               int opt;
-               socklen_t socklen = sizeof(int);
-
-               if (h->inst->send_buff_is_set) {
-                       opt = h->inst->send_buff;
-                       if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) {
-                               WARN("%s - Failed setting 'SO_SNDBUF', write performance may be sub-optimal: %s",
-                                    h->module_name, fr_syserror(errno));
-                       }
-               }
-
-               if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, &socklen) < 0) {
-                       WARN("%s - Failed getting 'SO_SNDBUF', write performance may be sub-optimal: %s",
-                            h->module_name, fr_syserror(errno));
-
-                       /*
-                        *      This controls how many packets we attempt
-                        *      to send at once.  Nothing bad happens if
-                        *      we get it wrong, but the user may see
-                        *      ENOBUFS errors at high packet rates.
-                        */
-                       h->send_buff_actual = h->inst->send_buff_is_set ?
-                                             h->inst->send_buff : h->max_packet_size * h->inst->max_send_coalesce;
-
-                       WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name,
-                            h->send_buff_actual);
-               } else {
-#ifdef __linux__
-                       /*
-                        *      Linux doubles the buffer when you set it
-                        *      to account for "overhead".
-                        */
-                       h->send_buff_actual = ((size_t)opt) / 2;
-#else
-                       h->send_buff_actual = (size_t)opt;
-#endif
-               }
-       }
-#else
-       h->send_buff_actual = h->inst->send_buff_is_set ?
-                             h->inst_send_buff : h->max_packet_size * h->inst->max_send_coalesce;
-
-       WARN("%s - Modifying 'SO_SNDBUF' value is not supported on this system, "
-            "write performance may be sub-optimal", h->module_name);
-       WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name, h->inst->send_buff_actual);
-#endif
-
-       h->fd = fd;
-
-       /*
-        *      If we're doing status checks, then we want at least
-        *      one positive response before signalling that the
-        *      connection is open.
-        *
-        *      To do this we install special I/O handlers that
-        *      only signal the connection as open once we get a
-        *      status-check response.
-        */
-       if (h->inst->parent->status_check) {
-               status_check_alloc(h);
-
-               /*
-                *      Start status checking.
-                *
-                *      If we've had no recent failures we need exactly
-                *      one response to bring the connection online,
-                *      otherwise we need inst->num_answers_to_alive
-                */
-               if (fr_event_fd_insert(h, NULL, conn->el, h->fd, NULL,
-                                      conn_writable_status_check, conn_error_status_check, conn) < 0) goto fail;
-       /*
-        *      If we're not doing status-checks, signal the connection
-        *      as open as soon as it becomes writable.
-        */
-       } else {
-               connection_signal_on_fd(conn, fd);
-       }
-
-       *h_out = h;
-
-       // @todo - initialize the tracking memory, etc.
-       // i.e. histograms (or hyperloglog) of packets, so we can see
-       // which connections / home servers are fast / slow.
-
-       return CONNECTION_STATE_CONNECTING;
-}
-
-/** Shutdown/close a file descriptor
- *
- */
-static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx)
-{
-       udp_handle_t *h = talloc_get_type_abort(handle, udp_handle_t);
-
-       /*
-        *      There's tracking entries still allocated
-        *      this is bad, they should have all been
-        *      released.
-        */
-       if (h->tt && (h->tt->num_requests != 0)) {
-#ifndef NDEBUG
-               radius_track_state_log(&default_log, L_ERR, __FILE__, __LINE__, h->tt, udp_tracking_entry_log);
-#endif
-               fr_assert_fail("%u tracking entries still allocated at conn close", h->tt->num_requests);
-       }
-
-       DEBUG4("Freeing rlm_radius_udp handle %p", handle);
-
-       talloc_free(h);
-}
-
-/** Connection failed
- *
- * @param[in] handle           of connection that failed.
- * @param[in] state    the connection was in when it failed.
- * @param[in] uctx     UNUSED.
- */
-static connection_state_t conn_failed(void *handle, connection_state_t state, UNUSED void *uctx)
-{
-       switch (state) {
-       /*
-        *      If the connection was connected when it failed,
-        *      we need to handle any outstanding packets and
-        *      timer events before reconnecting.
-        */
-       case CONNECTION_STATE_CONNECTED:
-       {
-               udp_handle_t    *h = talloc_get_type_abort(handle, udp_handle_t); /* h only available if connected */
-
-               /*
-                *      Reset the Status-Server checks.
-                */
-               if (h->status_u && h->status_u->ev) (void) fr_event_timer_delete(&h->status_u->ev);
-       }
-               break;
-
-       default:
-               break;
-       }
-
-       return CONNECTION_STATE_INIT;
-}
-
-CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
-static connection_t *thread_conn_alloc(trunk_connection_t *tconn, fr_event_list_t *el,
-                                         connection_conf_t const *conf,
-                                         char const *log_prefix, void *uctx)
-{
-       connection_t            *conn;
-       udp_thread_t            *thread = talloc_get_type_abort(uctx, udp_thread_t);
-
-       conn = connection_alloc(tconn, el,
-                                  &(connection_funcs_t){
-                                       .init = conn_init,
-                                       .close = conn_close,
-                                       .failed = conn_failed
-                                  },
-                                  conf,
-                                  log_prefix,
-                                  thread);
-       if (!conn) {
-               PERROR("%s - Failed allocating state handler for new connection", thread->inst->parent->name);
-               return NULL;
-       }
-
-       return conn;
-}
-
-/** Read and discard data
- *
- */
-static void conn_discard(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, void *uctx)
-{
-       trunk_connection_t      *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
-       uint8_t                 buffer[4096];
-       ssize_t                 slen;
-
-       while ((slen = read(fd, buffer, sizeof(buffer))) > 0);
-
-       if (slen < 0) {
-               switch (errno) {
-               case EBADF:
-               case ECONNRESET:
-               case ENOTCONN:
-               case ETIMEDOUT:
-                       ERROR("%s - Failed draining socket: %s", h->module_name, fr_syserror(errno));
-                       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-                       break;
-
-               default:
-                       break;
-               }
-       }
-}
-
-/** Connection errored
- *
- * We were signalled by the event loop that a fatal error occurred on this connection.
- *
- * @param[in] el       The event list signalling.
- * @param[in] fd       that errored.
- * @param[in] flags    El flags.
- * @param[in] fd_errno The nature of the error.
- * @param[in] uctx     The trunk connection handle (tconn).
- */
-static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
-{
-       trunk_connection_t      *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
-       connection_t            *conn = tconn->conn;
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-
-       ERROR("%s - Connection %s failed: %s", h->module_name, h->name, fr_syserror(fd_errno));
-
-       connection_signal_reconnect(conn, CONNECTION_FAILED);
-}
-
-CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
-static void thread_conn_notify(trunk_connection_t *tconn, connection_t *conn,
-                              fr_event_list_t *el,
-                              trunk_connection_event_t notify_on, UNUSED void *uctx)
-{
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-       fr_event_fd_cb_t        read_fn = NULL;
-       fr_event_fd_cb_t        write_fn = NULL;
-
-       switch (notify_on) {
-               /*
-                *      We may have sent multiple requests to the
-                *      other end, so it might be sending us multiple
-                *      replies.  We want to drain the socket, instead
-                *      of letting the packets sit in the UDP receive
-                *      queue.
-                */
-       case TRUNK_CONN_EVENT_NONE:
-               read_fn = conn_discard;
-               break;
-
-       case TRUNK_CONN_EVENT_READ:
-               read_fn = trunk_connection_callback_readable;
-               break;
-
-       case TRUNK_CONN_EVENT_WRITE:
-               write_fn = trunk_connection_callback_writable;
-               break;
-
-       case TRUNK_CONN_EVENT_BOTH:
-               read_fn = trunk_connection_callback_readable;
-               write_fn = trunk_connection_callback_writable;
-               break;
-
-       }
-
-       /*
-        *      Over-ride read for replication.
-        */
-       if (h->inst->parent->replicate) read_fn = conn_discard;
-
-       if (fr_event_fd_insert(h, NULL, el, h->fd,
-                              read_fn,
-                              write_fn,
-                              conn_error,
-                              tconn) < 0) {
-               PERROR("%s - Failed inserting FD event", h->module_name);
-
-               /*
-                *      May free the connection!
-                */
-               trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-       }
-}
-
-/*
- *  Return negative numbers to put 'a' at the top of the heap.
- *  Return positive numbers to put 'b' at the top of the heap.
- *
- *  We want the value with the lowest timestamp to be prioritized at
- *  the top of the heap.
- */
-static int8_t request_prioritise(void const *one, void const *two)
-{
-       udp_request_t const *a = one;
-       udp_request_t const *b = two;
-       int8_t ret;
-
-       // @todo - prioritize packets if there's a state?
-
-       /*
-        *      Prioritise status check packets
-        */
-       ret = (b->status_check - a->status_check);
-       if (ret != 0) return ret;
-
-       /*
-        *      Larger priority is more important.
-        */
-       ret = CMP(a->priority, b->priority);
-       if (ret != 0) return ret;
-
-       /*
-        *      Smaller timestamp (i.e. earlier) is more important.
-        */
-       return CMP_PREFER_SMALLER(fr_time_unwrap(a->recv_time), fr_time_unwrap(b->recv_time));
-}
-
-/** Decode response packet data, extracting relevant information and validating the packet
- *
- * @param[in] ctx                      to allocate pairs in.
- * @param[out] reply                   Pointer to head of pair list to add reply attributes to.
- * @param[out] response_code           The type of response packet.
- * @param[in] h                                connection handle.
- * @param[in] request                  the request.
- * @param[in] u                                UDP request.
- * @param[in] request_authenticator    from the original request.
- * @param[in] data                     to decode.
- * @param[in] data_len                 Length of input data.
- * @return
- *     - DECODE_FAIL_NONE on success.
- *     - DECODE_FAIL_* on failure.
- */
-static fr_radius_decode_fail_t decode(TALLOC_CTX *ctx, fr_pair_list_t *reply, uint8_t *response_code,
-                           udp_handle_t *h, request_t *request, udp_request_t *u,
-                           uint8_t const request_authenticator[static RADIUS_AUTH_VECTOR_LENGTH],
-                           uint8_t *data, size_t data_len)
-{
-       rlm_radius_udp_t const  *inst = talloc_get_type_abort_const(h->thread->inst, rlm_radius_udp_t);
-       rlm_radius_t const      *parent = inst->parent;
-       uint8_t                 code;
-       fr_radius_decode_ctx_t  decode_ctx;
-
-       *response_code = 0;     /* Initialise to keep the rest of the code happy */
-
-       RHEXDUMP3(data, data_len, "Read packet");
-
-       decode_ctx = (fr_radius_decode_ctx_t) {
-               .common = &inst->common_ctx,
-               .request_code = u->code,
-               .request_authenticator = request_authenticator,
-               .tmp_ctx = talloc(ctx, uint8_t),
-               .end = data + data_len,
-               .verify = true,
-               .require_message_authenticator = ((*(parent->received_message_authenticator) & parent->require_message_authenticator) |
-                                                 (parent->require_message_authenticator & FR_RADIUS_REQUIRE_MA_YES)) > 0
-       };
-
-       if (fr_radius_decode(ctx, reply, data, data_len, &decode_ctx) < 0) {
-               talloc_free(decode_ctx.tmp_ctx);
-               RPEDEBUG("Failed reading packet");
-               return DECODE_FAIL_UNKNOWN;
-       }
-       talloc_free(decode_ctx.tmp_ctx);
-
-       code = data[0];
-
-       RDEBUG("Received %s ID %d length %ld reply packet on connection %s",
-              fr_radius_packet_name[code], data[1], data_len, h->name);
-       log_request_pair_list(L_DBG_LVL_2, request, NULL, reply, NULL);
-
-       /*
-        *      This code is for BlastRADIUS mitigation.
-        *
-        *      The scenario where this applies is where we send Message-Authenticator
-        *      but the home server doesn't support it or require it, in which case
-        *      the response can be manipulated by an attacker.
-        */
-       if (u->code == FR_RADIUS_CODE_ACCESS_REQUEST) {
-               if ((parent->require_message_authenticator == FR_RADIUS_REQUIRE_MA_AUTO) &&
-                   !*(parent->received_message_authenticator) &&
-                   fr_pair_find_by_da(&request->request_pairs, NULL, attr_message_authenticator) &&
-                   !fr_pair_find_by_da(&request->request_pairs, NULL, attr_eap_message)) {
-                       RINFO("Packet contained a valid Message-Authenticator.  Setting \"require_message_authenticator = yes\"");
-                       *(parent->received_message_authenticator) = true;
-               }
-       }
-
-       *response_code = code;
-
-       /*
-        *      Record the fact we've seen a response
-        */
-       u->num_replies++;
-
-       /*
-        *      Fixup retry times
-        */
-       if (fr_time_gt(u->retry.start, h->mrs_time)) h->mrs_time = u->retry.start;
-
-       return DECODE_FAIL_NONE;
-}
-
-static int encode(rlm_radius_udp_t const *inst, request_t *request, udp_request_t *u, uint8_t id)
-{
-       ssize_t                 packet_len;
-       fr_radius_encode_ctx_t  encode_ctx;
-
-       fr_assert(inst->parent->allowed[u->code]);
-       fr_assert(!u->packet);
-
-       /*
-        *      This is essentially free, as this memory was
-        *      pre-allocated as part of the treq.
-        */
-       u->packet_len = inst->max_packet_size;
-       MEM(u->packet = talloc_array(u, uint8_t, u->packet_len));
-
-       /*
-        *      We should have at minimum 64-byte packets, so don't
-        *      bother doing run-time checks here.
-        */
-       fr_assert(u->packet_len >= (size_t) RADIUS_HEADER_LENGTH);
-
-       encode_ctx = (fr_radius_encode_ctx_t) {
-               .common = &inst->common_ctx,
-               .rand_ctx = (fr_fast_rand_t) {
-                       .a = fr_rand(),
-                       .b = fr_rand(),
-               },
-               .code = u->code,
-               .id = id,
-               .add_proxy_state = !inst->parent->originate,
-       };
-
-       /*
-        *      If we're sending a status check packet, update any
-        *      necessary timestamps.  Also, don't add Proxy-State, as
-        *      we're originating the packet.
-        */
-       if (u->status_check) {
-               fr_pair_t *vp;
-
-               vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_event_timestamp);
-               if (vp) vp->vp_date = fr_time_to_unix_time(u->retry.updated);
-
-               encode_ctx.add_proxy_state = false;
-       }
-
-       /*
-        *      Encode it, leaving room for Proxy-State if necessary.
-        */
-       packet_len = fr_radius_encode(&FR_DBUFF_TMP(u->packet, u->packet_len),
-                                     &request->request_pairs, &encode_ctx);
-       if (fr_pair_encode_is_error(packet_len)) {
-               RPERROR("Failed encoding packet");
-
-       error:
-               TALLOC_FREE(u->packet);
-               return -1;
-       }
-
-       if (packet_len < 0) {
-               size_t have;
-               size_t need;
-
-               have = u->packet_len;
-               need = have - packet_len;
-
-               if (need > RADIUS_MAX_PACKET_SIZE) {
-                       RERROR("Failed encoding packet.  Have %zu bytes of buffer, need %zu bytes",
-                              have, need);
-               } else {
-                       RERROR("Failed encoding packet.  Have %zu bytes of buffer, need %zu bytes.  "
-                              "Increase 'max_packet_size'", have, need);
-               }
-
-               goto error;
-       }
-       /*
-        *      The encoded packet should NOT over-run the input buffer.
-        */
-       fr_assert((size_t) packet_len <= u->packet_len);
-
-       /*
-        *      Add Proxy-State to the tail end of the packet.
-        *
-        *      We need to add it here, and NOT in
-        *      request->request_pairs, because multiple modules
-        *      may be sending the packets at the same time.
-        */
-       if (encode_ctx.add_proxy_state) {
-               fr_pair_t       *vp;
-
-               MEM(vp = fr_pair_afrom_da(u->packet, attr_proxy_state));
-               fr_pair_value_memdup(vp, (uint8_t const *) &inst->common_ctx.proxy_state, sizeof(inst->common_ctx.proxy_state), false);
-               fr_pair_append(&u->extra, vp);
-       }
-
-       /*
-        *      Update our version of the packet length.
-        */
-       u->packet_len = packet_len;
-
-       /*
-        *      Now that we're done mangling the packet, sign it.
-        */
-       if (fr_radius_sign(u->packet, NULL, (uint8_t const *) inst->secret,
-                          talloc_array_length(inst->secret) - 1) < 0) {
-               RERROR("Failed signing packet");
-               goto error;
-       }
-
-       return 0;
-}
-
-
-/** Revive a connection after "revive_interval"
- *
- */
-static void revive_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
-{
-       trunk_connection_t      *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
-
-       INFO("%s - Reviving connection %s", h->module_name, h->name);
-       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-}
-
-/** Mark a connection dead after "zombie_interval"
- *
- */
-static void zombie_timeout(fr_event_list_t *el, fr_time_t now, void *uctx)
-{
-       trunk_connection_t      *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
-
-       INFO("%s - No replies during 'zombie_period', marking connection %s as dead", h->module_name, h->name);
-
-       /*
-        *      Don't use this connection, and re-queue all of its
-        *      requests onto other connections.
-        */
-       trunk_connection_signal_inactive(tconn);
-       (void) trunk_connection_requests_requeue(tconn, TRUNK_REQUEST_STATE_ALL, 0, false);
-
-       /*
-        *      We do have status checks.  Try to reconnect the
-        *      connection immediately.  If the status checks pass,
-        *      then the connection will be marked "alive"
-        */
-       if (h->inst->parent->status_check) {
-               trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-               return;
-       }
-
-       /*
-        *      Revive the connection after a time.
-        */
-       if (fr_event_timer_at(h, el, &h->zombie_ev,
-                             fr_time_add(now, h->inst->parent->revive_interval), revive_timeout, tconn) < 0) {
-               ERROR("Failed inserting revive timeout for connection");
-               trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-       }
-}
-
-
-/** See if the connection is zombied.
- *
- *     We check for zombie when major events happen:
- *
- *     1) request hits its final timeout
- *     2) request timer hits, and it needs to be retransmitted
- *     3) a DUP packet comes in, and the request needs to be retransmitted
- *     4) we're sending a packet.
- *
- *  There MIGHT not be retries configured, so we MUST check for zombie
- *  when any new packet comes in.  Similarly, there MIGHT not be new
- *  packets, but retries are configured, so we have to check there,
- *  too.
- *
- *  Also, the socket might not be writable for a while.  There MIGHT
- *  be a long time between getting the timer / DUP signal, and the
- *  request finally being written to the socket.  So we need to check
- *  for zombie at BOTH the timeout and the mux / write function.
- *
- * @return
- *     - true if the connection is zombie.
- *     - false if the connection is not zombie.
- */
-static bool check_for_zombie(fr_event_list_t *el, trunk_connection_t *tconn, fr_time_t now, fr_time_t last_sent)
-{
-       udp_handle_t    *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
-
-       /*
-        *      We're replicating, and don't care about the health of
-        *      the home server, and this function should not be called.
-        */
-       fr_assert(!h->inst->replicate);
-
-       /*
-        *      If we're status checking OR already zombie, don't go to zombie
-        */
-       if (h->status_checking || h->zombie_ev) return true;
-
-       if (fr_time_eq(now, fr_time_wrap(0))) now = fr_time();
-
-       /*
-        *      We received a reply since this packet was sent, the connection isn't zombie.
-        */
-       if (fr_time_gteq(h->last_reply, last_sent)) return false;
-
-       /*
-        *      If we've seen ANY response in the allowed window, then the connection is still alive.
-        */
-       if (h->inst->parent->synchronous && fr_time_gt(last_sent, fr_time_wrap(0)) &&
-           (fr_time_lt(fr_time_add(last_sent, h->inst->parent->response_window), now))) return false;
-
-       WARN("%s - Entering Zombie state - connection %s", h->module_name, h->name);
-       if (h->inst->parent->status_check) {
-               h->status_checking = true;
-
-               /*
-                *      Queue up the status check packet.  It will be sent
-                *      when the connection is writable.
-                */
-               h->status_u->retry.start = fr_time_wrap(0);
-               h->status_r->treq = NULL;
-
-               if (trunk_request_enqueue_on_conn(&h->status_r->treq, tconn, h->status_request,
-                                                    h->status_u, h->status_r, true) != TRUNK_ENQUEUE_OK) {
-                       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-               }
-       } else {
-               if (fr_event_timer_at(h, el, &h->zombie_ev, fr_time_add(now, h->inst->parent->zombie_period),
-                                     zombie_timeout, tconn) < 0) {
-                       ERROR("Failed inserting zombie timeout for connection");
-                       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-               }
-       }
-
-       return true;
-}
-
-/** Handle retries.
- *
- */
-static void mod_retry(module_ctx_t const *mctx, request_t *request, fr_retry_t const *retry)
-{
-       udp_result_t            *r = talloc_get_type_abort(mctx->rctx, udp_result_t);
-       rlm_radius_t const      *parent = talloc_get_type_abort(mctx->mi->data, rlm_radius_t);
-       fr_time_t               now = retry->updated;
-
-       trunk_request_t         *treq = talloc_get_type_abort(r->treq, trunk_request_t);
-       trunk_connection_t      *tconn = treq->tconn;
-
-       fr_assert(request == treq->request);
-       fr_assert(treq->preq);                                          /* Must still have a protocol request */
-
-       switch (retry->state) {
-
-       case FR_RETRY_CONTINUE:
-               switch (treq->state) {
-
-               case TRUNK_REQUEST_STATE_INIT:
-               case TRUNK_REQUEST_STATE_UNASSIGNED:
-                       fr_assert(0);
-                       break;
-
-               case TRUNK_REQUEST_STATE_BACKLOG:
-                       RDEBUG("Request is still in the backlog queue to be sent - suppressing retransmission");
-                       return;
-
-               case TRUNK_REQUEST_STATE_PENDING:
-                       RDEBUG("Request is still in the pending queue to be sent - suppressing retransmission");
-                       return;
-
-               case TRUNK_REQUEST_STATE_PARTIAL:
-                       RDEBUG("Request was partially written, as IO is blocked - suppressing retransmission");
-                       return;
-
-               case TRUNK_REQUEST_STATE_SENT:
-                       fr_assert(tconn);
-                       r->is_retry = true;
-                       trunk_request_requeue(treq);
-                       return;
-
-               case TRUNK_REQUEST_STATE_REAPABLE:
-               case TRUNK_REQUEST_STATE_COMPLETE:
-               case TRUNK_REQUEST_STATE_FAILED:
-               case TRUNK_REQUEST_STATE_CANCEL:
-               case TRUNK_REQUEST_STATE_CANCEL_SENT:
-               case TRUNK_REQUEST_STATE_CANCEL_PARTIAL:
-               case TRUNK_REQUEST_STATE_CANCEL_COMPLETE:
-                       fr_assert(0);
-                       break;
-               }
-               break;
-
-       case FR_RETRY_MRD:
-               REDEBUG("Reached maximum_retransmit_duration (%pVs > %pVs), failing request",
-                       fr_box_time_delta(fr_time_sub(now, retry->start)), fr_box_time_delta(retry->config->mrd));
-               break;
-
-       case FR_RETRY_MRC:
-               REDEBUG("Reached maximum_retransmit_count (%u > %u), failing request",
-                       retry->count, retry->config->mrc);
-               break;
-       }
-
-       r->rcode = RLM_MODULE_FAIL;
-       trunk_request_signal_fail(treq);
-
-       /*
-        *      We don't do zombie stuff!
-        */
-       if (!tconn || parent->replicate) return;
-
-       check_for_zombie(unlang_interpret_event_list(request), tconn, now, retry->start);
-}
-
-
-CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
-static void request_mux(UNUSED fr_event_list_t *el,
-                       trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
-{
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-       rlm_radius_udp_t const  *inst = h->inst;
-       int                     sent;
-       uint16_t                i, queued;
-       size_t                  total_len = 0;
-
-       /*
-        *      Encode multiple packets in preparation
-        *      for transmission with sendmmsg.
-        */
-       for (i = 0, queued = 0; (i < inst->max_send_coalesce) && (total_len < h->send_buff_actual); i++) {
-               trunk_request_t         *treq;
-               udp_request_t           *u;
-               request_t               *request;
-
-               if (unlikely(trunk_connection_pop_request(&treq, tconn) < 0)) return;
-
-               /*
-                *      No more requests to send
-                */
-               if (!treq) break;
-
-               fr_assert((treq->state == TRUNK_REQUEST_STATE_PENDING) ||
-                          (treq->state == TRUNK_REQUEST_STATE_PARTIAL));
-
-               request = treq->request;
-               u = talloc_get_type_abort(treq->preq, udp_request_t);
-
-               fr_assert(!u->status_check);
-
-               /*
-                *      No previous packet, OR can't retransmit the
-                *      existing one.  Oh well.
-                *
-                *      Note that if we can't retransmit the previous
-                *      packet, then u->rr MUST already have been
-                *      deleted in the request_cancel() function
-                *      or request_release_conn() function when
-                *      the REQUEUE signal was received.
-                */
-               if (!u->packet) {
-                       fr_assert(!u->rr);
-
-                       if (unlikely(radius_track_entry_reserve(&u->rr, treq, h->tt, request, u->code, treq) < 0)) {
-#ifndef NDEBUG
-                               radius_track_state_log(&default_log, L_ERR, __FILE__, __LINE__,
-                                                      h->tt, udp_tracking_entry_log);
-#endif
-                               fr_assert_fail("Tracking entry allocation failed: %s", fr_strerror());
-                               trunk_request_signal_fail(treq);
-                               continue;
-                       }
-                       u->id = u->rr->id;
-
-                       RDEBUG("Sending %s ID %d length %ld over connection %s",
-                              fr_radius_packet_name[u->code], u->id, u->packet_len, h->name);
-
-                       if (encode(h->inst, request, u, u->id) < 0) {
-                               /*
-                                *      Need to do this because request_conn_release
-                                *      may not be called.
-                                */
-                               udp_request_reset(u);
-                               if (u->ev) (void) fr_event_timer_delete(&u->ev);
-                               trunk_request_signal_fail(treq);
-                               continue;
-                       }
-                       RHEXDUMP3(u->packet, u->packet_len, "Encoded packet");
-
-                       /*
-                        *      Remember the authentication vector, which now has the
-                        *      packet signature.
-                        */
-                       (void) radius_track_entry_update(u->rr, u->packet + RADIUS_AUTH_VECTOR_OFFSET);
-               } else {
-                       RDEBUG("Retransmitting %s ID %d length %ld over connection %s",
-                              fr_radius_packet_name[u->code], u->id, u->packet_len, h->name);
-               }
-
-               log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
-               if (!fr_pair_list_empty(&u->extra)) log_request_pair_list(L_DBG_LVL_2, request, NULL, &u->extra, NULL);
-
-               /*
-                *      Record pointers to the buffer we'll be writing
-                *      We store the treq so we can place it back in
-                *      the pending state if the sendmmsg call fails.
-                */
-               h->coalesced[queued].treq = treq;
-               h->coalesced[queued].out.iov_base = u->packet;
-               h->coalesced[queued].out.iov_len = u->packet_len;
-
-               /*
-                *      Record how much data we have in total.
-                *
-                *      Try not to exceed the SO_SNDBUF value of the
-                *      socket as we potentially just waste CPU
-                *      time re-encoding the packets.
-                */
-               total_len += u->packet_len;
-
-               /*
-                *      Tell the trunk API that this request is now in
-                *      the "sent" state.  And we don't want to see
-                *      this request again. The request hasn't actually
-                *      been sent, but it's the only way to get at the
-                *      next entry in the heap.
-                */
-               trunk_request_signal_sent(treq);
-               queued++;
-       }
-       if (queued == 0) return;        /* No work */
-
-       /*
-        *      Verify nothing accidentally freed the connection handle
-        */
-       (void)talloc_get_type_abort(h, udp_handle_t);
-
-       /*
-        *      Send the coalesced datagrams
-        */
-       sent = sendmmsg(h->fd, h->mmsgvec, queued, 0);
-       if (sent < 0) {         /* Error means no messages were sent */
-               sent = 0;
-
-               /*
-                *      Temporary conditions
-                */
-               switch (errno) {
-#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
-               case EWOULDBLOCK:       /* No outbound packet buffers, maybe? */
-#endif
-               case EAGAIN:            /* No outbound packet buffers, maybe? */
-               case EINTR:             /* Interrupted by signal */
-               case ENOBUFS:           /* No outbound packet buffers, maybe? */
-               case ENOMEM:            /* malloc failure in kernel? */
-                       WARN("%s - Failed sending data over connection %s: %s",
-                            h->module_name, h->name, fr_syserror(errno));
-                       break;
-
-               /*
-                *      Fatal, request specific conditions
-                *
-                *      sendmmsg will only return an error condition if the
-                *      first packet being sent errors.
-                *
-                *      When we get request specific errors, we need to fail
-                *      the first request in the set, and move the rest of
-                *      the packets back to the pending state.
-                */
-               case EMSGSIZE:          /* Packet size exceeds max size allowed on socket */
-                       ERROR("%s - Failed sending data over connection %s: %s",
-                             h->module_name, h->name, fr_syserror(errno));
-                       trunk_request_signal_fail(h->coalesced[0].treq);
-                       sent = 1;
-                       break;
-
-               /*
-                *      Will re-queue any 'sent' requests, so we don't
-                *      have to do any cleanup.
-                */
-               default:
-                       ERROR("%s - Failed sending data over connection %s: %s",
-                             h->module_name, h->name, fr_syserror(errno));
-                       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-                       return;
-               }
-       }
-
-       /*
-        *      For all messages that were actually sent by sendmmsg,
-        *      say what's up.
-        */
-       for (i = 0; i < sent; i++) {
-               trunk_request_t         *treq = h->coalesced[i].treq;
-               udp_request_t           *u;
-               request_t               *request;
-               char const              *action;
-
-               /*
-                *      It's UDP so there should never be partial writes
-                */
-               fr_assert((size_t)h->mmsgvec[i].msg_len == h->mmsgvec[i].msg_hdr.msg_iov->iov_len);
-
-               fr_assert(treq->state == TRUNK_REQUEST_STATE_SENT);
-
-               request = treq->request;
-               u = talloc_get_type_abort(treq->preq, udp_request_t);
-
-               /*
-                *      Don't print anything more for replicated requests.
-                */
-               if (inst->parent->replicate) {
-                       udp_result_t *r = talloc_get_type_abort(treq->rctx, udp_result_t);
-
-                       r->rcode = RLM_MODULE_OK;
-                       trunk_request_signal_complete(treq);
-               }
-
-               /*
-                *      Tell the admin what's going on
-                */
-               if (u->retry.count == 1) {
-                       action = inst->parent->originate ? "Originated" : "Proxied";
-                       h->last_sent = u->retry.start;
-                       if (fr_time_lteq(h->first_sent, h->last_idle)) h->first_sent = h->last_sent;
-
-               } else {
-                       action = "Retransmitted";
-               }
-
-               fr_assert(!u->status_check);
-
-
-               if (!inst->parent->synchronous) {
-                       RDEBUG("%s request.  Expecting response within %pVs", action,
-                              fr_box_time_delta(u->retry.rt));
-
-               } else {
-                       /*
-                        *      If the packet doesn't get a response,
-                        *      then udp_request_free() will notice, and run conn_zombie()
-                        */
-                       RDEBUG("%s request.  Relying on NAS to perform more retransmissions", action);
-               }
-       }
-
-       /*
-        *      Requests that weren't sent get re-enqueued
-        *
-        *      The cancel logic runs as per-normal and cleans up
-        *      the request ready for sending again...
-        */
-       for (i = sent; i < queued; i++) trunk_request_requeue(h->coalesced[i].treq);
-}
-
-/** Deal with Protocol-Error replies, and possible negotiation
- *
- */
-static void protocol_error_reply(udp_request_t *u, udp_result_t *r, udp_handle_t *h)
-{
-       bool            error_601 = false;
-       uint32_t        response_length = 0;
-       uint8_t const   *attr, *end;
-
-       end = h->buffer + fr_nbo_to_uint16(h->buffer + 2);
-
-       for (attr = h->buffer + RADIUS_HEADER_LENGTH;
-            attr < end;
-            attr += attr[1]) {
-               /*
-                *      Error-Cause = Response-Too-Big
-                */
-               if ((attr[0] == attr_error_cause->attr) && (attr[1] == 6)) {
-                       uint32_t error;
-
-                       memcpy(&error, attr + 2, 4);
-                       error = ntohl(error);
-                       if (error == 601) error_601 = true;
-                       continue;
-               }
-
-               /*
-                *      The other end wants us to increase our Response-Length
-                */
-               if ((attr[0] == attr_response_length->attr) && (attr[1] == 6)) {
-                       memcpy(&response_length, attr + 2, 4);
-                       continue;
-               }
-
-               /*
-                *      Protocol-Error packets MUST contain an
-                *      Original-Packet-Code attribute.
-                *
-                *      The attribute containing the
-                *      Original-Packet-Code is an extended
-                *      attribute.
-                */
-               if (attr[0] != attr_extended_attribute_1->attr) continue;
-
-                       /*
-                        *      ATTR + LEN + EXT-Attr + uint32
-                        */
-                       if (attr[1] != 7) continue;
-
-                       /*
-                        *      See if there's an Original-Packet-Code.
-                        */
-                       if (attr[2] != (uint8_t)attr_original_packet_code->attr) continue;
-
-                       /*
-                        *      Has to be an 8-bit number.
-                        */
-                       if ((attr[3] != 0) ||
-                           (attr[4] != 0) ||
-                           (attr[5] != 0)) {
-                               if (r) r->rcode = RLM_MODULE_FAIL;
-                               return;
-                       }
-
-                       /*
-                        *      The value has to match.  We don't
-                        *      currently multiplex different codes
-                        *      with the same IDs on connections.  So
-                        *      this check is just for RFC compliance,
-                        *      and for sanity.
-                        */
-                       if (attr[6] != u->code) {
-                               if (r) r->rcode = RLM_MODULE_FAIL;
-                               return;
-                       }
-       }
-
-       /*
-        *      Error-Cause = Response-Too-Big
-        *
-        *      The other end says it needs more room to send it's response
-        *
-        *      Limit it to reasonable values.
-        */
-       if (error_601 && response_length && (response_length > h->buflen)) {
-               if (response_length < 4096) response_length = 4096;
-               if (response_length > 65535) response_length = 65535;
-
-               DEBUG("%s - Increasing buffer size to %u for connection %s", h->module_name, response_length, h->name);
-
-               /*
-                *      Make sure to copy the packet over!
-                */
-               attr = h->buffer;
-               h->buflen = response_length;
-               MEM(h->buffer = talloc_array(h, uint8_t, h->buflen));
-
-               memcpy(h->buffer, attr, end - attr);
-       }
-
-       /*
-        *      fail - something went wrong internally, or with the connection.
-        *      invalid - wrong response to packet
-        *      handled - best remaining alternative :(
-        *
-        *      i.e. if the response is NOT accept, reject, whatever,
-        *      then we shouldn't allow the caller to do any more
-        *      processing of this packet.  There was a protocol
-        *      error, and the response is valid, but not useful for
-        *      anything.
-        */
-       if (r) r->rcode = RLM_MODULE_HANDLED;
-}
-
-
-/** Handle retries for a status check
- *
- */
-static void status_check_next(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
-{
-       trunk_connection_t      *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
-       udp_handle_t            *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
-
-       if (trunk_request_enqueue_on_conn(&h->status_r->treq, tconn, h->status_request,
-                                            h->status_u, h->status_r, true) != TRUNK_ENQUEUE_OK) {
-               trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-       }
-}
-
-
-/** Deal with replies replies to status checks and possible negotiation
- *
- */
-static void status_check_reply(trunk_request_t *treq, fr_time_t now)
-{
-       udp_handle_t            *h = talloc_get_type_abort(treq->tconn->conn->h, udp_handle_t);
-       rlm_radius_t const      *inst = h->inst->parent;
-       udp_request_t           *u = talloc_get_type_abort(treq->preq, udp_request_t);
-       udp_result_t            *r = talloc_get_type_abort(treq->rctx, udp_result_t);
-
-       fr_assert(treq->preq == h->status_u);
-       fr_assert(treq->rctx == h->status_r);
-
-       r->treq = NULL;
-
-       /*
-        *      @todo - do other negotiation and signaling.
-        */
-       if (h->buffer[0] == FR_RADIUS_CODE_PROTOCOL_ERROR) protocol_error_reply(u, NULL, h);
-
-       if (u->num_replies < inst->num_answers_to_alive) {
-               DEBUG("Received %d / %u replies for status check, on connection - %s",
-                     u->num_replies, inst->num_answers_to_alive, h->name);
-               DEBUG("Next status check packet will be in %pVs", fr_box_time_delta(fr_time_sub(u->retry.next, now)));
-
-               /*
-                *      Set the timer for the next retransmit.
-                */
-               if (fr_event_timer_at(h, h->thread->el, &u->ev, u->retry.next, status_check_next, treq->tconn) < 0) {
-                       trunk_connection_signal_reconnect(treq->tconn, CONNECTION_FAILED);
-               }
-               return;
-       }
-
-       DEBUG("Received enough replies to status check, marking connection as active - %s", h->name);
-
-       /*
-        *      Set the "last idle" time to now, so that we don't
-        *      restart zombie_period until sufficient time has
-        *      passed.
-        */
-       h->last_idle = fr_time();
-
-       /*
-        *      Reset retry interval and retransmission counters
-        *      also frees u->ev.
-        */
-       status_check_reset(h, u);
-       trunk_connection_signal_active(treq->tconn);
-}
-
-CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
-static void request_demux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
-{
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-
-       DEBUG3("%s - Reading data for connection %s", h->module_name, h->name);
-
-       while (true) {
-               ssize_t                 slen;
-
-               trunk_request_t *treq;
-               request_t               *request;
-               udp_request_t           *u;
-               udp_result_t            *r;
-               radius_track_entry_t    *rr;
-               fr_radius_decode_fail_t reason;
-               uint8_t                 code = 0;
-               fr_pair_list_t          reply;
-
-               fr_time_t               now;
-
-               fr_pair_list_init(&reply);
-               /*
-                *      Drain the socket of all packets.  If we're busy, this
-                *      saves a round through the event loop.  If we're not
-                *      busy, a few extra system calls don't matter.
-                */
-               slen = read(h->fd, h->buffer, h->buflen);
-               if (slen == 0) return;
-
-               if (slen < 0) {
-                       if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) return;
-
-                       ERROR("%s - Failed reading response from socket: %s",
-                             h->module_name, fr_syserror(errno));
-                       trunk_connection_signal_reconnect(tconn, CONNECTION_FAILED);
-                       return;
-               }
-
-               if (slen < RADIUS_HEADER_LENGTH) {
-                       ERROR("%s - Packet too short, expected at least %zu bytes got %zd bytes",
-                             h->module_name, (size_t)RADIUS_HEADER_LENGTH, slen);
-                       continue;
-               }
-
-               /*
-                *      Note that we don't care about packet codes.  All
-                *      packet codes share the same ID space.
-                */
-               rr = radius_track_entry_find(h->tt, h->buffer[1], NULL);
-               if (!rr) {
-                       WARN("%s - Ignoring reply with ID %i that arrived too late",
-                            h->module_name, h->buffer[1]);
-                       continue;
-               }
-
-               treq = talloc_get_type_abort(rr->uctx, trunk_request_t);
-               request = treq->request;
-               fr_assert(request != NULL);
-               u = talloc_get_type_abort(treq->preq, udp_request_t);
-               r = talloc_get_type_abort(treq->rctx, udp_result_t);
-
-               /*
-                *      Validate and decode the incoming packet
-                */
-
-               if (!check(h, &slen)) {
-                       RWARN("Ignoring malformed packet");
-                       continue;
-               }
-
-               reason = decode(request->reply_ctx, &reply, &code, h, request, u, rr->vector, h->buffer, (size_t)slen);
-               if (reason != DECODE_FAIL_NONE) continue;
-
-               /*
-                *      Only valid packets are processed
-                *      Otherwise an attacker could perform
-                *      a DoS attack against the proxying servers
-                *      by sending fake responses for upstream
-                *      servers.
-                */
-               h->last_reply = now = fr_time();
-
-               /*
-                *      Status-Server can have any reply code, we don't care
-                *      what it is.  So long as it's signed properly, we
-                *      accept it.  This flexibility is because we don't
-                *      expose Status-Server to the admins.  It's only used by
-                *      this module for internal signalling.
-                */
-               if (u == h->status_u) {
-                       fr_pair_list_free(&reply);      /* Probably want to pass this to status_check_reply? */
-                       status_check_reply(treq, now);
-                       trunk_request_signal_complete(treq);
-                       continue;
-               }
-
-               /*
-                *      Handle any state changes, etc. needed by receiving a
-                *      Protocol-Error reply packet.
-                *
-                *      Protocol-Error is permitted as a reply to any
-                *      packet.
-                */
-               switch (code) {
-               case FR_RADIUS_CODE_PROTOCOL_ERROR:
-                       protocol_error_reply(u, r, h);
-                       break;
-
-               default:
-                       break;
-               }
-
-               /*
-                *      Mark up the request as being an Access-Challenge, if
-                *      required.
-                *
-                *      We don't do this for other packet types, because the
-                *      ok/fail nature of the module return code will
-                *      automatically result in it the parent request
-                *      returning an ok/fail packet code.
-                */
-               if ((u->code == FR_RADIUS_CODE_ACCESS_REQUEST) && (code == FR_RADIUS_CODE_ACCESS_CHALLENGE)) {
-                       fr_pair_t       *vp;
-
-                       vp = fr_pair_find_by_da(&request->reply_pairs, NULL, attr_packet_type);
-                       if (!vp) {
-                               MEM(vp = fr_pair_afrom_da(request->reply_ctx, attr_packet_type));
-                               vp->vp_uint32 = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-                               fr_pair_append(&request->reply_pairs, vp);
-                       }
-               }
-
-               /*
-                *      Delete Proxy-State attributes from the reply.
-                */
-               fr_pair_delete_by_da(&reply, attr_proxy_state);
-
-               /*
-                *      If the reply has Message-Authenticator, delete
-                *      it from the proxy reply so that it isn't
-                *      copied over to our reply.  But also create a
-                *      reply.Message-Authenticator attribute, so that
-                *      it ends up in our reply.
-                */
-               if (fr_pair_find_by_da(&reply, NULL, attr_message_authenticator)) {
-                       fr_pair_t *vp;
-
-                       fr_pair_delete_by_da(&reply, attr_message_authenticator);
-
-                       MEM(vp = fr_pair_afrom_da(request->reply_ctx, attr_message_authenticator));
-                       (void) fr_pair_value_memdup(vp, (uint8_t const *) "", 1, false);
-                       fr_pair_append(&request->reply_pairs, vp);
-               }
-
-               treq->request->reply->code = code;
-               r->rcode = radius_code_to_rcode[code];
-               fr_pair_list_append(&request->reply_pairs, &reply);
-               trunk_request_signal_complete(treq);
-       }
-}
-
-/** Remove the request from any tracking structures
- *
- * Frees encoded packets if the request is being moved to a new connection
- */
-static void request_cancel(UNUSED connection_t *conn, void *preq_to_reset,
-                          trunk_cancel_reason_t reason, UNUSED void *uctx)
-{
-       udp_request_t   *u = talloc_get_type_abort(preq_to_reset, udp_request_t);
-
-       /*
-        *      Request has been requeued on the same
-        *      connection due to timeout or DUP signal.  We
-        *      keep the same packet to avoid re-encoding it.
-        */
-       if (reason == TRUNK_CANCEL_REASON_REQUEUE) {
-               /*
-                *      Delete the request_timeout
-                *
-                *      Note: There might not be a request timeout
-                *      set in the case where the request was
-                *      queued for sendmmsg but never actually
-                *      sent.
-                */
-               if (u->ev) (void) fr_event_timer_delete(&u->ev);
-       }
-
-       /*
-        *      Other cancellations are dealt with by
-        *      request_conn_release as the request is removed
-        *      from the trunk.
-        */
-}
-
-/** Clear out anything associated with the handle from the request
- *
- */
-static void request_conn_release(connection_t *conn, void *preq_to_reset, UNUSED void *uctx)
-{
-       udp_request_t           *u = talloc_get_type_abort(preq_to_reset, udp_request_t);
-       udp_handle_t            *h = talloc_get_type_abort(conn->h, udp_handle_t);
-
-       if (u->ev) (void)fr_event_timer_delete(&u->ev);
-       if (u->packet) udp_request_reset(u);
-
-       if (h->inst->parent->replicate) return;
-
-       u->num_replies = 0;
-
-       /*
-        *      If there are no outstanding tracking entries
-        *      allocated then the connection is "idle".
-        */
-       if (!h->tt || (h->tt->num_requests == 0)) h->last_idle = fr_time();
-}
-
-/** Write out a canned failure
- *
- */
-static void request_fail(request_t *request, void *preq, void *rctx,
-                        NDEBUG_UNUSED trunk_request_state_t state, UNUSED void *uctx)
-{
-       udp_result_t            *r = talloc_get_type_abort(rctx, udp_result_t);
-       udp_request_t           *u = talloc_get_type_abort(preq, udp_request_t);
-
-       fr_assert(!u->rr && !u->packet && fr_pair_list_empty(&u->extra) && !u->ev);     /* Dealt with by request_conn_release */
-
-       fr_assert(state != TRUNK_REQUEST_STATE_INIT);
-
-       if (u->status_check) return;
-
-       r->rcode = RLM_MODULE_FAIL;
-       r->treq = NULL;
-
-       unlang_interpret_mark_runnable(request);
-}
-
-/** Response has already been written to the rctx at this point
- *
- */
-static void request_complete(request_t *request, void *preq, void *rctx, UNUSED void *uctx)
-{
-       udp_result_t            *r = talloc_get_type_abort(rctx, udp_result_t);
-       udp_request_t           *u = talloc_get_type_abort(preq, udp_request_t);
-
-       fr_assert(!u->rr && !u->packet && fr_pair_list_empty(&u->extra) && !u->ev);     /* Dealt with by request_conn_release */
-
-       if (u->status_check) return;
-
-       r->treq = NULL;
-
-       unlang_interpret_mark_runnable(request);
-}
-
-/** Explicitly free resources associated with the protocol request
- *
- */
-static void request_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
-{
-       udp_request_t           *u = talloc_get_type_abort(preq_to_free, udp_request_t);
-
-       fr_assert(!u->rr && !u->packet && fr_pair_list_empty(&u->extra) && !u->ev);     /* Dealt with by request_conn_release */
-
-       /*
-        *      Don't free status check requests.
-        */
-       if (u->status_check) return;
-
-       talloc_free(u);
-}
-
-/** Resume execution of the request, returning the rcode set during trunk execution
- *
- */
-static unlang_action_t mod_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request)
-{
-       udp_result_t    *r = talloc_get_type_abort(mctx->rctx, udp_result_t);
-       rlm_rcode_t     rcode = r->rcode;
-
-       talloc_free(r);
-
-       RETURN_MODULE_RCODE(rcode);
-}
-
-static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
-{
-       rlm_radius_t const      *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_radius_t);
-
-       udp_thread_t            *t = talloc_get_type_abort(module_thread(inst->io_submodule)->data, udp_thread_t);
-       udp_result_t            *r = talloc_get_type_abort(mctx->rctx, udp_result_t);
-
-       /*
-        *      We received a duplicate packet, but we're not doing
-        *      synchronous proxying.  Ignore the dup, and rely on the
-        *      IO submodule to time it's own retransmissions.
-        */
-       if ((action == FR_SIGNAL_DUP) && !inst->synchronous) return;
-
-       /*
-        *      If we don't have a treq associated with the
-        *      rctx it's likely because the request was
-        *      scheduled, but hasn't yet been resumed, and
-        *      has received a signal, OR has been resumed
-        *      and immediately cancelled as the event loop
-        *      is exiting, in which case
-        *      unlang_request_is_scheduled will return false
-        *      (don't use it).
-        */
-       if (!r->treq) {
-               talloc_free(r);
-               return;
-       }
-
-       switch (action) {
-       /*
-        *      The request is being cancelled, tell the
-        *      trunk so it can clean up the treq.
-        */
-       case FR_SIGNAL_CANCEL:
-               trunk_request_signal_cancel(r->treq);
-               r->treq = NULL;
-               talloc_free(r);         /* Should be freed soon anyway, but better to be explicit */
-               return;
-
-       /*
-        *      Requeue the request on the same connection
-        *      causing a "retransmission" if the request
-        *      has already been sent out.
-        */
-       case FR_SIGNAL_DUP:
-               /*
-                *      If we're not synchronous, then rely on
-                *      request_retry() to do the retransmissions.
-                */
-               if (!t->inst->parent->synchronous) return;
-
-               /*
-                *      We are synchronous, retransmit the current
-                *      request on the same connection.
-                *
-                *      If it's zombie, we still resend it.  If the
-                *      connection is dead, then a callback will move
-                *      this request to a new connection.
-                */
-               trunk_request_requeue(r->treq);
-               return;
-
-       default:
-               return;
-       }
-}
-
-#ifndef NDEBUG
-/** Free a udp_result_t
- *
- * Allows us to set break points for debugging.
- */
-static int _udp_result_free(udp_result_t *r)
-{
-       trunk_request_t *treq;
-       udp_request_t           *u;
-
-       if (!r->treq) return 0;
-
-       treq = talloc_get_type_abort(r->treq, trunk_request_t);
-       u = talloc_get_type_abort(treq->preq, udp_request_t);
-
-       fr_assert_msg(!u->ev, "udp_result_t freed with active timer");
-
-       return 0;
-}
-#endif
-
-/** Free a udp_request_t
- */
-static int _udp_request_free(udp_request_t *u)
-{
-       if (u->ev) (void) fr_event_timer_delete(&u->ev);
-
-       fr_assert(u->rr == NULL);
-
-       return 0;
-}
-
-static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, void *instance, void *thread, request_t *request)
-{
-       rlm_radius_udp_t                *inst = talloc_get_type_abort(instance, rlm_radius_udp_t);
-       udp_thread_t                    *t = talloc_get_type_abort(thread, udp_thread_t);
-       udp_result_t                    *r;
-       udp_request_t                   *u;
-       trunk_request_t                 *treq;
-       fr_retry_config_t               *retry_config;
-
-       fr_assert(request->packet->code > 0);
-       fr_assert(request->packet->code < FR_RADIUS_CODE_MAX);
-
-       if (request->packet->code == FR_RADIUS_CODE_STATUS_SERVER) {
-               RWDEBUG("Status-Server is reserved for internal use, and cannot be sent manually.");
-               RETURN_MODULE_NOOP;
-       }
-
-       treq = trunk_request_alloc(t->trunk, request);
-       if (!treq) RETURN_MODULE_FAIL;
-
-       MEM(r = talloc_zero(request, udp_result_t));
-#ifndef NDEBUG
-       talloc_set_destructor(r, _udp_result_free);
-#endif
-
-       /*
-        *      Can't use compound literal - const issues.
-        */
-       MEM(u = talloc_zero(treq, udp_request_t));
-       u->code = request->packet->code;
-       u->synchronous = inst->parent->synchronous;
-       u->priority = request->async->priority;
-       u->recv_time = request->async->recv_time;
-       fr_pair_list_init(&u->extra);
-
-       r->rcode = RLM_MODULE_FAIL;
-
-       /*
-        *      Make sure that we print out the actual encoded value
-        *      of the Message-Authenticator attribute.  If the caller
-        *      asked for one, delete theirs (which has a bad value),
-        *      and remember to add one manually when we encode the
-        *      packet.  This is the only editing we do on the input
-        *      request.
-        *
-        *      @todo - don't edit the input packet!
-        */
-       if (fr_pair_find_by_da(&request->request_pairs, NULL, attr_message_authenticator)) {
-               u->require_message_authenticator = true;
-               pair_delete_request(attr_message_authenticator);
-       }
-
-       switch(trunk_request_enqueue(&treq, t->trunk, request, u, r)) {
-       case TRUNK_ENQUEUE_OK:
-       case TRUNK_ENQUEUE_IN_BACKLOG:
-               break;
-
-       case TRUNK_ENQUEUE_NO_CAPACITY:
-               REDEBUG("Unable to queue packet - connections at maximum capacity");
-       fail:
-               fr_assert(!u->rr && !u->packet);        /* Should not have been fed to the muxer */
-               trunk_request_free(&treq);              /* Return to the free list */
-               talloc_free(r);
-               RETURN_MODULE_FAIL;
-
-       case TRUNK_ENQUEUE_DST_UNAVAILABLE:
-               REDEBUG("All destinations are down - cannot send packet");
-               goto fail;
-
-       case TRUNK_ENQUEUE_FAIL:
-               REDEBUG("Unable to queue packet");
-               goto fail;
-       }
-
-       r->treq = treq; /* Remember for signalling purposes */
-       fr_assert(treq->rctx == r);
-
-       talloc_set_destructor(u, _udp_request_free);
-
-
-       if (inst->parent->synchronous || inst->parent->replicate) { /* @todo - or if stream! */
-               retry_config = &inst->retry_config;
-
-       } else {
-               retry_config = &inst->parent->retry[u->code];
-       }
-
-       /*
-        *      The event loop will take care of demux && sending the
-        *      packet, along with any retransmissions.
-        */
-       return unlang_module_yield_to_retry(request, mod_resume, mod_retry, mod_signal, 0, r, retry_config);
-}
-
-/** Instantiate thread data for the submodule.
- *
- */
-static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
-{
-       rlm_radius_udp_t                *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_udp_t);
-       udp_thread_t                    *thread = talloc_get_type_abort(mctx->thread, udp_thread_t);
-
-       static trunk_io_funcs_t io_funcs = {
-                                               .connection_alloc = thread_conn_alloc,
-                                               .connection_notify = thread_conn_notify,
-                                               .request_prioritise = request_prioritise,
-                                               .request_mux = request_mux,
-                                               .request_demux = request_demux,
-                                               .request_conn_release = request_conn_release,
-                                               .request_complete = request_complete,
-                                               .request_fail = request_fail,
-                                               .request_cancel = request_cancel,
-                                               .request_free = request_free
-                                       };
-
-       thread->el = mctx->el;
-       thread->inst = inst;
-       thread->trunk = trunk_alloc(thread, mctx->el, &io_funcs,
-                                   &inst->trunk_conf, inst->parent->name, thread, false);
-       if (!thread->trunk) return -1;
-
-       return 0;
-}
-
-static int mod_instantiate(module_inst_ctx_t const *mctx)
-{
-       rlm_radius_t            *parent = talloc_get_type_abort(mctx->mi->parent->data, rlm_radius_t);
-       rlm_radius_udp_t        *inst = talloc_get_type_abort(mctx->mi->data, rlm_radius_udp_t);
-       CONF_SECTION            *conf = mctx->mi->conf;
-
-       if (!parent) {
-               ERROR("IO module cannot be instantiated directly");
-               return -1;
-       }
-
-       inst->parent = parent;
-       inst->replicate = parent->replicate;
-
-       inst->retry_config = (fr_retry_config_t) {
-               .mrc = 1,
-               .mrd = inst->parent->response_window,
-       };
-
-       /*
-        *      Always need at least one mmsgvec
-        */
-       if (inst->max_send_coalesce == 0) inst->max_send_coalesce = 1;
-
-       /*
-        *      Ensure that we have a destination address.
-        */
-       if (inst->dst_ipaddr.af == AF_UNSPEC) {
-               cf_log_err(conf, "A value must be given for 'ipaddr'");
-               return -1;
-       }
-
-       inst->common_ctx = (fr_radius_ctx_t) {
-               .secret = inst->secret,
-               .secret_length = talloc_array_length(inst->secret) - 1,
-               .proxy_state = inst->parent->proxy_state,
-       };
-
-       /*
-        *      If src_ipaddr isn't set, make sure it's INADDR_ANY, of
-        *      the same address family as dst_ipaddr.
-        */
-       if (inst->src_ipaddr.af == AF_UNSPEC) {
-               memset(&inst->src_ipaddr, 0, sizeof(inst->src_ipaddr));
-
-               inst->src_ipaddr.af = inst->dst_ipaddr.af;
-
-               if (inst->src_ipaddr.af == AF_INET) {
-                       inst->src_ipaddr.prefix = 32;
-               } else {
-                       inst->src_ipaddr.prefix = 128;
-               }
-       }
-
-       else if (inst->src_ipaddr.af != inst->dst_ipaddr.af) {
-               cf_log_err(conf, "The 'ipaddr' and 'src_ipaddr' configuration items must "
-                          "be both of the same address family");
-               return -1;
-       }
-
-       if (!inst->dst_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
-        */
-       FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, 64);
-       FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
-
-
-#ifdef __linux__
-       if (inst->replicate) {
-               /*
-                *      Replicating: Set the receive buffer to zero.
-                */
-               inst->recv_buff_is_set = true;
-
-               /*
-                *      On Linux this has the effect of discarding
-                *      all incoming data in the kernel.
-                *      With macOS and others it's an invalid value.
-                */
-
-               inst->recv_buff = 0;
-       } else {
-#endif
-               if (inst->recv_buff_is_set) {
-                       FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, inst->max_packet_size);
-                       FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, (1 << 30));
-               }
-#ifdef __linux__
-       }
-#endif
-
-       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));
-       }
-
-       memcpy(&inst->trunk_conf, &inst->parent->trunk_conf, sizeof(inst->trunk_conf));
-       inst->trunk_conf.req_pool_headers = 4;  /* One for the request, one for the buffer, one for the tracking binding, one for Proxy-State VP */
-       inst->trunk_conf.req_pool_size = sizeof(udp_request_t) + inst->max_packet_size + sizeof(radius_track_entry_t ***) + sizeof(fr_pair_t) + 20;
-
-       return 0;
-}
-
-extern rlm_radius_io_t rlm_radius_udp;
-rlm_radius_io_t rlm_radius_udp = {
-       .common = {
-               .magic                  = MODULE_MAGIC_INIT,
-               .name                   = "radius_udp",
-               .inst_size              = sizeof(rlm_radius_udp_t),
-
-               .thread_inst_size       = sizeof(udp_thread_t),
-               .thread_inst_type       = "udp_thread_t",
-
-               .config                 = module_config,
-               .instantiate            = mod_instantiate,
-               .thread_instantiate     = mod_thread_instantiate,
-       },
-       .enqueue                = mod_enqueue,
-};
diff --git a/src/modules/rlm_radius/rlm_radius_udp.mk b/src/modules/rlm_radius/rlm_radius_udp.mk
deleted file mode 100644 (file)
index dba1b03..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-TARGETNAME     := rlm_radius_udp
-TARGET         := $(TARGETNAME)$(L)
-
-SOURCES                := rlm_radius_udp.c track.c
-
-TGT_PREREQS    := libfreeradius-radius$(L)