Coverage Report

Created: 2026-09-28 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/retry.c
Line
Count
Source
1
/*
2
 *   This library is free software; you can redistribute it and/or
3
 *   modify it under the terms of the GNU Lesser General Public
4
 *   License as published by the Free Software Foundation; either
5
 *   version 2.1 of the License, or (at your option) any later version.
6
 *
7
 *   This library 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 GNU
10
 *   Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public
13
 *   License along with this library; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** Handle RFC standard retransmissions
18
 *
19
 * @file src/lib/util/retry.c
20
 *
21
 * @copyright 2020 Network RADIUS SAS
22
 */
23
24
RCSID("$Id: e8d2a078c8bda913bee9f8696cd16f916da0068e $")
25
26
#include <freeradius-devel/util/retry.h>
27
#include <freeradius-devel/util/rand.h>
28
#include <freeradius-devel/util/uint128.h>
29
30
/** Initialize a retransmission counter
31
 *
32
 * @param[in,out] r the retransmission structure
33
 * @param now when the retransmission starts
34
 * @param config the counters to track.  They shouldn't change while the retransmission is happening
35
 */
36
void fr_retry_init(fr_retry_t *r, fr_time_t now, fr_retry_config_t const *config)
37
0
{
38
0
  int64_t     tenth;
39
0
  uint64_t    scale;
40
0
  fr_time_delta_t   rt;
41
0
  uint128_t    delay;
42
43
0
  memset(r, 0, sizeof(*r));
44
45
0
  r->config = config;
46
0
  r->count = 1;
47
0
  r->start = now;
48
0
  r->end = fr_time_add(now, config->mrd);
49
0
  r->updated = now;
50
0
  r->state = FR_RETRY_CONTINUE;
51
52
  /*
53
   *  Ensure that we always have an end time.
54
   *
55
   *  If there's no MRD. We artificially force the end to a day.  If we're still retrying after
56
   *  that, it's likely good reason to give up.  The rest of the server enforces much shorter
57
   *  lifetimes on requests.
58
   */
59
0
  if (fr_time_cmp(r->start, r->end) == 0) {
60
0
    if (!config->mrc) {
61
0
      r->end = fr_time_add(now, fr_time_delta_from_sec(86400));
62
0
    } else {
63
0
      r->end = fr_time_add(now, fr_time_delta_mul(config->mrt, config->mrc));
64
0
    }
65
0
  }
66
67
  /*
68
   *  Only 1 retry, the timeout is MRD, not IRT.
69
   */
70
0
  if (config->mrc == 1) {
71
0
    r->next = r->end;
72
0
    r->rt = config->mrd; /* mostly set for debug messages */
73
0
    return;
74
0
  }
75
76
  /*
77
   *  Initial:
78
   *
79
   *  RT = IRT + RAND * IRT
80
   *     = IRT * (1 + RAND)
81
   */
82
0
  tenth = fr_rand();
83
0
  tenth -= ((uint64_t) 1) << 31; /* scale it -2^31..+2^31 */
84
0
  tenth /= 5;          /* convert [-.499,+.499] to  [-.0999,+.0999] */
85
0
  scale = (((uint64_t) 1) << 32) + tenth; /* make it 1.RAND modulo 2^32 */
86
87
0
  delay = uint128_mul64(scale, fr_time_delta_unwrap(r->config->irt));
88
0
  rt = fr_time_delta_wrap(uint128_to_64(uint128_rshift(delay, 32)));
89
90
0
  r->rt = rt;
91
0
  r->next = fr_time_add(now, rt);
92
93
  /*
94
   *  Cap the "next" timer at the end.
95
   */
96
0
  if (fr_time_cmp(r->next, r->end) > 0) {
97
0
    r->next = r->end;
98
0
  }
99
0
}
100
101
/** Initialize a retransmission counter
102
 *
103
 * @param[in,out] r the retransmission structure
104
 * @param now the current time
105
 * @return
106
 *  - FR_RETRTY_CONTINUE - continue retransmitting
107
 *  - FR_RETRY_MRC - stop, maximum retransmission count has been reached
108
 *  - FR_RETRY_MDR - stop, maximum retransmission duration has been reached.
109
 */
110
fr_retry_state_t fr_retry_next(fr_retry_t *r, fr_time_t now)
111
0
{
112
0
  int64_t     tenth;
113
0
  uint64_t          scale;
114
0
  fr_time_delta_t   rt;
115
0
  uint128_t    delay;
116
117
  /*
118
   *  Increment retransmission counter
119
   */
120
0
  r->count++;
121
0
  r->updated = now;
122
123
  /*
124
   *  We retried too many times.  Fail.
125
   */
126
0
  if (r->config->mrc && (r->count > r->config->mrc)) {
127
    /*
128
     *  A count of 1 is really a simple duration.
129
     */
130
0
    if (r->config->mrc == 1) {
131
0
      r->state = FR_RETRY_MRD;
132
0
      return FR_RETRY_MRD;
133
0
    }
134
135
0
    r->state = FR_RETRY_MRC;
136
0
    return FR_RETRY_MRC;
137
0
  }
138
139
0
redo:
140
  /*
141
   *  Cap delay at the end.
142
   *
143
   *  Note that this code can still return MRD, even if MRD
144
   *  wasn't set.  The initialization function above
145
   *  artificially caps MRD at one day.
146
   */
147
0
  if (fr_time_cmp(now, r->end) >= 0) {
148
0
    r->state = FR_RETRY_MRD;
149
0
    return FR_RETRY_MRD;
150
0
  }
151
152
  /*
153
   *  RFC 5080 Section 2.2.1
154
   *
155
   *  RAND gives a random number between -0.1 and +0.1
156
   *
157
   *  Our random number generator returns 0..2^32, so we
158
   *  have to scale everything relative to that.
159
   *
160
   *  RT = 2*RTprev + RAND*RTprev
161
   *     = RTprev * (2 + RAND)
162
   */
163
0
  tenth = fr_rand();
164
0
  tenth -= ((uint64_t) 1) << 31; /* scale it -2^31..+2^31 */
165
0
  tenth /= 5;          /* convert [-.499,+.499] to  [-.0999,+.0999] */
166
0
  scale = (((uint64_t) 1) << 33) + tenth; /* make it 2.RAND modulo 2^32 */
167
168
0
  delay = uint128_mul64(scale, fr_time_delta_unwrap(r->rt));
169
0
  rt = fr_time_delta_wrap(uint128_to_64(uint128_rshift(delay, 32)));
170
171
  /*
172
   *  Cap delay at MRT.
173
   *
174
   *  RT = MRT + RAND * MRT
175
   *     = MRT * (1 + RAND)
176
   */
177
0
  if (fr_time_delta_ispos(r->config->mrt) && (fr_time_delta_gt(rt, r->config->mrt))) {
178
0
    tenth = fr_rand();
179
0
    tenth -= ((uint64_t) 1) << 31; /* scale it -2^31..+2^31 */
180
0
    tenth /= 5;          /* convert [-.499,+.499] to  [-.0999,+.0999] */
181
0
    scale = (((uint64_t) 1) << 32) + tenth; /* make it 1.RAND modulo 2^32 */
182
183
0
    delay = uint128_mul64(scale, fr_time_delta_unwrap(r->config->mrt));
184
0
    rt = fr_time_delta_wrap(uint128_to_64(uint128_rshift(delay, 32)));
185
0
  }
186
187
  /*
188
   *  And finally set the retransmission timer.
189
   */
190
0
  r->rt = rt;
191
192
  /*
193
   *  Add in the retransmission delay.  Note that we send
194
   *  the packet at "next + rt", and not "now + rt".  That
195
   *  way the timer won't drift.
196
   */
197
0
  r->next = fr_time_add(r->next, rt);
198
199
  /*
200
   *  The "next" retransmission time is in the past, AND
201
   *  we're already halfway through the time after that.
202
   *  Skip this retransmission, and set the time for the
203
   *  next one.
204
   *
205
   *  i.e. if we weren't serviced for one event, just skip
206
   *  it, and go to the next one.
207
   */
208
0
  if (fr_time_lt(fr_time_add(r->next, fr_time_delta_wrap((fr_time_delta_unwrap(rt) / 2))), now)) goto redo;
209
210
  /*
211
   *  Cap the "next" timer at when we stop sending.
212
   */
213
0
  if (fr_time_cmp(r->next, r->end) > 0) {
214
0
    r->next = r->end;
215
0
  }
216
217
0
  return FR_RETRY_CONTINUE;
218
0
}