The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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: 2bb33c83017b72c2a2f7823a566bc93a277013e3 $
19  * @file rlm_tacacs_tcp.c
20  * @brief TACACS+ transport
21  *
22  * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
23  */
24 RCSID("$Id: 2bb33c83017b72c2a2f7823a566bc93a277013e3 $")
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 #include <freeradius-devel/util/udp.h>
34 
35 #include <sys/socket.h>
36 #include <sys/uio.h>
37 
38 #include "rlm_tacacs.h"
39 
40 /** Static configuration for the module.
41  *
42  */
43 typedef struct {
44  rlm_tacacs_t *parent; //!< rlm_tacacs instance.
46 
47  fr_ipaddr_t dst_ipaddr; //!< IP of the home server.
48  fr_ipaddr_t src_ipaddr; //!< IP we open our socket on.
49  uint16_t dst_port; //!< Port of the home server.
50  char const *secret; //!< Shared secret.
51  size_t secretlen; //!< length of secret
52 
53  char const *interface; //!< Interface to bind to.
54 
55  uint32_t recv_buff; //!< How big the kernel's receive buffer should be.
56  uint32_t send_buff; //!< How big the kernel's send buffer should be.
57 
58  uint32_t max_packet_size; //!< Maximum packet size.
59  uint16_t max_send_coalesce; //!< Maximum number of packets to coalesce into one mmsg call.
60 
61  bool recv_buff_is_set; //!< Whether we were provided with a recv_buf
62  bool send_buff_is_set; //!< Whether we were provided with a send_buf
63 
64  fr_trunk_conf_t *trunk_conf; //!< trunk configuration
66 
67 typedef struct {
68  fr_event_list_t *el; //!< Event list.
69 
70  rlm_tacacs_tcp_t const *inst; //!< our instance
71 
72  fr_trunk_t *trunk; //!< trunk handler
73 } udp_thread_t;
74 
75 typedef struct {
76  fr_trunk_request_t *treq;
77  rlm_rcode_t rcode; //!< from the transport
78 } udp_result_t;
79 
80 typedef struct udp_request_s udp_request_t;
81 
82 typedef 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
87 } tcp_buffer_t;
88 
89 /** Track the handle, which is tightly correlated with the FD
90  *
91  */
92 typedef 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  fr_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.
105  udp_thread_t *thread;
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  fr_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_event_timer_t const *zombie_ev; //!< Zombie timeout.
132 
133  fr_trunk_connection_t *tconn; //!< trunk connection
134 } udp_handle_t;
135 
136 
137 /** Connect request_t to local tracking structure
138  *
139  */
140 struct udp_request_s {
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_event_timer_t const *ev; //!< timer for retransmissions
152  fr_retry_t retry; //!< retransmission timers
153 };
154 
155 static const conf_parser_t module_config[] = {
156  { FR_CONF_OFFSET_TYPE_FLAGS("ipaddr", FR_TYPE_COMBO_IP_ADDR, 0, rlm_tacacs_tcp_t, dst_ipaddr), },
157  { FR_CONF_OFFSET_TYPE_FLAGS("ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_tacacs_tcp_t, dst_ipaddr) },
158  { FR_CONF_OFFSET_TYPE_FLAGS("ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_tacacs_tcp_t, dst_ipaddr) },
159 
160  { FR_CONF_OFFSET("port", rlm_tacacs_tcp_t, dst_port) },
161 
162  { FR_CONF_OFFSET("secret", rlm_tacacs_tcp_t, secret) }, /* can be NULL */
163 
164  { FR_CONF_OFFSET("interface", rlm_tacacs_tcp_t, interface) },
165 
166  { FR_CONF_OFFSET_IS_SET("recv_buff", FR_TYPE_UINT32, 0, rlm_tacacs_tcp_t, recv_buff) },
167  { FR_CONF_OFFSET_IS_SET("send_buff", FR_TYPE_UINT32, 0, rlm_tacacs_tcp_t, send_buff) },
168 
169  { FR_CONF_OFFSET("max_packet_size", rlm_tacacs_tcp_t, max_packet_size), .dflt = STRINGIFY(FR_MAX_PACKET_SIZE) },
170  { FR_CONF_OFFSET("max_send_coalesce", rlm_tacacs_tcp_t, max_send_coalesce), .dflt = "1024" },
171 
172  { FR_CONF_OFFSET_TYPE_FLAGS("src_ipaddr", FR_TYPE_COMBO_IP_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
173  { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv4addr", FR_TYPE_IPV4_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
174  { FR_CONF_OFFSET_TYPE_FLAGS("src_ipv6addr", FR_TYPE_IPV6_ADDR, 0, rlm_tacacs_tcp_t, src_ipaddr) },
175 
177 };
178 
179 static fr_dict_t const *dict_tacacs;
180 
183  { .out = &dict_tacacs, .proto = "tacacs" },
184  { NULL }
185 };
186 
190 
193  { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
194  { .out = &attr_packet_hdr, .name = "Packet", .type = FR_TYPE_STRUCT, .dict = &dict_tacacs },
195  { .out = &attr_session_id, .name = "Packet.Session-ID", .type = FR_TYPE_UINT32, .dict = &dict_tacacs },
196  { NULL }
197 };
198 
199 /** Clear out any connection specific resources from a udp request
200  *
201  */
203 {
204  u->packet = NULL;
205 
206  fr_assert(h->active > 0);
207  fr_assert(h->tracking[u->id] != NULL);
208  fr_assert(h->tracking[u->id]->preq == u);
209 
210  h->tracking[u->id] = NULL;
211  u->outstanding = false;
212  h->active--;
213 
214  if (u->ev) (void)fr_event_timer_delete(&u->ev);
215 
216  /*
217  * We've sent 255 packets, and received all replies. Shut the connection down.
218  *
219  * Welcome to the insanity that is TACACS+.
220  */
221  if ((h->active == 0) && (h->id > 255)) {
223  }
224 }
225 
226 
227 /** Free a connection handle, closing associated resources
228  *
229  */
231 {
232  fr_assert(h->fd >= 0);
233 
235 
236  if (shutdown(h->fd, SHUT_RDWR) < 0) {
237  DEBUG3("%s - Failed shutting down connection %s: %s",
238  h->module_name, h->name, fr_syserror(errno));
239  }
240 
241  if (close(h->fd) < 0) {
242  DEBUG3("%s - Failed closing connection %s: %s",
243  h->module_name, h->name, fr_syserror(errno));
244  }
245 
246  h->fd = -1;
247 
248  DEBUG("%s - Connection closed - %s", h->module_name, h->name);
249 
250  return 0;
251 }
252 
253 /** Initialise a new outbound connection
254  *
255  * @param[out] h_out Where to write the new file descriptor.
256  * @param[in] conn to initialise.
257  * @param[in] uctx A #udp_thread_t
258  */
259 static fr_connection_state_t conn_init(void **h_out, fr_connection_t *conn, void *uctx)
260 {
261  int fd;
262  udp_handle_t *h;
263  udp_thread_t *thread = talloc_get_type_abort(uctx, udp_thread_t);
264 
265  MEM(h = talloc_zero(conn, udp_handle_t));
266  h->thread = thread;
267  h->inst = thread->inst;
268  h->module_name = h->inst->parent->name;
269  h->src_ipaddr = h->inst->src_ipaddr;
270  h->src_port = 0;
272  h->last_idle = fr_time();
273 
274  h->id = 1; /* clients send odd sequence numbers */
275  h->session_id = fr_rand();
276 
277  /*
278  * Initialize the buffer of coalesced packets we're doing to write.
279  */
280  h->coalesced = talloc_zero_array(h, fr_trunk_request_t *, h->inst->max_send_coalesce);
281 
282  /*
283  * Open the outgoing socket.
284  */
285  fd = fr_socket_client_tcp(NULL, &h->src_ipaddr, &h->inst->dst_ipaddr, h->inst->dst_port, true);
286  if (fd < 0) {
287  PERROR("%s - Failed opening socket", h->module_name);
288  talloc_free(h);
290  }
291 
292  /*
293  * Set the connection name.
294  */
295  h->name = fr_asprintf(h, "proto tcp local %pV port %u remote %pV port %u",
298 
299  talloc_set_destructor(h, _udp_handle_free);
300 
301 #ifdef SO_RCVBUF
302  if (h->inst->recv_buff_is_set) {
303  int opt;
304 
305  opt = h->inst->recv_buff;
306  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(int)) < 0) {
307  WARN("%s - Failed setting 'SO_RCVBUF': %s", h->module_name, fr_syserror(errno));
308  }
309  }
310 #endif
311 
312 #ifdef SO_SNDBUF
313  {
314  int opt;
315  socklen_t socklen = sizeof(int);
316 
317  if (h->inst->send_buff_is_set) {
318  opt = h->inst->send_buff;
319  if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) < 0) {
320  WARN("%s - Failed setting 'SO_SNDBUF', write performance may be sub-optimal: %s",
321  h->module_name, fr_syserror(errno));
322  }
323  }
324 
325  if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, &socklen) < 0) {
326  WARN("%s - Failed getting 'SO_SNDBUF', write performance may be sub-optimal: %s",
327  h->module_name, fr_syserror(errno));
328 
329  /*
330  * This controls how many packets we attempt
331  * to send at once. Nothing bad happens if
332  * we get it wrong, but the user may see
333  * ENOBUFS errors at high packet rates.
334  *
335  * Since this is TACACS, we have small
336  * packets and a maximum of 255 packets
337  * per connection. So don't set this too large.
338  */
339  if (h->inst->send_buff_is_set) {
341  } else {
343  if (h->send_buff_actual > 256*1024) h->send_buff_actual = 256*1024;
344  }
345 
346  WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name,
347  h->send_buff_actual);
348  } else {
349 #ifdef __linux__
350  /*
351  * Linux doubles the buffer when you set it
352  * to account for "overhead".
353  */
354  h->send_buff_actual = ((size_t)opt) / 2;
355 #else
356  h->send_buff_actual = (size_t)opt;
357 #endif
358  }
359  }
360 #else
362  h->inst_send_buff : h->max_packet_size * h->inst->max_send_coalesce;
363 
364  WARN("%s - Modifying 'SO_SNDBUF' value is not supported on this system, "
365  "write performance may be sub-optimal", h->module_name);
366  WARN("%s - Max coalesced outbound data will be %zu bytes", h->module_name, h->inst->send_buff_actual);
367 #endif
368 
369  /*
370  * Allow receiving of 2 max-sized packets. In practice, most packets will be less than this.
371  */
372  MEM(h->recv.data = talloc_array(h, uint8_t, h->max_packet_size * 2));
373  h->recv.read = h->recv.write = h->recv.data;
374  h->recv.end = h->recv.data + h->max_packet_size * 2;
375 
376  /*
377  * Use the system SO_SNDBUF for how many packets to send at once. In most circumstances the
378  * packets are small, and widely separated in time, and we really only need a very small buffer.
379  */
380  MEM(h->send.data = talloc_array(h, uint8_t, h->send_buff_actual));
381  h->send.read = h->send.write = h->send.data;
382  h->send.end = h->send.data + h->send_buff_actual;
383 
384  h->fd = fd;
385 
386  /*
387  * Signal the connection
388  * as open as soon as it becomes writable.
389  */
390  fr_connection_signal_on_fd(conn, fd);
391 
392  *h_out = h;
393 
394  // @todo - initialize the tracking memory, etc.
395  // i.e. histograms (or hyperloglog) of packets, so we can see
396  // which connections / home servers are fast / slow.
397 
399 }
400 
401 /** Shutdown/close a file descriptor
402  *
403  */
404 static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx)
405 {
406  udp_handle_t *h = talloc_get_type_abort(handle, udp_handle_t);
407 
408  /*
409  * There's tracking entries still allocated
410  * this is bad, they should have all been
411  * released.
412  */
413  fr_assert(!h->active);
414 
415  DEBUG4("Freeing rlm_tacacs_tcp handle %p", handle);
416 
417  talloc_free(h);
418 }
419 
420 
422  fr_connection_conf_t const *conf,
423  char const *log_prefix, void *uctx)
424 {
425  fr_connection_t *conn;
426  udp_thread_t *thread = talloc_get_type_abort(uctx, udp_thread_t);
427 
428  conn = fr_connection_alloc(tconn, el,
430  .init = conn_init,
431  .close = conn_close,
432  },
433  conf,
434  log_prefix,
435  thread);
436  if (!conn) {
437  PERROR("%s - Failed allocating state handler for new connection", thread->inst->parent->name);
438  return NULL;
439  }
440 
441  return conn;
442 }
443 
444 /** Connection errored
445  *
446  * We were signalled by the event loop that a fatal error occurred on this connection.
447  *
448  * @param[in] el The event list signalling.
449  * @param[in] fd that errored.
450  * @param[in] flags El flags.
451  * @param[in] fd_errno The nature of the error.
452  * @param[in] uctx The trunk connection handle (tconn).
453  */
454 static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
455 {
456  fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
457  fr_connection_t *conn = tconn->conn;
458  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
459 
460  ERROR("%s - Connection %s failed: %s", h->module_name, h->name, fr_syserror(fd_errno));
461 
463 }
464 
467  fr_trunk_connection_event_t notify_on, UNUSED void *uctx)
468 {
469  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
470  fr_event_fd_cb_t read_fn = NULL;
471  fr_event_fd_cb_t write_fn = NULL;
472 
473  switch (notify_on) {
475  return;
476 
479  break;
480 
483  break;
484 
488  break;
489 
490  }
491 
492  if (fr_event_fd_insert(h, NULL, el, h->fd,
493  read_fn,
494  write_fn,
495  conn_error,
496  tconn) < 0) {
497  PERROR("%s - Failed inserting FD event", h->module_name);
498 
499  /*
500  * May free the connection!
501  */
503  }
504 }
505 
506 /*
507  * Return negative numbers to put 'a' at the top of the heap.
508  * Return positive numbers to put 'b' at the top of the heap.
509  *
510  * We want the value with the lowest timestamp to be prioritized at
511  * the top of the heap.
512  */
513 static int8_t request_prioritise(void const *one, void const *two)
514 {
515  udp_request_t const *a = one;
516  udp_request_t const *b = two;
517  int8_t ret;
518 
519  /*
520  * Larger priority is more important.
521  */
522  ret = CMP(a->priority, b->priority);
523  if (ret != 0) return ret;
524 
525  /*
526  * Smaller timestamp (i.e. earlier) is more important.
527  */
529 }
530 
531 /** Decode response packet data, extracting relevant information and validating the packet
532  *
533  * @param[in] ctx to allocate pairs in.
534  * @param[out] reply Pointer to head of pair list to add reply attributes to.
535  * @param[out] response_code The type of response packet.
536  * @param[in] h connection handle.
537  * @param[in] request the request.
538  * @param[in] u UDP request.
539  * @param[in] data to decode.
540  * @param[in] data_len Length of input data.
541  * @return
542  * - <0 on error
543  * - >0 for how many bytes were decoded
544  */
545 static ssize_t decode(TALLOC_CTX *ctx, fr_pair_list_t *reply, uint8_t *response_code,
546  udp_handle_t *h, request_t *request, udp_request_t *u,
547  uint8_t *data, size_t data_len)
548 {
549  rlm_tacacs_tcp_t const *inst = h->thread->inst;
550  ssize_t packet_len;
551  int code;
552 
553  *response_code = 0; /* Initialise to keep the rest of the code happy */
554 
555  /*
556  * Check the session ID here, because we've lost the original packet.
557  */
558  if (h->session_id != fr_nbo_to_uint32(data + 4)) {
559  REDEBUG("Session ID %08x does not match expected number %08x",
560  fr_nbo_to_uint32(data + 4), h->session_id);
561  }
562 
563  /*
564  * Decode the attributes, in the context of the reply.
565  * This only fails if the packet is strangely malformed,
566  * or if we run out of memory.
567  */
568  packet_len = fr_tacacs_decode(ctx, reply, NULL, data, data_len, NULL, inst->secret, inst->secretlen, &code);
569  if (packet_len < 0) {
570  RPEDEBUG("Failed decoding TACACS+ reply packet");
571  fr_pair_list_free(reply);
572  return -1;
573  }
574 
575  RDEBUG("Received %s ID %d length %ld reply packet on connection %s",
576  fr_tacacs_packet_names[code], code, packet_len, h->name);
577  log_request_pair_list(L_DBG_LVL_2, request, NULL, reply, NULL);
578 
579  *response_code = code;
580 
581  /*
582  * Fixup retry times
583  */
584  if (fr_time_gt(u->retry.start, h->mrs_time)) h->mrs_time = u->retry.start;
585 
586  return packet_len;
587 }
588 
589 static int encode(udp_handle_t *h, request_t *request, udp_request_t *u)
590 {
591  ssize_t packet_len;
592  rlm_tacacs_tcp_t const *inst = h->inst;
593  fr_pair_t *hdr, *vp;
594 
595  fr_assert(inst->parent->allowed[u->code]);
596  fr_assert(!u->packet);
597  fr_assert(!u->outstanding);
598 
599  /*
600  * Encode the packet in the outbound buffer.
601  */
602  u->packet = h->send.write;
603 
604  /*
605  * Set the session ID, if it hasn't already been set.
606  */
607  hdr = fr_pair_find_by_da(&request->request_pairs, NULL, attr_packet_hdr);
608  if (!hdr) hdr = request->request_ctx;
609 
610  vp = fr_pair_find_by_da_nested(&hdr->vp_group, NULL, attr_session_id);
611  if (!vp) {
613 
614  vp->vp_uint32 = h->session_id;
615  fr_pair_append(&hdr->vp_group, vp);
617  }
618 
619  /*
620  * Encode the packet.
621  */
622  packet_len = fr_tacacs_encode(&FR_DBUFF_TMP(u->packet, (size_t) inst->max_packet_size), NULL,
623  inst->secret, inst->secretlen, request->reply->code, &request->request_pairs);
624  if (packet_len < 0) {
625  RPERROR("Failed encoding packet");
626  return -1;
627  }
628 
629  /*
630  * Update the ID and the actual packet length;
631  */
632  u->packet[1] = u->id;
633  u->packet_len = packet_len;
634  u->outstanding = true;
635 
636 // fr_tacacs_packet_log_hex(&default_log, u->packet);
637 
638  return 0;
639 }
640 
641 
642 /** Revive a connection after "revive_interval"
643  *
644  */
645 static void revive_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
646 {
647  fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
648  udp_handle_t *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
649 
650  INFO("%s - Reviving connection %s", h->module_name, h->name);
652 }
653 
654 /** Mark a connection dead after "zombie_interval"
655  *
656  */
657 static void zombie_timeout(fr_event_list_t *el, fr_time_t now, void *uctx)
658 {
659  fr_trunk_connection_t *tconn = talloc_get_type_abort(uctx, fr_trunk_connection_t);
660  udp_handle_t *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
661 
662  INFO("%s - No replies during 'zombie_period', marking connection %s as dead", h->module_name, h->name);
663 
664  /*
665  * Don't use this connection, and re-queue all of its
666  * requests onto other connections.
667  */
670 
671  /*
672  * Revive the connection after a time.
673  */
674  if (fr_event_timer_at(h, el, &h->zombie_ev,
675  fr_time_add(now, h->inst->parent->revive_interval), revive_timeout, h) < 0) {
676  ERROR("Failed inserting revive timeout for connection");
678  }
679 }
680 
681 
682 /** See if the connection is zombied.
683  *
684  * We check for zombie when major events happen:
685  *
686  * 1) request hits its final timeout
687  * 2) request timer hits, and it needs to be retransmitted
688  * 3) a DUP packet comes in, and the request needs to be retransmitted
689  * 4) we're sending a packet.
690  *
691  * There MIGHT not be retries configured, so we MUST check for zombie
692  * when any new packet comes in. Similarly, there MIGHT not be new
693  * packets, but retries are configured, so we have to check there,
694  * too.
695  *
696  * Also, the socket might not be writable for a while. There MIGHT
697  * be a long time between getting the timer / DUP signal, and the
698  * request finally being written to the socket. So we need to check
699  * for zombie at BOTH the timeout and the mux / write function.
700  *
701  * @return
702  * - true if the connection is zombie.
703  * - false if the connection is not zombie.
704  */
706 {
707  udp_handle_t *h = talloc_get_type_abort(tconn->conn->h, udp_handle_t);
708 
709  /*
710  * If we're already zombie, don't go to zombie
711  *
712  */
713  if (h->zombie_ev) return true;
714 
715  if (fr_time_eq(now, fr_time_wrap(0))) now = fr_time();
716 
717  /*
718  * We received a reply since this packet was sent, the connection isn't zombie.
719  */
720  if (fr_time_gteq(h->last_reply, last_sent)) return false;
721 
722  /*
723  * If we've seen ANY response in the allowed window, then the connection is still alive.
724  */
725  if (fr_time_gt(last_sent, fr_time_wrap(0)) &&
726  (fr_time_lt(fr_time_add(last_sent, h->inst->parent->response_window), now))) return false;
727 
728  /*
729  * Mark the connection as inactive, but keep sending
730  * packets on it.
731  */
732  WARN("%s - Entering Zombie state - connection %s", h->module_name, h->name);
734 
736  zombie_timeout, h) < 0) {
737  ERROR("Failed inserting zombie timeout for connection");
739  }
740 
741  return true;
742 }
743 
744 /** Handle retries.
745  *
746  * Note that with TCP we don't actually retry on this particular connection, but the retry timer allows us to
747  * fail over from one connection to another when a connection fails.
748  */
749 static void request_retry(fr_event_list_t *el, fr_time_t now, void *uctx)
750 {
751  fr_trunk_request_t *treq = talloc_get_type_abort(uctx, fr_trunk_request_t);
752  udp_request_t *u = talloc_get_type_abort(treq->preq, udp_request_t);
753  udp_result_t *r = talloc_get_type_abort(treq->rctx, udp_result_t);
754  request_t *request = treq->request;
755  fr_trunk_connection_t *tconn = treq->tconn;
756 
757  fr_assert(treq->state == FR_TRUNK_REQUEST_STATE_SENT); /* No other states should be timing out */
758  fr_assert(treq->preq); /* Must still have a protocol request */
759  fr_assert(tconn);
760 
761  switch (fr_retry_next(&u->retry, now)) {
762  /*
763  * Queue the request for retransmission.
764  *
765  * @todo - set up "next" timer here, instead of in
766  * request_mux() ? That way we can catch the case of
767  * packets sitting in the queue for extended periods of
768  * time, and still run the timers.
769  */
770  case FR_RETRY_CONTINUE:
772  return;
773 
774  case FR_RETRY_MRD:
775  REDEBUG("Reached maximum_retransmit_duration (%pVs > %pVs), failing request",
777  break;
778 
779  case FR_RETRY_MRC:
780  REDEBUG("Reached maximum_retransmit_count (%u > %u), failing request",
781  u->retry.count, u->retry.config->mrc);
782  break;
783  }
784 
785  r->rcode = RLM_MODULE_FAIL;
787 
788  check_for_zombie(el, tconn, now, u->retry.start);
789 }
790 
792  fr_trunk_connection_t *tconn, fr_connection_t *conn, UNUSED void *uctx)
793 {
794  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
795  rlm_tacacs_tcp_t const *inst = h->inst;
796  ssize_t sent;
797  uint16_t i, queued;
798  uint8_t const *written;
799  uint8_t *partial;
800 
801  /*
802  * Encode multiple packets in preparation for transmission with write()
803  */
804  for (i = 0, queued = 0; (i < inst->max_send_coalesce); i++) {
805  fr_trunk_request_t *treq;
806  udp_request_t *u;
807  request_t *request;
808 
809  if (unlikely(fr_trunk_connection_pop_request(&treq, tconn) < 0)) return;
810 
811  /*
812  * No more requests to send
813  */
814  if (!treq) break;
815 
816  /*
817  * The partial write MUST be the first one popped off of the request list.
818  *
819  * If we have a partial packet, then we know that there's partial data in the output
820  * buffer. However, the request MAY still be freed or timed out before we can write the
821  * data. As a result, we ignore the udp_request_t, and just keep writing the data.
822  */
823  if (treq->state == FR_TRUNK_REQUEST_STATE_PARTIAL) {
824  fr_assert(h->send.read == h->send.data);
825  fr_assert(h->send.write > h->send.read);
826 
827  fr_assert(i == 0);
828 
829  h->coalesced[0] = treq;
830  goto next;
831  }
832 
833  /*
834  * The request must still be pending.
835  */
837 
838  request = treq->request;
839  u = talloc_get_type_abort(treq->preq, udp_request_t);
840 
841  /*
842  * We'd like to retransmit the packet on this connection, but it's TCP so we don't.
843  *
844  * The retransmission timers are really there to move the packet to a new connection if
845  * the current connection is dead.
846  */
847  if (u->outstanding) continue;
848 
849  /*
850  * Not enough room for a full-sized packet, stop encoding packets
851  */
852  if ((h->send.end - h->send.write) < inst->max_packet_size) {
853  break;
854  }
855 
856  /*
857  * Start retransmissions from when the socket is writable.
858  */
859  fr_retry_init(&u->retry, fr_time(), &h->inst->parent->retry);
862 
863  /*
864  * Set up the packet for encoding.
865  */
866  u->id = h->id;
867  h->tconn = tconn;
868 
869  h->tracking[u->id] = treq;
870  h->id += 2;
871  h->active++;
872 
873  RDEBUG("Sending %s ID %d length %ld over connection %s",
874  fr_tacacs_packet_names[u->code], u->id, u->packet_len, h->name);
875 
876  if (encode(h, request, u) < 0) {
877  /*
878  * Need to do this because request_conn_release
879  * may not be called.
880  */
881  udp_request_reset(h, u);
883  continue;
884  }
885  RHEXDUMP3(u->packet, u->packet_len, "Encoded packet");
886 
887  log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
888 
889  /*
890  * Remember that we've encoded this packet.
891  */
892  h->coalesced[queued] = treq;
893  h->send.write += u->packet_len;
894 
895  fr_assert(h->send.write <= h->send.end);
896 
897  /*
898  * If we just hit this limit, stop using the connection.
899  *
900  * When we've received all replies (or timeouts), we'll close the connections.
901  */
902  if (h->id > 255) {
904  }
905 
906  next:
907  /*
908  * Tell the trunk API that this request is now in
909  * the "sent" state. And we don't want to see
910  * this request again. The request hasn't actually
911  * been sent, but it's the only way to get at the
912  * next entry in the heap.
913  */
915  queued++;
916  }
917 
918  if (queued == 0) return;
919 
920  /*
921  * Verify nothing accidentally freed the connection handle
922  */
923  (void)talloc_get_type_abort(h, udp_handle_t);
924 
925  /*
926  * Send the packets as one system call.
927  */
928  sent = write(h->fd, h->send.read, h->send.write - h->send.read);
929  if (sent < 0) { /* Error means no messages were sent */
930  /*
931  * Temporary conditions
932  */
933  switch (errno) {
934 #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
935  case EWOULDBLOCK: /* No outbound packet buffers, maybe? */
936 #endif
937  case EAGAIN: /* No outbound packet buffers, maybe? */
938  case EINTR: /* Interrupted by signal */
939  case ENOBUFS: /* No outbound packet buffers, maybe? */
940  case ENOMEM: /* malloc failure in kernel? */
941  WARN("%s - Failed sending data over connection %s: %s",
942  h->module_name, h->name, fr_syserror(errno));
943  sent = 0;
944  break;
945 
946  /*
947  * Will re-queue any 'sent' requests, so we don't
948  * have to do any cleanup.
949  */
950  default:
951  ERROR("%s - Failed sending data over connection %s: %s",
952  h->module_name, h->name, fr_syserror(errno));
954  return;
955  }
956  }
957 
958  written = h->send.read + sent;
959  partial = h->send.read;
960 
961  /*
962  * For all messages that were actually sent by writev()
963  * start the request timer.
964  */
965  for (i = 0; i < queued; i++) {
966  fr_trunk_request_t *treq = h->coalesced[i];
967  udp_request_t *u;
968  request_t *request;
969 
970  /*
971  * We *think* we sent this, but we might not had :(
972  */
973  fr_assert(treq->state == FR_TRUNK_REQUEST_STATE_SENT);
974 
975  request = treq->request;
976  u = talloc_get_type_abort(treq->preq, udp_request_t);
977 
978  /*
979  * This packet ends before the piece we've
980  * written, so we've written all of it.
981  */
982  if (u->packet + u->packet_len <= written) {
983  h->last_sent = u->retry.start;
985 
986  if (fr_event_timer_at(u, el, &u->ev, u->retry.next, request_retry, treq) < 0) {
987  RERROR("Failed inserting retransmit timeout for connection");
989  }
990 
991  /*
992  * If the packet doesn't get a response, then the timer will hit
993  * and will retransmit.
994  */
995  u->outstanding = true;
996  continue;
997  }
998 
999  /*
1000  * The packet starts before the piece we've written, BUT ends after the written piece.
1001  *
1002  * We only wrote part of this packet, remember the partial packet we wrote. Note that
1003  * we only track the packet data, and not the udp_request_t. The underlying request (and
1004  * u) may disappear at any time, even if there's still data in the buffer.
1005  *
1006  * Then, signal that isn't a partial packet, and stop processing the queue, as we know
1007  * that the next packet wasn't written.
1008  */
1009  if (u->packet < written) {
1010  size_t skip = written - u->packet;
1011  size_t left = u->packet_len - skip;
1012 
1013  fr_assert(u->packet + u->packet_len > written);
1014 
1015  memmove(h->send.data, u->packet, left);
1016 
1017  fr_assert(h->send.read == h->send.data);
1018  partial = h->send.data + left;
1019  u->outstanding = true;
1020 
1022  continue;
1023  }
1024 
1025  /*
1026  * The packet starts after the piece we've written, so we haven't written any of it.
1027  *
1028  * Requests that weren't sent get re-enqueued. Which means that they get re-encoded, but
1029  * oh well.
1030  *
1031  * The cancel logic runs as per-normal and cleans up
1032  * the request ready for sending again...
1033  */
1035  fr_assert(!u->outstanding); /* must have called udp_request_requeue() */
1036  }
1037 
1038  /*
1039  * Remember where to write the next packet. Either at the start of the buffer, or after the one
1040  * which was partially written.
1041  */
1042  h->send.write = partial;
1043 }
1044 
1046 {
1047  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
1048  bool do_read = true;
1049 
1050  DEBUG3("%s - Reading data for connection %s", h->module_name, h->name);
1051 
1052  while (true) {
1053  ssize_t slen;
1054  size_t available, used, packet_len;
1055 
1056  fr_trunk_request_t *treq;
1057  request_t *request;
1058  udp_request_t *u;
1059  udp_result_t *r;
1060  uint8_t code = 0;
1061  fr_pair_list_t reply;
1062 
1063  /*
1064  * Ensure that we can read at least one max-sized packet.
1065  *
1066  * If not, move the trailing bytes to the start of the buffer, and reset the read/write
1067  * pointers to the start of the buffer. Note that the read buffer has to be at least 2x
1068  * max_packet_size.
1069  */
1070  available = h->recv.end - h->recv.read;
1071  if (available < h->inst->max_packet_size) {
1072  fr_assert(h->recv.data + h->inst->max_packet_size < h->recv.read);
1073 
1074  used = h->recv.write - h->recv.read;
1075 
1076  memcpy(h->recv.data, h->recv.read, used);
1077  h->recv.read = h->recv.data;
1078  h->recv.write = h->recv.read + used;
1079  }
1080 
1081  /*
1082  * Read as much data as possible.
1083  *
1084  * We don't need to call read() on every round through the loop. Instead, we call it
1085  * only when this function first gets called, OR if the read stopped at the end of the
1086  * buffer.
1087  *
1088  * This allows us to read a large amount of data at once, and then process multiple
1089  * packets without calling read() too many times.
1090  */
1091  if (do_read) {
1092  slen = read(h->fd, h->recv.write, h->recv.end - h->recv.write);
1093  if (slen < 0) {
1094  if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) return;
1095 
1096  ERROR("%s - Failed reading response from socket: %s",
1097  h->module_name, fr_syserror(errno));
1099  return;
1100  }
1101 
1102  h->recv.write += slen;
1103  do_read = (h->recv.write == h->recv.end);
1104  }
1105 
1106  used = h->recv.write - h->recv.read;
1107 
1108  /*
1109  * We haven't received a full header, read more or return.
1110  */
1111  if (used < sizeof(fr_tacacs_packet_hdr_t)) {
1112  if (do_read) continue;
1113  return;
1114  }
1115 
1116  /*
1117  * The packet contains a 4 octet length in the
1118  * header, but the header bytes aren't included
1119  * in the 4 octet length field.
1120  */
1121  packet_len = fr_nbo_to_uint32(h->recv.read + 8) + FR_HEADER_LENGTH;
1122 
1123  /*
1124  * The packet is too large, reject it.
1125  */
1126  if (packet_len > h->inst->max_packet_size) {
1127  ERROR("%s - Packet is larger than max_packet_size",
1128  h->module_name);
1130  return;
1131  }
1132 
1133  /*
1134  * We haven't received the full packet, read more or return.
1135  */
1136  if (used < packet_len) {
1137  if (do_read) continue;
1138  return;
1139  }
1140 
1141  fr_assert(h->recv.read + packet_len <= h->recv.end);
1142 
1143  /*
1144  * TACACS+ doesn't care about packet codes. All packet of the codes share the same ID
1145  * space.
1146  */
1147  treq = h->tracking[h->recv.read[1]];
1148  if (!treq) {
1149  WARN("%s - Ignoring reply with ID %i that arrived too late",
1150  h->module_name, h->recv.data[1]);
1151 
1152  h->recv.read += packet_len;
1153  continue;
1154  }
1155 
1156  treq = talloc_get_type_abort(treq, fr_trunk_request_t);
1157  request = treq->request;
1158  fr_assert(request != NULL);
1159  u = talloc_get_type_abort(treq->preq, udp_request_t);
1160  r = talloc_get_type_abort(treq->rctx, udp_result_t);
1161 
1162  fr_pair_list_init(&reply);
1163 
1164  /*
1165  * Validate and decode the incoming packet
1166  */
1167  slen = decode(request->reply_ctx, &reply, &code, h, request, u, h->recv.read, packet_len);
1168  if (slen < 0) {
1169  // @todo - give real decode error?
1171  return;
1172  }
1173  h->recv.read += packet_len;
1174 
1175  /*
1176  * Only valid packets are processed.
1177  */
1178  h->last_reply = fr_time();
1179 
1180  treq->request->reply->code = code;
1181 
1182  // @todo - check various random locations for status of the reply: error, etc.
1183  r->rcode = RLM_MODULE_OK;
1184 // r->rcode = radius_code_to_rcode[code];
1185  fr_pair_list_append(&request->reply_pairs, &reply);
1187  }
1188 }
1189 
1190 /** Remove the request from any tracking structures
1191  *
1192  * Frees encoded packets if the request is being moved to a new connection
1193  */
1194 static void request_cancel(fr_connection_t *conn, void *preq_to_reset,
1195  fr_trunk_cancel_reason_t reason, UNUSED void *uctx)
1196 {
1197  udp_request_t *u = talloc_get_type_abort(preq_to_reset, udp_request_t);
1198 
1199  /*
1200  * Request has been requeued on the same
1201  * connection due to timeout or DUP signal.
1202  */
1203  if (reason == FR_TRUNK_CANCEL_REASON_REQUEUE) {
1204  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
1205 
1206  udp_request_reset(h, u);
1207  }
1208 
1209  /*
1210  * Other cancellations are dealt with by
1211  * request_conn_release as the request is removed
1212  * from the trunk.
1213  */
1214 }
1215 
1216 /** Clear out anything associated with the handle from the request
1217  *
1218  */
1219 static void request_conn_release(fr_connection_t *conn, void *preq_to_reset, UNUSED void *uctx)
1220 {
1221  udp_request_t *u = talloc_get_type_abort(preq_to_reset, udp_request_t);
1222  udp_handle_t *h = talloc_get_type_abort(conn->h, udp_handle_t);
1223 
1224  if (u->packet) udp_request_reset(h, u);
1225 
1226  /*
1227  * If there are no outstanding tracking entries
1228  * allocated then the connection is "idle".
1229  *
1230  * @todo - enable idle timeout?
1231  */
1232  if (!h->active) h->last_idle = fr_time();
1233 }
1234 
1235 /** Write out a canned failure
1236  *
1237  */
1238 static void request_fail(request_t *request, NDEBUG_UNUSED void *preq, void *rctx,
1239  NDEBUG_UNUSED fr_trunk_request_state_t state, UNUSED void *uctx)
1240 {
1241  udp_result_t *r = talloc_get_type_abort(rctx, udp_result_t);
1242 #ifndef NDEBUG
1243  udp_request_t *u = talloc_get_type_abort(preq, udp_request_t);
1244 #endif
1245 
1246  fr_assert(!u->ev); /* Dealt with by request_conn_release */
1247 
1249 
1250  r->rcode = RLM_MODULE_FAIL;
1251  r->treq = NULL;
1252 
1254 }
1255 
1256 /** Response has already been written to the rctx at this point
1257  *
1258  */
1259 static void request_complete(request_t *request, NDEBUG_UNUSED void *preq, void *rctx, UNUSED void *uctx)
1260 {
1261  udp_result_t *r = talloc_get_type_abort(rctx, udp_result_t);
1262 #ifndef NDEBUG
1263  udp_request_t *u = talloc_get_type_abort(preq, udp_request_t);
1264 #endif
1265 
1266  fr_assert(!u->packet && !u->ev); /* Dealt with by request_conn_release */
1267 
1268  r->treq = NULL;
1269 
1271 }
1272 
1273 /** Explicitly free resources associated with the protocol request
1274  *
1275  */
1276 static void request_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
1277 {
1278  udp_request_t *u = talloc_get_type_abort(preq_to_free, udp_request_t);
1279 
1280  fr_assert(!u->packet && !u->ev); /* Dealt with by request_conn_release */
1281 
1282  talloc_free(u);
1283 }
1284 
1285 /** Resume execution of the request, returning the rcode set during trunk execution
1286  *
1287  */
1288 static unlang_action_t mod_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request)
1289 {
1290  udp_result_t *r = talloc_get_type_abort(mctx->rctx, udp_result_t);
1291  rlm_rcode_t rcode = r->rcode;
1292 
1293  talloc_free(r);
1294 
1295  RETURN_MODULE_RCODE(rcode);
1296 }
1297 
1298 static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
1299 {
1300 // udp_thread_t *t = talloc_get_type_abort(mctx->thread, udp_thread_t);
1301  udp_result_t *r = talloc_get_type_abort(mctx->rctx, udp_result_t);
1302 
1303  /*
1304  * If we don't have a treq associated with the
1305  * rctx it's likely because the request was
1306  * scheduled, but hasn't yet been resumed, and
1307  * has received a signal, OR has been resumed
1308  * and immediately cancelled as the event loop
1309  * is exiting, in which case
1310  * unlang_request_is_scheduled will return false
1311  * (don't use it).
1312  */
1313  if (!r->treq) {
1314  talloc_free(r);
1315  return;
1316  }
1317 
1318  switch (action) {
1319  /*
1320  * The request is being cancelled, tell the
1321  * trunk so it can clean up the treq.
1322  */
1323  case FR_SIGNAL_CANCEL:
1325  r->treq = NULL;
1326  talloc_free(r); /* Should be freed soon anyway, but better to be explicit */
1327  return;
1328 
1329  /*
1330  * Requeue the request on the same connection
1331  * causing a "retransmission" if the request
1332  * has already been sent out.
1333  */
1334  case FR_SIGNAL_DUP:
1335  /*
1336  * Retransmit the current request on the same connection.
1337  *
1338  * If it's zombie, we still resend it. If the
1339  * connection is dead, then a callback will move
1340  * this request to a new connection.
1341  */
1343  return;
1344 
1345  default:
1346  return;
1347  }
1348 }
1349 
1350 #ifndef NDEBUG
1351 /** Free a udp_result_t
1352  *
1353  * Allows us to set break points for debugging.
1354  */
1356 {
1357  fr_trunk_request_t *treq;
1358  udp_request_t *u;
1359 
1360  if (!r->treq) return 0;
1361 
1362  treq = talloc_get_type_abort(r->treq, fr_trunk_request_t);
1363  u = talloc_get_type_abort(treq->preq, udp_request_t);
1364 
1365  fr_assert_msg(!u->ev, "udp_result_t freed with active timer");
1366 
1367  return 0;
1368 }
1369 #endif
1370 
1371 static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, void **rctx_out, UNUSED void *instance, void *thread, request_t *request)
1372 {
1373  udp_thread_t *t = talloc_get_type_abort(thread, udp_thread_t);
1374  udp_result_t *r;
1375  udp_request_t *u;
1376  fr_trunk_request_t *treq;
1378 
1379  fr_assert(FR_TACACS_PACKET_CODE_VALID(request->packet->code));
1380 
1381  treq = fr_trunk_request_alloc(t->trunk, request);
1382  if (!treq) RETURN_MODULE_FAIL;
1383 
1384  MEM(r = talloc_zero(request, udp_result_t));
1385 #ifndef NDEBUG
1386  talloc_set_destructor(r, _udp_result_free);
1387 #endif
1388 
1389  /*
1390  * Can't use compound literal - const issues.
1391  */
1392  MEM(u = talloc_zero(treq, udp_request_t));
1393  u->code = request->packet->code;
1394  u->priority = request->async->priority;
1395  u->recv_time = request->async->recv_time;
1396 
1397  r->rcode = RLM_MODULE_FAIL;
1398 
1399  q = fr_trunk_request_enqueue(&treq, t->trunk, request, u, r);
1400  if (q < 0) {
1401  fr_assert(!u->packet); /* Should not have been fed to the muxer */
1402  fr_trunk_request_free(&treq); /* Return to the free list */
1403  fail:
1404  talloc_free(r);
1406  }
1407 
1408  /*
1409  * All destinations are down.
1410  */
1411  if (q == FR_TRUNK_ENQUEUE_IN_BACKLOG) {
1412  RDEBUG("All destinations are down - cannot send packet");
1413  goto fail;
1414  }
1415 
1416  r->treq = treq; /* Remember for signalling purposes */
1417 
1418  *rctx_out = r;
1419 
1420  return UNLANG_ACTION_YIELD;
1421 }
1422 
1423 /** Instantiate thread data for the submodule.
1424  *
1425  */
1427 {
1428  rlm_tacacs_tcp_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_tacacs_tcp_t);
1429  udp_thread_t *thread = talloc_get_type_abort(mctx->thread, udp_thread_t);
1430 
1431  static fr_trunk_io_funcs_t io_funcs = {
1433  .connection_notify = thread_conn_notify,
1434  .request_prioritise = request_prioritise,
1435  .request_mux = request_mux,
1436  .request_demux = request_demux,
1437  .request_conn_release = request_conn_release,
1438  .request_complete = request_complete,
1439  .request_fail = request_fail,
1440  .request_cancel = request_cancel,
1441  .request_free = request_free
1442  };
1443 
1444  inst->trunk_conf = &inst->parent->trunk_conf;
1445 
1446  inst->trunk_conf->req_pool_headers = 2; /* One for the request, one for the buffer */
1447  inst->trunk_conf->req_pool_size = sizeof(udp_request_t) + inst->max_packet_size;
1448 
1449  thread->el = mctx->el;
1450  thread->inst = inst;
1451  thread->trunk = fr_trunk_alloc(thread, mctx->el, &io_funcs,
1452  inst->trunk_conf, inst->parent->name, thread, false);
1453  if (!thread->trunk) return -1;
1454 
1455  /*
1456  * Empty secrets don't exist
1457  */
1458  if (inst->secret && !*inst->secret) {
1459  talloc_const_free(inst->secret);
1460  inst->secret = NULL;
1461  }
1462 
1463  if (inst->secret) inst->secretlen = talloc_array_length(inst->secret) - 1;
1464 
1465  return 0;
1466 }
1467 
1468 static int mod_instantiate(module_inst_ctx_t const *mctx)
1469 {
1470  rlm_tacacs_t *parent = talloc_get_type_abort(mctx->inst->parent->data, rlm_tacacs_t);
1471  rlm_tacacs_tcp_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_tacacs_tcp_t);
1472  CONF_SECTION *conf = mctx->inst->conf;
1473 
1474  if (!parent) {
1475  ERROR("IO module cannot be instantiated directly");
1476  return -1;
1477  }
1478 
1479  inst->parent = parent;
1480 
1481  /*
1482  * Always need at least one mmsgvec
1483  */
1484  if (inst->max_send_coalesce == 0) inst->max_send_coalesce = 1;
1485 
1486  /*
1487  * Ensure that we have a destination address.
1488  */
1489  if (inst->dst_ipaddr.af == AF_UNSPEC) {
1490  cf_log_err(conf, "A value must be given for 'ipaddr'");
1491  return -1;
1492  }
1493 
1494  /*
1495  * If src_ipaddr isn't set, make sure it's INADDR_ANY, of
1496  * the same address family as dst_ipaddr.
1497  */
1498  if (inst->src_ipaddr.af == AF_UNSPEC) {
1499  memset(&inst->src_ipaddr, 0, sizeof(inst->src_ipaddr));
1500 
1501  inst->src_ipaddr.af = inst->dst_ipaddr.af;
1502 
1503  if (inst->src_ipaddr.af == AF_INET) {
1504  inst->src_ipaddr.prefix = 32;
1505  } else {
1506  inst->src_ipaddr.prefix = 128;
1507  }
1508  }
1509 
1510  else if (inst->src_ipaddr.af != inst->dst_ipaddr.af) {
1511  cf_log_err(conf, "The 'ipaddr' and 'src_ipaddr' configuration items must "
1512  "be both of the same address family");
1513  return -1;
1514  }
1515 
1516  if (!inst->dst_port) {
1517  cf_log_err(conf, "A value must be given for 'port'");
1518  return -1;
1519  }
1520 
1521  /*
1522  * Clamp max_packet_size first before checking recv_buff and send_buff
1523  */
1524  FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, >=, ((255 + (int) sizeof(fr_tacacs_packet_t)) & 0xffffff00));
1525  FR_INTEGER_BOUND_CHECK("max_packet_size", inst->max_packet_size, <=, 65535);
1526 
1527 
1528  if (inst->recv_buff_is_set) {
1529  FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, inst->max_packet_size);
1530  FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, (1 << 30));
1531  }
1532 
1533  if (inst->send_buff_is_set) {
1534  FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, >=, inst->max_packet_size);
1535  FR_INTEGER_BOUND_CHECK("send_buff", inst->send_buff, <=, (1 << 30));
1536  }
1537 
1538 
1539  return 0;
1540 }
1541 
1544  .common = {
1545  .magic = MODULE_MAGIC_INIT,
1546  .name = "tacacs_tcp",
1547  .inst_size = sizeof(rlm_tacacs_tcp_t),
1548 
1549  .thread_inst_size = sizeof(udp_thread_t),
1550  .thread_inst_type = "udp_thread_t",
1551 
1552  .config = module_config,
1553  .instantiate = mod_instantiate,
1554  .thread_instantiate = mod_thread_instantiate,
1555  },
1556  .enqueue = mod_enqueue,
1557  .signal = mod_signal,
1558  .resume = mod_resume,
1559 };
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:42
#define RCSID(id)
Definition: build.h:444
#define NDEBUG_UNUSED
Definition: build.h:324
#define CMP_PREFER_SMALLER(_a, _b)
Evaluates to +1 for a > b, and -1 for a < b.
Definition: build.h:102
#define STRINGIFY(x)
Definition: build.h:195
#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:110
#define unlikely(_x)
Definition: build.h:378
#define UNUSED
Definition: build.h:313
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_INTEGER_BOUND_CHECK(_name, _var, _op, _bound)
Definition: cf_parse.h:486
#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:268
#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:282
#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:563
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
fr_connection_state_t
Definition: connection.h:45
@ FR_CONNECTION_STATE_CONNECTING
Waiting for connection to establish.
Definition: connection.h:50
@ FR_CONNECTION_STATE_FAILED
Connection has failed.
Definition: connection.h:54
@ FR_CONNECTION_EXPIRED
Connection is being reconnected because it's at the end of its life.
Definition: connection.h:85
@ FR_CONNECTION_FAILED
Connection is being reconnected because it failed.
Definition: connection.h:84
Holds a complete set of functions for a connection.
Definition: connection.h:186
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition: dbuff.h:509
#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:208
#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:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
CONF_SECTION *_CONST conf
Module's instance configuration.
Definition: dl_module.h:166
dl_module_inst_t const *_CONST parent
Parent module's instance (if any).
Definition: dl_module.h:167
#define fr_event_fd_insert(...)
Definition: event.h:232
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:137
@ FR_EVENT_FILTER_IO
Combined filter for read/write functions/.
Definition: event.h:62
#define fr_event_timer_at(...)
Definition: event.h:250
IPv4/6 prefix.
Definition: merged_model.c:272
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition: interpret.c:1340
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:821
#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_timer_delete(fr_event_timer_t const **ev_p)
Delete a timer event from the event list.
Definition: event.c:1604
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:1253
Stores all information relating to an event list.
Definition: event.c:411
A timer event.
Definition: event.c:102
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition: log.h:71
unsigned short uint16_t
Definition: merged_model.c:31
@ FR_TYPE_IPV4_ADDR
32 Bit IPv4 Address.
Definition: merged_model.c:86
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
Definition: merged_model.c:119
@ FR_TYPE_IPV6_ADDR
128 Bit IPv6 Address.
Definition: merged_model.c:88
@ FR_TYPE_COMBO_IP_ADDR
IPv4 or IPv6 address depending on length.
Definition: merged_model.c:91
unsigned int uint32_t
Definition: merged_model.c:33
long int ssize_t
Definition: merged_model.c:24
unsigned char uint8_t
Definition: merged_model.c:30
unsigned long int size_t
Definition: merged_model.c:25
#define UINT8_MAX
Definition: merged_model.c:32
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:63
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
void * thread
Thread instance data.
Definition: module_ctx.h:62
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:59
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:51
Temporary structure to hold arguments for thread_instantiation calls.
Definition: module_ctx.h:58
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:158
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:688
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:278
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:1918
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:1340
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition: pair.c:46
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:765
char * fr_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Special version of asprintf which implements custom format specifiers.
Definition: print.c:876
char const * fr_tacacs_packet_names[FR_TACACS_CODE_MAX]
Definition: base.c:119
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
static char * secret
Definition: radclient-ng.c:69
#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:106
#define RETURN_MODULE_RCODE(_rcode)
Definition: rcode.h:64
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:43
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition: rcode.h:42
fr_time_delta_t revive_interval
Definition: rlm_radius.h:49
fr_retry_config_t retry[FR_RADIUS_CODE_MAX]
Definition: rlm_radius.h:67
char const * name
Definition: rlm_radius.h:43
fr_time_delta_t response_window
Definition: rlm_radius.h:47
fr_time_delta_t zombie_period
Definition: rlm_radius.h:48
uint32_t recv_buff
How big the kernel's receive buffer should be.
uint16_t dst_port
Port of the home server.
fr_ipaddr_t dst_ipaddr
IP of the home server.
uint32_t send_buff
How big the kernel's send buffer should be.
bool send_buff_is_set
Whether we were provided with a send_buf.
fr_ipaddr_t src_ipaddr
IP we open our socket on.
rlm_radius_t * parent
rlm_radius instance.
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.
static char const * name
module_t common
Common fields to all loadable modules.
Definition: rlm_tacacs.h:74
Public structure describing an I/O path for an outgoing socket.
Definition: rlm_tacacs.h:73
static void request_conn_release(fr_connection_t *conn, void *preq_to_reset, UNUSED void *uctx)
Clear out anything associated with the handle from the request.
fr_time_t last_reply
When we last received a reply.
static int8_t request_prioritise(void const *one, void const *two)
uint16_t src_port
Source port specific to this connection.
rlm_rcode_t rcode
from the transport
uint8_t * write
where we write data to
static fr_dict_attr_t const * attr_packet_type
fr_time_t first_sent
first time we sent a packet since going idle
fr_event_list_t * el
Event list.
CONF_SECTION * config
bool send_buff_is_set
Whether we were provided with a send_buf.
static int _udp_result_free(udp_result_t *r)
Free a udp_result_t.
size_t secretlen
length of secret
static fr_dict_attr_t const * attr_session_id
fr_trunk_t * trunk
trunk handler
char const * interface
Interface to bind to.
uint8_t * data
actual data
fr_time_t last_idle
last time we had nothing to do
size_t packet_len
Length of the packet.
static fr_dict_attr_t const * attr_packet_hdr
static void request_mux(fr_event_list_t *el, fr_trunk_connection_t *tconn, fr_connection_t *conn, UNUSED void *uctx)
static void udp_request_reset(udp_handle_t *h, udp_request_t *u)
Clear out any connection specific resources from a udp request.
fr_trunk_conf_t * trunk_conf
trunk configuration
rlm_tacacs_tcp_t const * inst
our instance
fr_dict_autoload_t rlm_tacacs_tcp_dict[]
fr_trunk_connection_t * tconn
trunk connection
uint8_t id
Last ID assigned to this packet.
fr_ipaddr_t src_ipaddr
Source IP address.
static void conn_error(UNUSED fr_event_list_t *el, UNUSED int fd, UNUSED int flags, int fd_errno, void *uctx)
Connection errored.
static ssize_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 *data, size_t data_len)
Decode response packet data, extracting relevant information and validating the packet.
static void mod_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
static void conn_close(UNUSED fr_event_list_t *el, void *handle, UNUSED void *uctx)
Shutdown/close a file descriptor.
char const * secret
Shared secret.
uint32_t session_id
for TACACS+ "security".
uint16_t dst_port
Port of the home server.
fr_time_t recv_time
copied from request->async->recv_time
static fr_dict_t const * dict_tacacs
fr_event_timer_t const * ev
timer for retransmissions
uint32_t send_buff
How big the kernel's send buffer should be.
int id
starts at 1.
static fr_connection_state_t conn_init(void **h_out, fr_connection_t *conn, void *uctx)
Initialise a new outbound connection.
uint8_t * end
end of the buffer
fr_time_t last_sent
last time we sent a packet.
static void request_free(UNUSED request_t *request, void *preq_to_free, UNUSED void *uctx)
Explicitly free resources associated with the protocol request.
rlm_radius_udp_t const * inst
our instance
static void zombie_timeout(fr_event_list_t *el, fr_time_t now, void *uctx)
Mark a connection dead after "zombie_interval".
uint32_t recv_buff
How big the kernel's receive buffer should be.
size_t send_buff_actual
What we believe the maximum SO_SNDBUF size to be.
struct udp_request_s udp_request_t
rlm_tacacs_io_t rlm_tacacs_tcp
fr_trunk_request_t ** coalesced
Outbound coalesced requests.
int fd
File descriptor.
tcp_buffer_t recv
receive buffer
fr_ipaddr_t src_ipaddr
IP we open our socket on.
uint8_t * packet
Packet we write to the network.
static void revive_timeout(UNUSED fr_event_list_t *el, UNUSED fr_time_t now, void *uctx)
Revive a connection after "revive_interval".
char const * name
From IP PORT to IP PORT.
uint32_t max_packet_size
Our max packet size. may be different from the parent.
static void request_cancel(fr_connection_t *conn, void *preq_to_reset, fr_trunk_cancel_reason_t reason, UNUSED void *uctx)
Remove the request from any tracking structures.
udp_coalesced_t * coalesced
Outbound coalesced requests.
static unlang_action_t mod_enqueue(rlm_rcode_t *p_result, void **rctx_out, UNUSED void *instance, void *thread, request_t *request)
bool recv_buff_is_set
Whether we were provided with a recv_buf.
static void thread_conn_notify(fr_trunk_connection_t *tconn, fr_connection_t *conn, fr_event_list_t *el, fr_trunk_connection_event_t notify_on, UNUSED void *uctx)
uint32_t max_packet_size
Maximum packet size.
fr_time_t mrs_time
Most recent sent time which had a reply.
rlm_radius_udp_t const * inst
Our module instance.
uint8_t code
Packet code.
static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
Instantiate thread data for the submodule.
static fr_connection_t * thread_conn_alloc(fr_trunk_connection_t *tconn, fr_event_list_t *el, fr_connection_conf_t const *conf, char const *log_prefix, void *uctx)
static void request_demux(UNUSED fr_event_list_t *el, fr_trunk_connection_t *tconn, fr_connection_t *conn, UNUSED void *uctx)
static unlang_action_t mod_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, UNUSED request_t *request)
Resume execution of the request, returning the rcode set during trunk execution.
fr_event_timer_t const * zombie_ev
Zombie timeout.
rlm_tacacs_tcp_t const * inst
Our module instance.
static void request_retry(fr_event_list_t *el, fr_time_t now, void *uctx)
Handle retries.
uint8_t * read
where we read data from
tcp_buffer_t send
send buffer
static void request_fail(request_t *request, NDEBUG_UNUSED void *preq, void *rctx, NDEBUG_UNUSED fr_trunk_request_state_t state, UNUSED void *uctx)
Write out a canned failure.
static int _udp_handle_free(udp_handle_t *h)
Free a connection handle, closing associated resources.
int active
active packets
rlm_tacacs_t * parent
rlm_tacacs instance.
char const * module_name
the module that opened the connection
fr_dict_attr_autoload_t rlm_tacacs_tcp_dict_attr[]
static const conf_parser_t module_config[]
static int encode(udp_handle_t *h, request_t *request, udp_request_t *u)
fr_trunk_request_t * treq
udp_thread_t * thread
bool outstanding
are we waiting for a reply?
fr_retry_t retry
retransmission timers
fr_ipaddr_t dst_ipaddr
IP of the home server.
static int mod_instantiate(module_inst_ctx_t const *mctx)
static bool check_for_zombie(fr_event_list_t *el, fr_trunk_connection_t *tconn, fr_time_t now, fr_time_t last_sent)
See if the connection is zombied.
uint32_t priority
copied from request->async->priority
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.
fr_trunk_request_t * tracking[UINT8_MAX]
all sequential!
uint16_t max_send_coalesce
Maximum number of packets to coalesce into one mmsg call.
Static configuration for the module.
Track the handle, which is tightly correlated with the FD.
Connect request_t to local tracking structure.
int fr_connection_signal_on_fd(fr_connection_t *conn, int fd)
Setup the connection to change states to connected or failed based on I/O events.
Definition: connection.c:1400
fr_connection_t * fr_connection_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, fr_connection_funcs_t const *funcs, fr_connection_conf_t const *conf, char const *log_prefix, void const *uctx)
Allocate a new connection.
Definition: connection.c:1507
void fr_connection_signal_reconnect(fr_connection_t *conn, fr_connection_reason_t reason)
Asynchronously signal the connection should be reconnected.
Definition: connection.c:1166
fr_signal_t
Definition: signal.h:48
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:729
RETURN_MODULE_FAIL
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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:321
#define FR_MAX_PACKET_SIZE
Definition: tacacs.h:27
static int talloc_const_free(void const *ptr)
Free const'd memory.
Definition: talloc.h:212
#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:288
#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
void fr_trunk_request_free(fr_trunk_request_t **treq_to_free)
If the trunk request is freed then update the target requests.
Definition: trunk.c:2217
void fr_trunk_request_signal_cancel(fr_trunk_request_t *treq)
Cancel a trunk request.
Definition: trunk.c:2047
fr_trunk_request_t * fr_trunk_request_alloc(fr_trunk_t *trunk, request_t *request)
(Pre-)Allocate a new trunk request
Definition: trunk.c:2369
void fr_trunk_request_signal_sent(fr_trunk_request_t *treq)
Signal that the request was written to a connection successfully.
Definition: trunk.c:1973
fr_trunk_enqueue_t fr_trunk_request_enqueue(fr_trunk_request_t **treq_out, fr_trunk_t *trunk, request_t *request, void *preq, void *rctx)
Enqueue a request that needs data written to the trunk.
Definition: trunk.c:2481
uint64_t fr_trunk_connection_requests_requeue(fr_trunk_connection_t *tconn, int states, uint64_t max, bool fail_bound)
Move requests off of a connection and requeue elsewhere.
Definition: trunk.c:1933
void fr_trunk_request_signal_complete(fr_trunk_request_t *treq)
Signal that a trunk request is complete.
Definition: trunk.c:1995
fr_trunk_enqueue_t fr_trunk_request_requeue(fr_trunk_request_t *treq)
Re-enqueue a request on the same connection.
Definition: trunk.c:2568
void fr_trunk_request_signal_partial(fr_trunk_request_t *treq)
Signal a partial write.
Definition: trunk.c:1952
fr_trunk_t * fr_trunk_alloc(TALLOC_CTX *ctx, fr_event_list_t *el, fr_trunk_io_funcs_t const *funcs, fr_trunk_conf_t const *conf, char const *log_prefix, void const *uctx, bool delay_start)
Allocate a new collection of connections.
Definition: trunk.c:4767
int fr_trunk_connection_pop_request(fr_trunk_request_t **treq_out, fr_trunk_connection_t *tconn)
Pop a request off a connection's pending queue.
Definition: trunk.c:3756
void fr_trunk_connection_signal_inactive(fr_trunk_connection_t *tconn)
Signal a trunk connection cannot accept more requests.
Definition: trunk.c:3810
void fr_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:3887
void fr_trunk_request_signal_fail(fr_trunk_request_t *treq)
Signal that a trunk request failed.
Definition: trunk.c:2027
void fr_trunk_connection_signal_reconnect(fr_trunk_connection_t *tconn, fr_connection_reason_t reason)
Signal a trunk connection is no longer viable.
Definition: trunk.c:3872
void fr_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:3904
Associates request queues with a connection.
Definition: trunk.c:127
Wraps a normal request.
Definition: trunk.c:97
Main trunk management handle.
Definition: trunk.c:189
fr_trunk_cancel_reason_t
Reasons for a request being cancelled.
Definition: trunk.h:55
@ FR_TRUNK_CANCEL_REASON_REQUEUE
A previously sent request is being requeued.
Definition: trunk.h:59
fr_trunk_request_state_t
Used for sanity checks and to simplify freeing.
Definition: trunk.h:161
@ FR_TRUNK_REQUEST_STATE_SENT
Was written to a socket. Waiting for a response.
Definition: trunk.h:172
@ FR_TRUNK_REQUEST_STATE_PENDING
In the queue of a connection and is pending writing.
Definition: trunk.h:168
@ FR_TRUNK_REQUEST_STATE_PARTIAL
Some of the request was written to the socket, more of it should be written later.
Definition: trunk.h:170
@ FR_TRUNK_REQUEST_STATE_INIT
Initial state.
Definition: trunk.h:162
fr_trunk_connection_alloc_t connection_alloc
Allocate a new fr_connection_t.
Definition: trunk.h:712
#define FR_TRUNK_REQUEST_STATE_ALL
All request states.
Definition: trunk.h:185
fr_trunk_connection_event_t
What type of I/O events the trunk connection is currently interested in receiving.
Definition: trunk.h:72
@ FR_TRUNK_CONN_EVENT_NONE
Don't notify the trunk on connection state changes.
Definition: trunk.h:73
@ FR_TRUNK_CONN_EVENT_WRITE
Trunk should be notified if a connection is writable.
Definition: trunk.h:77
@ FR_TRUNK_CONN_EVENT_READ
Trunk should be notified if a connection is readable.
Definition: trunk.h:75
@ FR_TRUNK_CONN_EVENT_BOTH
Trunk should be notified if a connection is readable or writable.
Definition: trunk.h:79
fr_trunk_enqueue_t
Definition: trunk.h:148
@ FR_TRUNK_ENQUEUE_IN_BACKLOG
Request should be enqueued in backlog.
Definition: trunk.h:149
Common configuration parameters for a trunk.
Definition: trunk.h:213
I/O functions to pass to fr_trunk_alloc.
Definition: trunk.h:711
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.
Definition: pair_inline.c:140
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
Definition: pair_inline.c:113
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.
Definition: pair_inline.c:182
static fr_slen_t parent
Definition: pair.h:844
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
Initialize a retransmission counter.
Definition: retry.c:84
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:43
fr_time_delta_t rt
retransmit interval
Definition: retry.h:46
uint32_t mrc
Maximum retransmission count.
Definition: retry.h:36
fr_retry_config_t const * config
master configuration
Definition: retry.h:42
@ FR_RETRY_MRC
reached maximum retransmission count
Definition: retry.h:57
@ FR_RETRY_CONTINUE
Definition: retry.h:56
@ FR_RETRY_MRD
reached maximum retransmission duration
Definition: retry.h:58
uint32_t count
number of sent packets
Definition: retry.h:47
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:44
#define fr_box_ipaddr(_val)
Definition: value.h:287
static fr_slen_t data
Definition: value.h:1259
#define fr_box_time_delta(_val)
Definition: value.h:336