osmo-hnbgw/src/osmo-hnbgw/context_map.c

223 lines
6.4 KiB
C

/* Mapper between RUA ContextID (24 bit, per HNB) and the SUA/SCCP
* Connection ID (32bit, per signalling link) */
/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
/* an expired mapping is destroyed after 1..2 * EXPIRY_TIMER_SECS */
#define EXPIRY_TIMER_SECS 23
#include <osmocom/core/timer.h>
#include <osmocom/sigtran/sccp_helpers.h>
#include <osmocom/hnbgw/hnbgw.h>
#include <osmocom/hnbgw/context_map.h>
#include <osmocom/hnbgw/mgw_fsm.h>
#include <osmocom/hnbgw/ps_rab_ass_fsm.h>
const struct value_string hnbgw_context_map_state_names[] = {
{MAP_S_NULL , "not-initialized"},
{MAP_S_ACTIVE , "active"},
{MAP_S_RESERVED1, "inactive-reserved"},
{MAP_S_RESERVED2, "inactive-discard"},
{0, NULL}
};
/* is a given SCCP USER SAP Connection ID in use for a given CN link? */
static int cn_id_in_use(struct hnbgw_cnlink *cn, uint32_t id)
{
struct hnbgw_context_map *map;
llist_for_each_entry(map, &cn->map_list, cn_list) {
if (map->scu_conn_id == id)
return 1;
}
return 0;
}
/* try to allocate a new SCCP User SAP Connection ID */
static int alloc_cn_conn_id(struct hnbgw_cnlink *cn, uint32_t *id_out)
{
uint32_t i;
uint32_t id;
/* SUA: RFC3868 sec 3.10.4:
* The source reference number is a 4 octet long integer.
* This is allocated by the source SUA instance.
* M3UA/SCCP: ITU-T Q.713 sec 3.3:
* The "source local reference" parameter field is a three-octet field containing a
* reference number which is generated and used by the local node to identify the
* connection section after the connection section is set up.
* The coding "all ones" is reserved for future use.
* Hence, let's simply use 24 bit ids to fit all link types (excluding 0x00ffffff).
*/
for (i = 0; i < 0x00ffffff; i++) {
id = cn->next_conn_id++;
if (cn->next_conn_id == 0x00ffffff)
cn->next_conn_id = 0;
if (!cn_id_in_use(cn, id)) {
*id_out = id;
return 1;
}
}
return -1;
}
/* Map from a HNB + ContextID to the SCCP-side Connection ID */
struct hnbgw_context_map *
context_map_alloc_by_hnb(struct hnb_context *hnb, uint32_t rua_ctx_id,
bool is_ps,
struct hnbgw_cnlink *cn_if_new)
{
struct hnbgw_context_map *map;
uint32_t new_scu_conn_id;
llist_for_each_entry(map, &hnb->map_list, hnb_list) {
if (map->state != MAP_S_ACTIVE)
continue;
if (map->cn_link != cn_if_new) {
continue;
}
if (map->rua_ctx_id == rua_ctx_id
&& map->is_ps == is_ps) {
return map;
}
}
if (alloc_cn_conn_id(cn_if_new, &new_scu_conn_id) < 0) {
LOGHNB(hnb, DMAIN, LOGL_ERROR, "Unable to allocate CN connection ID\n");
return NULL;
}
LOGHNB(hnb, DMAIN, LOGL_INFO, "Creating new Mapping RUA CTX %p/%u <-> SCU Conn ID %p/%u\n",
hnb, rua_ctx_id, cn_if_new, new_scu_conn_id);
/* allocate a new map entry. */
map = talloc_zero(hnb, struct hnbgw_context_map);
map->state = MAP_S_NULL;
map->cn_link = cn_if_new;
map->hnb_ctx = hnb;
map->rua_ctx_id = rua_ctx_id;
map->is_ps = is_ps;
map->scu_conn_id = new_scu_conn_id;
INIT_LLIST_HEAD(&map->ps_rab_ass);
INIT_LLIST_HEAD(&map->ps_rabs);
/* put it into both lists */
llist_add_tail(&map->hnb_list, &hnb->map_list);
llist_add_tail(&map->cn_list, &cn_if_new->map_list);
map->state = MAP_S_ACTIVE;
return map;
}
/* Map from a CN + Connection ID to HNB + Context ID */
struct hnbgw_context_map *
context_map_by_cn(struct hnbgw_cnlink *cn, uint32_t scu_conn_id)
{
struct hnbgw_context_map *map;
llist_for_each_entry(map, &cn->map_list, cn_list) {
if (map->state != MAP_S_ACTIVE)
continue;
if (map->scu_conn_id == scu_conn_id) {
return map;
}
}
/* we don't allocate new mappings in the CN->HNB
* direction, as the RUA=SCCP=SUA connections are always
* established from HNB towards CN. */
LOGP(DMAIN, LOGL_NOTICE, "Unable to resolve map for CN " "connection ID %p/%u\n", cn, scu_conn_id);
return NULL;
}
void context_map_deactivate(struct hnbgw_context_map *map)
{
LOG_MAP(map, DMAIN, LOGL_INFO, "Deactivating\n");
/* set the state to reserved. We still show up in the list and
* avoid re-allocation of the context-id until we are cleaned up
* by the context_map garbage collector timer */
if (map->state != MAP_S_RESERVED2)
map->state = MAP_S_RESERVED1;
/* Is SCCP still active and needs to be disconnected ungracefully? */
if (map->scu_conn_active) {
osmo_sccp_tx_disconn(map->hnb_ctx->gw->sccp.cnlink->sccp_user, map->scu_conn_id, NULL, 0);
map->scu_conn_active = false;
}
/* a possibly still existing MGW FSM must be terminated when the context
* map is deactivated. (this is a cornercase) */
if (map->mgw_fi) {
mgw_fsm_release(map);
OSMO_ASSERT(map->mgw_fi == NULL);
}
#if ENABLE_PFCP
hnbgw_gtpmap_release(map);
#endif
}
static struct osmo_timer_list context_map_tmr;
static void context_map_tmr_cb(void *data)
{
struct hnb_gw *gw = data;
struct hnbgw_cnlink *cn = gw->sccp.cnlink;
struct hnbgw_context_map *map, *next_map;
DEBUGP(DMAIN, "Running context mapper garbage collection\n");
llist_for_each_entry_safe(map, next_map, &cn->map_list, cn_list) {
switch (map->state) {
case MAP_S_RESERVED1:
/* first time we see this reserved
* entry: mark it for stage 2 */
map->state = MAP_S_RESERVED2;
break;
case MAP_S_RESERVED2:
/* second time we see this reserved
* entry: remove it */
LOG_MAP(map, DMAIN, LOGL_INFO, "Deallocating\n");
map->state = MAP_S_NULL;
llist_del(&map->cn_list);
llist_del(&map->hnb_list);
talloc_free(map);
break;
default:
break;
}
}
/* re-schedule this timer */
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
}
int context_map_init(struct hnb_gw *gw)
{
context_map_tmr.cb = context_map_tmr_cb;
context_map_tmr.data = gw;
osmo_timer_schedule(&context_map_tmr, EXPIRY_TIMER_SECS, 0);
return 0;
}