libgtp: Add back-reference to gsn from pdp context
[openggsn.git] / lib / ippool.c
bloba236fe7e4c47e4fb6006dc0f1db6a50f559e9a44
1 /*
2 * IP address pool functions.
3 * Copyright (C) 2003, 2004 Mondru AB.
4 * Copyright (C) 2017 by Harald Welte <laforge@gnumonks.org>
6 * The contents of this file may be used under the terms of the GNU
7 * General Public License Version 2, provided that the above copyright
8 * notice and this permission notice is included in all copies or
9 * substantial portions of the software.
13 #include <sys/types.h>
14 #include <netinet/in.h> /* in_addr */
15 #include <stdlib.h> /* calloc */
16 #include <stdio.h> /* sscanf */
17 #include <string.h>
18 #include <sys/socket.h>
19 #include <arpa/inet.h>
20 #include <netdb.h>
21 #include "syserr.h"
22 #include "ippool.h"
23 #include "lookup.h"
25 int ippool_printaddr(struct ippool_t *this)
27 unsigned int n;
28 printf("ippool_printaddr\n");
29 printf("Firstdyn %d\n", this->firstdyn - this->member);
30 printf("Lastdyn %d\n", this->lastdyn - this->member);
31 printf("Firststat %d\n", this->firststat - this->member);
32 printf("Laststat %d\n", this->laststat - this->member);
33 printf("Listsize %d\n", this->listsize);
35 for (n = 0; n < this->listsize; n++) {
36 char s[256];
37 in46a_ntop(&this->member[n].addr, s, sizeof(s));
38 printf("Unit %d inuse %d prev %d next %d addr %s\n",
40 this->member[n].inuse,
41 this->member[n].prev - this->member,
42 this->member[n].next - this->member,
43 s);
45 return 0;
48 int ippool_hashadd(struct ippool_t *this, struct ippoolm_t *member)
50 uint32_t hash;
51 struct ippoolm_t *p;
52 struct ippoolm_t *p_prev = NULL;
54 /* Insert into hash table */
55 hash = ippool_hash(&member->addr) & this->hashmask;
56 for (p = this->hash[hash]; p; p = p->nexthash)
57 p_prev = p;
58 if (!p_prev)
59 this->hash[hash] = member;
60 else
61 p_prev->nexthash = member;
62 return 0; /* Always OK to insert */
65 int ippool_hashdel(struct ippool_t *this, struct ippoolm_t *member)
67 uint32_t hash;
68 struct ippoolm_t *p;
69 struct ippoolm_t *p_prev = NULL;
71 /* Find in hash table */
72 hash = ippool_hash(&member->addr) & this->hashmask;
73 for (p = this->hash[hash]; p; p = p->nexthash) {
74 if (p == member) {
75 break;
77 p_prev = p;
80 if (p != member) {
81 SYS_ERR(DIP, LOGL_ERROR, 0,
82 "ippool_hashdel: Tried to delete member not in hash table");
83 return -1;
86 if (!p_prev)
87 this->hash[hash] = p->nexthash;
88 else
89 p_prev->nexthash = p->nexthash;
91 return 0;
94 static unsigned long int ippool_hash4(struct in_addr *addr)
96 return lookup((unsigned char *)&addr->s_addr, sizeof(addr->s_addr), 0);
99 static unsigned long int ippool_hash6(struct in6_addr *addr, unsigned int len)
101 /* TODO: Review hash spread for IPv6 */
102 return lookup((unsigned char *)addr->s6_addr, len, 0);
105 unsigned long int ippool_hash(struct in46_addr *addr)
107 if (addr->len == 4)
108 return ippool_hash4(&addr->v4);
109 else
110 return ippool_hash6(&addr->v6, addr->len);
113 /* Get IP address and mask */
114 int ippool_aton(struct in46_addr *addr, size_t *prefixlen, const char *pool_in, int number)
116 struct addrinfo *ai;
117 struct addrinfo hints = {
118 .ai_family = AF_UNSPEC,
119 .ai_socktype = SOCK_DGRAM,
120 .ai_flags = 0,
121 .ai_protocol = 0
123 char pool[strlen(pool_in)+1];
125 strcpy(pool, pool_in);
127 int err;
129 /* Find '/' and point to first char after it */
130 char *prefixlen_str = strchr(pool, '/');
131 if (prefixlen_str) {
132 *prefixlen_str = '\0';
133 prefixlen_str++;
134 if (*prefixlen_str == '\0') {
135 SYS_ERR(DIP, LOGL_ERROR, 0, "Empty prefix length specified");
136 return -1;
140 /* convert address */
141 if ((err = getaddrinfo(pool, NULL, &hints, &ai))) {
142 SYS_ERR(DIP, LOGL_ERROR, 0, "Bad address");
143 return -1;
146 /* Copy address, set lengths */
147 if (ai->ai_family == AF_INET) {
148 *prefixlen = 32;
149 addr->len = sizeof(struct in_addr);
150 addr->v4 = ((struct sockaddr_in*)ai->ai_addr)->sin_addr;
151 } else {
152 *prefixlen = 128;
153 addr->len = sizeof(struct in6_addr);
154 addr->v6 = ((struct sockaddr_in6*)ai->ai_addr)->sin6_addr;
156 freeaddrinfo(ai);
158 /* parse prefixlen */
159 if (prefixlen_str) {
160 char *e;
161 *prefixlen = strtol(prefixlen_str, &e, 10);
162 if (*e != '\0') {
163 SYS_ERR(DIP, LOGL_ERROR, 0, "Prefixlen is not an int");
164 return -1;
168 if (*prefixlen > (addr->len * 8)) {
169 SYS_ERR(DIP, LOGL_ERROR, 0, "Perfixlen too big");
170 return -1;
173 return 0;
176 /* Increase IPv4/IPv6 address by 1 */
177 void in46a_inc(struct in46_addr *addr)
179 size_t addrlen;
180 uint8_t *a = (uint8_t *)&addr->v6;
181 for (addrlen = addr->len; addrlen > 0; addrlen--) {
182 if (++a[addrlen-1])
183 break;
187 /* Create new address pool */
188 int ippool_new(struct ippool_t **this, const struct in46_prefix *dyn, const struct in46_prefix *stat,
189 int flags)
192 /* Parse only first instance of pool for now */
194 int i;
195 struct in46_addr addr;
196 size_t addrprefixlen;
197 struct in46_addr stataddr;
198 size_t stataddrprefixlen;
199 int listsize;
200 int dynsize;
201 unsigned int statsize;
203 if (!dyn || dyn->addr.len == 0) {
204 dynsize = 0;
205 } else {
206 addr = dyn->addr;
207 addrprefixlen = dyn->prefixlen;
208 /* we want to work with /64 prefixes, i.e. allocate /64 prefixes rather
209 * than /128 (single IPv6 addresses) */
210 if (addr.len == sizeof(struct in6_addr))
211 addr.len = 64/8;
213 /* Set IPPOOL_NONETWORK if IPPOOL_NOGATEWAY is set */
214 if (flags & IPPOOL_NOGATEWAY) {
215 flags |= IPPOOL_NONETWORK;
218 dynsize = (1 << (addr.len*8 - addrprefixlen)) -1;
219 if (flags & IPPOOL_NONETWORK) /* Exclude network address from pool */
220 dynsize--;
221 if (flags & IPPOOL_NOGATEWAY) /* Exclude gateway address from pool */
222 dynsize--;
223 if (flags & IPPOOL_NOBROADCAST) /* Exclude broadcast address from pool */
224 dynsize--;
227 if (!stat || stat->addr.len == 0) {
228 statsize = 0;
229 stataddr.len = 0;
230 stataddrprefixlen = 0;
231 } else {
232 stataddr = stat->addr;
233 stataddrprefixlen = stat->prefixlen;
235 statsize = (1 << (addr.len - stataddrprefixlen + 1)) -1;
236 if (statsize > IPPOOL_STATSIZE)
237 statsize = IPPOOL_STATSIZE;
240 listsize = dynsize + statsize; /* Allocate space for static IP addresses */
242 if (!(*this = calloc(sizeof(struct ippool_t), 1))) {
243 SYS_ERR(DIP, LOGL_ERROR, 0,
244 "Failed to allocate memory for ippool");
245 return -1;
248 (*this)->allowdyn = dyn ? 1 : 0;
249 (*this)->allowstat = stat ? 1 : 0;
250 if (stataddr.len > 0)
251 (*this)->stataddr = stataddr;
252 (*this)->stataddrprefixlen = stataddrprefixlen;
254 (*this)->listsize += listsize;
255 if (!((*this)->member = calloc(sizeof(struct ippoolm_t), listsize))) {
256 SYS_ERR(DIP, LOGL_ERROR, 0,
257 "Failed to allocate memory for members in ippool");
258 return -1;
261 for ((*this)->hashlog = 0;
262 ((1 << (*this)->hashlog) < listsize); (*this)->hashlog++) ;
264 /* printf ("Hashlog %d %d %d\n", (*this)->hashlog, listsize, (1 << (*this)->hashlog)); */
266 /* Determine hashsize */
267 (*this)->hashsize = 1 << (*this)->hashlog; /* Fails if mask=0: All Internet */
268 (*this)->hashmask = (*this)->hashsize - 1;
270 /* Allocate hash table */
271 if (!
272 ((*this)->hash =
273 calloc(sizeof(struct ippoolm_t), (*this)->hashsize))) {
274 SYS_ERR(DIP, LOGL_ERROR, 0,
275 "Failed to allocate memory for hash members in ippool");
276 return -1;
279 (*this)->firstdyn = NULL;
280 (*this)->lastdyn = NULL;
281 if (flags & IPPOOL_NOGATEWAY) {
282 in46a_inc(&addr);
283 in46a_inc(&addr);
284 } else if (flags & IPPOOL_NONETWORK) {
285 in46a_inc(&addr);
287 for (i = 0; i < dynsize; i++) {
288 (*this)->member[i].addr = addr;
289 in46a_inc(&addr);
291 (*this)->member[i].inuse = 0;
292 (*this)->member[i].pool = *this;
294 /* Insert into list of unused */
295 (*this)->member[i].prev = (*this)->lastdyn;
296 if ((*this)->lastdyn) {
297 (*this)->lastdyn->next = &((*this)->member[i]);
298 } else {
299 (*this)->firstdyn = &((*this)->member[i]);
301 (*this)->lastdyn = &((*this)->member[i]);
302 (*this)->member[i].next = NULL; /* Redundant */
304 (void)ippool_hashadd(*this, &(*this)->member[i]);
307 (*this)->firststat = NULL;
308 (*this)->laststat = NULL;
309 for (i = dynsize; i < listsize; i++) {
310 struct in46_addr *i6al = &(*this)->member[i].addr;
311 memset(i6al, 0, sizeof(*i6al));
312 (*this)->member[i].inuse = 0;
313 (*this)->member[i].pool = *this;
315 /* Insert into list of unused */
316 (*this)->member[i].prev = (*this)->laststat;
317 if ((*this)->laststat) {
318 (*this)->laststat->next = &((*this)->member[i]);
319 } else {
320 (*this)->firststat = &((*this)->member[i]);
322 (*this)->laststat = &((*this)->member[i]);
323 (*this)->member[i].next = NULL; /* Redundant */
326 if (0)
327 (void)ippool_printaddr(*this);
328 return 0;
331 /* Delete existing address pool */
332 int ippool_free(struct ippool_t *this)
334 free(this->hash);
335 free(this->member);
336 free(this);
337 return 0; /* Always OK */
340 /* Find an IP address in the pool */
341 int ippool_getip(struct ippool_t *this, struct ippoolm_t **member,
342 struct in46_addr *addr)
344 struct ippoolm_t *p;
345 uint32_t hash;
347 /* Find in hash table */
348 hash = ippool_hash(addr) & this->hashmask;
349 for (p = this->hash[hash]; p; p = p->nexthash) {
350 if (in46a_prefix_equal(&p->addr, addr)) {
351 if (member)
352 *member = p;
353 return 0;
356 if (member)
357 *member = NULL;
358 /*SYS_ERR(DIP, LOGL_ERROR, 0, "Address could not be found"); */
359 return -1;
363 * ippool_newip
364 * Get an IP address. If addr = 0.0.0.0 get a dynamic IP address. Otherwise
365 * check to see if the given address is available. If available within
366 * dynamic address space allocate it there, otherwise allocate within static
367 * address space.
369 int ippool_newip(struct ippool_t *this, struct ippoolm_t **member,
370 struct in46_addr *addr, int statip)
372 struct ippoolm_t *p;
373 struct ippoolm_t *p2 = NULL;
374 uint32_t hash;
376 /* If static:
377 * Look in dynaddr.
378 * If found remove from firstdyn/lastdyn linked list.
379 * Else allocate from stataddr.
380 * Remove from firststat/laststat linked list.
381 * Insert into hash table.
383 * If dynamic
384 * Remove from firstdyn/lastdyn linked list.
388 if (0)
389 (void)ippool_printaddr(this);
391 int specified = 0;
392 if (addr) {
393 if (addr->len == 4 && addr->v4.s_addr)
394 specified = 1;
395 if (addr->len == 16 && !IN6_IS_ADDR_UNSPECIFIED(&addr->v6))
396 specified = 1;
399 /* First check to see if this type of address is allowed */
400 if (specified && statip) { /* IP address given */
401 if (!this->allowstat) {
402 SYS_ERR(DIP, LOGL_ERROR, 0,
403 "Static IP address not allowed");
404 return -GTPCAUSE_NOT_SUPPORTED;
406 if (!in46a_within_mask(addr, &this->stataddr, this->stataddrprefixlen)) {
407 SYS_ERR(DIP, LOGL_ERROR, 0, "Static out of range");
408 return -1;
410 } else {
411 if (!this->allowdyn) {
412 SYS_ERR(DIP, LOGL_ERROR, 0,
413 "Dynamic IP address not allowed");
414 return -GTPCAUSE_NOT_SUPPORTED;
418 /* If IP address given try to find it in dynamic address pool */
419 if (specified) { /* IP address given */
420 /* Find in hash table */
421 hash = ippool_hash(addr) & this->hashmask;
422 for (p = this->hash[hash]; p; p = p->nexthash) {
423 if (in46a_prefix_equal(&p->addr, addr)) {
424 p2 = p;
425 break;
430 /* If IP was already allocated we can not use it */
431 if ((!statip) && (p2) && (p2->inuse)) {
432 p2 = NULL;
435 /* If not found yet and dynamic IP then allocate dynamic IP */
436 if ((!p2) && (!statip)) {
437 if (!this->firstdyn) {
438 SYS_ERR(DIP, LOGL_ERROR, 0,
439 "No more IP addresses available");
440 return -GTPCAUSE_ADDR_OCCUPIED;
441 } else
442 p2 = this->firstdyn;
445 if (p2) { /* Was allocated from dynamic address pool */
446 if (p2->inuse) {
447 SYS_ERR(DIP, LOGL_ERROR, 0,
448 "IP address allready in use");
449 return -GTPCAUSE_SYS_FAIL; /* Allready in use / Should not happen */
452 if (p2->addr.len != addr->len && !(addr->len == 16 && p2->addr.len == 8)) {
453 SYS_ERR(DIP, LOGL_ERROR, 0, "MS requested unsupported PDP context type");
454 return -GTPCAUSE_UNKNOWN_PDP;
457 /* Remove from linked list of free dynamic addresses */
458 if (p2->prev)
459 p2->prev->next = p2->next;
460 else
461 this->firstdyn = p2->next;
462 if (p2->next)
463 p2->next->prev = p2->prev;
464 else
465 this->lastdyn = p2->prev;
466 p2->next = NULL;
467 p2->prev = NULL;
468 p2->inuse = 1; /* Dynamic address in use */
470 *member = p2;
471 if (0)
472 (void)ippool_printaddr(this);
473 return 0; /* Success */
476 /* It was not possible to allocate from dynamic address pool */
477 /* Try to allocate from static address space */
479 if (specified && (statip)) { /* IP address given */
480 if (!this->firststat) {
481 SYS_ERR(DIP, LOGL_ERROR, 0,
482 "No more IP addresses available");
483 return -GTPCAUSE_ADDR_OCCUPIED; /* No more available */
484 } else
485 p2 = this->firststat;
487 if (p2->addr.len != addr->len) {
488 SYS_ERR(DIP, LOGL_ERROR, 0, "MS requested unsupported PDP context type");
489 return -GTPCAUSE_UNKNOWN_PDP;
492 /* Remove from linked list of free static addresses */
493 if (p2->prev)
494 p2->prev->next = p2->next;
495 else
496 this->firststat = p2->next;
497 if (p2->next)
498 p2->next->prev = p2->prev;
499 else
500 this->laststat = p2->prev;
501 p2->next = NULL;
502 p2->prev = NULL;
503 p2->inuse = 2; /* Static address in use */
504 memcpy(&p2->addr, addr, sizeof(addr));
505 *member = p2;
506 (void)ippool_hashadd(this, *member);
507 if (0)
508 (void)ippool_printaddr(this);
509 return 0; /* Success */
512 SYS_ERR(DIP, LOGL_ERROR, 0,
513 "Could not allocate IP address");
514 return -GTPCAUSE_SYS_FAIL; /* Should never get here. TODO: Bad code */
517 int ippool_freeip(struct ippool_t *this, struct ippoolm_t *member)
520 if (0)
521 (void)ippool_printaddr(this);
523 if (!member->inuse) {
524 SYS_ERR(DIP, LOGL_ERROR, 0, "Address not in use");
525 return -1; /* Not in use: Should not happen */
528 switch (member->inuse) {
529 case 0: /* Not in use: Should not happen */
530 SYS_ERR(DIP, LOGL_ERROR, 0, "Address not in use");
531 return -1;
532 case 1: /* Allocated from dynamic address space */
533 /* Insert into list of unused */
534 member->prev = this->lastdyn;
535 if (this->lastdyn) {
536 this->lastdyn->next = member;
537 } else {
538 this->firstdyn = member;
540 this->lastdyn = member;
542 member->inuse = 0;
543 member->peer = NULL;
544 if (0)
545 (void)ippool_printaddr(this);
546 return 0;
547 case 2: /* Allocated from static address space */
548 if (ippool_hashdel(this, member))
549 return -1;
550 /* Insert into list of unused */
551 member->prev = this->laststat;
552 if (this->laststat) {
553 this->laststat->next = member;
554 } else {
555 this->firststat = member;
557 this->laststat = member;
559 member->inuse = 0;
560 memset(&member->addr, 0, sizeof(member->addr));
561 member->peer = NULL;
562 member->nexthash = NULL;
563 if (0)
564 (void)ippool_printaddr(this);
565 return 0;
566 default: /* Should not happen */
567 SYS_ERR(DIP, LOGL_ERROR, 0,
568 "Could not free IP address");
569 return -1;