Moved generic RADIUS protocol support to a dedicated libradius
[strongswan.git] / src / libradius / radius_server.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 #include "radius_server.h"
17
18 #include <threading/mutex.h>
19 #include <threading/condvar.h>
20 #include <utils/linked_list.h>
21
22 typedef struct private_radius_server_t private_radius_server_t;
23
24 /**
25 * Private data of an radius_server_t object.
26 */
27 struct private_radius_server_t {
28
29 /**
30 * Public radius_server_t interface.
31 */
32 radius_server_t public;
33
34 /**
35 * list of radius sockets, as radius_socket_t
36 */
37 linked_list_t *sockets;
38
39 /**
40 * Total number of sockets, in list + currently in use
41 */
42 int socket_count;
43
44 /**
45 * mutex to lock sockets list
46 */
47 mutex_t *mutex;
48
49 /**
50 * condvar to wait for sockets
51 */
52 condvar_t *condvar;
53
54 /**
55 * Server name
56 */
57 char *name;
58
59 /**
60 * NAS-Identifier
61 */
62 chunk_t nas_identifier;
63
64 /**
65 * Preference boost for this server
66 */
67 int preference;
68
69 /**
70 * Is the server currently reachable
71 */
72 bool reachable;
73
74 /**
75 * Retry counter for unreachable servers
76 */
77 int retry;
78
79 /**
80 * reference count
81 */
82 refcount_t ref;
83 };
84
85 METHOD(radius_server_t, get_socket, radius_socket_t*,
86 private_radius_server_t *this)
87 {
88 radius_socket_t *skt;
89
90 this->mutex->lock(this->mutex);
91 while (this->sockets->remove_first(this->sockets, (void**)&skt) != SUCCESS)
92 {
93 this->condvar->wait(this->condvar, this->mutex);
94 }
95 this->mutex->unlock(this->mutex);
96 return skt;
97 }
98
99 METHOD(radius_server_t, put_socket, void,
100 private_radius_server_t *this, radius_socket_t *skt, bool result)
101 {
102 this->mutex->lock(this->mutex);
103 this->sockets->insert_last(this->sockets, skt);
104 this->mutex->unlock(this->mutex);
105 this->condvar->signal(this->condvar);
106 this->reachable = result;
107 }
108
109 METHOD(radius_server_t, get_nas_identifier, chunk_t,
110 private_radius_server_t *this)
111 {
112 return this->nas_identifier;
113 }
114
115 METHOD(radius_server_t, get_preference, int,
116 private_radius_server_t *this)
117 {
118 int pref;
119
120 if (this->socket_count == 0)
121 { /* don't have sockets, huh? */
122 return -1;
123 }
124 /* calculate preference between 0-100 + boost */
125 pref = this->preference;
126 pref += this->sockets->get_count(this->sockets) * 100 / this->socket_count;
127 if (this->reachable)
128 { /* reachable server get a boost: pref = 110-210 + boost */
129 return pref + 110;
130 }
131 /* Not reachable. Increase preference randomly to let it retry from
132 * time to time, especially if other servers have high load. */
133 this->retry++;
134 if (this->retry % 128 == 0)
135 { /* every 64th request gets 210, same as unloaded reachable */
136 return pref + 110;
137 }
138 if (this->retry % 32 == 0)
139 { /* every 32th request gets 190, wins against average loaded */
140 return pref + 90;
141 }
142 if (this->retry % 8 == 0)
143 { /* every 8th request gets 110, same as server under load */
144 return pref + 10;
145 }
146 /* other get ~100, less than fully loaded */
147 return pref;
148 }
149
150 METHOD(radius_server_t, get_name, char*,
151 private_radius_server_t *this)
152 {
153 return this->name;
154 }
155
156 METHOD(radius_server_t, get_ref, radius_server_t*,
157 private_radius_server_t *this)
158 {
159 ref_get(&this->ref);
160 return &this->public;
161 }
162
163
164 METHOD(radius_server_t, destroy, void,
165 private_radius_server_t *this)
166 {
167 if (ref_put(&this->ref))
168 {
169 this->mutex->destroy(this->mutex);
170 this->condvar->destroy(this->condvar);
171 this->sockets->destroy_offset(this->sockets,
172 offsetof(radius_socket_t, destroy));
173 free(this);
174 }
175 }
176
177 /**
178 * See header
179 */
180 radius_server_t *radius_server_create(char *name, char *address,
181 u_int16_t auth_port, u_int16_t acct_port,
182 char *nas_identifier, char *secret,
183 int sockets, int preference)
184 {
185 private_radius_server_t *this;
186 radius_socket_t *socket;
187
188 INIT(this,
189 .public = {
190 .get_socket = _get_socket,
191 .put_socket = _put_socket,
192 .get_nas_identifier = _get_nas_identifier,
193 .get_preference = _get_preference,
194 .get_name = _get_name,
195 .get_ref = _get_ref,
196 .destroy = _destroy,
197 },
198 .reachable = TRUE,
199 .nas_identifier = chunk_create(nas_identifier, strlen(nas_identifier)),
200 .socket_count = sockets,
201 .sockets = linked_list_create(),
202 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
203 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
204 .name = name,
205 .preference = preference,
206 .ref = 1,
207 );
208
209 while (sockets--)
210 {
211 socket = radius_socket_create(address, auth_port, acct_port,
212 chunk_create(secret, strlen(secret)));
213 if (!socket)
214 {
215 destroy(this);
216 return NULL;
217 }
218 this->sockets->insert_last(this->sockets, socket);
219 }
220 return &this->public;
221 }