blob: 0d70e32a75ca7aa9cf454e27c820370c09e9bb07 [file] [log] [blame]
Emeric Brun3bd697e2010-01-04 15:23:48 +01001/*
2 * Stick tables management functions.
3 *
4 * Copyright 2009-2010 EXCELIANCE, Emeric Brun <ebrun@exceliance.fr>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <string.h>
14
15#include <common/config.h>
16#include <common/memory.h>
17#include <common/mini-clist.h>
18#include <common/standard.h>
19#include <common/time.h>
20
21#include <ebmbtree.h>
22#include <ebsttree.h>
23
24#include <types/stick_table.h>
25
26#include <proto/proxy.h>
27#include <proto/session.h>
28#include <proto/task.h>
29
30
31/*
32 * Free an allocate sticked session <ts>.
33 * Decrease table <t> sticked session counter .
34 */
35void stksess_free(struct stktable *t, struct stksess *ts)
36{
37 t->current--;
38 pool_free2(t->pool,ts);
39}
40
41/*
42 * Init or modify <key> of th sticked session <ts> present in table <t>.
43 */
44void stksess_key(struct stktable *t, struct stksess *ts, struct stktable_key *key)
45{
46 if (t->type != STKTABLE_TYPE_STRING)
47 memcpy(ts->keys.key, key->key , t->key_size);
48 else {
49 memcpy(ts->keys.key, key->key, MIN(t->key_size - 1, key->key_len));
50 ts->keys.key[MIN(t->key_size - 1, key->key_len)] = 0;
51 }
52}
53
54
55/*
56 * Init sticked session <ts> using <key>.
57 */
58struct stksess *stksess_init(struct stktable *t, struct stksess * ts, struct stktable_key *key)
59{
60 ts->keys.node.leaf_p = NULL;
61 ts->exps.node.leaf_p = NULL;
62 ts->sid = 0;
63 stksess_key(t, ts, key);
64
65 return ts;
66}
67
68/*
69 * Trash oldest <to_batch> sticked sessions from table <t>
70 * Returns number of trashed sticked session.
71 */
72static int stktable_trash_oldest(struct stktable *t, int to_batch)
73{
74 struct stksess *ts;
75 struct eb32_node *eb;
76 int batched = 0;
77
78 eb = eb32_lookup_ge(&t->exps, now_ms - TIMER_LOOK_BACK);
79
80 while (batched < to_batch) {
81
82 if (unlikely(!eb)) {
83 /* we might have reached the end of the tree, typically because
84 * <now_ms> is in the first half and we're first scanning the last
85 * half. Let's loop back to the beginning of the tree now.
86 */
87 eb = eb32_first(&t->exps);
88 if (likely(!eb))
89 break;
90 }
91
92 /* timer looks expired, detach it from the queue */
93 ts = eb32_entry(eb, struct stksess, exps);
94 eb = eb32_next(eb);
95
96 eb32_delete(&ts->exps);
97
98 if (ts->expire != ts->exps.key) {
99
100 if (!tick_isset(ts->expire))
101 continue;
102
103 ts->exps.key = ts->expire;
104
105 eb32_insert(&t->exps, &ts->exps);
106
107 if (!eb || eb->key > ts->exps.key)
108 eb = &ts->exps;
109
110 continue;
111 }
112 /* session expired, trash it */
113
114 ebmb_delete(&ts->keys);
115 stksess_free(t, ts);
116 batched++;
117 }
118
119 return batched;
120}
121
122/*
123 * Allocate and initialise a new sticked session.
124 * The new sticked session is returned or NULL in case of lack of memory.
125 * Sticked sessions should only be allocated this way, and must be
126 * freed using stksess_free().
127 * Increase table <t> sticked session counter.
128 */
129struct stksess *stksess_new(struct stktable *t, struct stktable_key *key)
130{
131 struct stksess *ts;
132
133 if (unlikely(t->current == t->size)) {
134 if ( t->nopurge )
135 return NULL;
136
137 if (!stktable_trash_oldest(t, t->size >> 8))
138 return NULL;
139 }
140
141 ts = pool_alloc2(t->pool);
142 if (ts) {
143 t->current++;
144 stksess_init(t, ts, key);
145 }
146
147 return ts;
148}
149
150/*
151 * Lookup in table <t> for a sticked session identified by <key>.
152 * Returns pointer on requested sticked session or NULL if no one found.
153 */
154struct stksess *stktable_lookup(struct stktable *t, struct stktable_key *key)
155{
156 struct ebmb_node *eb;
157
158 /* lookup on track session */
159 if (t->type == STKTABLE_TYPE_STRING)
160 eb = ebst_lookup_len(&t->keys, key->key, key->key_len);
161 else
162 eb = ebmb_lookup(&t->keys, key->key, t->key_size);
163
164 if (unlikely(!eb)) {
165 /* no session found */
166 return NULL;
167 }
168
169 /* Existing session, returns server id */
170 return ebmb_entry(eb, struct stksess, keys);
171}
172
173/*
174 * Store sticked session if not present in table.
175 * Il already present, update the existing session.
176 */
177int stktable_store(struct stktable *t, struct stksess *tsess, int sid)
178{
179 struct stksess *ts;
180 struct ebmb_node *eb;
181
182 if (t->type == STKTABLE_TYPE_STRING)
183 eb = ebst_lookup(&(t->keys), (char *)tsess->keys.key);
184 else
185 eb = ebmb_lookup(&(t->keys), tsess->keys.key, t->key_size);
186
187 if (unlikely(!eb)) {
188 tsess->sid = sid;
189 ebmb_insert(&t->keys, &tsess->keys, t->key_size);
190
191 tsess->exps.key = tsess->expire = tick_add(now_ms, MS_TO_TICKS(t->expire));
192 eb32_insert(&t->exps, &tsess->exps);
193
194 if (t->expire) {
195 t->exp_task->expire = t->exp_next = tick_first(tsess->expire, t->exp_next);
196 task_queue(t->exp_task);
197 }
198 return 0;
199 }
200
201 /* Existing track session */
202 ts = ebmb_entry(eb, struct stksess, keys);
203
204 if ( ts->sid != sid )
205 ts->sid = sid;
206 return 1;
207}
208
209/*
210 * Trash expired sticked sessions from table <t>.
211 */
212static int stktable_trash_expired(struct stktable *t)
213{
214 struct stksess *ts;
215 struct eb32_node *eb;
216
217 eb = eb32_lookup_ge(&t->exps, now_ms - TIMER_LOOK_BACK);
218
219 while (1) {
220 if (unlikely(!eb)) {
221 /* we might have reached the end of the tree, typically because
222 * <now_ms> is in the first half and we're first scanning the last
223 * half. Let's loop back to the beginning of the tree now.
224 */
225 eb = eb32_first(&t->exps);
226 if (likely(!eb))
227 break;
228 }
229
230 if (likely(tick_is_lt(now_ms, eb->key))) {
231 /* timer not expired yet, revisit it later */
232 t->exp_next = eb->key;
233 return t->exp_next;
234 }
235
236 /* timer looks expired, detach it from the queue */
237 ts = eb32_entry(eb, struct stksess, exps);
238 eb = eb32_next(eb);
239
240 eb32_delete(&ts->exps);
241
242 if (!tick_is_expired(ts->expire, now_ms)) {
243 if (!tick_isset(ts->expire))
244 continue;
245
246 ts->exps.key = ts->expire;
247 eb32_insert(&t->exps, &ts->exps);
248
249 if (!eb || eb->key > ts->exps.key)
250 eb = &ts->exps;
251 continue;
252 }
253
254 /* session expired, trash it */
255 ebmb_delete(&ts->keys);
256 stksess_free(t, ts);
257 }
258
259 /* We have found no task to expire in any tree */
260 t->exp_next = TICK_ETERNITY;
261 return t->exp_next;
262}
263
264/*
265 * Task processing function to trash expired sticked sessions.
266 */
267static struct task *process_table_expire(struct task * task)
268{
269 struct stktable *t = (struct stktable *)task->context;
270
271 task->expire = stktable_trash_expired(t);
272 return task;
273}
274
275/* Perform minimal intializations, report 0 in case of error, 1 if OK. */
276int stktable_init(struct stktable *t)
277{
278 if (t->size) {
279 memset(&t->keys, 0, sizeof(t->keys));
280 memset(&t->exps, 0, sizeof(t->exps));
281
282 t->pool = create_pool("sticktables", sizeof(struct stksess) + t->key_size, MEM_F_SHARED);
283
284 t->exp_next = TICK_ETERNITY;
285 if ( t->expire ) {
286 t->exp_task = task_new();
287 t->exp_task->process = process_table_expire;
288 t->exp_task->expire = TICK_ETERNITY;
289 t->exp_task->context = (void *)t;
290 }
291 return t->pool != NULL;
292 }
293 return 1;
294}
295
296/*
297 * Configuration keywords of known table types
298 */
299struct stktable_type stktable_types[STKTABLE_TYPES] = { { "ip", 0, 4 } ,
300 { "integer", 0, 4 },
301 { "string", STKTABLE_TYPEFLAG_CUSTOMKEYSIZE, 32 } };
302
303
304/*
305 * Parse table type configuration.
306 * Returns 0 on successful parsing, else 1.
307 * <myidx> is set at next configuration <args> index.
308 */
309int stktable_parse_type(char **args, int *myidx, unsigned long *type, size_t *key_size)
310{
311 for (*type = 0; *type < STKTABLE_TYPES; (*type)++) {
312 if (strcmp(args[*myidx], stktable_types[*type].kw) != 0)
313 continue;
314
315 *key_size = stktable_types[*type].default_size;
316 (*myidx)++;
317
318 if (stktable_types[*type].flags & STKTABLE_TYPEFLAG_CUSTOMKEYSIZE) {
319 if (strcmp("len", args[*myidx]) == 0) {
320 (*myidx)++;
321 *key_size = atol(args[*myidx]);
322 if ( !*key_size )
323 break;
324 /* null terminated string needs +1 for '\0'. */
325 (*key_size)++;
326 (*myidx)++;
327 }
328 }
329 return 0;
330 }
331 return 1;
332}
333
334