| /* |
| * IP address pool functions. |
| * Copyright (C) 2003, 2004 Mondru AB. |
| * Copyright (C) 2017 by Harald Welte <laforge@gnumonks.org> |
| * |
| * The contents of this file may be used under the terms of the GNU |
| * General Public License Version 2, provided that the above copyright |
| * notice and this permission notice is included in all copies or |
| * substantial portions of the software. |
| * |
| */ |
| |
| #include <sys/types.h> |
| #include <netinet/in.h> /* in_addr */ |
| #include <stdlib.h> /* calloc */ |
| #include <stdio.h> /* sscanf */ |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #include "syserr.h" |
| #include "ippool.h" |
| #include "lookup.h" |
| |
| int ippool_printaddr(struct ippool_t *this) |
| { |
| unsigned int n; |
| printf("ippool_printaddr\n"); |
| printf("Firstdyn %td\n", this->firstdyn - this->member); |
| printf("Lastdyn %td\n", this->lastdyn - this->member); |
| printf("Firststat %td\n", this->firststat - this->member); |
| printf("Laststat %td\n", this->laststat - this->member); |
| printf("Listsize %u\n", this->listsize); |
| |
| for (n = 0; n < this->listsize; n++) { |
| char s[256]; |
| in46a_ntop(&this->member[n].addr, s, sizeof(s)); |
| printf("Unit %d inuse %d prev %td next %td addr %s\n", |
| n, |
| this->member[n].inuse, |
| this->member[n].prev - this->member, |
| this->member[n].next - this->member, |
| s); |
| } |
| return 0; |
| } |
| |
| int ippool_hashadd(struct ippool_t *this, struct ippoolm_t *member) |
| { |
| uint32_t hash; |
| struct ippoolm_t *p; |
| struct ippoolm_t *p_prev = NULL; |
| |
| /* Insert into hash table */ |
| hash = ippool_hash(&member->addr) & this->hashmask; |
| for (p = this->hash[hash]; p; p = p->nexthash) |
| p_prev = p; |
| if (!p_prev) |
| this->hash[hash] = member; |
| else |
| p_prev->nexthash = member; |
| return 0; /* Always OK to insert */ |
| } |
| |
| int ippool_hashdel(struct ippool_t *this, struct ippoolm_t *member) |
| { |
| uint32_t hash; |
| struct ippoolm_t *p; |
| struct ippoolm_t *p_prev = NULL; |
| |
| /* Find in hash table */ |
| hash = ippool_hash(&member->addr) & this->hashmask; |
| for (p = this->hash[hash]; p; p = p->nexthash) { |
| if (p == member) { |
| break; |
| } |
| p_prev = p; |
| } |
| |
| if (p != member) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "ippool_hashdel: Tried to delete member not in hash table"); |
| return -1; |
| } |
| |
| if (!p_prev) |
| this->hash[hash] = p->nexthash; |
| else |
| p_prev->nexthash = p->nexthash; |
| |
| return 0; |
| } |
| |
| static unsigned long int ippool_hash4(struct in_addr *addr) |
| { |
| return lookup((unsigned char *)&addr->s_addr, sizeof(addr->s_addr), 0); |
| } |
| |
| static unsigned long int ippool_hash6(struct in6_addr *addr, unsigned int len) |
| { |
| /* TODO: Review hash spread for IPv6 */ |
| return lookup((unsigned char *)addr->s6_addr, len, 0); |
| } |
| |
| unsigned long int ippool_hash(struct in46_addr *addr) |
| { |
| if (addr->len == 4) |
| return ippool_hash4(&addr->v4); |
| else |
| return ippool_hash6(&addr->v6, addr->len); |
| } |
| |
| /* Get IP address and mask */ |
| int ippool_aton(struct in46_addr *addr, size_t *prefixlen, const char *pool_in, int number) |
| { |
| struct addrinfo *ai; |
| struct addrinfo hints = { |
| .ai_family = AF_UNSPEC, |
| .ai_socktype = SOCK_DGRAM, |
| .ai_flags = 0, |
| .ai_protocol = 0 |
| }; |
| char pool[strlen(pool_in)+1]; |
| |
| strcpy(pool, pool_in); |
| |
| int err; |
| |
| /* Find '/' and point to first char after it */ |
| char *prefixlen_str = strchr(pool, '/'); |
| if (prefixlen_str) { |
| *prefixlen_str = '\0'; |
| prefixlen_str++; |
| if (*prefixlen_str == '\0') { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Empty prefix length specified"); |
| return -1; |
| } |
| } |
| |
| /* convert address */ |
| if ((err = getaddrinfo(pool, NULL, &hints, &ai))) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Bad address"); |
| return -1; |
| } |
| |
| /* Copy address, set lengths */ |
| if (ai->ai_family == AF_INET) { |
| *prefixlen = 32; |
| addr->len = sizeof(struct in_addr); |
| addr->v4 = ((struct sockaddr_in*)ai->ai_addr)->sin_addr; |
| } else { |
| *prefixlen = 128; |
| addr->len = sizeof(struct in6_addr); |
| addr->v6 = ((struct sockaddr_in6*)ai->ai_addr)->sin6_addr; |
| } |
| freeaddrinfo(ai); |
| |
| /* parse prefixlen */ |
| if (prefixlen_str) { |
| char *e; |
| *prefixlen = strtol(prefixlen_str, &e, 10); |
| if (*e != '\0') { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Prefixlen is not an int"); |
| return -1; |
| } |
| } |
| |
| if (*prefixlen > (addr->len * 8)) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Perfixlen too big"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* Increase IPv4/IPv6 address by 1 */ |
| void in46a_inc(struct in46_addr *addr) |
| { |
| size_t addrlen; |
| uint8_t *a = (uint8_t *)&addr->v6; |
| for (addrlen = addr->len; addrlen > 0; addrlen--) { |
| if (++a[addrlen-1]) |
| break; |
| } |
| } |
| |
| static bool addr_in_prefix_list(struct in46_addr *addr, struct in46_prefix *list, size_t list_size) |
| { |
| int i; |
| for (i = 0; i < list_size; i++) { |
| if (in46a_prefix_equal(addr, &list[i].addr)) |
| return true; |
| } |
| return false; |
| } |
| |
| /* Create new address pool */ |
| int ippool_new(struct ippool_t **this, const struct in46_prefix *dyn, const struct in46_prefix *stat, |
| int flags, struct in46_prefix *blacklist, size_t blacklist_size) |
| { |
| |
| /* Parse only first instance of pool for now */ |
| |
| int i; |
| struct in46_addr addr = { 0 }; |
| size_t addrprefixlen; |
| struct in46_addr stataddr; |
| size_t stataddrprefixlen; |
| int listsize; |
| int dynsize; |
| unsigned int statsize; |
| |
| if (!dyn || dyn->addr.len == 0) { |
| dynsize = 0; |
| } else { |
| addr = dyn->addr; |
| addrprefixlen = dyn->prefixlen; |
| /* we want to work with /64 prefixes, i.e. allocate /64 prefixes rather |
| * than /128 (single IPv6 addresses) */ |
| if (addr.len == sizeof(struct in6_addr)) |
| addr.len = 64/8; |
| |
| dynsize = (1 << (addr.len*8 - addrprefixlen)); |
| if (flags & IPPOOL_NONETWORK) /* Exclude network address from pool */ |
| dynsize--; |
| if (flags & IPPOOL_NOBROADCAST) /* Exclude broadcast address from pool */ |
| dynsize--; |
| /* Exclude included blacklist addresses from pool */ |
| for (i = 0; i < blacklist_size; i++) { |
| if (in46a_within_mask(&blacklist[i].addr, &addr, addrprefixlen)) |
| dynsize--; |
| } |
| } |
| |
| if (!stat || stat->addr.len == 0) { |
| statsize = 0; |
| stataddr.len = 0; |
| stataddrprefixlen = 0; |
| } else { |
| stataddr = stat->addr; |
| stataddrprefixlen = stat->prefixlen; |
| |
| statsize = (1 << (stataddr.len*8 - stataddrprefixlen)); |
| if (statsize > IPPOOL_STATSIZE) |
| statsize = IPPOOL_STATSIZE; |
| } |
| |
| listsize = dynsize + statsize; /* Allocate space for static IP addresses */ |
| |
| if (!(*this = calloc(sizeof(struct ippool_t), 1))) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Failed to allocate memory for ippool"); |
| return -1; |
| } |
| |
| (*this)->allowdyn = dyn ? 1 : 0; |
| (*this)->allowstat = stat ? 1 : 0; |
| if (stataddr.len > 0) |
| (*this)->stataddr = stataddr; |
| (*this)->stataddrprefixlen = stataddrprefixlen; |
| |
| (*this)->listsize += listsize; |
| if (!((*this)->member = calloc(sizeof(struct ippoolm_t), listsize))) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Failed to allocate memory for members in ippool"); |
| return -1; |
| } |
| |
| for ((*this)->hashlog = 0; |
| ((1 << (*this)->hashlog) < listsize); (*this)->hashlog++) ; |
| |
| /* printf ("Hashlog %d %d %d\n", (*this)->hashlog, listsize, (1 << (*this)->hashlog)); */ |
| |
| /* Determine hashsize */ |
| (*this)->hashsize = 1 << (*this)->hashlog; /* Fails if mask=0: All Internet */ |
| (*this)->hashmask = (*this)->hashsize - 1; |
| |
| /* Allocate hash table */ |
| (*this)->hash = calloc((*this)->hashsize, sizeof(struct ippoolm_t *)); |
| if (!(*this)->hash) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Failed to allocate memory for hash members in ippool"); |
| return -1; |
| } |
| |
| (*this)->firstdyn = NULL; |
| (*this)->lastdyn = NULL; |
| if (flags & IPPOOL_NONETWORK) { |
| in46a_inc(&addr); |
| } |
| for (i = 0; i < dynsize; i++) { |
| if (addr_in_prefix_list(&addr, blacklist, blacklist_size)) { |
| SYS_ERR(DIP, LOGL_DEBUG, 0, |
| "addr blacklisted from pool: %s", in46a_ntoa(&addr)); |
| in46a_inc(&addr); |
| i--; |
| continue; |
| } |
| (*this)->member[i].addr = addr; |
| in46a_inc(&addr); |
| |
| (*this)->member[i].inuse = 0; |
| (*this)->member[i].pool = *this; |
| |
| /* Insert into list of unused */ |
| (*this)->member[i].prev = (*this)->lastdyn; |
| if ((*this)->lastdyn) { |
| (*this)->lastdyn->next = &((*this)->member[i]); |
| } else { |
| (*this)->firstdyn = &((*this)->member[i]); |
| } |
| (*this)->lastdyn = &((*this)->member[i]); |
| (*this)->member[i].next = NULL; /* Redundant */ |
| |
| (void)ippool_hashadd(*this, &(*this)->member[i]); |
| } |
| |
| (*this)->firststat = NULL; |
| (*this)->laststat = NULL; |
| for (i = dynsize; i < listsize; i++) { |
| struct in46_addr *i6al = &(*this)->member[i].addr; |
| memset(i6al, 0, sizeof(*i6al)); |
| (*this)->member[i].inuse = 0; |
| (*this)->member[i].pool = *this; |
| |
| /* Insert into list of unused */ |
| (*this)->member[i].prev = (*this)->laststat; |
| if ((*this)->laststat) { |
| (*this)->laststat->next = &((*this)->member[i]); |
| } else { |
| (*this)->firststat = &((*this)->member[i]); |
| } |
| (*this)->laststat = &((*this)->member[i]); |
| (*this)->member[i].next = NULL; /* Redundant */ |
| } |
| |
| if (0) |
| (void)ippool_printaddr(*this); |
| return 0; |
| } |
| |
| /* Delete existing address pool */ |
| int ippool_free(struct ippool_t *this) |
| { |
| free(this->hash); |
| free(this->member); |
| free(this); |
| return 0; /* Always OK */ |
| } |
| |
| /* Find an IP address in the pool */ |
| int ippool_getip(struct ippool_t *this, struct ippoolm_t **member, |
| struct in46_addr *addr) |
| { |
| struct ippoolm_t *p; |
| uint32_t hash; |
| |
| /* Find in hash table */ |
| hash = ippool_hash(addr) & this->hashmask; |
| for (p = this->hash[hash]; p; p = p->nexthash) { |
| if (in46a_prefix_equal(&p->addr, addr)) { |
| if (member) |
| *member = p; |
| return 0; |
| } |
| } |
| if (member) |
| *member = NULL; |
| /*SYS_ERR(DIP, LOGL_ERROR, 0, "Address could not be found"); */ |
| return -1; |
| } |
| |
| /** |
| * ippool_newip |
| * Get an IP address. If addr = 0.0.0.0 get a dynamic IP address. Otherwise |
| * check to see if the given address is available. If available within |
| * dynamic address space allocate it there, otherwise allocate within static |
| * address space. |
| **/ |
| int ippool_newip(struct ippool_t *this, struct ippoolm_t **member, |
| struct in46_addr *addr, int statip) |
| { |
| struct ippoolm_t *p; |
| struct ippoolm_t *p2 = NULL; |
| uint32_t hash; |
| |
| /* If static: |
| * Look in dynaddr. |
| * If found remove from firstdyn/lastdyn linked list. |
| * Else allocate from stataddr. |
| * Remove from firststat/laststat linked list. |
| * Insert into hash table. |
| * |
| * If dynamic |
| * Remove from firstdyn/lastdyn linked list. |
| * |
| */ |
| |
| if (0) |
| (void)ippool_printaddr(this); |
| |
| int specified = 0; |
| if (addr) { |
| if (addr->len == 4 && addr->v4.s_addr) |
| specified = 1; |
| if (addr->len == 16 && !IN6_IS_ADDR_UNSPECIFIED(&addr->v6)) |
| specified = 1; |
| } |
| |
| /* First check to see if this type of address is allowed */ |
| if (specified && statip) { /* IP address given */ |
| if (!this->allowstat) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Static IP address not allowed"); |
| return -GTPCAUSE_NOT_SUPPORTED; |
| } |
| if (!in46a_within_mask(addr, &this->stataddr, this->stataddrprefixlen)) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Static out of range"); |
| return -1; |
| } |
| } else { |
| if (!this->allowdyn) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Dynamic IP address not allowed"); |
| return -GTPCAUSE_NOT_SUPPORTED; |
| } |
| } |
| |
| /* If IP address given try to find it in dynamic address pool */ |
| if (specified) { /* IP address given */ |
| /* Find in hash table */ |
| hash = ippool_hash(addr) & this->hashmask; |
| for (p = this->hash[hash]; p; p = p->nexthash) { |
| if (in46a_prefix_equal(&p->addr, addr)) { |
| p2 = p; |
| break; |
| } |
| } |
| } |
| |
| /* If IP was already allocated we can not use it */ |
| if ((!statip) && (p2) && (p2->inuse)) { |
| p2 = NULL; |
| } |
| |
| /* If not found yet and dynamic IP then allocate dynamic IP */ |
| if ((!p2) && (!statip)) { |
| if (!this->firstdyn) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "No more IP addresses available"); |
| return -GTPCAUSE_ADDR_OCCUPIED; |
| } else |
| p2 = this->firstdyn; |
| } |
| |
| if (p2) { /* Was allocated from dynamic address pool */ |
| if (p2->inuse) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "IP address allready in use"); |
| return -GTPCAUSE_SYS_FAIL; /* Allready in use / Should not happen */ |
| } |
| |
| if (p2->addr.len != addr->len && !(addr->len == 16 && p2->addr.len == 8)) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "MS requested unsupported PDP context type"); |
| return -GTPCAUSE_UNKNOWN_PDP; |
| } |
| |
| /* Remove from linked list of free dynamic addresses */ |
| if (p2->prev) |
| p2->prev->next = p2->next; |
| else |
| this->firstdyn = p2->next; |
| if (p2->next) |
| p2->next->prev = p2->prev; |
| else |
| this->lastdyn = p2->prev; |
| p2->next = NULL; |
| p2->prev = NULL; |
| p2->inuse = 1; /* Dynamic address in use */ |
| |
| *member = p2; |
| if (0) |
| (void)ippool_printaddr(this); |
| return 0; /* Success */ |
| } |
| |
| /* It was not possible to allocate from dynamic address pool */ |
| /* Try to allocate from static address space */ |
| |
| if (specified && (statip)) { /* IP address given */ |
| if (!this->firststat) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "No more IP addresses available"); |
| return -GTPCAUSE_ADDR_OCCUPIED; /* No more available */ |
| } else |
| p2 = this->firststat; |
| |
| if (p2->addr.len != addr->len) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "MS requested unsupported PDP context type"); |
| return -GTPCAUSE_UNKNOWN_PDP; |
| } |
| |
| /* Remove from linked list of free static addresses */ |
| if (p2->prev) |
| p2->prev->next = p2->next; |
| else |
| this->firststat = p2->next; |
| if (p2->next) |
| p2->next->prev = p2->prev; |
| else |
| this->laststat = p2->prev; |
| p2->next = NULL; |
| p2->prev = NULL; |
| p2->inuse = 2; /* Static address in use */ |
| /* p2->addr.len and addr->len already match (see above). */ |
| if (p2->addr.len == sizeof(struct in_addr)) |
| p2->addr.v4 = addr->v4; |
| else if (p2->addr.len == sizeof(struct in6_addr)) |
| p2->addr.v6 = addr->v6; |
| else { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "MS requested unsupported PDP context type"); |
| return -GTPCAUSE_UNKNOWN_PDP; |
| } |
| *member = p2; |
| (void)ippool_hashadd(this, *member); |
| if (0) |
| (void)ippool_printaddr(this); |
| return 0; /* Success */ |
| } |
| |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Could not allocate IP address"); |
| return -GTPCAUSE_SYS_FAIL; /* Should never get here. TODO: Bad code */ |
| } |
| |
| int ippool_freeip(struct ippool_t *this, struct ippoolm_t *member) |
| { |
| |
| if (0) |
| (void)ippool_printaddr(this); |
| |
| if (!member->inuse) { |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Address not in use"); |
| return -1; /* Not in use: Should not happen */ |
| } |
| |
| switch (member->inuse) { |
| case 0: /* Not in use: Should not happen */ |
| SYS_ERR(DIP, LOGL_ERROR, 0, "Address not in use"); |
| return -1; |
| case 1: /* Allocated from dynamic address space */ |
| /* Insert into list of unused */ |
| member->prev = this->lastdyn; |
| if (this->lastdyn) { |
| this->lastdyn->next = member; |
| } else { |
| this->firstdyn = member; |
| } |
| this->lastdyn = member; |
| |
| member->inuse = 0; |
| member->peer = NULL; |
| if (0) |
| (void)ippool_printaddr(this); |
| return 0; |
| case 2: /* Allocated from static address space */ |
| if (ippool_hashdel(this, member)) |
| return -1; |
| /* Insert into list of unused */ |
| member->prev = this->laststat; |
| if (this->laststat) { |
| this->laststat->next = member; |
| } else { |
| this->firststat = member; |
| } |
| this->laststat = member; |
| |
| member->inuse = 0; |
| memset(&member->addr, 0, sizeof(member->addr)); |
| member->peer = NULL; |
| member->nexthash = NULL; |
| if (0) |
| (void)ippool_printaddr(this); |
| return 0; |
| default: /* Should not happen */ |
| SYS_ERR(DIP, LOGL_ERROR, 0, |
| "Could not free IP address"); |
| return -1; |
| } |
| } |