Add grouping session functionality for load balancing and failover
[l2tpns.git] / grpsess.c
1 /*
2 * Fernando ALVES 2013
3 * Grouped session for load balancing and fail-over
4 * GPL licenced
5 */
6
7 #include <errno.h>
8 #include <ctype.h>
9 #include <string.h>
10 #include <sys/socket.h>
11 #include <linux/rtnetlink.h>
12
13 #include "l2tpns.h"
14 #include "util.h"
15
16 #ifdef BGP
17 #include "bgp.h"
18 #endif
19
20 union grp_iphash {
21 groupidt grp;
22 union grp_iphash *idx;
23 } grp_ip_hash[256]; // Mapping from IP address to group structures.
24
25 static groupidt gnextgrpid = 0;
26
27 // Find gruop by IP, < 1 for not found
28 //
29 // Confusingly enough, this 'ip' must be
30 // in _network_ order. This being the common
31 // case when looking it up from IP packet headers.
32 static groupidt grp_lookup_ipmap(in_addr_t ip)
33 {
34 uint8_t *a = (uint8_t *) &ip;
35 union grp_iphash *h = grp_ip_hash;
36
37 if (!(h = h[*a++].idx)) return 0;
38 if (!(h = h[*a++].idx)) return 0;
39 if (!(h = h[*a++].idx)) return 0;
40
41 return h[*a].grp;
42 }
43
44 //
45 // Take an IP address in HOST byte order and
46 // add it to the grouid by IP cache.
47 //
48 // (It's actually cached in network order)
49 //
50 static void grp_cache_ipmap(in_addr_t ip, groupidt g)
51 {
52 in_addr_t nip = htonl(ip); // MUST be in network order. I.e. MSB must in be ((char *) (&ip))[0]
53 uint8_t *a = (uint8_t *) &nip;
54 union grp_iphash *h = grp_ip_hash;
55 int i;
56
57 for (i = 0; i < 3; i++)
58 {
59 if (!(h[a[i]].idx || (h[a[i]].idx = calloc(256, sizeof(union grp_iphash)))))
60 return;
61
62 h = h[a[i]].idx;
63 }
64
65 h[a[3]].grp = g;
66
67 if (g > 0)
68 LOG(4, 0, 0, "Caching Group:%d ip address %s\n", g, fmtaddr(nip, 0));
69 else if (g == 0)
70 LOG(4, 0, 0, "Un-caching Group ip address %s\n", fmtaddr(nip, 0));
71 }
72
73 groupidt grp_groupbyip(in_addr_t ip)
74 {
75 groupidt g = grp_lookup_ipmap(ip);
76
77 if (g > 0 && g < MAXGROUPE)
78 return g;
79
80 return 0;
81 }
82
83 // Add a route
84 //
85 // This adds it to the routing table, advertises it
86 // via BGP if enabled, and stuffs it into the
87 // 'sessionbyip' cache.
88 //
89 // 'ip' must be in _host_ order.
90 //
91 static void grp_routeset(groupidt g, in_addr_t ip, int prefixlen, int add)
92 {
93 struct {
94 struct nlmsghdr nh;
95 struct rtmsg rt;
96 char buf[32];
97 } req;
98 int i;
99 in_addr_t n_ip;
100
101 if (!prefixlen) prefixlen = 32;
102
103 ip &= 0xffffffff << (32 - prefixlen);; // Force the ip to be the first one in the route.
104
105 memset(&req, 0, sizeof(req));
106
107 if (add)
108 {
109 req.nh.nlmsg_type = RTM_NEWROUTE;
110 req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE;
111 }
112 else
113 {
114 req.nh.nlmsg_type = RTM_DELROUTE;
115 req.nh.nlmsg_flags = NLM_F_REQUEST;
116 }
117
118 req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.rt));
119
120 req.rt.rtm_family = AF_INET;
121 req.rt.rtm_dst_len = prefixlen;
122 req.rt.rtm_table = RT_TABLE_MAIN;
123 req.rt.rtm_protocol = 42;
124 req.rt.rtm_scope = RT_SCOPE_LINK;
125 req.rt.rtm_type = RTN_UNICAST;
126
127 netlink_addattr(&req.nh, RTA_OIF, &tunidx, sizeof(int));
128 n_ip = htonl(ip);
129 netlink_addattr(&req.nh, RTA_DST, &n_ip, sizeof(n_ip));
130
131 LOG(1, 0, 0, "Route (Group) %s %s/%d\n", add ? "add" : "del", fmtaddr(htonl(ip), 0), prefixlen);
132
133 if (netlink_send(&req.nh) < 0)
134 LOG(0, 0, 0, "grp_routeset() error in sending netlink message: %s\n", strerror(errno));
135
136 #ifdef BGP
137 if (add)
138 bgp_add_route(htonl(ip), prefixlen);
139 else
140 bgp_del_route(htonl(ip), prefixlen);
141 #endif /* BGP */
142
143 // Add/Remove the IPs to the 'groupbyip' cache.
144 // Note that we add the zero address in the case of
145 // a network route. Roll on CIDR.
146
147 // Note that 'g == 0' implies this is the address pool.
148 // We still cache it here, because it will pre-fill
149 // the malloc'ed tree.
150 if (g)
151 {
152 if (!add) // Are we deleting a route?
153 g = 0; // Caching the session as '0' is the same as uncaching.
154
155 for (i = ip; i < ip+(1<<(32-prefixlen)) ; ++i)
156 grp_cache_ipmap(i, g);
157 }
158 }
159
160 // Set all route of a group
161 void grp_setgrouproute(groupidt g, int add)
162 {
163 int i;
164 for (i = 0; i < grpsession[g].nbroutesgrp; i++)
165 {
166 if (grpsession[g].route[i].ip != 0)
167 {
168 grp_routeset(g, grpsession[g].route[i].ip, grpsession[g].route[i].prefixlen, add);
169 }
170 }
171 }
172
173 // return group id by session
174 groupidt grp_groupbysession(sessionidt s)
175 {
176 groupidt g = 0;
177 int i;
178 for (g = gnextgrpid; g != 0; g = grpsession[g].prev)
179 {
180 for (i = 0; i < grpsession[g].nbsession; i++)
181 {
182 if (grpsession[g].sesslist[i].sid == s)
183 { // session found in group
184 return g;
185 }
186 }
187 }
188
189 return 0;
190 }
191
192 // Remove a session to a group
193 // return 1 if OK
194 void grp_removesession(groupidt g, sessionidt s)
195 {
196 int i;
197
198 if (grpsession[g].nbsession <= 0)
199 return;
200
201 for (i = 0; i < grpsession[g].nbsession; i++)
202 {
203 if (grpsession[g].sesslist[i].sid == s)
204 { // session found on group
205 --grpsession[g].nbsession;
206 if (grpsession[g].nbsession == 0)
207 {
208 // Group is empty, remove it
209
210 // Del all routes
211 grp_setgrouproute(g, 0);
212
213 if (gnextgrpid == g)
214 {
215 gnextgrpid = 0;
216 }
217 else
218 {
219 groupidt g2;
220 for (g2 = gnextgrpid; g2 != 0; g2 = grpsession[g2].prev)
221 {
222 if (grpsession[g2].prev == g)
223 {
224 grpsession[g2].prev = grpsession[g].prev;
225 break;
226 }
227 }
228 }
229
230 memset(&grpsession[g], 0, sizeof(grpsession[0]));
231 }
232 else
233 {
234 // remove the session
235 memmove(&grpsession[g].sesslist[i],
236 &grpsession[g].sesslist[i+1],
237 (grpsession[g].nbsession - i) * sizeof(grpsession[g].sesslist[i]));
238 }
239 return;
240 }
241 }
242 }
243
244 // Add a session to a group
245 // return 1 if OK
246 static int grp_addsession(groupidt g, sessionidt s, uint8_t weight)
247 {
248 int i;
249
250 for (i = 0; i < grpsession[g].nbsession; i++)
251 {
252 if (grpsession[g].sesslist[i].sid == s)
253 { // already in group
254 if ((!grpsession[g].sesslist[i].weight) || (weight > 1))
255 grpsession[g].sesslist[i].weight = weight; // update Weight session (for load-balancing)
256
257 return 1;
258 }
259 }
260
261 if (i >= MAXSESSINGRP)
262 {
263 LOG(1, s, session[s].tunnel, " Too many session for Group %d\n", g);
264 return 0;
265 }
266 else
267 { // add session id to group
268 if (i == 0)
269 {
270 // it's the first session of the group, set to next group
271 grpsession[g].prev = gnextgrpid;
272 gnextgrpid = g;
273 }
274 grpsession[g].sesslist[i].sid = s;
275 grpsession[g].sesslist[i].weight = weight;
276 grpsession[g].nbsession++;
277
278 return 1;
279 }
280 return 0;
281 }
282
283 // Add a route to a group
284 // return 1 if OK
285 static int grp_addroute(groupidt g, sessionidt s, in_addr_t ip, int prefixlen)
286 {
287 int i;
288
289 for (i = 0; i < MAXROUTEINGRP; i++)
290 {
291 if ((i >= grpsession[g].nbroutesgrp))
292 {
293 LOG(3, s, session[s].tunnel, " Radius reply Group %d contains route for %s/%d\n",
294 g, fmtaddr(htonl(ip), 0), prefixlen);
295
296 grpsession[g].route[i].ip = ip;
297 grpsession[g].route[i].prefixlen = prefixlen;
298 grpsession[g].nbroutesgrp++;
299 return 1;
300 }
301 else if ((grpsession[g].route[i].ip == ip) && (grpsession[g].route[i].prefixlen == prefixlen))
302 {
303 // route already defined in group
304 LOG(3, s, session[s].tunnel,
305 " Radius reply Group %d contains route for %s/%d (this already defined)\n",
306 g, fmtaddr(htonl(ip), 0), prefixlen);
307
308 return 1;
309 }
310 else if (grpsession[g].route[i].ip == 0)
311 {
312 LOG(3, s, session[s].tunnel, " Radius reply Group %d contains route for %s/%d (find empty on list!!!)\n",
313 g, fmtaddr(htonl(ip), 0), prefixlen);
314
315 grpsession[g].route[i].ip = ip;
316 grpsession[g].route[i].prefixlen = prefixlen;
317 return 1;
318 }
319 }
320
321 if (i >= MAXROUTEINGRP)
322 {
323 LOG(1, s, session[s].tunnel, " Too many routes for Group %d\n", g);
324 }
325 return 0;
326 }
327
328 // Process Sames vendor specific attribut radius
329 void grp_processvendorspecific(sessionidt s, uint8_t *pvs)
330 {
331 uint8_t attrib = *pvs;
332 groupidt grpid = 0;
333 uint8_t *n = pvs + 2;
334 uint8_t *e = pvs + pvs[1];
335
336 if ((attrib >= 22) && (attrib <= 23))
337 {
338 while (n < e && isdigit(*n))
339 {
340 grpid = grpid * 10 + *n - '0';
341 n++;
342 }
343 if ((grpid == 0) || (grpid >= MAXGROUPE))
344 {
345 LOG(1, s, session[s].tunnel, " Group Attribute Id %d not allowed\n", grpid);
346 return;
347 }
348 else if (*n != ',')
349 {
350 LOG(1, s, session[s].tunnel, " Group Attribute Id not defined\n");
351 return;
352 }
353
354 if (!grp_addsession(grpid, s, 1))
355 {
356 return;
357 }
358
359 n++;
360 }
361
362 //process, Sames vendor-specific 64520
363 if (attrib == 22) //SAMES-Group-Framed-Route
364 {
365 in_addr_t ip = 0;
366 uint8_t u = 0;
367 uint8_t bits = 0;
368
369 while (n < e && (isdigit(*n) || *n == '.'))
370 {
371 if (*n == '.')
372 {
373 ip = (ip << 8) + u;
374 u = 0;
375 }
376 else
377 u = u * 10 + *n - '0';
378 n++;
379 }
380 ip = (ip << 8) + u;
381 if (*n == '/')
382 {
383 n++;
384 while (n < e && isdigit(*n))
385 bits = bits * 10 + *n++ - '0';
386 }
387 else if ((ip >> 24) < 128)
388 bits = 8;
389 else if ((ip >> 24) < 192)
390 bits = 16;
391 else
392 bits = 24;
393
394 if (!grp_addroute(grpid, s, ip, bits))
395 return;
396 }
397 else if (attrib == 23) //SAMES-Group-Session-Weight
398 {
399 uint8_t weight = 0;
400
401 while (n < e && isdigit(*n))
402 weight = weight * 10 + *n++ - '0';
403
404 if (!weight)
405 {
406 LOG(1, s, session[s].tunnel, " Group-Session-Weight 0 GroupId %d not allowed\n", grpid);
407 return;
408 }
409 if (!grp_addsession(grpid, s, weight))
410 {
411 return;
412 }
413 }
414 else
415 {
416 LOG(3, s, session[s].tunnel, " Unknown vendor-specific: 64520, Attrib: %d\n", attrib);
417 }
418 }
419
420 // Init data structures
421 void grp_initdata()
422 {
423 // Set default value (10s)
424 config->grp_txrate_average_time = 10;
425
426 if (!(grpsession = shared_malloc(sizeof(groupsesst) * MAXGROUPE)))
427 {
428 LOG(0, 0, 0, "Error doing malloc for grouped session: %s\n", strerror(errno));
429 exit(1);
430 }
431
432 memset(grpsession, 0, sizeof(grpsession[0]) * MAXGROUPE);
433 }
434
435 // Update time_changed of the group
436 void grp_time_changed()
437 {
438 groupidt g;
439
440 for (g = gnextgrpid; g != 0; g = grpsession[g].prev)
441 {
442 grpsession[g].time_changed++;
443 }
444 }
445
446 // return the next session can be used on the group
447 sessionidt grp_getnextsession(groupidt g, in_addr_t ip)
448 {
449 sessionidt s = 0, s2 = 0, s3 = 0;
450 int i;
451 uint32_t ltime_changed = 0;
452 uint32_t mintxrate = 0xFFFFFFFF;
453
454 if (g >= MAXGROUPE)
455 return 0;
456
457 if ((s = sessionbyip(ip)))
458 {
459 if ( (session[s].ppp.phase > Establish) &&
460 (time_now - session[s].last_packet <= (config->echo_timeout + 2)) )
461 {
462 int recaltxrate = 0;
463
464 for (i = 0; i < grpsession[g].nbsession; i++)
465 {
466 if (s == grpsession[g].sesslist[i].sid)
467 {
468 if ((time_now - grpsession[g].sesslist[i].mark_time) > config->grp_txrate_average_time)
469 {
470 grpsession[g].sesslist[i].mark_time = time_now;
471 recaltxrate = 1;
472 break;
473 }
474 }
475 }
476
477 if (!recaltxrate)
478 return s;
479 }
480 }
481
482 if (grpsession[g].time_changed > config->grp_txrate_average_time)
483 {
484 ltime_changed = grpsession[g].time_changed;
485 grpsession[g].time_changed = 1;
486 }
487
488 for (i = 0; i < grpsession[g].nbsession; i++)
489 {
490 if ((s2 = grpsession[g].sesslist[i].sid))
491 {
492 s3 = s2;
493 if (ltime_changed)
494 {
495 grpsession[g].sesslist[i].tx_rate = session[s2].coutgrp_delta/ltime_changed;
496 session[s2].coutgrp_delta = grpsession[g].sesslist[i].tx_rate;
497 LOG(3, s2, session[s2].tunnel, "TX Rate: %d session weight: %d\n", grpsession[g].sesslist[i].tx_rate, grpsession[g].sesslist[i].weight);
498 }
499
500 if ( session[s2].ppp.phase > Establish &&
501 (time_now - session[s2].last_packet <= (config->echo_timeout + 2)) )
502 {
503 uint32_t txrate = grpsession[g].sesslist[i].tx_rate/grpsession[g].sesslist[i].weight;
504 if (txrate < mintxrate)
505 {
506 s = s2;
507 mintxrate = txrate;
508 }
509 }
510 }
511 }
512
513 if (!s)
514 s = s3;
515
516 if (s)
517 cache_ipmap(ntohl(ip), s);
518
519 return s;
520 }