| /* |
| * 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" |
| #include "queue.h" |
| |
| /* *********************************************************** |
| * 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. |
| * |
| *************************************************************/ |
| |
| static struct gsn_t *g_gsn; |
| |
| int pdp_init(struct gsn_t *gsn) |
| { |
| if (!g_gsn) { |
| g_gsn = gsn; |
| } else { |
| LOGP(DLGTP, LOGL_FATAL, "This interface is depreacted and doesn't support multiple GGSN!"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int pdp_newpdp(struct pdp_t **pdp, uint64_t imsi, uint8_t nsapi, |
| struct pdp_t *pdp_old) |
| { |
| return gtp_pdp_newpdp(g_gsn, pdp, imsi, nsapi, pdp_old); |
| } |
| |
| int gtp_pdp_newpdp(struct gsn_t *gsn, struct pdp_t **pdp, uint64_t imsi, uint8_t nsapi, |
| struct pdp_t *pdp_old) |
| { |
| struct pdp_t *pdpa = gsn->pdpa; |
| 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)->gsn = gsn; |
| (*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; |
| } |
| /* Default: Generate G-PDU sequence numbers on Tx */ |
| (*pdp)->tx_gpdu_seq = true; |
| INIT_LLIST_HEAD(&(*pdp)->qmsg_list_req); |
| return 0; |
| } |
| } |
| return EOF; /* No more available */ |
| } |
| |
| int pdp_freepdp(struct pdp_t *pdp) |
| { |
| struct qmsg_t *qmsg, *qmsg2; |
| struct pdp_t *pdpa = pdp->gsn->pdpa; |
| int rc; |
| |
| /* Remove all enqueued messages belonging to this pdp from req tx transmit |
| queue. queue_freemsg will call llist_del(). */ |
| llist_for_each_entry_safe(qmsg, qmsg2, &pdp->qmsg_list_req, entry) { |
| if ((rc = queue_freemsg(pdp->gsn->queue_req, qmsg))) |
| LOGP(DLGTP, LOGL_ERROR, |
| "Failed freeing qmsg from qmsg_list_req during pdp_freepdp()! %d\n", rc); |
| } |
| |
| 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 = &g_gsn->pdpa[0]; |
| return 0; |
| } |
| |
| int pdp_getgtp0(struct pdp_t **pdp, uint16_t fl) |
| { |
| return gtp_pdp_getgtp0(g_gsn, pdp, fl); |
| } |
| |
| |
| int gtp_pdp_getgtp0(struct gsn_t *gsn, struct pdp_t **pdp, uint16_t fl) |
| { |
| struct pdp_t *pdpa = gsn->pdpa; |
| |
| 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) |
| { |
| return gtp_pdp_getgtp1(g_gsn, pdp, tei); |
| } |
| |
| int gtp_pdp_getgtp1(struct gsn_t *gsn, struct pdp_t **pdp, uint32_t tei) |
| { |
| struct pdp_t *pdpa = gsn->pdpa; |
| |
| 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) |
| { |
| return gtp_pdp_getgtp1_peer_d(g_gsn, pdp, peer, teid_gn); |
| } |
| |
| int gtp_pdp_getgtp1_peer_d(struct gsn_t *gsn, struct pdp_t **pdp, const struct sockaddr_in *peer, uint32_t teid_gn) |
| { |
| struct pdp_t *pdpa = gsn->pdpa; |
| 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) |
| { |
| struct pdp_t **hashtid = pdp->gsn->hashtid; |
| 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) |
| { |
| struct pdp_t **hashtid = pdp->gsn->hashtid; |
| 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) |
| { |
| return gtp_pdp_tidget(g_gsn, pdp, tid); |
| } |
| |
| int gtp_pdp_tidget(struct gsn_t *gsn, struct pdp_t **pdp, uint64_t tid) |
| { |
| struct pdp_t **hashtid = gsn->hashtid; |
| 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 gtp_pdp_getimsi(g_gsn, pdp, imsi, nsapi); |
| } |
| |
| int gtp_pdp_getimsi(struct gsn_t *gsn, struct pdp_t **pdp, uint64_t imsi, uint8_t nsapi) |
| { |
| return gtp_pdp_tidget(gsn, pdp, pdp_gettid(imsi, nsapi)); |
| } |
| |
| |
| /* Various conversion functions */ |
| |
| 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; |
| } |
| |
| /* Count amount of secondary PDP contexts linked to this primary PDP context |
| * (itself included). Must be called on a primary PDP context. */ |
| unsigned int pdp_count_secondary(const struct pdp_t *pdp) |
| { |
| unsigned int n; |
| unsigned int count = 0; |
| OSMO_ASSERT(!pdp->secondary); |
| |
| for (n = 0; n < PDP_MAXNSAPI; n++) |
| if (pdp->secondary_tei[n]) |
| count++; |
| return count; |
| } |