eap-radius: Add ability to configure RADIUS retransmission behavior
[strongswan.git] / src / libradius / radius_config.c
1 /*
2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 /*
17 * Copyright (C) 2015 Thom Troy
18 *
19 * Permission is hereby granted, free of charge, to any person obtaining a copy
20 * of this software and associated documentation files (the "Software"), to deal
21 * in the Software without restriction, including without limitation the rights
22 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 * copies of the Software, and to permit persons to whom the Software is
24 * furnished to do so, subject to the following conditions:
25 *
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
28 *
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 * THE SOFTWARE.
36 */
37
38 #include "radius_config.h"
39
40 #include <threading/mutex.h>
41 #include <threading/condvar.h>
42 #include <collections/linked_list.h>
43
44 typedef struct private_radius_config_t private_radius_config_t;
45
46 /**
47 * Private data of an radius_config_t object.
48 */
49 struct private_radius_config_t {
50
51 /**
52 * Public radius_config_t interface.
53 */
54 radius_config_t public;
55
56 /**
57 * list of radius sockets, as radius_socket_t
58 */
59 linked_list_t *sockets;
60
61 /**
62 * Total number of sockets, in list + currently in use
63 */
64 int socket_count;
65
66 /**
67 * mutex to lock sockets list
68 */
69 mutex_t *mutex;
70
71 /**
72 * condvar to wait for sockets
73 */
74 condvar_t *condvar;
75
76 /**
77 * Server name
78 */
79 char *name;
80
81 /**
82 * NAS-Identifier
83 */
84 chunk_t nas_identifier;
85
86 /**
87 * Preference boost for this server
88 */
89 int preference;
90
91 /**
92 * Is the server currently reachable
93 */
94 bool reachable;
95
96 /**
97 * Retry counter for unreachable servers
98 */
99 int retry;
100
101 /**
102 * reference count
103 */
104 refcount_t ref;
105 };
106
107 METHOD(radius_config_t, get_socket, radius_socket_t*,
108 private_radius_config_t *this)
109 {
110 radius_socket_t *skt;
111
112 this->mutex->lock(this->mutex);
113 while (this->sockets->remove_first(this->sockets, (void**)&skt) != SUCCESS)
114 {
115 this->condvar->wait(this->condvar, this->mutex);
116 }
117 this->mutex->unlock(this->mutex);
118 return skt;
119 }
120
121 METHOD(radius_config_t, put_socket, void,
122 private_radius_config_t *this, radius_socket_t *skt, bool result)
123 {
124 this->mutex->lock(this->mutex);
125 this->sockets->insert_last(this->sockets, skt);
126 this->mutex->unlock(this->mutex);
127 this->condvar->signal(this->condvar);
128 this->reachable = result;
129 }
130
131 METHOD(radius_config_t, get_nas_identifier, chunk_t,
132 private_radius_config_t *this)
133 {
134 return this->nas_identifier;
135 }
136
137 METHOD(radius_config_t, get_preference, int,
138 private_radius_config_t *this)
139 {
140 int pref;
141
142 if (this->socket_count == 0)
143 { /* don't have sockets, huh? */
144 return -1;
145 }
146 /* calculate preference between 0-100 + boost */
147 pref = this->preference;
148 pref += this->sockets->get_count(this->sockets) * 100 / this->socket_count;
149 if (this->reachable)
150 { /* reachable server get a boost: pref = 110-210 + boost */
151 return pref + 110;
152 }
153 /* Not reachable. Increase preference randomly to let it retry from
154 * time to time, especially if other servers have high load. */
155 this->retry++;
156 if (this->retry % 128 == 0)
157 { /* every 64th request gets 210, same as unloaded reachable */
158 return pref + 110;
159 }
160 if (this->retry % 32 == 0)
161 { /* every 32th request gets 190, wins against average loaded */
162 return pref + 90;
163 }
164 if (this->retry % 8 == 0)
165 { /* every 8th request gets 110, same as server under load */
166 return pref + 10;
167 }
168 /* other get ~100, less than fully loaded */
169 return pref;
170 }
171
172 METHOD(radius_config_t, get_name, char*,
173 private_radius_config_t *this)
174 {
175 return this->name;
176 }
177
178 METHOD(radius_config_t, get_ref, radius_config_t*,
179 private_radius_config_t *this)
180 {
181 ref_get(&this->ref);
182 return &this->public;
183 }
184
185
186 METHOD(radius_config_t, destroy, void,
187 private_radius_config_t *this)
188 {
189 if (ref_put(&this->ref))
190 {
191 this->mutex->destroy(this->mutex);
192 this->condvar->destroy(this->condvar);
193 this->sockets->destroy_offset(this->sockets,
194 offsetof(radius_socket_t, destroy));
195 free(this);
196 }
197 }
198
199 /**
200 * See header
201 */
202 radius_config_t *radius_config_create(char *name, char *address,
203 u_int16_t auth_port, u_int16_t acct_port,
204 char *nas_identifier, char *secret,
205 int sockets, int preference,
206 u_int tries, double timeout, double base)
207 {
208 private_radius_config_t *this;
209 radius_socket_t *socket;
210
211 INIT(this,
212 .public = {
213 .get_socket = _get_socket,
214 .put_socket = _put_socket,
215 .get_nas_identifier = _get_nas_identifier,
216 .get_preference = _get_preference,
217 .get_name = _get_name,
218 .get_ref = _get_ref,
219 .destroy = _destroy,
220 },
221 .reachable = TRUE,
222 .nas_identifier = chunk_create(nas_identifier, strlen(nas_identifier)),
223 .socket_count = sockets,
224 .sockets = linked_list_create(),
225 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
226 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
227 .name = name,
228 .preference = preference,
229 .ref = 1,
230 );
231
232 while (sockets--)
233 {
234 socket = radius_socket_create(address, auth_port, acct_port,
235 chunk_create(secret, strlen(secret)),
236 tries, timeout, base);
237 if (!socket)
238 {
239 destroy(this);
240 return NULL;
241 }
242 this->sockets->insert_last(this->sockets, socket);
243 }
244 return &this->public;
245 }