/* xUA Routing Key Management (RKM) as per RFC 4666 */ /* (C) 2017 by Harald Welte * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "xua_internal.h" #include "xua_as_fsm.h" #include "xua_asp_fsm.h" const struct value_string m3ua_rkm_reg_status_vals[] = { { M3UA_RKM_REG_SUCCESS, "SUCCESS" }, { M3UA_RKM_REG_ERR_UNKNOWN, "Unknown Error" }, { M3UA_RKM_REG_ERR_INVAL_DPC, "Invalid Destination Pointcode" }, { M3UA_RKM_REG_ERR_INVAL_NET_APPEAR, "Invalid Network Appearance" }, { M3UA_RKM_REG_ERR_INVAL_RKEY, "Invalid Routing Key" }, { M3UA_RKM_REG_ERR_PERM_DENIED, "Permission Denied" }, { M3UA_RKM_REG_ERR_CANT_SUPP_UNQ_RT, "Cannot Support Unique Routing" }, { M3UA_RKM_REG_ERR_RKEY_NOT_PROVD, "Routing Key Not Provided" }, { M3UA_RKM_REG_ERR_INSUFF_RESRC, "Insufficient Resources" }, { M3UA_RKM_REG_ERR_UNSUPP_RK_PARAM, "Unsupported Routing Key Parameter" }, { M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE, "Unsupported Traffic Mode Type" }, { M3UA_RKM_REG_ERR_RKEY_CHG_REFUSED, "Routing Key Change Refused" }, { M3UA_RKM_REG_ERR_RKEY_ALRDY_REGD, "Routing Key Already Registered" }, { 0, NULL } }; const struct value_string m3ua_rkm_dereg_status_vals[] = { { M3UA_RKM_DEREG_SUCCESS, "SUCCSS" }, { M3UA_RKM_DEREG_ERR_UNKNOWN, "Unknown Error" }, { M3UA_RKM_DEREG_ERR_INVAL_RCTX, "Invalid Routing Context" }, { M3UA_RKM_DEREG_ERR_PERM_DENIED, "Permission Denied" }, { M3UA_RKM_DEREG_ERR_NOT_REGD, "Error: Not Registered" }, { M3UA_RKM_DEREG_ERR_ASP_ACTIVE, "Error: ASP Active" }, { 0, NULL } }; /* push a M3UA header to the front of the given message */ static void msgb_push_m3ua_hdr(struct msgb *msg, uint8_t msg_class, uint8_t msg_type) { struct xua_common_hdr *hdr; msg->l2h = msgb_push(msg, sizeof(*hdr)); hdr = (struct xua_common_hdr *) msg->l2h; hdr->version = M3UA_VERSION; hdr->spare = 0; hdr->msg_class = msg_class; hdr->msg_type = msg_type; hdr->msg_length = htonl(msgb_l2len(msg)); } /* SG: append a single registration result to given msgb */ static int msgb_append_reg_res(struct msgb *msg, uint32_t local_rk_id, uint32_t status, uint32_t rctx) { uint8_t *old_tail = msg->tail; /* One individual Registration Result according to Chapter 3.6.2 */ msgb_put_u16(msg, M3UA_IEI_REG_RESULT); /* outer IEI */ msgb_put_u16(msg, 24 + 4); /* outer length */ /* nested IEIs */ msgb_t16l16vp_put_u32(msg, M3UA_IEI_LOC_RKEY_ID, local_rk_id); msgb_t16l16vp_put_u32(msg, M3UA_IEI_REG_STATUS, status); msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rctx); return msg->tail - old_tail; } /* SG: append a single de-registration result to given msgb */ static int msgb_append_dereg_res(struct msgb *msg, uint32_t status, uint32_t rctx) { uint8_t *old_tail = msg->tail; /* One individual De-Registration Result according to Chapter 3.6.4 */ msgb_put_u16(msg, M3UA_IEI_DEREG_RESULT); /* outer IEI */ msgb_put_u16(msg, 16 + 4); /* outer length */ /* nested IEIs */ msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rctx); msgb_t16l16vp_put_u32(msg, M3UA_IEI_DEREG_STATUS, status); return msg->tail - old_tail; } /* ASP: send a RKM Registration Request message for a single routing key */ static void xua_rkm_send_reg_req(struct osmo_ss7_asp *asp, const struct osmo_ss7_routing_key *rkey, enum osmo_ss7_as_traffic_mode traf_mode) { struct msgb *msg = m3ua_msgb_alloc(__func__); int tmod = osmo_ss7_tmode_to_xua(traf_mode); /* One individual Registration Request according to Chapter 3.6.1 */ msgb_put_u16(msg, M3UA_IEI_ROUT_KEY); /* outer IEI */ msgb_put_u16(msg, 32 + 4); /* outer length */ /* nested IEIs */ msgb_t16l16vp_put_u32(msg, M3UA_IEI_LOC_RKEY_ID, rkey->l_rk_id); msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, rkey->context); msgb_t16l16vp_put_u32(msg, M3UA_IEI_TRAF_MODE_TYP, tmod); msgb_t16l16vp_put_u32(msg, M3UA_IEI_DEST_PC, rkey->pc); msgb_push_m3ua_hdr(msg, M3UA_MSGC_RKM, M3UA_RKM_REG_REQ); osmo_ss7_asp_send(asp, msg); } /* ASP: send a RKM De-Registration Request message for a single routing context */ static void xua_rkm_send_dereg_req(struct osmo_ss7_asp *asp, uint32_t route_ctx) { struct msgb *msg = m3ua_msgb_alloc(__func__); /* One individual De-Registration Request according to Chapter 3.6.3 */ msgb_t16l16vp_put_u32(msg, M3UA_IEI_ROUTE_CTX, route_ctx); msgb_push_m3ua_hdr(msg, M3UA_MSGC_RKM, M3UA_RKM_DEREG_REQ); osmo_ss7_asp_send(asp, msg); } /* maximum number of newly-assigned Application Servers in one dynamic * RKM REG request */ #define MAX_NEW_AS 16 /* SG: handle a single registration request IE (nested IEs in 'innner' */ static int handle_rkey_reg(struct osmo_ss7_asp *asp, struct xua_msg *inner, struct msgb *resp, struct osmo_ss7_as **newly_assigned_as, unsigned int max_nas_idx, unsigned int *nas_idx) { uint32_t rk_id, rctx, _tmode, dpc; enum osmo_ss7_as_traffic_mode tmode; struct osmo_ss7_as *as; struct osmo_ss7_route *rt; char namebuf[32]; /* mandatory local routing key ID */ rk_id = xua_msg_get_u32(inner, M3UA_IEI_LOC_RKEY_ID); /* ASP may already include a routing context value here */ rctx = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); /* traffic mode type (0 = undefined) */ _tmode = xua_msg_get_u32(inner, M3UA_IEI_TRAF_MODE_TYP); if (xua_msg_find_tag(inner, M3UA_IEI_TRAF_MODE_TYP) && _tmode != M3UA_TMOD_OVERRIDE && _tmode != M3UA_TMOD_LOADSHARE && _tmode != M3UA_TMOD_BCAST) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Invalid Traffic Mode %u\n", _tmode); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE, 0); return -1; } /* destination point code (mandatory) */ dpc = xua_msg_get_u32(inner, M3UA_IEI_DEST_PC); /* We don't support routing keys with the following criteria, so * we have to reject those */ /* TODO: network appearance (optional) */ /* TODO: service indicators (optional) */ /* TODO: originating point code list (optional) */ if (xua_msg_find_tag(inner, M3UA_IEI_NET_APPEAR) || xua_msg_find_tag(inner, M3UA_IEI_SVC_IND) || xua_msg_find_tag(inner, M3UA_IEI_ORIG_PC)) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Unsupported Routing Key\n"); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_UNSUPP_RK_PARAM, 0); return -1; } /* if the ASP did not include a routing context number, allocate * one locally (will be part of response) */ if (!rctx) rctx = osmo_ss7_find_free_rctx(asp->inst); LOGPASP(asp, DLSS7, LOGL_INFO, "RKM: Registering routing key %u for DPC %s\n", rctx, osmo_ss7_pointcode_print(asp->inst, dpc)); /* We have two cases here: * a) pre-configured routing context on both ASP and SG: We will * find the AS based on the RCTX send by the client, check if * the routing key matches, associated AS with ASP and return * success. * b) no routing context set on ASP, no pre-existing AS * definition on SG. We have to create the AS, set the RK, * allocate the RCTX and return that RCTX to the client. This * is a slightly non-standard interpretation of M3UA RKM * which requires the SG to not have a-priori-knowledge of * all AS/RK in situations where the ASP are trusted. */ /* check if there is already an AS for this routing key */ as = osmo_ss7_as_find_by_rctx(asp->inst, rctx); if (as) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Found existing AS for RCTX %u\n", rctx); if (as->cfg.routing_key.pc != dpc) { LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: DPC doesn't match, rejecting AS (%u != %u)\n", as->cfg.routing_key.pc, dpc); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INVAL_RKEY, 0); return -1; } if (_tmode) { /* if the peer has specified a traffic mode at all */ tmode = osmo_ss7_tmode_from_xua(_tmode); if (!as->cfg.mode_set_by_peer && !as->cfg.mode_set_by_vty) { as->cfg.mode = tmode; LOGPAS(as, DLSS7, LOGL_INFO, "RKM: Traffic mode set dynamically by peer to %s\n", osmo_ss7_as_traffic_mode_name(as->cfg.mode)); /* verify if existing AS has same traffic-mode as new request (if any) */ } else if (!osmo_ss7_as_tmode_compatible_xua(as, _tmode)) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Non-matching Traffic Mode %s\n", osmo_ss7_as_traffic_mode_name(tmode)); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_UNSUPP_TRAF_MODE, 0); return -1; } as->cfg.mode_set_by_peer = true; } } else if (asp->inst->cfg.permit_dyn_rkm_alloc) { /* Create an AS for this routing key */ snprintf(namebuf, sizeof(namebuf), "as-rkm-%u", rctx); as = osmo_ss7_as_find_or_create(asp->inst, namebuf, OSMO_SS7_ASP_PROT_M3UA); if (!as) { LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: Cannot create AS %s\n", namebuf); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INSUFF_RESRC, 0); return -1; } as->cfg.description = talloc_strdup(as, "Auto-generated by RKM"); as->rkm_dyn_allocated = true; if (!as->cfg.mode_set_by_vty && _tmode) { as->cfg.mode = osmo_ss7_tmode_from_xua(_tmode); as->cfg.mode_set_by_peer = true; } /* fill routing key */ as->cfg.routing_key.pc = dpc; as->cfg.routing_key.context = rctx; /* add route for that routing key */ rt = osmo_ss7_route_create(as->inst->rtable_system, dpc, 0xFFFFFF, namebuf); if (!rt) { LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: Cannot insert route for DPC %s / as %s\n", osmo_ss7_pointcode_print(asp->inst, dpc), namebuf); osmo_ss7_as_destroy(as); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_CANT_SUPP_UNQ_RT, 0); return -1; } /* append to list of newly assigned as */ if (*nas_idx >= max_nas_idx) { osmo_ss7_route_destroy(rt); osmo_ss7_as_destroy(as); LOGPASP(asp, DLSS7, LOGL_ERROR, "RKM: not enough room for newly assigned AS (max %u AS)\n", max_nas_idx+1); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_INSUFF_RESRC, 0); return -1; } newly_assigned_as[(*nas_idx)++] = as; } else { /* not permitted to create dynamic RKM entries */ LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: RCTX %u not found in configuration, and " "dynamic RKM allocation not permitted; permission denied\n", rctx); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_ERR_PERM_DENIED, 0); return -1; } /* Success: Add just-create AS to connected ASP + report success */ osmo_ss7_as_add_asp(as, asp->cfg.name); msgb_append_reg_res(resp, rk_id, M3UA_RKM_REG_SUCCESS, rctx); return 0; } /* SG: receive a registration request from ASP */ static int m3ua_rx_rkm_reg_req(struct osmo_ss7_asp *asp, struct xua_msg *xua) { struct xua_msg_part *part; struct msgb *resp = m3ua_msgb_alloc(__func__); struct osmo_ss7_as *newly_assigned_as[MAX_NEW_AS]; unsigned int i, nas_idx = 0; memset(newly_assigned_as, 0, sizeof(newly_assigned_as)); /* iterate over all routing key IEs in message */ llist_for_each_entry(part, &xua->headers, entry) { struct xua_msg *inner; if (part->tag != M3UA_IEI_ROUT_KEY) continue; inner = xua_from_nested(part); if (!inner) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "RKM: Unable to parse " "nested IE for Routing Key\n"); continue; } /* handle single registration and append result to * 'resp' */ handle_rkey_reg(asp, inner, resp, newly_assigned_as, ARRAY_SIZE(newly_assigned_as), &nas_idx); xua_msg_free(inner); } /* now first send the RKM REG Response */ msgb_push_m3ua_hdr(resp, M3UA_MSGC_RKM, M3UA_RKM_REG_RSP); osmo_ss7_asp_send(asp, resp); /* and *after* the RKM REG Response inform the newly assigned * ASs about the fact that there's an INACTIVE ASP for them, * which will cause them to send NOTIFY to the client */ for (i = 0; i < ARRAY_SIZE(newly_assigned_as); i++) { struct osmo_ss7_as *as = newly_assigned_as[i]; if (!as) continue; /* Notify AS that it has an INACTIVE ASP */ osmo_fsm_inst_dispatch(as->fi, XUA_ASPAS_ASP_INACTIVE_IND, asp); } return 0; } /* SG: handle a single routing key de-registration IE */ static int handle_rkey_dereg(struct osmo_ss7_asp *asp, uint32_t rctx, struct msgb *resp) { struct osmo_ss7_instance *inst = asp->inst; struct osmo_ss7_as *as; struct osmo_ss7_route *rt; as = osmo_ss7_as_find_by_rctx(inst, rctx); if (!as) { msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_INVAL_RCTX, 0); return -1; } /* Reject if not dynamically allocated (OS#4239) */ if (!as->rkm_dyn_allocated) { msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_NOT_REGD, 0); return -1; } /* Reject if ASP is not even part of AS */ if (!osmo_ss7_as_has_asp(as, asp)) { msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_INVAL_RCTX, 0); return -1; } /* Reject if ASP is still active */ if (asp->fi->state == XUA_ASP_S_ACTIVE) { msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_ASP_ACTIVE, 0); return -1; } rt = osmo_ss7_route_find_dpc(inst->rtable_system, as->cfg.routing_key.pc); if (!rt) { msgb_append_dereg_res(resp, M3UA_RKM_DEREG_ERR_UNKNOWN, 0); return -1; } LOGPASP(asp, DLSS7, LOGL_INFO, "RKM: De-Registering rctx %u for DPC %s\n", rctx, osmo_ss7_pointcode_print(inst, as->cfg.routing_key.pc)); /* remove ASP from AS */ osmo_ss7_as_del_asp(as, asp->cfg.name); /* FIXME: Rather than spoofing teh ASP-DOWN.ind to the AS here, * we should refuse RKM DEREG if the ASP is still ACTIVE */ osmo_fsm_inst_dispatch(as->fi, XUA_ASPAS_ASP_DOWN_IND, asp); /* if we were dynamically allocated, release the associated * route and destroy the AS */ if (as->rkm_dyn_allocated) { /* remove route + AS definition */ osmo_ss7_route_destroy(rt); osmo_ss7_as_destroy(as); } /* report success */ msgb_append_dereg_res(resp, M3UA_RKM_DEREG_SUCCESS, rctx); return 0; } /* SG: receive a De-Registration request from ASP */ static int m3ua_rx_rkm_dereg_req(struct osmo_ss7_asp *asp, struct xua_msg *xua) { struct xua_msg_part *part = xua_msg_find_tag(xua, M3UA_IEI_ROUTE_CTX); struct msgb *resp = m3ua_msgb_alloc(__func__); uint32_t *rctx; if (!part) return -1; for (rctx = (uint32_t *)part->dat; (uint8_t *)rctx < part->dat + part->len; rctx++) handle_rkey_dereg(asp, ntohl(*rctx), resp); msgb_push_m3ua_hdr(resp, M3UA_MSGC_RKM, M3UA_RKM_DEREG_RSP); osmo_ss7_asp_send(asp, resp); return 0; } /* ASP: handle a single registration response IE (nested IEs in 'inner') */ static int handle_rkey_reg_resp(struct osmo_ss7_asp *asp, struct xua_msg *inner) { struct osmo_xlm_prim *oxp; if (!xua_msg_find_tag(inner, M3UA_IEI_LOC_RKEY_ID) || !xua_msg_find_tag(inner, M3UA_IEI_REG_STATUS) || !xua_msg_find_tag(inner, M3UA_IEI_ROUTE_CTX)) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "Missing Inner IE in REG RESP\n"); /* FIXME: ERROR to peer */ return -1; } oxp = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_CONFIRM); if (!oxp) return -1; oxp->u.rk_reg.key.l_rk_id = xua_msg_get_u32(inner, M3UA_IEI_LOC_RKEY_ID); oxp->u.rk_reg.key.context = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); oxp->u.rk_reg.status = xua_msg_get_u32(inner, M3UA_IEI_REG_STATUS); LOGPASP(asp, DLSS7, LOGL_INFO, "Received RKM REG RES rctx=%u status=%s\n", oxp->u.rk_reg.key.context, get_value_string(m3ua_rkm_reg_status_vals, oxp->u.rk_reg.status)); /* Send primitive to LM */ xua_asp_send_xlm_prim(asp, oxp); return 0; } /* ASP: receive a registration response (ASP role) */ static int m3ua_rx_rkm_reg_rsp(struct osmo_ss7_asp *asp, struct xua_msg *xua) { struct xua_msg_part *part; struct xua_msg *inner = NULL; llist_for_each_entry(part, &xua->headers, entry) { /* skip other IEs and/or short REG_RES IEs */ if (part->tag != M3UA_IEI_REG_RESULT || part->len < 24) continue; /* we leave the above loop at the first valid * registration result (we only support one AS per ASP * for now) */ inner = xua_from_nested(part); if (!inner) continue; handle_rkey_reg_resp(asp, inner); xua_msg_free(inner); } return 0; } /* ASP: handle a single De-Registration response IE (nested IEs in 'inner' */ static int handle_rkey_dereg_resp(struct osmo_ss7_asp *asp, struct xua_msg *inner) { struct osmo_xlm_prim *oxp; if (!xua_msg_find_tag(inner, M3UA_IEI_DEREG_STATUS) || !xua_msg_find_tag(inner, M3UA_IEI_ROUTE_CTX)) { LOGPASP(asp, DLSS7, LOGL_NOTICE, "Missing Inner IE in DEREG RESP\n"); /* FIXME: ERROR to peer */ return -1; } oxp = xua_xlm_prim_alloc(OSMO_XLM_PRIM_M_RK_DEREG, PRIM_OP_CONFIRM); if (!oxp) return -1; oxp->u.rk_dereg.route_ctx = xua_msg_get_u32(inner, M3UA_IEI_ROUTE_CTX); oxp->u.rk_dereg.status = xua_msg_get_u32(inner, M3UA_IEI_DEREG_STATUS); LOGPASP(asp, DLSS7, LOGL_INFO, "Received RKM DEREG RES rctx=%u status=%s\n", oxp->u.rk_reg.key.context, get_value_string(m3ua_rkm_dereg_status_vals, oxp->u.rk_dereg.status)); /* Send primitive to LM */ xua_asp_send_xlm_prim(asp, oxp); return 0; } /* ASP: receive a De-Registration response */ static int m3ua_rx_rkm_dereg_rsp(struct osmo_ss7_asp *asp, struct xua_msg *xua) { struct xua_msg_part *part; struct xua_msg *inner = NULL; llist_for_each_entry(part, &xua->headers, entry) { /* skip other IEs and/or short REG_RES IEs */ if (part->tag != M3UA_IEI_DEREG_RESULT || part->len < 16) continue; /* we leave the above loop at the first valid * registration result (we only support one AS per ASP * for now) */ inner = xua_from_nested(part); if (!inner) continue; handle_rkey_dereg_resp(asp, inner); xua_msg_free(inner); } return 0; } /* process an incoming RKM message in xua format */ int m3ua_rx_rkm(struct osmo_ss7_asp *asp, struct xua_msg *xua) { int rc; switch (xua->hdr.msg_type) { /* SG Side */ case M3UA_RKM_REG_REQ: /* TOOD: ensure we are role SG */ rc = m3ua_rx_rkm_reg_req(asp, xua); break; case M3UA_RKM_DEREG_REQ: /* TOOD: ensure we are role SG */ rc = m3ua_rx_rkm_dereg_req(asp, xua); break; /* ASP Side */ case M3UA_RKM_REG_RSP: /* TOOD: ensure we are role ASP */ rc = m3ua_rx_rkm_reg_rsp(asp, xua); break; case M3UA_RKM_DEREG_RSP: /* TOOD: ensure we are role ASP */ rc = m3ua_rx_rkm_dereg_rsp(asp, xua); break; default: LOGPASP(asp, DLSS7, LOGL_ERROR, "Received unknown RKM msg_type %u\n", xua->hdr.msg_type); rc = -1; break; } return rc; } /* process a primitive from the xUA Layer Manager (LM) */ int osmo_xlm_sap_down(struct osmo_ss7_asp *asp, struct osmo_prim_hdr *oph) { struct osmo_xlm_prim *prim = (struct osmo_xlm_prim *) oph; LOGPASP(asp, DLSS7, LOGL_DEBUG, "Received XUA Layer Manager Primitive: %s)\n", osmo_xlm_prim_name(&prim->oph)); switch (OSMO_PRIM_HDR(&prim->oph)) { case OSMO_PRIM(OSMO_XLM_PRIM_M_RK_REG, PRIM_OP_REQUEST): /* Layer Manager asks us to send a Routing Key Reg Request */ xua_rkm_send_reg_req(asp, &prim->u.rk_reg.key, prim->u.rk_reg.traf_mode); break; case OSMO_PRIM(OSMO_XLM_PRIM_M_RK_DEREG, PRIM_OP_REQUEST): /* Layer Manager asks us to send a Routing Key De-Reg Request */ xua_rkm_send_dereg_req(asp, prim->u.rk_dereg.route_ctx); break; default: LOGPASP(asp, DLSS7, LOGL_ERROR, "Unknown XUA Layer Manager Primitive: %s\n", osmo_xlm_prim_name(&prim->oph)); break; } msgb_free(prim->oph.msg); return 0; } /* clean-up any dynamically created ASs + routes */ void xua_rkm_cleanup_dyn_as_for_asp(struct osmo_ss7_asp *asp) { struct osmo_ss7_instance *inst = asp->inst; struct osmo_ss7_as *as, *as2; llist_for_each_entry_safe(as, as2, &inst->as_list, list) { if (!osmo_ss7_as_has_asp(as, asp)) continue; /* FIXME: check if there are no other ASPs! */ if (!as->rkm_dyn_allocated) continue; osmo_ss7_as_destroy(as); } }