The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
state.c
Go to the documentation of this file.
1 /*
2  * This program 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
5  * (at 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: 4c58c4d3decae617b083f35e8b5722a8bd4d81e1 $
19  *
20  * @brief Multi-packet state handling
21  * @file src/lib/server/state.c
22  *
23  * @ingroup AVP
24  *
25  * For each round of a multi-round authentication method such as EAP,
26  * or a 2FA method such as OTP, a state entry will be created. The state
27  * entry holds data that should be available during the complete lifecycle
28  * of the authentication attempt.
29  *
30  * When a request is complete, #fr_request_to_state is called to transfer
31  * ownership of the state fr_pair_ts and state_ctx (which the fr_pair_ts
32  * are allocated in) to a #fr_state_entry_t. This #fr_state_entry_t holds the
33  * value of the State attribute, that will be send out in the response.
34  *
35  * When the next request is received, #fr_state_to_request is called to transfer
36  * the fr_pair_ts and state ctx to the new request.
37  *
38  * The ownership of the state_ctx and state fr_pair_ts is transferred as below:
39  *
40  * @verbatim
41  request -> state_entry -> request -> state_entry -> request -> free()
42  \-> reply \-> reply \-> access-reject/access-accept
43  * @endverbatim
44  *
45  * @copyright 2014 The FreeRADIUS server project
46  */
47 RCSID("$Id: 4c58c4d3decae617b083f35e8b5722a8bd4d81e1 $")
48 
49 #include <freeradius-devel/server/request.h>
50 #include <freeradius-devel/server/request_data.h>
51 #include <freeradius-devel/server/state.h>
52 
53 #include <freeradius-devel/io/listen.h>
54 
55 #include <freeradius-devel/util/debug.h>
56 #include <freeradius-devel/util/dlist.h>
57 #include <freeradius-devel/util/md5.h>
58 #include <freeradius-devel/util/misc.h>
59 #include <freeradius-devel/util/rand.h>
60 
61 /** Holds a state value, and associated fr_pair_ts and data
62  *
63  */
64 typedef struct {
65  uint64_t id; //!< State number within state heap.
66  fr_rb_node_t node; //!< Entry in the state rbtree.
67  union {
68  /** Server ID components
69  *
70  * State values should be unique to a given server
71  */
72  struct state_comp {
73  uint8_t tries; //!< Number of rounds so far in this state sequence.
74  uint8_t tx; //!< Bits changed in the tries counter for this round.
75  uint8_t r_0; //!< Random component.
76  uint8_t server_id; //!< Configured server ID. Used for debugging
77  //!< to locate authentication sessions originating
78  //!< from a particular backend authentication server.
79 
80  uint32_t context_id; //!< Hash of the current virtual server, xor'd with
81  //!< r1, r2, r3, r4 after the original state value
82  //!< is sent, but before the state entry is inserted
83  //!< into the tree. The receiving virtual server
84  //!< xor's its hash with the received state before
85  //!< performing the lookup. This means one virtual
86  //!< server can't act on a state entry generated by
87  //!< another, even though the state tree is global
88  //!< to all virtual servers.
89 
90  uint8_t vx_0; //!< Random component.
91  uint8_t r_5; //!< Random component.
92  uint8_t vx_1; //!< Random component.
93  uint8_t r_6; //!< Random component.
94 
95  uint8_t vx_2; //!< Random component.
96  uint8_t vx_3; //!< Random component.
97  uint8_t r_8; //!< Random component.
98  uint8_t r_9; //!< Random component.
99  } state_comp;
100 
101  uint8_t state[sizeof(struct state_comp)]; //!< State value in binary.
102  };
103 
104  uint64_t seq_start; //!< Number of first request in this sequence.
105  fr_time_t cleanup; //!< When this entry should be cleaned up.
106 
107  /*
108  * Should only even be in one at a time
109  */
110  union {
111  fr_dlist_t expire_entry; //!< Entry in the list of things to expire.
112  fr_dlist_t free_entry; //!< Entry in the list of things to free.
113  };
114 
115  int tries;
116 
117  fr_pair_t *ctx; //!< for all session specific data.
118 
119  fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
120 
121  request_t *thawed; //!< The request that thawed this entry.
122 
123  fr_state_tree_t *state_tree; //!< Tree this entry belongs to.
125 
126 /** A child of a fr_state_entry_t
127  *
128  * Children are tracked using the request data of parents.
129  *
130  * request data is added with identifiers that uniquely identify the
131  * subrequest it should be restored to.
132  *
133  * In this way a top level fr_state_entry_t can hold the session
134  * information for multiple children, and the children may hold
135  * state_child_entry_ts for grandchildren.
136  */
137 typedef struct {
138  fr_pair_t *ctx; //!< for all session specific data.
139 
140  fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
141 
142  request_t *thawed; //!< The request that thawed this entry.
144 
146  uint64_t id; //!< Next ID to assign.
147  uint64_t timed_out; //!< Number of states that were cleaned up due to
148  //!< timeout.
149  uint32_t max_sessions; //!< Maximum number of sessions we track.
150  uint32_t used_sessions; //!< How many sessions are currently in progress.
151  fr_rb_tree_t *tree; //!< rbtree used to lookup state value.
152  fr_dlist_head_t to_expire; //!< Linked list of entries to free.
153 
154  fr_time_delta_t timeout; //!< How long to wait before cleaning up state entries.
155 
156  bool thread_safe; //!< Whether we lock the tree whilst modifying it.
157  pthread_mutex_t mutex; //!< Synchronisation mutex.
158 
159  uint8_t server_id; //!< ID to use for load balancing.
160  uint32_t context_id; //!< ID binding state values to a context such
161  ///< as a virtual server.
162 
163  fr_dict_attr_t const *da; //!< State attribute used.
164 };
165 
166 #define PTHREAD_MUTEX_LOCK if (state->thread_safe) pthread_mutex_lock
167 #define PTHREAD_MUTEX_UNLOCK if (state->thread_safe) pthread_mutex_unlock
168 
169 static void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry);
170 
171 /** Compare two fr_state_entry_t based on their state value i.e. the value of the attribute
172  *
173  */
174 static int8_t state_entry_cmp(void const *one, void const *two)
175 {
176  fr_state_entry_t const *a = one, *b = two;
177  int ret;
178 
179  ret = memcmp(a->state, b->state, sizeof(a->state));
180  return CMP(ret, 0);
181 }
182 
183 /** Free the state tree
184  *
185  */
187 {
188  fr_state_entry_t *entry;
189 
190  if (state->thread_safe) pthread_mutex_destroy(&state->mutex);
191 
192  DEBUG4("Freeing state tree %p", state);
193 
194  while ((entry = fr_dlist_head(&state->to_expire))) {
195  DEBUG4("Freeing state entry %p (%"PRIu64")", entry, entry->id);
196  state_entry_unlink(state, entry);
197  talloc_free(entry);
198  }
199 
200  /*
201  * Free the rbtree
202  */
203  talloc_free(state->tree);
204 
205  return 0;
206 }
207 
208 /** Initialise a new state tree
209  *
210  * @param[in] ctx to link the lifecycle of the state tree to.
211  * @param[in] da Attribute used to store and retrieve state from.
212  * @param[in] thread_safe Whether we should mutex protect the state tree.
213  * @param[in] max_sessions we track state for.
214  * @param[in] timeout How long to wait before cleaning up entries.
215  * @param[in] server_id ID byte to use in load-balancing operations.
216  * @param[in] context_id Specifies a unique ctx id to prevent states being
217  * used in contexts for which they weren't intended.
218  * @return
219  * - A new state tree.
220  * - NULL on failure.
221  */
222 fr_state_tree_t *fr_state_tree_init(TALLOC_CTX *ctx, fr_dict_attr_t const *da, bool thread_safe,
223  uint32_t max_sessions, fr_time_delta_t timeout,
224  uint8_t server_id, uint32_t context_id)
225 {
226  fr_state_tree_t *state;
227 
228  state = talloc_zero(NULL, fr_state_tree_t);
229  if (!state) return 0;
230 
231  state->max_sessions = max_sessions;
232  state->timeout = timeout;
233 
234  /*
235  * Create a break in the contexts.
236  * We still want this to be freed at the same time
237  * as the parent, but we also need it to be thread
238  * safe, and multiple threads could be using the
239  * tree.
240  */
241  talloc_link_ctx(ctx, state);
242 
243  if (thread_safe && (pthread_mutex_init(&state->mutex, NULL) != 0)) {
244  talloc_free(state);
245  return NULL;
246  }
247 
248  fr_dlist_talloc_init(&state->to_expire, fr_state_entry_t, free_entry);
249 
250  /*
251  * We need to do controlled freeing of the
252  * rbtree, so that all the state entries
253  * are freed before it's destroyed. Hence
254  * it being parented from the NULL ctx.
255  */
256  state->tree = fr_rb_inline_talloc_alloc(NULL, fr_state_entry_t, node, state_entry_cmp, NULL);
257  if (!state->tree) {
258  talloc_free(state);
259  return NULL;
260  }
261  talloc_set_destructor(state, _state_tree_free);
262 
263  state->da = da; /* Remember which attribute we use to load/store state */
264  state->server_id = server_id;
265  state->context_id = context_id;
266  state->thread_safe = thread_safe;
267 
268  return state;
269 }
270 
271 /** Unlink an entry and remove if from the tree
272  *
273  */
274 static inline CC_HINT(always_inline)
276 {
277  /*
278  * Check the memory is still valid
279  */
280  (void) talloc_get_type_abort(entry, fr_state_entry_t);
281 
282  fr_dlist_remove(&state->to_expire, entry);
283  fr_rb_delete(state->tree, entry);
284 
285  DEBUG4("State ID %" PRIu64 " unlinked", entry->id);
286 }
287 
288 /** Frees any data associated with a state
289  *
290  */
292 {
293 #ifdef WITH_VERIFY_PTR
294  fr_dcursor_t cursor;
295  fr_pair_t *vp;
296 
297  /*
298  * Verify all state attributes are parented
299  * by the state context.
300  */
301  if (entry->ctx) {
302  for (vp = fr_pair_dcursor_init(&cursor, &entry->ctx->children);
303  vp;
304  vp = fr_dcursor_next(&cursor)) {
305  fr_assert(entry->ctx == talloc_parent(vp));
306  }
307  }
308 
309  /*
310  * Ensure any request data is parented by us
311  * so we know it'll be cleaned up.
312  */
313  (void)fr_cond_assert(request_data_verify_parent(entry->ctx, &entry->data));
314 #endif
315 
316  /*
317  * Should also free any state attributes
318  */
319  if (entry->ctx) TALLOC_FREE(entry->ctx);
320 
321  DEBUG4("State ID %" PRIu64 " freed", entry->id);
322 
323  entry->state_tree->used_sessions--;
324 
325  return 0;
326 }
327 
328 /** Create a new state entry
329  *
330  * @note Called with the mutex held.
331  */
333  fr_pair_list_t *reply_list, fr_state_entry_t *old)
334 {
335  size_t i;
336  uint32_t x;
337  fr_time_t now = fr_time();
338  fr_pair_t *vp;
339  fr_state_entry_t *entry, *next;
340 
341  uint8_t old_state[sizeof(old->state)];
342  int old_tries = 0;
343  uint64_t timed_out = 0;
344  bool too_many = false;
345  fr_dlist_head_t to_free;
346 
347  /*
348  * Shouldn't be in any lists if it's being reused
349  */
350  fr_assert(!old ||
351  (!fr_dlist_entry_in_list(&old->expire_entry) &&
353 
354  fr_dlist_init(&to_free, fr_state_entry_t, free_entry);
355 
356  /*
357  * Clean up expired entries
358  */
359  for (entry = fr_dlist_head(&state->to_expire);
360  entry != NULL;
361  entry = next) {
362  (void)talloc_get_type_abort(entry, fr_state_entry_t); /* Allow examination */
363  next = fr_dlist_next(&state->to_expire, entry); /* Advance *before* potential unlinking */
364 
365  if (entry == old) continue;
366 
367  /*
368  * Too old, we can delete it.
369  */
370  if (fr_time_lt(entry->cleanup, now)) {
371  state_entry_unlink(state, entry);
372  fr_dlist_insert_tail(&to_free, entry);
373  timed_out++;
374  continue;
375  }
376 
377  break;
378  }
379 
380  state->timed_out += timed_out;
381 
382  if (!old) {
383  too_many = (state->used_sessions == (uint32_t) state->max_sessions);
384  if (!too_many) state->used_sessions++; /* preemptively increment whilst we hold the mutex */
385  } else {
386  old_tries = old->tries;
387  memcpy(old_state, old->state, sizeof(old_state));
388  }
389 
390  PTHREAD_MUTEX_UNLOCK(&state->mutex);
391 
392  if (timed_out > 0) RWDEBUG("Cleaning up %"PRIu64" timed out state entries", timed_out);
393 
394  /*
395  * Now free the unlinked entries.
396  *
397  * We do it here as freeing may involve significantly more
398  * work than just freeing the data.
399  *
400  * If there's request data that was persisted it will now
401  * be freed also, and it may have complex destructors associated
402  * with it.
403  */
404  while ((entry = fr_dlist_head(&to_free)) != NULL) {
405  fr_dlist_remove(&to_free, entry);
406  talloc_free(entry);
407  }
408 
409  /*
410  * Have to do this post-cleanup, else we end up returning with
411  * a list full of entries to free with none of them being
412  * freed which is bad...
413  */
414  if (too_many) {
415  RERROR("Failed inserting state entry - At maximum ongoing session limit (%u)",
416  state->max_sessions);
417  PTHREAD_MUTEX_LOCK(&state->mutex); /* Caller expects this to be locked */
418  return NULL;
419  }
420 
421  /*
422  * Allocation doesn't need to occur inside the critical region
423  * and would add significantly to contention.
424  */
425  if (!old) {
426  MEM(entry = talloc_zero(NULL, fr_state_entry_t));
427  talloc_set_destructor(entry, _state_entry_free);
428  /* tree->used_sessions incremented above */
429  /*
430  * Reuse the old state entry cleaning up any memory associated
431  * with it.
432  */
433  } else {
434  _state_entry_free(old);
435  talloc_free_children(old);
436  memset(old, 0, sizeof(*old));
437  entry = old;
438  }
439 
440  entry->state_tree = state;
441 
442  request_data_list_init(&entry->data);
443 
444  entry->id = state->id++;
445 
446  /*
447  * Limit the lifetime of this entry based on how long the
448  * server takes to process a request. Doing it this way
449  * isn't perfect, but it's reasonable, and it's one less
450  * thing for an administrator to configure.
451  */
452  entry->cleanup = fr_time_add(now, state->timeout);
453 
454  /*
455  * Some modules create their own magic
456  * state attributes. If a state value already exists
457  * int the reply, we use that in preference to the
458  * old state.
459  */
460  vp = fr_pair_find_by_da(reply_list, NULL, state->da);
461  if (vp) {
462  if (DEBUG_ENABLED && (vp->vp_length > sizeof(entry->state))) {
463  WARN("State too long, will be truncated. Expected <= %zd bytes, got %zu bytes",
464  sizeof(entry->state), vp->vp_length);
465  }
466 
467  /*
468  * Assume our own State first.
469  */
470  if (vp->vp_length == sizeof(entry->state)) {
471  memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
472 
473  /*
474  * Too big? Get the MD5 hash, in order
475  * to depend on the entire contents of State.
476  */
477  } else if (vp->vp_length > sizeof(entry->state)) {
478  fr_md5_calc(entry->state, vp->vp_octets, vp->vp_length);
479 
480  /*
481  * Too small? Use the whole thing, and
482  * set the rest of my_entry.state to zero.
483  */
484  } else {
485  memcpy(entry->state, vp->vp_octets, vp->vp_length);
486  memset(&entry->state[vp->vp_length], 0, sizeof(entry->state) - vp->vp_length);
487  }
488  } else {
489  /*
490  * Base the new state on the old state if we had one.
491  */
492  if (old) {
493  memcpy(entry->state, old_state, sizeof(entry->state));
494  entry->tries = old_tries + 1;
495  /*
496  * 16 octets of randomness should be enough to
497  * have a globally unique state.
498  */
499  } else {
500  for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
501  x = fr_rand();
502  memcpy(entry->state + (i * 4), &x, sizeof(x));
503  }
504  }
505 
506  entry->state_comp.tries = entry->tries + 1;
507 
508  entry->state_comp.tx = entry->state_comp.tries ^ entry->tries;
509 
510  entry->state_comp.vx_0 = entry->state_comp.r_0 ^
511  ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 24) & 0xff);
512  entry->state_comp.vx_1 = entry->state_comp.r_0 ^
513  ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff);
514  entry->state_comp.vx_2 = entry->state_comp.r_0 ^
515  ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff);
516  entry->state_comp.vx_3 = entry->state_comp.r_0 ^
517  (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff);
518 
519  /*
520  * Allow a portion of the State attribute to be set,
521  * this is useful for debugging purposes.
522  */
523  entry->state_comp.server_id = state->server_id;
524 
525  MEM(vp = fr_pair_afrom_da(request->reply_ctx, state->da));
526  fr_pair_value_memdup(vp, entry->state, sizeof(entry->state), false);
527  fr_pair_append(reply_list, vp);
528  }
529 
530  DEBUG4("State ID %" PRIu64 " created, value 0x%pH, expires %pV",
531  entry->id, fr_box_octets(entry->state, sizeof(entry->state)),
532  fr_box_time_delta(fr_time_sub(entry->cleanup, now)));
533 
534  PTHREAD_MUTEX_LOCK(&state->mutex);
535 
536  /*
537  * XOR the server hash with four bytes of random data.
538  * We XOR is again before resolving, to ensure state lookups
539  * only succeed in the virtual server that created the state
540  * value.
541  */
542  *((uint32_t *)(&entry->state_comp.context_id)) ^= state->context_id;
543 
544  if (!fr_rb_insert(state->tree, entry)) {
545  RERROR("Failed inserting state entry - Insertion into state tree failed");
546  fr_pair_delete_by_da(reply_list, state->da);
547  talloc_free(entry);
548  return NULL;
549  }
550 
551  /*
552  * Link it to the end of the list, which is implicitly
553  * ordered by cleanup time.
554  */
555  fr_dlist_insert_tail(&state->to_expire, entry);
556 
557  return entry;
558 }
559 
560 /** Find the entry based on the State attribute and remove it from the state tree
561  *
562  */
564 {
565  fr_state_entry_t *entry, my_entry;
566 
567  /*
568  * Assume our own State first.
569  */
570  if (vb->vb_length == sizeof(my_entry.state)) {
571  memcpy(my_entry.state, vb->vb_octets, sizeof(my_entry.state));
572 
573  /*
574  * Too big? Get the MD5 hash, in order
575  * to depend on the entire contents of State.
576  */
577  } else if (vb->vb_length > sizeof(my_entry.state)) {
578  fr_md5_calc(my_entry.state, vb->vb_octets, vb->vb_length);
579 
580  /*
581  * Too small? Use the whole thing, and
582  * set the rest of my_entry.state to zero.
583  */
584  } else {
585  memcpy(my_entry.state, vb->vb_octets, vb->vb_length);
586  memset(&my_entry.state[vb->vb_length], 0, sizeof(my_entry.state) - vb->vb_length);
587  }
588 
589  /*
590  * Make it unique for different virtual servers handling the same request
591  */
592  my_entry.state_comp.context_id ^= state->context_id;
593 
594  entry = fr_rb_remove(state->tree, &my_entry);
595  if (entry) {
596  (void) talloc_get_type_abort(entry, fr_state_entry_t);
597  fr_dlist_remove(&state->to_expire, entry);
598  }
599 
600  return entry;
601 }
602 
603 /** Called when sending an Access-Accept/Access-Reject to discard state information
604  *
605  */
607 {
608  fr_state_entry_t *entry;
609  fr_pair_t *vp;
610 
611  vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da);
612  if (!vp) return;
613 
614  PTHREAD_MUTEX_LOCK(&state->mutex);
615  entry = state_entry_find_and_unlink(state, &vp->data);
616  if (!entry) {
617  PTHREAD_MUTEX_UNLOCK(&state->mutex);
618  return;
619  }
620  PTHREAD_MUTEX_UNLOCK(&state->mutex);
621 
622  /*
623  * If fr_state_to_request was never called, this ensures
624  * the state owned by entry is freed, otherwise this is
625  * mostly a NOOP, other than freeing the memory held by
626  * the entry.
627  */
628  TALLOC_FREE(entry);
629 
630  /*
631  * If fr_state_to_request was called, then the request
632  * holds the existing state data. We need to destroy it,
633  * and return the request to the state it was in when
634  * it was first allocated, just in case a user does something
635  * stupid like add more session-state attributes
636  * in one of the later sections.
637  */
638  talloc_free(request_state_replace(request, NULL));
639 
640  RDEBUG3("%s - discarded", state->da->name);
641 
642  return;
643 }
644 
645 /** Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request
646  *
647  * @note Does not copy the actual fr_pair_ts. The fr_pair_ts and their context
648  * are transferred between state entries as the conversation progresses.
649  *
650  * @note Called with the mutex free.
651  *
652  * @param[in] state tree to lookup state in.
653  * @param[in] request to restore state for.
654  * @return
655  * - 2 if the state attribute didn't match any known states.
656  * - 1 if no state attribute existed.
657  * - 0 on success (state restored)
658  * - -1 if a state entry has already been thawed by a another request.
659  */
661 {
662  fr_state_entry_t *entry;
663  fr_pair_t *vp;
664 
665  /*
666  * No State, don't do anything.
667  */
668  vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da);
669  if (!vp) {
670  RDEBUG3("No &request.%s attribute, can't restore &session-state", state->da->name);
671  if (request->seq_start == 0) request->seq_start = request->number; /* Need check for fake requests */
672  return 1;
673  }
674 
675  PTHREAD_MUTEX_LOCK(&state->mutex);
676  entry = state_entry_find_and_unlink(state, &vp->data);
677  if (!entry) {
678  PTHREAD_MUTEX_UNLOCK(&state->mutex);
679  RDEBUG2("No state entry matching &request.%pP found", vp);
680  return 2;
681  }
682  PTHREAD_MUTEX_UNLOCK(&state->mutex);
683 
684  /* Probably impossible in the current code */
685  if (unlikely(entry->thawed != NULL)) {
686  RERROR("State entry has already been thawed by a request %"PRIu64, entry->thawed->number);
687  return -2;
688  }
689 
690  /*
691  * Discard any existing session state, and replace it
692  * with the cached one.
693  */
694  fr_assert(entry->ctx);
695  talloc_free(request_state_replace(request, entry->ctx));
696  entry->ctx = NULL;
697 
698  request->seq_start = entry->seq_start;
699 
700  /*
701  * Associate old state with the request
702  *
703  * If the request is freed, it's freed immediately.
704  *
705  * Otherwise, if there's another round, we reuse
706  * the state entry and insert it back into the
707  * tree.
708  */
709  request_data_add(request, state, 0, entry, true, true, false);
710  request_data_restore(request, &entry->data);
711 
712  entry->thawed = request;
713 
714  if (!fr_pair_list_empty(&request->session_state_pairs)) {
715  RDEBUG2("Restored &session-state");
716  log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "&session-state.");
717  }
718 
719  RDEBUG3("%s - restored", state->da->name);
720 
721  /*
722  * Set sequence so that we can prioritize ongoing multi-packet sessions.
723  */
724  request->async->sequence = entry->tries;
725  REQUEST_VERIFY(request);
726  return 0;
727 }
728 
729 
730 /** Transfer ownership of the state fr_pair_ts and ctx, back to a state entry
731  *
732  * Put request->session_state_pairs into the State attribute. Put the State attribute
733  * into the vps list. Delete the original entry, if it exists
734  *
735  * Also creates a new state entry.
736  */
738 {
739  fr_state_entry_t *entry, *old;
741  fr_pair_t *state_ctx;
742 
743  old = request_data_get(request, state, 0);
745  request_data_by_persistance(&data, request, true);
746 
747  if (fr_pair_list_empty(&request->session_state_pairs) && fr_dlist_empty(&data)) return 0;
748 
749  if (!fr_pair_list_empty(&request->session_state_pairs)) {
750  RDEBUG2("Saving &session-state");
751  log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "&session-state.");
752 
753 #ifdef WITH_VERIFY_PTR
754  /*
755  * Double check all the session state pairs
756  * are parented correctly, else we'll get
757  * memory errors when we restore.
758  */
759  fr_pair_list_verify(__FILE__, __LINE__, request->session_state_ctx, &request->session_state_pairs);
760 #endif
761  }
762 
763  MEM(state_ctx = request_state_replace(request, NULL));
764  PTHREAD_MUTEX_LOCK(&state->mutex);
765 
766  /*
767  * Reuses old if possible
768  */
769  entry = state_entry_create(state, request, &request->reply_pairs, old);
770  if (!entry) {
771  PTHREAD_MUTEX_UNLOCK(&state->mutex);
772  RERROR("Creating state entry failed");
773 
774  talloc_free(request_state_replace(request, state_ctx));
775  request_data_restore(request, &data); /* Put it back again */
776  return -1;
777  }
778 
779  fr_assert(entry->ctx == NULL);
780  fr_assert(request->session_state_ctx);
781 
782  entry->seq_start = request->seq_start;
783  entry->ctx = state_ctx;
784  fr_dlist_move(&entry->data, &data);
785  PTHREAD_MUTEX_UNLOCK(&state->mutex);
786 
787  RDEBUG3("%s - saved", state->da->name);
788  REQUEST_VERIFY(request);
789 
790  return 0;
791 }
792 
793 /** Free any subrequest request data if the dlist head is freed
794  *
795  */
796 static int _free_child_data(state_child_entry_t *child_entry)
797 {
798  fr_dlist_talloc_free(&child_entry->data);
799  talloc_free(child_entry->ctx); /* Free the child's session_state_ctx if we own it */
800 
801  return 0;
802 }
803 
804 /** Store subrequest's session-state list and persistable request data in its parent
805  *
806  * @param[in] child The child request to retrieve state from.
807  * @param[in] unique_ptr A parent may have multiple subrequests spawned
808  * by different modules. This identifies the module
809  * or other facility that spawned the subrequest.
810  * @param[in] unique_int Further identification.
811  */
812 void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
813 {
814  state_child_entry_t *child_entry;
815  request_t *request = child; /* Stupid logging */
816 
817  if (!fr_cond_assert_msg(child->parent,
818  "Child request must have request->parent set when storing state")) return;
819 
820  RDEBUG3("Storing subrequest state in request %s", child->parent->name);
821 
822  if ((request_data_by_persistance_count(request, true) > 0) ||
823  !fr_pair_list_empty(&request->session_state_pairs)) {
824  MEM(child_entry = talloc_zero(request->parent->session_state_ctx, state_child_entry_t));
825  request_data_list_init(&child_entry->data);
826  talloc_set_destructor(child_entry, _free_child_data);
827 
828  child_entry->ctx = request_state_replace(child, NULL);
829 
830  /*
831  * Pull everything out of the child,
832  * add it to our temporary list head...
833  *
834  * request_data_add allocs persistable
835  * request dta in the session_state_ctx
836  * which is why we don't need to copy or
837  * reparent any of this.
838  */
839  request_data_by_persistance(&child_entry->data, request, true);
840 
841  /*
842  * ...and add the request_data from
843  * the child back into the parent.
844  */
845  request_data_talloc_add(request->parent, unique_ptr, unique_int,
846  state_child_entry_t, child_entry, true, false, true);
847  }
848 }
849 
850 /** Restore subrequest data from a parent request
851  *
852  * @param[in] child The child request to restore state to.
853  * @param[in] unique_ptr A parent may have multiple subrequests spawned
854  * by different modules. This identifies the module
855  * or other facility that spawned the subrequest.
856  * @param[in] unique_int Further identification.
857  */
858 void fr_state_restore_to_child(request_t *child, void const *unique_ptr, int unique_int)
859 {
860  state_child_entry_t *child_entry;
861  request_t *request = child; /* Stupid logging */
862 
863  if (!fr_cond_assert_msg(child->parent,
864  "Child request must have request->parent set when restoring state")) return;
865 
866 
867  child_entry = request_data_get(child->parent, unique_ptr, unique_int);
868  if (!child_entry) {
869  RDEBUG3("No child state found in parent %s", child->parent->name);
870  return;
871  }
872 
873  /*
874  * Shouldn't really be possible unless
875  * there's a logic bug in this API.
876  */
877  if (!fr_cond_assert_msg(!child_entry->thawed,
878  "Child state entry already thawed by %s - %p",
879  child_entry->thawed->name, child_entry->thawed)) return;
880 
881  RDEBUG3("Restoring subrequest state from request %s", child->parent->name);
882 
883  /*
884  * If we can restore from the parent, do so
885  */
886  fr_assert_msg(child_entry->ctx, "session child entry missing ctx");
887  talloc_free(request_state_replace(child, child_entry->ctx));
888  child_entry->ctx = NULL; /* No longer owns the ctx */
889  child_entry->thawed = child;
890 
891  request_data_restore(child, &child_entry->data); /* Put all the request data back */
892 
893  talloc_free(child_entry);
894 }
895 
896 /** Remove state from a child
897  *
898  * This is useful for modules like EAP, where we keep a persistent eap_session
899  * but may call multiple EAP method modules during negotiation, and need to
900  * discard the state between each module call.
901  *
902  * @param[in] parent Holding the child's state.
903  * @param[in] unique_ptr A parent may have multiple subrequests spawned
904  * by different modules. This identifies the module
905  * or other facility that spawned the subrequest.
906  * @param[in] unique_int Further identification.
907  */
908 void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
909 {
910  state_child_entry_t *child_entry;
911  request_t *request = parent; /* Stupid logging */
912 
913  child_entry = request_data_get(parent, unique_ptr, unique_int);
914  if (!child_entry) {
915  RDEBUG3("No child state found in parent %s", parent->name);
916  return;
917  }
918 
919  talloc_free(child_entry);
920 }
921 
922 /** Return number of entries created
923  *
924  */
926 {
927  return state->id;
928 }
929 
930 /** Return number of entries that timed out
931  *
932  */
934 {
935  return state->timed_out;
936 }
937 
938 /** Return number of entries we're currently tracking
939  *
940  */
942 {
943  return fr_rb_num_elements(state->tree);
944 }
#define HEXIFY(b1)
Definition: build.h:188
#define RCSID(id)
Definition: build.h:481
#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:379
next
Definition: dcursor.h:178
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition: dcursor.h:288
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:139
#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:210
#define fr_cond_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:156
static fr_time_delta_t timeout
Definition: dhcpclient.c:54
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition: dlist.h:260
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition: dlist.h:555
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
Definition: dlist.h:163
static void fr_dlist_talloc_free(fr_dlist_head_t *head)
Free all items in a doubly linked list (with talloc)
Definition: dlist.h:908
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition: dlist.h:501
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition: dlist.h:486
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition: dlist.h:378
static int fr_dlist_move(fr_dlist_head_t *list_dst, fr_dlist_head_t *list_src)
Merge two lists, inserting the source at the tail of the destination.
Definition: dlist.h:763
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition: dlist.h:638
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition: dlist.h:275
Head of a doubly linked list.
Definition: dlist.h:51
Entry in a doubly linked list.
Definition: dlist.h:41
if(rcode > 0)
Definition: fd_read.h:9
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:830
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define RERROR(fmt,...)
Definition: log.h:298
#define DEBUG4(_fmt,...)
Definition: log.h:267
#define DEBUG_ENABLED
True if global debug level 1 messages are enabled.
Definition: log.h:257
talloc_free(reap)
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition: log.h:71
unsigned int uint32_t
Definition: merged_model.c:33
void fr_md5_calc(uint8_t out[static MD5_DIGEST_LENGTH], uint8_t const *in, size_t inlen)
Perform a single digest operation on a single input buffer.
Definition: merged_model.c:251
unsigned char uint8_t
Definition: merged_model.c:30
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:693
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:283
int fr_pair_value_memdup(fr_pair_t *vp, uint8_t const *src, size_t len, bool tainted)
Copy data into an "octets" data type.
Definition: pair.c:2981
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
int fr_pair_delete_by_da(fr_pair_list_t *list, fr_dict_attr_t const *da)
Delete matching pairs from the specified list.
Definition: pair.c:1689
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define WARN(fmt,...)
Definition: radclient.h:47
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition: rand.c:106
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
Definition: rb.h:246
static bool fr_rb_node_inline_in_tree(fr_rb_node_t const *node)
Check to see if an item is in a tree by examining its inline fr_rb_node_t.
Definition: rb.h:314
void * fr_rb_remove(fr_rb_tree_t *tree, void const *data)
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
bool fr_rb_delete(fr_rb_tree_t *tree, void const *data)
The main red black tree structure.
Definition: rb.h:73
fr_pair_t * request_state_replace(request_t *request, fr_pair_t *new_state)
Replace the session_state_ctx with a new one.
Definition: request.c:637
#define REQUEST_VERIFY(_x)
Definition: request.h:276
int request_data_by_persistance_count(request_t *request, bool persist)
Return how many request data entries exist of a given persistence.
Definition: request_data.c:439
int request_data_by_persistance(fr_dlist_head_t *out, request_t *request, bool persist)
Loop over all the request data, pulling out ones matching persist state.
Definition: request_data.c:372
void * request_data_get(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request.
Definition: request_data.c:292
void request_data_list_init(fr_dlist_head_t *data)
Definition: request_data.c:92
void request_data_restore(request_t *request, fr_dlist_head_t *in)
Add request data back to a request.
Definition: request_data.c:461
#define request_data_talloc_add(_request, _unique_ptr, _unique_int, _type, _opaque, _free_on_replace, _free_on_parent, _persist)
Add opaque data to a request_t.
Definition: request_data.h:86
#define request_data_add(_request, _unique_ptr, _unique_int, _opaque, _free_on_replace, _free_on_parent, _persist)
Add opaque data to a request_t.
Definition: request_data.h:59
uint64_t id
State number within state heap.
Definition: state.c:65
void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
Remove state from a child.
Definition: state.c:908
uint64_t seq_start
Number of first request in this sequence.
Definition: state.c:104
fr_state_tree_t * fr_state_tree_init(TALLOC_CTX *ctx, fr_dict_attr_t const *da, bool thread_safe, uint32_t max_sessions, fr_time_delta_t timeout, uint8_t server_id, uint32_t context_id)
Initialise a new state tree.
Definition: state.c:222
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition: state.c:119
void fr_state_discard(fr_state_tree_t *state, request_t *request)
Called when sending an Access-Accept/Access-Reject to discard state information.
Definition: state.c:606
uint32_t max_sessions
Maximum number of sessions we track.
Definition: state.c:149
#define PTHREAD_MUTEX_UNLOCK
Definition: state.c:167
#define PTHREAD_MUTEX_LOCK
Definition: state.c:166
int fr_request_to_state(fr_state_tree_t *state, request_t *request)
Transfer ownership of the state fr_pair_ts and ctx, back to a state entry.
Definition: state.c:737
uint8_t server_id
ID to use for load balancing.
Definition: state.c:159
fr_dict_attr_t const * da
State attribute used.
Definition: state.c:163
int fr_state_to_request(fr_state_tree_t *state, request_t *request)
Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request.
Definition: state.c:660
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition: state.c:140
fr_rb_node_t node
Entry in the state rbtree.
Definition: state.c:66
request_t * thawed
The request that thawed this entry.
Definition: state.c:142
fr_time_t cleanup
When this entry should be cleaned up.
Definition: state.c:105
static int _state_tree_free(fr_state_tree_t *state)
Free the state tree.
Definition: state.c:186
static int _state_entry_free(fr_state_entry_t *entry)
Frees any data associated with a state.
Definition: state.c:291
static int8_t state_entry_cmp(void const *one, void const *two)
Compare two fr_state_entry_t based on their state value i.e.
Definition: state.c:174
fr_pair_t * ctx
for all session specific data.
Definition: state.c:117
pthread_mutex_t mutex
Synchronisation mutex.
Definition: state.c:157
static fr_state_entry_t * state_entry_create(fr_state_tree_t *state, request_t *request, fr_pair_list_t *reply_list, fr_state_entry_t *old)
Create a new state entry.
Definition: state.c:332
fr_pair_t * ctx
for all session specific data.
Definition: state.c:138
static void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry)
Unlink an entry and remove if from the tree.
Definition: state.c:275
fr_rb_tree_t * tree
rbtree used to lookup state value.
Definition: state.c:151
uint64_t fr_state_entries_tracked(fr_state_tree_t *state)
Return number of entries we're currently tracking.
Definition: state.c:941
static fr_state_entry_t * state_entry_find_and_unlink(fr_state_tree_t *state, fr_value_box_t const *vb)
Find the entry based on the State attribute and remove it from the state tree.
Definition: state.c:563
fr_dlist_head_t to_expire
Linked list of entries to free.
Definition: state.c:152
uint64_t fr_state_entries_created(fr_state_tree_t *state)
Return number of entries created.
Definition: state.c:925
bool thread_safe
Whether we lock the tree whilst modifying it.
Definition: state.c:156
uint64_t fr_state_entries_timeout(fr_state_tree_t *state)
Return number of entries that timed out.
Definition: state.c:933
static int _free_child_data(state_child_entry_t *child_entry)
Free any subrequest request data if the dlist head is freed.
Definition: state.c:796
fr_time_delta_t timeout
How long to wait before cleaning up state entries.
Definition: state.c:154
void fr_state_restore_to_child(request_t *child, void const *unique_ptr, int unique_int)
Restore subrequest data from a parent request.
Definition: state.c:858
uint64_t timed_out
Number of states that were cleaned up due to timeout.
Definition: state.c:147
uint64_t id
Next ID to assign.
Definition: state.c:146
fr_state_tree_t * state_tree
Tree this entry belongs to.
Definition: state.c:123
request_t * thawed
The request that thawed this entry.
Definition: state.c:121
uint32_t used_sessions
How many sessions are currently in progress.
Definition: state.c:150
uint32_t context_id
ID binding state values to a context such as a virtual server.
Definition: state.c:160
void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
Store subrequest's session-state list and persistable request data in its parent.
Definition: state.c:812
Holds a state value, and associated fr_pair_ts and data.
Definition: state.c:64
A child of a fr_state_entry_t.
Definition: state.c:137
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
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
int talloc_link_ctx(TALLOC_CTX *parent, TALLOC_CTX *child)
Link two different parent and child contexts, so the child is freed before the parent.
Definition: talloc.c:171
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition: time.h:196
#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
A time delta, a difference in time measured in nanoseconds.
Definition: time.h:80
"server local" time.
Definition: time.h:69
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
Definition: pair_inline.c:125
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition: pair.h:591
static fr_slen_t parent
Definition: pair.h:851
static fr_slen_t data
Definition: value.h:1265
#define fr_box_time_delta(_val)
Definition: value.h:343
#define fr_box_octets(_val, _len)
Definition: value.h:288