/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 | } |