/* Mapper between RUA ContextID (24 bit, per HNB) and the SUA/SCCP * Connection ID (32bit, per signalling link) */ /* (C) 2015 by Harald Welte * 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 . * */ /* an expired mapping is destroyed after 1..2 * EXPIRY_TIMER_SECS */ #define EXPIRY_TIMER_SECS 23 #include #include #include #include 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; for (i = 0; i < 0xffffffff; i++) { id = cn->next_conn_id++; 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); /* alloate 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; /* 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) { /* 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; /* 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); } } 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 */ 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; }