The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_tacacs_tcp.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 6d88be9981d121dd38e1a24c4d80a2f1164ce241 $
19 * @file rlm_tacacs_tcp.c
20 * @brief TACACS+ transport
21 *
22 * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
23 */
24RCSID("$Id: 6d88be9981d121dd38e1a24c4d80a2f1164ce241 $")
25
26#include <freeradius-devel/io/application.h>
27#include <freeradius-devel/io/listen.h>
28#include <freeradius-devel/io/pair.h>
29#include <freeradius-devel/missing.h>
30#include <freeradius-devel/server/connection.h>
31#include <freeradius-devel/util/debug.h>
32#include <freeradius-devel/util/heap.h>
33
34#include <sys/socket.h>
35#include <sys/uio.h>
36
37#include "rlm_tacacs.h"
38
39/** Static configuration for the module.
40 *
41 */
42typedef struct {
43 rlm_tacacs_t *parent; //!< rlm_tacacs instance.
45
46 fr_ipaddr_t dst_ipaddr; //!< IP of the home server.
47 fr_ipaddr_t src_ipaddr; //!< IP we open our socket on.
48 uint16_t dst_port; //!< Port of the home server.
49 char const *secret; //!< Shared secret.
50 size_t secretlen; //!< length of secret
51
52 char const *interface; //!< Interface to bind to.
53
54 uint32_t recv_buff; //!< How big the kernel's receive buffer should be.
55 uint32_t send_buff; //!< How big the kernel's send buffer should be.
56
57 uint32_t max_packet_size; //!< Maximum packet size.
58 uint16_t max_send_coalesce; //!< Maximum number of packets to coalesce into one mmsg call.
59
60 bool recv_buff_is_set; //!< Whether we were provided with a recv_buf
61 bool send_buff_is_set; //!< Whether we were provided with a send_buf
62
63 fr_pair_list_t *trigger_args; //!< Pairs passed to trigger request.
65
66typedef struct {
67 fr_event_list_t *el; //!< Event list.
68
69 rlm_tacacs_tcp_t const *inst; //!< our instance
70
71 trunk_conf_t trunk_conf; //!< trunk configuration
72 trunk_t *trunk; //!< trunk handler
74
75typedef struct {
77 rlm_rcode_t rcode; //!< from the transport
79
81
82typedef struct {
83 uint8_t *read; //!< where we read data from
84 uint8_t *write; //!< where we write data to
85 uint8_t *end; //!< end of the buffer
86 uint8_t *data; //!< actual data
88
89/** Track the handle, which is tightly correlated with the FD
90 *
91 */
92typedef struct {
93 char const *name; //!< From IP PORT to IP PORT.
94 char const *module_name; //!< the module that opened the connection
95
96 int fd; //!< File descriptor.
97
98 trunk_request_t **coalesced; //!< Outbound coalesced requests.
99
100 size_t send_buff_actual; //!< What we believe the maximum SO_SNDBUF size to be.
101 ///< We don't try and encode more packet data than this
102 ///< in one go.
103
104 rlm_tacacs_tcp_t const *inst; //!< Our module instance.
106
107 uint32_t session_id; //!< for TACACS+ "security".
108
109 uint32_t max_packet_size; //!< Our max packet size. may be different from the parent.
110
111 fr_ipaddr_t src_ipaddr; //!< Source IP address. May be altered on bind
112 //!< to be the actual IP address packets will be
113 //!< sent on. This is why we can't use the inst
114 //!< src_ipaddr field.
115 uint16_t src_port; //!< Source port specific to this connection.
116 //!< @todo - not set by socket_client_tcp()
117
118 tcp_buffer_t recv; //!< receive buffer
119 tcp_buffer_t send; //!< send buffer
120
121 int id; //!< starts at 1.
122 int active; //!< active packets
123 trunk_request_t *tracking[UINT8_MAX]; //!< all sequential!
124
125 fr_time_t mrs_time; //!< Most recent sent time which had a reply.
126 fr_time_t last_reply; //!< When we last received a reply.
127 fr_time_t first_sent; //!< first time we sent a packet since going idle
128 fr_time_t last_sent; //!< last time we sent a packet.
129 fr_time_t last_idle; //!< last time we had nothing to do
130
131 fr_timer_t *zombie_ev; //!< Zombie timeout.
132
133 trunk_connection_t *tconn; //!< trunk connection
135
136
137/** Connect request_t to local tracking structure
138 *
139 */
141 uint32_t priority; //!< copied from request->async->priority
142 fr_time_t recv_time; //!< copied from request->async->recv_time
143
144 uint8_t code; //!< Packet code.
145 uint8_t id; //!< Last ID assigned to this packet.
146 bool outstanding; //!< are we waiting for a reply?
147
148 uint8_t *packet; //!< Packet we write to the network.
149 size_t packet_len; //!< Length of the packet.
150
151 fr_timer_t *ev; //!< timer for retransmissions
152 fr_retry_t retry; //!< retransmission timers
153};
154
172
173static const conf_parser_t module_config[] = {
175 { FR_CONF_OFFSET_TYPE_FLAGS("ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_tacacs_tcp_t, dst_ipaddr) },
176 { FR_CONF_OFFSET_TYPE_FLAGS("ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_tacacs_tcp_t, dst_ipaddr) },
177
178 { FR_CONF_OFFSET("port", rlm_tacacs_tcp_t, dst_port) },
179
180 { FR_CONF_OFFSET("secret", rlm_tacacs_tcp_t, secret) }, /* can be NULL */
181
182 { FR_CONF_OFFSET("interface", rlm_tacacs_tcp_t, interface) },
183
184 { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, 0, rlm_tacacs_tcp_t, recv_buff) },
185 { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, 0, rlm_tacacs_tcp_t, send_buff) },
186
187 { FR_CONF_OFFSET("max_packet_size", rlm_tacacs_tcp_t, max_packet_size), .dflt = STRINGIFY(FR_MAX_PACKET_SIZE) },
188 { FR_CONF_OFFSET("max_send_coalesce", rlm_tacacs_tcp_t, max_send_coalesce), .dflt = "1024" },
189
190 { FR_CONF_OFFSET_TYPE_FLAGS("src_ipaddr", FR_TYPE_COMBO_IP_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
191 { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
192 { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
193
195};
196
197static fr_dict_t const *dict_tacacs;
198
201 { .out = &dict_tacacs, .proto = "tacacs" },
202 { NULL }
203};
204
208
211 { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
212 { .out = &attr_packet_hdr, .name = "Packet", .type = FR_TYPE_STRUCT, .dict = &dict_tacacs },
213 { .out = &attr_session_id, .name = "Packet.Session-ID", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
214 { NULL }
215};
216
217/** Clear out any connection specific resources from a tcp request
218 *
219 */
221{
222 req->packet = NULL;
223
224 fr_assert(h->active > 0);
225 fr_assert(h->tracking[req->id] != NULL);
226 fr_assert(h->tracking[req->id]->preq == req);
227
228 h->tracking[req->id] = NULL;
229 req->outstanding = false;
230 h->active--;
231
232 FR_TIMER_DISARM(req->ev);
233
234 /*
235 * We've sent 255 packets, and received all replies. Shut the connection down.
236 *
237 * Welcome to the insanity that is TACACS+.
238 */
239 if ((h->active == 0) && (h->id > 255)) {
241 }
242}
243
244
245/** Free a connection handle, closing associated resources
246 *
247 */
249{
250 fr_assert(h->fd >= 0);
251
253
254 if (shutdown(h->fd, SHUT_RDWR) < 0) {
255 DEBUG3("%s - Failed shutting down connection %s: %s",
256 h->module_name, h->name, fr_syserror(errno));
257 }
258
259 if (close(h->fd) < 0) {
260 DEBUG3("%s - Failed closing connection %s: %s",
261 h->module_name, h->name, fr_syserror(errno));
262 }
263
264 h->fd = -1;
265
266 DEBUG("%s - Connection closed - %s", h->module_name, h->name);
267
268 return 0;
269}
270
271/** Initialise a new outbound connection
272 *
273 * @param[out] h_out Where to write the new file descriptor.
274 * @param[in] conn to initialise.
275 * @param[in] uctx A #tcp_thread_t
276 */
277CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
278static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx)
279{
280 int fd;
281 tcp_handle_t *h;
282 tcp_thread_t *thread = talloc_get_type_abort(uctx, tcp_thread_t);
283
284 MEM(h = talloc_zero(conn, tcp_handle_t));
285 h->thread = thread;
286 h->inst = thread->inst;
287 h->module_name = h->inst->parent->name;
288 h->src_ipaddr = h->inst->src_ipaddr;
289 h->src_port = 0;
291 h->last_idle = fr_time();
292
293 h->id = 1; /* clients send odd sequence numbers */
294 h->session_id = fr_rand();
295
296 /*
297 * Initialize the buffer of coalesced packets we're doing to write.
298 */
299 h->coalesced = talloc_zero_array(h, trunk_request_t *, h->inst->max_send_coalesce);
300
301 /*
302 * Open the outgoing socket.
303 */
304 fd = fr_socket_client_tcp(NULL, &h->src_ipaddr, &h->inst->dst_ipaddr, h->inst->dst_port, true);
305 if (fd < 0) {
306 PERROR("%s - Failed opening socket", h->module_name);
307 talloc_free(h);
309 }
310
311 /*
312 * Set the connection name.
313 */
314 h->name = fr_asprintf(h, "proto tcp local %pV port %u remote %pV port %u",
317
318 talloc_set_destructor(h, _tcp_handle_free);
319
320#ifdef SO_RCVBUF
321 if (h->inst->recv_buff_is_set) {
322 int opt;
323
324 opt = h->inst->recv_buff;
325 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) {
326 WARN("%s - Failed setting 'SO_RCVBUF': %s", h->module_name, fr_syserror(errno));
327 }
328 }
329#endif
330
331#ifdef SO_SNDBUF
332 {
333 int opt;
334 socklen_t socklen = sizeof(int);
335
336 if (h->inst->send_buff_is_set) {
337 opt = h->inst->send_buff;
338 if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) {
339 WARN("%s - Failed setting 'SO_SNDBUF', write performance may be sub-optimal: %s",
340 h->module_name, fr_syserror(errno));
341 }
342 }
343
344 if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, &socklen) < 0) {
345 WARN("%s - Failed getting 'SO_SNDBUF', write performance may be sub-optimal: %s",
346 h->module_name, fr_syserror(errno));
347
348 /*
349 * This controls how many packets we attempt
350 * to send at once. Nothing bad happens if
351 * we get it wrong, but the user may see
352 * ENOBUFS errors at high packet rates.
353 *
354 * Since this is TACACS, we have small
355 * packets and a maximum of 255 packets
356 * per connection. So don't set this too large.
357 */
358 if (h->inst->send_buff_is_set) {
360 } else {
362 if (h->send_buff_actual > 256*1024) h->send_buff_actual = 256*1024;
363 }
364
365 WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name,
367 } else {
368#ifdef __linux__
369 /*
370 * Linux doubles the buffer when you set it
371 * to account for "overhead".
372 */
373 h->send_buff_actual = ((size_t)opt) / 2;
374#else
375 h->send_buff_actual = (size_t)opt;
376#endif
377 }
378 }
379#else
381 h->inst_send_buff : h->max_packet_size * h->inst->max_send_coalesce;
382
383 WARN("%s - Modifying 'SO_SNDBUF' value is not supported on this system, "
384 "write performance may be sub-optimal", h->module_name);
385 WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name, h->inst->send_buff_actual);
386#endif
387
388 /*
389 * Allow receiving of 2 max-sized packets. In practice, most packets will be less than this.
390 */
391 MEM(h->recv.data = talloc_array(h, uint8_t, h->max_packet_size * 2));
392 h->recv.read = h->recv.write = h->recv.data;
393 h->recv.end = h->recv.data + h->max_packet_size * 2;
394
395 /*
396 * Use the system SO_SNDBUF for how many packets to send at once. In most circumstances the
397 * packets are small, and widely separated in time, and we really only need a very small buffer.
398 */
399 MEM(h->send.data = talloc_array(h, uint8_t, h->send_buff_actual));
400 h->send.read = h->send.write = h->send.data;
401 h->send.end = h->send.data + h->send_buff_actual;
402
403 h->fd = fd;
404
405 /*
406 * Signal the connection
407 * as open as soon as it becomes writable.
408 */
409 connection_signal_on_fd(conn, fd);
410
411 *h_out = h;
412
413 // @todo - initialize the tracking memory, etc.
414 // i.e. histograms (or hyperloglog) of packets, so we can see
415 // which connections / home servers are fast / slow.
416
418}
419
420/** Shutdown/close a file descriptor
421 *
422 */
423static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx)
424{
425 tcp_handle_t *h = talloc_get_type_abort(handle, tcp_handle_t);
426
427 /*
428 * There's tracking entries still allocated
429 * this is bad, they should have all been
430 * released.
431 */
432 fr_assert(!h->active);
433
434 DEBUG4("Freeing rlm_tacacs_tcp handle %p", handle);
435
436 talloc_free(h);
437}
438
439CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
441 connection_conf_t const *conf,
442 char const *log_prefix, void *uctx)
443{
444 connection_t *conn;
445 tcp_thread_t *thread = talloc_get_type_abort(uctx, tcp_thread_t);
446
447 conn = connection_alloc(tconn, el,
449 .init = conn_init,
450 .close = conn_close,
451 },
452 conf,
453 log_prefix,
454 thread);
455 if (!conn) {
456 PERROR("%s - Failed allocating state handler for new connection", thread->inst->parent->name);
457 return NULL;
458 }
459
460 return conn;
461}
462
463/** Connection errored
464 *
465 * We were signalled by the event loop that a fatal error occurred on this connection.
466 *
467 * @param[in] el The event list signalling.
468 * @param[in] fd that errored.
469 * @param[in] flags El flags.
470 * @param[in] fd_errno The nature of the error.
471 * @param[in] uctx The trunk connection handle (tconn).
472 */
473static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
474{
475 trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
476 connection_t *conn = tconn->conn;
477 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
478
479 if (fd_errno) ERROR("%s - Connection %s failed: %s", h->module_name, h->name, fr_syserror(fd_errno));
480
482}
483
484CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
487 trunk_connection_event_t notify_on, UNUSED void *uctx)
488{
489 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
490 fr_event_fd_cb_t read_fn = NULL;
491 fr_event_fd_cb_t write_fn = NULL;
492
493 switch (notify_on) {
495 return;
496
499 break;
500
503 break;
504
508 break;
509
510 }
511
512 if (fr_event_fd_insert(h, NULL, el, h->fd,
513 read_fn,
514 write_fn,
516 tconn) < 0) {
517 PERROR("%s - Failed inserting FD event", h->module_name);
518
519 /*
520 * May free the connection!
521 */
523 }
524}
525
526/*
527 * Return negative numbers to put 'a' at the top of the heap.
528 * Return positive numbers to put 'b' at the top of the heap.
529 *
530 * We want the value with the lowest timestamp to be prioritized at
531 * the top of the heap.
532 */
533static int8_t request_prioritise(void const *one, void const *two)
534{
535 tcp_request_t const *a = one;
536 tcp_request_t const *b = two;
537 int8_t ret;
538
539 /*
540 * Larger priority is more important.
541 */
542 ret = CMP(a->priority, b->priority);
543 if (ret != 0) return ret;
544
545 /*
546 * Smaller timestamp (i.e. earlier) is more important.
547 */
549}
550
551/** Decode response packet data, extracting relevant information and validating the packet
552 *
553 * @param[in] ctx to allocate pairs in.
554 * @param[out] reply Pointer to head of pair list to add reply attributes to.
555 * @param[out] response_code The type of response packet.
556 * @param[in] h connection handle.
557 * @param[in] request the request.
558 * @param[in] req TCP request.
559 * @param[in] data to decode.
560 * @param[in] data_len Length of input data.
561 * @return
562 * - <0 on error
563 * - >0 for how many bytes were decoded
564 */
565static ssize_t decode(TALLOC_CTX *ctx, fr_pair_list_t *reply, uint8_t *response_code,
566 tcp_handle_t *h, request_t *request, tcp_request_t *req,
567 uint8_t *data, size_t data_len)
568{
569 rlm_tacacs_tcp_t const *inst = h->thread->inst;
570 ssize_t packet_len;
571 int code;
572
573 *response_code = 0; /* Initialise to keep the rest of the code happy */
574
575 /*
576 * Check the session ID.
577 */
578 if (memcmp(data + 4, req->packet + 4, 4) != 0) {
579 REDEBUG("Session ID %08x does not match expected number %08x",
581 }
582
583 /*
584 * Decode the attributes, in the context of the reply.
585 * This only fails if the packet is strangely malformed,
586 * or if we run out of memory.
587 */
588 packet_len = fr_tacacs_decode(ctx, reply, NULL, data, data_len, NULL, inst->secret, inst->secretlen, &code);
589 if (packet_len < 0) {
590 RPEDEBUG("Failed decoding TACACS+ reply packet");
591 fr_pair_list_free(reply);
592 return -1;
593 }
594
595 RDEBUG("Received %s ID %d length %ld reply packet on connection %s",
596 fr_tacacs_packet_names[code], code, packet_len, h->name);
597 log_request_pair_list(L_DBG_LVL_2, request, NULL, reply, NULL);
598
599 *response_code = code;
600
601 /*
602 * Fixup retry times
603 */
604 if (fr_time_gt(req->retry.start, h->mrs_time)) h->mrs_time = req->retry.start;
605
606 return packet_len;
607}
608
609static int encode(tcp_handle_t *h, request_t *request, tcp_request_t *req)
610{
611 ssize_t packet_len;
612 rlm_tacacs_tcp_t const *inst = h->inst;
613 fr_pair_t *hdr, *vp;
614
615 fr_assert(inst->parent->allowed[req->code]);
616 fr_assert(!req->packet);
617 fr_assert(!req->outstanding);
618
619 /*
620 * Encode the packet in the outbound buffer.
621 */
622 req->packet = h->send.write;
623
624 /*
625 * Set the session ID, if it hasn't already been set.
626 */
627 hdr = fr_pair_find_by_da(&request->request_pairs, NULL, attr_packet_hdr);
628 if (!hdr) hdr = request->request_ctx;
629
630 vp = fr_pair_find_by_da_nested(&hdr->vp_group, NULL, attr_session_id);
631 if (!vp) {
633
634 vp->vp_uint32 = h->session_id;
635 fr_pair_append(&hdr->vp_group, vp);
637 }
638
639 /*
640 * Encode the packet.
641 */
642 packet_len = fr_tacacs_encode(&FR_DBUFF_TMP(req->packet, (size_t) inst->max_packet_size), NULL,
643 inst->secret, inst->secretlen, request->reply->code, &request->request_pairs);
644 if (packet_len < 0) {
645 RPERROR("Failed encoding packet");
646 return -1;
647 }
648
649 /*
650 * Update the ID and the actual packet length;
651 */
652 req->packet[1] = req->id;
653 req->packet_len = packet_len;
654 req->outstanding = true;
655
656// fr_tacacs_packet_log_hex(&default_log, u->packet);
657
658 return 0;
659}
660
661
662/** Revive a connection after "revive_interval"
663 *
664 */
665static void revive_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
666{
667 trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
668 tcp_handle_t *h = talloc_get_type_abort(tconn->conn->h, tcp_handle_t);
669
670 INFO("%s - Reviving connection %s", h->module_name, h->name);
672}
673
674/** Mark a connection dead after "zombie_interval"
675 *
676 */
677static void zombie_timeout(fr_timer_list_t *tl, fr_time_t now, void *uctx)
678{
679 trunk_connection_t *tconn = talloc_get_type_abort(uctx, trunk_connection_t);
680 tcp_handle_t *h = talloc_get_type_abort(tconn->conn->h, tcp_handle_t);
681
682 INFO("%s - No replies during 'zombie_period', marking connection %s as dead", h->module_name, h->name);
683
684 /*
685 * Don't use this connection, and re-queue all of its
686 * requests onto other connections.
687 */
690
691 /*
692 * Revive the connection after a time.
693 */
694 if (fr_timer_at(h, tl, &h->zombie_ev,
695 fr_time_add(now, h->inst->parent->revive_interval), false, revive_timeout, h) < 0) {
696 ERROR("Failed inserting revive timeout for connection");
698 }
699}
700
701
702/** See if the connection is zombied.
703 *
704 * We check for zombie when major events happen:
705 *
706 * 1) request hits its final timeout
707 * 2) request timer hits, and it needs to be retransmitted
708 * 3) a DUP packet comes in, and the request needs to be retransmitted
709 * 4) we're sending a packet.
710 *
711 * There MIGHT not be retries configured, so we MUST check for zombie
712 * when any new packet comes in. Similarly, there MIGHT not be new
713 * packets, but retries are configured, so we have to check there,
714 * too.
715 *
716 * Also, the socket might not be writable for a while. There MIGHT
717 * be a long time between getting the timer / DUP signal, and the
718 * request finally being written to the socket. So we need to check
719 * for zombie at BOTH the timeout and the mux / write function.
720 *
721 * @return
722 * - true if the connection is zombie.
723 * - false if the connection is not zombie.
724 */
726{
727 tcp_handle_t *h = talloc_get_type_abort(tconn->conn->h, tcp_handle_t);
728
729 /*
730 * If we're already zombie, don't go to zombie
731 *
732 */
733 if (h->zombie_ev) return true;
734
735 if (fr_time_eq(now, fr_time_wrap(0))) now = fr_time();
736
737 /*
738 * We received a reply since this packet was sent, the connection isn't zombie.
739 */
740 if (fr_time_gteq(h->last_reply, last_sent)) return false;
741
742 /*
743 * If we've seen ANY response in the allowed window, then the connection is still alive.
744 */
745 if (fr_time_gt(last_sent, fr_time_wrap(0)) &&
746 (fr_time_lt(fr_time_add(last_sent, h->inst->parent->response_window), now))) return false;
747
748 /*
749 * Mark the connection as inactive, but keep sending
750 * packets on it.
751 */
752 WARN("%s - Entering Zombie state - connection %s", h->module_name, h->name);
754
755 if (fr_timer_at(h, tl, &h->zombie_ev, fr_time_add(now, h->inst->parent->zombie_period),
756 false, zombie_timeout, h) < 0) {
757 ERROR("Failed inserting zombie timeout for connection");
759 }
760
761 return true;
762}
763
764/** Handle retries.
765 *
766 * Note that with TCP we don't actually retry on this particular connection, but the retry timer allows us to
767 * fail over from one connection to another when a connection fails.
768 */
769static void request_retry(fr_timer_list_t *tl, fr_time_t now, void *uctx)
770{
771 trunk_request_t *treq = talloc_get_type_abort(uctx, trunk_request_t);
772 tcp_request_t *req = talloc_get_type_abort(treq->preq, tcp_request_t);
773 tcp_result_t *r = talloc_get_type_abort(treq->rctx, tcp_result_t);
774 request_t *request = treq->request;
775 trunk_connection_t *tconn = treq->tconn;
776
777 fr_assert(treq->state == TRUNK_REQUEST_STATE_SENT); /* No other states should be timing out */
778 fr_assert(treq->preq); /* Must still have a protocol request */
779 fr_assert(tconn);
780
781 switch (fr_retry_next(&req->retry, now)) {
782 /*
783 * Queue the request for retransmission.
784 *
785 * @todo - set up "next" timer here, instead of in
786 * request_mux() ? That way we can catch the case of
787 * packets sitting in the queue for extended periods of
788 * time, and still run the timers.
789 */
792 return;
793
794 case FR_RETRY_MRD:
795 REDEBUG("Reached maximum_retransmit_duration (%pVs > %pVs), failing request",
797 break;
798
799 case FR_RETRY_MRC:
800 REDEBUG("Reached maximum_retransmit_count (%u > %u), failing request",
801 req->retry.count, req->retry.config->mrc);
802 break;
803 }
804
807
808 check_for_zombie(tl, tconn, now, req->retry.start);
809}
810
811CC_NO_UBSAN(function) /* UBSAN: false positive - public vs private connection_t trips --fsanitize=function*/
813 trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
814{
815 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
816 rlm_tacacs_tcp_t const *inst = h->inst;
817 ssize_t sent;
818 uint16_t i, queued;
819 uint8_t const *written;
820 uint8_t *partial;
821
822 /*
823 * Encode multiple packets in preparation for transmission with write()
824 */
825 for (i = 0, queued = 0; (i < inst->max_send_coalesce); i++) {
826 trunk_request_t *treq;
827 tcp_request_t *req;
828 request_t *request;
829
830 if (unlikely(trunk_connection_pop_request(&treq, tconn) < 0)) return;
831
832 /*
833 * No more requests to send
834 */
835 if (!treq) break;
836
837 /*
838 * The partial write MUST be the first one popped off of the request list.
839 *
840 * If we have a partial packet, then we know that there's partial data in the output
841 * buffer. However, the request MAY still be freed or timed out before we can write the
842 * data. As a result, we ignore the tcp_request_t, and just keep writing the data.
843 */
844 if (treq->state == TRUNK_REQUEST_STATE_PARTIAL) {
845 fr_assert(h->send.read == h->send.data);
846 fr_assert(h->send.write > h->send.read);
847
848 fr_assert(i == 0);
849
850 h->coalesced[0] = treq;
851 goto next;
852 }
853
854 /*
855 * The request must still be pending.
856 */
858
859 request = treq->request;
860 req = talloc_get_type_abort(treq->preq, tcp_request_t);
861
862 /*
863 * We'd like to retransmit the packet on this connection, but it's TCP so we don't.
864 *
865 * The retransmission timers are really there to move the packet to a new connection if
866 * the current connection is dead.
867 */
868 if (req->outstanding) continue;
869
870 /*
871 * Not enough room for a full-sized packet, stop encoding packets
872 */
873 if ((h->send.end - h->send.write) < inst->max_packet_size) {
874 break;
875 }
876
877 /*
878 * Start retransmissions from when the socket is writable.
879 */
880 fr_retry_init(&req->retry, fr_time(), &h->inst->parent->retry);
883
884 /*
885 * Set up the packet for encoding.
886 */
887 req->id = h->id;
888 h->tconn = tconn;
889
890 h->tracking[req->id] = treq;
891 h->id += 2;
892 h->active++;
893
894 RDEBUG("Sending %s ID %d length %ld over connection %s",
895 fr_tacacs_packet_names[req->code], req->id, req->packet_len, h->name);
896
897 if (encode(h, request, req) < 0) {
898 /*
899 * Need to do this because request_conn_release
900 * may not be called.
901 */
902 tcp_request_reset(h, req);
904 continue;
905 }
906 RHEXDUMP3(req->packet, req->packet_len, "Encoded packet");
907
908 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
909
910 /*
911 * Remember that we've encoded this packet.
912 */
913 h->coalesced[queued] = treq;
914 h->send.write += req->packet_len;
915
916 fr_assert(h->send.write <= h->send.end);
917
918 /*
919 * If we just hit this limit, stop using the connection.
920 *
921 * When we've received all replies (or timeouts), we'll close the connections.
922 */
923 if (h->id > 255) {
925 }
926
927 next:
928 /*
929 * Tell the trunk API that this request is now in
930 * the "sent" state. And we don't want to see
931 * this request again. The request hasn't actually
932 * been sent, but it's the only way to get at the
933 * next entry in the heap.
934 */
936 queued++;
937 }
938
939 if (queued == 0) return;
940
941 /*
942 * Verify nothing accidentally freed the connection handle
943 */
944 (void)talloc_get_type_abort(h, tcp_handle_t);
945
946 /*
947 * Send the packets as one system call.
948 */
949 sent = write(h->fd, h->send.read, h->send.write - h->send.read);
950 if (sent < 0) { /* Error means no messages were sent */
951 /*
952 * Temporary conditions
953 */
954 switch (errno) {
955#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
956 case EWOULDBLOCK: /* No outbound packet buffers, maybe? */
957#endif
958 case EAGAIN: /* No outbound packet buffers, maybe? */
959 case EINTR: /* Interrupted by signal */
960 case ENOBUFS: /* No outbound packet buffers, maybe? */
961 case ENOMEM: /* malloc failure in kernel? */
962 WARN("%s - Failed sending data over connection %s: %s",
963 h->module_name, h->name, fr_syserror(errno));
964 sent = 0;
965 break;
966
967 /*
968 * Will re-queue any 'sent' requests, so we don't
969 * have to do any cleanup.
970 */
971 default:
972 ERROR("%s - Failed sending data over connection %s: %s",
973 h->module_name, h->name, fr_syserror(errno));
975 return;
976 }
977 }
978
979 written = h->send.read + sent;
980 partial = h->send.read;
981
982 /*
983 * For all messages that were actually sent by writev()
984 * start the request timer.
985 */
986 for (i = 0; i < queued; i++) {
987 trunk_request_t *treq = h->coalesced[i];
988 tcp_request_t *req;
989 request_t *request;
990
991 /*
992 * We *think* we sent this, but we might not had :(
993 */
994 fr_assert(treq->state == TRUNK_REQUEST_STATE_SENT);
995
996 request = treq->request;
997 req = talloc_get_type_abort(treq->preq, tcp_request_t);
998
999 /*
1000 * This packet ends before the piece we've
1001 * written, so we've written all of it.
1002 */
1003 if (req->packet + req->packet_len <= written) {
1004 h->last_sent = req->retry.start;
1006
1007 if (fr_timer_at(req, el->tl, &req->ev, req->retry.next, false, request_retry, treq) < 0) {
1008 RERROR("Failed inserting retransmit timeout for connection");
1010 }
1011
1012 /*
1013 * If the packet doesn't get a response, then the timer will hit
1014 * and will retransmit.
1015 */
1016 req->outstanding = true;
1017 continue;
1018 }
1019
1020 /*
1021 * The packet starts before the piece we've written, BUT ends after the written piece.
1022 *
1023 * We only wrote part of this packet, remember the partial packet we wrote. Note that
1024 * we only track the packet data, and not the tcp_request_t. The underlying request (and
1025 * u) may disappear at any time, even if there's still data in the buffer.
1026 *
1027 * Then, signal that isn't a partial packet, and stop processing the queue, as we know
1028 * that the next packet wasn't written.
1029 */
1030 if (req->packet < written) {
1031 size_t skip = written - req->packet;
1032 size_t left = req->packet_len - skip;
1033
1034 fr_assert(req->packet + req->packet_len > written);
1035
1036 memmove(h->send.data, req->packet, left);
1037
1038 fr_assert(h->send.read == h->send.data);
1039 partial = h->send.data + left;
1040 req->outstanding = true;
1041
1043 continue;
1044 }
1045
1046 /*
1047 * The packet starts after the piece we've written, so we haven't written any of it.
1048 *
1049 * Requests that weren't sent get re-enqueued. Which means that they get re-encoded, but
1050 * oh well.
1051 *
1052 * The cancel logic runs as per-normal and cleans up
1053 * the request ready for sending again...
1054 */
1056 fr_assert(!req->outstanding); /* must have called request_requeue() */
1057 }
1058
1059 /*
1060 * Remember where to write the next packet. Either at the start of the buffer, or after the one
1061 * which was partially written.
1062 */
1063 h->send.write = partial;
1064}
1065
1067{
1068 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
1069 bool do_read = true;
1070
1071 DEBUG3("%s - Reading data for connection %s", h->module_name, h->name);
1072
1073 while (true) {
1074 ssize_t slen;
1075 size_t available, used, packet_len;
1076
1077 trunk_request_t *treq;
1078 request_t *request;
1079 tcp_request_t *req;
1080 tcp_result_t *r;
1081 uint8_t code = 0;
1082 fr_pair_list_t reply;
1083
1084 /*
1085 * Ensure that we can read at least one max-sized packet.
1086 *
1087 * If not, move the trailing bytes to the start of the buffer, and reset the read/write
1088 * pointers to the start of the buffer. Note that the read buffer has to be at least 2x
1089 * max_packet_size.
1090 */
1091 available = h->recv.end - h->recv.read;
1092 if (available < h->inst->max_packet_size) {
1094
1095 used = h->recv.write - h->recv.read;
1096
1097 memcpy(h->recv.data, h->recv.read, used);
1098 h->recv.read = h->recv.data;
1099 h->recv.write = h->recv.read + used;
1100 }
1101
1102 /*
1103 * Read as much data as possible.
1104 *
1105 * We don't need to call read() on every round through the loop. Instead, we call it
1106 * only when this function first gets called, OR if the read stopped at the end of the
1107 * buffer.
1108 *
1109 * This allows us to read a large amount of data at once, and then process multiple
1110 * packets without calling read() too many times.
1111 */
1112 if (do_read) {
1113 slen = read(h->fd, h->recv.write, h->recv.end - h->recv.write);
1114 if (slen < 0) {
1115 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) return;
1116
1117 ERROR("%s - Failed reading response from socket: %s",
1118 h->module_name, fr_syserror(errno));
1120 return;
1121 }
1122
1123 h->recv.write += slen;
1124 do_read = (h->recv.write == h->recv.end);
1125 }
1126
1127 used = h->recv.write - h->recv.read;
1128
1129 /*
1130 * We haven't received a full header, read more or return.
1131 */
1132 if (used < sizeof(fr_tacacs_packet_hdr_t)) {
1133 if (do_read) continue;
1134 return;
1135 }
1136
1137 /*
1138 * The packet contains a 4 octet length in the
1139 * header, but the header bytes aren't included
1140 * in the 4 octet length field.
1141 */
1142 packet_len = fr_nbo_to_uint32(h->recv.read + 8) + FR_HEADER_LENGTH;
1143
1144 /*
1145 * The packet is too large, reject it.
1146 */
1147 if (packet_len > h->inst->max_packet_size) {
1148 ERROR("%s - Packet is larger than max_packet_size",
1149 h->module_name);
1151 return;
1152 }
1153
1154 /*
1155 * We haven't received the full packet, read more or return.
1156 */
1157 if (used < packet_len) {
1158 if (do_read) continue;
1159 return;
1160 }
1161
1162 fr_assert(h->recv.read + packet_len <= h->recv.end);
1163
1164 /*
1165 * TACACS+ doesn't care about packet codes. All packet of the codes share the same ID
1166 * space.
1167 */
1168 treq = h->tracking[h->recv.read[1]];
1169 if (!treq) {
1170 WARN("%s - Ignoring reply with ID %i that arrived too late",
1171 h->module_name, h->recv.data[1]);
1172
1173 h->recv.read += packet_len;
1174 continue;
1175 }
1176
1177 treq = talloc_get_type_abort(treq, trunk_request_t);
1178 request = treq->request;
1179 fr_assert(request != NULL);
1180 req = talloc_get_type_abort(treq->preq, tcp_request_t);
1181 r = talloc_get_type_abort(treq->rctx, tcp_result_t);
1182
1183 fr_pair_list_init(&reply);
1184
1185 /*
1186 * Validate and decode the incoming packet
1187 */
1188 slen = decode(request->reply_ctx, &reply, &code, h, request, req, h->recv.read, packet_len);
1189 if (slen < 0) {
1190 // @todo - give real decode error?
1192 return;
1193 }
1194 h->recv.read += packet_len;
1195
1196 /*
1197 * Only valid packets are processed.
1198 */
1199 h->last_reply = fr_time();
1200
1201 treq->request->reply->code = code;
1202
1203 r->rcode = tacacs_code_to_rcode[code];
1204 fr_pair_list_append(&request->reply_pairs, &reply);
1206 }
1207}
1208
1209/** Remove the request from any tracking structures
1210 *
1211 * Frees encoded packets if the request is being moved to a new connection
1212 */
1213static void request_cancel(connection_t *conn, void *preq_to_reset,
1214 trunk_cancel_reason_t reason, UNUSED void *uctx)
1215{
1216 tcp_request_t *req = talloc_get_type_abort(preq_to_reset, tcp_request_t);
1217
1218 /*
1219 * Request has been requeued on the same
1220 * connection due to timeout or DUP signal.
1221 */
1222 if (reason == TRUNK_CANCEL_REASON_REQUEUE) {
1223 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
1224
1225 tcp_request_reset(h, req);
1226 }
1227
1228 /*
1229 * Other cancellations are dealt with by
1230 * request_conn_release as the request is removed
1231 * from the trunk.
1232 */
1233}
1234
1235/** Clear out anything associated with the handle from the request
1236 *
1237 */
1238static void request_conn_release(connection_t *conn, void *preq_to_reset, UNUSED void *uctx)
1239{
1240 tcp_request_t *req = talloc_get_type_abort(preq_to_reset, tcp_request_t);
1241 tcp_handle_t *h = talloc_get_type_abort(conn->h, tcp_handle_t);
1242
1243 if (req->packet) tcp_request_reset(h, req);
1244
1245 /*
1246 * If there are no outstanding tracking entries
1247 * allocated then the connection is "idle".
1248 *
1249 * @todo - enable idle timeout?
1250 */
1251 if (!h->active) h->last_idle = fr_time();
1252}
1253
1254/** Write out a canned failure
1255 *
1256 */
1257static void request_fail(request_t *request, NDEBUG_UNUSED void *preq, void *rctx,
1258 NDEBUG_UNUSED trunk_request_state_t state, UNUSED void *uctx)
1259{
1260 tcp_result_t *r = talloc_get_type_abort(rctx, tcp_result_t);
1261#ifndef NDEBUG
1262 tcp_request_t *req = talloc_get_type_abort(preq, tcp_request_t);
1263#endif
1264
1265 fr_assert(!fr_timer_armed(req->ev)); /* Dealt with by request_conn_release */
1266
1268
1270 r->treq = NULL;
1271
1273}
1274
1275/** Response has already been written to the rctx at this point
1276 *
1277 */
1278static void request_complete(request_t *request, NDEBUG_UNUSED void *preq, void *rctx, UNUSED void *uctx)
1279{
1280 tcp_result_t *r = talloc_get_type_abort(rctx, tcp_result_t);
1281#ifndef NDEBUG
1282 tcp_request_t *req = talloc_get_type_abort(preq, tcp_request_t);
1283#endif
1284
1285 fr_assert(!req->packet && !fr_timer_armed(req->ev)); /* Dealt with by request_conn_release */
1286
1287 r->treq = NULL;
1288
1290}
1291
1292/** Explicitly free resources associated with the protocol request
1293 *
1294 */
1295static void request_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
1296{
1297 tcp_request_t *req = talloc_get_type_abort(preq_to_free, tcp_request_t);
1298
1299 fr_assert(!req->packet && !fr_timer_armed(req->ev)); /* Dealt with by request_conn_release */
1300
1301 talloc_free(req);
1302}
1303
1304/** Resume execution of the request, returning the rcode set during trunk execution
1305 *
1306 */
1308{
1309 tcp_result_t *r = talloc_get_type_abort(mctx->rctx, tcp_result_t);
1310 rlm_rcode_t rcode = r->rcode;
1311
1312 talloc_free(r);
1313
1314 RETURN_UNLANG_RCODE(rcode);
1315}
1316
1317static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
1318{
1319// tcp_thread_t *t = talloc_get_type_abort(mctx->thread, tcp_thread_t);
1320 tcp_result_t *r = talloc_get_type_abort(mctx->rctx, tcp_result_t);
1321
1322 /*
1323 * If we don't have a treq associated with the
1324 * rctx it's likely because the request was
1325 * scheduled, but hasn't yet been resumed, and
1326 * has received a signal, OR has been resumed
1327 * and immediately cancelled as the event loop
1328 * is exiting, in which case
1329 * unlang_request_is_scheduled will return false
1330 * (don't use it).
1331 */
1332 if (!r->treq) {
1333 talloc_free(r);
1334 return;
1335 }
1336
1337 switch (action) {
1338 /*
1339 * The request is being cancelled, tell the
1340 * trunk so it can clean up the treq.
1341 */
1342 case FR_SIGNAL_CANCEL:
1344 r->treq = NULL;
1345 talloc_free(r); /* Should be freed soon anyway, but better to be explicit */
1346 return;
1347
1348 /*
1349 * Requeue the request on the same connection
1350 * causing a "retransmission" if the request
1351 * has already been sent out.
1352 */
1353 case FR_SIGNAL_DUP:
1354 /*
1355 * Retransmit the current request on the same connection.
1356 *
1357 * If it's zombie, we still resend it. If the
1358 * connection is dead, then a callback will move
1359 * this request to a new connection.
1360 */
1362 return;
1363
1364 default:
1365 return;
1366 }
1367}
1368
1369#ifndef NDEBUG
1370/** Free a tcp_result_t
1371 *
1372 * Allows us to set break points for debugging.
1373 */
1375{
1376 trunk_request_t *treq;
1377 tcp_request_t *req;
1378
1379 if (!r->treq) return 0;
1380
1381 treq = talloc_get_type_abort(r->treq, trunk_request_t);
1382 req = talloc_get_type_abort(treq->preq, tcp_request_t);
1383
1384 fr_assert_msg(!req->ev, "tcp_result_t freed with active timer");
1385
1386 return 0;
1387}
1388#endif
1389
1390static unlang_action_t mod_enqueue(unlang_result_t *p_result, void **rctx_out, UNUSED void *instance, void *thread, request_t *request)
1391{
1392 tcp_thread_t *t = talloc_get_type_abort(thread, tcp_thread_t);
1393 tcp_result_t *r;
1394 tcp_request_t *req;
1395 trunk_request_t *treq;
1397
1398 fr_assert(FR_TACACS_PACKET_CODE_VALID(request->packet->code));
1399
1400 treq = trunk_request_alloc(t->trunk, request);
1401 if (!treq) RETURN_UNLANG_FAIL;
1402
1403 MEM(r = talloc_zero(request, tcp_result_t));
1404#ifndef NDEBUG
1405 talloc_set_destructor(r, _tcp_result_free);
1406#endif
1407
1408 /*
1409 * Can't use compound literal - const issues.
1410 */
1411 MEM(req = talloc_zero(treq, tcp_request_t));
1412 req->code = request->packet->code;
1413 req->priority = request->priority;
1414 req->recv_time = request->async->recv_time;
1415
1417
1418 q = trunk_request_enqueue(&treq, t->trunk, request, req, r);
1419 if (q < 0) {
1420 fr_assert(!req->packet); /* Should not have been fed to the muxer */
1421 trunk_request_free(&treq); /* Return to the free list */
1422 fail:
1423 talloc_free(r);
1425 }
1426
1427 /*
1428 * All destinations are down.
1429 */
1430 if (q == TRUNK_ENQUEUE_IN_BACKLOG) {
1431 RDEBUG("All destinations are down - cannot send packet");
1432 goto fail;
1433 }
1434
1435 r->treq = treq; /* Remember for signalling purposes */
1436
1437 *rctx_out = r;
1438
1439 return UNLANG_ACTION_YIELD;
1440}
1441
1442/** Instantiate thread data for the submodule.
1443 *
1444 */
1446{
1447 rlm_tacacs_tcp_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_tacacs_tcp_t);
1448 tcp_thread_t *thread = talloc_get_type_abort(mctx->thread, tcp_thread_t);
1449
1450 static trunk_io_funcs_t io_funcs = {
1452 .connection_notify = thread_conn_notify,
1453 .request_prioritise = request_prioritise,
1454 .request_mux = request_mux,
1455 .request_demux = request_demux,
1456 .request_conn_release = request_conn_release,
1457 .request_complete = request_complete,
1458 .request_fail = request_fail,
1459 .request_cancel = request_cancel,
1460 .request_free = request_free
1461 };
1462
1463 thread->el = mctx->el;
1464 thread->inst = inst;
1465 thread->trunk = trunk_alloc(thread, mctx->el, &io_funcs,
1466 &inst->parent->trunk_conf, inst->parent->name, thread, false, inst->trigger_args);
1467 if (!thread->trunk) return -1;
1468
1469 return 0;
1470}
1471
1472static int mod_instantiate(module_inst_ctx_t const *mctx)
1473{
1474 rlm_tacacs_t *parent = talloc_get_type_abort(mctx->mi->parent->data, rlm_tacacs_t);
1475 rlm_tacacs_tcp_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_tacacs_tcp_t);
1476 CONF_SECTION *conf = mctx->mi->conf;
1477 char *server = NULL;
1478
1479 if (!parent) {
1480 ERROR("IO module cannot be instantiated directly");
1481 return -1;
1482 }
1483
1484 inst->parent = parent;
1485
1486 /*
1487 * Always need at least one mmsgvec
1488 */
1489 if (inst->max_send_coalesce == 0) inst->max_send_coalesce = 1;
1490
1491 /*
1492 * Ensure that we have a destination address.
1493 */
1494 if (inst->dst_ipaddr.af == AF_UNSPEC) {
1495 cf_log_err(conf, "A value must be given for 'ipaddr'");
1496 return -1;
1497 }
1498
1499 /*
1500 * If src_ipaddr isn't set, make sure it's INADDR_ANY, of
1501 * the same address family as dst_ipaddr.
1502 */
1503 if (inst->src_ipaddr.af == AF_UNSPEC) {
1504 memset(&inst->src_ipaddr, 0, sizeof(inst->src_ipaddr));
1505
1506 inst->src_ipaddr.af = inst->dst_ipaddr.af;
1507
1508 if (inst->src_ipaddr.af == AF_INET) {
1509 inst->src_ipaddr.prefix = 32;
1510 } else {
1511 inst->src_ipaddr.prefix = 128;
1512 }
1513 }
1514
1515 else if (inst->src_ipaddr.af != inst->dst_ipaddr.af) {
1516 cf_log_err(conf, "The 'ipaddr' and 'src_ipaddr' configuration items must "
1517 "be both of the same address family");
1518 return -1;
1519 }
1520
1521 if (!inst->dst_port) {
1522 cf_log_err(conf, "A value must be given for 'port'");
1523 return -1;
1524 }
1525
1526 /*
1527 * Clamp max_packet_size first before checking recv_buff and send_buff
1528 */
1529 FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, ((255 + (int) sizeof(fr_tacacs_packet_t)) & 0xffffff00));
1530 FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
1531
1532
1533 if (inst->recv_buff_is_set) {
1534 FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, inst->max_packet_size);
1535 FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, (1 << 30));
1536 }
1537
1538 if (inst->send_buff_is_set) {
1539 FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, >=, inst->max_packet_size);
1540 FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, <=, (1 << 30));
1541 }
1542
1543
1544 /*
1545 * Empty secrets don't exist
1546 */
1547 if (inst->secret && !*inst->secret) {
1548 talloc_const_free(inst->secret);
1549 inst->secret = NULL;
1550 }
1551
1552 if (inst->secret) inst->secretlen = talloc_array_length(inst->secret) - 1;
1553
1554 if (!parent->trunk_conf.conn_triggers) return 0;
1555
1556 fr_value_box_aprint(inst, &server, fr_box_ipaddr(inst->dst_ipaddr), NULL);
1557
1558 MEM(inst->trigger_args = fr_pair_list_alloc(inst));
1559 if (module_trigger_args_build(inst->trigger_args, inst->trigger_args,
1562 .module = mctx->mi->module->name,
1563 .name = parent->name,
1564 .server = server,
1565 .port = inst->dst_port
1566 }) < 0) return -1;
1567
1568 return 0;
1569}
1570
1573 .common = {
1574 .magic = MODULE_MAGIC_INIT,
1575 .name = "tacacs_tcp",
1576 .inst_size = sizeof(rlm_tacacs_tcp_t),
1577
1578 .thread_inst_size = sizeof(tcp_thread_t),
1579 .thread_inst_type = "tcp_thread_t",
1580
1581 .config = module_config,
1582 .instantiate = mod_instantiate,
1583 .thread_instantiate = mod_thread_instantiate,
1584 },
1585 .enqueue = mod_enqueue,
1586 .signal = mod_signal,
1587 .resume = mod_resume,
1588};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
#define RCSID(id)
Definition build.h:485
#define NDEBUG_UNUSED
Definition build.h:328
#define CMP_PREFER_SMALLER(_a, _b)
Evaluates to +1 for a > b, and -1 for a < b.
Definition build.h:104
#define STRINGIFY(x)
Definition build.h:197
#define CC_NO_UBSAN(_sanitize)
Definition build.h:428
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
#define unlikely(_x)
Definition build.h:383
#define UNUSED
Definition build.h:317
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:660
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition cf_parse.h:520
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:283
#define FR_CONF_OFFSET_IS_SET(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct,...
Definition cf_parse.h:297
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:241
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:597
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition cf_util.c:1027
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition cf_util.c:683
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:286
#define cf_parent(_cf)
Definition cf_util.h:101
connection_state_t
Definition connection.h:47
@ CONNECTION_STATE_FAILED
Connection has failed.
Definition connection.h:56
@ CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition connection.h:52
@ CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition connection.h:86
@ CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition connection.h:85
Holds a complete set of functions for a connection.
Definition connection.h:195
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:514
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:202
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
#define DEBUG(fmt,...)
Definition dhcpclient.c:39
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:287
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:300
Specifies an attribute which must be present for the module to function.
Definition dict.h:286
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:299
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define fr_event_fd_insert(...)
Definition event.h:248
void(* fr_event_fd_cb_t)(fr_event_list_t *el, int fd, int flags, void *uctx)
Called when an IO event occurs on a file descriptor.
Definition event.h:151
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition event.h:84
IPv4/6 prefix.
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:1622
void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix)
Print a fr_pair_list_t.
Definition log.c:828
#define PERROR(_fmt,...)
Definition log.h:228
#define DEBUG3(_fmt,...)
Definition log.h:266
#define RERROR(fmt,...)
Definition log.h:298
#define DEBUG4(_fmt,...)
Definition log.h:267
#define RPERROR(fmt,...)
Definition log.h:302
#define RPEDEBUG(fmt,...)
Definition log.h:376
#define RHEXDUMP3(_data, _len, _fmt,...)
Definition log.h:705
talloc_free(reap)
int fr_event_fd_delete(fr_event_list_t *el, int fd, fr_event_filter_t filter)
Remove a file descriptor from the event loop.
Definition event.c:1203
Stores all information relating to an event list.
Definition event.c:377
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
unsigned short uint16_t
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
@ FR_TYPE_COMBO_IP_ADDR
IPv4 or IPv6 address depending on length.
unsigned int uint32_t
long int ssize_t
unsigned char uint8_t
unsigned long int size_t
#define UINT8_MAX
static size_t used
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
fr_event_list_t * el
Event list to register any IO handlers and timers against.
Definition module_ctx.h:68
void * thread
Thread instance data.
Definition module_ctx.h:67
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:64
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
Temporary structure to hold arguments for thread_instantiation calls.
Definition module_ctx.h:63
static const trunk_io_funcs_t io_funcs
Definition bio.c:2647
static uint32_t fr_nbo_to_uint32(uint8_t const data[static sizeof(uint32_t)])
Read an unsigned 32bit integer from wire format (big endian)
Definition nbo.h:167
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find a pair with a matching fr_dict_attr_t, by walking the nested fr_dict_attr_t tree.
Definition pair.c:777
int8_t fr_pair_cmp_by_parent_num(void const *a, void const *b)
Order attributes by their parent(s), attribute number, and tag.
Definition pair.c:1919
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:700
fr_pair_list_t * fr_pair_list_alloc(TALLOC_CTX *ctx)
Allocate a new pair list on the heap.
Definition pair.c:119
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1345
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:287
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:46
char * fr_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Special version of asprintf which implements custom format specifiers.
Definition print.c:874
char const * fr_tacacs_packet_names[FR_TACACS_CODE_MAX]
Definition base.c:120
ssize_t fr_tacacs_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *vendor, uint8_t const *buffer, size_t buffer_len, const uint8_t *original, char const *const secret, size_t secret_len, int *code)
Decode a TACACS+ packet.
Definition decode.c:409
ssize_t fr_tacacs_encode(fr_dbuff_t *dbuff, uint8_t const *original_packet, char const *secret, size_t secret_len, unsigned int code, fr_pair_list_t *vps)
Encode VPS into a raw TACACS packet.
Definition encode.c:363
#define fr_assert(_expr)
Definition rad_assert.h:38
static char * secret
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG(fmt,...)
Definition radclient.h:53
#define WARN(fmt,...)
Definition radclient.h:47
#define INFO(fmt,...)
Definition radict.c:54
static rs_t * conf
Definition radsniff.c:53
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:105
#define RETURN_UNLANG_RCODE(_rcode)
Definition rcode.h:57
#define RETURN_UNLANG_FAIL
Definition rcode.h:59
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:45
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:44
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:43
@ RLM_MODULE_UPDATED
OK (pairs modified).
Definition rcode.h:51
@ RLM_MODULE_HANDLED
The module handled the request, so stop.
Definition rcode.h:46
module_t common
Common fields to all loadable modules.
Definition rlm_tacacs.h:74
fr_retry_config_t retry
retries shared by all packet types
Definition rlm_tacacs.h:57
char const * name
Definition rlm_tacacs.h:45
fr_time_delta_t revive_interval
Definition rlm_tacacs.h:51
fr_time_delta_t zombie_period
Definition rlm_tacacs.h:50
fr_time_delta_t response_window
Definition rlm_tacacs.h:49
Public structure describing an I/O path for an outgoing socket.
Definition rlm_tacacs.h:73
static int8_t request_prioritise(void const *one, void const *two)
fr_retry_t retry
retransmission timers
uint8_t code
Packet code.
uint8_t * write
where we write data to
static fr_dict_attr_t const * attr_packet_type
static bool check_for_zombie(fr_timer_list_t *tl, trunk_connection_t *tconn, fr_time_t now, fr_time_t last_sent)
See if the connection is zombied.
CONF_SECTION * config
bool send_buff_is_set
Whether we were provided with a send_buf.
size_t send_buff_actual
What we believe the maximum SO_SNDBUF size to be.
size_t secretlen
length of secret
static void request_demux(UNUSED fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
static fr_dict_attr_t const * attr_session_id
static void request_mux(fr_event_list_t *el, trunk_connection_t *tconn, connection_t *conn, UNUSED void *uctx)
char const * interface
Interface to bind to.
char const * module_name
the module that opened the connection
int fd
File descriptor.
static ssize_t decode(TALLOC_CTX *ctx, fr_pair_list_t *reply, uint8_t *response_code, tcp_handle_t *h, request_t *request, tcp_request_t *req, uint8_t *data, size_t data_len)
Decode response packet data, extracting relevant information and validating the packet.
uint8_t * data
actual data
uint8_t id
Last ID assigned to this packet.
static fr_dict_attr_t const * attr_packet_hdr
uint16_t src_port
Source port specific to this connection.
size_t packet_len
Length of the packet.
fr_pair_list_t * trigger_args
Pairs passed to trigger request.
trunk_request_t * tracking[UINT8_MAX]
all sequential!
fr_dict_autoload_t rlm_tacacs_tcp_dict[]
static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
Connection errored.
trunk_connection_t * tconn
trunk connection
static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
trunk_request_t * treq
static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx)
Shutdown/close a file descriptor.
static void tcp_request_reset(tcp_handle_t *h, tcp_request_t *req)
Clear out any connection specific resources from a tcp request.
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)
fr_time_t last_reply
When we last received a reply.
char const * secret
Shared secret.
static unlang_action_t mod_enqueue(unlang_result_t *p_result, void **rctx_out, UNUSED void *instance, void *thread, request_t *request)
static int encode(tcp_handle_t *h, request_t *request, tcp_request_t *req)
fr_time_t recv_time
copied from request->async->recv_time
static void request_conn_release(connection_t *conn, void *preq_to_reset, UNUSED void *uctx)
Clear out anything associated with the handle from the request.
uint16_t dst_port
Port of the home server.
static fr_dict_t const * dict_tacacs
uint32_t send_buff
How big the kernel's send buffer should be.
static void request_retry(fr_timer_list_t *tl, fr_time_t now, void *uctx)
Handle retries.
fr_time_t mrs_time
Most recent sent time which had a reply.
uint8_t * end
end of the buffer
static void request_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
Explicitly free resources associated with the protocol request.
rlm_tacacs_tcp_t const * inst
Our module instance.
uint32_t recv_buff
How big the kernel's receive buffer should be.
int id
starts at 1.
rlm_tacacs_io_t rlm_tacacs_tcp
static int _tcp_handle_free(tcp_handle_t *h)
Free a connection handle, closing associated resources.
fr_time_t last_idle
last time we had nothing to do
uint32_t max_packet_size
Our max packet size. may be different from the parent.
fr_ipaddr_t src_ipaddr
IP we open our socket on.
static void request_fail(request_t *request, NDEBUG_UNUSED void *preq, void *rctx, NDEBUG_UNUSED trunk_request_state_t state, UNUSED void *uctx)
Write out a canned failure.
uint32_t session_id
for TACACS+ "security".
rlm_rcode_t rcode
from the transport
bool recv_buff_is_set
Whether we were provided with a recv_buf.
bool outstanding
are we waiting for a reply?
uint32_t max_packet_size
Maximum packet size.
char const * name
From IP PORT to IP PORT.
static void zombie_timeout(fr_timer_list_t *tl, fr_time_t now, void *uctx)
Mark a connection dead after "zombie_interval".
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
Instantiate thread data for the submodule.
trunk_conf_t trunk_conf
trunk configuration
fr_timer_t * ev
timer for retransmissions
static unlang_action_t mod_resume(unlang_result_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request)
Resume execution of the request, returning the rcode set during trunk execution.
trunk_t * trunk
trunk handler
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)
uint8_t * read
where we read data from
trunk_request_t ** coalesced
Outbound coalesced requests.
static rlm_rcode_t tacacs_code_to_rcode[FR_TACACS_CODE_MAX]
static connection_state_t conn_init(void **h_out, connection_t *conn, void *uctx)
Initialise a new outbound connection.
rlm_tacacs_t * parent
rlm_tacacs instance.
static void revive_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Revive a connection after "revive_interval".
static int _tcp_result_free(tcp_result_t *r)
Free a tcp_result_t.
fr_dict_attr_autoload_t rlm_tacacs_tcp_dict_attr[]
static const conf_parser_t module_config[]
uint8_t * packet
Packet we write to the network.
uint32_t priority
copied from request->async->priority
fr_ipaddr_t src_ipaddr
Source IP address.
tcp_buffer_t send
send buffer
rlm_tacacs_tcp_t const * inst
our instance
tcp_buffer_t recv
receive buffer
int active
active packets
fr_ipaddr_t dst_ipaddr
IP of the home server.
static int mod_instantiate(module_inst_ctx_t const *mctx)
tcp_thread_t * thread
static void request_cancel(connection_t *conn, void *preq_to_reset, trunk_cancel_reason_t reason, UNUSED void *uctx)
Remove the request from any tracking structures.
fr_event_list_t * el
Event list.
static void request_complete(request_t *request, NDEBUG_UNUSED void *preq, void *rctx, UNUSED void *uctx)
Response has already been written to the rctx at this point.
uint16_t max_send_coalesce
Maximum number of packets to coalesce into one mmsg call.
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_timer_t * zombie_ev
Zombie timeout.
Static configuration for the module.
Track the handle, which is tightly correlated with the FD.
Connect request_t to local tracking structure.
void connection_signal_reconnect(connection_t *conn, connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
int connection_signal_on_fd(connection_t *conn, int fd)
Setup the connection to change states to connected or failed based on I/O events.
connection_t * connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, connection_funcs_t const *funcs, connection_conf_t const *conf, char const *log_prefix, void const *uctx)
Allocate a new connection.
char const * name
Instance name e.g. user_database.
Definition module.h:355
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:349
size_t inst_size
Size of the module's instance data.
Definition module.h:212
void * data
Module's instance data.
Definition module.h:291
module_instance_t const * parent
Parent module's instance (if any).
Definition module.h:357
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_DUP
A duplicate request was received.
Definition signal.h:44
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
int fr_socket_client_tcp(char const *ifname, fr_ipaddr_t *src_ipaddr, fr_ipaddr_t const *dst_ipaddr, uint16_t dst_port, bool async)
Establish a connected TCP socket.
Definition socket.c:735
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define FR_HEADER_LENGTH
Definition tacacs.h:26
#define FR_TACACS_PACKET_CODE_VALID(_code)
Definition tacacs.h:322
@ FR_TACACS_CODE_ACCT_ERROR
Definition tacacs.h:315
@ FR_TACACS_CODE_AUTH_GETDATA
Definition tacacs.h:298
@ FR_TACACS_CODE_AUTH_RESTART
Definition tacacs.h:301
@ FR_TACACS_CODE_AUTZ_PASS_REPLACE
Definition tacacs.h:309
@ FR_TACACS_CODE_MAX
Definition tacacs.h:317
@ FR_TACACS_CODE_AUTH_GETUSER
Definition tacacs.h:299
@ FR_TACACS_CODE_AUTH_GETPASS
Definition tacacs.h:300
@ FR_TACACS_CODE_AUTZ_FAIL
Definition tacacs.h:310
@ FR_TACACS_CODE_AUTH_PASS
Definition tacacs.h:296
@ FR_TACACS_CODE_AUTZ_PASS_ADD
Definition tacacs.h:308
@ FR_TACACS_CODE_AUTH_FAIL
Definition tacacs.h:297
@ FR_TACACS_CODE_AUTH_ERROR
Definition tacacs.h:302
@ FR_TACACS_CODE_AUTZ_ERROR
Definition tacacs.h:311
@ FR_TACACS_CODE_ACCT_SUCCESS
Definition tacacs.h:314
#define FR_MAX_PACKET_SIZE
Definition tacacs.h:27
static int talloc_const_free(void const *ptr)
Free const'd memory.
Definition talloc.h:229
#define fr_time_gteq(_a, _b)
Definition time.h:238
static int64_t fr_time_unwrap(fr_time_t time)
Definition time.h:146
#define fr_time_wrap(_time)
Definition time.h:145
#define fr_time_lteq(_a, _b)
Definition time.h:240
#define fr_time_delta_ispos(_a)
Definition time.h:290
#define fr_time_eq(_a, _b)
Definition time.h:241
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition time.h:196
#define fr_time_gt(_a, _b)
Definition time.h:237
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
#define fr_time_lt(_a, _b)
Definition time.h:239
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:50
A timer event.
Definition timer.c:84
#define FR_TIMER_DISARM(_ev)
Definition timer.h:91
static bool fr_timer_armed(fr_timer_t *ev)
Definition timer.h:120
#define fr_timer_at(...)
Definition timer.h:81
int module_trigger_args_build(TALLOC_CTX *ctx, fr_pair_list_t *list, CONF_SECTION *cs, module_trigger_args_t *args)
Build trigger args pair list for modules.
Definition trigger.c:493
Common values used by modules when building trigger args.
Definition trigger.h:42
void trunk_connection_callback_readable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
Standard I/O read function.
Definition trunk.c:4030
void trunk_connection_callback_writable(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, void *uctx)
Standard I/O write function.
Definition trunk.c:4047
void trunk_request_signal_partial(trunk_request_t *treq)
Signal a partial write.
Definition trunk.c:2045
void trunk_request_signal_fail(trunk_request_t *treq)
Signal that a trunk request failed.
Definition trunk.c:2148
trunk_request_t * trunk_request_alloc(trunk_t *trunk, request_t *request)
(Pre-)Allocate a new trunk request
Definition trunk.c:2490
uint64_t trunk_connection_requests_requeue(trunk_connection_t *tconn, int states, uint64_t max, bool fail_bound)
Move requests off of a connection and requeue elsewhere.
Definition trunk.c:2026
trunk_enqueue_t trunk_request_enqueue(trunk_request_t **treq_out, trunk_t *trunk, request_t *request, void *preq, void *rctx)
Enqueue a request that needs data written to the trunk.
Definition trunk.c:2603
trunk_enqueue_t trunk_request_requeue(trunk_request_t *treq)
Re-enqueue a request on the same connection.
Definition trunk.c:2692
int trunk_connection_pop_request(trunk_request_t **treq_out, trunk_connection_t *tconn)
Pop a request off a connection's pending queue.
Definition trunk.c:3899
void trunk_request_signal_cancel(trunk_request_t *treq)
Cancel a trunk request.
Definition trunk.c:2168
trunk_t * trunk_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, trunk_io_funcs_t const *funcs, trunk_conf_t const *conf, char const *log_prefix, void const *uctx, bool delay_start, fr_pair_list_t *trigger_args)
Allocate a new collection of connections.
Definition trunk.c:4963
void trunk_request_free(trunk_request_t **treq_to_free)
If the trunk request is freed then update the target requests.
Definition trunk.c:2338
void trunk_connection_signal_inactive(trunk_connection_t *tconn)
Signal a trunk connection cannot accept more requests.
Definition trunk.c:3953
void trunk_request_signal_sent(trunk_request_t *treq)
Signal that the request was written to a connection successfully.
Definition trunk.c:2066
void trunk_request_signal_complete(trunk_request_t *treq)
Signal that a trunk request is complete.
Definition trunk.c:2110
void trunk_connection_signal_reconnect(trunk_connection_t *tconn, connection_reason_t reason)
Signal a trunk connection is no longer viable.
Definition trunk.c:4015
Associates request queues with a connection.
Definition trunk.c:133
Wraps a normal request.
Definition trunk.c:99
Main trunk management handle.
Definition trunk.c:215
#define TRUNK_REQUEST_STATE_ALL
All request states.
Definition trunk.h:195
trunk_connection_alloc_t connection_alloc
Allocate a new connection_t.
Definition trunk.h:737
trunk_connection_event_t
What type of I/O events the trunk connection is currently interested in receiving.
Definition trunk.h:72
@ TRUNK_CONN_EVENT_BOTH
Trunk should be notified if a connection is readable or writable.
Definition trunk.h:79
@ TRUNK_CONN_EVENT_WRITE
Trunk should be notified if a connection is writable.
Definition trunk.h:77
@ TRUNK_CONN_EVENT_NONE
Don't notify the trunk on connection state changes.
Definition trunk.h:73
@ TRUNK_CONN_EVENT_READ
Trunk should be notified if a connection is readable.
Definition trunk.h:75
trunk_cancel_reason_t
Reasons for a request being cancelled.
Definition trunk.h:55
@ TRUNK_CANCEL_REASON_REQUEUE
A previously sent request is being requeued.
Definition trunk.h:59
trunk_enqueue_t
Definition trunk.h:148
@ TRUNK_ENQUEUE_IN_BACKLOG
Request should be enqueued in backlog.
Definition trunk.h:149
trunk_request_state_t
Used for sanity checks and to simplify freeing.
Definition trunk.h:161
@ TRUNK_REQUEST_STATE_PARTIAL
Some of the request was written to the socket, more of it should be written later.
Definition trunk.h:170
@ TRUNK_REQUEST_STATE_INIT
Initial state.
Definition trunk.h:162
@ TRUNK_REQUEST_STATE_PENDING
In the queue of a connection and is pending writing.
Definition trunk.h:168
@ TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
Definition trunk.h:172
Common configuration parameters for a trunk.
Definition trunk.h:224
I/O functions to pass to trunk_alloc.
Definition trunk.h:736
close(uq->fd)
static fr_event_list_t * el
void fr_pair_list_sort(fr_pair_list_t *list, fr_cmp_t cmp)
Sort a doubly linked list of fr_pair_ts using merge sort.
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
void fr_pair_list_append(fr_pair_list_t *dst, fr_pair_list_t *src)
Appends a list of fr_pair_t from a temporary list to a destination list.
static fr_slen_t parent
Definition pair.h:841
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
Initialize a retransmission counter.
Definition retry.c:108
void fr_retry_init(fr_retry_t *r, fr_time_t now, fr_retry_config_t const *config)
Initialize a retransmission counter.
Definition retry.c:36
fr_time_t start
when we started the retransmission
Definition retry.h:53
fr_time_delta_t rt
retransmit interval
Definition retry.h:57
uint32_t mrc
Maximum retransmission count.
Definition retry.h:36
fr_retry_config_t const * config
master configuration
Definition retry.h:52
@ FR_RETRY_MRC
reached maximum retransmission count
Definition retry.h:47
@ FR_RETRY_CONTINUE
Definition retry.h:46
@ FR_RETRY_MRD
reached maximum retransmission duration
Definition retry.h:48
uint32_t count
number of sent packets
Definition retry.h:58
fr_time_delta_t mrd
Maximum retransmission duration.
Definition retry.h:35
fr_time_t next
when the next timer should be set
Definition retry.h:55
static fr_slen_t fr_value_box_aprint(TALLOC_CTX *ctx, char **out, fr_value_box_t const *data, fr_sbuff_escape_rules_t const *e_rules) 1(fr_value_box_print
#define fr_box_ipaddr(_val)
Definition value.h:316
static fr_slen_t data
Definition value.h:1291
#define fr_box_time_delta(_val)
Definition value.h:365