| /* |
| * OsmoGGSN - Gateway GPRS Support Node |
| * Copyright (C) 2002, 2003, 2004 Mondru AB. |
| * Copyright (C) 2017 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. |
| * |
| */ |
| |
| /* |
| * pdp.c: |
| * |
| */ |
| |
| #include <../config.h> |
| |
| #include <osmocom/core/logging.h> |
| |
| #ifdef HAVE_STDINT_H |
| #include <stdint.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <netinet/in.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include "pdp.h" |
| #include "gtp.h" |
| #include "lookupa.h" |
| |
| /* *********************************************************** |
| * Global variables TODO: most should be moved to gsn_t |
| *************************************************************/ |
| |
| static struct pdp_t pdpa[PDP_MAX]; /* PDP storage */ |
| static struct pdp_t *hashtid[PDP_MAX]; /* Hash table for IMSI + NSAPI */ |
| /* struct pdp_t* haship[PDP_MAX]; Hash table for IP and network interface */ |
| |
| /* *********************************************************** |
| * Functions related to PDP storage |
| * |
| * Lifecycle |
| * For a GGSN pdp context life begins with the reception of a |
| * create pdp context request. It normally ends with the reception |
| * of a delete pdp context request, but will also end with the |
| * reception of an error indication message. |
| * Provisions should probably be made for terminating pdp contexts |
| * based on either idle timeout, or by sending downlink probe |
| * messages (ping?) to see if the MS is still responding. |
| * |
| * For an SGSN pdp context life begins with the application just |
| * before sending off a create pdp context request. It normally |
| * ends when a delete pdp context response message is received |
| * from the GGSN, but should also end when with the reception of |
| * an error indication message. |
| * |
| * |
| * HASH Tables |
| * |
| * Downlink packets received in the GGSN are identified only by their |
| * network interface together with their destination IP address (Two |
| * network interfaces can use the same private IP address). Each IMSI |
| * (mobile station) can have several PDP contexts using the same IP |
| * address. In this case the traffic flow template (TFT) is used to |
| * determine the correct PDP context for a particular IMSI. Also it |
| * should be possible for each PDP context to use several IP adresses |
| * For fixed wireless access a mobile station might need a full class |
| * C network. Even in the case of several IP adresses the PDP context |
| * should be determined on the basis of the network IP address. |
| * Thus we need a hash table based on network interface + IP address. |
| * |
| * Uplink packets are for GTP0 identified by their IMSI and NSAPI, which |
| * is collectively called the tunnel identifier. There is also a 16 bit |
| * flow label that can be used for identification of uplink packets. This |
| * however is quite useless as it limits the number of contexts to 65536. |
| * For GTP1 uplink packets are identified by a Tunnel Endpoint Identifier |
| * (32 bit), or in some cases by the combination of IMSI and NSAPI. |
| * For GTP1 delete context requests there is a need to find the PDP |
| * contexts with the same IP address. This however can be done by using |
| * the IP hash table. |
| * Thus we need a hash table based on TID (IMSI and NSAPI). The TEID will |
| * be used for directly addressing the PDP context. |
| |
| * pdp_newpdp |
| * Gives you a pdp context with no hash references In some way |
| * this should have a limited lifetime. |
| * |
| * pdp_freepdp |
| * Frees a context that was previously allocated with |
| * pdp_newpdp |
| * |
| * |
| * pdp_getpdpIP |
| * An incoming IP packet is uniquely identified by a pointer |
| * to a network connection (void *) and an IP address |
| * (struct in_addr) |
| * |
| * pdp_getpdpGTP |
| * An incoming GTP packet is uniquely identified by a the |
| * TID (imsi + nsapi (8 octets)) in or by the Flow Label |
| * (2 octets) in gtp0 or by the Tunnel Endpoint Identifier |
| * (4 octets) in gtp1. |
| * |
| * This leads to an architecture where the receiving GSN |
| * chooses a Flow Label or a Tunnel Endpoint Identifier |
| * when the connection is setup. |
| * Thus no hash table is needed for GTP lookups. |
| * |
| *************************************************************/ |
| |
| int pdp_init() |
| { |
| memset(&pdpa, 0, sizeof(pdpa)); |
| memset(&hashtid, 0, sizeof(hashtid)); |
| /* memset(&haship, 0, sizeof(haship)); */ |
| |
| return 0; |
| } |
| |
| int pdp_newpdp(struct pdp_t **pdp, uint64_t imsi, uint8_t nsapi, |
| struct pdp_t *pdp_old) |
| { |
| int n; |
| for (n = 0; n < PDP_MAX; n++) { /* TODO: Need to do better than linear search */ |
| if (pdpa[n].inuse == 0) { |
| *pdp = &pdpa[n]; |
| if (NULL != pdp_old) |
| memcpy(*pdp, pdp_old, sizeof(struct pdp_t)); |
| else |
| memset(*pdp, 0, sizeof(struct pdp_t)); |
| (*pdp)->inuse = 1; |
| (*pdp)->imsi = imsi; |
| (*pdp)->nsapi = nsapi; |
| (*pdp)->fllc = (uint16_t) n + 1; |
| (*pdp)->fllu = (uint16_t) n + 1; |
| (*pdp)->teid_own = (uint32_t) n + 1; |
| if (!(*pdp)->secondary) |
| (*pdp)->teic_own = (uint32_t) n + 1; |
| pdp_tidset(*pdp, pdp_gettid(imsi, nsapi)); |
| |
| /* Insert reference in primary context */ |
| if (((*pdp)->teic_own > 0) |
| && ((*pdp)->teic_own <= PDP_MAX)) { |
| pdpa[(*pdp)->teic_own - |
| 1].secondary_tei[(*pdp)->nsapi & 0x0f] = |
| (*pdp)->teid_own; |
| } |
| |
| return 0; |
| } |
| } |
| return EOF; /* No more available */ |
| } |
| |
| int pdp_freepdp(struct pdp_t *pdp) |
| { |
| pdp_tiddel(pdp); |
| |
| /* Remove any references in primary context */ |
| if ((pdp->secondary) && (pdp->teic_own > 0) |
| && (pdp->teic_own <= PDP_MAX)) { |
| pdpa[pdp->teic_own - 1].secondary_tei[pdp->nsapi & 0x0f] = 0; |
| } |
| |
| memset(pdp, 0, sizeof(struct pdp_t)); |
| return 0; |
| } |
| |
| int pdp_getpdp(struct pdp_t **pdp) |
| { |
| *pdp = &pdpa[0]; |
| return 0; |
| } |
| |
| int pdp_getgtp0(struct pdp_t **pdp, uint16_t fl) |
| { |
| if ((fl > PDP_MAX) || (fl < 1)) { |
| return EOF; /* Not found */ |
| } else { |
| *pdp = &pdpa[fl - 1]; |
| if ((*pdp)->inuse) |
| return 0; |
| else |
| return EOF; |
| /* Context exists. We do no further validity checking. */ |
| } |
| } |
| |
| int pdp_getgtp1(struct pdp_t **pdp, uint32_t tei) |
| { |
| if ((tei > PDP_MAX) || (tei < 1)) { |
| return EOF; /* Not found */ |
| } else { |
| *pdp = &pdpa[tei - 1]; |
| if ((*pdp)->inuse) |
| return 0; |
| else |
| return EOF; |
| /* Context exists. We do no further validity checking. */ |
| } |
| } |
| |
| /* get a PDP based on the *peer* address + TEI-Data. Used for matching inbound Error Ind */ |
| int pdp_getgtp1_peer_d(struct pdp_t **pdp, const struct sockaddr_in *peer, uint32_t teid_gn) |
| { |
| unsigned int i; |
| |
| /* this is O(n) but we don't have (nor want) another hash... */ |
| for (i = 0; i < PDP_MAX; i++) { |
| struct pdp_t *candidate = &pdpa[i]; |
| if (candidate->inuse && candidate->teid_gn == teid_gn && |
| candidate->gsnru.l == sizeof(peer->sin_addr) && |
| !memcmp(&peer->sin_addr, candidate->gsnru.v, sizeof(peer->sin_addr))) { |
| *pdp = &pdpa[i]; |
| return 0; |
| } |
| } |
| return EOF; |
| } |
| |
| int pdp_tidhash(uint64_t tid) |
| { |
| return (lookup(&tid, sizeof(tid), 0) % PDP_MAX); |
| } |
| |
| int pdp_tidset(struct pdp_t *pdp, uint64_t tid) |
| { |
| int hash = pdp_tidhash(tid); |
| struct pdp_t *pdp2; |
| struct pdp_t *pdp_prev = NULL; |
| DEBUGP(DLGTP, "Begin pdp_tidset tid = %"PRIx64"\n", tid); |
| pdp->tidnext = NULL; |
| pdp->tid = tid; |
| for (pdp2 = hashtid[hash]; pdp2; pdp2 = pdp2->tidnext) |
| pdp_prev = pdp2; |
| if (!pdp_prev) |
| hashtid[hash] = pdp; |
| else |
| pdp_prev->tidnext = pdp; |
| DEBUGP(DLGTP, "End pdp_tidset\n"); |
| return 0; |
| } |
| |
| int pdp_tiddel(struct pdp_t *pdp) |
| { |
| int hash = pdp_tidhash(pdp->tid); |
| struct pdp_t *pdp2; |
| struct pdp_t *pdp_prev = NULL; |
| DEBUGP(DLGTP, "Begin pdp_tiddel tid = %"PRIx64"\n", pdp->tid); |
| for (pdp2 = hashtid[hash]; pdp2; pdp2 = pdp2->tidnext) { |
| if (pdp2 == pdp) { |
| if (!pdp_prev) |
| hashtid[hash] = pdp2->tidnext; |
| else |
| pdp_prev->tidnext = pdp2->tidnext; |
| DEBUGP(DLGTP, "End pdp_tiddel: PDP found\n"); |
| return 0; |
| } |
| pdp_prev = pdp2; |
| } |
| DEBUGP(DLGTP, "End pdp_tiddel: PDP not found\n"); |
| return EOF; /* End of linked list and not found */ |
| } |
| |
| int pdp_tidget(struct pdp_t **pdp, uint64_t tid) |
| { |
| int hash = pdp_tidhash(tid); |
| struct pdp_t *pdp2; |
| DEBUGP(DLGTP, "Begin pdp_tidget tid = %"PRIx64"\n", tid); |
| for (pdp2 = hashtid[hash]; pdp2; pdp2 = pdp2->tidnext) { |
| if (pdp2->tid == tid) { |
| *pdp = pdp2; |
| DEBUGP(DLGTP, "Begin pdp_tidget. Found\n"); |
| return 0; |
| } |
| } |
| DEBUGP(DLGTP, "Begin pdp_tidget. Not found\n"); |
| return EOF; /* End of linked list and not found */ |
| } |
| |
| int pdp_getimsi(struct pdp_t **pdp, uint64_t imsi, uint8_t nsapi) |
| { |
| return pdp_tidget(pdp, |
| (imsi & 0x0fffffffffffffffull) + |
| ((uint64_t) nsapi << 60)); |
| } |
| |
| /* |
| int pdp_iphash(void* ipif, struct ul66_t *eua) { |
| /#printf("IPhash %ld\n", lookup(eua->v, eua->l, ipif) % PDP_MAX);#/ |
| return (lookup(eua->v, eua->l, ipif) % PDP_MAX); |
| } |
| |
| int pdp_ipset(struct pdp_t *pdp, void* ipif, struct ul66_t *eua) { |
| int hash; |
| struct pdp_t *pdp2; |
| struct pdp_t *pdp_prev = NULL; |
| |
| if (PDP_DEBUG) printf("Begin pdp_ipset %d %d %2x%2x%2x%2x\n", |
| (unsigned) ipif, eua->l, |
| eua->v[2], eua->v[3], |
| eua->v[4], eua->v[5]); |
| |
| pdp->ipnext = NULL; |
| pdp->ipif = ipif; |
| pdp->eua.l = eua->l; |
| memcpy(pdp->eua.v, eua->v, eua->l); |
| |
| hash = pdp_iphash(pdp->ipif, &pdp->eua); |
| |
| for (pdp2 = haship[hash]; pdp2; pdp2 = pdp2->ipnext) |
| pdp_prev = pdp2; |
| if (!pdp_prev) |
| haship[hash] = pdp; |
| else |
| pdp_prev->ipnext = pdp; |
| if (PDP_DEBUG) printf("End pdp_ipset\n"); |
| return 0; |
| } |
| |
| int pdp_ipdel(struct pdp_t *pdp) { |
| int hash = pdp_iphash(pdp->ipif, &pdp->eua); |
| struct pdp_t *pdp2; |
| struct pdp_t *pdp_prev = NULL; |
| if (PDP_DEBUG) printf("Begin pdp_ipdel\n"); |
| for (pdp2 = haship[hash]; pdp2; pdp2 = pdp2->ipnext) { |
| if (pdp2 == pdp) { |
| if (!pdp_prev) |
| haship[hash] = pdp2->ipnext; |
| else |
| pdp_prev->ipnext = pdp2->ipnext; |
| if (PDP_DEBUG) printf("End pdp_ipdel: PDP found\n"); |
| return 0; |
| } |
| pdp_prev = pdp2; |
| } |
| if (PDP_DEBUG) printf("End pdp_ipdel: PDP not found\n"); |
| return EOF; /# End of linked list and not found #/ |
| } |
| |
| int pdp_ipget(struct pdp_t **pdp, void* ipif, struct ul66_t *eua) { |
| int hash = pdp_iphash(ipif, eua); |
| struct pdp_t *pdp2; |
| /#printf("Begin pdp_ipget %d %d %2x%2x%2x%2x\n", (unsigned)ipif, eua->l, |
| eua->v[2],eua->v[3],eua->v[4],eua->v[5]);#/ |
| for (pdp2 = haship[hash]; pdp2; pdp2 = pdp2->ipnext) { |
| if ((pdp2->ipif == ipif) && (pdp2->eua.l == eua->l) && |
| (memcmp(&pdp2->eua.v, &eua->v, eua->l) == 0)) { |
| *pdp = pdp2; |
| /#printf("End pdp_ipget. Found\n");#/ |
| return 0; |
| } |
| } |
| if (PDP_DEBUG) printf("End pdp_ipget Notfound %d %d %2x%2x%2x%2x\n", |
| (unsigned)ipif, eua->l, eua->v[2],eua->v[3],eua->v[4],eua->v[5]); |
| return EOF; /# End of linked list and not found #/ |
| } |
| */ |
| /* Various conversion functions */ |
| |
| int pdp_ntoeua(struct in_addr *src, struct ul66_t *eua) |
| { |
| eua->l = 6; |
| eua->v[0] = 0xf1; /* IETF */ |
| eua->v[1] = 0x21; /* IPv4 */ |
| memcpy(&eua->v[2], src, 4); /* Copy a 4 byte address */ |
| return 0; |
| } |
| |
| int pdp_euaton(struct ul66_t *eua, struct in_addr *dst) |
| { |
| if ((eua->l != 6) || (eua->v[0] != 0xf1) || (eua->v[1] != 0x21)) { |
| return EOF; |
| } |
| memcpy(dst, &eua->v[2], 4); /* Copy a 4 byte address */ |
| return 0; |
| } |
| |
| uint64_t pdp_gettid(uint64_t imsi, uint8_t nsapi) |
| { |
| return (imsi & 0x0fffffffffffffffull) + ((uint64_t) nsapi << 60); |
| } |
| |
| void pdp_set_imsi_nsapi(struct pdp_t *pdp, uint64_t teid) |
| { |
| pdp->imsi = teid & 0x0fffffffffffffffull; |
| pdp->nsapi = (teid & 0xf000000000000000ull) >> 60; |
| } |