osmo-ggsn/gtp/pdp.c

373 lines
10 KiB
C

/*
* 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,
(imsi & 0x0fffffffffffffffull) +
((uint64_t) nsapi << 60));
}
/* 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(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;
}